From 3428bd81a25e77fda407636672d57543b8f3e295 Mon Sep 17 00:00:00 2001 From: christian Date: Wed, 21 Oct 2026 15:12:10 -0400 Subject: [PATCH] NA: ALL AUTO-FIX key pauses or resumes auto-fix at every location; page keys keep their words under the band; rc4 ProP module at WS/SC - LOCATIONS page key at 3,1: shows ALL ON or the paused locations; tap pauses all, tap again sends each back to its hours - autofix auto: back to the auto-fix hours - the page name and number shrink into the top 60% while the locations band shows - WS and SC Propresenter-API on renewedvision-propresenter-api 1.2.0-rc4 (pinned manual, as NA) Co-Authored-By: Claude Opus 5.5 --- make_page.py | 11 +++++- ready-page.companionconfig | Bin 3450 -> 3466 bytes restart-page.companionconfig | Bin 3847 -> 3865 bytes settings/campuses.py | 72 +++++++++++++++++++++++++++++++++-- status-page.companionconfig | Bin 6527 -> 6546 bytes status_panel.py | 5 ++- tests/test_campus.py | 14 +++++++ 7 files changed, 96 insertions(+), 6 deletions(-) diff --git a/make_page.py b/make_page.py index b50281e..742f3cd 100644 --- a/make_page.py +++ b/make_page.py @@ -269,13 +269,18 @@ REMOTE_VAR = "sp_remote" # North Attleboro's summary of the other locat BAND_BLUE, BAND_DARK = 0x1E6FFF, 0x0B2A66 +def band_on(): + r = "$(custom:%s)" % REMOTE_VAR + return "$(internal:time_unix) - jsonpath(%s, '$.t') < 30 && jsonpath(%s, '$.n') > 0" % (r, r) + + def locations_band(flash_var): """A blue band across the bottom of NA's page-number key naming the other locations with a problem ("SC", "SC WS"): it flashes until that location acknowledges, then stays. Red stays NA's own (2026-10-21, the user: "i dont want the NA page buttons to flash red, that should he reserved for on site problems").""" r = "$(custom:%s)" % REMOTE_VAR - on = "$(internal:time_unix) - jsonpath(%s, '$.t') < 30 && jsonpath(%s, '$.n') > 0" % (r, r) + on = band_on() color = "jsonpath(%s, '$.f') == 1 && $(custom:%s) == 1 ? %d : %d" % (r, flash_var, BAND_DARK, BAND_BLUE) box = {"id": stable_id("nav", "band_box"), "name": "Locations", "usage": "auto", "type": "box", "enabled": prop(on, True), "opacity": prop(100), "x": prop(0), "y": prop(60), "width": prop(100), "height": prop(40), "rotation": prop(0), @@ -314,6 +319,10 @@ def nav_key(kind, names, status_page=None, hold_ms=1000, tap_page=1, cfg=None): paint(layer.get("children", [])) paint(key["style"]["layers"]) if kind == "pagenum" and (cfg.get("nav_locations_band") or False): + # While the band shows, the page's own name and number shrink into the top 60%, so nothing is hidden. + for layer in key["style"]["layers"]: + if layer["type"] == "group": + layer["height"] = prop("(%s) ? 60 : 100" % band_on(), True) key["style"]["layers"] += locations_band(opts["flash"]) if kind == "pagenum" and status_page: step = key["steps"]["0"] diff --git a/ready-page.companionconfig b/ready-page.companionconfig index 795944e0e60ad457bec51c0204d02edb06921d15..bd293a096b9409c1e1b1dfa0f7443de6eecf88a8 100644 GIT binary patch delta 3368 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 delta 3352 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 diff --git a/restart-page.companionconfig b/restart-page.companionconfig index 6ab7168361f95e163c7c18b1e170463f0926f657..4cfc36b8ac8d990fc3e2f70151a87091f136646d 100644 GIT binary patch delta 3196 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 delta 3158 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 diff --git a/settings/campuses.py b/settings/campuses.py index ed7b4de..0021313 100644 --- a/settings/campuses.py +++ b/settings/campuses.py @@ -74,9 +74,9 @@ def me(cfg, url=None): "user": pwd.getpwuid(os.getuid()).pw_name} -# Bumped when the pages are built differently (2: "campus" became "location", 2026-10-21), so the hub +# Bumped when the pages are built differently (2: "campus" became "location"; 3: ALL AUTO-FIX key, 2026-10-21), so the hub # re-imports them. -PAGES_VERSION = 2 +PAGES_VERSION = 3 def layout_sha(cfg): @@ -105,6 +105,9 @@ def http_json(url, body=None, timeout=3): VIEW = {"state": "sp_state_view", "heartbeat": "sp_hb_view"} REMOTE = make_page.REMOTE_VAR # which other locations have a problem: the blue band on NA's page-number keys +ALL_CMD = "sp_all_cmd" # a tap on NA's ALL AUTO-FIX key (LOCATIONS page) +ALL_AF = "sp_all_af" # what that key shows: {"t", "x", "paused"} +ALL_AF_POS = (3, 1) # where AUTO-FIX sits on every location's own status page SELECT = "sp_view" VIEW_CMD = "sp_view_cmd" # a tap or hold on the LOCATION page, passed on to that location's own watchdog MODE = "sp_view_mode" # "" = that location's status page, "RESTART" = its Restart page @@ -199,14 +202,51 @@ def build_overview(local_cfg, campuses, page, view_page): make_page.place(controls, (0, col), key) if restart: make_page.place(controls, (1, col), restart) + make_page.place(controls, ALL_AF_POS, all_autofix_key(local_cfg)) return make_page.page_export("%d\\nLocations" % page, page, controls, local_cfg) +def all_autofix_key(local_cfg): + """Tap: pause auto-fix at every location at once (NA too); tap again: every location back to its hours + (2026-10-21, the user: "add a key on NA to pause auto-fix at all locations"). Blue while none is paused, + grey naming the paused ones.""" + v = "$(custom:%s)" % ALL_AF + stale = "$(internal:time_unix) - jsonpath(%s, '$.t') > 30" % v + key = make_page.static_button("all_autofix", "AUTO-FIX\\nALL", 0x0B57D0, 0xFFFFFF, 1, + "Tap: pause auto-fix at every location (alarms stay on, holds still fix); tap again: all " + "back to their auto-fix hours.", "STATUS tile 'all autofix'") + for layer in key["style"]["layers"]: + if layer["type"] == "box": + layer["color"] = make_page.prop("%s ? %d : (jsonpath(%s, '$.paused') > 0 ? %d : %d)" % (stale, make_page.PURPLE, v, 0x262626, 0x0B57D0), True) + elif layer["type"] == "text": + layer["text"] = make_page.prop("%s ? `AUTO-FIX\\nALL ?` : `AUTO-FIX\\n${jsonpath(%s, '$.x')}`" % (stale, v), True) + key["steps"]["0"]["action_sets"]["up"] = [make_page.command_action("all_autofix", "press", ALL_CMD, "toggle $(internal:time_unix)")] + return key + + +def autofix_paused(state_json): + """True when that location's AUTO-FIX key says it was paused by hand ("AUTO-FIX\\nOFF\\nTIL ..."), None without one.""" + try: + af = json.loads(state_json or "{}").get("autofix") + except ValueError: + return None + if not af: + return None + lines = str(af.get("x") or "").split("\n") + return len(lines) > 2 and lines[1] == "OFF" and lines[2].startswith("TIL") + + +def all_autofix_summary(states, now): + """states: {id: that location's /api/state "state" JSON}. -> what NA's ALL AUTO-FIX key shows.""" + paused = sorted(cid.upper() for cid, s in states.items() if autofix_paused(s)) + return {"t": int(now), "paused": len(paused), "x": ("%s\nPAUSED" % " ".join(paused)) if paused else "ALL ON"} + + 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, MODE, REMOTE) if n not in have] + want += [n for n in (VIEW["state"], VIEW["heartbeat"], SELECT, VIEW_CMD, MODE, REMOTE, ALL_CMD, ALL_AF) if n not in have] if not want: return comp = cfg.get("companion") or {} @@ -342,6 +382,31 @@ class Mirror: save(self.site.data_dir, data) return data + def all_autofix(self, cfg, mine, others): + """NA's ALL AUTO-FIX key: show which locations are paused; a tap pauses them all, or resumes them all.""" + states = {cid: (self.remote.get(cid) or {}).get("state") for cid in others} + states[mine] = self.site.comp.get_custom((cfg.get("variables") or {}).get("state", "sp_state")) + summary = all_autofix_summary(states, time.time()) + self.site.comp.set_custom(ALL_AF, json.dumps(summary, separators=(",", ":"))) + text = (self.site.comp.get_custom(ALL_CMD) or "").strip(" ;") + if not text: + return + self.site.comp.set_custom(ALL_CMD, "") + stamps = [w for w in text.replace(";", " ").split() if w.isdigit()] + if not stamps or time.time() - int(stamps[-1]) > 30: + return # pressed while this was down: not acted on later + word = "auto" if summary["paused"] else "off" + hosts = {cid: urllib.parse.urlparse(e["url"]).hostname for cid, e in others.items()} + hosts[mine] = "127.0.0.1" + for cid, host in sorted(hosts.items()): + try: + comp = self.site.comp if cid == mine else sp.Companion(host, 8000, timeout=3) + before = (comp.get_custom("sp_cmd") or "").strip() + comp.set_custom("sp_cmd", ";".join([before, "autofix " + word] if before else ["autofix " + word])) + log.info("ALL AUTO-FIX: %s %s", cid, "paused" if word == "off" else "back to its hours") + except OSError as e: + log.warning("ALL AUTO-FIX: %s not reached: %s", cid, e) + def pass_on(self, pick, others, restart=False): """A tap or hold on the LOCATION page goes to the shown location's watchdog, through its Companion.""" text = (self.site.comp.get_custom(VIEW_CMD) or "").strip() @@ -388,6 +453,7 @@ class Mirror: t.join(2) if (data.get("pages") or {}).get("overview"): self.site.comp.set_custom(REMOTE, json.dumps(remote_summary(others, self.remote, time.time()), separators=(",", ":"))) + self.all_autofix(cfg, mine, others) # The LOCATION page shows the tapped location: its keys moved onto their cells. pick = (self.site.comp.get_custom(SELECT) or "").strip().lower() # held as "WS": the back key shows it if pick not in others and others: diff --git a/status-page.companionconfig b/status-page.companionconfig index 3e777f6e212214f7fc4e662611e48548d348a158..a5f8b735ec8851c0367e1b2733af03cfe8b640a9 100644 GIT binary patch delta 4348 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 F;Xhy{r_2BV delta 4314 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