Files
awg-dash/docs/openapi.yaml
T
embeddevio cc3640f13c
contract-openapi / openapi-contract (push) Has been cancelled
contract-openapi / ts-client-is-current (push) Has been cancelled
docs(api): OpenAPI 3.1 контракт API v1 как source of truth (Refs #19)
- docs/openapi.yaml: все эндпоинты ТЗ, единый формат ошибок {code,message,details},
  writeOnly для private_key/preshared_key/password, WS-кадры metrics/stream.
- requirements-contract.txt: зависимости контрактных тестов (jsonschema,
  openapi-spec-validator, PyYAML) до мержа каркаса PR #47.
- .gitea/workflows/contract-openapi.yml: lint + контрактный тест + проверка
  актуальности сгенерированного TS-клиента.
2026-10-22 00:20:36 +03:00

1255 lines
36 KiB
YAML

openapi: 3.1.0
info:
title: awg-dash API
version: 0.1.0
summary: REST + WebSocket control plane of the AmneziaWG gateway dashboard
description: |
Контракт API v1 (source of truth, ADR-008). Спецификация — источник истины:
реализация и UI-клиент генерируются/проверяются от неё.
Соглашения:
* Единый формат ошибки — схема `Error` (`{code, message, details}`) для любого
не-2xx ответа, включая 422 и ошибки внутренних исключений.
* Секреты (`private_key`, `preshared_key`, `password`) помечены `writeOnly`:
они допустимы в запросах и ЗАПРЕЩЕНЫ в ответах (`additionalProperties: false`
+ удаление `writeOnly`-свойств из ответной валидации).
* Аутентификация: `Authorization: Bearer <token>` для REST, cookie-сессия +
`X-CSRF-Token` для UI. `/api/v1/health`, `/api/v1/ready`, `/metrics` — открыты.
* Все таймстемпы — RFC 3339 (UTC), целые размеры — байты, длительности — секунды.
Генерация TS-клиента: `ui/scripts/generate-api.sh` →
`ui/src/api/generated/schema.d.ts`. Контрактный тест:
`pytest backend/tests/test_contract_responses.py`.
license:
name: MIT
identifier: MIT
servers:
- url: /
description: Встроенный uvicorn (ADR-007), тот же origin, что и UI
tags:
- name: ops
description: Служебные эндпоинты (health/ready/metrics), без аутентификации
- name: status
description: Состояние туннеля и сводный статус
- name: config
description: Чтение, запись и валидация конфигурации AmneziaWG
- name: routing
description: Правила маршрутизации, режим split-tunnel, GeoIP, импорт
- name: devices
description: Устройства за шлюзом и их маршрутизация
- name: logs
description: Журнал событий шлюза
- name: auth
description: Аутентификация UI и API
- name: metrics
description: Потоковые метрики (WebSocket)
security:
- bearerAuth: []
paths:
/api/v1/health:
get:
tags: [ops]
operationId: getHealth
summary: Liveness-проба
security: []
responses:
"200":
description: Процесс жив
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/ready:
get:
tags: [ops]
operationId: getReady
summary: Readiness-проба (готовность агента и зависимостей)
security: []
responses:
"200":
description: Состояние готовности (в том числе при ready=false)
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyResponse"
"503":
$ref: "#/components/responses/AgentUnavailable"
"500":
$ref: "#/components/responses/InternalError"
/metrics:
get:
tags: [ops]
operationId: getPrometheusMetrics
summary: Метрики в формате Prometheus text exposition
security: []
responses:
"200":
description: Текстовый экспорт метрик
content:
text/plain:
schema:
type: string
description: Prometheus text exposition format (version 0.0.4)
/api/v1/status:
get:
tags: [status]
operationId: getStatus
summary: Сводный статус шлюза, туннеля и GeoIP
responses:
"200":
description: Статус
content:
application/json:
schema:
$ref: "#/components/schemas/StatusResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"503":
$ref: "#/components/responses/AgentUnavailable"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/config:
get:
tags: [config]
operationId: getConfig
summary: Текущая конфигурация (без секретов)
responses:
"200":
description: Конфигурация; секретные поля отсутствуют
content:
application/json:
schema:
$ref: "#/components/schemas/ConfigResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
put:
tags: [config]
operationId: putConfig
summary: Атомарное применение конфигурации (validate → staging → apply → rollback)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ConfigUpdateRequest"
responses:
"200":
description: Конфигурация применена
content:
application/json:
schema:
$ref: "#/components/schemas/ConfigResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"409":
$ref: "#/components/responses/Conflict"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/config/validate:
post:
tags: [config]
operationId: validateConfig
summary: Сухая валидация конфигурации без применения
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ConfigUpdateRequest"
responses:
"200":
description: Результат валидации (в том числе при valid=false)
content:
application/json:
schema:
$ref: "#/components/schemas/ValidateResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/routing/rules:
get:
tags: [routing]
operationId: listRoutingRules
summary: Список правил маршрутизации
parameters:
- name: enabled
in: query
required: false
description: Фильтр по признаку включённости
schema:
type: boolean
- name: kind
in: query
required: false
schema:
$ref: "#/components/schemas/RuleKind"
responses:
"200":
description: Список правил
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRuleList"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
post:
tags: [routing]
operationId: createRoutingRule
summary: Создать правило маршрутизации
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRuleCreate"
responses:
"201":
description: Правило создано
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRule"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"409":
$ref: "#/components/responses/Conflict"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/routing/rules/{rule_id}:
parameters:
- $ref: "#/components/parameters/RuleId"
get:
tags: [routing]
operationId: getRoutingRule
summary: Правило по идентификатору
responses:
"200":
description: Правило
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRule"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
put:
tags: [routing]
operationId: updateRoutingRule
summary: Изменить правило
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRuleUpdate"
responses:
"200":
description: Обновлённое правило
content:
application/json:
schema:
$ref: "#/components/schemas/RoutingRule"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
delete:
tags: [routing]
operationId: deleteRoutingRule
summary: Удалить правило
responses:
"204":
description: Правило удалено
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/routing/mode:
put:
tags: [routing]
operationId: putRoutingMode
summary: Переключить режим split-tunnel
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ModeUpdateRequest"
responses:
"200":
description: Режим применён
content:
application/json:
schema:
$ref: "#/components/schemas/ModeUpdateResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"503":
$ref: "#/components/responses/AgentUnavailable"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/routing/geoip/update:
post:
tags: [routing]
operationId: updateGeoIp
summary: Обновить снапшот GeoIP (RIPE Stat → локальный кэш)
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/GeoIpUpdateRequest"
responses:
"200":
description: Обновление выполнено (или взято из кэша)
content:
application/json:
schema:
$ref: "#/components/schemas/GeoIpUpdateResponse"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"503":
$ref: "#/components/responses/UpstreamUnavailable"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/routing/import:
post:
tags: [routing]
operationId: importRoutingConfig
summary: Импорт .conf / JSON (пиры и правила)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ImportRequest"
responses:
"200":
description: Результат импорта
content:
application/json:
schema:
$ref: "#/components/schemas/ImportResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/metrics/stream:
get:
tags: [metrics]
operationId: streamMetrics
summary: WebSocket-поток метрик и статуса
description: |
WebSocket-апгрейд на том же пути. Клиент передаёт токен либо заголовком
`Authorization: Bearer <token>`, либо query-параметром `token`.
Сервер отправляет текстовые JSON-кадры схемы `WsFrame`; при `type=metrics`
поле `data` соответствует `MetricsSample`, при `type=status` — `StatusResponse`,
при `type=error` — `Error`.
parameters:
- name: token
in: query
required: false
description: Токен доступа для сред, где нельзя задать заголовок
schema:
type: string
- name: interval_ms
in: query
required: false
description: Период отправки кадров, мс
schema:
type: integer
minimum: 250
maximum: 60000
default: 1000
responses:
"101":
description: Переключение протокола на WebSocket
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/devices:
get:
tags: [devices]
operationId: listDevices
summary: Устройства за шлюзом
responses:
"200":
description: Список устройств
content:
application/json:
schema:
$ref: "#/components/schemas/DeviceList"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/logs:
get:
tags: [logs]
operationId: listLogs
summary: Журнал событий
parameters:
- name: limit
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 500
default: 100
- name: cursor
in: query
required: false
description: Курсор пагинации из предыдущего ответа (`next_cursor`)
schema:
type: string
- name: level
in: query
required: false
schema:
$ref: "#/components/schemas/LogLevel"
- name: q
in: query
required: false
description: Подстрока поиска по сообщению
schema:
type: string
responses:
"200":
description: Страница журнала
content:
application/json:
schema:
$ref: "#/components/schemas/LogList"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/auth/login:
post:
tags: [auth]
operationId: login
summary: Аутентификация (сессия UI + токен API)
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AuthLoginRequest"
responses:
"200":
description: Сессия создана
content:
application/json:
schema:
$ref: "#/components/schemas/AuthSession"
"401":
$ref: "#/components/responses/Unauthorized"
"422":
$ref: "#/components/responses/ValidationFailed"
"429":
$ref: "#/components/responses/RateLimited"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/auth/logout:
post:
tags: [auth]
operationId: logout
summary: Завершить сессию
responses:
"200":
description: Сессия завершена
content:
application/json:
schema:
$ref: "#/components/schemas/AuthSession"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
/api/v1/auth/session:
get:
tags: [auth]
operationId: getAuthSession
summary: Текущая сессия и CSRF-токен
responses:
"200":
description: Состояние сессии
content:
application/json:
schema:
$ref: "#/components/schemas/AuthSession"
"401":
$ref: "#/components/responses/Unauthorized"
"500":
$ref: "#/components/responses/InternalError"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque
description: "Токен доступа API (заголовок `Authorization: Bearer <token>`)"
sessionCookie:
type: apiKey
in: cookie
name: awg_session
description: Сессионная cookie UI (требуется вместе с `X-CSRF-Token` для изменяющих запросов)
parameters:
RuleId:
name: rule_id
in: path
required: true
description: Идентификатор правила
schema:
type: string
format: uuid
responses:
BadRequest:
description: Некорректный запрос
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Unauthorized:
description: Отсутствует или недействителен токен доступа
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Forbidden:
description: Недостаточно прав
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
NotFound:
description: Объект не найден
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Conflict:
description: Конфликт состояния (например, apply уже выполняется)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
ValidationFailed:
description: Тело запроса не соответствует схеме
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
RateLimited:
description: Превышен лимит запросов
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
UpstreamUnavailable:
description: Внешний источник (RIPE Stat) недоступен
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
AgentUnavailable:
description: Агент шлюза недоступен (unix-socket не отвечает)
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
InternalError:
description: Внутренняя ошибка
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
schemas:
ErrorCode:
type: string
description: Машиночитаемый код ошибки
enum:
- bad_request
- validation_failed
- unauthorized
- forbidden
- not_found
- conflict
- rate_limited
- upstream_unavailable
- agent_unavailable
- internal_error
Error:
type: object
description: Единый формат ошибки для всех не-2xx ответов
additionalProperties: false
required: [code, message, details]
properties:
code:
$ref: "#/components/schemas/ErrorCode"
message:
type: string
minLength: 1
description: Человекочитаемое описание
details:
description: Машиночитаемые детали (для validation_failed — список issues)
type: [object, "null"]
additionalProperties: true
default: null
Check:
type: object
additionalProperties: false
required: [name, ok]
properties:
name:
type: string
ok:
type: boolean
detail:
type: [string, "null"]
HealthResponse:
type: object
additionalProperties: false
required: [status, version, uptime_s, checks]
properties:
status:
type: string
enum: [ok, degraded, down]
version:
type: string
uptime_s:
type: integer
minimum: 0
checks:
type: array
items:
$ref: "#/components/schemas/Check"
ReadyResponse:
type: object
additionalProperties: false
required: [ready, checks]
properties:
ready:
type: boolean
checks:
type: array
items:
$ref: "#/components/schemas/Check"
RoutingMode:
type: string
description: Режим split-tunnel
enum: [geoip, dns, all-vpn, direct-only]
RuleKind:
type: string
enum: [domain, cidr, geoip, asn]
RuleAction:
type: string
enum: [vpn, direct]
LogLevel:
type: string
enum: [debug, info, warning, error]
TunnelState:
type: string
enum: [up, down, error]
TunnelStatus:
type: object
additionalProperties: false
required: [interface, state, handshake_age_s, rx_bytes, tx_bytes, endpoint, last_handshake_at]
properties:
interface:
type: string
examples: [awg0]
state:
$ref: "#/components/schemas/TunnelState"
handshake_age_s:
type: [integer, "null"]
minimum: 0
rx_bytes:
type: integer
minimum: 0
tx_bytes:
type: integer
minimum: 0
endpoint:
type: [string, "null"]
last_handshake_at:
type: [string, "null"]
format: date-time
GeoIpSource:
type: string
enum: [ripe-stat, cache, none]
GeoIpStatus:
type: object
additionalProperties: false
required: [source, prefix_count, updated_at]
properties:
source:
$ref: "#/components/schemas/GeoIpSource"
prefix_count:
type: integer
minimum: 0
updated_at:
type: [string, "null"]
format: date-time
StatusResponse:
type: object
additionalProperties: false
required: [version, uptime_s, mode, tunnel, geoip, kill_switch, dns_leak_protection]
properties:
version:
type: string
uptime_s:
type: integer
minimum: 0
mode:
$ref: "#/components/schemas/RoutingMode"
tunnel:
$ref: "#/components/schemas/TunnelStatus"
geoip:
$ref: "#/components/schemas/GeoIpStatus"
kill_switch:
type: boolean
dns_leak_protection:
type: boolean
TunnelConfig:
type: object
additionalProperties: false
required: [address, listen_port, mtu, private_key]
properties:
address:
type: string
description: Адрес интерфейса в CIDR
examples: ["10.8.0.2/32"]
listen_port:
type: integer
minimum: 1
maximum: 65535
mtu:
type: integer
minimum: 576
maximum: 1500
private_key:
type: string
writeOnly: true
description: Приватный ключ интерфейса; только запись, в ответах отсутствует
dns:
type: array
items:
type: string
PeerConfig:
type: object
additionalProperties: false
required: [public_key, endpoint, allowed_ips]
properties:
public_key:
type: string
preshared_key:
type: string
writeOnly: true
description: Preshared key; только запись, в ответах отсутствует
endpoint:
type: string
allowed_ips:
type: array
items:
type: string
persistent_keepalive:
type: [integer, "null"]
minimum: 0
maximum: 65535
DnsConfig:
type: object
additionalProperties: false
required: [mode, servers]
properties:
mode:
type: string
enum: [internal, external]
servers:
type: array
items:
type: string
Ipv6Policy:
type: string
enum: [direct, tunnel, block]
RoutingConfig:
type: object
additionalProperties: false
required: [mode, kill_switch, mtu_clamping, ipv6, geoip]
properties:
mode:
$ref: "#/components/schemas/RoutingMode"
kill_switch:
type: boolean
mtu_clamping:
type: boolean
ipv6:
$ref: "#/components/schemas/Ipv6Policy"
geoip:
$ref: "#/components/schemas/GeoIpStatus"
ConfigResponse:
type: object
additionalProperties: false
required: [tunnel, peers, dns, routing, updated_at]
properties:
tunnel:
$ref: "#/components/schemas/TunnelConfig"
peers:
type: array
items:
$ref: "#/components/schemas/PeerConfig"
dns:
$ref: "#/components/schemas/DnsConfig"
routing:
$ref: "#/components/schemas/RoutingConfig"
updated_at:
type: string
format: date-time
ConfigUpdateRequest:
type: object
description: Частичное обновление конфигурации (merge в текущее состояние)
additionalProperties: false
minProperties: 1
properties:
tunnel:
$ref: "#/components/schemas/TunnelConfig"
peers:
type: array
items:
$ref: "#/components/schemas/PeerConfig"
dns:
$ref: "#/components/schemas/DnsConfig"
routing:
$ref: "#/components/schemas/RoutingConfig"
ValidationIssue:
type: object
additionalProperties: false
required: [path, code, message, severity]
properties:
path:
type: string
description: JSON Pointer до проблемного поля
code:
type: string
message:
type: string
severity:
type: string
enum: [error, warning]
ValidateResponse:
type: object
additionalProperties: false
required: [valid, errors, warnings]
properties:
valid:
type: boolean
errors:
type: array
items:
$ref: "#/components/schemas/ValidationIssue"
warnings:
type: array
items:
$ref: "#/components/schemas/ValidationIssue"
RoutingRule:
type: object
additionalProperties: false
required: [id, name, kind, value, action, enabled, priority, comment, created_at, updated_at]
properties:
id:
type: string
format: uuid
name:
type: string
kind:
$ref: "#/components/schemas/RuleKind"
value:
type: string
action:
$ref: "#/components/schemas/RuleAction"
enabled:
type: boolean
priority:
type: integer
comment:
type: [string, "null"]
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
RoutingRuleCreate:
type: object
additionalProperties: false
required: [name, kind, value, action]
properties:
name:
type: string
minLength: 1
maxLength: 128
kind:
$ref: "#/components/schemas/RuleKind"
value:
type: string
minLength: 1
action:
$ref: "#/components/schemas/RuleAction"
enabled:
type: boolean
default: true
priority:
type: integer
minimum: 0
maximum: 10000
default: 100
comment:
type: [string, "null"]
RoutingRuleUpdate:
type: object
additionalProperties: false
minProperties: 1
properties:
name:
type: string
minLength: 1
maxLength: 128
kind:
$ref: "#/components/schemas/RuleKind"
value:
type: string
minLength: 1
action:
$ref: "#/components/schemas/RuleAction"
enabled:
type: boolean
priority:
type: integer
minimum: 0
maximum: 10000
comment:
type: [string, "null"]
RoutingRuleList:
type: object
additionalProperties: false
required: [items, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/RoutingRule"
total:
type: integer
minimum: 0
ModeUpdateRequest:
type: object
additionalProperties: false
required: [mode]
properties:
mode:
$ref: "#/components/schemas/RoutingMode"
ModeUpdateResponse:
type: object
additionalProperties: false
required: [mode, applied_at, reconcile_id]
properties:
mode:
$ref: "#/components/schemas/RoutingMode"
applied_at:
type: string
format: date-time
reconcile_id:
type: string
format: uuid
GeoIpUpdateRequest:
type: object
additionalProperties: false
properties:
force:
type: boolean
default: false
description: Игнорировать локальный кэш и обновить из RIPE Stat
GeoIpUpdateResponse:
type: object
additionalProperties: false
required: [status, source, prefix_count, updated_at, duration_ms]
properties:
status:
type: string
enum: [updated, cached, failed]
source:
$ref: "#/components/schemas/GeoIpSource"
prefix_count:
type: integer
minimum: 0
updated_at:
type: string
format: date-time
duration_ms:
type: integer
minimum: 0
ImportRequest:
type: object
additionalProperties: false
required: [format, content]
properties:
format:
type: string
enum: [amneziawg-conf, wireguard-conf, json]
content:
type: string
minLength: 1
apply:
type: boolean
default: false
description: Применить импорт сразу, а не только вернуть разбор
ImportResponse:
type: object
additionalProperties: false
required: [imported_peers, imported_rules, warnings]
properties:
imported_peers:
type: integer
minimum: 0
imported_rules:
type: integer
minimum: 0
warnings:
type: array
items:
type: string
Device:
type: object
additionalProperties: false
required: [name, ip, mac, vpn, ipsets, last_seen_at]
properties:
name:
type: string
ip:
type: string
mac:
type: [string, "null"]
vpn:
type: boolean
ipsets:
type: array
items:
type: string
last_seen_at:
type: [string, "null"]
format: date-time
DeviceList:
type: object
additionalProperties: false
required: [items, total]
properties:
items:
type: array
items:
$ref: "#/components/schemas/Device"
total:
type: integer
minimum: 0
LogEntry:
type: object
additionalProperties: false
required: [ts, level, logger, message, fields]
properties:
ts:
type: string
format: date-time
level:
$ref: "#/components/schemas/LogLevel"
logger:
type: string
message:
type: string
fields:
type: object
additionalProperties: true
LogList:
type: object
additionalProperties: false
required: [items, total, next_cursor]
properties:
items:
type: array
items:
$ref: "#/components/schemas/LogEntry"
total:
type: integer
minimum: 0
next_cursor:
type: [string, "null"]
AuthLoginRequest:
type: object
additionalProperties: false
required: [username, password]
properties:
username:
type: string
minLength: 1
password:
type: string
minLength: 1
writeOnly: true
AuthSession:
type: object
additionalProperties: false
required: [authenticated, username, expires_at, csrf_token]
properties:
authenticated:
type: boolean
username:
type: [string, "null"]
expires_at:
type: [string, "null"]
format: date-time
csrf_token:
type: [string, "null"]
MetricsSample:
type: object
additionalProperties: false
required: [rx_bytes, tx_bytes, rx_rate_bps, tx_rate_bps, handshake_age_s, active_peers]
properties:
rx_bytes:
type: integer
minimum: 0
tx_bytes:
type: integer
minimum: 0
rx_rate_bps:
type: integer
minimum: 0
tx_rate_bps:
type: integer
minimum: 0
handshake_age_s:
type: [integer, "null"]
minimum: 0
active_peers:
type: integer
minimum: 0
WsFrame:
type: object
additionalProperties: false
required: [type, ts, data]
properties:
type:
type: string
enum: [metrics, status, error]
ts:
type: string
format: date-time
data:
description: |
MetricsSample при type=metrics, StatusResponse при type=status,
Error при type=error.
type: object
additionalProperties: true