diff --git a/src/operator-dashboard/static/components/ServiceCard.jsx b/src/operator-dashboard/static/components/ServiceCard.jsx
index d398867..8de78df 100644
--- a/src/operator-dashboard/static/components/ServiceCard.jsx
+++ b/src/operator-dashboard/static/components/ServiceCard.jsx
@@ -1,8 +1,25 @@
+import { useEffect, useState } from 'react';
+
import Pill from './Pill.jsx';
-import { formatAge, formatBoolean, formatTimestamp } from '../lib/format.js';
+import { formatAge, formatAgeFromTimestamp, formatBoolean, formatTimestamp } from '../lib/format.js';
+
+function useNow(intervalMs = 1000) {
+ const [now, setNow] = useState(() => Date.now());
+
+ useEffect(() => {
+ const timer = window.setInterval(() => setNow(Date.now()), intervalMs);
+ return () => window.clearInterval(timer);
+ }, [intervalMs]);
+
+ return now;
+}
export default function ServiceCard({ service }) {
const healthLabel = service.health_label || service.health_status || (service.reachable ? 'online' : 'offline');
+ const now = useNow();
+ const freshnessAge = service.freshness_at
+ ? formatAgeFromTimestamp(service.freshness_at, now)
+ : formatAge(service.freshness_age_ms);
return (
@@ -14,7 +31,7 @@ export default function ServiceCard({ service }) {
{`Reachable ${formatBoolean(service.reachable)}`}
{`Paused ${formatBoolean(service.paused)}`}
{`Armed ${formatBoolean(service.armed)}`}
-
{`Freshness ${formatAge(service.freshness_age_ms)}${service.freshness_age_ms == null ? '' : ' ago'}`}
+
{`Freshness ${freshnessAge}${freshnessAge === 'Unavailable' ? '' : ' ago'}`}
{`Freshness at ${formatTimestamp(service.freshness_at)}`}
{service.base_url}
{service.last_error ?
{JSON.stringify(service.last_error)}
: null}
diff --git a/test/operator-dashboard-ui-static.test.mjs b/test/operator-dashboard-ui-static.test.mjs
index 2f4c119..f253552 100644
--- a/test/operator-dashboard-ui-static.test.mjs
+++ b/test/operator-dashboard-ui-static.test.mjs
@@ -30,6 +30,7 @@ test('funds page no longer renders duplicate quote and submission tables', () =>
});
test('dashboard freshness surfaces show age and exact timestamp evidence', () => {
+ assert.match(serviceCardSource, /formatAgeFromTimestamp\(service\.freshness_at, now\)/);
assert.match(serviceCardSource, /formatTimestamp\(service\.freshness_at\)/);
assert.match(serviceCardSource, /Freshness at/);
assert.match(stylesSource, /\.quote-row-flash td/);