168168
169169 .theme-toggle {
170170 background : var (--card ); border : 1px solid var (--border ); border-radius : 8px ;
171- width : 36px ; height : 36px ; cursor : pointer; font-size : 1.125 rem ;
171+ width : 36px ; height : 36px ; cursor : pointer;
172172 display : flex; align-items : center; justify-content : center; transition : all 0.2s ease;
173+ color : var (--subtle );
173174 }
174- .theme-toggle : hover { border-color : var (--border-hover ); background : var (--input-hover-bg ); }
175+ .theme-toggle : hover { border-color : var (--border-hover ); background : var (--input-hover-bg ); color : var ( --fg ); }
175176
176177 .target-table {
177178 width : 100% ; border-collapse : collapse; font-size : 0.8125rem ;
228229 .section-label {
229230 font-size : 0.6875rem ; font-weight : 700 ; text-transform : uppercase;
230231 letter-spacing : 0.06em ; color : var (--muted ); margin-bottom : 0.5rem ;
232+ display : flex; align-items : center; gap : 0.375rem ;
231233 }
232234
233235 @media (max-width : 639px ) {
239241 /* Settings button */
240242 .settings-btn {
241243 background : var (--card ); border : 1px solid var (--border ); border-radius : 8px ;
242- width : 36px ; height : 36px ; cursor : pointer; font-size : 1 rem ;
244+ width : 36px ; height : 36px ; cursor : pointer;
243245 display : flex; align-items : center; justify-content : center;
244246 transition : all 0.2s ease; color : var (--subtle );
245247 }
246248 .settings-btn : hover { border-color : var (--border-hover ); background : var (--input-hover-bg ); color : var (--fg ); }
247249
250+ /* Icon button shared */
251+ .icon-btn svg { display : block; }
252+
253+ /* App logo */
254+ .app-logo {
255+ width : 38px ; height : 38px ; border-radius : 10px ;
256+ background : var (--accent-soft ); color : var (--accent );
257+ display : flex; align-items : center; justify-content : center;
258+ flex-shrink : 0 ;
259+ }
260+
261+ /* Section label with icon */
262+ .section-label {
263+ display : flex; align-items : center; gap : 0.375rem ;
264+ }
265+
248266 /* Modal backdrop */
249267 .modal-backdrop {
250268 position : fixed; inset : 0 ; z-index : 50 ;
326344 <!-- Header -->
327345 < header class ="flex flex-wrap justify-between items-center gap-3 mb-4 flex-shrink-0 ">
328346 < div class ="flex items-center gap-3 sm:gap-4 ">
347+ < div class ="app-logo ">
348+ < svg xmlns ="http://www.w3.org/2000/svg " width ="20 " height ="20 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round "> < path d ="M5 12.55a11 11 0 0 1 14.08 0 "/> < path d ="M1.42 9a16 16 0 0 1 21.16 0 "/> < path d ="M8.53 16.11a6 6 0 0 1 6.95 0 "/> < circle cx ="12 " cy ="20 " r ="1 " fill ="currentColor " stroke ="none "/> </ svg >
349+ </ div >
329350 < div >
330351 < div class ="flex items-center gap-2.5 ">
331352 < h1 class ="text-xl sm:text-2xl font-extrabold tracking-tight "> Network Monitor</ h1 >
@@ -347,17 +368,27 @@ <h1 class="text-xl sm:text-2xl font-extrabold tracking-tight">Network Monitor</h
347368 < option > Last 1 hour</ option >
348369 < option > Last 24 hours</ option >
349370 </ select >
350- < button class ="theme-toggle " id ="themeToggle " title ="Toggle dark mode "> 🌙</ button >
351- < button class ="settings-btn " id ="settingsBtn " title ="Settings "> ⚙</ button >
371+ < button class ="theme-toggle icon-btn " id ="themeToggle " title ="Toggle dark mode ">
372+ < svg id ="iconMoon " xmlns ="http://www.w3.org/2000/svg " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round "> < path d ="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z "/> </ svg >
373+ < svg id ="iconSun " xmlns ="http://www.w3.org/2000/svg " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round " style ="display:none "> < circle cx ="12 " cy ="12 " r ="5 "/> < line x1 ="12 " y1 ="1 " x2 ="12 " y2 ="3 "/> < line x1 ="12 " y1 ="21 " x2 ="12 " y2 ="23 "/> < line x1 ="4.22 " y1 ="4.22 " x2 ="5.64 " y2 ="5.64 "/> < line x1 ="18.36 " y1 ="18.36 " x2 ="19.78 " y2 ="19.78 "/> < line x1 ="1 " y1 ="12 " x2 ="3 " y2 ="12 "/> < line x1 ="21 " y1 ="12 " x2 ="23 " y2 ="12 "/> < line x1 ="4.22 " y1 ="19.78 " x2 ="5.64 " y2 ="18.36 "/> < line x1 ="18.36 " y1 ="5.64 " x2 ="19.78 " y2 ="4.22 "/> </ svg >
374+ </ button >
375+ < button class ="settings-btn icon-btn " id ="settingsBtn " title ="Settings ">
376+ < svg xmlns ="http://www.w3.org/2000/svg " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round "> < circle cx ="12 " cy ="12 " r ="3 "/> < path d ="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z "/> </ svg >
377+ </ button >
352378 </ div >
353379 </ header >
354380
355381 <!-- Settings Modal -->
356382 < div class ="modal-backdrop " id ="modalBackdrop ">
357383 < div class ="modal " role ="dialog " aria-modal ="true " aria-labelledby ="modalTitle ">
358384 < div class ="modal-header ">
359- < h2 id ="modalTitle "> ⚙ Monitor Settings</ h2 >
360- < button class ="modal-close " id ="modalClose " title ="Close "> ✕</ button >
385+ < h2 id ="modalTitle " style ="display:flex;align-items:center;gap:0.5rem ">
386+ < svg xmlns ="http://www.w3.org/2000/svg " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 " stroke-linecap ="round " stroke-linejoin ="round " style ="color:var(--accent) "> < circle cx ="12 " cy ="12 " r ="3 "/> < path d ="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z "/> </ svg >
387+ Monitor Settings
388+ </ h2 >
389+ < button class ="modal-close " id ="modalClose " title ="Close ">
390+ < svg xmlns ="http://www.w3.org/2000/svg " width ="16 " height ="16 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < line x1 ="18 " y1 ="6 " x2 ="6 " y2 ="18 "/> < line x1 ="6 " y1 ="6 " x2 ="18 " y2 ="18 "/> </ svg >
391+ </ button >
361392 </ div >
362393 < div class ="modal-body ">
363394 < div class ="field-group ">
@@ -446,7 +477,10 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
446477
447478 <!-- Health score card -->
448479 < div class ="card p-4 flex flex-col items-center " id ="healthCard ">
449- < div class ="section-label w-full text-center mb-3 "> Connection Health</ div >
480+ < div class ="section-label w-full text-center mb-3 " style ="justify-content:center ">
481+ < svg xmlns ="http://www.w3.org/2000/svg " width ="11 " height ="11 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < path d ="M22 12h-4l-3 9L9 3l-3 9H2 "/> </ svg >
482+ Connection Health
483+ </ div >
450484 < div class ="health-ring " id ="healthRing ">
451485 < svg viewBox ="0 0 100 100 " width ="100 " height ="100 ">
452486 < circle class ="ring-bg " cx ="50 " cy ="50 " r ="42 " />
@@ -476,7 +510,10 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
476510
477511 <!-- Targets table -->
478512 < div class ="card p-4 flex-1 min-h-0 flex flex-col ">
479- < div class ="section-label "> Ping Targets</ div >
513+ < div class ="section-label ">
514+ < svg xmlns ="http://www.w3.org/2000/svg " width ="11 " height ="11 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < circle cx ="12 " cy ="12 " r ="10 "/> < circle cx ="12 " cy ="12 " r ="6 "/> < circle cx ="12 " cy ="12 " r ="2 "/> </ svg >
515+ Ping Targets
516+ </ div >
480517 < div class ="overflow-y-auto flex-1 ">
481518 < table class ="target-table " id ="targetTable ">
482519 < thead >
@@ -493,7 +530,10 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
493530
494531 <!-- DNS table -->
495532 < div class ="card p-4 flex flex-col ">
496- < div class ="section-label "> DNS Lookups</ div >
533+ < div class ="section-label ">
534+ < svg xmlns ="http://www.w3.org/2000/svg " width ="11 " height ="11 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < circle cx ="12 " cy ="12 " r ="10 "/> < line x1 ="2 " y1 ="12 " x2 ="22 " y2 ="12 "/> < path d ="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z "/> </ svg >
535+ DNS Lookups
536+ </ div >
497537 < table class ="target-table " id ="dnsTable ">
498538 < thead >
499539 < tr >
@@ -507,7 +547,10 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
507547
508548 <!-- Quick stats footer -->
509549 < div class ="card p-3 flex-shrink-0 ">
510- < div class ="section-label mb-2 "> 24h Summary</ div >
550+ < div class ="section-label mb-2 ">
551+ < svg xmlns ="http://www.w3.org/2000/svg " width ="11 " height ="11 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2.5 " stroke-linecap ="round " stroke-linejoin ="round "> < line x1 ="18 " y1 ="20 " x2 ="18 " y2 ="10 "/> < line x1 ="12 " y1 ="20 " x2 ="12 " y2 ="4 "/> < line x1 ="6 " y1 ="20 " x2 ="6 " y2 ="14 "/> </ svg >
552+ 24h Summary
553+ </ div >
511554 < div class ="grid grid-cols-2 gap-y-2 gap-x-4 text-xs ">
512555 < div class ="flex justify-between ">
513556 < span style ="color:var(--muted) "> Uptime</ span >
@@ -545,9 +588,12 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
545588 let chartInstances = { } ;
546589
547590 async function loadData ( ) {
548- const data = await fetch ( "/api/data" ) . then ( r => r . json ( ) ) . catch ( ( ) => null ) ;
591+ const [ data , cfg ] = await Promise . all ( [
592+ fetch ( "/api/data" ) . then ( r => r . json ( ) ) . catch ( ( ) => null ) ,
593+ fetch ( "/api/config" ) . then ( r => r . json ( ) ) . catch ( ( ) => null ) ,
594+ ] ) ;
549595 if ( ! data ) return ;
550- renderHeader ( data ) ;
596+ renderHeader ( data , cfg ) ;
551597 renderSummary ( data . summary ) ;
552598 renderCharts ( data . history , data . summary ) ;
553599 renderHealth ( data . summary ) ;
@@ -556,11 +602,13 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
556602 renderQuickStats ( data . summary , data . history ) ;
557603 }
558604
559- function renderHeader ( data ) {
605+ function renderHeader ( data , cfg ) {
560606 const now = new Date ( ) ;
561607 $ ( "#lastUpdated" ) . textContent = now . toLocaleTimeString ( [ ] , { hour : '2-digit' , minute : '2-digit' } ) ;
562- $ ( "#targetCount" ) . textContent = ( data . targets || [ ] ) . length ;
563- $ ( "#networkLabel" ) . textContent = data . network_id || "unknown" ;
608+ const targetCount = cfg ? cfg . ping_targets . length : ( data . targets || [ ] ) . length ;
609+ const intervalLabel = cfg ? ` · Ping every ${ cfg . ping_interval_s } s` : "" ;
610+ $ ( "#targetCount" ) . textContent = targetCount ;
611+ $ ( "#networkLabel" ) . textContent = ( data . network_id || "unknown" ) + intervalLabel ;
564612
565613 const loss = data . summary . packet_loss ;
566614 const badge = $ ( "#statusBadge" ) ;
@@ -576,12 +624,20 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
576624 }
577625 }
578626
627+ const SVG = {
628+ clock : `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>` ,
629+ arrowDown : `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>` ,
630+ arrowUp : `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>` ,
631+ activity : `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>` ,
632+ } ;
633+
579634 function renderSummary ( s ) {
635+ const lossColor = s . packet_loss > 1 ? "red" : "green" ;
580636 const items = [
581- { label : "Avg Latency" , value : s . latency_avg + " ms" , icon : "⏱" , color : "blue" , sub : "p95 " + s . latency_p95 + " ms" } ,
582- { label : "Download" , value : s . download_avg + " Mbps" , icon : "↓" , color : "green" , sub : "avg throughput" } ,
583- { label : "Upload" , value : s . upload_avg + " Mbps" , icon : "↑" , color : "purple" , sub : "avg throughput" } ,
584- { label : "Packet Loss" , value : s . packet_loss + "%" , icon : "✕" , color : s . packet_loss > 1 ? "red" : "green" , sub : s . packet_loss < 1 ? "healthy" : "elevated" } ,
637+ { label : "Avg Latency" , value : s . latency_avg + " ms" , icon : SVG . clock , color : "blue" , sub : "p95 " + s . latency_p95 + " ms" } ,
638+ { label : "Download" , value : s . download_avg + " Mbps" , icon : SVG . arrowDown , color : "green" , sub : "avg throughput" } ,
639+ { label : "Upload" , value : s . upload_avg + " Mbps" , icon : SVG . arrowUp , color : "purple" , sub : "avg throughput" } ,
640+ { label : "Packet Loss" , value : s . packet_loss + "%" , icon : SVG . activity , color : lossColor , sub : s . packet_loss < 1 ? "healthy" : "elevated" } ,
585641 ] ;
586642 $ ( "#summaryCards" ) . innerHTML = items . map ( m =>
587643 `<div class="card metric-card" data-color="${ m . color } ">
@@ -764,14 +820,18 @@ <h3><span class="chart-dot" style="background:var(--purple)"></span> DNS Resolut
764820
765821 // Theme toggle
766822 const toggle = $ ( "#themeToggle" ) ;
767- if ( localStorage . getItem ( "theme" ) === "dark" ) {
768- document . body . classList . add ( "dark-mode" ) ;
769- toggle . textContent = "☀️" ;
823+ function applyTheme ( dark ) {
824+ document . body . classList . toggle ( "dark-mode" , dark ) ;
825+ $ ( "#iconMoon" ) . style . display = dark ? "none" : "" ;
826+ $ ( "#iconSun" ) . style . display = dark ? "" : "none" ;
770827 }
828+ applyTheme ( localStorage . getItem ( "theme" ) === "dark" ) ;
771829 toggle . addEventListener ( "click" , ( ) => {
772830 const dark = document . body . classList . toggle ( "dark-mode" ) ;
773- toggle . textContent = dark ? "☀️" : "🌙" ;
831+ applyTheme ( dark ) ;
774832 localStorage . setItem ( "theme" , dark ? "dark" : "light" ) ;
833+ // re-render charts so tooltip theme updates
834+ loadData ( ) ;
775835 } ) ;
776836
777837 // Settings modal
0 commit comments