From 4ddf1241740d0afe33e6d9a1230f00220b55a24a Mon Sep 17 00:00:00 2001 From: Dejan R Date: Sun, 19 Apr 2026 12:44:28 +0200 Subject: [PATCH] added autorereload and API --- main.go | 61 +++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 1310020..3a9af1e 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -32,7 +33,7 @@ import ( //go:embed static var staticFiles embed.FS -const version = "0.9.0" +const version = "0.9.1" // --------------------------------------------------------------------------- // Config structs @@ -484,6 +485,7 @@ type PageData struct { ShowGauges bool ShowGaugeDigital bool ShowTrendChart bool + UIRevision uint64 } type NumericStats struct { @@ -528,6 +530,7 @@ var ( alarmTracker AlarmTracker uiTemplate = template.Must(template.New("ui").Parse(uiHTML)) cachedUI []byte + uiRevision uint64 = 1 ) // --------------------------------------------------------------------------- @@ -552,7 +555,7 @@ func getConfigSnapshot() Config { return cfg } -func buildCachedUI(config Config) ([]byte, error) { +func buildCachedUI(config Config, revision uint64) ([]byte, error) { data := PageData{ Title: config.UI.Title, Subtitle: config.UI.Subtitle, @@ -579,6 +582,7 @@ func buildCachedUI(config Config) ([]byte, error) { ShowGauges: boolValue(config.Modules.ShowGauges, true), ShowGaugeDigital: boolValue(config.Modules.ShowGaugeDigital, false), ShowTrendChart: boolValue(config.Modules.ShowTrendChart, true), + UIRevision: revision, } var buf bytes.Buffer @@ -590,7 +594,7 @@ func buildCachedUI(config Config) ([]byte, error) { func initCachedUI() { config := getConfigSnapshot() - payload, err := buildCachedUI(config) + payload, err := buildCachedUI(config, atomic.LoadUint64(&uiRevision)) if err != nil { log.Fatalf("failed to pre-render UI template: %v", err) } @@ -655,16 +659,20 @@ func reloadConfigSafely(configPath string) { updatedCfg := oldCfg hotReloadSectionsLocked(&updatedCfg, newCfg) - payload, err := buildCachedUI(updatedCfg) - if err != nil { - log.Printf("config reload rejected: failed to rebuild UI: %v", err) - return - } + if len(hotSections) > 0 { + nextUIRevision := atomic.LoadUint64(&uiRevision) + 1 + payload, err := buildCachedUI(updatedCfg, nextUIRevision) + if err != nil { + log.Printf("config reload rejected: failed to rebuild UI: %v", err) + return + } - cfgMu.Lock() - cfg = updatedCfg - cachedUI = payload - cfgMu.Unlock() + cfgMu.Lock() + cfg = updatedCfg + cachedUI = payload + atomic.StoreUint64(&uiRevision, nextUIRevision) + cfgMu.Unlock() + } if len(hotSections) == 0 && len(restartSections) == 0 { log.Printf("config reload checked: no effective changes") @@ -1764,6 +1772,14 @@ func apiData(w http.ResponseWriter, r *http.Request) { _ = json.NewEncoder(w).Encode(snapshotState()) } +func apiUIRevision(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "no-store") + _ = json.NewEncoder(w).Encode(map[string]uint64{ + "revision": atomic.LoadUint64(&uiRevision), + }) +} + func apiHistory(w http.ResponseWriter, r *http.Request) { window, label, err := parseWindow(r.URL.Query().Get("window")) if err != nil { @@ -1903,6 +1919,7 @@ func main() { mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticRoot)))) mux.HandleFunc("/", serveUI) mux.HandleFunc("/api/data", apiData) + mux.HandleFunc("/api/ui-revision", apiUIRevision) mux.HandleFunc("/api/history", apiHistory) mux.HandleFunc("/api/trend", apiTrend) mux.HandleFunc("/api/alarms", apiAlarms) @@ -2689,6 +2706,7 @@ const uiHTML = ` const SHOW_GAUGES = {{if .ShowGauges}}true{{else}}false{{end}}; const SHOW_GAUGE_DIGITAL = {{if .ShowGaugeDigital}}true{{else}}false{{end}}; const SHOW_TREND_CHART = {{if .ShowTrendChart}}true{{else}}false{{end}}; + const CURRENT_UI_REVISION = {{.UIRevision}}; const START_ANGLE = Math.PI * 0.75; const END_ANGLE = Math.PI * 2.25; @@ -2698,6 +2716,7 @@ const uiHTML = ` let currentWindow = DEFAULT_WINDOW; let currentTrendWindow = DEFAULT_TREND_WINDOW; let currentTheme = 'dark'; + let activeUIRevision = CURRENT_UI_REVISION; let historyBusy = false; let trendBusy = false; let alarmsBusy = false; @@ -3329,6 +3348,22 @@ const uiHTML = ` lineChart.update('none'); } + async function checkUIRevision() { + try { + const res = await fetch('/api/ui-revision', { cache: 'no-store' }); + if (!res.ok) return; + const d = await res.json(); + const revision = Number(d.revision) || 0; + if (revision > activeUIRevision) { + window.location.reload(); + return; + } + activeUIRevision = revision; + } catch (err) { + console.warn('UI revision check error:', err); + } + } + async function fetchLiveData() { try { const res = await fetch('/api/data', { cache: 'no-store' }); @@ -3683,8 +3718,10 @@ const uiHTML = ` fetchHistory(); fetchTrend(); fetchAlarms(); + checkUIRevision(); setInterval(fetchLiveData, POLL_MS); + setInterval(checkUIRevision, 1200); if (SHOW_TREND_CHART) { setInterval(fetchHistory, Math.max(1500, POLL_MS * 3));