Skip to content

Commit cd38b09

Browse files
committed
PR_26175_CHARLIE_010-system-health-history-and-closeout
1 parent 9740705 commit cd38b09

8 files changed

Lines changed: 227 additions & 4 deletions

File tree

admin/system-health.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ <h2>Admin</h2>
4343
<p>Local API Startup</p>
4444
<p>Database Health</p>
4545
<p>Storage Health</p>
46+
<p>Health Check History</p>
4647
<p>Runtime Environment</p>
4748
<p>Limits &amp; Capacity</p>
4849
<p>Diagnostics Plan</p>
@@ -152,6 +153,23 @@ <h2 id="admin-system-health-title">System Health Tables</h2>
152153
</tbody>
153154
</table>
154155
</div>
156+
<div class="table-wrapper">
157+
<table class="data-table" aria-label="Health check history">
158+
<caption>Health Check History</caption>
159+
<thead>
160+
<tr>
161+
<th scope="col">Time</th>
162+
<th scope="col">Environment</th>
163+
<th scope="col">Area</th>
164+
<th scope="col">Result</th>
165+
<th scope="col">Summary</th>
166+
</tr>
167+
</thead>
168+
<tbody data-admin-system-health-history-rows>
169+
<tr><td>Loading</td><td>Loading</td><td>Current environment</td><td data-health-status="PENDING" title="Reason: health check history has not loaded yet." aria-label="PENDING: health check history has not loaded yet.">PENDING</td><td>Waiting for safe API status.</td></tr>
170+
</tbody>
171+
</table>
172+
</div>
155173
<div class="table-wrapper">
156174
<table class="data-table" aria-label="Runtime environment">
157175
<caption>Runtime Environment - Alphabetical Variables</caption>

assets/theme-v2/js/admin-system-health.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class AdminSystemHealthController {
4747
node.dataset.adminSystemHealthStorageStatus,
4848
node,
4949
]));
50+
this.historyRows = root.querySelector("[data-admin-system-health-history-rows]");
5051
this.startupRows = root.querySelector("[data-admin-system-health-startup-rows]");
5152
this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]");
5253
}
@@ -107,6 +108,7 @@ class AdminSystemHealthController {
107108
});
108109
this.renderStartupPending(reason);
109110
this.renderStoragePending(reason);
111+
this.renderHistoryPending(reason);
110112
}
111113

112114
renderEnvironmentIdentity(environmentIdentity = {}) {
@@ -222,6 +224,45 @@ class AdminSystemHealthController {
222224
}
223225
}
224226

