@@ -384,8 +384,8 @@
function pointLabel ( t , range ) {
const d = new Date ( t * 1000 ) ;
if ( range === '24h' ) {
const hrs = Math . round ( ( Date . now ( ) - d . getTime ( ) ) / 3600000 ) ;
return hrs <= 0 ? 'This hour ' : hrs + ' h ago' ;
const hm = ( x ) => x . toLocaleTimeString ( undefined , { hour : '2-digit' , minute : '2-digit' } ) ;
return hm ( d ) + '– ' + hm ( new Date ( d . getTime ( ) + 3600000 ) ) ;
}
return d . toLocaleDateString ( undefined , { weekday : 'short' , day : 'numeric' , month : 'short' } ) ;
}
@@ -430,9 +430,35 @@
h ( 'div' , { class : 'gl top' } ) , h ( 'div' , { class : 'gl mid' } ) , h ( 'div' , { class : 'gl base' } ) ,
h ( 'div' , { class : 'yl top' } , fmtBytes ( top ) ) , h ( 'div' , { class : 'yl mid' } , fmtBytes ( top / 2 ) ) ,
bars ) ,
h ( 'div' , { class : 'xaxis' } ,
h ( 'span' , null , pointLabel ( points [ 0 ] . t , range ) ) ,
h ( 'span' , null , range === '24h' ? 'now' : pointLabel ( points [ points . length - 1 ] . t , range ) ) ) ) ;
timeAxis ( points . map ( ( p ) => p . t ) , range === '24h' ? 3600 : 86400 ) ) ;
}
// timeAxis labels the bottom of a chart with clock times or dates. Points
// are evenly spaced buckets of step seconds. Hourly buckets get the hour
// they start at, every 3 hours (6 on narrow screens), with the date at
// midnight; daily buckets get the date under the bar, every bar for a
// week and every 7th bar, counted back from today, for a month.
function timeAxis ( ts , step ) {
const n = ts . length ;
const ticks = [ ] ;
if ( step < 86400 ) {
for ( let i = 0 ; i < n ; i ++ ) {
const d = new Date ( ts [ i ] * 1000 ) ;
if ( d . getMinutes ( ) !== 0 || d . getHours ( ) % 3 !== 0 || i === 0 ) continue ;
const text = d . getHours ( ) === 0
? d . toLocaleDateString ( undefined , { weekday : 'short' , day : 'numeric' } )
: d . toLocaleTimeString ( undefined , { hour : '2-digit' , minute : '2-digit' } ) ;
ticks . push ( { at : i / n , text , minor : d . getHours ( ) % 6 !== 0 , edge : true } ) ;
}
} else {
const every = n > 10 ? 7 : 1 ;
for ( let i = n - 1 ; i >= 0 ; i -= every ) {
const d = new Date ( ts [ i ] * 1000 ) ;
ticks . push ( { at : ( i + 0.5 ) / n , text : d . toLocaleDateString ( undefined , n > 10 ? { day : 'numeric' , month : 'short' } : { weekday : 'short' , day : 'numeric' } ) } ) ;
}
}
return h ( 'div' , { class : 'xaxis' , 'aria-hidden' : 'true' } ,
ticks . map ( ( k ) => h ( 'span' , { class : ( k . minor ? 'minor' : '' ) + ( k . edge ? ' edge' : '' ) , style : { left : ( k . at * 100 ) . toFixed ( 3 ) + '%' } } , k . text ) ) ) ;
}
// latencyChart draws the median as a line over a min– max band, one point
@@ -489,7 +515,7 @@
h ( 'div' , { class : 'gl top' } ) , h ( 'div' , { class : 'gl mid' } ) , h ( 'div' , { class : 'gl base' } ) ,
h ( 'div' , { class : 'yl top' } , fmtMs ( top ) ) , h ( 'div' , { class : 'yl mid' } , fmtMs ( top / 2 ) ) ,
wrapEl ) ,
h ( 'div' , { class : 'xaxis' } , h ( 'span' , null , '24 h ago' ) , h ( 'span' , null , '12 h ago' ) , h ( 'span' , null , 'now' ) ) ) ;
timeAxis ( points . map ( ( p ) => p . t ) , 300 ) ) ;
}
// pairDialog creates an API token and shows it once, with the pairing QR
@@ -994,12 +1020,16 @@
// ---------- dashboard ----------
// RANGES are the time ranges offered above the traffic charts.
const RANGES = [ [ '24h' , '24 h' ] , [ '7d' , '7 days' ] , [ '30d' , '30 days' ] ] ;
async function viewDashboard ( wrap ) {
let range = '24h' ;
const draw = async ( ) => {
const [ st , pl , stats , logs ] = await Promise . all ( [
api ( 'GET' , '/status' ) ,
api ( 'GET' , '/peers' ) ,
api ( 'GET' , '/stats?range=24h ' ) ,
api ( 'GET' , '/stats?range=' + range ) ,
me . isAdmin ? api ( 'GET' , '/logs?audit=1&limit=6' ) . catch ( ( ) => null ) : null ,
] ) ;
const peers = pl . peers ;
@@ -1036,8 +1066,10 @@
h ( 'div' , { class : 's' } , 'Service up ' + ago ( st . started ) . replace ( ' ago' , '' ) + ' · ' + ( st . healthy ? 'all checks pass' : failing . length + ' check(s) failing' ) ) ) ) ,
h ( 'section' , { class : 'card' , 'aria-labelledby' : 'tput' } ,
h ( 'div' , { class : 'cardhead' } , h ( 'h2' , { id : 'tput' } , 'Traffic, all peers · last 24 hours' ) ) ,
chart ( stats . points , '24h' , 'total' , true ) ) ,
h ( 'div' , { class : 'cardhead' } , h ( 'h2' , { id : 'tput' } , 'Traffic, all peers' ) ,
h ( 'div' , { class : 'pills' , role : 'group' , 'aria-label' : 'Time range' } , RANGES . map ( ( [ k , t ] ) =>
h ( 'button' , { type : 'button' , class : range === k ? 'pill on' : 'pill' , 'aria-pressed' : String ( range === k ) , onClick : ( ) => { range = k ; draw ( ) . catch ( ( ) => { } ) ; } } , t ) ) ) ) ,
chart ( stats . points , range , 'total' , true ) ) ,
h ( 'div' , { class : 'cols' } ,
h ( 'section' , { class : 'card flush' } ,
@@ -1327,7 +1359,7 @@
sessMore . textContent = allSessions ? 'Show fewer' : 'Show all ' + sessions . length ;
} ;
drawSessions ( ) ;
let range = '7d ' ;
let range = '24h ' ;
const st = peerState ( p ) ;
const traffic = h ( 'div' ) ;
const totals = h ( 'div' , { class : 'legend-row' } ) ;
@@ -1349,7 +1381,7 @@
} ;
async function drawTraffic ( ) {
pills . replaceChildren ( ... [ [ '24h' , '24 h' ] , [ '7d' , '7 days' ] , [ '30d' , '30 days' ] ] . map ( ( [ k , t ] ) =>
pills . replaceChildren ( ... RANGES . map ( ( [ k , t ] ) =>
h ( 'button' , { type : 'button' , class : range === k ? 'pill on' : 'pill' , 'aria-pressed' : String ( range === k ) , onClick : ( ) => { range = k ; drawTraffic ( ) ; } } , t ) ) ) ;
const s = await api ( 'GET' , '/peers/' + id + '/stats?range=' + range ) ;
const down = s . points . reduce ( ( a , x ) => a + x . down , 0 ) , up = s . points . reduce ( ( a , x ) => a + x . up , 0 ) ;