Merge branch 'feature/vmops' into claude/production-system-image-smcxyr
# Conflicts: # tests/conftest.py # tests/dev_server.py # web/app.py # web/static/app.css # web/static/icons.svg
Этот коммит содержится в:
@@ -0,0 +1,27 @@
|
||||
{#- Карточка «Диски» на вкладке «Диски и загрузка»: размеры и форма увеличения. Ожидает vm из vm_detail.html. -#}
|
||||
{% from "_ui.html" import icon %}
|
||||
{% set dk = vmops_disks(vm.name) %}
|
||||
<article class="card">
|
||||
<div class="card-header"><div><h2>Диски</h2><p>Диск можно увеличить, уменьшить — нельзя</p></div></div>
|
||||
<div class="list">
|
||||
{% for disk in dk.disks %}
|
||||
<div class="list-item disk-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 }}{% if disk.size_text %} · {{ disk.size_text }}{% if disk.used_text %}, занято {{ disk.used_text }}{% endif %}{% endif %}</div>
|
||||
</div>
|
||||
{% if disk.resizable %}
|
||||
<form method="post" action="/vm/{{ vm.name }}/disks/{{ disk.target }}/resize" class="disk-resize" data-confirm="Диск {{ disk.name }} станет больше. Уменьшить его обратно уже не получится." data-confirm-title="Увеличить диск?" data-confirm-button="Увеличить" data-confirm-tone="brand" data-busy="Увеличиваем…">
|
||||
<label class="subtle small" for="disk-size-{{ disk.target }}">до</label>
|
||||
<div class="input-group"><input class="input" id="disk-size-{{ disk.target }}" type="number" name="size_gb" min="{{ disk.min_gb }}" max="{{ dk.max_gb }}" step="1" value="{{ disk.min_gb }}" required aria-label="Новый размер диска {{ disk.name }} в гигабайтах"><span class="addon">ГБ</span></div>
|
||||
<button class="btn btn-sm" type="submit">{{ icon('expand', 'icon-sm') }}Увеличить</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="list-item subtle">Дисков не найдено</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="card-footer disk-footer"><p class="subtle small">Свободно на сервере: <strong>{{ dk.free_text }}</strong>. {{ dk.hint }}</p></div>
|
||||
</article>
|
||||
@@ -0,0 +1,6 @@
|
||||
{#- Две маленькие шкалы (процессор, память) в строке машины на обзоре; panel.js заполняет их из /live/stats. -#}
|
||||
{% from "_ui.html" import icon %}
|
||||
<div class="vm-meters" data-live-stats="{{ vm.name }}" {% if not s.running %}hidden{% endif %}>
|
||||
<span class="vm-meter" title="Процессор">{{ icon('cpu', 'icon-sm') }}<span class="meter" data-stat="cpu-meter"><span data-stat="cpu-bar" style="--value:0%"></span></span><b data-stat="cpu">—</b></span>
|
||||
<span class="vm-meter" title="Память">{{ icon('memory-stick', 'icon-sm') }}<span class="meter" data-stat="mem-meter"><span data-stat="mem-bar" style="--value:0%"></span></span><b data-stat="mem">—</b></span>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
{#- Сообщения после действий vmops (пауза, диски, заметки): читаются из адреса страницы. -#}
|
||||
{% from "_ui.html" import alert %}
|
||||
{% for key in ('ops', 'disk', 'notes') %}
|
||||
{% if request.query_params.get(key ~ '_message') %}{{ alert('success', request.query_params.get(key ~ '_message')) }}{% endif %}
|
||||
{% if request.query_params.get(key ~ '_error') %}{{ alert('danger', request.query_params.get(key ~ '_error')) }}{% endif %}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,43 @@
|
||||
{#- Карточки на обзоре машины: нагрузка сейчас, спящий режим, заметки. Ожидает vm из vm_detail.html. -#}
|
||||
{% from "_ui.html" import icon %}
|
||||
{% set notes = vmops_notes(vm.name) %}
|
||||
{% set ops_s = (vm.info.state or 'shut off')|vm_state %}
|
||||
{% if ops_s.tone != 'neutral' %}
|
||||
<article class="card" data-live-stats="{{ vm.name }}">
|
||||
<div class="card-header"><div><h2>Нагрузка сейчас</h2><p>Обновляется каждые 5 секунд</p></div><span class="stat-icon">{{ icon('activity', 'icon-sm') }}</span></div>
|
||||
<div class="card-body load-grid">
|
||||
<div class="load-row">
|
||||
<div class="row-between"><span class="subtle">{{ icon('cpu', 'icon-sm') }}Процессор</span><strong data-stat="cpu">—</strong></div>
|
||||
<div class="meter" data-stat="cpu-meter"><span data-stat="cpu-bar" style="--value:0%"></span></div>
|
||||
</div>
|
||||
<div class="load-row">
|
||||
<div class="row-between"><span class="subtle">{{ icon('memory-stick', 'icon-sm') }}Память</span><strong data-stat="mem">—</strong></div>
|
||||
<div class="meter" data-stat="mem-meter"><span data-stat="mem-bar" style="--value:0%"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% if vmops_saved(vm.dominfo) %}
|
||||
<article class="card">
|
||||
<div class="card-body stack-sm">
|
||||
<div class="row" style="flex-wrap:nowrap;align-items:flex-start"><span class="list-icon brand">{{ icon('moon') }}</span><div class="grow"><strong>Машина в спящем режиме</strong><div class="subtle small">При запуске она продолжит работу с того же места: открытые программы и окна останутся.</div></div></div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/ops/forget-save" data-confirm="Сохранённое состояние будет удалено: всё, что было открыто внутри машины и не сохранено, пропадёт. При запуске система загрузится заново." data-confirm-title="Забыть сохранённое состояние?" data-confirm-button="Забыть" data-toast="Сохранённое состояние удалено" data-toast-tone="warning"><button class="btn btn-sm btn-danger-soft">{{ icon('x', 'icon-sm') }}Забыть сохранённое состояние</button></form>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
<article class="card">
|
||||
<div class="card-header"><div><h2>Заметки</h2><p>Название видно в списке машин</p></div><span class="stat-icon">{{ icon('notebook-pen', 'icon-sm') }}</span></div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/notes/save" data-toast="Заметки сохранены" data-busy="Сохраняем…">
|
||||
<div class="card-body stack-sm">
|
||||
<div class="field">
|
||||
<label for="vm-title">Название</label>
|
||||
<input class="input" id="vm-title" name="title" maxlength="80" value="{{ notes.title }}" placeholder="Например: Сайт компании" autocomplete="off">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="vm-description">Описание</label>
|
||||
<textarea class="input" id="vm-description" name="description" rows="4" maxlength="2000" placeholder="Что стоит внутри, кто отвечает, когда делать резервные копии…">{{ notes.description }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer"><button class="btn btn-sm btn-primary" type="submit">Сохранить заметки</button></div>
|
||||
</form>
|
||||
</article>
|
||||
@@ -0,0 +1,15 @@
|
||||
{#- Пункты меню машины: пауза, продолжение, спящий режим, клонирование.
|
||||
Перед include задайте ops_name (имя), ops_state (состояние virsh),
|
||||
ops_next (куда вернуться после действия) и ops_hints (подсказки под пунктами). -#}
|
||||
{% from "_ui.html" import icon %}
|
||||
{%- set ops = ops_state|vm_state -%}
|
||||
{%- macro item(label, hint, hints) -%}<span>{{ label }}{% if hints %}<small>{{ hint }}</small>{% endif %}</span>{%- endmacro -%}
|
||||
{% if ops.running %}
|
||||
<form method="post" action="/vm/{{ ops_name }}/ops/pause" data-toast="Машина {{ ops_name }} приостановлена" data-toast-tone="warning"><input type="hidden" name="next" value="{{ ops_next }}"><button class="menu-item">{{ icon('pause') }}{{ item('Приостановить', 'Заморозить, оставив в памяти', ops_hints) }}</button></form>
|
||||
<form method="post" action="/vm/{{ ops_name }}/ops/hibernate" data-confirm="Память машины сохранится на диск сервера, а сама машина выключится. При следующем запуске она продолжит работу с этого же места." data-confirm-title="Перевести {{ ops_name }} в спящий режим?" data-confirm-button="Усыпить" data-confirm-tone="brand" data-toast="Машина {{ ops_name }} засыпает"><input type="hidden" name="next" value="{{ ops_next }}"><button class="menu-item">{{ icon('moon') }}{{ item('Спящий режим', 'Сохранить на диск и выключить', ops_hints) }}</button></form>
|
||||
{% elif ops.raw == 'paused' %}
|
||||
<form method="post" action="/vm/{{ ops_name }}/ops/resume" data-toast="Машина {{ ops_name }} продолжает работу"><input type="hidden" name="next" value="{{ ops_next }}"><button class="menu-item">{{ icon('play') }}{{ item('Продолжить', 'Снять с паузы', ops_hints) }}</button></form>
|
||||
<form method="post" action="/vm/{{ ops_name }}/ops/hibernate" data-confirm="Память машины сохранится на диск сервера, а сама машина выключится. При следующем запуске она продолжит работу с этого же места." data-confirm-title="Перевести {{ ops_name }} в спящий режим?" data-confirm-button="Усыпить" data-confirm-tone="brand" data-toast="Машина {{ ops_name }} засыпает"><input type="hidden" name="next" value="{{ ops_next }}"><button class="menu-item">{{ icon('moon') }}{{ item('Спящий режим', 'Сохранить на диск и выключить', ops_hints) }}</button></form>
|
||||
{% endif %}
|
||||
<a class="menu-item" href="/vm/{{ ops_name }}/clone">{{ icon('copy') }}{{ item('Клонировать', 'Точная копия с новым именем', ops_hints) }}</a>
|
||||
<div class="menu-sep"></div>
|
||||
@@ -2,6 +2,7 @@
|
||||
{% from "_ui.html" import icon, badge, vm_badge, op_badge, meter, alert %}
|
||||
{% set page_title = 'Обзор' %}
|
||||
{% set running = vms|selectattr('state', 'in', ['running', 'idle', 'blocked'])|list|length %}
|
||||
{% set titles = vmops_titles() if vms else {} %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
@@ -26,6 +27,7 @@
|
||||
{% if profile.virtualization_warning %}
|
||||
{{ alert('warning', 'Машины будут работать медленно. Включите аппаратную виртуализацию (Intel VT-x / AMD-V) в настройках BIOS сервера.', 'Аппаратное ускорение недоступно') }}
|
||||
{% endif %}
|
||||
{% include "_vm_ops_alerts.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -69,17 +71,20 @@
|
||||
<span class="vm-avatar {{ 'on' if s.running else '' }}">{{ icon('monitor') }}<span class="status-dot {{ s.tone }}" data-live="dot"></span></span>
|
||||
<span style="min-width:0">
|
||||
<span class="vm-name truncate" style="display:block">{{ vm.name }}</span>
|
||||
<span class="vm-sub"><span data-live="ip">{{ vm.ip if vm.ip and vm.ip != '—' else ('IP появится после запуска' if not s.running else 'Получает адрес…') }}</span></span>
|
||||
<span class="vm-sub">{% if titles.get(vm.name) %}<span class="vm-title">{{ titles[vm.name] }}</span> · {% endif %}<span data-live="ip">{{ vm.ip if vm.ip and vm.ip != '—' else ('IP появится после запуска' if not s.running else 'Получает адрес…') }}</span></span>
|
||||
</span>
|
||||
</a>
|
||||
<div class="vm-col">
|
||||
<div class="vm-cell-label">Ресурсы</div>
|
||||
<div class="vm-cell-value">{{ vm.vcpus or '—' }} {{ plural(vm.vcpus or 0, 'ядро', 'ядра', 'ядер') }} · {{ vm.memory_mb|format_mb }}</div>
|
||||
{% include "_vm_live_meters.html" %}
|
||||
</div>
|
||||
<div class="vm-col">{{ vm_badge(vm.state) }}</div>
|
||||
<div class="vm-actions">
|
||||
{% if s.running %}
|
||||
<a class="btn btn-sm" href="/vm/{{ vm.name }}/console" target="_blank" rel="noopener">{{ icon('monitor', 'icon-sm') }}<span class="btn-label">Экран</span></a>
|
||||
{% elif vm.state == 'paused' %}
|
||||
<form method="post" action="/vm/{{ vm.name }}/ops/resume" class="inline-form" data-toast="Машина {{ vm.name }} продолжает работу"><input type="hidden" name="next" value="/"><button class="btn btn-sm">{{ icon('play', 'icon-sm') }}<span class="btn-label">Продолжить</span></button></form>
|
||||
{% else %}
|
||||
<form method="post" action="/vm/{{ vm.name }}/start" class="inline-form" data-toast="Машина {{ vm.name }} запускается"><button class="btn btn-sm">{{ icon('play', 'icon-sm') }}<span class="btn-label">Запустить</span></button></form>
|
||||
{% endif %}
|
||||
@@ -87,13 +92,14 @@
|
||||
<summary class="btn btn-sm btn-icon btn-ghost" aria-label="Действия">{{ icon('ellipsis') }}</summary>
|
||||
<div class="menu-list">
|
||||
<a class="menu-item" href="/vm/{{ vm.name }}">{{ icon('settings') }}Открыть настройки</a>
|
||||
{% set ops_name, ops_state, ops_next, ops_hints = vm.name, vm.state, '/', false %}{% include "_vm_ops_menu.html" %}
|
||||
{% if s.running %}
|
||||
<a class="menu-item" href="/vm/{{ vm.name }}/console" target="_blank" rel="noopener">{{ icon('monitor') }}Открыть экран</a>
|
||||
<form method="post" action="/vm/{{ vm.name }}/reboot" data-toast="Машина {{ vm.name }} перезагружается"><button class="menu-item">{{ icon('rotate-cw') }}Перезагрузить</button></form>
|
||||
<form method="post" action="/vm/{{ vm.name }}/shutdown" data-toast="Машина {{ vm.name }} выключается"><button class="menu-item">{{ icon('power') }}Выключить</button></form>
|
||||
<div class="menu-sep"></div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/destroy" data-confirm="Машина будет выключена сразу, как при отключении питания. Несохранённые данные внутри неё пропадут." data-confirm-title="Выключить {{ vm.name }} принудительно?" data-confirm-button="Выключить" data-toast="Машина {{ vm.name }} выключена" data-toast-tone="warning"><button class="menu-item danger">{{ icon('zap') }}Выключить принудительно</button></form>
|
||||
{% else %}
|
||||
{% elif vm.state != 'paused' %}
|
||||
<form method="post" action="/vm/{{ vm.name }}/start" data-toast="Машина {{ vm.name }} запускается"><button class="menu-item">{{ icon('play') }}Запустить</button></form>
|
||||
<div class="menu-sep"></div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/delete" data-confirm="Машина и все её диски будут удалены без возможности восстановления." data-confirm-title="Удалить {{ vm.name }}?" data-confirm-button="Удалить навсегда" data-toast="Машина {{ vm.name }} удалена" data-toast-tone="warning"><button class="menu-item danger">{{ icon('trash-2') }}Удалить машину</button></form>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
{% extends "base.html" %}
|
||||
{% from "_ui.html" import icon, vm_badge, alert %}
|
||||
{% set page_title = 'Клонировать ' ~ vm_name %}
|
||||
{% set st = state|vm_state %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="eyebrow"><a href="/vm/{{ vm_name }}">{{ icon('arrow-left', 'icon-sm') }} {{ vm_name }}</a></div>
|
||||
<div class="title-row"><h1>Клонировать {{ vm_name }}</h1><span data-live-vm="{{ vm_name }}" data-running="{{ '1' if st.running else '0' }}" data-reload-on-change>{{ vm_badge(state, 'lg') }}</span></div>
|
||||
<p class="lead">Точная копия машины: те же диски, процессор и память. У копии будет своё имя и свой сетевой адрес.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block alerts %}
|
||||
{% if error %}{{ alert('danger', error) }}{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="grid grid-main">
|
||||
<div class="stack">
|
||||
<article class="card">
|
||||
<div class="card-header"><div><h2>Новая машина</h2><p>Копия появится в списке машин выключенной</p></div></div>
|
||||
{% if blockers %}
|
||||
<div class="card-body stack-sm" style="padding-bottom:0">
|
||||
{% if 'running' in blockers %}
|
||||
<div class="alert warning" role="status">{{ icon('triangle-alert') }}<div><strong>Сначала выключите машину</strong>Диски копируются только у выключенной машины, иначе копия может получиться повреждённой.</div>
|
||||
{% if st.running %}<form method="post" action="/vm/{{ vm_name }}/shutdown" class="alert-action" data-toast="Машина выключается — это может занять до минуты"><button class="btn btn-sm">{{ icon('power', 'icon-sm') }}Выключить</button></form>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if 'space' in blockers %}{{ alert('danger', 'Копии нужно ' ~ needed_text ~ ', а свободно только ' ~ free_text ~ '. Удалите ненужные машины или образы.', 'Не хватает места на сервере') }}{% endif %}
|
||||
{% if 'no_disks' in blockers %}{{ alert('danger', 'У машины нет дисков, которые можно скопировать.') }}{% endif %}
|
||||
{% if 'busy' in blockers %}{{ alert('info', 'Эта машина уже клонируется. Дождитесь окончания в разделе «Задачи».') }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="/vm/{{ vm_name }}/clone/start" data-busy="Запускаем…">
|
||||
<div class="card-body form-grid">
|
||||
<div class="field">
|
||||
<label for="new_name">Имя копии</label>
|
||||
<input class="input" id="new_name" name="new_name" value="{{ new_name }}" required pattern="[A-Za-z0-9][A-Za-z0-9_.\-]{1,62}" maxlength="63" autocomplete="off" {% if blockers %}disabled{% endif %}>
|
||||
<span class="hint">Латинские буквы, цифры, точка, дефис и подчёркивание. Например: {{ suggested }}</span>
|
||||
</div>
|
||||
<div class="kv">
|
||||
<div style="grid-column: span 2"><span>Что скопируется</span><strong>{% for d in disks %}{{ d.name }}{% if d.size_text %} · {{ d.size_text }}{% endif %}{% if not loop.last %}<br>{% endif %}{% else %}—{% endfor %}</strong></div>
|
||||
<div><span>Нужно места</span><strong>{{ needed_text }}</strong></div>
|
||||
<div><span>Свободно на сервере</span><strong>{{ free_text }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<span class="subtle small" style="margin-right:auto">Занимает от минуты до часа — зависит от размера дисков</span>
|
||||
<a class="btn" href="/vm/{{ vm_name }}">Отмена</a>
|
||||
<button class="btn btn-primary" type="submit" {% if blockers %}disabled{% endif %}>{{ icon('copy') }}Клонировать</button>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<aside class="stack">
|
||||
<article class="card">
|
||||
<div class="card-header"><h2>Как это работает</h2></div>
|
||||
<div class="list">
|
||||
<div class="list-item"><span class="list-icon brand">{{ icon('hard-drive') }}</span><div class="grow"><div class="title">Диски копируются целиком</div><div class="meta">Внутри копии те же система, программы и пароли.</div></div></div>
|
||||
<div class="list-item"><span class="list-icon">{{ icon('network') }}</span><div class="grow"><div class="title">Свой сетевой адрес</div><div class="meta">Копия получит новый MAC-адрес, а IP появится после запуска.</div></div></div>
|
||||
<div class="list-item"><span class="list-icon">{{ icon('monitor') }}</span><div class="grow"><div class="title">Смените имя компьютера</div><div class="meta">Внутри системы копия называется как оригинал. Переименуйте её после первого запуска, чтобы не путаться.</div></div></div>
|
||||
<div class="list-item"><span class="list-icon">{{ icon('layers') }}</span><div class="grow"><div class="title">Снимки не копируются</div><div class="meta">В копию попадает только текущее состояние дисков.</div></div></div>
|
||||
</div>
|
||||
</article>
|
||||
</aside>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -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>
|
||||
@@ -48,6 +51,7 @@
|
||||
{% if msg %}{{ alert('success', msg) }}{% endif %}
|
||||
{% if err %}{{ alert('danger', err) }}{% endif %}
|
||||
{% endfor %}
|
||||
{% include "_vm_ops_alerts.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -98,6 +102,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 'Машина будет запускаться вместе с сервером' }}">
|
||||
@@ -191,16 +196,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>
|
||||
|
||||
Ссылка в новой задаче
Block a user