diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 3ff0d89..407211e 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -3,11 +3,10 @@ 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 { Thermometer, CpuIcon, - ChevronDown, - ChevronUp, Zap, HardDrive, Network, @@ -17,10 +16,13 @@ import { Cpu, MemoryStick, Cpu as Gpu, + Info, + Activity, + Gauge, } from "lucide-react" import useSWR from "swr" import { useState } from "react" -import { type HardwareData, fetcher } from "../types/hardware" +import { type HardwareData, type GPU, type NetworkInterfaceDetails, type DiskDetails, fetcher } from "../types/hardware" const getDeviceTypeColor = (type: string): string => { const lowerType = type.toLowerCase() @@ -44,7 +46,17 @@ export default function Hardware() { refreshInterval: 5000, }) - const [expandedPCIDevice, setExpandedPCIDevice] = useState(null) + const [selectedGPU, setSelectedGPU] = useState(null) + const [selectedNetworkInterface, setSelectedNetworkInterface] = useState(null) + const [selectedDisk, setSelectedDisk] = useState(null) + const [selectedPCIDevice, setSelectedPCIDevice] = useState(null) + + const { data: networkDetails } = useSWR( + selectedNetworkInterface ? `/api/hardware/network/${selectedNetworkInterface}` : null, + fetcher, + ) + + const { data: diskDetails } = useSWR(selectedDisk ? `/api/hardware/disk/${selectedDisk}` : null, fetcher) return (
@@ -194,7 +206,7 @@ export default function Hardware() { )} - {/* Thermal Monitoring - Restored blue progress bars */} + {/* Thermal Monitoring */} {hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (
@@ -235,7 +247,6 @@ export default function Hardware() { )} - {/* GPU Information - New dedicated GPU section */} {hardwareData?.gpus && hardwareData.gpus.length > 0 && (
@@ -248,13 +259,29 @@ export default function Hardware() {
{hardwareData.gpus.map((gpu, index) => ( -
+
setSelectedGPU(gpu)} + className="cursor-pointer rounded-lg border border-border/30 bg-background/50 p-4 transition-colors hover:bg-background/80" + >
- {gpu.name} + {gpu.name} {gpu.vendor}
+
+ Type + {gpu.type} +
+ + {gpu.driver_version && ( +
+ Driver + {gpu.driver_version} +
+ )} + {gpu.memory_total && (
Memory @@ -290,27 +317,11 @@ export default function Hardware() {
)} +
- {gpu.power_draw && gpu.power_draw !== "N/A" && ( -
- Power Draw - {gpu.power_draw} -
- )} - - {gpu.driver_version && ( -
- Driver - {gpu.driver_version} -
- )} - - {gpu.type && ( -
- Type - {gpu.type} -
- )} +
+ + Click for detailed information
))} @@ -318,7 +329,199 @@ export default function Hardware() { )} - {/* PCI Devices */} + !open && setSelectedGPU(null)}> + + + + + {selectedGPU?.name} + + Detailed GPU information and statistics + + + {selectedGPU && ( +
+ {/* Basic Information */} +
+

+ + Basic Information +

+
+
+ Vendor + {selectedGPU.vendor} +
+
+ Type + {selectedGPU.type} +
+ {selectedGPU.slot && ( +
+ PCI Slot + {selectedGPU.slot} +
+ )} + {selectedGPU.driver_version && ( +
+ Driver Version + {selectedGPU.driver_version} +
+ )} + {selectedGPU.pcie_gen && ( +
+ PCIe Generation + Gen {selectedGPU.pcie_gen} +
+ )} + {selectedGPU.pcie_width && ( +
+ PCIe Width + {selectedGPU.pcie_width} +
+ )} +
+
+ + {/* Performance Metrics */} + {(selectedGPU.utilization !== undefined || selectedGPU.temperature !== undefined) && ( +
+

+ + Performance Metrics +

+
+ {selectedGPU.utilization !== undefined && ( +
+
+ GPU Utilization + {selectedGPU.utilization}% +
+ +
+ )} + {selectedGPU.memory_utilization !== undefined && ( +
+
+ Memory Utilization + {selectedGPU.memory_utilization}% +
+ +
+ )} + {selectedGPU.temperature !== undefined && selectedGPU.temperature > 0 && ( +
+
+ Temperature + {selectedGPU.temperature}°C +
+ +
+ )} +
+
+ )} + + {/* Memory Information */} + {selectedGPU.memory_total && ( +
+

+ + Memory Information +

+
+
+

Total

+

{selectedGPU.memory_total}

+
+
+

Used

+

{selectedGPU.memory_used}

+
+
+

Free

+

{selectedGPU.memory_free}

+
+
+
+ )} + + {/* Clock Speeds */} + {(selectedGPU.clock_graphics || selectedGPU.clock_memory) && ( +
+

+ + Clock Speeds +

+
+ {selectedGPU.clock_graphics && ( +
+

Graphics Clock

+

{selectedGPU.clock_graphics}

+
+ )} + {selectedGPU.clock_memory && ( +
+

Memory Clock

+

{selectedGPU.clock_memory}

+
+ )} +
+
+ )} + + {/* Power Information */} + {(selectedGPU.power_draw || selectedGPU.power_limit) && ( +
+

+ + Power Information +

+
+ {selectedGPU.power_draw && selectedGPU.power_draw !== "N/A" && ( +
+

Current Draw

+

{selectedGPU.power_draw}

+
+ )} + {selectedGPU.power_limit && selectedGPU.power_limit !== "N/A" && ( +
+

Power Limit

+

{selectedGPU.power_limit}

+
+ )} +
+
+ )} + + {/* Running Processes */} + {selectedGPU.processes && selectedGPU.processes.length > 0 && ( +
+

+ + Running Processes +

+
+ {selectedGPU.processes.map((process, idx) => ( +
+
+

{process.name}

+

PID: {process.pid}

+
+ {process.memory} +
+ ))} +
+
+ )} +
+ )} +
+
+ {hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && (
@@ -329,82 +532,85 @@ export default function Hardware() {
-
- {hardwareData.pci_devices.map((device, index) => { - const deviceKey = `${device.slot}-${index}` - const isExpanded = expandedPCIDevice === deviceKey - - return ( -
-
setExpandedPCIDevice(isExpanded ? null : deviceKey)} - className="flex cursor-pointer items-start justify-between p-4 transition-colors hover:bg-background/80" - > -
-
- {device.type} - {device.slot} -
-

{device.device}

-

{device.vendor}

-
- {isExpanded ? ( - - ) : ( - - )} -
- - {isExpanded && ( -
-
- Device Type - {device.type} -
- -
- PCI Slot - {device.slot} -
- -
- Device Name - {device.device} -
- -
- Vendor - {device.vendor} -
- -
- Class - {device.class} -
- - {device.driver && ( -
- Driver - {device.driver} -
- )} - - {device.kernel_module && ( -
- Kernel Module - {device.kernel_module} -
- )} -
- )} +
+ {hardwareData.pci_devices.map((device, index) => ( +
setSelectedPCIDevice(device)} + className="cursor-pointer rounded-lg border border-border/30 bg-background/50 p-4 transition-colors hover:bg-background/80" + > +
+ {device.type} + {device.slot}
- ) - })} +

{device.device}

+

{device.vendor}

+
+ + Click for details +
+
+ ))}
)} - {/* Power Consumption - Only show if data exists */} + !open && setSelectedPCIDevice(null)}> + + + + + {selectedPCIDevice?.device} + + PCI device information + + + {selectedPCIDevice && ( +
+
+ Device Type + {selectedPCIDevice.type} +
+ +
+ PCI Slot + {selectedPCIDevice.slot} +
+ +
+ Device Name + {selectedPCIDevice.device} +
+ +
+ Vendor + {selectedPCIDevice.vendor} +
+ +
+ Class + {selectedPCIDevice.class} +
+ + {selectedPCIDevice.driver && ( +
+ Driver + {selectedPCIDevice.driver} +
+ )} + + {selectedPCIDevice.kernel_module && ( +
+ Kernel Module + {selectedPCIDevice.kernel_module} +
+ )} +
+ )} +
+
+ + {/* Power Consumption */} {hardwareData?.power_meter && (
@@ -429,7 +635,6 @@ export default function Hardware() { )} - {/* Fans - Only show if data exists */} {hardwareData?.fans && hardwareData.fans.length > 0 && (
@@ -448,7 +653,12 @@ export default function Hardware() { return (
- {fan.name} +
+ {fan.name} + + {fan.type} + +
{fan.speed.toFixed(0)} {fan.unit} @@ -456,6 +666,7 @@ export default function Hardware() {
+ {fan.adapter && {fan.adapter}}
) })} @@ -463,7 +674,7 @@ export default function Hardware() { )} - {/* Power Supplies - Only show if data exists */} + {/* Power Supplies */} {hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && (
@@ -491,7 +702,7 @@ export default function Hardware() { )} - {/* UPS - Only show if data exists and has content */} + {/* UPS */} {hardwareData?.ups && Object.keys(hardwareData.ups).length > 0 && hardwareData.ups.model && (
@@ -551,7 +762,6 @@ export default function Hardware() { )} - {/* Network Summary */} {hardwareData?.pci_devices && hardwareData.pci_devices.filter((d) => d.type.toLowerCase().includes("network")).length > 0 && ( @@ -567,22 +777,168 @@ export default function Hardware() { {hardwareData.pci_devices .filter((d) => d.type.toLowerCase().includes("network")) .map((device, index) => ( -
+
setSelectedNetworkInterface(device.device.split(" ")[0].toLowerCase())} + className="cursor-pointer rounded-lg border border-border/30 bg-background/50 p-3 transition-colors hover:bg-background/80" + >
{device.device} Ethernet

{device.vendor}

+
+ + Click for details +
))}
-

- For detailed network information, see the Network section -

)} - {/* Storage Summary - Larger badges with blue color */} + !open && setSelectedNetworkInterface(null)} + > + + + + + Network Interface: {selectedNetworkInterface} + + Detailed network interface information + + + {networkDetails && ( +
+ {/* Driver Information */} +
+

Driver Information

+
+ {networkDetails.driver && ( +
+ Driver + {networkDetails.driver} +
+ )} + {networkDetails.driver_version && ( +
+ Version + {networkDetails.driver_version} +
+ )} + {networkDetails.firmware_version && ( +
+ Firmware + {networkDetails.firmware_version} +
+ )} + {networkDetails.bus_info && ( +
+ Bus Info + {networkDetails.bus_info} +
+ )} +
+
+ + {/* Connection Status */} +
+

Connection Status

+
+ {networkDetails.link_detected && ( +
+ Link + + {networkDetails.link_detected} + +
+ )} + {networkDetails.speed && ( +
+ Speed + {networkDetails.speed} +
+ )} + {networkDetails.duplex && ( +
+ Duplex + {networkDetails.duplex} +
+ )} + {networkDetails.mtu && ( +
+ MTU + {networkDetails.mtu} +
+ )} +
+
+ + {/* Addresses */} + {(networkDetails.mac_address || + (networkDetails.ip_addresses && networkDetails.ip_addresses.length > 0)) && ( +
+

Addresses

+
+ {networkDetails.mac_address && ( +
+ MAC Address + {networkDetails.mac_address} +
+ )} + {networkDetails.ip_addresses && + networkDetails.ip_addresses.map((ip, idx) => ( +
+ {ip.type} + {ip.address} +
+ ))} +
+
+ )} + + {/* Statistics */} + {networkDetails.statistics && Object.keys(networkDetails.statistics).length > 0 && ( +
+

Statistics

+
+ {networkDetails.statistics.rx_bytes && ( +
+

RX Bytes

+

{networkDetails.statistics.rx_bytes}

+
+ )} + {networkDetails.statistics.rx_packets && ( +
+

RX Packets

+

{networkDetails.statistics.rx_packets}

+
+ )} + {networkDetails.statistics.tx_bytes && ( +
+

TX Bytes

+

{networkDetails.statistics.tx_bytes}

+
+ )} + {networkDetails.statistics.tx_packets && ( +
+

TX Packets

+

{networkDetails.statistics.tx_packets}

+
+ )} +
+
+ )} +
+ )} +
+
+ {hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && (
@@ -595,18 +951,180 @@ export default function Hardware() {
{hardwareData.storage_devices.map((device, index) => ( -
+
setSelectedDisk(device.name)} + className="cursor-pointer rounded-lg border border-border/30 bg-background/50 p-3 transition-colors hover:bg-background/80" + >
{device.name} {device.type}
{device.size &&

{device.size}

} {device.model &&

{device.model}

} +
+ + Click for details +
))}
)} + + !open && setSelectedDisk(null)}> + + + + + Disk: {selectedDisk} + + Detailed disk information + + + {diskDetails && ( +
+ {/* Basic Information */} +
+

Basic Information

+
+ {diskDetails.type && ( +
+ Type + {diskDetails.type} +
+ )} + {diskDetails.driver && ( +
+ Driver + {diskDetails.driver} +
+ )} + {diskDetails.model && ( +
+ Model + {diskDetails.model} +
+ )} + {diskDetails.serial && ( +
+ Serial + {diskDetails.serial} +
+ )} + {diskDetails.size && ( +
+ Size + {diskDetails.size} +
+ )} +
+
+ + {/* Technical Details */} +
+

Technical Details

+
+ {diskDetails.rotational !== undefined && ( +
+ Rotational + + {diskDetails.rotational ? "Yes (HDD)" : "No (SSD)"} + +
+ )} + {diskDetails.block_size && ( +
+ Block Size + {diskDetails.block_size} bytes +
+ )} + {diskDetails.scheduler && ( +
+ Scheduler + {diskDetails.scheduler} +
+ )} + {diskDetails.removable !== undefined && ( +
+ Removable + + {diskDetails.removable ? "Yes" : "No"} + +
+ )} + {diskDetails.read_only !== undefined && ( +
+ Read Only + + {diskDetails.read_only ? "Yes" : "No"} + +
+ )} +
+
+ + {/* SMART Information */} + {diskDetails.smart_available && ( +
+

SMART Information

+
+
+ SMART Enabled + + {diskDetails.smart_enabled ? "Yes" : "No"} + +
+ {diskDetails.smart_health && ( +
+ Health Status + + {diskDetails.smart_health} + +
+ )} + {diskDetails.temperature !== undefined && ( +
+ Temperature + {diskDetails.temperature}°C +
+ )} + {diskDetails.power_on_hours !== undefined && ( +
+ Power On Hours + {diskDetails.power_on_hours.toLocaleString()} hours +
+ )} +
+
+ )} + + {/* Partitions */} + {diskDetails.partitions && diskDetails.partitions.length > 0 && ( +
+

Partitions

+
+ {diskDetails.partitions.map((partition, idx) => ( +
+
+ {partition.name} + {partition.size && {partition.size}} +
+
+ {partition.fstype && {partition.fstype}} + {partition.mountpoint && ( + → {partition.mountpoint} + )} +
+
+ ))} +
+
+ )} +
+ )} +
+
) } diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index aa45d4b..ae38244 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1293,240 +1293,23 @@ def get_proxmox_vms(): 'vms': [] } -def get_ipmi_fans(): - """Get fan information from IPMI""" - fans = [] - try: - result = subprocess.run(['ipmitool', 'sensor'], capture_output=True, text=True, timeout=10) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if 'fan' in line.lower() and '|' in line: - parts = [p.strip() for p in line.split('|')] - if len(parts) >= 3: - name = parts[0] - value_str = parts[1] - unit = parts[2] if len(parts) > 2 else '' - - # Skip "DutyCycle" and "Presence" entries - if 'dutycycle' in name.lower() or 'presence' in name.lower(): - continue - - try: - value = float(value_str) - fans.append({ - 'name': name, - 'speed': value, - 'unit': unit - }) - print(f"[v0] IPMI Fan: {name} = {value} {unit}") - except ValueError: - continue - - print(f"[v0] Found {len(fans)} IPMI fans") - except FileNotFoundError: - print("[v0] ipmitool not found") - except Exception as e: - print(f"[v0] Error getting IPMI fans: {e}") - - return fans +# --- START OF UPDATED CODE --- -def get_ipmi_power(): - """Get power supply information from IPMI""" - power_supplies = [] - power_meter = None - - try: - result = subprocess.run(['ipmitool', 'sensor'], capture_output=True, text=True, timeout=10) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if ('power supply' in line.lower() or 'power meter' in line.lower()) and '|' in line: - parts = [p.strip() for p in line.split('|')] - if len(parts) >= 3: - name = parts[0] - value_str = parts[1] - unit = parts[2] if len(parts) > 2 else '' - - try: - value = float(value_str) - - if 'power meter' in name.lower(): - power_meter = { - 'name': name, - 'watts': value, - 'unit': unit - } - print(f"[v0] IPMI Power Meter: {value} {unit}") - else: - power_supplies.append({ - 'name': name, - 'watts': value, - 'unit': unit, - 'status': 'ok' if value > 0 else 'off' - }) - print(f"[v0] IPMI PSU: {name} = {value} {unit}") - except ValueError: - continue - - print(f"[v0] Found {len(power_supplies)} IPMI power supplies") - except FileNotFoundError: - print("[v0] ipmitool not found") - except Exception as e: - print(f"[v0] Error getting IPMI power: {e}") - - return { - 'power_supplies': power_supplies, - 'power_meter': power_meter - } - -def get_ups_info(): - """Get UPS information from NUT (upsc)""" - ups_data = {} - - try: - # First, list available UPS devices - result = subprocess.run(['upsc', '-l'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - ups_list = result.stdout.strip().split('\n') - if ups_list and ups_list[0]: - ups_name = ups_list[0] - print(f"[v0] Found UPS: {ups_name}") - - # Get detailed UPS info - result = subprocess.run(['upsc', ups_name], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if ':' in line: - key, value = line.split(':', 1) - key = key.strip() - value = value.strip() - - # Map common UPS variables - if key == 'device.model': - ups_data['model'] = value - elif key == 'ups.status': - ups_data['status'] = value - elif key == 'battery.charge': - ups_data['battery_charge'] = f"{value}%" - elif key == 'battery.runtime': - # Convert seconds to minutes - try: - runtime_sec = int(value) - runtime_min = runtime_sec // 60 - ups_data['time_left'] = f"{runtime_min} minutes" - except ValueError: - ups_data['time_left'] = value - elif key == 'ups.load': - ups_data['load_percent'] = f"{value}%" - elif key == 'input.voltage': - ups_data['line_voltage'] = f"{value}V" - elif key == 'ups.realpower': - ups_data['real_power'] = f"{value}W" - - print(f"[v0] UPS data: {ups_data}") - except FileNotFoundError: - print("[v0] upsc not found") - except Exception as e: - print(f"[v0] Error getting UPS info: {e}") - - return ups_data - -def get_temperature_info(): - """Get detailed temperature information from sensors command""" - temperatures = [] - power_meter = None - - try: - result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - current_adapter = None - current_sensor = None - - for line in result.stdout.split('\n'): - line = line.strip() - if not line: - continue - - # Detect adapter line - if line.startswith('Adapter:'): - current_adapter = line.replace('Adapter:', '').strip() - continue - - # Detect sensor name (lines without ':' at the start are sensor names) - if ':' in line and not line.startswith(' '): - parts = line.split(':', 1) - sensor_name = parts[0].strip() - value_part = parts[1].strip() - - if 'power' in sensor_name.lower() and 'W' in value_part: - try: - # Extract power value (e.g., "182.00 W" -> 182.00) - power_match = re.search(r'([\d.]+)\s*W', value_part) - if power_match: - power_value = float(power_match.group(1)) - power_meter = { - 'name': sensor_name, - 'watts': power_value, - 'adapter': current_adapter - } - print(f"[v0] Power meter sensor: {sensor_name} = {power_value}W") - except ValueError: - pass - - # Parse temperature sensors - elif '°C' in value_part or 'C' in value_part: - try: - # Extract temperature value - temp_match = re.search(r'([+-]?[\d.]+)\s*°?C', value_part) - if temp_match: - temp_value = float(temp_match.group(1)) - - # Extract high and critical values if present - high_match = re.search(r'high\s*=\s*([+-]?[\d.]+)', value_part) - crit_match = re.search(r'crit\s*=\s*([+-]?[\d.]+)', value_part) - - high_value = float(high_match.group(1)) if high_match else 0 - crit_value = float(crit_match.group(1)) if crit_match else 0 - - temperatures.append({ - 'name': sensor_name, - 'current': temp_value, - 'high': high_value, - 'critical': crit_value, - 'adapter': current_adapter - }) - except ValueError: - pass - - print(f"[v0] Found {len(temperatures)} temperature sensors") - if power_meter: - print(f"[v0] Found power meter: {power_meter['watts']}W") - - except FileNotFoundError: - print("[v0] sensors command not found") - except Exception as e: - print(f"[v0] Error getting temperature info: {e}") - - return { - 'temperatures': temperatures, - 'power_meter': power_meter - } - -def get_gpu_info(): - """Get GPU information from lspci and enrich with temperature/fan data from sensors""" +def get_gpu_detailed_info(): + """Get comprehensive GPU information using nvidia-smi, intel_gpu_top, and radeontop""" gpus = [] + # Get basic GPU info from lspci try: result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) if result.returncode == 0: for line in result.stdout.split('\n'): - # Match VGA, 3D, Display controllers if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller']): parts = line.split(':', 2) if len(parts) >= 3: slot = parts[0].strip() gpu_name = parts[2].strip() - # Determine vendor vendor = 'Unknown' if 'NVIDIA' in gpu_name or 'nVidia' in gpu_name: vendor = 'NVIDIA' @@ -1539,7 +1322,8 @@ def get_gpu_info(): 'slot': slot, 'name': gpu_name, 'vendor': vendor, - 'type': 'Discrete' if vendor in ['NVIDIA', 'AMD'] else 'Integrated' + 'type': 'Discrete' if vendor in ['NVIDIA', 'AMD'] else 'Integrated', + 'processes': [] } gpus.append(gpu) @@ -1547,6 +1331,99 @@ def get_gpu_info(): except Exception as e: print(f"[v0] Error detecting GPUs from lspci: {e}") + try: + # Get comprehensive NVIDIA GPU info + result = subprocess.run( + ['nvidia-smi', '--query-gpu=index,name,memory.total,memory.used,memory.free,temperature.gpu,power.draw,power.limit,utilization.gpu,utilization.memory,clocks.gr,clocks.mem,driver_version,pcie.link.gen.current,pcie.link.width.current', + '--format=csv,noheader,nounits'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line: + parts = [p.strip() for p in line.split(',')] + if len(parts) >= 15: + nvidia_info = { + 'index': int(parts[0]), + 'memory_total': f"{parts[2]} MB", + 'memory_used': f"{parts[3]} MB", + 'memory_free': f"{parts[4]} MB", + 'temperature': int(float(parts[5])) if parts[5] != '[N/A]' else 0, + 'power_draw': f"{parts[6]} W" if parts[6] != '[N/A]' else 'N/A', + 'power_limit': f"{parts[7]} W" if parts[7] != '[N/A]' else 'N/A', + 'utilization': int(float(parts[8])) if parts[8] != '[N/A]' else 0, + 'memory_utilization': int(float(parts[9])) if parts[9] != '[N/A]' else 0, + 'clock_graphics': f"{parts[10]} MHz" if parts[10] != '[N/A]' else 'N/A', + 'clock_memory': f"{parts[11]} MHz" if parts[11] != '[N/A]' else 'N/A', + 'driver_version': parts[12], + 'pcie_gen': parts[13], + 'pcie_width': f"x{parts[14]}" if parts[14] != '[N/A]' else 'N/A' + } + + # Match with existing GPU + for gpu in gpus: + if gpu['vendor'] == 'NVIDIA': + gpu.update(nvidia_info) + break + + # Get NVIDIA GPU processes + result = subprocess.run( + ['nvidia-smi', '--query-compute-apps=pid,process_name,used_memory', '--format=csv,noheader,nounits'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + processes = [] + for line in result.stdout.strip().split('\n'): + if line: + parts = [p.strip() for p in line.split(',')] + if len(parts) >= 3: + processes.append({ + 'pid': parts[0], + 'name': parts[1], + 'memory': f"{parts[2]} MB" + }) + + for gpu in gpus: + if gpu['vendor'] == 'NVIDIA': + gpu['processes'] = processes + break + + print(f"[v0] Enriched NVIDIA GPU(s) with nvidia-smi detailed data") + except FileNotFoundError: + print("[v0] nvidia-smi not found") + except Exception as e: + print(f"[v0] Error getting NVIDIA GPU detailed info: {e}") + + try: + result = subprocess.run(['intel_gpu_top', '-l'], capture_output=True, text=True, timeout=2) + if result.returncode == 0: + for gpu in gpus: + if gpu['vendor'] == 'Intel': + gpu['intel_gpu_top_available'] = True + print(f"[v0] Intel GPU tools available for {gpu['name']}") + except FileNotFoundError: + print("[v0] intel_gpu_top not found") + except Exception as e: + print(f"[v0] Error checking intel_gpu_top: {e}") + + try: + result = subprocess.run(['radeontop', '-d', '-', '-l', '1'], capture_output=True, text=True, timeout=2) + if result.returncode == 0: + for gpu in gpus: + if gpu['vendor'] == 'AMD': + gpu['radeontop_available'] = True + print(f"[v0] AMD GPU tools available for {gpu['name']}") + except FileNotFoundError: + print("[v0] radeontop not found") + except Exception as e: + print(f"[v0] Error checking radeontop: {e}") + + return gpus + +def identify_fan_sensors(): + """Identify and categorize fan sensors (CPU, Chassis, GPU)""" + fans = [] + try: result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) if result.returncode == 0: @@ -1557,513 +1434,820 @@ def get_gpu_info(): if not line: continue - # Detect adapter line if line.startswith('Adapter:'): current_adapter = line.replace('Adapter:', '').strip() continue - # Look for GPU-related sensors (nouveau, amdgpu, radeon, i915, etc.) if ':' in line and not line.startswith(' '): parts = line.split(':', 1) sensor_name = parts[0].strip() value_part = parts[1].strip() - # Check if this is a GPU sensor - gpu_sensor_keywords = ['nouveau', 'amdgpu', 'radeon', 'i915'] - is_gpu_sensor = any(keyword in current_adapter.lower() if current_adapter else False for keyword in gpu_sensor_keywords) - - if is_gpu_sensor: - # Try to match this sensor to a GPU - for gpu in gpus: - # Match nouveau to NVIDIA, amdgpu/radeon to AMD, i915 to Intel - if (('nouveau' in current_adapter.lower() and gpu['vendor'] == 'NVIDIA') or - (('amdgpu' in current_adapter.lower() or 'radeon' in current_adapter.lower()) and gpu['vendor'] == 'AMD') or - ('i915' in current_adapter.lower() and gpu['vendor'] == 'Intel')): - - # Parse temperature - if '°C' in value_part or 'C' in value_part: - temp_match = re.search(r'([+-]?[\d.]+)\s*°?C', value_part) - if temp_match: - gpu['temperature'] = float(temp_match.group(1)) - print(f"[v0] GPU {gpu['name']}: Temperature = {gpu['temperature']}°C") - - # Parse fan speed - elif 'RPM' in value_part: - rpm_match = re.search(r'([\d.]+)\s*RPM', value_part) - if rpm_match: - gpu['fan_speed'] = int(float(rpm_match.group(1))) - gpu['fan_unit'] = 'RPM' - print(f"[v0] GPU {gpu['name']}: Fan = {gpu['fan_speed']} RPM") + if 'RPM' in value_part: + rpm_match = re.search(r'([\d.]+)\s*RPM', value_part) + if rpm_match: + fan_speed = int(float(rpm_match.group(1))) + + fan_type = 'Unknown' + if 'cpu' in sensor_name.lower() or 'processor' in sensor_name.lower(): + fan_type = 'CPU Fan' + elif 'chassis' in sensor_name.lower() or 'case' in sensor_name.lower() or 'sys' in sensor_name.lower(): + fan_type = 'Chassis Fan' + elif 'gpu' in sensor_name.lower() or 'video' in sensor_name.lower(): + fan_type = 'GPU Fan' + elif current_adapter and ('nouveau' in current_adapter.lower() or 'nvidia' in current_adapter.lower() or 'amdgpu' in current_adapter.lower()): + fan_type = 'GPU Fan' + else: + # Try to infer from fan number + if 'fan1' in sensor_name.lower(): + fan_type = 'CPU Fan' + elif 'fan2' in sensor_name.lower(): + fan_type = 'Chassis Fan' + elif 'fan3' in sensor_name.lower(): + fan_type = 'GPU Fan' + else: + fan_type = 'System Fan' + + fans.append({ + 'name': sensor_name, + 'type': fan_type, + 'speed': fan_speed, + 'unit': 'RPM', + 'adapter': current_adapter + }) + print(f"[v0] Fan sensor: {sensor_name} ({fan_type}) = {fan_speed} RPM") + + print(f"[v0] Found {len(fans)} fan sensor(s)") except Exception as e: - print(f"[v0] Error enriching GPU data from sensors: {e}") + print(f"[v0] Error identifying fan sensors: {e}") try: - result = subprocess.run( - ['nvidia-smi', '--query-gpu=index,name,memory.total,memory.used,memory.free,temperature.gpu,power.draw,utilization.gpu,driver_version', - '--format=csv,noheader,nounits'], - capture_output=True, text=True, timeout=5 - ) + result = subprocess.run(['ipmitool', 'sensor'], capture_output=True, text=True, timeout=10) if result.returncode == 0: - nvidia_gpus = [] - for line in result.stdout.strip().split('\n'): - if line: - parts = [p.strip() for p in line.split(',')] - if len(parts) >= 9: - nvidia_gpu = { - 'index': int(parts[0]), - 'name': parts[1], - 'vendor': 'NVIDIA', - 'type': 'Discrete', - 'memory_total': f"{parts[2]} MB", - 'memory_used': f"{parts[3]} MB", - 'memory_free': f"{parts[4]} MB", - 'temperature': int(float(parts[5])) if parts[5] != '[N/A]' else 0, - 'power_draw': f"{parts[6]} W" if parts[6] != '[N/A]' else 'N/A', - 'utilization': int(float(parts[7])) if parts[7] != '[N/A]' else 0, - 'driver_version': parts[8] - } - nvidia_gpus.append(nvidia_gpu) - - # Merge nvidia-smi data with existing GPU data - for nvidia_gpu in nvidia_gpus: - # Find matching GPU in gpus list - matched = False - for gpu in gpus: - if gpu['vendor'] == 'NVIDIA' and nvidia_gpu['name'] in gpu['name']: - # Enrich with nvidia-smi data - gpu.update(nvidia_gpu) - matched = True - break - - # If no match found, add as new GPU - if not matched: - gpus.append(nvidia_gpu) - - print(f"[v0] Enriched {len(nvidia_gpus)} NVIDIA GPU(s) with nvidia-smi data") + for line in result.stdout.split('\n'): + if 'fan' in line.lower() and '|' in line: + parts = [p.strip() for p in line.split('|')] + if len(parts) >= 3: + name = parts[0] + value_str = parts[1] + unit = parts[2] if len(parts) > 2 else '' + + if 'dutycycle' in name.lower() or 'presence' in name.lower(): + continue + + try: + value = float(value_str) + + fan_type = 'System Fan' + if 'cpu' in name.lower(): + fan_type = 'CPU Fan' + elif 'sys' in name.lower() or 'chassis' in name.lower(): + fan_type = 'Chassis Fan' + + fans.append({ + 'name': name, + 'type': fan_type, + 'speed': value, + 'unit': unit, + 'adapter': 'IPMI' + }) + print(f"[v0] IPMI Fan: {name} ({fan_type}) = {value} {unit}") + except ValueError: + continue except FileNotFoundError: - print("[v0] nvidia-smi not found") + print("[v0] ipmitool not found") except Exception as e: - print(f"[v0] Error getting NVIDIA GPU info from nvidia-smi: {e}") + print(f"[v0] Error getting IPMI fans: {e}") - return gpus + return fans + +def identify_temperature_sensors(): + """Identify and categorize temperature sensors with better labeling""" + temperatures = [] + power_meter = None + + try: + result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + current_adapter = None + current_chip = None + + for line in result.stdout.split('\n'): + line_stripped = line.strip() + if not line_stripped: + continue + + # Detect chip name (lines without indentation before "Adapter:") + if not line.startswith(' ') and not line.startswith('Adapter:') and ':' not in line: + current_chip = line_stripped + continue + + if line_stripped.startswith('Adapter:'): + current_adapter = line_stripped.replace('Adapter:', '').strip() + continue + + if ':' in line_stripped and not line_stripped.startswith(' '): + parts = line_stripped.split(':', 1) + sensor_name = parts[0].strip() + value_part = parts[1].strip() + + if 'power' in sensor_name.lower() and 'W' in value_part: + try: + power_match = re.search(r'([\d.]+)\s*W', value_part) + if power_match: + power_value = float(power_match.group(1)) + power_meter = { + 'name': sensor_name, + 'watts': power_value, + 'adapter': current_adapter + } + print(f"[v0] Power meter sensor: {sensor_name} = {power_value}W") + except ValueError: + pass + + elif '°C' in value_part or 'C' in value_part: + try: + temp_match = re.search(r'([+-]?[\d.]+)\s*°?C', value_part) + if temp_match: + temp_value = float(temp_match.group(1)) + + high_match = re.search(r'high\s*=\s*([+-]?[\d.]+)', value_part) + crit_match = re.search(r'crit\s*=\s*([+-]?[\d.]+)', value_part) + + high_value = float(high_match.group(1)) if high_match else 0 + crit_value = float(crit_match.group(1)) if crit_match else 0 + + # Identify sensor type + identified_name = sensor_name + if sensor_name.lower() == 'temp1': + if current_adapter and 'pci' in current_adapter.lower(): + identified_name = 'PCI adapter' + elif current_adapter and 'isa' in current_adapter.lower(): + identified_name = 'ISA adapter' + elif current_chip: + identified_name = f'{current_chip} sensor' + else: + identified_name = 'System temperature' + elif 'composite' in sensor_name.lower(): + identified_name = 'Composite' + elif 'package' in sensor_name.lower(): + identified_name = 'Package id 0' + elif 'core' in sensor_name.lower(): + identified_name = sensor_name + + temperatures.append({ + 'name': identified_name, + 'original_name': sensor_name, + 'current': temp_value, + 'high': high_value, + 'critical': crit_value, + 'adapter': current_adapter, + 'chip': current_chip + }) + except ValueError: + pass + + print(f"[v0] Found {len(temperatures)} temperature sensors") + if power_meter: + print(f"[v0] Found power meter: {power_meter['watts']}W") + + except FileNotFoundError: + print("[v0] sensors command not found") + except Exception as e: + print(f"[v0] Error identifying temperature sensors: {e}") + + return { + 'temperatures': temperatures, + 'power_meter': power_meter + } + +def get_network_interface_details(interface_name): + """Get detailed information about a network interface""" + details = { + 'name': interface_name, + 'driver': None, + 'driver_version': None, + 'firmware_version': None, + 'bus_info': None, + 'link_detected': None, + 'speed': None, + 'duplex': None, + 'mtu': None, + 'mac_address': None, + 'ip_addresses': [], + 'statistics': {} + } + + try: + # Get ethtool information + result = subprocess.run(['ethtool', interface_name], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + line = line.strip() + if 'Speed:' in line: + details['speed'] = line.split(':', 1)[1].strip() + elif 'Duplex:' in line: + details['duplex'] = line.split(':', 1)[1].strip() + elif 'Link detected:' in line: + details['link_detected'] = line.split(':', 1)[1].strip() + + # Get driver information + result = subprocess.run(['ethtool', '-i', interface_name], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + line = line.strip() + if line.startswith('driver:'): + details['driver'] = line.split(':', 1)[1].strip() + elif line.startswith('version:'): + details['driver_version'] = line.split(':', 1)[1].strip() + elif line.startswith('firmware-version:'): + details['firmware_version'] = line.split(':', 1)[1].strip() + elif line.startswith('bus-info:'): + details['bus_info'] = line.split(':', 1)[1].strip() + + # Get IP addresses and MAC + result = subprocess.run(['ip', 'addr', 'show', interface_name], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + line = line.strip() + if line.startswith('link/ether'): + parts = line.split() + if len(parts) >= 2: + details['mac_address'] = parts[1] + elif line.startswith('inet '): + parts = line.split() + if len(parts) >= 2: + details['ip_addresses'].append({ + 'type': 'IPv4', + 'address': parts[1] + }) + elif line.startswith('inet6 '): + parts = line.split() + if len(parts) >= 2: + details['ip_addresses'].append({ + 'type': 'IPv6', + 'address': parts[1] + }) + elif 'mtu' in line.lower(): + mtu_match = re.search(r'mtu\s+(\d+)', line) + if mtu_match: + details['mtu'] = mtu_match.group(1) + + # Get statistics + result = subprocess.run(['ip', '-s', 'link', 'show', interface_name], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + lines = result.stdout.split('\n') + for i, line in enumerate(lines): + if 'RX:' in line and i + 1 < len(lines): + rx_line = lines[i + 1].strip().split() + if len(rx_line) >= 2: + details['statistics']['rx_bytes'] = rx_line[0] + details['statistics']['rx_packets'] = rx_line[1] + elif 'TX:' in line and i + 1 < len(lines): + tx_line = lines[i + 1].strip().split() + if len(tx_line) >= 2: + details['statistics']['tx_bytes'] = tx_line[0] + details['statistics']['tx_packets'] = tx_line[1] + + print(f"[v0] Got detailed info for network interface {interface_name}") + except Exception as e: + print(f"[v0] Error getting network interface details for {interface_name}: {e}") + + return details + +def get_disk_details(disk_name): + """Get detailed information about a disk""" + details = { + 'name': disk_name, + 'type': None, + 'driver': None, + 'model': None, + 'serial': None, + 'size': None, + 'block_size': None, + 'scheduler': None, + 'rotational': None, + 'removable': None, + 'read_only': None, + 'smart_available': False, + 'smart_enabled': False, + 'smart_health': None, + 'temperature': None, + 'power_on_hours': None, + 'partitions': [] + } + + try: + # Get basic disk information from lsblk + result = subprocess.run(['lsblk', '-J', '-o', 'NAME,TYPE,SIZE,MODEL,SERIAL,FSTYPE,MOUNTPOINT', f'/dev/{disk_name}'], + capture_output=True, text=True, timeout=5) + if result.returncode == 0: + import json + data = json.loads(result.stdout) + if 'blockdevices' in data and len(data['blockdevices']) > 0: + device = data['blockdevices'][0] + details['type'] = device.get('type', 'disk') + details['size'] = device.get('size') + details['model'] = device.get('model') + details['serial'] = device.get('serial') + + if 'children' in device: + for child in device['children']: + details['partitions'].append({ + 'name': child.get('name'), + 'size': child.get('size'), + 'fstype': child.get('fstype'), + 'mountpoint': child.get('mountpoint') + }) + + # Get sys information + sys_path = f'/sys/block/{disk_name}' + try: + with open(f'{sys_path}/queue/rotational', 'r') as f: + details['rotational'] = f.read().strip() == '1' + with open(f'{sys_path}/removable', 'r') as f: + details['removable'] = f.read().strip() == '1' + with open(f'{sys_path}/ro', 'r') as f: + details['read_only'] = f.read().strip() == '1' + with open(f'{sys_path}/queue/scheduler', 'r') as f: + scheduler_line = f.read().strip() + scheduler_match = re.search(r'\[([^\]]+)\]', scheduler_line) + if scheduler_match: + details['scheduler'] = scheduler_match.group(1) + with open(f'{sys_path}/queue/physical_block_size', 'r') as f: + details['block_size'] = f.read().strip() + except: + pass + + # Determine disk type + if disk_name.startswith('nvme'): + details['type'] = 'NVMe' + details['driver'] = 'nvme' + elif disk_name.startswith('sd'): + if details['rotational']: + details['type'] = 'HDD' + else: + details['type'] = 'SSD' + details['driver'] = 'sd' + elif disk_name.startswith('hd'): + details['type'] = 'IDE/PATA' + details['driver'] = 'ide' + elif disk_name.startswith('mmcblk'): + details['type'] = 'MMC/SD' + details['driver'] = 'mmc' + + # Try to get SMART data + try: + result = subprocess.run(['smartctl', '-i', f'/dev/{disk_name}'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + details['smart_available'] = True + for line in result.stdout.split('\n'): + if 'SMART support is:' in line and 'Enabled' in line: + details['smart_enabled'] = True + + if details['smart_enabled']: + result = subprocess.run(['smartctl', '-H', f'/dev/{disk_name}'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'SMART overall-health' in line or 'SMART Health Status' in line: + if 'PASSED' in line or 'OK' in line: + details['smart_health'] = 'PASSED' + else: + details['smart_health'] = 'FAILED' + + result = subprocess.run(['smartctl', '-A', f'/dev/{disk_name}'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + if 'Temperature' in line or 'Airflow_Temperature' in line: + parts = line.split() + if len(parts) >= 10: + try: + details['temperature'] = int(parts[9]) + except: + pass + elif 'Power_On_Hours' in line: + parts = line.split() + if len(parts) >= 10: + try: + details['power_on_hours'] = int(parts[9]) + except: + pass + except FileNotFoundError: + print(f"[v0] smartctl not found") + except Exception as e: + print(f"[v0] Error getting SMART data for {disk_name}: {e}") + + print(f"[v0] Got detailed info for disk {disk_name}") + except Exception as e: + print(f"[v0] Error getting disk details for {disk_name}: {e}") + + return details + +# --- END OF UPDATED CODE --- def get_hardware_info(): """Get comprehensive hardware information""" + hardware_data = { + 'cpu': {}, + 'motherboard': {}, + 'memory_modules': [], + 'temperatures': [], + 'power_meter': None, + 'gpus': [], + 'pci_devices': [], + 'fans': [], + 'power_supplies': [], + 'ups': {}, + 'storage_devices': [], + 'ipmi_fans': [], + 'ipmi_power': {} + } + + # CPU Information try: - # Initialize with default structure, including the new power_meter field - hardware_data = { - 'cpu': {}, - 'motherboard': {}, - 'memory_modules': [], - 'storage_devices': [], - 'network_cards': [], - 'graphics_cards': [], - 'gpus': [], # Added dedicated GPU array - 'pci_devices': [], - 'sensors': { - 'temperatures': [], - 'fans': [] - }, - 'power': {}, # This might be overwritten by ipmi_power or ups - 'ipmi_fans': [], # Added IPMI fans - 'ipmi_power': {}, # Added IPMI power - 'ups': {}, # Added UPS info - 'power_meter': None # Added placeholder for sensors power meter - } - - # CPU Information - try: - result = subprocess.run(['lscpu'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - cpu_info = {} - for line in result.stdout.split('\n'): - if ':' in line: - key, value = line.split(':', 1) - key = key.strip() - value = value.strip() - - if key == 'Model name': - cpu_info['model'] = value - elif key == 'CPU(s)': - cpu_info['total_threads'] = int(value) - elif key == 'Core(s) per socket': - cpu_info['cores_per_socket'] = int(value) - elif key == 'Socket(s)': - cpu_info['sockets'] = int(value) - elif key == 'CPU MHz': - cpu_info['current_mhz'] = float(value) - elif key == 'CPU max MHz': - cpu_info['max_mhz'] = float(value) - elif key == 'CPU min MHz': - cpu_info['min_mhz'] = float(value) - elif key == 'Virtualization': - cpu_info['virtualization'] = value - elif key == 'L1d cache': - cpu_info['l1d_cache'] = value - elif key == 'L1i cache': - cpu_info['l1i_cache'] = value - elif key == 'L2 cache': - cpu_info['l2_cache'] = value - elif key == 'L3 cache': - cpu_info['l3_cache'] = value - - hardware_data['cpu'] = cpu_info - print(f"[v0] CPU: {cpu_info.get('model', 'Unknown')}") - except Exception as e: - print(f"[v0] Error getting CPU info: {e}") - - # Motherboard Information - try: - result = subprocess.run(['dmidecode', '-t', 'baseboard'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - mb_info = {} - for line in result.stdout.split('\n'): - line = line.strip() - if line.startswith('Manufacturer:'): - mb_info['manufacturer'] = line.split(':', 1)[1].strip() - elif line.startswith('Product Name:'): - mb_info['model'] = line.split(':', 1)[1].strip() - elif line.startswith('Version:'): - mb_info['version'] = line.split(':', 1)[1].strip() - elif line.startswith('Serial Number:'): - mb_info['serial'] = line.split(':', 1)[1].strip() - - hardware_data['motherboard'] = mb_info - print(f"[v0] Motherboard: {mb_info.get('manufacturer', 'Unknown')} {mb_info.get('model', 'Unknown')}") - except Exception as e: - print(f"[v0] Error getting motherboard info: {e}") - - # BIOS Information - try: - result = subprocess.run(['dmidecode', '-t', 'bios'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - bios_info = {} - for line in result.stdout.split('\n'): - line = line.strip() - if line.startswith('Vendor:'): - bios_info['vendor'] = line.split(':', 1)[1].strip() - elif line.startswith('Version:'): - bios_info['version'] = line.split(':', 1)[1].strip() - elif line.startswith('Release Date:'): - bios_info['date'] = line.split(':', 1)[1].strip() - - hardware_data['motherboard']['bios'] = bios_info - print(f"[v0] BIOS: {bios_info.get('vendor', 'Unknown')} {bios_info.get('version', 'Unknown')}") - except Exception as e: - print(f"[v0] Error getting BIOS info: {e}") - - # Memory Modules - try: - result = subprocess.run(['dmidecode', '-t', 'memory'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - current_module = {} - for line in result.stdout.split('\n'): - line = line.strip() + result = subprocess.run(['lscpu'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + cpu_info = {} + for line in result.stdout.split('\n'): + if ':' in line: + key, value = line.split(':', 1) + key = key.strip() + value = value.strip() - if line.startswith('Memory Device'): - if current_module and current_module.get('size') != 'No Module Installed': - hardware_data['memory_modules'].append(current_module) - current_module = {} - elif line.startswith('Size:'): - size = line.split(':', 1)[1].strip() - current_module['size'] = size - elif line.startswith('Type:'): - current_module['type'] = line.split(':', 1)[1].strip() - elif line.startswith('Speed:'): - current_module['speed'] = line.split(':', 1)[1].strip() - elif line.startswith('Manufacturer:'): - current_module['manufacturer'] = line.split(':', 1)[1].strip() - elif line.startswith('Serial Number:'): - current_module['serial'] = line.split(':', 1)[1].strip() - elif line.startswith('Locator:'): - current_module['slot'] = line.split(':', 1)[1].strip() - - if current_module and current_module.get('size') != 'No Module Installed': - hardware_data['memory_modules'].append(current_module) - - print(f"[v0] Memory modules: {len(hardware_data['memory_modules'])} installed") - except Exception as e: - print(f"[v0] Error getting memory info: {e}") - - # Storage Devices (reuse existing function) - storage_info = get_storage_info() - hardware_data['storage_devices'] = storage_info.get('disks', []) - - # Graphics Cards (from lspci - will be duplicated by new PCI device listing, but kept for now) - try: - # Try nvidia-smi first - result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,temperature.gpu,power.draw', '--format=csv,noheader'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.strip().split('\n'): - if line: - parts = line.split(',') - if len(parts) >= 4: - hardware_data['graphics_cards'].append({ - 'name': parts[0].strip(), - 'memory': parts[1].strip(), - 'temperature': int(parts[2].strip().split(' ')[0]) if parts[2].strip() != 'N/A' and 'C' in parts[2] else 0, - 'power_draw': parts[3].strip(), - 'vendor': 'NVIDIA' - }) + if key == 'Model name': + cpu_info['model'] = value + elif key == 'CPU(s)': + cpu_info['total_threads'] = int(value) + elif key == 'Core(s) per socket': + cpu_info['cores_per_socket'] = int(value) + elif key == 'Socket(s)': + cpu_info['sockets'] = int(value) + elif key == 'CPU MHz': + cpu_info['current_mhz'] = float(value) + elif key == 'CPU max MHz': + cpu_info['max_mhz'] = float(value) + elif key == 'CPU min MHz': + cpu_info['min_mhz'] = float(value) + elif key == 'Virtualization': + cpu_info['virtualization'] = value + elif key == 'L1d cache': + cpu_info['l1d_cache'] = value + elif key == 'L1i cache': + cpu_info['l1i_cache'] = value + elif key == 'L2 cache': + cpu_info['l2_cache'] = value + elif key == 'L3 cache': + cpu_info['l3_cache'] = value - # Always check lspci for all GPUs (integrated and discrete) - result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - # Match VGA, 3D, Display controllers, and specific GPU keywords - if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller']): - parts = line.split(':', 2) - if len(parts) >= 3: - gpu_name = parts[2].strip() - - # Determine vendor - vendor = 'Unknown' - if 'NVIDIA' in gpu_name or 'nVidia' in gpu_name: - vendor = 'NVIDIA' - elif 'AMD' in gpu_name or 'ATI' in gpu_name or 'Radeon' in gpu_name: - vendor = 'AMD' - elif 'Intel' in gpu_name: - vendor = 'Intel' - - # Check if this GPU is already in the list (from nvidia-smi) - already_exists = False - for existing_gpu in hardware_data['graphics_cards']: - if gpu_name in existing_gpu['name'] or existing_gpu['name'] in gpu_name: - already_exists = True - # Update vendor if it was previously unknown - if existing_gpu['vendor'] == 'Unknown': - existing_gpu['vendor'] = vendor - break - - if not already_exists: - hardware_data['graphics_cards'].append({ - 'name': gpu_name, - 'vendor': vendor - }) - print(f"[v0] Found GPU: {gpu_name} ({vendor})") + hardware_data['cpu'] = cpu_info + print(f"[v0] CPU: {cpu_info.get('model', 'Unknown')}") + except Exception as e: + print(f"[v0] Error getting CPU info: {e}") + + # Motherboard Information + try: + result = subprocess.run(['dmidecode', '-t', 'baseboard'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + mb_info = {} + for line in result.stdout.split('\n'): + line = line.strip() + if line.startswith('Manufacturer:'): + mb_info['manufacturer'] = line.split(':', 1)[1].strip() + elif line.startswith('Product Name:'): + mb_info['model'] = line.split(':', 1)[1].strip() + elif line.startswith('Version:'): + mb_info['version'] = line.split(':', 1)[1].strip() + elif line.startswith('Serial Number:'): + mb_info['serial'] = line.split(':', 1)[1].strip() + + hardware_data['motherboard'] = mb_info + print(f"[v0] Motherboard: {mb_info.get('manufacturer', 'Unknown')} {mb_info.get('model', 'Unknown')}") + except Exception as e: + print(f"[v0] Error getting motherboard info: {e}") + + # BIOS Information + try: + result = subprocess.run(['dmidecode', '-t', 'bios'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + bios_info = {} + for line in result.stdout.split('\n'): + line = line.strip() + if line.startswith('Vendor:'): + bios_info['vendor'] = line.split(':', 1)[1].strip() + elif line.startswith('Version:'): + bios_info['version'] = line.split(':', 1)[1].strip() + elif line.startswith('Release Date:'): + bios_info['date'] = line.split(':', 1)[1].strip() + + hardware_data['motherboard']['bios'] = bios_info + print(f"[v0] BIOS: {bios_info.get('vendor', 'Unknown')} {bios_info.get('version', 'Unknown')}") + except Exception as e: + print(f"[v0] Error getting BIOS info: {e}") + + # Memory Modules + try: + result = subprocess.run(['dmidecode', '-t', 'memory'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + current_module = {} + for line in result.stdout.split('\n'): + line = line.strip() + + if line.startswith('Memory Device'): + if current_module and current_module.get('size') != 'No Module Installed': + hardware_data['memory_modules'].append(current_module) + current_module = {} + elif line.startswith('Size:'): + size = line.split(':', 1)[1].strip() + current_module['size'] = size + elif line.startswith('Type:'): + current_module['type'] = line.split(':', 1)[1].strip() + elif line.startswith('Speed:'): + current_module['speed'] = line.split(':', 1)[1].strip() + elif line.startswith('Manufacturer:'): + current_module['manufacturer'] = line.split(':', 1)[1].strip() + elif line.startswith('Serial Number:'): + current_module['serial'] = line.split(':', 1)[1].strip() + elif line.startswith('Locator:'): + current_module['slot'] = line.split(':', 1)[1].strip() + + if current_module and current_module.get('size') != 'No Module Installed': + hardware_data['memory_modules'].append(current_module) + + print(f"[v0] Memory modules: {len(hardware_data['memory_modules'])} installed") + except Exception as e: + print(f"[v0] Error getting memory info: {e}") + + # Storage Devices (reuse existing function) + storage_info = get_storage_info() + hardware_data['storage_devices'] = storage_info.get('disks', []) + + # Graphics Cards (from lspci - will be duplicated by new PCI device listing, but kept for now) + try: + # Try nvidia-smi first + result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,temperature.gpu,power.draw', '--format=csv,noheader'], + capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line: + parts = line.split(',') + if len(parts) >= 4: + hardware_data['graphics_cards'].append({ + 'name': parts[0].strip(), + 'memory': parts[1].strip(), + 'temperature': int(parts[2].strip().split(' ')[0]) if parts[2].strip() != 'N/A' and 'C' in parts[2] else 0, + 'power_draw': parts[3].strip(), + 'vendor': 'NVIDIA' + }) + + # Always check lspci for all GPUs (integrated and discrete) + result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + for line in result.stdout.split('\n'): + # Match VGA, 3D, Display controllers + if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller']): + parts = line.split(':', 2) + if len(parts) >= 3: + gpu_name = parts[2].strip() + + # Determine vendor + vendor = 'Unknown' + if 'NVIDIA' in gpu_name or 'nVidia' in gpu_name: + vendor = 'NVIDIA' + elif 'AMD' in gpu_name or 'ATI' in gpu_name or 'Radeon' in gpu_name: + vendor = 'AMD' + elif 'Intel' in gpu_name: + vendor = 'Intel' + + # Check if this GPU is already in the list (from nvidia-smi) + already_exists = False + for existing_gpu in hardware_data['graphics_cards']: + if gpu_name in existing_gpu['name'] or existing_gpu['name'] in gpu_name: + already_exists = True + # Update vendor if it was previously unknown + if existing_gpu['vendor'] == 'Unknown': + existing_gpu['vendor'] = vendor + break + + if not already_exists: + hardware_data['graphics_cards'].append({ + 'name': gpu_name, + 'vendor': vendor + }) + print(f"[v0] Found GPU: {gpu_name} ({vendor})") print(f"[v0] Graphics cards: {len(hardware_data['graphics_cards'])} found") - except Exception as e: - print(f"[v0] Error getting graphics cards: {e}") + except Exception as e: + print(f"[v0] Error getting graphics cards: {e}") + + try: + print("[v0] Getting PCI devices with driver information...") + # First get basic device info with lspci -vmm + result = subprocess.run(['lspci', '-vmm'], capture_output=True, text=True, timeout=10) + if result.returncode == 0: + current_device = {} + for line in result.stdout.split('\n'): + line = line.strip() + + if not line: + # Empty line = end of device + if current_device and 'Class' in current_device: + device_class = current_device.get('Class', '') + device_name = current_device.get('Device', '') + vendor = current_device.get('Vendor', '') + slot = current_device.get('Slot', 'Unknown') + + # Categorize and add important devices + device_type = 'Other' + include_device = False + + # Graphics/Display devices + if any(keyword in device_class for keyword in ['VGA', 'Display', '3D']): + device_type = 'Graphics Card' + include_device = True + # Storage controllers + elif any(keyword in device_class for keyword in ['SATA', 'RAID', 'Mass storage', 'Non-Volatile memory']): + device_type = 'Storage Controller' + include_device = True + # Network controllers + elif 'Ethernet' in device_class or 'Network' in device_class: + device_type = 'Network Controller' + include_device = True + # USB controllers + elif 'USB' in device_class: + device_type = 'USB Controller' + include_device = True + # Audio devices + elif 'Audio' in device_class or 'Multimedia' in device_class: + device_type = 'Audio Controller' + include_device = True + # Special devices (Coral TPU, etc.) + elif any(keyword in device_name.lower() for keyword in ['coral', 'tpu', 'edge']): + device_type = 'AI Accelerator' + include_device = True + # PCI bridges (usually not interesting for users) + elif 'Bridge' in device_class: + include_device = False + + if include_device: + pci_device = { + 'slot': slot, + 'type': device_type, + 'vendor': vendor, + 'device': device_name, + 'class': device_class + } + hardware_data['pci_devices'].append(pci_device) + + current_device = {} + elif ':' in line: + key, value = line.split(':', 1) + current_device[key.strip()] = value.strip() + + # Now get driver information with lspci -k + result_k = subprocess.run(['lspci', '-k'], capture_output=True, text=True, timeout=10) + if result_k.returncode == 0: + current_slot = None + current_driver = None + current_module = None + + for line in result_k.stdout.split('\n'): + # Match PCI slot line (e.g., "00:1f.2 SATA controller: ...") + if line and not line.startswith('\t'): + parts = line.split(' ', 1) + if parts: + current_slot = parts[0] + current_driver = None + current_module = None + # Match driver lines (indented with tab) + elif line.startswith('\t'): + line = line.strip() + if line.startswith('Kernel driver in use:'): + current_driver = line.split(':', 1)[1].strip() + elif line.startswith('Kernel modules:'): + current_module = line.split(':', 1)[1].strip() + + # Update the corresponding PCI device + if current_slot and (current_driver or current_module): + for device in hardware_data['pci_devices']: + if device['slot'] == current_slot: + if current_driver: + device['driver'] = current_driver + if current_module: + device['kernel_module'] = current_module + break + + print(f"[v0] Total PCI devices found: {len(hardware_data['pci_devices'])}") + except Exception as e: + print(f"[v0] Error getting PCI devices: {e}") + + # Sensors (Temperature and Fans) + try: + if hasattr(psutil, "sensors_temperatures"): + temps = psutil.sensors_temperatures() + if temps: + for sensor_name, entries in temps.items(): + for entry in entries: + hardware_data['temperatures'].append({ + 'name': f"{sensor_name} - {entry.label}" if entry.label else sensor_name, + 'current': entry.current, + 'high': entry.high if entry.high else 0, + 'critical': entry.critical if entry.critical else 0 + }) + + print(f"[v0] Temperature sensors: {len(hardware_data['temperatures'])} found") try: - print("[v0] Getting PCI devices with driver information...") - # First get basic device info with lspci -vmm - result = subprocess.run(['lspci', '-vmm'], capture_output=True, text=True, timeout=10) + result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) if result.returncode == 0: - current_device = {} + current_adapter = None + fans = [] + for line in result.stdout.split('\n'): line = line.strip() - if not line: - # Empty line = end of device - if current_device and 'Class' in current_device: - device_class = current_device.get('Class', '') - device_name = current_device.get('Device', '') - vendor = current_device.get('Vendor', '') - slot = current_device.get('Slot', 'Unknown') - - # Categorize and add important devices - device_type = 'Other' - include_device = False - - # Graphics/Display devices - if any(keyword in device_class for keyword in ['VGA', 'Display', '3D']): - device_type = 'Graphics Card' - include_device = True - # Storage controllers - elif any(keyword in device_class for keyword in ['SATA', 'RAID', 'Mass storage', 'Non-Volatile memory']): - device_type = 'Storage Controller' - include_device = True - # Network controllers - elif 'Ethernet' in device_class or 'Network' in device_class: - device_type = 'Network Controller' - include_device = True - # USB controllers - elif 'USB' in device_class: - device_type = 'USB Controller' - include_device = True - # Audio devices - elif 'Audio' in device_class or 'Multimedia' in device_class: - device_type = 'Audio Controller' - include_device = True - # Special devices (Coral TPU, etc.) - elif any(keyword in device_name.lower() for keyword in ['coral', 'tpu', 'edge']): - device_type = 'AI Accelerator' - include_device = True - # PCI bridges (usually not interesting for users) - elif 'Bridge' in device_class: - include_device = False - - if include_device: - pci_device = { - 'slot': slot, - 'type': device_type, - 'vendor': vendor, - 'device': device_name, - 'class': device_class - } - hardware_data['pci_devices'].append(pci_device) + continue + + if line.startswith('Adapter:'): + current_adapter = line.replace('Adapter:', '').strip() + continue + + if ':' in line and not line.startswith(' '): + parts = line.split(':', 1) + sensor_name = parts[0].strip() + value_part = parts[1].strip() - current_device = {} - elif ':' in line: - key, value = line.split(':', 1) - current_device[key.strip()] = value.strip() - - # Now get driver information with lspci -k - result_k = subprocess.run(['lspci', '-k'], capture_output=True, text=True, timeout=10) - if result_k.returncode == 0: - current_slot = None - current_driver = None - current_module = None + if 'RPM' in value_part: + rpm_match = re.search(r'([\d.]+)\s*RPM', value_part) + if rpm_match: + fan_speed = int(float(rpm_match.group(1))) + fans.append({ + 'name': sensor_name, + 'speed': fan_speed, + 'unit': 'RPM', + 'adapter': current_adapter + }) + print(f"[v0] Fan sensor: {sensor_name} = {fan_speed} RPM") - for line in result_k.stdout.split('\n'): - # Match PCI slot line (e.g., "00:1f.2 SATA controller: ...") - if line and not line.startswith('\t'): - parts = line.split(' ', 1) - if parts: - current_slot = parts[0] - current_driver = None - current_module = None - # Match driver lines (indented with tab) - elif line.startswith('\t'): - line = line.strip() - if line.startswith('Kernel driver in use:'): - current_driver = line.split(':', 1)[1].strip() - elif line.startswith('Kernel modules:'): - current_module = line.split(':', 1)[1].strip() - - # Update the corresponding PCI device - if current_slot and (current_driver or current_module): - for device in hardware_data['pci_devices']: - if device['slot'] == current_slot: - if current_driver: - device['driver'] = current_driver - if current_module: - device['kernel_module'] = current_module - break - - print(f"[v0] Total PCI devices found: {len(hardware_data['pci_devices'])}") + hardware_data['sensors']['fans'] = fans + print(f"[v0] Found {len(fans)} fan sensor(s)") except Exception as e: - print(f"[v0] Error getting PCI devices: {e}") - - # Sensors (Temperature and Fans) - try: - if hasattr(psutil, "sensors_temperatures"): - temps = psutil.sensors_temperatures() - if temps: - for sensor_name, entries in temps.items(): - for entry in entries: - hardware_data['sensors']['temperatures'].append({ - 'name': f"{sensor_name} - {entry.label}" if entry.label else sensor_name, - 'current': entry.current, - 'high': entry.high if entry.high else 0, - 'critical': entry.critical if entry.critical else 0 - }) - - print(f"[v0] Temperature sensors: {len(hardware_data['sensors']['temperatures'])} found") - - try: - result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - current_adapter = None - fans = [] - - for line in result.stdout.split('\n'): - line = line.strip() - if not line: - continue - - # Detect adapter line - if line.startswith('Adapter:'): - current_adapter = line.replace('Adapter:', '').strip() - continue - - # Parse fan sensors - if ':' in line and not line.startswith(' '): - parts = line.split(':', 1) - sensor_name = parts[0].strip() - value_part = parts[1].strip() - - # Look for fan sensors (RPM) - if 'RPM' in value_part: - rpm_match = re.search(r'([\d.]+)\s*RPM', value_part) - if rpm_match: - fan_speed = int(float(rpm_match.group(1))) - fans.append({ - 'name': sensor_name, - 'speed': fan_speed, - 'unit': 'RPM', - 'adapter': current_adapter - }) - print(f"[v0] Fan sensor: {sensor_name} = {fan_speed} RPM") - - hardware_data['sensors']['fans'] = fans - print(f"[v0] Found {len(fans)} fan sensor(s)") - except Exception as e: - print(f"[v0] Error getting fan info: {e}") - except Exception as e: - print(f"[v0] Error getting psutil sensors: {e}") - - # Power Supply / UPS - try: - result = subprocess.run(['apcaccess'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - ups_info = {} - for line in result.stdout.split('\n'): - if ':' in line: - key, value = line.split(':', 1) - key = key.strip() - value = value.strip() - - if key == 'MODEL': - ups_info['model'] = value - elif key == 'STATUS': - ups_info['status'] = value - elif key == 'BCHARGE': - ups_info['battery_charge'] = value - elif key == 'TIMELEFT': - ups_info['time_left'] = value - elif key == 'LOADPCT': - ups_info['load_percent'] = value - elif key == 'LINEV': - ups_info['line_voltage'] = value - - if ups_info: - hardware_data['power'] = ups_info - print(f"[v0] UPS found: {ups_info.get('model', 'Unknown')}") - except FileNotFoundError: - print("[v0] apcaccess not found - no UPS monitoring") - except Exception as e: - print(f"[v0] Error getting UPS info: {e}") - - temp_info = get_temperature_info() - hardware_data['sensors']['temperatures'] = temp_info['temperatures'] - hardware_data['power_meter'] = temp_info['power_meter'] - - ipmi_fans = get_ipmi_fans() - if ipmi_fans: - hardware_data['ipmi_fans'] = ipmi_fans - - ipmi_power = get_ipmi_power() - if ipmi_power['power_supplies'] or ipmi_power['power_meter']: - hardware_data['ipmi_power'] = ipmi_power - - ups_info = get_ups_info() - if ups_info: - hardware_data['ups'] = ups_info - - hardware_data['gpus'] = get_gpu_info() - - return hardware_data - + print(f"[v0] Error getting fan info: {e}") except Exception as e: - print(f"[v0] Error in get_hardware_info: {e}") - import traceback - traceback.print_exc() - return {} - + print(f"[v0] Error getting psutil sensors: {e}") + + # Power Supply / UPS + try: + result = subprocess.run(['apcaccess'], capture_output=True, text=True, timeout=5) + if result.returncode == 0: + ups_info = {} + for line in result.stdout.split('\n'): + if ':' in line: + key, value = line.split(':', 1) + key = key.strip() + value = value.strip() + + if key == 'MODEL': + ups_info['model'] = value + elif key == 'STATUS': + ups_info['status'] = value + elif key == 'BCHARGE': + ups_info['battery_charge'] = value + elif key == 'TIMELEFT': + ups_info['time_left'] = value + elif key == 'LOADPCT': + ups_info['load_percent'] = value + elif key == 'LINEV': + ups_info['line_voltage'] = value + + if ups_info: + hardware_data['power'] = ups_info + print(f"[v0] UPS found: {ups_info.get('model', 'Unknown')}") + except FileNotFoundError: + print("[v0] apcaccess not found - no UPS monitoring") + except Exception as e: + print(f"[v0] Error getting UPS info: {e}") + + temp_info = identify_temperature_sensors() + hardware_data['temperatures'] = temp_info['temperatures'] + hardware_data['power_meter'] = temp_info['power_meter'] + + ipmi_fans = get_ipmi_fans() + if ipmi_fans: + hardware_data['ipmi_fans'] = ipmi_fans + + ipmi_power = get_ipmi_power() + if ipmi_power['power_supplies'] or ipmi_power['power_meter']: + hardware_data['ipmi_power'] = ipmi_power + + ups_info = get_ups_info() + if ups_info: + hardware_data['ups'] = ups_info + + hardware_data['gpus'] = get_gpu_detailed_info() + + hardware_data['fans'] = identify_fan_sensors() + + return hardware_data @app.route('/api/system', methods=['GET']) def api_system(): @@ -2215,10 +2399,8 @@ def api_hardware(): 'cpu': hardware_info.get('cpu', {}), 'motherboard': hardware_info.get('motherboard', {}), 'memory_modules': hardware_info.get('memory_modules', []), - 'storage_devices': hardware_info.get('storage_devices', []), - 'pci_devices': hardware_info.get('pci_devices', []), - 'temperatures': hardware_info.get('sensors', {}).get('temperatures', []), - 'fans': hardware_info.get('sensors', {}).get('fans', []), # Use sensors fans + 'temperatures': hardware_info.get('temperatures', []), + 'fans': hardware_info.get('fans', []), # Use sensors fans 'power_supplies': hardware_info.get('ipmi_power', {}).get('power_supplies', []), # Use IPMI power supplies 'power_meter': hardware_info.get('power_meter'), 'ups': hardware_info.get('ups') if hardware_info.get('ups') else None, @@ -2375,9 +2557,30 @@ def api_vm_control(vmid): print(f"Error controlling VM: {e}") return jsonify({'error': str(e)}), 500 +@app.route('/api/hardware/network/') +def api_network_interface_details(interface_name): + """API endpoint for detailed network interface information""" + try: + details = get_network_interface_details(interface_name) + return jsonify(details) + except Exception as e: + print(f"[v0] Error in network interface details API: {e}") + return jsonify({'error': str(e)}), 500 + +@app.route('/api/hardware/disk/') +def api_disk_details(disk_name): + """API endpoint for detailed disk information""" + try: + details = get_disk_details(disk_name) + return jsonify(details) + except Exception as e: + print(f"[v0] Error in disk details API: {e}") + return jsonify({'error': str(e)}), 500 + if __name__ == '__main__': print("Starting ProxMenux Flask Server on port 8008...") print("Server will be accessible on all network interfaces (0.0.0.0:8008)") print("API endpoints available at: /api/system, /api/storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware") app.run(host='0.0.0.0', port=8008, debug=False) +.0.0.0', port=8008, debug=False) diff --git a/AppImage/types/hardware.ts b/AppImage/types/hardware.ts index 6a0a8d9..af1547f 100644 --- a/AppImage/types/hardware.ts +++ b/AppImage/types/hardware.ts @@ -1,9 +1,11 @@ export interface Temperature { name: string + original_name?: string current: number high?: number critical?: number adapter?: string + chip?: string } export interface PowerMeter { @@ -51,10 +53,42 @@ export interface PCIDevice { gpu_memory_clock?: string } +export interface GPUProcess { + pid: string + name: string + memory: string +} + +export interface GPU { + slot: string + name: string + vendor: string + type: string + index?: number + memory_total?: string + memory_used?: string + memory_free?: string + temperature?: number + power_draw?: string + power_limit?: string + utilization?: number + memory_utilization?: number + clock_graphics?: string + clock_memory?: string + driver_version?: string + pcie_gen?: string + pcie_width?: string + processes?: GPUProcess[] + intel_gpu_top_available?: boolean + radeontop_available?: boolean +} + export interface Fan { name: string + type: string speed: number unit: string + adapter?: string } export interface PowerSupply { @@ -73,15 +107,69 @@ export interface UPS { output_voltage?: number } +export interface DiskPartition { + name: string + size?: string + fstype?: string + mountpoint?: string +} + +export interface DiskDetails { + name: string + type?: string + driver?: string + model?: string + serial?: string + size?: string + block_size?: string + scheduler?: string + rotational?: boolean + removable?: boolean + read_only?: boolean + smart_available?: boolean + smart_enabled?: boolean + smart_health?: string + temperature?: number + power_on_hours?: number + partitions?: DiskPartition[] +} + +export interface NetworkInterfaceDetails { + name: string + driver?: string + driver_version?: string + firmware_version?: string + bus_info?: string + link_detected?: string + speed?: string + duplex?: string + mtu?: string + mac_address?: string + ip_addresses?: Array<{ + type: string + address: string + }> + statistics?: { + rx_bytes?: string + rx_packets?: string + tx_bytes?: string + tx_packets?: string + } +} + export interface HardwareData { temperatures?: Temperature[] power_meter?: PowerMeter network_cards?: NetworkInterface[] storage_devices?: StorageDevice[] pci_devices?: PCIDevice[] + gpus?: GPU[] fans?: Fan[] power_supplies?: PowerSupply[] ups?: UPS + cpu?: any + motherboard?: any + memory_modules?: any[] } export const fetcher = (url: string) => fetch(url).then((res) => res.json())