145 lines
4.7 KiB
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 ""
|
|
}
|