Add runtime environment readiness checks

This commit is contained in:
2026-06-30 14:28:49 +08:00
parent 442b521705
commit d9ea249ff0
12 changed files with 603 additions and 18 deletions

View File

@@ -157,6 +157,42 @@ type GpuPayload = {
}>;
};
type RuntimeCheck = {
module: string;
package?: string;
passed: boolean;
version?: string | null;
error?: string;
};
type RuntimeEnv = {
role: string;
name: string;
label: string;
env_file: string;
path?: string;
exists: boolean;
passed: boolean;
checks: RuntimeCheck[];
extra: Record<string, unknown>;
};
type RuntimeReadinessPayload = {
available: boolean;
passed: boolean;
generated_at: string;
cached: boolean;
cache_seconds: number;
envs: RuntimeEnv[];
specs: {
bootstrap_script: string;
verify_script: string;
env_files: string[];
task_default: string;
mmseg_default: string;
};
};
async function api<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
headers: { "Content-Type": "application/json" },
@@ -229,13 +265,15 @@ function useData() {
const [coverage, setCoverage] = useState<CoveragePayload | null>(null);
const [acceptance, setAcceptance] = useState<AcceptancePayload | null>(null);
const [deepAcceptance, setDeepAcceptance] = useState<DeepAcceptancePayload | null>(null);
const [runtimeReadiness, setRuntimeReadiness] = useState<RuntimeReadinessPayload | null>(null);
const [error, setError] = useState<string>("");
async function refresh() {
try {
const [catalogNext, gpusNext, jobsNext, resultsNext, curvesNext, datasetsNext, coverageNext, acceptanceNext, deepAcceptanceNext] = await Promise.all([
const [catalogNext, gpusNext, readinessNext, jobsNext, resultsNext, curvesNext, datasetsNext, coverageNext, acceptanceNext, deepAcceptanceNext] = await Promise.all([
api<Catalog>("/api/catalog"),
api<GpuPayload>("/api/system/gpus"),
api<RuntimeReadinessPayload>("/api/system/readiness"),
api<Job[]>("/api/jobs"),
api<ResultItem[]>("/api/results"),
api<TrainingCurve[]>("/api/results/curves"),
@@ -246,6 +284,7 @@ function useData() {
]);
setCatalog(catalogNext);
setGpus(gpusNext);
setRuntimeReadiness(readinessNext);
setJobs(jobsNext);
setResults(resultsNext.slice(0, 80));
setCurves(curvesNext.slice(0, 12));
@@ -277,7 +316,7 @@ function useData() {
return () => window.clearInterval(timer);
}, []);
return { catalog, gpus, jobs, results, curves, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh };
return { catalog, gpus, runtimeReadiness, jobs, results, curves, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh };
}
function StatusPill({ status }: { status: string }) {
@@ -300,7 +339,7 @@ function JobProgressBar({ progress }: { progress?: JobProgress }) {
}
function App() {
const { catalog, gpus, jobs, results, curves, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh } = useData();
const { catalog, gpus, runtimeReadiness, jobs, results, curves, datasets, datasetValidations, coverage, acceptance, deepAcceptance, error, refresh } = useData();
const [taskType, setTaskType] = useState("mock.echo");
const [params, setParams] = useState(JSON.stringify(defaultParams["mock.echo"], null, 2));
const [selectedJob, setSelectedJob] = useState<Job | null>(null);
@@ -492,6 +531,7 @@ function App() {
<a href="#jobs"><Terminal size={18} /></a>
<a href="#datasets"><Boxes size={18} /></a>
<a href="#gpu"><Cpu size={18} />GPU</a>
<a href="#runtime"><ShieldCheck size={18} /></a>
<a href="#coverage"><ClipboardCheck size={18} /></a>
<a href="#weights"><HardDrive size={18} /></a>
<a href="#results"><BarChart3 size={18} /></a>
@@ -768,7 +808,7 @@ function App() {
</div>
</section>
<section className="grid three">
<section className="grid four">
<div className="panel" id="gpu">
<div className="panelHead">
<div>
@@ -789,6 +829,37 @@ function App() {
))}
</div>
<div className="panel" id="runtime">
<div className="panelHead">
<div>
<p className="eyebrow">Runtime</p>
<h2></h2>
</div>
<ShieldCheck size={22} />
</div>
<div className="envList">
{(runtimeReadiness?.envs ?? []).map((env) => (
<div key={env.role} className={`envCard ${env.passed ? "ok" : "bad"}`}>
<div className="envHead">
<div>
<strong>{env.name}</strong>
<small>{env.label}</small>
</div>
<span>{env.passed ? "READY" : env.exists ? "CHECK" : "MISSING"}</span>
</div>
<div className="envChecks">
{env.checks.slice(0, 8).map((check) => (
<span key={check.module} className={check.passed ? "ok" : "bad"} title={check.error ?? check.package}>
{check.module}{check.version ? ` ${check.version}` : ""}
</span>
))}
</div>
</div>
))}
</div>
<p className="muted">{runtimeReadiness?.passed ? "runtime imports ready" : "run scripts/bootstrap_conda_envs.sh"} · {runtimeReadiness?.generated_at ?? "not checked"}</p>
</div>
<div className="panel" id="weights">
<div className="panelHead">
<div>