diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx index fd107a2f..d934ca6f 100644 --- a/AppImage/components/storage-overview.tsx +++ b/AppImage/components/storage-overview.tsx @@ -1470,6 +1470,353 @@ export function StorageOverview() { ) } +// Generate SMART Report HTML and open in new window (same pattern as Lynis/Latency reports) +function openSmartReport(disk: DiskInfo, testStatus: SmartTestStatus, smartAttributes: Array<{id: number; name: string; value: number; worst: number; threshold: number; raw_value: string; status: 'ok' | 'warning' | 'critical'}>) { + const now = new Date().toLocaleString() + const logoUrl = `${window.location.origin}/images/proxmenux-logo.png` + const reportId = `SMART-${Date.now().toString(36).toUpperCase()}` + + // Determine disk type + let diskType = "HDD" + if (disk.name.startsWith("nvme")) { + diskType = "NVMe" + } else if (!disk.rotation_rate || disk.rotation_rate === 0) { + diskType = "SSD" + } + + // Health status styling + const healthStatus = testStatus.smart_status || (testStatus.smart_data?.smart_status) || 'unknown' + const isHealthy = healthStatus.toLowerCase() === 'passed' + const healthColor = isHealthy ? '#16a34a' : healthStatus.toLowerCase() === 'failed' ? '#dc2626' : '#ca8a04' + const healthLabel = isHealthy ? 'PASSED' : healthStatus.toUpperCase() + + // Format power on time + const powerOnHours = disk.power_on_hours || testStatus.smart_data?.power_on_hours || 0 + const powerOnDays = Math.round(powerOnHours / 24) + const powerOnYears = Math.floor(powerOnHours / 8760) + const powerOnRemainingDays = Math.floor((powerOnHours % 8760) / 24) + const powerOnFormatted = powerOnYears > 0 + ? `${powerOnYears}y ${powerOnRemainingDays}d (${powerOnHours.toLocaleString()}h)` + : `${powerOnDays}d (${powerOnHours.toLocaleString()}h)` + + // Build attributes table + const attributeRows = smartAttributes.map((attr, i) => { + const statusColor = attr.status === 'ok' ? '#16a34a' : attr.status === 'warning' ? '#ca8a04' : '#dc2626' + const statusBg = attr.status === 'ok' ? '#16a34a15' : attr.status === 'warning' ? '#ca8a0415' : '#dc262615' + return ` + + ${attr.id} + ${attr.name} + ${attr.value} + ${attr.worst} + ${attr.threshold} + ${attr.raw_value} + ${attr.status.toUpperCase()} + + ` + }).join('') + + // Critical attributes to highlight + const criticalAttrs = smartAttributes.filter(a => a.status !== 'ok') + const hasCritical = criticalAttrs.length > 0 + + // Build recommendations + const recommendations: string[] = [] + if (isHealthy) { + recommendations.push('
Disk is Healthy

All SMART attributes are within normal ranges. Continue regular monitoring.

') + } else { + recommendations.push('
Critical: Disk Health Issue Detected

SMART has reported a health issue. Backup all data immediately and plan for disk replacement.

') + } + + if ((disk.reallocated_sectors ?? 0) > 0) { + recommendations.push(`
Reallocated Sectors Detected (${disk.reallocated_sectors})

The disk has bad sectors that have been remapped. Monitor closely and consider replacement if count increases.

`) + } + + if ((disk.pending_sectors ?? 0) > 0) { + recommendations.push(`
Pending Sectors (${disk.pending_sectors})

There are sectors waiting to be reallocated. This may indicate impending failure.

`) + } + + if (disk.temperature > 55 && diskType === 'HDD') { + recommendations.push(`
High Temperature (${disk.temperature}°C)

HDD is running hot. Improve case airflow or add cooling.

`) + } else if (disk.temperature > 70 && diskType === 'SSD') { + recommendations.push(`
High Temperature (${disk.temperature}°C)

SSD is running hot. Check airflow around the drive.

`) + } else if (disk.temperature > 80 && diskType === 'NVMe') { + recommendations.push(`
High Temperature (${disk.temperature}°C)

NVMe is overheating. Consider adding a heatsink or improving case airflow.

`) + } + + if (recommendations.length === 1 && isHealthy) { + recommendations.push('
Regular Maintenance

Schedule periodic extended SMART tests (monthly) to catch issues early.

') + recommendations.push('
Backup Strategy

Ensure critical data is backed up regularly regardless of disk health status.

') + } + + const html = ` + + + + +SMART Health Report - /dev/${disk.name} + + + + +
+
+
SMART Health Report
+
/dev/${disk.name}
+
+ +
+ + +
+
+ ProxMenux +
+

SMART Health Report

+

ProxMenux Monitor - Disk Health Analysis

+
+
+
+
Date: ${now}
+
Device: /dev/${disk.name}
+
ID: ${reportId}
+
+
+ + +
+
1. Executive Summary
+
+
+
${isHealthy ? '✓' : '✗'}
+
${healthLabel}
+
+
+

Disk Health Assessment

+

+ ${isHealthy + ? `This disk is operating within normal parameters. All SMART attributes are within acceptable thresholds. The disk has been powered on for approximately ${powerOnFormatted} and is currently operating at ${disk.temperature > 0 ? disk.temperature + '°C' : 'N/A'}. ${(disk.reallocated_sectors ?? 0) === 0 ? 'No bad sectors have been detected.' : `${disk.reallocated_sectors} reallocated sector(s) detected - monitor closely.`}` + : `This disk has reported a SMART health failure. Immediate action is required. Backup all critical data and plan for disk replacement.` + } +

