diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b0f546 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.bak +netmaker-gui +config/* +data/* diff --git a/README.md b/README.md index 6091a69..76bb3a0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ # netmaker-gui -Alternative UI for netmaker (see github.com/gravitl/netmaker +A responsive alternative UI for netmaker (https://github.com/gravitl/netmaker) + +Built with go and html/templates. +Missing following features compared to netmaker-ui (https://github.com/gravitl/netmaker-ui) +- DNS +- Netmaker v0.8.0 changes: in particular Relay Gateways + +You can use netmaker-gui at the same time as netmaker-ui. For example, one one running as dashboard.netmaker.example.com and the other at control.netmaker.example.com + +![both](https://github.com/mattkasun/netmaker-gui/raw/develop/screenshots/netmaker-gui-ui.png "GUI and UI") + + +## Installation: +To use along side of your existing netmaker installation insert the following to your docker-compose.yml file + +``` +netmaker-gui + container-name: netmaker-gui + image: nusak/netmaker-gui:v0.1.0 + restart: unless-stopped + ports: + - "8080:8080" + environment: + DATABASE: sqlite + volumes: + - sqldata:/data +``` + +and add an appropriate entry to your proxy relay. + +## Screenshots +### Browser +![netmaker-gui with browser](https://github.com/mattkasun/netmaker-gui/raw/develop/screenshots/netmaker-gui-browser.png "Netmaker-GUI with Browser") + +### Mobile +![netmaker-gui with phone](https://github.com/mattkasun/netmaker-gui/raw/develop/screenshots/netmaker-gui-phone.png "Netmaker-GUI with Phone") + +## Third Party Tools +- CSS - W3Schools https://w3.schools.com/w3css +- Icons - Material Icons https://fonts.google.com/icons +- Netmaker https://github.com/gravitl/netmaker diff --git a/go.mod b/go.mod index 75e9e34..351d3f0 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,12 @@ module github.com/mattkasun/netmaker-gui go 1.16 require ( - github.com/aws/aws-sdk-go v1.34.28 github.com/gin-contrib/sessions v0.0.3 github.com/gin-gonic/gin v1.7.4 github.com/go-playground/validator/v10 v10.9.0 // indirect - github.com/gravitl/netmaker v0.7.1 + github.com/gravitl/netmaker v0.7.3 github.com/rqlite/gorqlite v0.0.0-20210804113434-b4935d2eab04 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.7.0 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect diff --git a/go.sum b/go.sum index e924e0a..bbce16a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk= github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw= github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= @@ -89,14 +88,12 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= -github.com/gravitl/netmaker v0.7.1 h1:RTgGCookqpuqFP+Q91bZV2slrnpNeXGgrKHJN8o/GqY= -github.com/gravitl/netmaker v0.7.1/go.mod h1:oV1K5PZY3llJnTIBpeoLZ02evXD95zQFM0hk7MnFU2U= +github.com/gravitl/netmaker v0.7.3 h1:FtKGXca6O/KuJhavl2GZixfSlyxtkwAtCqcwf91mkT4= +github.com/gravitl/netmaker v0.7.3/go.mod h1:oV1K5PZY3llJnTIBpeoLZ02evXD95zQFM0hk7MnFU2U= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jinzhu/copier v0.3.2/go.mod h1:24xnZezI2Yqac9J61UC6/dG/k76ttpq0DdJI3QmUvro= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4 h1:nwOc1YaOrYJ37sEBrtWZrdqzK22hiJs3GpDmP3sR2Yw= @@ -121,6 +118,7 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= diff --git a/handlers.go b/handlers.go index ea7cdc5..15277aa 100644 --- a/handlers.go +++ b/handlers.go @@ -5,12 +5,15 @@ import ( "net/http" "net/url" "strconv" + "strings" + "time" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" controller "github.com/gravitl/netmaker/controllers" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/models" + "github.com/skip2/go-qrcode" ) func ProcessLogin(c *gin.Context) { @@ -19,18 +22,26 @@ func ProcessLogin(c *gin.Context) { AuthRequest.UserName = c.PostForm("user") AuthRequest.Password = c.PostForm("pass") session := sessions.Default(c) - jwt, err := controller.VerifyAuthRequest(AuthRequest) + //don't need the jwt + _, err := controller.VerifyAuthRequest(AuthRequest) if err != nil { - fmt.Println("error verifying AuthRequest: ", jwt, err) - fmt.Println("setting session err to: ", err) - session.Set("error", err) + fmt.Println("error verifying AuthRequest: ", err) + session.Set("message", err.Error()) session.Set("loggedIn", false) c.HTML(http.StatusUnauthorized, "Login", gin.H{"message": err}) } else { session.Set("loggedIn", true) - session.Set("token", jwt) + //init message + session.Set("message", "") + session.Options(sessions.Options{MaxAge: 1800}) + user, err := controller.GetUser(AuthRequest.UserName) + if err != nil { + fmt.Println("err retrieving user: ", err) + } + session.Set("username", user.UserName) + session.Set("isAdmin", user.IsAdmin) + session.Set("networks", user.Networks) session.Save() - fmt.Println("Successful login:\n", session.Get("loggedIn"), "\njwt:\n", jwt) location := url.URL{Path: "/"} c.Redirect(http.StatusFound, location.RequestURI()) } @@ -62,7 +73,7 @@ func NewUser(c *gin.Context) { func DisplayLanding(c *gin.Context) { var Data PageData - Data.Init("Networks") + Data.Init("Networks", c) c.HTML(http.StatusOK, "layout", Data) } @@ -200,6 +211,17 @@ func UpdateNetwork(c *gin.Context) { c.Redirect(http.StatusFound, location.RequestURI()) } +func RefreshKeys(c *gin.Context) { + net := c.Param("net") + _, err := controller.KeyUpdate(net) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + func NewKey(c *gin.Context) { var key models.AccessKey var err error @@ -280,3 +302,355 @@ func DeleteUser(c *gin.Context) { location := url.URL{Path: "/"} c.Redirect(http.StatusFound, location.RequestURI()) } + +func EditUser(c *gin.Context) { + session := sessions.Default(c) + username := session.Get("username").(string) + user, err := controller.GetUser(username) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.Abort() + } + c.HTML(http.StatusOK, "EditUser", user) +} + +func UpdateUser(c *gin.Context) { + var new models.User + username := c.Param("user") + user, err := controller.GetUserInternal(username) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.Abort() + return + } + new.UserName = c.PostForm("username") + new.Password = c.PostForm("password") + _, err = controller.UpdateUser(new, user) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.Abort() + return + } + session := sessions.Default(c) + session.Set("message", "user has been updated") + session.Save() + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + +func EditNode(c *gin.Context) { + network := c.PostForm("network") + mac := c.PostForm("mac") + var node models.Node + node, err := controller.GetNode(mac, network) + if err != nil { + fmt.Println("error getting node details \n", err) + c.JSON(http.StatusBadRequest, err) + } + c.HTML(http.StatusOK, "EditNode", node) +} + +func DeleteNode(c *gin.Context) { + mac := c.PostForm("mac") + net := c.PostForm("net") + fmt.Println("deleting node ", mac, net) + err := controller.DeleteNode(mac+"###"+net, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.Abort() + } + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + +func UpdateNode(c *gin.Context) { + + var node *models.Node + if err := c.ShouldBind(&node); err != nil { + fmt.Println("should bind") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + net := c.Param("net") + mac := c.Param("mac") + fmt.Printf("=============%T %T %T %v %v %v", net, mac, node, net, mac, node) + oldnode, err := models.GetNode(mac, net) + if err != nil { + fmt.Println("Get node with mac ", mac, " and Network ", net) + //c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + c.JSON(http.StatusBadRequest, node) + return + } + if err = oldnode.Update(node); err != nil { + fmt.Println("update network") + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) + +} + +func NodeHealth(c *gin.Context) { + nodes, err := models.GetAllNodes() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + var response []NodeStatus + var nodeHealth NodeStatus + for _, node := range nodes { + nodeHealth.Mac = node.MacAddress + lastupdate := time.Now().Sub(time.Unix(node.LastCheckIn, 0)) + if lastupdate.Minutes() > 15.0 { + nodeHealth.Status = "Dead: Node last checked in more than 15 minutes ago" + nodeHealth.Color = "w3-deep-orange" + } else if lastupdate.Minutes() > 5.0 { + nodeHealth.Status = "Warning: Node last checked in more than 5 minutes ago" + nodeHealth.Color = "w3-khaki" + } else { + nodeHealth.Status = "Healthy: Node checked in within the last 5 minutes" + nodeHealth.Color = "w3-teal" + } + response = append(response, nodeHealth) + } + c.JSON(http.StatusOK, response) + return +} + +func ProcessEgress(c *gin.Context) { + var egress models.EgressGatewayRequest + egress.NodeID = c.Param("mac") + egress.NetID = c.Param("net") + node, err := controller.GetNode(egress.NodeID, egress.NetID) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + egress.Ranges = strings.Split(c.PostForm("ranges"), ",") + egress.Interface = c.PostForm("interface") + + _, err = controller.CreateEgressGateway(egress) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + session := sessions.Default(c) + session.Set("message", node.Name+" is now a gateway") + session.Save() + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + +func CreateEgress(c *gin.Context) { + net := c.Param("net") + mac := c.Param("mac") + node, err := controller.GetNode(mac, net) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.HTML(http.StatusOK, "Egress", node) +} + +func DeleteEgress(c *gin.Context) { + net := c.Param("net") + mac := c.Param("mac") + _, err := controller.DeleteEgressGateway(net, mac) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Ingress Gateway Created"}) +} + +func CreateIngress(c *gin.Context) { + net := c.Param("net") + mac := c.Param("mac") + _, err := controller.CreateIngressGateway(net, mac) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Ingress Gateway Created"}) +} + +func DeleteIngress(c *gin.Context) { + net := c.Param("net") + mac := c.Param("mac") + _, err := controller.DeleteIngressGateway(net, mac) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Ingress Gateway Created"}) +} + +func CreateIngressClient(c *gin.Context) { + var client models.ExtClient + client.Network = c.Param("net") + client.IngressGatewayID = c.Param("mac") + + node, err := functions.GetNodeByMacAddress(client.Network, client.IngressGatewayID) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + client.IngressGatewayEndpoint = node.Endpoint + ":" + strconv.FormatInt(int64(node.ListenPort), 10) + + err = controller.CreateExtClient(client) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + session := sessions.Default(c) + session.Set("message", "external client has been created") + session.Save() + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + +func DeleteIngressClient(c *gin.Context) { + net := c.Param("net") + id := c.Param("id") + err := controller.DeleteExtClient(net, id) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + session := sessions.Default(c) + session.Set("message", "external client "+id+" @ "+net+" has been deleted") + session.Save() + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} + +func EditIngressClient(c *gin.Context) { + net := c.Param("net") + id := c.Param("id") + client, err := controller.GetExtClient(id, net) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.HTML(http.StatusOK, "EditExtClient", client) +} + +func GetQR(c *gin.Context) { + net := c.Param("net") + id := c.Param("id") + config, err := GetConf(net, id) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + b, err := qrcode.Encode(config, qrcode.Medium, 220) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.Header("Content-Type", "image/png") + c.Data(http.StatusOK, "application/octet-strean", b) +} + +func GetConf(net, id string) (string, error) { + client, err := controller.GetExtClient(id, net) + if err != nil { + return "", err + } + gwnode, err := functions.GetNodeByMacAddress(client.Network, client.IngressGatewayID) + if err != nil { + return "", err + } + network, err := functions.GetParentNetwork(client.Network) + if err != nil { + return "", err + } + keepalive := "" + if network.DefaultKeepalive != 0 { + keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive)) + } + gwendpoint := gwnode.Endpoint + ":" + strconv.Itoa(int(gwnode.ListenPort)) + newAllowedIPs := network.AddressRange + if egressGatewayRanges, err := client.GetEgressRangesOnNetwork(); err == nil { + for _, egressGatewayRange := range egressGatewayRanges { + newAllowedIPs += "," + egressGatewayRange + } + } + defaultDNS := "" + if network.DefaultExtClientDNS != "" { + defaultDNS = "DNS = " + network.DefaultExtClientDNS + } + + config := fmt.Sprintf(`[Interface] +Address = %s +PrivateKey = %s +%s + +[Peer] +PublicKey = %s +AllowedIPs = %s +Endpoint = %s +%s + +`, client.Address+"/32", + client.PrivateKey, + defaultDNS, + gwnode.PublicKey, + newAllowedIPs, + gwendpoint, + keepalive) + + return config, nil +} + +func GetClientConfig(c *gin.Context) { + net := c.Param("net") + id := c.Param("id") + config, err := GetConf(net, id) + b := []byte(config) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + filename := id + ".conf" + //c.FileAttachment(filepath, filename) + c.Header("Content-Description", "File Transfer") + c.Header("Content-Disposition", "attachment: filename="+filename) + c.Data(http.StatusOK, "application/octet-stream", b) +} + +func UpdateClient(c *gin.Context) { + net := c.Param("net") + id := c.Param("id") + newid := c.PostForm("newid") + + client, err := controller.GetExtClient(id, net) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + _, err = controller.UpdateExtClient(newid, net, client) + if err != nil { + fmt.Println(err) + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + session := sessions.Default(c) + session.Set("message", "External client has been updated") + session.Save() + location := url.URL{Path: "/"} + c.Redirect(http.StatusFound, location.RequestURI()) +} diff --git a/helpers.go b/helpers.go deleted file mode 100644 index 70bd636..0000000 --- a/helpers.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "net/http" - - "github.com/gravitl/netmaker/models" -) - -var backendURL = "https://api.netmaker.nusak.ca" - -func API(data interface{}, method, url, authorization string) (*http.Response, error) { - var request *http.Request - var err error - if data != "" { - payload, err := json.Marshal(data) - if err != nil { - return nil, err - } - request, err = http.NewRequest(method, backendURL+url, bytes.NewBuffer(payload)) - if err != nil { - return nil, err - } - request.Header.Set("Content-Type", "application/json") - } else { - request, err = http.NewRequest(method, backendURL+url, nil) - if err != nil { - return nil, err - } - } - if authorization != "" { - request.Header.Set("Authorization", "Bearer "+authorization) - } - client := http.Client{} - return client.Do(request) -} - -//func GetNetSummary() ([]NetSummary, error) { -// var network NetSummary -// var result []NetSummary -// //response, err := API("", http.MethodGet, "/api/networks", "secretkey") -// networks, err := models.GetNetworks() -// if err != nil { -// return result, err -// } -// //err = json.NewDecoder(response.Body).Decode(&body) -// //if err != nil { -// // return result, err -// //} -// for _, net := range networks { -// fmt.Println(net.NodesLastModified, net.NetworkLastModified) -// network.ID = net.NetID -// network.Name = net.DisplayName -// network.Address = net.AddressRange -// network.Keys = net.AccessKeys -// network.NodeModified = time.Unix(net.NodesLastModified, 0).Format(time.UnixDate) -// network.NetModified = time.Unix(net.NetworkLastModified, 0).Format(time.UnixDate) -// result = append(result, network) -// } -// return result, err -//} -// -func GetNodeSummary() ([]NodeSummary, error) { - var body []models.Node - var node NodeSummary - var result []NodeSummary - response, err := API("", http.MethodGet, "/api/nodes", "secretkey") - if err != nil { - return result, err - } - err = json.NewDecoder(response.Body).Decode(&body) - if err != nil { - return result, err - } - for _, net := range body { - node.Name = net.Name - node.Network = net.Network - node.PublicIP = net.Endpoint - node.SubNet = net.Address - result = append(result, node) - } - - return result, err -} - -//func GetNetDetails(net string) (models.Network, error) { -// var body models.Network -// response, err := API("", http.MethodGet, "/api/networks/"+net, "secretkey") -// -// if err != nil { -// return body, err -// } -// err = json.NewDecoder(response.Body).Decode(&body) -// return body, err -//} diff --git a/html/buttonbar.html b/html/buttonbar.html index e4e7caa..67debbb 100644 --- a/html/buttonbar.html +++ b/html/buttonbar.html @@ -6,14 +6,14 @@ - + {{/*NewNetwork*/}}
-
+

