diff --git a/backend/src/media_library_viewer_api/routers/dashboard.py b/backend/src/media_library_viewer_api/routers/dashboard.py index 0e73c6f..256c462 100644 --- a/backend/src/media_library_viewer_api/routers/dashboard.py +++ b/backend/src/media_library_viewer_api/routers/dashboard.py @@ -44,7 +44,32 @@ def get_monitoring_overview( ) -> dict[str, Any]: """Return one lightweight monitoring row per configured machine.""" machines = store.list_machines() - rows = [collect_machine_overview(machine, store) for machine in machines] + rows: list[dict[str, Any]] = [] + for machine in machines: + try: + rows.append(collect_machine_overview(machine, store)) + except Exception as exc: + logger.exception( + "Dashboard monitoring overview failed for machine_id=%s", + machine.get("id"), + ) + rows.append({ + "machine": {k: machine.get(k) for k in ("id", "name", "mode", "enabled", "host", "port", "username", "media_root", "path_prefix", "notes")}, + "status": "", + "status_error": str(exc), + "metrics_error": str(exc), + "disk_error": str(exc), + "sample_count": 0, + "latest_sample": None, + "cpu_summary": None, + "iowait_summary": None, + "mem_summary": None, + "net_rx_summary": None, + "net_tx_summary": None, + "disk_read_summary": None, + "disk_write_summary": None, + "disk": None, + }) poller = get_monitoring_poller().snapshot() enabled_count = sum(1 for machine in machines if machine.get("enabled")) logger.info("Dashboard monitoring machines=%s enabled=%s", len(machines), enabled_count) diff --git a/frontend/src/components/MonitoringOverviewTable.tsx b/frontend/src/components/MonitoringOverviewTable.tsx index 4d9f575..ff0350b 100644 --- a/frontend/src/components/MonitoringOverviewTable.tsx +++ b/frontend/src/components/MonitoringOverviewTable.tsx @@ -11,6 +11,7 @@ import { TableHead, TableRow, TableSortLabel, + Tooltip, Typography, } from "@mui/material"; import type { @@ -102,6 +103,19 @@ function sortableText(value: string) { return value.toLowerCase(); } +function getStatusChipProps(row: MonitoringMachineOverview) { + if (row.status_error) { + return { color: "error" as const, label: row.status_error }; + } + if (row.status === "running") { + return { color: "success" as const, label: "running" }; + } + if (row.status === "not running") { + return { color: "warning" as const, label: "not running" }; + } + return { color: "default" as const, label: row.status || "-" }; +} + function metricSortValue( row: MonitoringMachineOverview, key: SortKey, @@ -136,6 +150,34 @@ function metricSortValue( } } +function NoDataCell() { + return ( + + + No data + + + ); +} + function MetricCell({ value, min, @@ -441,7 +483,7 @@ export function MonitoringOverviewTable({ ) : ( sortedRows.map((row) => { const machine = row.machine; - const status = row.status || row.status_error || "-"; + const hasMetrics = row.sample_count > 0; const cpu = formatSummary( row.cpu_summary, (value) => `${value.toFixed(1)}%`, @@ -524,62 +566,106 @@ export function MonitoringOverviewTable({ {machine.mode} - + + {(() => { + const chip = getStatusChipProps(row); + return ( + + + ); + })()} + + + {hasMetrics ? ( + + ) : ( + + )} - + {hasMetrics ? ( + + ) : ( + + )} - + {hasMetrics ? ( + + ) : ( + + )} - + {hasMetrics ? ( + + ) : ( + + )} - + {hasMetrics ? ( + + ) : ( + + )} - + {hasMetrics ? ( + + ) : ( + + )} - - - - + {hasMetrics ? ( + + ) : ( + + )} {disk ? ( @@ -588,22 +674,34 @@ export function MonitoringOverviewTable({ min={disk.min} max={disk.max} /> - ) : ( + ) : hasMetrics ? ( "-" + ) : ( + )} - {updated.map((line) => ( + {hasMetrics ? ( + updated.map((line) => ( + + {line} + + )) + ) : ( - {line} + No data yet - ))} + )}