Пункт 18 ревью 2026-09-28 21:32 (docs/changes/027): после отката ROS
плата остаётся на более новой прошивке, а приложение сравнивало версии
на неравенство и показывало откат как обновление.
- devices.fw_state: unknown/update/downgrade/current; has_fw_update и
фильтры не считают откат обновлением; метка «в ROS: X».
- upgrade_firmware не понижает прошивку; общая запись — _flash_firmware.
- Задача fw_downgrade: проверка → обязательный бэкап → запись →
перезагрузка; API POST /devices/{id}/firmware/downgrade и
/batch/fw_downgrade (target_version обязателен).
- UI: «Откатить прошивку…» (строка и группа), общее окно отката с kind.
Тесты: 39 из 39. Стенд: временное недоступное устройство — 422/202,
задачи failed на проверке, бэкапов нет. Реальная запись прошивки при
откате и ручная проверка UI не выполнялись.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 lines
2.3 KiB
HTML
38 lines
2.3 KiB
HTML
{# Окно осознанного отката ROS/FW: одно устройство или группа (devs — исходный выбор), параметризовано видом
|
|
отката kind (ros|fw, изменение 027; по умолчанию ros — прежнее поведение и адреса без изменений). #}
|
|
{% import "_ui.html" as ui %}
|
|
<form method="post" action="/ui/downgrade" hx-post="/ui/downgrade" hx-target="#modal-body">
|
|
<header class="modal-head">
|
|
<h2 id="modal-title">{{ k.title }}</h2>
|
|
<button type="button" class="btn ghost-n icon" data-close aria-label="Закрыть">{{ ui.icon("x") }}</button>
|
|
</header>
|
|
<div class="modal-body">
|
|
{% if error %}<div class="form-err" role="alert">{{ error }}</div>{% endif %}
|
|
{% for d in devs %}<input type="hidden" name="device_ids" value="{{ d.id }}">{% endfor %}
|
|
<input type="hidden" name="kind" value="{{ kind }}">
|
|
{% if affected %}
|
|
<table class="grid" style="width:100%">
|
|
<thead><tr><th>Устройство</th><th>{{ k.col_label }}</th>{% if k.extra_label %}<th>{{ k.extra_label }}</th>{% endif %}</tr></thead>
|
|
<tbody>
|
|
{% for d in affected %}
|
|
<tr><td>{{ d.name }}</td><td class="c-mono">{{ d.status[k.installed_key] }} → {{ d.status[k.target_key] }}</td>{% if k.extra_label %}<td>{{ d.status[k.extra_key] }}</td>{% endif %}</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}<div class="form-err" role="alert">Среди выбранных устройств нет ни одного в состоянии отката.</div>{% endif %}
|
|
{% if skipped %}
|
|
<div class="note">Не будут затронуты:
|
|
<ul style="margin:4px 0 0 18px">{% for d, reason in skipped %}<li>{{ d.name }} — {{ reason }}</li>{% endfor %}</ul>
|
|
</div>
|
|
{% endif %}
|
|
<div class="note">{{ k.warn }}</div>
|
|
<label class="lbl"><span>Введите целевую версию <small>{{ k.hint }}</small></span>
|
|
<input class="input" name="target_version" placeholder="например, 7.23.7" data-enables="#downgrade-confirm" autofocus required></label>
|
|
</div>
|
|
<footer class="modal-foot">
|
|
<span class="grow"></span>
|
|
<button type="button" class="btn ghost-n" data-close>Отмена</button>
|
|
<button class="btn danger" id="downgrade-confirm" disabled>Откатить</button>
|
|
</footer>
|
|
</form>
|