"use client" import { Card } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Cpu, HardDrive, Thermometer, Zap, Loader2, CpuIcon, Cpu as Gpu, Network, MemoryStick, PowerIcon, FanIcon, Battery, Usb, BrainCircuit, AlertCircle } from "lucide-react" import { Download } from "lucide-react" import { Button } from "@/components/ui/button" import useSWR from "swr" import { useState, useEffect } from "react" import { type HardwareData, type GPU, type PCIDevice, type StorageDevice, type CoralTPU, type UsbDevice, fetcher as swrFetcher, } from "../types/hardware" import { fetchApi } from "@/lib/api-config" import { ScriptTerminalModal } from "./script-terminal-modal" import { GpuSwitchModeIndicator } from "./gpu-switch-mode-indicator" import { Settings2, CheckCircle2 } from "lucide-react" const parseLsblkSize = (sizeStr: string | undefined): number => { if (!sizeStr) return 0 // Remove spaces and convert to uppercase const cleaned = sizeStr.trim().toUpperCase() // Extract number and unit const match = cleaned.match(/^([\d.]+)([KMGT]?)$/) if (!match) return 0 const value = Number.parseFloat(match[1]) const unit = match[2] || "K" // Default to KB if no unit // Convert to KB switch (unit) { case "K": return value case "M": return value * 1024 case "G": return value * 1024 * 1024 case "T": return value * 1024 * 1024 * 1024 default: return value } } const formatMemory = (memoryKB: number | string): string => { const kb = typeof memoryKB === "string" ? Number.parseFloat(memoryKB) : memoryKB if (isNaN(kb)) return "N/A" // Convert KB to MB const mb = kb / 1024 // Convert to TB if >= 1024 GB if (mb >= 1024 * 1024) { const tb = mb / (1024 * 1024) return `${tb.toFixed(1)} TB` } if (mb >= 1024) { const gb = mb / 1024 // If GB value is greater than 999, convert to TB if (gb > 999) { return `${(gb / 1024).toFixed(2)} TB` } return `${gb.toFixed(1)} GB` } // Keep in MB if < 1024 MB return `${mb.toFixed(0)} MB` } const formatClock = (clockString: string | number): string => { let mhz: number if (typeof clockString === "number") { mhz = clockString } else { // Extract numeric value from string like "1138.179107 MHz" const match = clockString.match(/([\d.]+)\s*MHz/i) if (!match) return clockString mhz = Number.parseFloat(match[1]) } if (isNaN(mhz)) return String(clockString) // Convert to GHz if >= 1000 MHz if (mhz >= 1000) { const ghz = mhz / 1000 return `${ghz.toFixed(2)} GHz` } // Keep in MHz if < 1000 MHz return `${mhz.toFixed(0)} MHz` } const getDeviceTypeColor = (type: string): string => { const lowerType = type.toLowerCase() // UPS / battery — amber: warm orange-yellow, distinct from the orange used // for Storage and avoids the "warning" connotation of pure yellow. if (lowerType === "ups" || lowerType.includes("battery")) { return "bg-amber-500/10 text-amber-500 border-amber-500/20" } // Storage family — orange (Mass Storage USB class + PCI storage controllers) if (lowerType.includes("storage") || lowerType.includes("sata") || lowerType.includes("raid")) { return "bg-orange-500/10 text-orange-500 border-orange-500/20" } // Printer — rose, unmistakable if (lowerType.includes("printer")) { return "bg-rose-500/10 text-rose-500 border-rose-500/20" } // Audio family — teal (Audio, Audio/Video); placed before video so that // combined "Audio/Video" class labels read as audio-family. if (lowerType.includes("audio")) { return "bg-teal-500/10 text-teal-500 border-teal-500/20" } // Graphics / Video / Imaging — green (cameras, webcams, displays, GPUs). if ( lowerType.includes("graphics") || lowerType.includes("vga") || lowerType.includes("display") || lowerType.includes("video") || lowerType.includes("imaging") ) { return "bg-green-500/10 text-green-500 border-green-500/20" } // Network family — blue (Ethernet / Wi-Fi PCI controllers, USB Communications, // CDC Data, Wireless Controllers like Bluetooth dongles). if ( lowerType.includes("network") || lowerType.includes("ethernet") || lowerType.includes("communications") || lowerType.includes("wireless") || lowerType === "cdc data" ) { return "bg-blue-500/10 text-blue-500 border-blue-500/20" } // HID — purple: keyboards, mice, game controllers. if (lowerType === "hid") { return "bg-purple-500/10 text-purple-500 border-purple-500/20" } // USB host controllers (PCI-level) keep the existing purple identity. if (lowerType.includes("usb")) { return "bg-purple-500/10 text-purple-500 border-purple-500/20" } // Smart Card, Billboard, Diagnostic, Hub, Physical, Content Security, // Personal Healthcare, Miscellaneous, Application/Vendor Specific, unknown. return "bg-gray-500/10 text-gray-500 border-gray-500/20" } const getMonitoringToolRecommendation = (vendor: string): string => { const lowerVendor = vendor.toLowerCase() if (lowerVendor.includes("intel")) { return "To get extended GPU monitoring information, please install intel-gpu-tools or igt-gpu-tools package." } if (lowerVendor.includes("nvidia")) { return "For NVIDIA GPUs, real-time monitoring requires the proprietary drivers (nvidia-driver package). Install them only if your GPU is used directly by the host." } if (lowerVendor.includes("amd") || lowerVendor.includes("ati")) { return "To get extended GPU monitoring information for AMD GPUs, please install amdgpu_top. You can download it from: https://github.com/Umio-Yasuno/amdgpu_top" } return "To get extended GPU monitoring information, please install the appropriate GPU monitoring tools for your hardware." } const groupAndSortTemperatures = (temperatures: any[]) => { const groups = { CPU: [] as any[], GPU: [] as any[], NVME: [] as any[], PCI: [] as any[], OTHER: [] as any[], } temperatures.forEach((temp) => { const nameLower = temp.name.toLowerCase() const adapterLower = temp.adapter?.toLowerCase() || "" if (nameLower.includes("cpu") || nameLower.includes("core") || nameLower.includes("package")) { groups.CPU.push(temp) } else if (nameLower.includes("gpu") || adapterLower.includes("gpu")) { groups.GPU.push(temp) } else if (nameLower.includes("nvme") || adapterLower.includes("nvme")) { groups.NVME.push(temp) } else if (adapterLower.includes("pci")) { groups.PCI.push(temp) } else { groups.OTHER.push(temp) } }) return groups } export default function Hardware() { // Static data - loaded once on mount. Static fields (CPU, motherboard, memory // modules, PCI, disks, GPU list) don't change at runtime, so no auto-refresh. // `mutateStatic` is triggered explicitly after GPU switch-mode changes. const { data: staticHardwareData, error: staticError, isLoading: staticLoading, mutate: mutateStatic, } = useSWR("/api/hardware", swrFetcher, { revalidateOnFocus: false, revalidateOnReconnect: false, refreshInterval: 0, }) // Live data - only temperatures, fans, power, UPS. Polled every 5s. // Backend /api/hardware/live uses cached ipmitool output (10s) so this is cheap. const { data: dynamicHardwareData, error: dynamicError, } = useSWR("/api/hardware/live", swrFetcher, { refreshInterval: 5000, revalidateOnFocus: true, revalidateOnReconnect: true, dedupingInterval: 2000, }) // Merge: static fields from initial load, live fields from the 5s poll. // coral_tpus and usb_devices live in the dynamic payload so that the // "Install Drivers" button disappears immediately after install_coral.sh // finishes, without requiring a page reload. const hardwareData = staticHardwareData ? { ...staticHardwareData, temperatures: dynamicHardwareData?.temperatures ?? staticHardwareData.temperatures, fans: dynamicHardwareData?.fans ?? staticHardwareData.fans, power_meter: dynamicHardwareData?.power_meter ?? staticHardwareData.power_meter, power_supplies: dynamicHardwareData?.power_supplies ?? staticHardwareData.power_supplies, ups: dynamicHardwareData?.ups ?? staticHardwareData.ups, coral_tpus: dynamicHardwareData?.coral_tpus ?? staticHardwareData.coral_tpus, usb_devices: dynamicHardwareData?.usb_devices ?? staticHardwareData.usb_devices, } : undefined const error = staticError || dynamicError const isLoading = staticLoading useEffect(() => { if (hardwareData?.storage_devices) { console.log("[v0] Storage devices data from backend:", hardwareData.storage_devices) hardwareData.storage_devices.forEach((device) => { if (device.name.startsWith("nvme")) { console.log(`[v0] NVMe device ${device.name}:`, { pcie_gen: device.pcie_gen, pcie_width: device.pcie_width, pcie_max_gen: device.pcie_max_gen, pcie_max_width: device.pcie_max_width, }) } }) } }, [hardwareData]) const [selectedGPU, setSelectedGPU] = useState(null) const [realtimeGPUData, setRealtimeGPUData] = useState(null) const [detailsLoading, setDetailsLoading] = useState(false) const [selectedPCIDevice, setSelectedPCIDevice] = useState(null) const [selectedDisk, setSelectedDisk] = useState(null) const [selectedNetwork, setSelectedNetwork] = useState(null) const [selectedUPS, setSelectedUPS] = useState(null) const [showNvidiaInstaller, setShowNvidiaInstaller] = useState(false) const [installingNvidiaDriver, setInstallingNvidiaDriver] = useState(false) const [showAmdInstaller, setShowAmdInstaller] = useState(false) const [showIntelInstaller, setShowIntelInstaller] = useState(false) const [showCoralInstaller, setShowCoralInstaller] = useState(false) const [selectedCoral, setSelectedCoral] = useState(null) const [selectedUsbDevice, setSelectedUsbDevice] = useState(null) // GPU Switch Mode states const [editingSwitchModeGpu, setEditingSwitchModeGpu] = useState(null) // GPU slot being edited const [pendingSwitchModes, setPendingSwitchModes] = useState>({}) const [showSwitchModeModal, setShowSwitchModeModal] = useState(false) const [switchModeParams, setSwitchModeParams] = useState<{ gpuSlot: string; targetMode: "lxc" | "vm" } | null>(null) // Determine GPU mode based on driver (vfio-pci = VM, native driver = LXC) const getGpuSwitchMode = (gpu: GPU): "lxc" | "vm" | "unknown" => { const driver = gpu.pci_driver?.toLowerCase() || "" const kernelModule = gpu.pci_kernel_module?.toLowerCase() || "" // Check driver first if (driver === "vfio-pci") return "vm" if (driver === "nvidia" || driver === "amdgpu" || driver === "radeon" || driver === "i915" || driver === "xe" || driver === "nouveau" || driver === "mgag200") return "lxc" if (driver && driver !== "none" && driver !== "") return "lxc" // Fallback to kernel module if no driver if (kernelModule.includes("vfio")) return "vm" if (kernelModule.includes("nvidia") || kernelModule.includes("amdgpu") || kernelModule.includes("radeon") || kernelModule.includes("i915") || kernelModule.includes("xe") || kernelModule.includes("nouveau") || kernelModule.includes("mgag200")) return "lxc" if (kernelModule && kernelModule !== "none" && kernelModule !== "") return "lxc" return "unknown" } const handleSwitchModeEdit = (gpuSlot: string, e: React.MouseEvent) => { e.stopPropagation() // Prevent opening GPU modal setEditingSwitchModeGpu(gpuSlot) } const handleSwitchModeToggle = (gpu: GPU, e?: React.MouseEvent) => { const slot = gpu.slot const currentMode = getGpuSwitchMode(gpu) const pendingMode = pendingSwitchModes[slot] // Toggle between modes if (pendingMode) { // Already has pending - toggle it const newMode = pendingMode === "lxc" ? "vm" : "lxc" if (newMode === currentMode) { // Back to original - remove pending const newPending = { ...pendingSwitchModes } delete newPending[slot] setPendingSwitchModes(newPending) } else { setPendingSwitchModes({ ...pendingSwitchModes, [slot]: newMode }) } } else { // No pending - set opposite of current const newMode = currentMode === "lxc" ? "vm" : "lxc" setPendingSwitchModes({ ...pendingSwitchModes, [slot]: newMode }) } } const handleSwitchModeSave = (gpuSlot: string, e: React.MouseEvent) => { e.stopPropagation() const pendingMode = pendingSwitchModes[gpuSlot] const gpu = hardwareData?.gpus?.find(g => g.slot === gpuSlot) const currentMode = gpu ? getGpuSwitchMode(gpu) : "unknown" if (pendingMode && pendingMode !== currentMode && gpu) { // Mode has changed - save params and launch the script setSwitchModeParams({ gpuSlot: gpu.slot, targetMode: pendingMode }) setShowSwitchModeModal(true) } setEditingSwitchModeGpu(null) } const handleSwitchModeCancel = (gpuSlot: string, e: React.MouseEvent) => { e.stopPropagation() // Remove pending change for this GPU const newPending = { ...pendingSwitchModes } delete newPending[gpuSlot] setPendingSwitchModes(newPending) setEditingSwitchModeGpu(null) } const handleSwitchModeModalClose = () => { setShowSwitchModeModal(false) // Clear params and pending changes after script runs setSwitchModeParams(null) setPendingSwitchModes({}) // Refresh hardware data mutateStatic() } const handleInstallNvidiaDriver = () => { console.log("[v0] Opening NVIDIA installer terminal") setShowNvidiaInstaller(true) } const handleInstallAmdTools = () => { console.log("[v0] Opening AMD GPU tools installer terminal") setShowAmdInstaller(true) } const handleInstallIntelTools = () => { console.log("[v0] Opening Intel GPU tools installer terminal") setShowIntelInstaller(true) } useEffect(() => { if (!selectedGPU) return const pciDevice = findPCIDeviceForGPU(selectedGPU) const fullSlot = pciDevice?.slot || selectedGPU.slot if (!fullSlot) return const abortController = new AbortController() const fetchRealtimeData = async () => { try { const data = await fetchApi(`/api/gpu/${fullSlot}/realtime`) setRealtimeGPUData(data) setDetailsLoading(false) } catch (error) { if (error instanceof Error && error.name !== "AbortError") { console.error("[v0] Error fetching GPU realtime data:", error) } setRealtimeGPUData({ has_monitoring_tool: false }) setDetailsLoading(false) } } fetchRealtimeData() const interval = setInterval(fetchRealtimeData, 3000) return () => { clearInterval(interval) abortController.abort() } }, [selectedGPU]) const handleGPUClick = async (gpu: GPU) => { setSelectedGPU(gpu) setDetailsLoading(true) setRealtimeGPUData(null) } const findPCIDeviceForGPU = (gpu: GPU): PCIDevice | null => { if (!hardwareData?.pci_devices || !gpu.slot) return null // Try to find exact match first (e.g., "00:02.0") let pciDevice = hardwareData.pci_devices.find((d) => d.slot === gpu.slot) // If not found, try to match by partial slot (e.g., "00" matches "00:02.0") if (!pciDevice && gpu.slot.length <= 2) { pciDevice = hardwareData.pci_devices.find( (d) => d.slot.startsWith(gpu.slot + ":") && (d.type.toLowerCase().includes("vga") || d.type.toLowerCase().includes("graphics") || d.type.toLowerCase().includes("display")), ) } return pciDevice || null } const hasRealtimeData = (): boolean => { if (!realtimeGPUData) return false // Esto permite mostrar datos incluso cuando la GPU está inactiva (valores en 0 o null) return realtimeGPUData.has_monitoring_tool === true } if (isLoading) { return (
Loading hardware data...

Detecting CPU, GPU, storage and PCI devices

) } return (
{/* System Information - CPU & Motherboard */} {(hardwareData?.cpu || hardwareData?.motherboard) && (

System Information

{/* CPU Info */} {hardwareData?.cpu && Object.keys(hardwareData.cpu).length > 0 && (

CPU

{hardwareData.cpu.model && (
Model {hardwareData.cpu.model}
)} {hardwareData.cpu.cores_per_socket && hardwareData.cpu.sockets && (
Cores {hardwareData.cpu.sockets} × {hardwareData.cpu.cores_per_socket} ={" "} {hardwareData.cpu.sockets * hardwareData.cpu.cores_per_socket} cores
)} {hardwareData.cpu.total_threads && (
Threads {hardwareData.cpu.total_threads}
)} {hardwareData.cpu.l3_cache && (
L3 Cache {hardwareData.cpu.l3_cache}
)} {hardwareData.cpu.virtualization && (
Virtualization {hardwareData.cpu.virtualization}
)}
)} {/* Motherboard Info */} {hardwareData?.motherboard && Object.keys(hardwareData.motherboard).length > 0 && (

Motherboard

{hardwareData.motherboard.manufacturer && (
Manufacturer {hardwareData.motherboard.manufacturer}
)} {hardwareData.motherboard.model && (
Model {hardwareData.motherboard.model}
)} {hardwareData.motherboard.bios?.vendor && (
BIOS {hardwareData.motherboard.bios.vendor}
)} {hardwareData.motherboard.bios?.version && (
Version {hardwareData.motherboard.bios.version}
)} {hardwareData.motherboard.bios?.date && (
Date {hardwareData.motherboard.bios.date}
)}
)}
)} {/* Memory Modules */} {hardwareData?.memory_modules && hardwareData.memory_modules.length > 0 && (

Memory Modules

{hardwareData.memory_modules.length} installed
{hardwareData.memory_modules.map((module, index) => (
{module.slot}
{module.size && (
Size {formatMemory(module.size)}
)} {module.type && (
Type {module.type}
)} {(module.configured_speed || module.max_speed) && (
Speed {module.configured_speed && module.max_speed && module.configured_speed !== module.max_speed ? ( {module.configured_speed} (max: {module.max_speed}) ) : ( {module.configured_speed || module.max_speed} )}
)} {module.manufacturer && (
Manufacturer {module.manufacturer}
)}
))}
)} {/* Thermal Monitoring */} {hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (

Thermal Monitoring

{hardwareData.temperatures.length} sensors
{/* CPU Sensors */} {groupAndSortTemperatures(hardwareData.temperatures).CPU.length > 0 && (

CPU

{groupAndSortTemperatures(hardwareData.temperatures).CPU.length}
{groupAndSortTemperatures(hardwareData.temperatures).CPU.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) const isCritical = temp.current > (temp.critical || 90) return (
{temp.name} {temp.current.toFixed(1)}°C
{temp.adapter && {temp.adapter}}
) })}
)} {/* GPU Sensors */} {groupAndSortTemperatures(hardwareData.temperatures).GPU.length > 0 && (
1 ? "md:col-span-2" : ""} >

GPU

{groupAndSortTemperatures(hardwareData.temperatures).GPU.length}
1 ? "md:grid-cols-2" : ""}`} > {groupAndSortTemperatures(hardwareData.temperatures).GPU.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) const isCritical = temp.current > (temp.critical || 90) return (
{temp.name} {temp.current.toFixed(1)}°C
{temp.adapter && {temp.adapter}}
) })}
)} {/* NVME Sensors */} {groupAndSortTemperatures(hardwareData.temperatures).NVME.length > 0 && (
1 ? "md:col-span-2" : "" } >

NVME

{groupAndSortTemperatures(hardwareData.temperatures).NVME.length}
1 ? "md:grid-cols-2" : ""}`} > {groupAndSortTemperatures(hardwareData.temperatures).NVME.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) const isCritical = temp.current > (temp.critical || 90) return (
{temp.name} {temp.current.toFixed(1)}°C
{temp.adapter && {temp.adapter}}
) })}
)} {/* PCI Sensors */} {groupAndSortTemperatures(hardwareData.temperatures).PCI.length > 0 && (
1 ? "md:col-span-2" : ""} >

PCI

{groupAndSortTemperatures(hardwareData.temperatures).PCI.length}
1 ? "md:grid-cols-2" : ""}`} > {groupAndSortTemperatures(hardwareData.temperatures).PCI.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) const isCritical = temp.current > (temp.critical || 90) return (
{temp.name} {temp.current.toFixed(1)}°C
{temp.adapter && {temp.adapter}}
) })}
)} {/* OTHER Sensors */} {groupAndSortTemperatures(hardwareData.temperatures).OTHER.length > 0 && (
1 ? "md:col-span-2" : "" } >

OTHER

{groupAndSortTemperatures(hardwareData.temperatures).OTHER.length}
1 ? "md:grid-cols-2" : ""}`} > {groupAndSortTemperatures(hardwareData.temperatures).OTHER.map((temp, index) => { const percentage = temp.critical > 0 ? (temp.current / temp.critical) * 100 : (temp.current / 100) * 100 const isHot = temp.current > (temp.high || 80) const isCritical = temp.current > (temp.critical || 90) return (
{temp.name} {temp.current.toFixed(1)}°C
{temp.adapter && {temp.adapter}}
) })}
)}
)} {/* GPU Information - Enhanced with on-demand data fetching */} {hardwareData?.gpus && hardwareData.gpus.length > 0 && (

Graphics Cards

{hardwareData.gpus.length} GPU{hardwareData.gpus.length > 1 ? "s" : ""}
{hardwareData.gpus.map((gpu, index) => { const pciDevice = findPCIDeviceForGPU(gpu) const fullSlot = pciDevice?.slot || gpu.slot return (
{ // Don't open modal if we're editing this GPU's switch mode if (editingSwitchModeGpu !== fullSlot) { handleGPUClick(gpu) } }} className={`rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card p-4 transition-colors ${ editingSwitchModeGpu === fullSlot ? "cursor-default" : "cursor-pointer sm:hover:bg-white/5" }`} >
{gpu.name} {gpu.vendor}
Type {gpu.type}
{fullSlot && (
PCI Slot {fullSlot}
)} {gpu.pci_driver && (
Driver {gpu.pci_driver}
)} {gpu.pci_kernel_module && (
Kernel Module {gpu.pci_kernel_module}
)}
{/* GPU Switch Mode Indicator */} {getGpuSwitchMode(gpu) !== "unknown" && (
Switch Mode
{editingSwitchModeGpu === fullSlot ? ( <> ) : ( )}
handleSwitchModeToggle(gpu, e)} />
)}
) })}
)} {/* GPU Detail Modal - Shows immediately with basic info, then loads real-time data */} !open && setSelectedGPU(null)}> {selectedGPU && ( <> {selectedGPU.name} GPU Real-Time Monitoring

Basic Information

Vendor {selectedGPU.vendor}
Type {selectedGPU.type}
PCI Slot {findPCIDeviceForGPU(selectedGPU)?.slot || selectedGPU.slot}
{(findPCIDeviceForGPU(selectedGPU)?.driver || selectedGPU.pci_driver) && (
Driver {/* CHANGE: Added monitoring availability indicator */}
{findPCIDeviceForGPU(selectedGPU)?.driver || selectedGPU.pci_driver} {realtimeGPUData?.has_monitoring_tool === true && ( {realtimeGPUData?.driver_version ? `✓ v${realtimeGPUData.driver_version}` : "✓"} )}
)} {(findPCIDeviceForGPU(selectedGPU)?.kernel_module || selectedGPU.pci_kernel_module) && (
Kernel Module {findPCIDeviceForGPU(selectedGPU)?.kernel_module || selectedGPU.pci_kernel_module}
)}
{detailsLoading ? (

Loading real-time data...

) : realtimeGPUData?.has_monitoring_tool === true ? ( <>
Updating every 3 seconds

Real-Time Metrics

{realtimeGPUData.clock_graphics && (
Graphics Clock {formatClock(realtimeGPUData.clock_graphics)}
)} {realtimeGPUData.clock_memory && (
Memory Clock {formatClock(realtimeGPUData.clock_memory)}
)} {realtimeGPUData.power_draw && realtimeGPUData.power_draw !== "0.00 W" && (
Power Draw {realtimeGPUData.power_draw}
)} {realtimeGPUData.temperature !== undefined && realtimeGPUData.temperature !== null && (
Temperature {realtimeGPUData.temperature}°C
)}
{/* Engine Utilization (Intel/AMD) */} {(realtimeGPUData.engine_render !== undefined || realtimeGPUData.engine_blitter !== undefined || realtimeGPUData.engine_video !== undefined || realtimeGPUData.engine_video_enhance !== undefined) && (

Engine Utilization (Total)

{realtimeGPUData.engine_render !== undefined && (
Render/3D {typeof realtimeGPUData.engine_render === "number" ? `${realtimeGPUData.engine_render.toFixed(1)}%` : realtimeGPUData.engine_render}
)} {realtimeGPUData.engine_video !== undefined && (
Video {typeof realtimeGPUData.engine_video === "number" ? `${realtimeGPUData.engine_video.toFixed(1)}%` : realtimeGPUData.engine_video}
)} {realtimeGPUData.engine_blitter !== undefined && (
Blitter {typeof realtimeGPUData.engine_blitter === "number" ? `${realtimeGPUData.engine_blitter.toFixed(1)}%` : realtimeGPUData.engine_blitter}
)} {realtimeGPUData.engine_video_enhance !== undefined && (
VideoEnhance {typeof realtimeGPUData.engine_video_enhance === "number" ? `${realtimeGPUData.engine_video_enhance.toFixed(1)}%` : realtimeGPUData.engine_video_enhance}
)}
)} {/* CHANGE: Changed process name badge from blue to purple to match Intel/AMD */} {realtimeGPUData.processes && realtimeGPUData.processes.length > 0 && (

Active Processes ({realtimeGPUData.processes.length})

{realtimeGPUData.processes.map((proc: any, idx: number) => (
{proc.name}

PID: {proc.pid}

{proc.memory && ( {typeof proc.memory === "object" ? formatMemory(proc.memory.resident / 1024) : formatMemory(proc.memory)} )}
{proc.engines && Object.keys(proc.engines).length > 0 && (

Engine Utilization:

{Object.entries(proc.engines).map(([engineName, engineData]: [string, any]) => { const utilization = typeof engineData === "object" ? engineData.busy || 0 : engineData const utilizationNum = typeof utilization === "string" ? Number.parseFloat(utilization) : utilization if (utilizationNum === 0 || isNaN(utilizationNum)) return null return (
{engineName} {utilizationNum.toFixed(1)}%
) })}
)}
))}
)} {realtimeGPUData.processes && realtimeGPUData.processes.length === 0 && (

No active processes using the GPU

)} {/* Memory Info (NVIDIA) */} {realtimeGPUData.memory_total && (

Memory

Total {realtimeGPUData.memory_total}
Used {realtimeGPUData.memory_used}
Free {realtimeGPUData.memory_free}
{realtimeGPUData.utilization_memory !== undefined && (
Memory Utilization {realtimeGPUData.utilization_memory}%
)}
)} ) : (findPCIDeviceForGPU(selectedGPU)?.driver === 'vfio-pci' || selectedGPU.pci_driver === 'vfio-pci') ? (

GPU in Switch Mode VM

This GPU is assigned to a virtual machine via VFIO passthrough. Real-time monitoring is not available from the host because the GPU is controlled by the VM.

) : (

Extended Monitoring Not Available

{getMonitoringToolRecommendation(selectedGPU.vendor)}

{selectedGPU.vendor.toLowerCase().includes("nvidia") && ( )} {(selectedGPU.vendor.toLowerCase().includes("amd") || selectedGPU.vendor.toLowerCase().includes("ati")) && ( )} {selectedGPU.vendor.toLowerCase().includes("intel") && ( )}
)}
)}
{/* Coral TPU / AI Accelerators — only rendered when at least one device is detected. Unlike GPUs, Coral exposes no temperature/utilization/power counters, so the modal shows identity + driver state + an Install CTA when drivers are missing. */} {hardwareData?.coral_tpus && hardwareData.coral_tpus.length > 0 && (

Coral TPU / AI Accelerators

{hardwareData.coral_tpus.length} device{hardwareData.coral_tpus.length > 1 ? "s" : ""}
{hardwareData.coral_tpus.map((coral, index) => (
setSelectedCoral(coral)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-4 transition-colors" >
{coral.name} {coral.type === "usb" ? "USB" : "PCIe"}
{coral.form_factor && (
{coral.form_factor} {coral.interface_speed && · {coral.interface_speed}}
)}
{coral.type === "pcie" ? coral.slot : coral.bus_device}
{coral.drivers_ready ? ( <> Drivers ready ) : ( <> Drivers not installed )}
))}
{/* Primary CTA at the section level when ANY of the detected Coral devices is missing drivers — avoids a per-card button repetition. */} {hardwareData.coral_tpus.some((c) => !c.drivers_ready) && (

Install Coral TPU drivers

One or more detected Coral devices need drivers. A server reboot is required after installation.

)}
)} {/* Coral TPU detail modal */} !open && setSelectedCoral(null)}> {selectedCoral?.name} Coral TPU Device Information {selectedCoral && (
Connection {selectedCoral.type === "usb" ? "USB" : "PCIe / M.2"}
{selectedCoral.form_factor && (
Form Factor {selectedCoral.form_factor}
)} {selectedCoral.interface_speed && (
Link {selectedCoral.interface_speed}
)}
{selectedCoral.type === "usb" ? "Bus:Device" : "PCI Slot"} {selectedCoral.type === "usb" ? selectedCoral.bus_device : selectedCoral.slot}
Vendor / Product ID {selectedCoral.vendor_id}:{selectedCoral.device_id}
Vendor {selectedCoral.vendor}
{selectedCoral.type === "pcie" && selectedCoral.kernel_driver && (
Kernel Driver {selectedCoral.kernel_driver}
)} {selectedCoral.kernel_modules && (
Kernel Modules
gasket {selectedCoral.kernel_modules.gasket ? "✓" : "✗"} apex {selectedCoral.kernel_modules.apex ? "✓" : "✗"}
)} {selectedCoral.device_nodes && selectedCoral.device_nodes.length > 0 && (
Device Nodes {selectedCoral.device_nodes.join(", ")}
)} {selectedCoral.type === "usb" && (
Runtime State {selectedCoral.programmed ? "Programmed (runtime loaded)" : "Unprogrammed (runtime not loaded)"}
)}
Edge TPU Runtime {selectedCoral.edgetpu_runtime || not installed}
{typeof selectedCoral.temperature === "number" && (() => { const trips = selectedCoral.temperature_trips // Dynamic thresholds when the driver exposes trip points. // Otherwise fall back to conservative hardcoded limits. // trips are reported warn→critical, so [N-1] is critical (red) // and [N-2] is the throttle/warn level (amber). const redAt = trips && trips.length >= 1 ? trips[trips.length - 1] : 85 const amberAt = trips && trips.length >= 2 ? trips[trips.length - 2] : trips && trips.length === 1 ? redAt - 10 : 75 const color = selectedCoral.temperature >= redAt ? "text-red-500" : selectedCoral.temperature >= amberAt ? "text-amber-500" : "text-green-500" return (
Temperature
{selectedCoral.temperature.toFixed(1)} °C {trips && trips.length > 0 && (
Thresholds: {trips.map((t) => `${t.toFixed(0)}°C`).join(" · ")}
)}
) })()} {selectedCoral.thermal_warnings && selectedCoral.thermal_warnings.length > 0 && (
Hardware Warnings
{selectedCoral.thermal_warnings.map((w) => (
{w.name} {typeof w.threshold_c === "number" && ` @ ${w.threshold_c.toFixed(0)}°C`} {w.enabled ? "enabled" : "disabled"}
))}
)} {!selectedCoral.drivers_ready && ( )}
)}
{/* Power Consumption */} {hardwareData?.power_meter && (

Power Consumption

{hardwareData.power_meter.name}

{hardwareData.power_meter.adapter && (

{hardwareData.power_meter.adapter}

)}

{hardwareData.power_meter.watts.toFixed(1)} W

Current Draw

)} {/* Power Supplies */} {hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && (

Power Supplies

{hardwareData.power_supplies.length} PSUs
{hardwareData.power_supplies.map((psu, index) => (
{psu.name} {psu.status && ( {psu.status} )}

{psu.watts} W

Current Output

))}
)} {/* Fans */} {hardwareData?.fans && hardwareData.fans.length > 0 && (

System Fans

{hardwareData.fans.length} fans
{hardwareData.fans.map((fan, index) => { const isPercentage = fan.unit === "percent" || fan.unit === "%" const percentage = isPercentage ? fan.speed : Math.min((fan.speed / 5000) * 100, 100) return (
{fan.name} {isPercentage ? `${fan.speed.toFixed(0)} percent` : `${fan.speed.toFixed(0)} ${fan.unit}`}
{fan.adapter && {fan.adapter}}
) })}
)} {/* UPS */} {hardwareData?.ups && Array.isArray(hardwareData.ups) && hardwareData.ups.length > 0 && (

UPS Status

{hardwareData.ups.length} UPS
{hardwareData.ups.map((ups: any, index: number) => { const batteryCharge = ups.battery_charge_raw || Number.parseFloat(ups.battery_charge?.replace("%", "") || "0") const loadPercent = ups.load_percent_raw || Number.parseFloat(ups.load_percent?.replace("%", "") || "0") // Determine status badge color const getStatusColor = (status: string) => { if (!status) return "bg-gray-500/10 text-gray-500 border-gray-500/20" const statusUpper = status.toUpperCase() if (statusUpper.includes("OL")) return "bg-green-500/10 text-green-500 border-green-500/20" if (statusUpper.includes("OB")) return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" if (statusUpper.includes("LB")) return "bg-red-500/10 text-red-500 border-red-500/20" return "bg-blue-500/10 text-blue-500 border-blue-500/20" } return (
setSelectedUPS(ups)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-4 transition-colors" >
{ups.model || ups.name} {ups.is_remote && Remote: {ups.host}}
{ups.status || "Unknown"}
{ups.battery_charge && (
Battery Charge {ups.battery_charge}
)} {ups.load_percent && (
Load {ups.load_percent}
)} {ups.time_left && (
Runtime
{ups.time_left}
)} {ups.input_voltage && (
Input Voltage
{ups.input_voltage}
)}
) })}
)} setSelectedUPS(null)}> {selectedUPS && ( <> {selectedUPS.model || selectedUPS.name} UPS Detailed Information {selectedUPS.is_remote && ` • Remote: ${selectedUPS.host}`}
{/* Status Overview */}

Status Overview

Status {selectedUPS.status || "Unknown"}
Connection {selectedUPS.connection_type}
{selectedUPS.host && (
Host {selectedUPS.host}
)}
{/* Battery Information */}

Battery Information

{selectedUPS.battery_charge && (
Charge Level {selectedUPS.battery_charge}
)} {selectedUPS.time_left && (
Runtime Remaining {selectedUPS.time_left}
)} {selectedUPS.battery_voltage && (
Battery Voltage {selectedUPS.battery_voltage}
)} {selectedUPS.battery_date && (
Battery Date {selectedUPS.battery_date}
)}
{/* Input/Output Information */}

Power Information

{selectedUPS.input_voltage && (
Input Voltage {selectedUPS.input_voltage}
)} {selectedUPS.output_voltage && (
Output Voltage {selectedUPS.output_voltage}
)} {selectedUPS.input_frequency && (
Input Frequency {selectedUPS.input_frequency}
)} {selectedUPS.output_frequency && (
Output Frequency {selectedUPS.output_frequency}
)} {selectedUPS.load_percent && (
Load {selectedUPS.load_percent}
)} {selectedUPS.real_power && (
Real Power {selectedUPS.real_power}
)} {selectedUPS.apparent_power && (
Apparent Power {selectedUPS.apparent_power}
)}
{/* Device Information */}

Device Information

{selectedUPS.manufacturer && (
Manufacturer {selectedUPS.manufacturer}
)} {selectedUPS.model && (
Model {selectedUPS.model}
)} {selectedUPS.serial && (
Serial Number {selectedUPS.serial}
)} {selectedUPS.firmware && (
Firmware {selectedUPS.firmware}
)} {selectedUPS.driver && (
Driver {selectedUPS.driver}
)}
)}
{/* PCI Devices - Changed to modal */} {hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && (

PCI Devices

{hardwareData.pci_devices.length} devices
{hardwareData.pci_devices.map((device, index) => (
setSelectedPCIDevice(device)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-3 transition-colors" >
{device.type} {device.slot}

{device.device}

{device.vendor}

{device.driver && (

Driver: {device.driver}

)}
))}
)} {/* PCI Device Detail Modal */} setSelectedPCIDevice(null)}> {selectedPCIDevice?.device} PCI Device Information {selectedPCIDevice && (
Device Type {selectedPCIDevice.type}
PCI Slot {selectedPCIDevice.slot}
Device Name {selectedPCIDevice.device}
{selectedPCIDevice.sdevice && (
Product Name {selectedPCIDevice.sdevice}
)}
Vendor {selectedPCIDevice.vendor}
Class {selectedPCIDevice.class}
{selectedPCIDevice.driver && (
Driver {selectedPCIDevice.driver}
)} {selectedPCIDevice.kernel_module && (
Kernel Module {selectedPCIDevice.kernel_module}
)}
)}
{/* Network Summary - Clickable */} {hardwareData?.pci_devices && hardwareData.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length > 0 && (

Network Summary

{hardwareData.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length} interfaces
{hardwareData.pci_devices .filter((d) => d.type.toLowerCase().includes("network")) .map((device, index) => (
setSelectedNetwork(device)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-3 transition-colors" >
{device.device} {device.network_subtype || "Ethernet"}

{device.vendor}

{device.driver && (

Driver: {device.driver}

)}
))}
)} {/* Network Detail Modal */} setSelectedNetwork(null)}> {selectedNetwork?.device} Network Interface Information {selectedNetwork && (
Device Type {selectedNetwork.type}
PCI Slot {selectedNetwork.slot}
Vendor {selectedNetwork.vendor}
Class {selectedNetwork.class}
{selectedNetwork.driver && (
Driver {selectedNetwork.driver}
)} {selectedNetwork.kernel_module && (
Kernel Module {selectedNetwork.kernel_module}
)}
)}
{/* Storage Summary - Clickable */} {hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && (

Storage Summary

{ hardwareData.storage_devices.filter( (device) => device.type === "disk" && !device.name.startsWith("zd") && !device.name.startsWith("loop"), ).length }{" "} devices
{hardwareData.storage_devices .filter( (device) => device.type === "disk" && !device.name.startsWith("zd") && !device.name.startsWith("loop"), ) .map((device, index) => { const getDiskTypeBadge = (diskName: string, rotationRate: number | string | undefined) => { let diskType = "HDD" // Check if it's NVMe if (diskName.startsWith("nvme")) { diskType = "NVMe" } // Check rotation rate for SSD vs HDD else if (rotationRate !== undefined && rotationRate !== null) { // Handle both number and string formats const rateNum = typeof rotationRate === "string" ? Number.parseInt(rotationRate) : rotationRate if (rateNum === 0 || isNaN(rateNum)) { diskType = "SSD" } } // If rotation_rate is "Solid State Device" string else if (typeof rotationRate === "string" && rotationRate.includes("Solid State")) { diskType = "SSD" } const badgeStyles: Record = { NVMe: { className: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "NVMe SSD", }, SSD: { className: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "SSD", }, HDD: { className: "bg-blue-500/10 text-blue-500 border-blue-500/20", label: "HDD", }, } return badgeStyles[diskType] } const diskBadge = getDiskTypeBadge(device.name, device.rotation_rate) const getLinkSpeedInfo = (device: StorageDevice) => { // NVMe PCIe information if (device.name.startsWith("nvme") && (device.pcie_gen || device.pcie_width)) { const current = `${device.pcie_gen || ""} ${device.pcie_width || ""}`.trim() const max = device.pcie_max_gen && device.pcie_max_width ? `${device.pcie_max_gen} ${device.pcie_max_width}`.trim() : null const isLowerSpeed = max && current !== max return { text: current || null, maxText: max, isWarning: isLowerSpeed, color: isLowerSpeed ? "text-orange-500" : "text-blue-500", } } // SATA information if (device.sata_version) { return { text: device.sata_version, maxText: null, isWarning: false, color: "text-blue-500", } } // SAS information if (device.sas_version || device.sas_speed) { const text = [device.sas_version, device.sas_speed].filter(Boolean).join(" ") return { text: text || null, maxText: null, isWarning: false, color: "text-blue-500", } } // Generic link speed if (device.link_speed) { return { text: device.link_speed, maxText: null, isWarning: false, color: "text-blue-500", } } return null } const linkSpeed = getLinkSpeedInfo(device) return (
setSelectedDisk(device)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-3 transition-colors" >
{device.name} {diskBadge.label}
{device.size &&

{formatMemory(parseLsblkSize(device.size))}

} {device.model && (

{device.model}

)} {linkSpeed && (
{linkSpeed.text} {linkSpeed.maxText && linkSpeed.isWarning && ( (max: {linkSpeed.maxText}) )}
)}
) })}
)} {/* Disk Detail Modal */} setSelectedDisk(null)}> {selectedDisk?.name} Storage Device Hardware Information {selectedDisk && (
Device Name {selectedDisk.name}
Type {(() => { const getDiskTypeBadge = (diskName: string, rotationRate: number | string | undefined) => { let diskType = "HDD" if (diskName.startsWith("nvme")) { diskType = "NVMe" } else if (rotationRate !== undefined && rotationRate !== null) { const rateNum = typeof rotationRate === "string" ? Number.parseInt(rotationRate) : rotationRate if (rateNum === 0 || isNaN(rateNum)) { diskType = "SSD" } } else if (typeof rotationRate === "string" && rotationRate.includes("Solid State")) { diskType = "SSD" } const badgeStyles: Record = { NVMe: { className: "bg-purple-500/10 text-purple-500 border-purple-500/20", label: "NVMe SSD", }, SSD: { className: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20", label: "SSD", }, HDD: { className: "bg-blue-500/10 text-blue-500 border-blue-500/20", label: "HDD", }, } return badgeStyles[diskType] } const diskBadge = getDiskTypeBadge(selectedDisk.name, selectedDisk.rotation_rate) return {diskBadge.label} })()}
{selectedDisk.size && (
Capacity {formatMemory(parseLsblkSize(selectedDisk.size))}
)}

Interface Information

{/* NVMe PCIe Information */} {selectedDisk.name.startsWith("nvme") && ( <> {selectedDisk.pcie_gen || selectedDisk.pcie_width ? ( <>
Current Link Speed {selectedDisk.pcie_gen || "PCIe"} {selectedDisk.pcie_width || ""}
{selectedDisk.pcie_max_gen && selectedDisk.pcie_max_width && (
Maximum Link Speed {selectedDisk.pcie_max_gen} {selectedDisk.pcie_max_width}
)} ) : (
PCIe Link Speed Detecting...
)} )} {/* SATA Information */} {!selectedDisk.name.startsWith("nvme") && selectedDisk.sata_version && (
SATA Version {selectedDisk.sata_version}
)} {/* SAS Information */} {!selectedDisk.name.startsWith("nvme") && selectedDisk.sas_version && (
SAS Version {selectedDisk.sas_version}
)} {!selectedDisk.name.startsWith("nvme") && selectedDisk.sas_speed && (
SAS Speed {selectedDisk.sas_speed}
)} {/* Generic Link Speed - only show if no specific interface info */} {!selectedDisk.name.startsWith("nvme") && selectedDisk.link_speed && !selectedDisk.pcie_gen && !selectedDisk.sata_version && !selectedDisk.sas_version && (
Link Speed {selectedDisk.link_speed}
)} {selectedDisk.model && (
Model {selectedDisk.model}
)} {selectedDisk.family && (
Family {selectedDisk.family}
)} {selectedDisk.serial && (
Serial Number {selectedDisk.serial}
)} {selectedDisk.firmware && (
Firmware {selectedDisk.firmware}
)} {selectedDisk.interface && (
Interface {selectedDisk.interface}
)} {selectedDisk.driver && (
Driver {selectedDisk.driver}
)} {selectedDisk.rotation_rate !== undefined && selectedDisk.rotation_rate !== null && (
Rotation Rate
{typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate === -1 ? "N/A" : typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 ? `${selectedDisk.rotation_rate} rpm` : typeof selectedDisk.rotation_rate === "string" ? selectedDisk.rotation_rate : "Solid State Device"}
)} {selectedDisk.form_factor && (
Form Factor {selectedDisk.form_factor}
)}
)}
{/* USB Devices — everything physically plugged into the host's USB ports. Root hubs (vendor 1d6b) are already filtered out by the backend. The section is hidden on headless servers that have nothing attached. */} {hardwareData?.usb_devices && hardwareData.usb_devices.length > 0 && (

USB Devices

{hardwareData.usb_devices.length} device{hardwareData.usb_devices.length > 1 ? "s" : ""}
{hardwareData.usb_devices.map((usb, index) => (
setSelectedUsbDevice(usb)} className="cursor-pointer rounded-lg border border-white/10 sm:border-border bg-white/5 sm:bg-card sm:hover:bg-white/5 p-3 transition-colors" >
{usb.name} {usb.class_label}
{usb.speed_label &&
{usb.speed_label}
}
{usb.bus_device} · {usb.vendor_id}:{usb.product_id}
{usb.driver && (
Driver: {usb.driver}
)}
))}
)} {/* USB Device detail modal — mirrors the PCI Device modal for consistency. */} !open && setSelectedUsbDevice(null)}> {selectedUsbDevice?.name} USB Device Information {selectedUsbDevice && (
Class {selectedUsbDevice.class_label}
Bus:Device {selectedUsbDevice.bus_device}
Device Name {selectedUsbDevice.name}
{selectedUsbDevice.vendor && (
Vendor {selectedUsbDevice.vendor}
)}
Vendor / Product ID {selectedUsbDevice.vendor_id}:{selectedUsbDevice.product_id}
{selectedUsbDevice.speed_label && (
Speed {selectedUsbDevice.speed_label} {selectedUsbDevice.speed_mbps > 0 && ( ({selectedUsbDevice.speed_mbps} Mbps) )}
)}
Class Code 0x{selectedUsbDevice.class_code}
{selectedUsbDevice.driver && (
Driver {selectedUsbDevice.driver}
)} {selectedUsbDevice.serial && (
Serial {selectedUsbDevice.serial}
)}
)}
{/* NVIDIA Installation Monitor */} {/* { setNvidiaSessionId(null) mutateStatic() }} onComplete={(success) => { console.log("[v0] NVIDIA installation completed:", success ? "success" : "failed") if (success) { mutateStatic() } }} /> */} { setShowNvidiaInstaller(false) mutateStatic() }} scriptPath="/usr/local/share/proxmenux/scripts/gpu_tpu/nvidia_installer.sh" scriptName="nvidia_installer" params={{ EXECUTION_MODE: "web", }} title="NVIDIA Driver Installation" description="Installing NVIDIA proprietary drivers for GPU monitoring..." /> { setShowAmdInstaller(false) mutateStatic() }} scriptPath="/usr/local/share/proxmenux/scripts/gpu_tpu/amd_gpu_tools.sh" scriptName="amd_gpu_tools" params={{ EXECUTION_MODE: "web", }} title="AMD GPU Tools Installation" description="Installing amdgpu_top for AMD GPU monitoring..." /> { setShowIntelInstaller(false) mutateStatic() }} scriptPath="/usr/local/share/proxmenux/scripts/gpu_tpu/intel_gpu_tools.sh" scriptName="intel_gpu_tools" params={{ EXECUTION_MODE: "web", }} title="Intel GPU Tools Installation" description="Installing intel-gpu-tools for Intel GPU monitoring..." /> { setShowCoralInstaller(false) mutateStatic() }} scriptPath="/usr/local/share/proxmenux/scripts/gpu_tpu/install_coral.sh" scriptName="install_coral" params={{ EXECUTION_MODE: "web", }} title="Coral TPU Driver Installation" description="Installing gasket + apex kernel modules and Edge TPU runtime..." /> {/* GPU Switch Mode Modal */} {switchModeParams && ( )}
) }