mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +02:00
Devices: fixes from review round 14
- Retry now (while connected) and Forget identity wait for running installs. - Terminal windows get the headset's address by name, so a link-local IPv6 zone never has to pass through Windows' console. - Renaming the headset in use shows at once in the header. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5874fe33f6
commit
34e91988b1
3 files changed
+41
-4
No files matched your search
+26
-1
@@ -392,7 +392,8 @@ class Connecting(unittest.TestCase):
|
||||
for body in ({"action": "use", "id": other["id"]}, {"action": "remove", "id": d["id"]},
|
||||
{"action": "update", "id": d["id"], "port": 2222},
|
||||
{"action": "address-remove", "id": d["id"], "host": "localhost"},
|
||||
{"action": "address-update", "id": d["id"], "host": "localhost", "newHost": "127.0.0.1"}):
|
||||
{"action": "address-update", "id": d["id"], "host": "localhost", "newHost": "127.0.0.1"},
|
||||
{"action": "forget-identity", "id": d["id"]}):
|
||||
with self.assertRaises(fd.DeviceError, msg=body):
|
||||
fl.devices_action(self.link, body, open_setup=None, busy=lambda: 1)
|
||||
# Renaming, or changing another headset, is fine.
|
||||
@@ -400,6 +401,30 @@ class Connecting(unittest.TestCase):
|
||||
fl.devices_action(self.link, {"action": "update", "id": other["id"], "port": 2222}, None, busy=lambda: 1)
|
||||
self.assertEqual(self.reg.get(d["id"])["name"], "Desk")
|
||||
|
||||
def test_no_reconnecting_under_a_running_install(self):
|
||||
self.device("localhost")
|
||||
self.hosts({"localhost": "ok"})
|
||||
self.link.connect(["start"])
|
||||
with self.assertRaises(fd.DeviceError):
|
||||
fl.devices_action(self.link, {"action": "retry"}, None, busy=lambda: 1)
|
||||
fl.devices_action(self.link, {"action": "retry"}, None) # fine when nothing runs
|
||||
|
||||
def test_terminals_get_the_address_by_name(self):
|
||||
d = self.device("localhost")
|
||||
self.hosts({"localhost": "ok"})
|
||||
self.link.connect(["start"])
|
||||
alias, opts = self.link.named_route()
|
||||
self.assertEqual(alias, "frame-t")
|
||||
self.assertIn("HostName=localhost", opts)
|
||||
self.assertIn(f"HostKeyAlias=frame-control-{d['id']}", opts)
|
||||
|
||||
def test_a_rename_shows_at_once(self):
|
||||
d = self.device("localhost")
|
||||
self.hosts({"localhost": "ok"})
|
||||
self.link.connect(["start"])
|
||||
fl.devices_action(self.link, {"action": "update", "id": d["id"], "name": "Desk"}, None)
|
||||
self.assertEqual(self.link.snapshot()["device"]["name"], "Desk")
|
||||
|
||||
def test_stopping_mid_handshake_leaves_no_ssh_behind(self):
|
||||
self.device("localhost")
|
||||
self.hosts({"localhost": "slow"})
|
||||
|
||||
+14
-2
@@ -304,6 +304,15 @@ class Link:
|
||||
self.cond.notify_all()
|
||||
self.devices_changed()
|
||||
|
||||
def named_route(self):
|
||||
"""(alias, options) for a terminal window: like every command's, but with the
|
||||
address by name, not the IP it answered from. A zone's % can't be passed through
|
||||
Windows' console, and ssh resolves the name itself."""
|
||||
device = self.active_device()
|
||||
via = self.state.get("via") if self.state.get("phase") == "connected" else None
|
||||
host = via["host"] if via else (device["addresses"][0]["host"] if device.get("addresses") else None)
|
||||
return device["alias"], self.host_opts(device, host)
|
||||
|
||||
def first_route(self, device):
|
||||
"""Where commands go before any address has answered: the first one, with the
|
||||
headset's own pinned identity, so nothing reaches another device meanwhile."""
|
||||
@@ -946,8 +955,9 @@ def devices_action(link, body, open_setup, busy=lambda: 0):
|
||||
did = body.get("id")
|
||||
active = link.active_device()
|
||||
is_active = did == active["id"]
|
||||
moves = action == "use" or (is_active and (
|
||||
action in ("remove", "address-remove")
|
||||
moves = action == "use" or (action == "retry" and link.alive()) or (is_active and (
|
||||
# (a retry while connected would cut the install's connection)
|
||||
action in ("remove", "address-remove", "forget-identity")
|
||||
or (action == "update" and (body.get("user") is not None or body.get("port") is not None))
|
||||
or (action == "address-update" and body.get("newHost") not in (None, body.get("host")))))
|
||||
if moves and busy():
|
||||
@@ -965,6 +975,8 @@ def devices_action(link, body, open_setup, busy=lambda: 0):
|
||||
if is_active and login_changed:
|
||||
link.invalidate() # before anything else can fail: the old login mustn't stay in use
|
||||
msg = f"Saved {d['name']}"
|
||||
if is_active and not login_changed:
|
||||
link.publish(device=link.public_device(link.active_device())) # a new name shows at once
|
||||
if login_changed:
|
||||
# Only what changed, and only if the block still says what it did: Set Up
|
||||
# Connection may have written a new login meanwhile, which then stands.
|
||||
|
||||
+1
-1
@@ -582,7 +582,7 @@ def open_thing(body):
|
||||
raise Failure("open that from the app", 400)
|
||||
# The headset and address in use, as every other command gets them (one snapshot).
|
||||
with _route_lock:
|
||||
alias, opts = FRAME, list(HOST_OPTS)
|
||||
alias, opts = LINK.named_route() if LINK else (FRAME, list(HOST_OPTS))
|
||||
host = next((o.split("=", 1)[1].replace("%%", "%") for o in opts if o.startswith("HostName=")), None)
|
||||
if what in ("terminal", "reboot", "poweroff", "suspend", "rdp", "sftp") and host and host.endswith(".invalid"):
|
||||
raise Failure("No headset address to use: add one on the Devices tab", 400)
|
||||
|
||||
Reference in new issue
Block a user