import { ConsoleAuthSettings } from './auth/console-auth-settings.js'; import { AuditPage, type AuditDataSource } from './audit/audit-page.js'; import { createAuditApiDataSource } from './audit/audit-api-data-source.js'; import type { ReactNode } 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'; import { FleetPage, type FleetDataSource, type FleetSnapshot } from './fleet/fleet-page.js'; import { createFleetApiDataSource } from './fleet/fleet-api-data-source.js'; import { createFleetMessagesApiDataSource, type FleetMessagesDataSource, } from './fleet/fleet-messages-api-data-source.js'; import { AutomationModule, type AutomationDataSource } from './instances/automation-module.js'; import { CallsModule, type CallsDataSource } from './instances/calls-module.js'; import { CellularModule, type CellularDataSource } from './instances/cellular-module.js'; import { DeviceNetworkModule, type DeviceNetworkDataSource, } from './instances/device-network-module.js'; 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'; import { createMessagesApiDataSource } from './instances/messages-api-data-source.js'; import { NotificationsModule, type NotificationsDataSource, } from './instances/notifications-module.js'; import { OtaModule, type OtaDataSource } from './instances/ota-module.js'; import { InstanceSettingsPage } from './settings/instance-settings-page.js'; import { InstanceDetail, INSTANCE_MODULE_LABELS, type CapabilityDataSource, type InstanceCapabilityMap, } from './instances/instance-detail.js'; export type GlobalSection = 'fleet' | 'jobs' | 'audit' | 'settings'; export type InstanceModule = | 'overview' | 'cellular' | 'device-network' | 'messages' | 'calls' | 'esim' | 'notifications' | 'automation' | 'ota'; export type RouteKind = | 'redirect' | 'fleet' | 'instance-new' | `instance-${InstanceModule}` | 'jobs' | 'job-detail' | 'audit' | 'audit-detail' | 'settings-instances' | 'settings-instance-detail' | 'settings-system' | 'not-found'; export interface ResolvedRoute { kind: RouteKind; pathname: string; params?: Readonly>; to?: string; } export interface InstanceContext { id: string; name: string; origin: string; status: 'online' | 'offline' | 'auth-required' | 'degraded' | 'unknown'; authentication: 'authenticated' | 'auth-required' | 'unknown'; freshness: 'fresh' | 'stale' | 'expired' | 'unknown'; } export interface AppShellProps { pathname: string; version?: string; instance?: InstanceContext; fleetDataSource?: FleetDataSource; fleetMessagesDataSource?: FleetMessagesDataSource; fleetData?: FleetSnapshot; instanceDataSource?: InstanceDataSource; capabilities?: InstanceCapabilityMap; capabilityDataSource?: CapabilityDataSource; overviewDataSource?: OverviewDataSource; cellularDataSource?: CellularDataSource; deviceNetworkDataSource?: DeviceNetworkDataSource; messagesDataSource?: MessagesDataSource; callsDataSource?: CallsDataSource; esimDataSource?: EsimDataSource; notificationsDataSource?: NotificationsDataSource; automationDataSource?: AutomationDataSource; otaDataSource?: OtaDataSource; jobsDataSource?: JobsDataSource; auditDataSource?: AuditDataSource; eventStreamClient?: EventStreamClient; } const MODULE_LABELS = INSTANCE_MODULE_LABELS; const INSTANCE_MODULES = new Set(Object.keys(MODULE_LABELS)); function normalize(pathname: string): string { const path = pathname.split(/[?#]/u, 1)[0] ?? '/'; return path === '/' ? path : path.replace(/\/+$/u, '') || '/'; } function decode(value: string): string { try { return decodeURIComponent(value); } catch { return value; } } export function resolveRoute(input: string): ResolvedRoute { const pathname = normalize(input); if (pathname === '/') return { kind: 'redirect', pathname, to: '/fleet' }; const staticRoutes: Readonly> = { '/fleet': 'fleet', '/instances/new': 'instance-new', '/jobs': 'jobs', '/audit': 'audit', '/settings/instances': 'settings-instances', '/settings/system': 'settings-system', }; if (staticRoutes[pathname]) return { kind: staticRoutes[pathname], pathname }; const parts = pathname.split('/').filter(Boolean); if (parts[0] === 'instances' && parts.length === 3 && INSTANCE_MODULES.has(parts[2] ?? '')) return { kind: `instance-${parts[2] as InstanceModule}`, pathname, params: { instanceId: decode(parts[1] ?? '') }, }; if (parts[0] === 'jobs' && parts.length === 2) return { kind: 'job-detail', pathname, params: { jobId: decode(parts[1] ?? '') } }; if (parts[0] === 'audit' && parts.length === 2) return { kind: 'audit-detail', pathname, params: { eventId: decode(parts[1] ?? '') } }; if (parts[0] === 'settings' && parts[1] === 'instances' && parts.length === 3) return { kind: 'settings-instance-detail', pathname, params: { instanceId: decode(parts[2] ?? '') }, }; return { kind: 'not-found', pathname }; } function section(route: ResolvedRoute): GlobalSection | undefined { if (route.kind === 'fleet' || route.kind === 'instance-new' || route.kind.startsWith('instance-')) return 'fleet'; if (route.kind.startsWith('job')) return 'jobs'; if (route.kind.startsWith('audit')) return 'audit'; if (route.kind.startsWith('settings')) return 'settings'; return undefined; } function Page({ route, fleetDataSource, fleetMessagesDataSource, fleetData, instance, instanceDataSource, capabilities, capabilityDataSource, overviewDataSource, cellularDataSource, deviceNetworkDataSource, messagesDataSource, callsDataSource, esimDataSource, notificationsDataSource, automationDataSource, otaDataSource, jobsDataSource, auditDataSource, fleetRefreshSignal, detailRefreshSignal, }: { route: ResolvedRoute; fleetDataSource: FleetDataSource | undefined; fleetMessagesDataSource: FleetMessagesDataSource | undefined; fleetData: FleetSnapshot | undefined; instance: InstanceContext | undefined; instanceDataSource: InstanceDataSource | undefined; capabilities: InstanceCapabilityMap | undefined; capabilityDataSource: CapabilityDataSource | undefined; overviewDataSource: OverviewDataSource | undefined; cellularDataSource: CellularDataSource | undefined; deviceNetworkDataSource: DeviceNetworkDataSource | undefined; messagesDataSource: MessagesDataSource | undefined; callsDataSource: CallsDataSource | undefined; esimDataSource: EsimDataSource | undefined; notificationsDataSource: NotificationsDataSource | undefined; automationDataSource: AutomationDataSource | undefined; otaDataSource: OtaDataSource | undefined; jobsDataSource: JobsDataSource | undefined; auditDataSource: AuditDataSource | undefined; fleetRefreshSignal: number; detailRefreshSignal: number; }): ReactNode { if (route.kind === 'fleet') return ( ); if (route.kind === 'instance-new') return ( ); if (route.kind === 'settings-instance-detail') return ( ); if (route.kind === 'jobs') return ( ); if (route.kind === 'audit') return ( ); if (route.kind === 'settings-instances') return ( ); if (route.kind === 'settings-system') return ; if (route.kind === 'not-found') return (

页面未找到

请求的控制台页面不存在。

返回实例总览
); if (route.kind.startsWith('instance-')) { const module = route.kind.slice('instance-'.length) as InstanceModule; return ( ), } : {})} {...(module === 'cellular' && instance ? { moduleContent: ( ), } : {})} {...(module === 'device-network' && instance ? { moduleContent: ( ), } : {})} {...(module === 'messages' && instance ? { moduleContent: ( ), } : {})} {...(module === 'calls' && instance ? { moduleContent: ( ), } : {})} {...(module === 'esim' && instance ? { moduleContent: ( ), } : {})} {...(module === 'notifications' && instance ? { moduleContent: ( ), } : {})} {...(module === 'automation' && instance ? { moduleContent: ( ), } : {})} {...(module === 'ota' && instance ? { moduleContent: ( ), } : {})} /> ); } const labels: Partial> = { 'instance-new': '添加实例', jobs: '任务', 'job-detail': '任务详情', audit: '审计', 'audit-detail': '审计事件', 'settings-instances': '实例设置', 'settings-instance-detail': '实例设置', 'settings-system': '系统设置', }; return (

{labels[route.kind] ?? '多实例 SimAdmin 管理台'}

此页面已准备好展示结构化数据与操作。

); } export function AppShell({ pathname, version = 'dev', instance, fleetDataSource, fleetMessagesDataSource, fleetData, instanceDataSource, capabilities, capabilityDataSource, overviewDataSource, cellularDataSource, deviceNetworkDataSource, messagesDataSource, callsDataSource, esimDataSource, notificationsDataSource, automationDataSource, otaDataSource, jobsDataSource, auditDataSource, eventStreamClient, }: AppShellProps) { const defaultEventStreamClient = useMemo(() => createEventStreamClient(), []); const defaultFleetDataSource = useMemo(() => createFleetApiDataSource(), []); const defaultFleetMessagesDataSource = useMemo(() => createFleetMessagesApiDataSource(), []); const defaultInstanceDataSource = useMemo(() => createInstanceApiDataSource(), []); const defaultMessagesDataSource = useMemo(() => createMessagesApiDataSource(), []); const resolvedJobsDataSource = useMemo( () => jobsDataSource ?? createJobsApiDataSource(), [jobsDataSource], ); const resolvedAuditDataSource = useMemo( () => auditDataSource ?? createAuditApiDataSource(), [auditDataSource], ); 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(); 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, ); const currentSection = section(route); return (
跳到主要内容
多实例 SimAdmin 管理台 控制台 版本 {version}
{routeInstanceId && instanceLoading ? (

正在加载实例…

) : routeInstanceId && instanceLoadFailed ? (

无法加载此实例,请返回总览后重试。

) : ( )}
); }