Files
netbird/client/ui/debug.go

277 lines
8.2 KiB
Go

//go:build !(linux && 386)
package main
import (
"fmt"
"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) 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 nil, fmt.Errorf("get client: %v", err)
}
statusResp, err := conn.Status(s.ctx, &proto.StatusRequest{GetFullPeerStatus: true})
if err != nil {
log.Warnf("failed to get status for debug bundle: %v", err)
}
var statusOutput string
if statusResp != nil {
overview := nbstatus.ConvertToStatusOutputOverview(statusResp, anonymize, "", nil, nil, nil)
statusOutput = nbstatus.ParseToFullDetailSummary(overview)
}
request := &proto.DebugBundleRequest{
Anonymize: anonymize,
Status: statusOutput,
SystemInfo: systemInfo,
}
if uploadURL != "" {
request.UploadURL = uploadURL
}
resp, err := conn.DebugBundle(s.ctx, request)
if err != nil {
return nil, fmt.Errorf("failed to create debug bundle via daemon: %v", err)
}
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()
}