rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
dae8de8
·
1 Parent(s): 57babb2

feat: live time-since / time-until-next-probe counter in admin panel

Browse files

The "Updated 13:11:51" label was a static wall-clock string set once at
fetch time. Hard to tell at a glance how stale the snapshot is.

Now the label updates every second and shows three things:
Updated <wall-clock> · <Xm Ys ago> · next auto-probe in <Ym Ss>

- "since" uses STATE.health.updated_at (the actual backend probe time
via /api/admin/health), falling back to local fetch time if missing.
- "next auto-probe in" = last-probe-time + 5min cadence (the value
defined by PROBE_INTERVAL_MS, which mirrors backend/llm_health.py's
background_probe_loop interval).
- setInterval(renderUpdatedLabel, 1000) keeps both counters live.
- Brace balance verified (281/281).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

frontend/public/admin/llm-control.html CHANGED
@@ -389,7 +389,8 @@
389
  password: '',
390
  health: null, // { models: [ { model, status, latency_ms, last_error, last_success } ], counters: { healthy, degraded, down } }
391
  chains: null, // { brain: [...], fast_brain: [...], judge: [...] }
392
- usage: null // { brain: {...}, fast_brain: {...}, judge: {...} } from /api/admin/usage
 
393
  };
394
  var ROLES = ['brain', 'fast_brain', 'judge'];
395
  var ROLE_COLORS = {
@@ -896,13 +897,56 @@
896
  return null;
897
  }
898
 
 
 
 
 
899
  function setLastUpdated() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  var el = $('last-updated');
901
  if (!el) return;
902
- var now = new Date();
903
- el.textContent = 'Updated ' + now.toLocaleTimeString();
 
 
 
 
 
 
 
 
904
  }
905
 
 
 
 
 
906
  // ----- Fetchers -----
907
  function fetchHealth() {
908
  return apiGet('/api/admin/health').then(function (data) {
 
389
  password: '',
390
  health: null, // { models: [ { model, status, latency_ms, last_error, last_success } ], counters: { healthy, degraded, down } }
391
  chains: null, // { brain: [...], fast_brain: [...], judge: [...] }
392
+ usage: null, // { brain: {...}, fast_brain: {...}, judge: {...} } from /api/admin/usage
393
+ lastUpdatedAt: null // ms-since-epoch of the most recent successful snapshot fetch
394
  };
395
  var ROLES = ['brain', 'fast_brain', 'judge'];
396
  var ROLE_COLORS = {
 
897
  return null;
898
  }
899
 
900
+ // Backend's background probe runs on this cadence (see
901
+ // backend/llm_health.py::background_probe_loop — currently 5 min).
902
+ var PROBE_INTERVAL_MS = 5 * 60 * 1000;
903
+
904
  function setLastUpdated() {
905
+ STATE.lastUpdatedAt = Date.now();
906
+ renderUpdatedLabel();
907
+ }
908
+
909
+ function formatMinSec(ms) {
910
+ if (ms < 0) ms = 0;
911
+ var s = Math.floor(ms / 1000);
912
+ if (s < 60) return s + 's';
913
+ var m = Math.floor(s / 60);
914
+ var rem = s % 60;
915
+ if (m < 60) return rem > 0 ? (m + 'm ' + rem + 's') : (m + 'm');
916
+ var h = Math.floor(m / 60);
917
+ return h + 'h ' + (m % 60) + 'm';
918
+ }
919
+
920
+ // Source-of-truth for "when the probe data was last current" is the
921
+ // backend's updated_at (ISO string from /api/admin/health). Falls back
922
+ // to local fetch time if the field is missing.
923
+ function probeTimestampMs() {
924
+ if (STATE.health && STATE.health.updated_at) {
925
+ var t = Date.parse(STATE.health.updated_at);
926
+ if (!isNaN(t)) return t;
927
+ }
928
+ return STATE.lastUpdatedAt;
929
+ }
930
+
931
+ function renderUpdatedLabel() {
932
  var el = $('last-updated');
933
  if (!el) return;
934
+ var probeAt = probeTimestampMs();
935
+ if (!probeAt) return;
936
+ var now = Date.now();
937
+ var since = formatMinSec(now - probeAt);
938
+ // Next auto-probe = last probe + cadence. If that's already in the
939
+ // past (probe loop running slow or paused), clamp to "due now".
940
+ var nextAt = probeAt + PROBE_INTERVAL_MS;
941
+ var untilNext = nextAt > now ? formatMinSec(nextAt - now) : 'due now';
942
+ var wallClock = new Date(probeAt).toLocaleTimeString();
943
+ el.textContent = 'Updated ' + wallClock + ' · ' + since + ' ago · next auto-probe in ' + untilNext;
944
  }
945
 
946
+ // Tick the relative portion every second so the user sees how stale
947
+ // the snapshot is in real time without having to click Refresh.
948
+ setInterval(renderUpdatedLabel, 1000);
949
+
950
  // ----- Fetchers -----
951
  function fetchHealth() {
952
  return apiGet('/api/admin/health').then(function (data) {