import { createElement, useEffect } from 'react' import { Handle, Position, NodeResizer, useUpdateNodeInternals, type NodeProps, type Node } from '@xyflow/react' import { Cpu, MemoryStick, HardDrive, type LucideIcon } from 'lucide-react' import type { NodeData } from '@/types' import { resolveNodeColors } from '@/utils/nodeColors' import { resolveNodeIcon } from '@/utils/nodeIcons' import { useThemeStore } from '@/stores/themeStore' import { THEMES } from '@/utils/themes' import { useCanvasStore } from '@/stores/canvasStore' import { maskIp } from '@/utils/maskIp' import { BOTTOM_HANDLE_IDS, BOTTOM_HANDLE_POSITIONS } from '@/utils/handleUtils' interface BaseNodeProps extends NodeProps> { icon: LucideIcon } function formatStorage(gb: number): string { if (gb >= 1024) return `${(gb / 1024).toFixed(1).replace(/\.0$/, '')} TB` return `${gb} GB` } export function BaseNode({ id, data, selected, icon: typeIcon, width, height }: BaseNodeProps) { const updateNodeInternals = useUpdateNodeInternals() useEffect(() => { updateNodeInternals(id) }, [data.bottom_handles, id, updateNodeInternals]) const activeTheme = useThemeStore((s) => s.activeTheme) const hideIp = useCanvasStore((s) => s.hideIp) const theme = THEMES[activeTheme] const resolvedIcon = resolveNodeIcon(typeIcon, data.custom_icon) const colors = resolveNodeColors(data, activeTheme) const statusColor = theme.colors.statusColors[data.status] const isOnline = data.status === 'online' const showHardware = data.show_hardware && (data.cpu_count != null || data.cpu_model || data.ram_gb != null || data.disk_gb != null) return (
{/* Main row */}
{/* Icon */}
{createElement(resolvedIcon, { size: 15 })}
{/* Label + IP */}
{data.label}
{data.ip && (
{hideIp ? maskIp(data.ip) : data.ip}
)}
{/* Hardware section */} {showHardware && ( <>
{/* Line 1: CPU */} {(data.cpu_model || data.cpu_count != null) && (
{data.cpu_model && ( {data.cpu_model} )} {data.cpu_count != null && ( {data.cpu_model ? `ยท ${data.cpu_count}c` : `${data.cpu_count} cores`} )}
)} {/* Line 2: RAM + Disk */} {(data.ram_gb != null || data.disk_gb != null) && (
{data.ram_gb != null && ( {formatStorage(data.ram_gb)} )} {data.disk_gb != null && ( {formatStorage(data.disk_gb)} )}
)}
)} {/* Status dot */}
{(BOTTOM_HANDLE_POSITIONS[data.bottom_handles ?? 1] ?? BOTTOM_HANDLE_POSITIONS[1]).map((leftPct, idx) => { const sourceId = BOTTOM_HANDLE_IDS[idx] const targetId = idx === 0 ? 'bottom-t' : `bottom-${idx + 1}-t` return ( ) })}
) }