diff --git a/backend/app/api/routes/observability.py b/backend/app/api/routes/observability.py
index a45c974..2134035 100644
--- a/backend/app/api/routes/observability.py
+++ b/backend/app/api/routes/observability.py
@@ -157,8 +157,18 @@ async def read_summary(
problems=problems,
flows={
"total": len(names),
+ # What the engine will actually act on. Enabled is not enough:
+ # validation stops a flow as surely as quarantine does, and a
+ # paused one is holding its messages rather than running them.
"running": len(
- [n for n in names if controller.is_enabled(n) and n not in quarantined]
+ [
+ n
+ for n in names
+ if controller.is_enabled(n)
+ and n not in quarantined
+ and n not in paused
+ and n not in invalid
+ ]
),
"paused": len(paused),
"quarantined": len(quarantined),
diff --git a/backend/tests/api/routes/test_observability.py b/backend/tests/api/routes/test_observability.py
index 494541a..24ba915 100644
--- a/backend/tests/api/routes/test_observability.py
+++ b/backend/tests/api/routes/test_observability.py
@@ -91,6 +91,10 @@ def test_a_flow_that_cannot_run_makes_the_summary_degraded(
controller = client.app.state.flow_controller
before = controller.issues
+ before_flows = controller.store.list_flows
+ # The looping flow has to be one the store lists, otherwise "running"
+ # counts nothing either way and the assertion below proves nothing.
+ controller.store.list_flows = lambda: ["looping"]
controller.issues = [
ValidationIssue(
code="cycle",
@@ -110,9 +114,11 @@ def test_a_flow_that_cannot_run_makes_the_summary_degraded(
body = client.get(f"{PREFIX}/summary", headers=superuser_token_headers).json()
finally:
controller.issues = before
+ controller.store.list_flows = before_flows
assert body["status"] == "degraded"
assert body["flows"]["invalid"] == 1
+ assert body["flows"]["running"] == 0
assert any("looping" in problem for problem in body["problems"])
assert not any("broken" in problem for problem in body["problems"])
assert not any("hooky" in problem for problem in body["problems"])
diff --git a/frontend/src/components/Health/HealthActivity.tsx b/frontend/src/components/Health/HealthActivity.tsx
index a409485..2695192 100644
--- a/frontend/src/components/Health/HealthActivity.tsx
+++ b/frontend/src/components/Health/HealthActivity.tsx
@@ -8,7 +8,7 @@ import { UplotChart } from "@/components/Common/UplotChart"
import { useEngineEvents } from "@/components/Flow/liveStore"
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
import { Button } from "@/components/ui/button"
-import { cn, si } from "@/lib/utils"
+import { cn, dur } from "@/lib/utils"
import {
ago,
auditQueryOptions,
@@ -319,8 +319,8 @@ export function HealthActivity({ range }: { range: Range }) {
>
{run.status}
-
- {si(run.duration_ms)} ms
+
+ {dur(run.duration_ms)}
{ago(run.started_at)}
diff --git a/frontend/src/components/Health/HealthOverview.tsx b/frontend/src/components/Health/HealthOverview.tsx
index 6870898..3dd8705 100644
--- a/frontend/src/components/Health/HealthOverview.tsx
+++ b/frontend/src/components/Health/HealthOverview.tsx
@@ -6,7 +6,7 @@ import { type Range, RangePicker } from "@/components/Common/RangePicker"
import { Sparkline } from "@/components/Common/Sparkline"
import { PANEL_SECTION } from "@/components/Flow/SidePanel"
import { Badge } from "@/components/ui/badge"
-import { si } from "@/lib/utils"
+import { dur, si } from "@/lib/utils"
import {
ago,
CARD,
@@ -117,16 +117,18 @@ export function HealthOverview({