feat(web): redesign fleet cards and instance workspace

This commit is contained in:
chick
2026-07-19 18:54:26 +08:00
parent f2803896a8
commit 49ee4e6570
9 changed files with 458 additions and 74 deletions
+62 -1
View File
@@ -78,6 +78,13 @@ export interface InstanceContext {
status: 'online' | 'offline' | 'auth-required' | 'degraded' | 'unknown';
authentication: 'authenticated' | 'auth-required' | 'unknown';
freshness: 'fresh' | 'stale' | 'expired' | 'unknown';
resources?: Readonly<{
cpuPercent?: number;
memoryPercent?: number;
maxTemperatureCelsius?: number;
phoneNumbers?: readonly string[];
}>;
resourceSummaryState?: 'loading' | 'ready' | 'unavailable';
}
export interface AppShellProps {
pathname: string;
@@ -440,8 +447,13 @@ export function AppShell({
setInstanceLoading(false);
return;
}
const controller = new AbortController();
let active = true;
setInstanceLoading(true);
const fleetLoad = (fleetDataSource ?? defaultFleetDataSource).load(controller.signal).then(
(fleet) => ({ ok: true as const, fleet }),
() => ({ ok: false as const }),
);
void resolvedInstanceDataSource.get(routeInstanceId).then(
(owner) => {
if (!active) return;
@@ -457,8 +469,50 @@ export function AppShell({
status: 'unknown',
authentication: 'unknown',
freshness: 'unknown',
resourceSummaryState: 'loading',
});
setInstanceLoading(false);
void fleetLoad.then((result) => {
if (!active) return;
if (!result.ok) {
setLoadedInstance((current) =>
current?.id === routeInstanceId
? { ...current, resourceSummaryState: 'unavailable' }
: current,
);
return;
}
const fleet = result.fleet;
const fleetOwner = fleet.instances.find((candidate) => candidate.id === routeInstanceId);
const status = fleet.statuses.get(routeInstanceId);
const routeStatus = !status
? 'unknown'
: !status.reachable
? 'offline'
: status.authenticated === false
? 'auth-required'
: 'online';
const freshness = status?.summary?.freshness;
setLoadedInstance({
id: owner.id,
name: owner.name,
origin: fleetOwner?.url ?? owner.origin,
status: routeStatus,
authentication:
status?.authenticated === true
? 'authenticated'
: status?.authenticated === false
? 'auth-required'
: 'unknown',
freshness:
freshness === 'fresh' || freshness === 'stale' || freshness === 'expired'
? freshness
: 'unknown',
...(status?.summary?.resources ? { resources: status.summary.resources } : {}),
resourceSummaryState: status ? 'ready' : 'unavailable',
});
});
},
() => {
if (!active) return;
@@ -468,8 +522,15 @@ export function AppShell({
);
return () => {
active = false;
controller.abort();
};
}, [instance, resolvedInstanceDataSource, routeInstanceId]);
}, [
defaultFleetDataSource,
fleetDataSource,
instance,
resolvedInstanceDataSource,
routeInstanceId,
]);
const routeInstance = instance?.id === routeInstanceId ? instance : loadedInstance;
const refresh = useControlPlaneEvents(