mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Removed GAN
- Removed UI and module of GAN - this feature is moved to plugin
This commit is contained in:
parent
549e492ffd
commit
75c351e7e2
19
src/api.go
19
src/api.go
@ -144,24 +144,6 @@ func RegisterStatisticalAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing)
|
||||
}
|
||||
|
||||
// Register the APIs for Global Area Network management functions, Will be moving to plugin soon
|
||||
func RegisterGANAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/gan/network/info", ganManager.HandleGetNodeID)
|
||||
authRouter.HandleFunc("/api/gan/network/add", ganManager.HandleAddNetwork)
|
||||
authRouter.HandleFunc("/api/gan/network/remove", ganManager.HandleRemoveNetwork)
|
||||
authRouter.HandleFunc("/api/gan/network/list", ganManager.HandleListNetwork)
|
||||
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
|
||||
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
|
||||
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
|
||||
authRouter.HandleFunc("/api/gan/network/join", ganManager.HandleServerJoinNetwork)
|
||||
authRouter.HandleFunc("/api/gan/network/leave", ganManager.HandleServerLeaveNetwork)
|
||||
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
|
||||
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
|
||||
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)
|
||||
authRouter.HandleFunc("/api/gan/members/authorize", ganManager.HandleMemberAuthorization)
|
||||
authRouter.HandleFunc("/api/gan/members/delete", ganManager.HandleMemberDelete)
|
||||
}
|
||||
|
||||
// Register the APIs for Stream (TCP / UDP) Proxy management functions
|
||||
func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig)
|
||||
@ -344,7 +326,6 @@ func initAPIs(targetMux *http.ServeMux) {
|
||||
RegisterAccessRuleAPIs(authRouter)
|
||||
RegisterPathRuleAPIs(authRouter)
|
||||
RegisterStatisticalAPIs(authRouter)
|
||||
RegisterGANAPIs(authRouter)
|
||||
RegisterStreamProxyAPIs(authRouter)
|
||||
RegisterMDNSAPIs(authRouter)
|
||||
RegisterNetworkUtilsAPIs(authRouter)
|
||||
|
@ -1,80 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func TryLoadorAskUserForAuthkey() (string, error) {
|
||||
//Check for zt auth token
|
||||
value, exists := os.LookupEnv("ZT_AUTH")
|
||||
if !exists {
|
||||
log.Println("Environment variable ZT_AUTH not defined. Trying to load authtoken from file.")
|
||||
} else {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
authKey := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
if isAdmin() {
|
||||
//Read the secret file directly
|
||||
b, err := os.ReadFile("C:\\ProgramData\\ZeroTier\\One\\authtoken.secret")
|
||||
if err == nil {
|
||||
log.Println("Zerotier authkey loaded")
|
||||
authKey = string(b)
|
||||
} else {
|
||||
log.Println("Unable to read authkey at C:\\ProgramData\\ZeroTier\\One\\authtoken.secret: ", err.Error())
|
||||
}
|
||||
} else {
|
||||
//Elavate the permission to admin
|
||||
ak, err := readAuthTokenAsAdmin()
|
||||
if err == nil {
|
||||
log.Println("Zerotier authkey loaded")
|
||||
authKey = ak
|
||||
} else {
|
||||
log.Println("Unable to read authkey at C:\\ProgramData\\ZeroTier\\One\\authtoken.secret: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
} else if runtime.GOOS == "linux" {
|
||||
if isAdmin() {
|
||||
//Try to read from source using sudo
|
||||
ak, err := readAuthTokenAsAdmin()
|
||||
if err == nil {
|
||||
log.Println("Zerotier authkey loaded")
|
||||
authKey = strings.TrimSpace(ak)
|
||||
} else {
|
||||
log.Println("Unable to read authkey at /var/lib/zerotier-one/authtoken.secret: ", err.Error())
|
||||
}
|
||||
} else {
|
||||
//Try read from source
|
||||
b, err := os.ReadFile("/var/lib/zerotier-one/authtoken.secret")
|
||||
if err == nil {
|
||||
log.Println("Zerotier authkey loaded")
|
||||
authKey = string(b)
|
||||
} else {
|
||||
log.Println("Unable to read authkey at /var/lib/zerotier-one/authtoken.secret: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
b, err := os.ReadFile("/Library/Application Support/ZeroTier/One/authtoken.secret")
|
||||
if err == nil {
|
||||
log.Println("Zerotier authkey loaded")
|
||||
authKey = string(b)
|
||||
} else {
|
||||
log.Println("Unable to read authkey at /Library/Application Support/ZeroTier/One/authtoken.secret ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
authKey = strings.TrimSpace(authKey)
|
||||
|
||||
if authKey == "" {
|
||||
return "", errors.New("Unable to load authkey from file")
|
||||
}
|
||||
|
||||
return authKey, nil
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
func readAuthTokenAsAdmin() (string, error) {
|
||||
if utils.FileExists("./conf/authtoken.secret") {
|
||||
authKey, err := os.ReadFile("./conf/authtoken.secret")
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(authKey)), nil
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("sudo", "cat", "/var/lib/zerotier-one/authtoken.secret")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func isAdmin() bool {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return currentUser.Username == "root"
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
// Use admin permission to read auth token on Windows
|
||||
func readAuthTokenAsAdmin() (string, error) {
|
||||
//Check if the previous startup already extracted the authkey
|
||||
if utils.FileExists("./conf/authtoken.secret") {
|
||||
authKey, err := os.ReadFile("./conf/authtoken.secret")
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(authKey)), nil
|
||||
}
|
||||
}
|
||||
|
||||
verb := "runas"
|
||||
exe := "cmd.exe"
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
output, _ := filepath.Abs(filepath.Join("./conf/", "authtoken.secret"))
|
||||
os.WriteFile(output, []byte(""), 0775)
|
||||
args := fmt.Sprintf("/C type \"C:\\ProgramData\\ZeroTier\\One\\authtoken.secret\" > \"" + output + "\"")
|
||||
|
||||
verbPtr, _ := syscall.UTF16PtrFromString(verb)
|
||||
exePtr, _ := syscall.UTF16PtrFromString(exe)
|
||||
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
|
||||
argPtr, _ := syscall.UTF16PtrFromString(args)
|
||||
|
||||
var showCmd int32 = 1 //SW_NORMAL
|
||||
|
||||
err := windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Println("Please click agree to allow access to ZeroTier authtoken from ProgramData")
|
||||
retry := 0
|
||||
time.Sleep(3 * time.Second)
|
||||
for !utils.FileExists("./conf/authtoken.secret") && retry < 10 {
|
||||
time.Sleep(3 * time.Second)
|
||||
log.Println("Waiting for ZeroTier authtoken extraction...")
|
||||
retry++
|
||||
}
|
||||
|
||||
authKey, err := os.ReadFile("./conf/authtoken.secret")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(authKey)), nil
|
||||
}
|
||||
|
||||
// Check if admin on Windows
|
||||
func isAdmin() bool {
|
||||
_, err := os.Open("\\\\.\\PHYSICALDRIVE0")
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
)
|
||||
|
||||
/*
|
||||
Global Area Network
|
||||
Server side implementation
|
||||
|
||||
This module do a few things to help manage
|
||||
the system GANs
|
||||
|
||||
- Provide DHCP assign to client
|
||||
- Provide a list of connected nodes in the same VLAN
|
||||
- Provide proxy of packet if the target VLAN is online but not reachable
|
||||
|
||||
Also provide HTTP Handler functions for management
|
||||
- Create Network
|
||||
- Update Network Properties (Name / Desc)
|
||||
- Delete Network
|
||||
|
||||
- Authorize Node
|
||||
- Deauthorize Node
|
||||
- Set / Get Network Prefered Subnet Mask
|
||||
- Handle Node ping
|
||||
*/
|
||||
|
||||
type Node struct {
|
||||
Auth bool //If the node is authorized in this network
|
||||
ClientID string //The client ID
|
||||
MAC string //The tap MAC this client is using
|
||||
Name string //Name of the client in this network
|
||||
Description string //Description text
|
||||
ManagedIP net.IP //The IP address assigned by this network
|
||||
LastSeen int64 //Last time it is seen from this host
|
||||
ClientVersion string //Client application version
|
||||
PublicIP net.IP //Public IP address as seen from this host
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
UID string //UUID of the network, must be a 16 char random ASCII string
|
||||
Name string //Name of the network, ASCII only
|
||||
Description string //Description of the network
|
||||
CIDR string //The subnet masked use by this network
|
||||
Nodes []*Node //The nodes currently attached in this network
|
||||
}
|
||||
|
||||
type NetworkManagerOptions struct {
|
||||
Database *database.Database
|
||||
AuthToken string
|
||||
ApiPort int
|
||||
}
|
||||
|
||||
type NetworkMetaData struct {
|
||||
Desc string
|
||||
}
|
||||
|
||||
type MemberMetaData struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type NetworkManager struct {
|
||||
authToken string
|
||||
apiPort int
|
||||
ControllerID string
|
||||
option *NetworkManagerOptions
|
||||
networksMetadata map[string]NetworkMetaData
|
||||
}
|
||||
|
||||
// Create a new GAN manager
|
||||
func NewNetworkManager(option *NetworkManagerOptions) *NetworkManager {
|
||||
option.Database.NewTable("ganserv")
|
||||
|
||||
//Load network metadata
|
||||
networkMeta := map[string]NetworkMetaData{}
|
||||
if option.Database.KeyExists("ganserv", "networkmeta") {
|
||||
option.Database.Read("ganserv", "networkmeta", &networkMeta)
|
||||
}
|
||||
|
||||
//Start the zerotier instance if not exists
|
||||
|
||||
//Get controller info
|
||||
instanceInfo, err := getControllerInfo(option.AuthToken, option.ApiPort)
|
||||
if err != nil {
|
||||
log.Println("ZeroTier connection failed: ", err.Error())
|
||||
return &NetworkManager{
|
||||
authToken: option.AuthToken,
|
||||
apiPort: option.ApiPort,
|
||||
ControllerID: "",
|
||||
option: option,
|
||||
networksMetadata: networkMeta,
|
||||
}
|
||||
}
|
||||
|
||||
return &NetworkManager{
|
||||
authToken: option.AuthToken,
|
||||
apiPort: option.ApiPort,
|
||||
ControllerID: instanceInfo.Address,
|
||||
option: option,
|
||||
networksMetadata: networkMeta,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NetworkManager) GetNetworkMetaData(netid string) *NetworkMetaData {
|
||||
md, ok := m.networksMetadata[netid]
|
||||
if !ok {
|
||||
return &NetworkMetaData{}
|
||||
}
|
||||
|
||||
return &md
|
||||
}
|
||||
|
||||
func (m *NetworkManager) WriteNetworkMetaData(netid string, meta *NetworkMetaData) {
|
||||
m.networksMetadata[netid] = *meta
|
||||
m.option.Database.Write("ganserv", "networkmeta", m.networksMetadata)
|
||||
}
|
||||
|
||||
func (m *NetworkManager) GetMemberMetaData(netid string, memid string) *MemberMetaData {
|
||||
thisMemberData := MemberMetaData{}
|
||||
m.option.Database.Read("ganserv", "memberdata_"+netid+"_"+memid, &thisMemberData)
|
||||
return &thisMemberData
|
||||
}
|
||||
|
||||
func (m *NetworkManager) WriteMemeberMetaData(netid string, memid string, meta *MemberMetaData) {
|
||||
m.option.Database.Write("ganserv", "memberdata_"+netid+"_"+memid, meta)
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
func (m *NetworkManager) HandleGetNodeID(w http.ResponseWriter, r *http.Request) {
|
||||
if m.ControllerID == "" {
|
||||
//Node id not exists. Check again
|
||||
instanceInfo, err := getControllerInfo(m.option.AuthToken, m.option.ApiPort)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to access node id information")
|
||||
return
|
||||
}
|
||||
|
||||
m.ControllerID = instanceInfo.Address
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(m.ControllerID)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleAddNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
networkInfo, err := m.createNetwork()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Network created. Assign it the standard network settings
|
||||
err = m.configureNetwork(networkInfo.Nwid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Return the new network ID
|
||||
js, _ := json.Marshal(networkInfo.Nwid)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleRemoveNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
networkID, err := utils.PostPara(r, "id")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty network id given")
|
||||
return
|
||||
}
|
||||
|
||||
if !m.networkExists(networkID) {
|
||||
utils.SendErrorResponse(w, "network id not exists")
|
||||
return
|
||||
}
|
||||
|
||||
err = m.deleteNetwork(networkID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleListNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
netid, _ := utils.GetPara(r, "netid")
|
||||
if netid != "" {
|
||||
targetNetInfo, err := m.getNetworkInfoById(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(targetNetInfo)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
} else {
|
||||
// Return the list of networks as JSON
|
||||
networkIds, err := m.listNetworkIds()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
networkInfos := []*NetworkInfo{}
|
||||
for _, id := range networkIds {
|
||||
thisNetInfo, err := m.getNetworkInfoById(id)
|
||||
if err == nil {
|
||||
networkInfos = append(networkInfos, thisNetInfo)
|
||||
}
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(networkInfos)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleNetworkNaming(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "network id not given")
|
||||
return
|
||||
}
|
||||
|
||||
if !m.networkExists(netid) {
|
||||
utils.SendErrorResponse(w, "network not eixsts")
|
||||
}
|
||||
|
||||
newName, _ := utils.PostPara(r, "name")
|
||||
newDesc, _ := utils.PostPara(r, "desc")
|
||||
if newName != "" && newDesc != "" {
|
||||
//Strip away html from name and desc
|
||||
re := regexp.MustCompile("<[^>]*>")
|
||||
newName := re.ReplaceAllString(newName, "")
|
||||
newDesc := re.ReplaceAllString(newDesc, "")
|
||||
|
||||
//Set the new network name and desc
|
||||
err = m.setNetworkNameAndDescription(netid, newName, newDesc)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
//Get current name and description
|
||||
name, desc, err := m.getNetworkNameAndDescription(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal([]string{name, desc})
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleNetworkDetails(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "netid not given")
|
||||
return
|
||||
}
|
||||
|
||||
targetNetwork, err := m.getNetworkInfoById(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(targetNetwork)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *NetworkManager) HandleSetRanges(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "netid not given")
|
||||
return
|
||||
}
|
||||
cidr, err := utils.PostPara(r, "cidr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "cidr not given")
|
||||
return
|
||||
}
|
||||
ipstart, err := utils.PostPara(r, "ipstart")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "ipstart not given")
|
||||
return
|
||||
}
|
||||
ipend, err := utils.PostPara(r, "ipend")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "ipend not given")
|
||||
return
|
||||
}
|
||||
|
||||
//Validate the CIDR is real, the ip range is within the CIDR range
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid cidr string given")
|
||||
return
|
||||
}
|
||||
|
||||
startIP := net.ParseIP(ipstart)
|
||||
endIP := net.ParseIP(ipend)
|
||||
if startIP == nil || endIP == nil {
|
||||
utils.SendErrorResponse(w, "invalid start or end ip given")
|
||||
return
|
||||
}
|
||||
|
||||
withinRange := ipnet.Contains(startIP) && ipnet.Contains(endIP)
|
||||
if !withinRange {
|
||||
utils.SendErrorResponse(w, "given CIDR did not cover all of the start to end ip range")
|
||||
return
|
||||
}
|
||||
|
||||
err = m.configureNetwork(netid, startIP.String(), endIP.String(), strings.TrimSpace(cidr))
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Handle listing of network members. Set details=true for listing all details
|
||||
func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.GetPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "netid is empty")
|
||||
return
|
||||
}
|
||||
|
||||
details, _ := utils.GetPara(r, "detail")
|
||||
|
||||
memberIds, err := m.getNetworkMembers(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
if details == "" {
|
||||
//Only show client ids
|
||||
js, _ := json.Marshal(memberIds)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
//Show detail members info
|
||||
detailMemberInfo := []*MemberInfo{}
|
||||
for _, thisMemberId := range memberIds {
|
||||
memInfo, err := m.getNetworkMemberInfo(netid, thisMemberId)
|
||||
if err == nil {
|
||||
detailMemberInfo = append(detailMemberInfo, memInfo)
|
||||
}
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(detailMemberInfo)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Authorization of members
|
||||
func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
memberid, err := utils.PostPara(r, "memid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "memid not set")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if the target memeber exists
|
||||
if !m.memberExistsInNetwork(netid, memberid) {
|
||||
utils.SendErrorResponse(w, "member not exists in given network")
|
||||
return
|
||||
}
|
||||
|
||||
setAuthorized, err := utils.PostPara(r, "auth")
|
||||
if err != nil || setAuthorized == "" {
|
||||
//Get the member authorization state
|
||||
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(memberInfo.Authorized)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else if setAuthorized == "true" {
|
||||
m.AuthorizeMember(netid, memberid, true)
|
||||
} else if setAuthorized == "false" {
|
||||
m.AuthorizeMember(netid, memberid, false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "unknown operation state: "+setAuthorized)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Delete or Add IP for a member in a network
|
||||
func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
memberid, err := utils.PostPara(r, "memid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "memid not set")
|
||||
return
|
||||
}
|
||||
|
||||
opr, err := utils.PostPara(r, "opr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "opr not defined")
|
||||
return
|
||||
}
|
||||
|
||||
targetip, _ := utils.PostPara(r, "ip")
|
||||
|
||||
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if opr == "add" {
|
||||
if targetip == "" {
|
||||
utils.SendErrorResponse(w, "ip not set")
|
||||
return
|
||||
}
|
||||
|
||||
if !isValidIPAddr(targetip) {
|
||||
utils.SendErrorResponse(w, "ip address not valid")
|
||||
return
|
||||
}
|
||||
|
||||
newIpList := append(memberInfo.IPAssignments, targetip)
|
||||
err = m.setAssignedIps(netid, memberid, newIpList)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
|
||||
} else if opr == "del" {
|
||||
if targetip == "" {
|
||||
utils.SendErrorResponse(w, "ip not set")
|
||||
return
|
||||
}
|
||||
|
||||
//Delete user ip from the list
|
||||
newIpList := []string{}
|
||||
for _, thisIp := range memberInfo.IPAssignments {
|
||||
if thisIp != targetip {
|
||||
newIpList = append(newIpList, thisIp)
|
||||
}
|
||||
}
|
||||
|
||||
err = m.setAssignedIps(netid, memberid, newIpList)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
} else if opr == "get" {
|
||||
js, _ := json.Marshal(memberInfo.IPAssignments)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "unsupported opr type: "+opr)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle naming for members
|
||||
func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
memberid, err := utils.PostPara(r, "memid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "memid not set")
|
||||
return
|
||||
}
|
||||
|
||||
if !m.memberExistsInNetwork(netid, memberid) {
|
||||
utils.SendErrorResponse(w, "target member not exists in given network")
|
||||
return
|
||||
}
|
||||
|
||||
//Read memeber data
|
||||
targetMemberData := m.GetMemberMetaData(netid, memberid)
|
||||
|
||||
newname, err := utils.PostPara(r, "name")
|
||||
if err != nil {
|
||||
//Send over the member data
|
||||
js, _ := json.Marshal(targetMemberData)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
//Write member data
|
||||
targetMemberData.Name = newname
|
||||
m.WriteMemeberMetaData(netid, memberid, targetMemberData)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle delete of a given memver
|
||||
func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
memberid, err := utils.PostPara(r, "memid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "memid not set")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if that member is authorized.
|
||||
memberInfo, err := m.getNetworkMemberInfo(netid, memberid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "member not exists in given GANet")
|
||||
return
|
||||
}
|
||||
|
||||
if memberInfo.Authorized {
|
||||
//Deauthorized this member before deleting
|
||||
m.AuthorizeMember(netid, memberid, false)
|
||||
}
|
||||
|
||||
//Remove the memeber
|
||||
err = m.deleteMember(netid, memberid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Check if a given network id is a network hosted on this zoraxy node
|
||||
func (m *NetworkManager) IsLocalGAN(networkId string) bool {
|
||||
networks, err := m.listNetworkIds()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, network := range networks {
|
||||
if network == networkId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Handle server instant joining a given network
|
||||
func (m *NetworkManager) HandleServerJoinNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if the target network is a network hosted on this server
|
||||
if !m.IsLocalGAN(netid) {
|
||||
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
|
||||
return
|
||||
}
|
||||
|
||||
if m.memberExistsInNetwork(netid, m.ControllerID) {
|
||||
utils.SendErrorResponse(w, "controller already inside network")
|
||||
return
|
||||
}
|
||||
|
||||
//Join the network
|
||||
err = m.joinNetwork(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Handle server instant leaving a given network
|
||||
func (m *NetworkManager) HandleServerLeaveNetwork(w http.ResponseWriter, r *http.Request) {
|
||||
netid, err := utils.PostPara(r, "netid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "net id not set")
|
||||
return
|
||||
}
|
||||
|
||||
//Check if the target network is a network hosted on this server
|
||||
if !m.IsLocalGAN(netid) {
|
||||
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
|
||||
return
|
||||
}
|
||||
|
||||
//Leave the network
|
||||
err = m.leaveNetwork(netid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Remove it from target network if it is authorized
|
||||
err = m.deleteMember(netid, m.ControllerID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
//Get a random free IP from the pool
|
||||
func (n *Network) GetRandomFreeIP() (net.IP, error) {
|
||||
// Get all IP addresses in the subnet
|
||||
ips, err := GetAllAddressFromCIDR(n.CIDR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter out used IPs
|
||||
usedIPs := make(map[string]bool)
|
||||
for _, node := range n.Nodes {
|
||||
usedIPs[node.ManagedIP.String()] = true
|
||||
}
|
||||
availableIPs := []string{}
|
||||
for _, ip := range ips {
|
||||
if !usedIPs[ip] {
|
||||
availableIPs = append(availableIPs, ip)
|
||||
}
|
||||
}
|
||||
|
||||
// Randomly choose an available IP
|
||||
if len(availableIPs) == 0 {
|
||||
return nil, fmt.Errorf("no available IP")
|
||||
}
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
randIndex := rand.Intn(len(availableIPs))
|
||||
pickedFreeIP := availableIPs[randIndex]
|
||||
|
||||
return net.ParseIP(pickedFreeIP), nil
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package ganserv_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"imuslab.com/zoraxy/mod/ganserv"
|
||||
)
|
||||
|
||||
func TestGetRandomFreeIP(t *testing.T) {
|
||||
n := ganserv.Network{
|
||||
CIDR: "172.16.0.0/12",
|
||||
Nodes: []*ganserv.Node{
|
||||
{
|
||||
Name: "nodeC1",
|
||||
ManagedIP: net.ParseIP("172.16.1.142"),
|
||||
},
|
||||
{
|
||||
Name: "nodeC2",
|
||||
ManagedIP: net.ParseIP("172.16.5.174"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Call the function for 10 times
|
||||
for i := 0; i < 10; i++ {
|
||||
freeIP, err := n.GetRandomFreeIP()
|
||||
fmt.Println("["+strconv.Itoa(i)+"] Free IP address assigned: ", freeIP)
|
||||
|
||||
// Assert that no error occurred
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %s", err.Error())
|
||||
}
|
||||
|
||||
// Assert that the returned IP is a valid IPv4 address
|
||||
if freeIP.To4() == nil {
|
||||
t.Errorf("Invalid IP address format: %s", freeIP.String())
|
||||
}
|
||||
|
||||
// Assert that the returned IP is not already used by a node
|
||||
for _, node := range n.Nodes {
|
||||
if freeIP.Equal(node.ManagedIP) {
|
||||
t.Errorf("Returned IP is already in use: %s", freeIP.String())
|
||||
}
|
||||
}
|
||||
|
||||
n.Nodes = append(n.Nodes, &ganserv.Node{
|
||||
Name: "NodeT" + strconv.Itoa(i),
|
||||
ManagedIP: freeIP,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
//Generate all ip address from a CIDR
|
||||
func GetAllAddressFromCIDR(cidr string) ([]string, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ips []string
|
||||
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
|
||||
ips = append(ips, ip.String())
|
||||
}
|
||||
// remove network address and broadcast address
|
||||
return ips[1 : len(ips)-1], nil
|
||||
}
|
||||
|
||||
func inc(ip net.IP) {
|
||||
for j := len(ip) - 1; j >= 0; j-- {
|
||||
ip[j]++
|
||||
if ip[j] > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidIPAddr(ipAddr string) bool {
|
||||
ip := net.ParseIP(ipAddr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func ipWithinCIDR(ipAddr string, cidr string) bool {
|
||||
// Parse the CIDR string
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the IP address
|
||||
ip := net.ParseIP(ipAddr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if the IP address is in the CIDR range
|
||||
return ipNet.Contains(ip)
|
||||
}
|
@ -1,669 +0,0 @@
|
||||
package ganserv
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
zerotier.go
|
||||
|
||||
This hold the functions that required to communicate with
|
||||
a zerotier instance
|
||||
|
||||
See more on
|
||||
https://docs.zerotier.com/self-hosting/network-controllers/
|
||||
|
||||
*/
|
||||
|
||||
type NodeInfo struct {
|
||||
Address string `json:"address"`
|
||||
Clock int64 `json:"clock"`
|
||||
Config struct {
|
||||
Settings struct {
|
||||
AllowTCPFallbackRelay bool `json:"allowTcpFallbackRelay,omitempty"`
|
||||
ForceTCPRelay bool `json:"forceTcpRelay,omitempty"`
|
||||
HomeDir string `json:"homeDir,omitempty"`
|
||||
ListeningOn []string `json:"listeningOn,omitempty"`
|
||||
PortMappingEnabled bool `json:"portMappingEnabled,omitempty"`
|
||||
PrimaryPort int `json:"primaryPort,omitempty"`
|
||||
SecondaryPort int `json:"secondaryPort,omitempty"`
|
||||
SoftwareUpdate string `json:"softwareUpdate,omitempty"`
|
||||
SoftwareUpdateChannel string `json:"softwareUpdateChannel,omitempty"`
|
||||
SurfaceAddresses []string `json:"surfaceAddresses,omitempty"`
|
||||
TertiaryPort int `json:"tertiaryPort,omitempty"`
|
||||
} `json:"settings"`
|
||||
} `json:"config"`
|
||||
Online bool `json:"online"`
|
||||
PlanetWorldID int `json:"planetWorldId"`
|
||||
PlanetWorldTimestamp int64 `json:"planetWorldTimestamp"`
|
||||
PublicIdentity string `json:"publicIdentity"`
|
||||
TCPFallbackActive bool `json:"tcpFallbackActive"`
|
||||
Version string `json:"version"`
|
||||
VersionBuild int `json:"versionBuild"`
|
||||
VersionMajor int `json:"versionMajor"`
|
||||
VersionMinor int `json:"versionMinor"`
|
||||
VersionRev int `json:"versionRev"`
|
||||
}
|
||||
type ErrResp struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type NetworkInfo struct {
|
||||
AuthTokens []interface{} `json:"authTokens"`
|
||||
AuthorizationEndpoint string `json:"authorizationEndpoint"`
|
||||
Capabilities []interface{} `json:"capabilities"`
|
||||
ClientID string `json:"clientId"`
|
||||
CreationTime int64 `json:"creationTime"`
|
||||
DNS []interface{} `json:"dns"`
|
||||
EnableBroadcast bool `json:"enableBroadcast"`
|
||||
ID string `json:"id"`
|
||||
IPAssignmentPools []interface{} `json:"ipAssignmentPools"`
|
||||
Mtu int `json:"mtu"`
|
||||
MulticastLimit int `json:"multicastLimit"`
|
||||
Name string `json:"name"`
|
||||
Nwid string `json:"nwid"`
|
||||
Objtype string `json:"objtype"`
|
||||
Private bool `json:"private"`
|
||||
RemoteTraceLevel int `json:"remoteTraceLevel"`
|
||||
RemoteTraceTarget interface{} `json:"remoteTraceTarget"`
|
||||
Revision int `json:"revision"`
|
||||
Routes []interface{} `json:"routes"`
|
||||
Rules []struct {
|
||||
Not bool `json:"not"`
|
||||
Or bool `json:"or"`
|
||||
Type string `json:"type"`
|
||||
} `json:"rules"`
|
||||
RulesSource string `json:"rulesSource"`
|
||||
SsoEnabled bool `json:"ssoEnabled"`
|
||||
Tags []interface{} `json:"tags"`
|
||||
V4AssignMode struct {
|
||||
Zt bool `json:"zt"`
|
||||
} `json:"v4AssignMode"`
|
||||
V6AssignMode struct {
|
||||
SixPlane bool `json:"6plane"`
|
||||
Rfc4193 bool `json:"rfc4193"`
|
||||
Zt bool `json:"zt"`
|
||||
} `json:"v6AssignMode"`
|
||||
}
|
||||
|
||||
type MemberInfo struct {
|
||||
ActiveBridge bool `json:"activeBridge"`
|
||||
Address string `json:"address"`
|
||||
AuthenticationExpiryTime int `json:"authenticationExpiryTime"`
|
||||
Authorized bool `json:"authorized"`
|
||||
Capabilities []interface{} `json:"capabilities"`
|
||||
CreationTime int64 `json:"creationTime"`
|
||||
ID string `json:"id"`
|
||||
Identity string `json:"identity"`
|
||||
IPAssignments []string `json:"ipAssignments"`
|
||||
LastAuthorizedCredential interface{} `json:"lastAuthorizedCredential"`
|
||||
LastAuthorizedCredentialType string `json:"lastAuthorizedCredentialType"`
|
||||
LastAuthorizedTime int `json:"lastAuthorizedTime"`
|
||||
LastDeauthorizedTime int `json:"lastDeauthorizedTime"`
|
||||
NoAutoAssignIps bool `json:"noAutoAssignIps"`
|
||||
Nwid string `json:"nwid"`
|
||||
Objtype string `json:"objtype"`
|
||||
RemoteTraceLevel int `json:"remoteTraceLevel"`
|
||||
RemoteTraceTarget interface{} `json:"remoteTraceTarget"`
|
||||
Revision int `json:"revision"`
|
||||
SsoExempt bool `json:"ssoExempt"`
|
||||
Tags []interface{} `json:"tags"`
|
||||
VMajor int `json:"vMajor"`
|
||||
VMinor int `json:"vMinor"`
|
||||
VProto int `json:"vProto"`
|
||||
VRev int `json:"vRev"`
|
||||
}
|
||||
|
||||
// Get the zerotier node info from local service
|
||||
func getControllerInfo(token string, apiPort int) (*NodeInfo, error) {
|
||||
url := "http://localhost:" + strconv.Itoa(apiPort) + "/status"
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-ZT1-AUTH", token)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Read from zerotier service instance
|
||||
|
||||
defer resp.Body.Close()
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Parse the payload into struct
|
||||
thisInstanceInfo := NodeInfo{}
|
||||
err = json.Unmarshal(payload, &thisInstanceInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &thisInstanceInfo, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Network Functions
|
||||
*/
|
||||
//Create a zerotier network
|
||||
func (m *NetworkManager) createNetwork() (*NetworkInfo, error) {
|
||||
url := fmt.Sprintf("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/%s______", m.ControllerID)
|
||||
|
||||
data := []byte(`{}`)
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networkInfo := NetworkInfo{}
|
||||
err = json.Unmarshal(payload, &networkInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &networkInfo, nil
|
||||
}
|
||||
|
||||
// List network details
|
||||
func (m *NetworkManager) getNetworkInfoById(networkId string) (*NetworkInfo, error) {
|
||||
req, err := http.NewRequest("GET", os.ExpandEnv("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+networkId+"/"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", m.authToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil, errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
thisNetworkInfo := NetworkInfo{}
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, &thisNetworkInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &thisNetworkInfo, nil
|
||||
}
|
||||
|
||||
func (m *NetworkManager) setNetworkInfoByID(networkId string, newNetworkInfo *NetworkInfo) error {
|
||||
payloadBytes, err := json.Marshal(newNetworkInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payloadBuffer := bytes.NewBuffer(payloadBytes)
|
||||
|
||||
// Create the HTTP request
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkId + "/"
|
||||
req, err := http.NewRequest("POST", url, payloadBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", m.authToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Send the HTTP request
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Print the response status code
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List network IDs
|
||||
func (m *NetworkManager) listNetworkIds() ([]string, error) {
|
||||
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/", nil)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", m.authToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return []string{}, errors.New("network error")
|
||||
}
|
||||
|
||||
networkIds := []string{}
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, &networkIds)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
return networkIds, nil
|
||||
}
|
||||
|
||||
// wrapper for checking if a network id exists
|
||||
func (m *NetworkManager) networkExists(networkId string) bool {
|
||||
networkIds, err := m.listNetworkIds()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, thisid := range networkIds {
|
||||
if thisid == networkId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// delete a network
|
||||
func (m *NetworkManager) deleteNetwork(networkID string) error {
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
|
||||
client := &http.Client{}
|
||||
|
||||
// Create a new DELETE request
|
||||
req, err := http.NewRequest("DELETE", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the required authorization header
|
||||
req.Header.Set("X-Zt1-Auth", m.authToken)
|
||||
|
||||
// Send the request and get the response
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Close the response body when we're done
|
||||
defer resp.Body.Close()
|
||||
s, err := io.ReadAll(resp.Body)
|
||||
fmt.Println(string(s), err, resp.StatusCode)
|
||||
|
||||
// Print the response status code
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure network
|
||||
// Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
|
||||
func (m *NetworkManager) configureNetwork(networkID string, ipRangeStart string, ipRangeEnd string, routeTarget string) error {
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
|
||||
data := map[string]interface{}{
|
||||
"ipAssignmentPools": []map[string]string{
|
||||
{
|
||||
"ipRangeStart": ipRangeStart,
|
||||
"ipRangeEnd": ipRangeEnd,
|
||||
},
|
||||
},
|
||||
"routes": []map[string]interface{}{
|
||||
{
|
||||
"target": routeTarget,
|
||||
"via": nil,
|
||||
},
|
||||
},
|
||||
"v4AssignMode": "zt",
|
||||
"private": true,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
// Print the response status code
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NetworkManager) setAssignedIps(networkID string, memid string, newIps []string) error {
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/member/" + memid
|
||||
data := map[string]interface{}{
|
||||
"ipAssignments": newIps,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
// Print the response status code
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NetworkManager) setNetworkNameAndDescription(netid string, name string, desc string) error {
|
||||
// Convert string to rune slice
|
||||
r := []rune(name)
|
||||
|
||||
// Loop over runes and remove non-ASCII characters
|
||||
for i, v := range r {
|
||||
if v > 127 {
|
||||
r[i] = ' '
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back to string and trim whitespace
|
||||
name = strings.TrimSpace(string(r))
|
||||
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/"
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
// Print the response status code
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
meta := m.GetNetworkMetaData(netid)
|
||||
if meta != nil {
|
||||
meta.Desc = desc
|
||||
m.WriteNetworkMetaData(netid, meta)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *NetworkManager) getNetworkNameAndDescription(netid string) (string, string, error) {
|
||||
//Get name from network info
|
||||
netinfo, err := m.getNetworkInfoById(netid)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
name := netinfo.Name
|
||||
|
||||
//Get description from meta
|
||||
desc := ""
|
||||
networkMeta := m.GetNetworkMetaData(netid)
|
||||
if networkMeta != nil {
|
||||
desc = networkMeta.Desc
|
||||
}
|
||||
|
||||
return name, desc, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Member functions
|
||||
*/
|
||||
|
||||
func (m *NetworkManager) getNetworkMembers(networkId string) ([]string, error) {
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkId + "/member"
|
||||
reqBody := bytes.NewBuffer([]byte{})
|
||||
req, err := http.NewRequest("GET", url, reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("failed to get network members")
|
||||
}
|
||||
|
||||
memberList := map[string]int{}
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, &memberList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
members := make([]string, 0, len(memberList))
|
||||
for k := range memberList {
|
||||
members = append(members, k)
|
||||
}
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (m *NetworkManager) memberExistsInNetwork(netid string, memid string) bool {
|
||||
//Get a list of member
|
||||
memberids, err := m.getNetworkMembers(netid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, thisMemberId := range memberids {
|
||||
if thisMemberId == memid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Get a network memeber info by netid and memberid
|
||||
func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*MemberInfo, error) {
|
||||
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memberid, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", m.authToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
thisMemeberInfo := &MemberInfo{}
|
||||
payload, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(payload, &thisMemeberInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return thisMemeberInfo, nil
|
||||
}
|
||||
|
||||
// Set the authorization state of a member
|
||||
func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAuthorized bool) error {
|
||||
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/member/" + memberid
|
||||
payload := []byte(`{"authorized": true}`)
|
||||
if !setAuthorized {
|
||||
payload = []byte(`{"authorized": false}`)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-ZT1-AUTH", m.authToken)
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete a member from the network
|
||||
func (m *NetworkManager) deleteMember(netid string, memid string) error {
|
||||
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memid, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the host to join a given network
|
||||
func (m *NetworkManager) joinNetwork(netid string) error {
|
||||
req, err := http.NewRequest("POST", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make the host to leave a given network
|
||||
func (m *NetworkManager) leaveNetwork(netid string) error {
|
||||
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
26
src/start.go
26
src/start.go
@ -20,7 +20,6 @@ import (
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
|
||||
"imuslab.com/zoraxy/mod/forwardproxy"
|
||||
"imuslab.com/zoraxy/mod/ganserv"
|
||||
"imuslab.com/zoraxy/mod/geodb"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/info/logviewer"
|
||||
@ -156,6 +155,7 @@ func startupSequence() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
statisticCollector.SetAutoSave(STATISTIC_AUTO_SAVE_INTERVAL)
|
||||
|
||||
//Start the static web server
|
||||
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
||||
@ -247,24 +247,6 @@ func startupSequence() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Global Area Network
|
||||
|
||||
Require zerotier token to work
|
||||
*/
|
||||
usingZtAuthToken := *ztAuthToken
|
||||
if usingZtAuthToken == "" {
|
||||
usingZtAuthToken, err = ganserv.TryLoadorAskUserForAuthkey()
|
||||
if err != nil {
|
||||
SystemWideLogger.Println("Failed to load ZeroTier controller API authtoken")
|
||||
}
|
||||
}
|
||||
ganManager = ganserv.NewNetworkManager(&ganserv.NetworkManagerOptions{
|
||||
AuthToken: usingZtAuthToken,
|
||||
ApiPort: *ztAPIPort,
|
||||
Database: sysdb,
|
||||
})
|
||||
|
||||
//Create WebSSH Manager
|
||||
webSshManager = sshprox.NewSSHProxyManager()
|
||||
|
||||
@ -332,6 +314,12 @@ func startupSequence() {
|
||||
},
|
||||
Database: sysdb,
|
||||
Logger: SystemWideLogger,
|
||||
//TODO: REMOVE AFTER DEBUG
|
||||
PluginGroups: map[string][]string{
|
||||
"debug": {
|
||||
"org.aroz.zoraxy.debugger",
|
||||
},
|
||||
},
|
||||
CSRFTokenGen: func(r *http.Request) string {
|
||||
return csrf.Token(r)
|
||||
},
|
||||
|
@ -1,231 +0,0 @@
|
||||
<div id="ganetWindow" class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Global Area Network</h2>
|
||||
<p>Virtual Network Hub that allows all networked devices to communicate as if they all reside in the same physical data center or cloud region</p>
|
||||
</div>
|
||||
<div class="ui yellow message">
|
||||
<b>Deprecation Notice</b>
|
||||
<p>Global Area Network will be deprecating in v3.2.x and moved to Plugin</p>
|
||||
</div>
|
||||
<div class="gansnetworks">
|
||||
<div class="ganstats ui basic segment">
|
||||
<div style="float: right; max-width: 300px; margin-top: 0.4em;">
|
||||
<h1 class="ui header" style="text-align: right;">
|
||||
<span class="ganControllerID"></span>
|
||||
<div class="sub header">Network Controller ID</div>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="ui list">
|
||||
<div class="item">
|
||||
<i class="exchange icon"></i>
|
||||
<div class="content">
|
||||
<div class="header" style="font-size: 1.2em;" id="ganetCount">0</div>
|
||||
<div class="description">Networks</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<i class="desktop icon"></i>
|
||||
<div class="content">
|
||||
<div class="header" style="font-size: 1.2em;" id="ganodeCount">0</div>
|
||||
<div class="description" id="connectedNodes" count="0">Connected Nodes</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ganlist">
|
||||
<button class="ui basic orange button" onclick="addGANet();">Create New Network</button>
|
||||
<div class="ui divider"></div>
|
||||
<!--
|
||||
<div class="ui icon input" style="margin-bottom: 1em;">
|
||||
<input type="text" placeholder="Search a Network">
|
||||
<i class="circular search link icon"></i>
|
||||
</div>-->
|
||||
<div style="width: 100%; overflow-x: auto;">
|
||||
<table class="ui celled basic unstackable striped table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Network ID</th>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Subnet (Assign Range)</th>
|
||||
<th>Nodes</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="GANetList">
|
||||
<tr>
|
||||
<td colspan="6"><i class="ui green circle check icon"></i> No Global Area Network Found on this host</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/*
|
||||
Network Management Functions
|
||||
*/
|
||||
function handleAddNetwork(){
|
||||
let networkName = $("#networkName").val().trim();
|
||||
if (networkName == ""){
|
||||
msgbox("Network name cannot be empty", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
//Add network with default settings
|
||||
addGANet(networkName, "192.168.196.0/24");
|
||||
$("#networkName").val("");
|
||||
}
|
||||
|
||||
function initGANetID(){
|
||||
$.get("/api/gan/network/info", function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 5000)
|
||||
}else{
|
||||
if (data != ""){
|
||||
$(".ganControllerID").text(data);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addGANet() {
|
||||
$.cjax({
|
||||
url: "/api/gan/network/add",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {},
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
msgbox(response.error, false, 5000);
|
||||
}else{
|
||||
msgbox("Network added successfully");
|
||||
}
|
||||
console.log("Network added successfully:", response);
|
||||
listGANet();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log("Error adding network:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listGANet(){
|
||||
$("#connectedNodes").attr("count", "0");
|
||||
|
||||
$.get("/api/gan/network/list", function(data){
|
||||
$("#GANetList").empty();
|
||||
if (data.error != undefined){
|
||||
console.log(data.error);
|
||||
msgbox("Unable to load auth token for GANet", false, 5000);
|
||||
//token error or no zerotier found
|
||||
$(".gansnetworks").addClass("disabled");
|
||||
$("#GANetList").append(`<tr>
|
||||
<td colspan="6"><i class="red times circle icon"></i> Auth token access error or not found</td>
|
||||
</tr>`);
|
||||
$(".ganControllerID").text('Access Denied');
|
||||
}else{
|
||||
var nodeCount = 0;
|
||||
data.forEach(function(gan){
|
||||
$("#GANetList").append(`<tr class="ganetEntry" addr="${gan.nwid}">
|
||||
<td><a href="#" onclick="event.preventDefault(); openGANetDetails('${gan.nwid}');">${gan.nwid}</a></td>
|
||||
<td>${gan.name}</td>
|
||||
<td class="gandesc" addr="${gan.nwid}"></td>
|
||||
<td class="ganetSubnet"></td>
|
||||
<td class="ganetNodes"></td>
|
||||
<td>
|
||||
<button onclick="openGANetDetails('${gan.nwid}');" class="ui tiny basic icon button" title="Edit Network"><i class="edit icon"></i></button>
|
||||
<button onclick="removeGANet('${gan.nwid}');" class="ui tiny basic icon button" title="Remove Network"><i class="red remove icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
|
||||
nodeCount += 0;
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#GANetList").append(`<tr>
|
||||
<td colspan="6"><i class="ui green circle check icon"></i> No Global Area Network Found on this host</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
$("#ganodeCount").text(nodeCount);
|
||||
$("#ganetCount").text(data.length);
|
||||
|
||||
//Load description
|
||||
$(".gandesc").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let domEle = $(this);
|
||||
$.get("/api/gan/network/name?netid=" + addr, function(data){
|
||||
$(domEle).text(data[1]);
|
||||
});
|
||||
});
|
||||
|
||||
$(".ganetEntry").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let subnetEle = $(this).find(".ganetSubnet");
|
||||
let nodeEle = $(this).find(".ganetNodes");
|
||||
|
||||
$.get("/api/gan/network/list?netid=" + addr, function(data){
|
||||
if (data.routes != undefined && data.routes.length > 0){
|
||||
|
||||
if (data.ipAssignmentPools != undefined && data.ipAssignmentPools.length > 0){
|
||||
$(subnetEle).html(`${data.routes[0].target} <br> (${data.ipAssignmentPools[0].ipRangeStart} - ${data.ipAssignmentPools[0].ipRangeEnd})`);
|
||||
}else{
|
||||
$(subnetEle).html(`${data.routes[0].target}<br>(Unassigned Range)`);
|
||||
}
|
||||
}else{
|
||||
$(subnetEle).text("Unassigned");
|
||||
}
|
||||
//console.log(data);
|
||||
});
|
||||
|
||||
$.get("/api/gan/members/list?netid=" + addr, function(data){
|
||||
$(nodeEle).text(data.length);
|
||||
let currentNodesCount = parseInt($("#connectedNodes").attr("count"));
|
||||
currentNodesCount += data.length;
|
||||
$("#connectedNodes").attr("count", currentNodesCount);
|
||||
$("#ganodeCount").text($("#connectedNodes").attr("count"));
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Remove the given GANet
|
||||
function removeGANet(netid){
|
||||
if (confirm("Confirm remove Network " + netid + " PERMANENTLY ?"))
|
||||
$.cjax({
|
||||
url: "/api/gan/network/remove",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
id: netid,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("Net " + netid + " removed");
|
||||
}
|
||||
listGANet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openGANetDetails(netid){
|
||||
$("#ganetWindow").load("./components/gandetails.html", function(){
|
||||
setTimeout(function(){
|
||||
initGanetDetails(netid);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//Bind event to tab switch
|
||||
tabSwitchEventBind["gan"] = function(){
|
||||
//On switch over to this page, load info
|
||||
listGANet();
|
||||
initGANetID();
|
||||
}
|
||||
</script>
|
@ -1,687 +0,0 @@
|
||||
<div class="standardContainer">
|
||||
<button onclick="exitToGanList();" class="ui large circular black icon button"><i class="angle left icon"></i></button>
|
||||
<div style="max-width: 300px; margin-top: 1em;">
|
||||
<button onclick='$("#gannetDetailEdit").slideToggle("fast");' class="ui mini basic right floated circular icon button" style="display: inline-block; margin-top: 2.5em;"><i class="ui edit icon"></i></button>
|
||||
<h1 class="ui header">
|
||||
<span class="ganetID"></span>
|
||||
<div class="sub header ganetName"></div>
|
||||
</h1>
|
||||
<div class="ui divider"></div>
|
||||
<p><span class="ganetDesc"></span></p>
|
||||
|
||||
</div>
|
||||
<div id="gannetDetailEdit" class="ui form" style="margin-top: 1em; display:none;">
|
||||
<div class="ui divider"></div>
|
||||
<p>You can change the network name and description below. <br>The name and description is only for easy management purpose and will not effect the network operation.</p>
|
||||
<div class="field">
|
||||
<label>Network Name</label>
|
||||
<input type="text" id="gaNetNameInput" placeholder="">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Network Description</label>
|
||||
<textarea id="gaNetDescInput" style="resize: none;"></textarea>
|
||||
<button onclick="saveNameAndDesc(this);" class="ui basic right floated button" style="margin-top: 0.6em;"><i class="ui save icon"></i> Save</button>
|
||||
<button onclick='$("#gannetDetailEdit").slideUp("fast");' class="ui basic right floated button" style="margin-top: 0.6em;"><i class="ui red remove icon"></i> Cancel</button>
|
||||
</div>
|
||||
<br><br>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h2>Settings</h2>
|
||||
<div class="" style="overflow-x: auto;">
|
||||
<table class="ui basic celled unstackable table" style="min-width: 560px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4">IPv4 Auto-Assign</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="ganetRangeTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui form">
|
||||
<h3>Custom IP Range</h3>
|
||||
<p>Manual IP Range Configuration. The IP range must be within the selected CIDR range.
|
||||
<br>Use <code>Utilities > IP to CIDR tool</code> if you are not too familiar with CIDR notations.</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>IP Start</label>
|
||||
<input type="text" class="ganIpStart" placeholder="">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>IP End</label>
|
||||
<input type="text" class="ganIpEnd" placeholder="">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="setNetworkRange();" class="ui basic button"><i class="ui blue save icon"></i> Save Settings</button>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h2>Members</h2>
|
||||
<p>To join this network using command line, type <code>sudo zerotier-cli join <span class="ganetID"></span></code> on your device terminal</p>
|
||||
<div class="ui checkbox" style="margin-bottom: 1em;">
|
||||
<input id="showUnauthorizedMembers" type="checkbox" onchange="changeUnauthorizedVisibility(this.checked);" checked>
|
||||
<label>Show Unauthorized Members</label>
|
||||
</div>
|
||||
<div class="" style="overflow-x: auto;">
|
||||
<table class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Auth</th>
|
||||
<th>Address</th>
|
||||
<th>Name</th>
|
||||
<th>Managed IP</th>
|
||||
<th>Authorized Since</th>
|
||||
<th>Version</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="networkMemeberTable">
|
||||
<tr>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Add Controller as Member</h4>
|
||||
<p>Optionally you can add the network controller (ZeroTier running on the Zoraxy node) as member for cross GAN reverse proxy to bypass NAT limitations.</p>
|
||||
<button class="ui basic small button addControllerToNetworkBtn" onclick="ganAddControllerToNetwork(this);"><i class="green add icon"></i> Add Controller as Member</button>
|
||||
<button class="ui basic small button removeControllerFromNetworkBtn" onclick="ganRemoveControllerFromNetwork(this);"><i class="red sign-out icon"></i> Remove Controller from Member</button>
|
||||
<br><br>
|
||||
</div>
|
||||
<script>
|
||||
$(".checkbox").checkbox();
|
||||
var currentGANetID = "";
|
||||
var currentGANNetMemeberListener = undefined;
|
||||
var currentGaNetDetails = {};
|
||||
var currentGANMemberList = [];
|
||||
var netRanges = {
|
||||
"10.147.17.*": "10.147.17.0/24",
|
||||
"10.147.18.*": "10.147.18.0/24",
|
||||
"10.147.19.*": "10.147.19.0/24",
|
||||
"10.147.20.*": "10.147.20.0/24",
|
||||
"10.144.*.*": "10.144.0.0/16",
|
||||
"10.241.*.*": "10.241.0.0/16",
|
||||
"10.242.*.*": "10.242.0.0/16",
|
||||
"10.243.*.*": "10.243.0.0/16",
|
||||
"10.244.*.*": "10.244.0.0/16",
|
||||
"172.22.*.*": "172.22.0.0/15",
|
||||
"172.23.*.*": "172.23.0.0/16",
|
||||
"172.24.*.*": "172.24.0.0/14",
|
||||
"172.25.*.*": "172.25.0.0/16",
|
||||
"172.26.*.*": "172.26.0.0/15",
|
||||
"172.27.*.*": "172.27.0.0/16",
|
||||
"172.28.*.*": "172.28.0.0/15",
|
||||
"172.29.*.*": "172.29.0.0/16",
|
||||
"172.30.*.*": "172.30.0.0/15",
|
||||
"192.168.191.*": "192.168.191.0/24",
|
||||
"192.168.192.*": "192.168.192.0/24",
|
||||
"192.168.193.*": "192.168.193.0/24",
|
||||
"192.168.194.*": "192.168.194.0/24",
|
||||
"192.168.195.*": "192.168.195.0/24",
|
||||
"192.168.196.*": "192.168.196.0/24"
|
||||
}
|
||||
|
||||
function generateIPRangeTable(netRanges) {
|
||||
$("#ganetRangeTable").empty();
|
||||
const tableBody = document.getElementById('ganetRangeTable');
|
||||
const cidrs = Object.values(netRanges);
|
||||
|
||||
// Set the number of rows and columns to display in the table
|
||||
const numRows = 6;
|
||||
const numCols = 4;
|
||||
|
||||
let row = document.createElement('tr');
|
||||
let col = 0;
|
||||
for (let i = 0; i < cidrs.length; i++) {
|
||||
if (col >= numCols) {
|
||||
tableBody.appendChild(row);
|
||||
row = document.createElement('tr');
|
||||
col = 0;
|
||||
}
|
||||
|
||||
const td = document.createElement('td');
|
||||
td.setAttribute('class', `clickable iprange`);
|
||||
td.setAttribute('CIDR', cidrs[i]);
|
||||
td.innerHTML = cidrs[i];
|
||||
let thisCidr = cidrs[i];
|
||||
td.onclick = function(){
|
||||
selectNetworkRange(thisCidr, td);
|
||||
};
|
||||
|
||||
row.appendChild(td);
|
||||
col++;
|
||||
}
|
||||
|
||||
// Add any remaining cells to the table
|
||||
if (col > 0) {
|
||||
for (let i = col; i < numCols; i++) {
|
||||
row.appendChild(document.createElement('td'));
|
||||
}
|
||||
tableBody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function highlightCurrentGANetCIDR(){
|
||||
var currentCIDR = currentGaNetDetails.routes[0].target;
|
||||
$(".iprange").each(function(){
|
||||
if ($(this).attr("CIDR") == currentCIDR){
|
||||
$(this).addClass("active");
|
||||
populateStartEndIpByCidr(currentCIDR);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function populateStartEndIpByCidr(cidr){
|
||||
function cidrToRange(cidr) {
|
||||
var range = [2];
|
||||
cidr = cidr.split('/');
|
||||
var start = ip2long(cidr[0]);
|
||||
range[0] = long2ip(start);
|
||||
range[1] = long2ip(Math.pow(2, 32 - cidr[1]) + start - 1);
|
||||
return range;
|
||||
}
|
||||
var cidrRange = cidrToRange(cidr);
|
||||
$(".ganIpStart").val(cidrRange[0]);
|
||||
$(".ganIpEnd").val(cidrRange[1]);
|
||||
}
|
||||
|
||||
function selectNetworkRange(cidr, object){
|
||||
populateStartEndIpByCidr(cidr);
|
||||
|
||||
$(".iprange.active").removeClass("active");
|
||||
$(object).addClass("active");
|
||||
}
|
||||
|
||||
function setNetworkRange(){
|
||||
var ipstart = $(".ganIpStart").val().trim();
|
||||
var ipend = $(".ganIpEnd").val().trim();
|
||||
|
||||
if (ipstart == ""){
|
||||
$(".ganIpStart").parent().addClass("error");
|
||||
}else{
|
||||
$(".ganIpStart").parent().removeClass("error");
|
||||
}
|
||||
|
||||
if (ipend == ""){
|
||||
$(".ganIpEnd").parent().addClass("error");
|
||||
}else{
|
||||
$(".ganIpEnd").parent().removeClass("error");
|
||||
}
|
||||
|
||||
//Get CIDR from selected range group
|
||||
var cidr = $(".iprange.active").attr("cidr");
|
||||
|
||||
$.cjax({
|
||||
url: "/api/gan/network/setRange",
|
||||
metohd: "POST",
|
||||
data:{
|
||||
netid: currentGANetID,
|
||||
cidr: cidr,
|
||||
ipstart: ipstart,
|
||||
ipend: ipend
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000)
|
||||
}else{
|
||||
msgbox("Network Range Updated")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function saveNameAndDesc(object=undefined){
|
||||
var name = $("#gaNetNameInput").val();
|
||||
var desc = $("#gaNetDescInput").val();
|
||||
if (object != undefined){
|
||||
$(object).addClass("loading");
|
||||
}
|
||||
$.cjax({
|
||||
url: "/api/gan/network/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
name: name,
|
||||
desc: desc,
|
||||
},
|
||||
success: function(data){
|
||||
initNetNameAndDesc();
|
||||
if (object != undefined){
|
||||
$(object).removeClass("loading");
|
||||
msgbox("Network Metadata Updated");
|
||||
}
|
||||
$("#gannetDetailEdit").slideUp("fast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNetNameAndDesc(){
|
||||
//Get the details of the net
|
||||
$.get("/api/gan/network/name?netid=" + currentGANetID, function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$("#gaNetNameInput").val(data[0]);
|
||||
$(".ganetName").html(data[0]);
|
||||
$("#gaNetDescInput").val(data[1]);
|
||||
$(".ganetDesc").text(data[1]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNetDetails(){
|
||||
//Get the details of the net
|
||||
$.get("/api/gan/network/list?netid=" + currentGANetID, function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
currentGaNetDetails = data;
|
||||
highlightCurrentGANetCIDR();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Handle delete IP from memeber
|
||||
function deleteIpFromMemeber(memberid, ip){
|
||||
$.cjax({
|
||||
url: "/api/gan/members/ip",
|
||||
metohd: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: memberid,
|
||||
opr: "del",
|
||||
ip: ip,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("IP removed from member " + memberid)
|
||||
}
|
||||
renderMemeberTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addIpToMemeberFromInput(memberid, newip){
|
||||
function isValidIPv4Address(address) {
|
||||
// Split the address into its 4 components
|
||||
const parts = address.split('.');
|
||||
|
||||
// Check that there are 4 components
|
||||
if (parts.length !== 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that each component is a number between 0 and 255
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const part = parseInt(parts[i], 10);
|
||||
if (isNaN(part) || part < 0 || part > 255) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The address is valid
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isValidIPv4Address(newip)){
|
||||
msgbox(newip + " is not a valid IPv4 address", false, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
$.cjax({
|
||||
url: "/api/gan/members/ip",
|
||||
metohd: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: memberid,
|
||||
opr: "add",
|
||||
ip: newip,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
msgbox("IP added to member " + memberid)
|
||||
}
|
||||
renderMemeberTable();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Member table populate
|
||||
function renderMemeberTable(forceUpdate = false) {
|
||||
$.ajax({
|
||||
url: '/api/gan/members/list?netid=' + currentGANetID + '&detail=true',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
let tableBody = $('#networkMemeberTable');
|
||||
if (tableBody.length == 0){
|
||||
return;
|
||||
}
|
||||
data.sort((a, b) => a.address.localeCompare(b.address));
|
||||
//Check if the new object equal to the old one
|
||||
if (objectEqual(currentGANMemberList, data) && !forceUpdate){
|
||||
//Do not need to update it
|
||||
return;
|
||||
}
|
||||
tableBody.empty();
|
||||
currentGANMemberList = data;
|
||||
|
||||
var authroziedCount = 0;
|
||||
data.forEach((member) => {
|
||||
let lastAuthTime = new Date(member.lastAuthorizedTime).toLocaleString();
|
||||
if (member.lastAuthorizedTime == 0){
|
||||
lastAuthTime = "Never";
|
||||
}
|
||||
|
||||
let version = `${member.vMajor}.${member.vMinor}.${member.vProto}.${member.vRev}`;
|
||||
if (member.vMajor == -1){
|
||||
version = "Unknown";
|
||||
}
|
||||
|
||||
let authorizedCheckbox = `<div class="ui fitted checkbox">
|
||||
<input type="checkbox" addr="${member.address}" name="isAuthrozied" onchange="handleMemberAuth(this);">
|
||||
<label></label>
|
||||
</div>`;
|
||||
if (member.authorized){
|
||||
authorizedCheckbox = `<div class="ui fitted checkbox">
|
||||
<input type="checkbox" addr="${member.address}" name="isAuthrozied" onchange="handleMemberAuth(this);" checked="">
|
||||
<label></label>
|
||||
</div>`
|
||||
}
|
||||
|
||||
let rowClass = "authorized";
|
||||
let unauthorizedStyle = "";
|
||||
if (!$("#showUnauthorizedMembers")[0].checked && !member.authorized){
|
||||
unauthorizedStyle = "display:none;";
|
||||
}
|
||||
if (!member.authorized){
|
||||
rowClass = "unauthorized";
|
||||
}else{
|
||||
authroziedCount++;
|
||||
}
|
||||
|
||||
let assignedIp = "";
|
||||
|
||||
if (member.ipAssignments.length == 0){
|
||||
assignedIp = "Not assigned"
|
||||
}else{
|
||||
assignedIp = `<div class="ui list">`
|
||||
member.ipAssignments.forEach(function(thisIp){
|
||||
assignedIp += `<div class="item" style="width: 100%;">${thisIp} <a style="cursor:pointer; float: right;" title="Remove IP" onclick="deleteIpFromMemeber('${member.address}','${thisIp}');"><i class="red remove icon"></i></a></div>`;
|
||||
})
|
||||
assignedIp += `</div>`
|
||||
}
|
||||
const row = $(`<tr class="GANetMemberEntity ${rowClass}" style="${unauthorizedStyle}">`);
|
||||
row.append($(`<td class="GANetMember ${rowClass}" style="text-align: center;">`).html(authorizedCheckbox));
|
||||
row.append($('<td>').text(member.address));
|
||||
row.append($('<td>').html(`<span class="memberName" addr="${member.address}"></span> <a style="cursor:pointer; float: right;" title="Edit Memeber Name" onclick="renameMember('${member.address}');"><i class="grey edit icon"></i></a>`));
|
||||
row.append($('<td>').html(`${assignedIp}
|
||||
<div class="ui action mini fluid input" style="min-width: 200px;">
|
||||
<input type="text" placeholder="IPv4" onchange="$(this).val($(this).val().trim());">
|
||||
<button onclick="addIpToMemeberFromInput('${member.address}',$(this).parent().find('input').val());" class="ui basic icon button">
|
||||
<i class="add icon"></i>
|
||||
</button>
|
||||
</div>`));
|
||||
row.append($('<td>').text(lastAuthTime));
|
||||
row.append($('<td>').text(version));
|
||||
row.append($(`<td title="Deauthorize & Delete Memeber" style="text-align: center;" onclick="handleMemberDelete('${member.address}');">`).html(`<button class="ui basic mini icon button"><i class="red remove icon"></i></button>`));
|
||||
|
||||
tableBody.append(row);
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
tableBody.append(`<tr>
|
||||
<td colspan="7"><i class="green check circle icon"></i> No member has joined this network yet.</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
if (data.length > 0 && authroziedCount == 0 && !$("#showUnauthorizedMembers")[0].checked){
|
||||
//All nodes are unauthorized. Show tips to enable unauthorize display
|
||||
tableBody.append(`<tr>
|
||||
<td colspan="7"><i class="yellow exclamation circle icon"></i> Unauthorized nodes detected. Enable "Show Unauthorized Member" to change member access permission.</td>
|
||||
</tr>`);
|
||||
}
|
||||
|
||||
initNameForMembers();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.log('Error:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initNameForMembers(){
|
||||
$(".memberName").each(function(){
|
||||
let addr = $(this).attr("addr");
|
||||
let targetDOM = $(this);
|
||||
$.cjax({
|
||||
url: "/api/gan/members/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: addr,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
$(targetDOM).text("N/A");
|
||||
}else{
|
||||
$(targetDOM).text(data.Name);
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function renameMember(targetMemberAddr){
|
||||
if (targetMemberAddr == ""){
|
||||
msgbox("Member address cannot be empty", false, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
let newname = prompt("Enter a easy manageable name for " + targetMemberAddr, "");
|
||||
if (newname != null && newname.trim() != "") {
|
||||
$.cjax({
|
||||
url: "/api/gan/members/name",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid: currentGANetID,
|
||||
memid: targetMemberAddr,
|
||||
name: newname
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Member Name Updated");
|
||||
}
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//Helper function to check if two objects are equal recursively
|
||||
function objectEqual(obj1, obj2) {
|
||||
// compare types
|
||||
if (typeof obj1 !== typeof obj2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compare values
|
||||
if (typeof obj1 !== 'object' || obj1 === null) {
|
||||
return obj1 === obj2;
|
||||
}
|
||||
|
||||
const keys1 = Object.keys(obj1);
|
||||
const keys2 = Object.keys(obj2);
|
||||
|
||||
// compare keys
|
||||
if (keys1.length !== keys2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// recursively compare values
|
||||
if (!objectEqual(obj1[key], obj2[key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function changeUnauthorizedVisibility(visable){
|
||||
if(visable){
|
||||
$(".GANetMemberEntity.unauthorized").show();
|
||||
}else{
|
||||
$(".GANetMemberEntity.unauthorized").hide();
|
||||
}
|
||||
}
|
||||
|
||||
function handleMemberAuth(object){
|
||||
let targetMemberAddr = $(object).attr("addr");
|
||||
let isAuthed = object.checked;
|
||||
$.cjax({
|
||||
url: "/api/gan/members/authorize",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
memid: targetMemberAddr,
|
||||
auth: isAuthed
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
if (isAuthed){
|
||||
msgbox("Member Authorized");
|
||||
}else{
|
||||
msgbox("Member Deauthorized");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleMemberDelete(addr){
|
||||
if (confirm("Confirm delete member " + addr + " ?")){
|
||||
$.cjax({
|
||||
url: "/api/gan/members/delete",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
memid: addr,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Member Deleted");
|
||||
}
|
||||
renderMemeberTable(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Add and remove this controller node to network as member
|
||||
function ganAddControllerToNetwork(){
|
||||
$(".addControllerToNetworkBtn").addClass("disabled");
|
||||
$(".addControllerToNetworkBtn").addClass("loading");
|
||||
|
||||
$.cjax({
|
||||
url: "/api/gan/network/join",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
},
|
||||
success: function(data){
|
||||
$(".addControllerToNetworkBtn").removeClass("disabled");
|
||||
$(".addControllerToNetworkBtn").removeClass("loading");
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Controller joint " + currentGANetID);
|
||||
}
|
||||
setTimeout(function(){
|
||||
renderMemeberTable(true);
|
||||
}, 3000)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ganRemoveControllerFromNetwork(){
|
||||
$(".removeControllerFromNetworkBtn").addClass("disabled");
|
||||
$(".removeControllerFromNetworkBtn").addClass("loading");
|
||||
|
||||
$.cjax({
|
||||
url: "/api/gan/network/leave",
|
||||
method: "POST",
|
||||
data: {
|
||||
netid:currentGANetID,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Controller left " + currentGANetID);
|
||||
}
|
||||
renderMemeberTable(true);
|
||||
$(".removeControllerFromNetworkBtn").removeClass("disabled");
|
||||
$(".removeControllerFromNetworkBtn").removeClass("loading");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Entry points
|
||||
function initGanetDetails(ganetId){
|
||||
currentGANetID = ganetId;
|
||||
$(".ganetID").text(ganetId);
|
||||
initNetNameAndDesc(ganetId);
|
||||
generateIPRangeTable(netRanges);
|
||||
initNetDetails();
|
||||
renderMemeberTable(true);
|
||||
|
||||
//Setup a listener to listen for member list change
|
||||
if (currentGANNetMemeberListener == undefined){
|
||||
currentGANNetMemeberListener = setInterval(function(){
|
||||
if ($('#networkMemeberTable').length > 0 && currentGANetID){
|
||||
renderMemeberTable();
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Switch from other tabs back to this, exit to GAN list
|
||||
tabSwitchEventBind["gan"] = function(){
|
||||
exitToGanList();
|
||||
}
|
||||
|
||||
//Exit point
|
||||
function exitToGanList(){
|
||||
$("#gan").load("./components/gan.html", function(){
|
||||
if (tabSwitchEventBind["gan"]){
|
||||
tabSwitchEventBind["gan"]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
@ -67,9 +67,6 @@
|
||||
<a class="item" tag="access">
|
||||
<i class="simplistic ban icon"></i> Access Control
|
||||
</a>
|
||||
<a class="item" tag="gan">
|
||||
<i class="simplistic globe icon"></i> Global Area Network
|
||||
</a>
|
||||
<div class="ui divider menudivider">Security</div>
|
||||
<a class="item" tag="cert">
|
||||
<i class="simplistic lock icon"></i> TLS / SSL certificates
|
||||
@ -135,9 +132,6 @@
|
||||
<!-- Blacklist -->
|
||||
<div id="access" class="functiontab" target="access.html"></div>
|
||||
|
||||
<!-- Global Area Networking -->
|
||||
<div id="gan" class="functiontab" target="gan.html"></div>
|
||||
|
||||
<!-- SSO / Oauth services -->
|
||||
<div id="sso" class="functiontab" target="sso.html"></div>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user