Forráskód Böngészése

Merge branch 'develop' of https://github.com/gravitl/netmaker into NET-1911

abhishek9686 6 hónapja
szülő
commit
e50da805d4

+ 55 - 0
cli/cmd/gateway/create.go

@@ -0,0 +1,55 @@
+package gateway
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+	"strings"
+)
+
+var externalClientDNS string
+var isInternetGateway bool
+var metadata string
+var persistentKeepAlive uint
+var mtu uint
+
+var gatewayCreateCmd = &cobra.Command{
+	Use:   "create [NETWORK NAME] [NODE ID] [RELAYED NODES ID (comma separated)]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Create a new Gateway on a Netmaker network.",
+	Long: `
+Configures a node as a gateway in a specified network, allowing it to relay traffic for other nodes. The gateway can also function as an internet gateway if specified.
+
+Arguments:
+NETWORK NAME:		The name of the network where the gateway will be created.
+NODE ID:			The ID of the node to be configured as a gateway.
+RELAYED NODES ID:	A comma-separated list of node IDs that will be relayed through this gateway.
+`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(
+			functions.CreateGateway(
+				models.IngressRequest{
+					ExtclientDNS:        externalClientDNS,
+					IsInternetGateway:   isInternetGateway,
+					Metadata:            metadata,
+					PersistentKeepalive: int32(persistentKeepAlive),
+					MTU:                 int32(mtu),
+				},
+				models.RelayRequest{
+					NodeID:       args[0],
+					NetID:        args[1],
+					RelayedNodes: strings.Split(args[2], ","),
+				},
+			),
+		)
+	},
+}
+
+func init() {
+	gatewayCreateCmd.Flags().StringVarP(&externalClientDNS, "dns", "d", "", "the IP address of the DNS server to be used by external clients")
+	gatewayCreateCmd.Flags().BoolVarP(&isInternetGateway, "internet", "i", false, "if set, the gateway will route traffic to the internet")
+	gatewayCreateCmd.Flags().StringVarP(&metadata, "note", "n", "", "description or metadata to be associated with the gateway")
+	gatewayCreateCmd.Flags().UintVarP(&persistentKeepAlive, "keep-alive", "k", 20, "the keep-alive interval (in seconds) for maintaining persistent connections")
+	gatewayCreateCmd.Flags().UintVarP(&mtu, "mtu", "m", 1420, "the maximum transmission unit (MTU) size in bytes")
+	rootCmd.AddCommand(gatewayCreateCmd)
+}

+ 27 - 0
cli/cmd/gateway/delete.go

@@ -0,0 +1,27 @@
+package gateway
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var gatewayDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a Gateway.",
+	Long: `
+Removes the gateway configuration from a node in a specified network. The node itself remains, but it will no longer function as a gateway.
+
+Arguments:
+NETWORK NAME:	The name of the network from which the gateway configuration should be removed.
+NODE ID:		The ID of the node that is currently acting as a gateway.
+`,
+	Aliases: []string{"rm"},
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteGateway(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(gatewayDeleteCmd)
+}

+ 18 - 0
cli/cmd/gateway/root.go

@@ -0,0 +1,18 @@
+package gateway
+
+import (
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands.
+var rootCmd = &cobra.Command{
+	Use:     "gateway",
+	Short:   "Manage Gateways.",
+	Long:    `Manage Gateways.`,
+	Aliases: []string{"gw"},
+}
+
+// GetRoot returns the root subcommand.
+func GetRoot() *cobra.Command {
+	return rootCmd
+}

+ 6 - 5
cli/cmd/node/create_ingress.go

@@ -6,11 +6,12 @@ import (
 )
 
 var nodeCreateIngressCmd = &cobra.Command{
-	Use:     "create_remote_access_gateway [NETWORK NAME] [NODE ID]",
-	Args:    cobra.ExactArgs(2),
-	Short:   "Turn a Node into a Remote Access Gateway (Ingress)",
-	Long:    `Turn a Node into a Remote Access Gateway (Ingress) for a Network.`,
-	Aliases: []string{"create_rag"},
+	Use:        "create_remote_access_gateway [NETWORK NAME] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Turn a Node into a Remote Access Gateway (Ingress)",
+	Long:       `Turn a Node into a Remote Access Gateway (Ingress) for a Network.`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
+	Aliases:    []string{"create_rag"},
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.CreateIngress(args[0], args[1], failover))
 	},