× diff --git a/html/edit.html b/html/edit.html index 4d6dd7e..d8847a6 100644 --- a/html/edit.html +++ b/html/edit.html @@ -1,67 +1,69 @@ {{define "EditNet"}} - + - + {{template "Header"}} -
-

Edit Network {{.NetID}}

-
- - Address Range:

- {{if eq .IsDualStack "yes"}} - Address Range(IPv6):

- {{end}} - Local Range:

- Display Name:

- Default Interface:

- Default Port:

- Default PostUp:

- Default PostDown:

- Default Keepalive:

- Default CheckinInterval:

- {{ if eq .IsDualStack "yes"}} - Dual Stack - {{ if eq .DefaultSaveConfig "yes" }} - Default SaveConfig
+
+
+

Edit Network {{.NetID}}

+ + + Address Range:

+ {{if eq .IsDualStack "yes"}} + Address Range(IPv6):

+ {{end}} + Local Range:

+ Display Name:

+ Default Interface:

+ Default Port:

+ Default PostUp:

+ Default PostDown:

+ Default Keepalive:

+ Default CheckinInterval:

+ {{ if eq .IsDualStack "yes"}} + Dual Stack + {{ if eq .DefaultSaveConfig "yes" }} + Default SaveConfig
- {{ if eq .DefaultUDPHolePunch "yes" }} - UDP Hole Punch + {{ if eq .DefaultUDPHolePunch "yes" }} + UDP Hole Punch - {{ if eq .AllowManualSignUp "yes" }} - Allow Node Signup Without Keys
+ {{ if eq .AllowManualSignUp "yes" }} + Allow Node Signup Without Keys
-
- - -
- -
+
+ + +
+ +
+
diff --git a/html/extclient.html b/html/extclient.html new file mode 100644 index 0000000..87355e7 --- /dev/null +++ b/html/extclient.html @@ -0,0 +1,81 @@ +{{ define "ExtClient" }} +
+
+

