- Create SoraAdminView with overview, user stats, and generations tabs - Add /admin/sora route for Sora management - Add i18n support (zh/en) for Sora admin page - Extract Prometheus metrics to prommetrics package to avoid import cycles - Integrate SetDBConnections/SetRedisConnections in OpsMetricsCollector
211 lines
5.1 KiB
Go
211 lines
5.1 KiB
Go
package routes
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/prommetrics"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
// HealthChecker defines the interface for health check dependencies
|
|
type HealthChecker interface {
|
|
CheckDatabase() bool
|
|
CheckRedis() bool
|
|
}
|
|
|
|
var (
|
|
healthChecker HealthChecker
|
|
healthCheckerOnce sync.Once
|
|
)
|
|
|
|
// SetHealthChecker sets the health checker instance (called during app initialization)
|
|
func SetHealthChecker(checker HealthChecker) {
|
|
healthCheckerOnce.Do(func() {
|
|
healthChecker = checker
|
|
})
|
|
}
|
|
|
|
// RegisterCommonRoutes registers common routes (health check, metrics, etc.)
|
|
func RegisterCommonRoutes(r *gin.Engine) {
|
|
// Health check - enhanced with dependency checks
|
|
r.GET("/health", healthHandler)
|
|
|
|
// Readiness check - for Kubernetes readiness probe
|
|
r.GET("/ready", readinessHandler)
|
|
|
|
// Liveness check - for Kubernetes liveness probe
|
|
r.GET("/live", livenessHandler)
|
|
|
|
// Prometheus metrics endpoint - use the shared registry from prommetrics package
|
|
r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(prommetrics.Registry, promhttp.HandlerOpts{})))
|
|
|
|
// Claude Code telemetry logs (ignore, return 200 directly)
|
|
r.POST("/api/event_logging/batch", func(c *gin.Context) {
|
|
c.Status(http.StatusOK)
|
|
})
|
|
|
|
// Setup status endpoint (always returns needs_setup: false in normal mode)
|
|
// This is used by the frontend to detect when the service has restarted after setup
|
|
r.GET("/setup/status", func(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"code": 0,
|
|
"data": gin.H{
|
|
"needs_setup": false,
|
|
"step": "completed",
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
// healthHandler returns the health status of the service and its dependencies
|
|
func healthHandler(c *gin.Context) {
|
|
status := "ok"
|
|
statusCode := http.StatusOK
|
|
|
|
components := make(map[string]interface{})
|
|
allHealthy := true
|
|
|
|
// Check database
|
|
dbStatus := "unknown"
|
|
dbHealthy := false
|
|
if healthChecker != nil {
|
|
dbHealthy = healthChecker.CheckDatabase()
|
|
if dbHealthy {
|
|
dbStatus = "healthy"
|
|
} else {
|
|
dbStatus = "unhealthy"
|
|
allHealthy = false
|
|
}
|
|
}
|
|
components["database"] = gin.H{
|
|
"status": dbStatus,
|
|
"healthy": dbHealthy,
|
|
}
|
|
|
|
// Check Redis
|
|
redisStatus := "unknown"
|
|
redisHealthy := false
|
|
if healthChecker != nil {
|
|
redisHealthy = healthChecker.CheckRedis()
|
|
if redisHealthy {
|
|
redisStatus = "healthy"
|
|
} else {
|
|
redisStatus = "unhealthy"
|
|
allHealthy = false
|
|
}
|
|
}
|
|
components["redis"] = gin.H{
|
|
"status": redisStatus,
|
|
"healthy": redisHealthy,
|
|
}
|
|
|
|
// Overall status
|
|
if !allHealthy {
|
|
status = "degraded"
|
|
statusCode = http.StatusServiceUnavailable
|
|
}
|
|
|
|
response := gin.H{
|
|
"status": status,
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
"components": components,
|
|
}
|
|
|
|
c.JSON(statusCode, response)
|
|
}
|
|
|
|
// readinessHandler checks if the service is ready to accept traffic
|
|
func readinessHandler(c *gin.Context) {
|
|
// For readiness, we require all critical dependencies to be healthy
|
|
if healthChecker == nil {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "ready",
|
|
})
|
|
return
|
|
}
|
|
|
|
dbHealthy := healthChecker.CheckDatabase()
|
|
redisHealthy := healthChecker.CheckRedis()
|
|
|
|
if dbHealthy && redisHealthy {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "ready",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
})
|
|
return
|
|
}
|
|
|
|
components := make(map[string]bool)
|
|
components["database"] = dbHealthy
|
|
components["redis"] = redisHealthy
|
|
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{
|
|
"status": "not_ready",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
"components": components,
|
|
})
|
|
}
|
|
|
|
// livenessHandler checks if the service is alive (for Kubernetes liveness probe)
|
|
func livenessHandler(c *gin.Context) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"status": "alive",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
// Prometheus metric update functions - delegate to prommetrics package
|
|
|
|
// RecordHTTPRequest records an HTTP request for Prometheus metrics
|
|
func RecordHTTPRequest(method, path string, statusCode int, duration time.Duration) {
|
|
prommetrics.RecordHTTPRequest(method, path, statusCode, duration)
|
|
}
|
|
|
|
// SetDBConnections sets database connection metrics
|
|
func SetDBConnections(active, idle int) {
|
|
prommetrics.SetDBConnections(active, idle)
|
|
}
|
|
|
|
// SetRedisConnections sets Redis connection metrics
|
|
func SetRedisConnections(total, idle int) {
|
|
prommetrics.SetRedisConnections(total, idle)
|
|
}
|
|
|
|
// SetActiveAccounts sets the active accounts count
|
|
func SetActiveAccounts(count int) {
|
|
prommetrics.SetActiveAccounts(count)
|
|
}
|
|
|
|
// SetRequestQueueDepth sets the request queue depth
|
|
func SetRequestQueueDepth(depth int) {
|
|
prommetrics.SetRequestQueueDepth(depth)
|
|
}
|
|
|
|
// SetOpsHealthScore sets the overall health score
|
|
func SetOpsHealthScore(score int) {
|
|
prommetrics.SetOpsHealthScore(score)
|
|
}
|
|
|
|
// SetErrorRate sets the error rate
|
|
func SetErrorRate(rate float64) {
|
|
prommetrics.SetErrorRate(rate)
|
|
}
|
|
|
|
// SetSuccessRate sets the success rate
|
|
func SetSuccessRate(rate float64) {
|
|
prommetrics.SetSuccessRate(rate)
|
|
}
|
|
|
|
// SetQPS sets queries per second
|
|
func SetQPS(value float64) {
|
|
prommetrics.SetQPS(value)
|
|
}
|
|
|
|
// SetTPS sets tokens per second
|
|
func SetTPS(value float64) {
|
|
prommetrics.SetTPS(value)
|
|
}
|