import type { AccessGrant, ConnectionView, Platform, PlatformSession, PlatformStatus } from '../types' import { headscaleNodeDiscovery } from './headscale-node-discovery' const nowIso = () => new Date().toISOString() function platformListFromEnv(): Platform[] | null { try { const raw = process.env.PLATFORM_REGISTRY_JSON; if (!raw?.trim()) return null; const parsed = JSON.parse(raw) as Platform[]; return Array.isArray(parsed) ? parsed : null; } catch { return null; } } const defaultPlatforms: Platform[] = [ { id: 'futuredrive-f1tenth-01', name: 'F1Tenth', nodeKey: 'futuredrive-f1tenth-01', agentUrl: '', headscaleUser: 'mobility', tags: ['jetson', 'ros', 'f1tenth'], state: 'offline', lastSeenAt: null }, { id: 'futuredrive-mkmini-02', name: 'MK-mini', nodeKey: 'futuredrive-mkmini-02', agentUrl: '', headscaleUser: 'mobility', tags: ['jetson', 'ros', 'mkmini'], state: 'offline', lastSeenAt: null }, { id: 'futuredrive-simulator-01', name: 'Simulator', nodeKey: 'futuredrive-simulator-01', agentUrl: '', headscaleUser: 'mobility', tags: ['simulator', 'ros'], state: 'offline', lastSeenAt: null } ]; const platforms = new Map((platformListFromEnv() ?? defaultPlatforms).map((platform) => [platform.id, platform])); /* const platforms = new Map([ [ 'jetson-lab-01', { id: 'jetson-lab-01', name: 'Jetson Lab 01', nodeKey: 'headscale-node-jetson-lab-01', agentUrl: 'http://100.64.0.11:4600', headscaleUser: 'mobility', tags: ['jetson', 'ros', 'lab'], state: 'ready', lastSeenAt: nowIso() } ], [ 'sim-lab-01', { id: 'sim-lab-01', name: 'Simulator Lab 01', nodeKey: 'headscale-node-sim-lab-01', agentUrl: 'http://100.64.0.21:4600', headscaleUser: 'mobility', tags: ['simulator', 'ros'], state: 'ready', lastSeenAt: nowIso() } ] ]) */ const statusByPlatformId = new Map( Array.from(platforms.values()).map((platform) => [ platform.id, { platformId: platform.id, batteryPercent: platform.id.startsWith('jetson') ? 86 : null, network: 'online', ros: 'ok', processes: [ { name: 'mobility-agent', status: 'running' }, { name: 'roscore', status: 'running' } ], updatedAt: nowIso() } ]) ) export class PlatformRegistry { listPlatforms(): Platform[] { return Array.from(platforms.values()) } async resolvePlatform(platformId: string, accountEmail?: string): Promise { const platform = this.getPlatform(platformId) ?? { id: platformId, name: platformId, nodeKey: platformId, agentUrl: '', headscaleUser: 'tagged-devices', tags: ['teaching-device'], state: 'offline' as const, lastSeenAt: null, sshUser: null, workspaceRoot: null, } if (!platform) return null if (!accountEmail && platform.agentUrl) return platform const resolvedAgent = await headscaleNodeDiscovery.resolveAgent(platform, accountEmail) if (!resolvedAgent) { const next = { ...platform, agentUrl: '', state: 'offline' as const, lastSeenAt: platform.lastSeenAt } platforms.set(platformId, next) this.updateNetworkStatus(platformId, 'offline') return next } const next = { ...platform, agentUrl: resolvedAgent.agentUrl, state: platform.state === 'maintenance' ? platform.state : 'ready' as const, lastSeenAt: resolvedAgent.lastSeenAt ?? nowIso(), sshUser: resolvedAgent.sshUser ?? platform.sshUser ?? null, workspaceRoot: resolvedAgent.workspaceRoot ?? platform.workspaceRoot ?? null, } platforms.set(platformId, next) this.updateNetworkStatus(platformId, 'online') return next } getPlatform(platformId: string): Platform | null { return platforms.get(platformId) ?? null } getStatus(platformId: string): PlatformStatus | null { return statusByPlatformId.get(platformId) ?? null } setPlatformState(platformId: string, state: Platform['state']): Platform | null { const platform = platforms.get(platformId) if (!platform) return null const next = { ...platform, state, lastSeenAt: nowIso() } platforms.set(platformId, next) return next } listConnections(activeSessions: PlatformSession[], activeGrants: AccessGrant[]): ConnectionView[] { return this.listPlatforms().map((platform) => ({ platform, activeSession: activeSessions.find((session) => session.platformId === platform.id) ?? null, activeGrants: activeGrants.filter((grant) => grant.platformId === platform.id), status: this.getStatus(platform.id)! })) } private updateNetworkStatus(platformId: string, network: PlatformStatus['network']) { const status = statusByPlatformId.get(platformId) if (!status) return statusByPlatformId.set(platformId, { ...status, network, updatedAt: nowIso() }) } } export const platformRegistry = new PlatformRegistry()