diff --git a/CHANGELOG.md b/CHANGELOG.md index 747c813..c082320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,9 @@ per the process in [`docs/releasing.md`](docs/releasing.md). - `pithead doctor` now checks that Docker is enabled to start at boot (systemd) and warns if not — `restart: unless-stopped` only brings the stack back after a reboot when the daemon does too, which matters for an unattended miner (#137). +- Low-disk warning badge in the dashboard header (#138): a heads-up at 85% used of the data + filesystem and a prominent critical alert at 95%, on both the sync and main screens — the disk + bar alone is easy to miss, and a full data disk corrupts the Monero database mid-write. ### Changed diff --git a/build/dashboard/mining_dashboard/config/config.py b/build/dashboard/mining_dashboard/config/config.py index d4f8093..7aa4c3a 100644 --- a/build/dashboard/mining_dashboard/config/config.py +++ b/build/dashboard/mining_dashboard/config/config.py @@ -9,6 +9,13 @@ DISK_PATH = '/data' DB_FILE_PATH = os.path.join(DISK_PATH, "mining_data.db") +# Low-disk warning thresholds (% used of the data filesystem), surfaced as a top-bar badge (#138): +# a heads-up at WARN, a prominent critical alert near full. The chains keep growing and a full data +# disk corrupts monerod's DB mid-write, so this is a runtime guard distinct from the setup-time +# pre-flight estimate. (The disk *bar* uses its own colour thresholds; these gate the badge.) +DISK_WARN_PERCENT = 85 +DISK_CRITICAL_PERCENT = 95 + # --- Data Source File Paths --- # File paths for JSON metrics generated by local collectors STRATUM_STATS_PATH = f"{BASE_STATS_DIR}/local/stratum" diff --git a/build/dashboard/mining_dashboard/web/views.py b/build/dashboard/mining_dashboard/web/views.py index 219a4d4..871c323 100644 --- a/build/dashboard/mining_dashboard/web/views.py +++ b/build/dashboard/mining_dashboard/web/views.py @@ -15,7 +15,7 @@ import bisect import logging -from mining_dashboard.config.config import HOST_IP, UPDATE_INTERVAL +from mining_dashboard.config.config import HOST_IP, UPDATE_INTERVAL, DISK_WARN_PERCENT, DISK_CRITICAL_PERCENT from mining_dashboard.helper.utils import ( format_hashrate, format_duration, format_time_abs, is_ip_address, detect_host_ipv4, ) @@ -567,6 +567,17 @@ def build_badges(data, metrics, mode_variant): elif metrics.monero_mode == "Full": badges.append({"text": "XMR Full", "variant": "outline", "title": "Monero blockchain is full (not pruned)"}) + # Low-disk badge (Issue #138). The data filesystem fills as the chains grow and logs accumulate; + # a full disk corrupts monerod's DB mid-write. The disk *bar* shows the percentage, but it's easy + # to miss — surface a prominent top-bar badge near full, on both the sync and main screens. + disk_percent = (data.get('system', {}).get('disk', {}) or {}).get('percent', 0) or 0 + if disk_percent >= DISK_CRITICAL_PERCENT: + badges.append({"text": f"⚠ Disk {disk_percent:.0f}% full", "variant": "bad", + "title": "The data disk is almost full — free space now; a full disk can corrupt the Monero database."}) + elif disk_percent >= DISK_WARN_PERCENT: + badges.append({"text": f"Disk {disk_percent:.0f}% full", "variant": "warn", + "title": "The data disk is filling up — free space or move a data_dir before it runs out."}) + return badges diff --git a/build/dashboard/tests/web/test_views.py b/build/dashboard/tests/web/test_views.py index edb1ccc..28815de 100644 --- a/build/dashboard/tests/web/test_views.py +++ b/build/dashboard/tests/web/test_views.py @@ -371,6 +371,23 @@ def test_no_prune_badge_when_unknown(self): out = build_badges({}, _metrics(monero_mode="Unknown"), "ok") assert not any("XMR" in b["text"] for b in out) + def test_disk_badge_critical(self): + out = build_badges({"system": {"disk": {"percent": 96}}}, _metrics(), "ok") + assert any(b["variant"] == "bad" and "Disk 96% full" in b["text"] for b in out) + + def test_disk_badge_warn(self): + out = build_badges({"system": {"disk": {"percent": 88}}}, _metrics(), "ok") + assert any(b["variant"] == "warn" and "Disk 88% full" in b["text"] for b in out) + + def test_no_disk_badge_when_ample(self): + out = build_badges({"system": {"disk": {"percent": 50}}}, _metrics(), "ok") + assert not any("Disk" in b["text"] for b in out) + + def test_no_disk_badge_when_missing(self): + # No system/disk data (e.g. an early poll) must not emit a spurious or crashing badge. + out = build_badges({}, _metrics(), "ok") + assert not any("Disk" in b["text"] for b in out) + # --- System (presentation thresholds) -------------------------------------------------