client/systray: don't repeat account name for single-user tailnets (#19930)
Single-user tailnets often have the same tailnet display name as login name. This change omits the duplication when matching, and skips the user-switching submenu when only one account is configured, to clean up the account display a little bit. Fixes #16889 Signed-off-by: Evan Lowry <evan@tailscale.com>
This commit is contained in:
+35
-22
@@ -292,21 +292,23 @@ func (menu *Menu) rebuild() {
|
|||||||
accounts := systray.AddMenuItem(account, "")
|
accounts := systray.AddMenuItem(account, "")
|
||||||
setRemoteIcon(accounts, menu.curProfile.UserProfile.ProfilePicURL)
|
setRemoteIcon(accounts, menu.curProfile.UserProfile.ProfilePicURL)
|
||||||
time.Sleep(newMenuDelay)
|
time.Sleep(newMenuDelay)
|
||||||
for _, profile := range menu.allProfiles {
|
if len(menu.allProfiles) > 1 {
|
||||||
title := profileTitle(profile)
|
for _, profile := range menu.allProfiles {
|
||||||
var item *systray.MenuItem
|
title := profileTitle(profile)
|
||||||
if profile.ID == menu.curProfile.ID {
|
var item *systray.MenuItem
|
||||||
item = accounts.AddSubMenuItemCheckbox(title, "", true)
|
if profile.ID == menu.curProfile.ID {
|
||||||
} else {
|
item = accounts.AddSubMenuItemCheckbox(title, "", true)
|
||||||
item = accounts.AddSubMenuItem(title, "")
|
} else {
|
||||||
}
|
item = accounts.AddSubMenuItem(title, "")
|
||||||
setRemoteIcon(item, profile.UserProfile.ProfilePicURL)
|
|
||||||
onClick(ctx, item, func(ctx context.Context) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case menu.accountsCh <- profile.ID:
|
|
||||||
}
|
}
|
||||||
})
|
setRemoteIcon(item, profile.UserProfile.ProfilePicURL)
|
||||||
|
onClick(ctx, item, func(ctx context.Context) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case menu.accountsCh <- profile.ID:
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,16 +354,27 @@ func (menu *Menu) rebuild() {
|
|||||||
|
|
||||||
// profileTitle returns the title string for a profile menu item.
|
// profileTitle returns the title string for a profile menu item.
|
||||||
func profileTitle(profile ipn.LoginProfile) string {
|
func profileTitle(profile ipn.LoginProfile) string {
|
||||||
title := profile.Name
|
tailnet := ""
|
||||||
if profile.NetworkProfile.DomainName != "" {
|
if profile.NetworkProfile.DomainName != "" {
|
||||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
tailnet = profile.NetworkProfile.DisplayNameOrDefault()
|
||||||
// windows and mac don't support multi-line menu
|
|
||||||
title += " (" + profile.NetworkProfile.DisplayNameOrDefault() + ")"
|
|
||||||
} else {
|
|
||||||
title += "\n" + profile.NetworkProfile.DisplayNameOrDefault()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return title
|
// windows and mac don't support multi-line menu items.
|
||||||
|
multiline := runtime.GOOS != "windows" && runtime.GOOS != "darwin"
|
||||||
|
|
||||||
|
return formatProfileTitle(profile.Name, tailnet, multiline)
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatProfileTitle builds a profile menu label from a login name and an
|
||||||
|
// optional tailnet name. The tailnet portion is omitted when it matches the
|
||||||
|
// login name, so single-user tailnets don't show the same string twice.
|
||||||
|
func formatProfileTitle(name, tailnet string, multiline bool) string {
|
||||||
|
if tailnet == "" || strings.EqualFold(name, tailnet) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
if multiline {
|
||||||
|
return name + "\n" + tailnet
|
||||||
|
}
|
||||||
|
return name + " (" + tailnet + ")"
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -13,6 +13,33 @@ import (
|
|||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestProfileTitleMultiline(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
login string
|
||||||
|
tailnet string
|
||||||
|
multiline bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"no_tailnet", "alice@example.com", "", true, "alice@example.com"},
|
||||||
|
{"dup_exact", "example.com", "example.com", true, "example.com"},
|
||||||
|
{"dup_casefold", "Example.com", "example.com", false, "Example.com"},
|
||||||
|
{"distinct_multiline", "alice@example.com", "example.com", true, "alice@example.com\nexample.com"},
|
||||||
|
{"distinct_singleline", "alice@example.com", "example.com", false, "alice@example.com (example.com)"},
|
||||||
|
{"empty", "", "", true, ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := formatProfileTitle(tt.login, tt.tailnet, tt.multiline); got != tt.want {
|
||||||
|
t.Errorf("profileTitleMultiline; got %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRecommendedIsActive(t *testing.T) {
|
func TestRecommendedIsActive(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user