Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
736 lines
33 KiB
TypeScript
736 lines
33 KiB
TypeScript
import { DescriptionList } from "@govoplan/core-webui";
|
|
import { MetricGrid } from "@govoplan/core-webui";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { PauseCircle, PlayCircle, RefreshCw } from "lucide-react";
|
|
import { ContentGrid,
|
|
ActionBlockerHint,
|
|
Button,
|
|
Card,
|
|
ConfirmDialog,
|
|
DataGrid,
|
|
DocumentationHelpLink,
|
|
LoadingFrame,
|
|
MetricCard,
|
|
PageActionBar,
|
|
PageLayout,
|
|
StatusBadge,
|
|
TableActionGroup,
|
|
adminErrorMessage,
|
|
hasAnyScope,
|
|
i18nMessage,
|
|
type ApiSettings,
|
|
type AuthInfo,
|
|
type DataGridColumn } from
|
|
"@govoplan/core-webui";
|
|
import {
|
|
fetchOpsStatus,
|
|
cancelRuntimeNodeDrain,
|
|
drainRuntimeNode,
|
|
runOpsChecks,
|
|
type OpsCheck,
|
|
type OpsDeploymentProfile,
|
|
type OpsGovernanceModule,
|
|
type OpsInfrastructureCapability,
|
|
type OpsRecoveryOperation,
|
|
type OpsRuntimeNode,
|
|
type OpsRuntimeWorkStatus,
|
|
type OpsSizingAssumption,
|
|
type OpsStatus
|
|
} from "../../api/ops";
|
|
import {
|
|
OPS_DOCUMENTATION,
|
|
OPS_I18N,
|
|
OPS_RECOVERY_DOCUMENTATION,
|
|
} from "./interfacePatterns";
|
|
import {
|
|
heartbeatAgeLabel,
|
|
heartbeatAgeSeconds,
|
|
knownMetric,
|
|
knownQueueDepthTotal,
|
|
runtimeWorkTone,
|
|
shouldPollRuntimeStatus
|
|
} from "./runtimeStatus";
|
|
|
|
export default function OpsPage({ settings, auth }: {settings: ApiSettings;auth: AuthInfo;}) {
|
|
const [status, setStatus] = useState<OpsStatus | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [runningProbes, setRunningProbes] = useState(false);
|
|
const [error, setError] = useState("");
|
|
const [drainTarget, setDrainTarget] = useState<OpsRuntimeNode | null>(null);
|
|
const [nodeActionId, setNodeActionId] = useState("");
|
|
const loadInFlight = useRef<Promise<void> | null>(null);
|
|
|
|
const load = useCallback((background = false): Promise<void> => {
|
|
if (loadInFlight.current) return loadInFlight.current;
|
|
const request = (async () => {
|
|
if (!background) setLoading(true);
|
|
setError("");
|
|
try {
|
|
setStatus(await fetchOpsStatus(settings));
|
|
} catch (err) {
|
|
setError(adminErrorMessage(err));
|
|
} finally {
|
|
if (!background) setLoading(false);
|
|
}
|
|
})();
|
|
loadInFlight.current = request;
|
|
void request.finally(() => {
|
|
if (loadInFlight.current === request) loadInFlight.current = null;
|
|
});
|
|
return request;
|
|
}, [settings.accessToken, settings.apiBaseUrl, settings.apiKey]);
|
|
|
|
async function runChecks() {
|
|
setRunningProbes(true);
|
|
setLoading(true);
|
|
setError("");
|
|
try {
|
|
setStatus(await runOpsChecks(settings));
|
|
} catch (err) {
|
|
setError(adminErrorMessage(err));
|
|
} finally {
|
|
setRunningProbes(false);
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
async function confirmDrain() {
|
|
if (!drainTarget) return;
|
|
setNodeActionId(drainTarget.node_id);
|
|
setError("");
|
|
try {
|
|
await drainRuntimeNode(settings, drainTarget.node_id);
|
|
setDrainTarget(null);
|
|
setStatus(await fetchOpsStatus(settings));
|
|
} catch (err) {
|
|
setError(adminErrorMessage(err));
|
|
} finally {
|
|
setNodeActionId("");
|
|
}
|
|
}
|
|
|
|
async function cancelDrain(node: OpsRuntimeNode) {
|
|
setNodeActionId(node.node_id);
|
|
setError("");
|
|
try {
|
|
await cancelRuntimeNodeDrain(settings, node.node_id);
|
|
setStatus(await fetchOpsStatus(settings));
|
|
} catch (err) {
|
|
setError(adminErrorMessage(err));
|
|
} finally {
|
|
setNodeActionId("");
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
let intervalId: number | null = null;
|
|
const stopPolling = () => {
|
|
if (intervalId !== null) window.clearInterval(intervalId);
|
|
intervalId = null;
|
|
};
|
|
const startPolling = () => {
|
|
stopPolling();
|
|
if (document.visibilityState === "hidden") return;
|
|
intervalId = window.setInterval(() => {
|
|
if (shouldPollRuntimeStatus(document.visibilityState === "hidden", Boolean(loadInFlight.current))) {
|
|
void load(true);
|
|
}
|
|
}, 15_000);
|
|
};
|
|
const handleVisibility = () => {
|
|
if (document.visibilityState === "hidden") {
|
|
stopPolling();
|
|
return;
|
|
}
|
|
if (shouldPollRuntimeStatus(false, Boolean(loadInFlight.current))) void load(true);
|
|
startPolling();
|
|
};
|
|
void load();
|
|
startPolling();
|
|
document.addEventListener("visibilitychange", handleVisibility);
|
|
return () => {
|
|
stopPolling();
|
|
document.removeEventListener("visibilitychange", handleVisibility);
|
|
};
|
|
}, [load]);
|
|
|
|
const checks = status?.checks ?? [];
|
|
const warningCount = checks.filter((item) => item.state === "warning").length;
|
|
const errorCount = checks.filter((item) => item.state === "error").length;
|
|
const ready = status?.readiness.ready ?? false;
|
|
const canRunChecks = hasAnyScope(auth, ["ops:operations:run", "system:settings:write"]);
|
|
const runProbesDisabledReason = runningProbes
|
|
? OPS_I18N.runningProbes
|
|
: loading
|
|
? OPS_I18N.loading
|
|
: !canRunChecks
|
|
? OPS_I18N.runPermissionRequired
|
|
: undefined;
|
|
const queueDepths = status?.summary.worker_metrics.queue_depths ?? {};
|
|
const queuedTasks = knownQueueDepthTotal(queueDepths);
|
|
const storageUsage = status?.summary.storage_metrics?.capacity_used_percent;
|
|
|
|
return (
|
|
<PageLayout
|
|
archetype="overview"
|
|
title="i18n:govoplan-ops.ops.907a54c2" titleHelp={<DocumentationHelpLink reference={OPS_DOCUMENTATION} />}
|
|
description="i18n:govoplan-ops.runtime_health_deployment_profile_worker_split_a.55340156"
|
|
error={error}
|
|
actions={<PageActionBar
|
|
variant="overview"
|
|
refreshable
|
|
reloadAction={{ onReload: () => void load(), loading, disabledReason: loading ? OPS_I18N.loading : undefined }}
|
|
|
|
primaryActions={<Button variant="primary" onClick={() => void runChecks()} disabled={Boolean(runProbesDisabledReason)} disabledReason={runProbesDisabledReason}>i18n:govoplan-ops.surface.run_probes</Button>}
|
|
/>}
|
|
>
|
|
{status && !ready && (
|
|
<ActionBlockerHint
|
|
reason={{
|
|
summary: "i18n:govoplan-ops.readiness_blocked_summary",
|
|
details: i18nMessage("i18n:govoplan-ops.readiness_blocked_details", { value0: status.readiness.blockers.length }),
|
|
requiredAction: "i18n:govoplan-ops.readiness_blocked_action",
|
|
actor: "i18n:govoplan-ops.operations_operator",
|
|
target: "i18n:govoplan-ops.readiness_blocked_target"
|
|
}}
|
|
documentation={OPS_RECOVERY_DOCUMENTATION}
|
|
/>
|
|
)}
|
|
|
|
<LoadingFrame loading={loading} label="i18n:govoplan-ops.loading_operations_status.6890fe6e">
|
|
<MetricGrid>
|
|
<MetricCard label="i18n:govoplan-ops.profile.ff4fc027" value={status?.summary.active_profile ?? "-"} tone="info" detail={status?.summary.database_url ?? "i18n:govoplan-ops.no_database_url.51a2db0c"} />
|
|
<MetricCard label="i18n:govoplan-ops.readiness.1db9d6fb" value={ready ? "ready" : "not ready"} tone={ready ? "good" : "danger"} detail={status?.readiness.blockers.length ? `${status.readiness.blockers.length} blocker(s)` : "i18n:govoplan-ops.no_readiness_blockers.0df259bd"} />
|
|
<MetricCard label="i18n:govoplan-ops.modules.04e9462c" value={status?.summary.module_count ?? 0} tone="neutral" detail="i18n:govoplan-ops.enabled_in_the_runtime_registry.d2c6142d" />
|
|
<MetricCard label="i18n:govoplan-ops.permissions.842c35eb" value={status?.governance.summary.permission_count ?? 0} tone="neutral" detail="i18n:govoplan-ops.declared_governance_permissions.d08d3bf1" />
|
|
<MetricCard label="i18n:govoplan-ops.policies.e7800f56" value={status?.governance.summary.policy_count ?? 0} tone="neutral" detail="i18n:govoplan-ops.registered_policy_capabilities.112a2b64" />
|
|
<MetricCard label="i18n:govoplan-ops.workers.b6ef3acd" value={knownMetric(status?.summary.worker_metrics.workers)} tone={runtimeWorkTone(status?.summary.worker_metrics.state ?? "unreachable")} detail={workerMetricDetail(status)} />
|
|
<MetricCard label="Queued tasks" value={queuedTasks ?? "unavailable"} tone={queuedTasks === null ? "neutral" : queuedTasks ? "warning" : "good"} detail={queuedTasks === null ? "Queue depth unavailable" : `${Object.values(queueDepths).filter((value) => typeof value === "number").length} measured queue(s)`} />
|
|
<MetricCard label="Storage" value={storageUsage === undefined ? status?.summary.file_storage_backend ?? "-" : `${storageUsage}%`} tone={storageUsage !== undefined && storageUsage >= 90 ? "danger" : storageUsage !== undefined && storageUsage >= 75 ? "warning" : "neutral"} detail={storageMetricDetail(status)} />
|
|
<MetricCard label="Backup evidence" value={status?.summary.backup_state ?? "unknown"} tone={status?.summary.backup_state === "ok" ? "good" : "warning"} detail="Latest coordinated backup and restore-drill evidence" />
|
|
<MetricCard label="Failed operations" value={status?.summary.failed_operation_count ?? 0} tone={status?.summary.failed_operation_count ? "danger" : "good"} detail="Terminal failures or manual intervention" />
|
|
<MetricCard label="Unknown outcomes" value={status?.summary.outcome_unknown_count ?? 0} tone={status?.summary.outcome_unknown_count ? "danger" : "good"} detail={`${status?.summary.active_operation_count ?? 0} active recovery-ledger operation(s)`} />
|
|
<MetricCard label="i18n:govoplan-ops.redis.5eaa1f2f" value={status?.summary.redis_url ? "configured" : "-"} tone={status?.summary.celery_enabled ? "info" : "neutral"} detail={status?.summary.redis_url ?? "-"} />
|
|
<MetricCard label="i18n:govoplan-ops.warnings.1430f976" value={warningCount + errorCount} tone={errorCount ? "danger" : warningCount ? "warning" : "good"} detail="i18n:govoplan-ops.current_health_checks.7830bccf" />
|
|
<MetricCard label="Runtime nodes" value={status?.summary.runtime_node_count ?? 0} tone={status?.runtime_cluster.available ? "good" : "danger"} detail={runtimeNodeMetricDetail(status)} />
|
|
<MetricCard label="Database capacity" value={databaseCapacityValue(status)} tone={databaseCapacityTone(status)} detail="Peak pooled connections / available connections" />
|
|
<MetricCard label="Recovery" value={status?.summary.recovery_required_count ?? 0} tone={status?.summary.recovery_required_count ? "danger" : "good"} detail="Operations requiring recovery attention" />
|
|
<MetricCard label="Provider bindings" value={status?.governance.summary.configured_external_provider_count ?? 0} tone={status?.governance.summary.provider_attention_count ? "warning" : "good"} detail={`${status?.governance.summary.provider_attention_count ?? 0} requiring attention`} />
|
|
<MetricCard label="i18n:govoplan-ops.infrastructure_capabilities" value={status?.summary.infrastructure_capability_count ?? 0} tone={status?.infrastructure.available ? "good" : "neutral"} detail={i18nMessage("i18n:govoplan-ops.pending_post_install_tasks", { value0: status?.summary.pending_post_install_task_count ?? 0 })} />
|
|
</MetricGrid>
|
|
|
|
<ContentGrid columns={2} collapseAt="workspace" className="">
|
|
<Card title="i18n:govoplan-ops.health_checks.201c869f">
|
|
<CheckList checks={checks} />
|
|
</Card>
|
|
|
|
<Card title="Runtime cluster">
|
|
<RuntimeNodeTable
|
|
nodes={status?.runtime_cluster.nodes ?? []}
|
|
canManage={canRunChecks}
|
|
busyNodeId={nodeActionId}
|
|
onDrain={setDrainTarget}
|
|
onCancelDrain={(node) => void cancelDrain(node)}
|
|
/>
|
|
</Card>
|
|
|
|
<Card title="Worker and queue readiness">
|
|
<RuntimeWorkTable items={status?.runtime_work ?? []} />
|
|
</Card>
|
|
|
|
<Card title="Recovery evidence">
|
|
<RecoveryTable operations={status?.runtime_cluster.recovery.operations ?? []} />
|
|
</Card>
|
|
|
|
<Card title="i18n:govoplan-ops.governance_inventory.835d8e57">
|
|
<GovernanceTable modules={status?.governance.modules ?? []} />
|
|
</Card>
|
|
|
|
<Card title="i18n:govoplan-ops.deployment_profiles.b0caa179">
|
|
<ProfileList profiles={status?.deployment_profiles ?? []} />
|
|
</Card>
|
|
|
|
<Card title="i18n:govoplan-ops.infrastructure_capabilities">
|
|
<InfrastructureCapabilityTable items={status?.infrastructure.capabilities ?? []} />
|
|
{(status?.infrastructure.post_install_tasks.length ?? 0) > 0 && <DescriptionList variant="inline">
|
|
{status?.infrastructure.post_install_tasks.map((task) => <div key={task.resume_key}>
|
|
<dt><StatusBadge status="warning" label={task.state} /></dt>
|
|
<dd><strong>{task.summary}</strong><span className="muted"> · {task.owner_module} · {task.required_inputs.join(", ")}</span></dd>
|
|
</div>)}
|
|
</DescriptionList>}
|
|
</Card>
|
|
|
|
<Card title="i18n:govoplan-ops.sizing_assumptions.6ade9a90">
|
|
<SizingTable items={status?.sizing ?? []} />
|
|
</Card>
|
|
</ContentGrid>
|
|
</LoadingFrame>
|
|
<ConfirmDialog
|
|
open={Boolean(drainTarget)}
|
|
title="i18n:govoplan-ops.surface.drain_node"
|
|
message={i18nMessage("i18n:govoplan-ops.drain_runtime_node_message", { value0: drainTarget?.node_id ?? "i18n:govoplan-ops.this_node" })}
|
|
confirmLabel="i18n:govoplan-ops.drain_node"
|
|
busy={Boolean(nodeActionId)}
|
|
onCancel={() => setDrainTarget(null)}
|
|
onConfirm={() => void confirmDrain()}
|
|
/>
|
|
</PageLayout>);
|
|
|
|
}
|
|
|
|
function RuntimeWorkTable({ items }: { items: OpsRuntimeWorkStatus[] }) {
|
|
const columns: DataGridColumn<OpsRuntimeWorkStatus>[] = [
|
|
{
|
|
id: "provider",
|
|
header: "Backend",
|
|
width: "minmax(200px, 1fr)",
|
|
minWidth: 180,
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (item) => `${item.label} ${item.backend}`,
|
|
render: (item) => <div><strong>{item.label}</strong><span className="muted block">{item.backend} · {item.provider_id}</span></div>
|
|
},
|
|
{
|
|
id: "state",
|
|
header: "State",
|
|
width: 150,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (item) => item.state,
|
|
render: (item) => <StatusBadge status={stateTone(item.state)} label={item.state} />
|
|
},
|
|
{
|
|
id: "activity",
|
|
header: "Activity",
|
|
width: "minmax(180px, .7fr)",
|
|
minWidth: 170,
|
|
value: (item) => `${item.active_workers ?? ""} ${item.active_work ?? ""} ${item.reserved_work ?? ""}`,
|
|
render: (item) => `${knownMetric(item.active_workers)} workers · ${knownMetric(item.active_work)} active · ${knownMetric(item.reserved_work)} reserved`
|
|
},
|
|
{
|
|
id: "queues",
|
|
header: "Queue depth",
|
|
width: "minmax(220px, 1fr)",
|
|
minWidth: 190,
|
|
resizable: true,
|
|
value: (item) => Object.entries(item.queue_depths).map(([queue, depth]) => `${queue}:${depth ?? "unavailable"}`).join(" "),
|
|
render: (item) => runtimeQueueSummary(item.queue_depths)
|
|
},
|
|
{
|
|
id: "heartbeat",
|
|
header: "Last heartbeat",
|
|
width: "minmax(180px, .7fr)",
|
|
minWidth: 170,
|
|
sortable: true,
|
|
value: (item) => item.last_heartbeat_at ?? "",
|
|
render: (item) => {
|
|
const age = heartbeatAgeSeconds(Date.now(), item.last_heartbeat_at);
|
|
return <div>{heartbeatAgeLabel(age)}<span className="muted block">stale after {item.stale_after_seconds ?? "unavailable"}s</span></div>;
|
|
}
|
|
},
|
|
{
|
|
id: "guidance",
|
|
header: "Guidance",
|
|
width: "minmax(260px, 1.3fr)",
|
|
minWidth: 220,
|
|
resizable: true,
|
|
value: (item) => `${item.detail} ${item.guidance}`,
|
|
render: (item) => <div>{item.detail}<span className="muted block">{item.guidance}</span></div>
|
|
}
|
|
];
|
|
return <DataGrid id="ops-runtime-work-status" rows={items} columns={columns} getRowKey={(item) => item.provider_id} emptyText="Worker and queue status unavailable." />;
|
|
}
|
|
|
|
function RuntimeNodeTable({
|
|
nodes,
|
|
canManage,
|
|
busyNodeId,
|
|
onDrain,
|
|
onCancelDrain
|
|
}: {
|
|
nodes: OpsRuntimeNode[];
|
|
canManage: boolean;
|
|
busyNodeId: string;
|
|
onDrain: (node: OpsRuntimeNode) => void;
|
|
onCancelDrain: (node: OpsRuntimeNode) => void;
|
|
}) {
|
|
const columns: DataGridColumn<OpsRuntimeNode>[] = [
|
|
{
|
|
id: "node",
|
|
header: "Node",
|
|
width: "minmax(220px, 1fr)",
|
|
minWidth: 200,
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (node) => `${node.node_id} ${node.role}`,
|
|
render: (node) => <div><strong>{node.node_id}</strong><span className="muted block">{node.role} · {node.software_version}</span></div>
|
|
},
|
|
{
|
|
id: "state",
|
|
header: "State",
|
|
width: 150,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (node) => node.stale ? "stale" : node.state,
|
|
render: (node) => <StatusBadge status={node.stale ? "error" : stateTone(node.state)} label={node.stale ? "stale" : node.state} />
|
|
},
|
|
{
|
|
id: "heartbeat",
|
|
header: "Last heartbeat",
|
|
width: "minmax(210px, .8fr)",
|
|
minWidth: 190,
|
|
resizable: true,
|
|
sortable: true,
|
|
value: (node) => node.last_heartbeat_at,
|
|
render: (node) => <div>{heartbeatAgeLabel(heartbeatAgeSeconds(Date.now(), node.last_heartbeat_at))}<span className="muted block">{new Date(node.last_heartbeat_at).toLocaleString()}</span></div>
|
|
},
|
|
{
|
|
id: "queues",
|
|
header: "Queues",
|
|
width: "minmax(180px, .7fr)",
|
|
minWidth: 160,
|
|
resizable: true,
|
|
filterable: true,
|
|
value: (node) => node.queues.join(" "),
|
|
render: (node) => node.queues.join(", ") || "-"
|
|
},
|
|
{
|
|
id: "actions",
|
|
header: "",
|
|
width: 72,
|
|
sticky: "end",
|
|
resizable: false,
|
|
align: "right",
|
|
value: (node) => node.state,
|
|
render: (node) => {
|
|
const busy = busyNodeId === node.node_id;
|
|
const cancelling = node.state === "draining";
|
|
const disabledReason = !canManage
|
|
? OPS_I18N.runPermissionRequired
|
|
: busy
|
|
? OPS_I18N.nodeActionActive
|
|
: node.state === "stopped"
|
|
? OPS_I18N.stoppedNode
|
|
: !cancelling && node.stale
|
|
? OPS_I18N.staleNode
|
|
: undefined;
|
|
return (
|
|
<TableActionGroup
|
|
minimumSlots={1}
|
|
label={i18nMessage("i18n:govoplan-ops.lifecycle_actions_for_value", { value0: node.node_id })}
|
|
actions={[{
|
|
id: cancelling ? "cancel-drain" : "drain",
|
|
label: cancelling
|
|
? i18nMessage("i18n:govoplan-ops.cancel_drain_for_value", { value0: node.node_id })
|
|
: i18nMessage("i18n:govoplan-ops.drain_value", { value0: node.node_id }),
|
|
icon: cancelling ? <PlayCircle size={16} /> : <PauseCircle size={16} />,
|
|
disabled: Boolean(disabledReason),
|
|
disabledReason,
|
|
onClick: () => cancelling ? onCancelDrain(node) : onDrain(node)
|
|
}]}
|
|
/>
|
|
);
|
|
}
|
|
}
|
|
];
|
|
return <DataGrid id="ops-runtime-nodes" rows={nodes} columns={columns} getRowKey={(node) => `${node.node_id}:${node.incarnation}`} emptyText="No runtime nodes reported." />;
|
|
}
|
|
|
|
function RecoveryTable({ operations }: { operations: OpsRecoveryOperation[] }) {
|
|
const columns: DataGridColumn<OpsRecoveryOperation>[] = [
|
|
{
|
|
id: "operation",
|
|
header: "Operation",
|
|
width: "minmax(240px, 1fr)",
|
|
minWidth: 220,
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (operation) => `${operation.module_id} ${operation.operation_type}`,
|
|
render: (operation) => <div><strong>{operation.operation_type}</strong><span className="muted block">{operation.module_id} · {operation.mode}</span></div>
|
|
},
|
|
{
|
|
id: "resource",
|
|
header: "Resource",
|
|
width: "minmax(180px, .8fr)",
|
|
minWidth: 160,
|
|
resizable: true,
|
|
filterable: true,
|
|
value: (operation) => `${operation.resource_type ?? ""} ${operation.resource_id ?? ""}`,
|
|
render: (operation) => operation.resource_type ? `${operation.resource_type}: ${operation.resource_id ?? "-"}` : "-"
|
|
},
|
|
{
|
|
id: "status",
|
|
header: "Status",
|
|
width: 170,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (operation) => operation.status,
|
|
render: (operation) => <StatusBadge status={recoveryTone(operation.status)} label={operation.status.replaceAll("_", " ")} />
|
|
},
|
|
{
|
|
id: "evidence",
|
|
header: "Evidence",
|
|
width: 150,
|
|
sortable: true,
|
|
value: (operation) => operation.checkpoint_count,
|
|
render: (operation) => `${operation.checkpoint_count} checkpoint(s)`
|
|
},
|
|
{
|
|
id: "updated",
|
|
header: "Updated",
|
|
width: "minmax(210px, .8fr)",
|
|
minWidth: 190,
|
|
resizable: true,
|
|
sortable: true,
|
|
value: (operation) => operation.updated_at,
|
|
render: (operation) => new Date(operation.updated_at).toLocaleString()
|
|
}
|
|
];
|
|
return <DataGrid id="ops-recovery-operations" rows={operations} columns={columns} getRowKey={(operation) => operation.id} emptyText="No recovery operations recorded." />;
|
|
}
|
|
|
|
function GovernanceTable({ modules }: { modules: OpsGovernanceModule[] }) {
|
|
const columns: DataGridColumn<OpsGovernanceModule>[] = [
|
|
{
|
|
id: "module",
|
|
header: "i18n:govoplan-ops.module.b8ff0289",
|
|
width: "minmax(200px, 1fr)",
|
|
minWidth: 180,
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (module) => `${module.name} ${module.module_id} ${module.version}`,
|
|
render: (module) => <div><strong>{module.name}</strong><span className="muted block">{module.module_id} · {module.version}</span></div>
|
|
},
|
|
{
|
|
id: "authority",
|
|
header: "i18n:govoplan-ops.authority.8802e425",
|
|
width: "minmax(190px, .8fr)",
|
|
minWidth: 170,
|
|
resizable: true,
|
|
value: (module) => `${module.permission_count} ${module.role_template_count}`,
|
|
render: (module) => `${module.permission_count} permissions · ${module.role_template_count} roles`
|
|
},
|
|
{
|
|
id: "contracts",
|
|
header: "i18n:govoplan-ops.contracts.57d80902",
|
|
width: "minmax(190px, .8fr)",
|
|
minWidth: 170,
|
|
resizable: true,
|
|
value: (module) => `${module.capability_count} ${module.policy_count}`,
|
|
render: (module) => `${module.capability_count} capabilities · ${module.policy_count} policies`
|
|
},
|
|
{
|
|
id: "architecture",
|
|
header: "Architecture",
|
|
width: "minmax(210px, .9fr)",
|
|
minWidth: 190,
|
|
resizable: true,
|
|
sortable: true,
|
|
filterable: true,
|
|
value: (module) => module.architecture
|
|
? `${module.architecture.layer} ${module.architecture.kind} ${module.architecture.maturity}`
|
|
: "undeclared",
|
|
render: (module) => module.architecture ? (
|
|
<div>
|
|
<strong>{module.architecture.maturity}</strong>
|
|
<span className="muted block">{module.architecture.layer} · {module.architecture.kind}</span>
|
|
{module.architecture.known_limits.length ? <span className="muted block">{module.architecture.known_limits.length} known limit(s)</span> : null}
|
|
</div>
|
|
) : <span className="muted">staged declaration pending</span>
|
|
},
|
|
{
|
|
id: "providers",
|
|
header: "External providers",
|
|
width: "minmax(230px, 1fr)",
|
|
minWidth: 210,
|
|
resizable: true,
|
|
filterable: true,
|
|
value: (module) => `${module.external_provider_count} ${module.external_providers.map((provider) => `${provider.id} ${provider.maturity} ${provider.authority_modes.join(" ")} ${provider.runtime_state?.health ?? "unobserved"} ${provider.runtime_state?.freshness ?? ""}`).join(" ")}`,
|
|
render: (module) => module.external_provider_count ? (
|
|
<div>
|
|
<strong>{module.external_provider_count} declared</strong>
|
|
{module.external_providers.map((provider) => (
|
|
<span className="muted block" key={provider.id}>
|
|
{provider.label}: {provider.maturity} · {provider.runtime_state ? `${provider.runtime_state.health}/${provider.runtime_state.freshness} · ${provider.runtime_state.bindings?.length ?? 0} binding(s)` : "unobserved"}
|
|
</span>
|
|
))}
|
|
</div>
|
|
) : <span className="muted">none declared</span>
|
|
},
|
|
{
|
|
id: "controls",
|
|
header: "i18n:govoplan-ops.controls.0cdb80fb",
|
|
width: "minmax(190px, .8fr)",
|
|
minWidth: 170,
|
|
resizable: true,
|
|
value: (module) => `${module.access_control_count} ${module.search_provider_count}`,
|
|
render: (module) => `${module.access_control_count} access · ${module.search_provider_count} search`
|
|
},
|
|
{
|
|
id: "evidence",
|
|
header: "i18n:govoplan-ops.evidence.7ea014de",
|
|
width: "minmax(190px, .8fr)",
|
|
minWidth: 170,
|
|
resizable: true,
|
|
value: (module) => `${module.documentation_count} ${module.documentation_provider_count} ${module.migration_managed}`,
|
|
render: (module) => (
|
|
<div>
|
|
{module.documentation_count + module.documentation_provider_count} docs
|
|
<span className="muted block">{module.migration_managed ? "migration managed" : "no module migrations"}</span>
|
|
</div>
|
|
)
|
|
}
|
|
];
|
|
return (
|
|
<DataGrid
|
|
id="ops-governance-inventory"
|
|
rows={modules}
|
|
columns={columns}
|
|
getRowKey={(module) => module.module_id}
|
|
emptyText="i18n:govoplan-ops.no_modules_reported.847f06d9"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function CheckList({ checks }: {checks: OpsCheck[];}) {
|
|
if (!checks.length) return <p className="muted">i18n:govoplan-ops.no_health_checks_reported.03c067c4</p>;
|
|
return (
|
|
<DescriptionList variant="inline">
|
|
{checks.map((check) =>
|
|
<div key={check.id}>
|
|
<dt><StatusBadge status={stateTone(check.state)} label={check.state} /></dt>
|
|
<dd><strong>{check.label}</strong><span className="muted"> · {check.detail}</span></dd>
|
|
</div>
|
|
)}
|
|
</DescriptionList>);
|
|
|
|
}
|
|
|
|
function ProfileList({ profiles }: {profiles: OpsDeploymentProfile[];}) {
|
|
if (!profiles.length) return <p className="muted">i18n:govoplan-ops.no_deployment_profiles_reported.7c3af1db</p>;
|
|
const columns: DataGridColumn<OpsDeploymentProfile>[] = [
|
|
{ id: "profile", header: "i18n:govoplan-ops.profile.ff4fc027", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (profile) => `${profile.name} ${profile.id}`, render: (profile) => <div><strong>{profile.name}</strong><span className="muted block">{profile.id}</span></div> },
|
|
{ id: "status", header: "i18n:govoplan-ops.status.bae7d5be", width: 140, sortable: true, filterable: true, value: (profile) => profile.current ? "current" : "reference", render: (profile) => <StatusBadge status={profile.current ? "success" : "inactive"} label={profile.current ? "current" : "reference"} /> },
|
|
{ id: "components", header: "i18n:govoplan-ops.components.9289473e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (profile) => profile.components.join(" "), render: (profile) => profile.components.join(", ") },
|
|
{ id: "fit", header: "i18n:govoplan-ops.fit.dab564d8", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (profile) => profile.fit }
|
|
];
|
|
return (
|
|
<DataGrid id="ops-deployment-profiles" rows={profiles} columns={columns} getRowKey={(profile) => profile.id} />);
|
|
|
|
}
|
|
|
|
function SizingTable({ items }: {items: OpsSizingAssumption[];}) {
|
|
if (!items.length) return <p className="muted">i18n:govoplan-ops.no_sizing_assumptions_reported.17515959</p>;
|
|
const columns: DataGridColumn<OpsSizingAssumption>[] = [
|
|
{ id: "area", header: "i18n:govoplan-ops.area.2745deba", width: "minmax(180px, .7fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (item) => item.area, render: (item) => <strong>{item.area}</strong> },
|
|
{ id: "baseline", header: "i18n:govoplan-ops.baseline.e6ab7982", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.baseline },
|
|
{ id: "trigger", header: "i18n:govoplan-ops.scale_trigger.1c85e10e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.scale_trigger },
|
|
{ id: "note", header: "i18n:govoplan-ops.operator_note.1dc58f7b", width: "minmax(240px, 1.2fr)", minWidth: 200, resizable: true, filterable: true, value: (item) => item.operator_note }
|
|
];
|
|
return (
|
|
<DataGrid id="ops-sizing-assumptions" rows={items} columns={columns} getRowKey={(item) => item.area} />);
|
|
|
|
}
|
|
|
|
function InfrastructureCapabilityTable({ items }: {items: OpsInfrastructureCapability[];}) {
|
|
if (!items.length) return <p className="muted">i18n:govoplan-ops.no_infrastructure_capabilities</p>;
|
|
const columns: DataGridColumn<OpsInfrastructureCapability>[] = [
|
|
{ id: "capability", header: "i18n:govoplan-ops.capability", width: "minmax(220px, 1fr)", minWidth: 190, resizable: true, sortable: true, filterable: true, value: (item) => `${item.label} ${item.id}`, render: (item) => <div><strong>{item.label}</strong><span className="muted block">{item.id}</span></div> },
|
|
{ id: "state", header: "i18n:govoplan-ops.status.bae7d5be", width: 190, sortable: true, filterable: true, value: (item) => item.state, render: (item) => <StatusBadge status={capabilityTone(item.state)} label={item.state.replaceAll("_", " ")} /> },
|
|
{ id: "source", header: "i18n:govoplan-ops.source", width: "minmax(180px, .7fr)", minWidth: 160, resizable: true, filterable: true, value: (item) => item.source },
|
|
{ id: "endpoint", header: "i18n:govoplan-ops.endpoint", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => endpointLabel(item.endpoint) },
|
|
{ id: "consumers", header: "i18n:govoplan-ops.consumers", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.dependent_modules.join(" "), render: (item) => item.dependent_modules.join(", ") || "-" }
|
|
];
|
|
return <DataGrid id="ops-infrastructure-capabilities" rows={items} columns={columns} getRowKey={(item) => item.id} />;
|
|
}
|
|
|
|
function endpointLabel(endpoint: OpsInfrastructureCapability["endpoint"]): string {
|
|
if (typeof endpoint.host === "string") {
|
|
const scheme = typeof endpoint.scheme === "string" ? `${endpoint.scheme}://` : "";
|
|
const port = typeof endpoint.port === "number" ? `:${endpoint.port}` : "";
|
|
return `${scheme}${endpoint.host}${port}`;
|
|
}
|
|
return typeof endpoint.reference === "string" ? endpoint.reference : "-";
|
|
}
|
|
|
|
function capabilityTone(state: OpsInfrastructureCapability["state"]): string {
|
|
if (state === "configured" || state === "externally_supplied") return "success";
|
|
if (state === "available_unconfigured") return "warning";
|
|
return "inactive";
|
|
}
|
|
|
|
function stateTone(state: string): string {
|
|
if (["ok", "healthy", "idle"].includes(state)) return "success";
|
|
if (state === "busy") return "info";
|
|
if (["warning", "starting", "degraded", "unconfigured"].includes(state)) return "warning";
|
|
if (["error", "stale", "unreachable"].includes(state)) return "error";
|
|
return "inactive";
|
|
}
|
|
|
|
function recoveryTone(state: string): string {
|
|
if (["succeeded", "recovered"].includes(state)) return "success";
|
|
if (state === "rejected") return "warning";
|
|
if (["failed", "recovery_required"].includes(state)) return "error";
|
|
if (["running", "recovering", "prepared"].includes(state)) return "warning";
|
|
return "inactive";
|
|
}
|
|
|
|
function runtimeNodeMetricDetail(status: OpsStatus | null): string {
|
|
const cluster = status?.runtime_cluster;
|
|
if (!cluster?.available) return cluster?.detail ?? "Runtime directory unavailable";
|
|
const active = cluster.active ?? { api: 0, worker: 0 };
|
|
const expected = cluster.expected ?? { api: 0, worker: 0 };
|
|
return `${active.api}/${expected.api} API · ${active.worker}/${expected.worker} workers`;
|
|
}
|
|
|
|
function databaseCapacityValue(status: OpsStatus | null): string {
|
|
const peak = status?.summary.database_connection_peak;
|
|
const available = status?.summary.database_connection_available;
|
|
return typeof peak === "number" && typeof available === "number"
|
|
? `${peak} / ${available}`
|
|
: "not declared";
|
|
}
|
|
|
|
function databaseCapacityTone(status: OpsStatus | null): "good" | "warning" | "danger" {
|
|
const peak = status?.summary.database_connection_peak;
|
|
const available = status?.summary.database_connection_available;
|
|
if (typeof peak !== "number" || typeof available !== "number") {
|
|
return status?.runtime_cluster.state_profile === "shared" ? "danger" : "warning";
|
|
}
|
|
if (peak > available) return "danger";
|
|
return peak / available >= 0.8 ? "warning" : "good";
|
|
}
|
|
|
|
function workerMetricDetail(status: OpsStatus | null): string {
|
|
if (!status) return "Worker status unavailable";
|
|
const metrics = status.summary.worker_metrics;
|
|
if (metrics.state === "disabled") return "Workers intentionally disabled";
|
|
const queued = knownQueueDepthTotal(metrics.queue_depths ?? {});
|
|
return `${knownMetric(metrics.active_tasks)} active · ${knownMetric(metrics.reserved_tasks)} reserved · ${queued ?? "unavailable"} queued`;
|
|
}
|
|
|
|
function runtimeQueueSummary(depths: Record<string, number | null>): string {
|
|
const entries = Object.entries(depths);
|
|
if (!entries.length) return "unavailable";
|
|
return entries.map(([queue, depth]) => `${queue}: ${depth ?? "unavailable"}`).join(" · ");
|
|
}
|
|
|
|
function storageMetricDetail(status: OpsStatus | null): string {
|
|
const metrics = status?.summary.storage_metrics;
|
|
if (!metrics?.capacity_observable) {
|
|
return `${status?.summary.file_storage_backend ?? "Storage"} capacity is provider-managed or unavailable`;
|
|
}
|
|
return `${formatBytes(metrics.capacity_used_bytes ?? 0)} used of ${formatBytes(metrics.capacity_total_bytes ?? 0)}`;
|
|
}
|
|
|
|
function formatBytes(value: number): string {
|
|
if (!Number.isFinite(value) || value <= 0) return "0 B";
|
|
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1);
|
|
return `${(value / 1024 ** index).toFixed(index ? 1 : 0)} ${units[index]}`;
|
|
}
|