# План: 004 — колонки Upgrade ROS / Upgrade FW и меню действий «⋯» в UI ## Context Дашборд показывает только текущие версии; доступные для обновления версии видны лишь мелким текстом под ROS/FW и для ROS почти никогда не заполнены. Причины: - `latest-version` в `/rest/system/package/update` появляется только после `check-for-updates` — приложение его при опросе статуса не вызывает (`app/ros/operations.py::get_status`). - `ros_version` из `system/resource` имеет вид `7.24.4 (stable)`, а `latest-version` — `7.24.4`; прямое сравнение в шаблоне всегда даёт «отличаются». - В строке устройства пять кнопок в ряд (Статус, Бэкап, ROS, FW, Изменить) — таблица перегружена. Цель: отдельные колонки **Upgrade ROS** и **Upgrade FW** с версией, до которой можно обновиться, и все действия строки — в выпадающем меню под иконкой «⋯». ## Изменения **1. Статус: проверка обновлений** — `app/ros/operations.py::get_status` - Перед чтением `system/package/update` выполнять `POST system/package/update/check-for-updates` (таймаут ~30 с). Ошибка проверки (нет интернета на устройстве, таймаут) не ломает опрос: остаётся то, что вернул `GET`, в статус пишется `ros_check_error`. - В статус добавить `ros_installed` (`installed-version`, без суффикса «(stable)») — именно с ним сравнивается `ros_latest`. - `get_status(c, check_updates=True)`: параметр нужен только чтобы тест мог отключить проверку. - `set_channel` уже вызывает `refresh_status`, значит после смены канала доступная версия пересчитается сама. Инструмент `install_ros_update` (`check-for-updates` + `install`) не меняется. **2. Шаблон `app/ui/templates/_devices.html`** - Колонки: `…, ROS, Upgrade ROS, FW, Upgrade FW, …`. Убрать мелкие подсказки «доступна X» из колонок ROS/FW. - Upgrade ROS: `ros_latest`, если он есть и `!= ros_installed` → выделенный бейдж с версией; равен → «✓ актуально»; нет данных → «—» (в `title` — `ros_check_error`). - Upgrade FW: `fw_upgrade`, если `!= fw_current` → бейдж; равен → «✓ актуально»; нет RouterBOARD (CHR) → «—». - Колонка «Действия» → компактная колонка с кнопкой `⋯`; меню на `` (без внешних библиотек). Пункты те же, что были кнопками: Обновить статус, Бэкап, Обновить ROS, Обновить FW, Изменить. Атрибуты `hx-post` / `hx-target` / `hx-confirm` переносятся как есть. - `colspan` пустой строки 9 → 10. **3. Стили и поведение** — `app/ui/static/style.css`, `app/ui/templates/base.html` - CSS: `.menu` (relative), `.menu-list` (absolute, справа, тень, границы под тёмную и светлую темы, `z-index`), пункты-кнопки на всю ширину, опасные (ROS/FW) — акцентным цветом; `.badge` для Upgrade-версий (цвет `--warn`), «актуально» — `--ok`. - ~6 строк JS в `base.html`: закрывать открытое меню по клику вне его, по `Esc` и после выбора пункта (нужно, потому что действия с `hx-target="#jobs"` таблицу не перерисовывают и меню иначе останется открытым). **4. Артефакты проекта (CLAUDE.md)** - `docs/changes/004-upgrade-columns-and-actions-menu/plan.md` (копия этого плана) и `summary.md` в конце. - `README.md`: описание колонок и меню, строка в «Историю изменений». - Тесты: без новых; в существующем `test_status_parsing` (`tests/test_app.py`) добавить обработку `POST …/check-for-updates` в моке и проверку `ros_installed`/`ros_latest`. ## Проверка - `pytest` (4 теста). - `docker compose up -d --build --force-recreate`, затем `POST /api/v1/devices/1/refresh`: в статусе появились `ros_installed`, `ros_latest`; время опроса с проверкой обновлений замерить — при заметном замедлении оставить `check_updates` только в кнопке «Обновить статус», а не при каждом опросе. - Визуально в браузере (`http://localhost:8000`): колонки Upgrade ROS/FW, меню «⋯» открывается/закрывается (клик вне, Esc, выбор пункта), пункты работают (Статус, Бэкап, ROS/FW — с подтверждением, Изменить), светлая и тёмная тема, узкое окно. - Обновления ROS/FW фактически **не запускаются** (перезагрузка устройства); проверяется только отображение и подтверждение. ## Риски - `check-for-updates` ходит с устройства на серверы MikroTik: без интернета вернётся ошибка/таймаут → «—» с подсказкой, статус остаётся `online`. - Проверка добавляет секунды к опросу каждого устройства; параллелизм ограничен `MAX_CONCURRENCY` (см. пункт проверки выше).