Files
virtuality/web/templates/_downloads_catalog.html
T
Claude 4fd04be588 Скачивание образов по ссылке и из каталога готовых систем было добавлено
- web/features/downloads.py: фоновая операция типа «download» — urllib по 1 МиБ,
  таймаут 30 с, User-Agent Virtuality/<версия>, запись в .<имя>.part и переименование
  в конце; в сообщении скорость и остаток («245 МБ из 3,1 ГБ · 12 МБ/с · осталось 4 мин»);
  отмена между кусками (POST /api/operations/{id}/cancel и /downloads/{id}/cancel);
  проверка схемы, имени файла, занятости имени и свободного места (Content-Length + 512 МиБ);
  для каталога — проверка контрольной суммы (SHA256SUMS/SHA512SUMS/одиночный хеш),
  при несовпадении файл удаляется; после ISO — virsh pool-refresh virtuality-iso,
  после .img.xz — распаковка и запуск конвертации в qcow2 как при загрузке с компьютера.
- /iso и /disk-images: карточка «Скачать по ссылке», на /disk-images — «Каталог готовых
  систем» для процессора сервера с пометками «Рекомендуем», «Загружен», «Скачивается»;
  идущие скачивания вверху страниц с живым прогрессом (опрос раз в 2 с) и «Отменить».
- Тесты: парсер контрольных сумм, проверка ссылок и имён, загрузка с локального
  http-сервера, редирект, отмена, несовпадение суммы, нехватка места, .img.xz, маршруты.
- В ветку скопированы web/core.py, web/catalog.py, presenters.format_bytes и tests/fixtures
  из общей рабочей копии (без изменений), conftest подменяет run_cmd и каталоги и в core.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
2026-09-25 14:49:43 +00:00

37 строки
2.5 KiB
HTML

{#- Карточка «Каталог готовых систем» для страницы /disk-images. -#}
{% from "_ui.html" import icon, badge %}
{% set dl = downloads_panel('disk') %}
<article class="card">
<div class="card-header">
<div><h2>Каталог готовых систем</h2><p>Официальные образы для вашего процессора. Скачаем на сервер и проверим контрольную сумму — останется только создать машину.</p></div>
</div>
{% if dl.catalog %}
<div class="list">
{% for item in dl.catalog %}
<div class="list-item catalog-item">
<span class="list-icon {{ 'success' if item.status == 'ready' else 'info' if item.status == 'downloading' else 'brand' }}">{{ icon('circle-check' if item.status == 'ready' else 'download' if item.status == 'downloading' else 'box') }}</span>
<div class="grow">
<div class="row" style="gap:8px">
<span class="title">{{ item.title }}</span>
{% if item.recommended %}{{ badge('Рекомендуем', 'brand', dot=False) }}{% endif %}
{% if item.status == 'ready' %}{{ badge('Загружен', 'success') }}{% elif item.status == 'downloading' %}{{ badge('Скачивается', 'info') }}{% endif %}
</div>
<div class="meta">{{ item.hint }}{% if item.login %} · вход: <code>{{ item.login }}</code>{% endif %}</div>
</div>
{% if item.status == 'ready' %}
<a class="btn btn-sm btn-primary" href="/vm/create?image={{ item.ready_file|urlencode }}">{{ icon('plus', 'icon-sm') }}Создать машину</a>
{% elif item.status == 'downloading' %}
<a class="btn btn-sm btn-ghost" href="/operations/{{ item.operation_id }}">Прогресс{{ icon('chevron-right', 'icon-sm') }}</a>
{% else %}
<form method="post" action="/downloads/catalog/{{ item.id }}" class="inline-form" data-busy="Запускаем…">
<button type="submit" class="btn btn-sm">{{ icon('download', 'icon-sm') }}Скачать</button>
</form>
{% endif %}
</div>
{% endfor %}
</div>
{% else %}
<div class="card-body"><p class="muted">Для процессора этого сервера готовых образов в каталоге нет. Можно скачать образ по ссылке или загрузить с компьютера.</p></div>
{% endif %}
</article>