Files
virtuality/web/templates/backups.html
T
Claude 52e2033031 Бэкапы VM с ротацией и ночным расписанием были добавлены
Страница /backups: ручной бэкап любой VM (сжатый qcow2, работающая VM
ставится на паузу на время копирования), восстановление в выключенную
VM, удаление копий и настройка ночного расписания. Ротация хранит
заданное число копий. Установщик создаёт virtuality-backup.timer,
ночной запуск ведёт журнал /var/log/virtuality/backup.log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKUoZwHMCHnytfswFVRUHw
2026-08-15 19:26:58 +00:00

120 строки
6.2 KiB
HTML

<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Бэкапы — {{ app_name }}</title>
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
<div class="v-layout">
{% include "_sidebar.html" %}
<main class="v-main">
<header class="v-topbar">
<div>
<div class="v-title">Бэкапы</div>
<div class="v-subtitle">Сжатые копии дисков VM в /var/lib/virtuality/backups с ротацией и ночным расписанием</div>
</div>
<div class="top-meta">
<span>user: {{ user }}</span>
<a class="link-pill" href="/">← Дашборд</a>
<form method="post" action="/logout" class="logout-form"><input type="hidden" name="csrf_token" value="{{ csrf_token }}"><button class="ghost">Выйти</button></form>
</div>
</header>
{% if error %}<div class="alert danger">{{ error }}</div>{% endif %}
{% if message %}<div class="alert success">{{ message }}</div>{% endif %}
<section class="grid two">
<article class="card">
<div class="card-head">
<h2>Сделать бэкап сейчас</h2>
<span class="pill">qemu-img convert -c</span>
</div>
<form method="post" action="/backups/create" class="form-grid">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>
<span>Виртуальная машина</span>
<select name="vm_name" {% if vms %}required{% endif %}>
{% for vm in vms %}
<option value="{{ vm.name }}">{{ vm.name }} — {{ vm.state }}</option>
{% endfor %}
</select>
</label>
<button class="primary wide" {% if not vms %}disabled{% endif %}>Создать бэкап</button>
{% if not vms %}<div class="alert danger">VM пока нет.</div>{% endif %}
</form>
<p class="muted small-note">Работающая VM ставится на паузу на время копирования диска и продолжает работу автоматически. Бэкап сжимается — занимает заметно меньше исходного диска.</p>
</article>
<article class="card">
<div class="card-head">
<h2>Ночное расписание</h2>
<span class="pill">{{ 'включено' if schedule.enabled else 'выключено' }}</span>
</div>
<form method="post" action="/backups/schedule" class="form-grid">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label class="check-row">
<input type="checkbox" name="enabled" value="1" {% if schedule.enabled %}checked{% endif %}>
<span>Делать бэкапы каждую ночь (04:30 по Москве)</span>
</label>
<label>
<span>Хранить копий на VM</span>
<input type="number" name="keep" min="1" max="30" value="{{ schedule.keep }}" required>
</label>
<div class="label">Какие VM бэкапить</div>
{% for vm in vms %}
<label class="check-row">
<input type="checkbox" name="vms" value="{{ vm.name }}" {% if vm.name in schedule.vms %}checked{% endif %}>
<span>{{ vm.name }}</span>
</label>
{% else %}
<div class="muted small-note">VM пока нет — расписание можно настроить после их создания.</div>
{% endfor %}
<button class="primary wide">Сохранить расписание</button>
</form>
<p class="muted small-note">Старые копии сверх лимита удаляются автоматически после каждого успешного бэкапа. Журнал ночных бэкапов: /var/log/virtuality/backup.log.</p>
</article>
</section>
<section class="card">
<div class="card-head">
<h2>Сохранённые бэкапы</h2>
<span class="pill">{{ backups|length }} файлов</span>
</div>
<table>
<thead>
<tr><th>VM</th><th>Файл</th><th>Размер</th><th>Создан</th><th>Действия</th></tr>
</thead>
<tbody>
{% for backup in backups %}
<tr>
<td class="strong">{{ backup.vm }}</td>
<td>{{ backup.name }}</td>
<td>{{ backup.size }}</td>
<td>{{ backup.created }}</td>
<td class="actions">
<form method="post" action="/backups/{{ backup.vm }}/{{ backup.name }}/restore" onsubmit="return confirm('Восстановить VM {{ backup.vm }} из бэкапа {{ backup.name }}? Текущий диск VM будет перезаписан.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="ghost">Восстановить</button>
</form>
<form method="post" action="/backups/{{ backup.vm }}/{{ backup.name }}/delete" onsubmit="return confirm('Удалить бэкап {{ backup.name }}?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<button class="danger">Удалить</button>
</form>
</td>
</tr>
{% else %}
<tr><td colspan="5" class="muted">Бэкапов пока нет. Сделай первый бэкап кнопкой слева или включи ночное расписание.</td></tr>
{% endfor %}
</tbody>
</table>
<p class="muted small-note">Восстановление перезаписывает первый диск VM содержимым бэкапа — VM должна быть выключена.</p>
</section>
</main>
</div>
<script src="/static/panel.js" defer></script>
</body>
</html>