mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 06:00:33 +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,
|
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
|
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
|
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
|
connected, **Reconnect** applies your changes now rather than at the next
|
||||||
address just added or moved up is used now rather than at the next connection.
|
connection: it tries the addresses again, ranked as above, and the best-ranked
|
||||||
It doesn't pick a particular address: the first to answer in order wins, and a
|
one that answers promptly wins. It doesn't pick a particular address.
|
||||||
slow one loses to a later one.
|
|
||||||
|
|
||||||
## Networks
|
## Networks
|
||||||
|
|
||||||
|
|||||||
+28
-14
@@ -459,27 +459,41 @@ class Connecting(unittest.TestCase):
|
|||||||
|
|
||||||
def test_a_test_started_earlier_cant_overwrite_a_newer_one(self):
|
def test_a_test_started_earlier_cant_overwrite_a_newer_one(self):
|
||||||
d = self.device("nothing.invalid")
|
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):
|
def probe(host, port, update=None):
|
||||||
calls.append(host)
|
calls.append(host)
|
||||||
if len(calls) == 1:
|
n = len(calls)
|
||||||
release.wait(10) # the first test is still probing when the second finishes
|
entered[n].set()
|
||||||
return {"state": "refused", "detail": "first test", "ip": None, "rtt_ms": None}
|
release[n].wait(10)
|
||||||
return {"state": "refused", "detail": "second test", "ip": None, "rtt_ms": None}
|
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):
|
with mock.patch.object(fl, "probe", probe):
|
||||||
first = threading.Thread(target=self.link.test, args=(d["id"],))
|
try:
|
||||||
first.start()
|
tests[0].start()
|
||||||
while not calls:
|
self.assertTrue(entered[1].wait(5), "the first test never probed")
|
||||||
time.sleep(0.01)
|
tests[1].start()
|
||||||
self.link.test(d["id"])
|
self.assertTrue(entered[2].wait(5), "the second test never probed")
|
||||||
self.assertEqual(self.link.snapshot()["tests"][d["id"]]["rows"][0]["detail"], "second test")
|
# The first finishes while the second is still probing: it mustn't show its
|
||||||
release.set()
|
# rows or mark the second done.
|
||||||
first.join(10)
|
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"]]
|
result = self.link.snapshot()["tests"][d["id"]]
|
||||||
self.assertTrue(result["done"])
|
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):
|
def test_switching_to_a_headset_that_never_answers_stops_using_the_last_one(self):
|
||||||
self.device("localhost")
|
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)); }
|
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: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); }
|
.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);
|
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; }
|
opacity: 0; transform: translateY(10px); transition: opacity .2s, transform .2s; pointer-events: none; }
|
||||||
.toast.show { opacity: 1; transform: none; }
|
.toast.show { opacity: 1; transform: none; }
|
||||||
@@ -1439,7 +1439,7 @@
|
|||||||
<button type="submit" class="small danger" id="rmGo">Remove</button></div>
|
<button type="submit" class="small danger" id="rmGo">Remove</button></div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast" popover="manual"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
@@ -1483,6 +1483,12 @@ function toast(msg, err = false) {
|
|||||||
const el = $("toast");
|
const el = $("toast");
|
||||||
el.textContent = msg.split("\n").slice(-1)[0] || msg;
|
el.textContent = msg.split("\n").slice(-1)[0] || msg;
|
||||||
el.className = "toast show" + (err ? " err" : "");
|
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);
|
clearTimeout(toastTimer);
|
||||||
toastTimer = setTimeout(() => el.className = "toast" + (err ? " err" : ""), 3500);
|
toastTimer = setTimeout(() => el.className = "toast" + (err ? " err" : ""), 3500);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user