Files
govoplan-ops/webui/src/features/ops/OpsPage.tsx
T
2026-08-03 15:18:32 +02:00

573 lines
26 KiB
TypeScript

import { useEffect, useState } from "react";
import { PauseCircle, PlayCircle, RefreshCw } from "lucide-react";
import {
ActionBlockerHint,
Button,
Card,
ConfirmDialog,
DataGrid,
DismissibleAlert,
DocumentationHelpLink,
LoadingFrame,
MetricCard,
PageScrollViewport,
PageTitle,
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 OpsRecoveryOperation,
type OpsRuntimeNode,
type OpsSizingAssumption,
type OpsStatus
} from "../../api/ops";
import {
OPS_DOCUMENTATION,
OPS_I18N,
OPS_RECOVERY_DOCUMENTATION,
} from "./interfacePatterns";
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("");
async function load() {
setLoading(true);
setError("");
try {
setStatus(await fetchOpsStatus(settings));
} catch (err) {
setError(adminErrorMessage(err));
} finally {
setLoading(false);
}
}
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(() => {void load();}, [settings.apiBaseUrl, settings.apiKey, settings.accessToken]);
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 = Object.values(queueDepths).reduce((total, value) => total + value, 0);
const storageUsage = status?.summary.storage_metrics?.capacity_used_percent;
return (
<PageScrollViewport>
<div className="content-pad workspace-data-page">
<div className="page-heading split workspace-heading">
<div>
<PageTitle loading={loading}>i18n:govoplan-ops.ops.907a54c2</PageTitle>
<p>i18n:govoplan-ops.runtime_health_deployment_profile_worker_split_a.55340156</p>
</div>
<div className="button-row compact-actions">
<DocumentationHelpLink reference={OPS_DOCUMENTATION} />
<Button variant="primary" onClick={() => void runChecks()} disabled={Boolean(runProbesDisabledReason)} disabledReason={runProbesDisabledReason}>i18n:govoplan-ops.surface.run_probes</Button>
<Button onClick={() => void load()} disabled={loading} disabledReason={loading ? OPS_I18N.loading : undefined}><RefreshCw size={16} /> i18n:govoplan-ops.reload.cce71553</Button>
</div>
</div>
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
{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">
<div className="metric-grid">
<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={status?.summary.celery_enabled ? status.summary.worker_metrics.workers ?? 0 : "off"} tone={status?.summary.celery_enabled && !(status.summary.worker_metrics.missing_queues?.length) ? "good" : "warning"} detail={workerMetricDetail(status)} />
<MetricCard label="Queued tasks" value={queuedTasks} tone={queuedTasks ? "warning" : "good"} detail={Object.keys(queueDepths).length ? `${Object.keys(queueDepths).length} measured queue(s)` : "Queue depth unavailable"} />
<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`} />
</div>
<div className="dashboard-grid">
<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="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.sizing_assumptions.6ade9a90">
<SizingTable items={status?.sizing ?? []} />
</Card>
</div>
</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()}
/>
</div>
</PageScrollViewport>);
}
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) => new Date(node.last_heartbeat_at).toLocaleString()
},
{
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: 56,
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 (
<dl className="detail-list">
{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>
)}
</dl>);
}
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 stateTone(state: string): string {
if (state === "ok") return "success";
if (state === "warning") return "warning";
if (state === "error") 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?.summary.celery_enabled) return "i18n:govoplan-ops.celery_worker_setting.323d7737";
const metrics = status.summary.worker_metrics;
if (metrics.missing_queues?.length) return `Missing queues: ${metrics.missing_queues.join(", ")}`;
const queued = Object.values(metrics.queue_depths ?? {}).reduce((total, value) => total + value, 0);
return `${metrics.active_tasks ?? 0} active · ${queued} queued`;
}
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]}`;
}