refactor(api): update traffic chart response structure

Change the traffic chart API response from bucket-based to series-based
to better support frontend visualization libraries. The new format
provides a shared X-axis and aligned data arrays for each model series.

- Replace `buckets` with `x` and `series` in response
- Implement data alignment and zero-filling for time slots
- Update Swagger documentation including pending definitions

BREAKING CHANGE: The `GET /admin/logs/stats/traffic-chart` response
schema has changed. `buckets` and `models` fields are removed.
This commit is contained in:
zenfun
2026-01-08 18:40:44 +08:00
parent 341b54b185
commit f400ffde95
10 changed files with 3239 additions and 294 deletions

View File

@@ -1183,7 +1183,7 @@ const docTemplate = `{
"parameters": [
{
"type": "string",
"description": "time period: today, week, month, all",
"description": "time period: today, week, month, last7d, last30d, all",
"name": "period",
"in": "query"
},
@@ -1198,6 +1198,12 @@ const docTemplate = `{
"description": "unix seconds",
"name": "until",
"in": "query"
},
{
"type": "boolean",
"description": "include trend data comparing to previous period",
"name": "include_trends",
"in": "query"
}
],
"responses": {
@@ -1302,6 +1308,272 @@ const docTemplate = `{
}
}
},
"/admin/ip-bans": {
"get": {
"security": [
{
"AdminAuth": []
}
],
"description": "List all global IP/CIDR ban rules",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin",
"ip-bans"
],
"summary": "List IP bans",
"parameters": [
{
"type": "string",
"description": "Filter by status (active, expired)",
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/internal_api.IPBanView"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/gin.H"
}
}
}
},
"post": {
"security": [
{
"AdminAuth": []
}
],
"description": "Create a new global IP/CIDR ban rule",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin",
"ip-bans"
],
"summary": "Create an IP ban",
"parameters": [
{
"description": "IP Ban Info",
"name": "ban",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_api.CreateIPBanRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/internal_api.IPBanView"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"409": {
"description": "Conflict",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/gin.H"
}
}
}
}
},
"/admin/ip-bans/{id}": {
"get": {
"security": [
{
"AdminAuth": []
}
],
"description": "Get a single global IP/CIDR ban rule by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin",
"ip-bans"
],
"summary": "Get an IP ban",
"parameters": [
{
"type": "integer",
"description": "IP Ban ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_api.IPBanView"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/gin.H"
}
}
}
},
"put": {
"security": [
{
"AdminAuth": []
}
],
"description": "Update a global IP/CIDR ban rule",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin",
"ip-bans"
],
"summary": "Update an IP ban",
"parameters": [
{
"type": "integer",
"description": "IP Ban ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "IP Ban Update",
"name": "ban",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/internal_api.UpdateIPBanRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_api.IPBanView"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"409": {
"description": "Conflict",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/gin.H"
}
}
}
},
"delete": {
"security": [
{
"AdminAuth": []
}
],
"description": "Delete a global IP/CIDR ban rule",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"admin",
"ip-bans"
],
"summary": "Delete an IP ban",
"parameters": [
{
"type": "integer",
"description": "IP Ban ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/gin.H"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/gin.H"
}
}
}
}
},
"/admin/keys/{id}/access": {
"get": {
"security": [
@@ -1617,7 +1889,7 @@ const docTemplate = `{
"AdminAuth": []
}
],
"description": "Get time × model aggregated data for stacked traffic charts. Returns time buckets with per-model breakdown.",
"description": "Get time × model aggregated data for stacked traffic charts. Returns a shared time axis under ` + "`" + `x` + "`" + ` and per-model series arrays aligned to that axis. Models outside top_n are aggregated under the series name \"other\".",
"produces": [
"application/json"
],
@@ -3522,7 +3794,7 @@ const docTemplate = `{
"MasterAuth": []
}
],
"description": "Returns the identity of the authenticated user based on the Authorization header.\nSupports Admin Token, Master Key, and Child Key (API Key) authentication.\n\nResponse varies by token type:\n- Admin Token: {\"type\": \"admin\", \"role\": \"admin\"}\n- Master Key: {\"type\": \"master\", \"id\": 1, \"name\": \"...\", ...}\n- Child Key: {\"type\": \"key\", \"id\": 5, \"master_id\": 1, \"issued_by\": \"master\", ...}",
"description": "Returns complete identity and realtime statistics of the authenticated user.\nSupports Admin Token, Master Key, and Child Key (API Key) authentication.\nThis endpoint is designed for frontend initialization - call once after login\nand store the response for subsequent use.\n\n**Response varies by token type:**\n\n**Admin Token:**\n- type: \"admin\"\n- role: \"admin\"\n- permissions: [\"*\"] (full access)\n\n**Master Key:**\n- type: \"master\"\n- Basic info: id, name, group, namespaces, status, epoch, max_child_keys, global_qps\n- Timestamps: created_at, updated_at\n- Realtime stats: requests, tokens, qps, qps_limit, rate_limited\n\n**Child Key (API Key):**\n- type: \"key\"\n- Basic info: id, master_id, master_name, group, scopes, namespaces, status\n- Security: issued_at_epoch, issued_by, allow_ips, deny_ips, expires_at\n- Model limits: model_limits, model_limits_enabled\n- Quota: quota_limit, quota_used, quota_reset_at, quota_reset_type\n- Usage stats: request_count, used_tokens, last_accessed_at\n- Realtime stats: requests, tokens, qps, qps_limit, rate_limited\n\n**Error responses:**\n- 401: authorization header required\n- 401: invalid authorization header format\n- 401: invalid token\n- 401: token is not active\n- 401: token has expired\n- 401: token has been revoked\n- 401: master is not active",
"produces": [
"application/json"
],
@@ -5157,6 +5429,23 @@ const docTemplate = `{
}
}
},
"internal_api.CreateIPBanRequest": {
"type": "object",
"required": [
"cidr"
],
"properties": {
"cidr": {
"type": "string"
},
"expires_at": {
"type": "integer"
},
"reason": {
"type": "string"
}
}
},
"internal_api.CreateMasterRequest": {
"type": "object",
"required": [
@@ -5208,11 +5497,36 @@ const docTemplate = `{
"$ref": "#/definitions/internal_api.TopModelStat"
}
},
"trends": {
"description": "Only present when include_trends=true",
"allOf": [
{
"$ref": "#/definitions/internal_api.DashboardTrends"
}
]
},
"updated_at": {
"type": "integer"
}
}
},
"internal_api.DashboardTrends": {
"type": "object",
"properties": {
"error_rate": {
"$ref": "#/definitions/internal_api.TrendInfo"
},
"latency": {
"$ref": "#/definitions/internal_api.TrendInfo"
},
"requests": {
"$ref": "#/definitions/internal_api.TrendInfo"
},
"tokens": {
"$ref": "#/definitions/internal_api.TrendInfo"
}
}
},
"internal_api.DeleteLogsRequest": {
"type": "object",
"properties": {
@@ -5283,6 +5597,38 @@ const docTemplate = `{
}
}
},
"internal_api.IPBanView": {
"type": "object",
"properties": {
"cidr": {
"type": "string"
},
"created_at": {
"type": "integer"
},
"created_by": {
"type": "string"
},
"expires_at": {
"type": "integer"
},
"hit_count": {
"type": "integer"
},
"id": {
"type": "integer"
},
"reason": {
"type": "string"
},
"status": {
"type": "string"
},
"updated_at": {
"type": "integer"
}
}
},
"internal_api.IssueChildKeyRequest": {
"type": "object",
"properties": {
@@ -5907,42 +6253,36 @@ const docTemplate = `{
}
}
},
"internal_api.TrafficBucket": {
"internal_api.TrafficChartAxis": {
"type": "object",
"properties": {
"breakdown": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/internal_api.TrafficMetrics"
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"time": {
"type": "string"
"timestamps": {
"type": "array",
"items": {
"type": "integer"
}
},
"timestamp": {
"type": "integer"
},
"total": {
"$ref": "#/definitions/internal_api.TrafficMetrics"
"totals": {
"$ref": "#/definitions/internal_api.TrafficTotals"
}
}
},
"internal_api.TrafficChartResponse": {
"type": "object",
"properties": {
"buckets": {
"type": "array",
"items": {
"$ref": "#/definitions/internal_api.TrafficBucket"
}
},
"granularity": {
"type": "string"
},
"models": {
"series": {
"type": "array",
"items": {
"type": "string"
"$ref": "#/definitions/internal_api.TrafficSeries"
}
},
"since": {
@@ -5950,20 +6290,71 @@ const docTemplate = `{
},
"until": {
"type": "integer"
},
"x": {
"$ref": "#/definitions/internal_api.TrafficChartAxis"
}
}
},
"internal_api.TrafficMetrics": {
"internal_api.TrafficSeries": {
"type": "object",
"properties": {
"count": {
"type": "integer"
"data": {
"type": "array",
"items": {
"type": "integer"
}
},
"name": {
"type": "string"
},
"tokens_in": {
"type": "integer"
"type": "array",
"items": {
"type": "integer"
}
},
"tokens_out": {
"type": "integer"
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"internal_api.TrafficTotals": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"type": "integer"
}
},
"tokens_in": {
"type": "array",
"items": {
"type": "integer"
}
},
"tokens_out": {
"type": "array",
"items": {
"type": "integer"
}
}
}
},
"internal_api.TrendInfo": {
"type": "object",
"properties": {
"delta": {
"description": "Percentage change from previous period (nil if no baseline)",
"type": "number"
},
"direction": {
"description": "\"up\", \"down\", \"stable\", or \"new\" (no baseline)",
"type": "string"
}
}
},
@@ -6010,6 +6401,20 @@ const docTemplate = `{
}
}
},
"internal_api.UpdateIPBanRequest": {
"type": "object",
"properties": {
"expires_at": {
"$ref": "#/definitions/internal_api.optionalInt64"
},
"reason": {
"type": "string"
},
"status": {
"type": "string"
}
}
},
"internal_api.UpdateMasterRequest": {
"type": "object",
"properties": {
@@ -6071,9 +6476,49 @@ const docTemplate = `{
}
}
},
"internal_api.WhoamiRealtimeView": {
"type": "object",
"properties": {
"qps": {
"description": "Current QPS",
"type": "integer",
"example": 5
},
"qps_limit": {
"description": "QPS limit",
"type": "integer",
"example": 100
},
"rate_limited": {
"description": "Whether currently rate limited",
"type": "boolean",
"example": false
},
"requests": {
"description": "Total requests",
"type": "integer",
"example": 100
},
"tokens": {
"description": "Total tokens used",
"type": "integer",
"example": 50000
},
"updated_at": {
"description": "Last updated timestamp",
"type": "integer",
"example": 1703505600
}
}
},
"internal_api.WhoamiResponse": {
"type": "object",
"properties": {
"allow_ips": {
"description": "IP whitelist (for diagnostics)",
"type": "string",
"example": ""
},
"created_at": {
"type": "integer",
"example": 1703505600
@@ -6082,10 +6527,20 @@ const docTemplate = `{
"type": "string",
"example": "default"
},
"deny_ips": {
"description": "IP blacklist (for diagnostics)",
"type": "string",
"example": ""
},
"epoch": {
"type": "integer",
"example": 1
},
"expires_at": {
"description": "Expiration timestamp (0 = never)",
"type": "integer",
"example": 0
},
"global_qps": {
"type": "integer",
"example": 100
@@ -6107,15 +6562,35 @@ const docTemplate = `{
"type": "string",
"example": "master"
},
"last_accessed_at": {
"description": "Last access timestamp",
"type": "integer",
"example": 0
},
"master_id": {
"description": "Key fields (only present when type is \"key\")",
"type": "integer",
"example": 1
},
"master_name": {
"description": "Parent master name (for display)",
"type": "string",
"example": "tenant-a"
},
"max_child_keys": {
"type": "integer",
"example": 5
},
"model_limits": {
"description": "Model restrictions",
"type": "string",
"example": "gpt-4,claude"
},
"model_limits_enabled": {
"description": "Whether model limits are active",
"type": "boolean",
"example": false
},
"name": {
"type": "string",
"example": "tenant-a"
@@ -6124,6 +6599,46 @@ const docTemplate = `{
"type": "string",
"example": "default,ns1"
},
"permissions": {
"description": "Admin permissions (always [\"*\"])",
"type": "array",
"items": {
"type": "string"
}
},
"quota_limit": {
"description": "Token quota limit (-1 = unlimited)",
"type": "integer",
"example": -1
},
"quota_reset_at": {
"description": "Quota reset timestamp",
"type": "integer",
"example": 0
},
"quota_reset_type": {
"description": "Quota reset type",
"type": "string",
"example": "monthly"
},
"quota_used": {
"description": "Token quota used",
"type": "integer",
"example": 0
},
"realtime": {
"description": "Realtime stats (for master and key types)",
"allOf": [
{
"$ref": "#/definitions/internal_api.WhoamiRealtimeView"
}
]
},
"request_count": {
"description": "Total request count (from DB)",
"type": "integer",
"example": 0
},
"role": {
"description": "Admin fields (only present when type is \"admin\")",
"type": "string",
@@ -6145,6 +6660,11 @@ const docTemplate = `{
"updated_at": {
"type": "integer",
"example": 1703505600
},
"used_tokens": {
"description": "Total tokens used (from DB)",
"type": "integer",
"example": 0
}
}
},
@@ -6173,6 +6693,18 @@ const docTemplate = `{
}
}
},
"internal_api.optionalInt64": {
"type": "object",
"properties": {
"set": {
"type": "boolean"
},
"value": {
"type": "integer",
"format": "int64"
}
}
},
"internal_api.refreshModelRegistryRequest": {
"type": "object",
"properties": {