Add local-port and public-tunnel reachability checks
This commit is contained in:
+183
-4
@@ -64,8 +64,20 @@ type App struct {
|
||||
configLabel *widget.Label
|
||||
startButton *widget.Button
|
||||
routeBox *fyne.Container
|
||||
routeRows map[string]*routeRow
|
||||
checkSummary *widget.Label
|
||||
logText *widget.TextGrid
|
||||
banner fyne.CanvasObject
|
||||
|
||||
checkMu sync.Mutex
|
||||
checks map[string]client.RouteCheck
|
||||
checking bool
|
||||
lastCheck time.Time
|
||||
}
|
||||
|
||||
type routeRow struct {
|
||||
local *widget.Label
|
||||
public *widget.Label
|
||||
}
|
||||
|
||||
// Run creates and runs the desktop app.
|
||||
@@ -167,9 +179,16 @@ func (a *App) buildUI() {
|
||||
routeHeader := container.NewHBox(
|
||||
widget.NewLabelWithStyle("Routes", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
layout.NewSpacer(),
|
||||
widget.NewButtonWithIcon("Check now", theme.ViewRefreshIcon(), a.checkRoutes),
|
||||
widget.NewButtonWithIcon("Add route", theme.ContentAddIcon(), a.addRouteDialog),
|
||||
)
|
||||
a.routeBox = container.NewVBox()
|
||||
a.routeRows = map[string]*routeRow{}
|
||||
a.checks = map[string]client.RouteCheck{}
|
||||
a.checkSummary = widget.NewLabel("Not checked yet")
|
||||
a.checkSummary.Importance = widget.LowImportance
|
||||
checkDetails := widget.NewButton("Details", a.showCheckDetails)
|
||||
checkBar := container.NewHBox(a.checkSummary, checkDetails)
|
||||
a.logText = widget.NewTextGrid()
|
||||
logScroll := container.NewScroll(a.logText)
|
||||
logScroll.SetMinSize(fyne.NewSize(0, 220))
|
||||
@@ -190,6 +209,7 @@ func (a *App) buildUI() {
|
||||
a.banner,
|
||||
routeHeader,
|
||||
a.routeBox,
|
||||
checkBar,
|
||||
widget.NewLabelWithStyle("Log", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
logScroll,
|
||||
)
|
||||
@@ -230,10 +250,20 @@ func (a *App) refresh() {
|
||||
}
|
||||
a.refreshRoutes()
|
||||
a.updateStatus()
|
||||
if a.svc != nil && a.svc.CurrentStatus().Connected {
|
||||
a.maybeCheck()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) refreshRoutes() {
|
||||
a.routeBox.Objects = nil
|
||||
a.routeRows = map[string]*routeRow{}
|
||||
a.checkMu.Lock()
|
||||
checks := make(map[string]client.RouteCheck, len(a.checks))
|
||||
for name, check := range a.checks {
|
||||
checks[name] = check
|
||||
}
|
||||
a.checkMu.Unlock()
|
||||
for _, name := range a.routeNames() {
|
||||
route := a.cfg.Routes[name]
|
||||
linkURL := a.cfg.RouteURL(name)
|
||||
@@ -241,25 +271,169 @@ func (a *App) refreshRoutes() {
|
||||
if parsed, err := url.Parse(linkURL); err == nil {
|
||||
link.SetURL(parsed)
|
||||
}
|
||||
local := widget.NewLabel("local —")
|
||||
public := widget.NewLabel("public —")
|
||||
local.Importance = widget.LowImportance
|
||||
public.Importance = widget.LowImportance
|
||||
row := &routeRow{local: local, public: public}
|
||||
if check, ok := checks[name]; ok {
|
||||
setCheckLabel(local, "local", check.LocalUp, check.LocalError)
|
||||
setCheckLabel(public, "public", check.PublicUp, check.PublicError)
|
||||
}
|
||||
a.routeRows[name] = row
|
||||
copyButton := widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {
|
||||
a.app.Clipboard().SetContent(linkURL)
|
||||
})
|
||||
deleteButton := widget.NewButtonWithIcon("", theme.DeleteIcon(), func() {
|
||||
a.removeRoute(name)
|
||||
})
|
||||
row := container.NewHBox(
|
||||
container.NewGridWrap(fyne.NewSize(130, 32), widget.NewLabel(name)),
|
||||
container.NewGridWrap(fyne.NewSize(170, 32), widget.NewLabel(route.Target)),
|
||||
line := container.NewHBox(
|
||||
container.NewGridWrap(fyne.NewSize(120, 32), widget.NewLabel(name)),
|
||||
container.NewGridWrap(fyne.NewSize(150, 32), widget.NewLabel(route.Target)),
|
||||
link,
|
||||
layout.NewSpacer(),
|
||||
local,
|
||||
public,
|
||||
copyButton,
|
||||
deleteButton,
|
||||
)
|
||||
a.routeBox.Add(row)
|
||||
a.routeBox.Add(line)
|
||||
}
|
||||
a.routeBox.Refresh()
|
||||
}
|
||||
|
||||
func setCheckLabel(label *widget.Label, kind string, up bool, detail string) {
|
||||
if up {
|
||||
label.SetText(kind + " ✓")
|
||||
label.Importance = widget.SuccessImportance
|
||||
} else {
|
||||
label.SetText(kind + " ✗")
|
||||
label.Importance = widget.DangerImportance
|
||||
}
|
||||
label.Refresh()
|
||||
_ = detail
|
||||
}
|
||||
|
||||
func (a *App) checkRoutes() {
|
||||
if a.cfg == nil {
|
||||
return
|
||||
}
|
||||
routes := make(map[string]client.Route, len(a.cfg.Routes))
|
||||
for name, route := range a.cfg.Routes {
|
||||
routes[name] = route
|
||||
}
|
||||
if len(routes) == 0 {
|
||||
a.checkSummary.SetText("No routes")
|
||||
a.checkSummary.Importance = widget.LowImportance
|
||||
a.checkSummary.Refresh()
|
||||
return
|
||||
}
|
||||
snapshot := &client.Config{API: a.cfg.API, Hostname: a.cfg.Hostname, Routes: routes}
|
||||
a.checkMu.Lock()
|
||||
if a.checking {
|
||||
a.checkMu.Unlock()
|
||||
return
|
||||
}
|
||||
a.checking = true
|
||||
a.checkMu.Unlock()
|
||||
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
results := client.CheckAll(ctx, snapshot)
|
||||
checks := make(map[string]client.RouteCheck, len(results))
|
||||
for _, result := range results {
|
||||
checks[result.Route] = result
|
||||
}
|
||||
a.checkMu.Lock()
|
||||
a.checks = checks
|
||||
a.lastCheck = time.Now()
|
||||
a.checking = false
|
||||
a.checkMu.Unlock()
|
||||
fyne.Do(a.applyChecks)
|
||||
}()
|
||||
}
|
||||
|
||||
func (a *App) maybeCheck() {
|
||||
a.checkMu.Lock()
|
||||
due := !a.checking && time.Since(a.lastCheck) > 30*time.Second
|
||||
a.checkMu.Unlock()
|
||||
if due {
|
||||
a.checkRoutes()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) applyChecks() {
|
||||
a.checkMu.Lock()
|
||||
checks := make(map[string]client.RouteCheck, len(a.checks))
|
||||
for name, check := range a.checks {
|
||||
checks[name] = check
|
||||
}
|
||||
a.checkMu.Unlock()
|
||||
|
||||
results := make([]client.RouteCheck, 0, len(a.routeRows))
|
||||
for name, row := range a.routeRows {
|
||||
check, ok := checks[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
setCheckLabel(row.local, "local", check.LocalUp, check.LocalError)
|
||||
setCheckLabel(row.public, "public", check.PublicUp, check.PublicError)
|
||||
results = append(results, check)
|
||||
}
|
||||
if len(results) == 0 {
|
||||
a.checkSummary.SetText("Not checked yet")
|
||||
a.checkSummary.Importance = widget.LowImportance
|
||||
} else {
|
||||
a.checkSummary.SetText(client.CheckSummary(results))
|
||||
a.checkSummary.Importance = widget.SuccessImportance
|
||||
for _, result := range results {
|
||||
if !result.LocalUp || !result.PublicUp {
|
||||
a.checkSummary.Importance = widget.DangerImportance
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
a.checkSummary.Refresh()
|
||||
}
|
||||
|
||||
func (a *App) showCheckDetails() {
|
||||
a.checkMu.Lock()
|
||||
checks := make(map[string]client.RouteCheck, len(a.checks))
|
||||
for name, check := range a.checks {
|
||||
checks[name] = check
|
||||
}
|
||||
a.checkMu.Unlock()
|
||||
lines := []string{}
|
||||
for _, name := range a.routeNames() {
|
||||
check, ok := checks[name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if check.LocalError != "" {
|
||||
lines = append(lines, fmt.Sprintf("%s local: %s", name, check.LocalError))
|
||||
}
|
||||
if check.PublicError != "" {
|
||||
lines = append(lines, fmt.Sprintf("%s public: %s", name, check.PublicError))
|
||||
}
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
lines = []string{"No errors recorded."}
|
||||
}
|
||||
a.showInfo(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func (a *App) resetChecks() {
|
||||
a.checkMu.Lock()
|
||||
a.checks = map[string]client.RouteCheck{}
|
||||
a.checking = false
|
||||
a.lastCheck = time.Time{}
|
||||
a.checkMu.Unlock()
|
||||
a.checkSummary.SetText("Not checked yet")
|
||||
a.checkSummary.Importance = widget.LowImportance
|
||||
a.checkSummary.Refresh()
|
||||
}
|
||||
|
||||
func (a *App) refreshLog() {
|
||||
a.logText.SetText(a.logWriter.text(300))
|
||||
}
|
||||
@@ -307,6 +481,9 @@ func (a *App) updateStatus() {
|
||||
a.dot.Refresh()
|
||||
a.startButton.Enable()
|
||||
a.refreshRoutes()
|
||||
if status.Connected {
|
||||
a.maybeCheck()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) isRunning() bool {
|
||||
@@ -471,6 +648,7 @@ func (a *App) addRouteDialog() {
|
||||
a.showError(err)
|
||||
return
|
||||
}
|
||||
a.resetChecks()
|
||||
a.refresh()
|
||||
if !a.isRunning() && a.cfg.API != "" {
|
||||
a.start()
|
||||
@@ -487,6 +665,7 @@ func (a *App) removeRoute(name string) {
|
||||
a.showError(err)
|
||||
return
|
||||
}
|
||||
a.resetChecks()
|
||||
a.refresh()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user