/** * Admin Panel: Service Observability Report -- covers all of CDPI's real, * discovered subsystems (not just Azure/AI), reusing every existing real * endpoint rather than re-implementing them: * - Real Azure Monitor / Log Analytics QUERY-BACK (azure_monitor_query.py, * manage/observability-report/*) -- the one genuinely NEW telemetry * source this feature adds; everything else below already existed. * - Per-service Azure connectivity/health -> manage/azure-readiness (same * data Azure Readiness' Services tab shows; reused here as a compact * matrix, not duplicated logic). * - Document/resume processing -> manage/reports/resume-processing (real * document_extraction spans, see that endpoint's own docstring). * - External integrations (WhatsApp/email) -> * manage/reports/communication-delivery (real comms_delivery_log * records; email has NO delivery logging at all today -- a real, * stated gap, not a zero-volume coincidence, see that endpoint). * - Local LLM/trace token-cost-latency detail + the full Trace Explorer -> * AI Observability Center (link out, not duplicated). * - Real Azure resource cost -> Azure Readiness' Cost & Usage tab (link out). * * Every number is labeled with its real source. `overall_status` is * computed server-side from real ingestion evidence, never just "the page * loaded" or "a connection string is set". One section failing to load * (e.g. a role lacking comms.delivery.read) never blanks the rest of the * page -- each section fetches and fails independently. * * CUSTOMIZATION: which sections render is user-controlled (Customize panel * below) and persisted to localStorage ONLY -- this is a per-browser, * per-device convenience, explicitly NOT a server-persisted/per-account * preference (no backend dashboard-config model exists in this repo; see * docs/AZURE_OBSERVABILITY.md for the scoping decision to skip full * drag-and-drop/resize/named-layouts given this app's scale). Hidden * widgets are still fetched as "available" if their data loaded -- hiding * is a display preference, not a fetch skip, so toggling one back on is * instant. */ var OBS_REPORT_WIDGETS = [ { id: "kpis", label: "Executive KPIs" }, { id: "request_volume", label: "Request Volume (Azure Monitor)" }, { id: "ai_usage", label: "AI Model Usage (Azure Monitor)" }, { id: "ai_usage_chart", label: "AI Token Usage by Model (chart)" }, { id: "recent_errors", label: "Error Analysis (Azure Monitor)" }, { id: "service_health", label: "Azure Service Health Matrix" }, { id: "service_health_chart", label: "Service Health Breakdown (chart)" }, { id: "workflow_breakdown", label: "Traces by Workflow: Succeeded vs Failed (chart)" }, { id: "workflow_latency", label: "Average Duration by Workflow (chart)" }, { id: "document_processing", label: "Document / Resume Processing" }, { id: "external_integrations", label: "External Integrations (WhatsApp / Email)" }, { id: "local_recap", label: "Local Observability Recap" }, ]; var OBS_REPORT_WIDGETS_STORAGE_KEY = "cdpi_observability_report_hidden_widgets"; function _loadHiddenWidgets() { try { var raw = window.localStorage.getItem(OBS_REPORT_WIDGETS_STORAGE_KEY); return raw ? JSON.parse(raw) : []; } catch (e) { return []; } } function _saveHiddenWidgets(ids) { try { window.localStorage.setItem(OBS_REPORT_WIDGETS_STORAGE_KEY, JSON.stringify(ids)); } catch (e) { /* per-device convenience only; ignore quota/availability errors */ } } /** Adapts a persisted observability_snapshots.py snapshot into the same * summary/telemetry/serviceHealth/docProcessing/comms shapes the existing * chart/table sub-components below already consume -- so this snapshot * architecture change doesn't require rewriting every widget, only how * data gets INTO them. A source with status != "success" (not_configured, * failed, or simply absent from a snapshot taken before that source * existed) maps to an honest "unavailable" shape, never a fabricated * zero/empty-but-successful one. A "failed" source that has a real * last_known_good result from an earlier refresh uses THAT instead of * going blank -- a bad refresh never erases good data (see * observability_snapshots.run_refresh()'s docstring). */ function _sourceData(snapshot, sourceId) { var src = snapshot && snapshot.sources && snapshot.sources[sourceId]; if (!src) return null; if (src.status === "success") return src.data; if (src.status === "failed" && src.last_known_good && src.last_known_good.status === "success") return src.last_known_good.data; return null; } function _sourceUnavailableReason(snapshot, sourceId) { var src = snapshot && snapshot.sources && snapshot.sources[sourceId]; if (!src) return "No refresh has collected this source yet."; if (src.status === "not_configured") return "Not configured."; if (src.status === "failed") return "Last refresh failed: " + (src.error || "unknown error") + (src.last_known_good ? " (showing last known good data from an earlier refresh)" : ""); return null; } function _adaptSnapshot(snapshot) { var reqVol = _sourceData(snapshot, "azure_request_volume") || { available: false, buckets: [] }; var latency = _sourceData(snapshot, "azure_latency") || { available: false }; var aiUsage = _sourceData(snapshot, "azure_ai_usage") || { available: false, by_model: [] }; var recentErrors = _sourceData(snapshot, "azure_recent_errors") || { available: false, errors: [] }; var telHealth = _sourceData(snapshot, "azure_telemetry_health") || { azure_configured: false, ingestion_confirmed: false, detail: _sourceUnavailableReason(snapshot, "azure_telemetry_health") || "Unavailable." }; var localTraces = _sourceData(snapshot, "local_ai_traces") || { total_spans: 0, by_workflow: {} }; var localLlm = _sourceData(snapshot, "local_llm_calls") || { total_calls: 0, total_tokens: 0, total_cost_usd: 0, by_label: {} }; var totalReq = reqVol.available ? reqVol.buckets.reduce(function (s, b) { return s + b.total; }, 0) : null; var succeeded = reqVol.available ? reqVol.buckets.reduce(function (s, b) { return s + b.succeeded; }, 0) : null; var failedReq = reqVol.available ? reqVol.buckets.reduce(function (s, b) { return s + b.failed; }, 0) : null; var aiRequests = aiUsage.available ? aiUsage.by_model.reduce(function (s, m) { return s + m.calls; }, 0) : null; var aiTokens = aiUsage.available ? aiUsage.by_model.reduce(function (s, m) { return s + (m.total_tokens || 0); }, 0) : null; var aiCost = aiUsage.available ? Math.round(aiUsage.by_model.reduce(function (s, m) { return s + (m.total_cost_usd || 0); }, 0) * 1000000) / 1000000 : null; var overallStatus = "UNKNOWN"; if (!snapshot || !snapshot.collected_at) overallStatus = "NOT_CONFIGURED"; else if (!telHealth.azure_configured) overallStatus = "UNKNOWN"; else if (!telHealth.ingestion_confirmed) overallStatus = "CONFIGURED_NO_INGESTION"; else if (failedReq && totalReq && failedReq / totalReq > 0.1) overallStatus = "DEGRADED"; else overallStatus = "HEALTHY"; var summary = { reporting_period_hours: snapshot && snapshot.hours_window, generated_at: snapshot && snapshot.collected_at, overall_status: overallStatus, telemetry_health: telHealth, azure_telemetry: { configured: telHealth.azure_configured, total_requests: totalReq, succeeded_requests: succeeded, failed_requests: failedReq, success_rate: (totalReq ? succeeded / totalReq : null), failure_rate: (totalReq ? failedReq / totalReq : null), latency: latency, ai_requests: aiRequests, ai_total_tokens: aiTokens, ai_total_cost_usd: aiCost, unavailable_reason: reqVol.available ? null : _sourceUnavailableReason(snapshot, "azure_request_volume"), }, local_observability: { llm_calls: localLlm, ai_traces: localTraces, note: "Sourced from this process's local JSONL logs, as of the last refresh -- always available regardless of Azure Monitor configuration.", }, }; var telemetry = { reporting_period_hours: snapshot && snapshot.hours_window, request_volume: reqVol, ai_usage: aiUsage, recent_errors: recentErrors, }; return { summary: summary, telemetry: telemetry }; } function AzureObservabilityReport(props) { var api = props.api; var hoursState = React.useState(24); var hours = hoursState[0]; var setHours = hoursState[1]; // Persistent frontend snapshot of the already-persisted backend // snapshot (api.getObservabilitySnapshot() -- itself a pure read of // observability_snapshots.py's cached last-known-good data, never a // real Azure collection). This is a second, frontend-side cache layer // on top of that: per the "no loading flash on revisit" requirement, // opening this page never shows "Loading..." once it's been opened // once in this browser, even though the backend read is already fast. var pageSnapshot = CDPI_REPORT_SNAPSHOT.useReportSnapshot("azure_observability_report:" + hours, function () { return api.getObservabilitySnapshot(); }); var snapshot = pageSnapshot.state.data; var refreshingState = React.useState(false); var refreshing = refreshingState[0]; var setRefreshing = refreshingState[1]; var exportingState = React.useState(false); var exporting = exportingState[0]; var setExporting = exportingState[1]; var historyState = React.useState([]); var refreshHistory = historyState[0]; var setRefreshHistory = historyState[1]; var showHistoryState = React.useState(false); var showHistory = showHistoryState[0]; var setShowHistory = showHistoryState[1]; var serviceHealth = { services: (_sourceData(snapshot, "service_health") || {}).services || null }; var serviceHealthError = _sourceUnavailableReason(snapshot, "service_health"); var docProcessing = _sourceData(snapshot, "document_processing"); var docProcessingError = _sourceUnavailableReason(snapshot, "document_processing"); var comms = _sourceData(snapshot, "comms_delivery"); var commsError = _sourceUnavailableReason(snapshot, "comms_delivery"); var customizingState = React.useState(false); var customizing = customizingState[0]; var setCustomizing = customizingState[1]; var hiddenWidgetsState = React.useState(_loadHiddenWidgets); var hiddenWidgets = hiddenWidgetsState[0]; var setHiddenWidgets = hiddenWidgetsState[1]; function isVisible(widgetId) { return hiddenWidgets.indexOf(widgetId) === -1; } function toggleWidget(widgetId) { var next = isVisible(widgetId) ? hiddenWidgets.concat([widgetId]) : hiddenWidgets.filter(function (id) { return id !== widgetId; }); setHiddenWidgets(next); _saveHiddenWidgets(next); } function resetWidgets() { setHiddenWidgets([]); _saveHiddenWidgets([]); } // The persisted-data read itself (NEVER a real Azure/local collection // -- per the "manual refresh is the default; opening the dashboard // must not auto-collect" requirement) is `pageSnapshot` above -- // fetched once per `hours` the first time that window is viewed in // this browser, cached after that, so no "Loading..." flash on revisit. var loading = pageSnapshot.state.loading && !pageSnapshot.state.data; var error = pageSnapshot.state.error; /** The ONLY action that collects real data -- explicit user click, * never automatic. */ function refreshAll() { setRefreshing(true); api.refreshObservability(hours) .then(function () { window.CDPI_AZURE_CACHE.invalidatePrefix("azure-readiness:"); // keep Azure Readiness/Service Hub pages consistent with this real refresh return pageSnapshot.refresh(); // re-reads what the refresh just persisted, updating this page's own snapshot too }) .catch(function (err) { window.alert(err.message); }) .then(function () { setRefreshing(false); }); } function loadHistory() { api.getObservabilityRefreshHistory(20).then(function (r) { setRefreshHistory(r.history || []); }).catch(function () { setRefreshHistory([]); }); } var adapted = _adaptSnapshot(snapshot); var summary = adapted.summary; var telemetry = adapted.telemetry; function doExport() { setExporting(true); api.exportObservabilityReportCsv(hours) .then(function (csvText) { var blob = new Blob([csvText], { type: "text/csv" }); var url = URL.createObjectURL(blob); var a = document.createElement("a"); a.href = url; a.download = "azure_observability_request_volume_" + hours + "h.csv"; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }) .catch(function (err) { window.alert("Export failed: " + err.message); }) .then(function () { setExporting(false); }); } return (
Real telemetry across CDPI's actual subsystems -- Azure Monitor-ingested metrics, per-service Azure connectivity, document/resume processing, and external-integration delivery -- composed from this app's existing real endpoints, not re-implemented. See the AI Observability Center for full local trace detail, and Azure Readiness for live per-service checks and real cost.
{snapshot && snapshot.collected_at
?
No past refreshes recorded.
} {refreshHistory.length > 0 && (| Collected | Triggered by | Succeeded | Failed | Not configured |
|---|---|---|---|---|
| {new Date(h.collected_at).toLocaleString()} | {h.triggered_by} | {h.succeeded_count} | {h.failed_count} | {h.skipped_count} |
Which sections to show -- saved in this browser only (not a server-side, per-account preference; see docs/AZURE_OBSERVABILITY.md).
{OBS_REPORT_WIDGETS.map(function (w) { return ( ); })}Loading…
} {!loading && (Reporting period: last {props.hours}h · Generated: {s.generated_at ? new Date(s.generated_at).toLocaleString() : "never (no refresh yet)"} ·{" "} Data source: Azure Monitor (Log Analytics) + local JSONL · Environment: this app's configured Azure resource group
Last successful Azure telemetry ingestion:{" "} {s.telemetry_health.last_ingested_at ? new Date(s.telemetry_health.last_ingested_at).toLocaleString() : none confirmed -- {s.telemetry_health.detail}}
Unavailable — {rv.detail}
No requests recorded in this window.
Unavailable — {usage.detail}
} {usage.available && usage.by_model.length === 0 &&No AI calls recorded in this window.
} {usage.available && usage.by_model.length > 0 && (| Model | Provider | Calls | Failed | Tokens | Cost (USD) |
|---|---|---|---|---|---|
| {m.model || "—"} | {m.provider || "—"} | {m.calls} | {m.failed} | {m.total_tokens !== null ? m.total_tokens : "—"} | {m.total_cost_usd !== null ? ("$" + m.total_cost_usd) : "—"} |
Unavailable — {usage.detail}
No AI calls recorded in this window.
Unavailable — {props.error}
Loading…
No Azure services registered.
No traces recorded locally.
No traces recorded locally.
Unavailable — {errs.detail}
} {errs.available && errs.errors.length === 0 &&No failures found in this window.
} {errs.available && errs.errors.length > 0 && (| Time | Source | Workflow / Label | Stage | Error | Correlation ID |
|---|---|---|---|---|---|
| {new Date(e.timestamp).toLocaleString()} | {e.source} | {e.workflow || e.label || "—"} | {e.stage || "—"} | {e.error || "—"} | {e.correlation_id || "—"} |
{lo.note}
{lo.llm_calls.total_calls} LLM call(s) logged locally, {lo.llm_calls.total_tokens} token(s), ${lo.llm_calls.total_cost_usd} estimated cost. Full detail, per-trace spans, and the Trace Explorer live on the AI Observability Center page.
Unavailable — {props.error}
Loading…
Composed status per Azure integration (configuration, authentication, connectivity) — a successful application health check never implies every dependency below is healthy. See Azure Readiness to run a live check.
| Service | Status | Reason |
|---|---|---|
| {s.service_name} | {s.status} | {s.reason || "—"} |
Unavailable — {props.error}
Loading…
Source: {d.data_source}
| Logged at | Correlation ID | Error |
|---|---|---|
| {f.logged_at ? new Date(f.logged_at).toLocaleString() : "—"} | {f.correlation_id || "—"} | {f.error || "—"} |
Unavailable — {props.error}
Loading…
{props.data.note}
{channels.length === 0 &&No delivery records found.
} {channels.length > 0 && (| Channel | Submitted | Provider accepted | Failed |
|---|---|---|---|
| {ch} | {stats.submitted} | {stats.provider_accepted} | {stats.failed} |