Files
Luminary/internal/handler/admin/ip_dashboard.go
T
renjue 76ba500417
CI / docker (push) Successful in 2m4s
Initial commit: Luminary AI Gateway
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress,
ingress key governance, monitoring, and security controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 21:46:16 +08:00

244 lines
7.0 KiB
Go

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})
}