+
+
+
+ + +
+
2. Disk Information
+
+
+
Model
+
${disk.model || testStatus.smart_data?.model || 'Unknown'}
+
+
+
Serial
+
${disk.serial || testStatus.smart_data?.serial || 'Unknown'}
+
+
+
Capacity
+
${disk.size_formatted || 'Unknown'}
+
+
+
Type
+
${diskType}
+
+
+
+
+
${disk.temperature > 0 ? disk.temperature + '°C' : 'N/A'}
+
Temperature
+
+
+
${powerOnHours.toLocaleString()}h
+
Power On Time
+
+
+
${(disk.power_cycles ?? 0).toLocaleString()}
+
Power Cycles
+
+
+
${disk.reallocated_sectors ?? 0}
+
Reallocated Sectors
+
+
+
+ + +
+
3. SMART Attributes (${smartAttributes.length} total${hasCritical ? `, ${criticalAttrs.length} warning(s)` : ''})
+ + + + + + + + + + + + + + ${attributeRows || ''} + +
IDAttributeValueWorstThreshRaw ValueStatus
No SMART attributes available
+
+ + +
+
4. Last Self-Test Result
+ ${testStatus.last_test ? ` +
+
+
Test Type
+
${testStatus.last_test.type}
+
+
+
Result
+
${testStatus.last_test.status}
+
+
+
Completed
+
${testStatus.last_test.timestamp || 'N/A'}
+
+
+
Duration
+
${testStatus.last_test.duration || 'N/A'}
+
+
+ ` : ` +
+ No self-test history available. Run a SMART self-test to see results here. +
+ `} +
+ + +
+
5. Recommendations
+ ${recommendations.join('')} +
+ + + + + +` + + const blob = new Blob([html], { type: "text/html" }) + const url = URL.createObjectURL(blob) + window.open(url, "_blank") +} + // SMART Test Tab Component interface SmartTestTabProps { disk: DiskInfo @@ -1510,8 +1857,9 @@ function SmartTestTab({ disk }: SmartTestTabProps) { const [testStatus, setTestStatus] = useState({ status: 'idle' }) const [loading, setLoading] = useState(true) const [runningTest, setRunningTest] = useState<'short' | 'long' | null>(null) - const [showReport, setShowReport] = useState(false) - const [reportTab, setReportTab] = useState<'overview' | 'attributes' | 'history' | 'recommendations'>('overview') + + // Extract SMART attributes from testStatus for the report + const smartAttributes = testStatus.smart_data?.attributes || [] // Fetch current SMART status on mount useEffect(() => { @@ -1580,7 +1928,7 @@ function SmartTestTab({ disk }: SmartTestTabProps) { size="sm" onClick={() => runSmartTest('short')} disabled={runningTest !== null} - className="gap-2" + className="gap-2 bg-blue-500/10 border-blue-500/30 text-blue-500 hover:bg-blue-500/20 hover:text-blue-400" > {runningTest === 'short' ? ( @@ -1594,7 +1942,7 @@ function SmartTestTab({ disk }: SmartTestTabProps) { size="sm" onClick={() => runSmartTest('long')} disabled={runningTest !== null} - className="gap-2" + className="gap-2 bg-blue-500/10 border-blue-500/30 text-blue-500 hover:bg-blue-500/20 hover:text-blue-400" > {runningTest === 'long' ? ( @@ -1608,7 +1956,7 @@ function SmartTestTab({ disk }: SmartTestTabProps) { size="sm" onClick={fetchSmartStatus} disabled={runningTest !== null} - className="gap-2" + className="gap-2 bg-blue-500/10 border-blue-500/30 text-blue-500 hover:bg-blue-500/20 hover:text-blue-400" > Refresh Status @@ -1729,8 +2077,8 @@ function SmartTestTab({ disk }: SmartTestTabProps) {
- {/* Full SMART Report Dialog */} - - - - - - SMART Health Report: /dev/{disk.name} - - - Comprehensive analysis of disk health, SMART attributes, and recommendations - - - - {/* Report Tabs */} -
- - - - -
- - {/* Report Content */} -
- {/* Overview Tab */} - {reportTab === 'overview' && ( -
- {/* Health Score Card */} -
-
-

Overall Health

-
- {testStatus.smart_status === 'passed' ? ( - - ) : testStatus.smart_status === 'failed' ? ( - - ) : ( - - )} - - {testStatus.smart_status || 'Unknown'} - -
-
- -
-

Temperature

-
- - - {disk.temperature > 0 ? `${disk.temperature}°C` : 'N/A'} - -
-
- -
-

Power On Time

-
- - - {disk.power_on_hours ? `${disk.power_on_hours.toLocaleString()}h` : 'N/A'} - -
-
-
- - {/* Executive Summary */} -
-

- - Executive Summary -

-
-

- {testStatus.smart_status === 'passed' ? ( - <> - This disk is operating within normal parameters. All SMART attributes are within acceptable thresholds, - indicating good health. The disk has been powered on for approximately{' '} - - {disk.power_on_hours ? `${Math.round(disk.power_on_hours / 24)} days` : 'an unknown period'} - {' '} - and is currently operating at{' '} - {disk.temperature || 'N/A'}°C. - {disk.reallocated_sectors === 0 && disk.pending_sectors === 0 - ? ' No bad sectors have been detected.' - : disk.reallocated_sectors && disk.reallocated_sectors > 0 - ? ` ${disk.reallocated_sectors} sectors have been reallocated, which may indicate early signs of wear.` - : ''} - - ) : testStatus.smart_status === 'failed' ? ( - <> - Warning: This disk has failed SMART health assessment.{' '} - One or more critical SMART attributes have exceeded their failure threshold. - It is strongly recommended to backup all data immediately and consider replacing this disk. - {disk.reallocated_sectors && disk.reallocated_sectors > 0 - ? ` The disk has ${disk.reallocated_sectors} reallocated sectors, indicating physical media degradation.` - : ''} - - ) : ( - <> - The disk health status could not be fully determined. Some SMART attributes may be showing warning signs. - It is recommended to run a full SMART self-test and monitor the disk closely. - - )} -

-
-
- - {/* Key Metrics */} -
-

Key Metrics

-
-
-

Model

-

{disk.model || 'Unknown'}

-
-
-

Serial

-

{disk.serial?.replace(/\\x[0-9a-fA-F]{2}/g, '') || 'Unknown'}

-
-
-

Capacity

-

{disk.size_formatted || 'Unknown'}

-
-
-

Power Cycles

-

{disk.power_cycles?.toLocaleString() || 'N/A'}

-
-
-

Reallocated Sectors

-

0 ? 'text-yellow-500' : ''}`}> - {disk.reallocated_sectors ?? 0} -

-
-
-

Pending Sectors

-

0 ? 'text-yellow-500' : ''}`}> - {disk.pending_sectors ?? 0} -

-
-
-

CRC Errors

-

0 ? 'text-yellow-500' : ''}`}> - {disk.crc_errors ?? 0} -

-
-
-

Disk Type

-

- {disk.name.startsWith('nvme') ? 'NVMe' : !disk.rotation_rate || disk.rotation_rate === 0 ? 'SSD' : 'HDD'} -

