Cloud-образы, клонирование VM и шаблоны были добавлены
Каталог официальных cloud-образов Ubuntu/Debian (x86_64 и aarch64) со скачиванием в фоне, страница /cloud-images, создание VM из cloud-образа с настройкой пользователя, пароля и SSH-ключа через cloud-init seed, клонирование VM через virt-clone и флаг шаблона с блокировкой запуска. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PKUoZwHMCHnytfswFVRUHw
Этот коммит содержится в:
@@ -6,11 +6,13 @@
|
||||
</div>
|
||||
<nav class="v-nav">
|
||||
<a data-icon="⌂" class="{{ 'active' if path == '/' else '' }}" href="/">Обзор</a>
|
||||
<a data-icon="+" class="{{ 'active' if path.startswith('/vm/create') else '' }}" href="/vm/create">Создать VM</a>
|
||||
<div class="v-nav-group {{ 'open' if path.startswith('/iso') or path.startswith('/disk-images') else '' }}">
|
||||
<a data-icon="+" class="{{ 'active' if path == '/vm/create' else '' }}" href="/vm/create">Создать VM</a>
|
||||
<a data-icon="☁" class="{{ 'active' if path == '/vm/create-cloud' else '' }}" href="/vm/create-cloud">VM из облака</a>
|
||||
<div class="v-nav-group {{ 'open' if path.startswith('/iso') or path.startswith('/disk-images') or path.startswith('/cloud-images') else '' }}">
|
||||
<button class="v-nav-group-title" type="button">Образы</button>
|
||||
<a data-icon="◎" class="{{ 'active' if path.startswith('/iso') else '' }}" href="/iso">ISO</a>
|
||||
<a data-icon="▣" class="{{ 'active' if path.startswith('/disk-images') else '' }}" href="/disk-images">Диски</a>
|
||||
<a data-icon="☁" class="{{ 'active' if path.startswith('/cloud-images') else '' }}" href="/cloud-images">Cloud</a>
|
||||
</div>
|
||||
<a data-icon="⇄" class="{{ 'active' if path.startswith('/network') else '' }}" href="/network">Сеть</a>
|
||||
<a data-icon="⚙" class="{{ 'active' if path.startswith('/operations') else '' }}" href="/operations">Операции</a>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Cloud-образы — {{ 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">Cloud-образы</div>
|
||||
<div class="v-subtitle">Официальные cloud-образы Ubuntu и Debian для быстрого создания VM без установки с ISO</div>
|
||||
</div>
|
||||
<div class="top-meta">
|
||||
<span>user: {{ user }}</span>
|
||||
<a class="link-pill" href="/">← Дашборд</a>
|
||||
<form method="post" action="/logout" class="logout-form"><button class="ghost">Выйти</button></form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="grid two">
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Каталог образов</h2>
|
||||
<span class="pill">официальные зеркала</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Система</th><th>Архитектура</th><th>Статус</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in catalog %}
|
||||
<tr>
|
||||
<td class="strong">{{ entry.label }}</td>
|
||||
<td><span class="status {{ 'ok' if entry.native else 'warn' }}">{{ entry.arch }}{% if entry.native %} · родная{% endif %}</span></td>
|
||||
<td>{% if entry.downloaded %}<span class="status ok">скачан</span>{% else %}<span class="muted">не скачан</span>{% endif %}</td>
|
||||
<td class="actions">
|
||||
<form method="post" action="/cloud-images/download">
|
||||
<input type="hidden" name="key" value="{{ entry.key }}">
|
||||
<button class="{{ 'ghost' if entry.downloaded else 'primary' }}">{{ 'Скачать заново' if entry.downloaded else 'Скачать' }}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="muted small-note">Образы качаются с cloud-images.ubuntu.com и cloud.debian.org в фоне — прогресс виден в разделе «Операции». Для Raspberry / Orange Pi выбирай aarch64.</p>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Зачем это нужно</h2>
|
||||
<span class="pill">VM за минуту</span>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<div class="big-action static">
|
||||
<strong>Быстрее, чем ISO</strong>
|
||||
<span>Cloud-образ — уже установленная система. VM создаётся копированием диска и стартует сразу, без установщика.</span>
|
||||
</div>
|
||||
<div class="big-action static">
|
||||
<strong>Логин и SSH-ключ сразу</strong>
|
||||
<span>При создании VM задаёшь пользователя, пароль или SSH-ключ — cloud-init настроит их при первом старте.</span>
|
||||
</div>
|
||||
<div class="big-action static">
|
||||
<strong>ARM64 без боли</strong>
|
||||
<span>Для Raspberry Pi и Orange Pi это основной способ создавать гостевые VM.</span>
|
||||
</div>
|
||||
</div>
|
||||
<a class="link-pill" href="/vm/create-cloud">→ Создать VM из cloud-образа</a>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h2>Скачанные образы</h2>
|
||||
<span class="pill">{{ images|length }} файлов</span>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Файл</th><th>Система</th><th>Архитектура</th><th>Размер</th><th>Обновлён</th><th>Действия</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for image in images %}
|
||||
<tr>
|
||||
<td class="strong">{{ image.name }}</td>
|
||||
<td>{{ image.label }}</td>
|
||||
<td><span class="status ok">{{ image.arch }}</span></td>
|
||||
<td>{{ image.size }}</td>
|
||||
<td>{{ image.updated }}</td>
|
||||
<td class="actions">
|
||||
<a class="link-pill" href="/vm/create-cloud">Создать VM</a>
|
||||
<form method="post" action="/cloud-images/{{ image.name }}/delete" onsubmit="return confirm('Удалить образ {{ image.name }}?');">
|
||||
<button class="danger">Удалить</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="6" class="muted">Образов пока нет. Скачай Ubuntu или Debian из каталога выше.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/static/panel.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>VM из cloud-образа — {{ 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">VM из cloud-образа</div>
|
||||
<div class="v-subtitle">Готовая система с пользователем и SSH за минуту — без установки с ISO</div>
|
||||
</div>
|
||||
<div class="top-meta">
|
||||
<span>user: {{ user }}</span>
|
||||
<a class="link-pill" href="/">← Дашборд</a>
|
||||
<form method="post" action="/logout" class="logout-form"><button class="ghost">Выйти</button></form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert danger">{{ error }}</div>
|
||||
{% endif %}
|
||||
|
||||
<section class="grid two">
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Параметры</h2>
|
||||
<span class="pill">cloud-init</span>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/vm/create-cloud" class="form-grid">
|
||||
<label>
|
||||
<span>Имя VM</span>
|
||||
<input type="text" name="name" value="{{ form.name or '' }}" placeholder="ubuntu-web" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Cloud-образ</span>
|
||||
<select name="image_name" {% if images %}required{% endif %}>
|
||||
{% for image in images %}
|
||||
<option value="{{ image.name }}" {% if form.image_name == image.name %}selected{% endif %}>{{ image.label }} — {{ image.arch }} — {{ image.size }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="form-row">
|
||||
<label>
|
||||
<span>RAM, MB</span>
|
||||
<input type="number" name="memory" min="512" max="262144" step="512" value="{{ form.memory }}" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>CPU</span>
|
||||
<input type="number" name="vcpus" min="1" max="128" value="{{ form.vcpus }}" required>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span>Диск, GB</span>
|
||||
<input type="number" name="disk_size" min="4" max="4096" value="{{ form.disk_size }}" required>
|
||||
</label>
|
||||
|
||||
<div class="form-row">
|
||||
<label>
|
||||
<span>Пользователь в VM</span>
|
||||
<input type="text" name="username" value="{{ form.username or 'admin' }}" placeholder="admin" required>
|
||||
</label>
|
||||
<label>
|
||||
<span>Пароль (необязательно, если есть SSH-ключ)</span>
|
||||
<input type="password" name="password" value="" placeholder="пароль для входа">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label>
|
||||
<span>Публичный SSH-ключ (необязательно)</span>
|
||||
<input type="text" name="ssh_key" value="{{ form.ssh_key or '' }}" placeholder="ssh-ed25519 AAAA…">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Режим сети</span>
|
||||
<select name="network_mode" required>
|
||||
<option value="nat" {% if form.network_mode != 'bridge' %}selected{% endif %}>VPS NAT Router — virtuality-nat</option>
|
||||
<option value="bridge" {% if form.network_mode == 'bridge' %}selected{% endif %}>Bridge — br0 / локальная сеть</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Bridge для режима Bridge</span>
|
||||
<input type="text" name="bridge" value="{{ form.bridge }}" placeholder="br0">
|
||||
</label>
|
||||
|
||||
<div class="notice">
|
||||
Пользователь и пароль/ключ будут настроены cloud-init при первом старте VM. Вход: <b>ssh пользователь@IP</b> или через консоль на странице VM.
|
||||
</div>
|
||||
|
||||
<button type="submit" class="primary wide" {% if not images %}disabled{% endif %}>Создать VM из cloud-образа</button>
|
||||
{% if not images %}
|
||||
<div class="alert danger">Сначала скачай cloud-образ на странице <a href="/cloud-images">Cloud-образы</a>.</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Как это работает</h2>
|
||||
<a class="link-pill" href="/cloud-images">Открыть cloud-образы</a>
|
||||
</div>
|
||||
<div class="quick-actions">
|
||||
<div class="big-action static">
|
||||
<strong>1. Копия образа</strong>
|
||||
<span>Скачанный cloud-образ копируется в новый qcow2-диск VM и расширяется до выбранного размера.</span>
|
||||
</div>
|
||||
<div class="big-action static">
|
||||
<strong>2. Seed-диск cloud-init</strong>
|
||||
<span>Из имени, пользователя и пароля/ключа собирается маленький seed-ISO, который VM читает при первом старте.</span>
|
||||
</div>
|
||||
<div class="big-action static">
|
||||
<strong>3. Импорт и запуск</strong>
|
||||
<span>virt-install импортирует диск и запускает VM. Прогресс — в разделе «Операции».</span>
|
||||
</div>
|
||||
</div>
|
||||
<pre>qemu-img convert -O qcow2 cloud.qcow2 vm.qcow2
|
||||
qemu-img resize vm.qcow2 20G
|
||||
cloud-localds seed.iso user-data meta-data
|
||||
virt-install --import --disk vm.qcow2 …</pre>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<script src="/static/panel.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -75,6 +75,9 @@
|
||||
{% if iso_error %}<div class="alert danger">{{ iso_error }}</div>{% endif %}
|
||||
{% if boot_message %}<div class="alert success">{{ boot_message }}</div>{% endif %}
|
||||
{% if boot_error %}<div class="alert danger">{{ boot_error }}</div>{% endif %}
|
||||
{% if clone_message %}<div class="alert success">{{ clone_message }}</div>{% endif %}
|
||||
{% if clone_error %}<div class="alert danger">{{ clone_error }}</div>{% endif %}
|
||||
{% if is_template %}<div class="alert warn">Эта VM помечена как шаблон: запуск заблокирован. Клонируй её для создания новых машин.</div>{% endif %}
|
||||
|
||||
<section class="grid vm-four-grid">
|
||||
<article class="card">
|
||||
@@ -244,6 +247,39 @@
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid two">
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Клонирование</h2>
|
||||
<span class="pill">virt-clone</span>
|
||||
</div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/clone" class="form-grid">
|
||||
<label>
|
||||
<span>Имя новой VM</span>
|
||||
<input type="text" name="new_name" placeholder="{{ vm.name }}-copy" required>
|
||||
</label>
|
||||
<button class="primary wide" {% if is_running %}disabled{% endif %}>Клонировать VM</button>
|
||||
{% if is_running %}
|
||||
<p class="muted small-note">Клонирование доступно только для выключенной VM — сначала выключи её.</p>
|
||||
{% else %}
|
||||
<p class="muted small-note">Диски копируются целиком, MAC-адрес нового клона генерируется автоматически. Прогресс — в «Операциях».</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<div class="card-head">
|
||||
<h2>Шаблон</h2>
|
||||
<span class="pill">{{ 'template' if is_template else 'обычная VM' }}</span>
|
||||
</div>
|
||||
<form method="post" action="/vm/{{ vm.name }}/template" class="form-grid">
|
||||
<input type="hidden" name="enabled" value="{{ '0' if is_template else '1' }}">
|
||||
<button class="{{ 'ghost' if is_template else 'primary' }} wide">{{ 'Снять флаг шаблона' if is_template else 'Пометить как шаблон' }}</button>
|
||||
</form>
|
||||
<p class="muted small-note">Шаблон — эталонная VM для клонирования: запуск заблокирован, чтобы случайно не изменить эталонный диск. Подготовь VM, выключи её и пометь как шаблон.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-head">
|
||||
<h2>Консоль</h2>
|
||||
|
||||
Ссылка в новой задаче
Block a user