Rework the CONNECTIONS section
- Label each connection with its tailnet and account rather than the nickname alone. `tailscale switch --list --json` carries both, and on a multi-tailnet setup the nickname is the least useful field — "andre" says nothing about which tailnet you would be switching to. Renders as "headscale.pertento.ai (andre)", falling back through tailnet, then account, then profile id when a field is missing. - Mark the active connection with colour instead of a filled background: brand yellow on its label, currentFill cleared. The theme sets selected-border-width to 0, so clearing the fill leaves nothing drawn. Hover still highlights through `fill`, so the row keeps its affordance. Exit-node and Mullvad-region rows are untouched and still fill. - Sort the active connection to the top. Done in parseAccounts rather than at render time: the keyboard cursor indexes into tailscale.accounts, so sorting only in the view would leave j/k acting on a different row than the highlighted one. Array.sort is stable, so the remaining profiles keep the CLI's order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -57,11 +57,25 @@ function osIcon(os) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stock returned the nickname alone, which on a multi-tailnet setup is the
|
||||||
|
// least useful field — "andre" and "pastilhas" say nothing about which tailnet
|
||||||
|
// you would be switching to. `tailscale switch --list --json` carries both, so
|
||||||
|
// show the tailnet with the account in parentheses:
|
||||||
|
//
|
||||||
|
// headscale.pertento.ai (andre)
|
||||||
|
// offscale.pastilhas.dev (pastilhas)
|
||||||
|
//
|
||||||
|
// Falls back through the same chain as before when a field is absent, so a
|
||||||
|
// profile with no tailnet still renders something meaningful.
|
||||||
function accountLabel(account) {
|
function accountLabel(account) {
|
||||||
if (!account) return "Unknown account"
|
if (!account) return "Unknown account"
|
||||||
if (account.nickname) return String(account.nickname)
|
|
||||||
if (account.tailnet) return String(account.tailnet)
|
var tailnet = String(account.tailnet || "").trim()
|
||||||
if (account.account) return String(account.account)
|
var who = String(account.account || account.nickname || "").trim()
|
||||||
|
|
||||||
|
if (tailnet !== "" && who !== "") return tailnet + " (" + who + ")"
|
||||||
|
if (tailnet !== "") return tailnet
|
||||||
|
if (who !== "") return who
|
||||||
return String(account.id || "Unknown account")
|
return String(account.id || "Unknown account")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +306,19 @@ function parseAccounts(raw) {
|
|||||||
if (account.selected === true) selected = account
|
if (account.selected === true) selected = account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active connection first, everything else in the order the CLI listed it.
|
||||||
|
// Sorted here rather than in the view so the Repeater and the keyboard
|
||||||
|
// cursor (accountIndex) index into the same ordering — sorting only at
|
||||||
|
// render time would leave j/k selecting a different row than the one
|
||||||
|
// highlighted. Array.sort is stable, so the unselected profiles keep their
|
||||||
|
// relative order.
|
||||||
|
next.sort(function(left, right) {
|
||||||
|
var l = left.selected === true ? 0 : 1
|
||||||
|
var r = right.selected === true ? 0 : 1
|
||||||
|
return l - r
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
accounts: next,
|
accounts: next,
|
||||||
selectedAccountId: selected ? String(selected.id || "") : "",
|
selectedAccountId: selected ? String(selected.id || "") : "",
|
||||||
|
|||||||
@@ -854,7 +854,12 @@ Panel {
|
|||||||
current: selectedAccount
|
current: selectedAccount
|
||||||
foreground: root.foreground
|
foreground: root.foreground
|
||||||
fill: root.hoverFill
|
fill: root.hoverFill
|
||||||
currentFill: root.selectedFill
|
// No filled background on the active connection — it reads as the brand
|
||||||
|
// yellow on its label instead (see the Text below). `current` also selects
|
||||||
|
// the "selected" border spec, but the theme sets selected-border-width to
|
||||||
|
// 0, so clearing the fill leaves nothing drawn. Hover still highlights
|
||||||
|
// through `fill`, so the row keeps its affordance.
|
||||||
|
currentFill: "transparent"
|
||||||
|
|
||||||
implicitHeight: accountInner.implicitHeight + Style.spacing.xl
|
implicitHeight: accountInner.implicitHeight + Style.spacing.xl
|
||||||
|
|
||||||
@@ -888,7 +893,9 @@ Panel {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: accountRow.accountText
|
text: accountRow.accountText
|
||||||
color: root.foreground
|
// The active connection is marked by colour rather than a filled
|
||||||
|
// background: brand yellow here, themed white for the others.
|
||||||
|
color: accountRow.selectedAccount ? root.accent : root.foreground
|
||||||
font.family: root.fontFamily
|
font.family: root.fontFamily
|
||||||
font.pixelSize: Style.font.body
|
font.pixelSize: Style.font.body
|
||||||
font.bold: accountRow.selectedAccount
|
font.bold: accountRow.selectedAccount
|
||||||
|
|||||||
Reference in New Issue
Block a user