227+
renderHistoryPending(reason) {
228+
if (!this.historyRows) {
229+
return;
230+
}
231+
const row = document.createElement("tr");
232+
row.append(
233+
this.createCell("not available"),
234+
this.createCell("current environment"),
235+
this.createCell("Health Check History"),
236+
this.createStatusCell("PENDING", reason),
237+
this.createCell("Safe health check history is not available."),
238+
);
239+
this.historyRows.replaceChildren(row);
240+
}
241+
242+
renderHealthCheckHistory(historyRows = []) {
243+
if (!this.historyRows) {
244+
return;
245+
}
246+
const rows = Array.isArray(historyRows) ? historyRows : [];
247+
if (!rows.length) {
248+
this.renderHistoryPending("Safe Admin System Health API returned no current-environment health check history rows.");
249+
return;
250+
}
251+
const fragment = document.createDocumentFragment();
252+
rows.forEach((historyRow) => {
253+
const row = document.createElement("tr");
254+
row.append(
255+
this.createCell(historyRow.checkedAt),
256+
this.createCell(historyRow.environmentName),
257+
this.createCell(historyRow.area),
258+
this.createStatusCell(historyRow.result || historyRow.status, historyRow.summary),
259+
this.createCell(historyRow.summary),
260+
);
261+
fragment.append(row);
262+
});
263+
this.historyRows.replaceChildren(fragment);
264+
}
265+
225266
runStorageDiagnostics() {
226267
STORAGE_DIAGNOSTIC_ACTIONS.forEach(({ key }) => {
227268
this.setStorageStatus(key, "PENDING", "R2 diagnostic is running through the safe Admin System Health API.");
@@ -302,6 +343,7 @@ class AdminSystemHealthController {
302343
this.renderStartupDiagnostics(data?.localApiStartup || {});
303344
this.renderStorageStatus(data?.storageStatus || {});
304345
this.runStorageDiagnostics();
346+
this.renderHealthCheckHistory(data?.healthCheckHistory || []);
305347
this.renderRuntimeEnvironment(data?.runtimeEnvironment || {});
306348
} catch (error) {
307349
const message = error instanceof Error ? error.message : "Safe Admin System Health API is unavailable.";
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# PR_26175_CHARLIE_010 System Health History and Closeout
2+
3+
## Scope
4+
5+
Team: Charlie
6+
7+
Purpose: Add current-environment Health Check History and close out the System Health stacked expansion chain.
8+
9+
## Chain
10+
11+
- `PR_26175_CHARLIE_006-project-instructions-system-health-infrastructure`
12+
- Branch: `PR_26175_CHARLIE_006-project-instructions-system-health-infrastructure`
13+
- Commit: `c870b812faded383094fb13fc60ae9cfc7f14889`
14+
- Result: pushed to origin, not merged.
15+
- `PR_26175_CHARLIE_007-system-health-environment-identity`
16+
- Branch: `pr/26175-CHARLIE-007-system-health-environment-identity`
17+
- Commit: `6556e73efcd8ae13b51b56288416c88688e67634`
18+
- Draft PR: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/151
19+
- `PR_26175_CHARLIE_008-system-health-current-database-health`
20+
- Branch: `pr/26175-CHARLIE-008-system-health-current-database-health`
21+
- Commit: `2603aeb6e5a7ccca516051953cbd70a5a6c94c4b`
22+
- Draft PR: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/152
23+
- `PR_26175_CHARLIE_009-system-health-current-r2-health`
24+
- Branch: `pr/26175-CHARLIE-009-system-health-current-r2-health`
25+
- Commit: `9740705fbe73dffd43744ab66338f8e4a925eed4`
26+
- Draft PR: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/153
27+
- `PR_26175_CHARLIE_010-system-health-history-and-closeout`
28+
- Branch: `pr/26175-CHARLIE-010-system-health-history-and-closeout`
29+
- Commit and draft PR URL are assigned after this report is committed and pushed.
30+
31+
## Changes
32+
33+
- Added Health Check History to the Admin System Health page.
34+
- Health Check History is derived from the current deployment only:
35+
- Environment Summary
36+
- Database Health
37+
- Storage Health
38+
- Runtime Health
39+
- Warning and failure rows are generated only from current-environment health rows.
40+
- Creator sessions remain blocked from Admin System Health and do not trigger status or storage health requests.
41+
42+
## Governance
43+
44+
- PASS: System Health is one page per deployed environment.
45+
- PASS: Each deployment actively checks only itself.
46+
- PASS: The Environment Map remains a static reference for Local, DEV, IST, UAT, and PRD.
47+
- PASS: System Health does not actively check peer databases or peer R2 folders.
48+
- PASS: Cancelled initiatives remain not doing:
49+
- Environment Isolation & Developer Experience
50+
- multi-port workspace framework
51+
- Alpha/Beta/User runtime separation
52+
- runtime port management initiative
53+
54+
## Validation
55+
56+
- PASS: `node --check src/dev-runtime/server/local-api-router.mjs`
57+
- PASS: `node --check assets/theme-v2/js/admin-system-health.js`
58+
- PASS: `git diff --check`
59+
- PASS: `node --test tests/dev-runtime/AdminHealthOperations.test.mjs`
60+
- PASS: `node --test tests/dev-runtime/PublicEnvironmentConfig.test.mjs`
61+
- PASS: `npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line`
62+
63+
## Artifacts
64+
65+
- `tmp/PR_26175_CHARLIE_007-system-health-environment-identity_delta.zip`
66+
- `tmp/PR_26175_CHARLIE_008-system-health-current-database-health_delta.zip`
67+
- `tmp/PR_26175_CHARLIE_009-system-health-current-r2-health_delta.zip`
68+
- `tmp/PR_26175_CHARLIE_010-system-health-history-and-closeout_delta.zip`

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
99
(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
10-
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 294/294; executed functions 33/37
10+
(88%) assets/theme-v2/js/admin-system-health.js - executed lines 334/334; executed functions 35/40
1111

1212
Guardrail warnings:
1313
(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Exercised tool entry points detected:
1818

1919
Changed runtime JS files covered:
2020
(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
21-
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 294/294; executed functions 33/37
21+
(88%) assets/theme-v2/js/admin-system-health.js - executed lines 334/334; executed functions 35/40
2222

2323
Files with executed line/function counts where available:
2424
(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14
@@ -28,7 +28,7 @@ Files with executed line/function counts where available:
2828
(74%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1001/1001; executed functions 69/93
2929
(80%) src/api/admin-owner-navigation.js - executed lines 42/42; executed functions 4/5
3030
(83%) assets/js/shared/status.js - executed lines 37/37; executed functions 5/6
31-
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 294/294; executed functions 33/37
31+
(88%) assets/theme-v2/js/admin-system-health.js - executed lines 334/334; executed functions 35/40
3232
(91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11
3333
(100%) src/api/admin-system-health-api-client.js - executed lines 19/19; executed functions 3/3
3434

@@ -39,4 +39,4 @@ Changed JS files considered:
3939
(0%) src/dev-runtime/server/local-api-router.mjs - changed JS file not collected as browser runtime coverage
4040
(0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage
4141
(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage
42-
(89%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage
42+
(88%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage

src/dev-runtime/server/local-api-router.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,6 +1028,71 @@ function systemHealthRuntimeEnvironment(env = process.env) {
10281028
};
10291029
}
10301030

1031+
function systemHealthHistoryRow({ checkedAt, environmentName, area, result, summary, kind = "recent check" }) {
1032+
return {
1033+
area,
1034+
checkedAt,
1035+
environmentName,
1036+
kind,
1037+
result: normalizeHealthStatus(result),
1038+
summary: String(summary || "No summary returned."),
1039+
};
1040+
}
1041+
1042+
function systemHealthCheckHistory({
1043+
checkedAt,
1044+
databaseStatus = {},
1045+
environmentIdentity = {},
1046+
runtimeEnvironment = {},
1047+
storageStatus = {},
1048+
}) {
1049+
const environmentName = environmentIdentity.name || "Unknown";
1050+
const databaseResponseTime = Number.isFinite(databaseStatus.responseTimeMs)
1051+
? `${databaseStatus.responseTimeMs} ms`
1052+
: "not available";
1053+
const recentChecks = [
1054+
systemHealthHistoryRow({
1055+
area: "Environment Summary",
1056+
checkedAt,
1057+
environmentName,
1058+
result: environmentIdentity.status,
1059+
summary: `Current deployment ${environmentName}; hosting ${environmentIdentity.hostingModel || "not configured"}; storage folder ${environmentIdentity.storageFolder || "not configured"}.`,
1060+
}),
1061+
systemHealthHistoryRow({
1062+
area: "Database Health",
1063+
checkedAt,
1064+
environmentName,
1065+
result: databaseStatus.connectivityStatus || databaseStatus.status,
1066+
summary: `${databaseStatus.databaseType || "PostgreSQL"} connectivity ${databaseStatus.connectivity || "not configured"}; response time ${databaseResponseTime}; version ${databaseStatus.version || "not available"}.`,
1067+
}),
1068+
systemHealthHistoryRow({
1069+
area: "Storage Health",
1070+
checkedAt,
1071+
environmentName,
1072+
result: storageStatus.status,
1073+
summary: `Cloudflare R2 folder ${storageStatus.environmentFolder || environmentIdentity.storageFolder || "not configured"}; bucket ${storageStatus.configured ? "configured" : "not configured"}; last checked ${storageStatus.lastChecked || checkedAt}.`,
1074+
}),
1075+
systemHealthHistoryRow({
1076+
area: "Runtime Health",
1077+
checkedAt,
1078+
environmentName,
1079+
result: runtimeEnvironment.status,
1080+
summary: runtimeEnvironment.message || "Runtime environment health unavailable.",
1081+
}),
1082+
];
1083+
const issueRows = recentChecks
1084+
.filter((row) => row.result !== "PASS")
1085+
.map((row) => systemHealthHistoryRow({
1086+
area: `${row.area} ${row.result === "FAIL" ? "Failure" : "Warning"}`,
1087+
checkedAt: row.checkedAt,
1088+
environmentName: row.environmentName,
1089+
kind: row.result === "FAIL" ? "failure" : "warning",
1090+
result: row.result,
1091+
summary: row.summary,
1092+
}));
1093+
return [...recentChecks, ...issueRows];
1094+
}
1095+
10311096
function systemHealthR2Readiness(storageStatus) {
10321097
const credentialsConfigured = storageStatus.accessKeyConfigured === true && storageStatus.secretKeyConfigured === true;
10331098
const configurationReady = storageStatus.configured === true
@@ -3838,6 +3903,13 @@ LIMIT 1;
38383903
const r2Readiness = systemHealthR2Readiness(storageStatus);
38393904
const runtimeEnvironment = systemHealthRuntimeEnvironment();
38403905
const operationsHealth = adminOperationsHealth(this.standaloneTables);
3906+
const healthCheckHistory = systemHealthCheckHistory({
3907+
checkedAt,
3908+
databaseStatus,
3909+
environmentIdentity,
3910+
runtimeEnvironment,
3911+
storageStatus,
3912+
});
38413913
const importBlocked = promotionFoundation.importOverwriteAllowed === false
38423914
&& promotionFoundation.browserExecutionAllowed === false
38433915
&& promotionFoundation.destructiveOperationsAllowed === false;
@@ -3961,6 +4033,7 @@ LIMIT 1;
39614033
message: "Admin System Health loaded safe status only.",
39624034
environmentIdentity,
39634035
environmentMap,
4036+
healthCheckHistory,
39644037
operationsHealth,
39654038
overview,
39664039
pressureLabels: SYSTEM_HEALTH_LIMIT_PRESSURE_LABELS,

tests/dev-runtime/AdminHealthOperations.test.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ test("Admin can view operational health while Creator sessions are blocked", asy
144144
assert.equal(typeof health.databaseStatus.version, "string");
145145
assert.equal(health.storageStatus.environmentFolder, "/local");
146146
assert.equal(typeof health.storageStatus.lastChecked, "string");
147+
assert.equal(Array.isArray(health.healthCheckHistory), true);
148+
assert.deepEqual(
149+
health.healthCheckHistory.slice(0, 4).map((row) => row.area),
150+
["Environment Summary", "Database Health", "Storage Health", "Runtime Health"],
151+
);
152+
assert.equal(health.healthCheckHistory.every((row) => row.environmentName === "Local"), true);
153+
assert.equal(JSON.stringify(health.healthCheckHistory).includes("/dev"), false);
154+
assert.equal(JSON.stringify(health.healthCheckHistory).includes("/uat"), false);
147155
assert.deepEqual(
148156
health.environmentMap.map((row) => row.name),
149157
["Local", "DEV", "IST", "UAT", "PRD"],

tests/playwright/tools/AdminHealthOperationsPage.spec.mjs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ test("Admin System Health renders Postgres diagnostics through the safe status A
164164
await expect(page.locator("[data-admin-system-health-storage-value='upload']")).toContainText("/dev");
165165
await expect(page.locator("[data-admin-system-health-storage-value='read']")).not.toHaveText("Health object");
166166
await expect(page.locator("[data-admin-system-health-storage-value='delete']")).not.toHaveText("Health object");
167+
const historyTable = page.getByRole("table", { name: "Health check history" });
168+
await expect(historyTable).toContainText("DEV");
169+
await expect(historyTable).toContainText("Environment Summary");
170+
await expect(historyTable).toContainText("Database Health");
171+
await expect(historyTable).toContainText("Storage Health");
172+
await expect(historyTable).toContainText("Runtime Health");
173+
await expect(historyTable).not.toContainText("IST");
174+
await expect(historyTable).not.toContainText("UAT");
175+
await expect(historyTable).not.toContainText("PRD");
176+
await expect(historyTable).not.toContainText("/local");
177+
await expect(historyTable).not.toContainText("/ist");
178+
await expect(historyTable).not.toContainText("/uat");
179+
await expect(historyTable).not.toContainText("/prd");
167180
await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("********");
168181
await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("GAMEFOUNDRY_ADMIN_HEALTH_DATABASE_URL");
169182
await expect(page.getByRole("table", { name: "Runtime environment" })).toContainText("GAMEFOUNDRY_ADMIN_HEALTH_PUBLIC_FLAG");
@@ -216,6 +229,7 @@ test("Creator sessions cannot access Admin System Health operations", async ({ p
216229
await expect(page.locator("[data-session-access-blocked='admin']")).toBeVisible();
217230
await expect(page.getByRole("table", { name: "Environment identity" })).toHaveCount(0);
218231
await expect(page.getByRole("table", { name: "Environment map" })).toHaveCount(0);
232+
await expect(page.getByRole("table", { name: "Health check history" })).toHaveCount(0);
219233
expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health/status"))).toBe(false);
220234
expect(context.requestUrls.some((url) => url.includes("/api/admin/system-health/storage-connectivity-action"))).toBe(false);
221235
expect(context.pageErrors).toEqual([]);

0 commit comments

Comments
 (0)