Browse Source

auto update script executed

Toby Chui 1 year ago
parent
commit
882444df47

+ 2 - 2
api.go

@@ -4,8 +4,8 @@ import (
 	"encoding/json"
 	"net/http"
 
-	"imuslab.com/arozos/ReverseProxy/mod/auth"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/auth"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 /*

+ 1 - 1
blacklist.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"net/http"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 /*

+ 1 - 1
cert.go

@@ -9,7 +9,7 @@ import (
 	"os"
 	"path/filepath"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 // Check if the default certificates is correctly setup

+ 1 - 1
config.go

@@ -8,7 +8,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 type Record struct {

+ 2 - 1
go.mod

@@ -1,4 +1,4 @@
-module imuslab.com/arozos/ReverseProxy
+module imuslab.com/Zoarxy
 
 go 1.16
 
@@ -7,5 +7,6 @@ require (
 	github.com/gorilla/sessions v1.2.1
 	github.com/gorilla/websocket v1.4.2
 	github.com/oschwald/geoip2-golang v1.8.0
+	github.com/syndtr/goleveldb v1.0.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
 )

+ 17 - 0
go.sum

@@ -3,12 +3,20 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
 github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
 github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
 github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
@@ -21,10 +29,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.3/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 22 - 12
main.go

@@ -10,12 +10,13 @@ import (
 	"syscall"
 	"time"
 
-	"imuslab.com/arozos/ReverseProxy/mod/aroz"
-	"imuslab.com/arozos/ReverseProxy/mod/auth"
-	"imuslab.com/arozos/ReverseProxy/mod/database"
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
-	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
+	"imuslab.com/Zoarxy/mod/aroz"
+	"imuslab.com/Zoarxy/mod/auth"
+	"imuslab.com/Zoarxy/mod/database"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/redirection"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/tlscert"
 )
 
 //General flags
@@ -26,12 +27,13 @@ var (
 	name    = "Zoraxy"
 	version = "2.1"
 
-	handler        *aroz.ArozHandler
-	sysdb          *database.Database
-	authAgent      *auth.AuthAgent
-	tlsCertManager *tlscert.Manager
-	redirectTable  *redirection.RuleTable
-	geodbStore     *geodb.Store
+	handler            *aroz.ArozHandler
+	sysdb              *database.Database
+	authAgent          *auth.AuthAgent
+	tlsCertManager     *tlscert.Manager
+	redirectTable      *redirection.RuleTable
+	geodbStore         *geodb.Store
+	statisticCollector *statistic.Collector
 )
 
 // Kill signal handler. Do something before the system the core terminate.
@@ -107,6 +109,14 @@ func main() {
 		panic(err)
 	}
 
+	//Create a statistic collector
+	statisticCollector, err = statistic.NewStatisticCollector(statistic.CollectorOption{
+		Database: sysdb,
+	})
+	if err != nil {
+		panic(err)
+	}
+
 	//Initiate management interface APIs
 	initAPIs()
 

+ 2 - 2
mod/auth/auth.go

@@ -17,8 +17,8 @@ import (
 	"log"
 
 	"github.com/gorilla/sessions"
-	db "imuslab.com/arozos/ReverseProxy/mod/database"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	db "imuslab.com/Zoarxy/mod/database"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 type AuthAgent struct {

+ 4 - 3
mod/dynamicproxy/Server.go

@@ -5,7 +5,7 @@ import (
 	"os"
 	"strings"
 
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/geodb"
 )
 
 /*
@@ -26,13 +26,14 @@ func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		} else {
 			w.Write(template)
 		}
-
+		h.logRequest(r, false, 403, "blacklist")
 		return
 	}
 
 	//Check if this is a redirection url
 	if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
-		h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
+		statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
+		h.logRequest(r, statusCode != 500, statusCode, "redirect")
 		return
 	}
 

+ 7 - 5
mod/dynamicproxy/dynamicproxy.go

@@ -12,11 +12,12 @@ import (
 	"sync"
 	"time"
 
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/dpcore"
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy/redirection"
-	"imuslab.com/arozos/ReverseProxy/mod/geodb"
-	"imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
-	"imuslab.com/arozos/ReverseProxy/mod/tlscert"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/dpcore"
+	"imuslab.com/Zoarxy/mod/dynamicproxy/redirection"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/reverseproxy"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/tlscert"
 )
 
 /*
@@ -29,6 +30,7 @@ type RouterOption struct {
 	TlsManager         *tlscert.Manager
 	RedirectRuleTable  *redirection.RuleTable
 	GeodbStore         *geodb.Store
+	StatisticCollector *statistic.Collector
 }
 
 type Router struct {

+ 30 - 1
mod/dynamicproxy/proxyRequestHandler.go

@@ -2,13 +2,16 @@ package dynamicproxy
 
 import (
 	"errors"
+	"fmt"
 	"log"
 	"net"
 	"net/http"
 	"net/url"
 	"strings"
 
-	"imuslab.com/arozos/ReverseProxy/mod/websocketproxy"
+	"imuslab.com/Zoarxy/mod/geodb"
+	"imuslab.com/Zoarxy/mod/statistic"
+	"imuslab.com/Zoarxy/mod/websocketproxy"
 )
 
 func (router *Router) getTargetProxyEndpointFromRequestURI(requestURI string) *ProxyEndpoint {
@@ -67,6 +70,7 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
 		if target.RequireTLS {
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
 		}
+		h.logRequest(r, true, 101, "subdomain-websocket")
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler.ServeHTTP(w, r)
 		return
@@ -79,11 +83,15 @@ func (h *ProxyHandler) subdomainRequest(w http.ResponseWriter, r *http.Request,
 		if errors.As(err, &dnsError) {
 			http.ServeFile(w, r, "./web/hosterror.html")
 			log.Println(err.Error())
+			h.logRequest(r, false, 404, "subdomain-http")
 		} else {
 			http.ServeFile(w, r, "./web/rperror.html")
 			log.Println(err.Error())
+			h.logRequest(r, false, 521, "subdomain-http")
 		}
 	}
+
+	h.logRequest(r, true, 200, "subdomain-http")
 }
 
 func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, target *ProxyEndpoint) {
@@ -101,6 +109,7 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
 		if target.RequireTLS {
 			u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
 		}
+		h.logRequest(r, true, 101, "vdir-websocket")
 		wspHandler := websocketproxy.NewProxy(u)
 		wspHandler.ServeHTTP(w, r)
 		return
@@ -113,10 +122,30 @@ func (h *ProxyHandler) proxyRequest(w http.ResponseWriter, r *http.Request, targ
 		if errors.As(err, &dnsError) {
 			http.ServeFile(w, r, "./web/hosterror.html")
 			log.Println(err.Error())
+			h.logRequest(r, false, 404, "vdir-http")
 		} else {
 			http.ServeFile(w, r, "./web/rperror.html")
 			log.Println(err.Error())
+			h.logRequest(r, false, 521, "vdir-http")
 		}
 	}
+	h.logRequest(r, true, 200, "vdir-http")
+
+}
 
+func (h *ProxyHandler) logRequest(r *http.Request, succ bool, statusCode int, forwardType string) {
+	if h.Parent.Option.StatisticCollector != nil {
+		go func() {
+			requestInfo := statistic.RequestInfo{
+				IpAddr:                        geodb.GetRequesterIP(r),
+				RequestOriginalCountryISOCode: h.Parent.Option.GeodbStore.GetRequesterCountryISOCode(r),
+				Succ:                          succ,
+				StatusCode:                    statusCode,
+				ForwardType:                   forwardType,
+			}
+			fmt.Println(requestInfo)
+			h.Parent.Option.StatisticCollector.RecordRequest(requestInfo)
+		}()
+
+	}
 }

+ 4 - 1
mod/dynamicproxy/redirection/handler.go

@@ -22,7 +22,8 @@ func (t *RuleTable) IsRedirectable(r *http.Request) bool {
 
 //Handle the redirect request, return after calling this function to prevent
 //multiple write to the response writer
-func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) {
+//Return the status code of the redirection handling
+func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) int {
 	requestPath := r.Host + r.URL.Path
 	rr := t.MatchRedirectRule(requestPath)
 	if rr != nil {
@@ -41,10 +42,12 @@ func (t *RuleTable) HandleRedirect(w http.ResponseWriter, r *http.Request) {
 		}
 
 		http.Redirect(w, r, redirectTarget, rr.StatusCode)
+		return rr.StatusCode
 	} else {
 		//Invalid usage
 		w.WriteHeader(http.StatusInternalServerError)
 		w.Write([]byte("500 - Internal Server Error"))
 		log.Println("Target request URL do not have matching redirect rule. Check with IsRedirectable before calling HandleRedirect!")
+		return 500
 	}
 }

+ 1 - 1
mod/dynamicproxy/redirection/redirection.go

@@ -9,7 +9,7 @@ import (
 	"strings"
 	"sync"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 type RuleTable struct {

+ 1 - 1
mod/dynamicproxy/subdomain.go

@@ -4,7 +4,7 @@ import (
 	"log"
 	"net/url"
 
-	"imuslab.com/arozos/ReverseProxy/mod/reverseproxy"
+	"imuslab.com/Zoarxy/mod/reverseproxy"
 )
 
 /*

+ 14 - 1
mod/geodb/geodb.go

@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/oschwald/geoip2-golang"
-	"imuslab.com/arozos/ReverseProxy/mod/database"
+	"imuslab.com/Zoarxy/mod/database"
 )
 
 type Store struct {
@@ -176,6 +176,19 @@ func (s *Store) IsBlacklisted(ipAddr string) bool {
 	return false
 }
 
+func (s *Store) GetRequesterCountryISOCode(r *http.Request) string {
+	ipAddr := GetRequesterIP(r)
+	if ipAddr == "" {
+		return ""
+	}
+	countryCode, err := s.ResolveCountryCodeFromIP(ipAddr)
+	if err != nil {
+		return ""
+	}
+
+	return countryCode.CountryIsoCode
+}
+
 //Utilities function
 func GetRequesterIP(r *http.Request) string {
 	ip := r.Header.Get("X-Forwarded-For")

+ 20 - 0
mod/statistic/handler.go

@@ -0,0 +1,20 @@
+package statistic
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"imuslab.com/Zoarxy/mod/utils"
+)
+
+/*
+	Handler.go
+
+	This script handles incoming request for loading the statistic of the day
+
+*/
+
+func (c *Collector) HandleTodayStatLoad(w http.ResponseWriter, r *http.Request) {
+	js, _ := json.Marshal(c.DailySummary)
+	utils.SendJSONResponse(w, string(js))
+}

+ 184 - 0
mod/statistic/statistic.go

@@ -0,0 +1,184 @@
+package statistic
+
+import (
+	"strings"
+	"sync"
+	"time"
+
+	"imuslab.com/Zoarxy/mod/database"
+)
+
+/*
+	Statistic Package
+
+	This packet is designed to collection information
+	and store them for future analysis
+*/
+
+//Faststat, a interval summary for all collected data and avoid
+//looping through every data everytime a overview is needed
+type DailySummary struct {
+	TotalRequest int64 //Total request of the day
+	ErrorRequest int64 //Invalid request of the day, including error or not found
+	ValidRequest int64 //Valid request of the day
+	//Type counters
+	ForwardTypes    sync.Map //Map that hold the forward types
+	RequestOrigin   sync.Map //Map that hold [country ISO code]: visitor counter
+	RequestClientIp sync.Map //Map that hold all unique request IPs
+}
+
+type RequestInfo struct {
+	IpAddr                        string
+	RequestOriginalCountryISOCode string
+	Succ                          bool
+	StatusCode                    int
+	ForwardType                   string
+}
+
+type CollectorOption struct {
+	Database *database.Database
+}
+
+type Collector struct {
+	rtdataStopChan chan bool
+	DailySummary   *DailySummary
+	Option         *CollectorOption
+}
+
+func NewStatisticCollector(option CollectorOption) (*Collector, error) {
+	option.Database.NewTable("stats")
+
+	//Create the collector object
+	thisCollector := Collector{
+		DailySummary: newDailySummary(),
+		Option:       &option,
+	}
+
+	//Load the stat if exists for today
+	//This will exists if the program was forcefully restarted
+	year, month, day := time.Now().Date()
+	summary := thisCollector.LoadSummaryOfDay(year, month, day)
+	if summary != nil {
+		thisCollector.DailySummary = summary
+	}
+
+	//Schedule the realtime statistic clearing at midnight everyday
+	rtstatStopChan := thisCollector.ScheduleResetRealtimeStats()
+	thisCollector.rtdataStopChan = rtstatStopChan
+
+	return &thisCollector, nil
+}
+
+//Write the current in-memory summary to database file
+func (c *Collector) SaveSummaryOfDay() {
+	//When it is called in 0:00am, make sure it is stored as yesterday key
+	t := time.Now().Add(-30 * time.Second)
+	summaryKey := t.Format("02_01_2006")
+	c.Option.Database.Write("stats", summaryKey, c.DailySummary)
+}
+
+//Load the summary of a day given
+func (c *Collector) LoadSummaryOfDay(year int, month time.Month, day int) *DailySummary {
+	date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
+	summaryKey := date.Format("02_01_2006")
+	var targetSummary = newDailySummary()
+	c.Option.Database.Read("stats", summaryKey, &targetSummary)
+	return targetSummary
+}
+
+//This function gives the current slot in the 288- 5 minutes interval of the day
+func (c *Collector) GetCurrentRealtimeStatIntervalId() int {
+	now := time.Now()
+	startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local).Unix()
+	secondsSinceStartOfDay := now.Unix() - startOfDay
+	interval := secondsSinceStartOfDay / (5 * 60)
+	return int(interval)
+}
+
+func (c *Collector) Close() {
+	//Stop the ticker
+	c.rtdataStopChan <- true
+
+	//Write the buffered data into database
+	c.SaveSummaryOfDay()
+
+}
+
+//Main function to record all the inbound traffics
+//Note that this function run in go routine and might have concurrent R/W issue
+//Please make sure there is no racing paramters in this function
+func (c *Collector) RecordRequest(ri RequestInfo) {
+	go func() {
+		c.DailySummary.TotalRequest++
+		if ri.Succ {
+			c.DailySummary.ValidRequest++
+		} else {
+			c.DailySummary.ErrorRequest++
+		}
+
+		//Store the request info into correct types of maps
+		ft, ok := c.DailySummary.ForwardTypes.Load(ri.ForwardType)
+		if !ok {
+			c.DailySummary.ForwardTypes.Store(ri.ForwardType, 1)
+		} else {
+			c.DailySummary.ForwardTypes.Store(ri.ForwardType, ft.(int)+1)
+		}
+
+		originISO := strings.ToLower(ri.RequestOriginalCountryISOCode)
+		fo, ok := c.DailySummary.RequestOrigin.Load(originISO)
+		if !ok {
+			c.DailySummary.RequestOrigin.Store(originISO, 1)
+		} else {
+			c.DailySummary.RequestOrigin.Store(originISO, fo.(int)+1)
+		}
+
+		fi, ok := c.DailySummary.RequestClientIp.Load(ri.IpAddr)
+		if !ok {
+			c.DailySummary.RequestClientIp.Store(ri.IpAddr, 1)
+		} else {
+			c.DailySummary.RequestClientIp.Store(ri.IpAddr, fi.(int)+1)
+		}
+	}()
+}
+
+//nightly task
+func (c *Collector) ScheduleResetRealtimeStats() chan bool {
+	doneCh := make(chan bool)
+
+	go func() {
+		defer close(doneCh)
+
+		for {
+			// calculate duration until next midnight
+			now := time.Now()
+
+			// Get midnight of the next day in the local time zone
+			midnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
+
+			// Calculate the duration until midnight
+			duration := midnight.Sub(now)
+			select {
+			case <-time.After(duration):
+				// store daily summary to database and reset summary
+				c.SaveSummaryOfDay()
+				c.DailySummary = newDailySummary()
+			case <-doneCh:
+				// stop the routine
+				return
+			}
+		}
+	}()
+
+	return doneCh
+}
+
+func newDailySummary() *DailySummary {
+	return &DailySummary{
+		TotalRequest:    0,
+		ErrorRequest:    0,
+		ValidRequest:    0,
+		ForwardTypes:    sync.Map{},
+		RequestOrigin:   sync.Map{},
+		RequestClientIp: sync.Map{},
+	}
+}

