feat: Swagger UI + OpenAPI 3.1.0 спека (v0.4.0)
- /swagger — Swagger UI (CDN, без pip-зависимостей) - /api/v1/svc/openapi.json — динамическая OpenAPI 3.1.0 спека - Nubes-брендированный topbar (лого + версия) - Bearer-токен предзаполняется автоматически - 17 эндпоинтов с полными схемами, примерами, описаниями - Ссылка «Swagger» на главной странице
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
<div class="header-top">
|
||||
<img src="{{ url_for('static', filename='logo.svg') }}" alt="Nubes" class="logo" width="150" height="33">
|
||||
<span class="badge badge-blue">polygon v{{ version }}</span>
|
||||
<a href="/swagger" class="swagger-link">📋 Swagger</a>
|
||||
</div>
|
||||
<div class="header-status">
|
||||
<span class="dot dot-green"></span>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Polygon API — Swagger</title>
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css">
|
||||
<style>
|
||||
html { box-sizing: border-box; overflow-y: scroll; }
|
||||
*, *:before, *:after { box-sizing: inherit; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
/* Nubes-брендированный topbar */
|
||||
.swagger-ui .topbar {
|
||||
background: #fff;
|
||||
border-bottom: 4px solid #2563eb;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.swagger-ui .topbar .wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
max-width: 1460px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.swagger-ui .topbar a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.swagger-ui .topbar img.logo {
|
||||
height: 28px;
|
||||
width: auto;
|
||||
}
|
||||
.swagger-ui .topbar .version-badge {
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 100px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
.swagger-ui .topbar .nav-link {
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
font-family: system-ui, sans-serif;
|
||||
margin-left: auto;
|
||||
}
|
||||
.swagger-ui .topbar .nav-link:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
/* Скрываем лого Swagger по умолчанию (заменяем своим) */
|
||||
.swagger-ui .topbar .link img[alt="Swagger UI"] {
|
||||
display: none;
|
||||
}
|
||||
/* Инфо-блок в description */
|
||||
.swagger-ui .markdown p {
|
||||
font-size: 14px;
|
||||
}
|
||||
.swagger-ui .info .title {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-standalone-preset.js" crossorigin></script>
|
||||
|
||||
<script>
|
||||
window.onload = function () {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "{{ openapi_url }}",
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
||||
layout: "StandaloneLayout",
|
||||
defaultModelsExpandDepth: 1,
|
||||
defaultModelExpandDepth: 2,
|
||||
docExpansion: "list",
|
||||
filter: true,
|
||||
showExtensions: true,
|
||||
showCommonExtensions: true,
|
||||
// Предзаполняем токен
|
||||
onComplete: function () {
|
||||
// Предзаполнить Bearer токен
|
||||
const authToken = "{{ auth_token }}";
|
||||
if (authToken) {
|
||||
// Swagger UI хранит авторизацию в localStorage
|
||||
try {
|
||||
const key = "swagger_auth_bearerAuth_" + encodeURIComponent(window.location.origin);
|
||||
const auth = JSON.parse(localStorage.getItem(key) || "{}");
|
||||
auth.value = "Bearer " + authToken;
|
||||
localStorage.setItem(key, JSON.stringify(auth));
|
||||
ui.preauthorizeApiKey("bearerAuth", authToken);
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
},
|
||||
requestInterceptor: function (req) {
|
||||
// Добавляем заголовок Authorization если Swagger UI его не добавил
|
||||
if (!req.headers.Authorization && "{{ auth_token }}") {
|
||||
req.headers.Authorization = "Bearer {{ auth_token }}";
|
||||
}
|
||||
return req;
|
||||
},
|
||||
});
|
||||
|
||||
// Кастомный topbar с лого Nubes
|
||||
const observer = new MutationObserver(function () {
|
||||
const topbar = document.querySelector(".swagger-ui .topbar .wrapper");
|
||||
if (topbar) {
|
||||
observer.disconnect();
|
||||
// Заменяем содержимое ссылки на свой логотип
|
||||
const link = topbar.querySelector("a");
|
||||
if (link) {
|
||||
link.innerHTML = '<img class="logo" src="{{ url_for("static", filename="logo.svg") }}" alt="Nubes">' +
|
||||
'<span class="version-badge">polygon v{{ version }}</span>';
|
||||
link.href = "/";
|
||||
}
|
||||
// Добавляем ссылку «← На главную»
|
||||
const navLink = document.createElement("a");
|
||||
navLink.className = "nav-link";
|
||||
navLink.href = "/";
|
||||
navLink.textContent = "← На главную";
|
||||
topbar.appendChild(navLink);
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
};
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user