Step 2: the settings page can change things - shared draft, Review & Apply, rollback, history, undo

- settings/apply.py: the Apply pipeline (check, keep history, write, restart the watchdog, re-import
  pages by the marker-only rule, recolour page keys), rollback when the watchdog does not come back,
  the recording guard with the Emergency (LIVE) override, history entries and Go back / Undo.
- settings/server.py: the write routes behind the editing code (draft PUT/DELETE with 409 on a
  conflict, validate, apply, restore, undo, watchdog restart/stop/start, commands to the watchdog,
  the switcher file upload / pick newest / discard, gear notes).
- settings/ui/app.js: every screen grows inputs with Editing on; keys (checks, fixes, hold-always,
  members), computers, fixes, site setup (schedules, Dante devices, Spotify, ATEM), Review & Apply
  with live progress, History with Go back and Undo, Run this fix now.
- schema.unmask, push_page.push, nav_keys.colour; tests for all of it (163 pass).
- Verified live on NA Ingest: a page-only Apply, a restart Apply, Undo, history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 14:06:42 -04:00
co-authored by Claude Fable 5.1
parent ae3e85f152
commit 02d0c77cd9
10 changed files with 1686 additions and 145 deletions
+31
View File
@@ -75,6 +75,37 @@ def find_nav_keys(host, port, status_page):
return found
def colour(host, port, status_page, coloured, record_path, revert=False):
"""Colour (or restore) the nav keys on every page; returns how many keys changed."""
record = json.load(open(record_path)) if os.path.exists(record_path) else {}
found = find_nav_keys(host, port, status_page)
if revert:
work = [(p, r, c, k, "restore") for p, r, c, k, ours in found if ours]
else:
work = [(p, r, c, k, "colour") for p, r, c, k, ours in found if k in coloured]
work += [(p, r, c, k, "restore") for p, r, c, k, ours in found if ours and k not in coloured]
ws = WebSocket(host, port, "/trpc")
done = 0
try:
api = Trpc(ws)
for page, row, col, kind, what in work:
location = {"pageNumber": page, "row": row, "column": col}
if what == "restore":
api.mutation("controls.resetControl", {"location": location, "newType": kind})
record.pop("%d/%d/%d" % (page, row, col), None)
else:
r, c = NAV_LOCATIONS[kind]
source = {"pageNumber": status_page, "row": int(r), "column": int(c)}
if not api.mutation("controls.copyControl", {"fromLocation": source, "toLocation": location}):
raise RuntimeError("copy to page %d row %d col %d failed" % (page, row, col))
record["%d/%d/%d" % (page, row, col)] = kind
done += 1
finally:
ws.close()
json.dump(record, open(record_path, "w"), indent=1, sort_keys=True)
return done
def main():
cfg = json.load(open(os.path.join(HERE, "config.json")))
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])