Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ per the process in [`docs/releasing.md`](docs/releasing.md).

## [Unreleased]

## [1.5.1] - 2026-07-14

### Fixed

- **Worker Inspect edit path now activates for configured rigs (#508).** The dashboard reads a masked
copy of `config.json` (the container never holds real tokens, #440), where a rig's `token` is the
sentinel `{"__secret__": true}`. The worker-endpoint loader required a string token and dropped the
entry whole, so every worker showed as non-editable even with `dashboard.workers[]` set correctly.
The loader now accepts the masked sentinel as "token present"; the host-side runner still supplies
the real token when it dials the rig.

## [1.5.0] - 2026-07-13

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.5.0
1.5.1
13 changes: 11 additions & 2 deletions build/dashboard/mining_dashboard/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,18 @@ def load_worker_endpoints(path=None):
continue
entry["control_port"] = cport
if "token" in item:
if not isinstance(item["token"], str) or not _WORKER_NAME_RE.match(item["token"]):
tok = item["token"]
# The container only ever reads the MASKED config (#440), where a real token is replaced
# by the sentinel {"__secret__": true}. Keep the entry then (token present, value hidden)
# so the worker stays editable — the HOST-side runner supplies the real token when it
# dials the rig (#508). A genuinely bad token (bad string, or any other dict) still drops
# the whole entry, fail-closed.
if isinstance(tok, dict) and tok.get("__secret__") is True:
entry["token"] = tok
elif isinstance(tok, str) and _WORKER_NAME_RE.match(tok):
entry["token"] = tok
else:
continue
entry["token"] = item["token"]
if "watts" in item:
watts = _valid_watts(item["watts"])
if watts is None:
Expand Down
2 changes: 1 addition & 1 deletion build/dashboard/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "mining-dashboard"
# Keep in lockstep with the top-level VERSION file — the single source of truth for the stack version
# (#44). A shell test (tests/stack/run.sh) fails if these drift; the dashboard *displays* the version
# from VERSION (baked in as PITHEAD_VERSION, #58), so this is packaging metadata only.
version = "1.5.0"
version = "1.5.1"
description = "Monitoring dashboard and XvB switching engine for Pithead"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
31 changes: 31 additions & 0 deletions build/dashboard/tests/config/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,37 @@ def test_invalid_entries_are_dropped_whole(self, tmp_path):
)
assert got == [{"name": "ok", "port": 8081}]

def test_masked_sentinel_token_is_kept_but_other_dicts_drop(self, tmp_path):
# The container ONLY ever reads the masked config (#440), where a real token is the sentinel
# {"__secret__": true}. The entry must SURVIVE (token present, value hidden) so the worker is
# editable — the host-side runner uses the real token when it dials the rig (#508). Any other
# dict is not the sentinel and drops the whole entry, fail-closed.
got = self._load(
tmp_path,
{
"dashboard": {
"workers": [
{
"name": "rig1",
"host": "10.0.0.5",
"control_port": 8082,
"token": {"__secret__": True},
},
{"name": "rig2", "host": "10.0.0.6", "token": {"__secret__": False}},
{"name": "rig3", "host": "10.0.0.7", "token": {"foo": "bar"}},
]
}
},
)
assert got == [
{
"name": "rig1",
"host": "10.0.0.5",
"control_port": 8082,
"token": {"__secret__": True},
},
]

def test_missing_file_and_missing_key_read_empty(self, tmp_path):
from mining_dashboard.config.config import load_worker_endpoints

Expand Down
2 changes: 1 addition & 1 deletion build/dashboard/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.