Add Tailscale panel connection controls
This commit is contained in:
@@ -94,6 +94,56 @@ migration=$(grep -rl 'Place the system update indicator next to weather in the b
|
||||
|
||||
TMPDIR=$(mktemp -d)
|
||||
mkdir -p "$TMPDIR/home/.config/omarchy"
|
||||
|
||||
cat >"$TMPDIR/home/.config/omarchy/shell.json" <<'JSON'
|
||||
{
|
||||
"version": 1,
|
||||
"bar": {
|
||||
"layout": {
|
||||
"left": [{ "id": "omarchy.menu" }, { "id": "omarchy.workspaces" }],
|
||||
"center": [{ "id": "omarchy.clock" }, { "id": "omarchy.weather" }],
|
||||
"right": [{ "id": "omarchy.tray" }, { "id": "omarchy.bluetooth" }]
|
||||
}
|
||||
},
|
||||
"plugins": []
|
||||
}
|
||||
JSON
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell append right omarchy.tailscale
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "omarchy.tailscale", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends right widgets after tray"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell append left local.left
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.left | ids == ["omarchy.menu", "omarchy.workspaces", "local.left"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends left widgets after workspaces"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell append center local.center
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.center | ids == ["omarchy.clock", "omarchy.weather", "local.center"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config appends center widgets after weather"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell prepend right local.first
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["local.first", "omarchy.tray", "omarchy.tailscale", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config prepends widgets to section start"
|
||||
|
||||
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" omarchy-config-shell append right local.first
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
.bar.layout.right | ids == ["omarchy.tray", "local.first", "omarchy.tailscale", "omarchy.bluetooth"]
|
||||
' "$TMPDIR/home/.config/omarchy/shell.json" >/dev/null
|
||||
pass "shell config moves existing widgets without duplicates"
|
||||
|
||||
cat >"$TMPDIR/home/.config/omarchy/shell.json" <<'JSON'
|
||||
{
|
||||
"version": 1,
|
||||
|
||||
@@ -130,7 +130,10 @@ ShellRoot {
|
||||
screenRecording.moduleName = "ScreenRecording"
|
||||
root.injectBar(screenRecording)
|
||||
screenRecording.triggerPress(Qt.LeftButton)
|
||||
root.assertTrue(root.commandCount("omarchy-capture-screenrecording") === 1, "Screen Recording left click runs capture command")
|
||||
root.assertTrue(root.commandCount("omarchy-menu toggle trigger.capture.screenrecord") === 1, "Screen Recording left click opens capture menu when idle")
|
||||
screenRecording.recording = true
|
||||
screenRecording.triggerPress(Qt.LeftButton)
|
||||
root.assertTrue(root.commandCount("omarchy-capture-screenrecording --stop-recording") === 1, "Screen Recording left click stops active recording")
|
||||
}
|
||||
|
||||
var dictation = root.createIndicator("Dictation")
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
run_node_test <<'JS'
|
||||
const tailscale = requireFromRoot('shell/plugins/panels/tailscale/Model.js')
|
||||
|
||||
assertDeepEqual(
|
||||
tailscale.filterIPv4(['100.64.0.1', 'fd7a:115c:a1e0::1', '192.168.1.2']),
|
||||
['100.64.0.1'],
|
||||
'tailscale keeps only Tailscale IPv4 addresses'
|
||||
)
|
||||
assertDeepEqual(
|
||||
tailscale.filterIPv6(['100.64.0.1', 'fd7a:115c:a1e0::1', 'fe80::1']),
|
||||
['fd7a:115c:a1e0::1'],
|
||||
'tailscale keeps only Tailscale IPv6 addresses'
|
||||
)
|
||||
|
||||
assertEqual(tailscale.cleanDnsName('work.tailnet.ts.net.'), 'work.tailnet.ts.net', 'tailscale strips trailing DNS dot')
|
||||
assertEqual(tailscale.displayHostName('localhost', 'work.tailnet.ts.net.'), 'work', 'tailscale falls back from localhost to short DNS name')
|
||||
|
||||
const status = tailscale.parseStatus(JSON.stringify({
|
||||
BackendState: 'Running',
|
||||
AuthURL: '',
|
||||
TailscaleIPs: ['100.74.97.73', 'fd7a:115c:a1e0::ff32:6149'],
|
||||
Self: {
|
||||
HostName: 'dhh-fd',
|
||||
DNSName: 'dhh-fd.tail32f559.ts.net.',
|
||||
TailscaleIPs: ['100.74.97.73']
|
||||
},
|
||||
Peer: {
|
||||
onlineB: {
|
||||
HostName: 'zed',
|
||||
DNSName: 'zed.tail32f559.ts.net.',
|
||||
TailscaleIPs: ['100.1.1.2'],
|
||||
Online: true,
|
||||
OS: 'linux',
|
||||
ExitNodeOption: true,
|
||||
ExitNode: true
|
||||
},
|
||||
offline: {
|
||||
HostName: 'offline',
|
||||
DNSName: 'offline.tail32f559.ts.net.',
|
||||
TailscaleIPs: ['100.1.1.3'],
|
||||
Online: false,
|
||||
OS: 'linux'
|
||||
},
|
||||
onlineA: {
|
||||
HostName: 'alpha',
|
||||
DNSName: 'alpha.tail32f559.ts.net.',
|
||||
TailscaleIPs: ['100.1.1.1', 'fd7a:115c:a1e0::1901:334b'],
|
||||
Online: true,
|
||||
OS: 'macos'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
assert(status.ok && status.running, 'tailscale parses running status')
|
||||
assertEqual(status.selfIp, '100.74.97.73', 'tailscale parses self IP')
|
||||
assertDeepEqual(status.peers.map(peer => peer.HostName), ['alpha', 'zed'], 'tailscale filters offline peers and sorts online peers')
|
||||
assertDeepEqual(status.peers[0].TailscaleIPv6, ['fd7a:115c:a1e0::1901:334b'], 'tailscale preserves peer IPv6 addresses for copy menu')
|
||||
assert(status.peers[1].ExitNodeOption && status.peers[1].ExitNode, 'tailscale preserves exit node flags')
|
||||
|
||||
const stopped = tailscale.parseStatus(JSON.stringify({
|
||||
BackendState: 'Stopped',
|
||||
Peer: {
|
||||
online: {
|
||||
HostName: 'alpha',
|
||||
DNSName: 'alpha.tail32f559.ts.net.',
|
||||
TailscaleIPs: ['100.1.1.1'],
|
||||
Online: true,
|
||||
OS: 'macos'
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
assert(stopped.ok && !stopped.running, 'tailscale parses stopped status')
|
||||
|
||||
const accounts = tailscale.parseAccounts(JSON.stringify([
|
||||
{
|
||||
id: 'db1b',
|
||||
nickname: 'Home',
|
||||
tailnet: 'dhh.github',
|
||||
account: 'dhh@github',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: '1785',
|
||||
nickname: 'Work',
|
||||
tailnet: '37signals.com',
|
||||
account: 'david@37signals.com',
|
||||
selected: false
|
||||
}
|
||||
]))
|
||||
|
||||
assertEqual(accounts.accounts.length, 2, 'tailscale parses multiple connections')
|
||||
assertEqual(accounts.selectedAccountId, 'db1b', 'tailscale records selected connection id')
|
||||
assertEqual(accounts.selectedAccountLabel, 'Home', 'tailscale labels connections by nickname')
|
||||
assertDeepEqual(
|
||||
accounts.accounts.map(account => account.nickname),
|
||||
['Home', 'Work'],
|
||||
'tailscale preserves connection nicknames'
|
||||
)
|
||||
assertEqual(
|
||||
tailscale.accountLabel({ nickname: '', tailnet: 'tailnet.example', account: 'user@example', id: 'abcd' }),
|
||||
'tailnet.example',
|
||||
'tailscale labels connections by tailnet when nickname is missing'
|
||||
)
|
||||
|
||||
assertDeepEqual(tailscale.parseStatus('{'), { ok: false, unavailable: true, message: 'Status error', error: 'Failed to parse tailscale status' }, 'tailscale reports invalid status JSON')
|
||||
assertDeepEqual(tailscale.parseAccounts('{'), { accounts: [], selectedAccountId: '', selectedAccountLabel: '' }, 'tailscale handles invalid account JSON')
|
||||
JS
|
||||
Reference in New Issue
Block a user