feat(ops): show deployment capability receipt

This commit is contained in:
2026-08-07 01:59:57 +02:00
parent 71666db45c
commit d26a601dda
7 changed files with 389 additions and 1 deletions
+34
View File
@@ -17,6 +17,28 @@ export type OpsDeploymentProfile = {
fit: string;
};
export type OpsInfrastructureCapability = {
id: string;
label: string;
state: "configured" | "available_unconfigured" | "externally_supplied" | "unavailable";
source: string;
detail: string;
endpoint: Record<string, string | number | boolean | null>;
secret_refs: string[];
dependent_modules: string[];
};
export type OpsPostInstallTask = {
id: string;
resume_key: string;
capability_id: string;
state: string;
owner_module: string;
summary: string;
required_inputs: string[];
secret_boundary: string;
};
export type OpsSizingAssumption = {
area: string;
baseline: string;
@@ -186,6 +208,8 @@ export type OpsStatus = {
failed_operation_count?: number;
outcome_unknown_count?: number;
active_operation_count?: number;
infrastructure_capability_count?: number;
pending_post_install_task_count?: number;
};
readiness: {
ready: boolean;
@@ -220,6 +244,16 @@ export type OpsStatus = {
deployment_profiles: OpsDeploymentProfile[];
sizing: OpsSizingAssumption[];
runtime_cluster: OpsRuntimeCluster;
infrastructure: {
configured: boolean;
available: boolean;
schema_version?: number | null;
installation_id?: string | null;
profile?: string | null;
capabilities: OpsInfrastructureCapability[];
post_install_tasks: OpsPostInstallTask[];
error?: string | null;
};
};
export function fetchOpsStatus(settings: ApiSettings): Promise<OpsStatus> {
+39
View File
@@ -29,6 +29,7 @@ import {
type OpsCheck,
type OpsDeploymentProfile,
type OpsGovernanceModule,
type OpsInfrastructureCapability,
type OpsRecoveryOperation,
type OpsRuntimeNode,
type OpsSizingAssumption,
@@ -168,6 +169,7 @@ export default function OpsPage({ settings, auth }: {settings: ApiSettings;auth:
<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 })} />
</div>
<div className="dashboard-grid">
@@ -197,6 +199,16 @@ export default function OpsPage({ settings, auth }: {settings: ApiSettings;auth:
<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 && <dl className="detail-list">
{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>)}
</dl>}
</Card>
<Card title="i18n:govoplan-ops.sizing_assumptions.6ade9a90">
<SizingTable items={status?.sizing ?? []} />
</Card>
@@ -507,6 +519,33 @@ function SizingTable({ items }: {items: OpsSizingAssumption[];}) {
}
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 (state === "ok") return "success";
if (state === "warning") return "warning";
+14
View File
@@ -11,6 +11,13 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-ops.surface.governance": "Governance inventory",
"i18n:govoplan-ops.surface.deployment": "Deployment profiles",
"i18n:govoplan-ops.surface.sizing": "Sizing assumptions",
"i18n:govoplan-ops.infrastructure_capabilities": "Infrastructure capabilities",
"i18n:govoplan-ops.pending_post_install_tasks": "{value0} pending post-install task(s)",
"i18n:govoplan-ops.no_infrastructure_capabilities": "No deployment capability receipt is available.",
"i18n:govoplan-ops.capability": "Capability",
"i18n:govoplan-ops.source": "Source",
"i18n:govoplan-ops.endpoint": "Endpoint",
"i18n:govoplan-ops.consumers": "Consumers",
"i18n:govoplan-ops.surface.run_probes": "Run operational probes",
"i18n:govoplan-ops.surface.drain_node": "Drain runtime node",
"i18n:govoplan-ops.reason.loading": "Operations status is loading.",
@@ -80,6 +87,13 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-ops.surface.governance": "Governance-Inventar",
"i18n:govoplan-ops.surface.deployment": "Bereitstellungsprofile",
"i18n:govoplan-ops.surface.sizing": "Dimensionierungsannahmen",
"i18n:govoplan-ops.infrastructure_capabilities": "Infrastruktur-Fähigkeiten",
"i18n:govoplan-ops.pending_post_install_tasks": "{value0} ausstehende Nachinstallationsaufgabe(n)",
"i18n:govoplan-ops.no_infrastructure_capabilities": "Es ist kein Bereitstellungsnachweis für Infrastruktur-Fähigkeiten verfügbar.",
"i18n:govoplan-ops.capability": "Fähigkeit",
"i18n:govoplan-ops.source": "Quelle",
"i18n:govoplan-ops.endpoint": "Endpunkt",
"i18n:govoplan-ops.consumers": "Verwendende Module",
"i18n:govoplan-ops.surface.run_probes": "Betriebsprüfungen ausführen",
"i18n:govoplan-ops.surface.drain_node": "Laufzeitknoten leeren",
"i18n:govoplan-ops.reason.loading": "Der Betriebsstatus wird geladen.",