From f14a0393b7617d2f26b0d9c33c43dbae831c64fd Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 12 Apr 2026 22:50:30 +0200 Subject: [PATCH] update storage-overview.tsx --- AppImage/components/storage-overview.tsx | 97 +++++++++++++++++++----- AppImage/scripts/flask_server.py | 62 ++++++++++++--- 2 files changed, 130 insertions(+), 29 deletions(-) diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx index e16cf1f2..5622eece 100644 --- a/AppImage/components/storage-overview.tsx +++ b/AppImage/components/storage-overview.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { HardDrive, Database, AlertTriangle, CheckCircle2, XCircle, Square, Thermometer, Archive, Info, Clock, Usb, Server, Activity, FileText, Play, Loader2 } from "lucide-react" +import { HardDrive, Database, AlertTriangle, CheckCircle2, XCircle, Square, Thermometer, Archive, Info, Clock, Usb, Server, Activity, FileText, Play, Loader2, Download } from "lucide-react" import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" @@ -1869,6 +1869,10 @@ interface SmartTestStatus { status: 'ok' | 'warning' | 'critical' }> } + tools_installed?: { + smartctl: boolean + nvme: boolean + } } function SmartTestTab({ disk }: SmartTestTabProps) { @@ -1897,6 +1901,35 @@ function SmartTestTab({ disk }: SmartTestTabProps) { } const [testError, setTestError] = useState(null) + const [installing, setInstalling] = useState(false) + + // Check if required tools are installed for this disk type + const isNvme = disk.name.startsWith('nvme') + const toolsAvailable = testStatus.tools_installed + ? (isNvme ? testStatus.tools_installed.nvme : testStatus.tools_installed.smartctl) + : true // Assume true until we get the status + + const installSmartTools = async () => { + try { + setInstalling(true) + setTestError(null) + const res = await fetch('/api/storage/smart/tools/install', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ install_all: true }) + }) + const data = await res.json() + if (data.success) { + fetchSmartStatus() + } else { + setTestError(data.error || 'Installation failed. Try manually: apt-get install smartmontools nvme-cli') + } + } catch { + setTestError('Failed to install tools. Try manually: apt-get install smartmontools nvme-cli') + } finally { + setInstalling(false) + } + } const runSmartTest = async (testType: 'short' | 'long') => { try { @@ -1944,6 +1977,50 @@ function SmartTestTab({ disk }: SmartTestTabProps) { ) } + // If tools not available, show install button only + if (!toolsAvailable && !loading) { + return ( +
+
+
+ +
+

SMART Tools Not Installed

+

+ {isNvme + ? 'nvme-cli is required to run SMART tests on NVMe disks.' + : 'smartmontools is required to run SMART tests on this disk.'} +

+
+
+ + + + {testError && ( +
+ +
+

Installation Failed

+

{testError}

+
+
+ )} +
+
+ ) + } + return (
{/* Quick Actions */} @@ -2001,28 +2078,12 @@ function SmartTestTab({ disk }: SmartTestTabProps) { {testError && (
-
+

Failed to start test

{testError}

)} - - {/* Tools not installed warning */} - {testStatus.tools_installed && (!testStatus.tools_installed.smartctl || !testStatus.tools_installed.nvme) && ( -
- -
-

SMART tools not fully installed

-

- {!testStatus.tools_installed.smartctl && 'smartmontools (for SATA/SAS) '} - {!testStatus.tools_installed.smartctl && !testStatus.tools_installed.nvme && 'and '} - {!testStatus.tools_installed.nvme && 'nvme-cli (for NVMe) '} - not found. Click a test button to auto-install. -

-
-
- )}
{/* Test Progress */} diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index d9c0c35d..8516b17a 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -6368,8 +6368,18 @@ def _ensure_smart_tools(install_if_missing=False): has_nvme = shutil.which('nvme') is not None installed = {'smartctl': False, 'nvme': False} + install_errors = [] - if install_if_missing: + if install_if_missing and (not has_smartctl or not has_nvme): + # Run apt-get update first + try: + subprocess.run( + ['apt-get', 'update'], + capture_output=True, text=True, timeout=60 + ) + except Exception: + pass + if not has_smartctl: try: # Install smartmontools @@ -6380,8 +6390,10 @@ def _ensure_smart_tools(install_if_missing=False): if proc.returncode == 0: has_smartctl = shutil.which('smartctl') is not None installed['smartctl'] = has_smartctl - except Exception: - pass + else: + install_errors.append(f"smartmontools: {proc.stderr.strip()}") + except Exception as e: + install_errors.append(f"smartmontools: {str(e)}") if not has_nvme: try: @@ -6393,13 +6405,16 @@ def _ensure_smart_tools(install_if_missing=False): if proc.returncode == 0: has_nvme = shutil.which('nvme') is not None installed['nvme'] = has_nvme - except Exception: - pass + else: + install_errors.append(f"nvme-cli: {proc.stderr.strip()}") + except Exception as e: + install_errors.append(f"nvme-cli: {str(e)}") return { 'smartctl': has_smartctl, 'nvme': has_nvme, - 'just_installed': installed + 'just_installed': installed, + 'install_errors': install_errors } def _parse_smart_attributes(output_lines): @@ -6697,25 +6712,50 @@ def api_smart_tools_install(): """Install SMART tools (smartmontools and nvme-cli).""" try: data = request.get_json() or {} - packages = data.get('packages', ['smartmontools', 'nvme-cli']) + install_all = data.get('install_all', False) + packages = data.get('packages', ['smartmontools', 'nvme-cli'] if install_all else []) + + if not packages and install_all: + packages = ['smartmontools', 'nvme-cli'] + + if not packages: + return jsonify({'error': 'No packages specified'}), 400 + + # Run apt-get update first + update_proc = subprocess.run( + ['apt-get', 'update'], + capture_output=True, text=True, timeout=60 + ) results = {} + all_success = True for pkg in packages: if pkg not in ('smartmontools', 'nvme-cli'): results[pkg] = {'success': False, 'error': 'Invalid package name'} + all_success = False continue - # Update apt cache and install + # Install package proc = subprocess.run( ['apt-get', 'install', '-y', pkg], capture_output=True, text=True, timeout=120 ) + success = proc.returncode == 0 results[pkg] = { - 'success': proc.returncode == 0, - 'output': proc.stdout if proc.returncode == 0 else proc.stderr + 'success': success, + 'output': proc.stdout if success else proc.stderr } + if not success: + all_success = False - return jsonify(results) + # Check what's now installed + tools = _ensure_smart_tools() + + return jsonify({ + 'success': all_success, + 'results': results, + 'tools': tools + }) except subprocess.TimeoutExpired: return jsonify({'error': 'Installation timeout'}), 504 except Exception as e: