mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-06 23:57:21 +02:00
Updates v2.6.1
+ Added reverse proxy TLS skip verification + Added basic auth + Edit proxy settings + Whitelist + TCP Proxy (experimental) + Info (Utilities page)
This commit is contained in:
parent
5952a1b55f
commit
20fd8e9a49
195
src/accesslist.go
Normal file
195
src/accesslist.go
Normal file
@ -0,0 +1,195 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
accesslist.go
|
||||
|
||||
This script file is added to extend the
|
||||
reverse proxy function to include
|
||||
banning / whitelist a specific IP address or country code
|
||||
*/
|
||||
|
||||
/*
|
||||
Blacklist Related
|
||||
*/
|
||||
|
||||
// List a of blacklisted ip address or country code
|
||||
func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype, err := utils.GetPara(r, "type")
|
||||
if err != nil {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
resulst := []string{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
resulst = geodbStore.GetAllBlacklistedIp()
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(resulst)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
}
|
||||
|
||||
func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddIPToBlackList(ipAddr)
|
||||
}
|
||||
|
||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||
enable, err := utils.PostPara(r, "enable")
|
||||
if err != nil {
|
||||
//Return the current enabled state
|
||||
currentEnabled := geodbStore.BlacklistEnabled
|
||||
js, _ := json.Marshal(currentEnabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enable == "true" {
|
||||
geodbStore.ToggleBlacklist(true)
|
||||
} else if enable == "false" {
|
||||
geodbStore.ToggleBlacklist(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Whitelist Related
|
||||
*/
|
||||
|
||||
func handleListWhitelisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype, err := utils.GetPara(r, "type")
|
||||
if err != nil {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
resulst := []string{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllWhitelistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
resulst = geodbStore.GetAllWhitelistedIp()
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(resulst)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
}
|
||||
|
||||
func handleCountryWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddCountryCodeToWhitelist(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleCountryWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveCountryCodeFromWhitelist(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleIpWhitelistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddIPToWhiteList(ipAddr)
|
||||
}
|
||||
|
||||
func handleIpWhitelistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveIPFromWhiteList(ipAddr)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleWhitelistEnable(w http.ResponseWriter, r *http.Request) {
|
||||
enable, err := utils.PostPara(r, "enable")
|
||||
if err != nil {
|
||||
//Return the current enabled state
|
||||
currentEnabled := geodbStore.WhitelistEnabled
|
||||
js, _ := json.Marshal(currentEnabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enable == "true" {
|
||||
geodbStore.ToggleWhitelist(true)
|
||||
} else if enable == "false" {
|
||||
geodbStore.ToggleWhitelist(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
16
src/api.go
16
src/api.go
@ -45,7 +45,9 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus)
|
||||
authRouter.HandleFunc("/api/proxy/list", ReverseProxyList)
|
||||
authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint)
|
||||
authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials)
|
||||
authRouter.HandleFunc("/api/proxy/tlscheck", HandleCheckSiteSupportTLS)
|
||||
authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet)
|
||||
authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect)
|
||||
@ -71,6 +73,14 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove)
|
||||
authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable)
|
||||
|
||||
//Whitelist APIs
|
||||
authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted)
|
||||
authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd)
|
||||
authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove)
|
||||
authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd)
|
||||
authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove)
|
||||
authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable)
|
||||
|
||||
//Statistic & uptime monitoring API
|
||||
authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad)
|
||||
authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary)
|
||||
@ -97,6 +107,9 @@ func initAPIs() {
|
||||
authRouter.HandleFunc("/api/tcpprox/config/add", tcpProxyManager.HandleAddProxyConfig)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/edit", tcpProxyManager.HandleEditProxyConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/list", tcpProxyManager.HandleListConfigs)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/start", tcpProxyManager.HandleStartProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/stop", tcpProxyManager.HandleStopProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/delete", tcpProxyManager.HandleRemoveProxy)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/status", tcpProxyManager.HandleGetProxyStatus)
|
||||
authRouter.HandleFunc("/api/tcpprox/config/validate", tcpProxyManager.HandleConfigValidate)
|
||||
|
||||
@ -123,6 +136,9 @@ func initAPIs() {
|
||||
http.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||
http.HandleFunc("/api/account/new", HandleNewPasswordSetup)
|
||||
|
||||
//Others
|
||||
http.HandleFunc("/api/info/x", HandleZoraxyInfo)
|
||||
|
||||
//If you got APIs to add, append them here
|
||||
}
|
||||
|
||||
|
102
src/blacklist.go
102
src/blacklist.go
@ -1,102 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
blacklist.go
|
||||
|
||||
This script file is added to extend the
|
||||
reverse proxy function to include
|
||||
banning a specific IP address or country code
|
||||
*/
|
||||
|
||||
//List a of blacklisted ip address or country code
|
||||
func handleListBlacklisted(w http.ResponseWriter, r *http.Request) {
|
||||
bltype, err := utils.GetPara(r, "type")
|
||||
if err != nil {
|
||||
bltype = "country"
|
||||
}
|
||||
|
||||
resulst := []string{}
|
||||
if bltype == "country" {
|
||||
resulst = geodbStore.GetAllBlacklistedCountryCode()
|
||||
} else if bltype == "ip" {
|
||||
resulst = geodbStore.GetAllBlacklistedIp()
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(resulst)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
}
|
||||
|
||||
func handleCountryBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddCountryCodeToBlackList(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleCountryBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
countryCode, err := utils.PostPara(r, "cc")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty country code")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveCountryCodeFromBlackList(countryCode)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleIpBlacklistAdd(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.AddIPToBlackList(ipAddr)
|
||||
}
|
||||
|
||||
func handleIpBlacklistRemove(w http.ResponseWriter, r *http.Request) {
|
||||
ipAddr, err := utils.PostPara(r, "ip")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid or empty ip address")
|
||||
return
|
||||
}
|
||||
|
||||
geodbStore.RemoveIPFromBlackList(ipAddr)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func handleBlacklistEnable(w http.ResponseWriter, r *http.Request) {
|
||||
enable, err := utils.PostPara(r, "enable")
|
||||
if err != nil {
|
||||
//Return the current enabled state
|
||||
currentEnabled := geodbStore.Enabled
|
||||
js, _ := json.Marshal(currentEnabled)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enable == "true" {
|
||||
geodbStore.ToggleBlacklist(true)
|
||||
} else if enable == "false" {
|
||||
geodbStore.ToggleBlacklist(false)
|
||||
} else {
|
||||
utils.SendErrorResponse(w, "invalid enable state: only true and false is accepted")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
@ -38,9 +38,10 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no
|
||||
var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
var (
|
||||
name = "Zoraxy"
|
||||
version = "2.6"
|
||||
version = "2.6.1"
|
||||
nodeUUID = "generic"
|
||||
development = false //Set this to false to use embedded web fs
|
||||
development = true //Set this to false to use embedded web fs
|
||||
bootTime = time.Now().Unix()
|
||||
|
||||
/*
|
||||
Binary Embedding File System
|
||||
@ -56,7 +57,7 @@ var (
|
||||
authAgent *auth.AuthAgent //Authentication agent
|
||||
tlsCertManager *tlscert.Manager //TLS / SSL management
|
||||
redirectTable *redirection.RuleTable //Handle special redirection rule sets
|
||||
geodbStore *geodb.Store //GeoIP database
|
||||
geodbStore *geodb.Store //GeoIP database, also handle black list and whitelist features
|
||||
netstatBuffers *netstat.NetStatBuffers //Realtime graph buffers
|
||||
statisticCollector *statistic.Collector //Collecting statistic from visitors
|
||||
uptimeMonitor *uptime.Monitor //Uptime monitor service worker
|
||||
|
@ -25,6 +25,7 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
/*
|
||||
General Access Check
|
||||
*/
|
||||
|
||||
//Check if this ip is in blacklist
|
||||
clientIpAddr := geodb.GetRequesterIP(r)
|
||||
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
|
||||
@ -40,6 +41,20 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Check if this ip is in whitelist
|
||||
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
template, err := os.ReadFile("./web/forbidden.html")
|
||||
if err != nil {
|
||||
w.Write([]byte("403 - Forbidden"))
|
||||
} else {
|
||||
w.Write(template)
|
||||
}
|
||||
h.logRequest(r, false, 403, "whitelist", "")
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Redirection Routing
|
||||
*/
|
||||
|
@ -242,10 +242,44 @@ func (router *Router) AddVirtualDirectoryProxyService(options *VdirOptions) erro
|
||||
|
||||
router.ProxyEndpoints.Store(options.RootName, &endpointObject)
|
||||
|
||||
log.Println("Adding Proxy Rule: ", options.RootName+" to "+domain)
|
||||
log.Println("Registered Proxy Rule: ", options.RootName+" to "+domain)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Load routing from RP
|
||||
*/
|
||||
func (router *Router) LoadProxy(ptype string, key string) (*ProxyEndpoint, error) {
|
||||
if ptype == "vdir" {
|
||||
proxy, ok := router.ProxyEndpoints.Load(key)
|
||||
if !ok {
|
||||
return nil, errors.New("target proxy not found")
|
||||
}
|
||||
return proxy.(*ProxyEndpoint), nil
|
||||
} else if ptype == "subd" {
|
||||
proxy, ok := router.SubdomainEndpoint.Load(key)
|
||||
if !ok {
|
||||
return nil, errors.New("target proxy not found")
|
||||
}
|
||||
return proxy.(*ProxyEndpoint), nil
|
||||
}
|
||||
|
||||
return nil, errors.New("unsupported ptype")
|
||||
}
|
||||
|
||||
/*
|
||||
Save routing from RP
|
||||
*/
|
||||
func (router *Router) SaveProxy(ptype string, key string, newConfig *ProxyEndpoint) {
|
||||
if ptype == "vdir" {
|
||||
router.ProxyEndpoints.Store(key, newConfig)
|
||||
|
||||
} else if ptype == "subd" {
|
||||
router.SubdomainEndpoint.Store(key, newConfig)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Remove routing from RP
|
||||
*/
|
||||
|
@ -75,7 +75,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u)
|
||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@ -128,7 +128,7 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u)
|
||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package geodb
|
||||
import (
|
||||
_ "embed"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -11,13 +10,22 @@ import (
|
||||
)
|
||||
|
||||
//go:embed geoipv4.csv
|
||||
var geoipv4 []byte //Original embedded csv file
|
||||
var geoipv4 []byte //Geodb dataset for ipv4
|
||||
|
||||
//go:embed geoipv6.csv
|
||||
var geoipv6 []byte //Geodb dataset for ipv6
|
||||
|
||||
type Store struct {
|
||||
Enabled bool
|
||||
BlacklistEnabled bool
|
||||
WhitelistEnabled bool
|
||||
geodb [][]string //Parsed geodb list
|
||||
//geoipCache sync.Map
|
||||
geodbIpv6 [][]string //Parsed geodb list for ipv6
|
||||
|
||||
geotrie *trie
|
||||
geotrieIpv6 *trie
|
||||
|
||||
//geoipCache sync.Map
|
||||
|
||||
sysdb *database.Database
|
||||
}
|
||||
|
||||
@ -32,7 +40,13 @@ func NewGeoDb(sysdb *database.Database) (*Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parsedGeoDataIpv6, err := parseCSV(geoipv6)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blacklistEnabled := false
|
||||
whitelistEnabled := false
|
||||
if sysdb != nil {
|
||||
err = sysdb.NewTable("blacklist-cn")
|
||||
if err != nil {
|
||||
@ -44,27 +58,46 @@ func NewGeoDb(sysdb *database.Database) (*Store, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("blacklist")
|
||||
err = sysdb.NewTable("whitelist-cn")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sysdb.Read("blacklist", "enabled", &blacklistEnabled)
|
||||
|
||||
err = sysdb.NewTable("whitelist-ip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sysdb.NewTable("blackwhitelist")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysdb.Read("blackwhitelist", "blacklistEnabled", &blacklistEnabled)
|
||||
sysdb.Read("blackwhitelist", "whitelistEnabled", &whitelistEnabled)
|
||||
} else {
|
||||
log.Println("Database pointer set to nil: Entering debug mode")
|
||||
}
|
||||
|
||||
return &Store{
|
||||
Enabled: blacklistEnabled,
|
||||
BlacklistEnabled: blacklistEnabled,
|
||||
WhitelistEnabled: whitelistEnabled,
|
||||
geodb: parsedGeoData,
|
||||
//geoipCache: sync.Map{},
|
||||
geotrie: constrctTrieTree(parsedGeoData),
|
||||
geodbIpv6: parsedGeoDataIpv6,
|
||||
geotrieIpv6: constrctTrieTree(parsedGeoDataIpv6),
|
||||
sysdb: sysdb,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Store) ToggleBlacklist(enabled bool) {
|
||||
s.sysdb.Write("blacklist", "enabled", enabled)
|
||||
s.Enabled = enabled
|
||||
s.sysdb.Write("blackwhitelist", "blacklistEnabled", enabled)
|
||||
s.BlacklistEnabled = enabled
|
||||
}
|
||||
|
||||
func (s *Store) ToggleWhitelist(enabled bool) {
|
||||
s.sysdb.Write("blackwhitelist", "whitelistEnabled", enabled)
|
||||
s.WhitelistEnabled = enabled
|
||||
}
|
||||
|
||||
func (s *Store) ResolveCountryCodeFromIP(ipstring string) (*CountryInfo, error) {
|
||||
@ -79,6 +112,10 @@ func (s *Store) Close() {
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Country code based black / white list
|
||||
*/
|
||||
|
||||
func (s *Store) AddCountryCodeToBlackList(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Write("blacklist-cn", countryCode, true)
|
||||
@ -89,6 +126,16 @@ func (s *Store) RemoveCountryCodeFromBlackList(countryCode string) {
|
||||
s.sysdb.Delete("blacklist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) AddCountryCodeToWhitelist(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Write("whitelist-cn", countryCode, true)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveCountryCodeFromWhitelist(countryCode string) {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
s.sysdb.Delete("whitelist-cn", countryCode)
|
||||
}
|
||||
|
||||
func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
var isBlacklisted bool = false
|
||||
@ -96,6 +143,13 @@ func (s *Store) IsCountryCodeBlacklisted(countryCode string) bool {
|
||||
return isBlacklisted
|
||||
}
|
||||
|
||||
func (s *Store) IsCountryCodeWhitelisted(countryCode string) bool {
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
var isWhitelisted bool = false
|
||||
s.sysdb.Read("whitelist-cn", countryCode, &isWhitelisted)
|
||||
return isWhitelisted
|
||||
}
|
||||
|
||||
func (s *Store) GetAllBlacklistedCountryCode() []string {
|
||||
bannedCountryCodes := []string{}
|
||||
entries, err := s.sysdb.ListTable("blacklist-cn")
|
||||
@ -110,6 +164,24 @@ func (s *Store) GetAllBlacklistedCountryCode() []string {
|
||||
return bannedCountryCodes
|
||||
}
|
||||
|
||||
func (s *Store) GetAllWhitelistedCountryCode() []string {
|
||||
whitelistedCountryCode := []string{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-cn")
|
||||
if err != nil {
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
whitelistedCountryCode = append(whitelistedCountryCode, ip)
|
||||
}
|
||||
|
||||
return whitelistedCountryCode
|
||||
}
|
||||
|
||||
/*
|
||||
IP based black / whitelist
|
||||
*/
|
||||
|
||||
func (s *Store) AddIPToBlackList(ipAddr string) {
|
||||
s.sysdb.Write("blacklist-ip", ipAddr, true)
|
||||
}
|
||||
@ -118,6 +190,14 @@ func (s *Store) RemoveIPFromBlackList(ipAddr string) {
|
||||
s.sysdb.Delete("blacklist-ip", ipAddr)
|
||||
}
|
||||
|
||||
func (s *Store) AddIPToWhiteList(ipAddr string) {
|
||||
s.sysdb.Write("whitelist-ip", ipAddr, true)
|
||||
}
|
||||
|
||||
func (s *Store) RemoveIPFromWhiteList(ipAddr string) {
|
||||
s.sysdb.Delete("whitelist-ip", ipAddr)
|
||||
}
|
||||
|
||||
func (s *Store) IsIPBlacklisted(ipAddr string) bool {
|
||||
var isBlacklisted bool = false
|
||||
s.sysdb.Read("blacklist-ip", ipAddr, &isBlacklisted)
|
||||
@ -142,6 +222,30 @@ func (s *Store) IsIPBlacklisted(ipAddr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) IsIPWhitelisted(ipAddr string) bool {
|
||||
var isBlacklisted bool = false
|
||||
s.sysdb.Read("whitelist-ip", ipAddr, &isBlacklisted)
|
||||
if isBlacklisted {
|
||||
return true
|
||||
}
|
||||
|
||||
//Check for IP wildcard and CIRD rules
|
||||
AllBlacklistedIps := s.GetAllBlacklistedIp()
|
||||
for _, blacklistRule := range AllBlacklistedIps {
|
||||
wildcardMatch := MatchIpWildcard(ipAddr, blacklistRule)
|
||||
if wildcardMatch {
|
||||
return true
|
||||
}
|
||||
|
||||
cidrMatch := MatchIpCIDR(ipAddr, blacklistRule)
|
||||
if cidrMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) GetAllBlacklistedIp() []string {
|
||||
bannedIps := []string{}
|
||||
entries, err := s.sysdb.ListTable("blacklist-ip")
|
||||
@ -157,9 +261,27 @@ func (s *Store) GetAllBlacklistedIp() []string {
|
||||
return bannedIps
|
||||
}
|
||||
|
||||
// Check if a IP address is blacklisted, in either country or IP blacklist
|
||||
func (s *Store) GetAllWhitelistedIp() []string {
|
||||
whitelistedIp := []string{}
|
||||
entries, err := s.sysdb.ListTable("whitelist-ip")
|
||||
if err != nil {
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
for _, keypairs := range entries {
|
||||
ip := string(keypairs[0])
|
||||
whitelistedIp = append(whitelistedIp, ip)
|
||||
}
|
||||
|
||||
return whitelistedIp
|
||||
}
|
||||
|
||||
/*
|
||||
Check if a IP address is blacklisted, in either country or IP blacklist
|
||||
IsBlacklisted default return is false (allow access)
|
||||
*/
|
||||
func (s *Store) IsBlacklisted(ipAddr string) bool {
|
||||
if !s.Enabled {
|
||||
if !s.BlacklistEnabled {
|
||||
//Blacklist not enabled. Always return false
|
||||
return false
|
||||
}
|
||||
@ -185,6 +307,40 @@ func (s *Store) IsBlacklisted(ipAddr string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
IsWhitelisted check if a given IP address is in the current
|
||||
server's white list.
|
||||
|
||||
Note that the Whitelist default result is true even
|
||||
when encountered error
|
||||
*/
|
||||
func (s *Store) IsWhitelisted(ipAddr string) bool {
|
||||
if !s.WhitelistEnabled {
|
||||
//Whitelist not enabled. Always return true (allow access)
|
||||
return true
|
||||
}
|
||||
|
||||
if ipAddr == "" {
|
||||
//Unable to get the target IP address, assume ok
|
||||
return true
|
||||
}
|
||||
|
||||
countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsCountryCodeWhitelisted(countryCode.CountryIsoCode) {
|
||||
return true
|
||||
}
|
||||
|
||||
if s.IsIPWhitelisted(ipAddr) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||
ipAddr := GetRequesterIP(r)
|
||||
if ipAddr == "" {
|
||||
@ -197,54 +353,3 @@ func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
|
||||
|
||||
return countryCode.CountryIsoCode
|
||||
}
|
||||
|
||||
// Utilities function
|
||||
func GetRequesterIP(r *http.Request) string {
|
||||
ip := r.Header.Get("X-Forwarded-For")
|
||||
if ip == "" {
|
||||
ip = r.Header.Get("X-Real-IP")
|
||||
if ip == "" {
|
||||
ip = strings.Split(r.RemoteAddr, ":")[0]
|
||||
}
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// Match the IP address with a wildcard string
|
||||
func MatchIpWildcard(ipAddress, wildcard string) bool {
|
||||
// Split IP address and wildcard into octets
|
||||
ipOctets := strings.Split(ipAddress, ".")
|
||||
wildcardOctets := strings.Split(wildcard, ".")
|
||||
|
||||
// Check that both have 4 octets
|
||||
if len(ipOctets) != 4 || len(wildcardOctets) != 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check each octet to see if it matches the wildcard or is an exact match
|
||||
for i := 0; i < 4; i++ {
|
||||
if wildcardOctets[i] == "*" {
|
||||
continue
|
||||
}
|
||||
if ipOctets[i] != wildcardOctets[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match ip address with CIDR
|
||||
func MatchIpCIDR(ip string, cidr string) bool {
|
||||
// parse the CIDR string
|
||||
_, cidrnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the IP address
|
||||
ipAddr := net.ParseIP(ip)
|
||||
|
||||
// check if the IP address is within the CIDR range
|
||||
return cidrnet.Contains(ipAddr)
|
||||
}
|
||||
|
84470
src/mod/geodb/geoipv6.csv
Normal file
84470
src/mod/geodb/geoipv6.csv
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,13 @@ func (s *Store) search(ip string) string {
|
||||
*/
|
||||
|
||||
//Search in geotrie tree
|
||||
cc := s.geotrie.search(ip)
|
||||
cc := ""
|
||||
if IsIPv6(ip) {
|
||||
cc = s.geotrieIpv6.search(ip)
|
||||
} else {
|
||||
cc = s.geotrie.search(ip)
|
||||
}
|
||||
|
||||
/*
|
||||
if cc != "" {
|
||||
s.geoipCache.Store(ip, cc)
|
||||
|
115
src/mod/geodb/netutils.go
Normal file
115
src/mod/geodb/netutils.go
Normal file
@ -0,0 +1,115 @@
|
||||
package geodb
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Utilities function
|
||||
func GetRequesterIP(r *http.Request) string {
|
||||
ip := r.Header.Get("X-Real-Ip")
|
||||
if ip == "" {
|
||||
ip = r.Header.Get("X-Forwarded-For")
|
||||
}
|
||||
if ip == "" {
|
||||
ip = r.RemoteAddr
|
||||
}
|
||||
|
||||
/*
|
||||
Possible shits that might be extracted by this code
|
||||
127.0.0.1:61001
|
||||
[15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7]:61002
|
||||
127.0.0.1
|
||||
158.250.160.114,109.21.249.211
|
||||
[15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7],109.21.249.211
|
||||
|
||||
We need to extract just the first ip address
|
||||
*/
|
||||
requesterRawIp := ip
|
||||
if strings.Contains(requesterRawIp, ",") {
|
||||
//Trim off all the forwarder IPs
|
||||
requesterRawIp = strings.Split(requesterRawIp, ",")[0]
|
||||
}
|
||||
|
||||
//Trim away the port number
|
||||
reqHost, _, err := net.SplitHostPort(requesterRawIp)
|
||||
if err == nil {
|
||||
requesterRawIp = reqHost
|
||||
}
|
||||
|
||||
if strings.HasPrefix(requesterRawIp, "[") && strings.HasSuffix(requesterRawIp, "]") {
|
||||
//e.g. [15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7]
|
||||
requesterRawIp = requesterRawIp[1 : len(requesterRawIp)-1]
|
||||
}
|
||||
|
||||
return requesterRawIp
|
||||
}
|
||||
|
||||
// Match the IP address with a wildcard string
|
||||
func MatchIpWildcard(ipAddress, wildcard string) bool {
|
||||
// Split IP address and wildcard into octets
|
||||
ipOctets := strings.Split(ipAddress, ".")
|
||||
wildcardOctets := strings.Split(wildcard, ".")
|
||||
|
||||
// Check that both have 4 octets
|
||||
if len(ipOctets) != 4 || len(wildcardOctets) != 4 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check each octet to see if it matches the wildcard or is an exact match
|
||||
for i := 0; i < 4; i++ {
|
||||
if wildcardOctets[i] == "*" {
|
||||
continue
|
||||
}
|
||||
if ipOctets[i] != wildcardOctets[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Match ip address with CIDR
|
||||
func MatchIpCIDR(ip string, cidr string) bool {
|
||||
// parse the CIDR string
|
||||
_, cidrnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the IP address
|
||||
ipAddr := net.ParseIP(ip)
|
||||
|
||||
// check if the IP address is within the CIDR range
|
||||
return cidrnet.Contains(ipAddr)
|
||||
}
|
||||
|
||||
// Check if a ip is private IP range
|
||||
func IsPrivateIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return ip.IsPrivate()
|
||||
}
|
||||
|
||||
// Check if an Ip string is ipv6
|
||||
func IsIPv6(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return ip.To4() == nil && ip.To16() != nil
|
||||
}
|
||||
|
||||
// Check if an Ip string is ipv6
|
||||
func IsIPv4(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return ip.To4() != nil
|
||||
}
|
@ -24,6 +24,10 @@ func ipToBitString(ip string) string {
|
||||
|
||||
// Convert the IP address to a 4-byte slice
|
||||
ipBytes := parsedIP.To4()
|
||||
if ipBytes == nil {
|
||||
//This is an IPv6 address
|
||||
ipBytes = parsedIP.To16()
|
||||
}
|
||||
|
||||
// Convert each byte in the IP address to its 8-bit binary representation
|
||||
var result []string
|
||||
@ -36,23 +40,38 @@ func ipToBitString(ip string) string {
|
||||
}
|
||||
|
||||
func bitStringToIp(bitString string) string {
|
||||
// Split the bit string into four 8-bit segments
|
||||
segments := []string{
|
||||
bitString[:8],
|
||||
bitString[8:16],
|
||||
bitString[16:24],
|
||||
bitString[24:32],
|
||||
// Check if the bit string represents an IPv4 or IPv6 address
|
||||
isIPv4 := len(bitString) == 32
|
||||
|
||||
// Split the bit string into 8-bit segments
|
||||
segments := make([]string, 0)
|
||||
if isIPv4 {
|
||||
for i := 0; i < 4; i++ {
|
||||
segments = append(segments, bitString[i*8:(i+1)*8])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 16; i++ {
|
||||
segments = append(segments, bitString[i*8:(i+1)*8])
|
||||
}
|
||||
}
|
||||
|
||||
// Convert each segment to its decimal equivalent
|
||||
var decimalSegments []int
|
||||
for _, s := range segments {
|
||||
i, _ := strconv.ParseInt(s, 2, 64)
|
||||
decimalSegments = append(decimalSegments, int(i))
|
||||
decimalSegments := make([]int, len(segments))
|
||||
for i, s := range segments {
|
||||
val, _ := strconv.ParseInt(s, 2, 64)
|
||||
decimalSegments[i] = int(val)
|
||||
}
|
||||
|
||||
// Join the decimal segments with dots to form the IP address string
|
||||
// Construct the IP address string based on the type (IPv4 or IPv6)
|
||||
if isIPv4 {
|
||||
return fmt.Sprintf("%d.%d.%d.%d", decimalSegments[0], decimalSegments[1], decimalSegments[2], decimalSegments[3])
|
||||
} else {
|
||||
ip := make(net.IP, net.IPv6len)
|
||||
for i := 0; i < net.IPv6len; i++ {
|
||||
ip[i] = byte(decimalSegments[i])
|
||||
}
|
||||
return ip.String()
|
||||
}
|
||||
}
|
||||
|
||||
// inititlaizing a new trie
|
||||
@ -93,17 +112,11 @@ func isReservedIP(ip string) bool {
|
||||
if parsedIP.IsLinkLocalUnicast() || parsedIP.IsLinkLocalMulticast() {
|
||||
return true
|
||||
}
|
||||
// Check if the IP address is in the private address ranges
|
||||
privateRanges := []*net.IPNet{
|
||||
{IP: net.ParseIP("10.0.0.0"), Mask: net.CIDRMask(8, 32)},
|
||||
{IP: net.ParseIP("172.16.0.0"), Mask: net.CIDRMask(12, 32)},
|
||||
{IP: net.ParseIP("192.168.0.0"), Mask: net.CIDRMask(16, 32)},
|
||||
}
|
||||
for _, r := range privateRanges {
|
||||
if r.Contains(parsedIP) {
|
||||
|
||||
if parsedIP.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If the IP address is not a reserved address, return false
|
||||
return false
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
|
||||
r.Header.Set("A-Upgrade", "websocket")
|
||||
requestURL = strings.TrimPrefix(requestURL, "/")
|
||||
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
|
||||
wspHandler := websocketproxy.NewProxy(u)
|
||||
wspHandler := websocketproxy.NewProxy(u, false)
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
@ -145,7 +145,9 @@ func (c *Collector) RecordRequest(ri RequestInfo) {
|
||||
//Filter out CF forwarded requests
|
||||
if strings.Contains(ri.IpAddr, ",") {
|
||||
ips := strings.Split(strings.TrimSpace(ri.IpAddr), ",")
|
||||
if len(ips) >= 1 {
|
||||
if len(ips) >= 1 && IsValidIPAddress(strings.TrimPrefix(ips[0], "[")) {
|
||||
//Example when forwarded from CF: 158.250.160.114,109.21.249.211
|
||||
//Or IPv6 [15c4:cbb4:cc98:4291:ffc1:3a46:06a1:51a7],109.21.249.211
|
||||
ri.IpAddr = ips[0]
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package statistic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -26,3 +27,19 @@ func IsBeforeToday(dateString string) bool {
|
||||
today := time.Now().UTC().Truncate(24 * time.Hour)
|
||||
return date.Before(today) || dateString == time.Now().Format(layout)
|
||||
}
|
||||
|
||||
// Check if the IP string is a valid ip address
|
||||
func IsValidIPAddress(ip string) bool {
|
||||
// Check if the string is a valid IPv4 address
|
||||
if parsedIP := net.ParseIP(ip); parsedIP != nil && parsedIP.To4() != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the string is a valid IPv6 address
|
||||
if parsedIP := net.ParseIP(ip); parsedIP != nil && parsedIP.To16() != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the string is neither a valid IPv4 nor IPv6 address, return false
|
||||
return false
|
||||
}
|
||||
|
@ -157,6 +157,11 @@ func (c *ProxyRelayConfig) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) IsRunning() bool {
|
||||
return c.Running || c.stopChan != nil
|
||||
}
|
||||
|
||||
// Stop a running proxy if running
|
||||
func (c *ProxyRelayConfig) Stop() {
|
||||
if c.Running || c.stopChan != nil {
|
||||
|
@ -122,6 +122,77 @@ func (m *Manager) HandleListConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *Manager) HandleStartProxy(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.PostPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyConfig, err := m.GetConfigByUUID(uuid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = targetProxyConfig.Start()
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (m *Manager) HandleStopProxy(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.PostPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyConfig, err := m.GetConfigByUUID(uuid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !targetProxyConfig.IsRunning() {
|
||||
utils.SendErrorResponse(w, "target proxy service is not running")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyConfig.Stop()
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (m *Manager) HandleRemoveProxy(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.PostPara(r, "uuid")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid uuid given")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyConfig, err := m.GetConfigByUUID(uuid)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if targetProxyConfig.IsRunning() {
|
||||
utils.SendErrorResponse(w, "Service is running")
|
||||
return
|
||||
}
|
||||
|
||||
err = m.RemoveConfig(targetProxyConfig.UUID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (m *Manager) HandleGetProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||
uuid, err := utils.GetPara(r, "uuid")
|
||||
if err != nil {
|
||||
|
@ -132,10 +132,12 @@ func (m *Manager) EditConfig(configUUID string, newName string, newPortA string,
|
||||
foundConfig.Timeout = newTimeout
|
||||
}
|
||||
|
||||
/*
|
||||
err = foundConfig.ValidateConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*/
|
||||
|
||||
m.SaveConfigToDatabase()
|
||||
|
||||
|
@ -1,34 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
-----END CERTIFICATE-----
|
@ -1,52 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
+iSj2pW0Rbdy7oOcFRF/iM2GwFYc1Q==
|
||||
MIIEwQIBADANBgkqhkiG9w0BAQEFAASCBKswggSnAgEAAoIBAgDFq/dCrgMGigew
|
||||
Zb2b6vR2CMjzfaCtfZzRnowlBCgTsHrB+lQjQ7J9G6BNfJeiJFmAvG5E6CIJSanw
|
||||
VotuXtGy9siYxsI+jcP9FsJPVokI1ctnRrvK9wNhzrChBbidTKSiEd9Pai1y745h
|
||||
LpphFm5cawpMCo9LARdEy2vz3CQ+Bk+ysIPbssUcE6YhxUAEO8UpG6LAdZIeEV9T
|
||||
mfUBkvJh5igs1D19k+/M+mQwP4cwqLgwzCv+8sTQPBTmNJ9UWr5x3rzNWKGn039P
|
||||
vvJqHkQgf0JibibwByzht8vwMAvGdpI3cqYbDonfms5esm8kt2G3jY8NE6ZCtc3y
|
||||
4bzAaQGIGQIDAQABAoIBARA+w8FdH66H5X3fvqdztceFjU5FgtD/Q8YOa6IXJ1wG
|
||||
4u/SLNwBEkgp3xC/Lo8KwbhMxBsxoKp2vVqdIjRd4on8shusKgaODA9esXVnvTdW
|
||||
qrLAI2rYxhRhsi5pk/SJefY/1cRnC3koquDdvZ5BA6zgtyXssD4PxuPGfAa8jtXy
|
||||
GIPUDj/Na+pFf3u6iKGGFA7xmKA+Jx3xL77zRdiG5bS70uTUJIpbJ9fdFDTEwyb+
|
||||
5hy6gmiPZ96bg3LnCl23jBx1RqvZxMxm6nHkEtMStoqczCkDIqypq0GaDD3Op5P9
|
||||
TDVnrz37FQn4PWrq2VoqoKNcigcloBd620dL3p8jVcihAoGBD5yXW+uCGWDEufD0
|
||||
Dvqd5pWD4pXlIP5E6Br9KFaDV0hHEyiJrXk6BzUL0EB7w+8kFafHW2gnvIpS99Tk
|
||||
mI37v/8qGqOerrsru/KtbwXR524LsSFBMXoJ+KtEanlZu+qBvhXsRA2ov/dqO+mb
|
||||
XEpJbcXGMcFuzeTRoss1JpFUcOKlAoGBDKlqhldAjzpWigd29hjK8saoN3nO7XW4
|
||||
O4QjRfnU2M/4pcOZrvu3DyPRfF5et2KAfp6pyYDwFfYWpSzzwblyZQTYCaGT5MCP
|
||||
e3V5ee0dCjdIkB92XGv9xzZLLWClMcoLEEV2knRVY8LdbDAQHxFgOdCkOIWNfw07
|
||||
+BmM72YHHhllAoGBDptOqrxQ/3mg1vBxCUrHTiT6PphMx2/f/OKzlnhLbvC7P1ug
|
||||
ZWSVPIUPRovuwMYRFwnh5s4uz6MEOclBENNXhq4xMLeCEq4hHzrRtpzVZhl6awJY
|
||||
QviSN83Wt2BO6xlgxv8wDgRRrTrKdL//knwW89QlugvnplC/K/fBBRLY1L3ZAoGB
|
||||
BOg3r57rF1c9qLrh4NiU9ugE05MynhbscqxwWzNKyUg4jk2zJvzI4mY4TuHoBVx4
|
||||
fhoRpVWCNpCsEBHO2np7mij5bSogvhvev7M0hAtgINByH+EBpyn3LZieJBT7kMND
|
||||
7GdvX60UVthzpfUumkvKpj11F66yutWvMyT72OAKzCB9AoGBBHixLZSz89STQNNT
|
||||
rYcSDW79Lj18Z6/HBhLwbteMfuYun6HUssh2CKR7awFa/UOmYySiCAV97m38hjDB
|
||||
JC5eMEskRGGrejddtUGjIhNX1hanAkhlnbRwVZc97XvXjryDGBZtaTN/2x4lD59t
|
||||
mKYLZqGfZ+fMnaWoxLrCnn0cjIBK
|
||||
-----END PRIVATE KEY-----
|
@ -2,6 +2,7 @@
|
||||
package websocketproxy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@ -47,15 +48,18 @@ type WebsocketProxy struct {
|
||||
Dialer *websocket.Dialer
|
||||
|
||||
Verbal bool
|
||||
SkipTlsValidation bool
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
// request to the given target.
|
||||
func ProxyHandler(target *url.URL) http.Handler { return NewProxy(target) }
|
||||
func ProxyHandler(target *url.URL, skipTlsValidation bool) http.Handler {
|
||||
return NewProxy(target, skipTlsValidation)
|
||||
}
|
||||
|
||||
// NewProxy returns a new Websocket reverse proxy that rewrites the
|
||||
// URL's to the scheme, host and base path provider in target.
|
||||
func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
|
||||
backend := func(r *http.Request) *url.URL {
|
||||
// Shallow copy
|
||||
u := *target
|
||||
@ -64,7 +68,7 @@ func NewProxy(target *url.URL) *WebsocketProxy {
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend, Verbal: false}
|
||||
return &WebsocketProxy{Backend: backend, Verbal: false, SkipTlsValidation: skipTlsValidation}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
@ -84,8 +88,16 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
dialer := w.Dialer
|
||||
if w.Dialer == nil {
|
||||
if w.SkipTlsValidation {
|
||||
//Disable TLS secure check if target allow skip verification
|
||||
bypassDialer := websocket.DefaultDialer
|
||||
bypassDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
dialer = bypassDialer
|
||||
} else {
|
||||
//Just use the default dialer come with gorilla websocket
|
||||
dialer = DefaultDialer
|
||||
}
|
||||
}
|
||||
|
||||
// Pass headers from the incoming request to the dialer to forward them to
|
||||
// the final destinations.
|
||||
|
@ -28,7 +28,7 @@ func TestProxy(t *testing.T) {
|
||||
}
|
||||
|
||||
u, _ := url.Parse(backendURL)
|
||||
proxy := NewProxy(u)
|
||||
proxy := NewProxy(u, false)
|
||||
proxy.Upgrader = upgrader
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
@ -281,23 +281,123 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
/*
|
||||
ReverseProxyHandleEditEndpoint handles proxy endpoint edit
|
||||
This endpoint do not handle
|
||||
basic auth credential update. The credential
|
||||
will be loaded from old config and reused
|
||||
*/
|
||||
func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
eptype, err := utils.PostPara(r, "type") //Support root, vdir and subd
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "type not defined")
|
||||
return
|
||||
}
|
||||
|
||||
rootNameOrMatchingDomain, err := utils.PostPara(r, "rootname")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Target proxy rule not defined")
|
||||
return
|
||||
}
|
||||
|
||||
endpoint, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "endpoint not defined")
|
||||
return
|
||||
}
|
||||
|
||||
tls, _ := utils.PostPara(r, "tls")
|
||||
if tls == "" {
|
||||
tls = "false"
|
||||
}
|
||||
|
||||
useTLS := (tls == "true")
|
||||
|
||||
stv, _ := utils.PostPara(r, "tlsval")
|
||||
if stv == "" {
|
||||
stv = "false"
|
||||
}
|
||||
|
||||
skipTlsValidation := (stv == "true")
|
||||
|
||||
rba, _ := utils.PostPara(r, "bauth")
|
||||
if rba == "" {
|
||||
rba = "false"
|
||||
}
|
||||
|
||||
requireBasicAuth := (rba == "true")
|
||||
|
||||
//Load the previous basic auth credentials from current proxy rules
|
||||
targetProxyEntry, err := dynamicProxyRouter.LoadProxy(eptype, rootNameOrMatchingDomain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Target proxy config not found or could not be loaded")
|
||||
return
|
||||
}
|
||||
|
||||
if eptype == "vdir" {
|
||||
thisOption := dynamicproxy.VdirOptions{
|
||||
RootName: targetProxyEntry.RootOrMatchingDomain,
|
||||
Domain: endpoint,
|
||||
RequireTLS: useTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
RequireBasicAuth: requireBasicAuth,
|
||||
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
|
||||
}
|
||||
dynamicProxyRouter.AddVirtualDirectoryProxyService(&thisOption)
|
||||
|
||||
} else if eptype == "subd" {
|
||||
thisOption := dynamicproxy.SubdOptions{
|
||||
MatchingDomain: targetProxyEntry.RootOrMatchingDomain,
|
||||
Domain: endpoint,
|
||||
RequireTLS: useTLS,
|
||||
SkipCertValidations: skipTlsValidation,
|
||||
RequireBasicAuth: requireBasicAuth,
|
||||
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
|
||||
}
|
||||
dynamicProxyRouter.AddSubdomainRoutingService(&thisOption)
|
||||
}
|
||||
|
||||
//Save it to file
|
||||
thisProxyConfigRecord := Record{
|
||||
ProxyType: eptype,
|
||||
Rootname: targetProxyEntry.RootOrMatchingDomain,
|
||||
ProxyTarget: endpoint,
|
||||
UseTLS: useTLS,
|
||||
SkipTlsValidation: skipTlsValidation,
|
||||
RequireBasicAuth: requireBasicAuth,
|
||||
BasicAuthCredentials: targetProxyEntry.BasicAuthCredentials,
|
||||
}
|
||||
SaveReverseProxyConfig(&thisProxyConfigRecord)
|
||||
|
||||
//Update the current running config
|
||||
targetProxyEntry.Domain = endpoint
|
||||
targetProxyEntry.RequireTLS = useTLS
|
||||
targetProxyEntry.SkipCertValidations = skipTlsValidation
|
||||
targetProxyEntry.RequireBasicAuth = requireBasicAuth
|
||||
dynamicProxyRouter.SaveProxy(eptype, targetProxyEntry.RootOrMatchingDomain, targetProxyEntry)
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
ep, err := utils.GetPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ep given")
|
||||
return
|
||||
}
|
||||
|
||||
ptype, err := utils.PostPara(r, "ptype")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ptype given")
|
||||
return
|
||||
}
|
||||
|
||||
err = dynamicProxyRouter.RemoveProxy(ptype, ep)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
RemoveReverseProxyConfig(ep)
|
||||
@ -311,6 +411,139 @@ func DeleteProxyEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
/*
|
||||
Handle update request for basic auth credential
|
||||
Require paramter: ep (Endpoint) and pytype (proxy Type)
|
||||
if request with GET, the handler will return current credentials
|
||||
on this endpoint by its username
|
||||
|
||||
if request is POST, the handler will write the results to proxy config
|
||||
*/
|
||||
func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
ep, err := utils.GetPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ep given")
|
||||
return
|
||||
}
|
||||
|
||||
ptype, err := utils.GetPara(r, "ptype")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ptype given")
|
||||
return
|
||||
}
|
||||
|
||||
//Load the target proxy object from router
|
||||
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
usernames := []string{}
|
||||
for _, cred := range targetProxy.BasicAuthCredentials {
|
||||
usernames = append(usernames, cred.Username)
|
||||
}
|
||||
|
||||
js, _ := json.Marshal(usernames)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
} else if r.Method == http.MethodPost {
|
||||
//Write to target
|
||||
ep, err := utils.PostPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ep given")
|
||||
return
|
||||
}
|
||||
|
||||
ptype, err := utils.PostPara(r, "ptype")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ptype given")
|
||||
return
|
||||
}
|
||||
|
||||
if ptype != "vdir" && ptype != "subd" {
|
||||
utils.SendErrorResponse(w, "Invalid ptype given")
|
||||
return
|
||||
}
|
||||
|
||||
creds, err := utils.PostPara(r, "creds")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ptype given")
|
||||
return
|
||||
}
|
||||
|
||||
//Load the target proxy object from router
|
||||
targetProxy, err := dynamicProxyRouter.LoadProxy(ptype, ep)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
//Try to marshal the content of creds into the suitable structure
|
||||
newCredentials := []*dynamicproxy.BasicAuthUnhashedCredentials{}
|
||||
err = json.Unmarshal([]byte(creds), &newCredentials)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Malformed credential data")
|
||||
return
|
||||
}
|
||||
|
||||
//Merge the credentials into the original config
|
||||
//If a new username exists in old config with no pw given, keep the old pw hash
|
||||
//If a new username is found with new password, hash it and push to credential slice
|
||||
mergedCredentials := []*dynamicproxy.BasicAuthCredentials{}
|
||||
for _, credential := range newCredentials {
|
||||
if credential.Password == "" {
|
||||
//Check if exists in the old credential files
|
||||
keepUnchange := false
|
||||
for _, oldCredEntry := range targetProxy.BasicAuthCredentials {
|
||||
if oldCredEntry.Username == credential.Username {
|
||||
//Exists! Reuse the old hash
|
||||
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
|
||||
Username: oldCredEntry.Username,
|
||||
PasswordHash: oldCredEntry.PasswordHash,
|
||||
})
|
||||
keepUnchange = true
|
||||
}
|
||||
}
|
||||
|
||||
if !keepUnchange {
|
||||
//This is a new username with no pw given
|
||||
utils.SendErrorResponse(w, "Access password for "+credential.Username+" is empty!")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
//This username have given password
|
||||
mergedCredentials = append(mergedCredentials, &dynamicproxy.BasicAuthCredentials{
|
||||
Username: credential.Username,
|
||||
PasswordHash: auth.Hash(credential.Password),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
targetProxy.BasicAuthCredentials = mergedCredentials
|
||||
|
||||
//Save it to file
|
||||
thisProxyConfigRecord := Record{
|
||||
ProxyType: ptype,
|
||||
Rootname: targetProxy.RootOrMatchingDomain,
|
||||
ProxyTarget: targetProxy.Domain,
|
||||
UseTLS: targetProxy.RequireTLS,
|
||||
SkipTlsValidation: targetProxy.SkipCertValidations,
|
||||
RequireBasicAuth: targetProxy.RequireBasicAuth,
|
||||
BasicAuthCredentials: targetProxy.BasicAuthCredentials,
|
||||
}
|
||||
SaveReverseProxyConfig(&thisProxyConfigRecord)
|
||||
|
||||
//Replace runtime configuration
|
||||
dynamicProxyRouter.SaveProxy(ptype, ep, targetProxy)
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
http.Error(w, "invalid usage", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func ReverseProxyStatus(w http.ResponseWriter, r *http.Request) {
|
||||
js, _ := json.Marshal(dynamicProxyRouter)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
1273
src/web/components/access.html
Normal file
1273
src/web/components/access.html
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,714 +0,0 @@
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
<h2>Blacklist</h2>
|
||||
<p>Setup blacklist based on estimated IP geographic location or IP address</p>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" id="enableBlacklist">
|
||||
<label>Enable Blacklist</label>
|
||||
</div>
|
||||
<div id="toggleSucc" style="float: right; display:none; color: #2abd4d;" >
|
||||
<i class="ui green checkmark icon"></i> Setting Saved
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="info circle icon"></i> Blacklist function require complex checking logic to validate each incoming request. Not recommend enabling this feature on servers with low end hardware.
|
||||
</div>
|
||||
<h4>Country Blacklist</h4>
|
||||
<p><i class="yellow exclamation triangle icon"></i>
|
||||
This will block all requests from the selected country. The requester's location is estimated from their IP address and may not be 100% accurate.</p>
|
||||
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Select Country</label>
|
||||
<div id="countrySelector" class="ui fluid search selection dropdown">
|
||||
<input type="hidden" name="country">
|
||||
<i class="dropdown icon"></i>
|
||||
<div class="default text">Select Country</div>
|
||||
<div class="menu">
|
||||
<div class="item" data-value="af"><i class="af flag"></i>Afghanistan</div>
|
||||
<div class="item" data-value="ax"><i class="ax flag"></i>Aland Islands</div>
|
||||
<div class="item" data-value="al"><i class="al flag"></i>Albania</div>
|
||||
<div class="item" data-value="dz"><i class="dz flag"></i>Algeria</div>
|
||||
<div class="item" data-value="as"><i class="as flag"></i>American Samoa</div>
|
||||
<div class="item" data-value="ad"><i class="ad flag"></i>Andorra</div>
|
||||
<div class="item" data-value="ao"><i class="ao flag"></i>Angola</div>
|
||||
<div class="item" data-value="ai"><i class="ai flag"></i>Anguilla</div>
|
||||
<div class="item" data-value="ag"><i class="ag flag"></i>Antigua</div>
|
||||
<div class="item" data-value="ar"><i class="ar flag"></i>Argentina</div>
|
||||
<div class="item" data-value="am"><i class="am flag"></i>Armenia</div>
|
||||
<div class="item" data-value="aw"><i class="aw flag"></i>Aruba</div>
|
||||
<div class="item" data-value="au"><i class="au flag"></i>Australia</div>
|
||||
<div class="item" data-value="at"><i class="at flag"></i>Austria</div>
|
||||
<div class="item" data-value="az"><i class="az flag"></i>Azerbaijan</div>
|
||||
<div class="item" data-value="bs"><i class="bs flag"></i>Bahamas</div>
|
||||
<div class="item" data-value="bh"><i class="bh flag"></i>Bahrain</div>
|
||||
<div class="item" data-value="bd"><i class="bd flag"></i>Bangladesh</div>
|
||||
<div class="item" data-value="bb"><i class="bb flag"></i>Barbados</div>
|
||||
<div class="item" data-value="by"><i class="by flag"></i>Belarus</div>
|
||||
<div class="item" data-value="be"><i class="be flag"></i>Belgium</div>
|
||||
<div class="item" data-value="bz"><i class="bz flag"></i>Belize</div>
|
||||
<div class="item" data-value="bj"><i class="bj flag"></i>Benin</div>
|
||||
<div class="item" data-value="bm"><i class="bm flag"></i>Bermuda</div>
|
||||
<div class="item" data-value="bt"><i class="bt flag"></i>Bhutan</div>
|
||||
<div class="item" data-value="bo"><i class="bo flag"></i>Bolivia</div>
|
||||
<div class="item" data-value="ba"><i class="ba flag"></i>Bosnia</div>
|
||||
<div class="item" data-value="bw"><i class="bw flag"></i>Botswana</div>
|
||||
<div class="item" data-value="bv"><i class="bv flag"></i>Bouvet Island</div>
|
||||
<div class="item" data-value="br"><i class="br flag"></i>Brazil</div>
|
||||
<div class="item" data-value="vg"><i class="vg flag"></i>British Virgin Islands</div>
|
||||
<div class="item" data-value="bn"><i class="bn flag"></i>Brunei</div>
|
||||
<div class="item" data-value="bg"><i class="bg flag"></i>Bulgaria</div>
|
||||
<div class="item" data-value="bf"><i class="bf flag"></i>Burkina Faso</div>
|
||||
<div class="item" data-value="mm"><i class="mm flag"></i>Burma</div>
|
||||
<div class="item" data-value="bi"><i class="bi flag"></i>Burundi</div>
|
||||
<div class="item" data-value="tc"><i class="tc flag"></i>Caicos Islands</div>
|
||||
<div class="item" data-value="kh"><i class="kh flag"></i>Cambodia</div>
|
||||
<div class="item" data-value="cm"><i class="cm flag"></i>Cameroon</div>
|
||||
<div class="item" data-value="ca"><i class="ca flag"></i>Canada</div>
|
||||
<div class="item" data-value="cv"><i class="cv flag"></i>Cape Verde</div>
|
||||
<div class="item" data-value="ky"><i class="ky flag"></i>Cayman Islands</div>
|
||||
<div class="item" data-value="cf"><i class="cf flag"></i>Central African Republic</div>
|
||||
<div class="item" data-value="td"><i class="td flag"></i>Chad</div>
|
||||
<div class="item" data-value="cl"><i class="cl flag"></i>Chile</div>
|
||||
<div class="item" data-value="cn"><i class="cn flag"></i>China</div>
|
||||
<div class="item" data-value="cx"><i class="cx flag"></i>Christmas Island</div>
|
||||
<div class="item" data-value="cc"><i class="cc flag"></i>Cocos Islands</div>
|
||||
<div class="item" data-value="co"><i class="co flag"></i>Colombia</div>
|
||||
<div class="item" data-value="km"><i class="km flag"></i>Comoros</div>
|
||||
<div class="item" data-value="cg"><i class="cg flag"></i>Congo Brazzaville</div>
|
||||
<div class="item" data-value="cd"><i class="cd flag"></i>Congo</div>
|
||||
<div class="item" data-value="ck"><i class="ck flag"></i>Cook Islands</div>
|
||||
<div class="item" data-value="cr"><i class="cr flag"></i>Costa Rica</div>
|
||||
<div class="item" data-value="ci"><i class="ci flag"></i>Cote Divoire</div>
|
||||
<div class="item" data-value="hr"><i class="hr flag"></i>Croatia</div>
|
||||
<div class="item" data-value="cu"><i class="cu flag"></i>Cuba</div>
|
||||
<div class="item" data-value="cy"><i class="cy flag"></i>Cyprus</div>
|
||||
<div class="item" data-value="cz"><i class="cz flag"></i>Czech Republic</div>
|
||||
<div class="item" data-value="dk"><i class="dk flag"></i>Denmark</div>
|
||||
<div class="item" data-value="dj"><i class="dj flag"></i>Djibouti</div>
|
||||
<div class="item" data-value="dm"><i class="dm flag"></i>Dominica</div>
|
||||
<div class="item" data-value="do"><i class="do flag"></i>Dominican Republic</div>
|
||||
<div class="item" data-value="ec"><i class="ec flag"></i>Ecuador</div>
|
||||
<div class="item" data-value="eg"><i class="eg flag"></i>Egypt</div>
|
||||
<div class="item" data-value="sv"><i class="sv flag"></i>El Salvador</div>
|
||||
<div class="item" data-value="gb"><i class="gb flag"></i>England</div>
|
||||
<div class="item" data-value="gq"><i class="gq flag"></i>Equatorial Guinea</div>
|
||||
<div class="item" data-value="er"><i class="er flag"></i>Eritrea</div>
|
||||
<div class="item" data-value="ee"><i class="ee flag"></i>Estonia</div>
|
||||
<div class="item" data-value="et"><i class="et flag"></i>Ethiopia</div>
|
||||
<div class="item" data-value="eu"><i class="eu flag"></i>European Union</div>
|
||||
<div class="item" data-value="fk"><i class="fk flag"></i>Falkland Islands</div>
|
||||
<div class="item" data-value="fo"><i class="fo flag"></i>Faroe Islands</div>
|
||||
<div class="item" data-value="fj"><i class="fj flag"></i>Fiji</div>
|
||||
<div class="item" data-value="fi"><i class="fi flag"></i>Finland</div>
|
||||
<div class="item" data-value="fr"><i class="fr flag"></i>France</div>
|
||||
<div class="item" data-value="gf"><i class="gf flag"></i>French Guiana</div>
|
||||
<div class="item" data-value="pf"><i class="pf flag"></i>French Polynesia</div>
|
||||
<div class="item" data-value="tf"><i class="tf flag"></i>French Territories</div>
|
||||
<div class="item" data-value="ga"><i class="ga flag"></i>Gabon</div>
|
||||
<div class="item" data-value="gm"><i class="gm flag"></i>Gambia</div>
|
||||
<div class="item" data-value="ge"><i class="ge flag"></i>Georgia</div>
|
||||
<div class="item" data-value="de"><i class="de flag"></i>Germany</div>
|
||||
<div class="item" data-value="gh"><i class="gh flag"></i>Ghana</div>
|
||||
<div class="item" data-value="gi"><i class="gi flag"></i>Gibraltar</div>
|
||||
<div class="item" data-value="gr"><i class="gr flag"></i>Greece</div>
|
||||
<div class="item" data-value="gl"><i class="gl flag"></i>Greenland</div>
|
||||
<div class="item" data-value="gd"><i class="gd flag"></i>Grenada</div>
|
||||
<div class="item" data-value="gp"><i class="gp flag"></i>Guadeloupe</div>
|
||||
<div class="item" data-value="gu"><i class="gu flag"></i>Guam</div>
|
||||
<div class="item" data-value="gt"><i class="gt flag"></i>Guatemala</div>
|
||||
<div class="item" data-value="gw"><i class="gw flag"></i>Guinea-Bissau</div>
|
||||
<div class="item" data-value="gn"><i class="gn flag"></i>Guinea</div>
|
||||
<div class="item" data-value="gy"><i class="gy flag"></i>Guyana</div>
|
||||
<div class="item" data-value="ht"><i class="ht flag"></i>Haiti</div>
|
||||
<div class="item" data-value="hm"><i class="hm flag"></i>Heard Island</div>
|
||||
<div class="item" data-value="hn"><i class="hn flag"></i>Honduras</div>
|
||||
<div class="item" data-value="hk"><i class="hk flag"></i>Hong Kong</div>
|
||||
<div class="item" data-value="hu"><i class="hu flag"></i>Hungary</div>
|
||||
<div class="item" data-value="is"><i class="is flag"></i>Iceland</div>
|
||||
<div class="item" data-value="in"><i class="in flag"></i>India</div>
|
||||
<div class="item" data-value="io"><i class="io flag"></i>Indian Ocean Territory</div>
|
||||
<div class="item" data-value="id"><i class="id flag"></i>Indonesia</div>
|
||||
<div class="item" data-value="ir"><i class="ir flag"></i>Iran</div>
|
||||
<div class="item" data-value="iq"><i class="iq flag"></i>Iraq</div>
|
||||
<div class="item" data-value="ie"><i class="ie flag"></i>Ireland</div>
|
||||
<div class="item" data-value="il"><i class="il flag"></i>Israel</div>
|
||||
<div class="item" data-value="it"><i class="it flag"></i>Italy</div>
|
||||
<div class="item" data-value="jm"><i class="jm flag"></i>Jamaica</div>
|
||||
<div class="item" data-value="jp"><i class="jp flag"></i>Japan</div>
|
||||
<div class="item" data-value="jo"><i class="jo flag"></i>Jordan</div>
|
||||
<div class="item" data-value="kz"><i class="kz flag"></i>Kazakhstan</div>
|
||||
<div class="item" data-value="ke"><i class="ke flag"></i>Kenya</div>
|
||||
<div class="item" data-value="ki"><i class="ki flag"></i>Kiribati</div>
|
||||
<div class="item" data-value="kw"><i class="kw flag"></i>Kuwait</div>
|
||||
<div class="item" data-value="kg"><i class="kg flag"></i>Kyrgyzstan</div>
|
||||
<div class="item" data-value="la"><i class="la flag"></i>Laos</div>
|
||||
<div class="item" data-value="lv"><i class="lv flag"></i>Latvia</div>
|
||||
<div class="item" data-value="lb"><i class="lb flag"></i>Lebanon</div>
|
||||
<div class="item" data-value="ls"><i class="ls flag"></i>Lesotho</div>
|
||||
<div class="item" data-value="lr"><i class="lr flag"></i>Liberia</div>
|
||||
<div class="item" data-value="ly"><i class="ly flag"></i>Libya</div>
|
||||
<div class="item" data-value="li"><i class="li flag"></i>Liechtenstein</div>
|
||||
<div class="item" data-value="lt"><i class="lt flag"></i>Lithuania</div>
|
||||
<div class="item" data-value="lu"><i class="lu flag"></i>Luxembourg</div>
|
||||
<div class="item" data-value="mo"><i class="mo flag"></i>Macau</div>
|
||||
<div class="item" data-value="mk"><i class="mk flag"></i>Macedonia</div>
|
||||
<div class="item" data-value="mg"><i class="mg flag"></i>Madagascar</div>
|
||||
<div class="item" data-value="mw"><i class="mw flag"></i>Malawi</div>
|
||||
<div class="item" data-value="my"><i class="my flag"></i>Malaysia</div>
|
||||
<div class="item" data-value="mv"><i class="mv flag"></i>Maldives</div>
|
||||
<div class="item" data-value="ml"><i class="ml flag"></i>Mali</div>
|
||||
<div class="item" data-value="mt"><i class="mt flag"></i>Malta</div>
|
||||
<div class="item" data-value="mh"><i class="mh flag"></i>Marshall Islands</div>
|
||||
<div class="item" data-value="mq"><i class="mq flag"></i>Martinique</div>
|
||||
<div class="item" data-value="mr"><i class="mr flag"></i>Mauritania</div>
|
||||
<div class="item" data-value="mu"><i class="mu flag"></i>Mauritius</div>
|
||||
<div class="item" data-value="yt"><i class="yt flag"></i>Mayotte</div>
|
||||
<div class="item" data-value="mx"><i class="mx flag"></i>Mexico</div>
|
||||
<div class="item" data-value="fm"><i class="fm flag"></i>Micronesia</div>
|
||||
<div class="item" data-value="md"><i class="md flag"></i>Moldova</div>
|
||||
<div class="item" data-value="mc"><i class="mc flag"></i>Monaco</div>
|
||||
<div class="item" data-value="mn"><i class="mn flag"></i>Mongolia</div>
|
||||
<div class="item" data-value="me"><i class="me flag"></i>Montenegro</div>
|
||||
<div class="item" data-value="ms"><i class="ms flag"></i>Montserrat</div>
|
||||
<div class="item" data-value="ma"><i class="ma flag"></i>Morocco</div>
|
||||
<div class="item" data-value="mz"><i class="mz flag"></i>Mozambique</div>
|
||||
<div class="item" data-value="na"><i class="na flag"></i>Namibia</div>
|
||||
<div class="item" data-value="nr"><i class="nr flag"></i>Nauru</div>
|
||||
<div class="item" data-value="np"><i class="np flag"></i>Nepal</div>
|
||||
<div class="item" data-value="an"><i class="an flag"></i>Netherlands Antilles</div>
|
||||
<div class="item" data-value="nl"><i class="nl flag"></i>Netherlands</div>
|
||||
<div class="item" data-value="nc"><i class="nc flag"></i>New Caledonia</div>
|
||||
<div class="item" data-value="pg"><i class="pg flag"></i>New Guinea</div>
|
||||
<div class="item" data-value="nz"><i class="nz flag"></i>New Zealand</div>
|
||||
<div class="item" data-value="ni"><i class="ni flag"></i>Nicaragua</div>
|
||||
<div class="item" data-value="ne"><i class="ne flag"></i>Niger</div>
|
||||
<div class="item" data-value="ng"><i class="ng flag"></i>Nigeria</div>
|
||||
<div class="item" data-value="nu"><i class="nu flag"></i>Niue</div>
|
||||
<div class="item" data-value="nf"><i class="nf flag"></i>Norfolk Island</div>
|
||||
<div class="item" data-value="kp"><i class="kp flag"></i>North Korea</div>
|
||||
<div class="item" data-value="mp"><i class="mp flag"></i>Northern Mariana Islands</div>
|
||||
<div class="item" data-value="no"><i class="no flag"></i>Norway</div>
|
||||
<div class="item" data-value="om"><i class="om flag"></i>Oman</div>
|
||||
<div class="item" data-value="pk"><i class="pk flag"></i>Pakistan</div>
|
||||
<div class="item" data-value="pw"><i class="pw flag"></i>Palau</div>
|
||||
<div class="item" data-value="ps"><i class="ps flag"></i>Palestine</div>
|
||||
<div class="item" data-value="pa"><i class="pa flag"></i>Panama</div>
|
||||
<div class="item" data-value="py"><i class="py flag"></i>Paraguay</div>
|
||||
<div class="item" data-value="pe"><i class="pe flag"></i>Peru</div>
|
||||
<div class="item" data-value="ph"><i class="ph flag"></i>Philippines</div>
|
||||
<div class="item" data-value="pn"><i class="pn flag"></i>Pitcairn Islands</div>
|
||||
<div class="item" data-value="pl"><i class="pl flag"></i>Poland</div>
|
||||
<div class="item" data-value="pt"><i class="pt flag"></i>Portugal</div>
|
||||
<div class="item" data-value="pr"><i class="pr flag"></i>Puerto Rico</div>
|
||||
<div class="item" data-value="qa"><i class="qa flag"></i>Qatar</div>
|
||||
<div class="item" data-value="re"><i class="re flag"></i>Reunion</div>
|
||||
<div class="item" data-value="ro"><i class="ro flag"></i>Romania</div>
|
||||
<div class="item" data-value="ru"><i class="ru flag"></i>Russia</div>
|
||||
<div class="item" data-value="rw"><i class="rw flag"></i>Rwanda</div>
|
||||
<div class="item" data-value="sh"><i class="sh flag"></i>Saint Helena</div>
|
||||
<div class="item" data-value="kn"><i class="kn flag"></i>Saint Kitts and Nevis</div>
|
||||
<div class="item" data-value="lc"><i class="lc flag"></i>Saint Lucia</div>
|
||||
<div class="item" data-value="pm"><i class="pm flag"></i>Saint Pierre</div>
|
||||
<div class="item" data-value="vc"><i class="vc flag"></i>Saint Vincent</div>
|
||||
<div class="item" data-value="ws"><i class="ws flag"></i>Samoa</div>
|
||||
<div class="item" data-value="sm"><i class="sm flag"></i>San Marino</div>
|
||||
<div class="item" data-value="gs"><i class="gs flag"></i>Sandwich Islands</div>
|
||||
<div class="item" data-value="st"><i class="st flag"></i>Sao Tome</div>
|
||||
<div class="item" data-value="sa"><i class="sa flag"></i>Saudi Arabia</div>
|
||||
<div class="item" data-value="sn"><i class="sn flag"></i>Senegal</div>
|
||||
<div class="item" data-value="cs"><i class="cs flag"></i>Serbia</div>
|
||||
<div class="item" data-value="rs"><i class="rs flag"></i>Serbia</div>
|
||||
<div class="item" data-value="sc"><i class="sc flag"></i>Seychelles</div>
|
||||
<div class="item" data-value="sl"><i class="sl flag"></i>Sierra Leone</div>
|
||||
<div class="item" data-value="sg"><i class="sg flag"></i>Singapore</div>
|
||||
<div class="item" data-value="sk"><i class="sk flag"></i>Slovakia</div>
|
||||
<div class="item" data-value="si"><i class="si flag"></i>Slovenia</div>
|
||||
<div class="item" data-value="sb"><i class="sb flag"></i>Solomon Islands</div>
|
||||
<div class="item" data-value="so"><i class="so flag"></i>Somalia</div>
|
||||
<div class="item" data-value="za"><i class="za flag"></i>South Africa</div>
|
||||
<div class="item" data-value="kr"><i class="kr flag"></i>South Korea</div>
|
||||
<div class="item" data-value="es"><i class="es flag"></i>Spain</div>
|
||||
<div class="item" data-value="lk"><i class="lk flag"></i>Sri Lanka</div>
|
||||
<div class="item" data-value="sd"><i class="sd flag"></i>Sudan</div>
|
||||
<div class="item" data-value="sr"><i class="sr flag"></i>Suriname</div>
|
||||
<div class="item" data-value="sj"><i class="sj flag"></i>Svalbard</div>
|
||||
<div class="item" data-value="sz"><i class="sz flag"></i>Swaziland</div>
|
||||
<div class="item" data-value="se"><i class="se flag"></i>Sweden</div>
|
||||
<div class="item" data-value="ch"><i class="ch flag"></i>Switzerland</div>
|
||||
<div class="item" data-value="sy"><i class="sy flag"></i>Syria</div>
|
||||
<div class="item" data-value="tw"><i class="tw flag"></i>Taiwan</div>
|
||||
<div class="item" data-value="tj"><i class="tj flag"></i>Tajikistan</div>
|
||||
<div class="item" data-value="tz"><i class="tz flag"></i>Tanzania</div>
|
||||
<div class="item" data-value="th"><i class="th flag"></i>Thailand</div>
|
||||
<div class="item" data-value="tl"><i class="tl flag"></i>Timorleste</div>
|
||||
<div class="item" data-value="tg"><i class="tg flag"></i>Togo</div>
|
||||
<div class="item" data-value="tk"><i class="tk flag"></i>Tokelau</div>
|
||||
<div class="item" data-value="to"><i class="to flag"></i>Tonga</div>
|
||||
<div class="item" data-value="tt"><i class="tt flag"></i>Trinidad</div>
|
||||
<div class="item" data-value="tn"><i class="tn flag"></i>Tunisia</div>
|
||||
<div class="item" data-value="tr"><i class="tr flag"></i>Turkey</div>
|
||||
<div class="item" data-value="tm"><i class="tm flag"></i>Turkmenistan</div>
|
||||
<div class="item" data-value="tv"><i class="tv flag"></i>Tuvalu</div>
|
||||
<div class="item" data-value="ug"><i class="ug flag"></i>Uganda</div>
|
||||
<div class="item" data-value="ua"><i class="ua flag"></i>Ukraine</div>
|
||||
<div class="item" data-value="ae"><i class="ae flag"></i>United Arab Emirates</div>
|
||||
<div class="item" data-value="us"><i class="us flag"></i>United States</div>
|
||||
<div class="item" data-value="uy"><i class="uy flag"></i>Uruguay</div>
|
||||
<div class="item" data-value="um"><i class="um flag"></i>Us Minor Islands</div>
|
||||
<div class="item" data-value="vi"><i class="vi flag"></i>Us Virgin Islands</div>
|
||||
<div class="item" data-value="uz"><i class="uz flag"></i>Uzbekistan</div>
|
||||
<div class="item" data-value="vu"><i class="vu flag"></i>Vanuatu</div>
|
||||
<div class="item" data-value="va"><i class="va flag"></i>Vatican City</div>
|
||||
<div class="item" data-value="ve"><i class="ve flag"></i>Venezuela</div>
|
||||
<div class="item" data-value="vn"><i class="vn flag"></i>Vietnam</div>
|
||||
<div class="item" data-value="wf"><i class="wf flag"></i>Wallis and Futuna</div>
|
||||
<div class="item" data-value="eh"><i class="eh flag"></i>Western Sahara</div>
|
||||
<div class="item" data-value="ye"><i class="ye flag"></i>Yemen</div>
|
||||
<div class="item" data-value="zm"><i class="zm flag"></i>Zambia</div>
|
||||
<div class="item" data-value="zw"><i class="zw flag"></i>Zimbabwe</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic red button" id="ban-btn" onclick="addCountryToBlacklist();"><i class="ui red ban icon"></i> Blacklist Country</button>
|
||||
</div>
|
||||
<table class="ui unstackable basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ISO Code</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="banned-list">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>IP Blacklist</h4>
|
||||
<p>Black a certain IP or IP range</p>
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>IP Address</label>
|
||||
<input id="ipAddressInput" type="text" placeholder="IP Address">
|
||||
</div>
|
||||
<button id="addIpButton" onclick="addIpBlacklist();" class="ui basic red icon button">
|
||||
<i class="ban icon"></i> Blacklist IP
|
||||
</button>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<i class="ui info circle icon"></i> IP Address support the following formats
|
||||
<div class="ui bulleted list">
|
||||
<div class="item">Fixed IP Address (e.g. 192.128.4.100)</div>
|
||||
<div class="item">IP Wildcard (e.g. 172.164.*.*)</div>
|
||||
<div class="item">CIDR String (e.g. 128.32.0.1/16)</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="ui unstackable basic celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Remove</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="blacklistIpTable">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<h4>Visitor IP list</h4>
|
||||
<button style="margin-top: -1em;" onclick="initBlacklistQuickBanTable();" class="ui green small right floated circular basic icon button"><i class="ui refresh icon"></i></button>
|
||||
<p>Observe strange traffic on your sites? Ban them in the list below.</p>
|
||||
|
||||
<table class="ui celled unstackable table" id="ipTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th>Access Count</th>
|
||||
<th>Blacklist</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pagination"></div>
|
||||
</div>
|
||||
<script>
|
||||
$(".dropdown").dropdown();
|
||||
|
||||
function filterCountries(codesToShow) {
|
||||
// get all items in the dropdown
|
||||
const items = document.querySelectorAll('.ui.fluid.search.selection.dropdown .menu .item');
|
||||
// loop through all items
|
||||
items.forEach(item => {
|
||||
// get the value of the item (i.e. the country code)
|
||||
const code = item.dataset.value;
|
||||
// if the code is in the array of codes to show, show the item
|
||||
if (codesToShow.includes(code)) {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
// otherwise, hide the item
|
||||
else {
|
||||
item.style.display = 'block';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Get the country name from the dropdown
|
||||
function getCountryName(countryCode) {
|
||||
return $('#countrySelector .item[data-value="' + countryCode.toLowerCase() + '"]').text().trim();
|
||||
}
|
||||
|
||||
function initBannedCountryList(){
|
||||
$.get("/api/blacklist/list?type=country", function(data) {
|
||||
let bannedListHtml = '';
|
||||
data.forEach((countryCode) => {
|
||||
bannedListHtml += `
|
||||
<tr>
|
||||
<td><i class="${countryCode} flag"></i> ${getCountryName(countryCode)} (${countryCode.toUpperCase()})</td>
|
||||
<td><button class="ui red basic mini icon button" onclick="removeFromBannedList('${countryCode}')"><i class="trash icon"></i></button></td>
|
||||
</tr>
|
||||
`;
|
||||
});
|
||||
$('#banned-list').html(bannedListHtml);
|
||||
filterCountries(data);
|
||||
if (data.length === 0) {
|
||||
$('#banned-list').append(`
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<i class="green check circle icon"></i>
|
||||
There are no blacklisted countries
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
initBannedCountryList();
|
||||
|
||||
function addCountryToBlacklist() {
|
||||
var countryCode = $("#countrySelector").dropdown("get value").toLowerCase();
|
||||
$('#countrySelector').dropdown('clear');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "/api/blacklist/country/add",
|
||||
data: { cc: countryCode },
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
alert(response.error);
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
// handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeFromBannedList(countryCode){
|
||||
if (confirm("Confirm removing " + getCountryName(countryCode) + " from blacklist?")){
|
||||
countryCode = countryCode.toLowerCase();
|
||||
$.ajax({
|
||||
url: "/api/blacklist/country/remove",
|
||||
method: "POST",
|
||||
data: { cc: countryCode },
|
||||
success: function(response) {
|
||||
if (response.error != undefined){
|
||||
alert(response.error);
|
||||
}
|
||||
initBannedCountryList();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error("Error removing country from blacklist: " + error);
|
||||
// Handle error response
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initIpBanTable(){
|
||||
$.get('/api/blacklist/list?type=ip', function(data) {
|
||||
$('#blacklistIpTable').html("");
|
||||
if (data.length === 0) {
|
||||
$('#blacklistIpTable').append(`
|
||||
<tr>
|
||||
<td colspan="2"><i class="green check circle icon"></i>There are no blacklisted IP addresses</td>
|
||||
</tr>
|
||||
`);
|
||||
} else {
|
||||
$.each(data, function(index, ip) {
|
||||
let icon = "globe icon";
|
||||
if (isLAN(ip)){
|
||||
icon = "desktop icon";
|
||||
}else if (isHomeAddr(ip)){
|
||||
icon = "home icon";
|
||||
}
|
||||
$('#blacklistIpTable').append(`
|
||||
<tr class="blacklistItem" ip="${encodeURIComponent(ip)}">
|
||||
<td><i class="${icon}"></i> ${ip}</td>
|
||||
<td><button class="ui icon basic mini red button" onclick="removeIpBlacklist('${ip}');"><i class="trash alternate icon"></i></button></td>
|
||||
</tr>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
initBlacklistQuickBanTable();
|
||||
});
|
||||
}
|
||||
initIpBanTable();
|
||||
|
||||
//Check if a input is a valid IP address, wildcard of a IP address or a CIDR string
|
||||
function isValidIpFilter(input) {
|
||||
// Check if input is a valid IP address
|
||||
const isValidIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/.test(input);
|
||||
|
||||
if (isValidIp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a wildcard IP address
|
||||
const isValidWildcardIp = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])\.([01]?[0-9]?[0-9]|\*|\*\/[0-9]|[01]?[0-9]?[0-9]-[01]?[0-9]?[0-9]|\[\d+,\d+\])$/.test(input);
|
||||
|
||||
if (isValidWildcardIp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if input is a valid CIDR address string
|
||||
const isValidCidr = /^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\/([0-9]|[1-2][0-9]|3[0-2])$/.test(input);
|
||||
|
||||
if (isValidCidr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Input is not a valid IP address, wildcard IP address, or CIDR address string
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#ipAddressInput").on("input", function() {
|
||||
$(this).val($(this).val().trim());
|
||||
var ipAddress = $(this).val();
|
||||
if (!isValidIpFilter(ipAddress)) {
|
||||
$(this).parent().addClass("error");
|
||||
} else {
|
||||
$(this).parent().removeClass("error");
|
||||
}
|
||||
});
|
||||
|
||||
function isLAN(ipAddress) {
|
||||
function ip2long(ipAddress) {
|
||||
// Convert the IP address to a 32-bit integer
|
||||
const parts = ipAddress.split(".");
|
||||
return (
|
||||
(parseInt(parts[0]) << 24) |
|
||||
(parseInt(parts[1]) << 16) |
|
||||
(parseInt(parts[2]) << 8) |
|
||||
parseInt(parts[3])
|
||||
);
|
||||
}
|
||||
|
||||
// Define the LAN IP address ranges
|
||||
const LAN_RANGES = [
|
||||
{ start: "10.0.0.0", end: "10.255.255.255" },
|
||||
{ start: "172.16.0.0", end: "172.31.255.255" },
|
||||
{ start: "192.168.0.0", end: "192.168.255.255" }
|
||||
];
|
||||
|
||||
// Check if the IP address is within any of the LAN ranges
|
||||
for (let i = 0; i < LAN_RANGES.length; i++) {
|
||||
const rangeStart = ip2long(LAN_RANGES[i].start);
|
||||
const rangeEnd = ip2long(LAN_RANGES[i].end);
|
||||
const ipAddressLong = ip2long(ipAddress);
|
||||
|
||||
if (ipAddressLong >= rangeStart && ipAddressLong <= rangeEnd) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isHomeAddr(ipAddress) {
|
||||
const specialIpAddresses = ['0.0.0.0', '127.0.0.1', '::1'];
|
||||
return specialIpAddresses.includes(ipAddress);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function addIpBlacklist(){
|
||||
let targetIp = $("#ipAddressInput").val().trim();
|
||||
if (targetIp == ""){
|
||||
alert("IP address is empty")
|
||||
return
|
||||
}
|
||||
if (!isValidIpFilter(targetIp)){
|
||||
if (!confirm("This doesn't seems like a valid IP address. Continue anyway?")){
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: "/api/blacklist/ip/add",
|
||||
type: "POST",
|
||||
data: {ip: targetIp.toLowerCase()},
|
||||
success: function(response) {
|
||||
if (response.error !== undefined) {
|
||||
alert(response.error);
|
||||
} else {
|
||||
initIpBanTable();
|
||||
}
|
||||
|
||||
$("#ipAddressInput").val("");
|
||||
$("#ipAddressInput").parent().remvoeClass("error");
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to add IP address to blacklist.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeIpBlacklist(ipaddr){
|
||||
if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){
|
||||
$.ajax({
|
||||
url: "/api/blacklist/ip/remove",
|
||||
type: "POST",
|
||||
data: {ip: ipaddr.toLowerCase()},
|
||||
success: function(response) {
|
||||
if (response.error !== undefined) {
|
||||
alert(response.error);
|
||||
} else {
|
||||
initIpBanTable();
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
alert("Failed to remove IP address from blacklist.");
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//function to check for blacklist enable
|
||||
function enableBlacklist() {
|
||||
var isChecked = $('#enableBlacklist').is(':checked');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/blacklist/enable',
|
||||
data: { enable: isChecked },
|
||||
success: function(data){
|
||||
$("#toggleSucc").stop().finish().fadeIn("fast").delay(3000).fadeOut("fast");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initBlacklistEnableState(){
|
||||
$.get('/api/blacklist/enable', function(data){
|
||||
if (data == true){
|
||||
$('#enableBlacklist').parent().checkbox("set checked");
|
||||
}
|
||||
|
||||
//Register on change event
|
||||
$("#enableBlacklist").on("change", function(){
|
||||
enableBlacklist();
|
||||
})
|
||||
});
|
||||
}
|
||||
initBlacklistEnableState();
|
||||
|
||||
//Load the summary to ip access table
|
||||
function initBlacklistQuickBanTable(){
|
||||
$.get("/api/stats/summary", function(data){
|
||||
initIpAccessTable(data.RequestClientIp);
|
||||
})
|
||||
}
|
||||
initBlacklistQuickBanTable();
|
||||
|
||||
var blacklist_entriesPerPage = 30;
|
||||
var blacklist_currentPage = 1;
|
||||
var blacklist_totalPages = 0;
|
||||
|
||||
function initIpAccessTable(ipAccessCounts){
|
||||
blacklist_totalPages = Math.ceil(Object.keys(ipAccessCounts).length / blacklist_entriesPerPage);
|
||||
|
||||
function sortkv(obj) {
|
||||
var sortable = [];
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
sortable.push([key, obj[key]]);
|
||||
}
|
||||
}
|
||||
sortable.sort(function(a, b) {
|
||||
return b[1] - a[1];
|
||||
});
|
||||
var sortedObj = {};
|
||||
sortable.forEach(function(item) {
|
||||
sortedObj[item[0]] = item[1];
|
||||
});
|
||||
return sortedObj;
|
||||
}
|
||||
|
||||
ipAccessCounts = sortkv(ipAccessCounts);
|
||||
|
||||
function renderTable() {
|
||||
var tableBody = $("#ipTable tbody");
|
||||
tableBody.empty();
|
||||
|
||||
var startIndex = (blacklist_currentPage - 1) * blacklist_entriesPerPage;
|
||||
var endIndex = startIndex + blacklist_entriesPerPage;
|
||||
var slicedEntries = Object.entries(ipAccessCounts).slice(startIndex, endIndex);
|
||||
|
||||
slicedEntries.forEach(function([ip, accessCount]) {
|
||||
var row = $("<tr>").appendTo(tableBody);
|
||||
$("<td>").text(ip).appendTo(row);
|
||||
$("<td>").text(accessCount).appendTo(row);
|
||||
if (ipInBlacklist(ip)){
|
||||
$("<td>").html(`<button class="ui basic green small icon button" title"Unban IP" onclick="handleUnban('${ip}');"><i class="green check icon"></i></button>`).appendTo(row);
|
||||
}else{
|
||||
$("<td>").html(`<button class="ui basic red small icon button" title"Ban IP" onclick="handleBanIp('${ip}');"><i class="red ban icon"></i></button>`).appendTo(row);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function renderPagination() {
|
||||
var paginationDiv = $(".pagination");
|
||||
paginationDiv.empty();
|
||||
|
||||
for (var i = 1; i <= blacklist_totalPages; i++) {
|
||||
var button = $("<button>").text(i).addClass("ui small basic compact button");
|
||||
if (i === blacklist_currentPage) {
|
||||
button.addClass("disabled");
|
||||
}
|
||||
button.click(function() {
|
||||
blacklist_currentPage = parseInt($(this).text());
|
||||
renderTable();
|
||||
renderPagination();
|
||||
});
|
||||
button.appendTo(paginationDiv);
|
||||
}
|
||||
}
|
||||
|
||||
renderTable();
|
||||
renderPagination();
|
||||
}
|
||||
|
||||
function ipInBlacklist(targetIp){
|
||||
let inBlacklist = false;
|
||||
$(".blacklistItem").each(function(){
|
||||
if ($(this).attr("ip") == encodeURIComponent(targetIp)){
|
||||
inBlacklist = true;
|
||||
}
|
||||
});
|
||||
return inBlacklist;
|
||||
}
|
||||
|
||||
function handleBanIp(targetIp){
|
||||
$("#ipAddressInput").val(targetIp);
|
||||
addIpBlacklist();
|
||||
}
|
||||
|
||||
function handleUnban(targetIp){
|
||||
removeIpBlacklist(targetIp);
|
||||
}
|
||||
</script>
|
@ -39,15 +39,16 @@
|
||||
<input type="text" id="certdomain" placeholder="example.com / blog.example.com">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Public Key</label>
|
||||
<label>Public Key (.pem)</label>
|
||||
<input type="file" id="pubkeySelector" onchange="handleFileSelect(event, 'pub')">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Private Key</label>
|
||||
<label>Private Key (.key)</label>
|
||||
<input type="file" id="prikeySelector" onchange="handleFileSelect(event, 'pri')">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button>
|
||||
<button class="ui basic button" onclick="handleDomainUploadByKeypress();"><i class="ui teal upload icon"></i> Upload</button><br>
|
||||
<small>You have intermediate certificate? <a style="cursor:pointer;" onclick="showSideWrapper('snippet/intermediateCertConv.html');">Open Conversion Tool</a></small>
|
||||
</div>
|
||||
<div id="certUploadSuccMsg" class="ui green message" style="display:none;">
|
||||
<i class="ui checkmark icon"></i> Certificate for domain <span id="certUploadingDomain"></span> uploaded.
|
||||
@ -100,11 +101,11 @@
|
||||
|
||||
//List the stored certificates
|
||||
function initManagedDomainCertificateList(){
|
||||
$("#certifiedDomainList").html("");
|
||||
$.get("/api/cert/list?date=true", function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 5000);
|
||||
}else{
|
||||
$("#certifiedDomainList").html("");
|
||||
data.forEach(entry => {
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td>${entry.Domain}</td>
|
||||
@ -112,7 +113,13 @@
|
||||
<td>${entry.ExpireDate}</td>
|
||||
<td><button title="Delete key-pair" class="ui mini basic red icon button" onclick="deleteCertificate('${entry.Domain}');"><i class="ui red trash icon"></i></button></td>
|
||||
</tr>`);
|
||||
})
|
||||
});
|
||||
|
||||
if (data.length == 0){
|
||||
$("#certifiedDomainList").append(`<tr>
|
||||
<td colspan="4"><i class="ui times circle icon"></i> No valid keypairs found</td>
|
||||
</tr>`);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -126,7 +126,7 @@
|
||||
var nodeCount = 0;
|
||||
data.forEach(function(gan){
|
||||
$("#GANetList").append(`<tr class="ganetEntry" addr="${gan.nwid}">
|
||||
<td>${gan.nwid}</td>
|
||||
<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>
|
||||
|
@ -11,7 +11,7 @@
|
||||
<a class="nettools item bluefont" data-tab="tab3">Interface</a>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment active" data-tab="tab1">
|
||||
<div class="ui bottom attached tab segment nettoolstab active" data-tab="tab1">
|
||||
<h2>Multicast DNS (mDNS) Scanner</h2>
|
||||
<p>Discover mDNS enabled service in this gateway forwarded network</p>
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/mdns.html',1000, 640);">View Discovery</button>
|
||||
@ -21,7 +21,7 @@
|
||||
<button class="ui basic larger circular button" onclick="launchToolWithSize('./tools/ipscan.html',1000, 640);">Start Scanner</button>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment" data-tab="tab2">
|
||||
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab2">
|
||||
<div id="websshTool" style="position: relative;">
|
||||
<h2>Web SSH</h2>
|
||||
<p>Connect to a network node within the local area network of the gateway</p>
|
||||
@ -101,7 +101,7 @@
|
||||
<button class="ui basic button" onclick="listWoL();"><i class="ui green refresh icon"></i> Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="ui bottom attached tab segment" data-tab="tab3">
|
||||
<div class="ui bottom attached tab segment nettoolstab" data-tab="tab3">
|
||||
<h2>Network Interfaces</h2>
|
||||
<p>Network Interface Card (NIC) currently installed on this host</p>
|
||||
<table id="network-interfaces-table" class="ui selectable inverted striped celled table">
|
||||
@ -125,10 +125,10 @@
|
||||
|
||||
// Switch tabs when clicking on the menu items
|
||||
$('.menu .nettools.item').on('click', function() {
|
||||
$('.menu .item').removeClass('active');
|
||||
$('.menu .nettools.item').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
var tab = $(this).attr('data-tab');
|
||||
$('.tab.segment').removeClass('active');
|
||||
$('.nettoolstab.tab.segment').removeClass('active');
|
||||
$('div[data-tab="' + tab + '"]').addClass('active');
|
||||
});
|
||||
|
||||
|
@ -299,4 +299,142 @@
|
||||
const regex = /^(localhost|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}|[a-z0-9]+([\-.]{1}[a-z0-9]+)*\.[a-z]{2,}\.)$/i;
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Inline editor for subd.html and vdir.html
|
||||
*/
|
||||
|
||||
function editEndpoint(endpointType, uuid) {
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
var columns = row.find('td[data-label]');
|
||||
var payload = $(row).attr("payload");
|
||||
payload = JSON.parse(decodeURIComponent(payload));
|
||||
|
||||
//console.log(payload);
|
||||
columns.each(function(index) {
|
||||
var column = $(this);
|
||||
var oldValue = column.text().trim();
|
||||
|
||||
if ($(this).attr("editable") == "false"){
|
||||
//This col do not allow edit. Skip
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an input element based on the column content
|
||||
var input;
|
||||
var datatype = $(this).attr("datatype");
|
||||
if (datatype == "domain"){
|
||||
let domain = payload.Domain;
|
||||
let tls = payload.RequireTLS;
|
||||
if (tls){
|
||||
tls = "checked";
|
||||
}else{
|
||||
tls = "";
|
||||
}
|
||||
input = `
|
||||
<div class="ui mini fluid input">
|
||||
<input type="text" class="Domain" value="${domain}">
|
||||
</div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireTLS" ${tls}>
|
||||
<label>Require TLS</label>
|
||||
</div>
|
||||
`;
|
||||
column.empty().append(input);
|
||||
|
||||
}else if (datatype == "skipver"){
|
||||
let skipTLSValidation = payload.SkipCertValidations;
|
||||
let checkstate = "";
|
||||
if (skipTLSValidation){
|
||||
checkstate = "checked";
|
||||
}
|
||||
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="SkipCertValidations" ${checkstate}>
|
||||
<label>Skip Verification</label>
|
||||
<small>Check this if you are using self signed certificates</small>
|
||||
</div>`);
|
||||
}else if (datatype == "basicauth"){
|
||||
let requireBasicAuth = payload.RequireBasicAuth;
|
||||
let checkstate = "";
|
||||
if (requireBasicAuth){
|
||||
checkstate = "checked";
|
||||
}
|
||||
column.empty().append(`<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="RequireBasicAuth" ${checkstate}>
|
||||
<label>Require Basic Auth</label>
|
||||
</div> <button class="ui basic tiny button" style="margin-left: 0.4em;" onclick="editBasicAuthCredentials('${endpointType}','${uuid}');"><i class="ui blue lock icon"></i> Edit Credentials</button>`);
|
||||
|
||||
}else if (datatype == 'action'){
|
||||
column.empty().append(`
|
||||
<button title="Cancel" onclick="exitProxyInlineEdit('${endpointType}');" class="ui basic small circular icon button"><i class="ui remove icon"></i></button>
|
||||
<button title="Save" onclick="saveProxyInlineEdit('${uuid}');" class="ui basic small circular icon button"><i class="ui green save icon"></i></button>
|
||||
`);
|
||||
}else{
|
||||
//Unknown field. Leave it untouched
|
||||
}
|
||||
});
|
||||
|
||||
$("#" + endpointType).find(".editBtn").addClass("disabled");
|
||||
}
|
||||
|
||||
function exitProxyInlineEdit(){
|
||||
listSubd();
|
||||
listVdirs();
|
||||
$("#" + endpointType).find(".editBtn").removeClass("disabled");
|
||||
}
|
||||
|
||||
function saveProxyInlineEdit(uuid){
|
||||
var row = $('tr[eptuuid="' + uuid + '"]');
|
||||
if (row.length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
var epttype = $(row).attr("class");
|
||||
if (epttype == "subdEntry"){
|
||||
epttype = "subd";
|
||||
}else if (epttype == "vdirEntry"){
|
||||
epttype = "vdir";
|
||||
}
|
||||
|
||||
let newDomain = $(row).find(".Domain").val();
|
||||
let requireTLS = $(row).find(".RequireTLS")[0].checked;
|
||||
let skipCertValidations = $(row).find(".SkipCertValidations")[0].checked;
|
||||
let requireBasicAuth = $(row).find(".RequireBasicAuth")[0].checked;
|
||||
|
||||
console.log(newDomain, requireTLS, skipCertValidations, requireBasicAuth)
|
||||
|
||||
$.ajax({
|
||||
url: "/api/proxy/edit",
|
||||
method: "POST",
|
||||
data: {
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ep":newDomain,
|
||||
"tls" :requireTLS,
|
||||
"tlsval": skipCertValidations,
|
||||
"bauth" :requireBasicAuth,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error !== undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy endpoint updated");
|
||||
if (epttype == "subd"){
|
||||
listSubd();
|
||||
}else if (epttype == "vdir"){
|
||||
listVdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function editBasicAuthCredentials(endpointType, uuid){
|
||||
let payload = encodeURIComponent(JSON.stringify({
|
||||
ept: endpointType,
|
||||
ep: uuid
|
||||
}));
|
||||
showSideWrapper("snippet/basicAuthEditor.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
</script>
|
@ -212,7 +212,7 @@
|
||||
}else{
|
||||
//Two dates are given and they are not identical
|
||||
loadStatisticByRange(startdate, enddate);
|
||||
console.log(startdate, enddate);
|
||||
//console.log(startdate, enddate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -270,7 +270,7 @@
|
||||
|
||||
function loadStatisticByRange(startdate, endDate){
|
||||
$.getJSON("/api/analytic/loadRange?start=" + startdate + "&end=" + endDate, function(data){
|
||||
console.log(data);
|
||||
//console.log(data);
|
||||
//Destroy all the previous charts
|
||||
|
||||
statisticCharts.forEach(function(thisChart){
|
||||
@ -368,7 +368,6 @@
|
||||
|
||||
function renderRefererTable(refererList){
|
||||
const sortedEntries = Object.entries(refererList).sort(([, valueA], [, valueB]) => valueB - valueA);
|
||||
console.log(sortedEntries);
|
||||
$("#stats_RefererTable").html("");
|
||||
let endStop = 100;
|
||||
if (sortedEntries.length < 100){
|
||||
|
@ -547,10 +547,7 @@
|
||||
//Bind event to tab switch
|
||||
tabSwitchEventBind["status"] = function(){
|
||||
//On switch over to this page, resize the chart
|
||||
$("#networkActivityPlaceHolder").hide();
|
||||
$("#networkActivity").show().delay(100, function(){
|
||||
updateChartSize();
|
||||
});
|
||||
handleChartAccumulateResize();
|
||||
|
||||
}
|
||||
|
||||
@ -558,11 +555,6 @@
|
||||
handleChartAccumulateResize();
|
||||
});
|
||||
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
// it could be either hidden or visible
|
||||
//handleChartAccumulateResize();
|
||||
});
|
||||
|
||||
|
||||
//Initialize chart data
|
||||
initChart();
|
||||
|
@ -22,10 +22,11 @@
|
||||
<button class="ui icon right floated basic button" onclick="listSubd();"><i class="green refresh icon"></i> Refresh</button>
|
||||
<br><br>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function listSubd(){
|
||||
$("#subdList").html(``);
|
||||
$.get("/api/proxy/list?type=subd", function(data){
|
||||
$("#subdList").html(``);
|
||||
if (data.error !== undefined){
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
|
||||
@ -37,16 +38,17 @@
|
||||
}else{
|
||||
data.forEach(subd => {
|
||||
let tlsIcon = "";
|
||||
let subdData = encodeURIComponent(JSON.stringify(subd));
|
||||
if (subd.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
}
|
||||
$("#subdList").append(`<tr>
|
||||
<td data-label="">${subd.RootOrMatchingDomain}</td>
|
||||
<td data-label="">${subd.Domain} ${tlsIcon}</td>
|
||||
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" data-label="">
|
||||
<button class="ui circular mini basic icon button" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
$("#subdList").append(`<tr eptuuid="${subd.RootOrMatchingDomain}" payload="${subdData}" class="subdEntry">
|
||||
<td data-label="" editable="false">${subd.RootOrMatchingDomain}</td>
|
||||
<td data-label="" editable="true" datatype="domain">${subd.Domain} ${tlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="skipver">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="" editable="true" datatype="basicauth">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" editable="true" datatype="action" data-label="">
|
||||
<button class="ui circular mini basic icon button editBtn" onclick='editEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("subd","${subd.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
|
@ -41,7 +41,36 @@
|
||||
</div>
|
||||
<button id="addTcpProxyButton" class="ui basic button" type="submit"><i class="ui blue add icon"></i> Create</button>
|
||||
<button id="editTcpProxyButton" class="ui basic button" onclick="confirmEditTCPProxyConfig(event);"><i class="ui blue save icon"></i> Update</button>
|
||||
<table class="ui celled padded table">
|
||||
<button class="ui basic red button" onclick="event.preventDefault(); cancelTCPProxyEdit(event);"><i class="ui red remove icon"></i> Cancel</button>
|
||||
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui basic segment">
|
||||
<h3>TCP Proxy Configs</h3>
|
||||
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<div style="overflow-x: auto; min-height: 400px;">
|
||||
<table id="proxyTable" class="ui celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Port/Addr A</th>
|
||||
<th>Port/Addr B</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui basic inverted segment" style="background-color: #414141; border-radius: 0.6em;">
|
||||
<h3>Proxy Mode</h3>
|
||||
<p>TCP Proxy support the following TCP sockets proxy modes</p>
|
||||
<table class="ui celled padded inverted basic table">
|
||||
<thead>
|
||||
<tr><th class="single line">Mode</th>
|
||||
<th>Public-IP</th>
|
||||
@ -51,24 +80,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Listen</h3>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Transport</h3>
|
||||
<h4 class="ui center aligned inverted header">Transport</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
@ -85,7 +97,24 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h3 class="ui center aligned header">Starter</h3>
|
||||
<h4 class="ui center aligned inverted header">Listen</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui green check icon"></i><br>
|
||||
A: <i class="ui remove icon"></i><br>
|
||||
B: <i class="ui remove icon"></i><br>
|
||||
</td>
|
||||
<td>
|
||||
<i class="ui red times icon"></i>
|
||||
</td>
|
||||
<td>Port A (e.g. 8080) <i class="arrow right icon"></i> Server<br>
|
||||
Port B (e.g. 8081) <i class="arrow right icon"></i> Server<br>
|
||||
<small>Server will act as a bridge to proxy traffic between Port A and B</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<h4 class="ui center aligned inverted header">Starter</h4>
|
||||
</td>
|
||||
<td class="single line">
|
||||
Server: <i class="ui times icon"></i><br>
|
||||
@ -102,29 +131,6 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div class="ui basic segment">
|
||||
<div style="overflow-x: auto;">
|
||||
<h3>TCP Proxy Configs</h3>
|
||||
<p>A list of TCP proxy configs created on this host. To enable them, use the toggle button on the right.</p>
|
||||
<table id="proxyTable" class="ui basic celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>PortA</th>
|
||||
<th>PortB</th>
|
||||
<th>Mode</th>
|
||||
<th>Timeout (s)</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
@ -171,11 +177,30 @@
|
||||
|
||||
function clearTCPProxyAddEditForm(){
|
||||
$('#tcpProxyForm input, #tcpProxyForm select').val('');
|
||||
$('#tcpProxyForm select').dropdown('clear');
|
||||
}
|
||||
|
||||
function cancelTCPProxyEdit(event) {
|
||||
clearTCPProxyAddEditForm();
|
||||
$('#addproxyConfig').slideUp('fast');
|
||||
}
|
||||
|
||||
function validateTCPProxyConfig(form){
|
||||
//Check if name is filled. If not, generate a random name for it
|
||||
var name = form.find('input[name="name"]').val()
|
||||
if (name == ""){
|
||||
let randomName = "Proxy Rule (#" + Math.round(Date.now()/1000) + ")";
|
||||
form.find('input[name="name"]').val(randomName);
|
||||
}
|
||||
|
||||
// Validate timeout is an integer
|
||||
var timeout = parseInt(form.find('input[name="timeout"]').val());
|
||||
if (form.find('input[name="timeout"]').val() == ""){
|
||||
//Not set. Assign a random one to it
|
||||
form.find('input[name="timeout"]').val("10");
|
||||
timeout = 10;
|
||||
}
|
||||
|
||||
if (isNaN(timeout)) {
|
||||
form.find('input[name="timeout"]').parent().addClass("error");
|
||||
msgbox('Timeout must be a valid integer', false, 5000);
|
||||
@ -206,10 +231,10 @@
|
||||
|
||||
proxyConfigs.forEach(function(config) {
|
||||
var runningLogo = '<i class="red circle icon"></i>';
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i></button>`;
|
||||
var startButton = `<button onclick="startTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="play icon"></i> Start Proxy</button>`;
|
||||
if (config.Running){
|
||||
runningLogo = '<i class="green circle icon"></i>';
|
||||
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i></button>`;
|
||||
startButton = `<button onclick="stopTcpProx('${config.UUID}');" class="ui button" title="Start Proxy"><i class="red stop icon"></i> Stop Proxy</button>`;
|
||||
}
|
||||
|
||||
var modeText = "Unknown";
|
||||
@ -230,11 +255,11 @@
|
||||
row.append($('<td>').text(modeText));
|
||||
row.append($('<td>').text(config.Timeout));
|
||||
row.append($('<td>').html(`
|
||||
<div class="ui basic icon buttons">
|
||||
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i></button>
|
||||
<div class="ui basic vertical fluid tiny buttons">
|
||||
<button class="ui button" onclick="validateProxyConfig('${config.UUID}', this);" title="Validate Config"><i class="teal question circle outline icon"></i> CXN Test</button>
|
||||
${startButton}
|
||||
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i></button>
|
||||
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red button" title="Delete Config"><i class="trash icon"></i></button>
|
||||
<button onclick="editTCPProxyConfig('${config.UUID}');" class="ui button" title="Edit Config"><i class="edit icon"></i> Edit </button>
|
||||
<button onclick="deleteTCPProxyConfig('${config.UUID}');" class="ui red basic button" title="Delete Config"><i class="trash icon"></i> Remove</button>
|
||||
</div>
|
||||
`));
|
||||
tableBody.append(row);
|
||||
@ -261,10 +286,11 @@
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
$(btn).html(`<i class="red times icon"></i>`);
|
||||
let errormsg = data.error.charAt(0).toUpperCase() + data.error.slice(1);
|
||||
$(btn).html(`<i class="red times icon"></i> ${errormsg}`);
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
$(btn).html(`<i class="green check icon"></i>`);
|
||||
$(btn).html(`<i class="green check icon"></i> Config Valid`);
|
||||
msgbox("Config Check Passed");
|
||||
}
|
||||
}
|
||||
@ -335,7 +361,55 @@
|
||||
}
|
||||
|
||||
function deleteTCPProxyConfig(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/delete",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Proxy Config Removed");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//Start a TCP proxy by their config UUID
|
||||
function startTcpProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/start",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Started");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
//Stop a TCP proxy by their config UUID
|
||||
function stopTcpProx(configUUID){
|
||||
$.ajax({
|
||||
url: "/api/tcpprox/config/stop",
|
||||
method: "POST",
|
||||
data: {uuid: configUUID},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
msgbox("Service Stopped");
|
||||
initProxyConfigList();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function initProxyConfigList(){
|
||||
|
@ -4,6 +4,8 @@
|
||||
<p>You might find these tools or information helpful when setting up your gateway server</p>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
|
||||
|
||||
<div class="selfauthOnly">
|
||||
<h3>Account Management</h3>
|
||||
<p>Functions to help management the current account</p>
|
||||
@ -34,6 +36,11 @@
|
||||
<p>The following SMTP settings help you to reset your password in case you have lost your management account.</p>
|
||||
<form id="email-form" class="ui form">
|
||||
<div class="field">
|
||||
<label>Sender Address</label>
|
||||
<input type="text" name="senderAddr" placeholder="E.g. noreply@zoraxy.arozos.com">
|
||||
</div>
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Connection setup for email service provider</p>
|
||||
<div class="fields">
|
||||
<div class="twelve wide field">
|
||||
<label>SMTP Provider Hostname</label>
|
||||
@ -48,34 +55,33 @@
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Address</label>
|
||||
<input type="text" name="senderAddr" placeholder="E.g. admin@zoraxy.arozos.com">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<p><i class="caret down icon"></i> Credentials for SMTP server authentications</p>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>Sender Username</label>
|
||||
<input type="text" name="username" placeholder="Username of the email account">
|
||||
<input type="text" name="username" placeholder="E.g. admin">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label>Sender Domain</label>
|
||||
<div class="ui labeled input">
|
||||
<div class="ui basic label">
|
||||
@
|
||||
</div>
|
||||
<input type="text" name="domain" min="1" max="65534" placeholder="E.g. arozos.com">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Sender Password</label>
|
||||
<input type="password" name="password" placeholder="Password of the email account">
|
||||
<small>Leave empty to use the old password</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<p> Email for sending account reset link</p>
|
||||
<p> <i class="caret down icon"></i> Email for sending account reset link</p>
|
||||
<div class="field">
|
||||
<label>Admin Address</label>
|
||||
<label>Admin / Receiver Address</label>
|
||||
<input type="text" name="recvAddr" placeholder="E.g. personalEmail@gmail.com">
|
||||
</div>
|
||||
|
||||
@ -83,7 +89,7 @@
|
||||
<button class="ui basic button" onclick="event.preventDefault(); sendTestEmail(this);"><i class="teal mail icon"></i> Send Test Email</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h3> IP Address to CIDR</h3>
|
||||
<p>No experience with CIDR notations? Here are some tools you can use to make setting up easier.</p>
|
||||
<div class="ui basic segment">
|
||||
@ -110,6 +116,45 @@
|
||||
</div>
|
||||
<p>Results: <div id="ipRangeOutput">N/A</div></p>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div id="zoraxyinfo">
|
||||
<h3 class="ui header">
|
||||
System Information
|
||||
</h3>
|
||||
<p>Basic information about this zoraxy host</p>
|
||||
<table class="ui very basic collapsing celled table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Host UUID</td>
|
||||
<td class="uuid"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td class="version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Build</td>
|
||||
<td class="development"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Running Since</td>
|
||||
<td class="boottime"></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>ZeroTier Linked</td>
|
||||
<td class="zt"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Enable SSH Loopback</td>
|
||||
<td class="sshlb"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Zoraxy is developed by tobychui for <a href="//imuslab.com" target="_blank">imuslab</a> and open source under <a href="https://www.gnu.org/licenses/agpl-3.0.txt">AGPL</a></p>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
@ -122,7 +167,44 @@
|
||||
//Using external auth manager. Hide options
|
||||
$(".selfauthOnly").hide();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
$.get("/api/info/x", function(data){
|
||||
function timeConverter(UNIX_timestamp){
|
||||
var a = new Date(UNIX_timestamp * 1000);
|
||||
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
var year = a.getFullYear();
|
||||
var month = months[a.getMonth()];
|
||||
var date = a.getDate();
|
||||
var hour = a.getHours();
|
||||
var min = a.getMinutes();
|
||||
var sec = a.getSeconds();
|
||||
var time = date + ' ' + month + ' ' + year + ' ' + hour + ':' + min + ':' + sec ;
|
||||
return time;
|
||||
}
|
||||
|
||||
function secondsToDhms(seconds) {
|
||||
seconds = Number(seconds);
|
||||
var d = Math.floor(seconds / (3600*24));
|
||||
var h = Math.floor(seconds % (3600*24) / 3600);
|
||||
var m = Math.floor(seconds % 3600 / 60);
|
||||
var s = Math.floor(seconds % 60);
|
||||
|
||||
var dDisplay = d > 0 ? d + (d == 1 ? " day, " : " days, ") : "";
|
||||
var hDisplay = h > 0 ? h + (h == 1 ? " hour, " : " hours, ") : "";
|
||||
var mDisplay = m > 0 ? m + (m == 1 ? " minute, " : " minutes, ") : "";
|
||||
var sDisplay = s > 0 ? s + (s == 1 ? " second" : " seconds") : "";
|
||||
return dDisplay + hDisplay + mDisplay + sDisplay;
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
$("#zoraxyinfo .uuid").text(data.NodeUUID);
|
||||
$("#zoraxyinfo .development").text(data.Development?"Development":"Release");
|
||||
$("#zoraxyinfo .version").text(data.Version);
|
||||
$("#zoraxyinfo .boottime").text(timeConverter(data.BootTime) + ` ( ${secondsToDhms(parseInt(Date.now()/1000) - data.BootTime)} ago)`);
|
||||
$("#zoraxyinfo .zt").html(data.ZerotierConnected?`<i class="ui green check icon"></i> Connected`:`<i class="ui red times icon"></i> Link Error`);
|
||||
$("#zoraxyinfo .sshlb").html(data.EnableSshLoopback?`<i class="ui yellow exclamation triangle icon"></i> Enabled`:`Disabled`);
|
||||
});
|
||||
|
||||
function changePassword() {
|
||||
const oldPassword = document.getElementsByName('oldPassword')[0].value;
|
||||
|
@ -27,8 +27,8 @@
|
||||
<script>
|
||||
//Virtual directories functions
|
||||
function listVdirs(){
|
||||
$("#vdirList").html(``);
|
||||
$.get("/api/proxy/list?type=vdir", function(data){
|
||||
$("#vdirList").html(``);
|
||||
if (data.error !== undefined){
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="" colspan="3"><i class="remove icon"></i> ${data.error}</td>
|
||||
@ -40,16 +40,17 @@
|
||||
}else{
|
||||
data.forEach(vdir => {
|
||||
let tlsIcon = "";
|
||||
let vdirData = encodeURIComponent(JSON.stringify(vdir));
|
||||
if (vdir.RequireTLS){
|
||||
tlsIcon = `<i class="green lock icon" title="TLS Mode"></i>`;
|
||||
}
|
||||
$("#vdirList").append(`<tr>
|
||||
<td data-label="">${vdir.RootOrMatchingDomain}</td>
|
||||
<td data-label="">${vdir.Domain} ${tlsIcon}</td>
|
||||
<td data-label="">${!subd.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="">${subd.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" data-label="">
|
||||
<button class="ui circular mini basic icon button" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
$("#vdirList").append(`<tr eptuuid="${vdir.RootOrMatchingDomain}" payload="${vdirData}" class="vdirEntry">
|
||||
<td data-label="" editable="false">${vdir.RootOrMatchingDomain}</td>
|
||||
<td data-label="" editable="true" datatype="domain">${vdir.Domain} ${tlsIcon}</td>
|
||||
<td data-label="" editable="true" datatype="skipver">${!vdir.SkipCertValidations?`<i class="ui green check icon"></i>`:`<i class="ui yellow exclamation circle icon" title="TLS/SSL Verification will be skipped on this host"></i>`}</td>
|
||||
<td data-label="" editable="true" datatype="basicauth">${vdir.RequireBasicAuth?`<i class="ui green check icon"></i>`:`<i class="ui grey remove icon"></i>`}</td>
|
||||
<td class="center aligned" editable="true" datatype="action" data-label="">
|
||||
<button class="ui circular mini basic icon button editBtn" onclick='editEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="edit icon"></i></button>
|
||||
<button class="ui circular mini red basic icon button" onclick='deleteEndpoint("vdir","${vdir.RootOrMatchingDomain}")'><i class="trash icon"></i></button>
|
||||
</td>
|
||||
</tr>`);
|
||||
|
@ -1,11 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- Zoraxy Forbidden Template -->
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css">
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js
|
||||
"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js"></script>
|
||||
<title>Forbidden</title>
|
||||
<style>
|
||||
#msg{
|
||||
@ -39,7 +39,7 @@
|
||||
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
|
||||
<div class="ui divider"></div>
|
||||
<p>You do not have permission to view this directory or page. <br>
|
||||
This might be caused by the site admin has blacklisted your country or IP address</p>
|
||||
This might cause by the region limit setting of this site.</p>
|
||||
<div class="ui divider"></div>
|
||||
<div style="text-align: left;">
|
||||
<small>Request time: <span id="reqtime"></span></small><br>
|
||||
|
@ -55,8 +55,8 @@
|
||||
<a class="item" tag="redirectset">
|
||||
<i class="simplistic level up alternate icon"></i> Redirection
|
||||
</a>
|
||||
<a class="item" tag="blacklist">
|
||||
<i class="simplistic ban icon"></i> Blacklist
|
||||
<a class="item" tag="access">
|
||||
<i class="simplistic ban icon"></i> Access Control
|
||||
</a>
|
||||
<div class="ui divider menudivider">Bridging</div>
|
||||
<a class="item" tag="gan">
|
||||
@ -109,7 +109,7 @@
|
||||
<div id="redirectset" class="functiontab" target="redirection.html"></div>
|
||||
|
||||
<!-- Blacklist -->
|
||||
<div id="blacklist" class="functiontab" target="blacklist.html"></div>
|
||||
<div id="access" class="functiontab" target="access.html"></div>
|
||||
|
||||
<!-- Global Area Networking -->
|
||||
<div id="gan" class="functiontab" target="gan.html"></div>
|
||||
@ -131,6 +131,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sideWrapper" style="display:none;">
|
||||
<div class="fadingBackground" onclick="hideSideWrapper();"></div>
|
||||
<div class="content">
|
||||
<div class="sideWrapperMenu"></div>
|
||||
<iframe src="snippet/placeholder.html"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui container" style="color: grey; font-size: 90%">
|
||||
@ -251,7 +258,6 @@
|
||||
});
|
||||
$('html,body').animate({scrollTop: 0}, 'fast');
|
||||
window.location.hash = tabID;
|
||||
|
||||
}
|
||||
|
||||
$(window).on("resize", function(){
|
||||
@ -274,6 +280,36 @@
|
||||
$("#messageBox").stop().finish().fadeIn("fast").delay(delayDuration).fadeOut("fast");
|
||||
}
|
||||
|
||||
/*
|
||||
Toggles for side wrapper
|
||||
*/
|
||||
|
||||
function showSideWrapper(scriptpath=""){
|
||||
if (scriptpath != ""){
|
||||
$(".sideWrapper iframe").attr("src", scriptpath);
|
||||
}
|
||||
|
||||
if ($(".sideWrapper .content").transition("is animating") || $(".sideWrapper .content").transition("is visible")){
|
||||
return
|
||||
}
|
||||
$(".sideWrapper").show();
|
||||
$(".sideWrapper .fadingBackground").fadeIn("fast");
|
||||
$(".sideWrapper .content").transition('slide left in', 300);
|
||||
}
|
||||
|
||||
function hideSideWrapper(discardFrameContent = false){
|
||||
if (discardFrameContent){
|
||||
$(".sideWrapper iframe").attr("src", "snippet/placeholder.html");
|
||||
}
|
||||
if ($(".sideWrapper .content").transition("is animating") || !$(".sideWrapper .content").transition("is visible")){
|
||||
return
|
||||
}
|
||||
$(".sideWrapper .content").transition('slide left out', 300, function(){
|
||||
$(".sideWrapper .fadingBackground").fadeOut("fast", function(){
|
||||
$(".sideWrapper").hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -86,6 +86,7 @@ body{
|
||||
right: 1em;
|
||||
display:none;
|
||||
max-width: 300px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
/* Standard containers */
|
||||
@ -119,6 +120,45 @@ body{
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Side Wrapper
|
||||
*/
|
||||
.sideWrapper{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 52px;
|
||||
height: 100%;
|
||||
width: calc(100% - 5em);
|
||||
max-width: 500px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sideWrapper .content{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.sideWrapper iframe{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
.sideWrapper .fadingBackground{
|
||||
position: fixed;
|
||||
top: 52px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 52px);
|
||||
background-color: rgba(38,38,38,0.26);
|
||||
}
|
||||
|
||||
.sideWrapperMenu{
|
||||
height: 3px;
|
||||
background-color: #414141;
|
||||
}
|
||||
|
||||
/*
|
||||
RWD Rules
|
||||
@ -398,6 +438,27 @@ body{
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Access Control
|
||||
*/
|
||||
|
||||
.ui.very.compact.table th{
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
}
|
||||
|
||||
|
||||
.ui.very.compact.table td{
|
||||
padding-top: 0.1em;
|
||||
padding-bottom: 0.1em;
|
||||
}
|
||||
|
||||
#ipTable.disabled{
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/*
|
||||
Uptime Monitor
|
||||
*/
|
||||
|
196
src/web/snippet/basicAuthEditor.html
Normal file
196
src/web/snippet/basicAuthEditor.html
Normal file
@ -0,0 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Basic Auth Credential
|
||||
<div class="sub header" id="epname"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrolling content ui form">
|
||||
<div id="inlineEditBasicAuthCredentials" class="field">
|
||||
<p>Enter the username and password for allowing them to access this proxy endpoint</p>
|
||||
<table class="ui very basic compacted unstackable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Password</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="inlineEditBasicAuthCredentialTable">
|
||||
<tr>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="three small fields credentialEntry">
|
||||
<div class="field">
|
||||
<input id="inlineEditBasicAuthCredUsername" type="text" placeholder="Username" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<input id="inlineEditBasicAuthCredPassword" type="password" placeholder="Password" autocomplete="off">
|
||||
</div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" onclick="addCredentialsToEditingList();"><i class="blue add icon"></i> Add Credential</button>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" style="float: right;" onclick="saveCredentials();"><i class="green save icon"></i> Save</button>
|
||||
<button class="ui basic button" style="float: right;" onclick="cancelCredentialEdit();"><i class="remove icon"></i> Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let editingCredentials = [];
|
||||
let editingEndpoint = {};
|
||||
|
||||
if (window.location.hash.length > 1){
|
||||
let payloadHash = window.location.hash.substr(1);
|
||||
try{
|
||||
payloadHash = JSON.parse(decodeURIComponent(payloadHash));
|
||||
loadBasicAuthCredentials(payloadHash.ept, payloadHash.ep);
|
||||
$("#epname").text(payloadHash.ep);
|
||||
editingEndpoint = payloadHash;
|
||||
}catch(ex){
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
|
||||
function loadBasicAuthCredentials(endpointType, uuid){
|
||||
$.ajax({
|
||||
url: "/api/proxy/updateCredentials",
|
||||
method: "GET",
|
||||
data: {
|
||||
ep: uuid,
|
||||
ptype: endpointType
|
||||
},
|
||||
success: function(data){
|
||||
//Push the existing account to list
|
||||
for(var i = 0; i < data.length; i++){
|
||||
// Create a new credential object
|
||||
var credential = {
|
||||
username: data[i],
|
||||
password: ""
|
||||
};
|
||||
|
||||
// Add the credential to the global credentials array
|
||||
editingCredentials.push(credential);
|
||||
}
|
||||
console.log(data);
|
||||
updateEditingCredentialList();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addCredentialsToEditingList() {
|
||||
// Retrieve the username and password input values
|
||||
var username = $('#inlineEditBasicAuthCredUsername').val();
|
||||
var password = $('#inlineEditBasicAuthCredPassword').val();
|
||||
|
||||
if(username == "" || password == ""){
|
||||
parent.msgbox("Username or password cannot be empty", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (alreadyExists(username)){
|
||||
parent.msgbox("Credential with same username already exists", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new credential object
|
||||
var credential = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
// Add the credential to the global credentials array
|
||||
editingCredentials.push(credential);
|
||||
|
||||
// Clear the input fields
|
||||
$('#inlineEditBasicAuthCredUsername').val('');
|
||||
$('#inlineEditBasicAuthCredPassword').val('');
|
||||
|
||||
// Update the table body with the credentials
|
||||
updateEditingCredentialList();
|
||||
}
|
||||
|
||||
function updateEditingCredentialList() {
|
||||
var tableBody = $('#inlineEditBasicAuthCredentialTable');
|
||||
tableBody.empty();
|
||||
|
||||
if (editingCredentials.length === 0) {
|
||||
tableBody.append('<tr><td colspan="3"><i class="ui green circle check icon"></i> No Entered Credential</td></tr>');
|
||||
} else {
|
||||
for (var i = 0; i < editingCredentials.length; i++) {
|
||||
var credential = editingCredentials[i];
|
||||
var username = credential.username;
|
||||
var password = credential.password.replace(/./g, '*'); // Replace each character with '*'
|
||||
|
||||
if (credential.password == ""){
|
||||
password = `<span style="color: #c9c9c9;"><i class="eye slash outline icon"></i> Hidden<span>`;
|
||||
}
|
||||
var row = '<tr>' +
|
||||
'<td>' + username + '</td>' +
|
||||
'<td>' + password + '</td>' +
|
||||
'<td><button class="ui basic button" onclick="removeCredentialFromEditingList(' + i + ');"><i class="red remove icon"></i> Remove</button></td>' +
|
||||
'</tr>';
|
||||
|
||||
tableBody.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeCredentialFromEditingList(index) {
|
||||
// Remove the credential from the credentials array
|
||||
editingCredentials.splice(index, 1);
|
||||
|
||||
// Update the table body
|
||||
updateEditingCredentialList();
|
||||
}
|
||||
|
||||
function alreadyExists(username){
|
||||
let isExists = false;
|
||||
editingCredentials.forEach(function(cred){
|
||||
if (cred.username == username){
|
||||
isExists = true;
|
||||
}
|
||||
});
|
||||
return isExists;
|
||||
}
|
||||
|
||||
function cancelCredentialEdit(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
|
||||
function saveCredentials(){
|
||||
$.ajax({
|
||||
url: "/api/proxy/updateCredentials",
|
||||
method: "POST",
|
||||
data: {
|
||||
ep: editingEndpoint.ep,
|
||||
ptype: editingEndpoint.ept,
|
||||
creds: JSON.stringify(editingCredentials)
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false, 6000);
|
||||
}else{
|
||||
parent.msgbox("Credentials Updated");
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
102
src/web/snippet/intermediateCertConv.html
Normal file
102
src/web/snippet/intermediateCertConv.html
Normal file
@ -0,0 +1,102 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Intermediate Certificate Converter
|
||||
<div class="sub header">Convert .cer files to .pem</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui message">
|
||||
<div class="header">
|
||||
Why I need this?
|
||||
</div>
|
||||
<p>If you have 3 certificate files (2 x .cer + 1 x .key) provided by your ISP, you will need to merge the root and intermediate certificates in order to upload it to Zoraxy. This tool will automate the process for you.</p>
|
||||
</div>
|
||||
|
||||
<div class="ui form">
|
||||
<div class="field">
|
||||
<label>Select Root Certificate</label>
|
||||
<input type="file" id="rootCertificateInput">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Select Intermediate Certificate</label>
|
||||
<input type="file" id="intermediateCertificateInput">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Export File Name (Optional)</label>
|
||||
<input type="text" id="exportFilename" value="domain.pem">
|
||||
</div>
|
||||
<button class="ui basic button" onclick="convertCertificates()"> <i class="ui blue exchange icon"></i> Convert</button>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<button class="ui basic button" style="float: right;" onclick="parent.hideSideWrapper();"><i class="remove icon"></i> Cancel</button>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function mergeAndDownload(certificatesArray=["",""]){
|
||||
if (certificatesArray[0] == "" || certificatesArray[1] == ""){
|
||||
//Data not ready
|
||||
return;
|
||||
}
|
||||
var filename = $("#exportFilename").val().trim();
|
||||
if (filename == ""){
|
||||
filename = "export.pem";
|
||||
}
|
||||
//Basically just concat both together. See https://github.com/tobychui/zoraxy/wiki/Import-your-own-certificate
|
||||
generateAndDownload(certificatesArray[0] + '\n' + certificatesArray[1], filename);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function convertCertificates() {
|
||||
var rootCertificateFile = document.getElementById('rootCertificateInput').files[0];
|
||||
var intermediateCertificateFile = document.getElementById('intermediateCertificateInput').files[0];
|
||||
var readerr = new FileReader();
|
||||
var readeri = new FileReader();
|
||||
let certificates = ["",""];
|
||||
readerr.onload = function(event) {
|
||||
var rootCertificateContent = event.target.result;
|
||||
|
||||
certificates[0] = rootCertificateContent;
|
||||
mergeAndDownload(certificates);
|
||||
console.log('Root Certificate Content:', rootCertificateContent);
|
||||
};
|
||||
|
||||
readeri.onload = function(event) {
|
||||
var intermediateCertificateContent = event.target.result;
|
||||
|
||||
certificates[1] = intermediateCertificateContent;
|
||||
mergeAndDownload(certificates);
|
||||
console.log('Intermediate Certificate Content:', intermediateCertificateContent);
|
||||
};
|
||||
|
||||
readerr.readAsText(rootCertificateFile);
|
||||
readeri.readAsText(intermediateCertificateFile);
|
||||
}
|
||||
|
||||
function generateAndDownload(content, filename) {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(content));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
19
src/web/snippet/placeholder.html
Normal file
19
src/web/snippet/placeholder.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||
<script src="../script/semantic/semantic.min.js"></script>
|
||||
<style>
|
||||
body{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ui active inverted dimmer">
|
||||
<div class="ui text loader">Loading Snippet</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -214,14 +214,16 @@ func HandleIpScan(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
/*
|
||||
WAKE ON LAN
|
||||
|
||||
Handle wake on LAN
|
||||
Support following methods
|
||||
/?set=xxx&name=xxx Record a new MAC address into the database
|
||||
/?wake=xxx Wake a server given its MAC address
|
||||
/?del=xxx Delete a server given its MAC address
|
||||
/ Default: list all recorded WoL MAC address
|
||||
|
||||
*/
|
||||
|
||||
func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) {
|
||||
set, _ := utils.PostPara(r, "set")
|
||||
del, _ := utils.PostPara(r, "del")
|
||||
@ -297,3 +299,30 @@ func HandleWakeOnLan(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Zoraxy Host Info
|
||||
*/
|
||||
|
||||
func HandleZoraxyInfo(w http.ResponseWriter, r *http.Request) {
|
||||
type ZoraxyInfo struct {
|
||||
Version string
|
||||
NodeUUID string
|
||||
Development bool
|
||||
BootTime int64
|
||||
EnableSshLoopback bool
|
||||
ZerotierConnected bool
|
||||
}
|
||||
|
||||
info := ZoraxyInfo{
|
||||
Version: version,
|
||||
NodeUUID: nodeUUID,
|
||||
Development: development,
|
||||
BootTime: bootTime,
|
||||
EnableSshLoopback: *allowSshLoopback,
|
||||
ZerotierConnected: ganManager.ControllerID != "",
|
||||
}
|
||||
|
||||
js, _ := json.MarshalIndent(info, "", " ")
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user