Available Ingress Gateways + {{range .Nodes}} +

+ + + +
+ {{end}} +
+ +
+

External Clients

+ {{range .ExtClients}} + +
+

+

+
+
+ + +
+
+ +
+
+ +
+
+ +
+ +
+
+ {{end}} +
+
+{{end}} + +{{ define "EditExtClient" }} + + + + + + + + + {{template "Header"}} + +
+
+
+
+
+
+
+

Editing client:{{.ClientID}}

+

+
+ +
+
+ +
+
+
+
+ + +{{end}} + + + + + + + diff --git a/html/header.html b/html/header.html index 150d51d..ca3eb1a 100644 --- a/html/header.html +++ b/html/header.html @@ -1,32 +1,32 @@ {{define "Header"}} -
+
+
    
+
+ Netmaker Makes Networks +
+
+
Netmaker Makes Networks
+
+
+
+ +
+ {{ if .Admin }} + + + {{end}} + -
-
-
- Docs - -
-
+ -
- Logout - -
+
-
+
+ +
@@ -43,12 +43,9 @@

©2021 Matthew R Kasun

-
-
+
+

Create New User

-
- -
@@ -66,13 +63,13 @@

Create New User

-
-
-
+
+

Delete User

@@ -83,10 +80,40 @@

Delete User

- +
+{{end}} + +{{define "EditUser"}} + + + + + + + + + {{template "Header"}} +
+
+

Updating User {{.UserName}}

+
+

+

+

+ +

+
+ +
+
+
+ {{ template "script" }} + + + {{end}} diff --git a/html/keys.html b/html/keys.html index b956cf1..1ac1146 100644 --- a/html/keys.html +++ b/html/keys.html @@ -30,7 +30,7 @@ {{/*NewKey*/}}
-
+

× diff --git a/html/layout.html b/html/layout.html index c0b0075..2fc86f8 100644 --- a/html/layout.html +++ b/html/layout.html @@ -19,8 +19,7 @@ --> - - + {{template "Header" . }} {{template "buttonbar"}} {{template "Sidebar" .}} @@ -29,6 +28,7 @@ {{template "Networks" .}} {{template "Nodes" .}} {{template "Keys" .}} + {{template "ExtClient" .}}
{{ template "script" }} diff --git a/html/login.html b/html/login.html index f4fceeb..6f7f60c 100644 --- a/html/login.html +++ b/html/login.html @@ -1,27 +1,32 @@ {{define "Login"}} - - - Login - - - - - - -

{{.message}}

-

Please log in

-
-
- - - - - - - -
- + + + Login + + + + + + +
+
  +
