Страница «Настройки» была добавлена: доступ, пароль, сеть машин, хранилище, время, обновления, сервер

- web/features/settings.py + settings.html: всё, что спрашивал мастер, меняется в одном месте
- перезагрузка/выключение с ожиданием возврата панели, архив настроек и отчёт для поддержки
- сообщения после редиректов показываются на любой странице (?message=, ?error=)
- virtuality-ctl set: настройки обновлений применяются без перезапуска панели
- tests/test_settings.py

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
Этот коммит содержится в:
Claude
2026-09-25 14:55:31 +00:00
родитель 55f7d971f6
Коммит 2745a1280f
11 изменённых файлов: 497 добавлений и 8 удалений
+39
Просмотреть файл
@@ -0,0 +1,39 @@
{% extends "setup_base.html" %}
{% from "_ui.html" import icon %}
{% block rail %}{% endblock %}
{% block step %}
<div class="card-header"><div><h2>{{ 'Перезагружаем сервер' if action == 'reboot' else 'Выключаем сервер' }}</h2><p>Машины выключаются корректно, это может занять до нескольких минут.</p></div></div>
<div class="card-body stack" data-power="{{ action }}">
<div class="row" style="gap:14px"><span class="spinner" style="width:22px;height:22px;color:var(--brand)"></span><strong data-power-status>{{ message }}…</strong></div>
<div class="progress indeterminate"><span></span></div>
{% if action == 'reboot' %}
<p class="subtle small">Когда сервер снова включится, эта страница откроет панель сама. Обычно это занимает 1–2 минуты.</p>
{% else %}
<p class="subtle small">Чтобы включить сервер снова, нажмите кнопку питания на нём. Эту вкладку можно закрыть.</p>
{% endif %}
</div>
<div class="card-footer"><a class="btn btn-ghost" href="/">На главную</a></div>
{% endblock %}
{% block scripts %}
{% if action == 'reboot' %}
<script>
(function () {
var status = document.querySelector('[data-power-status]');
var down = false, started = Date.now();
function poll() {
fetch('/healthz', { cache: 'no-store' }).then(function (r) {
if (!r.ok) throw new Error('down');
if (down) { status.textContent = 'Сервер снова работает — открываем панель'; setTimeout(function () { location.href = '/'; }, 1000); return; }
status.textContent = 'Выключаем машины и перезагружаемся… (' + Math.round((Date.now() - started) / 1000) + ' с)';
setTimeout(poll, 3000);
}).catch(function () {
down = true;
status.textContent = 'Сервер перезагружается, ждём… (' + Math.round((Date.now() - started) / 1000) + ' с)';
setTimeout(poll, 4000);
});
}
setTimeout(poll, 5000);
})();
</script>
{% endif %}
{% endblock %}