Add Settings window to Agent UI

Agent systray UI has been extended with
a setting window that allows configuring 
management URL, admin URL and 
supports pre-shared key.
While for the Netbird managed version 
the Settings are not necessary, it helps
to properly configure the self-hosted version.
This commit is contained in:
Givi Khojanashvili
2022-04-15 19:30:12 +04:00
committed by GitHub
parent 196207402d
commit 951e011a9c
25 changed files with 767 additions and 244 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"runtime"
"strconv"
@@ -20,18 +21,24 @@ import (
"github.com/netbirdio/netbird/client/proto"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
)
const (
defaulFailTimeout = time.Duration(3 * time.Second)
fastFailTimeout = time.Second
)
func main() {
var daemonAddr string
if err := checkPIDFile(); err != nil {
fmt.Println(err)
return
}
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731"
@@ -42,37 +49,177 @@ func main() {
defaultDaemonAddr,
"Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
var showSettings bool
flag.BoolVar(&showSettings, "settings", false, "run settings windows")
flag.Parse()
client := newServiceClient(daemonAddr)
systray.Run(client.onTrayReady, client.onTrayExit)
a := app.New()
client := newServiceClient(daemonAddr, a, showSettings)
if showSettings {
a.Run()
} else {
if err := checkPIDFile(); err != nil {
fmt.Println(err)
return
}
systray.Run(client.onTrayReady, client.onTrayExit)
}
}
//go:embed connected.ico
var iconConnected []byte
var iconConnectedICO []byte
//go:embed connected.png
var iconConnectedPNG []byte
//go:embed disconnected.ico
var iconDisconnected []byte
var iconDisconnectedICO []byte
//go:embed disconnected.png
var iconDisconnectedPNG []byte
type serviceClient struct {
ctx context.Context
addr string
conn proto.DaemonServiceClient
mStatus *systray.MenuItem
mUp *systray.MenuItem
mDown *systray.MenuItem
ctx context.Context
addr string
conn proto.DaemonServiceClient
icConnected []byte
icDisconnected []byte
// systray menu itmes
mStatus *systray.MenuItem
mUp *systray.MenuItem
mDown *systray.MenuItem
mAdminPanel *systray.MenuItem
mSettings *systray.MenuItem
mQuit *systray.MenuItem
// application with main windows.
app fyne.App
wSettings fyne.Window
showSettings bool
// input elements for settings form
iMngURL *widget.Entry
iAdminURL *widget.Entry
iConfigFile *widget.Entry
iLogFile *widget.Entry
iPreSharedKey *widget.Entry
// observable settings over correspondign iMngURL and iPreSharedKey values.
managementURL string
preSharedKey string
adminURL string
}
func newServiceClient(addr string) *serviceClient {
// newServiceClient instance constructor
//
// This constructor olso build UI elements for settings window.
func newServiceClient(addr string, a fyne.App, showSettings bool) *serviceClient {
s := &serviceClient{
ctx: context.Background(),
addr: addr,
app: a,
showSettings: showSettings,
}
if runtime.GOOS == "windows" {
s.icConnected = iconConnectedICO
s.icDisconnected = iconDisconnectedICO
} else {
s.icConnected = iconConnectedPNG
s.icDisconnected = iconDisconnectedPNG
}
if showSettings {
s.showUIElements()
return s
}
return s
}
func (s *serviceClient) up() error {
conn, err := s.client()
func (s *serviceClient) showUIElements() {
// add settings window UI elements.
s.wSettings = s.app.NewWindow("Settings")
s.iMngURL = widget.NewEntry()
s.iAdminURL = widget.NewEntry()
s.iConfigFile = widget.NewEntry()
s.iConfigFile.Disable()
s.iLogFile = widget.NewEntry()
s.iLogFile.Disable()
s.iPreSharedKey = widget.NewPasswordEntry()
s.wSettings.SetContent(s.getSettingsForm())
s.wSettings.Resize(fyne.NewSize(600, 100))
s.getSrvConfig()
s.wSettings.Show()
}
// getSettingsForm to embed it into settings window.
func (s *serviceClient) getSettingsForm() *widget.Form {
return &widget.Form{
Items: []*widget.FormItem{
{Text: "Management URL", Widget: s.iMngURL},
{Text: "Admin URL", Widget: s.iAdminURL},
{Text: "Pre-shared Key", Widget: s.iPreSharedKey},
{Text: "Config File", Widget: s.iConfigFile},
{Text: "Log File", Widget: s.iLogFile},
},
SubmitText: "Save",
OnSubmit: func() {
if s.iPreSharedKey.Text != "" && s.iPreSharedKey.Text != "**********" {
// validate preSharedKey if it added
if _, err := wgtypes.ParseKey(s.iPreSharedKey.Text); err != nil {
dialog.ShowError(fmt.Errorf("Invalid Pre-shared Key Value"), s.wSettings)
return
}
}
defer s.wSettings.Close()
// if management URL or Pre-shared key changed, we try to re-login with new settings.
if s.managementURL != s.iMngURL.Text || s.preSharedKey != s.iPreSharedKey.Text ||
s.adminURL != s.iAdminURL.Text {
s.managementURL = s.iMngURL.Text
s.preSharedKey = s.iPreSharedKey.Text
s.adminURL = s.iAdminURL.Text
client, err := s.getSrvClient(fastFailTimeout)
if err != nil {
log.Errorf("get daemon client: %v", err)
return
}
_, err = client.Login(s.ctx, &proto.LoginRequest{
ManagementUrl: s.iMngURL.Text,
AdminURL: s.iAdminURL.Text,
PreSharedKey: s.iPreSharedKey.Text,
})
if err != nil {
log.Errorf("login to management URL: %v", err)
return
}
_, err = client.Up(s.ctx, &proto.UpRequest{})
if err != nil {
log.Errorf("login to management URL: %v", err)
return
}
}
s.wSettings.Close()
},
OnCancel: func() {
s.wSettings.Close()
},
}
}
func (s *serviceClient) menuUpClick() error {
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return err
@@ -97,8 +244,8 @@ func (s *serviceClient) up() error {
return nil
}
func (s *serviceClient) down() error {
conn, err := s.client()
func (s *serviceClient) menuDownClick() error {
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return err
@@ -124,7 +271,7 @@ func (s *serviceClient) down() error {
}
func (s *serviceClient) updateStatus() {
conn, err := s.client()
conn, err := s.getSrvClient(defaulFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return
@@ -137,12 +284,12 @@ func (s *serviceClient) updateStatus() {
}
if status.Status == string(internal.StatusConnected) {
systray.SetTemplateIcon(iconConnected, iconConnected)
systray.SetIcon(s.icConnected)
s.mStatus.SetTitle("Connected")
s.mUp.Disable()
s.mDown.Enable()
} else {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
systray.SetIcon(s.icDisconnected)
s.mStatus.SetTitle("Disconnected")
s.mDown.Disable()
s.mUp.Enable()
@@ -150,25 +297,23 @@ func (s *serviceClient) updateStatus() {
}
func (s *serviceClient) onTrayReady() {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
systray.SetIcon(s.icDisconnected)
// setup systray menu items
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
s.mStatus.Disable()
systray.AddSeparator()
s.mUp = systray.AddMenuItem("Connect", "Connect")
s.mDown = systray.AddMenuItem("Disconnect", "Disconnect")
s.mDown.Disable()
mURL := systray.AddMenuItem("Admin Panel", "Wiretrustee Admin Panel")
s.mAdminPanel = systray.AddMenuItem("Admin Panel", "Wiretrustee Admin Panel")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Quit the client app")
s.mSettings = systray.AddMenuItem("Settings", "Settings of the application")
systray.AddSeparator()
s.mQuit = systray.AddMenuItem("Quit", "Quit the client app")
go func() {
s.getSrvConfig()
for {
s.updateStatus()
time.Sleep(time.Second * 3)
@@ -179,19 +324,42 @@ func (s *serviceClient) onTrayReady() {
var err error
for {
select {
case <-mURL.ClickedCh:
err = open.Run("https://app.wiretrustee.com")
case <-s.mAdminPanel.ClickedCh:
err = open.Run(s.adminURL)
case <-s.mUp.ClickedCh:
s.mUp.Disable()
if err = s.up(); err != nil {
if err = s.menuUpClick(); err != nil {
s.mUp.Enable()
}
case <-s.mDown.ClickedCh:
s.mDown.Disable()
if err = s.down(); err != nil {
if err = s.menuDownClick(); err != nil {
s.mDown.Enable()
}
case <-mQuit.ClickedCh:
case <-s.mSettings.ClickedCh:
s.mSettings.Disable()
go func() {
defer s.mSettings.Enable()
proc, err := os.Executable()
if err != nil {
log.Errorf("show settings: %v", err)
return
}
cmd := exec.Command(proc, "--settings=true")
out, err := cmd.CombinedOutput()
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
log.Errorf("start settings UI: %v, %s", err, string(out))
return
}
if len(out) != 0 {
log.Info("settings change:", string(out))
}
// update config in systray when settings windows closed
s.getSrvConfig()
}()
case <-s.mQuit.ClickedCh:
systray.Quit()
return
}
@@ -204,12 +372,13 @@ func (s *serviceClient) onTrayReady() {
func (s *serviceClient) onTrayExit() {}
func (s *serviceClient) client() (proto.DaemonServiceClient, error) {
// getSrvClient connection to the service.
func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonServiceClient, error) {
if s.conn != nil {
return s.conn, nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
conn, err := grpc.DialContext(
@@ -226,6 +395,40 @@ func (s *serviceClient) client() (proto.DaemonServiceClient, error) {
return s.conn, nil
}
// getSrvConfig from the service to show it in the settings window.
func (s *serviceClient) getSrvConfig() {
s.managementURL = "https://api.wiretrustee.com:33073"
s.adminURL = "https://app.netbird.io"
conn, err := s.getSrvClient(fastFailTimeout)
if err != nil {
log.Errorf("get client: %v", err)
return
}
cfg, err := conn.GetConfig(s.ctx, &proto.GetConfigRequest{})
if err != nil {
log.Errorf("get config settings from server: %v", err)
return
}
if cfg.ManagementUrl != "" {
s.managementURL = cfg.ManagementUrl
}
if cfg.AdminURL != "" {
s.adminURL = cfg.AdminURL
}
s.preSharedKey = cfg.PreSharedKey
if s.showSettings {
s.iMngURL.SetText(s.managementURL)
s.iAdminURL.SetText(s.adminURL)
s.iConfigFile.SetText(cfg.ConfigFile)
s.iLogFile.SetText(cfg.LogFile)
s.iPreSharedKey.SetText(cfg.PreSharedKey)
}
}
// checkPIDFile exists and return error, or write new.
func checkPIDFile() error {
pidFile := path.Join(os.TempDir(), "wiretrustee-ui.pid")

BIN
client/ui/connected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
client/ui/disconnected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Name=Netbird Agent
Exec=/usr/bin/wiretrustee-ui
Icon=netbird
Type=Application
Terminal=false
Categories=Utility;
Keywords=netbird;

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Wiretrustee UI
Exec=/usr/bin/wiretrustee-ui
Icon=/usr/share/icons/hicolor/256x256/wiretrustee.png
Comment=Wiretrustee client UI.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB