cleanup: consolidate unified resources adapters into useResourcesAsLegacy hook
This cleanup addresses transition debt from the unified resources migration: Frontend cleanup: - Move all Resource→Legacy type conversions to useResourcesAsLegacy() hook - Add asNodes() and asDockerHosts() adapter functions to the hook - Simplify DockerRoute, HostsRoute, DashboardView to use the centralized hook - Remove ~300 lines of duplicated adapter code from App.tsx - Remove debug console.log statements from Dashboard.tsx - Fix CPU value conversion (divide by 100) for Dashboard compatibility Backend fixes (from previous session): - Fix parentID format in converters (VM, Container, Storage) to match Node.ID - Format changed from 'instance/node/nodename' to 'instance-nodename' - Update tests to match new parentID format This consolidates all legacy type conversion logic in one place, making future cleanup easier when components are migrated to use unified resources directly.
This commit is contained in:
parent
f003ad7239
commit
7e8611e0ae
7 changed files with 221 additions and 356 deletions
|
|
@ -547,9 +547,9 @@ The dedicated `/resources` unified view was abandoned in favor of migrating exis
|
|||
4. **Cleanup (Phase 6) - COMPLETED:**
|
||||
- [x] Remove debug console.log statements from frontend routes
|
||||
- [x] Remove legacy fallback code from route components (Docker, Hosts, Dashboard)
|
||||
- [x] Simplify route components to use centralized `useResourcesAsLegacy()` hook
|
||||
- [ ] Remove unused legacy arrays from backend StateFrontend (optional - still broadcast for API compatibility)
|
||||
- [ ] Remove legacy AI context fallback (optional - verify AI uses unified model first)
|
||||
- [ ] Simplify route components to use resources directly (remove adapter layer)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import { UpdateProgressModal } from './components/UpdateProgressModal';
|
|||
import type { UpdateStatus } from './api/updates';
|
||||
import { AIChat } from './components/AI/AIChat';
|
||||
import { aiChatStore } from './stores/aiChat';
|
||||
import { useResourcesAsLegacy } from './hooks/useResources';
|
||||
|
||||
const Dashboard = lazy(() =>
|
||||
import('./components/Dashboard/Dashboard').then((module) => ({ default: module.Dashboard })),
|
||||
|
|
@ -92,169 +93,29 @@ export const useDarkMode = () => {
|
|||
return context;
|
||||
};
|
||||
|
||||
// Docker route component that properly uses activeAlerts from useWebSocket
|
||||
// Docker route component - uses unified resources via useResourcesAsLegacy hook
|
||||
function DockerRoute() {
|
||||
const wsContext = useContext(WebSocketContext);
|
||||
if (!wsContext) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const { state, activeAlerts } = wsContext;
|
||||
const { activeAlerts } = wsContext;
|
||||
const { asDockerHosts } = useResourcesAsLegacy();
|
||||
|
||||
// Use unified resources if available, fall back to legacy state.dockerHosts
|
||||
const hosts = createMemo(() => {
|
||||
const dockerHostResources = state.resources?.filter(r => r.type === 'docker-host') ?? [];
|
||||
const dockerContainerResources = state.resources?.filter(r => r.type === 'docker-container') ?? [];
|
||||
|
||||
// If we have unified resources, convert and reconstruct hierarchy
|
||||
if (dockerHostResources.length > 0) {
|
||||
return dockerHostResources.map(h => {
|
||||
const platformData = h.platformData as Record<string, unknown> | undefined;
|
||||
|
||||
// Find containers belonging to this host
|
||||
const hostContainers = dockerContainerResources
|
||||
.filter(c => c.parentId === h.id)
|
||||
.map(c => {
|
||||
const cPlatform = c.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
image: cPlatform?.image as string ?? '',
|
||||
state: c.status === 'running' ? 'running' : 'exited',
|
||||
status: c.status,
|
||||
health: cPlatform?.health as string | undefined,
|
||||
cpuPercent: c.cpu?.current ?? 0,
|
||||
memoryUsageBytes: c.memory?.used ?? 0,
|
||||
memoryLimitBytes: c.memory?.total ?? 0,
|
||||
memoryPercent: c.memory?.current ?? 0,
|
||||
uptimeSeconds: c.uptime ?? 0,
|
||||
restartCount: cPlatform?.restartCount as number ?? 0,
|
||||
exitCode: cPlatform?.exitCode as number ?? 0,
|
||||
createdAt: cPlatform?.createdAt as number ?? 0,
|
||||
startedAt: cPlatform?.startedAt as number | undefined,
|
||||
finishedAt: cPlatform?.finishedAt as number | undefined,
|
||||
ports: cPlatform?.ports,
|
||||
labels: cPlatform?.labels as Record<string, string> | undefined,
|
||||
networks: cPlatform?.networks,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: h.id,
|
||||
agentId: platformData?.agentId as string ?? h.id,
|
||||
hostname: h.identity?.hostname ?? h.name,
|
||||
displayName: h.displayName || h.name,
|
||||
customDisplayName: platformData?.customDisplayName as string | undefined,
|
||||
machineId: h.identity?.machineId,
|
||||
os: platformData?.os as string | undefined,
|
||||
kernelVersion: platformData?.kernelVersion as string | undefined,
|
||||
architecture: platformData?.architecture as string | undefined,
|
||||
runtime: platformData?.runtime as string ?? 'docker',
|
||||
runtimeVersion: platformData?.runtimeVersion as string | undefined,
|
||||
dockerVersion: platformData?.dockerVersion as string | undefined,
|
||||
cpus: platformData?.cpus as number ?? 0,
|
||||
totalMemoryBytes: h.memory?.total ?? 0,
|
||||
uptimeSeconds: h.uptime ?? 0,
|
||||
cpuUsagePercent: h.cpu?.current,
|
||||
loadAverage: platformData?.loadAverage as number[] | undefined,
|
||||
memory: h.memory ? {
|
||||
total: h.memory.total ?? 0,
|
||||
used: h.memory.used ?? 0,
|
||||
free: h.memory.free ?? 0,
|
||||
usage: h.memory.current,
|
||||
} : undefined,
|
||||
disks: platformData?.disks,
|
||||
networkInterfaces: platformData?.networkInterfaces,
|
||||
status: h.status === 'online' || h.status === 'running' ? 'online' : h.status,
|
||||
lastSeen: h.lastSeen,
|
||||
intervalSeconds: platformData?.intervalSeconds as number ?? 30,
|
||||
agentVersion: platformData?.agentVersion as string | undefined,
|
||||
containers: hostContainers,
|
||||
services: platformData?.services,
|
||||
tasks: platformData?.tasks,
|
||||
swarm: platformData?.swarm,
|
||||
tokenId: platformData?.tokenId as string | undefined,
|
||||
tokenName: platformData?.tokenName as string | undefined,
|
||||
tokenHint: platformData?.tokenHint as string | undefined,
|
||||
tokenLastUsedAt: platformData?.tokenLastUsedAt as number | undefined,
|
||||
hidden: platformData?.hidden as boolean | undefined,
|
||||
pendingUninstall: platformData?.pendingUninstall as boolean | undefined,
|
||||
command: platformData?.command,
|
||||
isLegacy: platformData?.isLegacy as boolean | undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Return empty array if no unified resources available
|
||||
return [];
|
||||
});
|
||||
|
||||
return <DockerHosts hosts={hosts() as any} activeAlerts={activeAlerts} />;
|
||||
return <DockerHosts hosts={asDockerHosts() as any} activeAlerts={activeAlerts} />;
|
||||
}
|
||||
|
||||
// Hosts route component - uses unified resources via useResourcesAsLegacy hook
|
||||
function HostsRoute() {
|
||||
const wsContext = useContext(WebSocketContext);
|
||||
if (!wsContext) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const { state } = wsContext;
|
||||
|
||||
// Use unified resources if available, fall back to legacy state.hosts
|
||||
// During migration: resources may be empty initially, so we need the fallback
|
||||
const hosts = createMemo(() => {
|
||||
const unifiedHosts = state.resources?.filter(r => r.type === 'host') ?? [];
|
||||
|
||||
// If we have unified resources, convert them to legacy Host format
|
||||
// (This will be simplified once HostsOverview is migrated to use resources directly)
|
||||
if (unifiedHosts.length > 0) {
|
||||
return unifiedHosts.map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
hostname: r.identity?.hostname ?? r.name,
|
||||
displayName: r.displayName || r.name,
|
||||
platform: platformData?.platform as string | undefined,
|
||||
osName: platformData?.osName as string | undefined,
|
||||
osVersion: platformData?.osVersion as string | undefined,
|
||||
kernelVersion: platformData?.kernelVersion as string | undefined,
|
||||
architecture: platformData?.architecture as string | undefined,
|
||||
cpuCount: platformData?.cpuCount as number | undefined,
|
||||
cpuUsage: r.cpu?.current,
|
||||
loadAverage: platformData?.loadAverage as number[] | undefined,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disks: platformData?.disks as Array<{
|
||||
total: number;
|
||||
used: number;
|
||||
free: number;
|
||||
usage: number;
|
||||
mountpoint?: string;
|
||||
}> | undefined,
|
||||
diskIO: platformData?.diskIO,
|
||||
networkInterfaces: platformData?.networkInterfaces,
|
||||
sensors: platformData?.sensors,
|
||||
raid: platformData?.raid,
|
||||
status: r.status === 'online' || r.status === 'running' ? 'online' : r.status,
|
||||
uptimeSeconds: r.uptime,
|
||||
lastSeen: r.lastSeen,
|
||||
intervalSeconds: platformData?.intervalSeconds as number | undefined,
|
||||
agentVersion: platformData?.agentVersion as string | undefined,
|
||||
tokenId: platformData?.tokenId as string | undefined,
|
||||
tokenName: platformData?.tokenName as string | undefined,
|
||||
tags: r.tags,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Return empty array if no unified resources available
|
||||
return [];
|
||||
});
|
||||
const { asHosts } = useResourcesAsLegacy();
|
||||
|
||||
return (
|
||||
<HostsOverview hosts={hosts() as any} connectionHealth={state.connectionHealth ?? {}} />
|
||||
<HostsOverview hosts={asHosts() as any} connectionHealth={state.connectionHealth ?? {}} />
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -870,142 +731,12 @@ function App() {
|
|||
// Pass through the store directly (only when initialized)
|
||||
const enhancedStore = () => wsStore();
|
||||
|
||||
// Dashboard view using unified resources with fallback
|
||||
// Dashboard view - uses unified resources via useResourcesAsLegacy hook
|
||||
const DashboardView = () => {
|
||||
// Use unified resources if available, fall back to legacy data
|
||||
const vms = createMemo(() => {
|
||||
const vmResources = state().resources?.filter(r => r.type === 'vm') ?? [];
|
||||
if (vmResources.length > 0) {
|
||||
return vmResources.map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
vmid: platformData?.vmid as number ?? 0,
|
||||
name: r.name,
|
||||
node: platformData?.node as string ?? '',
|
||||
instance: platformData?.instance as string ?? r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'qemu',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
cpus: platformData?.cpus as number ?? 1,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: platformData?.template as boolean ?? false,
|
||||
lastBackup: platformData?.lastBackup as number ?? 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: platformData?.lock as string ?? '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
// Return empty array if no unified resources available
|
||||
return [];
|
||||
});
|
||||
|
||||
const containers = createMemo(() => {
|
||||
const containerResources = state().resources?.filter(r => r.type === 'container') ?? [];
|
||||
if (containerResources.length > 0) {
|
||||
return containerResources.map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
vmid: platformData?.vmid as number ?? 0,
|
||||
name: r.name,
|
||||
node: platformData?.node as string ?? '',
|
||||
instance: platformData?.instance as string ?? r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'lxc',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
cpus: platformData?.cpus as number ?? 1,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: platformData?.template as boolean ?? false,
|
||||
lastBackup: platformData?.lastBackup as number ?? 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: platformData?.lock as string ?? '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
};
|
||||
});
|
||||
}
|
||||
// Return empty array if no unified resources available
|
||||
return [];
|
||||
});
|
||||
|
||||
const nodes = createMemo(() => {
|
||||
const nodeResources = state().resources?.filter(r => r.type === 'node') ?? [];
|
||||
if (nodeResources.length > 0) {
|
||||
return nodeResources.map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
displayName: r.displayName,
|
||||
instance: r.platformId,
|
||||
host: platformData?.host as string ?? '',
|
||||
status: r.status,
|
||||
type: 'node',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
uptime: r.uptime ?? 0,
|
||||
loadAverage: platformData?.loadAverage as number[] ?? [],
|
||||
kernelVersion: platformData?.kernelVersion as string ?? '',
|
||||
pveVersion: platformData?.pveVersion as string ?? '',
|
||||
cpuInfo: platformData?.cpuInfo ?? { model: '', cores: 0, sockets: 0, mhz: '' },
|
||||
temperature: platformData?.temperature,
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
connectionHealth: platformData?.connectionHealth as string ?? 'unknown',
|
||||
isClusterMember: platformData?.isClusterMember as boolean | undefined,
|
||||
clusterName: platformData?.clusterName as string | undefined,
|
||||
};
|
||||
});
|
||||
}
|
||||
// Return empty array if no unified resources available
|
||||
return [];
|
||||
});
|
||||
const { asVMs, asContainers, asNodes } = useResourcesAsLegacy();
|
||||
|
||||
return (
|
||||
<Dashboard vms={vms() as any} containers={containers() as any} nodes={nodes() as any} />
|
||||
<Dashboard vms={asVMs() as any} containers={asContainers() as any} nodes={asNodes() as any} />
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -723,6 +723,7 @@ export function Dashboard(props: DashboardProps) {
|
|||
guests.forEach((guest) => {
|
||||
// Node.ID is formatted as "instance-nodename", so we need to match that
|
||||
const nodeId = `${guest.instance}-${guest.node}`;
|
||||
|
||||
if (!groups[nodeId]) {
|
||||
groups[nodeId] = [];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,76 +231,82 @@ export function useResourcesAsLegacy() {
|
|||
|
||||
// Convert resources to legacy VM format
|
||||
const asVMs = createMemo(() => {
|
||||
return byType('vm').map(r => ({
|
||||
id: r.id,
|
||||
vmid: parseInt(r.id.split('-').pop() ?? '0', 10),
|
||||
name: r.name,
|
||||
node: r.parentId ?? '',
|
||||
instance: r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'qemu',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
cpus: 1, // Not available in unified model
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: false,
|
||||
lastBackup: 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
}));
|
||||
return byType('vm').map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
vmid: platformData?.vmid as number ?? parseInt(r.id.split('-').pop() ?? '0', 10),
|
||||
name: r.name,
|
||||
node: platformData?.node as string ?? '',
|
||||
instance: platformData?.instance as string ?? r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'qemu',
|
||||
cpu: (r.cpu?.current ?? 0) / 100, // Convert from percentage to ratio for Dashboard
|
||||
cpus: platformData?.cpus as number ?? 1,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: platformData?.diskRead as number ?? 0,
|
||||
diskWrite: platformData?.diskWrite as number ?? 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: platformData?.template as boolean ?? false,
|
||||
lastBackup: platformData?.lastBackup as number ?? 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: platformData?.lock as string ?? '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Convert resources to legacy Container format
|
||||
const asContainers = createMemo(() => {
|
||||
return byType('container').map(r => ({
|
||||
id: r.id,
|
||||
vmid: parseInt(r.id.split('-').pop() ?? '0', 10),
|
||||
name: r.name,
|
||||
node: r.parentId ?? '',
|
||||
instance: r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'lxc',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
cpus: 1,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: 0,
|
||||
diskWrite: 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: false,
|
||||
lastBackup: 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
}));
|
||||
return byType('container').map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
vmid: platformData?.vmid as number ?? parseInt(r.id.split('-').pop() ?? '0', 10),
|
||||
name: r.name,
|
||||
node: platformData?.node as string ?? '',
|
||||
instance: platformData?.instance as string ?? r.platformId,
|
||||
status: r.status === 'running' ? 'running' : 'stopped',
|
||||
type: 'lxc',
|
||||
cpu: (r.cpu?.current ?? 0) / 100, // Convert from percentage to ratio for Dashboard
|
||||
cpus: platformData?.cpus as number ?? 1,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
networkIn: r.network?.rxBytes ?? 0,
|
||||
networkOut: r.network?.txBytes ?? 0,
|
||||
diskRead: platformData?.diskRead as number ?? 0,
|
||||
diskWrite: platformData?.diskWrite as number ?? 0,
|
||||
uptime: r.uptime ?? 0,
|
||||
template: platformData?.template as boolean ?? false,
|
||||
lastBackup: platformData?.lastBackup as number ?? 0,
|
||||
tags: r.tags ?? [],
|
||||
lock: platformData?.lock as string ?? '',
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Convert resources to legacy Host format
|
||||
|
|
@ -377,11 +383,134 @@ export function useResourcesAsLegacy() {
|
|||
});
|
||||
});
|
||||
|
||||
// Convert resources to legacy Node format
|
||||
const asNodes = createMemo(() => {
|
||||
return byType('node').map(r => {
|
||||
const platformData = r.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
displayName: r.displayName,
|
||||
instance: r.platformId,
|
||||
host: platformData?.host as string ?? '',
|
||||
status: r.status,
|
||||
type: 'node',
|
||||
cpu: r.cpu?.current ?? 0,
|
||||
memory: r.memory ? {
|
||||
total: r.memory.total ?? 0,
|
||||
used: r.memory.used ?? 0,
|
||||
free: r.memory.free ?? 0,
|
||||
usage: r.memory.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
disk: r.disk ? {
|
||||
total: r.disk.total ?? 0,
|
||||
used: r.disk.used ?? 0,
|
||||
free: r.disk.free ?? 0,
|
||||
usage: r.disk.current,
|
||||
} : { total: 0, used: 0, free: 0, usage: 0 },
|
||||
uptime: r.uptime ?? 0,
|
||||
loadAverage: platformData?.loadAverage as number[] ?? [],
|
||||
kernelVersion: platformData?.kernelVersion as string ?? '',
|
||||
pveVersion: platformData?.pveVersion as string ?? '',
|
||||
cpuInfo: platformData?.cpuInfo ?? { model: '', cores: 0, sockets: 0, mhz: '' },
|
||||
temperature: platformData?.temperature,
|
||||
lastSeen: new Date(r.lastSeen).toISOString(),
|
||||
connectionHealth: platformData?.connectionHealth as string ?? 'unknown',
|
||||
isClusterMember: platformData?.isClusterMember as boolean | undefined,
|
||||
clusterName: platformData?.clusterName as string | undefined,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Convert resources to legacy DockerHost format (including nested containers)
|
||||
const asDockerHosts = createMemo(() => {
|
||||
const dockerHostResources = byType('docker-host');
|
||||
const dockerContainerResources = byType('docker-container');
|
||||
|
||||
return dockerHostResources.map(h => {
|
||||
const platformData = h.platformData as Record<string, unknown> | undefined;
|
||||
|
||||
// Find containers belonging to this host
|
||||
const hostContainers = dockerContainerResources
|
||||
.filter(c => c.parentId === h.id)
|
||||
.map(c => {
|
||||
const cPlatform = c.platformData as Record<string, unknown> | undefined;
|
||||
return {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
image: cPlatform?.image as string ?? '',
|
||||
state: c.status === 'running' ? 'running' : 'exited',
|
||||
status: c.status,
|
||||
health: cPlatform?.health as string | undefined,
|
||||
cpuPercent: c.cpu?.current ?? 0,
|
||||
memoryUsageBytes: c.memory?.used ?? 0,
|
||||
memoryLimitBytes: c.memory?.total ?? 0,
|
||||
memoryPercent: c.memory?.current ?? 0,
|
||||
uptimeSeconds: c.uptime ?? 0,
|
||||
restartCount: cPlatform?.restartCount as number ?? 0,
|
||||
exitCode: cPlatform?.exitCode as number ?? 0,
|
||||
createdAt: cPlatform?.createdAt as number ?? 0,
|
||||
startedAt: cPlatform?.startedAt as number | undefined,
|
||||
finishedAt: cPlatform?.finishedAt as number | undefined,
|
||||
ports: cPlatform?.ports,
|
||||
labels: cPlatform?.labels as Record<string, string> | undefined,
|
||||
networks: cPlatform?.networks,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: h.id,
|
||||
agentId: platformData?.agentId as string ?? h.id,
|
||||
hostname: h.identity?.hostname ?? h.name,
|
||||
displayName: h.displayName || h.name,
|
||||
customDisplayName: platformData?.customDisplayName as string | undefined,
|
||||
machineId: h.identity?.machineId,
|
||||
os: platformData?.os as string | undefined,
|
||||
kernelVersion: platformData?.kernelVersion as string | undefined,
|
||||
architecture: platformData?.architecture as string | undefined,
|
||||
runtime: platformData?.runtime as string ?? 'docker',
|
||||
runtimeVersion: platformData?.runtimeVersion as string | undefined,
|
||||
dockerVersion: platformData?.dockerVersion as string | undefined,
|
||||
cpus: platformData?.cpus as number ?? 0,
|
||||
totalMemoryBytes: h.memory?.total ?? 0,
|
||||
uptimeSeconds: h.uptime ?? 0,
|
||||
cpuUsagePercent: h.cpu?.current,
|
||||
loadAverage: platformData?.loadAverage as number[] | undefined,
|
||||
memory: h.memory ? {
|
||||
total: h.memory.total ?? 0,
|
||||
used: h.memory.used ?? 0,
|
||||
free: h.memory.free ?? 0,
|
||||
usage: h.memory.current,
|
||||
} : undefined,
|
||||
disks: platformData?.disks,
|
||||
networkInterfaces: platformData?.networkInterfaces,
|
||||
status: h.status === 'online' || h.status === 'running' ? 'online' : h.status,
|
||||
lastSeen: h.lastSeen,
|
||||
intervalSeconds: platformData?.intervalSeconds as number ?? 30,
|
||||
agentVersion: platformData?.agentVersion as string | undefined,
|
||||
containers: hostContainers,
|
||||
services: platformData?.services,
|
||||
tasks: platformData?.tasks,
|
||||
swarm: platformData?.swarm,
|
||||
tokenId: platformData?.tokenId as string | undefined,
|
||||
tokenName: platformData?.tokenName as string | undefined,
|
||||
tokenHint: platformData?.tokenHint as string | undefined,
|
||||
tokenLastUsedAt: platformData?.tokenLastUsedAt as number | undefined,
|
||||
hidden: platformData?.hidden as boolean | undefined,
|
||||
pendingUninstall: platformData?.pendingUninstall as boolean | undefined,
|
||||
command: platformData?.command,
|
||||
isLegacy: platformData?.isLegacy as boolean | undefined,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
resources,
|
||||
asVMs,
|
||||
asContainers,
|
||||
asHosts,
|
||||
asNodes,
|
||||
asDockerHosts,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -584,6 +584,10 @@ export function createWebSocketStore(url: string) {
|
|||
setState('physicalDisks', reconcile(message.data.physicalDisks, { key: 'id' }));
|
||||
// Handle unified resources
|
||||
if (message.data.resources !== undefined) {
|
||||
logger.debug('[WebSocket] Updating resources', {
|
||||
count: message.data.resources?.length || 0,
|
||||
types: [...new Set(message.data.resources?.map((r: any) => r.type) || [])],
|
||||
});
|
||||
setState('resources', reconcile(message.data.resources, { key: 'id' }));
|
||||
}
|
||||
// Sync active alerts from state
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ func FromVM(vm models.VM) Resource {
|
|||
}
|
||||
platformDataJSON, _ := json.Marshal(platformData)
|
||||
|
||||
// Parent is the node
|
||||
parentID := fmt.Sprintf("%s/node/%s", vm.Instance, vm.Node)
|
||||
// Parent is the node - format matches Node.ID: instance-nodename
|
||||
parentID := fmt.Sprintf("%s-%s", vm.Instance, vm.Node)
|
||||
|
||||
return Resource{
|
||||
ID: vm.ID,
|
||||
|
|
@ -232,8 +232,8 @@ func FromContainer(ct models.Container) Resource {
|
|||
}
|
||||
platformDataJSON, _ := json.Marshal(platformData)
|
||||
|
||||
// Parent is the node
|
||||
parentID := fmt.Sprintf("%s/node/%s", ct.Instance, ct.Node)
|
||||
// Parent is the node - format matches Node.ID: instance-nodename
|
||||
parentID := fmt.Sprintf("%s-%s", ct.Instance, ct.Node)
|
||||
|
||||
return Resource{
|
||||
ID: ct.ID,
|
||||
|
|
@ -758,7 +758,7 @@ func FromStorage(s models.Storage) Resource {
|
|||
PlatformID: s.Instance,
|
||||
PlatformType: PlatformProxmoxPVE,
|
||||
SourceType: SourceAPI,
|
||||
ParentID: fmt.Sprintf("%s/node/%s", s.Instance, s.Node),
|
||||
ParentID: fmt.Sprintf("%s-%s", s.Instance, s.Node), // Format matches Node.ID: instance-nodename
|
||||
Status: status,
|
||||
Disk: disk,
|
||||
LastSeen: time.Now(), // Storage doesn't have LastSeen
|
||||
|
|
|
|||
|
|
@ -160,8 +160,8 @@ func TestFromVM(t *testing.T) {
|
|||
if r.Status != StatusRunning {
|
||||
t.Errorf("Expected status %s, got %s", StatusRunning, r.Status)
|
||||
}
|
||||
if r.ParentID != "pve1/node/node1" {
|
||||
t.Errorf("Expected parent pve1/node/node1, got %s", r.ParentID)
|
||||
if r.ParentID != "pve1-node1" {
|
||||
t.Errorf("Expected parent pve1-node1, got %s", r.ParentID)
|
||||
}
|
||||
|
||||
// Check CPU is converted to percentage
|
||||
|
|
@ -229,8 +229,8 @@ func TestFromContainer(t *testing.T) {
|
|||
if r.Status != StatusStopped {
|
||||
t.Errorf("Expected status %s, got %s", StatusStopped, r.Status)
|
||||
}
|
||||
if r.ParentID != "pve1/node/node1" {
|
||||
t.Errorf("Expected parent pve1/node/node1, got %s", r.ParentID)
|
||||
if r.ParentID != "pve1-node1" {
|
||||
t.Errorf("Expected parent pve1-node1, got %s", r.ParentID)
|
||||
}
|
||||
|
||||
var pd ContainerPlatformData
|
||||
|
|
|
|||
Loading…
Reference in a new issue