Devices: fixes from review round 19

- A switch the server refuses no longer drops answers the page is waiting for
  (an install's job id): only an actual change of headset does.
- Test now goes through a jump host when the alias uses one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
saphidandClaude Opus 5.5 committed 2026-09-29 00:25:10 +10:00
1 parent c3e3f2629c
commit 6597a90059
3 files changed
+20 -5

No files matched your search

+11 -1
View File
@@ -382,7 +382,7 @@ class Connecting(unittest.TestCase):
self.assertEqual(rows["::1"]["ssh"], "wrong") self.assertEqual(rows["::1"]["ssh"], "wrong")
self.assertEqual(rows["nothing.invalid"]["state"], "unresolved") self.assertEqual(rows["nothing.invalid"]["state"], "unresolved")
self.assertEqual(self.routes, []) self.assertEqual(self.routes, [])
self.assertTrue(all("ControlPath=none" in c for c in self.calls())) self.assertTrue(all("ControlPath=none" in c for c in self.calls() if "-G" not in c))
def test_switching_to_a_headset_that_never_answers_stops_using_the_last_one(self): def test_switching_to_a_headset_that_never_answers_stops_using_the_last_one(self):
self.device("localhost") self.device("localhost")
@@ -483,6 +483,16 @@ class Connecting(unittest.TestCase):
self.assertIsNotNone(proc.poll()) self.assertIsNotNone(proc.poll())
self.assertIsNone(self.link.master) self.assertIsNone(self.link.master)
def test_test_now_goes_through_a_jump_host(self):
d = self.device("10.99.99.99")
self.pin(d["id"])
self.hosts({"10.99.99.99": "ok"})
with mock.patch.object(fl, "ssh_g", return_value=("frame-t", 22, "steamos", True)):
self.link.test(d["id"])
row = self.link.snapshot()["tests"][d["id"]]["rows"][0]
self.assertEqual(row["ssh"], "ok", row)
self.assertIn("jump host", row["detail"])
def test_devices_api_checks_everything(self): def test_devices_api_checks_everything(self):
d = self.device("localhost") d = self.device("localhost")
bad = [{"action": "address-add", "id": d["id"], "host": "-oProxyCommand=touch /tmp/x"}, bad = [{"action": "address-add", "id": d["id"], "host": "-oProxyCommand=touch /tmp/x"},
+9 -3
View File
@@ -926,12 +926,18 @@ class Link:
put() put()
net = self.state["network"] or {} net = self.state["network"] or {}
proxied = ssh_g(device["alias"])[3]
def one(i, a): def one(i, a):
res = probe(a["host"], device["port"], update=lambda **f: (rows[i].update(f), put())) if proxied: # through a jump host: a direct probe says nothing, ssh itself is the test
res = {"state": "answered", "detail": "Through a jump host", "ip": None, "rtt_ms": None}
else:
res = probe(a["host"], device["port"], update=lambda **f: (rows[i].update(f), put()))
rows[i].update({k: res.get(k) for k in ("state", "detail", "ip", "rtt_ms")}) rows[i].update({k: res.get(k) for k in ("state", "detail", "ip", "rtt_ms")})
put() put()
if res["state"] != "answered": if res["state"] != "answered":
return return
lead = f"Answered in {res['rtt_ms']:g} ms" if res.get("rtt_ms") is not None else "Through the jump host"
rows[i]["ssh"] = "checking" rows[i]["ssh"] = "checking"
put() put()
argv = [*self.mux_base[:3], "-o", "ControlPath=none", "-o", "ConnectTimeout=8", argv = [*self.mux_base[:3], "-o", "ControlPath=none", "-o", "ConnectTimeout=8",
@@ -942,10 +948,10 @@ class Link:
errors="replace", timeout=20) errors="replace", timeout=20)
err = r.stderr.strip() err = r.stderr.strip()
if r.returncode == 0: if r.returncode == 0:
rows[i].update(ssh="ok", detail=f"Answered in {res['rtt_ms']:g} ms · SSH works") rows[i].update(ssh="ok", detail=f"{lead} · SSH works")
self.reg.record_success(device_id, a["host"], net.get("id"), res["rtt_ms"]) self.reg.record_success(device_id, a["host"], net.get("id"), res["rtt_ms"])
elif UNKNOWN.search(err): elif UNKNOWN.search(err):
rows[i].update(ssh="unpinned", detail=f"Answered in {res['rtt_ms']:g} ms · identity not saved yet") rows[i].update(ssh="unpinned", detail=f"{lead} · identity not saved yet")
elif CHANGED.search(err): elif CHANGED.search(err):
rows[i].update(ssh="wrong", detail="Answered as a different headset") rows[i].update(ssh="wrong", detail="Answered as a different headset")
elif DENIED.search(err): elif DENIED.search(err):
-1
View File
@@ -2676,7 +2676,6 @@ $("devSel").onchange = e => useDevice(e.target.value);
async function useDevice(id) { async function useDevice(id) {
const d = dv.list.find(x => x.id === id); const d = dv.list.find(x => x.id === id);
if (!d || d.active) return; if (!d || d.active) return;
devGen++; // from here on, answers about the last headset are dropped
const res = await act(`Switch to ${d.name}`, () => api("/api/devices", { action: "use", id })); const res = await act(`Switch to ${d.name}`, () => api("/api/devices", { action: "use", id }));
if (!res) { // refused (an install is running): show the headset still in use if (!res) { // refused (an install is running): show the headset still in use
const current = dv.list.find(x => x.active); const current = dv.list.find(x => x.active);