5fe7135e2c
Tasks 3.4-3.6 of the overview-dashboard plan. Polling (3.4/3.5): - OverviewPollerService: one timer at the fastest configured cadence, each target polled when its own interval is due, fan-out per sweep, no retries (the next tick is the retry, so damping stays the only place that decides whether a failure is worth showing). - OverviewSnapshotStore: atomic swap of a complete immutable graph plus a change event. Pages read it and never probe — the cache-first requirement. - LeaderResolver: per-group leader from akka-cluster data, with the split-brain flag scoped to members that are actually answering, so an ordinary failover is not misread as a disagreement. UI (3.6): ThemeShell + StatusPill/TechCard composition against the mockup; instance cards carry the status stripe (dashed for Unreachable), the check detail list and the raw ready/active signal line. Two defects found by verification rather than by review: - The active tier answers with a status code and an EMPTY body, but the client demanded parseable JSON on every probe — so a healthy pair rendered as "role unknown" instead of Active/Standby. Split into ProbeAsync (ready tier, body is the payload) and ProbeStatusAsync (active tier, code is the answer). Caught by a live smoke run against two fake health endpoints; the poller tests now serve an empty body so they hold the fix. - SweepAsync published even when cancellation was already requested, leaving a half-probed registry as the store's final state on shutdown. It now checks the token itself rather than relying on the transport to throw. Also: app.UseAntiforgery() is required despite the anonymous-by-design pipeline — AddRazorComponents stamps antiforgery metadata unconditionally and the endpoint middleware hard-fails without it (every page was 500). Chosen over DisableAntiforgery() so a future form is protected by default. 147 tests, 0 warnings. Live-verified end to end against fake endpoints: Up/Unreachable/Active/Standby, leader chip, cluster-data line, KPI counts.
189 lines
5.1 KiB
JSON
189 lines
5.1 KiB
JSON
{
|
|
"plan": "docs/plans/2026-07-22-overview-dashboard-impl-plan.md",
|
|
"tasks": [
|
|
{
|
|
"id": "1",
|
|
"subject": "0.1 Health writer: optional per-entry `data` object",
|
|
"status": "completed",
|
|
"blockedBy": []
|
|
},
|
|
{
|
|
"id": "2",
|
|
"subject": "0.2 AkkaClusterHealthCheck: populate cluster-view data",
|
|
"status": "completed",
|
|
"blockedBy": []
|
|
},
|
|
{
|
|
"id": "3",
|
|
"subject": "0.3 Health 0.2.0: version, test, pack, publish, verify",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"1",
|
|
"2"
|
|
]
|
|
},
|
|
{
|
|
"id": "4",
|
|
"subject": "1.1 ScadaBridge: bump Health packages to 0.2.0",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"3"
|
|
]
|
|
},
|
|
{
|
|
"id": "5",
|
|
"subject": "1.2 ScadaBridge: site health checks (akka-cluster, localdb, active-node)",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"4"
|
|
]
|
|
},
|
|
{
|
|
"id": "6",
|
|
"subject": "1.3 ScadaBridge: map MapZbHealth on the site pipeline",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"5"
|
|
]
|
|
},
|
|
{
|
|
"id": "7",
|
|
"subject": "1.4 ScadaBridge: site health tests",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"6"
|
|
]
|
|
},
|
|
{
|
|
"id": "8",
|
|
"subject": "2.1 OtOpcUa: verify `akka` check is the shared AkkaClusterHealthCheck",
|
|
"status": "completed",
|
|
"blockedBy": []
|
|
},
|
|
{
|
|
"id": "9",
|
|
"subject": "2.2 OtOpcUa: bump Health to 0.2.0",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"3",
|
|
"8"
|
|
]
|
|
},
|
|
{
|
|
"id": "10",
|
|
"subject": "2.3 Alignment bumps: mxgw + HistorianGateway Health 0.2.0",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"3"
|
|
]
|
|
},
|
|
{
|
|
"id": "11",
|
|
"subject": "3.1 Overview: scaffold project",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"3"
|
|
]
|
|
},
|
|
{
|
|
"id": "12",
|
|
"subject": "3.2 Overview: registry options + validation",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"11"
|
|
]
|
|
},
|
|
{
|
|
"id": "13",
|
|
"subject": "3.3 Overview: health client + status model",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"11"
|
|
]
|
|
},
|
|
{
|
|
"id": "14",
|
|
"subject": "3.4 Overview: poller + snapshot store",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"12",
|
|
"13"
|
|
]
|
|
},
|
|
{
|
|
"id": "15",
|
|
"subject": "3.5 Overview: leader aggregation",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"13"
|
|
]
|
|
},
|
|
{
|
|
"id": "16",
|
|
"subject": "3.6 Overview: UI (ThemeShell, Overview page, InstanceCard)",
|
|
"status": "completed",
|
|
"blockedBy": [
|
|
"14",
|
|
"15"
|
|
]
|
|
},
|
|
{
|
|
"id": "17",
|
|
"subject": "3.7 Overview: self-observability",
|
|
"status": "pending",
|
|
"blockedBy": [
|
|
"14"
|
|
]
|
|
},
|
|
{
|
|
"id": "18",
|
|
"subject": "3.8 Overview: tests",
|
|
"status": "pending",
|
|
"blockedBy": [
|
|
"16",
|
|
"17"
|
|
]
|
|
},
|
|
{
|
|
"id": "19",
|
|
"subject": "3.9 Overview: config + docker",
|
|
"status": "pending",
|
|
"blockedBy": [
|
|
"17"
|
|
]
|
|
},
|
|
{
|
|
"id": "20",
|
|
"subject": "4 Live acceptance (design \u00a79)",
|
|
"status": "pending",
|
|
"blockedBy": [
|
|
"7",
|
|
"9",
|
|
"18",
|
|
"19"
|
|
]
|
|
}
|
|
],
|
|
"notes": {
|
|
"deferred": [
|
|
"Task 2.2 rig check (admin node /health/ready shows data.leader) \u2014 live docker-dev, folded into Phase 4.",
|
|
"Phase 1 DoD rig curls (site :8084 ready/active split) \u2014 live docker rig, folded into Phase 4."
|
|
],
|
|
"branches": {
|
|
"scadaproj": "feat/health-0.2.0-cluster-data (80668a0) -> feat/overview-dashboard, stacked",
|
|
"ScadaBridge": "feat/site-node-health (commit 47850c0f)",
|
|
"OtOpcUa": "feat/health-0.2.0-bump (commit 2e515c34, made in an isolated git worktree \u2014 feat/mesh-phase6 working tree untouched)",
|
|
"HistorianGateway": "chore/health-0.2.0-bump (commit 51f0c2b)",
|
|
"MxAccessGateway": "chore/health-0.2.0-bump (commit 2d54ace)"
|
|
},
|
|
"front_loaded": [
|
|
"Validator / health-client / status-derivation tests from task 3.8 were written with tasks 3.2-3.3 so each batch is verified; 3.8 covers the remainder (poller, leader, store, bUnit, boot).",
|
|
"Poller / snapshot-store / leader-resolver / bUnit InstanceCard + Overview page tests were written with tasks 3.4-3.6; task 3.8 now covers only the WebApplicationFactory boot tests."
|
|
],
|
|
"deviations": [
|
|
"Task 3.6 said 'no antiforgery'. AddRazorComponents stamps antiforgery metadata on every component endpoint unconditionally and the endpoint middleware hard-fails without a matching middleware, so every page returned 500. Resolved with app.UseAntiforgery() (inert here, no forms) rather than MapRazorComponents(...).DisableAntiforgery(), which would silently expose the first form anyone adds. No authentication was added.",
|
|
"Bootstrap IS vendored (copied from HistorianGateway, v5.3.3). Not optional: ThemeShell and TechButton are built on Bootstrap utility classes, so the shared kit does not render without it."
|
|
]
|
|
}
|
|
}
|