+
+
+

Login Form

+

{{.message}}

+

Please log in

+ + +

+ +

+

+ +
+
+ {{end}} diff --git a/html/networks.html b/html/networks.html index 116d05b..1d2b5bf 100644 --- a/html/networks.html +++ b/html/networks.html @@ -2,34 +2,35 @@
{{range .Networks}} -
    -
  • {{.DisplayName}} {{.AddressRange}}
  • -
+
+

+ +

{{ end }}
- {{ range .Networks }} -
- {{.NetID}} -
-
- {{ end }} + {{ range .Networks }} +
+ {{.NetID}} +
+
+ {{ end }}
{{ end }} diff --git a/html/nodes.html b/html/nodes.html index c08c783..88f58a0 100644 --- a/html/nodes.html +++ b/html/nodes.html @@ -1,23 +1,140 @@ {{ define "Nodes" }} -
+
- {{range .Nodes}} -

{{.Name}} {{.Endpoint}} {{.Address}}

+ {{range .Nodes}} +

+ {{if eq .IsEgressGateway "yes" }} + + {{ else }} +
+ +
+ {{end}} + {{if eq .IsIngressGateway "yes"}} + + {{ else }} + {{end}} + {{end}}
{{range .Nodes}}
{{.Name }} - -
- -
-
- - +
+ + + +
+ +
+
+
+
+
+ + + +
{{end}}
{{ end }} +{{ define "Egress" }} + + + + + + + + + {{template "Header"}} + +
+
+
+

