From 311a624698590a831b6bdb26bcf25af94428b10c Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 18:08:28 +0100 Subject: [PATCH 01/10] Update customizable_post_install.sh --- scripts/post_install/customizable_post_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index a9d48ed..fc456d5 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -2671,7 +2671,7 @@ enable_vfio_iommu() { msg_info "$(translate "Checking conflicting drivers blacklist...")" touch "$blacklist_file" - local blacklist_drivers=("nouveau" "lbm-nouveau" "amdgpu" "radeon" "nvidia" "nvidiafb") + local blacklist_drivers=("nouveau" "lbm-nouveau" "radeon" "nvidia" "nvidiafb") for driver in "${blacklist_drivers[@]}"; do if ! grep -q "^blacklist $driver" "$blacklist_file"; then echo "blacklist $driver" >> "$blacklist_file" From 9490f79c6d00a8b07e9cc8ec7030dca0771da46f Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 18:14:13 +0100 Subject: [PATCH 02/10] Update build-appimage.yml --- .github/workflows/build-appimage.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 37bac0e..9fdffcb 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -1,12 +1,7 @@ name: Build ProxMenux Monitor AppImage on: - push: - branches: [ main ] - paths: [ 'AppImage/**' ] - pull_request: - branches: [ main ] - paths: [ 'AppImage/**' ] + workflow_dispatch: permissions: From 06604ff0d1e82c33833d18aed2d0dafa397f5c4a Mon Sep 17 00:00:00 2001 From: MacRimi <123239993+MacRimi@users.noreply.github.com> Date: Mon, 3 Nov 2025 18:15:17 +0100 Subject: [PATCH 03/10] Add manual build workflow for AppImage --- .../workflows/{build-appimage.yml => build-appimage-manual.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build-appimage.yml => build-appimage-manual.yml} (100%) diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage-manual.yml similarity index 100% rename from .github/workflows/build-appimage.yml rename to .github/workflows/build-appimage-manual.yml From a554af939e2cb1b78959e951cb36e080b879eda5 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 18:24:43 +0100 Subject: [PATCH 04/10] Create build-appimage.yml --- .github/workflows/build-appimage.yml | 56 ++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/build-appimage.yml diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml new file mode 100644 index 0000000..0d8e146 --- /dev/null +++ b/.github/workflows/build-appimage.yml @@ -0,0 +1,56 @@ +name: Build ProxMenux Monitor AppImage + +on: + push: + branches: [ main ] + paths: [ 'AppImage/**' ] + pull_request: + branches: [ main ] + paths: [ 'AppImage/**' ] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + working-directory: AppImage + run: npm install --legacy-peer-deps + + - name: Build Next.js app + working-directory: AppImage + run: npm run build + + - name: Install Python dependencies + run: | + sudo apt-get update + sudo apt-get install -y python3 python3-pip python3-venv + + - name: Make build script executable + working-directory: AppImage + run: chmod +x scripts/build_appimage.sh + + - name: Build AppImage + working-directory: AppImage + run: ./scripts/build_appimage.sh + + - name: Get version from package.json + id: version + working-directory: AppImage + run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Upload AppImage artifact + uses: actions/upload-artifact@v4 + with: + name: ProxMenux-${{ steps.version.outputs.VERSION }}-AppImage + path: AppImage/dist/*.AppImage + retention-days: 30 From d787c3caa0d66735b01227929ff140238c4457d2 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 18:35:16 +0100 Subject: [PATCH 05/10] Update AppImage --- AppImage/components/hardware.tsx | 7 +- AppImage/components/metrics-dialog.tsx | 7 +- AppImage/components/network-traffic-chart.tsx | 6 +- AppImage/components/node-metrics-charts.tsx | 7 +- AppImage/components/proxmox-dashboard.tsx | 13 ++-- AppImage/components/system-logs.tsx | 9 ++- AppImage/components/system-overview.tsx | 16 ++--- AppImage/lib/api-config.ts | 71 +++++++++++++++++++ 8 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 AppImage/lib/api-config.ts diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 6fe121c..f0a5f6f 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -188,7 +188,12 @@ export default function Hardware() { const fetchRealtimeData = async () => { try { - const apiUrl = `http://${window.location.hostname}:8008/api/gpu/${fullSlot}/realtime` + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + const apiUrl = isStandardPort + ? `/api/gpu/${fullSlot}/realtime` + : `${protocol}//${hostname}:8008/api/gpu/${fullSlot}/realtime` const response = await fetch(apiUrl, { method: "GET", diff --git a/AppImage/components/metrics-dialog.tsx b/AppImage/components/metrics-dialog.tsx index 4cf49ae..b8c1c31 100644 --- a/AppImage/components/metrics-dialog.tsx +++ b/AppImage/components/metrics-dialog.tsx @@ -118,8 +118,11 @@ export function MetricsView({ vmid, vmName, vmType, onBack }: MetricsViewProps) setError(null) try { - const baseUrl = - typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + const baseUrl = isStandardPort ? "" : `${protocol}//${hostname}:8008` + const apiUrl = `${baseUrl}/api/vms/${vmid}/metrics?timeframe=${timeframe}` const response = await fetch(apiUrl) diff --git a/AppImage/components/network-traffic-chart.tsx b/AppImage/components/network-traffic-chart.tsx index 34d9a34..a1576f3 100644 --- a/AppImage/components/network-traffic-chart.tsx +++ b/AppImage/components/network-traffic-chart.tsx @@ -75,8 +75,10 @@ export function NetworkTrafficChart({ setError(null) try { - const baseUrl = - typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + const baseUrl = isStandardPort ? "" : `${protocol}//${hostname}:8008` const apiUrl = interfaceName ? `${baseUrl}/api/network/${interfaceName}/metrics?timeframe=${timeframe}` diff --git a/AppImage/components/node-metrics-charts.tsx b/AppImage/components/node-metrics-charts.tsx index 9da7cc5..a8d86e4 100644 --- a/AppImage/components/node-metrics-charts.tsx +++ b/AppImage/components/node-metrics-charts.tsx @@ -86,8 +86,11 @@ export function NodeMetricsCharts() { setError(null) try { - const baseUrl = - typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + const baseUrl = isStandardPort ? "" : `${protocol}//${hostname}:8008` + const apiUrl = `${baseUrl}/api/node/metrics?timeframe=${timeframe}` console.log("[v0] Fetching node metrics from:", apiUrl) diff --git a/AppImage/components/proxmox-dashboard.tsx b/AppImage/components/proxmox-dashboard.tsx index 028e49e..da744ba 100644 --- a/AppImage/components/proxmox-dashboard.tsx +++ b/AppImage/components/proxmox-dashboard.tsx @@ -11,6 +11,7 @@ import { VirtualMachines } from "./virtual-machines" import Hardware from "./hardware" import { SystemLogs } from "./system-logs" import { OnboardingCarousel } from "./onboarding-carousel" +import { getApiUrl } from "../lib/api-config" import { RefreshCw, AlertTriangle, @@ -67,8 +68,7 @@ export function ProxmoxDashboard() { console.log("[v0] Fetching system data from Flask server...") console.log("[v0] Current window location:", window.location.href) - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/system` + const apiUrl = getApiUrl("/api/system") console.log("[v0] API URL:", apiUrl) @@ -235,13 +235,8 @@ export function ProxmoxDashboard() {

• The ProxMenux server should start automatically on port 8008

• Try accessing:{" "} - - http://{typeof window !== "undefined" ? window.location.host : "localhost:8008"}/api/health + + {getApiUrl("/api/health")}

diff --git a/AppImage/components/system-logs.tsx b/AppImage/components/system-logs.tsx index 144e862..853a9eb 100644 --- a/AppImage/components/system-logs.tsx +++ b/AppImage/components/system-logs.tsx @@ -125,7 +125,14 @@ export function SystemLogs() { const getApiUrl = (endpoint: string) => { if (typeof window !== "undefined") { - return `${window.location.protocol}//${window.location.hostname}:8008${endpoint}` + const { protocol, hostname, port } = window.location + const isStandardPort = port === "" || port === "80" || port === "443" + + if (isStandardPort) { + return endpoint + } else { + return `${protocol}//${hostname}:8008${endpoint}` + } } return `http://localhost:8008${endpoint}` } diff --git a/AppImage/components/system-overview.tsx b/AppImage/components/system-overview.tsx index 468799f..850e1ac 100644 --- a/AppImage/components/system-overview.tsx +++ b/AppImage/components/system-overview.tsx @@ -8,6 +8,7 @@ import { Cpu, MemoryStick, Thermometer, Server, Zap, AlertCircle, HardDrive, Net import { NodeMetricsCharts } from "./node-metrics-charts" import { NetworkTrafficChart } from "./network-traffic-chart" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select" +import { getApiUrl } from "../lib/api-config" interface SystemData { cpu_usage: number @@ -97,8 +98,7 @@ interface ProxmoxStorageData { const fetchSystemData = async (): Promise => { try { - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/system` + const apiUrl = getApiUrl("/api/system") const response = await fetch(apiUrl, { method: "GET", @@ -122,8 +122,7 @@ const fetchSystemData = async (): Promise => { const fetchVMData = async (): Promise => { try { - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/vms` + const apiUrl = getApiUrl("/api/vms") const response = await fetch(apiUrl, { method: "GET", @@ -147,8 +146,7 @@ const fetchVMData = async (): Promise => { const fetchStorageData = async (): Promise => { try { - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/storage/summary` + const apiUrl = getApiUrl("/api/storage/summary") const response = await fetch(apiUrl, { method: "GET", @@ -173,8 +171,7 @@ const fetchStorageData = async (): Promise => { const fetchNetworkData = async (): Promise => { try { - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/network/summary` + const apiUrl = getApiUrl("/api/network/summary") const response = await fetch(apiUrl, { method: "GET", @@ -199,8 +196,7 @@ const fetchNetworkData = async (): Promise => { const fetchProxmoxStorageData = async (): Promise => { try { - const baseUrl = typeof window !== "undefined" ? `${window.location.protocol}//${window.location.hostname}:8008` : "" - const apiUrl = `${baseUrl}/api/proxmox-storage` + const apiUrl = getApiUrl("/api/proxmox-storage") const response = await fetch(apiUrl, { method: "GET", diff --git a/AppImage/lib/api-config.ts b/AppImage/lib/api-config.ts new file mode 100644 index 0000000..de2bcc0 --- /dev/null +++ b/AppImage/lib/api-config.ts @@ -0,0 +1,71 @@ +/** + * API Configuration for ProxMenux Monitor + * Handles API URL generation with automatic proxy detection + */ + +/** + * Gets the base URL for API calls + * Automatically detects if running behind a proxy by checking if we're on a standard port + * + * @returns Base URL for API endpoints + */ +export function getApiBaseUrl(): string { + if (typeof window === "undefined") { + return "" + } + + const { protocol, hostname, port } = window.location + + // If accessing via standard ports (80/443) or no port, assume we're behind a proxy + // In this case, use relative URLs so the proxy handles routing + const isStandardPort = port === "" || port === "80" || port === "443" + + if (isStandardPort) { + // Behind a proxy - use relative URL + return "" + } else { + // Direct access - use explicit port 8008 + return `${protocol}//${hostname}:8008` + } +} + +/** + * Constructs a full API URL + * + * @param endpoint - API endpoint path (e.g., '/api/system') + * @returns Full API URL + */ +export function getApiUrl(endpoint: string): string { + const baseUrl = getApiBaseUrl() + + // Ensure endpoint starts with / + const normalizedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}` + + return `${baseUrl}${normalizedEndpoint}` +} + +/** + * Fetches data from an API endpoint with error handling + * + * @param endpoint - API endpoint path + * @param options - Fetch options + * @returns Promise with the response data + */ +export async function fetchApi(endpoint: string, options?: RequestInit): Promise { + const url = getApiUrl(endpoint) + + const response = await fetch(url, { + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + cache: "no-store", + }) + + if (!response.ok) { + throw new Error(`API request failed: ${response.status} ${response.statusText}`) + } + + return response.json() +} From a70b33ce13cb675d120573badf46ed8b896542f6 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 19:02:41 +0100 Subject: [PATCH 06/10] Update AppImage --- AppImage/components/hardware.tsx | 16 +++++++++------- AppImage/components/storage-overview.tsx | 6 ++++++ AppImage/scripts/flask_server.py | 20 ++++++++++++++++++++ 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index f0a5f6f..2ff78e8 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -1788,13 +1788,15 @@ export default function Hardware() { {selectedDisk.rotation_rate !== undefined && selectedDisk.rotation_rate !== null && (
Rotation Rate - - {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 - ? `${selectedDisk.rotation_rate} rpm` - : typeof selectedDisk.rotation_rate === "string" - ? selectedDisk.rotation_rate - : "Solid State Device"} - +
+ {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate === -1 + ? "N/A" + : typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 + ? `${selectedDisk.rotation_rate} rpm` + : typeof selectedDisk.rotation_rate === "string" + ? selectedDisk.rotation_rate + : "Solid State Device"} +
)} diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx index e7263cc..7debfe6 100644 --- a/AppImage/components/storage-overview.tsx +++ b/AppImage/components/storage-overview.tsx @@ -211,6 +211,12 @@ export function StorageOverview() { if (diskName.startsWith("nvme")) { return "NVMe" } + // rotation_rate = -1 means HDD but RPM is unknown (detected via kernel rotational flag) + // rotation_rate = 0 or undefined means SSD + // rotation_rate > 0 means HDD with known RPM + if (rotationRate === -1) { + return "HDD" + } if (!rotationRate || rotationRate === 0) { return "SSD" } diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 7533f0b..e3c9084 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1387,6 +1387,26 @@ def get_smart_data(disk_name): smart_data['health'] = 'warning' # print(f"[v0] Health: WARNING (temperature {smart_data['temperature']}°C)") pass + + # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM + # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate + # This fixes detection for older disks that don't report RPM via smartctl + if smart_data['rotation_rate'] == 0: + try: + rotational_path = f"/sys/block/{disk_name}/queue/rotational" + if os.path.exists(rotational_path): + with open(rotational_path, 'r') as f: + rotational = int(f.read().strip()) + if rotational == 1: + # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" + smart_data['rotation_rate'] = -1 + # If rotational == 0, it's an SSD, keep rotation_rate as 0 + except Exception as e: + pass # If we can't read the file, leave rotation_rate as is + + except FileNotFoundError: + # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") + pass except FileNotFoundError: # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") From 5158c5f359e74c7dff11b3c01dce635148c1fd18 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 19:12:07 +0100 Subject: [PATCH 07/10] Update flask_server.py --- AppImage/scripts/flask_server.py | 34 ++++++++++++++------------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index e3c9084..261b0af 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1388,26 +1388,22 @@ def get_smart_data(disk_name): # print(f"[v0] Health: WARNING (temperature {smart_data['temperature']}°C)") pass - # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM - # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate - # This fixes detection for older disks that don't report RPM via smartctl - if smart_data['rotation_rate'] == 0: - try: - rotational_path = f"/sys/block/{disk_name}/queue/rotational" - if os.path.exists(rotational_path): - with open(rotational_path, 'r') as f: - rotational = int(f.read().strip()) - if rotational == 1: - # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" - smart_data['rotation_rate'] = -1 - # If rotational == 0, it's an SSD, keep rotation_rate as 0 - except Exception as e: - pass # If we can't read the file, leave rotation_rate as is + # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM + # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate + # This fixes detection for older disks that don't report RPM via smartctl + if smart_data['rotation_rate'] == 0: + try: + rotational_path = f"/sys/block/{disk_name}/queue/rotational" + if os.path.exists(rotational_path): + with open(rotational_path, 'r') as f: + rotational = int(f.read().strip()) + if rotational == 1: + # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" + smart_data['rotation_rate'] = -1 + # If rotational == 0, it's an SSD, keep rotation_rate as 0 + except Exception as e: + pass # If we can't read the file, leave rotation_rate as is - except FileNotFoundError: - # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") - pass - except FileNotFoundError: # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") pass From 128edc08e2d222645e108d23db46a4eb141be58b Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 23:13:24 +0100 Subject: [PATCH 08/10] Update flask_server.py --- AppImage/scripts/flask_server.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 261b0af..d514f9c 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1388,22 +1388,22 @@ def get_smart_data(disk_name): # print(f"[v0] Health: WARNING (temperature {smart_data['temperature']}°C)") pass - # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM - # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate - # This fixes detection for older disks that don't report RPM via smartctl - if smart_data['rotation_rate'] == 0: - try: - rotational_path = f"/sys/block/{disk_name}/queue/rotational" - if os.path.exists(rotational_path): - with open(rotational_path, 'r') as f: - rotational = int(f.read().strip()) - if rotational == 1: - # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" - smart_data['rotation_rate'] = -1 - # If rotational == 0, it's an SSD, keep rotation_rate as 0 - except Exception as e: - pass # If we can't read the file, leave rotation_rate as is - + # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM + # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate + # This fixes detection for older disks that don't report RPM via smartctl + if smart_data['rotation_rate'] == 0: + try: + rotational_path = f"/sys/block/{disk_name}/queue/rotational" + if os.path.exists(rotational_path): + with open(rotational_path, 'r') as f: + rotational = int(f.read().strip()) + if rotational == 1: + # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" + smart_data['rotation_rate'] = -1 + # If rotational == 0, it's an SSD, keep rotation_rate as 0 + except Exception as e: + pass # If we can't read the file, leave rotation_rate as is + except FileNotFoundError: # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") pass From 31d7f7e3e9113177b00da380e66d61598b41e729 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 23:17:27 +0100 Subject: [PATCH 09/10] Update appImage --- AppImage/components/hardware.tsx | 16 +++++++--------- AppImage/components/storage-overview.tsx | 6 ------ AppImage/scripts/flask_server.py | 16 ---------------- 3 files changed, 7 insertions(+), 31 deletions(-) diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 2ff78e8..f0a5f6f 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -1788,15 +1788,13 @@ export default function Hardware() { {selectedDisk.rotation_rate !== undefined && selectedDisk.rotation_rate !== null && (
Rotation Rate -
- {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate === -1 - ? "N/A" - : typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 - ? `${selectedDisk.rotation_rate} rpm` - : typeof selectedDisk.rotation_rate === "string" - ? selectedDisk.rotation_rate - : "Solid State Device"} -
+ + {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 + ? `${selectedDisk.rotation_rate} rpm` + : typeof selectedDisk.rotation_rate === "string" + ? selectedDisk.rotation_rate + : "Solid State Device"} +
)} diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx index 7debfe6..e7263cc 100644 --- a/AppImage/components/storage-overview.tsx +++ b/AppImage/components/storage-overview.tsx @@ -211,12 +211,6 @@ export function StorageOverview() { if (diskName.startsWith("nvme")) { return "NVMe" } - // rotation_rate = -1 means HDD but RPM is unknown (detected via kernel rotational flag) - // rotation_rate = 0 or undefined means SSD - // rotation_rate > 0 means HDD with known RPM - if (rotationRate === -1) { - return "HDD" - } if (!rotationRate || rotationRate === 0) { return "SSD" } diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index d514f9c..7533f0b 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1387,22 +1387,6 @@ def get_smart_data(disk_name): smart_data['health'] = 'warning' # print(f"[v0] Health: WARNING (temperature {smart_data['temperature']}°C)") pass - - # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM - # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate - # This fixes detection for older disks that don't report RPM via smartctl - if smart_data['rotation_rate'] == 0: - try: - rotational_path = f"/sys/block/{disk_name}/queue/rotational" - if os.path.exists(rotational_path): - with open(rotational_path, 'r') as f: - rotational = int(f.read().strip()) - if rotational == 1: - # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" - smart_data['rotation_rate'] = -1 - # If rotational == 0, it's an SSD, keep rotation_rate as 0 - except Exception as e: - pass # If we can't read the file, leave rotation_rate as is except FileNotFoundError: # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.") From 11e3f53a2fc814cbd8d8b21c19df8b2b33af95ac Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 3 Nov 2025 23:26:04 +0100 Subject: [PATCH 10/10] Update AppImage --- AppImage/components/hardware.tsx | 16 +++++++++------- AppImage/components/storage-overview.tsx | 6 ++++++ AppImage/scripts/flask_server.py | 17 +++++++++++++++++ 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index f0a5f6f..2ff78e8 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -1788,13 +1788,15 @@ export default function Hardware() { {selectedDisk.rotation_rate !== undefined && selectedDisk.rotation_rate !== null && (
Rotation Rate - - {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 - ? `${selectedDisk.rotation_rate} rpm` - : typeof selectedDisk.rotation_rate === "string" - ? selectedDisk.rotation_rate - : "Solid State Device"} - +
+ {typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate === -1 + ? "N/A" + : typeof selectedDisk.rotation_rate === "number" && selectedDisk.rotation_rate > 0 + ? `${selectedDisk.rotation_rate} rpm` + : typeof selectedDisk.rotation_rate === "string" + ? selectedDisk.rotation_rate + : "Solid State Device"} +
)} diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx index e7263cc..7debfe6 100644 --- a/AppImage/components/storage-overview.tsx +++ b/AppImage/components/storage-overview.tsx @@ -211,6 +211,12 @@ export function StorageOverview() { if (diskName.startsWith("nvme")) { return "NVMe" } + // rotation_rate = -1 means HDD but RPM is unknown (detected via kernel rotational flag) + // rotation_rate = 0 or undefined means SSD + // rotation_rate > 0 means HDD with known RPM + if (rotationRate === -1) { + return "HDD" + } if (!rotationRate || rotationRate === 0) { return "SSD" } diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 7533f0b..783c611 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1387,6 +1387,23 @@ def get_smart_data(disk_name): smart_data['health'] = 'warning' # print(f"[v0] Health: WARNING (temperature {smart_data['temperature']}°C)") pass + + # CHANGE: Use -1 to indicate HDD with unknown RPM instead of inventing 7200 RPM + # Fallback: Check kernel's rotational flag if smartctl didn't provide rotation_rate + # This fixes detection for older disks that don't report RPM via smartctl + if smart_data['rotation_rate'] == 0: + try: + rotational_path = f"/sys/block/{disk_name}/queue/rotational" + if os.path.exists(rotational_path): + with open(rotational_path, 'r') as f: + rotational = int(f.read().strip()) + if rotational == 1: + # Disk is rotational (HDD), use -1 to indicate "HDD but RPM unknown" + smart_data['rotation_rate'] = -1 + # If rotational == 0, it's an SSD, keep rotation_rate as 0 + except Exception as e: + pass # If we can't read the file, leave rotation_rate as is + except FileNotFoundError: # print(f"[v0] ERROR: smartctl not found - install smartmontools for disk monitoring.")