Панель: редактирование конфига кластера, reapply при старте, UI и таблица

- PUT/GET конфигурации кластера, страница редактирования и модалка после сохранения
- После смены kind-config: флаг в meta и start_cluster_reapply (kind delete + create)
- Старт/стоп: полноэкранный спиннер до завершения job; модалки и документация API
- Таблица кластеров: колонка Имя 40% при table-layout fixed; чекбоксы без width 100%
- Карточки ресурсов узлов на странице кластера: до 3 в ряд; прочие правки стилей и dashboard.js
This commit is contained in:
Sergey Antropoff
2026-04-04 10:49:40 +03:00
parent af0d1705cc
commit 52538d9816
14 changed files with 2571 additions and 137 deletions
+384
View File
@@ -0,0 +1,384 @@
{# Редактирование kind-config.yaml и meta.json для существующего каталога кластера.
Автор: Сергей Антропов — https://devops.org.ru #}
{% extends "base.html" %}
{% block page_title %}Редактирование {{ cluster_name }}{% endblock %}
{% block body_attrs %}data-cluster-name="{{ cluster_name | e }}"{% 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 %}
<p class="cluster-detail-breadcrumb muted" aria-label="Навигация">
<a href="/">Панель</a>
<span aria-hidden="true"> / </span>
<a href="/cluster/{{ cluster_name }}">Кластер <code>{{ cluster_name | e }}</code></a>
<span aria-hidden="true"> / </span>
<span>Редактирование</span>
</p>
<section class="card hero-panel cluster-create-hero" aria-labelledby="edit-hero-title">
<h1 class="page-title" id="edit-hero-title">Редактирование: <code>{{ cluster_name | e }}</code></h1>
<p class="muted page-lead" id="edit-kind-note">
Загрузка подсказки…
</p>
</section>
<section class="card create-cluster-card">
<h2 class="create-cluster-card__title">Конфигурация на диске</h2>
<p class="muted cluster-edit-hint">
Изменения пишутся в <code>clusters/{{ cluster_name | e }}/kind-config.yaml</code> и <code>meta.json</code>.
После сохранения конфигурации YAML показывается окно с тем, как изменения попадут в kind; кратко: при «Старт» зарегистрированный кластер пересоздаётся по новому файлу (данные в <code>clusters/{{ cluster_name | e }}/</code> сохраняются).
</p>
<form id="form-cluster-edit" novalidate>
<fieldset class="cluster-edit-mode-fieldset">
<legend class="cluster-edit-mode-legend">Режим настройки</legend>
<div
class="cluster-edit-segmented"
role="radiogroup"
aria-label="Режим настройки кластера kind"
>
<div class="cluster-edit-segment-item">
<input
type="radio"
name="save_mode"
id="seg-mode-simple"
class="cluster-edit-segment-native"
value="simple"
checked
/>
<label for="seg-mode-simple" 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">Тег образа kindest/node</span>
<span class="cluster-edit-segment-sub">и число worker-нод</span>
</span>
</label>
</div>
<div class="cluster-edit-segment-item">
<input
type="radio"
name="save_mode"
id="seg-mode-advanced"
class="cluster-edit-segment-native"
value="yaml"
/>
<label for="seg-mode-advanced" 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">и патчи kubeadm</span>
</span>
</label>
</div>
</div>
<p class="muted cluster-edit-mode-hint" id="cluster-edit-mode-hint"></p>
</fieldset>
<div class="create-form-grid" id="edit-simple-block">
<div class="create-form-field">
<label for="version-select-edit">
<a
href="https://hub.docker.com/r/kindest/node/tags"
target="_blank"
rel="noopener noreferrer"
class="create-form-doc-link"
title="Теги kindest/node на Docker Hub"
>Тег образа</a><span class="create-form-doc-link__ext" aria-hidden="true"></span>
</label>
<select id="version-select-edit" name="kubernetes_version_preset">
<option value="">— загрузить список —</option>
</select>
</div>
<div class="create-form-field">
<label for="fld-workers-edit">Worker-ноды (020)</label>
<input id="fld-workers-edit" name="workers" type="number" min="0" max="20" step="1" value="0" />
</div>
<div class="create-form-field create-form-span">
<label for="kubernetes_version_edit">Тег kindest/node (вручную)</label>
<input
id="kubernetes_version_edit"
name="kubernetes_version"
type="text"
placeholder="1.29.4 или v1.29.4"
autocomplete="off"
/>
</div>
</div>
<div class="cluster-edit-advanced-wrap hidden" id="edit-advanced-block" aria-hidden="true">
<div class="cluster-edit-advanced-intro">
<p class="cluster-edit-advanced-intro-text">
Поля соответствуют манифесту <code>kind: Cluster</code> (<code>apiVersion: kind.x-k8s.io/v1alpha4</code>).
Подробнее — в
<a href="https://kind.sigs.k8s.io/docs/user/configuration/" target="_blank" rel="noopener noreferrer">документации kind</a>.
</p>
</div>
<div class="cluster-edit-panels">
<!-- Панель: API и сеть кластера -->
<section class="cluster-edit-panel" aria-labelledby="adv-panel-net-heading">
<div class="cluster-edit-panel-head">
<h3 class="cluster-edit-panel-title" id="adv-panel-net-heading">Сеть кластера</h3>
<p class="cluster-edit-panel-desc muted">
Секция <code>networking:</code> — семейство IP, подсети, API server, CNI и режим kube-proxy.
</p>
</div>
<div class="cluster-edit-panel-body cluster-edit-panel-grid">
<div class="create-form-field">
<label for="fld-adv-api-version">apiVersion конфига</label>
<select id="fld-adv-api-version" name="adv_api_version">
<option value="kind.x-k8s.io/v1alpha4" selected>kind.x-k8s.io/v1alpha4</option>
</select>
</div>
<div class="create-form-field">
<label for="fld-adv-ip-family">ipFamily</label>
<select id="fld-adv-ip-family" name="adv_ip_family">
<option value="">по умолчанию (ipv4)</option>
<option value="ipv4">ipv4</option>
<option value="ipv6">ipv6</option>
<option value="dual">dual (dual-stack)</option>
</select>
</div>
<div class="create-form-field">
<label for="fld-adv-api-addr">apiServerAddress</label>
<input
id="fld-adv-api-addr"
type="text"
placeholder="127.0.0.1 — не задавать, если не нужно"
autocomplete="off"
spellcheck="false"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-api-port">apiServerPort</label>
<input
id="fld-adv-api-port"
type="number"
min="1"
max="65535"
placeholder="пусто — случайный порт kind"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-pod-subnet">podSubnet</label>
<input
id="fld-adv-pod-subnet"
type="text"
placeholder="напр. 10.244.0.0/16"
autocomplete="off"
spellcheck="false"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-svc-subnet">serviceSubnet</label>
<input
id="fld-adv-svc-subnet"
type="text"
placeholder="напр. 10.96.0.0/12"
autocomplete="off"
spellcheck="false"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-kube-proxy">kubeProxyMode</label>
<select id="fld-adv-kube-proxy" name="adv_kube_proxy">
<option value="">по умолчанию (iptables)</option>
<option value="iptables">iptables</option>
<option value="nftables">nftables (K8s 1.31+)</option>
<option value="ipvs">ipvs</option>
<option value="none">none (отключить kube-proxy)</option>
</select>
</div>
<div class="create-form-field cluster-edit-panel-span">
<label class="cluster-edit-check-label cluster-edit-check-label--card">
<input type="checkbox" id="fld-adv-disable-cni" />
<span class="cluster-edit-check-inline">
<span class="cluster-edit-check-title">Отключить встроенный CNI (kindnetd)</span>
<span class="cluster-edit-check-sep muted" aria-hidden="true"></span>
<span class="cluster-edit-check-hint muted">networking.disableDefaultCNI; свой CNI (Calico и т.д.)</span>
</span>
</label>
</div>
</div>
</section>
<!-- Панель: ноды -->
<section class="cluster-edit-panel" aria-labelledby="adv-panel-nodes-heading">
<div class="cluster-edit-panel-head">
<h3 class="cluster-edit-panel-title" id="adv-panel-nodes-heading">Ноды и образ</h3>
<p class="cluster-edit-panel-desc muted">
Список <code>nodes:</code> — роли control-plane / worker и образ <code>kindest/node</code> (версия Kubernetes).
</p>
</div>
<div class="cluster-edit-panel-body cluster-edit-panel-grid">
<div class="create-form-field">
<label for="version-select-adv">
<a
href="https://hub.docker.com/r/kindest/node/tags"
target="_blank"
rel="noopener noreferrer"
class="create-form-doc-link"
>Каталог тегов</a><span class="create-form-doc-link__ext" aria-hidden="true"></span>
</label>
<select id="version-select-adv" name="adv_kubernetes_version_preset">
<option value="">— загрузить список —</option>
</select>
</div>
<div class="create-form-field cluster-edit-panel-span">
<label for="fld-adv-image">Образ узла (<code>image:</code>)</label>
<input
id="fld-adv-image"
type="text"
placeholder="kindest/node:v1.29.4"
autocomplete="off"
spellcheck="false"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-cp">Control-plane нод</label>
<input id="fld-adv-cp" type="number" min="1" max="5" step="1" value="1" />
<span class="field-hint muted">Несколько CP — сценарий HA; обычно 1 или 3</span>
</div>
<div class="create-form-field">
<label for="fld-adv-workers">Worker-нод</label>
<input id="fld-adv-workers" type="number" min="0" max="20" step="1" value="0" />
</div>
</div>
</section>
<!-- Панель: проброс портов -->
<section class="cluster-edit-panel" aria-labelledby="adv-panel-ports-heading">
<div class="cluster-edit-panel-head">
<h3 class="cluster-edit-panel-title" id="adv-panel-ports-heading">Проброс портов</h3>
<p class="cluster-edit-panel-desc muted">
<code>extraPortMappings</code> на <strong>первой</strong> control-plane (удобно для Docker Desktop / Ingress).
</p>
</div>
<div class="cluster-edit-panel-body">
<div class="create-form-field cluster-edit-panel-span">
<label class="cluster-edit-check-label cluster-edit-check-label--card">
<input type="checkbox" id="fld-adv-port-enable" />
<span class="cluster-edit-check-inline">
<span class="cluster-edit-check-title">Включить проброс порта</span>
<span class="cluster-edit-check-sep muted" aria-hidden="true"></span>
<span class="cluster-edit-check-hint muted">
См.
<a
href="https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings"
target="_blank"
rel="noopener noreferrer"
>документацию kind</a>
</span>
</span>
</label>
</div>
<div class="cluster-edit-port-card hidden" id="adv-port-fields-row">
<div class="cluster-edit-panel-grid cluster-edit-panel-grid--tight">
<div class="create-form-field">
<label for="fld-adv-host-port">hostPort</label>
<input id="fld-adv-host-port" type="number" min="1" max="65535" placeholder="8080" />
</div>
<div class="create-form-field">
<label for="fld-adv-container-port">containerPort</label>
<input id="fld-adv-container-port" type="number" min="1" max="65535" value="80" />
</div>
<div class="create-form-field">
<label for="fld-adv-listen-addr">listenAddress</label>
<input
id="fld-adv-listen-addr"
type="text"
placeholder="127.0.0.1 или 0.0.0.0"
autocomplete="off"
spellcheck="false"
/>
</div>
<div class="create-form-field">
<label for="fld-adv-port-proto">protocol</label>
<select id="fld-adv-port-proto">
<option value="TCP" selected>TCP</option>
<option value="UDP">UDP</option>
<option value="SCTP">SCTP</option>
</select>
</div>
</div>
</div>
</div>
</section>
<!-- Панель: kubeadm -->
<section class="cluster-edit-panel" aria-labelledby="adv-panel-kubeadm-heading">
<div class="cluster-edit-panel-head">
<h3 class="cluster-edit-panel-title" id="adv-panel-kubeadm-heading">Патчи kubeadm</h3>
<p class="cluster-edit-panel-desc muted">
<code>kubeadmConfigPatches</code> для <strong>первой</strong> control-plane (YAML после <code>|</code>).
</p>
</div>
<div class="cluster-edit-panel-body">
<div class="create-form-field">
<label for="fld-adv-kubeadm">Фрагмент YAML</label>
<textarea
id="fld-adv-kubeadm"
class="cluster-edit-kubeadm-textarea mono"
rows="8"
spellcheck="false"
placeholder="kind: InitConfiguration&#10;nodeRegistration:&#10; kubeletExtraArgs:&#10; system-reserved: memory=500Mi"
></textarea>
</div>
</div>
</section>
</div>
<div class="cluster-edit-yaml-preview-card">
<div class="cluster-edit-yaml-preview-head">
<span class="cluster-edit-yaml-preview-title">Предпросмотр kind-config.yaml</span>
<span class="muted cluster-edit-yaml-preview-note">итог сохранения</span>
</div>
<pre id="adv-yaml-preview" class="mono cluster-edit-yaml-preview-pre" aria-live="polite"></pre>
</div>
</div>
<div class="create-form-field">
<label for="fld-description">Описание (meta.json, необязательно)</label>
<input id="fld-description" name="description" type="text" maxlength="2000" autocomplete="off" placeholder="Заметка для панели" />
</div>
<div class="row create-actions cluster-edit-actions">
<button type="submit">Сохранить</button>
<a class="btn-secondary cluster-edit-cancel-link" href="/cluster/{{ cluster_name }}">Отмена</a>
</div>
<p id="edit-msg" class="msg" role="status" aria-live="polite"></p>
</form>
</section>
{# После успешного PUT /config — как применяются изменения (пересоздание при «Старт» и т.д.). #}
<div
id="cluster-edit-save-modal-overlay"
class="modal-overlay cluster-edit-save-modal-overlay hidden"
role="dialog"
aria-modal="true"
aria-labelledby="cluster-edit-save-modal-title"
aria-hidden="true"
>
<div class="modal-box modal-box--cluster-edit-save" role="document">
<h3 id="cluster-edit-save-modal-title" class="cluster-edit-save-modal-title">Сохранено</h3>
<div id="cluster-edit-save-modal-body" class="cluster-edit-save-modal-body muted"></div>
<div class="confirm-modal-actions">
<button type="button" id="cluster-edit-save-modal-ok">Понятно</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/cluster_edit.js" defer></script>
{% endblock %}