Changeset 0.33.0 (#207)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
643
server/static/openapi.yaml
Normal file
643
server/static/openapi.yaml
Normal file
@@ -0,0 +1,643 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Chat Switchboard API
|
||||
description: |
|
||||
Multi-provider AI chat platform with team workspaces, workflow automation,
|
||||
and extensibility via Starlark packages.
|
||||
|
||||
**Authentication:** Most endpoints require a Bearer JWT token obtained via
|
||||
`POST /api/v1/auth/login`. Include it as `Authorization: Bearer <token>`.
|
||||
|
||||
**WebSocket:** Real-time events are delivered over WebSocket at `/ws`.
|
||||
Obtain a ticket via `POST /api/v1/ws/ticket` and connect with `?ticket=<id>`.
|
||||
version: "${VERSION}"
|
||||
contact:
|
||||
name: Chat Switchboard
|
||||
|
||||
servers:
|
||||
- url: /
|
||||
description: Current instance
|
||||
|
||||
tags:
|
||||
- name: Auth
|
||||
description: Authentication and session management
|
||||
- name: Channels
|
||||
description: Chat channel CRUD and messaging
|
||||
- name: Completions
|
||||
description: AI completion requests (streaming and non-streaming)
|
||||
- name: Health
|
||||
description: System health and readiness probes
|
||||
- name: Admin
|
||||
description: Platform administration (requires admin role)
|
||||
- name: Usage
|
||||
description: Token usage and cost tracking
|
||||
- name: WebSocket
|
||||
description: Real-time event delivery
|
||||
|
||||
paths:
|
||||
# ── Auth ───────────────────────────────────
|
||||
/api/v1/auth/register:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Register a new user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [username, password]
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
minLength: 3
|
||||
password:
|
||||
type: string
|
||||
minLength: 8
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
display_name:
|
||||
type: string
|
||||
responses:
|
||||
"201":
|
||||
description: User created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AuthResponse"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"409":
|
||||
description: Username already taken
|
||||
|
||||
/api/v1/auth/login:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Authenticate and obtain JWT tokens
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [username, password]
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Login successful
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AuthResponse"
|
||||
"401":
|
||||
description: Invalid credentials
|
||||
"429":
|
||||
description: Rate limited
|
||||
|
||||
/api/v1/auth/refresh:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Refresh an expired access token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [refresh_token]
|
||||
properties:
|
||||
refresh_token:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: New token pair
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AuthResponse"
|
||||
"401":
|
||||
description: Invalid or expired refresh token
|
||||
|
||||
/api/v1/auth/logout:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Invalidate the current session
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Logged out
|
||||
|
||||
# ── Channels ───────────────────────────────
|
||||
/api/v1/channels:
|
||||
get:
|
||||
tags: [Channels]
|
||||
summary: List channels accessible to the current user
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: type
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [direct, group, workflow]
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
- name: per_page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 50
|
||||
responses:
|
||||
"200":
|
||||
description: Channel list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
channels:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Channel"
|
||||
total:
|
||||
type: integer
|
||||
post:
|
||||
tags: [Channels]
|
||||
summary: Create a new channel
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [direct, group]
|
||||
default: direct
|
||||
system_prompt:
|
||||
type: string
|
||||
responses:
|
||||
"201":
|
||||
description: Channel created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Channel"
|
||||
|
||||
/api/v1/channels/{id}:
|
||||
get:
|
||||
tags: [Channels]
|
||||
summary: Get channel details
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ChannelID"
|
||||
responses:
|
||||
"200":
|
||||
description: Channel details
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Channel"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
put:
|
||||
tags: [Channels]
|
||||
summary: Update channel settings
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ChannelID"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
system_prompt:
|
||||
type: string
|
||||
ai_mode:
|
||||
type: string
|
||||
enum: [auto, off, mention_only]
|
||||
responses:
|
||||
"200":
|
||||
description: Updated
|
||||
delete:
|
||||
tags: [Channels]
|
||||
summary: Archive a channel
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ChannelID"
|
||||
responses:
|
||||
"200":
|
||||
description: Archived
|
||||
|
||||
# ── Completions ────────────────────────────
|
||||
/api/v1/chat/completions:
|
||||
post:
|
||||
tags: [Completions]
|
||||
summary: Send a message and get an AI completion
|
||||
description: |
|
||||
Streams SSE events by default. Set `stream: false` for a single JSON response.
|
||||
Supports tool calling, @mentions, multi-model routing, and workflow integration.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [content, channel_id]
|
||||
properties:
|
||||
channel_id:
|
||||
type: string
|
||||
format: uuid
|
||||
content:
|
||||
type: string
|
||||
model:
|
||||
type: string
|
||||
description: Override the default model
|
||||
persona_id:
|
||||
type: string
|
||||
format: uuid
|
||||
provider_config_id:
|
||||
type: string
|
||||
format: uuid
|
||||
max_tokens:
|
||||
type: integer
|
||||
temperature:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 2
|
||||
top_p:
|
||||
type: number
|
||||
minimum: 0
|
||||
maximum: 1
|
||||
stream:
|
||||
type: boolean
|
||||
default: true
|
||||
file_ids:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
disabled_tools:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: |
|
||||
**Streaming:** SSE events (`data: {"content":"..."}`, `data: [DONE]`).
|
||||
**Non-streaming:** OpenAI-compatible JSON response.
|
||||
content:
|
||||
text/event-stream:
|
||||
schema:
|
||||
type: string
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
model:
|
||||
type: string
|
||||
choices:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: object
|
||||
properties:
|
||||
role:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
finish_reason:
|
||||
type: string
|
||||
usage:
|
||||
type: object
|
||||
properties:
|
||||
prompt_tokens:
|
||||
type: integer
|
||||
completion_tokens:
|
||||
type: integer
|
||||
total_tokens:
|
||||
type: integer
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"429":
|
||||
description: Token budget exceeded
|
||||
|
||||
# ── Health ─────────────────────────────────
|
||||
/health:
|
||||
get:
|
||||
tags: [Health]
|
||||
summary: Basic health check
|
||||
responses:
|
||||
"200":
|
||||
description: System status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ok
|
||||
version:
|
||||
type: string
|
||||
database:
|
||||
type: boolean
|
||||
schema_version:
|
||||
type: integer
|
||||
|
||||
/healthz/live:
|
||||
get:
|
||||
tags: [Health]
|
||||
summary: Kubernetes liveness probe
|
||||
responses:
|
||||
"200":
|
||||
description: Process alive
|
||||
|
||||
/healthz/ready:
|
||||
get:
|
||||
tags: [Health]
|
||||
summary: Kubernetes readiness probe
|
||||
description: Returns 503 if the database is unreachable (2s timeout).
|
||||
responses:
|
||||
"200":
|
||||
description: Ready to serve traffic
|
||||
"503":
|
||||
description: Database unavailable
|
||||
|
||||
/metrics:
|
||||
get:
|
||||
tags: [Health]
|
||||
summary: Prometheus metrics endpoint
|
||||
description: |
|
||||
Returns all `switchboard_*` metrics in Prometheus text exposition format.
|
||||
Includes HTTP request latency, WebSocket connections, completion tokens,
|
||||
DB pool stats, and provider health gauges.
|
||||
responses:
|
||||
"200":
|
||||
description: Prometheus text format
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
# ── WebSocket ──────────────────────────────
|
||||
/api/v1/ws/ticket:
|
||||
post:
|
||||
tags: [WebSocket]
|
||||
summary: Obtain a WebSocket connection ticket
|
||||
description: |
|
||||
Returns a single-use ticket (30s TTL) for authenticating the WebSocket
|
||||
upgrade at `/ws?ticket=<id>`. Cross-pod safe (v0.32.0).
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Ticket issued
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ticket:
|
||||
type: string
|
||||
|
||||
# ── Admin: Stats ───────────────────────────
|
||||
/api/v1/admin/stats:
|
||||
get:
|
||||
tags: [Admin]
|
||||
summary: Platform statistics
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Aggregate counts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
users:
|
||||
type: integer
|
||||
channels:
|
||||
type: integer
|
||||
messages:
|
||||
type: integer
|
||||
|
||||
# ── Admin: Provider Health ─────────────────
|
||||
/api/v1/admin/providers/health:
|
||||
get:
|
||||
tags: [Admin]
|
||||
summary: Current health status for all providers
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Provider health summaries
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
providers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ProviderHealth"
|
||||
|
||||
# ── Admin: Usage ───────────────────────────
|
||||
/api/v1/admin/usage:
|
||||
get:
|
||||
tags: [Admin, Usage]
|
||||
summary: Aggregated token usage
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: since
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: until
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: period
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [7d, 30d, 90d]
|
||||
- name: group_by
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
enum: [model, user, provider, day]
|
||||
responses:
|
||||
"200":
|
||||
description: Usage data
|
||||
|
||||
# ── Admin: Dashboard ───────────────────────
|
||||
/api/v1/admin/dashboard:
|
||||
get:
|
||||
tags: [Admin]
|
||||
summary: Real-time observability dashboard data
|
||||
description: |
|
||||
Aggregates provider health, 24h usage, DB pool stats, WebSocket count,
|
||||
recent errors, and uptime into a single response. Designed for the
|
||||
built-in admin dashboard (v0.33.0).
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Dashboard snapshot
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
providers:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ProviderHealth"
|
||||
usage_24h:
|
||||
type: object
|
||||
db_pool:
|
||||
type: object
|
||||
properties:
|
||||
open_connections:
|
||||
type: integer
|
||||
in_use:
|
||||
type: integer
|
||||
idle:
|
||||
type: integer
|
||||
ws_connections:
|
||||
type: integer
|
||||
uptime_seconds:
|
||||
type: number
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
parameters:
|
||||
ChannelID:
|
||||
name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
responses:
|
||||
BadRequest:
|
||||
description: Invalid request
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
NotFound:
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
|
||||
schemas:
|
||||
AuthResponse:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
user:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
username:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
enum: [admin, user]
|
||||
|
||||
Channel:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
title:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
enum: [direct, group, workflow]
|
||||
user_id:
|
||||
type: string
|
||||
format: uuid
|
||||
team_id:
|
||||
type: string
|
||||
format: uuid
|
||||
ai_mode:
|
||||
type: string
|
||||
enum: [auto, off, mention_only]
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
ProviderHealth:
|
||||
type: object
|
||||
properties:
|
||||
provider_config_id:
|
||||
type: string
|
||||
format: uuid
|
||||
status:
|
||||
type: string
|
||||
enum: [healthy, degraded, down, unknown]
|
||||
request_count:
|
||||
type: integer
|
||||
error_count:
|
||||
type: integer
|
||||
error_rate:
|
||||
type: number
|
||||
avg_latency_ms:
|
||||
type: number
|
||||
max_latency_ms:
|
||||
type: integer
|
||||
267
server/static/swagger.html
Normal file
267
server/static/swagger.html
Normal file
@@ -0,0 +1,267 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Chat Switchboard — API Docs</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
||||
<style>
|
||||
/* ── Palette — WCAG AA 4.5:1 minimum contrast ────────────────── */
|
||||
:root {
|
||||
--sw-bg: #ffffff;
|
||||
--sw-bg-secondary: #f5f5f7;
|
||||
--sw-bg-section: #fafafa;
|
||||
--sw-text: #1b1b1b; /* 17.8:1 on #fff */
|
||||
--sw-text-secondary:#4e4e4e; /* 8.5:1 on #fff */
|
||||
--sw-text-muted: #636363; /* 6.1:1 on #fff */
|
||||
--sw-border: #d5d5d5;
|
||||
--sw-link: #1a56a8; /* 7.8:1 on #fff */
|
||||
--sw-code-bg: #f0f0f2;
|
||||
--sw-code-text: #1b1b1b;
|
||||
--sw-input-bg: #ffffff;
|
||||
--sw-input-border: #b0b0b0;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--sw-bg: #161625;
|
||||
--sw-bg-secondary: #1e1e30;
|
||||
--sw-bg-section: #222236;
|
||||
--sw-text: #dcdce4; /* 13.5:1 on #161625 */
|
||||
--sw-text-secondary:#a8a8b8; /* 8.0:1 on #161625 */
|
||||
--sw-text-muted: #8e8ea0; /* 5.7:1 on #161625 */
|
||||
--sw-border: #363648;
|
||||
--sw-link: #7cacf8; /* 8.0:1 on #161625 */
|
||||
--sw-code-bg: #1c1c2e;
|
||||
--sw-code-text: #dcdce4;
|
||||
--sw-input-bg: #1e1e30;
|
||||
--sw-input-border: #484860;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Global ──────────────────────────────────────────────────── */
|
||||
body { margin: 0; background: var(--sw-bg); color: var(--sw-text); }
|
||||
.swagger-ui { background: var(--sw-bg); color: var(--sw-text); }
|
||||
.swagger-ui .wrapper { background: var(--sw-bg); }
|
||||
|
||||
/* Hide default Swagger branding bar */
|
||||
.swagger-ui .topbar { display: none; }
|
||||
|
||||
/* ── Override low-contrast utility classes ────────────────────── */
|
||||
.swagger-ui .silver,
|
||||
.swagger-ui .light-silver { color: var(--sw-text-muted); }
|
||||
.swagger-ui .moon-gray,
|
||||
.swagger-ui .gray { color: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Info header ─────────────────────────────────────────────── */
|
||||
.swagger-ui .info .title,
|
||||
.swagger-ui .info .title small pre { color: var(--sw-text); }
|
||||
.swagger-ui .info .base-url { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .info p,
|
||||
.swagger-ui .info li,
|
||||
.swagger-ui .info table td,
|
||||
.swagger-ui .info table th,
|
||||
.swagger-ui .renderedMarkdown p,
|
||||
.swagger-ui .markdown p { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .info a,
|
||||
.swagger-ui .renderedMarkdown a,
|
||||
.swagger-ui .markdown a { color: var(--sw-link); }
|
||||
.swagger-ui .info h1,
|
||||
.swagger-ui .info h2,
|
||||
.swagger-ui .info h3 { color: var(--sw-text); }
|
||||
|
||||
/* ── Scheme / server selector ────────────────────────────────── */
|
||||
.swagger-ui .scheme-container {
|
||||
background: var(--sw-bg-secondary);
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid var(--sw-border);
|
||||
}
|
||||
.swagger-ui .scheme-container .schemes > label { color: var(--sw-text); }
|
||||
|
||||
/* ── Tag groups ──────────────────────────────────────────────── */
|
||||
.swagger-ui .opblock-tag {
|
||||
color: var(--sw-text);
|
||||
border-bottom-color: var(--sw-border);
|
||||
}
|
||||
.swagger-ui .opblock-tag small { color: var(--sw-text-muted); }
|
||||
.swagger-ui .opblock-tag a { color: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Operation blocks ────────────────────────────────────────── */
|
||||
.swagger-ui .opblock .opblock-summary-description { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .opblock .opblock-summary-path,
|
||||
.swagger-ui .opblock .opblock-summary-path__deprecated { color: var(--sw-text); }
|
||||
.swagger-ui .opblock-description-wrapper p,
|
||||
.swagger-ui .opblock-external-docs-wrapper p { color: var(--sw-text-secondary); }
|
||||
|
||||
/* Operation block section headers */
|
||||
.swagger-ui .opblock .opblock-section-header {
|
||||
background: var(--sw-bg-section);
|
||||
border-bottom: 1px solid var(--sw-border);
|
||||
box-shadow: none;
|
||||
}
|
||||
.swagger-ui .opblock .opblock-section-header h4 { color: var(--sw-text); }
|
||||
.swagger-ui .opblock .opblock-section-header label { color: var(--sw-text-secondary); }
|
||||
|
||||
/* Operation block body (expanded) */
|
||||
.swagger-ui .opblock-body { background: var(--sw-bg); }
|
||||
.swagger-ui .opblock-body pre.microlight { background: var(--sw-code-bg); color: var(--sw-code-text); }
|
||||
|
||||
/* ── Parameters table ────────────────────────────────────────── */
|
||||
.swagger-ui .parameters-col_name,
|
||||
.swagger-ui .parameter__name { color: var(--sw-text); }
|
||||
.swagger-ui .parameter__type { color: var(--sw-text-muted); }
|
||||
.swagger-ui .parameter__deprecated { color: #dc3545; }
|
||||
.swagger-ui .parameter__name.required::after { color: #dc3545; }
|
||||
.swagger-ui .parameter__in { color: var(--sw-text-muted); }
|
||||
.swagger-ui table thead tr td,
|
||||
.swagger-ui table thead tr th {
|
||||
color: var(--sw-text);
|
||||
border-bottom-color: var(--sw-border);
|
||||
}
|
||||
.swagger-ui table tbody tr td { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .parameters-container,
|
||||
.swagger-ui .responses-wrapper { background: var(--sw-bg); }
|
||||
|
||||
/* ── Responses ───────────────────────────────────────────────── */
|
||||
.swagger-ui .response-col_status { color: var(--sw-text); }
|
||||
.swagger-ui .response-col_description { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .response-col_links { color: var(--sw-text-muted); }
|
||||
.swagger-ui .response-control-media-type__accept-message { color: var(--sw-text-muted); }
|
||||
.swagger-ui .responses-inner { background: var(--sw-bg); }
|
||||
.swagger-ui .responses-inner h4,
|
||||
.swagger-ui .responses-inner h5 { color: var(--sw-text); }
|
||||
.swagger-ui .response .response-col_description__inner p { color: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Models / Schemas ────────────────────────────────────────── */
|
||||
.swagger-ui .model-title { color: var(--sw-text); }
|
||||
.swagger-ui section.models { border-color: var(--sw-border); background: var(--sw-bg); }
|
||||
.swagger-ui section.models h4 { color: var(--sw-text); }
|
||||
.swagger-ui section.models .model-container { background: var(--sw-bg); border-bottom-color: var(--sw-border); }
|
||||
.swagger-ui .model { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .model .property { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .model .property.primitive { color: var(--sw-text-muted); }
|
||||
.swagger-ui .model-box { background: var(--sw-bg-secondary); }
|
||||
.swagger-ui .model-toggle::after { color: var(--sw-text-secondary); }
|
||||
.swagger-ui span.model-toggle { color: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Code / pre blocks ───────────────────────────────────────── */
|
||||
.swagger-ui pre,
|
||||
.swagger-ui pre.microlight {
|
||||
background: var(--sw-code-bg);
|
||||
color: var(--sw-code-text);
|
||||
border: 1px solid var(--sw-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.swagger-ui .highlight-code > .microlight code { color: var(--sw-code-text); }
|
||||
.swagger-ui .example code,
|
||||
.swagger-ui .model code {
|
||||
background: var(--sw-code-bg);
|
||||
color: var(--sw-code-text);
|
||||
}
|
||||
.swagger-ui .copy-to-clipboard { background: var(--sw-bg-section); }
|
||||
|
||||
/* ── Form controls ───────────────────────────────────────────── */
|
||||
.swagger-ui select {
|
||||
background: var(--sw-input-bg);
|
||||
color: var(--sw-text);
|
||||
border-color: var(--sw-input-border);
|
||||
}
|
||||
.swagger-ui input[type=text],
|
||||
.swagger-ui input[type=search],
|
||||
.swagger-ui input[type=email],
|
||||
.swagger-ui input[type=password],
|
||||
.swagger-ui input[type=file] {
|
||||
background: var(--sw-input-bg);
|
||||
color: var(--sw-text);
|
||||
border-color: var(--sw-input-border);
|
||||
}
|
||||
.swagger-ui textarea {
|
||||
background: var(--sw-input-bg);
|
||||
color: var(--sw-text);
|
||||
border-color: var(--sw-input-border);
|
||||
}
|
||||
.swagger-ui .body-param textarea {
|
||||
background: var(--sw-input-bg);
|
||||
color: var(--sw-text);
|
||||
}
|
||||
|
||||
/* ── Buttons ──────────────────────────────────────────────────── */
|
||||
.swagger-ui .btn {
|
||||
color: var(--sw-text);
|
||||
border-color: var(--sw-input-border);
|
||||
background: transparent;
|
||||
}
|
||||
.swagger-ui .btn:hover { background: var(--sw-bg-secondary); }
|
||||
.swagger-ui .btn.authorize { color: #49cc90; border-color: #49cc90; }
|
||||
.swagger-ui .btn.cancel { color: #dc3545; border-color: #dc3545; }
|
||||
.swagger-ui .btn.execute { color: #fff; background: #4990e2; border-color: #4990e2; }
|
||||
.swagger-ui .try-out__btn { color: var(--sw-text-secondary); border-color: var(--sw-input-border); }
|
||||
|
||||
/* ── Tabs ─────────────────────────────────────────────────────── */
|
||||
.swagger-ui .tab li { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .tab li.active { color: var(--sw-text); }
|
||||
.swagger-ui .tab li button.tablinks { color: inherit; }
|
||||
.swagger-ui .opblock .tab-header .tab-item { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .opblock .tab-header .tab-item.active h4 { color: var(--sw-text); }
|
||||
|
||||
/* ── Auth dialog ─────────────────────────────────────────────── */
|
||||
.swagger-ui .dialog-ux .modal-ux {
|
||||
background: var(--sw-bg);
|
||||
border: 1px solid var(--sw-border);
|
||||
}
|
||||
.swagger-ui .dialog-ux .modal-ux-header { border-bottom-color: var(--sw-border); }
|
||||
.swagger-ui .dialog-ux .modal-ux-header h3 { color: var(--sw-text); }
|
||||
.swagger-ui .dialog-ux .modal-ux-content p { color: var(--sw-text-secondary); }
|
||||
.swagger-ui .dialog-ux .modal-ux-content h4 { color: var(--sw-text); }
|
||||
.swagger-ui .dialog-ux .modal-ux-content label { color: var(--sw-text); }
|
||||
.swagger-ui .dialog-ux .backdrop-ux {
|
||||
background: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* ── Auth wrapper ────────────────────────────────────────────── */
|
||||
.swagger-ui .auth-wrapper { background: var(--sw-bg); }
|
||||
.swagger-ui .auth-wrapper .auth-container h4 { color: var(--sw-text); }
|
||||
.swagger-ui .auth-wrapper .auth-container p { color: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Filter / search ─────────────────────────────────────────── */
|
||||
.swagger-ui .filter-container { background: var(--sw-bg-secondary); }
|
||||
.swagger-ui .filter-container .operation-filter-input {
|
||||
background: var(--sw-input-bg);
|
||||
color: var(--sw-text);
|
||||
border-color: var(--sw-input-border);
|
||||
}
|
||||
|
||||
/* ── Loading ──────────────────────────────────────────────────── */
|
||||
.swagger-ui .loading-container .loading::after { color: var(--sw-text-muted); }
|
||||
|
||||
/* ── Arrows / toggles ────────────────────────────────────────── */
|
||||
.swagger-ui .expand-operation svg { fill: var(--sw-text-secondary); }
|
||||
.swagger-ui .models-control svg { fill: var(--sw-text-secondary); }
|
||||
.swagger-ui .expand-methods svg { fill: var(--sw-text-secondary); }
|
||||
|
||||
/* ── Markdown content inside descriptions ─────────────────────── */
|
||||
.swagger-ui .renderedMarkdown code {
|
||||
background: var(--sw-code-bg);
|
||||
color: var(--sw-code-text);
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.swagger-ui .renderedMarkdown pre code {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||
<script>
|
||||
SwaggerUIBundle({
|
||||
url: '/api/docs/openapi.yaml',
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
||||
layout: 'BaseLayout',
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user