-
-
-
-
- )} - - {/* Attributes Tab */} - {reportTab === 'attributes' && ( -
-
-

Understanding SMART Attributes

-

- SMART (Self-Monitoring, Analysis and Reporting Technology) attributes are sensors built into hard drives and SSDs. - Each attribute has a current value, a worst recorded value, and a threshold. When the current value drops below the threshold, - the attribute is considered failed. Values typically decrease from 100 (or 200/253 on some drives) as the attribute degrades. -

-
- - {testStatus.smart_data?.attributes && testStatus.smart_data.attributes.length > 0 ? ( -
-
-
ID
-
Attribute Name
-
Value
-
Worst
-
Threshold
-
Status
-
-
- {testStatus.smart_data.attributes.map((attr) => ( -
-
{attr.id}
-
-

{attr.name.replace(/_/g, ' ')}

-

Raw: {attr.raw_value}

-
-
{attr.value}
-
{attr.worst}
-
{attr.threshold}
-
- {attr.status === 'ok' ? ( - - ) : attr.status === 'warning' ? ( - - ) : ( - - )} -
-
- ))} -
-
- ) : ( -
- -

No SMART attribute data available.

-

Run a SMART test to collect attribute data.

-
- )} -
- )} - - {/* History Tab */} - {reportTab === 'history' && ( -
- {testStatus.last_test ? ( -
-
-

- {testStatus.last_test.status === 'passed' ? ( - - ) : ( - - )} - Last Test Result -

- - {testStatus.last_test.status} - -
-
-
-

Test Type

-

{testStatus.last_test.type}

-
-
-

Completed

-

{testStatus.last_test.timestamp}

-
-
-
- ) : ( -
- -

No test history available.

-

Run a SMART self-test to see results here.

-
- )} - -
-

About Self-Tests

-
-

- Short Test (~2 minutes): Performs a quick check of the disk's - basic functionality including read/seek tests on a small portion of the disk surface. -

-

- Extended Test (hours): Performs a comprehensive surface scan - of the entire disk. Duration depends on disk size - typically 1-2 hours per TB. -

-
-
-
- )} - - {/* Recommendations Tab */} - {reportTab === 'recommendations' && ( -
- {/* Status-based recommendations */} - {testStatus.smart_status === 'passed' && ( -
-
- -
-

Disk is Healthy

-

- All SMART attributes are within normal ranges. Continue with regular monitoring. -

-
-
-
- )} - - {testStatus.smart_status === 'failed' && ( -
-
- -
-

Critical: Disk Replacement Recommended

-

- This disk has failed SMART health assessment. Backup all data immediately and plan for disk replacement. -

-
-
-
- )} - - {/* Conditional recommendations */} -
-

Recommendations

- - {(disk.reallocated_sectors ?? 0) > 0 && ( -
-
- -
-

Reallocated Sectors Detected

-

- {disk.reallocated_sectors} sectors have been reallocated. This indicates the disk has found and - remapped bad sectors. Monitor this value - if it increases rapidly, consider replacing the disk. -

-
-
-
- )} - - {(disk.pending_sectors ?? 0) > 0 && ( -
-
- -
-

Pending Sectors Detected

-

- {disk.pending_sectors} sectors are pending reallocation. These sectors may be unreadable. - Run an extended self-test to force reallocation attempts. -

-
-
-
- )} - - {disk.temperature > 55 && ( -
-
- -
-

Elevated Temperature

-

- Current temperature ({disk.temperature}°C) is above optimal. Improve airflow or reduce disk activity. - Sustained high temperatures can reduce disk lifespan. -

-
-
-
- )} - - {(disk.power_on_hours ?? 0) > 35000 && ( -
-
- -
-

High Power-On Hours

-

- This disk has been running for {Math.round((disk.power_on_hours ?? 0) / 8760)} years. - While still operational, consider planning for replacement as disks typically have a 3-5 year lifespan. -

-
-
-
- )} - - {/* General best practices */} -
-

Best Practices

-
    -
  • - - Run a short SMART test monthly to catch early issues -
  • -
  • - - Run an extended test quarterly for comprehensive verification -
  • -
  • - - Maintain regular backups - SMART can detect some failures but not all -
  • -
  • - - Keep disk temperatures below 50°C for optimal lifespan -
  • -
  • - - Replace disks proactively after 4-5 years of heavy use -
  • -
-
-
-
- )} -
- - {/* Report Footer */} -
-

- Report generated by ProxMenux Monitor -

- -
-
-
+ ) }