chore: sync GovOPlaN module split state
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
DismissibleAlert,
|
||||
LoadingFrame,
|
||||
MetricCard,
|
||||
PageTitle,
|
||||
StatusBadge,
|
||||
adminErrorMessage,
|
||||
type ApiSettings } from
|
||||
"@govoplan/core-webui";
|
||||
import { fetchOpsStatus, type OpsCheck, type OpsDeploymentProfile, type OpsSizingAssumption, type OpsStatus } from "../../api/ops";
|
||||
|
||||
export default function OpsPage({ settings }: {settings: ApiSettings;}) {
|
||||
const [status, setStatus] = useState<OpsStatus | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
setStatus(await fetchOpsStatus(settings));
|
||||
} catch (err) {
|
||||
setError(adminErrorMessage(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
return (
|
||||
<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">
|
||||
<Button onClick={() => void load()} disabled={loading}><RefreshCw size={16} /> i18n:govoplan-ops.reload.cce71553</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
|
||||
|
||||
<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.workers.b6ef3acd" value={status?.summary.celery_enabled ? "split" : "off"} tone={status?.summary.celery_enabled ? "good" : "warning"} detail={status?.summary.celery_queues?.length ? status.summary.celery_queues.join(", ") : "i18n:govoplan-ops.celery_worker_setting.323d7737"} />
|
||||
<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" />
|
||||
</div>
|
||||
|
||||
<div className="dashboard-grid">
|
||||
<Card title="i18n:govoplan-ops.health_checks.201c869f">
|
||||
<CheckList checks={checks} />
|
||||
</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>
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
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>;
|
||||
return (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr><th>i18n:govoplan-ops.profile.ff4fc027</th><th>i18n:govoplan-ops.status.bae7d5be</th><th>i18n:govoplan-ops.components.9289473e</th><th>i18n:govoplan-ops.fit.dab564d8</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{profiles.map((profile) =>
|
||||
<tr key={profile.id}>
|
||||
<td><strong>{profile.name}</strong><span className="muted block">{profile.id}</span></td>
|
||||
<td><StatusBadge status={profile.current ? "success" : "inactive"} label={profile.current ? "current" : "reference"} /></td>
|
||||
<td>{profile.components.join(", ")}</td>
|
||||
<td>{profile.fit}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
function SizingTable({ items }: {items: OpsSizingAssumption[];}) {
|
||||
if (!items.length) return <p className="muted">i18n:govoplan-ops.no_sizing_assumptions_reported.17515959</p>;
|
||||
return (
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr><th>i18n:govoplan-ops.area.2745deba</th><th>i18n:govoplan-ops.baseline.e6ab7982</th><th>i18n:govoplan-ops.scale_trigger.1c85e10e</th><th>i18n:govoplan-ops.operator_note.1dc58f7b</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item) =>
|
||||
<tr key={item.area}>
|
||||
<td><strong>{item.area}</strong></td>
|
||||
<td>{item.baseline}</td>
|
||||
<td>{item.scale_trigger}</td>
|
||||
<td>{item.operator_note}</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>);
|
||||
|
||||
}
|
||||
|
||||
function stateTone(state: string): string {
|
||||
if (state === "ok") return "success";
|
||||
if (state === "warning") return "warning";
|
||||
if (state === "error") return "error";
|
||||
return "inactive";
|
||||
}
|
||||
Reference in New Issue
Block a user