Веб-интерфейс: страница /clusters, навигация и крошки для кластеров

- Выделена страница списка кластеров, панель упрощена; nav_active и крошки
  ведут в раздел Кластеры; theme.js синхронизирует активную пилюлю по URL.
- Доработки дашборда, аддонов, журнала, стилей и API-документации.
- Поддержка Podman: docker-compose.podman.yml, скрипты сокета; Makefile и env.
This commit is contained in:
Sergey Antropoff
2026-04-04 13:42:21 +03:00
parent 17f6233fd7
commit eb063aec20
38 changed files with 3990 additions and 734 deletions
+1
View File
@@ -57,6 +57,7 @@
{# Прокрутка только у пилюль: overflow-x на nav обрезал бы выпадающее меню API (position: absolute). #}
<div class="nav-links-scroll">
<a href="/" class="nav-link nav-pill{% if nav_active|default('') == 'panel' %} nav-pill--active{% endif %}">Панель</a>
<a href="/clusters" class="nav-link nav-pill{% if nav_active|default('') == 'clusters' %} nav-pill--active{% endif %}">Кластеры</a>
<a href="/cluster-addons" class="nav-link nav-pill{% if nav_active|default('') == 'cluster_addons' %} nav-pill--active{% endif %}">Аддоны</a>
<a href="/journal" class="nav-link nav-pill{% if nav_active|default('') == 'journal' %} nav-pill--active{% endif %}">Журнал</a>
<a href="/documentation" class="nav-link nav-pill{% if nav_active|default('') == 'documentation' %} nav-pill--active{% endif %}">Документация</a>
+146 -41
View File
@@ -38,46 +38,66 @@
<h1 class="page-title" id="addons-hero-title">Дополнения кластера (Helm)</h1>
<p class="muted page-lead">
Выберите кластер с сохранённым kubeconfig. Установка и удаление выполняются через <code>helm</code> в контейнере веб-приложения;
операции могут занимать несколько минут — дождитесь ответа сервера. Для kind у ingress-nginx задан NodePort <strong>30080</strong>.
операции могут занимать несколько минут — ход и вывод команд отображаются в журнале ниже (как при развёртывании кластера). История операций сохраняется в каталоге кластера.
</p>
</section>
<section class="card create-cluster-card cluster-addons-toolbar-card">
<h2 class="create-cluster-card__title">Кластер</h2>
<div class="cluster-addons-cluster-row">
<label for="addons-cluster-select" class="cluster-addons-cluster-label">Активный кластер</label>
<select id="addons-cluster-select" class="cluster-addons-cluster-select" aria-describedby="addons-cluster-hint">
<label for="addons-cluster-select" class="cluster-addons-cluster-label">Выберите кластер для просмотра статуса аддонов.</label>
<select id="addons-cluster-select" class="cluster-addons-cluster-select" aria-label="Выберите кластер для просмотра статуса аддонов">
<option value="">— загрузка списка —</option>
</select>
<button type="button" class="btn-secondary" id="addons-refresh-status">Обновить статус</button>
</div>
<p id="addons-cluster-hint" class="muted cluster-addons-hint">
Нужен файл <code>clusters/&lt;имя&gt;/kubeconfig</code> и доступ API из контейнера.
</p>
<p id="addons-versions-hint" class="muted cluster-addons-hint">
Список версий чартов подгружается с сервера (<code>GET /api/v1/helm/chart-versions</code>); значение «Последняя» не передаёт <code>--version</code> в Helm.
</p>
<div id="addons-status-bar" class="cluster-addons-status-bar muted" role="status" aria-live="polite"></div>
<div id="addons-status-bar" class="cluster-addons-status-bar" role="status" aria-live="polite"></div>
<p id="addons-bootstrap-msg" class="cluster-addons-bootstrap-msg muted hidden" role="status" aria-live="polite"></p>
</section>
<div
id="addons-cards-wrap"
class="cluster-addons-cards-wrap hidden"
aria-hidden="true"
>
<div class="cluster-addons-grid">
<section class="card cluster-addon-card" data-addon="ingress-nginx">
<h2 class="cluster-addon-card__title">ingress-nginx</h2>
<p class="muted cluster-addon-card__desc">Контроллер Ingress; для kind: NodePort HTTP на порту 30080.</p>
<header class="cluster-addon-card__header">
<h2 class="cluster-addon-card__title">ingress-nginx</h2>
<span class="cluster-addon-card__badge cluster-addon-card__badge--off" role="status">Не установлен</span>
</header>
<p class="muted cluster-addon-card__desc">Контроллер Ingress.</p>
<div class="create-form-field">
<label for="addons-ing-version-select">Версия чарта</label>
<select id="addons-ing-version-select" class="cluster-addons-version-select" aria-describedby="addons-versions-hint">
<select id="addons-ing-version-select" class="cluster-addons-version-select" aria-label="Версия чарта ingress-nginx">
<option value="">Последняя (из репозитория)</option>
</select>
</div>
<div class="cluster-addon-card__actions row">
<details class="cluster-addon-card__values-details">
<summary class="cluster-addon-card__values-summary">Values чарта (YAML)</summary>
<p class="muted cluster-addon-card__values-hint">
Содержимое из <code>helm show values</code> плюс сервис для kind; подставляется при смене версии чарта. После <code>-f</code> Helm применяет <code>--set</code> для NodePort.
</p>
<label for="addons-values-yaml-ingress-nginx" class="sr-only">Полные YAML values для ingress-nginx</label>
<textarea
id="addons-values-yaml-ingress-nginx"
class="cluster-addon-card__values-textarea mono"
rows="12"
spellcheck="false"
aria-label="Полные YAML values чарта ingress-nginx"
></textarea>
</details>
<div class="cluster-addon-card__actions row cluster-addon-card__actions--equal">
<button type="button" class="btn-addon-install" data-endpoint="ingress-nginx">Установить</button>
<button type="button" class="btn-secondary btn-addon-remove" data-endpoint="ingress-nginx">Удалить</button>
<button type="button" class="btn-secondary btn-addon-remove hidden" data-endpoint="ingress-nginx" hidden>Удалить</button>
</div>
</section>
<section class="card cluster-addon-card" data-addon="kube-prometheus-stack">
<h2 class="cluster-addon-card__title">kube-prometheus-stack</h2>
<header class="cluster-addon-card__header">
<h2 class="cluster-addon-card__title">kube-prometheus-stack</h2>
<span class="cluster-addon-card__badge cluster-addon-card__badge--off" role="status">Не установлен</span>
</header>
<p class="muted cluster-addon-card__desc">Prometheus Operator, Alertmanager, Grafana.</p>
<div class="create-form-field">
<label for="addons-grafana-user">Логин администратора Grafana</label>
@@ -93,14 +113,31 @@
<option value="">Последняя (из репозитория)</option>
</select>
</div>
<div class="cluster-addon-card__actions row">
<details class="cluster-addon-card__values-details">
<summary class="cluster-addon-card__values-summary">Values чарта (YAML)</summary>
<p class="muted cluster-addon-card__values-hint">
Вывод <code>helm show values</code> с подставленными логином и паролем Grafana из полей выше; обновляется при смене версии или учётных данных. Пустое поле при установке — сервер соберёт values сам.
</p>
<label for="addons-values-yaml-kube-prometheus-stack" class="sr-only">Полные YAML values для kube-prometheus-stack</label>
<textarea
id="addons-values-yaml-kube-prometheus-stack"
class="cluster-addon-card__values-textarea mono"
rows="12"
spellcheck="false"
aria-label="Полные YAML values чарта kube-prometheus-stack"
></textarea>
</details>
<div class="cluster-addon-card__actions row cluster-addon-card__actions--equal">
<button type="button" class="btn-addon-install" data-endpoint="kube-prometheus-stack">Установить</button>
<button type="button" class="btn-secondary btn-addon-remove" data-endpoint="kube-prometheus-stack">Удалить</button>
<button type="button" class="btn-secondary btn-addon-remove hidden" data-endpoint="kube-prometheus-stack" hidden>Удалить</button>
</div>
</section>
<section class="card cluster-addon-card" data-addon="metrics-server">
<h2 class="cluster-addon-card__title">metrics-server</h2>
<header class="cluster-addon-card__header">
<h2 class="cluster-addon-card__title">metrics-server</h2>
<span class="cluster-addon-card__badge cluster-addon-card__badge--off" role="status">Не установлен</span>
</header>
<p class="muted cluster-addon-card__desc">Метрики узлов и подов для <code>kubectl top</code>; для kind включён <code>--kubelet-insecure-tls</code>.</p>
<div class="create-form-field">
<label for="addons-ms-version-select">Версия чарта</label>
@@ -108,14 +145,31 @@
<option value="">Последняя (из репозитория)</option>
</select>
</div>
<div class="cluster-addon-card__actions row">
<details class="cluster-addon-card__values-details">
<summary class="cluster-addon-card__values-summary">Values чарта (YAML)</summary>
<p class="muted cluster-addon-card__values-hint">
<code>helm show values</code> плюс аргументы для kind (<code>--kubelet-insecure-tls</code> и т.д.). Пустое поле — автосборка на сервере.
</p>
<label for="addons-values-yaml-metrics-server" class="sr-only">Полные YAML values для metrics-server</label>
<textarea
id="addons-values-yaml-metrics-server"
class="cluster-addon-card__values-textarea mono"
rows="12"
spellcheck="false"
aria-label="Полные YAML values чарта metrics-server"
></textarea>
</details>
<div class="cluster-addon-card__actions row cluster-addon-card__actions--equal">
<button type="button" class="btn-addon-install" data-endpoint="metrics-server">Установить</button>
<button type="button" class="btn-secondary btn-addon-remove" data-endpoint="metrics-server">Удалить</button>
<button type="button" class="btn-secondary btn-addon-remove hidden" data-endpoint="metrics-server" hidden>Удалить</button>
</div>
</section>
<section class="card cluster-addon-card cluster-addon-card--wide" data-addon="istio-kiali">
<h2 class="cluster-addon-card__title">Istio + Kiali</h2>
<header class="cluster-addon-card__header">
<h2 class="cluster-addon-card__title">Istio + Kiali</h2>
<span class="cluster-addon-card__badge cluster-addon-card__badge--off" role="status">Не установлен</span>
</header>
<p class="muted cluster-addon-card__desc">
Чарты <code>istio/base</code>, <code>istio/istiod</code> и <code>kiali-server</code> в <code>istio-system</code>. Вход в Kiali — стратегия
<strong>login</strong> (секрет с логином и паролем).
@@ -142,32 +196,83 @@
</select>
</div>
</div>
<div class="cluster-addon-card__actions row">
<details class="cluster-addon-card__values-details">
<summary class="cluster-addon-card__values-summary">Values чартов Istio и Kiali (YAML)</summary>
<p class="muted cluster-addon-card__values-hint">
Три блока из <code>helm show values</code> для выбранных версий. Логин и пароль Kiali вносятся в секрет Kubernetes отдельно; для kiali-server дополнительно задаётся <code>auth.strategy=login</code> через <code>--set</code>.
</p>
<p class="cluster-addon-card__values-subhead">kiali-server</p>
<label for="addons-values-yaml-istio-kiali" class="sr-only">YAML values kiali-server</label>
<textarea
id="addons-values-yaml-istio-kiali"
class="cluster-addon-card__values-textarea mono"
rows="10"
spellcheck="false"
aria-label="Полные YAML values чарта kiali-server"
></textarea>
<p class="cluster-addon-card__values-subhead">istio/base</p>
<label for="addons-values-yaml-istio-base" class="sr-only">YAML values istio-base</label>
<textarea
id="addons-values-yaml-istio-base"
class="cluster-addon-card__values-textarea mono"
rows="10"
spellcheck="false"
aria-label="Полные YAML values чарта istio/base"
></textarea>
<p class="cluster-addon-card__values-subhead">istio/istiod</p>
<label for="addons-values-yaml-istio-istiod" class="sr-only">YAML values istiod</label>
<textarea
id="addons-values-yaml-istio-istiod"
class="cluster-addon-card__values-textarea mono"
rows="10"
spellcheck="false"
aria-label="Полные YAML values чарта istio/istiod"
></textarea>
</details>
<div class="cluster-addon-card__actions row cluster-addon-card__actions--equal">
<button type="button" class="btn-addon-install" data-endpoint="istio-kiali">Установить</button>
<button type="button" class="btn-secondary btn-addon-remove" data-endpoint="istio-kiali">Удалить</button>
<button type="button" class="btn-secondary btn-addon-remove hidden" data-endpoint="istio-kiali" hidden>Удалить</button>
</div>
</section>
</div>
</div>
<section class="card cluster-addons-log-card">
<h2 class="create-cluster-card__title">Журнал последней операции</h2>
<pre id="addons-log-pre" class="mono cluster-addons-log-pre" role="log" aria-live="polite"></pre>
{# Журнал операции Helm: те же блоки, что на странице создания кластера (прогресс + job-log-panel). Без полноэкранного спиннера. #}
<section
id="addons-helm-progress-wrap"
class="card create-progress-wrap cluster-addons-helm-progress hidden"
aria-hidden="true"
aria-labelledby="addons-helm-progress-title"
>
<h2 class="create-cluster-card__title" id="addons-helm-progress-title">Ход операции Helm</h2>
<p class="create-progress-hint muted" id="addons-helm-progress-hint">
Выполняется команда в контейнере веб-приложения; полный вывод появится по завершении.
</p>
<div
class="progress-track"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="0"
aria-labelledby="addons-helm-progress-label"
id="addons-helm-progress-track"
>
<div class="progress-bar" id="addons-helm-progress-bar" style="width: 0%"></div>
</div>
<p class="progress-label" id="addons-helm-progress-label"></p>
<div class="job-log-panel-wrap">
<label for="addons-helm-log-panel" class="job-log-label muted">Журнал установки / удаления</label>
<pre
id="addons-helm-log-panel"
class="mono job-log-panel"
role="log"
aria-live="polite"
aria-relevant="additions"
></pre>
</div>
</section>
<div
id="addons-busy-overlay"
class="page-loading-overlay cluster-addons-busy-overlay hidden"
role="status"
aria-live="polite"
aria-busy="false"
aria-hidden="true"
>
<div class="page-loading-backdrop" aria-hidden="true"></div>
<div class="page-loading-center">
<span class="spinner page-loading-spinner" aria-hidden="true"></span>
<span id="addons-busy-label" class="page-loading-label">Выполняется операция Helm…</span>
</div>
</div>
{% include "partials/dashboard_modals.html" %}
{% endblock %}
{% block scripts %}
+9 -7
View File
@@ -119,16 +119,18 @@
<section class="card jobs-card">
<div class="row spread jobs-card-toolbar">
<h2 id="jobs-heading" class="jobs-card-title">Последние задания</h2>
<button type="button" id="btn-jobs-clear" class="btn-secondary">Очистить завершённые</button>
<button type="button" id="btn-jobs-refresh" class="btn-secondary">Обновить</button>
</div>
<div class="table-wrap">
<table id="tbl-jobs" aria-label="Последние задания создания">
<div class="table-wrap journal-table-wrap">
<table id="tbl-jobs" class="journal-table" aria-label="Последние задания из журналов кластеров">
<thead>
<tr>
<th>Дата, время <span class="th-hint">(UTC)</span></th>
<th>Кластер</th>
<th>Статус</th>
<th>Сообщение</th>
<th scope="col">Завершено (UTC)</th>
<th scope="col">Кластер</th>
<th scope="col">Тип задания</th>
<th scope="col">Статус</th>
<th scope="col">Сообщение</th>
<th scope="col">Лог</th>
</tr>
</thead>
<tbody></tbody>
+144 -12
View File
@@ -35,7 +35,7 @@
</div>
<p class="cluster-detail-breadcrumb muted" aria-label="Навигация">
<a href="/">Панель</a>
<a href="/clusters">Кластеры</a>
<span aria-hidden="true"> / </span>
<span>Кластер <code>{{ cluster_name | e }}</code></span>
</p>
@@ -78,7 +78,9 @@
<h2 id="cluster-k8s-heading" class="cluster-detail-section-title">Kubernetes (kubectl)</h2>
<p class="muted cluster-k8s-scope-hint">
Данные по <strong>всему кластеру</strong>: ресурсы в namespace — со всех пространств имён; узлы и список
namespace — без ограничения по отдельному ns.
namespace — без ограничения по отдельному ns. В карточках ресурсов выпадающий список справа от заголовка
содержит все namespace из ответа API и из фактических объектов в таблицах; при выборе «Все namespace» строки
таблицы упорядочены по namespace, затем по имени ресурса.
</p>
<p id="cluster-kube-global-hint" class="muted cluster-kube-hint hidden" role="status"></p>
</section>
@@ -94,52 +96,182 @@
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-pods-title">
<h2 class="cluster-detail-section-title" id="k8s-pods-title">Поды</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-pods-title">Поды</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-pods" class="sr-only">Поды: фильтр по namespace</label>
<select
id="cluster-k8s-filter-pods"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="pods"
aria-label="Поды: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-pods-root" class="cluster-k8s-root" role="region" aria-label="Поды"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-deploy-title">
<h2 class="cluster-detail-section-title" id="k8s-deploy-title">Deployments</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-deploy-title">Deployments</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-deployments" class="sr-only">Deployments: фильтр по namespace</label>
<select
id="cluster-k8s-filter-deployments"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="deployments"
aria-label="Deployments: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-deployments-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-sts-title">
<h2 class="cluster-detail-section-title" id="k8s-sts-title">StatefulSets</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-sts-title">StatefulSets</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-statefulsets" class="sr-only">StatefulSets: фильтр по namespace</label>
<select
id="cluster-k8s-filter-statefulsets"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="statefulsets"
aria-label="StatefulSets: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-statefulsets-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-ds-title">
<h2 class="cluster-detail-section-title" id="k8s-ds-title">DaemonSets</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-ds-title">DaemonSets</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-daemonsets" class="sr-only">DaemonSets: фильтр по namespace</label>
<select
id="cluster-k8s-filter-daemonsets"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="daemonsets"
aria-label="DaemonSets: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-daemonsets-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-rs-title">
<h2 class="cluster-detail-section-title" id="k8s-rs-title">ReplicaSets</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-rs-title">ReplicaSets</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-replicasets" class="sr-only">ReplicaSets: фильтр по namespace</label>
<select
id="cluster-k8s-filter-replicasets"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="replicasets"
aria-label="ReplicaSets: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-replicasets-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-jobs-title">
<h2 class="cluster-detail-section-title" id="k8s-jobs-title">Jobs</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-jobs-title">Jobs</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-jobs" class="sr-only">Jobs: фильтр по namespace</label>
<select
id="cluster-k8s-filter-jobs"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="jobs"
aria-label="Jobs: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-jobs-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-cj-title">
<h2 class="cluster-detail-section-title" id="k8s-cj-title">CronJobs</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-cj-title">CronJobs</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-cronjobs" class="sr-only">CronJobs: фильтр по namespace</label>
<select
id="cluster-k8s-filter-cronjobs"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="cronjobs"
aria-label="CronJobs: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-cronjobs-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-svc-title">
<h2 class="cluster-detail-section-title" id="k8s-svc-title">Сервисы</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-svc-title">Сервисы</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-services" class="sr-only">Сервисы: фильтр по namespace</label>
<select
id="cluster-k8s-filter-services"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="services"
aria-label="Сервисы: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-services-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-ing-title">
<h2 class="cluster-detail-section-title" id="k8s-ing-title">Ingresses</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-ing-title">Ingresses</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-ingresses" class="sr-only">Ingresses: фильтр по namespace</label>
<select
id="cluster-k8s-filter-ingresses"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="ingresses"
aria-label="Ingresses: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-ingresses-root" class="cluster-k8s-root" role="region"></div>
</section>
<section class="card cluster-k8s-resource-card" aria-labelledby="k8s-pvc-title">
<h2 class="cluster-detail-section-title" id="k8s-pvc-title">PVC</h2>
<div class="cluster-k8s-card-header">
<h2 class="cluster-detail-section-title" id="k8s-pvc-title">PVC</h2>
<div class="cluster-k8s-ns-filter-wrap">
<label for="cluster-k8s-filter-pvcs" class="sr-only">PVC: фильтр по namespace</label>
<select
id="cluster-k8s-filter-pvcs"
class="cluster-k8s-ns-select"
data-cluster-k8s-ns-filter="pvcs"
aria-label="PVC: фильтр по namespace"
>
<option value="">Все namespace</option>
</select>
</div>
</div>
<div id="cluster-k8s-pvcs-root" class="cluster-k8s-root" role="region" aria-label="PersistentVolumeClaim"></div>
</section>
+1 -1
View File
@@ -17,7 +17,7 @@
{% block content %}
<p class="cluster-detail-breadcrumb muted" aria-label="Навигация">
<a href="/">Панель</a>
<a href="/clusters">Кластеры</a>
<span aria-hidden="true"> / </span>
<a href="/cluster/{{ cluster_name }}">Кластер <code>{{ cluster_name | e }}</code></a>
<span aria-hidden="true"> / </span>
+81
View File
@@ -0,0 +1,81 @@
{# Список кластеров и сводка ресурсов узлов (перенесено с панели).
Автор: Сергей Антропов — https://devops.org.ru #}
{% extends "base.html" %}
{% block page_title %}Кластеры{% endblock %}
{% block body_extra_class %} dashboard-clusters-loading{% endblock %}
{% block body_attrs %}data-dashboard-mode="clusters"{% endblock %}
{% block footer %}
<div class="footer-inner">
<p class="footer-copyright">
© {{ app_title }} ·
<a href="https://devops.org.ru" target="_blank" rel="noopener">devops.org.ru</a>
</p>
</div>
{% endblock %}
{% block content %}
<div
id="clusters-page-loading-overlay"
class="page-loading-overlay"
role="status"
aria-live="polite"
aria-busy="true"
aria-label="Загрузка списка кластеров"
>
<div class="page-loading-backdrop" aria-hidden="true"></div>
<div class="page-loading-center">
<span class="spinner page-loading-spinner" aria-hidden="true"></span>
<span class="page-loading-label">Загрузка кластеров и метрик узлов…</span>
</div>
</div>
<section class="card hero-panel clusters-page-hero" aria-labelledby="clusters-page-title">
<div class="clusters-page-hero-row">
<div class="clusters-page-hero-text">
<h1 class="page-title" id="clusters-page-title">Кластеры</h1>
<p class="muted page-lead clusters-page-hero-lead">
Запущенные узлы kind, kubeconfig и действия.
</p>
</div>
<div class="clusters-page-hero-actions">
<a href="/cluster-create" class="clusters-page-create-btn">Создать кластер</a>
</div>
</div>
</section>
<section id="aggregate-donuts-section" class="card aggregate-donuts-card" aria-labelledby="donuts-heading">
<h2 id="donuts-heading" class="donuts-card-title">Ресурсы узлов (сводка)</h2>
<div id="aggregate-donuts" class="aggregate-donuts" aria-live="polite"></div>
</section>
<section class="card clusters-card">
<h2>Список кластеров</h2>
<div class="table-wrap">
<table id="tbl-clusters" aria-label="Список кластеров">
<thead>
<tr>
<th class="col-cluster-name">Имя</th>
<th class="col-kind">kind</th>
<th class="col-kubeconfig">kubeconfig</th>
<th class="col-version">Версия</th>
<th class="col-workers">Workers</th>
<th class="col-state">Состояние</th>
<th class="col-actions">Действия</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<p id="list-msg" class="msg muted"></p>
</section>
{% include "partials/dashboard_modals.html" %}
{% endblock %}
{% block scripts %}
<script src="/static/js/dashboard.js" defer></script>
{% endblock %}
+10 -25
View File
@@ -1,4 +1,4 @@
{# Главная: создание (CTA), статистика, донаты, таблица кластеров.
{# Главная: CTA, статистика, карточка «Ресурсы узлов» (только донаты); список кластеров на /clusters.
Автор: Сергей Антропов — https://devops.org.ru #}
{% extends "base.html" %}
@@ -36,7 +36,7 @@
<div class="cta-hero-inner">
<h1 class="page-title" id="cta-hero-title">Создать кластер</h1>
<p class="muted page-lead cta-hero-lead">
Кластеры kind, kubeconfig и kubectl — ниже на странице. Данные обновляются автоматически.
Список кластеров, сводка по узлам и действия — на странице <a href="/clusters">Кластеры</a>. Данные там обновляются автоматически.
</p>
<a href="/cluster-create" class="btn btn-cta-large">Перейти к форме создания</a>
</div>
@@ -53,30 +53,15 @@
</aside>
</div>
<section id="aggregate-donuts-section" class="card aggregate-donuts-card" aria-labelledby="donuts-heading">
<h2 id="donuts-heading" class="donuts-card-title">Ресурсы узлов (сводка)</h2>
<div id="aggregate-donuts" class="aggregate-donuts" aria-live="polite"></div>
</section>
<section class="card clusters-card">
<h2>Кластеры</h2>
<div class="table-wrap">
<table id="tbl-clusters" aria-label="Список кластеров">
<thead>
<tr>
<th class="col-cluster-name">Имя</th>
<th class="col-kind">kind</th>
<th class="col-kubeconfig">kubeconfig</th>
<th class="col-version">Версия</th>
<th class="col-workers">Workers</th>
<th class="col-state">Состояние</th>
<th class="col-actions">Действия</th>
</tr>
</thead>
<tbody></tbody>
</table>
<section
id="home-resources-summary-card"
class="card aggregate-donuts-card home-resources-summary-card"
aria-labelledby="home-resources-summary-heading"
>
<div id="home-resources-summary-wrap" class="home-resources-summary-inner">
<h2 id="home-resources-summary-heading" class="donuts-card-title">Ресурсы узлов (сводка)</h2>
<div id="aggregate-donuts" class="aggregate-donuts" aria-live="polite"></div>
</div>
<p id="list-msg" class="msg muted"></p>
</section>
{% include "partials/dashboard_modals.html" %}
+79 -15
View File
@@ -1,4 +1,5 @@
{# Агрегированный журнал завершённых заданий (JSON в каталогах кластеров).
{# Журналы: задания по кластеру, развёртывание (provision_log), Helm (helm_addon_log).
Переключатель режима — как «Простой / Расширенный» на странице редактирования кластера.
Автор: Сергей Антропов — https://devops.org.ru #}
{% extends "base.html" %}
@@ -18,7 +19,6 @@
{% endblock %}
{% block content %}
{# Полноэкранный спиннер до первой загрузки журнала; скрывается в journal.js #}
<div
id="journal-page-loading-overlay"
class="page-loading-overlay"
@@ -35,36 +35,100 @@
</div>
<section class="card hero-panel cluster-create-hero" aria-labelledby="journal-hero-title">
<h1 class="page-title" id="journal-hero-title">Журнал заданий</h1>
<p class="muted page-lead">
Записи о завершённых операциях (<strong>create</strong>, <strong>start</strong>, <strong>stop</strong> и др.) хранятся в
<code>clusters/&lt;имя&gt;/journal/jobs_history.json</code> на томе с хоста. Сводка по всем кластерам, по <strong>30</strong> записей на страницу;
детали задания в процессе — по-прежнему в <a href="/cluster-create">создании кластера</a> и <code>GET /api/v1/jobs/{id}</code>.
</p>
<h1 class="page-title" id="journal-hero-title">Журналы</h1>
</section>
<section class="card create-cluster-card journal-toolbar-card">
<h2 class="create-cluster-card__title">Действия</h2>
<div class="journal-toolbar-row">
<h2 class="create-cluster-card__title">Вид журнала</h2>
<fieldset class="cluster-edit-mode-fieldset journal-view-fieldset">
<legend class="cluster-edit-mode-legend">Режим просмотра</legend>
<div
class="cluster-edit-segmented"
role="radiogroup"
aria-label="Какой журнал показывать"
>
<div class="cluster-edit-segment-item">
<input
type="radio"
name="journal_view"
id="journal-seg-per-cluster"
class="cluster-edit-segment-native"
value="per_cluster"
checked
/>
<label for="journal-seg-per-cluster" class="cluster-edit-segment-label">
<span class="cluster-edit-segment-title">По кластеру</span>
<span class="cluster-edit-segment-sub-wrap">
<span class="cluster-edit-segment-sub">История заданий</span>
<span class="cluster-edit-segment-sub">create / start / stop</span>
</span>
</label>
</div>
<div class="cluster-edit-segment-item">
<input
type="radio"
name="journal_view"
id="journal-seg-provision"
class="cluster-edit-segment-native"
value="provision"
/>
<label for="journal-seg-provision" class="cluster-edit-segment-label">
<span class="cluster-edit-segment-title">Развёртывание</span>
<span class="cluster-edit-segment-sub-wrap">
<span class="cluster-edit-segment-sub">Последний provision_log</span>
<span class="cluster-edit-segment-sub">по каждому кластеру</span>
</span>
</label>
</div>
<div class="cluster-edit-segment-item">
<input
type="radio"
name="journal_view"
id="journal-seg-helm"
class="cluster-edit-segment-native"
value="helm_addons"
/>
<label for="journal-seg-helm" class="cluster-edit-segment-label">
<span class="cluster-edit-segment-title">Helm-аддоны</span>
<span class="cluster-edit-segment-sub-wrap">
<span class="cluster-edit-segment-sub">История операций Helm</span>
<span class="cluster-edit-segment-sub">install / delete</span>
</span>
</label>
</div>
</div>
</fieldset>
<div id="journal-cluster-picker" class="journal-cluster-picker">
<label for="journal-cluster-select" class="journal-cluster-picker-label">Кластер</label>
<select id="journal-cluster-select" class="journal-cluster-select" aria-describedby="journal-cluster-picker-hint">
<option value="">— загрузка —</option>
</select>
<p id="journal-cluster-picker-hint" class="muted journal-cluster-picker-hint">
Журнал заданий только для выбранного каталога в <code>clusters/</code>.
</p>
</div>
<div class="journal-toolbar-actions">
<button type="button" class="btn-secondary" id="journal-refresh-btn">Обновить</button>
<span class="muted journal-page-size-hint">На странице по <strong>30</strong> записей.</span>
</div>
<p id="journal-status" class="muted journal-status" role="status" aria-live="polite"></p>
</section>
<section class="card journal-list-card">
<h2 class="create-cluster-card__title">Последние записи</h2>
<div id="journal-empty" class="muted journal-empty hidden">Нет записей в журналах кластеров (или каталоги кластеров ещё не созданы).</div>
<h2 class="create-cluster-card__title" id="journal-table-section-title">Записи</h2>
<div id="journal-empty" class="muted journal-empty hidden">Нет записей.</div>
<div id="journal-table-wrap" class="journal-table-wrap">
<table class="journal-table" id="journal-table" aria-describedby="journal-hero-title">
<thead>
<tr>
<th scope="col">Завершено (UTC)</th>
<th scope="col">Кластер</th>
<th scope="col">Тип</th>
<th scope="col" id="journal-th-kind">Тип / операция</th>
<th scope="col">Статус</th>
<th scope="col">Сообщение</th>
<th scope="col">Журнал</th>
<th scope="col">Лог</th>
</tr>
</thead>
<tbody id="journal-tbody"></tbody>
+1 -1
View File
@@ -54,7 +54,7 @@
</div>
</div>
{# Журнал фонового задания (ошибка / отмена) — страница «Последние задания». #}
{# Журнал задания по API GET /jobs/{id} (модалка при необходимости из скриптов). #}
<div
id="job-log-modal-overlay"
class="modal-overlay job-log-modal-overlay hidden"