traffic: expose runtime appmarks items + show in gui
This commit is contained in:
@@ -148,6 +148,8 @@ func Run() {
|
||||
mux.HandleFunc("/api/v1/traffic/candidates", handleTrafficCandidates)
|
||||
// per-app runtime marks (systemd scope / cgroup -> fwmark)
|
||||
mux.HandleFunc("/api/v1/traffic/appmarks", handleTrafficAppMarks)
|
||||
// list runtime marks items (for UI)
|
||||
mux.HandleFunc("/api/v1/traffic/appmarks/items", handleTrafficAppMarksItems)
|
||||
// persistent app profiles (saved launch configs)
|
||||
mux.HandleFunc("/api/v1/traffic/app-profiles", handleTrafficAppProfiles)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -238,6 +239,57 @@ func handleTrafficAppMarks(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func handleTrafficAppMarksItems(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
_ = pruneExpiredAppMarks()
|
||||
|
||||
appMarksMu.Lock()
|
||||
st := loadAppMarksState()
|
||||
appMarksMu.Unlock()
|
||||
|
||||
now := time.Now().UTC()
|
||||
items := make([]TrafficAppMarkItemView, 0, len(st.Items))
|
||||
for _, it := range st.Items {
|
||||
rem := 0
|
||||
exp, err := time.Parse(time.RFC3339, strings.TrimSpace(it.ExpiresAt))
|
||||
if err == nil {
|
||||
rem = int(exp.Sub(now).Seconds())
|
||||
if rem < 0 {
|
||||
rem = 0
|
||||
}
|
||||
}
|
||||
items = append(items, TrafficAppMarkItemView{
|
||||
ID: it.ID,
|
||||
Target: it.Target,
|
||||
Cgroup: it.Cgroup,
|
||||
CgroupRel: it.CgroupRel,
|
||||
Level: it.Level,
|
||||
Unit: it.Unit,
|
||||
Command: it.Command,
|
||||
AppKey: it.AppKey,
|
||||
AddedAt: it.AddedAt,
|
||||
ExpiresAt: it.ExpiresAt,
|
||||
RemainingSec: rem,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort: target -> app_key -> remaining desc.
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
if items[i].Target != items[j].Target {
|
||||
return items[i].Target < items[j].Target
|
||||
}
|
||||
if items[i].AppKey != items[j].AppKey {
|
||||
return items[i].AppKey < items[j].AppKey
|
||||
}
|
||||
return items[i].RemainingSec > items[j].RemainingSec
|
||||
})
|
||||
|
||||
writeJSON(w, http.StatusOK, TrafficAppMarksItemsResponse{Items: items, Message: "ok"})
|
||||
}
|
||||
|
||||
func appMarksGetStatus() (vpnCount int, directCount int) {
|
||||
_ = pruneExpiredAppMarks()
|
||||
|
||||
|
||||
@@ -227,6 +227,27 @@ type TrafficAppMarksStatusResponse struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// EN: Detailed list item for runtime per-app marks (for UI).
|
||||
// RU: Детальный элемент списка runtime per-app меток (для UI).
|
||||
type TrafficAppMarkItemView struct {
|
||||
ID uint64 `json:"id"`
|
||||
Target string `json:"target"` // vpn|direct
|
||||
Cgroup string `json:"cgroup,omitempty"`
|
||||
CgroupRel string `json:"cgroup_rel,omitempty"`
|
||||
Level int `json:"level,omitempty"`
|
||||
Unit string `json:"unit,omitempty"`
|
||||
Command string `json:"command,omitempty"`
|
||||
AppKey string `json:"app_key,omitempty"`
|
||||
AddedAt string `json:"added_at,omitempty"`
|
||||
ExpiresAt string `json:"expires_at,omitempty"`
|
||||
RemainingSec int `json:"remaining_sec,omitempty"`
|
||||
}
|
||||
|
||||
type TrafficAppMarksItemsResponse struct {
|
||||
Items []TrafficAppMarkItemView `json:"items"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// traffic app profiles (persistent app launcher configs)
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user