From f6209b97e2da540e1f1c04bccc87fc88c0905681 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Thu, 9 Oct 2025 19:00:58 +0200 Subject: [PATCH] Update AppImage --- AppImage/components/hardware.tsx | 174 +++++++++++++++++++++---------- AppImage/scripts/flask_server.py | 115 +++++++------------- 2 files changed, 157 insertions(+), 132 deletions(-) diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 8ef936d..f77e716 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -19,7 +19,7 @@ import { Loader2, } from "lucide-react" import useSWR from "swr" -import { useState } from "react" +import { useState, useEffect } from "react" import { type HardwareData, type GPU, type PCIDevice, type StorageDevice, fetcher } from "../types/hardware" const getDeviceTypeColor = (type: string): string => { @@ -62,6 +62,7 @@ export default function Hardware() { const [selectedGPU, setSelectedGPU] = useState(null) const [realtimeGPUData, setRealtimeGPUData] = useState(null) const [detailsLoading, setDetailsLoading] = useState(false) + const [isPolling, setIsPolling] = useState(false) const [selectedPCIDevice, setSelectedPCIDevice] = useState(null) const [selectedDisk, setSelectedDisk] = useState(null) const [selectedNetwork, setSelectedNetwork] = useState(null) @@ -78,12 +79,14 @@ export default function Hardware() { setSelectedGPU(gpu) setDetailsLoading(true) setRealtimeGPUData(null) + setIsPolling(true) console.log("[v0] Modal opened, fetching realtime data...") if (!fullSlot) { console.log("[v0] No slot found, showing basic info only") setDetailsLoading(false) + setIsPolling(false) return } @@ -107,12 +110,49 @@ export default function Hardware() { } catch (error) { console.error("[v0] Error fetching GPU realtime data:", error) setRealtimeGPUData({ has_monitoring_tool: false }) + setIsPolling(false) } finally { setDetailsLoading(false) console.log("[v0] Finished loading GPU data") } } + useEffect(() => { + if (!isPolling || !selectedGPU || !realtimeGPUData?.has_monitoring_tool) { + return + } + + const pciDevice = findPCIDeviceForGPU(selectedGPU) + const fullSlot = pciDevice?.slot || selectedGPU.slot + + if (!fullSlot) { + return + } + + const pollInterval = setInterval(async () => { + try { + const response = await fetch(`http://localhost:8008/api/gpu/${fullSlot}/realtime`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + signal: AbortSignal.timeout(5000), + }) + + if (response.ok) { + const data = await response.json() + setRealtimeGPUData(data) + } + } catch (error) { + console.error("[v0] Error polling GPU data:", error) + } + }, 2000) // Poll every 2 seconds + + return () => { + clearInterval(pollInterval) + } + }, [isPolling, selectedGPU, realtimeGPUData?.has_monitoring_tool]) + const findPCIDeviceForGPU = (gpu: GPU): PCIDevice | null => { if (!hardwareData?.pci_devices || !gpu.slot) return null @@ -396,6 +436,7 @@ export default function Hardware() { onOpenChange={() => { setSelectedGPU(null) setRealtimeGPUData(null) + setIsPolling(false) }} > @@ -404,7 +445,11 @@ export default function Hardware() { {selectedGPU.name} - {detailsLoading ? "Loading real-time monitoring data..." : "GPU Information"} + {detailsLoading + ? "Loading real-time monitoring data..." + : isPolling && realtimeGPUData?.has_monitoring_tool + ? "Live monitoring (updates every 2s)" + : "GPU Information"} @@ -454,6 +499,13 @@ export default function Hardware() { ) : realtimeGPUData?.has_monitoring_tool === true ? ( <> + {isPolling && ( +
+
+ Live monitoring active +
+ )} +

Real-Time Metrics @@ -469,61 +521,37 @@ export default function Hardware() {

- ) : ( -
- Temperature - N/A -
- )} + ) : null} - {realtimeGPUData.utilization_gpu !== undefined && realtimeGPUData.utilization_gpu !== null ? ( -
-
- GPU Utilization - - {typeof realtimeGPUData.utilization_gpu === "string" - ? realtimeGPUData.utilization_gpu - : `${realtimeGPUData.utilization_gpu}%`} - -
- +
+
+ GPU Utilization + {realtimeGPUData.utilization_gpu || "0.0%"}
- ) : ( -
-
- GPU Utilization - 0.0% -
- -
- )} + +
- {realtimeGPUData.clock_graphics && ( + {realtimeGPUData.clock_graphics !== undefined && realtimeGPUData.clock_graphics !== null && (
Graphics Clock {realtimeGPUData.clock_graphics}
)} - {realtimeGPUData.clock_memory && ( -
- Memory Clock - {realtimeGPUData.clock_memory} -
- )} - {realtimeGPUData.power_draw && realtimeGPUData.power_draw !== "N/A" && ( + + {realtimeGPUData.power_draw !== undefined && realtimeGPUData.power_draw !== null && (
Power Draw {realtimeGPUData.power_draw}
)} - {realtimeGPUData.power_limit && ( + {realtimeGPUData.power_limit !== undefined && realtimeGPUData.power_limit !== null && (
Power Limit {realtimeGPUData.power_limit} @@ -532,7 +560,7 @@ export default function Hardware() {
- {/* Engine Utilization (Intel/AMD) */} + {/* Engine Utilization (Intel/AMD) - Always show even if 0% */} {(realtimeGPUData.engine_render !== undefined || realtimeGPUData.engine_blitter !== undefined || realtimeGPUData.engine_video !== undefined || @@ -546,9 +574,20 @@ export default function Hardware() {
Render/3D - {realtimeGPUData.engine_render.toFixed(2)}% + + {typeof realtimeGPUData.engine_render === "string" + ? realtimeGPUData.engine_render + : `${realtimeGPUData.engine_render.toFixed(1)}%`} +
- +
)} {realtimeGPUData.engine_blitter !== undefined && ( @@ -556,19 +595,39 @@ export default function Hardware() {
Blitter - {realtimeGPUData.engine_blitter.toFixed(2)}% + {typeof realtimeGPUData.engine_blitter === "string" + ? realtimeGPUData.engine_blitter + : `${realtimeGPUData.engine_blitter.toFixed(1)}%`}
- + )} {realtimeGPUData.engine_video !== undefined && (
Video - {realtimeGPUData.engine_video.toFixed(2)}% + + {typeof realtimeGPUData.engine_video === "string" + ? realtimeGPUData.engine_video + : `${realtimeGPUData.engine_video.toFixed(1)}%`} +
- +
)} {realtimeGPUData.engine_video_enhance !== undefined && ( @@ -576,10 +635,19 @@ export default function Hardware() {
VideoEnhance - {realtimeGPUData.engine_video_enhance.toFixed(2)}% + {typeof realtimeGPUData.engine_video_enhance === "string" + ? realtimeGPUData.engine_video_enhance + : `${realtimeGPUData.engine_video_enhance.toFixed(1)}%`}
- + )} diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 06fb48b..6bc0302 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -3,7 +3,7 @@ ProxMenux Flask Server Provides REST API endpoints for Proxmox monitoring data Runs on port 8008 and serves system metrics, storage info, network stats, etc. -Also serves the Next.js dashboard as static files. +Also serves the Next.js dashboard as static files """ from flask import Flask, jsonify, request, send_from_directory, send_file @@ -1626,25 +1626,46 @@ def get_detailed_gpu_info(gpu): print(f"[v0] Current user: {os.getenv('USER', 'unknown')}", flush=True) print(f"[v0] Current working directory: {os.getcwd()}", flush=True) - cmd = ['intel_gpu_top', '-J'] - print(f"[v0] Executing command: {' '.join(cmd)}", flush=True) + try: + version_result = subprocess.run(['intel_gpu_top', '--version'], + capture_output=True, text=True, timeout=2) + if version_result.returncode == 0: + version_info = version_result.stdout.strip() + print(f"[v0] intel_gpu_top version: {version_info}", flush=True) + except Exception as e: + print(f"[v0] Could not get intel_gpu_top version: {e}", flush=True) + + # Check DRM device permissions + drm_devices = ['/dev/dri/card0', '/dev/dri/renderD128'] + for drm_dev in drm_devices: + if os.path.exists(drm_dev): + can_read = os.access(drm_dev, os.R_OK) + can_write = os.access(drm_dev, os.W_OK) + print(f"[v0] {drm_dev}: read={can_read}, write={can_write}", flush=True) + else: + print(f"[v0] {drm_dev}: does not exist", flush=True) + + cmd = 'intel_gpu_top -J' + print(f"[v0] Executing command: {cmd}", flush=True) process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, - bufsize=1 + bufsize=1, + shell=True, + env=os.environ.copy() ) print(f"[v0] Process started with PID: {process.pid}", flush=True) - print(f"[v0] Waiting 1 second for intel_gpu_top to initialize...", flush=True) - time.sleep(1) + print(f"[v0] Waiting 2 seconds for intel_gpu_top to initialize...", flush=True) + time.sleep(2) print(f"[v0] Starting to read JSON objects...", flush=True) start_time = time.time() - timeout = 8 # Increased timeout from 5 to 8 seconds + timeout = 3 # Reduced timeout for faster has_monitoring_tool detection json_objects = [] buffer = "" brace_count = 0 @@ -1709,6 +1730,14 @@ def get_detailed_gpu_info(gpu): print(f"[v0] Collected {len(json_objects)} JSON objects total", flush=True) + if not any('clients' in obj for obj in json_objects): + try: + stderr_output = process.stderr.read() + if stderr_output: + print(f"[v0] intel_gpu_top stderr: {stderr_output[:500]}", flush=True) + except: + pass + best_json = None for json_obj in json_objects: if 'clients' in json_obj and len(json_obj['clients']) > 0: @@ -1825,10 +1854,6 @@ def get_detailed_gpu_info(gpu): print(f"[v0] WARNING: No data retrieved from intel_gpu_top", flush=True) else: print(f"[v0] WARNING: No valid JSON objects found", flush=True) - # Check stderr for errors - stderr_output = process.stderr.read() - if stderr_output: - print(f"[v0] intel_gpu_top stderr: {stderr_output}", flush=True) except Exception as e: print(f"[v0] Error running intel_gpu_top: {e}", flush=True) @@ -2144,74 +2169,6 @@ def get_gpu_info(): return gpus -def get_disk_hardware_info(disk_name): - """Get detailed hardware information for a disk""" - disk_info = {} - - try: - # Get disk type (HDD, SSD, NVMe) - result = subprocess.run(['lsblk', '-d', '-n', '-o', 'NAME,ROTA,TYPE', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - parts = result.stdout.strip().split() - if len(parts) >= 2: - rota = parts[1] - disk_info['type'] = 'HDD' if rota == '1' else 'SSD' - if disk_name.startswith('nvme'): - disk_info['type'] = 'NVMe SSD' - - # Get driver/kernel module - try: - # For NVMe - if disk_name.startswith('nvme'): - disk_info['driver'] = 'nvme' - disk_info['interface'] = 'PCIe/NVMe' - # For SATA/SAS - else: - result = subprocess.run(['udevadm', 'info', '--query=property', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if 'ID_BUS=' in line: - bus = line.split('=')[1].strip() - disk_info['interface'] = bus.upper() - if 'ID_MODEL=' in line: - model = line.split('=')[1].strip() - disk_info['model'] = model - if 'ID_SERIAL_SHORT=' in line: - serial = line.split('=')[1].strip() - disk_info['serial'] = serial - except Exception as e: - print(f"[v0] Error getting disk driver info: {e}") - - # Get SMART data - try: - result = subprocess.run(['smartctl', '-i', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if 'Model Family:' in line: - disk_info['family'] = line.split(':', 1)[1].strip() - elif 'Device Model:' in line or 'Model Number:' in line: - disk_info['model'] = line.split(':', 1)[1].strip() - elif 'Serial Number:' in line: - disk_info['serial'] = line.split(':', 1)[1].strip() - elif 'Firmware Version:' in line: - disk_info['firmware'] = line.split(':', 1)[1].strip() - elif 'Rotation Rate:' in line: - disk_info['rotation_rate'] = line.split(':', 1)[1].strip() - elif 'Form Factor:' in line: - disk_info['form_factor'] = line.split(':', 1)[1].strip() - elif 'SATA Version is:' in line: - disk_info['sata_version'] = line.split(':', 1)[1].strip() - except Exception as e: - print(f"[v0] Error getting SMART info: {e}") - - except Exception as e: - print(f"[v0] Error getting disk hardware info: {e}") - - return disk_info - def get_hardware_info(): """Get comprehensive hardware information""" try: