Files
ProxmoxDash/traffic_history.go

145 lines
4.7 KiB
Go

package main
import (
"net/http"
"strconv"
"strings"
"time"
)
type TrafficIPDetail struct {
IP string `json:"ip"`
Country string `json:"country"`
Requests int64 `json:"requests"`
LastSeen int64 `json:"lastSeen"`
City string `json:"city,omitempty"`
Region string `json:"region,omitempty"`
}
type TrafficHistory struct {
Events []TrafficEvent `json:"events"`
IPs []TrafficIPDetail `json:"ips"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
Snapshot int64 `json:"snapshot"`
MaxSequence int64 `json:"maxSequence"`
CityReady bool `json:"cityReady"`
}
func (s *Store) trafficHistoryRoute(mux *http.ServeMux) {
mux.HandleFunc("/api/traffic/history", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.WriteHeader(405)
return
}
if !s.TrafficSettings().Enabled {
http.Error(w, "Карта отключена", 404)
return
}
q := r.URL.Query()
page, _ := strconv.Atoi(q.Get("page"))
snapshot, _ := strconv.ParseInt(q.Get("snapshot"), 10, 64)
maxSeq, _ := strconv.ParseInt(q.Get("maxSequence"), 10, 64)
if q.Get("maxSequence") == "0" {
maxSeq = -1
}
if page < 0 || page > 10000 {
http.Error(w, "Invalid page", 400)
return
}
kind := q.Get("kind")
metricType := q.Get("type")
if metricType != "" && metricType != "request" && metricType != "alert" && metricType != "decision" {
http.Error(w, "Invalid type", 400)
return
}
if kind != "" && kind != "request" && kind != "alert" && kind != "decision" {
http.Error(w, "Invalid kind", 400)
return
}
result, err := s.TrafficHistory(q.Get("domain"), kind, q.Get("country"), q.Get("ip"), q.Get("type"), q.Get("external") == "1", q.Get("hideMonitoring") == "1", q.Get("group") == "ip", page, snapshot, maxSeq)
writeJSON(w, result, err)
})
}
func (s *Store) TrafficHistory(domain, kind, country, ip, metricType string, external, hide, group bool, page int, snapshot, maxSeq int64) (TrafficHistory, error) {
now := time.Now().Unix()
if snapshot <= 0 || snapshot > now {
snapshot = now
}
if snapshot < now-86400 {
snapshot = now
}
settings := s.TrafficSettings()
v := TrafficHistory{Events: []TrafficEvent{}, IPs: []TrafficIPDetail{}, Page: page, PageSize: 50, Snapshot: snapshot, CityReady: trafficCityReady()}
if maxSeq == 0 {
if err := s.db.QueryRow(`SELECT COALESCE(MAX(seq),0) FROM traffic_events WHERE agent=?`, settings.AgentID).Scan(&maxSeq); err != nil {
return v, err
}
}
v.MaxSequence = maxSeq
where := ` WHERE agent=? AND ts>=? AND ts<=? AND seq<=?`
args := []any{settings.AgentID, snapshot - 86400, snapshot, maxSeq}
if hide {
clause, values := monitoringSQL(settings.MonitoringRules)
where += clause
args = append(args, values...)
}
if external {
where += ` AND country!='LAN'`
}
for _, filter := range []struct{ column, value string }{{"domain", domain}, {"kind", kind}, {"kind", metricType}, {"country", country}, {"ip", ip}} {
if filter.value != "" {
where += " AND " + filter.column + "=?"
args = append(args, filter.value)
}
}
count := "COUNT(*)"
if group {
where += ` AND kind='request'`
count = "COUNT(DISTINCT ip)"
}
if err := s.db.QueryRow("SELECT "+count+" FROM traffic_events"+where, args...).Scan(&v.Total); err != nil {
return v, err
}
paged := append(append([]any{}, args...), v.PageSize, page*v.PageSize)
if group {
rows, err := s.db.Query(`SELECT ip,MIN(country),COUNT(*),MAX(ts) FROM traffic_events`+where+` GROUP BY ip ORDER BY COUNT(*) DESC,ip LIMIT ? OFFSET ?`, paged...)
if err != nil {
return v, err
}
defer rows.Close()
for rows.Next() {
var item TrafficIPDetail
if err = rows.Scan(&item.IP, &item.Country, &item.Requests, &item.LastSeen); err != nil {
return v, err
}
item.City, item.Region = lookupTrafficCity(item.IP)
v.IPs = append(v.IPs, item)
}
return v, rows.Err()
}
rows, err := s.db.Query(`SELECT seq,event_id,ts,kind,ip,domain,method,path,status,reason,country,agent FROM traffic_events`+where+` ORDER BY ts DESC,seq DESC LIMIT ? OFFSET ?`, paged...)
if err != nil {
return v, err
}
defer rows.Close()
for rows.Next() {
var e TrafficEvent
if err = rows.Scan(&e.Sequence, &e.ID, &e.Time, &e.Kind, &e.IP, &e.Domain, &e.Method, &e.Path, &e.Status, &e.Reason, &e.Country, &e.Agent); err != nil {
return v, err
}
e.Monitoring = isMonitoringRequest(e, settings.MonitoringRules)
e.City, e.Region = lookupTrafficCity(e.IP)
v.Events = append(v.Events, e)
}
return v, rows.Err()
}
func cityName(names map[string]string) string {
for _, lang := range []string{"ru", "en"} {
if s := strings.TrimSpace(names[lang]); s != "" {
return s
}
}
return ""
}