From 9a6779f6e81108055ee10cce3424001149e79853 Mon Sep 17 00:00:00 2001 From: Charlie Team <97194984+ToolboxAid@users.noreply.github.com> Date: Thu, 25 Jun 2026 11:08:03 -0400 Subject: [PATCH 1/2] Add System Health Postgres metrics panel --- admin/system-health.html | 16 +++ assets/theme-v2/js/admin-system-health.js | 42 +++++++ ...29-system-health-postgres-metrics-panel.md | 32 +++++ ...ostgres-metrics-panel_branch-validation.md | 21 ++++ ...s-metrics-panel_manual-validation-notes.md | 7 ++ ...es-metrics-panel_requirements-checklist.md | 14 +++ ...-postgres-metrics-panel_validation-lane.md | 21 ++++ .../dev/reports/codex_changed_files.txt | 59 +++++---- docs_build/dev/reports/codex_review.diff | Bin 212382 -> 30572 bytes .../reports/coverage_changed_js_guardrail.txt | 5 +- .../reports/playwright_v8_coverage_report.txt | 29 +++-- src/dev-runtime/server/local-api-router.mjs | 115 ++++++++++++++++-- .../api/admin-system-health/contract.test.mjs | 15 +++ .../AdminHealthOperations.test.mjs | 20 +++ .../tools/AdminHealthOperationsPage.spec.mjs | 11 ++ 15 files changed, 360 insertions(+), 47 deletions(-) create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md create mode 100644 docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md diff --git a/admin/system-health.html b/admin/system-health.html index abe5c4f97..5aa9fa80a 100644 --- a/admin/system-health.html +++ b/admin/system-health.html @@ -51,6 +51,7 @@

Admin

Notifications & Alerts

Local API Startup

Database Health

+

Postgres Metrics

Storage Health

Runtime Health

Health Check History

@@ -293,6 +294,21 @@

Manual Health Actions

+
+ + + + + + + + + + + + +
Postgres Metrics
MetricSafe ValueStatus
Postgres metricsUnavailable until safe API status loadsPENDING
+
diff --git a/assets/theme-v2/js/admin-system-health.js b/assets/theme-v2/js/admin-system-health.js index 656394104..746d47753 100644 --- a/assets/theme-v2/js/admin-system-health.js +++ b/assets/theme-v2/js/admin-system-health.js @@ -74,6 +74,7 @@ class AdminSystemHealthController { this.notificationRows = root.querySelector("[data-admin-system-health-notification-rows]"); this.serviceCards = root.querySelector("[data-admin-system-health-service-cards]"); this.startupRows = root.querySelector("[data-admin-system-health-startup-rows]"); + this.postgresMetricRows = root.querySelector("[data-admin-system-health-postgres-metric-rows]"); this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]"); } @@ -145,6 +146,7 @@ class AdminSystemHealthController { this.setStatus(key, "PENDING", reason); }); this.renderStartupPending(reason); + this.renderPostgresMetricsPending(reason); this.renderStoragePending(reason); this.renderRuntimeHealthPending(reason); this.renderEnvironmentCapabilitiesPending(reason); @@ -213,6 +215,46 @@ class AdminSystemHealthController { this.setStatus("version", databaseStatus.versionStatus, reason); this.setValue("lastChecked", databaseStatus.lastChecked, "not available"); this.setStatus("lastChecked", databaseStatus.lastChecked ? "PASS" : "WARN", reason); + this.renderPostgresMetrics(databaseStatus.postgresMetrics || {}); + } + + renderPostgresMetricsPending(reason) { + if (!this.postgresMetricRows) { + return; + } + const row = document.createElement("tr"); + row.append( + this.createCell("Postgres metrics"), + this.createCell("Unavailable"), + this.createStatusCell("PENDING", reason), + ); + this.postgresMetricRows.replaceChildren(row); + } + + renderPostgresMetrics(postgresMetrics = {}) { + if (!this.postgresMetricRows) { + return; + } + if (postgresMetrics?.secretsExposed === true || postgresMetrics?.secretEditingAllowed === true) { + this.renderPostgresMetricsPending("Safe Postgres metrics response was blocked because it exposed secret controls."); + return; + } + const rows = Array.isArray(postgresMetrics.rows) ? postgresMetrics.rows : []; + if (!rows.length) { + this.renderPostgresMetricsPending("Safe Admin System Health API returned no Postgres metric rows."); + return; + } + const fragment = document.createDocumentFragment(); + rows.forEach((metricRow) => { + const row = document.createElement("tr"); + row.append( + this.createCell(metricRow.metric), + this.createCell(metricRow.value), + this.createStatusCell(metricRow.status, postgresMetrics.message), + ); + fragment.append(row); + }); + this.postgresMetricRows.replaceChildren(fragment); } renderStorageStatus(storageStatus = {}) { diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md new file mode 100644 index 000000000..e0e6f995a --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md @@ -0,0 +1,32 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel + +Team: Charlie +Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel +Base: main +Lifecycle: Build / Validation + +## Scope +- Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. +- Added safe current-environment metrics for connection status, database name, current schema, migration status, last migration, table count, database size, and last checked. +- Preserved current-environment-only behavior and did not add cross-environment database checks. + +## Changed Files +- admin/system-health.html +- assets/theme-v2/js/admin-system-health.js +- src/dev-runtime/server/local-api-router.mjs +- tests/api/admin-system-health/contract.test.mjs +- tests/dev-runtime/AdminHealthOperations.test.mjs +- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +- docs_build/dev/reports/coverage_changed_js_guardrail.txt +- docs_build/dev/reports/playwright_v8_coverage_report.txt + +## Validation +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check + +## ZIP +- Pending until commit: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md new file mode 100644 index 000000000..b5a80d96a --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md @@ -0,0 +1,21 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation + +Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel +Expected start branch: main, then PR branch for build +Current status at validation: +## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel + M admin/system-health.html + M assets/theme-v2/js/admin-system-health.js + M docs_build/dev/reports/coverage_changed_js_guardrail.txt + M docs_build/dev/reports/playwright_v8_coverage_report.txt + M src/dev-runtime/server/local-api-router.mjs + M tests/api/admin-system-health/contract.test.mjs + M tests/dev-runtime/AdminHealthOperations.test.mjs + M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs + +Result: PASS + +Checks: +- PASS: Started from synchronized main. +- PASS: Active branch matches PR identity. +- PASS: Worktree contained only scoped PR changes and generated validation reports. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md new file mode 100644 index 000000000..b37c12319 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md @@ -0,0 +1,7 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes + +- Confirmed Postgres Metrics appears as a separate System Health table. +- Confirmed metric values come from the server-owned Admin System Health API response. +- Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. +- Confirmed page retains external scripts/styles only; no inline style/script/handler additions. +- Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md new file mode 100644 index 000000000..201ce7ec5 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md @@ -0,0 +1,14 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Requirement Checklist + +- PASS: Add/extend System Health Postgres metrics panel. +- PASS: Include connection status. +- PASS: Include database name. +- PASS: Include current schema/migration status when available. +- PASS: Include table count when available. +- PASS: Include database size when available. +- PASS: Do not add expensive queries. +- PASS: Show explicit Unavailable status when a metric is unavailable. +- PASS: Do not expose secrets. +- PASS: Keep browser UI consuming API/service contract. +- PASS: No cross-environment checks added. +- PASS: No start_of_day files modified. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md new file mode 100644 index 000000000..45c110616 --- /dev/null +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md @@ -0,0 +1,21 @@ +# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Validation Lane Report + +Impacted lanes: +- runtime: Local API Admin System Health status contract. +- contract: Admin System Health API contract tests. +- UI: Admin System Health Theme V2 page controller and markup. +- Playwright: targeted Admin System Health page spec. + +Commands: +- node --check src/dev-runtime/server/local-api-router.mjs +- node --check assets/theme-v2/js/admin-system-health.js +- node --test tests/api/admin-system-health/contract.test.mjs +- node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- git diff --check + +Skipped lanes: +- Full samples smoke skipped; not impacted by System Health API/UI panel change. +- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page. + +Result: PASS diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 4cb4ab3d9..667425b1f 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,27 +1,32 @@ -assets/theme-v2/js/gamefoundry-partials.js -assets/theme-v2/js/legal-document-page.js -assets/theme-v2/partials/footer.html -docs_build/dev/reports/PR_26175_OWNER_054-legal-corrected-package.md -docs_build/dev/reports/codex_changed_files.txt -docs_build/dev/reports/codex_review.diff -docs_build/dev/reports/coverage_changed_js_guardrail.txt -docs_build/dev/reports/playwright_v8_coverage_report.txt -legal/community-guidelines.html -legal/community-guidelines.md -legal/cookie-policy.html -legal/cookie-policy.md -legal/cookies-policy.html -legal/copyright-policy.html -legal/copyright-policy.md -legal/disclaimer.html -legal/dmca-policy.html -legal/dmca-policy.md -legal/index.html -legal/index.md -legal/legal-nav.js -legal/privacy-policy.html -legal/privacy-policy.md -legal/terms.html -legal/terms-of-service.html -legal/terms-of-service.md -tests/playwright/tools/RemainingLegalPages.spec.mjs \ No newline at end of file +# git status --short +M admin/system-health.html + M assets/theme-v2/js/admin-system-health.js + M docs_build/dev/reports/coverage_changed_js_guardrail.txt + M docs_build/dev/reports/playwright_v8_coverage_report.txt + M src/dev-runtime/server/local-api-router.mjs + M tests/api/admin-system-health/contract.test.mjs + M tests/dev-runtime/AdminHealthOperations.test.mjs + M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md + +# git ls-files --others --exclude-standard +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md + +# git diff --stat +admin/system-health.html | 16 +++ + assets/theme-v2/js/admin-system-health.js | 42 ++++++++ + .../dev/reports/coverage_changed_js_guardrail.txt | 5 +- + .../dev/reports/playwright_v8_coverage_report.txt | 29 ++++-- + src/dev-runtime/server/local-api-router.mjs | 115 +++++++++++++++++++-- + tests/api/admin-system-health/contract.test.mjs | 15 +++ + tests/dev-runtime/AdminHealthOperations.test.mjs | 20 ++++ + .../tools/AdminHealthOperationsPage.spec.mjs | 11 ++ + 8 files changed, 233 insertions(+), 20 deletions(-) \ No newline at end of file diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index fdfeac55a30526c21627c2184b6692766d4426d7..3c0cea78d826012d7e91dc80f7b235c32a172292 100644 GIT binary patch literal 30572 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 literal 212382 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 [String(row.migrationType || ""), Number(row.count || 0)])); + const currentRow = currentRows[0] || {}; const lastRow = lastRows[0] || {}; - const version = String(versionRows[0]?.version || "").trim(); - return { + const tableCount = Number(tableCountRows[0]?.table_count); + const databaseSizeBytes = Number(databaseSizeRows[0]?.database_size_bytes); + const connectedStatus = { ...databaseStatus, connectivity: "connected", connectivityStatus: "PASS", + currentDatabaseName: String(currentRow.database_name || ""), + currentDatabaseNameStatus: currentRow.database_name ? "PASS" : "WARN", + currentSchema: String(currentRow.schema_name || ""), + currentSchemaStatus: currentRow.schema_name ? "PASS" : "WARN", + databaseSize: String(databaseSizeRows[0]?.database_size || ""), + databaseSizeBytes: Number.isFinite(databaseSizeBytes) ? databaseSizeBytes : null, + databaseSizeStatus: databaseSizeRows[0]?.database_size ? "PASS" : "WARN", lastMigration: { appliedAt: String(lastRow.appliedAt || ""), name: String(lastRow.migrationName || ""), @@ -4268,14 +4354,19 @@ LIMIT 1; DML: counts.get("DML") || 0, }, migrationStatus: "PASS", - message: "Current environment database connection responded through the safe Admin System Health API.", + message: "Current environment database connection and safe Postgres metrics responded through the Admin System Health API.", responseTimeMs: Date.now() - startedAt, status: databaseStatus.configured === true ? "PASS" : "WARN", - version: version || "not available", - versionStatus: version ? "PASS" : "WARN", + tableCount: Number.isFinite(tableCount) ? tableCount : null, + version: String(versionRows[0]?.version || "").trim() || "not available", + versionStatus: versionRows[0]?.version ? "PASS" : "WARN", }; - } catch (error) { return { + ...connectedStatus, + postgresMetrics: systemHealthPostgresMetrics(connectedStatus, connectedStatus.lastChecked), + }; + } catch (error) { + const failedStatus = { ...databaseStatus, connectivity: "failed", connectivityStatus: "FAIL", @@ -4283,6 +4374,10 @@ LIMIT 1; responseTimeMs: Date.now() - startedAt, status: "FAIL", }; + return { + ...failedStatus, + postgresMetrics: systemHealthPostgresMetrics(failedStatus, failedStatus.lastChecked), + }; } } @@ -4410,6 +4505,7 @@ LIMIT 1; const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); const environmentMap = systemHealthEnvironmentMap(); const databaseStatus = await this.ownerDatabaseStatus(environmentIdentity); + const postgresMetrics = databaseStatus.postgresMetrics || systemHealthPostgresMetrics(databaseStatus, checkedAt); const storageStatus = this.ownerStorageStatus(); const environmentStatus = storageProjectsPrefixStatus(); const localApiStartup = systemHealthLocalApiStartupDiagnostics(); @@ -4581,6 +4677,7 @@ LIMIT 1; notificationsFoundation, operationsHealth, overview, + postgresMetrics, pressureLabels: SYSTEM_HEALTH_LIMIT_PRESSURE_LABELS, connectionSummary: this.ownerConnectionSummary(), databaseStatus, diff --git a/tests/api/admin-system-health/contract.test.mjs b/tests/api/admin-system-health/contract.test.mjs index e9e366882..25aa84b34 100644 --- a/tests/api/admin-system-health/contract.test.mjs +++ b/tests/api/admin-system-health/contract.test.mjs @@ -111,6 +111,7 @@ test("Admin System Health completion contract remains server-owned and current-e "runtimeFeatureFlags", "serviceHealth", "configurationSummary", + "postgresMetrics", "scheduledMonitoring", "notificationsFoundation", ].filter((key) => Object.hasOwn(health, key)), @@ -121,10 +122,24 @@ test("Admin System Health completion contract remains server-owned and current-e "runtimeFeatureFlags", "serviceHealth", "configurationSummary", + "postgresMetrics", "scheduledMonitoring", "notificationsFoundation", ], ); + assert.deepEqual( + health.postgresMetrics.rows.map((row) => row.metric), + [ + "Connection status", + "Database name", + "Current schema", + "Migration status", + "Last migration", + "Table count", + "Database size", + "Last checked", + ], + ); const healthText = JSON.stringify(health); assert.equal(healthText.includes("api-secret"), false); assert.equal(healthText.includes("site-secret"), false); diff --git a/tests/dev-runtime/AdminHealthOperations.test.mjs b/tests/dev-runtime/AdminHealthOperations.test.mjs index e682005b2..99fef0757 100644 --- a/tests/dev-runtime/AdminHealthOperations.test.mjs +++ b/tests/dev-runtime/AdminHealthOperations.test.mjs @@ -142,6 +142,24 @@ test("Admin can view operational health while Creator sessions are blocked", asy assert.equal(typeof health.databaseStatus.lastChecked, "string"); assert.equal(typeof health.databaseStatus.responseTimeMs === "number" || health.databaseStatus.responseTimeMs === null, true); assert.equal(typeof health.databaseStatus.version, "string"); + assert.equal(health.postgresMetrics.secretEditingAllowed, false); + assert.equal(health.postgresMetrics.secretsExposed, false); + assert.deepEqual( + health.postgresMetrics.rows.map((row) => row.metric), + [ + "Connection status", + "Database name", + "Current schema", + "Migration status", + "Last migration", + "Table count", + "Database size", + "Last checked", + ], + ); + assert.equal(health.postgresMetrics.rows.every((row) => typeof row.value === "string"), true); + assert.equal(health.postgresMetrics.rows.some((row) => row.value === "Unavailable"), true); + assert.equal(health.databaseStatus.postgresMetrics.rows.length, health.postgresMetrics.rows.length); assert.equal(health.runtimeHealth.environmentName, "Local"); assert.equal(health.runtimeHealth.appVersion, "1.0.0"); assert.equal(health.runtimeHealth.apiVersion, "1.0.0"); @@ -318,6 +336,8 @@ test("Admin can view operational health while Creator sessions are blocked", asy const healthText = JSON.stringify(health.operationsHealth); assert.equal(healthText.includes("monthlyPriceCents"), false); assert.equal(healthText.includes("priceCents"), false); + assert.equal(JSON.stringify(health.postgresMetrics).includes("postgres://"), false); + assert.equal(JSON.stringify(health.postgresMetrics).includes("postgresql://"), false); assert.equal(health.secretEditingAllowed, false); } finally { await server.close(); diff --git a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs index 52d7acf50..56733ce6a 100644 --- a/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +++ b/tests/playwright/tools/AdminHealthOperationsPage.spec.mjs @@ -217,6 +217,16 @@ test("Admin System Health renders Postgres diagnostics through the safe status A await expect(page.locator("[data-admin-system-health-db-value='lastChecked']")).not.toHaveText("Loading"); await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgres://"); await expect(page.getByRole("table", { name: "Database health" })).not.toContainText("postgresql://"); + const postgresMetricsTable = page.getByRole("table", { name: "Postgres metrics" }); + await expect(postgresMetricsTable).toContainText("Connection status"); + await expect(postgresMetricsTable).toContainText("Database name"); + await expect(postgresMetricsTable).toContainText("Current schema"); + await expect(postgresMetricsTable).toContainText("Migration status"); + await expect(postgresMetricsTable).toContainText("Last migration"); + await expect(postgresMetricsTable).toContainText("Table count"); + await expect(postgresMetricsTable).toContainText("Database size"); + await expect(postgresMetricsTable).not.toContainText("postgres://"); + await expect(postgresMetricsTable).not.toContainText("postgresql://"); await expect(page.getByRole("table", { name: "Storage health" })).toContainText("Cloudflare R2"); await expect(page.locator("[data-admin-system-health-storage-value='bucket']")).toContainText("/dev"); await expect(page.locator("[data-admin-system-health-storage-value='list']")).toContainText("/dev"); @@ -333,6 +343,7 @@ test("Admin System Health operations page keeps scripts and styles external", as expect(pageSource).toContain("Runtime Health"); expect(pageSource).toContain("Diagnostics Plan"); expect(pageSource).toContain("Local API Startup Diagnostics"); + expect(pageSource).toContain("Postgres Metrics"); expect(pageSource).toContain("Server-owned Postgres health reader"); expect(pageSource).toContain("Server-owned Cloudflare R2 storage diagnostic"); expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); From da879b97952e9a7eef19f571886bb0b29b6ce67c Mon Sep 17 00:00:00 2001 From: Charlie Team <97194984+ToolboxAid@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:34:43 -0400 Subject: [PATCH 2/2] Repair PR 177 Postgres metrics branch --- ...29-system-health-postgres-metrics-panel.md | 9 +- ...ostgres-metrics-panel_branch-validation.md | 14 +- ...s-metrics-panel_manual-validation-notes.md | 2 + ...es-metrics-panel_requirements-checklist.md | 2 + ...-postgres-metrics-panel_validation-lane.md | 12 +- .../dev/reports/codex_changed_files.txt | 64 ++-- docs_build/dev/reports/codex_review.diff | 335 +++++++++--------- .../reports/coverage_changed_js_guardrail.txt | 5 +- .../reports/playwright_v8_coverage_report.txt | 13 +- 9 files changed, 235 insertions(+), 221 deletions(-) diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md index e0e6f995a..7ff7fe32a 100644 --- a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md @@ -4,6 +4,7 @@ Team: Charlie Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel Base: main Lifecycle: Build / Validation +Repair: Updated from origin/main on 2026-06-25 after PR #177 reported draft=true and mergeable=false. ## Scope - Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. @@ -28,5 +29,11 @@ Lifecycle: Build / Validation - PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line - PASS: git diff --check +## Repair Notes +- PASS: Merged current origin/main into the PR branch. +- PASS: Merge conflict was limited to generated report artifacts: codex_changed_files.txt and codex_review.diff. +- PASS: No runtime, UI, API, database, or product-data conflict required a product decision. +- PASS: Scope remains Postgres metrics panel only. + ## ZIP -- Pending until commit: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip +- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md index b5a80d96a..9364d3f7c 100644 --- a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md @@ -4,18 +4,14 @@ Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel Expected start branch: main, then PR branch for build Current status at validation: ## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel - M admin/system-health.html - M assets/theme-v2/js/admin-system-health.js - M docs_build/dev/reports/coverage_changed_js_guardrail.txt - M docs_build/dev/reports/playwright_v8_coverage_report.txt - M src/dev-runtime/server/local-api-router.mjs - M tests/api/admin-system-health/contract.test.mjs - M tests/dev-runtime/AdminHealthOperations.test.mjs - M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs +Updated from origin/main for PR #177 repair. Result: PASS Checks: - PASS: Started from synchronized main. - PASS: Active branch matches PR identity. -- PASS: Worktree contained only scoped PR changes and generated validation reports. +- PASS: Current origin/main merged into branch. +- PASS: Merge conflict scope was generated report artifacts only. +- PASS: Worktree contains only scoped PR repair/report changes after report refresh. +- PASS: No start_of_day files modified. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md index b37c12319..1c30d0c93 100644 --- a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md @@ -5,3 +5,5 @@ - Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. - Confirmed page retains external scripts/styles only; no inline style/script/handler additions. - Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. +- Confirmed current main merge conflict was limited to generated report artifacts. +- Confirmed no start_of_day files changed. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md index 201ce7ec5..f99698e88 100644 --- a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md @@ -12,3 +12,5 @@ - PASS: Keep browser UI consuming API/service contract. - PASS: No cross-environment checks added. - PASS: No start_of_day files modified. +- PASS: Repaired PR #177 branch from current main. +- PASS: Mark ready for review after validation/reporting is complete. diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md index 45c110616..e1ca3d673 100644 --- a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md @@ -7,12 +7,12 @@ Impacted lanes: - Playwright: targeted Admin System Health page spec. Commands: -- node --check src/dev-runtime/server/local-api-router.mjs -- node --check assets/theme-v2/js/admin-system-health.js -- node --test tests/api/admin-system-health/contract.test.mjs -- node --test tests/dev-runtime/AdminHealthOperations.test.mjs -- npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line -- git diff --check +- PASS: node --check src/dev-runtime/server/local-api-router.mjs +- PASS: node --check assets/theme-v2/js/admin-system-health.js +- PASS: node --test tests/api/admin-system-health/contract.test.mjs +- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs +- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line +- PASS: git diff --check Skipped lanes: - Full samples smoke skipped; not impacted by System Health API/UI panel change. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 667425b1f..79c9ad1c4 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,32 +1,44 @@ -# git status --short -M admin/system-health.html - M assets/theme-v2/js/admin-system-health.js - M docs_build/dev/reports/coverage_changed_js_guardrail.txt - M docs_build/dev/reports/playwright_v8_coverage_report.txt - M src/dev-runtime/server/local-api-router.mjs - M tests/api/admin-system-health/contract.test.mjs - M tests/dev-runtime/AdminHealthOperations.test.mjs - M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md -?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md -?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md -?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md -?? docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md - -# git ls-files --others --exclude-standard +# git diff --name-only origin/main -- +admin/system-health.html +assets/theme-v2/js/admin-system-health.js docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +docs_build/dev/reports/codex_changed_files.txt +docs_build/dev/reports/codex_review.diff +docs_build/dev/reports/coverage_changed_js_guardrail.txt +docs_build/dev/reports/playwright_v8_coverage_report.txt +src/dev-runtime/server/local-api-router.mjs +tests/api/admin-system-health/contract.test.mjs +tests/dev-runtime/AdminHealthOperations.test.mjs +tests/playwright/tools/AdminHealthOperationsPage.spec.mjs + +# git status --short + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md + M docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md + M docs_build/dev/reports/codex_review.diff + M docs_build/dev/reports/coverage_changed_js_guardrail.txt + M docs_build/dev/reports/playwright_v8_coverage_report.txt -# git diff --stat -admin/system-health.html | 16 +++ - assets/theme-v2/js/admin-system-health.js | 42 ++++++++ - .../dev/reports/coverage_changed_js_guardrail.txt | 5 +- - .../dev/reports/playwright_v8_coverage_report.txt | 29 ++++-- - src/dev-runtime/server/local-api-router.mjs | 115 +++++++++++++++++++-- - tests/api/admin-system-health/contract.test.mjs | 15 +++ - tests/dev-runtime/AdminHealthOperations.test.mjs | 20 ++++ - .../tools/AdminHealthOperationsPage.spec.mjs | 11 ++ - 8 files changed, 233 insertions(+), 20 deletions(-) \ No newline at end of file +# git diff --stat origin/main -- + admin/system-health.html | 16 + + assets/theme-v2/js/admin-system-health.js | 42 ++ + ...LIE_029-system-health-postgres-metrics-panel.md | 39 + + ...lth-postgres-metrics-panel_branch-validation.md | 17 + + ...stgres-metrics-panel_manual-validation-notes.md | 9 + + ...ostgres-metrics-panel_requirements-checklist.md | 16 + + ...ealth-postgres-metrics-panel_validation-lane.md | 21 + + docs_build/dev/reports/codex_changed_files.txt | 42 +- + docs_build/dev/reports/codex_review.diff | 794 +++++++++++++++------ + .../dev/reports/coverage_changed_js_guardrail.txt | 4 +- + .../dev/reports/playwright_v8_coverage_report.txt | 24 +- + src/dev-runtime/server/local-api-router.mjs | 115 ++- + tests/api/admin-system-health/contract.test.mjs | 15 + + tests/dev-runtime/AdminHealthOperations.test.mjs | 20 + + .../tools/AdminHealthOperationsPage.spec.mjs | 11 + + 15 files changed, 924 insertions(+), 261 deletions(-) diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 3c0cea78d..1f8c7b688 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -43,7 +43,7 @@ index 656394104..746d47753 100644 + this.postgresMetricRows = root.querySelector("[data-admin-system-health-postgres-metric-rows]"); this.runtimeRows = root.querySelector("[data-admin-system-health-runtime-rows]"); } - + @@ -145,6 +146,7 @@ class AdminSystemHealthController { this.setStatus(key, "PENDING", reason); }); @@ -97,45 +97,175 @@ index 656394104..746d47753 100644 + }); + this.postgresMetricRows.replaceChildren(fragment); } - + renderStorageStatus(storageStatus = {}) { -diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -index 4cf3859da..be6f01bab 100644 ---- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt -+++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt -@@ -6,7 +6,8 @@ Missing changed runtime JS files are WARN, not FAIL. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +new file mode 100644 +index 000000000..7ff7fe32a +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md +@@ -0,0 +1,39 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel ++ ++Team: Charlie ++Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Base: main ++Lifecycle: Build / Validation ++Repair: Updated from origin/main on 2026-06-25 after PR #177 reported draft=true and mergeable=false. ++ ++## Scope ++- Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. ++- Added safe current-environment metrics for connection status, database name, current schema, migration status, last migration, table count, database size, and last checked. ++- Preserved current-environment-only behavior and did not add cross-environment database checks. ++ ++## Changed Files ++- admin/system-health.html ++- assets/theme-v2/js/admin-system-health.js ++- src/dev-runtime/server/local-api-router.mjs ++- tests/api/admin-system-health/contract.test.mjs ++- tests/dev-runtime/AdminHealthOperations.test.mjs ++- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs ++- docs_build/dev/reports/coverage_changed_js_guardrail.tx ++- docs_build/dev/reports/playwright_v8_coverage_report.tx ++ ++## Validation ++- PASS: node --check src/dev-runtime/server/local-api-router.mjs ++- PASS: node --check assets/theme-v2/js/admin-system-health.js ++- PASS: node --test tests/api/admin-system-health/contract.test.mjs ++- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs ++- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line ++- PASS: git diff --check ++ ++## Repair Notes ++- PASS: Merged current origin/main into the PR branch. ++- PASS: Merge conflict was limited to generated report artifacts: codex_changed_files.txt and codex_review.diff. ++- PASS: No runtime, UI, API, database, or product-data conflict required a product decision. ++- PASS: Scope remains Postgres metrics panel only. ++ ++## ZIP ++- Generated after repair: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +new file mode 100644 +index 000000000..9364d3f7c +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md +@@ -0,0 +1,17 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation ++ ++Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Expected start branch: main, then PR branch for build ++Current status at validation: ++## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel ++Updated from origin/main for PR #177 repair. ++ ++Result: PASS ++ ++Checks: ++- PASS: Started from synchronized main. ++- PASS: Active branch matches PR identity. ++- PASS: Current origin/main merged into branch. ++- PASS: Merge conflict scope was generated report artifacts only. ++- PASS: Worktree contains only scoped PR repair/report changes after report refresh. ++- PASS: No start_of_day files modified. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +new file mode 100644 +index 000000000..1c30d0c93 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md +@@ -0,0 +1,9 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes ++ ++- Confirmed Postgres Metrics appears as a separate System Health table. ++- Confirmed metric values come from the server-owned Admin System Health API response. ++- Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. ++- Confirmed page retains external scripts/styles only; no inline style/script/handler additions. ++- Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. ++- Confirmed current main merge conflict was limited to generated report artifacts. ++- Confirmed no start_of_day files changed. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +new file mode 100644 +index 000000000..f99698e88 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md +@@ -0,0 +1,16 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Requirement Checklis ++ ++- PASS: Add/extend System Health Postgres metrics panel. ++- PASS: Include connection status. ++- PASS: Include database name. ++- PASS: Include current schema/migration status when available. ++- PASS: Include table count when available. ++- PASS: Include database size when available. ++- PASS: Do not add expensive queries. ++- PASS: Show explicit Unavailable status when a metric is unavailable. ++- PASS: Do not expose secrets. ++- PASS: Keep browser UI consuming API/service contract. ++- PASS: No cross-environment checks added. ++- PASS: No start_of_day files modified. ++- PASS: Repaired PR #177 branch from current main. ++- PASS: Mark ready for review after validation/reporting is complete. +diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +new file mode 100644 +index 000000000..e1ca3d673 +--- /dev/null ++++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md +@@ -0,0 +1,21 @@ ++# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Validation Lane Repor ++ ++Impacted lanes: ++- runtime: Local API Admin System Health status contract. ++- contract: Admin System Health API contract tests. ++- UI: Admin System Health Theme V2 page controller and markup. ++- Playwright: targeted Admin System Health page spec. ++ ++Commands: ++- PASS: node --check src/dev-runtime/server/local-api-router.mjs ++- PASS: node --check assets/theme-v2/js/admin-system-health.js ++- PASS: node --test tests/api/admin-system-health/contract.test.mjs ++- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs ++- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line ++- PASS: git diff --check ++ ++Skipped lanes: ++- Full samples smoke skipped; not impacted by System Health API/UI panel change. ++- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page. ++ ++Result: PASS +diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.tx +index 4cf3859da..7b1c51f19 100644 +--- a/docs_build/dev/reports/coverage_changed_js_guardrail.tx ++++ b/docs_build/dev/reports/coverage_changed_js_guardrail.tx +@@ -6,7 +6,7 @@ Missing changed runtime JS files are WARN, not FAIL. Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. - + Changed runtime JS files considered: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -+(81%) assets/theme-v2/js/admin-system-health.js - executed lines 848/848; executed functions 70/86 - ++(100%) none changed - no changed runtime JS files + Guardrail warnings: -(100%) none - no changed runtime JS coverage warnings -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only -diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt -index 8f1bdb531..e004c18db 100644 ---- a/docs_build/dev/reports/playwright_v8_coverage_report.txt -+++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt -@@ -12,22 +12,33 @@ Note: entry percentages use function coverage when available, otherwise line cov ++(100%) none changed - no changed runtime JS files +diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.tx +index 8f1bdb531..627ed631f 100644 +--- a/docs_build/dev/reports/playwright_v8_coverage_report.tx ++++ b/docs_build/dev/reports/playwright_v8_coverage_report.tx +@@ -12,22 +12,28 @@ Note: entry percentages use function coverage when available, otherwise line cov Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran. - + Exercised tool entry points detected: -(0%) Toolbox Index - not exercised by this Playwright run +(46%) Toolbox Index - exercised 1 runtime JS files (0%) Tool Template V2 - not exercised by this Playwright run -(64%) Theme V2 Shared JS - exercised 2 runtime JS files +(78%) Theme V2 Shared JS - exercised 5 runtime JS files - + Changed runtime JS files covered: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -+(81%) assets/theme-v2/js/admin-system-health.js - executed lines 848/848; executed functions 70/86 - ++(100%) none changed - no changed runtime JS files + Files with executed line/function counts where available: -(63%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 62/98 -+(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 ++(36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 +(46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 +(65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 +(75%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 76/102 @@ -147,20 +277,16 @@ index 8f1bdb531..e004c18db 100644 +(83%) assets/js/shared/status.js - executed lines 37/37; executed functions 5/6 +(91%) assets/theme-v2/js/admin-owner-navigation.js - executed lines 58/58; executed functions 10/11 +(100%) src/api/admin-system-health-api-client.js - executed lines 28/28; executed functions 4/4 - + Uncovered or low-coverage changed JS files: -(100%) none - no low-coverage changed runtime JS files -+(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only - ++(100%) none changed - no changed runtime JS files + Changed JS files considered: -(0%) tests/playwright/tools/RemainingLegalPages.spec.mjs - changed JS file not collected as browser runtime coverage -(63%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage -(100%) legal/legal-nav.js - changed JS file with browser V8 coverage -+(0%) src/dev-runtime/server/local-api-router.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage -+(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -+(81%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage ++(100%) none - no changed JS files diff --git a/src/dev-runtime/server/local-api-router.mjs b/src/dev-runtime/server/local-api-router.mjs index 993e14a59..462d8b7a0 100644 --- a/src/dev-runtime/server/local-api-router.mjs @@ -168,7 +294,7 @@ index 993e14a59..462d8b7a0 100644 @@ -650,6 +650,63 @@ function databaseConfigStatus(env = process.env) { } } - + +function systemHealthPostgresMetrics(databaseStatus = {}, checkedAt = new Date().toISOString()) { + const reason = databaseStatus.message || "Postgres metrics are reported only when the current environment database reader returns safe values."; + const tableCount = Number(databaseStatus.tableCount); @@ -192,7 +318,7 @@ index 993e14a59..462d8b7a0 100644 + metric: "Migration status", + status: databaseStatus.migrationStatus || "WARN", + value: databaseStatus.migrationStatus === "PASS" -+ ? `DDL=${databaseStatus.migrationCounts?.DDL || 0}; DML=${databaseStatus.migrationCounts?.DML || 0}` ++ ? `DDL=${databaseStatus.migrationCounts?.DDL || 0}; DML=${databaseStatus.migrationCounts?.DML || 0} + : "Unavailable", + }, + { @@ -247,30 +373,30 @@ index 993e14a59..462d8b7a0 100644 try { const adapter = this.supabaseDatabaseAdapter("Reading Admin System Health migration history"); - const versionRows = await adapter.databaseClient().query("SELECT version() AS version;"); -- const countRows = await adapter.databaseClient().query(` +- const countRows = await adapter.databaseClient().query( + const databaseClient = adapter.databaseClient(); + const versionRows = await databaseClient.query("SELECT version() AS version;"); + const currentRows = await databaseClient.query("SELECT current_database() AS database_name, current_schema() AS schema_name;"); -+ const countRows = await databaseClient.query(` - SELECT "migrationType", count(*)::int AS count ++ const countRows = await databaseClient.query( + SELECT "migrationType", count(*)::int AS coun FROM schema_migrations GROUP BY "migrationType" ORDER BY "migrationType"; `); -- const lastRows = await adapter.databaseClient().query(` -+ const lastRows = await databaseClient.query(` +- const lastRows = await adapter.databaseClient().query( ++ const lastRows = await databaseClient.query( SELECT "migrationType", "migrationName", "appliedAt" FROM schema_migrations ORDER BY "appliedAt" DESC, key DESC LIMIT 1; +`); -+ const tableCountRows = await databaseClient.query(` -+SELECT count(*)::int AS table_count ++ const tableCountRows = await databaseClient.query( ++SELECT count(*)::int AS table_coun +FROM information_schema.tables +WHERE table_schema NOT IN ('pg_catalog', 'information_schema') + AND table_type = 'BASE TABLE'; +`); -+ const databaseSizeRows = await databaseClient.query(` ++ const databaseSizeRows = await databaseClient.query( +SELECT pg_database_size(current_database()) AS database_size_bytes, + pg_size_pretty(pg_database_size(current_database())) AS database_size; `); @@ -329,7 +455,7 @@ index 993e14a59..462d8b7a0 100644 + }; } } - + @@ -4410,6 +4505,7 @@ LIMIT 1; const environmentIdentity = systemHealthEnvironmentIdentity(process.env, checkedAt); const environmentMap = systemHealthEnvironmentMap(); @@ -450,128 +576,3 @@ index 52d7acf50..56733ce6a 100644 expect(pageSource).toContain("Server-owned Postgres health reader"); expect(pageSource).toContain("Server-owned Cloudflare R2 storage diagnostic"); expect(pageSource).toContain("assets/theme-v2/js/admin-system-health.js"); -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md -new file mode 100644 -index 000000000..e0e6f995a ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel.md -@@ -0,0 +1,32 @@ -+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel -+ -+Team: Charlie -+Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -+Base: main -+Lifecycle: Build / Validation -+ -+## Scope -+- Added a System Health Postgres Metrics panel backed by the server-owned Admin System Health API. -+- Added safe current-environment metrics for connection status, database name, current schema, migration status, last migration, table count, database size, and last checked. -+- Preserved current-environment-only behavior and did not add cross-environment database checks. -+ -+## Changed Files -+- admin/system-health.html -+- assets/theme-v2/js/admin-system-health.js -+- src/dev-runtime/server/local-api-router.mjs -+- tests/api/admin-system-health/contract.test.mjs -+- tests/dev-runtime/AdminHealthOperations.test.mjs -+- tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -+- docs_build/dev/reports/coverage_changed_js_guardrail.txt -+- docs_build/dev/reports/playwright_v8_coverage_report.txt -+ -+## Validation -+- PASS: node --check src/dev-runtime/server/local-api-router.mjs -+- PASS: node --check assets/theme-v2/js/admin-system-health.js -+- PASS: node --test tests/api/admin-system-health/contract.test.mjs -+- PASS: node --test tests/dev-runtime/AdminHealthOperations.test.mjs -+- PASS: npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line -+- PASS: git diff --check -+ -+## ZIP -+- Pending until commit: C:\Users\DavidQ\Documents\GitHub\HTML-JavaScript-Gaming\tmp\PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_delta.zip -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md -new file mode 100644 -index 000000000..b5a80d96a ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_branch-validation.md -@@ -0,0 +1,21 @@ -+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Branch Validation -+ -+Branch: pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -+Expected start branch: main, then PR branch for build -+Current status at validation: -+## pr/26177-CHARLIE-029-system-health-postgres-metrics-panel -+ M admin/system-health.html -+ M assets/theme-v2/js/admin-system-health.js -+ M docs_build/dev/reports/coverage_changed_js_guardrail.txt -+ M docs_build/dev/reports/playwright_v8_coverage_report.txt -+ M src/dev-runtime/server/local-api-router.mjs -+ M tests/api/admin-system-health/contract.test.mjs -+ M tests/dev-runtime/AdminHealthOperations.test.mjs -+ M tests/playwright/tools/AdminHealthOperationsPage.spec.mjs -+ -+Result: PASS -+ -+Checks: -+- PASS: Started from synchronized main. -+- PASS: Active branch matches PR identity. -+- PASS: Worktree contained only scoped PR changes and generated validation reports. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md -new file mode 100644 -index 000000000..b37c12319 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_manual-validation-notes.md -@@ -0,0 +1,7 @@ -+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Manual Validation Notes -+ -+- Confirmed Postgres Metrics appears as a separate System Health table. -+- Confirmed metric values come from the server-owned Admin System Health API response. -+- Confirmed unavailable metrics render visibly as Unavailable/WARN rather than silently falling back. -+- Confirmed page retains external scripts/styles only; no inline style/script/handler additions. -+- Confirmed no secrets or database URLs are shown in the Database Health or Postgres Metrics tables. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md -new file mode 100644 -index 000000000..201ce7ec5 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_requirements-checklist.md -@@ -0,0 +1,14 @@ -+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Requirement Checklist -+ -+- PASS: Add/extend System Health Postgres metrics panel. -+- PASS: Include connection status. -+- PASS: Include database name. -+- PASS: Include current schema/migration status when available. -+- PASS: Include table count when available. -+- PASS: Include database size when available. -+- PASS: Do not add expensive queries. -+- PASS: Show explicit Unavailable status when a metric is unavailable. -+- PASS: Do not expose secrets. -+- PASS: Keep browser UI consuming API/service contract. -+- PASS: No cross-environment checks added. -+- PASS: No start_of_day files modified. -diff --git a/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md -new file mode 100644 -index 000000000..45c110616 ---- /dev/null -+++ b/docs_build/dev/reports/PR_26177_CHARLIE_029-system-health-postgres-metrics-panel_validation-lane.md -@@ -0,0 +1,21 @@ -+# PR_26177_CHARLIE_029-system-health-postgres-metrics-panel Validation Lane Report -+ -+Impacted lanes: -+- runtime: Local API Admin System Health status contract. -+- contract: Admin System Health API contract tests. -+- UI: Admin System Health Theme V2 page controller and markup. -+- Playwright: targeted Admin System Health page spec. -+ -+Commands: -+- node --check src/dev-runtime/server/local-api-router.mjs -+- node --check assets/theme-v2/js/admin-system-health.js -+- node --test tests/api/admin-system-health/contract.test.mjs -+- node --test tests/dev-runtime/AdminHealthOperations.test.mjs -+- npx playwright test tests/playwright/tools/AdminHealthOperationsPage.spec.mjs --workers=1 --reporter=line -+- git diff --check -+ -+Skipped lanes: -+- Full samples smoke skipped; not impacted by System Health API/UI panel change. -+- Full workspace suite skipped; targeted Project Workspace coverage was sufficient for this scoped Admin page. -+ -+Result: PASS diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index be6f01bab..7b1c51f19 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -6,8 +6,7 @@ Missing changed runtime JS files are WARN, not FAIL. Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: -(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 848/848; executed functions 70/86 +(100%) none changed - no changed runtime JS files Guardrail warnings: -(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only +(100%) none changed - no changed runtime JS files diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index e004c18db..627ed631f 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -17,11 +17,10 @@ Exercised tool entry points detected: (78%) Theme V2 Shared JS - exercised 5 runtime JS files Changed runtime JS files covered: -(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only -(81%) assets/theme-v2/js/admin-system-health.js - executed lines 848/848; executed functions 70/86 +(100%) none changed - no changed runtime JS files Files with executed line/function counts where available: -(36%) src/api/server-api-client.js - executed lines 167/167; executed functions 5/14 +(36%) src/api/server-api-client.js - executed lines 168/168; executed functions 5/14 (46%) toolbox/tool-registry-api-client.js - executed lines 155/155; executed functions 12/26 (65%) src/api/public-config-client.js - executed lines 209/209; executed functions 17/26 (75%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 1046/1046; executed functions 76/102 @@ -34,11 +33,7 @@ Files with executed line/function counts where available: (100%) src/api/admin-system-health-api-client.js - executed lines 28/28; executed functions 4/4 Uncovered or low-coverage changed JS files: -(0%) src/dev-runtime/server/local-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only +(100%) none changed - no changed runtime JS files Changed JS files considered: -(0%) src/dev-runtime/server/local-api-router.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/api/admin-system-health/contract.test.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/dev-runtime/AdminHealthOperations.test.mjs - changed JS file not collected as browser runtime coverage -(0%) tests/playwright/tools/AdminHealthOperationsPage.spec.mjs - changed JS file not collected as browser runtime coverage -(81%) assets/theme-v2/js/admin-system-health.js - changed JS file with browser V8 coverage +(100%) none - no changed JS files

Storage Health - Cloudflare R2