Files
Luminary/internal/handler/admin/routing_logs.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

221 lines
6.1 KiB
Go

package admin
import (
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/rose_cat707/luminary/internal/model"
"github.com/rose_cat707/luminary/internal/pricing"
)
func (h *Handler) ListRoutingRules(c *gin.Context) {
ingressID := parseUint(c.Param("id"))
c.JSON(http.StatusOK, gin.H{"data": h.cache.ListRoutingRules(ingressID)})
}
func (h *Handler) CreateRoutingRule(c *gin.Context) {
ingressID := parseUint(c.Param("id"))
var req struct {
ModelPattern string `json:"model_pattern"`
ProviderID uint `json:"provider_id"`
ProviderKeyID uint `json:"provider_key_id"`
Priority int `json:"priority"`
Enabled *bool `json:"enabled"`
}
if err := c.ShouldBindJSON(&req); err != nil || req.ModelPattern == "" || req.ProviderID == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
rule := model.RoutingRule{
IngressKeyID: ingressID,
ModelPattern: req.ModelPattern,
ProviderID: req.ProviderID,
ProviderKeyID: req.ProviderKeyID,
Priority: req.Priority,
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.ListRoutingRules(ingressID)
rules = append(rules, rule)
h.cache.SetRoutingRules(ingressID, rules)
c.JSON(http.StatusCreated, rule)
}
func (h *Handler) UpdateRoutingRule(c *gin.Context) {
ruleID := parseUint(c.Param("ruleId"))
var rule model.RoutingRule
if err := h.db.DB().First(&rule, ruleID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
var req struct {
ModelPattern string `json:"model_pattern"`
ProviderID uint `json:"provider_id"`
ProviderKeyID uint `json:"provider_key_id"`
Priority int `json:"priority"`
Enabled *bool `json:"enabled"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
return
}
if req.ModelPattern != "" {
rule.ModelPattern = req.ModelPattern
}
if req.ProviderID > 0 {
rule.ProviderID = req.ProviderID
}
if req.ProviderKeyID >= 0 {
rule.ProviderKeyID = req.ProviderKeyID
}
rule.Priority = req.Priority
if req.Enabled != nil {
rule.Enabled = *req.Enabled
}
h.db.DB().Save(&rule)
h.reloadRoutingRules(rule.IngressKeyID)
c.JSON(http.StatusOK, rule)
}
func (h *Handler) DeleteRoutingRule(c *gin.Context) {
ruleID := parseUint(c.Param("ruleId"))
var rule model.RoutingRule
if err := h.db.DB().First(&rule, ruleID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
h.db.DB().Delete(&rule)
h.reloadRoutingRules(rule.IngressKeyID)
c.JSON(http.StatusOK, gin.H{"ok": true})
}
func (h *Handler) reloadRoutingRules(ingressID uint) {
var rules []model.RoutingRule
h.db.DB().Where("ingress_key_id = ?", ingressID).Find(&rules)
h.cache.SetRoutingRules(ingressID, rules)
}
func (h *Handler) ListRequestLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
if page < 1 {
page = 1
}
if pageSize <= 0 || pageSize > 50 {
pageSize = 20
}
q := h.db.DB().Model(&model.UsageRecord{})
if ingressID := c.Query("ingress_key_id"); ingressID != "" {
q = q.Where("ingress_key_id = ?", ingressID)
}
if status := c.Query("status"); status != "" {
q = q.Where("status = ?", status)
}
if endpoint := c.Query("endpoint"); endpoint != "" {
q = q.Where("endpoint = ?", endpoint)
}
if clientIP := c.Query("client_ip"); clientIP != "" {
q = q.Where("client_ip LIKE ?", "%"+clientIP+"%")
}
if modelName := c.Query("model"); modelName != "" {
q = q.Where("model LIKE ?", "%"+modelName+"%")
}
var total int64
q.Count(&total)
var logs []model.UsageRecord
q.Order("created_at desc").
Offset((page - 1) * pageSize).
Limit(pageSize).
Find(&logs)
type item struct {
ID uint `json:"id"`
IngressKeyID uint `json:"ingress_key_id"`
IngressName string `json:"ingress_name"`
ClientIP string `json:"client_ip"`
Endpoint string `json:"endpoint"`
Model string `json:"model"`
LatencyMs int64 `json:"latency_ms"`
Status string `json:"status"`
Stream bool `json:"stream"`
ErrorMsg string `json:"error_msg"`
CreatedAt time.Time `json:"created_at"`
}
out := make([]item, 0, len(logs))
for _, log := range logs {
name := ""
if log.Endpoint == model.EndpointAdminLogin {
name = "管理台"
} else if k, ok := h.cache.GetIngressKey(log.IngressKeyID); ok {
name = k.Name
}
out = append(out, item{
ID: log.ID,
IngressKeyID: log.IngressKeyID,
IngressName: name,
ClientIP: log.ClientIP,
Endpoint: log.Endpoint,
Model: log.Model,
LatencyMs: log.LatencyMs,
Status: log.Status,
Stream: log.Stream,
ErrorMsg: log.ErrorMsg,
CreatedAt: log.CreatedAt,
})
}
c.JSON(http.StatusOK, gin.H{
"data": out,
"total": total,
"page": page,
"page_size": pageSize,
"max_kept": model.MaxUsageLogRecords,
})
}
func (h *Handler) GetRequestLog(c *gin.Context) {
id := parseUint(c.Param("id"))
var log model.UsageRecord
if err := h.db.DB().First(&log, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
name := ""
if log.Endpoint == model.EndpointAdminLogin {
name = "管理台"
} else if k, ok := h.cache.GetIngressKey(log.IngressKeyID); ok {
name = k.Name
}
c.JSON(http.StatusOK, gin.H{
"data": gin.H{
"id": log.ID,
"ingress_key_id": log.IngressKeyID,
"ingress_name": name,
"client_ip": log.ClientIP,
"endpoint": log.Endpoint,
"model": log.Model,
"latency_ms": log.LatencyMs,
"status": log.Status,
"stream": log.Stream,
"error_msg": log.ErrorMsg,
"request_body": log.RequestBody,
"response_body": log.ResponseBody,
"created_at": log.CreatedAt,
},
})
}
func (h *Handler) PricingCatalog(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": pricing.ListCatalog()})
}