@ -72,6 +72,11 @@ type Menu struct {
curProfile ipn . LoginProfile
curProfile ipn . LoginProfile
allProfiles [ ] ipn . LoginProfile
allProfiles [ ] ipn . LoginProfile
// readonly is whether the systray app is running in read-only mode.
// This is set if LocalAPI returns a permission error,
// typically because the user needs to run `tailscale set --operator=$USER`.
readonly bool
bgCtx context . Context // ctx for background tasks not involving menu item clicks
bgCtx context . Context // ctx for background tasks not involving menu item clicks
bgCancel context . CancelFunc
bgCancel context . CancelFunc
@ -153,6 +158,8 @@ func (menu *Menu) updateState() {
defer menu . mu . Unlock ( )
defer menu . mu . Unlock ( )
menu . init ( )
menu . init ( )
menu . readonly = false
var err error
var err error
menu . status , err = menu . lc . Status ( menu . bgCtx )
menu . status , err = menu . lc . Status ( menu . bgCtx )
if err != nil {
if err != nil {
@ -160,6 +167,9 @@ func (menu *Menu) updateState() {
}
}
menu . curProfile , menu . allProfiles , err = menu . lc . ProfileStatus ( menu . bgCtx )
menu . curProfile , menu . allProfiles , err = menu . lc . ProfileStatus ( menu . bgCtx )
if err != nil {
if err != nil {
if local . IsAccessDeniedError ( err ) {
menu . readonly = true
}
log . Print ( err )
log . Print ( err )
}
}
}
}
@ -182,6 +192,15 @@ func (menu *Menu) rebuild() {
systray . ResetMenu ( )
systray . ResetMenu ( )
if menu . readonly {
const readonlyMsg = "No permission to manage Tailscale.\nSee tailscale.com/s/cli-operator"
m := systray . AddMenuItem ( readonlyMsg , "" )
onClick ( ctx , m , func ( _ context . Context ) {
webbrowser . Open ( "https://tailscale.com/s/cli-operator" )
} )
systray . AddSeparator ( )
}
menu . connect = systray . AddMenuItem ( "Connect" , "" )
menu . connect = systray . AddMenuItem ( "Connect" , "" )
menu . disconnect = systray . AddMenuItem ( "Disconnect" , "" )
menu . disconnect = systray . AddMenuItem ( "Disconnect" , "" )
menu . disconnect . Hide ( )
menu . disconnect . Hide ( )
@ -222,10 +241,16 @@ func (menu *Menu) rebuild() {
setAppIcon ( disconnected )
setAppIcon ( disconnected )
}
}
if menu . readonly {
menu . connect . Disable ( )
menu . disconnect . Disable ( )
}
account := "Account"
account := "Account"
if pt := profileTitle ( menu . curProfile ) ; pt != "" {
if pt := profileTitle ( menu . curProfile ) ; pt != "" {
account = pt
account = pt
}
}
if ! menu . readonly {
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 )
@ -245,6 +270,7 @@ func (menu *Menu) rebuild() {
}
}
} )
} )
}
}
}
if menu . status != nil && menu . status . Self != nil && len ( menu . status . Self . TailscaleIPs ) > 0 {
if menu . status != nil && menu . status . Self != nil && len ( menu . status . Self . TailscaleIPs ) > 0 {
title := fmt . Sprintf ( "This Device: %s (%s)" , menu . status . Self . HostName , menu . status . Self . TailscaleIPs [ 0 ] )
title := fmt . Sprintf ( "This Device: %s (%s)" , menu . status . Self . HostName , menu . status . Self . TailscaleIPs [ 0 ] )
@ -255,7 +281,9 @@ func (menu *Menu) rebuild() {
}
}
systray . AddSeparator ( )
systray . AddSeparator ( )
if ! menu . readonly {
menu . rebuildExitNodeMenu ( ctx )
menu . rebuildExitNodeMenu ( ctx )
}
if menu . status != nil {
if menu . status != nil {
menu . more = systray . AddMenuItem ( "More settings" , "" )
menu . more = systray . AddMenuItem ( "More settings" , "" )