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
- ))}
+ )}