mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-20 09:47:49 +02:00
[client] Add debug for duration option to netbird ui (#3772)
This commit is contained in:
parent
7b64953eed
commit
01c3719c5d
@ -235,13 +235,6 @@ func runForDuration(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
|
return fmt.Errorf("failed to bundle debug: %v", status.Convert(err).Message())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable network map persistence after creating the debug bundle
|
|
||||||
if _, err := client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{
|
|
||||||
Enabled: false,
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("failed to disable network map persistence: %v", status.Convert(err).Message())
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateWasDown {
|
if stateWasDown {
|
||||||
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
if _, err := client.Down(cmd.Context(), &proto.DownRequest{}); err != nil {
|
||||||
return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
|
return fmt.Errorf("failed to down: %v", status.Convert(err).Message())
|
||||||
|
@ -51,14 +51,16 @@ func (s *Server) DebugBundle(_ context.Context, req *proto.DebugBundleRequest) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.GetUploadURL() == "" {
|
if req.GetUploadURL() == "" {
|
||||||
|
|
||||||
return &proto.DebugBundleResponse{Path: path}, nil
|
return &proto.DebugBundleResponse{Path: path}, nil
|
||||||
}
|
}
|
||||||
key, err := uploadDebugBundle(context.Background(), req.GetUploadURL(), s.config.ManagementURL.String(), path)
|
key, err := uploadDebugBundle(context.Background(), req.GetUploadURL(), s.config.ManagementURL.String(), path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Errorf("failed to upload debug bundle to %s: %v", req.GetUploadURL(), err)
|
||||||
return &proto.DebugBundleResponse{Path: path, UploadFailureReason: err.Error()}, nil
|
return &proto.DebugBundleResponse{Path: path, UploadFailureReason: err.Error()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("debug bundle uploaded to %s with key %s", req.GetUploadURL(), key)
|
||||||
|
|
||||||
return &proto.DebugBundleResponse{Path: path, UploadedKey: key}, nil
|
return &proto.DebugBundleResponse{Path: path, UploadedKey: key}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,11 +54,14 @@ func main() {
|
|||||||
daemonAddr, showSettings, showNetworks, showDebug, errorMsg, saveLogsInFile := parseFlags()
|
daemonAddr, showSettings, showNetworks, showDebug, errorMsg, saveLogsInFile := parseFlags()
|
||||||
|
|
||||||
// Initialize file logging if needed.
|
// Initialize file logging if needed.
|
||||||
|
var logFile string
|
||||||
if saveLogsInFile {
|
if saveLogsInFile {
|
||||||
if err := initLogFile(); err != nil {
|
file, err := initLogFile()
|
||||||
|
if err != nil {
|
||||||
log.Errorf("error while initializing log: %v", err)
|
log.Errorf("error while initializing log: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logFile = file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the Fyne application.
|
// Create the Fyne application.
|
||||||
@ -72,7 +75,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the service client (this also builds the settings or networks UI if requested).
|
// Create the service client (this also builds the settings or networks UI if requested).
|
||||||
client := newServiceClient(daemonAddr, a, showSettings, showNetworks, showDebug)
|
client := newServiceClient(daemonAddr, logFile, a, showSettings, showNetworks, showDebug)
|
||||||
|
|
||||||
// Watch for theme/settings changes to update the icon.
|
// Watch for theme/settings changes to update the icon.
|
||||||
go watchSettingsChanges(a, client)
|
go watchSettingsChanges(a, client)
|
||||||
@ -115,9 +118,9 @@ func parseFlags() (daemonAddr string, showSettings, showNetworks, showDebug bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initLogFile initializes logging into a file.
|
// initLogFile initializes logging into a file.
|
||||||
func initLogFile() error {
|
func initLogFile() (string, error) {
|
||||||
logFile := path.Join(os.TempDir(), fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
|
logFile := path.Join(os.TempDir(), fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
|
||||||
return util.InitLog("trace", logFile)
|
return logFile, util.InitLog("trace", logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// watchSettingsChanges listens for Fyne theme/settings changes and updates the client icon.
|
// watchSettingsChanges listens for Fyne theme/settings changes and updates the client icon.
|
||||||
@ -160,9 +163,10 @@ var iconConnectingMacOS []byte
|
|||||||
var iconErrorMacOS []byte
|
var iconErrorMacOS []byte
|
||||||
|
|
||||||
type serviceClient struct {
|
type serviceClient struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
addr string
|
cancel context.CancelFunc
|
||||||
conn proto.DaemonServiceClient
|
addr string
|
||||||
|
conn proto.DaemonServiceClient
|
||||||
|
|
||||||
icAbout []byte
|
icAbout []byte
|
||||||
icConnected []byte
|
icConnected []byte
|
||||||
@ -224,12 +228,13 @@ type serviceClient struct {
|
|||||||
updateIndicationLock sync.Mutex
|
updateIndicationLock sync.Mutex
|
||||||
isUpdateIconActive bool
|
isUpdateIconActive bool
|
||||||
showNetworks bool
|
showNetworks bool
|
||||||
wRoutes fyne.Window
|
wNetworks fyne.Window
|
||||||
|
|
||||||
eventManager *event.Manager
|
eventManager *event.Manager
|
||||||
|
|
||||||
exitNodeMu sync.Mutex
|
exitNodeMu sync.Mutex
|
||||||
mExitNodeItems []menuHandler
|
mExitNodeItems []menuHandler
|
||||||
|
logFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
type menuHandler struct {
|
type menuHandler struct {
|
||||||
@ -240,11 +245,14 @@ type menuHandler struct {
|
|||||||
// newServiceClient instance constructor
|
// newServiceClient instance constructor
|
||||||
//
|
//
|
||||||
// This constructor also builds the UI elements for the settings window.
|
// This constructor also builds the UI elements for the settings window.
|
||||||
func newServiceClient(addr string, a fyne.App, showSettings bool, showNetworks bool, showDebug bool) *serviceClient {
|
func newServiceClient(addr string, logFile string, a fyne.App, showSettings bool, showNetworks bool, showDebug bool) *serviceClient {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
s := &serviceClient{
|
s := &serviceClient{
|
||||||
ctx: context.Background(),
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
app: a,
|
app: a,
|
||||||
|
logFile: logFile,
|
||||||
sendNotification: false,
|
sendNotification: false,
|
||||||
|
|
||||||
showAdvancedSettings: showSettings,
|
showAdvancedSettings: showSettings,
|
||||||
@ -256,9 +264,7 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showNetworks b
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case showSettings:
|
case showSettings:
|
||||||
|
|
||||||
s.showSettingsUI()
|
s.showSettingsUI()
|
||||||
return s
|
|
||||||
case showNetworks:
|
case showNetworks:
|
||||||
s.showNetworksUI()
|
s.showNetworksUI()
|
||||||
case showDebug:
|
case showDebug:
|
||||||
@ -309,6 +315,8 @@ func (s *serviceClient) updateIcon() {
|
|||||||
func (s *serviceClient) showSettingsUI() {
|
func (s *serviceClient) showSettingsUI() {
|
||||||
// add settings window UI elements.
|
// add settings window UI elements.
|
||||||
s.wSettings = s.app.NewWindow("NetBird Settings")
|
s.wSettings = s.app.NewWindow("NetBird Settings")
|
||||||
|
s.wSettings.SetOnClosed(s.cancel)
|
||||||
|
|
||||||
s.iMngURL = widget.NewEntry()
|
s.iMngURL = widget.NewEntry()
|
||||||
s.iAdminURL = widget.NewEntry()
|
s.iAdminURL = widget.NewEntry()
|
||||||
s.iConfigFile = widget.NewEntry()
|
s.iConfigFile = widget.NewEntry()
|
||||||
@ -784,7 +792,7 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
func (s *serviceClient) runSelfCommand(command, arg string) {
|
func (s *serviceClient) runSelfCommand(command, arg string) {
|
||||||
proc, err := os.Executable()
|
proc, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("show %s failed with error: %v", command, err)
|
log.Errorf("Error getting executable path: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,14 +801,48 @@ func (s *serviceClient) runSelfCommand(command, arg string) {
|
|||||||
fmt.Sprintf("--daemon-addr=%s", s.addr),
|
fmt.Sprintf("--daemon-addr=%s", s.addr),
|
||||||
)
|
)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
if out := s.attachOutput(cmd); out != nil {
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
|
defer func() {
|
||||||
log.Errorf("start %s UI: %v, %s", command, err, string(out))
|
if err := out.Close(); err != nil {
|
||||||
|
log.Errorf("Error closing log file %s: %v", s.logFile, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Running command: %s --%s=%s --daemon-addr=%s", proc, command, arg, s.addr)
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
var exitErr *exec.ExitError
|
||||||
|
if errors.As(err, &exitErr) {
|
||||||
|
log.Printf("Command '%s %s' failed with exit code %d", command, arg, exitErr.ExitCode())
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed to start/run command '%s %s': %v", command, arg, err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(out) != 0 {
|
|
||||||
log.Infof("command %s executed: %s", command, string(out))
|
log.Printf("Command '%s %s' completed successfully.", command, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) attachOutput(cmd *exec.Cmd) *os.File {
|
||||||
|
if s.logFile == "" {
|
||||||
|
// attach child's streams to parent's streams
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out, err := os.OpenFile(s.logFile, os.O_WRONLY|os.O_APPEND, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to open log file %s: %v", s.logFile, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cmd.Stdout = out
|
||||||
|
cmd.Stderr = out
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizedVersion(version string) string {
|
func normalizedVersion(version string) string {
|
||||||
@ -813,9 +855,7 @@ func normalizedVersion(version string) string {
|
|||||||
|
|
||||||
// onTrayExit is called when the tray icon is closed.
|
// onTrayExit is called when the tray icon is closed.
|
||||||
func (s *serviceClient) onTrayExit() {
|
func (s *serviceClient) onTrayExit() {
|
||||||
for _, item := range s.mExitNodeItems {
|
s.cancel()
|
||||||
item.cancel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSrvClient connection to the service.
|
// getSrvClient connection to the service.
|
||||||
@ -824,7 +864,7 @@ func (s *serviceClient) getSrvClient(timeout time.Duration) (proto.DaemonService
|
|||||||
return s.conn, nil
|
return s.conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(s.ctx, timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
conn, err := grpc.DialContext(
|
conn, err := grpc.DialContext(
|
||||||
|
@ -3,8 +3,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
@ -13,18 +17,46 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/internal"
|
||||||
"github.com/netbirdio/netbird/client/proto"
|
"github.com/netbirdio/netbird/client/proto"
|
||||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||||
uptypes "github.com/netbirdio/netbird/upload-server/types"
|
uptypes "github.com/netbirdio/netbird/upload-server/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Initial state for the debug collection
|
||||||
|
type debugInitialState struct {
|
||||||
|
wasDown bool
|
||||||
|
logLevel proto.LogLevel
|
||||||
|
isLevelTrace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug collection parameters
|
||||||
|
type debugCollectionParams struct {
|
||||||
|
duration time.Duration
|
||||||
|
anonymize bool
|
||||||
|
systemInfo bool
|
||||||
|
upload bool
|
||||||
|
uploadURL string
|
||||||
|
enablePersistence bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI components for progress tracking
|
||||||
|
type progressUI struct {
|
||||||
|
statusLabel *widget.Label
|
||||||
|
progressBar *widget.ProgressBar
|
||||||
|
uiControls []fyne.Disableable
|
||||||
|
window fyne.Window
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) showDebugUI() {
|
func (s *serviceClient) showDebugUI() {
|
||||||
w := s.app.NewWindow("NetBird Debug")
|
w := s.app.NewWindow("NetBird Debug")
|
||||||
w.Resize(fyne.NewSize(600, 400))
|
w.SetOnClosed(s.cancel)
|
||||||
|
|
||||||
|
w.Resize(fyne.NewSize(600, 500))
|
||||||
w.SetFixedSize(true)
|
w.SetFixedSize(true)
|
||||||
|
|
||||||
anonymizeCheck := widget.NewCheck("Anonymize sensitive information (Public IPs, domains, ...)", nil)
|
anonymizeCheck := widget.NewCheck("Anonymize sensitive information (public IPs, domains, ...)", nil)
|
||||||
systemInfoCheck := widget.NewCheck("Include system information", nil)
|
systemInfoCheck := widget.NewCheck("Include system information (routes, interfaces, ...)", nil)
|
||||||
systemInfoCheck.SetChecked(true)
|
systemInfoCheck.SetChecked(true)
|
||||||
uploadCheck := widget.NewCheck("Upload bundle automatically after creation", nil)
|
uploadCheck := widget.NewCheck("Upload bundle automatically after creation", nil)
|
||||||
uploadCheck.SetChecked(true)
|
uploadCheck.SetChecked(true)
|
||||||
@ -34,11 +66,6 @@ func (s *serviceClient) showDebugUI() {
|
|||||||
uploadURL.SetText(uptypes.DefaultBundleURL)
|
uploadURL.SetText(uptypes.DefaultBundleURL)
|
||||||
uploadURL.SetPlaceHolder("Enter upload URL")
|
uploadURL.SetPlaceHolder("Enter upload URL")
|
||||||
|
|
||||||
statusLabel := widget.NewLabel("")
|
|
||||||
statusLabel.Hide()
|
|
||||||
|
|
||||||
createButton := widget.NewButton("Create Debug Bundle", nil)
|
|
||||||
|
|
||||||
uploadURLContainer := container.NewVBox(
|
uploadURLContainer := container.NewVBox(
|
||||||
uploadURLLabel,
|
uploadURLLabel,
|
||||||
uploadURL,
|
uploadURL,
|
||||||
@ -52,7 +79,71 @@ func (s *serviceClient) showDebugUI() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createButton.OnTapped = s.getCreateHandler(createButton, statusLabel, uploadCheck, uploadURL, anonymizeCheck, systemInfoCheck, w)
|
debugModeContainer := container.NewHBox()
|
||||||
|
runForDurationCheck := widget.NewCheck("Run with trace logs before creating bundle", nil)
|
||||||
|
runForDurationCheck.SetChecked(true)
|
||||||
|
|
||||||
|
forLabel := widget.NewLabel("for")
|
||||||
|
|
||||||
|
durationInput := widget.NewEntry()
|
||||||
|
durationInput.SetText("1")
|
||||||
|
minutesLabel := widget.NewLabel("minute")
|
||||||
|
durationInput.Validator = func(s string) error {
|
||||||
|
return validateMinute(s, minutesLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
noteLabel := widget.NewLabel("Note: NetBird will be brought up and down during collection")
|
||||||
|
|
||||||
|
runForDurationCheck.OnChanged = func(checked bool) {
|
||||||
|
if checked {
|
||||||
|
forLabel.Show()
|
||||||
|
durationInput.Show()
|
||||||
|
minutesLabel.Show()
|
||||||
|
noteLabel.Show()
|
||||||
|
} else {
|
||||||
|
forLabel.Hide()
|
||||||
|
durationInput.Hide()
|
||||||
|
minutesLabel.Hide()
|
||||||
|
noteLabel.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugModeContainer.Add(runForDurationCheck)
|
||||||
|
debugModeContainer.Add(forLabel)
|
||||||
|
debugModeContainer.Add(durationInput)
|
||||||
|
debugModeContainer.Add(minutesLabel)
|
||||||
|
|
||||||
|
statusLabel := widget.NewLabel("")
|
||||||
|
statusLabel.Hide()
|
||||||
|
|
||||||
|
progressBar := widget.NewProgressBar()
|
||||||
|
progressBar.Hide()
|
||||||
|
|
||||||
|
createButton := widget.NewButton("Create Debug Bundle", nil)
|
||||||
|
|
||||||
|
// UI controls that should be disabled during debug collection
|
||||||
|
uiControls := []fyne.Disableable{
|
||||||
|
anonymizeCheck,
|
||||||
|
systemInfoCheck,
|
||||||
|
uploadCheck,
|
||||||
|
uploadURL,
|
||||||
|
runForDurationCheck,
|
||||||
|
durationInput,
|
||||||
|
createButton,
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton.OnTapped = s.getCreateHandler(
|
||||||
|
statusLabel,
|
||||||
|
progressBar,
|
||||||
|
uploadCheck,
|
||||||
|
uploadURL,
|
||||||
|
anonymizeCheck,
|
||||||
|
systemInfoCheck,
|
||||||
|
runForDurationCheck,
|
||||||
|
durationInput,
|
||||||
|
uiControls,
|
||||||
|
w,
|
||||||
|
)
|
||||||
|
|
||||||
content := container.NewVBox(
|
content := container.NewVBox(
|
||||||
widget.NewLabel("Create a debug bundle to help troubleshoot issues with NetBird"),
|
widget.NewLabel("Create a debug bundle to help troubleshoot issues with NetBird"),
|
||||||
@ -62,7 +153,11 @@ func (s *serviceClient) showDebugUI() {
|
|||||||
uploadCheck,
|
uploadCheck,
|
||||||
uploadURLContainer,
|
uploadURLContainer,
|
||||||
widget.NewLabel(""),
|
widget.NewLabel(""),
|
||||||
|
debugModeContainer,
|
||||||
|
noteLabel,
|
||||||
|
widget.NewLabel(""),
|
||||||
statusLabel,
|
statusLabel,
|
||||||
|
progressBar,
|
||||||
createButton,
|
createButton,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,18 +167,46 @@ func (s *serviceClient) showDebugUI() {
|
|||||||
w.Show()
|
w.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateMinute(s string, minutesLabel *widget.Label) error {
|
||||||
|
if val, err := strconv.Atoi(s); err != nil || val < 1 {
|
||||||
|
return fmt.Errorf("must be a number ≥ 1")
|
||||||
|
}
|
||||||
|
if s == "1" {
|
||||||
|
minutesLabel.SetText("minute")
|
||||||
|
} else {
|
||||||
|
minutesLabel.SetText("minutes")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// disableUIControls disables the provided UI controls
|
||||||
|
func disableUIControls(controls []fyne.Disableable) {
|
||||||
|
for _, control := range controls {
|
||||||
|
control.Disable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enableUIControls enables the provided UI controls
|
||||||
|
func enableUIControls(controls []fyne.Disableable) {
|
||||||
|
for _, control := range controls {
|
||||||
|
control.Enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) getCreateHandler(
|
func (s *serviceClient) getCreateHandler(
|
||||||
createButton *widget.Button,
|
|
||||||
statusLabel *widget.Label,
|
statusLabel *widget.Label,
|
||||||
|
progressBar *widget.ProgressBar,
|
||||||
uploadCheck *widget.Check,
|
uploadCheck *widget.Check,
|
||||||
uploadURL *widget.Entry,
|
uploadURL *widget.Entry,
|
||||||
anonymizeCheck *widget.Check,
|
anonymizeCheck *widget.Check,
|
||||||
systemInfoCheck *widget.Check,
|
systemInfoCheck *widget.Check,
|
||||||
|
runForDurationCheck *widget.Check,
|
||||||
|
duration *widget.Entry,
|
||||||
|
uiControls []fyne.Disableable,
|
||||||
w fyne.Window,
|
w fyne.Window,
|
||||||
) func() {
|
) func() {
|
||||||
return func() {
|
return func() {
|
||||||
createButton.Disable()
|
disableUIControls(uiControls)
|
||||||
statusLabel.SetText("Creating debug bundle...")
|
|
||||||
statusLabel.Show()
|
statusLabel.Show()
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
@ -91,22 +214,329 @@ func (s *serviceClient) getCreateHandler(
|
|||||||
url = uploadURL.Text
|
url = uploadURL.Text
|
||||||
if url == "" {
|
if url == "" {
|
||||||
statusLabel.SetText("Error: Upload URL is required when upload is enabled")
|
statusLabel.SetText("Error: Upload URL is required when upload is enabled")
|
||||||
createButton.Enable()
|
enableUIControls(uiControls)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.handleDebugCreation(anonymizeCheck.Checked, systemInfoCheck.Checked, uploadCheck.Checked, url, statusLabel, createButton, w)
|
params := &debugCollectionParams{
|
||||||
|
anonymize: anonymizeCheck.Checked,
|
||||||
|
systemInfo: systemInfoCheck.Checked,
|
||||||
|
upload: uploadCheck.Checked,
|
||||||
|
uploadURL: url,
|
||||||
|
enablePersistence: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
runForDuration := runForDurationCheck.Checked
|
||||||
|
if runForDuration {
|
||||||
|
minutes, err := time.ParseDuration(duration.Text + "m")
|
||||||
|
if err != nil {
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Error: Invalid duration: %v", err))
|
||||||
|
enableUIControls(uiControls)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params.duration = minutes
|
||||||
|
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Running in debug mode for %d minutes...", int(minutes.Minutes())))
|
||||||
|
progressBar.Show()
|
||||||
|
progressBar.SetValue(0)
|
||||||
|
|
||||||
|
go s.handleRunForDuration(
|
||||||
|
statusLabel,
|
||||||
|
progressBar,
|
||||||
|
uiControls,
|
||||||
|
w,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.SetText("Creating debug bundle...")
|
||||||
|
go s.handleDebugCreation(
|
||||||
|
anonymizeCheck.Checked,
|
||||||
|
systemInfoCheck.Checked,
|
||||||
|
uploadCheck.Checked,
|
||||||
|
url,
|
||||||
|
statusLabel,
|
||||||
|
uiControls,
|
||||||
|
w,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) handleRunForDuration(
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
progressBar *widget.ProgressBar,
|
||||||
|
uiControls []fyne.Disableable,
|
||||||
|
w fyne.Window,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
) {
|
||||||
|
progressUI := &progressUI{
|
||||||
|
statusLabel: statusLabel,
|
||||||
|
progressBar: progressBar,
|
||||||
|
uiControls: uiControls,
|
||||||
|
window: w,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := s.getSrvClient(failFastTimeout)
|
||||||
|
if err != nil {
|
||||||
|
handleError(progressUI, fmt.Sprintf("Failed to get client for debug: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
initialState, err := s.getInitialState(conn)
|
||||||
|
if err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statusOutput, err := s.collectDebugData(conn, initialState, params, progressUI)
|
||||||
|
if err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.createDebugBundleFromCollection(conn, params, statusOutput, progressUI); err != nil {
|
||||||
|
handleError(progressUI, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.restoreServiceState(conn, initialState)
|
||||||
|
|
||||||
|
progressUI.statusLabel.SetText("Bundle created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get initial state of the service
|
||||||
|
func (s *serviceClient) getInitialState(conn proto.DaemonServiceClient) (*debugInitialState, error) {
|
||||||
|
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(" get status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logLevelResp, err := conn.GetLogLevel(s.ctx, &proto.GetLogLevelRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get log level: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasDown := statusResp.Status != string(internal.StatusConnected) &&
|
||||||
|
statusResp.Status != string(internal.StatusConnecting)
|
||||||
|
|
||||||
|
initialLogLevel := logLevelResp.GetLevel()
|
||||||
|
initialLevelTrace := initialLogLevel >= proto.LogLevel_TRACE
|
||||||
|
|
||||||
|
return &debugInitialState{
|
||||||
|
wasDown: wasDown,
|
||||||
|
logLevel: initialLogLevel,
|
||||||
|
isLevelTrace: initialLevelTrace,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle progress tracking during collection
|
||||||
|
func startProgressTracker(ctx context.Context, wg *sync.WaitGroup, duration time.Duration, progress *progressUI) {
|
||||||
|
progress.progressBar.Show()
|
||||||
|
progress.progressBar.SetValue(0)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
endTime := startTime.Add(duration)
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
ticker := time.NewTicker(500 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
remaining := time.Until(endTime)
|
||||||
|
if remaining <= 0 {
|
||||||
|
remaining = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
progressVal := float64(elapsed) / float64(duration)
|
||||||
|
if progressVal > 1.0 {
|
||||||
|
progressVal = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.progressBar.SetValue(progressVal)
|
||||||
|
progress.statusLabel.SetText(fmt.Sprintf("Running with trace logs... %s remaining", formatDuration(remaining)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) configureServiceForDebug(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
state *debugInitialState,
|
||||||
|
enablePersistence bool,
|
||||||
|
) error {
|
||||||
|
if state.wasDown {
|
||||||
|
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("bring service up: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("Service brought up for debug")
|
||||||
|
time.Sleep(time.Second * 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.isLevelTrace {
|
||||||
|
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: proto.LogLevel_TRACE}); err != nil {
|
||||||
|
return fmt.Errorf("set log level to TRACE: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("Log level set to TRACE for debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("bring service down: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
if enablePersistence {
|
||||||
|
if _, err := conn.SetNetworkMapPersistence(s.ctx, &proto.SetNetworkMapPersistenceRequest{
|
||||||
|
Enabled: true,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("enable network map persistence: %v", err)
|
||||||
|
}
|
||||||
|
log.Info("Network map persistence enabled for debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
|
||||||
|
return fmt.Errorf("bring service back up: %v", err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) collectDebugData(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
state *debugInitialState,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
progress *progressUI,
|
||||||
|
) (string, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, params.duration)
|
||||||
|
defer cancel()
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
startProgressTracker(ctx, &wg, params.duration, progress)
|
||||||
|
|
||||||
|
if err := s.configureServiceForDebug(conn, state, params.enablePersistence); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
postUpStatus, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to get post-up status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var postUpStatusOutput string
|
||||||
|
if postUpStatus != nil {
|
||||||
|
overview := nbstatus.ConvertToStatusOutputOverview(postUpStatus, params.anonymize, "", nil, nil, nil)
|
||||||
|
postUpStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
||||||
|
}
|
||||||
|
headerPostUp := fmt.Sprintf("----- NetBird post-up - Timestamp: %s", time.Now().Format(time.RFC3339))
|
||||||
|
statusOutput := fmt.Sprintf("%s\n%s", headerPostUp, postUpStatusOutput)
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
progress.progressBar.Hide()
|
||||||
|
progress.statusLabel.SetText("Collecting debug data...")
|
||||||
|
|
||||||
|
preDownStatus, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to get pre-down status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var preDownStatusOutput string
|
||||||
|
if preDownStatus != nil {
|
||||||
|
overview := nbstatus.ConvertToStatusOutputOverview(preDownStatus, params.anonymize, "", nil, nil, nil)
|
||||||
|
preDownStatusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
||||||
|
}
|
||||||
|
headerPreDown := fmt.Sprintf("----- NetBird pre-down - Timestamp: %s - Duration: %s",
|
||||||
|
time.Now().Format(time.RFC3339), params.duration)
|
||||||
|
statusOutput = fmt.Sprintf("%s\n%s\n%s", statusOutput, headerPreDown, preDownStatusOutput)
|
||||||
|
|
||||||
|
return statusOutput, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the debug bundle with collected data
|
||||||
|
func (s *serviceClient) createDebugBundleFromCollection(
|
||||||
|
conn proto.DaemonServiceClient,
|
||||||
|
params *debugCollectionParams,
|
||||||
|
statusOutput string,
|
||||||
|
progress *progressUI,
|
||||||
|
) error {
|
||||||
|
progress.statusLabel.SetText("Creating debug bundle with collected logs...")
|
||||||
|
|
||||||
|
request := &proto.DebugBundleRequest{
|
||||||
|
Anonymize: params.anonymize,
|
||||||
|
Status: statusOutput,
|
||||||
|
SystemInfo: params.systemInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.upload {
|
||||||
|
request.UploadURL = params.uploadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.DebugBundle(s.ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create debug bundle: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show appropriate dialog based on upload status
|
||||||
|
localPath := resp.GetPath()
|
||||||
|
uploadFailureReason := resp.GetUploadFailureReason()
|
||||||
|
uploadedKey := resp.GetUploadedKey()
|
||||||
|
|
||||||
|
if params.upload {
|
||||||
|
if uploadFailureReason != "" {
|
||||||
|
showUploadFailedDialog(progress.window, localPath, uploadFailureReason)
|
||||||
|
} else {
|
||||||
|
showUploadSuccessDialog(progress.window, localPath, uploadedKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showBundleCreatedDialog(progress.window, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
enableUIControls(progress.uiControls)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore service to original state
|
||||||
|
func (s *serviceClient) restoreServiceState(conn proto.DaemonServiceClient, state *debugInitialState) {
|
||||||
|
if state.wasDown {
|
||||||
|
if _, err := conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
|
||||||
|
log.Errorf("Failed to restore down state: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Service state restored to down")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !state.isLevelTrace {
|
||||||
|
if _, err := conn.SetLogLevel(s.ctx, &proto.SetLogLevelRequest{Level: state.logLevel}); err != nil {
|
||||||
|
log.Errorf("Failed to restore log level: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Info("Log level restored to original setting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors during debug collection
|
||||||
|
func handleError(progress *progressUI, errMsg string) {
|
||||||
|
log.Errorf("%s", errMsg)
|
||||||
|
progress.statusLabel.SetText(errMsg)
|
||||||
|
progress.progressBar.Hide()
|
||||||
|
enableUIControls(progress.uiControls)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serviceClient) handleDebugCreation(
|
func (s *serviceClient) handleDebugCreation(
|
||||||
anonymize bool,
|
anonymize bool,
|
||||||
systemInfo bool,
|
systemInfo bool,
|
||||||
upload bool,
|
upload bool,
|
||||||
uploadURL string,
|
uploadURL string,
|
||||||
statusLabel *widget.Label,
|
statusLabel *widget.Label,
|
||||||
createButton *widget.Button,
|
uiControls []fyne.Disableable,
|
||||||
w fyne.Window,
|
w fyne.Window,
|
||||||
) {
|
) {
|
||||||
log.Infof("Creating debug bundle (Anonymized: %v, System Info: %v, Upload Attempt: %v)...",
|
log.Infof("Creating debug bundle (Anonymized: %v, System Info: %v, Upload Attempt: %v)...",
|
||||||
@ -116,7 +546,7 @@ func (s *serviceClient) handleDebugCreation(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to create debug bundle: %v", err)
|
log.Errorf("Failed to create debug bundle: %v", err)
|
||||||
statusLabel.SetText(fmt.Sprintf("Error creating bundle: %v", err))
|
statusLabel.SetText(fmt.Sprintf("Error creating bundle: %v", err))
|
||||||
createButton.Enable()
|
enableUIControls(uiControls)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +564,7 @@ func (s *serviceClient) handleDebugCreation(
|
|||||||
showBundleCreatedDialog(w, localPath)
|
showBundleCreatedDialog(w, localPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
createButton.Enable()
|
enableUIControls(uiControls)
|
||||||
statusLabel.SetText("Bundle created successfully")
|
statusLabel.SetText("Bundle created successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,32 +603,47 @@ func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploa
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatDuration formats a duration in HH:MM:SS format
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
d = d.Round(time.Second)
|
||||||
|
h := d / time.Hour
|
||||||
|
d %= time.Hour
|
||||||
|
m := d / time.Minute
|
||||||
|
d %= time.Minute
|
||||||
|
s := d / time.Second
|
||||||
|
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// createButtonWithAction creates a button with the given label and action
|
||||||
|
func createButtonWithAction(label string, action func()) *widget.Button {
|
||||||
|
button := widget.NewButton(label, action)
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
|
||||||
// showUploadFailedDialog displays a dialog when upload fails
|
// showUploadFailedDialog displays a dialog when upload fails
|
||||||
func showUploadFailedDialog(parent fyne.Window, localPath, failureReason string) {
|
func showUploadFailedDialog(w fyne.Window, localPath, failureReason string) {
|
||||||
content := container.NewVBox(
|
content := container.NewVBox(
|
||||||
widget.NewLabel(fmt.Sprintf("Bundle upload failed:\n%s\n\n"+
|
widget.NewLabel(fmt.Sprintf("Bundle upload failed:\n%s\n\n"+
|
||||||
"A local copy was saved at:\n%s", failureReason, localPath)),
|
"A local copy was saved at:\n%s", failureReason, localPath)),
|
||||||
)
|
)
|
||||||
|
|
||||||
customDialog := dialog.NewCustom("Upload Failed", "Cancel", content, parent)
|
customDialog := dialog.NewCustom("Upload Failed", "Cancel", content, w)
|
||||||
|
|
||||||
buttonBox := container.NewHBox(
|
buttonBox := container.NewHBox(
|
||||||
widget.NewButton("Open File", func() {
|
createButtonWithAction("Open file", func() {
|
||||||
log.Infof("Attempting to open local file: %s", localPath)
|
log.Infof("Attempting to open local file: %s", localPath)
|
||||||
if openErr := open.Start(localPath); openErr != nil {
|
if openErr := open.Start(localPath); openErr != nil {
|
||||||
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
||||||
dialog.ShowError(fmt.Errorf("Failed to open the local file:\n%s\n\nError: %v", localPath, openErr), parent)
|
dialog.ShowError(fmt.Errorf("open the local file:\n%s\n\nError: %v", localPath, openErr), w)
|
||||||
}
|
}
|
||||||
customDialog.Hide()
|
|
||||||
}),
|
}),
|
||||||
widget.NewButton("Open Folder", func() {
|
createButtonWithAction("Open folder", func() {
|
||||||
folderPath := filepath.Dir(localPath)
|
folderPath := filepath.Dir(localPath)
|
||||||
log.Infof("Attempting to open local folder: %s", folderPath)
|
log.Infof("Attempting to open local folder: %s", folderPath)
|
||||||
if openErr := open.Start(folderPath); openErr != nil {
|
if openErr := open.Start(folderPath); openErr != nil {
|
||||||
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
||||||
dialog.ShowError(fmt.Errorf("Failed to open the local folder:\n%s\n\nError: %v", folderPath, openErr), parent)
|
dialog.ShowError(fmt.Errorf("open the local folder:\n%s\n\nError: %v", folderPath, openErr), w)
|
||||||
}
|
}
|
||||||
customDialog.Hide()
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -207,7 +652,8 @@ func showUploadFailedDialog(parent fyne.Window, localPath, failureReason string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// showUploadSuccessDialog displays a dialog when upload succeeds
|
// showUploadSuccessDialog displays a dialog when upload succeeds
|
||||||
func showUploadSuccessDialog(parent fyne.Window, localPath, uploadedKey string) {
|
func showUploadSuccessDialog(w fyne.Window, localPath, uploadedKey string) {
|
||||||
|
log.Infof("Upload key: %s", uploadedKey)
|
||||||
keyEntry := widget.NewEntry()
|
keyEntry := widget.NewEntry()
|
||||||
keyEntry.SetText(uploadedKey)
|
keyEntry.SetText(uploadedKey)
|
||||||
keyEntry.Disable()
|
keyEntry.Disable()
|
||||||
@ -215,62 +661,63 @@ func showUploadSuccessDialog(parent fyne.Window, localPath, uploadedKey string)
|
|||||||
content := container.NewVBox(
|
content := container.NewVBox(
|
||||||
widget.NewLabel("Bundle uploaded successfully!"),
|
widget.NewLabel("Bundle uploaded successfully!"),
|
||||||
widget.NewLabel(""),
|
widget.NewLabel(""),
|
||||||
widget.NewLabel("Upload Key:"),
|
widget.NewLabel("Upload key:"),
|
||||||
keyEntry,
|
keyEntry,
|
||||||
widget.NewLabel(""),
|
widget.NewLabel(""),
|
||||||
widget.NewLabel(fmt.Sprintf("Local copy saved at:\n%s", localPath)),
|
widget.NewLabel(fmt.Sprintf("Local copy saved at:\n%s", localPath)),
|
||||||
)
|
)
|
||||||
|
|
||||||
customDialog := dialog.NewCustom("Upload Successful", "OK", content, parent)
|
customDialog := dialog.NewCustom("Upload Successful", "OK", content, w)
|
||||||
|
|
||||||
buttonBox := container.NewHBox(
|
copyBtn := createButtonWithAction("Copy key", func() {
|
||||||
widget.NewButton("Copy Key", func() {
|
w.Clipboard().SetContent(uploadedKey)
|
||||||
parent.Clipboard().SetContent(uploadedKey)
|
log.Info("Upload key copied to clipboard")
|
||||||
log.Info("Upload key copied to clipboard")
|
})
|
||||||
}),
|
|
||||||
widget.NewButton("Open Local Folder", func() {
|
|
||||||
folderPath := filepath.Dir(localPath)
|
|
||||||
log.Infof("Attempting to open local folder: %s", folderPath)
|
|
||||||
if openErr := open.Start(folderPath); openErr != nil {
|
|
||||||
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
|
||||||
dialog.ShowError(fmt.Errorf("Failed to open the local folder:\n%s\n\nError: %v", folderPath, openErr), parent)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
buttonBox := createButtonBox(localPath, w, copyBtn)
|
||||||
content.Add(buttonBox)
|
content.Add(buttonBox)
|
||||||
customDialog.Show()
|
customDialog.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// showBundleCreatedDialog displays a dialog when bundle is created without upload
|
// showBundleCreatedDialog displays a dialog when bundle is created without upload
|
||||||
func showBundleCreatedDialog(parent fyne.Window, localPath string) {
|
func showBundleCreatedDialog(w fyne.Window, localPath string) {
|
||||||
content := container.NewVBox(
|
content := container.NewVBox(
|
||||||
widget.NewLabel(fmt.Sprintf("Bundle created locally at:\n%s\n\n"+
|
widget.NewLabel(fmt.Sprintf("Bundle created locally at:\n%s\n\n"+
|
||||||
"Administrator privileges may be required to access the file.", localPath)),
|
"Administrator privileges may be required to access the file.", localPath)),
|
||||||
)
|
)
|
||||||
|
|
||||||
customDialog := dialog.NewCustom("Debug Bundle Created", "Cancel", content, parent)
|
customDialog := dialog.NewCustom("Debug Bundle Created", "Cancel", content, w)
|
||||||
|
|
||||||
buttonBox := container.NewHBox(
|
|
||||||
widget.NewButton("Open File", func() {
|
|
||||||
log.Infof("Attempting to open local file: %s", localPath)
|
|
||||||
if openErr := open.Start(localPath); openErr != nil {
|
|
||||||
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
|
||||||
dialog.ShowError(fmt.Errorf("Failed to open the local file:\n%s\n\nError: %v", localPath, openErr), parent)
|
|
||||||
}
|
|
||||||
customDialog.Hide()
|
|
||||||
}),
|
|
||||||
widget.NewButton("Open Folder", func() {
|
|
||||||
folderPath := filepath.Dir(localPath)
|
|
||||||
log.Infof("Attempting to open local folder: %s", folderPath)
|
|
||||||
if openErr := open.Start(folderPath); openErr != nil {
|
|
||||||
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
|
||||||
dialog.ShowError(fmt.Errorf("Failed to open the local folder:\n%s\n\nError: %v", folderPath, openErr), parent)
|
|
||||||
}
|
|
||||||
customDialog.Hide()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
buttonBox := createButtonBox(localPath, w, nil)
|
||||||
content.Add(buttonBox)
|
content.Add(buttonBox)
|
||||||
customDialog.Show()
|
customDialog.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createButtonBox(localPath string, w fyne.Window, elems ...fyne.Widget) *fyne.Container {
|
||||||
|
box := container.NewHBox()
|
||||||
|
for _, elem := range elems {
|
||||||
|
box.Add(elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileBtn := createButtonWithAction("Open file", func() {
|
||||||
|
log.Infof("Attempting to open local file: %s", localPath)
|
||||||
|
if openErr := open.Start(localPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local file '%s': %v", localPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local file:\n%s\n\nError: %v", localPath, openErr), w)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
folderBtn := createButtonWithAction("Open folder", func() {
|
||||||
|
folderPath := filepath.Dir(localPath)
|
||||||
|
log.Infof("Attempting to open local folder: %s", folderPath)
|
||||||
|
if openErr := open.Start(folderPath); openErr != nil {
|
||||||
|
log.Errorf("Failed to open local folder '%s': %v", folderPath, openErr)
|
||||||
|
dialog.ShowError(fmt.Errorf("open the local folder:\n%s\n\nError: %v", folderPath, openErr), w)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
box.Add(fileBtn)
|
||||||
|
box.Add(folderBtn)
|
||||||
|
|
||||||
|
return box
|
||||||
|
}
|
||||||
|
@ -34,7 +34,8 @@ const (
|
|||||||
type filter string
|
type filter string
|
||||||
|
|
||||||
func (s *serviceClient) showNetworksUI() {
|
func (s *serviceClient) showNetworksUI() {
|
||||||
s.wRoutes = s.app.NewWindow("Networks")
|
s.wNetworks = s.app.NewWindow("Networks")
|
||||||
|
s.wNetworks.SetOnClosed(s.cancel)
|
||||||
|
|
||||||
allGrid := container.New(layout.NewGridLayout(3))
|
allGrid := container.New(layout.NewGridLayout(3))
|
||||||
go s.updateNetworks(allGrid, allNetworks)
|
go s.updateNetworks(allGrid, allNetworks)
|
||||||
@ -78,8 +79,8 @@ func (s *serviceClient) showNetworksUI() {
|
|||||||
|
|
||||||
content := container.NewBorder(nil, buttonBox, nil, nil, scrollContainer)
|
content := container.NewBorder(nil, buttonBox, nil, nil, scrollContainer)
|
||||||
|
|
||||||
s.wRoutes.SetContent(content)
|
s.wNetworks.SetContent(content)
|
||||||
s.wRoutes.Show()
|
s.wNetworks.Show()
|
||||||
|
|
||||||
s.startAutoRefresh(10*time.Second, tabs, allGrid, overlappingGrid, exitNodeGrid)
|
s.startAutoRefresh(10*time.Second, tabs, allGrid, overlappingGrid, exitNodeGrid)
|
||||||
}
|
}
|
||||||
@ -148,7 +149,7 @@ func (s *serviceClient) updateNetworks(grid *fyne.Container, f filter) {
|
|||||||
grid.Add(resolvedIPsSelector)
|
grid.Add(resolvedIPsSelector)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.wRoutes.Content().Refresh()
|
s.wNetworks.Content().Refresh()
|
||||||
grid.Refresh()
|
grid.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +306,7 @@ func (s *serviceClient) getNetworksRequest(f filter, appendRoute bool) *proto.Se
|
|||||||
func (s *serviceClient) showError(err error) {
|
func (s *serviceClient) showError(err error) {
|
||||||
wrappedMessage := wrapText(err.Error(), 50)
|
wrappedMessage := wrapText(err.Error(), 50)
|
||||||
|
|
||||||
dialog.ShowError(fmt.Errorf("%s", wrappedMessage), s.wRoutes)
|
dialog.ShowError(fmt.Errorf("%s", wrappedMessage), s.wNetworks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) startAutoRefresh(interval time.Duration, tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
func (s *serviceClient) startAutoRefresh(interval time.Duration, tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
||||||
@ -316,14 +317,15 @@ func (s *serviceClient) startAutoRefresh(interval time.Duration, tabs *container
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
s.wRoutes.SetOnClosed(func() {
|
s.wNetworks.SetOnClosed(func() {
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
|
s.cancel()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceClient) updateNetworksBasedOnDisplayTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
func (s *serviceClient) updateNetworksBasedOnDisplayTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
||||||
grid, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
grid, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
||||||
s.wRoutes.Content().Refresh()
|
s.wNetworks.Content().Refresh()
|
||||||
s.updateNetworks(grid, f)
|
s.updateNetworks(grid, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +375,7 @@ func (s *serviceClient) recreateExitNodeMenu(exitNodes []*proto.Network) {
|
|||||||
node.Selected,
|
node.Selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(s.ctx)
|
||||||
s.mExitNodeItems = append(s.mExitNodeItems, menuHandler{
|
s.mExitNodeItems = append(s.mExitNodeItems, menuHandler{
|
||||||
MenuItem: menuItem,
|
MenuItem: menuItem,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user