mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-05-01 03:46:22 +00:00
update storage-overview.tsx
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user