+ 5 - 4
cli/cmd/node/create_relay.go

@@ -8,10 +8,11 @@ import (
 )
 
 var hostCreateRelayCmd = &cobra.Command{
-	Use:   "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]",
-	Args:  cobra.ExactArgs(3),
-	Short: "Turn a Node into a Relay",
-	Long:  `Turn a Node into a Relay`,
+	Use:        "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]",
+	Args:       cobra.ExactArgs(3),
+	Short:      "Turn a Node into a Relay",
+	Long:       `Turn a Node into a Relay`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ",")))
 	},

+ 6 - 5
cli/cmd/node/delete_ingress.go

@@ -6,11 +6,12 @@ import (
 )
 
 var nodeDeleteIngressCmd = &cobra.Command{
-	Use:     "delete_remote_access_gateway [NETWORK NAME] [NODE ID]",
-	Args:    cobra.ExactArgs(2),
-	Short:   "Delete Remote Access Gateway role from a Node",
-	Long:    `Delete Remote Access Gateway role from a Node`,
-	Aliases: []string{"delete_rag"},
+	Use:        "delete_remote_access_gateway [NETWORK NAME] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Delete Remote Access Gateway role from a Node",
+	Long:       `Delete Remote Access Gateway role from a Node`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
+	Aliases:    []string{"delete_rag"},
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.DeleteIngress(args[0], args[1]))
 	},

+ 5 - 4
cli/cmd/node/delete_relay.go

@@ -6,10 +6,11 @@ import (
 )
 
 var hostDeleteRelayCmd = &cobra.Command{
-	Use:   "delete_relay [NETWORK] [NODE ID]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Delete Relay from a node",
-	Long:  `Delete Relay from a node`,
+	Use:        "delete_relay [NETWORK] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Delete Relay from a node",
+	Long:       `Delete Relay from a node`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.DeleteRelay(args[0], args[1]))
 	},

+ 2 - 0
cli/cmd/root.go

@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"github.com/gravitl/netmaker/cli/cmd/gateway"
 	"os"
 
 	"github.com/gravitl/netmaker/cli/cmd/acl"
@@ -55,4 +56,5 @@ func init() {
 	rootCmd.AddCommand(host.GetRoot())
 	rootCmd.AddCommand(enrollment_key.GetRoot())
 	rootCmd.AddCommand(failover.GetRoot())
+	rootCmd.AddCommand(gateway.GetRoot())
 }

+ 18 - 0
cli/functions/gateway.go

@@ -0,0 +1,18 @@
+package functions
+
+import (
+	"fmt"
+	"github.com/gravitl/netmaker/models"
+	"net/http"
+)
+
+func CreateGateway(ingressRequest models.IngressRequest, relayRequest models.RelayRequest) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/gateway", relayRequest.NetID, relayRequest.NodeID), &models.CreateGwReq{
+		IngressRequest: ingressRequest,
+		RelayRequest:   relayRequest,
+	})
+}
+
+func DeleteGateway(networkID, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/gateway", networkID, nodeID), nil)
+}

+ 1 - 0
controllers/network.go

@@ -464,6 +464,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype))
 		return
 	}
+	go logic.UnlinkNetworkAndTagsFromEnrollmentKeys(network, true)
 	go logic.DeleteNetworkRoles(network)
 	go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
 	//delete network from allocated ip map

+ 49 - 2
controllers/node.go

@@ -31,6 +31,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/nodes/{network}/status", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodeStatus))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 
@@ -328,7 +329,7 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 	}
 
 	nodes = logic.AddStaticNodestoList(nodes)
-	nodes = logic.AddStatusToNodes(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
 	// returns all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName)
@@ -368,7 +369,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 
 	}
 	nodes = logic.AddStaticNodestoList(nodes)
-	nodes = logic.AddStatusToNodes(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
 	// return all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
@@ -377,6 +378,52 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNodes)
 }
 
