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/);