117117 padding : .4rem .85rem ;border-radius : 999px ;cursor : pointer;transition : border-color .15s , color .15s , background .15s }
118118 .toggle : hover {border-color : var (--accent );color : var (--accent )}
119119 .toggle .on {background : var (--accent-soft );border-color : # dcd8f5 ;color : var (--accent )}
120+ .tabs {display : flex;flex-wrap : wrap;gap : .4rem ;margin : .2rem 0 1rem }
121+ .tab {font-size : .84rem ;font-weight : 600 ;border : 1px solid var (--line );background : # fff ;color : var (--muted );padding : .42rem .85rem ;border-radius : 999px ;cursor : pointer;display : inline-flex;align-items : center;gap : .45rem ;transition : border-color .15s , color .15s , background .15s }
122+ .tab : hover {border-color : var (--accent );color : var (--accent )}
123+ .tab .on {background : var (--accent-soft );border-color : # dcd8f5 ;color : var (--accent )}
124+ .tab .tabn {font-family : var (--mono );font-size : .74rem ;font-weight : 500 ;color : var (--faint )}
125+ .tab .on .tabn {color : var (--accent )}
120126 .caveat {font-size : .8rem ;color : var (--faint );background : # fff ;border : 1px solid var (--line );border-radius : 10px ;padding : .55rem .8rem ;margin-bottom : 1rem }
121127 .rtable {width : 100% ;border-collapse : collapse;background : var (--surface );border : 1px solid var (--line );border-radius : 14px ;overflow : hidden;font-size : .86rem }
122128 .rtable thead th {text-align : left;font-size : .7rem ;font-weight : 700 ;letter-spacing : .06em ;text-transform : uppercase;color : var (--faint );
@@ -349,7 +355,14 @@ <h2>${esc(c.name || short(pk))}</h2>
349355}
350356
351357// ---- relay directory (firehose health data) ----
352- const relaysState = { online : true , sortKey : 'lastChecked' , dir : - 1 , rows : [ ] , meta : { } } ;
358+ const relaysState = { filter : 'online' , sortKey : 'lastChecked' , dir : - 1 , rows : [ ] , meta : { } } ;
359+ const RTABS = [
360+ { key : 'all' , label : 'All' } ,
361+ { key : 'online' , label : 'Online' } ,
362+ { key : 'writable' , label : 'Writable' } ,
363+ { key : 'paid' , label : 'Paid' } ,
364+ { key : 'auth' , label : 'Auth' } ,
365+ ] ;
353366const RCOLS = [
354367 { key : 'relay' , label : 'Relay' , num : false } ,
355368 { key : 'online' , label : 'Status' , num : false } ,
@@ -394,20 +407,22 @@ <h2>${esc(c.name || short(pk))}</h2>
394407}
395408
396409function renderRelays ( ) {
397- const { rows, meta, sortKey, dir, online } = relaysState ;
410+ const { rows, meta, sortKey, dir, filter } = relaysState ;
411+ const counts = meta . counts || { } ;
398412 const sorted = [ ...rows ] . sort ( ( a , b ) => cmpRelay ( a , b , sortKey , dir ) ) ;
399413 const head = RCOLS . map ( ( c ) => `<th class="${ c . num ? 'num' : '' } " data-key="${ c . key } ">${ c . label } ${ sortKey === c . key ? ` <span class="arrow">${ dir < 0 ? '▼' : '▲' } </span>` : '' } </th>` ) . join ( '' ) ;
414+ const tabs = RTABS . map ( ( t ) => `<button class="tab ${ filter === t . key ? 'on' : '' } " data-filter="${ t . key } ">${ t . label } <span class="tabn">${ fmt ( counts [ t . key ] || 0 ) } </span></button>` ) . join ( '' ) ;
415+ const label = filter === 'all' ? 'relays' : `${ filter } relays` ;
400416 view . innerHTML = `<div class="detail">
401417 <div class="back" id="back">← all identities</div>
402- <div class="relays-head"><h2>Relay directory</h2>
403- <span class="sub"><strong>${ fmt ( meta . online ) } </strong> online of <strong>${ fmt ( meta . total ) } </strong> known relays</span></div>
404- <div class="caveat">Health snapshot · relays last checked <strong>${ esc ( meta . lastChecked ? ageStr ( meta . lastChecked ) + ' ago' : 'unknown' ) } </strong>. Live monitoring is paused, so these figures reflect that snapshot.</div>
405- <div class="relays-controls"><button class="toggle ${ online ? 'on' : '' } " id="r-online">Online only</button></div>
418+ <div class="relays-head"><h2>Relay directory</h2></div>
419+ <div class="tabs">${ tabs } </div>
420+ <div class="caveat">Health checked live · relays last checked <strong>${ esc ( meta . lastChecked ? ageStr ( meta . lastChecked ) + ' ago' : 'unknown' ) } </strong>.</div>
406421 <div style="overflow-x:auto"><table class="rtable"><thead><tr>${ head } </tr></thead><tbody>${ sorted . map ( relayRow ) . join ( '' ) } </tbody></table></div>
407- <div class="caveat" style="margin-top:1rem">Showing ${ sorted . length } relays . Click a column to sort.</div>
422+ <div class="caveat" style="margin-top:1rem">Showing ${ sorted . length } ${ label } . Click a column to sort.</div>
408423 </div>` ;
409424 $ ( '#back' ) . onclick = ( ) => navigate ( '/' ) ;
410- $ ( '#r-online ') . onclick = ( ) => { relaysState . online = ! relaysState . online ; relaysView ( ) ; } ;
425+ view . querySelectorAll ( '.tabs .tab ') . forEach ( ( b ) => { b . onclick = ( ) => { relaysState . filter = b . dataset . filter ; relaysView ( ) ; } ; } ) ;
411426 view . querySelectorAll ( '.rtable thead th' ) . forEach ( ( th ) => { th . onclick = ( ) => {
412427 const k = th . dataset . key ;
413428 if ( relaysState . sortKey === k ) relaysState . dir = - relaysState . dir ;
@@ -420,7 +435,7 @@ <h2>${esc(c.name || short(pk))}</h2>
420435 if ( $ ( '#q' ) ) $ ( '#q' ) . value = '' ;
421436 $ ( '#stat' ) . innerHTML = '' ;
422437 view . innerHTML = '<div class="loading">loading relay directory…</div>' ;
423- const data = await fetch ( `/api/relays-directory?limit=2000 ${ relaysState . online ? '&online=1' : '' } ` ) . then ( ( r ) => r . json ( ) ) . catch ( ( ) => null ) ;
438+ const data = await fetch ( `/api/relays-directory?filter= ${ encodeURIComponent ( relaysState . filter ) } &limit=2000 ` ) . then ( ( r ) => r . json ( ) ) . catch ( ( ) => null ) ;
424439 if ( ! data || ! Array . isArray ( data . relays ) ) { view . innerHTML = '<div class="loading">Could not load the relay directory.</div>' ; return ; }
425440 relaysState . rows = data . relays ; relaysState . meta = data ;
426441 renderRelays ( ) ;
0 commit comments