diff --git a/src/voxip_kiosk/agent.py b/src/voxip_kiosk/agent.py index 8dd6a75..8413008 100644 --- a/src/voxip_kiosk/agent.py +++ b/src/voxip_kiosk/agent.py @@ -40,6 +40,7 @@ PROBE_MAX_AGE_LOADING = 10 PROBE_MAX_AGE_ONLINE = 60 HUNG_RELOAD_AFTER = 3 HUNG_RESTART_AFTER = 6 +NO_NETWORK = "Geen netwerkverbinding (netwerkkabel los?)" BLANK_URLS = {"", "about:blank", "chrome://newtab/", "chrome://new-tab-page/"} @@ -124,6 +125,17 @@ class Agent: log.warning(message) self.state["last_error"] = {"at": int(self.wall()), "message": message} + def has_network(self) -> bool: + """False when no interface has an address, e.g. the cable is out and there is no wifi.""" + return bool(self.addresses()) + + def show_offline(self, config: dict[str, Any], reason: str) -> None: + if self.offline_since is None: + self.offline_since = self.wall() + self.state["offline_since"] = int(self.offline_since) + self.error(f"Kiosk-URL niet bereikbaar ({reason}); offlinepagina getoond") + self.navigate(self.offline_url(config, reason)) + def setup_required(self) -> bool: return self.paths.setup_code_file.exists() and not self.paths.admin_file.exists() @@ -187,6 +199,8 @@ class Agent: if kind != "remote" or url_changed: ok, reason = self.reachable(target, 0 if url_changed else PROBE_MAX_AGE_LOADING) + if not ok and not self.has_network(): + reason = NO_NETWORK if ok: if self.offline_since is not None: log.info("Kiosk-URL weer bereikbaar na %.0f s", self.wall() - self.offline_since) @@ -195,14 +209,19 @@ class Agent: self.offline_since = None self.state["offline_since"] = None elif kind != "offline" or url_changed: - if self.offline_since is None: - self.offline_since = self.wall() - self.state["offline_since"] = int(self.offline_since) - self.error(f"Kiosk-URL niet bereikbaar ({reason}); offlinepagina getoond") - self.navigate(self.offline_url(config, reason)) + self.show_offline(config, reason) self.hung = 0 return + # Without any network the page cannot recover by itself: say so on screen. When only + # the server is unreachable the page stays, so InfoFlow can show its offline cache. + if not self.has_network(): + self.state.update(target_reachable=False, reason=NO_NETWORK, checked_at=int(self.wall())) + self._probe = None + self.hung = 0 + self.show_offline(config, NO_NETWORK) + return + if self.last_load is None: self.last_load = self.clock() diff --git a/tests/test_agent.py b/tests/test_agent.py index 00d997f..1648351 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -100,7 +100,7 @@ def test_chromium_error_page_is_replaced(env): assert "offline.html" in browser.url -def test_running_page_is_left_alone_when_network_drops(env): +def test_running_page_is_left_alone_when_the_server_is_unreachable(env): """Pages with their own offline cache (e.g. InfoFlow displays) must keep running.""" agent, browser, probe, clock, _ = env browser.url = "https://schermen-ijsselflat.voxip.nl" @@ -178,3 +178,27 @@ def test_offline_page_never_receives_the_token(env, paths): agent.tick() assert "offline.html" in browser.url assert "geheim" not in browser.url and "3456" in browser.url + + +def test_offline_page_when_the_network_link_is_gone_then_back(env): + agent, browser, probe, clock, _ = env + network = {"eth0": ["192.168.1.20/24"]} + agent.addresses = lambda: dict(network) + browser.url = "https://schermen-ijsselflat.voxip.nl" + agent.tick() + assert browser.navigations == [] + + network.clear() # cable out + probe.ok = False + clock.t += 5 + agent.tick() + assert "offline.html" in browser.url and "netwerkkabel" in browser.url + clock.t += 5 + agent.tick() + assert len(browser.navigations) == 1 # no re-navigation while it stays down + + network["eth0"] = ["192.168.1.20/24"] # cable back in + probe.ok = True + clock.t += 11 + agent.tick() + assert browser.url == "https://schermen-ijsselflat.voxip.nl" diff --git a/www/kiosk.css b/www/kiosk.css index 642d8fd..6ac7802 100644 --- a/www/kiosk.css +++ b/www/kiosk.css @@ -1,5 +1,5 @@ /* Local pages shown on the kiosk screen itself (file://). Same look as the InfoFlow - display (dark theme): flat dark grey, white type, panels with a blue top edge. */ + display (dark theme): flat dark grey, white type, plain panels. */ :root { color-scheme: dark; --bg: #111827; @@ -21,7 +21,7 @@ body { font-family: ui-sans-serif, system-ui, "DejaVu Sans", "Liberation Sans", .clock { font-size: clamp(36px, 8vmin, 110px); font-weight: 300; line-height: 1.1; font-variant-numeric: tabular-nums; } .middle { flex: 1; display: flex; align-items: center; justify-content: center; padding: 3vmin 0; } -.panel { width: min(100%, 1100px); background: var(--panel); border-top: 4px solid var(--accent); border-radius: 8px; padding: 4.5vmin 5vmin; } +.panel { width: min(100%, 1100px); background: var(--panel); border-radius: 8px; padding: 4.5vmin 5vmin; } h1 { font-size: clamp(26px, 4.6vmin, 64px); font-weight: 600; margin: 0 0 1.5vmin; } p { font-size: clamp(16px, 2.6vmin, 34px); color: var(--muted); margin: 0 0 1vmin; } .link { margin-top: 3.5vmin; padding-top: 2.5vmin; border-top: 1px solid var(--line); font-size: clamp(14px, 2.2vmin, 28px); color: var(--muted); overflow-wrap: anywhere; }