From 6de057052066c9c137131351c3f39b41a2885fc9 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Tue, 16 Apr 2019 18:20:56 +0200 Subject: ui: implement log dialog; some refactoring in manage tunnels window to share some bits Signed-off-by: Alexander Neumann Signed-off-by: Jason A. Donenfeld --- ui/logdialog.go | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++ ui/manage_tunnels.go | 85 +++++-------------------- ui/util.go | 45 +++++++++++++ 3 files changed, 234 insertions(+), 70 deletions(-) create mode 100644 ui/logdialog.go create mode 100644 ui/util.go diff --git a/ui/logdialog.go b/ui/logdialog.go new file mode 100644 index 00000000..6cc176e1 --- /dev/null +++ b/ui/logdialog.go @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package ui + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/lxn/win" + + "github.com/lxn/walk" + "golang.zx2c4.com/wireguard/windows/ringlogger" +) + +func runLogDialog(owner walk.Form, logger *ringlogger.Ringlogger) { + dlg := &LogDialog{logger: logger} + dlg.model = newLogModel(dlg, logger) + defer func() { + dlg.model.quit <- true + }() + + var disposables walk.Disposables + defer disposables.Treat() + + showError := func(err error) bool { + if err == nil { + return false + } + + walk.MsgBox(owner, "Viewing log dialog failed", err.Error(), walk.MsgBoxIconError) + + return true + } + + var err error + + if dlg.Dialog, err = walk.NewDialog(owner); showError(err) { + return + } + disposables.Add(dlg) + + dlg.SetTitle("WireGuard Log") + dlg.SetLayout(walk.NewVBoxLayout()) + dlg.SetMinMaxSize(walk.Size{600, 400}, walk.Size{}) + + if dlg.logView, err = walk.NewTableView(dlg); showError(err) { + return + } + dlg.logView.SetAlternatingRowBGColor(walk.Color(win.GetSysColor(win.COLOR_BTNFACE))) + dlg.logView.SetLastColumnStretched(true) + + stampCol := walk.NewTableViewColumn() + stampCol.SetName("Stamp") + stampCol.SetTitle("Time") + stampCol.SetFormat("2006-01-02 15:04:05.000") + stampCol.SetWidth(150) + dlg.logView.Columns().Add(stampCol) + + msgCol := walk.NewTableViewColumn() + msgCol.SetName("Line") + msgCol.SetTitle("Log message") + dlg.logView.Columns().Add(msgCol) + + dlg.logView.SetModel(dlg.model) + dlg.logView.SetCurrentIndex(len(dlg.model.items) - 1) + + buttonsContainer, err := walk.NewComposite(dlg) + buttonsContainer.SetLayout(walk.NewHBoxLayout()) + + saveButton, err := walk.NewPushButton(buttonsContainer) + saveButton.SetText("Save") + saveButton.Clicked().Attach(dlg.onSaveButtonClicked) + + walk.NewHSpacer(buttonsContainer) + + closeButton, err := walk.NewPushButton(buttonsContainer) + closeButton.SetText("Close") + closeButton.Clicked().Attach(dlg.onCloseButtonClicked) + + dlg.SetDefaultButton(closeButton) + dlg.SetCancelButton(closeButton) + + disposables.Spare() + + dlg.Run() +} + +type LogDialog struct { + *walk.Dialog + logView *walk.TableView + logger *ringlogger.Ringlogger + model *logModel +} + +func (dlg *LogDialog) onSaveButtonClicked() { + fd := walk.FileDialog{ + Filter: "Text Files (*.txt)|*.txt|All Files (*.*)|*.*", + FilePath: fmt.Sprintf("wireguard-log-%s.txt", time.Now().Format("2006-01-02T150405")), + Title: "Export log to file", + } + + if ok, _ := fd.ShowSave(dlg); !ok { + return + } + + extensions := []string{".log", ".txt"} + if fd.FilterIndex < 3 && !strings.HasSuffix(fd.FilePath, extensions[fd.FilterIndex-1]) { + fd.FilePath = fd.FilePath + extensions[fd.FilterIndex-1] + } + + writeFileWithOverwriteHandling(dlg, fd.FilePath, func(file *os.File) error { + if _, err := dlg.logger.WriteTo(file); err != nil { + return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %v", err) + } + + return nil + }) +} + +func (dlg *LogDialog) onCloseButtonClicked() { + dlg.Accept() +} + +type logModel struct { + walk.ReflectTableModelBase + dlg *LogDialog + quit chan bool + logger *ringlogger.Ringlogger + items []ringlogger.FollowLine +} + +func newLogModel(dlg *LogDialog, logger *ringlogger.Ringlogger) *logModel { + mdl := &logModel{dlg: dlg, quit: make(chan bool), logger: logger} + mdl.items, _ = logger.FollowFromCursor(ringlogger.CursorAll) + + go func() { + ticker := time.NewTicker(time.Second) + + for { + select { + case <-ticker.C: + items, _ := mdl.logger.FollowFromCursor(ringlogger.CursorAll) + + if len(items) > 0 && (len(mdl.items) == 0 || items[len(items)-1].Stamp.After(mdl.items[len(mdl.items)-1].Stamp)) { + mdl.dlg.Synchronize(func() { + scrollToMostRecent := mdl.dlg.logView.CurrentIndex() == len(mdl.items)-1 + + mdl.items = items + mdl.PublishRowsReset() + + if scrollToMostRecent { + mdl.dlg.logView.SetCurrentIndex(len(mdl.items) - 1) + } + }) + } + + case <-mdl.quit: + ticker.Stop() + break + } + } + }() + + return mdl +} + +func (mdl *logModel) Items() interface{} { + return mdl.items +} diff --git a/ui/manage_tunnels.go b/ui/manage_tunnels.go index bd94c551..353b4f68 100644 --- a/ui/manage_tunnels.go +++ b/ui/manage_tunnels.go @@ -100,9 +100,9 @@ func (mtw *ManageTunnelsWindow) setup() error { addAction.SetText("Add empty tunnel") addAction.Triggered().Attach(mtw.onAddTunnel) - exportLogAction := walk.NewAction() - exportLogAction.SetText("Export log to file...") - exportLogAction.Triggered().Attach(mtw.onExportLog) + viewLogAction := walk.NewAction() + viewLogAction.SetText("View Log") + viewLogAction.Triggered().Attach(mtw.onViewLog) exportTunnelsAction := walk.NewAction() exportTunnelsAction.SetText("Export tunnels to zip...") @@ -121,7 +121,7 @@ func (mtw *ManageTunnelsWindow) setup() error { deleteAction.Triggered().Attach(mtw.onDelete) settingsMenu, _ := walk.NewMenu() - settingsMenu.Actions().Add(exportLogAction) + settingsMenu.Actions().Add(viewLogAction) settingsMenu.Actions().Add(exportTunnelsAction) settingsMenuAction, _ := tunnelsToolBar.Actions().AddMenu(settingsMenu) settingsMenuAction.SetText("Export") @@ -288,7 +288,7 @@ func (mtw *ManageTunnelsWindow) importFiles(paths []string) { } func (mtw *ManageTunnelsWindow) exportTunnels(filePath string) { - mtw.writeFileWithOverwriteHandling(filePath, func(file *os.File) error { + writeFileWithOverwriteHandling(mtw, filePath, func(file *os.File) error { writer := zip.NewWriter(file) for _, tunnel := range mtw.tunnelsView.model.tunnels { @@ -359,48 +359,6 @@ func (mtw *ManageTunnelsWindow) TunnelDeleted() *walk.StringEvent { return mtw.tunnelDeletedPublisher.Event() } -func (mtw *ManageTunnelsWindow) exportLog(filePath string) { - mtw.writeFileWithOverwriteHandling(filePath, func(file *os.File) error { - if _, err := mtw.logger.WriteTo(file); err != nil { - return fmt.Errorf("exportLog: Ringlogger.WriteTo failed: %v", err) - } - - return nil - }) -} - -func (mtw *ManageTunnelsWindow) writeFileWithOverwriteHandling(filePath string, write func(file *os.File) error) bool { - showError := func(err error) bool { - if err == nil { - return false - } - - walk.MsgBox(mtw, "Writing file failed", err.Error(), walk.MsgBoxIconError) - - return true - } - - file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600) - if err != nil { - if os.IsExist(err) { - if walk.DlgCmdNo == walk.MsgBox(mtw, "Writing file failed", fmt.Sprintf(`File "%s" already exists. - -Do you want to overwrite it?`, filePath), walk.MsgBoxYesNo|walk.MsgBoxDefButton2|walk.MsgBoxIconWarning) { - return false - } - - if file, err = os.Create(filePath); err != nil { - return !showError(err) - } - } else { - return !showError(err) - } - } - defer file.Close() - - return !showError(write(file)) -} - // Handlers func (mtw *ManageTunnelsWindow) onEditTunnel() { @@ -447,10 +405,10 @@ func (mtw *ManageTunnelsWindow) onDelete() { } func (mtw *ManageTunnelsWindow) onImport() { - dlg := &walk.FileDialog{} - // dlg.InitialDirPath - dlg.Filter = "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*" - dlg.Title = "Import tunnel(s) from file..." + dlg := walk.FileDialog{ + Filter: "Configuration Files (*.zip, *.conf)|*.zip;*.conf|All Files (*.*)|*.*", + Title: "Import tunnel(s) from file...", + } if ok, _ := dlg.ShowOpenMultiple(mtw); !ok { return @@ -460,9 +418,10 @@ func (mtw *ManageTunnelsWindow) onImport() { } func (mtw *ManageTunnelsWindow) onExportTunnels() { - dlg := &walk.FileDialog{} - dlg.Filter = "Configuration ZIP Files (*.zip)|*.zip" - dlg.Title = "Export tunnels to zip..." + dlg := walk.FileDialog{ + Filter: "Configuration ZIP Files (*.zip)|*.zip", + Title: "Export tunnels to zip...", + } if ok, _ := dlg.ShowSave(mtw); !ok { return @@ -475,20 +434,6 @@ func (mtw *ManageTunnelsWindow) onExportTunnels() { mtw.exportTunnels(dlg.FilePath) } -func (mtw *ManageTunnelsWindow) onExportLog() { - dlg := walk.FileDialog{ - Filter: "Log Files (*.log)|*.log|Text Files (*.txt)|*.txt|All Files (*.*)|*.*", - Title: "Export log to file", - } - - if ok, _ := dlg.ShowSave(mtw); !ok { - return - } - - extensions := []string{".log", ".txt"} - if dlg.FilterIndex < 3 && !strings.HasSuffix(dlg.FilePath, extensions[dlg.FilterIndex-1]) { - dlg.FilePath = dlg.FilePath + extensions[dlg.FilterIndex-1] - } - - mtw.exportLog(dlg.FilePath) +func (mtw *ManageTunnelsWindow) onViewLog() { + runLogDialog(mtw, mtw.logger) } diff --git a/ui/util.go b/ui/util.go new file mode 100644 index 00000000..b17f106c --- /dev/null +++ b/ui/util.go @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: MIT + * + * Copyright (C) 2019 WireGuard LLC. All Rights Reserved. + */ + +package ui + +import ( + "fmt" + "os" + + "github.com/lxn/walk" +) + +func writeFileWithOverwriteHandling(owner walk.Form, filePath string, write func(file *os.File) error) bool { + showError := func(err error) bool { + if err == nil { + return false + } + + walk.MsgBox(owner, "Writing file failed", err.Error(), walk.MsgBoxIconError) + + return true + } + + file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600) + if err != nil { + if os.IsExist(err) { + if walk.DlgCmdNo == walk.MsgBox(owner, "Writing file failed", fmt.Sprintf(`File "%s" already exists. + +Do you want to overwrite it?`, filePath), walk.MsgBoxYesNo|walk.MsgBoxDefButton2|walk.MsgBoxIconWarning) { + return false + } + + if file, err = os.Create(filePath); err != nil { + return !showError(err) + } + } else { + return !showError(err) + } + } + defer file.Close() + + return !showError(write(file)) +} -- cgit v1.2.3-59-g8ed1b