Make {{.Name}} into Gateway

+
+ +
+
+
+

+

+

+

+ +

+ +
+ +
+ +
+
+
+
+{{template "script"}} +{{end}} + +{{define "EditNode"}} + + + + + + + + + {{template "Header"}} +
+
+

Edit Node {{.Name}}

+
+ Address:

+ Address Range(IPv6):

+ Local Range:

+ Name:

+ Listen Port:

+ PublicKey:

+ EndPoint:

+ PostUp:

+ PostDown:

+ Persistent Keepalive:

+ Save Config:

+ Interface:

+ Last Modified:

+ Last CheckIn:

+ MacAddress:

+ Network:

+ Local Address:

+ Egress Gateway Ranges:

+ Allowed IPs:

+ UDP Hole Punch:

+ Static:

+
+ +
+
+ +
+
+
+ + +{{end}} + + + + + + + + + diff --git a/html/script.html b/html/script.html index 621a1e5..98f9731 100644 --- a/html/script.html +++ b/html/script.html @@ -1,7 +1,17 @@ {{define "script"}} -{{end}} + {{end}} diff --git a/html/sidebar.html b/html/sidebar.html index 2dabb49..47fee09 100644 --- a/html/sidebar.html +++ b/html/sidebar.html @@ -2,9 +2,9 @@ {{end}} diff --git a/main.go b/main.go index 8b9a3e4..616128b 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,10 @@ -//©2021 Matthew R Kasun mkasun@nusak.ca +//Copyright ©2021 Matthew R Kasun mkasun@nusak.ca + +//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +//Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. package main @@ -13,6 +19,7 @@ import ( "github.com/gin-gonic/gin" controller "github.com/gravitl/netmaker/controllers" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/servercfg" ) var Data PageData @@ -26,11 +33,13 @@ func main() { } func SetupRouter() *gin.Engine { + fmt.Println("using database: ", servercfg.GetDB()) if err := database.InitializeDatabase(); err != nil { - log.Fatal("Error connecting to Database", err) + log.Fatal("Error connecting to Database:\n", err) } router := gin.Default() store := memstore.NewStore([]byte("secret")) + router.Use(sessions.Sessions("netmaker", store)) router.LoadHTMLGlob("html/*") router.Static("images", "./images") @@ -46,10 +55,29 @@ func SetupRouter() *gin.Engine { private.POST("/edit_network", EditNetwork) private.POST("/delete_network", DeleteNetwork) private.POST("/update_network", UpdateNetwork) + private.GET("/refreshkeys/:net", RefreshKeys) private.POST("/create_key", NewKey) private.POST("/delete_key", DeleteKey) private.POST("/create_user", CreateUser) private.POST("/delete_user", DeleteUser) + private.GET("/edit_user", EditUser) + private.POST("/update_user/:user", UpdateUser) + private.POST("/edit_node", EditNode) + private.POST("/delete_node", DeleteNode) + private.POST("/update_node/:net/:mac", UpdateNode) + private.GET("/node_health", NodeHealth) + private.POST("/create_egress/:net/:mac", CreateEgress) + private.POST("/process_egress/:net/:mac", ProcessEgress) + private.POST("/delete_egress/:net/:mac", DeleteEgress) + private.POST("/create_ingress/:net/:mac", CreateIngress) + private.POST("/delete_ingress/:net/:mac", DeleteIngress) + private.POST("/create_ingress_client/:net/:mac", CreateIngressClient) + private.POST("/delete_ingress_client/:net/:id", DeleteIngressClient) + private.POST("/edit_ingress_client/:net/:id", EditIngressClient) + private.POST("/get_qr/:net/:id", GetQR) + private.POST("/get_client_config/:net/:id", GetClientConfig) + private.POST("/update_client/:net/:id", UpdateClient) + private.GET("/logout", LogOut) } return router @@ -57,7 +85,10 @@ func SetupRouter() *gin.Engine { func AuthRequired(c *gin.Context) { session := sessions.Default(c) - fmt.Println("checking authorization\n", session) + options := session.Options + + fmt.Println("checking authorization\n", options) + fmt.Printf("type %v value %s\n", options, options) loggedIn := session.Get("loggedIn") fmt.Println("loggedIn status: ", loggedIn) if loggedIn != true { @@ -75,7 +106,7 @@ func AuthRequired(c *gin.Context) { } else { message := session.Get("error") fmt.Println("user exists --- message\n", message) - c.HTML(http.StatusOK, "Login", gin.H{"messge": message}) + c.HTML(http.StatusUnauthorized, "Login", gin.H{"messge": message}) c.Abort() } } diff --git a/screenshots/netmaker-gui-browser.png b/screenshots/netmaker-gui-browser.png new file mode 100644 index 0000000..c38d828 Binary files /dev/null and b/screenshots/netmaker-gui-browser.png differ diff --git a/screenshots/netmaker-gui-phone.png b/screenshots/netmaker-gui-phone.png new file mode 100644 index 0000000..6ce22d8 Binary files /dev/null and b/screenshots/netmaker-gui-phone.png differ diff --git a/screenshots/netmaker-gui-ui.png b/screenshots/netmaker-gui-ui.png new file mode 100644 index 0000000..f9440f8 Binary files /dev/null and b/screenshots/netmaker-gui-ui.png differ diff --git a/types.go b/types.go index b35c560..14ab85d 100644 --- a/types.go +++ b/types.go @@ -3,138 +3,113 @@ package main import ( "fmt" + "github.com/gin-contrib/sessions" + "github.com/gin-gonic/gin" controller "github.com/gravitl/netmaker/controllers" + "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/models" ) -type User struct { - UserName string - Password string - IsAdmin bool -} - -type ErrorResponse struct { - Code int - Message string -} - -type Success struct { - Code int - Message string - Response Auth -} - -type Auth struct { - UserName string - AuthToken string +type NodeStatus struct { + Mac string + Status string + Color string } //PageData -contains data for html template type PageData struct { - Page string - Networks []models.Network - Nodes []models.Node - Users []models.ReturnUser + Page string + Message string + Admin bool + Networks []models.Network + Nodes []models.Node + Users []models.ReturnUser + ExtClients []models.ExtClient } //Initializes (fetches) page data from backend -func (data *PageData) Init(page string) { +func (data *PageData) Init(page string, c *gin.Context) { data.Page = page + session := sessions.Default(c) + user := session.Get("username").(string) + isAdmin := session.Get("isAdmin").(bool) + data.Message = session.Get("message").(string) + data.Admin = isAdmin + allowedNets := session.Get("networks").([]string) networks, err := models.GetNetworks() if err != nil { //panic(err) fmt.Println("error geting network data", err) } - data.Networks = networks + extclients, err := functions.GetAllExtClients() + if err != nil { + fmt.Println("error getting external client data", err) + } nodes, err := models.GetAllNodes() if err != nil { fmt.Println("error getting node data", err) } - data.Nodes = nodes users, err := controller.GetUsers() if err != nil { fmt.Println("error getting user data", err) } - data.Users = users - -} + if isAdmin { + data.Networks = networks + data.Nodes = nodes + data.Users = users + data.ExtClients = extclients + } else { + var nets []models.Network + for _, network := range networks { + if SliceContains(allowedNets, network.NetID) { + nets = append(nets, network) + } + data.Networks = nets + } + var hosts []models.Node + for _, node := range nodes { + if SliceContains(allowedNets, node.Network) { + hosts = append(hosts, node) + } + data.Nodes = hosts + } + user := models.ReturnUser{user, allowedNets, isAdmin} + data.Users = append([]models.ReturnUser{}, user) -//NetSummary - contains summary network data for html template -type NetSummary struct { - Name string - ID string - Address string - NodeModified string - NetModified string - Keys []models.AccessKey - //AddressRange string - //NodeLastModified string - //NetworkLastModified string -} + var clients []models.ExtClient + for _, client := range extclients { + if SliceContains(allowedNets, client.Network) { + clients = append(clients, client) + } + data.ExtClients = clients + } + } -//NodeSummary - contains summary node data for html template -type NodeSummary struct { - Name string - Network string - PublicIP string - SubNet string - // PublicIP string - // SubNetIP string - // Status string - // PublicKey string - // ListenPort string - // LastCheckin string } -//NetDetail - contains detailed network data for html template -//type NetDetail struct { -// Name string -// AddressRange string -// IP6Address string -// LocalRange string -// DisplayName string -// NodeLastModified string -// NetworkLastModified string -// DefaultInterface string -// DefaultPort string -// PostUp string -// PostDown string -// KeepAlive string -// CheckinInterval string -// DualStack bool -// SaveConfig bool -// UDPHolePunch bool -// KeyRequired bool -//} +func GetAllExtClients() []models.ExtClient { + var clients []models.ExtClient + var client models.ExtClient + client.ClientID = "clientid" + client.Description = "description" + client.PrivateKey = "private key" + client.PublicKey = "my public key" + client.Network = "net" + client.Address = "10.2.2.23" + client.IngressGatewayID = "tbd" -//NodeDetail - contains detailed node data for html template -type NodeDetail struct { - Name string - Interface string - Network string - AddressRange string - IP6Address string - ListenPort string - PublicKey string - Endpoint string - Expires string - PostUp string - PostDown string - KeepAlive string - CheckinInterval string - EgressGatewayRange string - AllowedIPs string - DualStack bool - SaveConfig bool - UDPHolePunch bool - KeyRequired bool - Static bool + clients = append(clients, client) + return clients } -type NewNet struct { - Name string - Address string - Dual string - Local string - UDP string +func SliceContains(s []string, x string) bool { + if len(s) == 0 { + return false + } + for i := range s { + if s[i] == x { + return true + } + } + return false }