feat: fix dashboard real-time data sync, stabilize node order and eliminate UI jitter
Release / Build & Release (push) Canceled after 0s
Release / Build & Release (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package cluster
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GeoLocation struct {
|
||||
CountryCode string `json:"countryCode"`
|
||||
Country string `json:"country"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
var (
|
||||
geoCache sync.Map
|
||||
client = &http.Client{Timeout: 4 * time.Second}
|
||||
)
|
||||
|
||||
func isPrivateIP(ipStr string) bool {
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ExtractIPFromRequest retrieves the real client IP from HTTP headers or RemoteAddr
|
||||
func ExtractIPFromRequest(r *http.Request) string {
|
||||
if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
|
||||
parts := strings.Split(fwd, ",")
|
||||
for _, p := range parts {
|
||||
clean := strings.TrimSpace(p)
|
||||
if clean != "" && !isPrivateIP(clean) {
|
||||
return clean
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
clean := strings.TrimSpace(parts[0])
|
||||
if clean != "" {
|
||||
return clean
|
||||
}
|
||||
}
|
||||
}
|
||||
if realIP := strings.TrimSpace(r.Header.Get("X-Real-IP")); realIP != "" {
|
||||
return realIP
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err == nil {
|
||||
return host
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
// ResolveIP returns countryCode (e.g. "HK", "SG") and countryName (e.g. "Hong Kong", "Singapore")
|
||||
func ResolveIP(ipStr string) (string, string) {
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
if ipStr == "" {
|
||||
return "", ""
|
||||
}
|
||||
if isPrivateIP(ipStr) {
|
||||
return "CN", "中国"
|
||||
}
|
||||
if v, ok := geoCache.Load(ipStr); ok {
|
||||
if g, ok := v.(GeoLocation); ok {
|
||||
return g.CountryCode, g.Country
|
||||
}
|
||||
}
|
||||
|
||||
url := "http://ip-api.com/json/" + ipStr + "?fields=status,country,countryCode"
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return "", ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var g GeoLocation
|
||||
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
|
||||
return "", ""
|
||||
}
|
||||
if g.Status == "success" && g.CountryCode != "" {
|
||||
geoCache.Store(ipStr, g)
|
||||
return g.CountryCode, g.Country
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// ResolveLocalPublicIP queries the hub host's public IP location
|
||||
func ResolveLocalPublicIP() (string, string) {
|
||||
if v, ok := geoCache.Load("__local__"); ok {
|
||||
if g, ok := v.(GeoLocation); ok {
|
||||
return g.CountryCode, g.Country
|
||||
}
|
||||
}
|
||||
resp, err := client.Get("http://ip-api.com/json/?fields=status,country,countryCode")
|
||||
if err != nil {
|
||||
return "HK", "中国香港"
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var g GeoLocation
|
||||
if err := json.NewDecoder(resp.Body).Decode(&g); err != nil {
|
||||
return "HK", "中国香港"
|
||||
}
|
||||
if g.Status == "success" && g.CountryCode != "" {
|
||||
geoCache.Store("__local__", g)
|
||||
return g.CountryCode, g.Country
|
||||
}
|
||||
return "HK", "中国香港"
|
||||
}
|
||||
Reference in New Issue
Block a user