"use client" import { Card } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Thermometer, CpuIcon, HardDrive, Cpu, MemoryStick, Cpu as Gpu } from "lucide-react" import useSWR from "swr" import { useState, useEffect } from "react" import { type HardwareData, type GPU, type PCIDevice, type StorageDevice, fetcher } from "../types/hardware" 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` } // Convert to GB if >= 1024 MB if (mb >= 1024) { const gb = mb / 1024 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() if (lowerType.includes("storage") || lowerType.includes("sata") || lowerType.includes("raid")) { return "bg-orange-500/10 text-orange-500 border-orange-500/20" } if (lowerType.includes("usb")) { return "bg-purple-500/10 text-purple-500 border-purple-500/20" } if (lowerType.includes("network") || lowerType.includes("ethernet")) { return "bg-blue-500/10 text-blue-500 border-blue-500/20" } if (lowerType.includes("graphics") || lowerType.includes("vga") || lowerType.includes("display")) { return "bg-green-500/10 text-green-500 border-green-500/20" } 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() { const { data: hardwareData, error, isLoading, } = useSWR("/api/hardware", fetcher, { refreshInterval: 5000, }) 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) 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 apiUrl = `http://${window.location.hostname}:8008/api/gpu/${fullSlot}/realtime` const response = await fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json", }, signal: abortController.signal, }) if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`) } const data = await response.json() setRealtimeGPUData(data) setDetailsLoading(false) } catch (error) { // Only log non-abort errors if (error instanceof Error && error.name !== "AbortError") { console.error("[v0] Error fetching GPU realtime data:", error) } setRealtimeGPUData({ has_monitoring_tool: false }) setDetailsLoading(false) } } // Initial fetch fetchRealtimeData() // Poll every 3 seconds 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...
) } 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.speed && (
Speed {module.speed}
)} {module.manufacturer && (
Manufacturer {module.manufacturer}
)}
))}
)} {/* 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) => (
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} {device.type}
{device.size &&

{formatMemory(parseLsblkSize(device.size))}

} {device.model && (

{device.model}

)} {device.driver && (

Driver: {device.driver}

)}
))}

Click on a device for detailed hardware information

)} {/* Thermal Monitoring */} {hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (

Thermal Monitoring

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

CPU

{groupedTemps.CPU.length}
{groupedTemps.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 */} {groupedTemps.GPU.length > 0 && (
1 ? "md:col-span-2" : ""}>

GPU

{groupedTemps.GPU.length}
1 ? "md:grid-cols-2" : ""}`}> {groupedTemps.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 */} {groupedTemps.NVME.length > 0 && (
1 ? "md:col-span-2" : ""}>

NVME

{groupedTemps.NVME.length}
1 ? "md:grid-cols-2" : ""}`}> {groupedTemps.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 */} {groupedTemps.PCI.length > 0 && (
1 ? "md:col-span-2" : ""}>

PCI

{groupedTemps.PCI.length}
1 ? "md:grid-cols-2" : ""}`}> {groupedTemps.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 */} {groupedTemps.OTHER.length > 0 && (
1 ? "md:col-span-2" : ""}>

OTHER

{groupedTemps.OTHER.length}
1 ? "md:grid-cols-2" : ""}`}> {groupedTemps.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 (
handleGPUClick(gpu)} 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" >
{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 Detail Modal - Shows immediately with basic info, then loads real-time data */} { setSelectedGPU(null) setRealtimeGPUData(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 && ( Monitoring Available )}
)}
{/* Real-Time Data */} {realtimeGPUData && hasRealtimeData() && (

Real-Time Data

{realtimeGPUData.data.map((data, index) => (
{data.name} {data.value}
))}
)} {/* Monitoring Tool Recommendation */} {!hasRealtimeData() && (

Monitoring Tool Recommendation

{getMonitoringToolRecommendation(selectedGPU.vendor)}

)}
)}
) }