2024-06-14 11:17:55 +02:00
|
|
|
//go:build !(linux && 386) && !freebsd
|
2024-04-23 14:42:53 +02:00
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-07-04 16:50:07 +02:00
|
|
|
"sort"
|
2024-04-23 14:42:53 +02:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"fyne.io/fyne/v2"
|
|
|
|
"fyne.io/fyne/v2/container"
|
|
|
|
"fyne.io/fyne/v2/dialog"
|
|
|
|
"fyne.io/fyne/v2/layout"
|
|
|
|
"fyne.io/fyne/v2/widget"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
"github.com/netbirdio/netbird/client/proto"
|
|
|
|
)
|
|
|
|
|
2024-06-27 14:32:30 +02:00
|
|
|
const (
|
2024-12-20 11:30:28 +01:00
|
|
|
allNetworksText = "All networks"
|
|
|
|
overlappingNetworksText = "Overlapping networks"
|
|
|
|
exitNodeNetworksText = "Exit-node networks"
|
|
|
|
allNetworks filter = "all"
|
|
|
|
overlappingNetworks filter = "overlapping"
|
|
|
|
exitNodeNetworks filter = "exit-node"
|
|
|
|
getClientFMT = "get client: %v"
|
2024-06-27 14:32:30 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type filter string
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) showNetworksUI() {
|
|
|
|
s.wRoutes = s.app.NewWindow("Networks")
|
2024-04-23 14:42:53 +02:00
|
|
|
|
2024-06-27 14:32:30 +02:00
|
|
|
allGrid := container.New(layout.NewGridLayout(3))
|
2024-12-20 11:30:28 +01:00
|
|
|
go s.updateNetworks(allGrid, allNetworks)
|
2024-06-27 14:32:30 +02:00
|
|
|
overlappingGrid := container.New(layout.NewGridLayout(3))
|
|
|
|
exitNodeGrid := container.New(layout.NewGridLayout(3))
|
2024-04-23 14:42:53 +02:00
|
|
|
routeCheckContainer := container.NewVBox()
|
2024-06-27 14:32:30 +02:00
|
|
|
tabs := container.NewAppTabs(
|
2024-12-20 11:30:28 +01:00
|
|
|
container.NewTabItem(allNetworksText, allGrid),
|
|
|
|
container.NewTabItem(overlappingNetworksText, overlappingGrid),
|
|
|
|
container.NewTabItem(exitNodeNetworksText, exitNodeGrid),
|
2024-06-27 14:32:30 +02:00
|
|
|
)
|
|
|
|
tabs.OnSelected = func(item *container.TabItem) {
|
2024-12-20 11:30:28 +01:00
|
|
|
s.updateNetworksBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-06-27 14:32:30 +02:00
|
|
|
}
|
|
|
|
tabs.OnUnselected = func(item *container.TabItem) {
|
|
|
|
grid, _ := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
|
|
|
grid.Objects = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
routeCheckContainer.Add(tabs)
|
2024-04-23 14:42:53 +02:00
|
|
|
scrollContainer := container.NewVScroll(routeCheckContainer)
|
|
|
|
scrollContainer.SetMinSize(fyne.NewSize(200, 300))
|
|
|
|
|
|
|
|
buttonBox := container.NewHBox(
|
|
|
|
layout.NewSpacer(),
|
|
|
|
widget.NewButton("Refresh", func() {
|
2024-12-20 11:30:28 +01:00
|
|
|
s.updateNetworksBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-04-23 14:42:53 +02:00
|
|
|
}),
|
|
|
|
widget.NewButton("Select all", func() {
|
2024-06-27 14:32:30 +02:00
|
|
|
_, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-12-20 11:30:28 +01:00
|
|
|
s.selectAllFilteredNetworks(f)
|
|
|
|
s.updateNetworksBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-04-23 14:42:53 +02:00
|
|
|
}),
|
|
|
|
widget.NewButton("Deselect All", func() {
|
2024-06-27 14:32:30 +02:00
|
|
|
_, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-12-20 11:30:28 +01:00
|
|
|
s.deselectAllFilteredNetworks(f)
|
|
|
|
s.updateNetworksBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-04-23 14:42:53 +02:00
|
|
|
}),
|
|
|
|
layout.NewSpacer(),
|
|
|
|
)
|
|
|
|
|
|
|
|
content := container.NewBorder(nil, buttonBox, nil, nil, scrollContainer)
|
|
|
|
|
|
|
|
s.wRoutes.SetContent(content)
|
|
|
|
s.wRoutes.Show()
|
|
|
|
|
2024-06-27 14:32:30 +02:00
|
|
|
s.startAutoRefresh(10*time.Second, tabs, allGrid, overlappingGrid, exitNodeGrid)
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) updateNetworks(grid *fyne.Container, f filter) {
|
2024-04-23 14:42:53 +02:00
|
|
|
grid.Objects = nil
|
2024-06-27 14:32:30 +02:00
|
|
|
grid.Refresh()
|
2024-04-23 14:42:53 +02:00
|
|
|
idHeader := widget.NewLabelWithStyle(" ID", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
2024-12-20 11:30:28 +01:00
|
|
|
networkHeader := widget.NewLabelWithStyle("Range/Domains", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
2024-06-13 13:24:24 +02:00
|
|
|
resolvedIPsHeader := widget.NewLabelWithStyle("Resolved IPs", fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
2024-04-23 14:42:53 +02:00
|
|
|
|
|
|
|
grid.Add(idHeader)
|
|
|
|
grid.Add(networkHeader)
|
2024-06-13 13:24:24 +02:00
|
|
|
grid.Add(resolvedIPsHeader)
|
2024-06-27 14:32:30 +02:00
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
filteredRoutes, err := s.getFilteredNetworks(f)
|
2024-06-27 14:32:30 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
sortNetworksByIDs(filteredRoutes)
|
2024-07-04 16:50:07 +02:00
|
|
|
|
2024-06-27 14:32:30 +02:00
|
|
|
for _, route := range filteredRoutes {
|
2024-04-23 14:42:53 +02:00
|
|
|
r := route
|
|
|
|
|
2024-06-13 13:24:24 +02:00
|
|
|
checkBox := widget.NewCheck(r.GetID(), func(checked bool) {
|
2024-12-20 11:30:28 +01:00
|
|
|
s.selectNetwork(r.ID, checked)
|
2024-04-23 14:42:53 +02:00
|
|
|
})
|
|
|
|
checkBox.Checked = route.Selected
|
|
|
|
checkBox.Resize(fyne.NewSize(20, 20))
|
|
|
|
checkBox.Refresh()
|
|
|
|
|
|
|
|
grid.Add(checkBox)
|
2024-12-20 11:30:28 +01:00
|
|
|
network := r.GetRange()
|
2024-06-13 13:24:24 +02:00
|
|
|
domains := r.GetDomains()
|
2024-06-24 16:18:00 +02:00
|
|
|
|
|
|
|
if len(domains) == 0 {
|
|
|
|
grid.Add(widget.NewLabel(network))
|
|
|
|
grid.Add(widget.NewLabel(""))
|
|
|
|
continue
|
2024-06-13 13:24:24 +02:00
|
|
|
}
|
2024-06-24 16:18:00 +02:00
|
|
|
|
|
|
|
// our selectors are only for display
|
|
|
|
noopFunc := func(_ string) {
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
domainsSelector := widget.NewSelect(domains, noopFunc)
|
|
|
|
domainsSelector.Selected = domains[0]
|
|
|
|
grid.Add(domainsSelector)
|
|
|
|
|
|
|
|
var resolvedIPsList []string
|
2024-12-20 11:30:28 +01:00
|
|
|
for domain, ipList := range r.GetResolvedIPs() {
|
|
|
|
resolvedIPsList = append(resolvedIPsList, fmt.Sprintf("%s: %s", domain, strings.Join(ipList.GetIps(), ", ")))
|
2024-06-24 16:18:00 +02:00
|
|
|
}
|
2024-06-13 13:24:24 +02:00
|
|
|
|
2024-06-24 16:18:00 +02:00
|
|
|
if len(resolvedIPsList) == 0 {
|
|
|
|
grid.Add(widget.NewLabel(""))
|
|
|
|
continue
|
2024-06-13 13:24:24 +02:00
|
|
|
}
|
2024-06-24 16:18:00 +02:00
|
|
|
|
|
|
|
// TODO: limit width within the selector display
|
|
|
|
resolvedIPsSelector := widget.NewSelect(resolvedIPsList, noopFunc)
|
|
|
|
resolvedIPsSelector.Selected = resolvedIPsList[0]
|
|
|
|
resolvedIPsSelector.Resize(fyne.NewSize(100, 100))
|
|
|
|
grid.Add(resolvedIPsSelector)
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
s.wRoutes.Content().Refresh()
|
2024-06-13 13:24:24 +02:00
|
|
|
grid.Refresh()
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) getFilteredNetworks(f filter) ([]*proto.Network, error) {
|
|
|
|
routes, err := s.fetchNetworks()
|
2024-06-27 14:32:30 +02:00
|
|
|
if err != nil {
|
2024-07-04 16:50:07 +02:00
|
|
|
log.Errorf(getClientFMT, err)
|
|
|
|
s.showError(fmt.Errorf(getClientFMT, err))
|
2024-06-27 14:32:30 +02:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
switch f {
|
2024-12-20 11:30:28 +01:00
|
|
|
case overlappingNetworks:
|
|
|
|
return getOverlappingNetworks(routes), nil
|
|
|
|
case exitNodeNetworks:
|
|
|
|
return getExitNodeNetworks(routes), nil
|
2024-06-27 14:32:30 +02:00
|
|
|
default:
|
|
|
|
}
|
|
|
|
return routes, nil
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func getOverlappingNetworks(routes []*proto.Network) []*proto.Network {
|
|
|
|
var filteredRoutes []*proto.Network
|
|
|
|
existingRange := make(map[string][]*proto.Network)
|
2024-06-27 14:32:30 +02:00
|
|
|
for _, route := range routes {
|
2024-07-04 16:50:07 +02:00
|
|
|
if len(route.Domains) > 0 {
|
|
|
|
continue
|
|
|
|
}
|
2024-12-20 11:30:28 +01:00
|
|
|
if r, exists := existingRange[route.GetRange()]; exists {
|
2024-06-27 14:32:30 +02:00
|
|
|
r = append(r, route)
|
2024-12-20 11:30:28 +01:00
|
|
|
existingRange[route.GetRange()] = r
|
2024-06-27 14:32:30 +02:00
|
|
|
} else {
|
2024-12-20 11:30:28 +01:00
|
|
|
existingRange[route.GetRange()] = []*proto.Network{route}
|
2024-06-27 14:32:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, r := range existingRange {
|
|
|
|
if len(r) > 1 {
|
|
|
|
filteredRoutes = append(filteredRoutes, r...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filteredRoutes
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func getExitNodeNetworks(routes []*proto.Network) []*proto.Network {
|
|
|
|
var filteredRoutes []*proto.Network
|
2024-06-27 14:32:30 +02:00
|
|
|
for _, route := range routes {
|
2024-12-20 11:30:28 +01:00
|
|
|
if route.Range == "0.0.0.0/0" {
|
2024-06-27 14:32:30 +02:00
|
|
|
filteredRoutes = append(filteredRoutes, route)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return filteredRoutes
|
|
|
|
}
|
2024-07-04 16:50:07 +02:00
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func sortNetworksByIDs(routes []*proto.Network) {
|
2024-07-04 16:50:07 +02:00
|
|
|
sort.Slice(routes, func(i, j int) bool {
|
|
|
|
return strings.ToLower(routes[i].GetID()) < strings.ToLower(routes[j].GetID())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) fetchNetworks() ([]*proto.Network, error) {
|
2024-04-23 14:42:53 +02:00
|
|
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
|
|
|
if err != nil {
|
2024-07-04 16:50:07 +02:00
|
|
|
return nil, fmt.Errorf(getClientFMT, err)
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
resp, err := conn.ListNetworks(s.ctx, &proto.ListNetworksRequest{})
|
2024-04-23 14:42:53 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to list routes: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.Routes, nil
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) selectNetwork(id string, checked bool) {
|
2024-04-23 14:42:53 +02:00
|
|
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
|
|
|
if err != nil {
|
2024-07-04 16:50:07 +02:00
|
|
|
log.Errorf(getClientFMT, err)
|
|
|
|
s.showError(fmt.Errorf(getClientFMT, err))
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
req := &proto.SelectNetworksRequest{
|
|
|
|
NetworkIDs: []string{id},
|
|
|
|
Append: checked,
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if checked {
|
2024-12-20 11:30:28 +01:00
|
|
|
if _, err := conn.SelectNetworks(s.ctx, req); err != nil {
|
|
|
|
log.Errorf("failed to select network: %v", err)
|
|
|
|
s.showError(fmt.Errorf("failed to select network: %v", err))
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
log.Infof("Route %s selected", id)
|
|
|
|
} else {
|
2024-12-20 11:30:28 +01:00
|
|
|
if _, err := conn.DeselectNetworks(s.ctx, req); err != nil {
|
|
|
|
log.Errorf("failed to deselect network: %v", err)
|
|
|
|
s.showError(fmt.Errorf("failed to deselect network: %v", err))
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
2024-12-20 11:30:28 +01:00
|
|
|
log.Infof("Network %s deselected", id)
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) selectAllFilteredNetworks(f filter) {
|
2024-04-23 14:42:53 +02:00
|
|
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
|
|
|
if err != nil {
|
2024-07-04 16:50:07 +02:00
|
|
|
log.Errorf(getClientFMT, err)
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
req := s.getNetworksRequest(f, true)
|
|
|
|
if _, err := conn.SelectNetworks(s.ctx, req); err != nil {
|
|
|
|
log.Errorf("failed to select all networks: %v", err)
|
|
|
|
s.showError(fmt.Errorf("failed to select all networks: %v", err))
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
log.Debug("All networks selected")
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) deselectAllFilteredNetworks(f filter) {
|
2024-04-23 14:42:53 +02:00
|
|
|
conn, err := s.getSrvClient(defaultFailTimeout)
|
|
|
|
if err != nil {
|
2024-07-04 16:50:07 +02:00
|
|
|
log.Errorf(getClientFMT, err)
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
req := s.getNetworksRequest(f, false)
|
|
|
|
if _, err := conn.DeselectNetworks(s.ctx, req); err != nil {
|
|
|
|
log.Errorf("failed to deselect all networks: %v", err)
|
|
|
|
s.showError(fmt.Errorf("failed to deselect all networks: %v", err))
|
2024-04-23 14:42:53 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
log.Debug("All networks deselected")
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) getNetworksRequest(f filter, appendRoute bool) *proto.SelectNetworksRequest {
|
|
|
|
req := &proto.SelectNetworksRequest{}
|
|
|
|
if f == allNetworks {
|
2024-06-27 14:32:30 +02:00
|
|
|
req.All = true
|
|
|
|
} else {
|
2024-12-20 11:30:28 +01:00
|
|
|
routes, err := s.getFilteredNetworks(f)
|
2024-06-27 14:32:30 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for _, route := range routes {
|
2024-12-20 11:30:28 +01:00
|
|
|
req.NetworkIDs = append(req.NetworkIDs, route.GetID())
|
2024-06-27 14:32:30 +02:00
|
|
|
}
|
|
|
|
req.Append = appendRoute
|
|
|
|
}
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
2024-04-23 14:42:53 +02:00
|
|
|
func (s *serviceClient) showError(err error) {
|
|
|
|
wrappedMessage := wrapText(err.Error(), 50)
|
|
|
|
|
|
|
|
dialog.ShowError(fmt.Errorf("%s", wrappedMessage), s.wRoutes)
|
|
|
|
}
|
|
|
|
|
2024-06-27 14:32:30 +02:00
|
|
|
func (s *serviceClient) startAutoRefresh(interval time.Duration, tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
2024-04-23 14:42:53 +02:00
|
|
|
ticker := time.NewTicker(interval)
|
|
|
|
go func() {
|
|
|
|
for range ticker.C {
|
2024-12-20 11:30:28 +01:00
|
|
|
s.updateNetworksBasedOnDisplayTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
2024-04-23 14:42:53 +02:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
s.wRoutes.SetOnClosed(func() {
|
|
|
|
ticker.Stop()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-12-20 11:30:28 +01:00
|
|
|
func (s *serviceClient) updateNetworksBasedOnDisplayTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) {
|
2024-06-27 14:32:30 +02:00
|
|
|
grid, f := getGridAndFilterFromTab(tabs, allGrid, overlappingGrid, exitNodesGrid)
|
|
|
|
s.wRoutes.Content().Refresh()
|
2024-12-20 11:30:28 +01:00
|
|
|
s.updateNetworks(grid, f)
|
2024-06-27 14:32:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func getGridAndFilterFromTab(tabs *container.AppTabs, allGrid, overlappingGrid, exitNodesGrid *fyne.Container) (*fyne.Container, filter) {
|
|
|
|
switch tabs.Selected().Text {
|
2024-12-20 11:30:28 +01:00
|
|
|
case overlappingNetworksText:
|
|
|
|
return overlappingGrid, overlappingNetworks
|
|
|
|
case exitNodeNetworksText:
|
|
|
|
return exitNodesGrid, exitNodeNetworks
|
2024-06-27 14:32:30 +02:00
|
|
|
default:
|
2024-12-20 11:30:28 +01:00
|
|
|
return allGrid, allNetworks
|
2024-06-27 14:32:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 14:42:53 +02:00
|
|
|
// wrapText inserts newlines into the text to ensure that each line is
|
|
|
|
// no longer than 'lineLength' runes.
|
|
|
|
func wrapText(text string, lineLength int) string {
|
|
|
|
var sb strings.Builder
|
|
|
|
var currentLineLength int
|
|
|
|
|
|
|
|
for _, runeValue := range text {
|
|
|
|
sb.WriteRune(runeValue)
|
|
|
|
currentLineLength++
|
|
|
|
|
|
|
|
if currentLineLength >= lineLength || runeValue == '\n' {
|
|
|
|
sb.WriteRune('\n')
|
|
|
|
currentLineLength = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return sb.String()
|
|
|
|
}
|