mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 01:00:18 +02:00
Merge branch 'ui-3-home' into ui-4-store
This commit is contained in:
commit
cc9af9eba4
3 files changed
+39
-20
No files matched your search
+3
-4
@@ -79,10 +79,9 @@ the headset reports a LAN IP on the same network as this computer and that IP
|
||||
isn't saved, it offers to add it. The offer puts the address first in the list,
|
||||
so on that network it wins over the Tailscale name; away from home the Tailscale
|
||||
name still leads. A new or edited address is tested straight away. While
|
||||
connected, **Reconnect** tries the addresses again in their current order, so an
|
||||
address just added or moved up is used now rather than at the next connection.
|
||||
It doesn't pick a particular address: the first to answer in order wins, and a
|
||||
slow one loses to a later one.
|
||||
connected, **Reconnect** applies your changes now rather than at the next
|
||||
connection: it tries the addresses again, ranked as above, and the best-ranked
|
||||
one that answers promptly wins. It doesn't pick a particular address.
|
||||
|
||||
## Networks
|
||||
|
||||
|
||||
+28
-14
@@ -459,27 +459,41 @@ class Connecting(unittest.TestCase):
|
||||
|
||||
def test_a_test_started_earlier_cant_overwrite_a_newer_one(self):
|
||||
d = self.device("nothing.invalid")
|
||||
release, calls = threading.Event(), []
|
||||
entered = {1: threading.Event(), 2: threading.Event()}
|
||||
release = {1: threading.Event(), 2: threading.Event()}
|
||||
calls = []
|
||||
|
||||
def probe(host, port, update=None):
|
||||
calls.append(host)
|
||||
if len(calls) == 1:
|
||||
release.wait(10) # the first test is still probing when the second finishes
|
||||
return {"state": "refused", "detail": "first test", "ip": None, "rtt_ms": None}
|
||||
return {"state": "refused", "detail": "second test", "ip": None, "rtt_ms": None}
|
||||
n = len(calls)
|
||||
entered[n].set()
|
||||
release[n].wait(10)
|
||||
return {"state": "refused", "detail": f"test {n}", "ip": None, "rtt_ms": None}
|
||||
|
||||
tests = [threading.Thread(target=self.link.test, args=(d["id"],), daemon=True) for _ in range(2)]
|
||||
with mock.patch.object(fl, "probe", probe):
|
||||
first = threading.Thread(target=self.link.test, args=(d["id"],))
|
||||
first.start()
|
||||
while not calls:
|
||||
time.sleep(0.01)
|
||||
self.link.test(d["id"])
|
||||
self.assertEqual(self.link.snapshot()["tests"][d["id"]]["rows"][0]["detail"], "second test")
|
||||
release.set()
|
||||
first.join(10)
|
||||
try:
|
||||
tests[0].start()
|
||||
self.assertTrue(entered[1].wait(5), "the first test never probed")
|
||||
tests[1].start()
|
||||
self.assertTrue(entered[2].wait(5), "the second test never probed")
|
||||
# The first finishes while the second is still probing: it mustn't show its
|
||||
# rows or mark the second done.
|
||||
release[1].set()
|
||||
tests[0].join(10)
|
||||
self.assertFalse(tests[0].is_alive())
|
||||
running = self.link.snapshot()["tests"][d["id"]]
|
||||
self.assertFalse(running["done"])
|
||||
self.assertEqual(running["rows"][0]["detail"], "Waiting")
|
||||
release[2].set()
|
||||
tests[1].join(10)
|
||||
self.assertFalse(tests[1].is_alive())
|
||||
finally:
|
||||
for e in release.values():
|
||||
e.set()
|
||||
result = self.link.snapshot()["tests"][d["id"]]
|
||||
self.assertTrue(result["done"])
|
||||
self.assertEqual(result["rows"][0]["detail"], "second test")
|
||||
self.assertEqual(result["rows"][0]["detail"], "test 2")
|
||||
|
||||
def test_switching_to_a_headset_that_never_answers_stops_using_the_last_one(self):
|
||||
self.device("localhost")
|
||||
|
||||
+8
-2
@@ -394,7 +394,7 @@
|
||||
white-space: pre-wrap; padding: 12px calc(28px + env(safe-area-inset-right)) 12px calc(28px + env(safe-area-inset-left)); }
|
||||
.log:empty::before { content: "Actions you take here are logged with their output."; color: var(--dim); }
|
||||
.log .e { color: #ff8a73; } .log .ok { color: #a4d007; } .log time { color: var(--dim); }
|
||||
.toast { position: fixed; bottom: calc(58px + env(safe-area-inset-bottom)); right: calc(28px + env(safe-area-inset-right)); z-index: 30; max-width: 460px; padding: 12px 16px; border-radius: 3px;
|
||||
.toast { position: fixed; inset: auto; margin: 0; border: 0; overflow: visible; width: auto; height: auto; bottom: calc(58px + env(safe-area-inset-bottom)); right: calc(28px + env(safe-area-inset-right)); z-index: 30; max-width: 460px; padding: 12px 16px; border-radius: 3px;
|
||||
background: #3d4450; color: #fff; box-shadow: 0 10px 30px rgba(0,0,0,.5); border-left: 3px solid var(--blue);
|
||||
opacity: 0; transform: translateY(10px); transition: opacity .2s, transform .2s; pointer-events: none; }
|
||||
.toast.show { opacity: 1; transform: none; }
|
||||
@@ -1439,7 +1439,7 @@
|
||||
<button type="submit" class="small danger" id="rmGo">Remove</button></div>
|
||||
</form>
|
||||
</dialog>
|
||||
<div class="toast" id="toast"></div>
|
||||
<div class="toast" id="toast" popover="manual"></div>
|
||||
|
||||
<script>
|
||||
const $ = (id) => document.getElementById(id);
|
||||
@@ -1483,6 +1483,12 @@ function toast(msg, err = false) {
|
||||
const el = $("toast");
|
||||
el.textContent = msg.split("\n").slice(-1)[0] || msg;
|
||||
el.className = "toast show" + (err ? " err" : "");
|
||||
// A popover, so it shows above an open dialog (a modal one covers everything else): opened
|
||||
// again when a dialog has opened since, which puts it back on top.
|
||||
if (el.showPopover && (!el.matches(":popover-open") || document.querySelector("dialog:modal"))) {
|
||||
try { el.hidePopover(); } catch {}
|
||||
el.showPopover();
|
||||
}
|
||||
clearTimeout(toastTimer);
|
||||
toastTimer = setTimeout(() => el.className = "toast" + (err ? " err" : ""), 3500);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user