fix: harden Polygon operation validation
This commit is contained in:
+293
-183
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -7,90 +8,95 @@
|
||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- ── Header ── -->
|
||||
<header class="header">
|
||||
<div class="header-top">
|
||||
<span class="badge badge-blue">polygon v{{ version }}</span>
|
||||
<img src="{{ url_for('static', filename='logo.svg') }}" alt="Nubes" class="logo" width="108" height="24">
|
||||
{% if stands_list|length > 1 %}
|
||||
{% for s in stands_list %}
|
||||
<a href="/{{ s.id }}/" class="stand-chip-inline {% if s.id == current_stand %}stand-active{% endif %}">{{ s.id }}</a>
|
||||
{% endfor %}
|
||||
<span class="sep">·</span>
|
||||
{% endif %}
|
||||
<a href="/swagger" class="swagger-link">📋 Swagger</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="dot dot-green"></span>
|
||||
<span class="status-text">Работает</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Сервисов: <strong>{{ svc_count }}</strong></span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Инстансов: <strong>{{ inst_count }}</strong></span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Задержка: <strong>{{ delay }}s</strong></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<!-- ── Что такое Polygon ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Что такое Polygon?</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong>Polygon</strong> — это эмулятор REST API облачной платформы
|
||||
<strong>Nubes</strong> для интеграционных тестов.
|
||||
Он полностью повторяет контракты реального API: сервисы, инстансы,
|
||||
операции, параметры, валидацию — но работает <strong>без реальной
|
||||
инфраструктуры</strong>, в памяти, с мгновенным откликом.
|
||||
</p>
|
||||
<div class="grid-2col">
|
||||
<div>
|
||||
<h3 class="h4">Зачем нужен</h3>
|
||||
<ul>
|
||||
<li>Тестировать создание/изменение/удаление инстансов без реальных ресурсов</li>
|
||||
<li>Отлаживать UI автодеплоя (<code>app-autotest</code>) на мок-данных</li>
|
||||
<li>Писать интеграционные тесты с детерминированным состоянием</li>
|
||||
<li>Проверять краевые случаи (ошибки валидации, сбои операций)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="h4">Как устроен</h3>
|
||||
<ul>
|
||||
<li>Flask 3.0 + gunicorn, 1 воркер (состояние в памяти)</li>
|
||||
<li>Конфигурация сервисов — из YAML (генерируется из STANDS)</li>
|
||||
<li>Все операции — синхронные, с настраиваемой задержкой</li>
|
||||
<li>Управляемый сброс состояния через <code>/_mock/reset</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- ── Header ── -->
|
||||
<header class="header">
|
||||
<div class="header-top">
|
||||
<span class="badge badge-blue">polygon v{{ version }}</span>
|
||||
<img src="{{ url_for('static', filename='logo.svg') }}" alt="Nubes" class="logo" width="108" height="24">
|
||||
{% if stands_list|length > 1 %}
|
||||
{% for s in stands_list %}
|
||||
<a href="/{{ s.id }}/" class="stand-chip-inline {% if s.id == current_stand %}stand-active{% endif %}">{{
|
||||
s.id }}</a>
|
||||
{% endfor %}
|
||||
<span class="sep">·</span>
|
||||
{% endif %}
|
||||
<a href="/swagger" class="swagger-link">📋 Swagger</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="dot dot-green"></span>
|
||||
<span class="status-text">Работает</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Сервисов: <strong>{{ svc_count }}</strong></span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Инстансов: <strong>{{ inst_count }}</strong></span>
|
||||
<span class="sep">·</span>
|
||||
<span class="header-stat">Задержка: <strong>{{ delay }}s</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</header>
|
||||
|
||||
<!-- ── Как использовать ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Как использовать</h2>
|
||||
{% if stands_list|length > 1 %}<span class="muted">Стенд: <strong>{{ current_stand }}</strong></span>{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<main>
|
||||
|
||||
<h3 class="h4">📡 Базовые команды</h3>
|
||||
<!-- ── Что такое Polygon ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Что такое Polygon?</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>
|
||||
<strong>Polygon</strong> — это эмулятор REST API облачной платформы
|
||||
<strong>Nubes</strong> для интеграционных тестов.
|
||||
Он полностью повторяет контракты реального API: сервисы, инстансы,
|
||||
операции, параметры, валидацию — но работает <strong>без реальной
|
||||
инфраструктуры</strong>, в памяти, с мгновенным откликом.
|
||||
</p>
|
||||
<div class="grid-2col">
|
||||
<div>
|
||||
<h3 class="h4">Зачем нужен</h3>
|
||||
<ul>
|
||||
<li>Тестировать создание/изменение/удаление инстансов без реальных ресурсов</li>
|
||||
<li>Отлаживать UI автодеплоя (<code>app-autotest</code>) на мок-данных</li>
|
||||
<li>Писать интеграционные тесты с детерминированным состоянием</li>
|
||||
<li>Проверять краевые случаи (ошибки валидации, сбои операций)</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="h4">Как устроен</h3>
|
||||
<ul>
|
||||
<li>Flask 3.0 + gunicorn, 1 воркер (состояние в памяти)</li>
|
||||
<li>Конфигурация сервисов — из YAML (генерируется из STANDS)</li>
|
||||
<li>Все операции — синхронные, с настраиваемой задержкой</li>
|
||||
<li>Управляемый сброс состояния через <code>/_mock/reset</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p><strong>Получить список сервисов:</strong></p>
|
||||
<pre class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/services | python3 -m json.tool</code></pre>
|
||||
<!-- ── Как использовать ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Как использовать</h2>
|
||||
{% if stands_list|length > 1 %}<span class="muted">Стенд: <strong>{{ current_stand }}</strong></span>{%
|
||||
endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<p><strong>Создать инстанс:</strong></p>
|
||||
<pre class="code-block"><code>curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/instances \
|
||||
<h3 class="h4">📡 Базовые команды</h3>
|
||||
|
||||
<p><strong>Получить список сервисов:</strong></p>
|
||||
<pre
|
||||
class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/services | python3 -m json.tool</code></pre>
|
||||
|
||||
<p><strong>Создать инстанс:</strong></p>
|
||||
<pre class="code-block"><code>curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/instances \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"serviceId": 1, "displayName": "my-instance"}' | python3 -m json.tool</code></pre>
|
||||
|
||||
<p><strong>Создать операцию, установить параметры, запустить:</strong></p>
|
||||
<pre class="code-block"><code># 1. Создать операцию (получить opUid из ответа)
|
||||
<p><strong>Создать операцию, установить параметры, запустить:</strong></p>
|
||||
<pre
|
||||
class="code-block"><code># 1. Создать операцию (получить opUid из ответа)
|
||||
curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/instanceOperations \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"instanceUid": "<uid>", "operation": "create", "svcOperationId": 18}'
|
||||
@@ -103,132 +109,236 @@ curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }
|
||||
# 3. Запустить операцию (будет ждать {{ delay }}s)
|
||||
curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/instanceOperations/<opUid>/run</code></pre>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid var(--border);">
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid var(--border);">
|
||||
|
||||
<h3 class="h4">🛠 Управление полигоном (служебные эндпоинты)</h3>
|
||||
<h3 class="h4">🛠 Управление полигоном (служебные эндпоинты)</h3>
|
||||
<p>Если на сервере задан <code>MOCK_AUTH_TOKEN</code>, для запросов к <code>/_mock/*</code> добавьте
|
||||
заголовок <code>-H "X-Mock-Auth: <token>"</code>.</p>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-item">
|
||||
<h4>⏱ Задержка операций</h4>
|
||||
<p>Каждый <code>POST /run</code> ждёт <strong>{{ delay }} секунд</strong> перед ответом — эмуляция времени выполнения в облаке.</p>
|
||||
<p><strong>Текущая:</strong> <code>{{ delay }}s</code></p>
|
||||
<pre class="code-block"><code># Сделать операции мгновенными
|
||||
<div class="feature-grid">
|
||||
<div class="feature-item">
|
||||
<h4>⏱ Задержка операций</h4>
|
||||
<p>Каждый <code>POST /run</code> ждёт <strong>{{ delay }} секунд</strong> перед ответом —
|
||||
эмуляция времени выполнения в облаке.</p>
|
||||
<p><strong>Текущая:</strong> <code>{{ delay }}s</code></p>
|
||||
<pre
|
||||
class="code-block"><code># Сделать операции мгновенными
|
||||
curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/delay/0
|
||||
|
||||
# Поставить задержку 3 секунды (эмуляция долгой операции)
|
||||
curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/delay/3</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<h4>🧹 Сброс состояния</h4>
|
||||
<p>Удаляет <strong>все</strong> инстансы и операции на текущем стенде. Полезно перед каждым тестом.</p>
|
||||
<pre class="code-block"><code>curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/reset</code></pre>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<h4>🧹 Сброс состояния</h4>
|
||||
<p>Удаляет <strong>все</strong> инстансы и операции на текущем стенде. Полезно перед каждым
|
||||
тестом.</p>
|
||||
<pre
|
||||
class="code-block"><code>curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/reset</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<h4>💥 Симуляция ошибок</h4>
|
||||
<p>Следующий <code>POST /run</code> <strong>упадёт</strong> с ошибкой. One-shot: после использования флаг сбрасывается.</p>
|
||||
<pre class="code-block"><code># Включить
|
||||
<div class="feature-item">
|
||||
<h4>💥 Симуляция ошибок</h4>
|
||||
<p>Следующий <code>POST /run</code> <strong>упадёт</strong> с ошибкой. One-shot: после
|
||||
использования флаг сбрасывается.</p>
|
||||
<pre class="code-block"><code># Включить
|
||||
curl -s -X POST {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/fail-next
|
||||
|
||||
# Следующий run → {"ok": false, "error": "mock failure..."}
|
||||
# После этого — снова работает нормально</code></pre>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<h4>📊 Отладка</h4>
|
||||
<p>Посмотреть текущее состояние всех инстансов и операций:</p>
|
||||
<pre
|
||||
class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/state | python3 -m json.tool</code></pre>
|
||||
<p>Список загруженных сервисов:</p>
|
||||
<pre
|
||||
class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/services | python3 -m json.tool</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid var(--border);">
|
||||
|
||||
<h3 class="h4">🔁 Жизненный цикл операции</h3>
|
||||
<ol style="line-height: 2;">
|
||||
<li><code>GET /api/v1/svc/instanceOperations/default/{svcOperationId}</code> — узнать какие
|
||||
параметры нужны</li>
|
||||
<li><code>POST /api/v1/svc/instanceOperations</code> — создать операцию (получить opUid)</li>
|
||||
<li><code>POST /api/v1/svc/instanceOperationCfsParams</code> × N — установить каждый параметр</li>
|
||||
<li><code>GET /api/v1/svc/instanceOperations/{opUid}/validate-cfs</code> — проверить корректность
|
||||
</li>
|
||||
<li><code>POST /api/v1/svc/instanceOperations/{opUid}/run</code> — выполнить (ждёт <strong>{{ delay
|
||||
}}s</strong>)</li>
|
||||
<li><code>GET /api/v1/svc/instanceOperations/{opUid}?fields=dtFinish,isSuccessful</code> — проверить
|
||||
результат</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="feature-item">
|
||||
<h4>📊 Отладка</h4>
|
||||
<p>Посмотреть текущее состояние всех инстансов и операций:</p>
|
||||
<pre class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/state | python3 -m json.tool</code></pre>
|
||||
<p>Список загруженных сервисов:</p>
|
||||
<pre class="code-block"><code>curl -s {{ endpoint }}/{% if stands_list|length > 1 %}{{ current_stand }}/{% endif %}api/v1/svc/_mock/services | python3 -m json.tool</code></pre>
|
||||
<!-- ── API ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>API эндпоинты</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Метод</th>
|
||||
<th>Путь</th>
|
||||
<th>Описание</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/health</code></td>
|
||||
<td>Healthcheck</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/</code></td>
|
||||
<td>Главная страница</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/services</code></td>
|
||||
<td>Список сервисов</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/services/<id></code></td>
|
||||
<td>Детали сервиса</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/instances</code></td>
|
||||
<td>Список инстансов</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/instances/<uid></code></td>
|
||||
<td>Детали инстанса</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/instances</code></td>
|
||||
<td>Создать инстанс</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperations/default/<id></code></td>
|
||||
<td>Шаблон операции и параметры</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperations</code></td>
|
||||
<td>Создать операцию</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperations/<uid></code></td>
|
||||
<td>Статус операции</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperationCfsParams</code></td>
|
||||
<td>Установить параметр операции</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperations/<uid>/validate-cfs</code></td>
|
||||
<td>Проверить параметры</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/instanceOperations/<uid>/run</code></td>
|
||||
<td>Запустить операцию</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/_mock/reset</code></td>
|
||||
<td>Сброс состояния</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/_mock/state</code></td>
|
||||
<td>Дамп состояния</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/_mock/services</code></td>
|
||||
<td>Список загруженных сервисов</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/_mock/delay/<s></code></td>
|
||||
<td>Задать задержку</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-post">POST</span></td>
|
||||
<td><code>/api/v1/svc/_mock/fail-next</code></td>
|
||||
<td>Следующая операция упадёт</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/swagger</code></td>
|
||||
<td>Swagger UI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="method method-get">GET</span></td>
|
||||
<td><code>/api/v1/svc/openapi.json</code></td>
|
||||
<td>OpenAPI JSON</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid var(--border);">
|
||||
<!-- ── Сервисы ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Загруженные сервисы <span class="muted">({{ svc_count }})</span></h2>
|
||||
</div>
|
||||
<div class="card-body table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Операций</th>
|
||||
<th>CfsParams</th>
|
||||
<th>StateParams</th>
|
||||
<th>StateOut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for svc in svc_list %}
|
||||
<tr>
|
||||
<td><code>{{ svc.id }}</code></td>
|
||||
<td>{{ svc.display_name }}</td>
|
||||
<td>{{ svc.op_count }}</td>
|
||||
<td>{% if svc.has_cfs %}<span class="dot dot-green"></span>{% else %}<span
|
||||
class="dot dot-gray"></span>{% endif %}</td>
|
||||
<td>{% if svc.has_state %}<span class="dot dot-green"></span>{% else %}<span
|
||||
class="dot dot-gray"></span>{% endif %}</td>
|
||||
<td>{% if svc.has_out %}<span class="dot dot-green"></span>{% else %}<span
|
||||
class="dot dot-gray"></span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h3 class="h4">🔁 Жизненный цикл операции</h3>
|
||||
<ol style="line-height: 2;">
|
||||
<li><code>GET /instanceOperations/default/{svcOperationId}</code> — узнать какие параметры нужны</li>
|
||||
<li><code>POST /instanceOperations</code> — создать операцию (получить opUid)</li>
|
||||
<li><code>POST /instanceOperationCfsParams</code> × N — установить каждый параметр</li>
|
||||
<li><code>GET /{opUid}/validate-cfs</code> — проверить корректность</li>
|
||||
<li><code>POST /{opUid}/run</code> — выполнить (ждёт <strong>{{ delay }}s</strong>)</li>
|
||||
<li><code>GET /{opUid}?fields=dtFinish,isSuccessful</code> — проверить результат</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- ── API ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>API эндпоинты</h2>
|
||||
</div>
|
||||
<div class="card-body table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Метод</th>
|
||||
<th>Путь</th>
|
||||
<th>Описание</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/health</code></td><td>Healthcheck</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/services</code></td><td>Список сервисов</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/services/<id></code></td><td>Детали сервиса</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/instances</code></td><td>Список инстансов</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/instances/<uid></code></td><td>Детали инстанса</td></tr>
|
||||
<tr><td><span class="method method-post">POST</span></td><td><code>/api/v1/svc/instances</code></td><td>Создать инстанс</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/instanceOperations</code></td><td>Список операций</td></tr>
|
||||
<tr><td><span class="method method-post">POST</span></td><td><code>/api/v1/svc/instanceOperations/<uid>/run</code></td><td>Запустить операцию</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/instanceOperations/<uid>/status</code></td><td>Статус операции</td></tr>
|
||||
<tr><td><span class="method method-post">POST</span></td><td><code>/api/v1/svc/_mock/reset</code></td><td>Сброс состояния</td></tr>
|
||||
<tr><td><span class="method method-get">GET</span></td><td><code>/api/v1/svc/_mock/state</code></td><td>Дамп состояния</td></tr>
|
||||
<tr><td><span class="method method-post">POST</span></td><td><code>/api/v1/svc/_mock/delay/<s></code></td><td>Задать задержку</td></tr>
|
||||
<tr><td><span class="method method-post">POST</span></td><td><code>/api/v1/svc/_mock/fail-next</code></td><td>Следующая операция упадёт</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── Сервисы ── -->
|
||||
<section class="card">
|
||||
<div class="card-header">
|
||||
<h2>Загруженные сервисы <span class="muted">({{ svc_count }})</span></h2>
|
||||
</div>
|
||||
<div class="card-body table-wrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Название</th>
|
||||
<th>Операций</th>
|
||||
<th>CfsParams</th>
|
||||
<th>StateParams</th>
|
||||
<th>StateOut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for svc in svc_list %}
|
||||
<tr>
|
||||
<td><code>{{ svc.id }}</code></td>
|
||||
<td>{{ svc.display_name }}</td>
|
||||
<td>{{ svc.op_count }}</td>
|
||||
<td>{% if svc.has_cfs %}<span class="dot dot-green"></span>{% else %}<span class="dot dot-gray"></span>{% endif %}</td>
|
||||
<td>{% if svc.has_state %}<span class="dot dot-green"></span>{% else %}<span class="dot dot-gray"></span>{% endif %}</td>
|
||||
<td>{% if svc.has_out %}<span class="dot dot-green"></span>{% else %}<span class="dot dot-gray"></span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
polygon v{{ version }} · Nubes Mock API · Flask + gunicorn
|
||||
</footer>
|
||||
<footer class="footer">
|
||||
polygon v{{ version }} · Nubes Mock API · Flask + gunicorn
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
+43
-35
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -7,18 +8,29 @@
|
||||
<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; }
|
||||
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;
|
||||
@@ -27,15 +39,18 @@
|
||||
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;
|
||||
@@ -45,6 +60,7 @@
|
||||
font-weight: 600;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.swagger-ui .topbar .nav-link {
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
@@ -52,58 +68,49 @@
|
||||
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>
|
||||
<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 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({
|
||||
spec: {{ spec_json | safe }},
|
||||
dom_id: "#swagger-ui",
|
||||
<script>
|
||||
window.onload = function () {
|
||||
const ui = SwaggerUIBundle({
|
||||
spec: {{ spec_json | safe }},
|
||||
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,
|
||||
// Предзаполняем токен для _mock/*
|
||||
onComplete: function () {
|
||||
const authToken = {{ auth_token | tojson }};
|
||||
if (authToken) {
|
||||
ui.preauthorizeApiKey("mockAuth", authToken);
|
||||
}
|
||||
},
|
||||
requestInterceptor: function (req) {
|
||||
// Добавляем X-Mock-Auth только для _mock/* эндпоинтов
|
||||
if (req.url.indexOf("/_mock/") !== -1 && {{ auth_token | tojson }}) {
|
||||
req.headers["X-Mock-Auth"] = {{ auth_token | tojson }};
|
||||
}
|
||||
return req;
|
||||
},
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
||||
layout: "StandaloneLayout",
|
||||
defaultModelsExpandDepth: 1,
|
||||
defaultModelExpandDepth: 2,
|
||||
docExpansion: "list",
|
||||
filter: true,
|
||||
showExtensions: true,
|
||||
showCommonExtensions: true,
|
||||
});
|
||||
|
||||
// Кастомный topbar с лого Nubes
|
||||
@@ -128,7 +135,8 @@
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user