diff --git a/AppImage/components/storage-overview.tsx b/AppImage/components/storage-overview.tsx
index 03a0c6bc..7007f519 100644
--- a/AppImage/components/storage-overview.tsx
+++ b/AppImage/components/storage-overview.tsx
@@ -1818,7 +1818,7 @@ 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: SmartAttribute[], observations: DiskObservation[] = [], lastTestDate?: string, targetWindow?: Window) {
+function openSmartReport(disk: DiskInfo, testStatus: SmartTestStatus, smartAttributes: SmartAttribute[], observations: DiskObservation[] = [], lastTestDate?: string, targetWindow?: Window, isHistorical = false) {
const now = new Date().toLocaleString()
const logoUrl = `${window.location.origin}/images/proxmenux-logo.png`
const reportId = `SMART-${Date.now().toString(36).toUpperCase()}`
@@ -2417,7 +2417,7 @@ function openSmartReport(disk: DiskInfo, testStatus: SmartTestStatus, smartAttri
${now}
-
Last Test Type
+
${isHistorical ? 'Test Type' : 'Last Test Type'}
${testStatus.last_test?.type || 'N/A'}
@@ -2740,7 +2740,7 @@ ${!isNvmeDisk && diskType === 'SSD' ? (() => {
-
${isNvmeDisk ? '5' : '4'}. Last Self-Test Result
+
${isNvmeDisk ? '5' : '4'}. ${isHistorical ? 'Self-Test Result' : 'Last Self-Test Result'}
${testStatus.last_test ? `
@@ -2789,6 +2789,28 @@ ${!isNvmeDisk && diskType === 'SSD' ? (() => {
` : ''}
+ ` : lastTestDate ? `
+
+
+
${isHistorical ? 'Test Type' : 'Last Test Type'}
+
${testStatus.test_type || 'Extended'}
+
+
+
+
Date
+
${new Date(lastTestDate).toLocaleString()}
+
+
+
At Power-On Hours
+
${fmtNum(powerOnHours)}h
+
+
+
+ Note: This disk's firmware does not maintain an internal self-test log. Test results are tracked by ProxMenux Monitor.
+
` : `
No self-test history available. Run a SMART self-test to see results here.
@@ -3353,51 +3375,11 @@ function HistoryTab({ disk }: { disk: DiskInfo }) {
try {
setViewingReport(entry.filename)
- const jsonData = await fetchApi
>(`/api/storage/smart/${disk.name}/history/${entry.filename}`)
+ // Fetch full SMART status from backend (same data as SMART tab uses)
+ const fullStatus = await fetchApi(`/api/storage/smart/${disk.name}`)
+ const attrs = fullStatus.smart_data?.attributes || []
- const isNvme = disk.name.includes('nvme')
- let attrs: SmartAttribute[] = []
-
- if (isNvme) {
- const fieldMap: [string, string][] = [
- ['critical_warning', 'Critical Warning'], ['temperature', 'Temperature'],
- ['avail_spare', 'Available Spare'], ['percent_used', 'Percentage Used'],
- ['data_units_written', 'Data Units Written'], ['data_units_read', 'Data Units Read'],
- ['power_cycles', 'Power Cycles'], ['power_on_hours', 'Power On Hours'],
- ['unsafe_shutdowns', 'Unsafe Shutdowns'], ['media_errors', 'Media Errors'],
- ['num_err_log_entries', 'Error Log Entries'],
- ]
- fieldMap.forEach(([key, name], i) => {
- if (jsonData[key] !== undefined) {
- const v = jsonData[key] as number
- const status = (key === 'critical_warning' || key === 'media_errors') && v > 0 ? 'critical' as const : 'ok' as const
- attrs.push({ id: i + 1, name, value: String(v), worst: '-', threshold: '-', raw_value: String(v), status })
- }
- })
- } else {
- const ataTable = (jsonData as Record)?.ata_smart_attributes as { table?: Array> }
- if (ataTable?.table) {
- attrs = ataTable.table.map(a => ({
- id: (a.id as number) || 0,
- name: (a.name as string) || '',
- value: (a.value as number) || 0,
- worst: (a.worst as number) || 0,
- threshold: (a.thresh as number) || 0,
- raw_value: (a.raw as Record)?.string as string || String((a.raw as Record)?.value || 0),
- status: 'ok' as const
- }))
- }
- }
-
- const testStatus: SmartTestStatus = {
- status: 'idle',
- smart_data: { device: disk.name, model: disk.model || '', serial: disk.serial || '', firmware: '', smart_status: 'passed', temperature: disk.temperature, power_on_hours: disk.power_on_hours || 0, attributes: attrs }
- }
-
- // Generate report — write directly into the already-open window (avoids popup blocker)
- // openSmartReport creates a blob and calls window.open, but we already have a window.
- // So we call the same logic but write to our existing window.
- openSmartReport(disk, testStatus, attrs, [], entry.timestamp, reportWindow || undefined)
+ openSmartReport(disk, fullStatus, attrs, [], entry.timestamp, reportWindow || undefined, true)
} catch {
if (reportWindow && !reportWindow.closed) {
reportWindow.document.body.innerHTML = 'Failed to load report data.
'
@@ -3845,7 +3827,7 @@ function ScheduleTab({ disk }: { disk: DiskInfo }) {
diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py
index ad4da480..aa411788 100644
--- a/AppImage/scripts/flask_server.py
+++ b/AppImage/scripts/flask_server.py
@@ -7156,7 +7156,10 @@ def api_smart_run_test(disk_name):
[ -z "$op" ] || [ "$op" -eq 0 ] && break
sleep {sleep_interval}
done
- nvme smart-log -o json {device} > {json_path} 2>/dev/null
+ # Save complete data: smartctl gives device info + health + self-test log in one JSON
+ smartctl -a --json=c {device} > {json_path} 2>/dev/null
+ # Fallback to nvme smart-log if smartctl fails
+ [ ! -s {json_path} ] && nvme smart-log -o json {device} > {json_path} 2>/dev/null
''',
shell=True, start_new_session=True,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL