Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions libts/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,17 @@ func SetExitNode(ctx context.Context, peer *ipnstate.PeerStatus) error {

return nil
}

// List all profiles on this Tailscale client.
func ListProfiles(ctx context.Context) ([]ipn.LoginProfile, error) {
_, all, err := ts.ProfileStatus(ctx)
if err != nil {
return nil, err
}
return all, nil
}

// Switch to the given Tailscale profile.
func SwitchProfile(ctx context.Context, profileID ipn.ProfileID) error {
return ts.SwitchProfile(ctx, profileID)
}
45 changes: 41 additions & 4 deletions menus.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,13 +250,45 @@ func (m *model) updateMenus() {
exitNode = "Exit Node"
}

accountTitle := "Account"
accountsTitle := "Accounts"
reauthenticateButtonLabel := "[Reauthenticate]"
if m.state.Self.KeyExpiry != nil {
reauthenticateButtonLabel = "[Reauthenticate Now]"

duration := time.Until(*m.state.Self.KeyExpiry)
accountTitle += " - Key Expires in " + ui.FormatDuration(duration)
accountsTitle += " - Current Key Expires in " + ui.FormatDuration(duration)
}

profiles, err := libts.ListProfiles(ctx)
var profilesItems []ui.SubmenuItem
if err != nil {
profilesItems = []ui.SubmenuItem{
&ui.LabeledSubmenuItem{
Label: "Failed to load accounts list, try run tsui as sudo.",
},
}
} else {
// Create a submenu item for each profile
for _, profile := range profiles {
// Highlight the current profile
variant := ui.SubmenuItemVariantDefault
if m.state.User.LoginName == profile.Name {
variant = ui.SubmenuItemVariantAccent
}
// Append the profile item
profilesItems = append(profilesItems, &ui.LabeledSubmenuItem{
Label: string(profile.ID),
AdditionalLabel: profile.Name,
Variant: variant,
OnActivate: func() tea.Msg {
if err := libts.SwitchProfile(ctx, profile.ID); err != nil {
return errorMsg(err)
}

return successMsg(fmt.Sprintf("Switched to profile: %s", profile.Name))
},
})
}
}

submenuItems := []ui.SubmenuItem{
Expand Down Expand Up @@ -327,8 +359,13 @@ func (m *model) updateMenus() {
),

&ui.SpacerSubmenuItem{},
&ui.TitleSubmenuItem{Label: accountTitle},
&ui.TitleSubmenuItem{Label: accountsTitle},
}

// Append all profile items dynamically
submenuItems = append(submenuItems, profilesItems...)

submenuItems = append(submenuItems,
&ui.LabeledSubmenuItem{
Label: reauthenticateButtonLabel,
// Reauthenticating is basically the same as the first-time login flow.
Expand All @@ -346,7 +383,7 @@ func (m *model) updateMenus() {
return successMsg("Logged out.")
},
},
}
)

// On Linux, show the advanced Linux settings.
if runtime.GOOS == "linux" {
Expand Down