package admin import ( "database/sql" "errors" "net/http" "strconv" "time" "git.asio.asia/nixevol/NixMsg/internal/httpx" ) func (h *Handler) handleOverview(w http.ResponseWriter, r *http.Request) { ctx := r.Context() var total, selfCount, disabled, online int err := h.db.Read.QueryRowContext(ctx, ` SELECT COUNT(*), COALESCE(SUM(CASE WHEN source = 'self' THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END), 0), COALESCE(SUM(CASE WHEN online_since IS NOT NULL AND (offline_since IS NULL OR online_since > offline_since) THEN 1 ELSE 0 END), 0) FROM endpoints`).Scan(&total, &selfCount, &disabled, &online) if err != nil { httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } var groupsTotal int if err := h.db.Read.QueryRowContext(ctx, `SELECT COUNT(*) FROM groups`).Scan(&groupsTotal); err != nil { httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } var pending int if err := h.db.Read.QueryRowContext(ctx, `SELECT COUNT(*) FROM deliveries WHERE state = 'pending'`).Scan(&pending); err != nil { httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } var scheduled int if err := h.db.Read.QueryRowContext(ctx, `SELECT COUNT(*) FROM messages WHERE state = 'scheduled'`).Scan(&scheduled); err != nil { httpx.WriteError(w, http.StatusInternalServerError, "internal", "内部错误") return } httpx.WriteOK(w, map[string]any{ "version": h.version, "endpoints_total": total, "endpoints_self": selfCount, "endpoints_online": online, "endpoints_disabled": disabled, "groups_total": groupsTotal, "messages_pending": pending, "messages_scheduled": scheduled, "uptime_ms": time.Since(h.startedAt).Milliseconds(), }) } func (h *Handler) handleSettingsGet(w http.ResponseWriter, _ *http.Request) { cfg := h.cfg httpx.WriteOK(w, map[string]any{ "listen": cfg.Listen, "admin_listen": cfg.AdminListen, "session_idle_days": cfg.SessionIdleDays, "record_retention_days": cfg.RecordRetentionDays, "idempotency_hours": cfg.IdempotencyHours, "receipt_retention_days": cfg.ReceiptRetentionDays, "sqlite_synchronous": cfg.SQLiteSynchronous, "limits": map[string]any{ "max_body_bytes": cfg.Limits.MaxBodyBytes, "max_meta_bytes": cfg.Limits.MaxMetaBytes, "max_frame_bytes": cfg.Limits.MaxFrameBytes, "max_ttl_seconds": cfg.Limits.MaxTTLSeconds, "max_schedule_seconds": cfg.Limits.MaxScheduleSeconds, "max_group_members": cfg.Limits.MaxGroupMembers, "grace_seconds": cfg.Limits.GraceSeconds, "ack_timeout_seconds": cfg.Limits.AckTimeoutSeconds, "delivery_window": cfg.Limits.DeliveryWindow, "receipt_window": cfg.Limits.ReceiptWindow, "requests_per_second": cfg.Limits.RequestsPerSecond, "max_pending_per_sender": cfg.Limits.MaxPendingPerSender, "max_pending_per_receiver": cfg.Limits.MaxPendingPerReceiver, }, }) } func nullInt64API(v sql.NullInt64) any { if !v.Valid { return nil } return v.Int64 } func parseInt64Query(s string) (int64, bool) { if s == "" { return 0, false } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return 0, false } return n, true } func isNoRows(err error) bool { return errors.Is(err, sql.ErrNoRows) }