Повседневные операции с машиной были добавлены: диск, клон, пауза, заметки, нагрузка

Модуль web/features/vmops.py (router подключён двумя строками в app.py):

- Увеличение диска на вкладке «Диски и загрузка»: размер из qemu-img info,
  только рост, проверка свободного места; выключенная машина — qemu-img resize,
  работающая — virsh blockresize. Диски вне /var/lib/virtuality не трогаются.
- Клонирование: страница /vm/{name}/clone с проверками (машина выключена,
  хватает места, имя свободно), фоновая операция virt-clone с явными --file
  в хранилище образов, прогресс по росту файлов копии, pool-refresh после.
- Пауза, продолжение, спящий режим (managedsave как операция) и удаление
  сохранённого состояния — /vm/{name}/ops/{action}; пункты меню на обзоре
  и странице машины, кнопка «Продолжить» для приостановленной машины.
- Заметки: название и описание через virsh desc (--new-desc=ТЕКСТ одним
  аргументом, чтобы текст вида «--edit» не стал опцией); название видно
  под именем машины на обзоре (virsh list --all --title, парсер в presenters).
- Живая нагрузка: /live/stats по virsh domstats для всех активных машин
  одним вызовом, CPU % по разнице cpu.time между опросами; шкалы в строках
  обзора и карточка «Нагрузка сейчас» на странице машины (panel.js, 5 с).

Тесты для всех парсеров и маршрутов (tests/test_vmops.py), демо-данные
в dev_server.py (три машины в разных состояниях, живые шкалы), значки
notebook-pen и expand в спрайте.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
Этот коммит содержится в:
Claude
2026-09-25 15:00:41 +00:00
родитель 7bf7a0f6b7
Коммит 34684634af
19 изменённых файлов: 1410 добавлений и 19 удалений
+6 -10
Просмотреть файл
@@ -21,12 +21,15 @@
{% if s.running %}
<a class="btn btn-primary" href="/vm/{{ vm.name }}/console" target="_blank" rel="noopener">{{ icon('monitor') }}Открыть экран</a>
<form method="post" action="/vm/{{ vm.name }}/shutdown" class="inline-form" data-toast="Машина выключается — это может занять до минуты"><button class="btn">{{ icon('power') }}Выключить</button></form>
{% elif s.raw == 'paused' %}
<form method="post" action="/vm/{{ vm.name }}/ops/resume" class="inline-form" data-toast="Машина продолжает работу"><button class="btn btn-primary">{{ icon('play') }}Продолжить</button></form>
{% else %}
<form method="post" action="/vm/{{ vm.name }}/start" class="inline-form" data-toast="Машина запускается"><button class="btn btn-primary">{{ icon('play') }}Запустить</button></form>
{% endif %}
<details class="menu">
<summary class="btn btn-icon" aria-label="Другие действия">{{ icon('ellipsis') }}</summary>
<div class="menu-list">
{% set ops_name, ops_state, ops_next, ops_hints = vm.name, s.raw, '/vm/' ~ vm.name, true %}{% include "_vm_ops_menu.html" %}
{% if s.running %}
<form method="post" action="/vm/{{ vm.name }}/reboot" data-toast="Машина перезагружается"><button class="menu-item">{{ icon('rotate-cw') }}<span>Перезагрузить<small>Мягкая перезагрузка системы</small></span></button></form>
<form method="post" action="/vm/{{ vm.name }}/destroy" data-confirm="Машина будет выключена сразу, как при отключении питания. Несохранённые данные внутри неё пропадут." data-confirm-title="Выключить принудительно?" data-confirm-button="Выключить" data-toast="Машина выключена" data-toast-tone="warning"><button class="menu-item danger">{{ icon('zap') }}<span>Выключить принудительно<small>Если система зависла</small></span></button></form>
@@ -46,6 +49,7 @@
{% if msg %}{{ alert('success', msg) }}{% endif %}
{% if err %}{{ alert('danger', err) }}{% endif %}
{% endfor %}
{% include "_vm_ops_alerts.html" %}
{% endblock %}
{% block content %}
@@ -94,6 +98,7 @@
</div>
<div class="stack">
{% include "_vm_ops_cards.html" %}
<article class="card">
<div class="card-body stack-sm">
<form method="post" action="/vm/{{ vm.name }}/{{ 'autostart-disable' if vm.autostart_enabled else 'autostart' }}" class="switch-row" data-no-busy data-toast="{{ 'Автозапуск выключен' if vm.autostart_enabled else 'Машина будет запускаться вместе с сервером' }}">
@@ -198,16 +203,7 @@
</div>
</article>
<article class="card">
<div class="card-header"><h2>Диски</h2></div>
<div class="list">
{% for disk in vm.disk_list %}
<div class="list-item"><span class="list-icon">{{ icon('disc-3' if disk.is_cdrom else 'hard-drive') }}</span><div class="grow"><div class="title truncate">{{ disk.name }}</div><div class="meta">{{ disk.label }} · {{ disk.target }}</div></div></div>
{% else %}
<div class="list-item subtle">Дисков не найдено</div>
{% endfor %}
</div>
</article>
{% include "_vm_disks.html" %}
<article class="card">
<div class="card-header"><h2>Сетевые карты</h2></div>