Every campus matches NA, tailored: Restart page, Companion-Mac programs, NA's auto-fix hours, nightly backups, restart from NA
- Auto-setup: programs on the Companion Mac itself (ProPresenter at Woonsocket) get a local process check and local start/restart fixes; every campus gets a Restart page (keys arrive as computers join); NA's auto-fix hours (Tue, Wed, Sun); a program found after its Companion connection joins that key instead of making a second one. settings.campus rebuild remakes a campus from what is found now, keeping its name, PIN and pages; upgrade-companion brings Companion to 5.0.7 from NA's copy (/campus/companion.zip), as Woonsocket was; setup stops with that advice on an older one. - NA's CAMPUSES page has a RESTART key per campus; the CAMPUS page shows that campus's Restart page and a hold there restarts it at that campus. - Backups: backup/backup-campuses.sh, nightly 04:30 on NA (installed everywhere, acts on the hub), into Drive > Waters Creative > Apps > Status Panel Backups/<campus>, 30 kept, alert email via the checklist's alert.js. Campuses report the account to back up with. sites/ws/config.json in the repo. - Woonsocket rebuilt: 9 keys (PROP with ProPresenter on its Mac), Restart page 20 (SUPERRACK, VISTA, ABLETON, PROP), auto-fix on NA's hours; all OK. Both campuses backed up to Drive by the job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+43
-16
@@ -63,8 +63,11 @@ def my_url(cfg):
|
||||
|
||||
|
||||
def me(cfg, url=None):
|
||||
import pwd
|
||||
site = cfg.get("site") or {}
|
||||
return {"id": site.get("id") or "new", "name": site.get("name") or "This campus", "url": url or my_url(cfg)}
|
||||
# The account this panel runs as: the hub logs in with it for the nightly backup.
|
||||
return {"id": site.get("id") or "new", "name": site.get("name") or "This campus", "url": url or my_url(cfg),
|
||||
"user": pwd.getpwuid(os.getuid()).pw_name}
|
||||
|
||||
|
||||
def layout_sha(cfg):
|
||||
@@ -94,6 +97,7 @@ def http_json(url, body=None, timeout=3):
|
||||
VIEW = {"state": "sp_state_view", "heartbeat": "sp_hb_view"}
|
||||
SELECT = "sp_view"
|
||||
VIEW_CMD = "sp_view_cmd" # a tap or hold on the CAMPUS page, passed on to that campus's own watchdog
|
||||
MODE = "sp_view_mode" # "" = that campus's status page, "RESTART" = its Restart page
|
||||
CELLS = [(r, c) for r in range(4) for c in range(1, 8)]
|
||||
|
||||
|
||||
@@ -132,14 +136,16 @@ def build_view(local_cfg, page, overview_page):
|
||||
"Tap: back to the CAMPUSES page (page %d)." % overview_page, "STATUS jump key")
|
||||
for layer in back["style"]["layers"]:
|
||||
if layer["type"] == "text": # which campus this page shows, then the way back
|
||||
layer["text"] = make_page.prop("`${$(custom:%s)}\\nALL SITES`" % SELECT, True)
|
||||
layer["text"] = make_page.prop("`${$(custom:%s)} ${$(custom:%s)}\\nALL SITES`" % (SELECT, MODE), True)
|
||||
make_page.place(controls, (3, 0), back)
|
||||
return make_page.page_export("%d\\nCampus" % page, page, controls, local_cfg)
|
||||
|
||||
|
||||
def select_action(cid):
|
||||
return {"type": "action", "id": make_page.stable_id("campus_" + cid, "select"), "definitionId": "custom_variable_set_value",
|
||||
"connectionId": "internal", "options": {"name": make_page.prop(SELECT), "value": make_page.prop(cid.upper())}, "children": {}}
|
||||
def select_action(cid, mode=""):
|
||||
return [{"type": "action", "id": make_page.stable_id("campus_" + cid + mode, "select"), "definitionId": "custom_variable_set_value",
|
||||
"connectionId": "internal", "options": {"name": make_page.prop(SELECT), "value": make_page.prop(cid.upper())}, "children": {}},
|
||||
{"type": "action", "id": make_page.stable_id("campus_" + cid + mode, "mode"), "definitionId": "custom_variable_set_value",
|
||||
"connectionId": "internal", "options": {"name": make_page.prop(MODE), "value": make_page.prop(mode)}, "children": {}}]
|
||||
|
||||
|
||||
def campus_key(cid, name, names, actions, note, local_cfg):
|
||||
@@ -160,18 +166,29 @@ def campus_key(cid, name, names, actions, note, local_cfg):
|
||||
|
||||
|
||||
def build_overview(local_cfg, campuses, page, view_page):
|
||||
"""Row 0: each campus's ALL key (tap: its status page). Row 1: its RESTART key (tap: its Restart page)."""
|
||||
controls = make_page.nav_controls(local_cfg)
|
||||
mine = (local_cfg.get("site") or {}).get("id")
|
||||
for cid, cell in zip(sorted(campuses, key=lambda c: (c != mine, c)), CELLS):
|
||||
rp = (local_cfg.get("restart_page") or {}).get("page")
|
||||
for col, cid in enumerate(sorted(campuses, key=lambda c: (c != mine, c))[:7], start=1):
|
||||
name = campuses[cid].get("name", cid)
|
||||
tag = short(cid, name)
|
||||
if cid == mine:
|
||||
key = campus_key(cid, name, local_cfg["variables"], [make_page.set_page_action("campus_" + cid, "go", local_cfg["page"])],
|
||||
"Tap: its status page (page %d)." % local_cfg["page"], local_cfg)
|
||||
restart = make_page.static_button("campus_rs_" + cid, "%s\\nRESTART" % tag, 0x37474F, 0xFFFFFF, rp,
|
||||
"Tap: this campus's Restart page (page %s)." % rp, "STATUS jump key") if rp else None
|
||||
else:
|
||||
key = campus_key(cid, name, {"state": "sp_state_" + cid, "heartbeat": "sp_hb_" + cid},
|
||||
[select_action(cid), make_page.set_page_action("campus_" + cid, "go", view_page)],
|
||||
select_action(cid) + [make_page.set_page_action("campus_" + cid, "go", view_page)],
|
||||
"Tap: its status page, live (page %d)." % view_page, local_cfg)
|
||||
make_page.place(controls, cell, key)
|
||||
restart = make_page.static_button("campus_rs_" + cid, "%s\\nRESTART" % tag, 0x37474F, 0xFFFFFF, view_page,
|
||||
"Tap: %s's Restart page, live (page %d); hold a key there to restart it at %s." % (name, view_page, name),
|
||||
"STATUS jump key")
|
||||
restart["steps"]["0"]["action_sets"]["up"] = select_action(cid, "RESTART") + restart["steps"]["0"]["action_sets"]["up"]
|
||||
make_page.place(controls, (0, col), key)
|
||||
if restart:
|
||||
make_page.place(controls, (1, col), restart)
|
||||
return make_page.page_export("%d\\nCampuses" % page, page, controls, local_cfg)
|
||||
|
||||
|
||||
@@ -179,7 +196,7 @@ def ensure_variables(cfg, ids):
|
||||
from settings import campus
|
||||
have, _ = campus.companion_state(cfg)
|
||||
want = [n for cid in ids for n in ("sp_state_" + cid, "sp_hb_" + cid) if n not in have]
|
||||
want += [n for n in (VIEW["state"], VIEW["heartbeat"], SELECT, VIEW_CMD) if n not in have]
|
||||
want += [n for n in (VIEW["state"], VIEW["heartbeat"], SELECT, VIEW_CMD, MODE) if n not in have]
|
||||
if not want:
|
||||
return
|
||||
comp = cfg.get("companion") or {}
|
||||
@@ -250,6 +267,7 @@ class Mirror:
|
||||
self.site = site
|
||||
self.remote = {} # id -> last /api/state answer
|
||||
self.keys = {} # id -> [{"id", "pos"}] of its status page (from its /api/config)
|
||||
self.rkeys = {} # id -> the same for its Restart page
|
||||
|
||||
@staticmethod
|
||||
def is_hub(data):
|
||||
@@ -294,6 +312,7 @@ class Mirror:
|
||||
if st and (layouts.get(cid) != st.get("layout") or cid not in self.keys):
|
||||
remote_cfg = http_json(e["url"].rstrip("/") + "/api/config", timeout=8)["config"]
|
||||
self.keys[cid] = [{"id": t["id"], "pos": t["pos"]} for t in remote_cfg.get("tiles", []) if t.get("pos")]
|
||||
self.rkeys[cid] = [{"id": k["id"], "pos": k["pos"]} for k in (remote_cfg.get("restart_page") or {}).get("keys", []) if k.get("pos")]
|
||||
layouts[cid] = st.get("layout")
|
||||
ensure_variables(cfg, list(others))
|
||||
taken = self.taken_pages(cfg) | {p for p in pages.values() if p}
|
||||
@@ -313,13 +332,14 @@ class Mirror:
|
||||
save(self.site.data_dir, data)
|
||||
return data
|
||||
|
||||
def pass_on(self, pick, others):
|
||||
def pass_on(self, pick, others, restart=False):
|
||||
"""A tap or hold on the CAMPUS page goes to the shown campus's watchdog, through its Companion."""
|
||||
text = (self.site.comp.get_custom(VIEW_CMD) or "").strip()
|
||||
if not text:
|
||||
return
|
||||
self.site.comp.set_custom(VIEW_CMD, "")
|
||||
cmds = campus_command(self.keys.get(pick), text, time.time()) if pick else []
|
||||
keys = (self.rkeys if restart else self.keys).get(pick)
|
||||
cmds = campus_command(keys, text, time.time(), restart) if pick else []
|
||||
if not cmds:
|
||||
return
|
||||
host = urllib.parse.urlparse(others[pick]["url"]).hostname
|
||||
@@ -362,20 +382,23 @@ class Mirror:
|
||||
# Nothing tapped yet (or a campus that left): show the first one rather than a purple page.
|
||||
pick = sorted(others)[0]
|
||||
self.site.comp.set_custom(SELECT, pick.upper())
|
||||
self.pass_on(pick if pick in others else None, others)
|
||||
restart = (self.site.comp.get_custom(MODE) or "").strip().upper() == "RESTART"
|
||||
self.pass_on(pick if pick in others else None, others, restart)
|
||||
st = self.remote.get(pick)
|
||||
if st and pick in self.keys and (data.get("pages") or {}).get("view"):
|
||||
keys = (self.rkeys if restart else self.keys).get(pick)
|
||||
if st and keys is not None and (data.get("pages") or {}).get("view"):
|
||||
try:
|
||||
state = json.loads(st.get("state") or "{}")
|
||||
except ValueError:
|
||||
state = {}
|
||||
self.site.comp.set_custom(VIEW["state"], json.dumps(view_state(state, self.keys[pick]), separators=(",", ":")))
|
||||
self.site.comp.set_custom(VIEW["state"], json.dumps(view_state(state, keys), separators=(",", ":")))
|
||||
self.site.comp.set_custom(VIEW["heartbeat"], str(st.get("hb") or "0"))
|
||||
|
||||
|
||||
def campus_command(keys, text, now):
|
||||
def campus_command(keys, text, now, restart=False):
|
||||
"""The CAMPUS page's taps and holds ("tap 1 3 <time>;hold 0 2 <time>") as that campus's own
|
||||
commands ("ack hd1", "restart hd1 <time>"), for the keys at those cells. Stale ones are dropped."""
|
||||
commands ("ack hd1", "restart hd1 <time>"; on its Restart page a hold is "force rs_x <time>" and a
|
||||
tap does nothing, as on its own deck), for the keys at those cells. Stale ones are dropped."""
|
||||
at = {tuple(k["pos"]): k["id"] for k in keys or []}
|
||||
out = []
|
||||
for part in (text or "").split(";"):
|
||||
@@ -385,6 +408,10 @@ def campus_command(keys, text, now):
|
||||
tid = at.get((int(w[1]), int(w[2])))
|
||||
if not tid or now - int(w[3]) > 15:
|
||||
continue
|
||||
if restart:
|
||||
if w[0] == "hold":
|
||||
out.append("force %s %s" % (tid, w[3]))
|
||||
continue
|
||||
out.append("ack %s" % tid if w[0] == "tap" else "restart %s %s" % (tid, w[3]))
|
||||
return out
|
||||
|
||||
|
||||
Reference in New Issue
Block a user