Settings page: ON/OFF switches everywhere, the Auto switch screen, a Companion button picker

- ON/OFF on every key, check, fix, Restart and READY key, computer, MIDI link, HyperDeck,
  Dante device, watched Companion connection, switcher and Spotify check, outlet fix, and one
  main switch each for auto-fix, the auto switch and the READY page. Switched off = kept in
  the settings, grey OFF on the deck, never checked or run (effective_config in the watchdog).
- Settings › Auto switch: a plain summary, what counts as down (each trigger editable in place:
  address, program and computer, Dante device, the button it presses, timings), the hours
  folded away, the buttons; + Button, + What counts as down, Remove.
- Pick a Companion button by clicking it on its page, drawn like Companion (read-only API:
  /api/companion/pages and /api/companion/page).
- STREAM STB / STREAM FOH Restart keys removed (the Studio B page has its own); the alert names
  the switch-back button instead.
- Dante screen: shows the DANTE key's device list again (it picked STB AUDIO's one device).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 14:30:41 -04:00
co-authored by Claude Opus 5.5
parent 14fce2cf9a
commit 5adab6c518
11 changed files with 663 additions and 82 deletions
+20 -4
View File
@@ -113,6 +113,8 @@ field("auto_fix.windows[].start", "hhmm", None, "", "Schedules", required=True)
field("auto_fix.windows[].end", "hhmm", None, "", "Schedules", required=True)
field("auto_fix.while_muted", "list", [], "Auto-fixes that keep running inside the window even while ALARMS is "
"muted (harmless ones, like playing the Spotify playlist).", "Schedules")
field("auto_fix.disabled", "bool", False, "Auto-fix switched off as a whole: nothing is fixed by itself (holds "
"still work).", "Schedules")
field("auto_fix.after_seconds", "int", 20, "A fault must last this long before auto-fix acts.", "Schedules",
advanced=True, lo=5, hi=600)
field("auto_fix.max_tries", "int", 2, "Tries per fault, then it is yours to hold.", "Schedules", advanced=True,
@@ -130,6 +132,8 @@ field("auto_fix.keep_months", "int", 1, "Daily log files are deleted after this
advanced=True, lo=1, hi=12)
field("failover", "dict", {}, "Automatic fallback: when a fix marked failover may act (it acts even while ALARMS is "
"muted).", "Schedules")
field("failover.disabled", "bool", False, "The auto switch switched off as a whole: nothing is pressed by itself.",
"Schedules")
field("failover.windows", "list", [{"days": ["sun"], "start": "06:30", "end": "13:30"}], "When a failover may switch "
"by itself.", "Schedules")
field("failover.windows[].days", "days", None, "", "Schedules", required=True)
@@ -184,6 +188,8 @@ field("hosts.*.ssh", "str", None, "How this Mac logs in: user@address.", "Comput
field("hosts.*.mac", "mac", None, "The PC's hardware address, to wake it when it is off.", "Computers")
field("hosts.*.wake_broadcast", "ip", None, "The broadcast address the wake-up goes to.", "Computers", advanced=True)
field("hosts.*.name", "str", None, "The computer, as people say it.", "Computers", apply="none")
field("hosts.*.disabled", "bool", False, "Switched off: kept, but nothing logs in to it - its checks and fixes are "
"skipped (a computer that is away or being rebuilt).", "Computers")
field("hosts.*.os", "enum", "mac", "Mac or Windows (a PC's checks are written for cmd.exe).", "Computers",
options=["mac", "windows", "other"])
field("hosts.*.role", "str", None, "What it is for.", "Computers", apply="none")
@@ -229,6 +235,8 @@ field("fixes.*.mac", "mac", None, "The device expected on that port.", "Fixes")
field("fixes.*.playlist", "str", None, "A playlist other than the site's (rare).", "Fixes", advanced=True)
field("fixes.*.template", "str", None, "The builder this fix was made with.", "Fixes", advanced=True, apply="none")
field("fixes.*.params", "dict", None, "The builder's fields.", "Fixes", advanced=True, apply="none")
field("fixes.*.disabled", "bool", False, "Switched off: kept, but never runs (a hold says TURNED OFF; auto-fix "
"and the auto switch skip it).", "Fixes")
field("fixes.*.action", "enum", None, "What a READY page fix does.", "Fixes", options=list(sp.READY_ACTIONS))
field("fixes.*.page", "int", None, "The Companion page the button is on.", "Fixes", lo=1)
field("fixes.*.row", "int", None, "The button's row on that page (the top row is 0).", "Fixes", lo=0)
@@ -250,6 +258,10 @@ field("tiles[].kind", "enum", None, "A special key: master (ALL), service (ALARM
options=["master", "service", "network", "ready"], apply="pages")
field("tiles[].members", "list", None, "A group: the keys it sums up.", "Keys")
field("tiles[].checks", "list", [], "What the key watches.", "Keys")
field("tiles[].disabled", "bool", False, "Switched off: the key stays on the page (grey, OFF) but checks nothing "
"and fixes nothing, like a connection switched off in Companion.", "Keys")
field("tiles[].checks[].devices_off", "list", [], "Dante devices switched off: kept in the list, not watched.", "Keys")
field("tiles[].checks[].disabled", "bool", False, "Switched off: this check is kept but not looked at.", "Keys")
field("tiles[].hold_always", "str", None, "A fix a hold runs even when the key is green.", "Keys", apply="pages")
field("tiles[].checks[].type", "enum", None, "What kind of check.", "Keys", required=True)
field("tiles[].checks[].conn", "str", None, "The Companion connection.", "Keys")
@@ -316,6 +328,8 @@ field("restart_page.keys[].id", "id", None, "", "Keys", advanced=True, required=
field("restart_page.keys[].label", "str", None, "", "Keys", required=True, apply="pages")
field("restart_page.keys[].pos", "list", None, "[row, column].", "Keys", required=True, apply="pages")
field("restart_page.keys[].fix", "str", None, "What holding it runs.", "Keys", required=True)
field("restart_page.keys[].disabled", "bool", False, "Switched off: the key stays on the page (OFF) but a hold does "
"nothing.", "Keys", apply="pages")
field("restart_page.keys[].watch", "list", [], "Keep flashing HOLD TO FIX while these fixes' checks or "
"'conn:<label>' connections are down.", "Keys")
@@ -329,6 +343,8 @@ field("ready_page.back_pos", "list", None, "Where the 'back to Status' key sits
field("ready_page.keys", "list", [], "The checklist keys (each like a Status page key, placed on the READY page).",
"Ready", apply="pages")
field("ready", "dict", {}, "Where the READY page looks.", "Ready")
field("ready.disabled", "bool", False, "The READY page switched off as a whole: its keys show OFF and nothing is "
"looked at.", "Ready")
field("ready.interval_seconds", "int", 120, "Look this often (never while a HyperDeck is recording; a hold looks at "
"once).", "Ready", advanced=True, lo=30, hi=3600)
field("ready.due", "list", [], "From when gaps are red instead of amber.", "Ready")
@@ -382,7 +398,7 @@ CHECKS = {
"hyperdeck": {"fields": ["conn", "optional", "disk_service_only", "full_minutes", "low_minutes", "rec_sync", "ip"],
"subs": ["conn", "disk", "rec"], "identity": ["conn"], "module": "bmd-hyperdeck",
"question": "Is a recorder OK?", "words": "A HyperDeck is connected, has a disk, and records with the others."},
"dante": {"fields": ["conn", "devices", "count_all", "service_only"], "subs": ["conn", "<device name>"], "identity": ["conn"],
"dante": {"fields": ["conn", "devices", "count_all", "service_only", "devices_off"], "subs": ["conn", "<device name>"], "identity": ["conn"],
"module": "audinate-dantecontroller", "question": "Is Dante complete?", "words": "Every listed Dante device is present."},
"shure": {"fields": ["conn", "channels", "battery_err_bars", "battery_warn_bars"], "subs": ["conn", "ch<N>_batt", "ch<N>_rf"],
"identity": ["conn"], "module": "shure-wireless", "question": "Is a device answering?", "words": "A Shure receiver: batteries and interference."},
@@ -421,7 +437,7 @@ CHECKS = {
"ready_manual": {"fields": ["item", "msg"], "subs": ["done"], "identity": ["item"], "module": None,
"question": "Is the weekend ready?", "words": "A line of the weekly checklist a person taps done."},
}
COMMON_CHECK_FIELDS = ["type", "fix", "tag", "service_only", "debounce_seconds"]
COMMON_CHECK_FIELDS = ["type", "fix", "tag", "service_only", "debounce_seconds", "disabled"]
FIX_KINDS = sp.FIX_KINDS
FIX_KIND_FIELDS = {
"ssh": ["host", "run", "timeout", "reconnect", "conn"], "local": ["run", "timeout", "reconnect", "conn"],
@@ -451,7 +467,7 @@ GLOSSARY = {
"MISSING", "SLIDES OLD?", "SLIDES", "NO TC", "NO LTC", "NO BG", "NO PLAYLIST", "OLD PLAN", "SYNC ERROR",
"NOT ON PLAN", "DECLINED", "WRONG DECK", "NOTHING ON", "NOT SENT", "COPYING TO", "NO PCO LOGIN",
"PCO UNREACHABLE", "NO PLAN IN PCO", "PROP UNREACHABLE", "LINEUP UNREACHABLE", "RESOLUME UNREACHABLE",
"UNREACHABLE", "CHECK FAILED", "NO CLIP KNOWN", "LTC WRONG", "PCO WRITE FAILED", "IN PCO", "NO SET TO COPY", "SYNC SLOW", "NO PLAN YET", "VOX",
"UNREACHABLE", "CHECK FAILED", "NO CLIP KNOWN", "TURNED OFF", "LTC WRONG", "PCO WRITE FAILED", "IN PCO", "NO SET TO COPY", "SYNC SLOW", "NO PLAN YET", "VOX",
],
"ask Chris": ["NOT IN COMPANION", "CHECK BUG", "NO DATA", "WATCHDOG DOWN", "ERR", "PROCESS", "WEB"],
"what a key says while you hold it": [
@@ -650,7 +666,7 @@ def validate(cfg, live=None):
if fix.get("undo") and fix["undo"] not in fixes:
errors.append("%s: the fix that switches back (undo) is unknown: %s" % (where, fix["undo"]))
for k in fix:
if k not in FIX_KIND_FIELDS.get(kind, []) and k not in ("kind", "short", "label", "restart_ok", "never_auto",
if k not in FIX_KIND_FIELDS.get(kind, []) and k not in ("kind", "short", "label", "restart_ok", "never_auto", "disabled",
"template", "params", "timeout", "failover", "undo"):
warnings.append("%s: %s is not a field of a %s fix" % (where, k, kind))
named = set()