mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(api): add dashboard summary and system realtime endpoints
Add new admin API endpoints for dashboard metrics and system-wide realtime statistics: - Add /admin/dashboard/summary endpoint with aggregated metrics including requests, tokens, latency, masters, keys, and provider keys statistics with time period filtering - Add /admin/realtime endpoint for system-level realtime stats aggregated across all masters - Add status filter parameter to ListAPIKeys endpoint - Add hour grouping option to log stats aggregation - Update OpenAPI documentation with new endpoints and schemas
This commit is contained in:
@@ -219,6 +219,7 @@ func main() {
|
|||||||
handler := api.NewHandler(db, logDB, syncService, logWriter, rdb, logPartitioner)
|
handler := api.NewHandler(db, logDB, syncService, logWriter, rdb, logPartitioner)
|
||||||
adminHandler := api.NewAdminHandler(db, logDB, masterService, syncService, statsService, logPartitioner)
|
adminHandler := api.NewAdminHandler(db, logDB, masterService, syncService, statsService, logPartitioner)
|
||||||
masterHandler := api.NewMasterHandler(db, logDB, masterService, syncService, statsService, logPartitioner)
|
masterHandler := api.NewMasterHandler(db, logDB, masterService, syncService, statsService, logPartitioner)
|
||||||
|
dashboardHandler := api.NewDashboardHandler(db, logDB, statsService, logPartitioner)
|
||||||
internalHandler := api.NewInternalHandler(db)
|
internalHandler := api.NewInternalHandler(db)
|
||||||
featureHandler := api.NewFeatureHandler(rdb)
|
featureHandler := api.NewFeatureHandler(rdb)
|
||||||
authHandler := api.NewAuthHandler(db, rdb, adminService, masterService)
|
authHandler := api.NewAuthHandler(db, rdb, adminService, masterService)
|
||||||
@@ -354,6 +355,8 @@ func main() {
|
|||||||
adminGroup.GET("/logs/webhook", handler.GetLogWebhookConfig)
|
adminGroup.GET("/logs/webhook", handler.GetLogWebhookConfig)
|
||||||
adminGroup.PUT("/logs/webhook", handler.UpdateLogWebhookConfig)
|
adminGroup.PUT("/logs/webhook", handler.UpdateLogWebhookConfig)
|
||||||
adminGroup.GET("/stats", adminHandler.GetAdminStats)
|
adminGroup.GET("/stats", adminHandler.GetAdminStats)
|
||||||
|
adminGroup.GET("/realtime", adminHandler.GetAdminRealtime)
|
||||||
|
adminGroup.GET("/dashboard/summary", dashboardHandler.GetSummary)
|
||||||
adminGroup.GET("/apikey-stats/summary", adminHandler.GetAPIKeyStatsSummary)
|
adminGroup.GET("/apikey-stats/summary", adminHandler.GetAPIKeyStatsSummary)
|
||||||
adminGroup.POST("/bindings", handler.CreateBinding)
|
adminGroup.POST("/bindings", handler.CreateBinding)
|
||||||
adminGroup.GET("/bindings", handler.ListBindings)
|
adminGroup.GET("/bindings", handler.ListBindings)
|
||||||
|
|||||||
451
docs/docs.go
451
docs/docs.go
@@ -51,7 +51,7 @@ const docTemplate = `{
|
|||||||
"AdminAuth": []
|
"AdminAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "List API keys",
|
"description": "List API keys with optional filters",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -77,6 +77,12 @@ const docTemplate = `{
|
|||||||
"description": "filter by group_id",
|
"description": "filter by group_id",
|
||||||
"name": "group_id",
|
"name": "group_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "filter by status (active, suspended, auto_disabled, manual_disabled)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -362,6 +368,37 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/apikey-stats/summary": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Aggregate APIKey success/failure stats across all provider groups",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "APIKey stats summary (admin)",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.APIKeyStatsSummaryResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/bindings": {
|
"/admin/bindings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -680,6 +717,63 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/dashboard/summary": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns aggregated metrics for dashboard display including requests, tokens, latency, masters, keys, and provider keys statistics",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Dashboard summary",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "time period: today, week, month, all",
|
||||||
|
"name": "period",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "unix seconds",
|
||||||
|
"name": "since",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "unix seconds",
|
||||||
|
"name": "until",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.DashboardSummaryResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/features": {
|
"/admin/features": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1011,7 +1105,7 @@ const docTemplate = `{
|
|||||||
"AdminAuth": []
|
"AdminAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.",
|
"description": "Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month/hour). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1036,10 +1130,11 @@ const docTemplate = `{
|
|||||||
"enum": [
|
"enum": [
|
||||||
"model",
|
"model",
|
||||||
"day",
|
"day",
|
||||||
"month"
|
"month",
|
||||||
|
"hour"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "group by dimension: model, day, month. Returns GroupedStatsResponse when specified.",
|
"description": "group by dimension: model, day, month, hour. Returns GroupedStatsResponse when specified.",
|
||||||
"name": "group_by",
|
"name": "group_by",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
@@ -2776,6 +2871,37 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/realtime": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Return aggregated realtime counters across all masters",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "System-level realtime stats (admin)",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.SystemRealtimeView"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/stats": {
|
"/admin/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2898,6 +3024,52 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/internal/apikey-stats/flush": {
|
||||||
|
"post": {
|
||||||
|
"description": "Internal endpoint for flushing accumulated APIKey stats from DP to CP database",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"internal"
|
||||||
|
],
|
||||||
|
"summary": "Flush API key stats",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Stats to flush",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.apiKeyStatsFlushRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/internal/stats/flush": {
|
"/internal/stats/flush": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Internal endpoint for flushing accumulated key usage stats from DP to CP database",
|
"description": "Internal endpoint for flushing accumulated key usage stats from DP to CP database",
|
||||||
@@ -3573,6 +3745,12 @@ const docTemplate = `{
|
|||||||
"github_com_ez-api_ez-api_internal_dto.APIKeyDTO": {
|
"github_com_ez-api_ez-api_internal_dto.APIKeyDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3585,9 +3763,18 @@ const docTemplate = `{
|
|||||||
"ban_until": {
|
"ban_until": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"group_id": {
|
"group_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"project_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3666,6 +3853,9 @@ const docTemplate = `{
|
|||||||
"google_project": {
|
"google_project": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"headers_profile": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"models": {
|
"models": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -3675,6 +3865,9 @@ const docTemplate = `{
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"static_headers": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3686,6 +3879,12 @@ const docTemplate = `{
|
|||||||
"github_com_ez-api_ez-api_internal_model.APIKey": {
|
"github_com_ez-api_ez-api_internal_model.APIKey": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3704,15 +3903,36 @@ const docTemplate = `{
|
|||||||
"deletedAt": {
|
"deletedAt": {
|
||||||
"$ref": "#/definitions/gorm.DeletedAt"
|
"$ref": "#/definitions/gorm.DeletedAt"
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"group_id": {
|
"group_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"project_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3911,12 +4131,21 @@ const docTemplate = `{
|
|||||||
"deletedAt": {
|
"deletedAt": {
|
||||||
"$ref": "#/definitions/gorm.DeletedAt"
|
"$ref": "#/definitions/gorm.DeletedAt"
|
||||||
},
|
},
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"google_location": {
|
"google_location": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"google_project": {
|
"google_project": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"headers_profile": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
@@ -3927,9 +4156,21 @@ const docTemplate = `{
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"static_headers": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"description": "openai, anthropic, gemini",
|
"description": "openai, anthropic, gemini",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -4046,6 +4287,26 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.APIKeyStatsSummaryResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.AboutResponse": {
|
"internal_api.AboutResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4161,6 +4422,17 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.CountStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.CreateMasterRequest": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -4182,6 +4454,41 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.DashboardSummaryResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keys": {
|
||||||
|
"$ref": "#/definitions/internal_api.CountStats"
|
||||||
|
},
|
||||||
|
"latency": {
|
||||||
|
"$ref": "#/definitions/internal_api.LatencyStats"
|
||||||
|
},
|
||||||
|
"masters": {
|
||||||
|
"$ref": "#/definitions/internal_api.CountStats"
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"provider_keys": {
|
||||||
|
"$ref": "#/definitions/internal_api.ProviderKeyStats"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"$ref": "#/definitions/internal_api.RequestStats"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"$ref": "#/definitions/internal_api.TokenStats"
|
||||||
|
},
|
||||||
|
"top_models": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.TopModelStat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.DeleteLogsRequest": {
|
"internal_api.DeleteLogsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4217,6 +4524,10 @@ const docTemplate = `{
|
|||||||
"description": "For group_by=day",
|
"description": "For group_by=day",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"hour": {
|
||||||
|
"description": "For group_by=hour",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"description": "For group_by=model",
|
"description": "For group_by=model",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -4284,6 +4595,14 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.LatencyStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_ms": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.ListLogsResponse": {
|
"internal_api.ListLogsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4460,6 +4779,20 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.MasterRealtimeSummaryView": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"master_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"qps": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_limited": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.MasterRealtimeView": {
|
"internal_api.MasterRealtimeView": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4633,6 +4966,23 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.ProviderKeyStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"auto_disabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"suspended": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.ProviderUsageAgg": {
|
"internal_api.ProviderUsageAgg": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4653,6 +5003,23 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.RequestStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.StatusResponse": {
|
"internal_api.StatusResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4670,6 +5037,43 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.SystemRealtimeView": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"by_master": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.MasterRealtimeSummaryView"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qps": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_limited_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rpm": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_api.TokenStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"input": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.TokenView": {
|
"internal_api.TokenView": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4741,6 +5145,20 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.TopModelStat": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.UpdateAccessRequest": {
|
"internal_api.UpdateAccessRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4896,6 +5314,31 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.apiKeyStatsFlushEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"api_key_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_api.apiKeyStatsFlushRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keys": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.apiKeyStatsFlushEntry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.refreshModelRegistryRequest": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"AdminAuth": []
|
"AdminAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "List API keys",
|
"description": "List API keys with optional filters",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -71,6 +71,12 @@
|
|||||||
"description": "filter by group_id",
|
"description": "filter by group_id",
|
||||||
"name": "group_id",
|
"name": "group_id",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "filter by status (active, suspended, auto_disabled, manual_disabled)",
|
||||||
|
"name": "status",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -356,6 +362,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/apikey-stats/summary": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Aggregate APIKey success/failure stats across all provider groups",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "APIKey stats summary (admin)",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.APIKeyStatsSummaryResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/bindings": {
|
"/admin/bindings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -674,6 +711,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/dashboard/summary": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns aggregated metrics for dashboard display including requests, tokens, latency, masters, keys, and provider keys statistics",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Dashboard summary",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "time period: today, week, month, all",
|
||||||
|
"name": "period",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "unix seconds",
|
||||||
|
"name": "since",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "unix seconds",
|
||||||
|
"name": "until",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.DashboardSummaryResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/features": {
|
"/admin/features": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -710,7 +804,7 @@
|
|||||||
"AdminAuth": []
|
"AdminAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Updates selected feature flags (meta:features). Values are stored as strings. Example: dp_claude_cross_upstream controls whether /v1/messages can route to OpenAI/compatible and Google-family providers.",
|
"description": "Updates selected feature flags (meta:features). Values are stored as strings.",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1005,7 +1099,7 @@
|
|||||||
"AdminAuth": []
|
"AdminAuth": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.",
|
"description": "Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month/hour). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1030,10 +1124,11 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"model",
|
"model",
|
||||||
"day",
|
"day",
|
||||||
"month"
|
"month",
|
||||||
|
"hour"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "group by dimension: model, day, month. Returns GroupedStatsResponse when specified.",
|
"description": "group by dimension: model, day, month, hour. Returns GroupedStatsResponse when specified.",
|
||||||
"name": "group_by",
|
"name": "group_by",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
@@ -2770,6 +2865,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/realtime": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"AdminAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Return aggregated realtime counters across all masters",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "System-level realtime stats (admin)",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.SystemRealtimeView"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/stats": {
|
"/admin/stats": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2892,6 +3018,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/internal/apikey-stats/flush": {
|
||||||
|
"post": {
|
||||||
|
"description": "Internal endpoint for flushing accumulated APIKey stats from DP to CP database",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"internal"
|
||||||
|
],
|
||||||
|
"summary": "Flush API key stats",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Stats to flush",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_api.apiKeyStatsFlushRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gin.H"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/internal/stats/flush": {
|
"/internal/stats/flush": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Internal endpoint for flushing accumulated key usage stats from DP to CP database",
|
"description": "Internal endpoint for flushing accumulated key usage stats from DP to CP database",
|
||||||
@@ -3567,6 +3739,12 @@
|
|||||||
"github_com_ez-api_ez-api_internal_dto.APIKeyDTO": {
|
"github_com_ez-api_ez-api_internal_dto.APIKeyDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3579,9 +3757,18 @@
|
|||||||
"ban_until": {
|
"ban_until": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"group_id": {
|
"group_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"project_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3660,6 +3847,9 @@
|
|||||||
"google_project": {
|
"google_project": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"headers_profile": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"models": {
|
"models": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -3669,6 +3859,9 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"static_headers": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3680,6 +3873,12 @@
|
|||||||
"github_com_ez-api_ez-api_internal_model.APIKey": {
|
"github_com_ez-api_ez-api_internal_model.APIKey": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"access_token": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3698,15 +3897,36 @@
|
|||||||
"deletedAt": {
|
"deletedAt": {
|
||||||
"$ref": "#/definitions/gorm.DeletedAt"
|
"$ref": "#/definitions/gorm.DeletedAt"
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"group_id": {
|
"group_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"project_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"updatedAt": {
|
"updatedAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@@ -3905,12 +4125,21 @@
|
|||||||
"deletedAt": {
|
"deletedAt": {
|
||||||
"$ref": "#/definitions/gorm.DeletedAt"
|
"$ref": "#/definitions/gorm.DeletedAt"
|
||||||
},
|
},
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"google_location": {
|
"google_location": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"google_project": {
|
"google_project": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"headers_profile": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
@@ -3921,9 +4150,21 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"static_headers": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"description": "openai, anthropic, gemini",
|
"description": "openai, anthropic, gemini",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -4040,6 +4281,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.APIKeyStatsSummaryResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"failure_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failure_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.AboutResponse": {
|
"internal_api.AboutResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4155,6 +4416,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.CountStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.CreateMasterRequest": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -4176,6 +4448,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.DashboardSummaryResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keys": {
|
||||||
|
"$ref": "#/definitions/internal_api.CountStats"
|
||||||
|
},
|
||||||
|
"latency": {
|
||||||
|
"$ref": "#/definitions/internal_api.LatencyStats"
|
||||||
|
},
|
||||||
|
"masters": {
|
||||||
|
"$ref": "#/definitions/internal_api.CountStats"
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"provider_keys": {
|
||||||
|
"$ref": "#/definitions/internal_api.ProviderKeyStats"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"$ref": "#/definitions/internal_api.RequestStats"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"$ref": "#/definitions/internal_api.TokenStats"
|
||||||
|
},
|
||||||
|
"top_models": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.TopModelStat"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.DeleteLogsRequest": {
|
"internal_api.DeleteLogsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4211,6 +4518,10 @@
|
|||||||
"description": "For group_by=day",
|
"description": "For group_by=day",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"hour": {
|
||||||
|
"description": "For group_by=hour",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"description": "For group_by=model",
|
"description": "For group_by=model",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@@ -4278,6 +4589,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.LatencyStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"avg_ms": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.ListLogsResponse": {
|
"internal_api.ListLogsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4454,6 +4773,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.MasterRealtimeSummaryView": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"master_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"qps": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_limited": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.MasterRealtimeView": {
|
"internal_api.MasterRealtimeView": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4627,6 +4960,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.ProviderKeyStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"active": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"auto_disabled": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"suspended": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.ProviderUsageAgg": {
|
"internal_api.ProviderUsageAgg": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4647,6 +4997,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.RequestStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"error_rate": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"failed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.StatusResponse": {
|
"internal_api.StatusResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4664,6 +5031,43 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.SystemRealtimeView": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"by_master": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.MasterRealtimeSummaryView"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"qps": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_limited_count": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rpm": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_api.TokenStats": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"input": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.TokenView": {
|
"internal_api.TokenView": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4735,6 +5139,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.TopModelStat": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"tokens": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.UpdateAccessRequest": {
|
"internal_api.UpdateAccessRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -4890,6 +5308,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.apiKeyStatsFlushEntry": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"api_key_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"success_requests": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_api.apiKeyStatsFlushRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"keys": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/internal_api.apiKeyStatsFlushEntry"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.refreshModelRegistryRequest": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
github_com_ez-api_ez-api_internal_dto.APIKeyDTO:
|
github_com_ez-api_ez-api_internal_dto.APIKeyDTO:
|
||||||
properties:
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
account_id:
|
||||||
|
type: string
|
||||||
api_key:
|
api_key:
|
||||||
type: string
|
type: string
|
||||||
auto_ban:
|
auto_ban:
|
||||||
@@ -13,8 +17,14 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
ban_until:
|
ban_until:
|
||||||
type: string
|
type: string
|
||||||
|
expires_at:
|
||||||
|
type: string
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
project_id:
|
||||||
|
type: string
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
weight:
|
weight:
|
||||||
@@ -66,12 +76,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
google_project:
|
google_project:
|
||||||
type: string
|
type: string
|
||||||
|
headers_profile:
|
||||||
|
type: string
|
||||||
models:
|
models:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
static_headers:
|
||||||
|
type: string
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
@@ -79,6 +93,10 @@ definitions:
|
|||||||
type: object
|
type: object
|
||||||
github_com_ez-api_ez-api_internal_model.APIKey:
|
github_com_ez-api_ez-api_internal_model.APIKey:
|
||||||
properties:
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
account_id:
|
||||||
|
type: string
|
||||||
api_key:
|
api_key:
|
||||||
type: string
|
type: string
|
||||||
auto_ban:
|
auto_ban:
|
||||||
@@ -91,12 +109,26 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
deletedAt:
|
deletedAt:
|
||||||
$ref: '#/definitions/gorm.DeletedAt'
|
$ref: '#/definitions/gorm.DeletedAt'
|
||||||
|
expires_at:
|
||||||
|
type: string
|
||||||
|
failure_rate:
|
||||||
|
type: number
|
||||||
|
failure_requests:
|
||||||
|
type: integer
|
||||||
group_id:
|
group_id:
|
||||||
type: integer
|
type: integer
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
project_id:
|
||||||
|
type: string
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
success_rate:
|
||||||
|
type: number
|
||||||
|
success_requests:
|
||||||
|
type: integer
|
||||||
|
total_requests:
|
||||||
|
type: integer
|
||||||
updatedAt:
|
updatedAt:
|
||||||
type: string
|
type: string
|
||||||
weight:
|
weight:
|
||||||
@@ -228,10 +260,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
deletedAt:
|
deletedAt:
|
||||||
$ref: '#/definitions/gorm.DeletedAt'
|
$ref: '#/definitions/gorm.DeletedAt'
|
||||||
|
failure_rate:
|
||||||
|
type: number
|
||||||
|
failure_requests:
|
||||||
|
type: integer
|
||||||
google_location:
|
google_location:
|
||||||
type: string
|
type: string
|
||||||
google_project:
|
google_project:
|
||||||
type: string
|
type: string
|
||||||
|
headers_profile:
|
||||||
|
type: string
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
models:
|
models:
|
||||||
@@ -239,8 +277,16 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
static_headers:
|
||||||
|
type: string
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
|
success_rate:
|
||||||
|
type: number
|
||||||
|
success_requests:
|
||||||
|
type: integer
|
||||||
|
total_requests:
|
||||||
|
type: integer
|
||||||
type:
|
type:
|
||||||
description: openai, anthropic, gemini
|
description: openai, anthropic, gemini
|
||||||
type: string
|
type: string
|
||||||
@@ -317,6 +363,19 @@ definitions:
|
|||||||
description: Valid is true if Time is not NULL
|
description: Valid is true if Time is not NULL
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.APIKeyStatsSummaryResponse:
|
||||||
|
properties:
|
||||||
|
failure_rate:
|
||||||
|
type: number
|
||||||
|
failure_requests:
|
||||||
|
type: integer
|
||||||
|
success_rate:
|
||||||
|
type: number
|
||||||
|
success_requests:
|
||||||
|
type: integer
|
||||||
|
total_requests:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.AboutResponse:
|
internal_api.AboutResponse:
|
||||||
properties:
|
properties:
|
||||||
description:
|
description:
|
||||||
@@ -393,6 +452,13 @@ definitions:
|
|||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.CountStats:
|
||||||
|
properties:
|
||||||
|
active:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.CreateMasterRequest:
|
internal_api.CreateMasterRequest:
|
||||||
properties:
|
properties:
|
||||||
global_qps:
|
global_qps:
|
||||||
@@ -407,6 +473,29 @@ definitions:
|
|||||||
- group
|
- group
|
||||||
- name
|
- name
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.DashboardSummaryResponse:
|
||||||
|
properties:
|
||||||
|
keys:
|
||||||
|
$ref: '#/definitions/internal_api.CountStats'
|
||||||
|
latency:
|
||||||
|
$ref: '#/definitions/internal_api.LatencyStats'
|
||||||
|
masters:
|
||||||
|
$ref: '#/definitions/internal_api.CountStats'
|
||||||
|
period:
|
||||||
|
type: string
|
||||||
|
provider_keys:
|
||||||
|
$ref: '#/definitions/internal_api.ProviderKeyStats'
|
||||||
|
requests:
|
||||||
|
$ref: '#/definitions/internal_api.RequestStats'
|
||||||
|
tokens:
|
||||||
|
$ref: '#/definitions/internal_api.TokenStats'
|
||||||
|
top_models:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/internal_api.TopModelStat'
|
||||||
|
type: array
|
||||||
|
updated_at:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.DeleteLogsRequest:
|
internal_api.DeleteLogsRequest:
|
||||||
properties:
|
properties:
|
||||||
before:
|
before:
|
||||||
@@ -430,6 +519,9 @@ definitions:
|
|||||||
date:
|
date:
|
||||||
description: For group_by=day
|
description: For group_by=day
|
||||||
type: string
|
type: string
|
||||||
|
hour:
|
||||||
|
description: For group_by=hour
|
||||||
|
type: string
|
||||||
model:
|
model:
|
||||||
description: For group_by=model
|
description: For group_by=model
|
||||||
type: string
|
type: string
|
||||||
@@ -474,6 +566,11 @@ definitions:
|
|||||||
tokens:
|
tokens:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.LatencyStats:
|
||||||
|
properties:
|
||||||
|
avg_ms:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
internal_api.ListLogsResponse:
|
internal_api.ListLogsResponse:
|
||||||
properties:
|
properties:
|
||||||
items:
|
items:
|
||||||
@@ -590,6 +687,15 @@ definitions:
|
|||||||
tokens_out:
|
tokens_out:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.MasterRealtimeSummaryView:
|
||||||
|
properties:
|
||||||
|
master_id:
|
||||||
|
type: integer
|
||||||
|
qps:
|
||||||
|
type: integer
|
||||||
|
rate_limited:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
internal_api.MasterRealtimeView:
|
internal_api.MasterRealtimeView:
|
||||||
properties:
|
properties:
|
||||||
qps:
|
qps:
|
||||||
@@ -703,6 +809,17 @@ definitions:
|
|||||||
user_agent:
|
user_agent:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.ProviderKeyStats:
|
||||||
|
properties:
|
||||||
|
active:
|
||||||
|
type: integer
|
||||||
|
auto_disabled:
|
||||||
|
type: integer
|
||||||
|
suspended:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.ProviderUsageAgg:
|
internal_api.ProviderUsageAgg:
|
||||||
properties:
|
properties:
|
||||||
provider_id:
|
provider_id:
|
||||||
@@ -716,6 +833,17 @@ definitions:
|
|||||||
tokens:
|
tokens:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.RequestStats:
|
||||||
|
properties:
|
||||||
|
error_rate:
|
||||||
|
type: number
|
||||||
|
failed:
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.StatusResponse:
|
internal_api.StatusResponse:
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
@@ -728,6 +856,30 @@ definitions:
|
|||||||
example: 0.1.0
|
example: 0.1.0
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.SystemRealtimeView:
|
||||||
|
properties:
|
||||||
|
by_master:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/internal_api.MasterRealtimeSummaryView'
|
||||||
|
type: array
|
||||||
|
qps:
|
||||||
|
type: integer
|
||||||
|
rate_limited_count:
|
||||||
|
type: integer
|
||||||
|
rpm:
|
||||||
|
type: integer
|
||||||
|
updated_at:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
internal_api.TokenStats:
|
||||||
|
properties:
|
||||||
|
input:
|
||||||
|
type: integer
|
||||||
|
output:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.TokenView:
|
internal_api.TokenView:
|
||||||
properties:
|
properties:
|
||||||
allow_ips:
|
allow_ips:
|
||||||
@@ -775,6 +927,15 @@ definitions:
|
|||||||
used_tokens:
|
used_tokens:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.TopModelStat:
|
||||||
|
properties:
|
||||||
|
model:
|
||||||
|
type: string
|
||||||
|
requests:
|
||||||
|
type: integer
|
||||||
|
tokens:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.UpdateAccessRequest:
|
internal_api.UpdateAccessRequest:
|
||||||
properties:
|
properties:
|
||||||
default_namespace:
|
default_namespace:
|
||||||
@@ -884,6 +1045,22 @@ definitions:
|
|||||||
example: 1703505600
|
example: 1703505600
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.apiKeyStatsFlushEntry:
|
||||||
|
properties:
|
||||||
|
api_key_id:
|
||||||
|
type: integer
|
||||||
|
requests:
|
||||||
|
type: integer
|
||||||
|
success_requests:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
internal_api.apiKeyStatsFlushRequest:
|
||||||
|
properties:
|
||||||
|
keys:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/internal_api.apiKeyStatsFlushEntry'
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
internal_api.refreshModelRegistryRequest:
|
internal_api.refreshModelRegistryRequest:
|
||||||
properties:
|
properties:
|
||||||
ref:
|
ref:
|
||||||
@@ -974,7 +1151,7 @@ paths:
|
|||||||
- Public
|
- Public
|
||||||
/admin/api-keys:
|
/admin/api-keys:
|
||||||
get:
|
get:
|
||||||
description: List API keys
|
description: List API keys with optional filters
|
||||||
parameters:
|
parameters:
|
||||||
- description: page (1-based)
|
- description: page (1-based)
|
||||||
in: query
|
in: query
|
||||||
@@ -988,6 +1165,10 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: group_id
|
name: group_id
|
||||||
type: integer
|
type: integer
|
||||||
|
- description: filter by status (active, suspended, auto_disabled, manual_disabled)
|
||||||
|
in: query
|
||||||
|
name: status
|
||||||
|
type: string
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
@@ -1174,6 +1355,25 @@ paths:
|
|||||||
summary: Batch api keys
|
summary: Batch api keys
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/admin/apikey-stats/summary:
|
||||||
|
get:
|
||||||
|
description: Aggregate APIKey success/failure stats across all provider groups
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.APIKeyStatsSummaryResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: APIKey stats summary (admin)
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/admin/bindings:
|
/admin/bindings:
|
||||||
get:
|
get:
|
||||||
description: List all configured bindings
|
description: List all configured bindings
|
||||||
@@ -1377,6 +1577,43 @@ paths:
|
|||||||
summary: Batch bindings
|
summary: Batch bindings
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/admin/dashboard/summary:
|
||||||
|
get:
|
||||||
|
description: Returns aggregated metrics for dashboard display including requests,
|
||||||
|
tokens, latency, masters, keys, and provider keys statistics
|
||||||
|
parameters:
|
||||||
|
- description: 'time period: today, week, month, all'
|
||||||
|
in: query
|
||||||
|
name: period
|
||||||
|
type: string
|
||||||
|
- description: unix seconds
|
||||||
|
in: query
|
||||||
|
name: since
|
||||||
|
type: integer
|
||||||
|
- description: unix seconds
|
||||||
|
in: query
|
||||||
|
name: until
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.DashboardSummaryResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Dashboard summary
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/admin/features:
|
/admin/features:
|
||||||
get:
|
get:
|
||||||
description: Returns all feature flags stored in Redis (meta:features)
|
description: Returns all feature flags stored in Redis (meta:features)
|
||||||
@@ -1400,8 +1637,7 @@ paths:
|
|||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Updates selected feature flags (meta:features). Values are stored
|
description: Updates selected feature flags (meta:features). Values are stored
|
||||||
as strings. Example: dp_claude_cross_upstream controls whether /v1/messages
|
as strings.
|
||||||
can route to OpenAI/compatible and Google-family providers.
|
|
||||||
parameters:
|
parameters:
|
||||||
- description: Feature map
|
- description: Feature map
|
||||||
in: body
|
in: body
|
||||||
@@ -1589,7 +1825,7 @@ paths:
|
|||||||
/admin/logs/stats:
|
/admin/logs/stats:
|
||||||
get:
|
get:
|
||||||
description: Aggregate log stats with basic filtering. Use group_by param for
|
description: Aggregate log stats with basic filtering. Use group_by param for
|
||||||
grouped statistics (model/day/month). Without group_by returns LogStatsResponse;
|
grouped statistics (model/day/month/hour). Without group_by returns LogStatsResponse;
|
||||||
with group_by returns GroupedStatsResponse.
|
with group_by returns GroupedStatsResponse.
|
||||||
parameters:
|
parameters:
|
||||||
- description: unix seconds
|
- description: unix seconds
|
||||||
@@ -1600,12 +1836,13 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: until
|
name: until
|
||||||
type: integer
|
type: integer
|
||||||
- description: 'group by dimension: model, day, month. Returns GroupedStatsResponse
|
- description: 'group by dimension: model, day, month, hour. Returns GroupedStatsResponse
|
||||||
when specified.'
|
when specified.'
|
||||||
enum:
|
enum:
|
||||||
- model
|
- model
|
||||||
- day
|
- day
|
||||||
- month
|
- month
|
||||||
|
- hour
|
||||||
in: query
|
in: query
|
||||||
name: group_by
|
name: group_by
|
||||||
type: string
|
type: string
|
||||||
@@ -2719,6 +2956,25 @@ paths:
|
|||||||
summary: Update provider group
|
summary: Update provider group
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/admin/realtime:
|
||||||
|
get:
|
||||||
|
description: Return aggregated realtime counters across all masters
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.SystemRealtimeView'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: System-level realtime stats (admin)
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
/admin/stats:
|
/admin/stats:
|
||||||
get:
|
get:
|
||||||
description: Aggregate request stats across all masters
|
description: Aggregate request stats across all masters
|
||||||
@@ -2801,6 +3057,37 @@ paths:
|
|||||||
summary: Get current identity
|
summary: Get current identity
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
|
/internal/apikey-stats/flush:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Internal endpoint for flushing accumulated APIKey stats from DP
|
||||||
|
to CP database
|
||||||
|
parameters:
|
||||||
|
- description: Stats to flush
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.apiKeyStatsFlushRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
summary: Flush API key stats
|
||||||
|
tags:
|
||||||
|
- internal
|
||||||
/internal/stats/flush:
|
/internal/stats/flush:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
@@ -92,13 +92,14 @@ func (h *Handler) CreateAPIKey(c *gin.Context) {
|
|||||||
|
|
||||||
// ListAPIKeys godoc
|
// ListAPIKeys godoc
|
||||||
// @Summary List API keys
|
// @Summary List API keys
|
||||||
// @Description List API keys
|
// @Description List API keys with optional filters
|
||||||
// @Tags admin
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security AdminAuth
|
// @Security AdminAuth
|
||||||
// @Param page query int false "page (1-based)"
|
// @Param page query int false "page (1-based)"
|
||||||
// @Param limit query int false "limit (default 50, max 200)"
|
// @Param limit query int false "limit (default 50, max 200)"
|
||||||
// @Param group_id query int false "filter by group_id"
|
// @Param group_id query int false "filter by group_id"
|
||||||
|
// @Param status query string false "filter by status (active, suspended, auto_disabled, manual_disabled)"
|
||||||
// @Success 200 {array} model.APIKey
|
// @Success 200 {array} model.APIKey
|
||||||
// @Failure 500 {object} gin.H
|
// @Failure 500 {object} gin.H
|
||||||
// @Router /admin/api-keys [get]
|
// @Router /admin/api-keys [get]
|
||||||
@@ -108,6 +109,9 @@ func (h *Handler) ListAPIKeys(c *gin.Context) {
|
|||||||
if groupID := strings.TrimSpace(c.Query("group_id")); groupID != "" {
|
if groupID := strings.TrimSpace(c.Query("group_id")); groupID != "" {
|
||||||
q = q.Where("group_id = ?", groupID)
|
q = q.Where("group_id = ?", groupID)
|
||||||
}
|
}
|
||||||
|
if status := strings.TrimSpace(c.Query("status")); status != "" {
|
||||||
|
q = q.Where("status = ?", status)
|
||||||
|
}
|
||||||
query := parseListQuery(c)
|
query := parseListQuery(c)
|
||||||
q = applyListPagination(q, query)
|
q = applyListPagination(q, query)
|
||||||
if err := q.Find(&keys).Error; err != nil {
|
if err := q.Find(&keys).Error; err != nil {
|
||||||
|
|||||||
271
internal/api/dashboard_handler.go
Normal file
271
internal/api/dashboard_handler.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ez-api/ez-api/internal/model"
|
||||||
|
"github.com/ez-api/ez-api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DashboardHandler handles dashboard-related API endpoints
|
||||||
|
type DashboardHandler struct {
|
||||||
|
db *gorm.DB
|
||||||
|
logDB *gorm.DB
|
||||||
|
statsService *service.StatsService
|
||||||
|
logPartitioner *service.LogPartitioner
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDashboardHandler creates a new DashboardHandler
|
||||||
|
func NewDashboardHandler(db *gorm.DB, logDB *gorm.DB, statsService *service.StatsService, logPartitioner *service.LogPartitioner) *DashboardHandler {
|
||||||
|
if logDB == nil {
|
||||||
|
logDB = db
|
||||||
|
}
|
||||||
|
return &DashboardHandler{
|
||||||
|
db: db,
|
||||||
|
logDB: logDB,
|
||||||
|
statsService: statsService,
|
||||||
|
logPartitioner: logPartitioner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) logDBConn() *gorm.DB {
|
||||||
|
if h == nil || h.logDB == nil {
|
||||||
|
return h.db
|
||||||
|
}
|
||||||
|
return h.logDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *DashboardHandler) logBaseQuery() *gorm.DB {
|
||||||
|
return logBaseQuery(h.logDBConn(), h.logPartitioner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestStats contains request-related statistics
|
||||||
|
type RequestStats struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Success int64 `json:"success"`
|
||||||
|
Failed int64 `json:"failed"`
|
||||||
|
ErrorRate float64 `json:"error_rate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenStats contains token usage statistics
|
||||||
|
type TokenStats struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Input int64 `json:"input"`
|
||||||
|
Output int64 `json:"output"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatencyStats contains latency statistics
|
||||||
|
type LatencyStats struct {
|
||||||
|
AvgMs float64 `json:"avg_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountStats contains simple count statistics
|
||||||
|
type CountStats struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Active int64 `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderKeyStats contains provider key statistics
|
||||||
|
type ProviderKeyStats struct {
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Active int64 `json:"active"`
|
||||||
|
Suspended int64 `json:"suspended"`
|
||||||
|
AutoDisabled int64 `json:"auto_disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TopModelStat contains model usage statistics
|
||||||
|
type TopModelStat struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DashboardSummaryResponse is the response for dashboard summary endpoint
|
||||||
|
type DashboardSummaryResponse struct {
|
||||||
|
Period string `json:"period,omitempty"`
|
||||||
|
Requests RequestStats `json:"requests"`
|
||||||
|
Tokens TokenStats `json:"tokens"`
|
||||||
|
Latency LatencyStats `json:"latency"`
|
||||||
|
Masters CountStats `json:"masters"`
|
||||||
|
Keys CountStats `json:"keys"`
|
||||||
|
ProviderKeys ProviderKeyStats `json:"provider_keys"`
|
||||||
|
TopModels []TopModelStat `json:"top_models"`
|
||||||
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSummary godoc
|
||||||
|
// @Summary Dashboard summary
|
||||||
|
// @Description Returns aggregated metrics for dashboard display including requests, tokens, latency, masters, keys, and provider keys statistics
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Security AdminAuth
|
||||||
|
// @Param period query string false "time period: today, week, month, all"
|
||||||
|
// @Param since query int false "unix seconds"
|
||||||
|
// @Param until query int false "unix seconds"
|
||||||
|
// @Success 200 {object} DashboardSummaryResponse
|
||||||
|
// @Failure 400 {object} gin.H
|
||||||
|
// @Failure 500 {object} gin.H
|
||||||
|
// @Router /admin/dashboard/summary [get]
|
||||||
|
func (h *DashboardHandler) GetSummary(c *gin.Context) {
|
||||||
|
rng, err := parseStatsRange(c)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build log query with time range
|
||||||
|
logQuery := h.logBaseQuery()
|
||||||
|
logQuery = applyStatsRange(logQuery, rng)
|
||||||
|
|
||||||
|
// 1. Request statistics
|
||||||
|
var totalRequests int64
|
||||||
|
if err := logQuery.Session(&gorm.Session{}).Count(&totalRequests).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count requests", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type statusCount struct {
|
||||||
|
StatusCode int
|
||||||
|
Cnt int64
|
||||||
|
}
|
||||||
|
var statusCounts []statusCount
|
||||||
|
if err := logQuery.Session(&gorm.Session{}).
|
||||||
|
Select("status_code, COUNT(*) as cnt").
|
||||||
|
Group("status_code").
|
||||||
|
Scan(&statusCounts).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count by status", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var successCount, failedCount int64
|
||||||
|
for _, sc := range statusCounts {
|
||||||
|
if sc.StatusCode >= 200 && sc.StatusCode < 400 {
|
||||||
|
successCount += sc.Cnt
|
||||||
|
} else {
|
||||||
|
failedCount += sc.Cnt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errorRate := 0.0
|
||||||
|
if totalRequests > 0 {
|
||||||
|
errorRate = float64(failedCount) / float64(totalRequests)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Token statistics
|
||||||
|
type tokenSums struct {
|
||||||
|
TokensIn int64
|
||||||
|
TokensOut int64
|
||||||
|
AvgLatency float64
|
||||||
|
}
|
||||||
|
var ts tokenSums
|
||||||
|
if err := logQuery.Session(&gorm.Session{}).
|
||||||
|
Select("COALESCE(SUM(tokens_in),0) as tokens_in, COALESCE(SUM(tokens_out),0) as tokens_out, COALESCE(AVG(latency_ms),0) as avg_latency").
|
||||||
|
Scan(&ts).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to aggregate tokens", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Master statistics
|
||||||
|
var totalMasters, activeMasters int64
|
||||||
|
if err := h.db.Model(&model.Master{}).Count(&totalMasters).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count masters", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Model(&model.Master{}).Where("status = ?", "active").Count(&activeMasters).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count active masters", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Key (child token) statistics
|
||||||
|
var totalKeys, activeKeys int64
|
||||||
|
if err := h.db.Model(&model.Key{}).Count(&totalKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Model(&model.Key{}).Where("status = ?", "active").Count(&activeKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count active keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Provider key statistics
|
||||||
|
var totalProviderKeys, activeProviderKeys, suspendedProviderKeys, autoDisabledProviderKeys int64
|
||||||
|
if err := h.db.Model(&model.APIKey{}).Count(&totalProviderKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count provider keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Model(&model.APIKey{}).Where("status = ?", "active").Count(&activeProviderKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count active provider keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Model(&model.APIKey{}).Where("status = ?", "suspended").Count(&suspendedProviderKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count suspended provider keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.db.Model(&model.APIKey{}).Where("status = ?", "auto_disabled").Count(&autoDisabledProviderKeys).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count auto_disabled provider keys", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Top models (limit to 10)
|
||||||
|
type modelStat struct {
|
||||||
|
ModelName string
|
||||||
|
Cnt int64
|
||||||
|
Tokens int64
|
||||||
|
}
|
||||||
|
var topModels []modelStat
|
||||||
|
if err := logQuery.Session(&gorm.Session{}).
|
||||||
|
Select("model_name, COUNT(*) as cnt, COALESCE(SUM(tokens_in + tokens_out),0) as tokens").
|
||||||
|
Group("model_name").
|
||||||
|
Order("cnt DESC").
|
||||||
|
Limit(10).
|
||||||
|
Scan(&topModels).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get top models", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topModelStats := make([]TopModelStat, 0, len(topModels))
|
||||||
|
for _, m := range topModels {
|
||||||
|
topModelStats = append(topModelStats, TopModelStat{
|
||||||
|
Model: m.ModelName,
|
||||||
|
Requests: m.Cnt,
|
||||||
|
Tokens: m.Tokens,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, DashboardSummaryResponse{
|
||||||
|
Period: rng.Period,
|
||||||
|
Requests: RequestStats{
|
||||||
|
Total: totalRequests,
|
||||||
|
Success: successCount,
|
||||||
|
Failed: failedCount,
|
||||||
|
ErrorRate: errorRate,
|
||||||
|
},
|
||||||
|
Tokens: TokenStats{
|
||||||
|
Total: ts.TokensIn + ts.TokensOut,
|
||||||
|
Input: ts.TokensIn,
|
||||||
|
Output: ts.TokensOut,
|
||||||
|
},
|
||||||
|
Latency: LatencyStats{
|
||||||
|
AvgMs: ts.AvgLatency,
|
||||||
|
},
|
||||||
|
Masters: CountStats{
|
||||||
|
Total: totalMasters,
|
||||||
|
Active: activeMasters,
|
||||||
|
},
|
||||||
|
Keys: CountStats{
|
||||||
|
Total: totalKeys,
|
||||||
|
Active: activeKeys,
|
||||||
|
},
|
||||||
|
ProviderKeys: ProviderKeyStats{
|
||||||
|
Total: totalProviderKeys,
|
||||||
|
Active: activeProviderKeys,
|
||||||
|
Suspended: suspendedProviderKeys,
|
||||||
|
AutoDisabled: autoDisabledProviderKeys,
|
||||||
|
},
|
||||||
|
TopModels: topModelStats,
|
||||||
|
UpdatedAt: time.Now().UTC().Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -233,6 +233,8 @@ type GroupedStatsItem struct {
|
|||||||
Date string `json:"date,omitempty"`
|
Date string `json:"date,omitempty"`
|
||||||
// For group_by=month
|
// For group_by=month
|
||||||
Month string `json:"month,omitempty"`
|
Month string `json:"month,omitempty"`
|
||||||
|
// For group_by=hour
|
||||||
|
Hour string `json:"hour,omitempty"`
|
||||||
|
|
||||||
Count int64 `json:"count"`
|
Count int64 `json:"count"`
|
||||||
TokensIn int64 `json:"tokens_in"`
|
TokensIn int64 `json:"tokens_in"`
|
||||||
@@ -346,13 +348,13 @@ func (h *Handler) deleteLogsBefore(cutoff time.Time, keyID uint, modelName strin
|
|||||||
|
|
||||||
// LogStats godoc
|
// LogStats godoc
|
||||||
// @Summary Log stats (admin)
|
// @Summary Log stats (admin)
|
||||||
// @Description Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.
|
// @Description Aggregate log stats with basic filtering. Use group_by param for grouped statistics (model/day/month/hour). Without group_by returns LogStatsResponse; with group_by returns GroupedStatsResponse.
|
||||||
// @Tags admin
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security AdminAuth
|
// @Security AdminAuth
|
||||||
// @Param since query int false "unix seconds"
|
// @Param since query int false "unix seconds"
|
||||||
// @Param until query int false "unix seconds"
|
// @Param until query int false "unix seconds"
|
||||||
// @Param group_by query string false "group by dimension: model, day, month. Returns GroupedStatsResponse when specified." Enums(model, day, month)
|
// @Param group_by query string false "group by dimension: model, day, month, hour. Returns GroupedStatsResponse when specified." Enums(model, day, month, hour)
|
||||||
// @Success 200 {object} LogStatsResponse "Default aggregated stats (when group_by is not specified)"
|
// @Success 200 {object} LogStatsResponse "Default aggregated stats (when group_by is not specified)"
|
||||||
// @Success 200 {object} GroupedStatsResponse "Grouped stats (when group_by is specified)"
|
// @Success 200 {object} GroupedStatsResponse "Grouped stats (when group_by is specified)"
|
||||||
// @Failure 500 {object} gin.H
|
// @Failure 500 {object} gin.H
|
||||||
@@ -377,6 +379,9 @@ func (h *Handler) LogStats(c *gin.Context) {
|
|||||||
case "month":
|
case "month":
|
||||||
h.logStatsByMonth(c, q)
|
h.logStatsByMonth(c, q)
|
||||||
return
|
return
|
||||||
|
case "hour":
|
||||||
|
h.logStatsByHour(c, q)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default: aggregated stats (backward compatible)
|
// Default: aggregated stats (backward compatible)
|
||||||
@@ -508,6 +513,37 @@ func (h *Handler) logStatsByMonth(c *gin.Context, q *gorm.DB) {
|
|||||||
c.JSON(http.StatusOK, GroupedStatsResponse{Items: items})
|
c.JSON(http.StatusOK, GroupedStatsResponse{Items: items})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logStatsByHour handles group_by=hour
|
||||||
|
func (h *Handler) logStatsByHour(c *gin.Context, q *gorm.DB) {
|
||||||
|
type hourStats struct {
|
||||||
|
Hour string
|
||||||
|
Cnt int64
|
||||||
|
TokensIn int64
|
||||||
|
TokensOut int64
|
||||||
|
AvgLatencyMs float64
|
||||||
|
}
|
||||||
|
var rows []hourStats
|
||||||
|
// PostgreSQL DATE_TRUNC for hour-level aggregation
|
||||||
|
if err := q.Select(`DATE_TRUNC('hour', created_at) as hour, COUNT(*) as cnt, COALESCE(SUM(tokens_in),0) as tokens_in, COALESCE(SUM(tokens_out),0) as tokens_out, COALESCE(AVG(latency_ms),0) as avg_latency_ms`).
|
||||||
|
Group("hour").
|
||||||
|
Order("hour ASC").
|
||||||
|
Scan(&rows).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to aggregate by hour", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
items := make([]GroupedStatsItem, 0, len(rows))
|
||||||
|
for _, r := range rows {
|
||||||
|
items = append(items, GroupedStatsItem{
|
||||||
|
Hour: r.Hour,
|
||||||
|
Count: r.Cnt,
|
||||||
|
TokensIn: r.TokensIn,
|
||||||
|
TokensOut: r.TokensOut,
|
||||||
|
AvgLatencyMs: r.AvgLatencyMs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, GroupedStatsResponse{Items: items})
|
||||||
|
}
|
||||||
|
|
||||||
// ListSelfLogs godoc
|
// ListSelfLogs godoc
|
||||||
// @Summary List logs (master)
|
// @Summary List logs (master)
|
||||||
// @Description List request logs for the authenticated master
|
// @Description List request logs for the authenticated master
|
||||||
|
|||||||
@@ -112,3 +112,73 @@ func (h *MasterHandler) GetSelfRealtime(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, toMasterRealtimeView(stats))
|
c.JSON(http.StatusOK, toMasterRealtimeView(stats))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemRealtimeView represents system-level realtime statistics
|
||||||
|
type SystemRealtimeView struct {
|
||||||
|
QPS int64 `json:"qps"`
|
||||||
|
RPM int64 `json:"rpm"`
|
||||||
|
RateLimitedCount int64 `json:"rate_limited_count"`
|
||||||
|
ByMaster []MasterRealtimeSummaryView `json:"by_master"`
|
||||||
|
UpdatedAt *int64 `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterRealtimeSummaryView is a brief summary of a master's realtime stats
|
||||||
|
type MasterRealtimeSummaryView struct {
|
||||||
|
MasterID uint `json:"master_id"`
|
||||||
|
QPS int64 `json:"qps"`
|
||||||
|
RateLimited bool `json:"rate_limited"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdminRealtime godoc
|
||||||
|
// @Summary System-level realtime stats (admin)
|
||||||
|
// @Description Return aggregated realtime counters across all masters
|
||||||
|
// @Tags admin
|
||||||
|
// @Produce json
|
||||||
|
// @Security AdminAuth
|
||||||
|
// @Success 200 {object} SystemRealtimeView
|
||||||
|
// @Failure 500 {object} gin.H
|
||||||
|
// @Router /admin/realtime [get]
|
||||||
|
func (h *AdminHandler) GetAdminRealtime(c *gin.Context) {
|
||||||
|
if h.statsService == nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "stats service not configured"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all active master IDs
|
||||||
|
var masterIDs []uint
|
||||||
|
if err := h.db.Model(&model.Master{}).
|
||||||
|
Where("status = ?", "active").
|
||||||
|
Pluck("id", &masterIDs).Error; err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load masters", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := h.statsService.GetSystemRealtimeSnapshot(c.Request.Context(), masterIDs)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load realtime stats", "details": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedAt *int64
|
||||||
|
if stats.UpdatedAt != nil {
|
||||||
|
sec := stats.UpdatedAt.Unix()
|
||||||
|
updatedAt = &sec
|
||||||
|
}
|
||||||
|
|
||||||
|
byMaster := make([]MasterRealtimeSummaryView, 0, len(stats.ByMaster))
|
||||||
|
for _, m := range stats.ByMaster {
|
||||||
|
byMaster = append(byMaster, MasterRealtimeSummaryView{
|
||||||
|
MasterID: m.MasterID,
|
||||||
|
QPS: m.QPS,
|
||||||
|
RateLimited: m.RateLimited,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, SystemRealtimeView{
|
||||||
|
QPS: stats.TotalQPS,
|
||||||
|
RPM: stats.TotalRPM,
|
||||||
|
RateLimitedCount: stats.RateLimitedCount,
|
||||||
|
ByMaster: byMaster,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,3 +153,69 @@ func readCmdTime(cmd *redis.StringCmd) *time.Time {
|
|||||||
tm := time.Unix(v, 0).UTC()
|
tm := time.Unix(v, 0).UTC()
|
||||||
return &tm
|
return &tm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemRealtimeSnapshot contains aggregated realtime stats across all masters
|
||||||
|
type SystemRealtimeSnapshot struct {
|
||||||
|
TotalQPS int64 `json:"qps"`
|
||||||
|
TotalRPM int64 `json:"rpm"`
|
||||||
|
RateLimitedCount int64 `json:"rate_limited_count"`
|
||||||
|
ByMaster []MasterRealtimeSummary `json:"by_master"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterRealtimeSummary is a brief summary of a master's realtime stats
|
||||||
|
type MasterRealtimeSummary struct {
|
||||||
|
MasterID uint `json:"master_id"`
|
||||||
|
QPS int64 `json:"qps"`
|
||||||
|
RateLimited bool `json:"rate_limited"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemRealtimeSnapshot aggregates realtime stats across all masters
|
||||||
|
func (s *StatsService) GetSystemRealtimeSnapshot(ctx context.Context, masterIDs []uint) (SystemRealtimeSnapshot, error) {
|
||||||
|
if s == nil || s.rdb == nil {
|
||||||
|
return SystemRealtimeSnapshot{}, fmt.Errorf("redis client is required")
|
||||||
|
}
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalQPS int64
|
||||||
|
var rateLimitedCount int64
|
||||||
|
byMaster := make([]MasterRealtimeSummary, 0, len(masterIDs))
|
||||||
|
var latestUpdatedAt *time.Time
|
||||||
|
|
||||||
|
for _, masterID := range masterIDs {
|
||||||
|
snapshot, err := s.GetMasterRealtimeSnapshot(ctx, masterID)
|
||||||
|
if err != nil {
|
||||||
|
continue // Skip masters with errors
|
||||||
|
}
|
||||||
|
|
||||||
|
totalQPS += snapshot.QPS
|
||||||
|
if snapshot.RateLimited {
|
||||||
|
rateLimitedCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
byMaster = append(byMaster, MasterRealtimeSummary{
|
||||||
|
MasterID: masterID,
|
||||||
|
QPS: snapshot.QPS,
|
||||||
|
RateLimited: snapshot.RateLimited,
|
||||||
|
})
|
||||||
|
|
||||||
|
if snapshot.UpdatedAt != nil {
|
||||||
|
if latestUpdatedAt == nil || snapshot.UpdatedAt.After(*latestUpdatedAt) {
|
||||||
|
latestUpdatedAt = snapshot.UpdatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate RPM as QPS * 60 (since we're measuring requests per second)
|
||||||
|
totalRPM := totalQPS * 60
|
||||||
|
|
||||||
|
return SystemRealtimeSnapshot{
|
||||||
|
TotalQPS: totalQPS,
|
||||||
|
TotalRPM: totalRPM,
|
||||||
|
RateLimitedCount: rateLimitedCount,
|
||||||
|
ByMaster: byMaster,
|
||||||
|
UpdatedAt: latestUpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user