diff --git a/cmd/wg-portal/assets/tpl/admin_index.gohtml b/cmd/wg-portal/assets/tpl/admin_index.gohtml index 94e6b90..0d8fe23 100644 --- a/cmd/wg-portal/assets/tpl/admin_index.gohtml +++ b/cmd/wg-portal/assets/tpl/admin_index.gohtml @@ -166,7 +166,7 @@ Identifier - Public Key + Name {{if eq $.Interface.Type "server"}} User {{end}} @@ -190,7 +190,7 @@ {{$p.Identifier}} - {{$p.PublicKey}} + {{$p.DisplayName}} {{if eq $p.Interface.Type "server"}} {{$p.UserIdentifier}} {{end}} @@ -200,7 +200,7 @@ {{if eq $p.Interface.Type "client"}} {{$p.Endpoint.Value}} {{end}} - never {/{$p.LastHandshake}} + {-{$p.LastHandshake}} {{if eq $.Session.IsAdmin true}} @@ -276,29 +276,21 @@ {{end}} -

Currently listed peers: {{len $.PagedInterfacePeers}}

+

Currently listed peers: {{len $.PagedInterfacePeers}}, page size: {{$.PageSize}}

diff --git a/cmd/wg-portal/server.go b/cmd/wg-portal/server.go index 20ad143..cb6a4b8 100644 --- a/cmd/wg-portal/server.go +++ b/cmd/wg-portal/server.go @@ -98,6 +98,8 @@ "startsWith": strings.HasPrefix, "isConfigValid": isConfigValid, "getSortIcon": getSortIcon, + "intRange": intRange, + "intAdd": intAdd, }) // Setup templates @@ -198,3 +200,17 @@ return "fa-sort-alpha-up" } } + +// https://stackoverflow.com/questions/57762069/how-to-iterate-over-a-range-of-numbers-in-golang-template +func intRange(start, end int) []int { + n := end - start + 1 + result := make([]int, n) + for i := 0; i < n; i++ { + result[i] = start + i + } + return result +} + +func intAdd(one, two int) int { + return one + two +} diff --git a/cmd/wg-portal/ui/pages_admin.go b/cmd/wg-portal/ui/pages_admin.go index 3a4c4c5..8b4bc90 100644 --- a/cmd/wg-portal/ui/pages_admin.go +++ b/cmd/wg-portal/ui/pages_admin.go @@ -2,15 +2,89 @@ import ( "fmt" + "math" "net/http" + "sort" + "strconv" + "strings" "github.com/h44z/wg-portal/internal/persistence" "github.com/gin-gonic/gin" ) +func (h *handler) processMetaRequest(c *gin.Context) bool { + if newPageStr := c.Query("page"); newPageStr != "" { + currentSession := h.session.GetData(c) + newPage, err := strconv.Atoi(newPageStr) + if err != nil { + return false + } + currentSession.CurrentPage = newPage + h.session.SetData(c, currentSession) + + return true + } + + if newPageSizeStr := c.Query("pagesize"); newPageSizeStr != "" { + currentSession := h.session.GetData(c) + newPageSize, err := strconv.Atoi(newPageSizeStr) + if err != nil { + return false + } + if newPageSize < 25 { + return false + } + currentSession.CurrentPage = newPageSize + h.session.SetData(c, currentSession) + + return true + } + + if sort := c.Query("sort"); sort != "" { + currentSession := h.session.GetData(c) + if currentSession.SortedBy["peers"] != sort { + currentSession.SortedBy["peers"] = sort + currentSession.SortDirection["peers"] = "asc" + } else { + if currentSession.SortDirection["peers"] == "asc" { + currentSession.SortDirection["peers"] = "desc" + } else { + currentSession.SortDirection["peers"] = "asc" + } + } + h.session.SetData(c, currentSession) + + return true + } + + if iface := c.Query("iface"); iface != "" { + currentSession := h.session.GetData(c) + currentSession.InterfaceIdentifier = persistence.InterfaceIdentifier(iface) + currentSession.CurrentPage = 1 // reset page + h.session.SetData(c, currentSession) + return true + } + + if search, searching := c.GetQuery("search"); searching { + currentSession := h.session.GetData(c) + currentSession.Search["peers"] = search + currentSession.CurrentPage = 1 // reset page + h.session.SetData(c, currentSession) + + return true + } + + return false +} + func (h *handler) handleAdminIndexGet() gin.HandlerFunc { return func(c *gin.Context) { + if h.processMetaRequest(c) { + c.Redirect(http.StatusSeeOther, c.Request.URL.Path) + return + } + currentSession := h.session.GetData(c) interfaces, err := h.backend.GetInterfaces() @@ -20,6 +94,7 @@ } var iface *persistence.InterfaceConfig + var peers []*persistence.PeerConfig if currentSession.InterfaceIdentifier != "" { iface, err = h.backend.GetInterface(currentSession.InterfaceIdentifier) if err != nil { @@ -27,58 +102,86 @@ fmt.Sprintf("failed to load selected interface %s", currentSession.InterfaceIdentifier)) return } + peers, err = h.backend.GetPeers(currentSession.InterfaceIdentifier) + if err != nil { + h.HandleError(c, http.StatusInternalServerError, err, + fmt.Sprintf("failed to load peers for %s", currentSession.InterfaceIdentifier)) + return + } } + // TODO test peers + /*iface = &persistence.InterfaceConfig{ + BaseModel: persistence.BaseModel{}, + Identifier: "wg0", + KeyPair: persistence.KeyPair{}, + ListenPort: 0, + AddressStr: "", + DnsStr: "", + DnsSearchStr: "", + Mtu: 0, + FirewallMark: 0, + RoutingTable: "", + PreUp: "", + PostUp: "", + PreDown: "", + PostDown: "", + SaveConfig: false, + Enabled: false, + DisplayName: "", + Type: "", + DriverType: "", + PeerDefNetworkStr: "", + PeerDefDnsStr: "", + PeerDefDnsSearchStr: "", + PeerDefEndpoint: "", + PeerDefAllowedIPsStr: "", + PeerDefMtu: 0, + PeerDefPersistentKeepalive: 0, + PeerDefFirewallMark: 0, + PeerDefRoutingTable: "", + PeerDefPreUp: "", + PeerDefPostUp: "", + PeerDefPreDown: "", + PeerDefPostDown: "", + } + peers = make([]*persistence.PeerConfig, 33) + for i := 0; i < 33; i++ { + peers[i] = &persistence.PeerConfig{Identifier: persistence.PeerIdentifier(fmt.Sprintf("%d", i)), DisplayName: fmt.Sprintf("Name%d", i), Interface: &persistence.PeerInterfaceConfig{}} + }*/ + // TODO end test peers + + peers = sortAndFilterPeers(currentSession, peers) + + activePeers := 0 + for _, p := range peers { + if p.DisabledAt.Valid { // disabled peer + continue + } + activePeers++ + } + + start := (currentSession.CurrentPage - 1) * currentSession.PageSize + end := start + currentSession.PageSize + if end >= len(peers) { + end = len(peers) + } + pagedPeers := peers[start:end] + c.HTML(http.StatusOK, "admin_index.gohtml", gin.H{ - "Route": c.Request.URL.Path, - "Alerts": h.session.GetFlashes(c), - "Session": currentSession, - "Static": h.getStaticData(), - "Interface": iface, - "InterfacePeers": []persistence.PeerConfig{}, - "PagedInterfacePeers": []persistence.PeerConfig{ - { - Endpoint: persistence.StringConfigOption{ - Value: "vpn.test.net", - Overridable: false, - }, - AllowedIPsStr: persistence.StringConfigOption{ - Value: "10.0.0.0/8,192.168.1.0/24", - Overridable: false, - }, - KeyPair: persistence.KeyPair{ - PrivateKey: "privkey", - PublicKey: "pubkey", - }, - PresharedKey: "psk", - PersistentKeepalive: persistence.IntConfigOption{ - Value: 16, - Overridable: true, - }, - DisplayName: "Display Name", - Identifier: "abc123", - UserIdentifier: "nouser", - Interface: &persistence.PeerInterfaceConfig{ - Identifier: "wg0", - Type: persistence.InterfaceTypeServer, - PublicKey: "srvpub", - AddressStr: persistence.StringConfigOption{ - Value: "10.0.0.1/32,192.168.1.1/32", - }, - DnsStr: persistence.StringConfigOption{}, - DnsSearchStr: persistence.StringConfigOption{}, - Mtu: persistence.IntConfigOption{}, - FirewallMark: persistence.Int32ConfigOption{}, - RoutingTable: persistence.StringConfigOption{}, - PreUp: persistence.StringConfigOption{}, - PostUp: persistence.StringConfigOption{}, - PreDown: persistence.StringConfigOption{}, - PostDown: persistence.StringConfigOption{}, - }, - }, - }, - "Interfaces": interfaces, - "TotalPeers": 12, + "Route": c.Request.URL.Path, + "Alerts": h.session.GetFlashes(c), + "Session": currentSession, + "Static": h.getStaticData(), + "Interface": iface, + "InterfacePeers": peers, + "PagedInterfacePeers": pagedPeers, + "Interfaces": interfaces, + "TotalPeers": len(peers), + "ActivePeers": activePeers, + "Page": currentSession.CurrentPage, + "PageSize": currentSession.PageSize, + "TotalPages": int(math.Ceil(float64(len(peers)) / float64(currentSession.PageSize))), }) } } @@ -177,3 +280,62 @@ }) } } + +func sortAndFilterPeers(session SessionData, peers []*persistence.PeerConfig) []*persistence.PeerConfig { + filteredPeers := make([]*persistence.PeerConfig, 0, len(peers)) + search := session.Search["peers"] + for i := range peers { + if search == "" || + strings.Contains(string(peers[i].Identifier), strings.ToLower(search)) || + strings.Contains(peers[i].DisplayName, search) || + strings.Contains(string(peers[i].UserIdentifier), search) || + strings.Contains(peers[i].PublicKey, search) { + filteredPeers = append(filteredPeers, peers[i]) + } + } + + sortKey := session.SortedBy["peers"] + sortDirection := session.SortDirection["peers"] + sortPeers(sortKey, sortDirection, filteredPeers) + + return filteredPeers +} + +func sortPeers(sortKey string, sortDirection string, peers []*persistence.PeerConfig) { + sort.Slice(peers, func(i, j int) bool { + var sortValueLeft string + var sortValueRight string + + switch sortKey { + case "id": + sortValueLeft = string(peers[i].Identifier) + sortValueRight = string(peers[j].Identifier) + case "pubKey": + sortValueLeft = peers[i].PublicKey + sortValueRight = peers[j].PublicKey + case "displayName": + sortValueLeft = peers[i].DisplayName + sortValueRight = peers[j].DisplayName + case "ip": + sortValueLeft = peers[i].Interface.AddressStr.GetValue() + sortValueRight = peers[j].Interface.AddressStr.GetValue() + case "endpoint": + sortValueLeft = peers[i].Endpoint.GetValue() + sortValueRight = peers[j].Endpoint.GetValue() + /*case "handshake": + if peers[i].Peer == nil { + return true + } else if peers[j].Peer == nil { + return false + } + sortValueLeft = peers[i].Peer.LastHandshakeTime.Format(time.RFC3339) + sortValueRight = peers[j].Peer.LastHandshakeTime.Format(time.RFC3339)*/ + } + + if sortDirection == "asc" { + return sortValueLeft < sortValueRight + } else { + return sortValueLeft > sortValueRight + } + }) +} diff --git a/cmd/wg-portal/ui/session.go b/cmd/wg-portal/ui/session.go index 0198dc1..15c61e2 100644 --- a/cmd/wg-portal/ui/session.go +++ b/cmd/wg-portal/ui/session.go @@ -35,7 +35,8 @@ SortedBy map[string]string SortDirection map[string]string Search map[string]string - CurrentPage map[string]int + CurrentPage int + PageSize int // alert that is printed on top of the page AlertData string @@ -99,8 +100,10 @@ func (g GinSessionStore) DefaultSessionData() SessionData { return SessionData{ Search: map[string]string{"peers": "", "userpeers": "", "users": ""}, - SortedBy: map[string]string{"peers": "handshake", "userpeers": "id", "users": "email"}, - SortDirection: map[string]string{"peers": "desc", "userpeers": "asc", "users": "asc"}, + SortedBy: map[string]string{"peers": "id", "userpeers": "id", "users": "email"}, + SortDirection: map[string]string{"peers": "asc", "userpeers": "asc", "users": "asc"}, + CurrentPage: 1, + PageSize: 25, Email: "", Firstname: "", Lastname: "",