Skip to content

Commit 9701da2

Browse files
authored
Merge pull request #152 from ToolboxAid/pr/26175-CHARLIE-008-system-health-current-database-health
PR_26175_CHARLIE_008-system-health-current-database-health
2 parents 0e25639 + b94cf52 commit 9701da2

8 files changed

Lines changed: 93 additions & 35 deletions

File tree

admin/system-health.html

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,11 @@ <h2 id="admin-system-health-title">System Health Tables</h2>
124124
</tr>
125125
</thead>
126126
<tbody>
127-
<tr><td>Provider</td><td data-admin-system-health-db-value="provider">Postgres</td><td data-health-status="PASS" data-admin-system-health-db-status="provider">PASS</td></tr>
128-
<tr><td>Host</td><td data-admin-system-health-db-value="host">Configured host placeholder</td><td data-health-status="PENDING" data-admin-system-health-db-status="host" title="Reason: host reader is intentionally not wired in this foundation PR." aria-label="PENDING: host reader is intentionally not wired in this foundation PR.">PENDING</td></tr>
129-
<tr><td>Port</td><td data-admin-system-health-db-value="port">5432</td><td data-health-status="PASS" data-admin-system-health-db-status="port">PASS</td></tr>
130-
<tr><td>Database</td><td data-admin-system-health-db-value="database">Configured database placeholder</td><td data-health-status="PENDING" data-admin-system-health-db-status="database" title="Reason: database name reader is intentionally not wired in this foundation PR." aria-label="PENDING: database name reader is intentionally not wired in this foundation PR.">PENDING</td></tr>
131-
<tr><td>Migration Version</td><td data-admin-system-health-db-value="migration">Pending migration reader</td><td data-health-status="PENDING" data-admin-system-health-db-status="migration" title="Reason: migration version reader is intentionally not wired in this foundation PR." aria-label="PENDING: migration version reader is intentionally not wired in this foundation PR.">PENDING</td></tr>
132-
<tr><td>Status</td><td data-admin-system-health-db-value="connection">Connection check pending</td><td data-health-status="PENDING" data-admin-system-health-db-status="connection" title="Reason: live Postgres connection checks are intentionally not wired in this foundation PR." aria-label="PENDING: live Postgres connection checks are intentionally not wired in this foundation PR.">PENDING</td></tr>
127+
<tr><td>Database type</td><td data-admin-system-health-db-value="type">Loading</td><td data-health-status="PENDING" data-admin-system-health-db-status="type" title="Reason: database health has not loaded yet." aria-label="PENDING: database health has not loaded yet.">PENDING</td></tr>
128+
<tr><td>Connectivity</td><td data-admin-system-health-db-value="connectivity">Loading</td><td data-health-status="PENDING" data-admin-system-health-db-status="connectivity" title="Reason: database health has not loaded yet." aria-label="PENDING: database health has not loaded yet.">PENDING</td></tr>
129+
<tr><td>Response time</td><td data-admin-system-health-db-value="responseTime">Loading</td><td data-health-status="PENDING" data-admin-system-health-db-status="responseTime" title="Reason: database health has not loaded yet." aria-label="PENDING: database health has not loaded yet.">PENDING</td></tr>
130+
<tr><td>Version</td><td data-admin-system-health-db-value="version">Loading</td><td data-health-status="PENDING" data-admin-system-health-db-status="version" title="Reason: database health has not loaded yet." aria-label="PENDING: database health has not loaded yet.">PENDING</td></tr>
131+
<tr><td>Last checked</td><td data-admin-system-health-db-value="lastChecked">Loading</td><td data-health-status="PENDING" data-admin-system-health-db-status="lastChecked" title="Reason: database health has not loaded yet." aria-label="PENDING: database health has not loaded yet.">PENDING</td></tr>
133132
</tbody>
134133
</table>
135134
</div>

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

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class AdminSystemHealthController {
101101
["name", "hostingModel", "siteUrl", "apiUrl", "databaseModel", "storageFolder", "lastHealthCheck"].forEach((key) => {
102102
this.setEnvironmentStatus(key, "PENDING", reason);
103103
});
104-
["host", "database", "migration", "connection"].forEach((key) => {
104+
["type", "connectivity", "responseTime", "version", "lastChecked"].forEach((key) => {
105105
this.setStatus(key, "PENDING", reason);
106106
});
107107
this.renderStartupPending(reason);
@@ -134,25 +134,21 @@ class AdminSystemHealthController {
134134
}
135135

136136
renderPostgresStatus(databaseStatus = {}) {
137-
const migrationCounts = databaseStatus.migrationCounts || {};
138-
const lastMigration = databaseStatus.lastMigration || {};
139-
const migrationSummary = lastMigration.name
140-
? `${asText(lastMigration.type, "migration")} ${lastMigration.name}${lastMigration.appliedAt ? ` at ${lastMigration.appliedAt}` : ""}`
141-
: `DDL=${migrationCounts.DDL || 0}; DML=${migrationCounts.DML || 0}; last migration not recorded`;
142-
const connectionReason = databaseStatus.message || "Postgres diagnostic status returned by the safe Admin System Health API.";
137+
const reason = databaseStatus.message || "Current environment database health returned by the safe Admin System Health API.";
138+
const responseTime = Number.isFinite(databaseStatus.responseTimeMs)
139+
? `${databaseStatus.responseTimeMs} ms`
140+
: "not available";
143141

144-
this.setValue("provider", "Postgres");
145-
this.setStatus("provider", "PASS");
146-
this.setValue("host", databaseStatus.host, "not configured");
147-
this.setStatus("host", databaseStatus.hostStatus, connectionReason);
148-
this.setValue("port", databaseStatus.port ? String(databaseStatus.port) : "", "not configured");
149-
this.setStatus("port", databaseStatus.portStatus, connectionReason);
150-
this.setValue("database", databaseStatus.databaseName, "not configured");
151-
this.setStatus("database", databaseStatus.databaseNameStatus, connectionReason);
152-
this.setValue("migration", migrationSummary);
153-
this.setStatus("migration", databaseStatus.lastMigrationStatus || databaseStatus.migrationStatus, connectionReason);
154-
this.setValue("connection", databaseStatus.configured === true ? connectionReason : databaseStatus.message || "Postgres configuration is not complete.");
155-
this.setStatus("connection", databaseStatus.status, connectionReason);
142+
this.setValue("type", databaseStatus.databaseType, "PostgreSQL");
143+
this.setStatus("type", databaseStatus.databaseType ? "PASS" : "WARN", reason);
144+
this.setValue("connectivity", databaseStatus.connectivity, databaseStatus.message || "not configured");
145+
this.setStatus("connectivity", databaseStatus.connectivityStatus || databaseStatus.status, reason);
146+
this.setValue("responseTime", responseTime);
147+
this.setStatus("responseTime", Number.isFinite(databaseStatus.responseTimeMs) ? "PASS" : "WARN", reason);
148+
this.setValue("version", databaseStatus.version, "not available");
149+
this.setStatus("version", databaseStatus.versionStatus, reason);
150+
this.setValue("lastChecked", databaseStatus.lastChecked, "not available");
151+
this.setStatus("lastChecked", databaseStatus.lastChecked ? "PASS" : "WARN", reason);
156152
}
157153

158154
renderStorageStatus(storageStatus = {}) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# PR_26175_CHARLIE_008 System Health Current Database Health
2+
3+
## Scope
4+
5+
Team: Charlie
6+
7+
Purpose: Add current-environment database health only to Admin System Health.
8+
9+
## Changes
10+
11+
- Replaced the Database Health table body with current-environment fields:
12+
- database type
13+
- connectivity
14+
- response time
15+
- version
16+
- last checked
17+
- Added safe server-owned database status fields to the Admin System Health API payload.
18+
- Database type follows the current environment identity:
19+
- Local, DEV, and IST: Local Docker PostgreSQL
20+
- UAT and PRD: Supabase PostgreSQL
21+
- Updated focused API and Playwright tests.
22+
23+
## Architecture Constraint
24+
25+
PASS. Database health reads only the currently configured deployment database. No database checks are made for peer environments.
26+
27+
## Validation
28+
29+
- PASS: `node --check src/dev-runtime/server/local-api-router.mjs`
30+
- PASS: `node --check assets/theme-v2/js/admin-system-health.js`
31+
- PASS: `git diff --check`
32+
- PASS: `node --test tests/dev-runtime/AdminHealthOperations.test.mjs`
33+
- PASS: `npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line`
34+
35+
## Artifact
36+
37+
- `tmp/PR_26175_CHARLIE_008-system-health-current-database-health_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 288/288; executed functions 33/37
10+
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 284/284; executed functions 33/37
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: 2 additions & 2 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 288/288; executed functions 33/37
21+
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 284/284; executed functions 33/37
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 288/288; executed functions 33/37
31+
(89%) assets/theme-v2/js/admin-system-health.js - executed lines 284/284; executed functions 33/37
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

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3653,9 +3653,14 @@ class ApiRuntimeDataSource {
36533653
};
36543654
}
36553655

3656-
async ownerDatabaseStatus() {
3656+
async ownerDatabaseStatus(environmentIdentity = systemHealthEnvironmentIdentity()) {
3657+
const startedAt = Date.now();
36573658
const databaseStatus = {
36583659
...databaseConfigStatus(),
3660+
connectivity: "not configured",
3661+
connectivityStatus: "WARN",
3662+
databaseType: environmentIdentity.databaseModel || "PostgreSQL",
3663+
lastChecked: new Date().toISOString(),
36593664
lastMigration: {
36603665
appliedAt: "",
36613666
name: "",
@@ -3667,10 +3672,14 @@ class ApiRuntimeDataSource {
36673672
DML: 0,
36683673
},
36693674
migrationStatus: "WARN",
3675+
responseTimeMs: null,
36703676
status: "WARN",
3677+
version: "",
3678+
versionStatus: "WARN",
36713679
};
36723680
try {
36733681
const adapter = this.supabaseDatabaseAdapter("Reading Admin System Health migration history");
3682+
const versionRows = await adapter.databaseClient().query("SELECT version() AS version;");
36743683
const countRows = await adapter.databaseClient().query(`
36753684
SELECT "migrationType", count(*)::int AS count
36763685
FROM schema_migrations
@@ -3685,8 +3694,11 @@ LIMIT 1;
36853694
`);
36863695
const counts = new Map(countRows.map((row) => [String(row.migrationType || ""), Number(row.count || 0)]));
36873696
const lastRow = lastRows[0] || {};
3697+
const version = String(versionRows[0]?.version || "").trim();
36883698
return {
36893699
...databaseStatus,
3700+
connectivity: "connected",
3701+
connectivityStatus: "PASS",
36903702
lastMigration: {
36913703
appliedAt: String(lastRow.appliedAt || ""),
36923704
name: String(lastRow.migrationName || ""),
@@ -3698,12 +3710,20 @@ LIMIT 1;
36983710
DML: counts.get("DML") || 0,
36993711
},
37003712
migrationStatus: "PASS",
3713+
message: "Current environment database connection responded through the safe Admin System Health API.",
3714+
responseTimeMs: Date.now() - startedAt,
37013715
status: databaseStatus.configured === true ? "PASS" : "WARN",
3716+
version: version || "not available",
3717+
versionStatus: version ? "PASS" : "WARN",
37023718
};
37033719
} catch (error) {
37043720
return {
37053721
...databaseStatus,
3706-
message: `Migration history read failed: ${error instanceof Error ? error.message : String(error || "Unknown database error.")}`,
3722+
connectivity: "failed",
3723+
connectivityStatus: "FAIL",
3724+
message: `Current environment database health read failed: ${error instanceof Error ? error.message : String(error || "Unknown database error.")}`,
3725+
responseTimeMs: Date.now() - startedAt,
3726+
status: "FAIL",
37073727
};
37083728
}
37093729
}
@@ -3756,7 +3776,7 @@ LIMIT 1;
37563776
const checkedAt = new Date().toISOString();
37573777
const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt);
37583778
const environmentMap = systemHealthEnvironmentMap();
3759-
const databaseStatus = await this.ownerDatabaseStatus();
3779+
const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity);
37603780
const storageStatus = this.ownerStorageStatus();
37613781
const environmentStatus = storageProjectsPrefixStatus();
37623782
const localApiStartup = systemHealthLocalApiStartupDiagnostics();

tests/dev-runtime/AdminHealthOperations.test.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,11 @@ test("Admin can view operational health while Creator sessions are blocked", asy
137137
assert.equal(health.environmentIdentity.storageFolder, "/local");
138138
assert.equal(health.environmentIdentity.siteUrl.includes("site-user"), false);
139139
assert.equal(health.environmentIdentity.apiUrl.includes("api-user"), false);
140+
assert.equal(health.databaseStatus.databaseType, "Local Docker PostgreSQL");
141+
assert.ok(["connected", "failed", "not configured"].includes(health.databaseStatus.connectivity));
142+
assert.equal(typeof health.databaseStatus.lastChecked, "string");
143+
assert.equal(typeof health.databaseStatus.responseTimeMs === "number" || health.databaseStatus.responseTimeMs === null, true);
144+
assert.equal(typeof health.databaseStatus.version, "string");
140145
assert.deepEqual(
141146
health.environmentMap.map((row) => row.name),
142147
["Local", "DEV", "IST", "UAT", "PRD"],

tests/playwright/tools/AdminHealthOperationsPage.spec.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,11 @@ test("Admin System Health renders Postgres diagnostics through the safe status A
151151
await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).toContainText("deferred/cancelled");
152152
await expect(page.getByRole("table", { name: "Local API startup diagnostics" })).not.toContainText("secret");
153153
await expect(page.getByRole("table", { name: "Database health" })).toContainText("Postgres");
154-
await expect(page.locator("[data-admin-system-health-db-value='provider']")).toHaveText("Postgres");
155-
await expect(page.locator("[data-admin-system-health-db-value='host']")).not.toHaveText("Configured host placeholder");
156-
await expect(page.locator("[data-admin-system-health-db-value='database']")).not.toHaveText("Configured database placeholder");
157-
await expect(page.locator("[data-admin-system-health-db-value='connection']")).not.toHaveText("Connection check pending");
154+
await expect(page.locator("[data-admin-system-health-db-value='type']")).toHaveText("Local Docker PostgreSQL");
155+
await expect(page.locator("[data-admin-system-health-db-value='connectivity']")).not.toHaveText("Loading");
156+
await expect(page.locator("[data-admin-system-health-db-value='responseTime']")).not.toHaveText("Loading");
157+
await expect(page.locator("[data-admin-system-health-db-value='version']")).not.toHaveText("Loading");
158+
await expect(page.locator("[data-admin-system-health-db-value='lastChecked']")).not.toHaveText("Loading");
158159
await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgres://");
159160
await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgresql://");
160161
await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2");

0 commit comments

Comments
 (0)