mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
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:
43
docs/api.md
43
docs/api.md
@@ -371,6 +371,49 @@ curl "http://localhost:8080/admin/logs/stats?group_by=minute&since=1704150000&un
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 流量图表统计
|
||||||
|
`GET /admin/logs/stats/traffic-chart`
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
| 参数 | 类型 | 说明 | 约束 |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `granularity` | string | 时间粒度:`hour` / `minute` | `minute` 必须提供 `since` + `until` 且跨度 ≤ 6 小时 |
|
||||||
|
| `since` | int | 起始时间 (Unix 秒) | 默认 24 小时前 |
|
||||||
|
| `until` | int | 结束时间 (Unix 秒) | 默认当前 |
|
||||||
|
| `top_n` | int | Top 模型数量 | 1-20,默认 5,其余聚合为 `other` |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"granularity": "hour",
|
||||||
|
"since": 1735689600,
|
||||||
|
"until": 1735776000,
|
||||||
|
"x": {
|
||||||
|
"labels": ["2025-01-01T00:00:00Z", "2025-01-01T01:00:00Z"],
|
||||||
|
"timestamps": [1735689600, 1735693200],
|
||||||
|
"totals": {
|
||||||
|
"data": [2050, 1800],
|
||||||
|
"tokens_in": [82000, 70000],
|
||||||
|
"tokens_out": [128000, 110000]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"series": [
|
||||||
|
{
|
||||||
|
"name": "gpt-4-turbo",
|
||||||
|
"data": [1200, 900],
|
||||||
|
"tokens_in": [50000, 38000],
|
||||||
|
"tokens_out": [80000, 62000]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "other",
|
||||||
|
"data": [850, 900],
|
||||||
|
"tokens_in": [32000, 32000],
|
||||||
|
"tokens_out": [48000, 48000]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### 6.2 API Key 状态筛选
|
### 6.2 API Key 状态筛选
|
||||||
|
|
||||||
`GET /admin/api-keys?status=active|suspended|auto_disabled|manual_disabled`
|
`GET /admin/api-keys?status=active|suspended|auto_disabled|manual_disabled`
|
||||||
|
|||||||
542
docs/docs.go
542
docs/docs.go
@@ -1308,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": {
|
"/admin/keys/{id}/access": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1623,7 +1889,7 @@ const docTemplate = `{
|
|||||||
"AdminAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -3528,7 +3794,7 @@ const docTemplate = `{
|
|||||||
"MasterAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -5163,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": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -5314,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": {
|
"internal_api.IssueChildKeyRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5938,42 +6253,36 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficBucket": {
|
"internal_api.TrafficChartAxis": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"breakdown": {
|
"labels": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"additionalProperties": {
|
"items": {
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": {
|
"timestamps": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"totals": {
|
||||||
"type": "integer"
|
"$ref": "#/definitions/internal_api.TrafficTotals"
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficChartResponse": {
|
"internal_api.TrafficChartResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"buckets": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficBucket"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"granularity": {
|
"granularity": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"models": {
|
"series": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/internal_api.TrafficSeries"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"since": {
|
"since": {
|
||||||
@@ -5981,20 +6290,58 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"until": {
|
"until": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"$ref": "#/definitions/internal_api.TrafficChartAxis"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficMetrics": {
|
"internal_api.TrafficSeries": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"data": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tokens_in": {
|
"tokens_in": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tokens_out": {
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6054,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": {
|
"internal_api.UpdateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6115,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": {
|
"internal_api.WhoamiResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"allow_ips": {
|
||||||
|
"description": "IP whitelist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
@@ -6126,10 +6527,20 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default"
|
"example": "default"
|
||||||
},
|
},
|
||||||
|
"deny_ips": {
|
||||||
|
"description": "IP blacklist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"epoch": {
|
"epoch": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"description": "Expiration timestamp (0 = never)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"global_qps": {
|
"global_qps": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 100
|
"example": 100
|
||||||
@@ -6151,15 +6562,35 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "master"
|
"example": "master"
|
||||||
},
|
},
|
||||||
|
"last_accessed_at": {
|
||||||
|
"description": "Last access timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"master_id": {
|
"master_id": {
|
||||||
"description": "Key fields (only present when type is \"key\")",
|
"description": "Key fields (only present when type is \"key\")",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"master_name": {
|
||||||
|
"description": "Parent master name (for display)",
|
||||||
|
"type": "string",
|
||||||
|
"example": "tenant-a"
|
||||||
|
},
|
||||||
"max_child_keys": {
|
"max_child_keys": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 5
|
"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": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "tenant-a"
|
"example": "tenant-a"
|
||||||
@@ -6168,6 +6599,46 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default,ns1"
|
"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": {
|
"role": {
|
||||||
"description": "Admin fields (only present when type is \"admin\")",
|
"description": "Admin fields (only present when type is \"admin\")",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -6189,6 +6660,11 @@ const docTemplate = `{
|
|||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
|
},
|
||||||
|
"used_tokens": {
|
||||||
|
"description": "Total tokens used (from DB)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6217,6 +6693,18 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.optionalInt64": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"set": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.refreshModelRegistryRequest": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -176,17 +176,33 @@
|
|||||||
"granularity": "hour",
|
"granularity": "hour",
|
||||||
"since": 1735689600,
|
"since": 1735689600,
|
||||||
"until": 1735776000,
|
"until": 1735776000,
|
||||||
"models": ["gpt-4-turbo", "claude-3.5-sonnet", "llama-3-70b", "other"],
|
"x": {
|
||||||
"buckets": [
|
"labels": ["2025-01-01T00:00:00Z", "2025-01-01T01:00:00Z"],
|
||||||
|
"timestamps": [1735689600, 1735693200],
|
||||||
|
"totals": {
|
||||||
|
"data": [2050, 1800],
|
||||||
|
"tokens_in": [82000, 70000],
|
||||||
|
"tokens_out": [128000, 110000]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"series": [
|
||||||
{
|
{
|
||||||
"time": "2025-01-01T00:00:00Z",
|
"name": "gpt-4-turbo",
|
||||||
"timestamp": 1735689600,
|
"data": [1200, 900],
|
||||||
"breakdown": {
|
"tokens_in": [50000, 38000],
|
||||||
"gpt-4-turbo": { "count": 1200, "tokens_in": 50000, "tokens_out": 80000 },
|
"tokens_out": [80000, 62000]
|
||||||
"claude-3.5-sonnet": { "count": 800, "tokens_in": 30000, "tokens_out": 45000 },
|
},
|
||||||
"other": { "count": 50, "tokens_in": 2000, "tokens_out": 3000 }
|
{
|
||||||
},
|
"name": "claude-3.5-sonnet",
|
||||||
"total": { "count": 2050, "tokens_in": 82000, "tokens_out": 128000 }
|
"data": [800, 700],
|
||||||
|
"tokens_in": [30000, 26000],
|
||||||
|
"tokens_out": [45000, 40000]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "other",
|
||||||
|
"data": [50, 200],
|
||||||
|
"tokens_in": [2000, 6000],
|
||||||
|
"tokens_out": [3000, 8000]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -196,11 +212,11 @@
|
|||||||
|
|
||||||
| 图表类型 | 数据映射 |
|
| 图表类型 | 数据映射 |
|
||||||
|----------|----------|
|
|----------|----------|
|
||||||
| X 轴 | `buckets[i].time` (格式化为 00:00, 04:00 等) |
|
| X 轴 | `x.labels` (格式化为 00:00, 04:00 等) |
|
||||||
| Y 轴 | 请求数量 |
|
| Y 轴 | 请求数量 |
|
||||||
| 堆叠系列 | `models` 数组,每个模型一个系列 |
|
| 堆叠系列 | `series` 数组,每个模型一个系列 |
|
||||||
| 系列数据 | `buckets[i].breakdown[model].count` |
|
| 系列数据 | `series[i].data` |
|
||||||
| 总量标签 | `buckets[i].total.count` |
|
| 总量标签 | `x.totals.data` |
|
||||||
|
|
||||||
**时间范围选择器映射**:
|
**时间范围选择器映射**:
|
||||||
|
|
||||||
@@ -226,7 +242,7 @@
|
|||||||
|
|
||||||
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|
| 功能点 | 用户操作 | 前端行为 | 后端请求 | 成功反馈 | 异常处理 | 风险/边界 |
|
||||||
|--------|----------|----------|----------|----------|----------|-----------|
|
|--------|----------|----------|----------|----------|----------|-----------|
|
||||||
| 图表加载 | 页面加载 | 请求默认 24H 图表 | `GET /admin/logs/stats/traffic-chart` | 渲染堆叠柱状图 | 请求失败 → 显示错误提示 | `buckets` 为空 → 显示 No Data |
|
| 图表加载 | 页面加载 | 请求默认 24H 图表 | `GET /admin/logs/stats/traffic-chart` | 渲染堆叠柱状图 | 请求失败 → 显示错误提示 | `series` 为空或 `x.labels` 为空 → 显示 No Data |
|
||||||
| 时间粒度切换 | 选择分钟级范围 | 自动切换 granularity=minute | `GET /admin/logs/stats/traffic-chart` | 图表更细粒度 | 400 → 回退到 hour 并提示 | minute 范围 ≤ 6h |
|
| 时间粒度切换 | 选择分钟级范围 | 自动切换 granularity=minute | `GET /admin/logs/stats/traffic-chart` | 图表更细粒度 | 400 → 回退到 hour 并提示 | minute 范围 ≤ 6h |
|
||||||
| Top 模型限制 | 修改显示数量 | 约束 top_n ≤ 20 | `GET /admin/logs/stats/traffic-chart` | 图表更新 | 400 → 自动回退到 20 | 模型过多时显示 "other" |
|
| Top 模型限制 | 修改显示数量 | 约束 top_n ≤ 20 | `GET /admin/logs/stats/traffic-chart` | 图表更新 | 400 → 自动回退到 20 | 模型过多时显示 "other" |
|
||||||
|
|
||||||
|
|||||||
@@ -1302,6 +1302,272 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/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": {
|
"/admin/keys/{id}/access": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1617,7 +1883,7 @@
|
|||||||
"AdminAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -3522,7 +3788,7 @@
|
|||||||
"MasterAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -5157,6 +5423,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.CreateIPBanRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cidr"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"cidr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.CreateMasterRequest": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -5308,6 +5591,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"internal_api.IssueChildKeyRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5932,42 +6247,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficBucket": {
|
"internal_api.TrafficChartAxis": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"breakdown": {
|
"labels": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"additionalProperties": {
|
"items": {
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": {
|
"timestamps": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"totals": {
|
||||||
"type": "integer"
|
"$ref": "#/definitions/internal_api.TrafficTotals"
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficChartResponse": {
|
"internal_api.TrafficChartResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"buckets": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficBucket"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"granularity": {
|
"granularity": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"models": {
|
"series": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/internal_api.TrafficSeries"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"since": {
|
"since": {
|
||||||
@@ -5975,20 +6284,58 @@
|
|||||||
},
|
},
|
||||||
"until": {
|
"until": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"$ref": "#/definitions/internal_api.TrafficChartAxis"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficMetrics": {
|
"internal_api.TrafficSeries": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"data": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tokens_in": {
|
"tokens_in": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tokens_out": {
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6048,6 +6395,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.UpdateIPBanRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expires_at": {
|
||||||
|
"$ref": "#/definitions/internal_api.optionalInt64"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.UpdateMasterRequest": {
|
"internal_api.UpdateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6109,9 +6470,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"internal_api.WhoamiResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"allow_ips": {
|
||||||
|
"description": "IP whitelist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
@@ -6120,10 +6521,20 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default"
|
"example": "default"
|
||||||
},
|
},
|
||||||
|
"deny_ips": {
|
||||||
|
"description": "IP blacklist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"epoch": {
|
"epoch": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"description": "Expiration timestamp (0 = never)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"global_qps": {
|
"global_qps": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 100
|
"example": 100
|
||||||
@@ -6145,15 +6556,35 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "master"
|
"example": "master"
|
||||||
},
|
},
|
||||||
|
"last_accessed_at": {
|
||||||
|
"description": "Last access timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"master_id": {
|
"master_id": {
|
||||||
"description": "Key fields (only present when type is \"key\")",
|
"description": "Key fields (only present when type is \"key\")",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"master_name": {
|
||||||
|
"description": "Parent master name (for display)",
|
||||||
|
"type": "string",
|
||||||
|
"example": "tenant-a"
|
||||||
|
},
|
||||||
"max_child_keys": {
|
"max_child_keys": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 5
|
"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": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "tenant-a"
|
"example": "tenant-a"
|
||||||
@@ -6162,6 +6593,46 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default,ns1"
|
"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": {
|
"role": {
|
||||||
"description": "Admin fields (only present when type is \"admin\")",
|
"description": "Admin fields (only present when type is \"admin\")",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -6183,6 +6654,11 @@
|
|||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
|
},
|
||||||
|
"used_tokens": {
|
||||||
|
"description": "Total tokens used (from DB)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6211,6 +6687,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.optionalInt64": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"set": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.refreshModelRegistryRequest": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -562,6 +562,17 @@ definitions:
|
|||||||
- title
|
- title
|
||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.CreateIPBanRequest:
|
||||||
|
properties:
|
||||||
|
cidr:
|
||||||
|
type: string
|
||||||
|
expires_at:
|
||||||
|
type: integer
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cidr
|
||||||
|
type: object
|
||||||
internal_api.CreateMasterRequest:
|
internal_api.CreateMasterRequest:
|
||||||
properties:
|
properties:
|
||||||
global_qps:
|
global_qps:
|
||||||
@@ -661,6 +672,27 @@ definitions:
|
|||||||
$ref: '#/definitions/internal_api.GroupedStatsItem'
|
$ref: '#/definitions/internal_api.GroupedStatsItem'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.IPBanView:
|
||||||
|
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
|
||||||
|
type: object
|
||||||
internal_api.IssueChildKeyRequest:
|
internal_api.IssueChildKeyRequest:
|
||||||
properties:
|
properties:
|
||||||
allow_ips:
|
allow_ips:
|
||||||
@@ -1070,44 +1102,65 @@ definitions:
|
|||||||
tokens:
|
tokens:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficBucket:
|
internal_api.TrafficChartAxis:
|
||||||
properties:
|
properties:
|
||||||
breakdown:
|
labels:
|
||||||
additionalProperties:
|
items:
|
||||||
$ref: '#/definitions/internal_api.TrafficMetrics'
|
type: string
|
||||||
type: object
|
type: array
|
||||||
time:
|
timestamps:
|
||||||
type: string
|
items:
|
||||||
timestamp:
|
type: integer
|
||||||
type: integer
|
type: array
|
||||||
total:
|
totals:
|
||||||
$ref: '#/definitions/internal_api.TrafficMetrics'
|
$ref: '#/definitions/internal_api.TrafficTotals'
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficChartResponse:
|
internal_api.TrafficChartResponse:
|
||||||
properties:
|
properties:
|
||||||
buckets:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/internal_api.TrafficBucket'
|
|
||||||
type: array
|
|
||||||
granularity:
|
granularity:
|
||||||
type: string
|
type: string
|
||||||
models:
|
series:
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: '#/definitions/internal_api.TrafficSeries'
|
||||||
type: array
|
type: array
|
||||||
since:
|
since:
|
||||||
type: integer
|
type: integer
|
||||||
until:
|
until:
|
||||||
type: integer
|
type: integer
|
||||||
|
x:
|
||||||
|
$ref: '#/definitions/internal_api.TrafficChartAxis'
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficMetrics:
|
internal_api.TrafficSeries:
|
||||||
properties:
|
properties:
|
||||||
count:
|
data:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
tokens_in:
|
tokens_in:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
tokens_out:
|
tokens_out:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
internal_api.TrafficTotals:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
tokens_in:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
tokens_out:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrendInfo:
|
internal_api.TrendInfo:
|
||||||
properties:
|
properties:
|
||||||
@@ -1146,6 +1199,15 @@ definitions:
|
|||||||
min_tpm_tokens_1m:
|
min_tpm_tokens_1m:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.UpdateIPBanRequest:
|
||||||
|
properties:
|
||||||
|
expires_at:
|
||||||
|
$ref: '#/definitions/internal_api.optionalInt64'
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
internal_api.UpdateMasterRequest:
|
internal_api.UpdateMasterRequest:
|
||||||
properties:
|
properties:
|
||||||
global_qps:
|
global_qps:
|
||||||
@@ -1186,17 +1248,56 @@ definitions:
|
|||||||
description: active/suspended
|
description: active/suspended
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.WhoamiRealtimeView:
|
||||||
|
properties:
|
||||||
|
qps:
|
||||||
|
description: Current QPS
|
||||||
|
example: 5
|
||||||
|
type: integer
|
||||||
|
qps_limit:
|
||||||
|
description: QPS limit
|
||||||
|
example: 100
|
||||||
|
type: integer
|
||||||
|
rate_limited:
|
||||||
|
description: Whether currently rate limited
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
requests:
|
||||||
|
description: Total requests
|
||||||
|
example: 100
|
||||||
|
type: integer
|
||||||
|
tokens:
|
||||||
|
description: Total tokens used
|
||||||
|
example: 50000
|
||||||
|
type: integer
|
||||||
|
updated_at:
|
||||||
|
description: Last updated timestamp
|
||||||
|
example: 1703505600
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.WhoamiResponse:
|
internal_api.WhoamiResponse:
|
||||||
properties:
|
properties:
|
||||||
|
allow_ips:
|
||||||
|
description: IP whitelist (for diagnostics)
|
||||||
|
example: ""
|
||||||
|
type: string
|
||||||
created_at:
|
created_at:
|
||||||
example: 1703505600
|
example: 1703505600
|
||||||
type: integer
|
type: integer
|
||||||
default_namespace:
|
default_namespace:
|
||||||
example: default
|
example: default
|
||||||
type: string
|
type: string
|
||||||
|
deny_ips:
|
||||||
|
description: IP blacklist (for diagnostics)
|
||||||
|
example: ""
|
||||||
|
type: string
|
||||||
epoch:
|
epoch:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
expires_at:
|
||||||
|
description: Expiration timestamp (0 = never)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
global_qps:
|
global_qps:
|
||||||
example: 100
|
example: 100
|
||||||
type: integer
|
type: integer
|
||||||
@@ -1213,19 +1314,64 @@ definitions:
|
|||||||
issued_by:
|
issued_by:
|
||||||
example: master
|
example: master
|
||||||
type: string
|
type: string
|
||||||
|
last_accessed_at:
|
||||||
|
description: Last access timestamp
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
master_id:
|
master_id:
|
||||||
description: Key fields (only present when type is "key")
|
description: Key fields (only present when type is "key")
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
master_name:
|
||||||
|
description: Parent master name (for display)
|
||||||
|
example: tenant-a
|
||||||
|
type: string
|
||||||
max_child_keys:
|
max_child_keys:
|
||||||
example: 5
|
example: 5
|
||||||
type: integer
|
type: integer
|
||||||
|
model_limits:
|
||||||
|
description: Model restrictions
|
||||||
|
example: gpt-4,claude
|
||||||
|
type: string
|
||||||
|
model_limits_enabled:
|
||||||
|
description: Whether model limits are active
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
name:
|
name:
|
||||||
example: tenant-a
|
example: tenant-a
|
||||||
type: string
|
type: string
|
||||||
namespaces:
|
namespaces:
|
||||||
example: default,ns1
|
example: default,ns1
|
||||||
type: string
|
type: string
|
||||||
|
permissions:
|
||||||
|
description: Admin permissions (always ["*"])
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
quota_limit:
|
||||||
|
description: Token quota limit (-1 = unlimited)
|
||||||
|
example: -1
|
||||||
|
type: integer
|
||||||
|
quota_reset_at:
|
||||||
|
description: Quota reset timestamp
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
|
quota_reset_type:
|
||||||
|
description: Quota reset type
|
||||||
|
example: monthly
|
||||||
|
type: string
|
||||||
|
quota_used:
|
||||||
|
description: Token quota used
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
|
realtime:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/internal_api.WhoamiRealtimeView'
|
||||||
|
description: Realtime stats (for master and key types)
|
||||||
|
request_count:
|
||||||
|
description: Total request count (from DB)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
role:
|
role:
|
||||||
description: Admin fields (only present when type is "admin")
|
description: Admin fields (only present when type is "admin")
|
||||||
example: admin
|
example: admin
|
||||||
@@ -1243,6 +1389,10 @@ definitions:
|
|||||||
updated_at:
|
updated_at:
|
||||||
example: 1703505600
|
example: 1703505600
|
||||||
type: integer
|
type: integer
|
||||||
|
used_tokens:
|
||||||
|
description: Total tokens used (from DB)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
internal_api.apiKeyStatsFlushEntry:
|
internal_api.apiKeyStatsFlushEntry:
|
||||||
properties:
|
properties:
|
||||||
@@ -1260,6 +1410,14 @@ definitions:
|
|||||||
$ref: '#/definitions/internal_api.apiKeyStatsFlushEntry'
|
$ref: '#/definitions/internal_api.apiKeyStatsFlushEntry'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.optionalInt64:
|
||||||
|
properties:
|
||||||
|
set:
|
||||||
|
type: boolean
|
||||||
|
value:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.refreshModelRegistryRequest:
|
internal_api.refreshModelRegistryRequest:
|
||||||
properties:
|
properties:
|
||||||
ref:
|
ref:
|
||||||
@@ -2197,6 +2355,177 @@ paths:
|
|||||||
summary: Update feature flags
|
summary: Update feature flags
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/admin/ip-bans:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: List all global IP/CIDR ban rules
|
||||||
|
parameters:
|
||||||
|
- description: Filter by status (active, expired)
|
||||||
|
in: query
|
||||||
|
name: status
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/internal_api.IPBanView'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: List IP bans
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a new global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban Info
|
||||||
|
in: body
|
||||||
|
name: ban
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.CreateIPBanRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Create an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
/admin/ip-bans/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Delete a global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Delete an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get a single global IP/CIDR ban rule by ID
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Get an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update a global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: IP Ban Update
|
||||||
|
in: body
|
||||||
|
name: ban
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.UpdateIPBanRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Update an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
/admin/keys/{id}/access:
|
/admin/keys/{id}/access:
|
||||||
get:
|
get:
|
||||||
description: Returns key default_namespace and namespaces
|
description: Returns key default_namespace and namespaces
|
||||||
@@ -2403,7 +2732,8 @@ paths:
|
|||||||
/admin/logs/stats/traffic-chart:
|
/admin/logs/stats/traffic-chart:
|
||||||
get:
|
get:
|
||||||
description: Get time × model aggregated data for stacked traffic charts. Returns
|
description: Get time × model aggregated data for stacked traffic charts. Returns
|
||||||
time buckets with per-model breakdown.
|
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".
|
||||||
parameters:
|
parameters:
|
||||||
- description: 'Time granularity: hour (default) or minute'
|
- description: 'Time granularity: hour (default) or minute'
|
||||||
enum:
|
enum:
|
||||||
@@ -3615,13 +3945,41 @@ paths:
|
|||||||
/auth/whoami:
|
/auth/whoami:
|
||||||
get:
|
get:
|
||||||
description: |-
|
description: |-
|
||||||
Returns the identity of the authenticated user based on the Authorization header.
|
Returns complete identity and realtime statistics of the authenticated user.
|
||||||
Supports Admin Token, Master Key, and Child Key (API Key) authentication.
|
Supports Admin Token, Master Key, and Child Key (API Key) authentication.
|
||||||
|
This endpoint is designed for frontend initialization - call once after login
|
||||||
|
and store the response for subsequent use.
|
||||||
|
|
||||||
Response varies by token type:
|
**Response varies by token type:**
|
||||||
- Admin Token: {"type": "admin", "role": "admin"}
|
|
||||||
- Master Key: {"type": "master", "id": 1, "name": "...", ...}
|
**Admin Token:**
|
||||||
- Child Key: {"type": "key", "id": 5, "master_id": 1, "issued_by": "master", ...}
|
- type: "admin"
|
||||||
|
- role: "admin"
|
||||||
|
- permissions: ["*"] (full access)
|
||||||
|
|
||||||
|
**Master Key:**
|
||||||
|
- type: "master"
|
||||||
|
- Basic info: id, name, group, namespaces, status, epoch, max_child_keys, global_qps
|
||||||
|
- Timestamps: created_at, updated_at
|
||||||
|
- Realtime stats: requests, tokens, qps, qps_limit, rate_limited
|
||||||
|
|
||||||
|
**Child Key (API Key):**
|
||||||
|
- type: "key"
|
||||||
|
- Basic info: id, master_id, master_name, group, scopes, namespaces, status
|
||||||
|
- Security: issued_at_epoch, issued_by, allow_ips, deny_ips, expires_at
|
||||||
|
- Model limits: model_limits, model_limits_enabled
|
||||||
|
- Quota: quota_limit, quota_used, quota_reset_at, quota_reset_type
|
||||||
|
- Usage stats: request_count, used_tokens, last_accessed_at
|
||||||
|
- Realtime stats: requests, tokens, qps, qps_limit, rate_limited
|
||||||
|
|
||||||
|
**Error responses:**
|
||||||
|
- 401: authorization header required
|
||||||
|
- 401: invalid authorization header format
|
||||||
|
- 401: invalid token
|
||||||
|
- 401: token is not active
|
||||||
|
- 401: token has expired
|
||||||
|
- 401: token has been revoked
|
||||||
|
- 401: master is not active
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|||||||
@@ -1183,7 +1183,7 @@ const docTemplate = `{
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "time period: today, week, month, all",
|
"description": "time period: today, week, month, last7d, last30d, all",
|
||||||
"name": "period",
|
"name": "period",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
@@ -1198,6 +1198,12 @@ const docTemplate = `{
|
|||||||
"description": "unix seconds",
|
"description": "unix seconds",
|
||||||
"name": "until",
|
"name": "until",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "include trend data comparing to previous period",
|
||||||
|
"name": "include_trends",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"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": {
|
"/admin/keys/{id}/access": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1617,7 +1889,7 @@ const docTemplate = `{
|
|||||||
"AdminAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -3522,7 +3794,7 @@ const docTemplate = `{
|
|||||||
"MasterAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"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": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -5208,11 +5497,36 @@ const docTemplate = `{
|
|||||||
"$ref": "#/definitions/internal_api.TopModelStat"
|
"$ref": "#/definitions/internal_api.TopModelStat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"trends": {
|
||||||
|
"description": "Only present when include_trends=true",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/internal_api.DashboardTrends"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer"
|
"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": {
|
"internal_api.DeleteLogsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"internal_api.IssueChildKeyRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5907,42 +6253,36 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficBucket": {
|
"internal_api.TrafficChartAxis": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"breakdown": {
|
"labels": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"additionalProperties": {
|
"items": {
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": {
|
"timestamps": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"totals": {
|
||||||
"type": "integer"
|
"$ref": "#/definitions/internal_api.TrafficTotals"
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficChartResponse": {
|
"internal_api.TrafficChartResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"buckets": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficBucket"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"granularity": {
|
"granularity": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"models": {
|
"series": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/internal_api.TrafficSeries"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"since": {
|
"since": {
|
||||||
@@ -5950,20 +6290,71 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"until": {
|
"until": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"$ref": "#/definitions/internal_api.TrafficChartAxis"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficMetrics": {
|
"internal_api.TrafficSeries": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"data": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tokens_in": {
|
"tokens_in": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tokens_out": {
|
"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": {
|
"internal_api.UpdateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"internal_api.WhoamiResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"allow_ips": {
|
||||||
|
"description": "IP whitelist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
@@ -6082,10 +6527,20 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default"
|
"example": "default"
|
||||||
},
|
},
|
||||||
|
"deny_ips": {
|
||||||
|
"description": "IP blacklist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"epoch": {
|
"epoch": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"description": "Expiration timestamp (0 = never)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"global_qps": {
|
"global_qps": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 100
|
"example": 100
|
||||||
@@ -6107,15 +6562,35 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "master"
|
"example": "master"
|
||||||
},
|
},
|
||||||
|
"last_accessed_at": {
|
||||||
|
"description": "Last access timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"master_id": {
|
"master_id": {
|
||||||
"description": "Key fields (only present when type is \"key\")",
|
"description": "Key fields (only present when type is \"key\")",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"master_name": {
|
||||||
|
"description": "Parent master name (for display)",
|
||||||
|
"type": "string",
|
||||||
|
"example": "tenant-a"
|
||||||
|
},
|
||||||
"max_child_keys": {
|
"max_child_keys": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 5
|
"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": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "tenant-a"
|
"example": "tenant-a"
|
||||||
@@ -6124,6 +6599,46 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default,ns1"
|
"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": {
|
"role": {
|
||||||
"description": "Admin fields (only present when type is \"admin\")",
|
"description": "Admin fields (only present when type is \"admin\")",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -6145,6 +6660,11 @@ const docTemplate = `{
|
|||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"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": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1177,7 +1177,7 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "time period: today, week, month, all",
|
"description": "time period: today, week, month, last7d, last30d, all",
|
||||||
"name": "period",
|
"name": "period",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
@@ -1192,6 +1192,12 @@
|
|||||||
"description": "unix seconds",
|
"description": "unix seconds",
|
||||||
"name": "until",
|
"name": "until",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "include trend data comparing to previous period",
|
||||||
|
"name": "include_trends",
|
||||||
|
"in": "query"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
@@ -1296,6 +1302,272 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/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": {
|
"/admin/keys/{id}/access": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1611,7 +1883,7 @@
|
|||||||
"AdminAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -3516,7 +3788,7 @@
|
|||||||
"MasterAuth": []
|
"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": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -5151,6 +5423,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.CreateIPBanRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"cidr"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"cidr": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.CreateMasterRequest": {
|
"internal_api.CreateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -5202,11 +5491,36 @@
|
|||||||
"$ref": "#/definitions/internal_api.TopModelStat"
|
"$ref": "#/definitions/internal_api.TopModelStat"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"trends": {
|
||||||
|
"description": "Only present when include_trends=true",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/internal_api.DashboardTrends"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer"
|
"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": {
|
"internal_api.DeleteLogsRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5277,6 +5591,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"internal_api.IssueChildKeyRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -5901,42 +6247,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficBucket": {
|
"internal_api.TrafficChartAxis": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"breakdown": {
|
"labels": {
|
||||||
"type": "object",
|
"type": "array",
|
||||||
"additionalProperties": {
|
"items": {
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"time": {
|
"timestamps": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"timestamp": {
|
"totals": {
|
||||||
"type": "integer"
|
"$ref": "#/definitions/internal_api.TrafficTotals"
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficMetrics"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficChartResponse": {
|
"internal_api.TrafficChartResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"buckets": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/internal_api.TrafficBucket"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"granularity": {
|
"granularity": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"models": {
|
"series": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/internal_api.TrafficSeries"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"since": {
|
"since": {
|
||||||
@@ -5944,20 +6284,71 @@
|
|||||||
},
|
},
|
||||||
"until": {
|
"until": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"x": {
|
||||||
|
"$ref": "#/definitions/internal_api.TrafficChartAxis"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"internal_api.TrafficMetrics": {
|
"internal_api.TrafficSeries": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"data": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tokens_in": {
|
"tokens_in": {
|
||||||
"type": "integer"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tokens_out": {
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6004,6 +6395,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.UpdateIPBanRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expires_at": {
|
||||||
|
"$ref": "#/definitions/internal_api.optionalInt64"
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.UpdateMasterRequest": {
|
"internal_api.UpdateMasterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -6065,9 +6470,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"internal_api.WhoamiResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"allow_ips": {
|
||||||
|
"description": "IP whitelist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
@@ -6076,10 +6521,20 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default"
|
"example": "default"
|
||||||
},
|
},
|
||||||
|
"deny_ips": {
|
||||||
|
"description": "IP blacklist (for diagnostics)",
|
||||||
|
"type": "string",
|
||||||
|
"example": ""
|
||||||
|
},
|
||||||
"epoch": {
|
"epoch": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"description": "Expiration timestamp (0 = never)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"global_qps": {
|
"global_qps": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 100
|
"example": 100
|
||||||
@@ -6101,15 +6556,35 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "master"
|
"example": "master"
|
||||||
},
|
},
|
||||||
|
"last_accessed_at": {
|
||||||
|
"description": "Last access timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
|
},
|
||||||
"master_id": {
|
"master_id": {
|
||||||
"description": "Key fields (only present when type is \"key\")",
|
"description": "Key fields (only present when type is \"key\")",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
},
|
},
|
||||||
|
"master_name": {
|
||||||
|
"description": "Parent master name (for display)",
|
||||||
|
"type": "string",
|
||||||
|
"example": "tenant-a"
|
||||||
|
},
|
||||||
"max_child_keys": {
|
"max_child_keys": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 5
|
"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": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "tenant-a"
|
"example": "tenant-a"
|
||||||
@@ -6118,6 +6593,46 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "default,ns1"
|
"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": {
|
"role": {
|
||||||
"description": "Admin fields (only present when type is \"admin\")",
|
"description": "Admin fields (only present when type is \"admin\")",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -6139,6 +6654,11 @@
|
|||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1703505600
|
"example": 1703505600
|
||||||
|
},
|
||||||
|
"used_tokens": {
|
||||||
|
"description": "Total tokens used (from DB)",
|
||||||
|
"type": "integer",
|
||||||
|
"example": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -6167,6 +6687,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_api.optionalInt64": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"set": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_api.refreshModelRegistryRequest": {
|
"internal_api.refreshModelRegistryRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -562,6 +562,17 @@ definitions:
|
|||||||
- title
|
- title
|
||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.CreateIPBanRequest:
|
||||||
|
properties:
|
||||||
|
cidr:
|
||||||
|
type: string
|
||||||
|
expires_at:
|
||||||
|
type: integer
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- cidr
|
||||||
|
type: object
|
||||||
internal_api.CreateMasterRequest:
|
internal_api.CreateMasterRequest:
|
||||||
properties:
|
properties:
|
||||||
global_qps:
|
global_qps:
|
||||||
@@ -596,9 +607,24 @@ definitions:
|
|||||||
items:
|
items:
|
||||||
$ref: '#/definitions/internal_api.TopModelStat'
|
$ref: '#/definitions/internal_api.TopModelStat'
|
||||||
type: array
|
type: array
|
||||||
|
trends:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/internal_api.DashboardTrends'
|
||||||
|
description: Only present when include_trends=true
|
||||||
updated_at:
|
updated_at:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.DashboardTrends:
|
||||||
|
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'
|
||||||
|
type: object
|
||||||
internal_api.DeleteLogsRequest:
|
internal_api.DeleteLogsRequest:
|
||||||
properties:
|
properties:
|
||||||
before:
|
before:
|
||||||
@@ -646,6 +672,27 @@ definitions:
|
|||||||
$ref: '#/definitions/internal_api.GroupedStatsItem'
|
$ref: '#/definitions/internal_api.GroupedStatsItem'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.IPBanView:
|
||||||
|
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
|
||||||
|
type: object
|
||||||
internal_api.IssueChildKeyRequest:
|
internal_api.IssueChildKeyRequest:
|
||||||
properties:
|
properties:
|
||||||
allow_ips:
|
allow_ips:
|
||||||
@@ -1055,44 +1102,74 @@ definitions:
|
|||||||
tokens:
|
tokens:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficBucket:
|
internal_api.TrafficChartAxis:
|
||||||
properties:
|
properties:
|
||||||
breakdown:
|
labels:
|
||||||
additionalProperties:
|
items:
|
||||||
$ref: '#/definitions/internal_api.TrafficMetrics'
|
type: string
|
||||||
type: object
|
type: array
|
||||||
time:
|
timestamps:
|
||||||
type: string
|
items:
|
||||||
timestamp:
|
type: integer
|
||||||
type: integer
|
type: array
|
||||||
total:
|
totals:
|
||||||
$ref: '#/definitions/internal_api.TrafficMetrics'
|
$ref: '#/definitions/internal_api.TrafficTotals'
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficChartResponse:
|
internal_api.TrafficChartResponse:
|
||||||
properties:
|
properties:
|
||||||
buckets:
|
|
||||||
items:
|
|
||||||
$ref: '#/definitions/internal_api.TrafficBucket'
|
|
||||||
type: array
|
|
||||||
granularity:
|
granularity:
|
||||||
type: string
|
type: string
|
||||||
models:
|
series:
|
||||||
items:
|
items:
|
||||||
type: string
|
$ref: '#/definitions/internal_api.TrafficSeries'
|
||||||
type: array
|
type: array
|
||||||
since:
|
since:
|
||||||
type: integer
|
type: integer
|
||||||
until:
|
until:
|
||||||
type: integer
|
type: integer
|
||||||
|
x:
|
||||||
|
$ref: '#/definitions/internal_api.TrafficChartAxis'
|
||||||
type: object
|
type: object
|
||||||
internal_api.TrafficMetrics:
|
internal_api.TrafficSeries:
|
||||||
properties:
|
properties:
|
||||||
count:
|
data:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
tokens_in:
|
tokens_in:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
tokens_out:
|
tokens_out:
|
||||||
type: integer
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
internal_api.TrafficTotals:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
tokens_in:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
tokens_out:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
internal_api.TrendInfo:
|
||||||
|
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
|
||||||
type: object
|
type: object
|
||||||
internal_api.UpdateAccessRequest:
|
internal_api.UpdateAccessRequest:
|
||||||
properties:
|
properties:
|
||||||
@@ -1122,6 +1199,15 @@ definitions:
|
|||||||
min_tpm_tokens_1m:
|
min_tpm_tokens_1m:
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.UpdateIPBanRequest:
|
||||||
|
properties:
|
||||||
|
expires_at:
|
||||||
|
$ref: '#/definitions/internal_api.optionalInt64'
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
internal_api.UpdateMasterRequest:
|
internal_api.UpdateMasterRequest:
|
||||||
properties:
|
properties:
|
||||||
global_qps:
|
global_qps:
|
||||||
@@ -1162,17 +1248,56 @@ definitions:
|
|||||||
description: active/suspended
|
description: active/suspended
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.WhoamiRealtimeView:
|
||||||
|
properties:
|
||||||
|
qps:
|
||||||
|
description: Current QPS
|
||||||
|
example: 5
|
||||||
|
type: integer
|
||||||
|
qps_limit:
|
||||||
|
description: QPS limit
|
||||||
|
example: 100
|
||||||
|
type: integer
|
||||||
|
rate_limited:
|
||||||
|
description: Whether currently rate limited
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
|
requests:
|
||||||
|
description: Total requests
|
||||||
|
example: 100
|
||||||
|
type: integer
|
||||||
|
tokens:
|
||||||
|
description: Total tokens used
|
||||||
|
example: 50000
|
||||||
|
type: integer
|
||||||
|
updated_at:
|
||||||
|
description: Last updated timestamp
|
||||||
|
example: 1703505600
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.WhoamiResponse:
|
internal_api.WhoamiResponse:
|
||||||
properties:
|
properties:
|
||||||
|
allow_ips:
|
||||||
|
description: IP whitelist (for diagnostics)
|
||||||
|
example: ""
|
||||||
|
type: string
|
||||||
created_at:
|
created_at:
|
||||||
example: 1703505600
|
example: 1703505600
|
||||||
type: integer
|
type: integer
|
||||||
default_namespace:
|
default_namespace:
|
||||||
example: default
|
example: default
|
||||||
type: string
|
type: string
|
||||||
|
deny_ips:
|
||||||
|
description: IP blacklist (for diagnostics)
|
||||||
|
example: ""
|
||||||
|
type: string
|
||||||
epoch:
|
epoch:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
expires_at:
|
||||||
|
description: Expiration timestamp (0 = never)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
global_qps:
|
global_qps:
|
||||||
example: 100
|
example: 100
|
||||||
type: integer
|
type: integer
|
||||||
@@ -1189,19 +1314,64 @@ definitions:
|
|||||||
issued_by:
|
issued_by:
|
||||||
example: master
|
example: master
|
||||||
type: string
|
type: string
|
||||||
|
last_accessed_at:
|
||||||
|
description: Last access timestamp
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
master_id:
|
master_id:
|
||||||
description: Key fields (only present when type is "key")
|
description: Key fields (only present when type is "key")
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
master_name:
|
||||||
|
description: Parent master name (for display)
|
||||||
|
example: tenant-a
|
||||||
|
type: string
|
||||||
max_child_keys:
|
max_child_keys:
|
||||||
example: 5
|
example: 5
|
||||||
type: integer
|
type: integer
|
||||||
|
model_limits:
|
||||||
|
description: Model restrictions
|
||||||
|
example: gpt-4,claude
|
||||||
|
type: string
|
||||||
|
model_limits_enabled:
|
||||||
|
description: Whether model limits are active
|
||||||
|
example: false
|
||||||
|
type: boolean
|
||||||
name:
|
name:
|
||||||
example: tenant-a
|
example: tenant-a
|
||||||
type: string
|
type: string
|
||||||
namespaces:
|
namespaces:
|
||||||
example: default,ns1
|
example: default,ns1
|
||||||
type: string
|
type: string
|
||||||
|
permissions:
|
||||||
|
description: Admin permissions (always ["*"])
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
type: array
|
||||||
|
quota_limit:
|
||||||
|
description: Token quota limit (-1 = unlimited)
|
||||||
|
example: -1
|
||||||
|
type: integer
|
||||||
|
quota_reset_at:
|
||||||
|
description: Quota reset timestamp
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
|
quota_reset_type:
|
||||||
|
description: Quota reset type
|
||||||
|
example: monthly
|
||||||
|
type: string
|
||||||
|
quota_used:
|
||||||
|
description: Token quota used
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
|
realtime:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/internal_api.WhoamiRealtimeView'
|
||||||
|
description: Realtime stats (for master and key types)
|
||||||
|
request_count:
|
||||||
|
description: Total request count (from DB)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
role:
|
role:
|
||||||
description: Admin fields (only present when type is "admin")
|
description: Admin fields (only present when type is "admin")
|
||||||
example: admin
|
example: admin
|
||||||
@@ -1219,6 +1389,10 @@ definitions:
|
|||||||
updated_at:
|
updated_at:
|
||||||
example: 1703505600
|
example: 1703505600
|
||||||
type: integer
|
type: integer
|
||||||
|
used_tokens:
|
||||||
|
description: Total tokens used (from DB)
|
||||||
|
example: 0
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
internal_api.apiKeyStatsFlushEntry:
|
internal_api.apiKeyStatsFlushEntry:
|
||||||
properties:
|
properties:
|
||||||
@@ -1236,6 +1410,14 @@ definitions:
|
|||||||
$ref: '#/definitions/internal_api.apiKeyStatsFlushEntry'
|
$ref: '#/definitions/internal_api.apiKeyStatsFlushEntry'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
internal_api.optionalInt64:
|
||||||
|
properties:
|
||||||
|
set:
|
||||||
|
type: boolean
|
||||||
|
value:
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
internal_api.refreshModelRegistryRequest:
|
internal_api.refreshModelRegistryRequest:
|
||||||
properties:
|
properties:
|
||||||
ref:
|
ref:
|
||||||
@@ -2086,7 +2268,7 @@ paths:
|
|||||||
description: Returns aggregated metrics for dashboard display including requests,
|
description: Returns aggregated metrics for dashboard display including requests,
|
||||||
tokens, latency, masters, keys, and provider keys statistics
|
tokens, latency, masters, keys, and provider keys statistics
|
||||||
parameters:
|
parameters:
|
||||||
- description: 'time period: today, week, month, all'
|
- description: 'time period: today, week, month, last7d, last30d, all'
|
||||||
in: query
|
in: query
|
||||||
name: period
|
name: period
|
||||||
type: string
|
type: string
|
||||||
@@ -2098,6 +2280,10 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: until
|
name: until
|
||||||
type: integer
|
type: integer
|
||||||
|
- description: include trend data comparing to previous period
|
||||||
|
in: query
|
||||||
|
name: include_trends
|
||||||
|
type: boolean
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
@@ -2169,6 +2355,177 @@ paths:
|
|||||||
summary: Update feature flags
|
summary: Update feature flags
|
||||||
tags:
|
tags:
|
||||||
- admin
|
- admin
|
||||||
|
/admin/ip-bans:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: List all global IP/CIDR ban rules
|
||||||
|
parameters:
|
||||||
|
- description: Filter by status (active, expired)
|
||||||
|
in: query
|
||||||
|
name: status
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/internal_api.IPBanView'
|
||||||
|
type: array
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: List IP bans
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a new global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban Info
|
||||||
|
in: body
|
||||||
|
name: ban
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.CreateIPBanRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Create an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
/admin/ip-bans/{id}:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Delete a global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gin.H'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Delete an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get a single global IP/CIDR ban rule by ID
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Get an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Update a global IP/CIDR ban rule
|
||||||
|
parameters:
|
||||||
|
- description: IP Ban ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: IP Ban Update
|
||||||
|
in: body
|
||||||
|
name: ban
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_api.UpdateIPBanRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
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'
|
||||||
|
security:
|
||||||
|
- AdminAuth: []
|
||||||
|
summary: Update an IP ban
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
- ip-bans
|
||||||
/admin/keys/{id}/access:
|
/admin/keys/{id}/access:
|
||||||
get:
|
get:
|
||||||
description: Returns key default_namespace and namespaces
|
description: Returns key default_namespace and namespaces
|
||||||
@@ -2375,7 +2732,8 @@ paths:
|
|||||||
/admin/logs/stats/traffic-chart:
|
/admin/logs/stats/traffic-chart:
|
||||||
get:
|
get:
|
||||||
description: Get time × model aggregated data for stacked traffic charts. Returns
|
description: Get time × model aggregated data for stacked traffic charts. Returns
|
||||||
time buckets with per-model breakdown.
|
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".
|
||||||
parameters:
|
parameters:
|
||||||
- description: 'Time granularity: hour (default) or minute'
|
- description: 'Time granularity: hour (default) or minute'
|
||||||
enum:
|
enum:
|
||||||
@@ -3587,13 +3945,41 @@ paths:
|
|||||||
/auth/whoami:
|
/auth/whoami:
|
||||||
get:
|
get:
|
||||||
description: |-
|
description: |-
|
||||||
Returns the identity of the authenticated user based on the Authorization header.
|
Returns complete identity and realtime statistics of the authenticated user.
|
||||||
Supports Admin Token, Master Key, and Child Key (API Key) authentication.
|
Supports Admin Token, Master Key, and Child Key (API Key) authentication.
|
||||||
|
This endpoint is designed for frontend initialization - call once after login
|
||||||
|
and store the response for subsequent use.
|
||||||
|
|
||||||
Response varies by token type:
|
**Response varies by token type:**
|
||||||
- Admin Token: {"type": "admin", "role": "admin"}
|
|
||||||
- Master Key: {"type": "master", "id": 1, "name": "...", ...}
|
**Admin Token:**
|
||||||
- Child Key: {"type": "key", "id": 5, "master_id": 1, "issued_by": "master", ...}
|
- type: "admin"
|
||||||
|
- role: "admin"
|
||||||
|
- permissions: ["*"] (full access)
|
||||||
|
|
||||||
|
**Master Key:**
|
||||||
|
- type: "master"
|
||||||
|
- Basic info: id, name, group, namespaces, status, epoch, max_child_keys, global_qps
|
||||||
|
- Timestamps: created_at, updated_at
|
||||||
|
- Realtime stats: requests, tokens, qps, qps_limit, rate_limited
|
||||||
|
|
||||||
|
**Child Key (API Key):**
|
||||||
|
- type: "key"
|
||||||
|
- Basic info: id, master_id, master_name, group, scopes, namespaces, status
|
||||||
|
- Security: issued_at_epoch, issued_by, allow_ips, deny_ips, expires_at
|
||||||
|
- Model limits: model_limits, model_limits_enabled
|
||||||
|
- Quota: quota_limit, quota_used, quota_reset_at, quota_reset_type
|
||||||
|
- Usage stats: request_count, used_tokens, last_accessed_at
|
||||||
|
- Realtime stats: requests, tokens, qps, qps_limit, rate_limited
|
||||||
|
|
||||||
|
**Error responses:**
|
||||||
|
- 401: authorization header required
|
||||||
|
- 401: invalid authorization header format
|
||||||
|
- 401: invalid token
|
||||||
|
- 401: token is not active
|
||||||
|
- 401: token has expired
|
||||||
|
- 401: token has been revoked
|
||||||
|
- 401: master is not active
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|||||||
@@ -609,34 +609,35 @@ func (h *Handler) logStatsByMinute(c *gin.Context, q *gorm.DB, sinceTime, untilT
|
|||||||
c.JSON(http.StatusOK, GroupedStatsResponse{Items: items})
|
c.JSON(http.StatusOK, GroupedStatsResponse{Items: items})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrafficMetrics contains the metrics for a model in a bucket
|
// TrafficSeries contains the metrics for a model aligned to the shared time axis.
|
||||||
type TrafficMetrics struct {
|
type TrafficSeries struct {
|
||||||
Count int64 `json:"count"`
|
Name string `json:"name"`
|
||||||
TokensIn int64 `json:"tokens_in"`
|
Data []int64 `json:"data"`
|
||||||
TokensOut int64 `json:"tokens_out"`
|
TokensIn []int64 `json:"tokens_in"`
|
||||||
|
TokensOut []int64 `json:"tokens_out"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelMetricsMap is a map from model name to TrafficMetrics.
|
// TrafficTotals contains aggregated totals aligned to the shared time axis.
|
||||||
// Keys are model names (e.g. "gpt-4", "claude-3-opus") or "other" for aggregated remaining models.
|
type TrafficTotals struct {
|
||||||
// Example: {"gpt-4": {"count": 10, "tokens_in": 1000, "tokens_out": 500}, "other": {"count": 3, "tokens_in": 200, "tokens_out": 100}}
|
Data []int64 `json:"data"`
|
||||||
type ModelMetricsMap map[string]TrafficMetrics
|
TokensIn []int64 `json:"tokens_in"`
|
||||||
|
TokensOut []int64 `json:"tokens_out"`
|
||||||
|
}
|
||||||
|
|
||||||
// TrafficBucket represents one time bucket with model breakdown
|
// TrafficChartAxis defines the shared time axis for chart data.
|
||||||
type TrafficBucket struct {
|
type TrafficChartAxis struct {
|
||||||
Time string `json:"time"`
|
Labels []string `json:"labels"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamps []int64 `json:"timestamps"`
|
||||||
// Breakdown is a map from model name (e.g. "gpt-4", "claude-3-opus", "other") to its metrics
|
Totals TrafficTotals `json:"totals"`
|
||||||
Breakdown ModelMetricsMap `json:"breakdown"`
|
|
||||||
Total TrafficMetrics `json:"total"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrafficChartResponse is the response for traffic chart API
|
// TrafficChartResponse is the response for traffic chart API
|
||||||
type TrafficChartResponse struct {
|
type TrafficChartResponse struct {
|
||||||
Granularity string `json:"granularity"`
|
Granularity string `json:"granularity"`
|
||||||
Since int64 `json:"since"`
|
Since int64 `json:"since"`
|
||||||
Until int64 `json:"until"`
|
Until int64 `json:"until"`
|
||||||
Models []string `json:"models"`
|
X TrafficChartAxis `json:"x"`
|
||||||
Buckets []TrafficBucket `json:"buckets"`
|
Series []TrafficSeries `json:"series"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -644,9 +645,121 @@ const (
|
|||||||
maxTrafficTopN = 20
|
maxTrafficTopN = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type trafficBucketRow struct {
|
||||||
|
Bucket time.Time
|
||||||
|
ModelName string
|
||||||
|
Cnt int64
|
||||||
|
TokensIn int64
|
||||||
|
TokensOut int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTrafficChartSeriesResponse(rows []trafficBucketRow, topN int, granularity string, sinceTime, untilTime time.Time) TrafficChartResponse {
|
||||||
|
bucketLabels := make(map[int64]string)
|
||||||
|
bucketOrder := make([]int64, 0)
|
||||||
|
for _, r := range rows {
|
||||||
|
ts := r.Bucket.Unix()
|
||||||
|
if _, exists := bucketLabels[ts]; !exists {
|
||||||
|
bucketLabels[ts] = r.Bucket.UTC().Format(time.RFC3339)
|
||||||
|
bucketOrder = append(bucketOrder, ts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modelCounts := make(map[string]int64)
|
||||||
|
for _, r := range rows {
|
||||||
|
modelCounts[r.ModelName] += r.Cnt
|
||||||
|
}
|
||||||
|
|
||||||
|
type modelCount struct {
|
||||||
|
name string
|
||||||
|
count int64
|
||||||
|
}
|
||||||
|
modelList := make([]modelCount, 0, len(modelCounts))
|
||||||
|
for name, cnt := range modelCounts {
|
||||||
|
modelList = append(modelList, modelCount{name, cnt})
|
||||||
|
}
|
||||||
|
for i := 0; i < len(modelList)-1; i++ {
|
||||||
|
for j := i + 1; j < len(modelList); j++ {
|
||||||
|
if modelList[j].count > modelList[i].count {
|
||||||
|
modelList[i], modelList[j] = modelList[j], modelList[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topModels := make(map[string]bool, topN)
|
||||||
|
seriesNames := make([]string, 0, topN+1)
|
||||||
|
for i := 0; i < len(modelList) && i < topN; i++ {
|
||||||
|
topModels[modelList[i].name] = true
|
||||||
|
seriesNames = append(seriesNames, modelList[i].name)
|
||||||
|
}
|
||||||
|
if len(modelList) > topN {
|
||||||
|
seriesNames = append(seriesNames, "other")
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketIndex := make(map[int64]int, len(bucketOrder))
|
||||||
|
labels := make([]string, len(bucketOrder))
|
||||||
|
timestamps := make([]int64, len(bucketOrder))
|
||||||
|
for i, ts := range bucketOrder {
|
||||||
|
bucketIndex[ts] = i
|
||||||
|
labels[i] = bucketLabels[ts]
|
||||||
|
timestamps[i] = ts
|
||||||
|
}
|
||||||
|
|
||||||
|
series := make([]TrafficSeries, len(seriesNames))
|
||||||
|
seriesIndex := make(map[string]int, len(seriesNames))
|
||||||
|
for i, name := range seriesNames {
|
||||||
|
series[i] = TrafficSeries{
|
||||||
|
Name: name,
|
||||||
|
Data: make([]int64, len(bucketOrder)),
|
||||||
|
TokensIn: make([]int64, len(bucketOrder)),
|
||||||
|
TokensOut: make([]int64, len(bucketOrder)),
|
||||||
|
}
|
||||||
|
seriesIndex[name] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
totals := TrafficTotals{
|
||||||
|
Data: make([]int64, len(bucketOrder)),
|
||||||
|
TokensIn: make([]int64, len(bucketOrder)),
|
||||||
|
TokensOut: make([]int64, len(bucketOrder)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range rows {
|
||||||
|
ts := r.Bucket.Unix()
|
||||||
|
idx, ok := bucketIndex[ts]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modelKey := r.ModelName
|
||||||
|
if !topModels[modelKey] {
|
||||||
|
modelKey = "other"
|
||||||
|
}
|
||||||
|
if seriesIdx, exists := seriesIndex[modelKey]; exists {
|
||||||
|
series[seriesIdx].Data[idx] += r.Cnt
|
||||||
|
series[seriesIdx].TokensIn[idx] += r.TokensIn
|
||||||
|
series[seriesIdx].TokensOut[idx] += r.TokensOut
|
||||||
|
}
|
||||||
|
|
||||||
|
totals.Data[idx] += r.Cnt
|
||||||
|
totals.TokensIn[idx] += r.TokensIn
|
||||||
|
totals.TokensOut[idx] += r.TokensOut
|
||||||
|
}
|
||||||
|
|
||||||
|
return TrafficChartResponse{
|
||||||
|
Granularity: granularity,
|
||||||
|
Since: sinceTime.Unix(),
|
||||||
|
Until: untilTime.Unix(),
|
||||||
|
X: TrafficChartAxis{
|
||||||
|
Labels: labels,
|
||||||
|
Timestamps: timestamps,
|
||||||
|
Totals: totals,
|
||||||
|
},
|
||||||
|
Series: series,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetTrafficChart godoc
|
// GetTrafficChart godoc
|
||||||
// @Summary Traffic chart data (admin)
|
// @Summary Traffic chart data (admin)
|
||||||
// @Description Get time × model aggregated data for stacked traffic charts. Returns time buckets with per-model breakdown. The 'breakdown' field in each bucket is a map where keys are model names (e.g. "gpt-4", "claude-3-opus") and values are TrafficMetrics objects. Models outside top_n are aggregated under the key "other". Example: {"gpt-4": {"count": 10, "tokens_in": 1000, "tokens_out": 500}, "other": {"count": 3, "tokens_in": 200, "tokens_out": 100}}
|
// @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".
|
||||||
// @Tags admin
|
// @Tags admin
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security AdminAuth
|
// @Security AdminAuth
|
||||||
@@ -731,14 +844,7 @@ func (h *Handler) GetTrafficChart(c *gin.Context) {
|
|||||||
truncFunc = "DATE_TRUNC('hour', created_at)"
|
truncFunc = "DATE_TRUNC('hour', created_at)"
|
||||||
}
|
}
|
||||||
|
|
||||||
type bucketModelStats struct {
|
var rows []trafficBucketRow
|
||||||
Bucket time.Time
|
|
||||||
ModelName string
|
|
||||||
Cnt int64
|
|
||||||
TokensIn int64
|
|
||||||
TokensOut int64
|
|
||||||
}
|
|
||||||
var rows []bucketModelStats
|
|
||||||
|
|
||||||
if err := q.Select(truncFunc + " as bucket, model_name, COUNT(*) as cnt, COALESCE(SUM(tokens_in),0) as tokens_in, COALESCE(SUM(tokens_out),0) as tokens_out").
|
if err := q.Select(truncFunc + " as bucket, model_name, COUNT(*) as cnt, COALESCE(SUM(tokens_in),0) as tokens_in, COALESCE(SUM(tokens_out),0) as tokens_out").
|
||||||
Group("bucket, model_name").
|
Group("bucket, model_name").
|
||||||
@@ -748,88 +854,7 @@ func (h *Handler) GetTrafficChart(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate global model counts for top_n selection
|
c.JSON(http.StatusOK, buildTrafficChartSeriesResponse(rows, topN, granularity, sinceTime, untilTime))
|
||||||
modelCounts := make(map[string]int64)
|
|
||||||
for _, r := range rows {
|
|
||||||
modelCounts[r.ModelName] += r.Cnt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get top N models
|
|
||||||
type modelCount struct {
|
|
||||||
name string
|
|
||||||
count int64
|
|
||||||
}
|
|
||||||
var modelList []modelCount
|
|
||||||
for name, cnt := range modelCounts {
|
|
||||||
modelList = append(modelList, modelCount{name, cnt})
|
|
||||||
}
|
|
||||||
// Sort by count descending
|
|
||||||
for i := 0; i < len(modelList)-1; i++ {
|
|
||||||
for j := i + 1; j < len(modelList); j++ {
|
|
||||||
if modelList[j].count > modelList[i].count {
|
|
||||||
modelList[i], modelList[j] = modelList[j], modelList[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
topModels := make(map[string]bool)
|
|
||||||
var modelNames []string
|
|
||||||
for i := 0; i < len(modelList) && i < topN; i++ {
|
|
||||||
topModels[modelList[i].name] = true
|
|
||||||
modelNames = append(modelNames, modelList[i].name)
|
|
||||||
}
|
|
||||||
hasOther := len(modelList) > topN
|
|
||||||
if hasOther {
|
|
||||||
modelNames = append(modelNames, "other")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build buckets with breakdown
|
|
||||||
bucketMap := make(map[int64]*TrafficBucket)
|
|
||||||
var bucketOrder []int64
|
|
||||||
|
|
||||||
for _, r := range rows {
|
|
||||||
ts := r.Bucket.Unix()
|
|
||||||
bucket, exists := bucketMap[ts]
|
|
||||||
if !exists {
|
|
||||||
bucket = &TrafficBucket{
|
|
||||||
Time: r.Bucket.UTC().Format(time.RFC3339),
|
|
||||||
Timestamp: ts,
|
|
||||||
Breakdown: make(map[string]TrafficMetrics),
|
|
||||||
}
|
|
||||||
bucketMap[ts] = bucket
|
|
||||||
bucketOrder = append(bucketOrder, ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
modelKey := r.ModelName
|
|
||||||
if !topModels[modelKey] {
|
|
||||||
modelKey = "other"
|
|
||||||
}
|
|
||||||
|
|
||||||
existing := bucket.Breakdown[modelKey]
|
|
||||||
bucket.Breakdown[modelKey] = TrafficMetrics{
|
|
||||||
Count: existing.Count + r.Cnt,
|
|
||||||
TokensIn: existing.TokensIn + r.TokensIn,
|
|
||||||
TokensOut: existing.TokensOut + r.TokensOut,
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket.Total.Count += r.Cnt
|
|
||||||
bucket.Total.TokensIn += r.TokensIn
|
|
||||||
bucket.Total.TokensOut += r.TokensOut
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build response buckets in order
|
|
||||||
buckets := make([]TrafficBucket, 0, len(bucketOrder))
|
|
||||||
for _, ts := range bucketOrder {
|
|
||||||
buckets = append(buckets, *bucketMap[ts])
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, TrafficChartResponse{
|
|
||||||
Granularity: granularity,
|
|
||||||
Since: sinceTime.Unix(),
|
|
||||||
Until: untilTime.Unix(),
|
|
||||||
Models: modelNames,
|
|
||||||
Buckets: buckets,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListSelfLogs godoc
|
// ListSelfLogs godoc
|
||||||
|
|||||||
@@ -355,6 +355,83 @@ func TestLogStats_DefaultBehavior(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildTrafficChartSeriesResponse(t *testing.T) {
|
||||||
|
bucket1 := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
bucket2 := time.Date(2025, 1, 1, 1, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
rows := []trafficBucketRow{
|
||||||
|
{Bucket: bucket1, ModelName: "a", Cnt: 5, TokensIn: 10, TokensOut: 20},
|
||||||
|
{Bucket: bucket1, ModelName: "b", Cnt: 3, TokensIn: 6, TokensOut: 12},
|
||||||
|
{Bucket: bucket2, ModelName: "a", Cnt: 2, TokensIn: 4, TokensOut: 8},
|
||||||
|
{Bucket: bucket2, ModelName: "c", Cnt: 8, TokensIn: 16, TokensOut: 32},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := buildTrafficChartSeriesResponse(rows, 2, "hour", bucket1, bucket2)
|
||||||
|
|
||||||
|
if len(resp.X.Labels) != 2 || len(resp.X.Timestamps) != 2 {
|
||||||
|
t.Fatalf("expected 2 time buckets, got labels=%d timestamps=%d", len(resp.X.Labels), len(resp.X.Timestamps))
|
||||||
|
}
|
||||||
|
if resp.X.Labels[0] != bucket1.Format(time.RFC3339) || resp.X.Labels[1] != bucket2.Format(time.RFC3339) {
|
||||||
|
t.Fatalf("unexpected labels: %+v", resp.X.Labels)
|
||||||
|
}
|
||||||
|
if resp.X.Timestamps[0] != bucket1.Unix() || resp.X.Timestamps[1] != bucket2.Unix() {
|
||||||
|
t.Fatalf("unexpected timestamps: %+v", resp.X.Timestamps)
|
||||||
|
}
|
||||||
|
if resp.X.Totals.Data[0] != 8 || resp.X.Totals.Data[1] != 10 {
|
||||||
|
t.Fatalf("unexpected totals data: %+v", resp.X.Totals.Data)
|
||||||
|
}
|
||||||
|
if resp.X.Totals.TokensIn[0] != 16 || resp.X.Totals.TokensIn[1] != 20 {
|
||||||
|
t.Fatalf("unexpected totals tokens_in: %+v", resp.X.Totals.TokensIn)
|
||||||
|
}
|
||||||
|
if resp.X.Totals.TokensOut[0] != 32 || resp.X.Totals.TokensOut[1] != 40 {
|
||||||
|
t.Fatalf("unexpected totals tokens_out: %+v", resp.X.Totals.TokensOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
seriesByName := make(map[string]TrafficSeries, len(resp.Series))
|
||||||
|
for _, s := range resp.Series {
|
||||||
|
seriesByName[s.Name] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range []string{"a", "c", "other"} {
|
||||||
|
if _, ok := seriesByName[name]; !ok {
|
||||||
|
t.Fatalf("missing series %q", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aSeries := seriesByName["a"]
|
||||||
|
if aSeries.Data[0] != 5 || aSeries.Data[1] != 2 {
|
||||||
|
t.Fatalf("unexpected series a data: %+v", aSeries.Data)
|
||||||
|
}
|
||||||
|
if aSeries.TokensIn[0] != 10 || aSeries.TokensIn[1] != 4 {
|
||||||
|
t.Fatalf("unexpected series a tokens_in: %+v", aSeries.TokensIn)
|
||||||
|
}
|
||||||
|
if aSeries.TokensOut[0] != 20 || aSeries.TokensOut[1] != 8 {
|
||||||
|
t.Fatalf("unexpected series a tokens_out: %+v", aSeries.TokensOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
cSeries := seriesByName["c"]
|
||||||
|
if cSeries.Data[0] != 0 || cSeries.Data[1] != 8 {
|
||||||
|
t.Fatalf("unexpected series c data: %+v", cSeries.Data)
|
||||||
|
}
|
||||||
|
if cSeries.TokensIn[0] != 0 || cSeries.TokensIn[1] != 16 {
|
||||||
|
t.Fatalf("unexpected series c tokens_in: %+v", cSeries.TokensIn)
|
||||||
|
}
|
||||||
|
if cSeries.TokensOut[0] != 0 || cSeries.TokensOut[1] != 32 {
|
||||||
|
t.Fatalf("unexpected series c tokens_out: %+v", cSeries.TokensOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
otherSeries := seriesByName["other"]
|
||||||
|
if otherSeries.Data[0] != 3 || otherSeries.Data[1] != 0 {
|
||||||
|
t.Fatalf("unexpected series other data: %+v", otherSeries.Data)
|
||||||
|
}
|
||||||
|
if otherSeries.TokensIn[0] != 6 || otherSeries.TokensIn[1] != 0 {
|
||||||
|
t.Fatalf("unexpected series other tokens_in: %+v", otherSeries.TokensIn)
|
||||||
|
}
|
||||||
|
if otherSeries.TokensOut[0] != 12 || otherSeries.TokensOut[1] != 0 {
|
||||||
|
t.Fatalf("unexpected series other tokens_out: %+v", otherSeries.TokensOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTrafficChart_TopNOtherAggregation(t *testing.T) {
|
func TestTrafficChart_TopNOtherAggregation(t *testing.T) {
|
||||||
// Skip test when running with SQLite (no DATE_TRUNC support)
|
// Skip test when running with SQLite (no DATE_TRUNC support)
|
||||||
// This test requires PostgreSQL for time truncation functions
|
// This test requires PostgreSQL for time truncation functions
|
||||||
|
|||||||
Reference in New Issue
Block a user