feat(web): make instance overview card-first

This commit is contained in:
chick
2026-07-18 23:23:41 +08:00
parent abfd07f8a3
commit 2411801240
5 changed files with 719 additions and 223 deletions
+78 -23
View File
@@ -1,7 +1,7 @@
import { AuditPage, type AuditDataSource } from './audit/audit-page.js';
import { createAuditApiDataSource } from './audit/audit-api-data-source.js';
import type { ReactNode } from 'react';
import { useMemo } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useControlPlaneEvents } from './events/use-control-plane-events.js';
import { createEventStreamClient, type EventStreamClient } from './events/event-stream-client.js';
@@ -18,6 +18,7 @@ import {
import { EsimModule, type EsimDataSource } from './instances/esim-module.js';
import { OverviewSystemPage, type OverviewDataSource } from './instances/overview-system.js';
import { InstanceEditor, type InstanceDataSource } from './instances/instance-crud.js';
import { createInstanceApiDataSource } from './instances/instance-api-data-source.js';
import { JobsPage, type JobsDataSource } from './jobs/jobs-page.js';
import { createJobsApiDataSource } from './jobs/jobs-api-data-source.js';
import { MessagesModule, type MessagesDataSource } from './instances/messages-module.js';
@@ -198,6 +199,7 @@ function Page({
return (
<FleetPage
{...(fleetDataSource ? { dataSource: fleetDataSource } : {})}
{...(instanceDataSource ? { instanceDataSource } : {})}
{...(fleetData ? { initialData: fleetData } : {})}
refreshSignal={fleetRefreshSignal}
/>
@@ -407,6 +409,7 @@ export function AppShell({
}: AppShellProps) {
const defaultEventStreamClient = useMemo(() => createEventStreamClient(), []);
const defaultFleetDataSource = useMemo(() => createFleetApiDataSource(), []);
const defaultInstanceDataSource = useMemo(() => createInstanceApiDataSource(), []);
const resolvedJobsDataSource = useMemo(
() => jobsDataSource ?? createJobsApiDataSource(),
[jobsDataSource],
@@ -418,6 +421,50 @@ export function AppShell({
const resolved = resolveRoute(pathname);
const route = resolved.kind === 'redirect' ? resolveRoute(resolved.to ?? '/fleet') : resolved;
const routeInstanceId = route.params?.instanceId;
const resolvedInstanceDataSource = instanceDataSource ?? defaultInstanceDataSource;
const [loadedInstance, setLoadedInstance] = useState<InstanceContext>();
const [instanceLoading, setInstanceLoading] = useState(false);
const [instanceLoadFailed, setInstanceLoadFailed] = useState(false);
useEffect(() => {
setLoadedInstance(undefined);
setInstanceLoadFailed(false);
if (!routeInstanceId || instance?.id === routeInstanceId) {
setInstanceLoading(false);
return;
}
let active = true;
setInstanceLoading(true);
void resolvedInstanceDataSource.get(routeInstanceId).then(
(owner) => {
if (!active) return;
if (owner.id !== routeInstanceId) {
setInstanceLoadFailed(true);
setInstanceLoading(false);
return;
}
setLoadedInstance({
id: owner.id,
name: owner.name,
origin: owner.origin,
status: 'unknown',
authentication: 'unknown',
freshness: 'unknown',
});
setInstanceLoading(false);
},
() => {
if (!active) return;
setInstanceLoadFailed(true);
setInstanceLoading(false);
},
);
return () => {
active = false;
};
}, [instance, resolvedInstanceDataSource, routeInstanceId]);
const routeInstance = instance?.id === routeInstanceId ? instance : loadedInstance;
const refresh = useControlPlaneEvents(
routeInstanceId,
eventStreamClient ?? defaultEventStreamClient,
@@ -455,28 +502,36 @@ export function AppShell({
</ul>
</nav>
<main id="main-content" tabIndex={-1}>
<Page
route={route}
fleetDataSource={fleetDataSource ?? defaultFleetDataSource}
fleetData={fleetData}
instance={instance}
instanceDataSource={instanceDataSource}
capabilities={capabilities}
capabilityDataSource={capabilityDataSource}
overviewDataSource={overviewDataSource}
cellularDataSource={cellularDataSource}
deviceNetworkDataSource={deviceNetworkDataSource}
messagesDataSource={messagesDataSource}
callsDataSource={callsDataSource}
esimDataSource={esimDataSource}
notificationsDataSource={notificationsDataSource}
automationDataSource={automationDataSource}
otaDataSource={otaDataSource}
jobsDataSource={resolvedJobsDataSource}
auditDataSource={resolvedAuditDataSource}
fleetRefreshSignal={refresh.fleet}
detailRefreshSignal={refresh.detail}
/>
{routeInstanceId && instanceLoading ? (
<p role="status"></p>
) : routeInstanceId && instanceLoadFailed ? (
<p role="alert" className="state-panel state-error">
</p>
) : (
<Page
route={route}
fleetDataSource={fleetDataSource ?? defaultFleetDataSource}
fleetData={fleetData}
instance={routeInstance}
instanceDataSource={resolvedInstanceDataSource}
capabilities={capabilities}
capabilityDataSource={capabilityDataSource}
overviewDataSource={overviewDataSource}
cellularDataSource={cellularDataSource}
deviceNetworkDataSource={deviceNetworkDataSource}
messagesDataSource={messagesDataSource}
callsDataSource={callsDataSource}
esimDataSource={esimDataSource}
notificationsDataSource={notificationsDataSource}
automationDataSource={automationDataSource}
otaDataSource={otaDataSource}
jobsDataSource={resolvedJobsDataSource}
auditDataSource={resolvedAuditDataSource}
fleetRefreshSignal={refresh.fleet}
detailRefreshSignal={refresh.detail}
/>
)}
</main>
</div>
</div>