package app import ( "net/http" "sort" "strings" "time" ) 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 := -1 // persistent by default expRaw := strings.TrimSpace(it.ExpiresAt) if expRaw != "" { exp, err := time.Parse(time.RFC3339, expRaw) if err == nil { rem = int(exp.Sub(now).Seconds()) if rem < 0 { rem = 0 } } else { 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"}) }