+// @Summary     Get all nodes status on the network
+// @Router      /api/v1/nodes/{network}/status [get]
+// @Tags        Nodes
+// @Securitydefinitions.oauth2.application OAuth2Application
+// @Success     200 {array} models.ApiNode
+// @Failure     500 {object} models.ErrorResponse
+// Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
+func getNetworkNodeStatus(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	netID := params["network"]
+	// validate network
+	_, err := logic.GetNetwork(netID)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to get network %v", err), "badrequest"))
+		return
+	}
+	var nodes []models.Node
+	nodes, err = logic.GetNetworkNodes(netID)
+	if err != nil {
+		logger.Log(0, "error fetching all nodes info: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	username := r.Header.Get("user")
+	if r.Header.Get("ismaster") == "no" {
+		user, err := logic.GetUser(username)
+		if err != nil {
+			return
+		}
+		userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
+		if err != nil {
+			return
+		}
+		if !userPlatformRole.FullAccess {
+			nodes = logic.GetFilteredNodesByUserAccess(*user, nodes)
+		}
+
+	}
+	nodes = logic.AddStaticNodestoList(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
+	// return all the nodes in JSON/API format
+	apiNodesStatusMap := logic.GetNodesStatusAPI(nodes[:])
+	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
+	logic.ReturnSuccessResponseWithJson(w, r, apiNodesStatusMap, "fetched nodes with metric status")
+}
+
 // @Summary     Get an individual node
 // @Router      /api/nodes/{network}/{nodeid} [get]
 // @Tags        Nodes

+ 9 - 0
controllers/user.go

@@ -258,6 +258,15 @@ func getUserV1(w http.ResponseWriter, r *http.Request) {
 	resp := models.ReturnUserWithRolesAndGroups{
 		ReturnUser:   user,
 		PlatformRole: userRoleTemplate,
+		UserGroups:   map[models.UserGroupID]models.UserGroup{},
+	}
+	for gId := range user.UserGroups {
+		grp, err := logic.GetUserGroup(gId)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+		resp.UserGroups[gId] = grp
 	}
 	logger.Log(2, r.Header.Get("user"), "fetched user", usernameFetched)
 	logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched user with role info")

+ 57 - 1
logic/enrollmentkey.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strings"
 	"sync"
 	"time"
 
@@ -120,7 +121,6 @@ func UpdateEnrollmentKey(keyId string, relayId uuid.UUID, groups []models.TagID)
 }
 
 // GetAllEnrollmentKeys - fetches all enrollment keys from DB
-// TODO drop double pointer
 func GetAllEnrollmentKeys() ([]models.EnrollmentKey, error) {
 	currentKeys, err := getEnrollmentKeysMap()
 	if err != nil {
@@ -335,3 +335,59 @@ func RemoveTagFromEnrollmentKeys(deletedTagID models.TagID) {
 
 	}
 }
+
+func UnlinkNetworkAndTagsFromEnrollmentKeys(network string, delete bool) error {
+	keys, err := GetAllEnrollmentKeys()
+	if err != nil {
+		return fmt.Errorf("failed to retrieve keys: %w", err)
+	}
+
+	var errs []error
+	for _, key := range keys {
+		newNetworks := []string{}
+		newTags := []models.TagID{}
+		update := false
+
+		// Check and update networks
+		for _, net := range key.Networks {
+			if net == network {
+				update = true
+				continue
+			}
+			newNetworks = append(newNetworks, net)
+		}
+
+		// Check and update tags
+		for _, tag := range key.Groups {
+			tagParts := strings.Split(tag.String(), ".")
+			if len(tagParts) == 0 {
+				continue
+			}
+			tagNetwork := tagParts[0]
+			if tagNetwork == network {
+				update = true
+				continue
+			}
+			newTags = append(newTags, tag)
+		}
+
+		if update && len(newNetworks) == 0 && delete {
+			if err := DeleteEnrollmentKey(key.Value, true); err != nil {
+				errs = append(errs, fmt.Errorf("failed to delete key %s: %w", key.Value, err))
+			}
+			continue
+		}
+		if update {
+			key.Networks = newNetworks
+			key.Groups = newTags
+			if err := upsertEnrollmentKey(&key); err != nil {
+				errs = append(errs, fmt.Errorf("failed to update key %s: %w", key.Value, err))
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return fmt.Errorf("errors unlinking network/tags from keys: %v", errs)
+	}
+	return nil
+}

+ 17 - 2
logic/nodes.go

@@ -444,7 +444,7 @@ func AddStaticNodestoList(nodes []models.Node) []models.Node {
 	return nodes
 }
 
-func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) {
+func AddStatusToNodes(nodes []models.Node, statusCall bool) (nodesWithStatus []models.Node) {
 	aclDefaultPolicyStatusMap := make(map[string]bool)
 	for _, node := range nodes {
 		if _, ok := aclDefaultPolicyStatusMap[node.Network]; !ok {
@@ -452,7 +452,12 @@ func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) {
 			defaultPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 			aclDefaultPolicyStatusMap[node.Network] = defaultPolicy.Enabled
 		}
-		GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network])
+		if statusCall {
+			GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network])
+		} else {
+			GetNodeCheckInStatus(&node, true)
+		}
+
 		nodesWithStatus = append(nodesWithStatus, node)
 	}
 	return
@@ -573,6 +578,16 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode {
 	return apiNodes[:]
 }
 
+// GetNodesStatusAPI - gets nodes status
+func GetNodesStatusAPI(nodes []models.Node) map[string]models.ApiNodeStatus {
+	apiStatusNodesMap := make(map[string]models.ApiNodeStatus)
+	for i := range nodes {
+		newApiNode := nodes[i].ConvertToStatusNode()
+		apiStatusNodesMap[newApiNode.ID] = *newApiNode
+	}
+	return apiStatusNodesMap
+}
+
 // DeleteExpiredNodes - goroutine which deletes nodes which are expired
 func DeleteExpiredNodes(ctx context.Context, peerUpdate chan *models.Node) {
 	// Delete Expired Nodes Every Hour

+ 2 - 2
logic/status.go

@@ -6,9 +6,9 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
-var GetNodeStatus = getNodeStatus
+var GetNodeStatus = GetNodeCheckInStatus
 
-func getNodeStatus(node *models.Node, t bool) {
+func GetNodeCheckInStatus(node *models.Node, t bool) {
 	// On CE check only last check-in time
 	if node.IsStatic {
 		if !node.StaticNode.Enabled {

+ 1 - 0
logic/user_mgmt.go

@@ -60,6 +60,7 @@ var DeleteNetworkRoles = func(netID string) {}
 var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {}
 var CreateDefaultUserPolicies = func(netID models.NetworkID) {}
 var GetUserGroupsInNetwork = func(netID models.NetworkID) (networkGrps map[models.UserGroupID]models.UserGroup) { return }
+var GetUserGroup = func(groupId models.UserGroupID) (userGrps models.UserGroup, err error) { return }
 var AddGlobalNetRolesToAdmins = func(u models.User) {}
 
 // GetRole - fetches role template by id

+ 20 - 0
models/api_node.go

@@ -8,6 +8,13 @@ import (
 	"golang.org/x/exp/slog"
 )
 
+type ApiNodeStatus struct {
+	ID         string     `json:"id"`
+	IsStatic   bool       `json:"is_static"`
+	IsUserNode bool       `json:"is_user_node"`
+	Status     NodeStatus `json:"status"`
+}
+
 // ApiNode is a stripped down Node DTO that exposes only required fields to external systems
 type ApiNode struct {
 	ID                         string   `json:"id,omitempty" validate:"required,min=5,id_unique"`
@@ -132,6 +139,19 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	return &convertedNode
 }
 
+func (nm *Node) ConvertToStatusNode() *ApiNodeStatus {
+	apiNode := ApiNodeStatus{}
+	if nm.IsStatic {
+		apiNode.ID = nm.StaticNode.ClientID
+	} else {
+		apiNode.ID = nm.ID.String()
+	}
+	apiNode.IsStatic = nm.IsStatic
+	apiNode.IsUserNode = nm.IsUserNode
+	apiNode.Status = nm.Status
+	return &apiNode
+}
+
 // Node.ConvertToAPINode - converts a node to an API node
 func (nm *Node) ConvertToAPINode() *ApiNode {
 	apiNode := ApiNode{}

+ 12 - 11
models/structs.go

@@ -32,17 +32,18 @@ type IngressGwUsers struct {
 
 // UserRemoteGws - struct to hold user's remote gws
 type UserRemoteGws struct {
-	GwID              string    `json:"remote_access_gw_id"`
-	GWName            string    `json:"gw_name"`
-	Network           string    `json:"network"`
-	Connected         bool      `json:"connected"`
-	IsInternetGateway bool      `json:"is_internet_gateway"`
-	GwClient          ExtClient `json:"gw_client"`
-	GwPeerPublicKey   string    `json:"gw_peer_public_key"`
-	GwListenPort      int       `json:"gw_listen_port"`
-	Metadata          string    `json:"metadata"`
-	AllowedEndpoints  []string  `json:"allowed_endpoints"`
-	NetworkAddresses  []string  `json:"network_addresses"`
+	GwID              string     `json:"remote_access_gw_id"`
+	GWName            string     `json:"gw_name"`
+	Network           string     `json:"network"`
+	Connected         bool       `json:"connected"`
+	IsInternetGateway bool       `json:"is_internet_gateway"`
+	GwClient          ExtClient  `json:"gw_client"`
+	GwPeerPublicKey   string     `json:"gw_peer_public_key"`
+	GwListenPort      int        `json:"gw_listen_port"`
+	Metadata          string     `json:"metadata"`
+	AllowedEndpoints  []string   `json:"allowed_endpoints"`
+	NetworkAddresses  []string   `json:"network_addresses"`
+	Status            NodeStatus `json:"status"`
 }
 
 // UserRAGs - struct for user access gws

+ 1 - 0
models/user_mgmt.go

@@ -159,6 +159,7 @@ type User struct {
 type ReturnUserWithRolesAndGroups struct {
 	ReturnUser
 	PlatformRole UserRolePermissionTemplate `json:"platform_role"`
+	UserGroups   map[UserGroupID]UserGroup  `json:"user_group_ids"`
 }
 
 // ReturnUser - return user struct

+ 10 - 0
pro/controllers/users.go

@@ -1102,6 +1102,10 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				slog.Error("failed to get node network", "error", err)
 				continue
 			}
+			nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false)
+			if len(nodesWithStatus) > 0 {
+				node = nodesWithStatus[0]
+			}
 
 			gws := userGws[node.Network]
 			extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient)
@@ -1117,6 +1121,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				Metadata:          node.Metadata,
 				AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 				NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
+				Status:            node.Status,
 			})
 			userGws[node.Network] = gws
 			delete(userGwNodes, node.ID.String())
@@ -1138,6 +1143,10 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 			continue
 		}
+		nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false)
+		if len(nodesWithStatus) > 0 {
+			node = nodesWithStatus[0]
+		}
 		network, err := logic.GetNetwork(node.Network)
 		if err != nil {
 			slog.Error("failed to get node network", "error", err)
@@ -1154,6 +1163,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			Metadata:          node.Metadata,
 			AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 			NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
+			Status:            node.Status,
 		})
 		userGws[node.Network] = gws
 	}

+ 1 - 0
pro/initialize.go

@@ -133,6 +133,7 @@ func InitPro() {
 	logic.IntialiseGroups = proLogic.UserGroupsInit
 	logic.AddGlobalNetRolesToAdmins = proLogic.AddGlobalNetRolesToAdmins
 	logic.GetUserGroupsInNetwork = proLogic.GetUserGroupsInNetwork
+	logic.GetUserGroup = proLogic.GetUserGroup
 	logic.GetNodeStatus = proLogic.GetNodeStatus
 }
 

+ 6 - 4
pro/logic/user_mgmt.go

@@ -216,8 +216,9 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 
 	// create default network groups
 	var NetworkAdminGroup = models.UserGroup{
-		ID:   models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)),
-		Name: fmt.Sprintf("%s Admin Group", netID),
+		ID:      models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)),
+		Name:    fmt.Sprintf("%s Admin Group", netID),
+		Default: true,
 		NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{
 			netID: {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkAdmin)): {},
@@ -226,8 +227,9 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 		MetaData: fmt.Sprintf("can manage your network `%s` configuration including adding and removing devices.", netID),
 	}
 	var NetworkUserGroup = models.UserGroup{
-		ID:   models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)),
-		Name: fmt.Sprintf("%s User Group", netID),
+		ID:      models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)),
+		Name:    fmt.Sprintf("%s User Group", netID),
+		Default: true,
 		NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{
 			netID: {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {},