"use strict"; (function () { const notice = document.getElementById("healthNotice"); const content = document.getElementById("healthContent"); const refresh = document.getElementById("healthRefresh"); function setText(id, value, fallback) { const element = document.getElementById(id); if (element) { element.textContent = value === null || value === undefined || value === "" ? (fallback || "Unavailable") : String(value); } } function number(value) { return typeof value === "number" ? value.toLocaleString() : "Unavailable"; } function timestamp(value) { if (!value) return "Unavailable"; const date = new Date(value); return Number.isNaN(date.valueOf()) ? "Unavailable" : date.toLocaleString(); } function factualAge(value) { if (!value) return "Unavailable"; const observed = new Date(value); if (Number.isNaN(observed.valueOf())) return "Unavailable"; const seconds = Math.max(0, Math.floor((Date.now() - observed.valueOf()) / 1000)); if (seconds < 60) return seconds + " seconds ago"; const minutes = Math.floor(seconds / 60); if (minutes < 60) return minutes + (minutes === 1 ? " minute ago" : " minutes ago"); const hours = Math.floor(minutes / 60); if (hours < 24) return hours + (hours === 1 ? " hour ago" : " hours ago"); const days = Math.floor(hours / 24); return days + (days === 1 ? " day ago" : " days ago"); } function replaceFacts(id, facts) { const list = document.getElementById(id); list.replaceChildren(); for (const [label, value] of facts) { const row = document.createElement("div"); const term = document.createElement("dt"); const detail = document.createElement("dd"); term.textContent = label; detail.textContent = value; row.append(term, detail); list.append(row); } } function render(payload) { const data = payload.data; const app = data.application; const postgres = data.postgresql; const migration = postgres.migration; const telemetry = postgres.telemetry; setText("healthAppStatus", app.status); setText("healthEnvironment", app.environment); setText("healthBuildCommit", app.build_commit); setText("healthBuildVersion", app.build_version); setText("healthServerTime", timestamp(app.server_time)); setText("healthMigrationState", migration.state); setText("healthCurrentRevision", migration.current_revision); setText("healthExpectedRevision", migration.expected_revision); setText("healthPostgresqlStatus", postgres.status); setText("healthRedisStatus", data.redis.status); setText("healthSnapshotCount", telemetry ? number(telemetry.snapshot_count) : null); setText("healthOldestObservation", telemetry ? timestamp(telemetry.oldest_observed_at) : null); setText("healthLatestObservation", telemetry ? timestamp(telemetry.latest_observed_at) : null); setText("healthObservationAge", telemetry ? factualAge(telemetry.latest_observed_at) : null); const fleet = postgres.fleet; replaceFacts("healthFleetFacts", fleet ? [ ["Total miners", number(fleet.total_miners)], ["Without Asset ID", number(fleet.miners_without_asset_id)], ["Unmapped", number(fleet.unmapped_miners)], ["In multiple positions", number(fleet.miners_in_multiple_positions)], ["Duplicate current MAC groups", number(fleet.duplicate_current_mac_groups)] ] : [["Diagnostics", "Unavailable"]]); const locations = postgres.locations; replaceFacts("healthLocationFacts", locations ? [ ["Sites", number(locations.sites)], ["Rooms", number(locations.rooms)], ["Positions", number(locations.positions)], ["Occupied", number(locations.occupied_positions)], ["Empty", number(locations.empty_positions)], ["Legacy unscoped", number(locations.unscoped_legacy_positions)], ["Expected MAC mismatches", number(locations.expected_mac_mismatches)] ] : [["Diagnostics", "Unavailable"]]); const repairs = postgres.repairs; replaceFacts("healthRepairFacts", repairs ? [ ["Active modular repairs", number(repairs.active_modular_repairs)], ["Duplicate active groups", number(repairs.duplicate_active_repair_groups)], ["Legacy repair only", number(repairs.legacy_repair_only_miners)], ["Lifecycle repair without active repair", number(repairs.lifecycle_repair_without_active_repair)], ["Active repair lifecycle mismatches", number(repairs.active_repair_lifecycle_mismatches)], ["Waiting placement", number(repairs.waiting_placement_records)], ["Repair events", number(repairs.repair_events)], ["Repair parts", number(repairs.repair_parts)] ] : [["Diagnostics", "Unavailable"]]); const retention = postgres.retention; replaceFacts("healthRetentionFacts", retention ? [ ["Raw telemetry retention", retention.raw_retention_enabled ? "Enabled" : "Not enabled"], ["Hourly rollups", retention.hourly.table_present ? "Present; manual population" : "Unavailable"], ["Hourly latest bucket", timestamp(retention.hourly.latest_bucket)], ["Daily rollups", retention.daily.table_present ? "Present; manual population" : "Unavailable"], ["Daily latest bucket", timestamp(retention.daily.latest_bucket)], ["Latest recorded run", timestamp(retention.latest_recorded_run_at)], ["Latest run mode", retention.latest_recorded_run_mode || "None recorded"], ["Completeness", "Not guaranteed"] ] : [["Diagnostics", "Unavailable"]]); const tableBody = document.getElementById("healthTableCounts"); tableBody.replaceChildren(); const rows = postgres.table_counts || []; for (const table of rows) { const row = document.createElement("tr"); const name = document.createElement("th"); const count = document.createElement("td"); name.scope = "row"; name.textContent = table.name; count.textContent = number(table.row_count); row.append(name, count); tableBody.append(row); } if (!rows.length) { const row = document.createElement("tr"); const cell = document.createElement("td"); cell.colSpan = 2; cell.textContent = "Table diagnostics unavailable."; row.append(cell); tableBody.append(row); } content.hidden = false; notice.dataset.state = payload.meta.partial ? "partial" : "ready"; notice.textContent = payload.meta.partial ? "Diagnostics loaded with unavailable sections: " + payload.meta.unavailable_sections.join(", ") + "." : "Diagnostics refreshed at " + timestamp(payload.meta.generated_at) + "."; } async function load() { refresh.disabled = true; notice.dataset.state = "loading"; notice.textContent = "Loading diagnostics."; try { const response = await fetch("/api/system/health-summary", { credentials: "same-origin", headers: {"Accept": "application/json"} }); const payload = await response.json().catch(function () { return {}; }); if (!response.ok) throw new Error("System Health request failed"); render(payload); } catch (error) { notice.dataset.state = "error"; notice.textContent = "System Health diagnostics could not be loaded."; } finally { refresh.disabled = false; } } refresh.addEventListener("click", load); load(); }());