+ 1 - 1
mod/tlscert/tlscert.go

@@ -11,7 +11,7 @@ import (
 	"path/filepath"
 	"strings"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 type Manager struct {

+ 1 - 1
redirect.go

@@ -5,7 +5,7 @@ import (
 	"net/http"
 	"strconv"
 
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 /*

+ 3 - 2
reverseproxy.go

@@ -10,8 +10,8 @@ import (
 	"strings"
 	"time"
 
-	"imuslab.com/arozos/ReverseProxy/mod/dynamicproxy"
-	"imuslab.com/arozos/ReverseProxy/mod/utils"
+	"imuslab.com/Zoarxy/mod/dynamicproxy"
+	"imuslab.com/Zoarxy/mod/utils"
 )
 
 var (
@@ -51,6 +51,7 @@ func ReverseProxtInit() {
 		TlsManager:         tlsCertManager,
 		RedirectRuleTable:  redirectTable,
 		GeodbStore:         geodbStore,
+		StatisticCollector: statisticCollector,
 	})
 	if err != nil {
 		log.Println(err.Error())

+ 1 - 0
web/components/utils.html

@@ -30,6 +30,7 @@
 </div>
   
 <h3><i class="ui code icon"></i> IP Address Converter</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">
     <h5><i class="chevron down icon"></i> IP Range to CIDR Conversion</h5>
     <div class="ui message">

+ 24 - 2
web/index.html

@@ -70,6 +70,12 @@
                 }
             }
 
+            .menudivider{
+                font-size: 0.8em !important;
+                color: #9c9c9c !important;
+                padding-left: 0.6em;
+            }
+
         </style>
     </head>
     <body>
@@ -101,7 +107,7 @@
                     <a class="item" tag="setroot">
                         <i class="home icon"></i> Set Proxy Root
                     </a>
-                    <div class="ui divider"></div>
+                    <div class="ui divider menudivider">Access & Connections</div>
                     <a class="item" tag="cert">
                         <i class="orange lock icon"></i> TLS / SSL certificate
                     </a>
@@ -111,7 +117,23 @@
                     <a class="item" tag="blacklist">
                         <i class="red ban icon"></i> Blacklist
                     </a>
-                    <div class="ui divider"></div>
+                    <div class="ui divider menudivider">Bridging</div>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> TCP Proxy
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> HTTP over Websocket
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> UDP Hole Punching
+                    </a>
+                    <div class="ui divider menudivider">Others</div>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> Uptime Monitor
+                    </a>
+                    <a class="item" tag="">
+                        <i class="remove icon"></i> Network Tools
+                    </a>
                     <a class="item" tag="utils">
                         <i class="grey paperclip icon"></i> Utilities
                     </a>