update storage-overview.tsx

This commit is contained in:
MacRimi
2026-04-12 22:28:15 +02:00
parent 441ee8e948
commit 47145ab9d1

View File

@@ -6362,11 +6362,45 @@ def _get_smart_json_path(disk_name):
"""Get path to SMART JSON file for a disk.""" """Get path to SMART JSON file for a disk."""
return os.path.join(SMART_DIR, f"{disk_name}.json") return os.path.join(SMART_DIR, f"{disk_name}.json")
def _ensure_smart_tools(): def _ensure_smart_tools(install_if_missing=False):
"""Check if SMART tools are installed.""" """Check if SMART tools are installed and optionally install them."""
has_smartctl = shutil.which('smartctl') is not None has_smartctl = shutil.which('smartctl') is not None
has_nvme = shutil.which('nvme') is not None has_nvme = shutil.which('nvme') is not None
return {'smartctl': has_smartctl, 'nvme': has_nvme}
installed = {'smartctl': False, 'nvme': False}
if install_if_missing:
if not has_smartctl:
try:
# Install smartmontools
proc = subprocess.run(
['apt-get', 'install', '-y', 'smartmontools'],
capture_output=True, text=True, timeout=120
)
if proc.returncode == 0:
has_smartctl = shutil.which('smartctl') is not None
installed['smartctl'] = has_smartctl
except Exception:
pass
if not has_nvme:
try:
# Install nvme-cli
proc = subprocess.run(
['apt-get', 'install', '-y', 'nvme-cli'],
capture_output=True, text=True, timeout=120
)
if proc.returncode == 0:
has_nvme = shutil.which('nvme') is not None
installed['nvme'] = has_nvme
except Exception:
pass
return {
'smartctl': has_smartctl,
'nvme': has_nvme,
'just_installed': installed
}
def _parse_smart_attributes(output_lines): def _parse_smart_attributes(output_lines):
"""Parse SMART attributes from smartctl output.""" """Parse SMART attributes from smartctl output."""
@@ -6516,16 +6550,37 @@ def api_smart_status(disk_name):
lines = proc.stdout.split('\n') lines = proc.stdout.split('\n')
for line in lines: for line in lines:
if line.startswith('# ') or line.startswith('# '): if line.startswith('# ') or line.startswith('# '):
# Format: # 1 Short offline Completed without error 00% 18453 -
test_type = 'short' if 'Short' in line else 'long' if 'Extended' in line or 'Long' in line else 'unknown'
test_status = 'passed' if 'Completed without error' in line or 'without error' in line.lower() else 'failed'
# Extract completion status text (everything between test type and percentage)
completion_text = ''
if 'Completed' in line:
match = re.search(r'(Completed[^0-9%]+)', line)
if match:
completion_text = match.group(1).strip()
elif 'without error' in line.lower():
completion_text = 'Completed without error'
# Extract lifetime hours (power-on hours when test completed)
lifetime_hours = None
parts = line.split() parts = line.split()
if len(parts) >= 5: for i, p in enumerate(parts):
test_type = 'short' if 'Short' in line else 'long' if 'Extended' in line or 'Long' in line else 'unknown' if p.endswith('%') and i + 1 < len(parts):
test_status = 'passed' if 'Completed without error' in line else 'failed' try:
result['last_test'] = { lifetime_hours = int(parts[i + 1])
'type': test_type, except ValueError:
'status': test_status, pass
'timestamp': ' '.join(parts[-5:-2]) if len(parts) > 5 else 'unknown' break
}
break result['last_test'] = {
'type': test_type,
'status': test_status,
'timestamp': completion_text or 'Completed',
'lifetime_hours': lifetime_hours
}
break
return jsonify(result) return jsonify(result)
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
@@ -6553,16 +6608,18 @@ def api_smart_run_test(disk_name):
if test_type not in ('short', 'long'): if test_type not in ('short', 'long'):
return jsonify({'error': 'Invalid test type. Use "short" or "long"'}), 400 return jsonify({'error': 'Invalid test type. Use "short" or "long"'}), 400
tools = _ensure_smart_tools()
is_nvme = _is_nvme(disk_name) is_nvme = _is_nvme(disk_name)
# Check tools and auto-install if missing
tools = _ensure_smart_tools(install_if_missing=True)
# Ensure SMART directory exists # Ensure SMART directory exists
os.makedirs(SMART_DIR, exist_ok=True) os.makedirs(SMART_DIR, exist_ok=True)
json_path = _get_smart_json_path(disk_name) json_path = _get_smart_json_path(disk_name)
if is_nvme: if is_nvme:
if not tools['nvme']: if not tools['nvme']:
return jsonify({'error': 'nvme-cli not installed'}), 400 return jsonify({'error': 'nvme-cli not installed. Please run: apt-get install nvme-cli'}), 400
# NVMe: self-test-code 1=short, 2=long # NVMe: self-test-code 1=short, 2=long
code = 1 if test_type == 'short' else 2 code = 1 if test_type == 'short' else 2
@@ -6574,21 +6631,22 @@ def api_smart_run_test(disk_name):
if proc.returncode != 0: if proc.returncode != 0:
return jsonify({'error': f'Failed to start test: {proc.stderr}'}), 500 return jsonify({'error': f'Failed to start test: {proc.stderr}'}), 500
# For long test, start background monitor # Start background monitor to save JSON when test completes
if test_type == 'long': sleep_interval = 10 if test_type == 'short' else 60
subprocess.Popen( subprocess.Popen(
f''' f'''
while nvme device-self-test {device} --self-test-code=0 2>/dev/null | grep -qi 'in progress'; do sleep 5
sleep 60 while nvme device-self-test {device} --self-test-code=0 2>/dev/null | grep -qi 'in progress'; do
done sleep {sleep_interval}
nvme smart-log -o json {device} > {json_path} 2>/dev/null done
''', nvme smart-log -o json {device} > {json_path} 2>/dev/null
shell=True, start_new_session=True, ''',
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL shell=True, start_new_session=True,
) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
else: else:
if not tools['smartctl']: if not tools['smartctl']:
return jsonify({'error': 'smartmontools not installed'}), 400 return jsonify({'error': 'smartmontools not installed. Please run: apt-get install smartmontools'}), 400
test_flag = '-t short' if test_type == 'short' else '-t long' test_flag = '-t short' if test_type == 'short' else '-t long'
proc = subprocess.run( proc = subprocess.run(
@@ -6599,18 +6657,19 @@ def api_smart_run_test(disk_name):
if proc.returncode not in (0, 4): # 4 = test started successfully if proc.returncode not in (0, 4): # 4 = test started successfully
return jsonify({'error': f'Failed to start test: {proc.stderr}'}), 500 return jsonify({'error': f'Failed to start test: {proc.stderr}'}), 500
# For long test, start background monitor # Start background monitor to save JSON when test completes
if test_type == 'long': sleep_interval = 10 if test_type == 'short' else 60
subprocess.Popen( subprocess.Popen(
f''' f'''
while smartctl -c {device} 2>/dev/null | grep -qiE 'Self-test routine in progress|[1-9][0-9]?% of test remaining'; do sleep 5
sleep 60 while smartctl -c {device} 2>/dev/null | grep -qiE 'Self-test routine in progress|[1-9][0-9]?% of test remaining'; do
done sleep {sleep_interval}
smartctl --json=c {device} > {json_path} 2>/dev/null done
''', smartctl --json=c {device} > {json_path} 2>/dev/null
shell=True, start_new_session=True, ''',
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL shell=True, start_new_session=True,
) stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
return jsonify({ return jsonify({
'success': True, 'success': True,