mirror of
https://github.com/netbirdio/netbird.git
synced 2025-06-19 00:06:58 +02:00
[client] Add debug upload option to netbird ui (#3768)
This commit is contained in:
parent
b5419ef11a
commit
9bc7d788f0
@ -42,7 +42,6 @@ const (
|
||||
blockLANAccessFlag = "block-lan-access"
|
||||
uploadBundle = "upload-bundle"
|
||||
uploadBundleURL = "upload-bundle-url"
|
||||
defaultBundleURL = "https://upload.debug.netbird.io" + types.GetURLPath
|
||||
)
|
||||
|
||||
var (
|
||||
@ -188,7 +187,7 @@ func init() {
|
||||
|
||||
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().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
|
||||
|
@ -51,7 +51,7 @@ const (
|
||||
)
|
||||
|
||||
func main() {
|
||||
daemonAddr, showSettings, showNetworks, errorMsg, saveLogsInFile := parseFlags()
|
||||
daemonAddr, showSettings, showNetworks, showDebug, errorMsg, saveLogsInFile := parseFlags()
|
||||
|
||||
// Initialize file logging if needed.
|
||||
if saveLogsInFile {
|
||||
@ -72,13 +72,13 @@ func main() {
|
||||
}
|
||||
|
||||
// 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.
|
||||
go watchSettingsChanges(a, client)
|
||||
|
||||
// Run in window mode if any UI flag was set.
|
||||
if showSettings || showNetworks {
|
||||
if showSettings || showNetworks || showDebug {
|
||||
a.Run()
|
||||
return
|
||||
}
|
||||
@ -99,7 +99,7 @@ func main() {
|
||||
}
|
||||
|
||||
// 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"
|
||||
if runtime.GOOS == "windows" {
|
||||
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.BoolVar(&showSettings, "settings", false, "run settings 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")
|
||||
|
||||
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.BoolVar(&saveLogsInFile, "use-log-file", false, fmt.Sprintf("save logs in a file: %s/netbird-ui-PID.log", os.TempDir()))
|
||||
flag.Parse()
|
||||
return
|
||||
}
|
||||
|
||||
// initLogFile initializes logging into a file.
|
||||
func initLogFile() error {
|
||||
tmpDir := "/tmp"
|
||||
if runtime.GOOS == "windows" {
|
||||
tmpDir = os.TempDir()
|
||||
}
|
||||
logFile := path.Join(tmpDir, 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)
|
||||
}
|
||||
|
||||
@ -231,7 +223,7 @@ type serviceClient struct {
|
||||
daemonVersion string
|
||||
updateIndicationLock sync.Mutex
|
||||
isUpdateIconActive bool
|
||||
showRoutes bool
|
||||
showNetworks bool
|
||||
wRoutes fyne.Window
|
||||
|
||||
eventManager *event.Manager
|
||||
@ -248,7 +240,7 @@ type menuHandler struct {
|
||||
// newServiceClient instance constructor
|
||||
//
|
||||
// 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{
|
||||
ctx: context.Background(),
|
||||
addr: addr,
|
||||
@ -256,17 +248,21 @@ func newServiceClient(addr string, a fyne.App, showSettings bool, showRoutes boo
|
||||
sendNotification: false,
|
||||
|
||||
showAdvancedSettings: showSettings,
|
||||
showRoutes: showRoutes,
|
||||
showNetworks: showNetworks,
|
||||
update: version.NewUpdate(),
|
||||
}
|
||||
|
||||
s.setNewIcons()
|
||||
|
||||
if showSettings {
|
||||
switch {
|
||||
case showSettings:
|
||||
|
||||
s.showSettingsUI()
|
||||
return s
|
||||
} else if showRoutes {
|
||||
case showNetworks:
|
||||
s.showNetworksUI()
|
||||
case showDebug:
|
||||
s.showDebugUI()
|
||||
}
|
||||
|
||||
return s
|
||||
@ -743,11 +739,10 @@ func (s *serviceClient) onTrayReady() {
|
||||
s.runSelfCommand("settings", "true")
|
||||
}()
|
||||
case <-s.mCreateDebugBundle.ClickedCh:
|
||||
s.mCreateDebugBundle.Disable()
|
||||
go func() {
|
||||
if err := s.createAndOpenDebugBundle(); err != nil {
|
||||
log.Errorf("Failed to create debug bundle: %v", err)
|
||||
s.app.SendNotification(fyne.NewNotification("Error", "Failed to create debug bundle"))
|
||||
}
|
||||
defer s.mCreateDebugBundle.Enable()
|
||||
s.runSelfCommand("debug", "true")
|
||||
}()
|
||||
case <-s.mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
|
@ -7,44 +7,270 @@ import (
|
||||
"path/filepath"
|
||||
|
||||
"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/netbirdio/netbird/client/proto"
|
||||
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)
|
||||
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})
|
||||
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)
|
||||
statusOutput := nbstatus.ParseToFullDetailSummary(overview)
|
||||
var statusOutput string
|
||||
if statusResp != nil {
|
||||
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil)
|
||||
statusOutput = nbstatus.ParseToFullDetailSummary(overview)
|
||||
}
|
||||
|
||||
resp, err := conn.DebugBundle(s.ctx, &proto.DebugBundleRequest{
|
||||
Anonymize: true,
|
||||
request := &proto.DebugBundleRequest{
|
||||
Anonymize: anonymize,
|
||||
Status: statusOutput,
|
||||
SystemInfo: true,
|
||||
})
|
||||
SystemInfo: systemInfo,
|
||||
}
|
||||
|
||||
if uploadURL != "" {
|
||||
request.UploadURL = uploadURL
|
||||
}
|
||||
|
||||
resp, err := conn.DebugBundle(s.ctx, request)
|
||||
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())
|
||||
if err := open.Start(bundleDir); err != nil {
|
||||
return fmt.Errorf("failed to open debug bundle directory: %v", err)
|
||||
}
|
||||
|
||||
s.app.SendNotification(fyne.NewNotification(
|
||||
"Debug Bundle",
|
||||
fmt.Sprintf("Debug bundle created at %s. Administrator privileges are required to access it.", resp.GetPath()),
|
||||
))
|
||||
|
||||
return nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// showUploadFailedDialog displays a dialog when upload fails
|
||||
func showUploadFailedDialog(parent fyne.Window, localPath, failureReason string) {
|
||||
content := container.NewVBox(
|
||||
widget.NewLabel(fmt.Sprintf("Bundle upload failed:\n%s\n\n"+
|
||||
"A local copy was saved at:\n%s", failureReason, localPath)),
|
||||
)
|
||||
|
||||
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"
|
||||
// GetURLPath is the path for the GetURL request
|
||||
GetURLPath = "/upload-url"
|
||||
|
||||
DefaultBundleURL = "https://upload.debug.netbird.io" + GetURLPath
|
||||
)
|
||||
|
||||
// GetURLResponse is the response for the GetURL request
|
||||
|
Loading…
x
Reference in New Issue
Block a user