Skip to content

Commit 0b41be7

Browse files
authored
Merge pull request #329 from Blazity/codex/system-health
feat(dashboard): add system health diagnostics
2 parents 73d612c + b838343 commit 0b41be7

16 files changed

Lines changed: 1728 additions & 7 deletions

File tree

apps/dashboard/app/(cockpit)/cockpit-shell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const TITLE_FOR_SCREEN: Record<string, string> = {
5858
editor: "Workflow editor",
5959
profiles: "Harness profiles",
6060
checks: "Pre-PR checks",
61+
health: "System health",
6162
users: "Users",
6263
trace: "Run trace",
6364
ticket: "Ticket runs",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Suspense } from "react";
2+
import { HealthData } from "@/app/health-data";
3+
import { HealthSkeleton } from "@/app/health-skeleton";
4+
5+
export default function HealthPage() {
6+
return (
7+
<Suspense fallback={<HealthSkeleton />}>
8+
<HealthData />
9+
</Suspense>
10+
);
11+
}

apps/dashboard/app/health-data.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { redirect } from "next/navigation";
2+
import type { SystemHealthResponse } from "@shared/contracts";
3+
import { getJSON } from "@/lib/api/server";
4+
import { requireSession } from "@/lib/auth/session";
5+
import { HealthScreen } from "@/components/cockpit/screens/health";
6+
7+
export async function HealthData() {
8+
const session = await requireSession();
9+
if (!session.canManageUsers) redirect("/");
10+
const data = await getJSON<SystemHealthResponse>("/api/v1/system/health");
11+
return <HealthScreen data={data} />;
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Block } from "./skeleton-block";
2+
3+
export function HealthSkeleton() {
4+
return (
5+
<div className="mx-auto w-full max-w-[1120px] px-4 pb-10 pt-5 lg:px-6 lg:pt-6">
6+
<div className="mb-5 flex items-end justify-between border-b border-neutral-300 pb-5">
7+
<div className="space-y-2">
8+
<Block className="h-3 w-36" />
9+
<Block className="h-8 w-52" />
10+
<Block className="h-4 w-[480px] max-w-full" />
11+
</div>
12+
<Block className="h-8 w-24" />
13+
</div>
14+
<Block className="mb-5 h-[92px]" />
15+
<div className="grid gap-4">
16+
<Block className="h-[330px]" />
17+
<Block className="h-[220px]" />
18+
</div>
19+
</div>
20+
);
21+
}

apps/dashboard/components/cockpit/chrome.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import test from "node:test";
33

44
import { cockpitNavItems } from "./chrome";
55

6-
test("Harness Profiles is always discoverable while user management remains role-gated", () => {
6+
test("Harness Profiles is always discoverable while administration remains role-gated", () => {
77
const memberIds = cockpitNavItems({ canManageUsers: false }).map(
88
(item) => item.id,
99
);
1010
assert.ok(memberIds.includes("profiles"));
11+
assert.ok(!memberIds.includes("health"));
1112
assert.ok(!memberIds.includes("users"));
1213

1314
const adminIds = cockpitNavItems({ canManageUsers: true }).map(
1415
(item) => item.id,
1516
);
1617
assert.ok(adminIds.includes("profiles"));
18+
assert.ok(adminIds.includes("health"));
1719
assert.ok(adminIds.includes("users"));
1820
});

apps/dashboard/components/cockpit/chrome.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ const NAV = [
1414
{ id: "editor", label: "Workflow editor", glyph: "▷", group: "flow" },
1515
{ id: "profiles", label: "Harness profiles", glyph: "⌘", group: "flow" },
1616
{ id: "checks", label: "Pre-PR checks", glyph: "☑", group: "flow" },
17+
{ id: "health", label: "System health", glyph: "+", group: "team" },
1718
{ id: "users", label: "Users", glyph: "U", group: "team" },
1819
];
1920

2021
const NAV_GROUPS = [
2122
{ id: "obs", label: "Observability" },
2223
{ id: "flow", label: "Workflow" },
23-
{ id: "team", label: "Users" },
24+
{ id: "team", label: "Administration" },
2425
];
2526

2627
export const MOBILE_MORE_NAV_IDS = [
@@ -31,6 +32,7 @@ export const MOBILE_MORE_NAV_IDS = [
3132
"cost",
3233
"profiles",
3334
"checks",
35+
"health",
3436
"users",
3537
] as const;
3638

@@ -44,7 +46,8 @@ export function cockpitNavItems({
4446
canManageUsers: boolean;
4547
}) {
4648
return NAV.filter(
47-
(item) => item.id !== "users" || canManageUsers,
49+
(item) =>
50+
(item.id !== "users" && item.id !== "health") || canManageUsers,
4851
);
4952
}
5053

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import React from "react";
4+
import { act, create } from "react-test-renderer";
5+
import { AppRouterContext } from "next/dist/shared/lib/app-router-context.shared-runtime";
6+
import type { SystemHealthResponse } from "@shared/contracts";
7+
import { HealthScreen } from "./health";
8+
9+
(globalThis as typeof globalThis & { React: typeof React }).React = React;
10+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
11+
12+
const data: SystemHealthResponse = {
13+
generatedAt: "2026-08-20T12:00:00.000Z",
14+
summary: { total: 2, live: 1, down: 1, notConfigured: 0, criticalDown: 1 },
15+
integrations: [
16+
{
17+
id: "database",
18+
label: "Database",
19+
group: "core",
20+
envVars: ["DATABASE_URL"],
21+
critical: true,
22+
mode: "live",
23+
ping: { ok: true, latencyMs: 12 },
24+
},
25+
{
26+
id: "jira",
27+
label: "Jira",
28+
group: "core",
29+
envVars: ["JIRA_API_TOKEN"],
30+
critical: true,
31+
mode: "down",
32+
ping: { ok: false, latencyMs: 40, error: "Jira authentication check failed." },
33+
},
34+
],
35+
alerts: [
36+
{
37+
severity: "critical",
38+
integrationId: "jira",
39+
message: "Jira: Jira authentication check failed.",
40+
fixHint: "Check JIRA_API_TOKEN and the provider setup.",
41+
},
42+
],
43+
};
44+
45+
function textOf(value: unknown): string {
46+
if (typeof value === "string" || typeof value === "number") return String(value);
47+
if (Array.isArray(value)) return value.map(textOf).join(" ");
48+
if (value && typeof value === "object" && "children" in value) {
49+
return textOf((value as { children?: unknown }).children);
50+
}
51+
return "";
52+
}
53+
54+
test("health screen exposes the failed service, fix hint, and safe env names", () => {
55+
const router = { refresh() {} };
56+
let renderer!: ReturnType<typeof create>;
57+
act(() => {
58+
renderer = create(
59+
<AppRouterContext.Provider value={router as never}>
60+
<HealthScreen data={data} />
61+
</AppRouterContext.Provider>,
62+
);
63+
});
64+
const text = textOf(renderer.toJSON());
65+
assert.match(text, /Action required/);
66+
assert.match(text, /Jira authentication check failed/);
67+
assert.match(text, /JIRA_API_TOKEN/);
68+
assert.match(text, /DATABASE_URL/);
69+
assert.doesNotMatch(text, /secret-value/);
70+
act(() => renderer.unmount());
71+
});
72+
73+
test("scan again refreshes the server data", () => {
74+
let refreshes = 0;
75+
const router = { refresh: () => refreshes++ };
76+
let renderer!: ReturnType<typeof create>;
77+
act(() => {
78+
renderer = create(
79+
<AppRouterContext.Provider value={router as never}>
80+
<HealthScreen data={{ ...data, alerts: [], summary: { ...data.summary, down: 0, criticalDown: 0 } }} />
81+
</AppRouterContext.Provider>,
82+
);
83+
});
84+
const button = renderer.root.findByProps({ children: "Scan again" });
85+
act(() => button.props.onClick());
86+
assert.equal(refreshes, 1);
87+
act(() => renderer.unmount());
88+
});

0 commit comments

Comments
 (0)