package admin import ( "net/http" "time" "github.com/gin-gonic/gin" "github.com/rose_cat707/luminary/internal/model" ) func (h *Handler) ListIPRules(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"data": h.cache.ListIPRules()}) } func (h *Handler) CreateIPRule(c *gin.Context) { var req struct { CIDR string `json:"cidr"` Type model.IPRuleType `json:"type"` Scope model.IPRuleScope `json:"scope"` Enabled *bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil || req.CIDR == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } if req.Type == "" { req.Type = model.IPRuleAllow } if req.Scope == "" { req.Scope = model.IPScopeProxy } rule := model.IPRule{CIDR: req.CIDR, Type: req.Type, Scope: req.Scope, Enabled: true} if req.Enabled != nil { rule.Enabled = *req.Enabled } if err := h.db.DB().Create(&rule).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } rules := h.cache.ListIPRules() rules = append(rules, rule) h.cache.SetIPRules(rules) c.JSON(http.StatusCreated, rule) } func (h *Handler) UpdateIPRule(c *gin.Context) { id := parseUint(c.Param("id")) var rule model.IPRule if err := h.db.DB().First(&rule, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } var req struct { CIDR string `json:"cidr"` Type model.IPRuleType `json:"type"` Scope model.IPRuleScope `json:"scope"` Enabled *bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } if req.CIDR != "" { rule.CIDR = req.CIDR } if req.Type != "" { rule.Type = req.Type } if req.Scope != "" { rule.Scope = req.Scope } if req.Enabled != nil { rule.Enabled = *req.Enabled } h.db.DB().Save(&rule) h.reloadIPRules() c.JSON(http.StatusOK, rule) } func (h *Handler) DeleteIPRule(c *gin.Context) { id := parseUint(c.Param("id")) h.db.DB().Delete(&model.IPRule{}, id) h.reloadIPRules() c.JSON(http.StatusOK, gin.H{"ok": true}) } func (h *Handler) reloadIPRules() { var rules []model.IPRule h.db.DB().Find(&rules) h.cache.SetIPRules(rules) } func (h *Handler) DashboardOverview(c *gin.Context) { since := time.Now().Add(-24 * time.Hour) var totalRequests int64 var totalTokens int64 var errorCount int64 h.db.DB().Model(&model.UsageRecord{}).Where("created_at >= ?", since).Count(&totalRequests) h.db.DB().Model(&model.UsageRecord{}).Where("created_at >= ?", since).Select("COALESCE(SUM(tokens_in + tokens_out), 0)").Scan(&totalTokens) h.db.DB().Model(&model.UsageRecord{}).Where("created_at >= ? AND status = ?", since, "error").Count(&errorCount) activeKeys := len(h.cache.ListIngressKeys()) errorRate := 0.0 if totalRequests > 0 { errorRate = float64(errorCount) / float64(totalRequests) } c.JSON(http.StatusOK, gin.H{ "total_requests_24h": totalRequests, "total_tokens_24h": totalTokens, "error_rate": errorRate, "active_ingress_keys": activeKeys, "providers": len(h.cache.ListProviders()), }) } func (h *Handler) DashboardProviders(c *gin.Context) { providers := h.cache.ListProviders() health := h.cache.GetHealthStatus() type item struct { model.Provider Health model.HealthCheckRecord `json:"health"` } var out []item for _, p := range providers { out = append(out, item{Provider: p, Health: health[p.ID]}) } c.JSON(http.StatusOK, gin.H{"data": out}) } func (h *Handler) DashboardIngressUsage(c *gin.Context) { keys := h.cache.ListIngressKeys() c.JSON(http.StatusOK, gin.H{"data": keys}) } func (h *Handler) DashboardEgressUsage(c *gin.Context) { keys := h.cache.ListAllProviderKeys() c.JSON(http.StatusOK, gin.H{"data": keys}) } func (h *Handler) DashboardProviderModelUsage(c *gin.Context) { since := time.Now().Add(-24 * time.Hour) type row struct { ProviderID uint `json:"provider_id"` Model string `json:"model"` RequestCount int64 `json:"request_count"` TokenCount int64 `json:"token_count"` Cost float64 `json:"cost"` } var rows []row h.db.DB().Model(&model.UsageRecord{}). Select("provider_id, model, COUNT(*) as request_count, COALESCE(SUM(tokens_in + tokens_out), 0) as token_count, COALESCE(SUM(cost), 0) as cost"). Where("created_at >= ? AND provider_id > 0 AND model != ''", since). Group("provider_id, model"). Order("request_count desc"). Limit(50). Scan(&rows) type item struct { ProviderID uint `json:"provider_id"` ProviderName string `json:"provider_name"` Model string `json:"model"` RequestCount int64 `json:"request_count"` TokenCount int64 `json:"token_count"` Cost float64 `json:"cost"` } out := make([]item, 0, len(rows)) for _, r := range rows { name := "" if p, ok := h.cache.GetProvider(r.ProviderID); ok { name = p.Name } out = append(out, item{ ProviderID: r.ProviderID, ProviderName: name, Model: r.Model, RequestCount: r.RequestCount, TokenCount: r.TokenCount, Cost: r.Cost, }) } c.JSON(http.StatusOK, gin.H{"data": out}) } func (h *Handler) DashboardClientIPUsage(c *gin.Context) { since := time.Now().Add(-24 * time.Hour) type item struct { ClientIP string `json:"client_ip"` RequestCount int64 `json:"request_count"` TokenCount int64 `json:"token_count"` Cost float64 `json:"cost"` } var out []item h.db.DB().Model(&model.UsageRecord{}). Select("client_ip, COUNT(*) as request_count, COALESCE(SUM(tokens_in + tokens_out), 0) as token_count, COALESCE(SUM(cost), 0) as cost"). Where("created_at >= ? AND client_ip != ''", since). Group("client_ip"). Order("request_count desc"). Limit(50). Scan(&out) c.JSON(http.StatusOK, gin.H{"data": out}) } func (h *Handler) DashboardRequestTrend(c *gin.Context) { since := time.Now().Add(-23 * time.Hour).Truncate(time.Hour) type aggRow struct { Hour string `gorm:"column:hour"` Requests int64 `gorm:"column:requests"` Errors int64 `gorm:"column:errors"` Tokens int64 `gorm:"column:tokens"` } var rows []aggRow h.db.DB().Model(&model.UsageRecord{}). Select(`strftime('%Y-%m-%d %H:00', created_at) as hour, COUNT(*) as requests, SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors, COALESCE(SUM(tokens_in + tokens_out), 0) as tokens`). Where("created_at >= ?", since). Group("hour"). Order("hour"). Scan(&rows) byHour := make(map[string]aggRow, len(rows)) for _, r := range rows { byHour[r.Hour] = r } type bucket struct { Hour string `json:"hour"` Requests int64 `json:"requests"` Errors int64 `json:"errors"` Tokens int64 `json:"tokens"` } out := make([]bucket, 24) for i := 0; i < 24; i++ { t := since.Add(time.Duration(i) * time.Hour) key := t.Format("2006-01-02 15:00") if r, ok := byHour[key]; ok { out[i] = bucket{Hour: key, Requests: r.Requests, Errors: r.Errors, Tokens: r.Tokens} } else { out[i] = bucket{Hour: key} } } c.JSON(http.StatusOK, gin.H{"data": out}) }