feat(web): add overview system module foundation

This commit is contained in:
chick
2026-07-17 19:40:13 +08:00
parent be1741c2f8
commit 4a595540e9
7 changed files with 1106 additions and 6 deletions
+185
View File
@@ -0,0 +1,185 @@
import { useEffect, useRef, useState } from 'react';
import type { InstanceContext } from '../app-shell.js';
export type OverviewFieldValue = string | number | boolean | null;
export type OverviewSection = Readonly<Record<string, OverviewFieldValue>>;
export interface OverviewSnapshot {
readonly observedAt?: string;
readonly device: OverviewSection;
readonly sim: OverviewSection;
readonly network: OverviewSection;
readonly stats: OverviewSection;
readonly cpu: OverviewSection;
readonly connectivity: OverviewSection;
}
export interface OverviewDataSource {
/** Implementations are injected by the application owner; this component has no production endpoint. */
load(instanceId: string, signal: AbortSignal): Promise<OverviewSnapshot>;
}
export interface OverviewSystemPageProps {
readonly instance: InstanceContext;
readonly dataSource?: OverviewDataSource;
/** Change this value when an external owner has requested a refresh. */
readonly refreshSignal?: unknown;
}
type ReadState =
| { kind: 'idle' }
| { kind: 'loading'; snapshot?: OverviewSnapshot }
| { kind: 'ready'; snapshot: OverviewSnapshot }
| { kind: 'error'; message: string; snapshot?: OverviewSnapshot };
const SECTIONS = [
['device', 'Device'],
['sim', 'SIM'],
['network', 'Network'],
['stats', 'Statistics'],
['cpu', 'CPU'],
['connectivity', 'Connectivity'],
] as const;
function StructuredSection({ label, values }: { label: string; values: OverviewSection }) {
const entries = Object.entries(values);
return (
<section className="overview-card" aria-label={label}>
<h2>{label}</h2>
{entries.length ? (
<dl>
{entries.map(([key, value]) => (
<div key={key}>
<dt>{key}</dt>
<dd>{value == null ? 'Unavailable' : String(value)}</dd>
</div>
))}
</dl>
) : (
<p>No {label.toLocaleLowerCase()} data was supplied.</p>
)}
</section>
);
}
export function OverviewSystemPage({
instance,
dataSource,
refreshSignal,
}: OverviewSystemPageProps) {
const requestOwner = useRef(0);
const [retry, setRetry] = useState(0);
const [state, setState] = useState<ReadState>({ kind: 'idle' });
useEffect(() => {
const request = ++requestOwner.current;
const controller = new AbortController();
if (instance.authentication === 'auth-required') {
setState({ kind: 'idle' });
return () => controller.abort();
}
if (!dataSource) {
setState({ kind: 'idle' });
return () => controller.abort();
}
setState((current) => ({
kind: 'loading',
...(current.kind === 'ready' || current.kind === 'error'
? current.snapshot
? { snapshot: current.snapshot }
: {}
: {}),
}));
void dataSource.load(instance.id, controller.signal).then(
(snapshot) => {
if (request === requestOwner.current && !controller.signal.aborted)
setState({ kind: 'ready', snapshot });
},
(reason: unknown) => {
void reason;
if (request === requestOwner.current && !controller.signal.aborted)
setState((current) => ({
kind: 'error',
message: 'Overview data could not be loaded.',
...(current.kind === 'loading' && current.snapshot
? { snapshot: current.snapshot }
: {}),
}));
},
);
return () => controller.abort();
}, [dataSource, instance.authentication, instance.id, refreshSignal, retry]);
if (instance.authentication === 'auth-required')
return (
<div className="state-panel state-error" role="alert">
Authentication is required before overview data can be read for this instance.
</div>
);
if (!dataSource)
return (
<div className="state-panel" role="status" aria-label="Overview unavailable">
No safe overview read data source is available. This console will not invent or call an
uncontracted production endpoint.
</div>
);
const retainedSnapshot =
state.kind === 'loading' || state.kind === 'error' ? state.snapshot : undefined;
if ((state.kind === 'loading' || state.kind === 'idle') && !retainedSnapshot)
return (
<p role="status" aria-label="Overview loading status">
Loading overview
</p>
);
if (state.kind === 'error' && !state.snapshot)
return (
<div className="state-panel state-error" role="alert">
<p>Unable to load overview: {state.message}</p>
<button type="button" onClick={() => setRetry((value) => value + 1)}>
Retry loading overview
</button>
</div>
);
const snapshot = state.kind === 'ready' ? state.snapshot : retainedSnapshot;
if (!snapshot) return null;
return (
<div className="overview-system">
{state.kind === 'loading' ? <p role="status">Refreshing overview</p> : null}
{state.kind === 'error' ? (
<div className="state-panel state-error" role="alert">
<p>Refresh failed; showing the last known overview.</p>
<button type="button" onClick={() => setRetry((value) => value + 1)}>
Retry loading overview
</button>
</div>
) : null}
{instance.freshness !== 'fresh' ? (
<p className="state-panel" role="status" aria-label="Overview freshness">
Overview data is {instance.freshness}; verify freshness before relying on these values.
</p>
) : null}
{snapshot.observedAt ? <p>Observed {snapshot.observedAt}</p> : null}
<div className="overview-grid">
{SECTIONS.map(([key, label]) => (
<StructuredSection key={key} label={label} values={snapshot[key]} />
))}
</div>
<section className="state-panel" aria-label="System actions">
<h2>System actions</h2>
<p>
Restart is unavailable because the backend R3 restart operation is not implemented. No
action is offered.
</p>
</section>
</div>
);
}