Step 0 of the settings page: registry, safe restart, remembered state, test verb, page markers
- schema.py: every setting with type, default, help, owner screen and the rules a config must pass; tests/test_schema.py keeps it in step with the code (every key the code reads is declared, every default matches, check types and fix kinds agree, the glossary covers every word a key can show, today's config still builds today's pages apart from the key notes) - status_panel.py: VERSION + started.json; SIGTERM lets a running fix finish and starts nothing new (RESTARTING SOON); fixes-running.json and live-now.json for the settings page; state.json keeps the ALARMS override, acknowledgements, auto-fix tries and fix times across restarts, keyed by what a check is about rather than its position; 'test' and 'resurvey' commands; queued commands dropped at start; overnight windows; a HyperDeck switched off is always switched back on; the spotify fix kind (playlist set once, in config 'spotify'); Windows remote checks batched for cmd.exe with conditional echoes; every fix's kind and host validated; never_auto; wording and numbers from config (site block, timing, page_hold_ms, auto-fix keep_months); hyperdeck_probe removed; --check and --exit-timeout - make_page.py: numbers from config, markers on every key, nav templates per Companion version, --out; push_page.py: a page is overwritten only when every key carries a marker (foreign keys named by position), secret-free fetch, host from the site; nav_keys.py: host from the site; netgear.py: switches and the shared-port limit from config - install.sh: validates with the new code first, keeps prev/ for --rollback, --keep-config, derived ExitTimeOut, wider deploy list, waits for the start stamp - config.json: site, timing, spotify sections; Windows hosts marked; deployed to NA Ingest and both pages re-imported once (--force) so every key carries its marker Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+36
-23
@@ -5,9 +5,10 @@ Does exactly what Import/Export -> Import -> "import into page N" does, over the
|
||||
same internal API the Companion web UI uses (tRPC over a WebSocket at /trpc):
|
||||
upload the file in chunks, check its SHA-1, then import it as a single page.
|
||||
|
||||
Safety: it looks at what is on the target page, not just its name. It refuses unless the
|
||||
page holds nothing but page up / page number / page down keys, or is already the page this
|
||||
file builds (same name). A page named "PAGE" can still hold real buttons (pages 22 and 97 do).
|
||||
Safety: it looks at what is on the target page, not just its name. It refuses unless every
|
||||
key on the page (besides Companion's own page up / number / down keys) carries one of the
|
||||
markers make_page.py writes into its notes, i.e. the page holds nothing a person made by hand.
|
||||
A key someone added is named by its position, so it can be moved first.
|
||||
|
||||
python3 push_page.py # the status page, from config.json
|
||||
python3 push_page.py --which restart # the Restart page
|
||||
@@ -29,6 +30,9 @@ import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
from make_page import MARKERS # noqa: E402
|
||||
|
||||
CHUNK = 256 * 1024
|
||||
|
||||
|
||||
@@ -124,8 +128,10 @@ NAV_MARKER = "STATUS nav key" # our coloured page number key (make_page
|
||||
|
||||
|
||||
def fetch_page(host, port, page):
|
||||
"""The page as Companion exports it ({"page": {"name", "controls": {row: {col: control}}}})."""
|
||||
with urllib.request.urlopen("http://%s:%d/int/export/page/%d" % (host, port, page), timeout=10) as r:
|
||||
"""The page as Companion exports it ({"page": {"name", "controls": {row: {col: control}}}}),
|
||||
without any connection secrets."""
|
||||
url = "http://%s:%d/int/export/page/%d?includeSecrets=false" % (host, port, page)
|
||||
with urllib.request.urlopen(url, timeout=10) as r:
|
||||
raw = r.read()
|
||||
try:
|
||||
raw = gzip.decompress(raw)
|
||||
@@ -134,29 +140,36 @@ def fetch_page(host, port, page):
|
||||
return json.loads(raw)
|
||||
|
||||
|
||||
def real_buttons(export):
|
||||
"""How many buttons a page holds besides its page up / number / down keys."""
|
||||
def foreign_buttons(export):
|
||||
"""[(row, col)] of every key on the page that neither Companion nor this tool made."""
|
||||
page = export.get("page") or {}
|
||||
n = 0
|
||||
for row in (page.get("controls") or {}).values():
|
||||
for ctl in (row or {}).values():
|
||||
found = []
|
||||
for r, row in sorted((page.get("controls") or {}).items(), key=lambda kv: int(kv[0])):
|
||||
for c, ctl in sorted((row or {}).items(), key=lambda kv: int(kv[0])):
|
||||
if not ctl or ctl.get("type") in NAV_TYPES:
|
||||
continue
|
||||
if NAV_MARKER in ((ctl.get("options") or {}).get("notes") or ""):
|
||||
notes = (ctl.get("options") or {}).get("notes") or ""
|
||||
if any(m in notes for m in MARKERS):
|
||||
continue
|
||||
n += 1
|
||||
return n
|
||||
found.append((int(r), int(c)))
|
||||
return found
|
||||
|
||||
|
||||
def safe_target(export, expected_name):
|
||||
"""(ok, reason). OK only for a page with nothing but nav keys, or the page this file builds."""
|
||||
def real_buttons(export):
|
||||
"""How many keys a page holds that this tool did not make."""
|
||||
return len(foreign_buttons(export))
|
||||
|
||||
|
||||
def safe_target(export, expected_name=None):
|
||||
"""(ok, reason). OK only when every key on the page is Companion's own page key or one of ours,
|
||||
whatever the page is called: nothing a person made by hand is ever overwritten."""
|
||||
name = ((export.get("page") or {}).get("name") or "")
|
||||
if name == expected_name:
|
||||
return True, "it is already %r" % name
|
||||
n = real_buttons(export)
|
||||
if n == 0:
|
||||
return True, "it holds only page keys"
|
||||
return False, "it is called %r and holds %d buttons" % (name, n)
|
||||
foreign = foreign_buttons(export)
|
||||
if not foreign:
|
||||
return True, "it holds only page keys and keys made by this tool"
|
||||
where = ", ".join("row %d col %d" % rc for rc in foreign[:6]) + (" ..." if len(foreign) > 6 else "")
|
||||
return False, "it is called %r and holds %d key%s not made by this tool (%s) - move or delete them first" % (
|
||||
name, len(foreign), "" if len(foreign) == 1 else "s", where)
|
||||
|
||||
|
||||
def page_name(host, port, page):
|
||||
@@ -169,8 +182,8 @@ def main():
|
||||
cfg = json.load(open(os.path.join(HERE, "config.json")))
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument("--which", choices=("status", "restart"), default="status")
|
||||
ap.add_argument("--host", default="na-ingest.local")
|
||||
ap.add_argument("--port", type=int, default=8000)
|
||||
ap.add_argument("--host", default=cfg.get("site", {}).get("ingest_hostname") or cfg["companion"].get("host", "127.0.0.1"))
|
||||
ap.add_argument("--port", type=int, default=cfg["companion"].get("port", 8000))
|
||||
ap.add_argument("--page", type=int)
|
||||
ap.add_argument("--file")
|
||||
ap.add_argument("--force", action="store_true", help="overwrite the page whatever is on it")
|
||||
|
||||
Reference in New Issue
Block a user