mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-09 15:25:20 +02:00
[client] Add debug upload option to netbird ui (#3768)
This commit is contained in:
@ -42,7 +42,6 @@ const (
|
|||||||
blockLANAccessFlag = "block-lan-access"
|
blockLANAccessFlag = "block-lan-access"
|
||||||
uploadBundle = "upload-bundle"
|
uploadBundle = "upload-bundle"
|
||||||
uploadBundleURL = "upload-bundle-url"
|
uploadBundleURL = "upload-bundle-url"
|
||||||
defaultBundleURL = "https://upload.debug.netbird.io" + types.GetURLPath
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -188,7 +187,7 @@ func init() {
|
|||||||
|
|
||||||
debugCmd.PersistentFlags().BoolVarP(&debugSystemInfoFlag, systemInfoFlag, "S", true, "Adds system information to the debug bundle")
|
debugCmd.PersistentFlags().BoolVarP(&debugSystemInfoFlag, systemInfoFlag, "S", true, "Adds system information to the debug bundle")
|
||||||
debugCmd.PersistentFlags().BoolVarP(&debugUploadBundle, uploadBundle, "U", false, fmt.Sprintf("Uploads the debug bundle to a server from URL defined by %s", uploadBundleURL))
|
debugCmd.PersistentFlags().BoolVarP(&debugUploadBundle, uploadBundle, "U", false, fmt.Sprintf("Uploads the debug bundle to a server from URL defined by %s", uploadBundleURL))
|
||||||
debugCmd.PersistentFlags().StringVar(&debugUploadBundleURL, uploadBundleURL, defaultBundleURL, "Service URL to get an URL to upload the debug bundle")
|
debugCmd.PersistentFlags().StringVar(&debugUploadBundleURL, uploadBundleURL, types.DefaultBundleURL, "Service URL to get an URL to upload the debug bundle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupCloseHandler handles SIGTERM signal and exits with success
|
// SetupCloseHandler handles SIGTERM signal and exits with success
|
||||||
|
@ -51,7 +51,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
daemonAddr, showSettings, showNetworks, errorMsg, saveLogsInFile := parseFlags()
|
daemonAddr, showSettings, showNetworks, showDebug, errorMsg, saveLogsInFile := parseFlags()
|
||||||
|
|
||||||
// Initialize file logging if needed.
|
// Initialize file logging if needed.
|
||||||
if saveLogsInFile {
|
if saveLogsInFile {
|
||||||
@ -72,13 +72,13 @@ 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)
|
client := newServiceClient(daemonAddr, 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)
|
||||||
|
|
||||||
// Run in window mode if any UI flag was set.
|
// Run in window mode if any UI flag was set.
|
||||||
if showSettings || showNetworks {
|
if showSettings || showNetworks || showDebug {
|
||||||
a.Run()
|
a.Run()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -99,7 +99,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFlags reads and returns all needed command-line flags.
|
// parseFlags reads and returns all needed command-line flags.
|
||||||
func parseFlags() (daemonAddr string, showSettings, showNetworks bool, errorMsg string, saveLogsInFile bool) {
|
func parseFlags() (daemonAddr string, showSettings, showNetworks, showDebug bool, errorMsg string, saveLogsInFile bool) {
|
||||||
defaultDaemonAddr := "unix:///var/run/netbird.sock"
|
defaultDaemonAddr := "unix:///var/run/netbird.sock"
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
defaultDaemonAddr = "tcp://127.0.0.1:41731"
|
||||||
@ -107,24 +107,16 @@ func parseFlags() (daemonAddr string, showSettings, showNetworks bool, errorMsg
|
|||||||
flag.StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
flag.StringVar(&daemonAddr, "daemon-addr", defaultDaemonAddr, "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
|
||||||
flag.BoolVar(&showSettings, "settings", false, "run settings window")
|
flag.BoolVar(&showSettings, "settings", false, "run settings window")
|
||||||
flag.BoolVar(&showNetworks, "networks", false, "run networks window")
|
flag.BoolVar(&showNetworks, "networks", false, "run networks window")
|
||||||
|
flag.BoolVar(&showDebug, "debug", false, "run debug window")
|
||||||
flag.StringVar(&errorMsg, "error-msg", "", "displays an error message window")
|
flag.StringVar(&errorMsg, "error-msg", "", "displays an error message window")
|
||||||
|
flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", os.TempDir()))
|
||||||
tmpDir := "/tmp"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
tmpDir = os.TempDir()
|
|
||||||
}
|
|
||||||
flag.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", tmpDir))
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// initLogFile initializes logging into a file.
|
// initLogFile initializes logging into a file.
|
||||||
func initLogFile() error {
|
func initLogFile() error {
|
||||||
tmpDir := "/tmp"
|
logFile := path.Join(os.TempDir(), fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
tmpDir = os.TempDir()
|
|
||||||
}
|
|
||||||
logFile := path.Join(tmpDir, fmt.Sprintf("netbird-ui-%d.log", os.Getpid()))
|
|
||||||
return util.InitLog("trace", logFile)
|
return util.InitLog("trace", logFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +223,7 @@ type serviceClient struct {
|
|||||||
daemonVersion string
|
daemonVersion string
|
||||||
updateIndicationLock sync.Mutex
|
updateIndicationLock sync.Mutex
|
||||||
isUpdateIconActive bool
|
isUpdateIconActive bool
|
||||||
showRoutes bool
|
showNetworks bool
|
||||||
wRoutes fyne.Window
|
wRoutes fyne.Window
|
||||||
|
|
||||||
eventManager *event.Manager
|
eventManager *event.Manager
|
||||||
@ -248,7 +240,7 @@ 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, showRoutes bool) *serviceClient {
|
func newServiceClient(addr string, a fyne.App, showSettings bool, showNetworks bool, showDebug bool) *serviceClient {
|
||||||
s := &serviceClient{
|
s := &serviceClient{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
addr: addr,
|
addr: addr,
|
||||||
@ -256,17 +248,21 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
|
|||||||
sendNotification: false,
|
sendNotification: false,
|
||||||
|
|
||||||
showAdvancedSettings: showSettings,
|
showAdvancedSettings: showSettings,
|
||||||
showRoutes: showRoutes,
|
showNetworks: showNetworks,
|
||||||
update: version.NewUpdate(),
|
update: version.NewUpdate(),
|
||||||
}
|
}
|
||||||
|
|
||||||
s.setNewIcons()
|
s.setNewIcons()
|
||||||
|
|
||||||
if showSettings {
|
switch {
|
||||||
|
case showSettings:
|
||||||
|
|
||||||
s.showSettingsUI()
|
s.showSettingsUI()
|
||||||
return s
|
return s
|
||||||
} else if showRoutes {
|
case showNetworks:
|
||||||
s.showNetworksUI()
|
s.showNetworksUI()
|
||||||
|
case showDebug:
|
||||||
|
s.showDebugUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
@ -743,11 +739,10 @@ func (s *serviceClient) onTrayReady() {
|
|||||||
s.runSelfCommand("settings", "true")
|
s.runSelfCommand("settings", "true")
|
||||||
}()
|
}()
|
||||||
case <-s.mCreateDebugBundle.ClickedCh:
|
case <-s.mCreateDebugBundle.ClickedCh:
|
||||||
|
s.mCreateDebugBundle.Disable()
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.createAndOpenDebugBundle(); err != nil {
|
defer s.mCreateDebugBundle.Enable()
|
||||||
log.Errorf("Failed to create debug bundle: %v", err)
|
s.runSelfCommand("debug", "true")
|
||||||
s.app.SendNotification(fyne.NewNotification("Error", "Failed to create debug bundle"))
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
case <-s.mQuit.ClickedCh:
|
case <-s.mQuit.ClickedCh:
|
||||||
systray.Quit()
|
systray.Quit()
|
||||||
|
@ -7,44 +7,270 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *serviceClient) createAndOpenDebugBundle() error {
|
func (s *serviceClient) showDebugUI() {
|
||||||
|
w := s.app.NewWindow("NetBird Debug")
|
||||||
|
w.Resize(fyne.NewSize(600, 400))
|
||||||
|
w.SetFixedSize(true)
|
||||||
|
|
||||||
|
anonymizeCheck := widget.NewCheck("Anonymize sensitive information (Public IPs, domains, ...)", nil)
|
||||||
|
systemInfoCheck := widget.NewCheck("Include system information", nil)
|
||||||
|
systemInfoCheck.SetChecked(true)
|
||||||
|
uploadCheck := widget.NewCheck("Upload bundle automatically after creation", nil)
|
||||||
|
uploadCheck.SetChecked(true)
|
||||||
|
|
||||||
|
uploadURLLabel := widget.NewLabel("Debug upload URL:")
|
||||||
|
uploadURL := widget.NewEntry()
|
||||||
|
uploadURL.SetText(uptypes.DefaultBundleURL)
|
||||||
|
uploadURL.SetPlaceHolder("Enter upload URL")
|
||||||
|
|
||||||
|
statusLabel := widget.NewLabel("")
|
||||||
|
statusLabel.Hide()
|
||||||
|
|
||||||
|
createButton := widget.NewButton("Create Debug Bundle", nil)
|
||||||
|
|
||||||
|
uploadURLContainer := container.NewVBox(
|
||||||
|
uploadURLLabel,
|
||||||
|
uploadURL,
|
||||||
|
)
|
||||||
|
|
||||||
|
uploadCheck.OnChanged = func(checked bool) {
|
||||||
|
if checked {
|
||||||
|
uploadURLContainer.Show()
|
||||||
|
} else {
|
||||||
|
uploadURLContainer.Hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton.OnTapped = s.getCreateHandler(createButton, statusLabel, uploadCheck, uploadURL, anonymizeCheck, systemInfoCheck, w)
|
||||||
|
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel("Create a debug bundle to help troubleshoot issues with NetBird"),
|
||||||
|
widget.NewLabel(""),
|
||||||
|
anonymizeCheck,
|
||||||
|
systemInfoCheck,
|
||||||
|
uploadCheck,
|
||||||
|
uploadURLContainer,
|
||||||
|
widget.NewLabel(""),
|
||||||
|
statusLabel,
|
||||||
|
createButton,
|
||||||
|
)
|
||||||
|
|
||||||
|
paddedContent := container.NewPadded(content)
|
||||||
|
w.SetContent(paddedContent)
|
||||||
|
|
||||||
|
w.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) getCreateHandler(
|
||||||
|
createButton *widget.Button,
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
uploadCheck *widget.Check,
|
||||||
|
uploadURL *widget.Entry,
|
||||||
|
anonymizeCheck *widget.Check,
|
||||||
|
systemInfoCheck *widget.Check,
|
||||||
|
w fyne.Window,
|
||||||
|
) func() {
|
||||||
|
return func() {
|
||||||
|
createButton.Disable()
|
||||||
|
statusLabel.SetText("Creating debug bundle...")
|
||||||
|
statusLabel.Show()
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if uploadCheck.Checked {
|
||||||
|
url = uploadURL.Text
|
||||||
|
if url == "" {
|
||||||
|
statusLabel.SetText("Error: Upload URL is required when upload is enabled")
|
||||||
|
createButton.Enable()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go s.handleDebugCreation(anonymizeCheck.Checked, systemInfoCheck.Checked, uploadCheck.Checked, url, statusLabel, createButton, w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) handleDebugCreation(
|
||||||
|
anonymize bool,
|
||||||
|
systemInfo bool,
|
||||||
|
upload bool,
|
||||||
|
uploadURL string,
|
||||||
|
statusLabel *widget.Label,
|
||||||
|
createButton *widget.Button,
|
||||||
|
w fyne.Window,
|
||||||
|
) {
|
||||||
|
log.Infof("Creating debug bundle (Anonymized: %v, System Info: %v, Upload Attempt: %v)...",
|
||||||
|
anonymize, systemInfo, upload)
|
||||||
|
|
||||||
|
resp, err := s.createDebugBundle(anonymize, systemInfo, uploadURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create debug bundle: %v", err)
|
||||||
|
statusLabel.SetText(fmt.Sprintf("Error creating bundle: %v", err))
|
||||||
|
createButton.Enable()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localPath := resp.GetPath()
|
||||||
|
uploadFailureReason := resp.GetUploadFailureReason()
|
||||||
|
uploadedKey := resp.GetUploadedKey()
|
||||||
|
|
||||||
|
if upload {
|
||||||
|
if uploadFailureReason != "" {
|
||||||
|
showUploadFailedDialog(w, localPath, uploadFailureReason)
|
||||||
|
} else {
|
||||||
|
showUploadSuccessDialog(w, localPath, uploadedKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showBundleCreatedDialog(w, localPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton.Enable()
|
||||||
|
statusLabel.SetText("Bundle created successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *serviceClient) createDebugBundle(anonymize bool, systemInfo bool, uploadURL string) (*proto.DebugBundleResponse, error) {
|
||||||
conn, err := s.getSrvClient(failFastTimeout)
|
conn, err := s.getSrvClient(failFastTimeout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("get client: %v", err)
|
return nil, fmt.Errorf("get client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get status: %v", err)
|
log.Warnf("failed to get status for debug bundle: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, true, "", nil, nil, nil)
|
var statusOutput string
|
||||||
statusOutput := nbstatus.ParseToFullDetailSummary(overview)
|
if statusResp != nil {
|
||||||
|
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil)
|
||||||
|
statusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := conn.DebugBundle(s.ctx, &proto.DebugBundleRequest{
|
request := &proto.DebugBundleRequest{
|
||||||
Anonymize: true,
|
Anonymize: anonymize,
|
||||||
Status: statusOutput,
|
Status: statusOutput,
|
||||||
SystemInfo: true,
|
SystemInfo: systemInfo,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if uploadURL != "" {
|
||||||
|
request.UploadURL = uploadURL
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := conn.DebugBundle(s.ctx, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create debug bundle: %v", err)
|
return nil, fmt.Errorf("failed to create debug bundle via daemon: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bundleDir := filepath.Dir(resp.GetPath())
|
return resp, nil
|
||||||
if err := open.Start(bundleDir); err != nil {
|
}
|
||||||
return fmt.Errorf("failed to open debug bundle directory: %v", err)
|
|
||||||
}
|
// showUploadFailedDialog displays a dialog when upload fails
|
||||||
|
func showUploadFailedDialog(parent fyne.Window, localPath, failureReason string) {
|
||||||
s.app.SendNotification(fyne.NewNotification(
|
content := container.NewVBox(
|
||||||
"Debug Bundle",
|
widget.NewLabel(fmt.Sprintf("Bundle upload failed:\n%s\n\n"+
|
||||||
fmt.Sprintf("Debug bundle created at %s. Administrator privileges are required to access it.", resp.GetPath()),
|
"A local copy was saved at:\n%s", failureReason, localPath)),
|
||||||
))
|
)
|
||||||
|
|
||||||
return nil
|
customDialog := dialog.NewCustom("Upload Failed", "Cancel", content, parent)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// showUploadSuccessDialog displays a dialog when upload succeeds
|
||||||
|
func showUploadSuccessDialog(parent fyne.Window, localPath, uploadedKey string) {
|
||||||
|
keyEntry := widget.NewEntry()
|
||||||
|
keyEntry.SetText(uploadedKey)
|
||||||
|
keyEntry.Disable()
|
||||||
|
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel("Bundle uploaded successfully!"),
|
||||||
|
widget.NewLabel(""),
|
||||||
|
widget.NewLabel("Upload Key:"),
|
||||||
|
keyEntry,
|
||||||
|
widget.NewLabel(""),
|
||||||
|
widget.NewLabel(fmt.Sprintf("Local copy saved at:\n%s", localPath)),
|
||||||
|
)
|
||||||
|
|
||||||
|
customDialog := dialog.NewCustom("Upload Successful", "OK", content, parent)
|
||||||
|
|
||||||
|
buttonBox := container.NewHBox(
|
||||||
|
widget.NewButton("Copy Key", func() {
|
||||||
|
parent.Clipboard().SetContent(uploadedKey)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// showBundleCreatedDialog displays a dialog when bundle is created without upload
|
||||||
|
func showBundleCreatedDialog(parent fyne.Window, localPath string) {
|
||||||
|
content := container.NewVBox(
|
||||||
|
widget.NewLabel(fmt.Sprintf("Bundle created locally at:\n%s\n\n"+
|
||||||
|
"Administrator privileges may be required to access the file.", localPath)),
|
||||||
|
)
|
||||||
|
|
||||||
|
customDialog := dialog.NewCustom("Debug Bundle Created", "Cancel", content, parent)
|
||||||
|
|
||||||
|
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()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
content.Add(buttonBox)
|
||||||
|
customDialog.Show()
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ const (
|
|||||||
ClientHeaderValue = "netbird"
|
ClientHeaderValue = "netbird"
|
||||||
// GetURLPath is the path for the GetURL request
|
// GetURLPath is the path for the GetURL request
|
||||||
GetURLPath = "/upload-url"
|
GetURLPath = "/upload-url"
|
||||||
|
|
||||||
|
DefaultBundleURL = "https://upload.debug.netbird.io" + GetURLPath
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetURLResponse is the response for the GetURL request
|
// GetURLResponse is the response for the GetURL request
|
||||||
|
Reference in New Issue
Block a user