mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-10-11 20:36:17 +00:00
Update AppImage
This commit is contained in:
@@ -25,6 +25,8 @@ interface DiskInfo {
|
|||||||
reallocated_sectors?: number
|
reallocated_sectors?: number
|
||||||
pending_sectors?: number
|
pending_sectors?: number
|
||||||
crc_errors?: number
|
crc_errors?: number
|
||||||
|
rotation_rate?: number // Added rotation rate (RPM)
|
||||||
|
power_cycles?: number // Added power cycle count
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ZFSPool {
|
interface ZFSPool {
|
||||||
@@ -152,6 +154,40 @@ export function StorageOverview() {
|
|||||||
return `${days}d`
|
return `${days}d`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatRotationRate = (rpm: number | undefined) => {
|
||||||
|
if (!rpm || rpm === 0) return "SSD"
|
||||||
|
return `${rpm.toLocaleString()} RPM`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDiskType = (diskName: string, rotationRate: number | undefined): string => {
|
||||||
|
if (diskName.startsWith("nvme")) {
|
||||||
|
return "NVMe"
|
||||||
|
}
|
||||||
|
if (!rotationRate || rotationRate === 0) {
|
||||||
|
return "SSD"
|
||||||
|
}
|
||||||
|
return "HDD"
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDiskTypeBadge = (diskName: string, rotationRate: number | undefined) => {
|
||||||
|
const diskType = getDiskType(diskName, rotationRate)
|
||||||
|
const badgeStyles: Record<string, { className: string; label: string }> = {
|
||||||
|
NVMe: {
|
||||||
|
className: "bg-purple-500/10 text-purple-500 border-purple-500/20",
|
||||||
|
label: "NVMe",
|
||||||
|
},
|
||||||
|
SSD: {
|
||||||
|
className: "bg-cyan-500/10 text-cyan-500 border-cyan-500/20",
|
||||||
|
label: "SSD",
|
||||||
|
},
|
||||||
|
HDD: {
|
||||||
|
className: "bg-blue-500/10 text-blue-500 border-blue-500/20",
|
||||||
|
label: "HDD",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return badgeStyles[diskType]
|
||||||
|
}
|
||||||
|
|
||||||
const handleDiskClick = (disk: DiskInfo) => {
|
const handleDiskClick = (disk: DiskInfo) => {
|
||||||
setSelectedDisk(disk)
|
setSelectedDisk(disk)
|
||||||
setDetailsOpen(true)
|
setDetailsOpen(true)
|
||||||
@@ -396,7 +432,12 @@ export function StorageOverview() {
|
|||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
<HardDrive className="h-5 w-5 text-muted-foreground" />
|
||||||
<div>
|
<div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<h3 className="font-semibold">/dev/{disk.name}</h3>
|
<h3 className="font-semibold">/dev/{disk.name}</h3>
|
||||||
|
<Badge className={getDiskTypeBadge(disk.name, disk.rotation_rate).className}>
|
||||||
|
{getDiskTypeBadge(disk.name, disk.rotation_rate).label}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
{disk.model && disk.model !== "Unknown" && (
|
{disk.model && disk.model !== "Unknown" && (
|
||||||
<p className="text-sm text-muted-foreground">{disk.model}</p>
|
<p className="text-sm text-muted-foreground">{disk.model}</p>
|
||||||
)}
|
)}
|
||||||
@@ -496,6 +537,18 @@ export function StorageOverview() {
|
|||||||
: "N/A"}
|
: "N/A"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Rotation Rate</p>
|
||||||
|
<p className="font-medium">{formatRotationRate(selectedDisk.rotation_rate)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Power Cycles</p>
|
||||||
|
<p className="font-medium">
|
||||||
|
{selectedDisk.power_cycles && selectedDisk.power_cycles > 0
|
||||||
|
? selectedDisk.power_cycles.toLocaleString()
|
||||||
|
: "N/A"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-muted-foreground">SMART Status</p>
|
<p className="text-sm text-muted-foreground">SMART Status</p>
|
||||||
<p className="font-medium capitalize">{selectedDisk.smart_status}</p>
|
<p className="font-medium capitalize">{selectedDisk.smart_status}</p>
|
||||||
|
@@ -395,7 +395,9 @@ def get_storage_info():
|
|||||||
'serial': smart_data.get('serial', 'Unknown'),
|
'serial': smart_data.get('serial', 'Unknown'),
|
||||||
'reallocated_sectors': smart_data.get('reallocated_sectors', 0),
|
'reallocated_sectors': smart_data.get('reallocated_sectors', 0),
|
||||||
'pending_sectors': smart_data.get('pending_sectors', 0),
|
'pending_sectors': smart_data.get('pending_sectors', 0),
|
||||||
'crc_errors': smart_data.get('crc_errors', 0)
|
'crc_errors': smart_data.get('crc_errors', 0),
|
||||||
|
'rotation_rate': smart_data.get('rotation_rate', 0), # Added
|
||||||
|
'power_cycles': smart_data.get('power_cycles', 0) # Added
|
||||||
}
|
}
|
||||||
|
|
||||||
storage_data['disk_count'] += 1
|
storage_data['disk_count'] += 1
|
||||||
@@ -511,7 +513,9 @@ def get_smart_data(disk_name):
|
|||||||
'serial': 'Unknown',
|
'serial': 'Unknown',
|
||||||
'reallocated_sectors': 0,
|
'reallocated_sectors': 0,
|
||||||
'pending_sectors': 0,
|
'pending_sectors': 0,
|
||||||
'crc_errors': 0
|
'crc_errors': 0,
|
||||||
|
'rotation_rate': 0, # Added rotation rate (RPM)
|
||||||
|
'power_cycles': 0, # Added power cycle count
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"[v0] ===== Starting SMART data collection for /dev/{disk_name} =====")
|
print(f"[v0] ===== Starting SMART data collection for /dev/{disk_name} =====")
|
||||||
@@ -544,9 +548,6 @@ def get_smart_data(disk_name):
|
|||||||
stderr_preview = result.stderr[:200].replace('\n', ' ')
|
stderr_preview = result.stderr[:200].replace('\n', ' ')
|
||||||
print(f"[v0] stderr: {stderr_preview}")
|
print(f"[v0] stderr: {stderr_preview}")
|
||||||
|
|
||||||
# smartctl returns: 0=OK, 2=SMART disabled, 4=threshold exceeded, 8=error log has errors
|
|
||||||
# 64=device open failed (but sometimes still has partial data)
|
|
||||||
# We'll try to parse ANY output if stdout is not empty
|
|
||||||
has_output = result.stdout and len(result.stdout.strip()) > 50
|
has_output = result.stdout and len(result.stdout.strip()) > 50
|
||||||
|
|
||||||
if has_output:
|
if has_output:
|
||||||
@@ -572,6 +573,10 @@ def get_smart_data(disk_name):
|
|||||||
smart_data['serial'] = data['serial_number']
|
smart_data['serial'] = data['serial_number']
|
||||||
print(f"[v0] Serial: {smart_data['serial']}")
|
print(f"[v0] Serial: {smart_data['serial']}")
|
||||||
|
|
||||||
|
if 'rotation_rate' in data:
|
||||||
|
smart_data['rotation_rate'] = data['rotation_rate']
|
||||||
|
print(f"[v0] Rotation Rate: {smart_data['rotation_rate']} RPM")
|
||||||
|
|
||||||
# Extract SMART status
|
# Extract SMART status
|
||||||
if 'smart_status' in data and 'passed' in data['smart_status']:
|
if 'smart_status' in data and 'passed' in data['smart_status']:
|
||||||
smart_data['smart_status'] = 'passed' if data['smart_status']['passed'] else 'failed'
|
smart_data['smart_status'] = 'passed' if data['smart_status']['passed'] else 'failed'
|
||||||
@@ -593,6 +598,9 @@ def get_smart_data(disk_name):
|
|||||||
if attr_id == 9: # Power_On_Hours
|
if attr_id == 9: # Power_On_Hours
|
||||||
smart_data['power_on_hours'] = raw_value
|
smart_data['power_on_hours'] = raw_value
|
||||||
print(f"[v0] Power On Hours (ID 9): {raw_value}")
|
print(f"[v0] Power On Hours (ID 9): {raw_value}")
|
||||||
|
elif attr_id == 12: # Power_Cycle_Count
|
||||||
|
smart_data['power_cycles'] = raw_value
|
||||||
|
print(f"[v0] Power Cycles (ID 12): {raw_value}")
|
||||||
elif attr_id == 194: # Temperature_Celsius
|
elif attr_id == 194: # Temperature_Celsius
|
||||||
if smart_data['temperature'] == 0:
|
if smart_data['temperature'] == 0:
|
||||||
smart_data['temperature'] = raw_value
|
smart_data['temperature'] = raw_value
|
||||||
@@ -621,6 +629,9 @@ def get_smart_data(disk_name):
|
|||||||
if 'power_on_hours' in nvme_data:
|
if 'power_on_hours' in nvme_data:
|
||||||
smart_data['power_on_hours'] = nvme_data['power_on_hours']
|
smart_data['power_on_hours'] = nvme_data['power_on_hours']
|
||||||
print(f"[v0] NVMe Power On Hours: {smart_data['power_on_hours']}")
|
print(f"[v0] NVMe Power On Hours: {smart_data['power_on_hours']}")
|
||||||
|
if 'power_cycles' in nvme_data:
|
||||||
|
smart_data['power_cycles'] = nvme_data['power_cycles']
|
||||||
|
print(f"[v0] NVMe Power Cycles: {smart_data['power_cycles']}")
|
||||||
|
|
||||||
# If we got good data, break out of the loop
|
# If we got good data, break out of the loop
|
||||||
if smart_data['model'] != 'Unknown' and smart_data['serial'] != 'Unknown':
|
if smart_data['model'] != 'Unknown' and smart_data['serial'] != 'Unknown':
|
||||||
@@ -651,6 +662,18 @@ def get_smart_data(disk_name):
|
|||||||
smart_data['serial'] = line.split(':', 1)[1].strip()
|
smart_data['serial'] = line.split(':', 1)[1].strip()
|
||||||
print(f"[v0] Found serial: {smart_data['serial']}")
|
print(f"[v0] Found serial: {smart_data['serial']}")
|
||||||
|
|
||||||
|
elif line.startswith('Rotation Rate:') and smart_data['rotation_rate'] == 0:
|
||||||
|
rate_str = line.split(':', 1)[1].strip()
|
||||||
|
if 'rpm' in rate_str.lower():
|
||||||
|
try:
|
||||||
|
smart_data['rotation_rate'] = int(rate_str.split()[0])
|
||||||
|
print(f"[v0] Found rotation rate: {smart_data['rotation_rate']} RPM")
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
pass
|
||||||
|
elif 'Solid State Device' in rate_str:
|
||||||
|
smart_data['rotation_rate'] = 0 # SSD
|
||||||
|
print(f"[v0] Found SSD (no rotation)")
|
||||||
|
|
||||||
# SMART status detection
|
# SMART status detection
|
||||||
elif 'SMART overall-health self-assessment test result:' in line:
|
elif 'SMART overall-health self-assessment test result:' in line:
|
||||||
if 'PASSED' in line:
|
if 'PASSED' in line:
|
||||||
@@ -706,6 +729,10 @@ def get_smart_data(disk_name):
|
|||||||
raw_clean = raw_value.split()[0].replace('h', '').replace(',', '')
|
raw_clean = raw_value.split()[0].replace('h', '').replace(',', '')
|
||||||
smart_data['power_on_hours'] = int(raw_clean)
|
smart_data['power_on_hours'] = int(raw_clean)
|
||||||
print(f"[v0] Power On Hours: {smart_data['power_on_hours']}")
|
print(f"[v0] Power On Hours: {smart_data['power_on_hours']}")
|
||||||
|
elif attr_id == '12': # Power Cycle Count
|
||||||
|
raw_clean = raw_value.split()[0].replace(',', '')
|
||||||
|
smart_data['power_cycles'] = int(raw_clean)
|
||||||
|
print(f"[v0] Power Cycles: {smart_data['power_cycles']}")
|
||||||
elif attr_id == '194' and smart_data['temperature'] == 0: # Temperature
|
elif attr_id == '194' and smart_data['temperature'] == 0: # Temperature
|
||||||
temp_str = raw_value.split()[0]
|
temp_str = raw_value.split()[0]
|
||||||
smart_data['temperature'] = int(temp_str)
|
smart_data['temperature'] = int(temp_str)
|
||||||
|
Reference in New Issue
Block a user