Имя входит в ключи бэкапов в S3 (backups/<имя>/…), поэтому после создания не меняется (docs/changes/015): - API: PATCH с другим именем → 400 «Имя устройства нельзя изменить», то же имя допустимо; - UI: в окне изменения поле имени только для чтения, форма изменения присланное имя игнорирует; - вёрстка: минимальная ширина таблиц устройств и файлов — в узком окне колонка с именем не схлопывается, включается горизонтальная прокрутка. Тесты: 12 из 12. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ros_control
Централизованное управление парком MikroTik RouterOS: WEB UI и JSON API.
Дизайн и требования — ros_control.svg; макет интерфейса — утверждён и перенесён в приложение (см. docs/changes/006-ui-redesign).
Admin Dashboard ⇄ Control Server ⇄ RouterOS REST (на каждом устройстве)
⇅ ⇅
Metadata DB S3 (Yandex Object Storage)
Возможности
Устройства
- Список устройств: добавление, изменение, удаление; пароли хранятся зашифрованно (Fernet). Имя устройства в таблице кликабельно — открывает окно изменения; само имя задаётся только при создании и не меняется (оно входит в ключи бэкапов).
- К устройству можно добавить примечание (до 500 символов): видно подсказкой при наведении на имя.
- Статус: online/offline, модель, канал обновлений, версии ROS и FW, uptime, время запроса бэкапа. Колонки Upgrade ROS и Upgrade FW показывают версию, до которой можно обновиться (устройство проверяет обновления на серверах MikroTik; без интернета на устройстве — «—»).
- Мониторинг доступности: сервер опрашивает устройства каждые 30 с (
POLL_INTERVAL), недоступность определяется за ~4 с на устройство (ROS_CONNECT_TIMEOUT); страница обновляет статусы сама, без перезагрузки.
Группы и фильтры
- Устройство относится к одной группе: вкладки групп со счётчиками, страница «Группы» (создание, переименование, удаление), перенос отмеченных устройств в группу. При добавлении устройства группу можно создать «на месте» (пункт «+ Новая группа…»).
- Фильтры устройств: поиск (имя, адрес, модель), статус, наличие обновлений, канал. Фильтры бэкапов: группа, устройство, тип файла, период, имя файла. Фильтры попадают в URL.
Операции
- Действия по устройству — в меню «⋯» строки; действия над выбранными устройствами (бэкап, статус, обновление ROS/FW, канал, перенос в группу) появляются в полосе инструментов при выборе строк; через API их можно запускать и над всей группой.
- Бэкап:
.backup(без шифрования) и.rsc(сshow-sensitive, т.е. с паролями и ключами — из него можно восстановить все сущности, использующие пароль). Сервер создаёт файлы через REST, скачивает во временную папку, загружает в S3 и удаляет файлы с устройства. - Список бэкапов в бакете, скачивание (временная ссылка), удаление одного файла или группы выбранных (чекбоксы, кнопка «Удалить» с подтверждением).
- Канал обновлений; обновление ROS (устройство перезагружается после скачивания); обновление FW (перезагрузка сразу после появления в журнале устройства записи «Firmware upgraded successfully…»).
- Долгие и групповые операции выполняются задачами (карточка «Задачи», состояние — в UI и через API).
Интерфейс
Экран строится сверху вниз: приложение (навигация) → страница (заголовок, счётчики, главное действие) → вкладки групп → полоса инструментов таблицы → данные; «Задачи» — отдельная карточка. Пока ничего не выбрано, полоса показывает фильтры; при выборе строк — действия над выбранными.
Добавление и изменение устройств, создание и переименование групп — в окнах поверх страницы (запасные страницы /devices/new, /devices/{id}/edit работают без JavaScript). Выпадающие меню не обрезаются таблицей и раскрываются вверх, если снизу нет места. Светлая и тёмная темы переключаются по настройке системы. Шрифты IBM Plex лежат в app/ui/static/fonts (лицензия OFL), внешние ресурсы не загружаются.
Требования к устройствам
- Поддерживаются и RouterBOARD, и виртуальные CHR (у них нет
/system/routerboard: версия FW не показывается, обновление FW пропускается). - RouterOS 7.1+, включённый сервис
www-ssl(REST API, порт 443; неapi-ssl8729 и неapi8728). Еслиwww-sslнедоступен, для доверенной сети можно указать сервисwww(порт 80) и снять флаг «HTTPS» у устройства — логин и пароль пойдут открытым текстом. - Пользователь с политиками
read,write,policy,test,ftp,reboot,sensitive(проще всего — группаfull). Встроенная группаwriteс запретомftp/policyне подходит: бэкап падает сnot enough permissions. После смены прав пересоздайте подключение к устройству. - Доступ устройств к S3 не нужен: файлы забирает и загружает Control Server (ему нужен доступ к
storage.yandexcloud.net:443).
Безопасность
- Замените значения по умолчанию
API_TOKENиSESSION_SECRETна случайные (openssl rand -hex 24); порт 8000 не стоит открывать в недоверенную сеть. - Пароли устройств шифруются ключом
SECRET_KEY(Fernet); потеря ключа = потеря доступа к сохранённым паролям. - Секреты в бэкапах:
.rscсоздаётся сshow-sensitive, а.backup— без шифрования, поэтому файлы содержат пароли и ключи открытым текстом. Ограничьте доступ к бакету и ссылкам скачивания.
Запуск
cp .env.example .env # заполнить SECRET_KEY, ADMIN_PASSWORD, API_TOKEN, S3_*
docker compose up -d --build # UI: http://localhost:8000, OpenAPI: /docs
БД (SQLite) хранится в Docker-томе ros_data (docker volume inspect ros_control_ros_data), переживает пересборку контейнера; схема обновляется автоматически при старте. Остановка: docker compose down (том сохраняется; down -v удалит БД).
Ключ Fernet: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())".
Настройки (.env)
| Переменная | По умолчанию | Назначение |
|---|---|---|
SECRET_KEY |
— | ключ Fernet для паролей устройств (обязателен) |
SESSION_SECRET |
change-me |
подпись cookie-сессии UI |
ADMIN_USER / ADMIN_PASSWORD |
admin / change-me |
вход в UI |
API_TOKEN |
change-me |
Bearer-токен API |
DATABASE_URL |
sqlite:///./data/ros_control.db |
база метаданных (в compose — том) |
S3_ENDPOINT, S3_REGION, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY, S3_PREFIX |
Yandex Object Storage, backups |
бакет для резервных копий |
MAX_CONCURRENCY |
10 |
одновременные обращения к устройствам |
ROS_CONNECT_TIMEOUT |
4 |
секунд на установление соединения (скорость обнаружения недоступности) |
ROS_TIMEOUT |
30 |
секунд на ответ устройства (долгие операции) |
POLL_INTERVAL |
30 |
период фонового опроса, с; 0 — выключить |
UPDATE_CHECK_INTERVAL |
1800 |
как часто опрос проверяет обновления ROS, с |
Разработка
python3 -m venv venv && ./venv/bin/pip install -r requirements.txt
set -a; . ./.env; set +a
./venv/bin/uvicorn app.main:app --reload
./venv/bin/python -m pytest # 12 тестов, фоновый опрос в тестах выключен
API v1
Заголовок Authorization: Bearer <API_TOKEN>. Интерактивная документация — /docs. Пример:
curl -s -H "Authorization: Bearer $API_TOKEN" http://localhost:8000/api/v1/devices | python3 -m json.tool
Устройство: name, host, port, username, password (только при записи), verify_tls, use_tls (false — HTTP), group_id (null — без группы), note.
| Метод | Путь | Назначение |
|---|---|---|
| GET/POST | /api/v1/devices |
список (фильтры group, q, status, updates, channel) / добавить |
| GET/PATCH/DELETE | /api/v1/devices/{id} |
получить / изменить (имя менять нельзя — 400) / удалить |
| POST | /api/v1/devices/refresh, /devices/{id}/refresh |
обновить статус |
| POST | /api/v1/devices/{id}/backups |
бэкап (задача) |
| PUT | /api/v1/devices/{id}/update/channel |
{"channel": "stable|long-term|testing|development"} |
| POST | /api/v1/devices/{id}/update/install |
обновление ROS (задача) |
| POST | /api/v1/devices/{id}/firmware/upgrade |
обновление FW (задача) |
| GET/POST | /api/v1/groups |
группы (с числом устройств) / создать |
| PATCH/DELETE | /api/v1/groups/{id} |
переименовать / удалить (устройства остаются без группы) |
| POST | /api/v1/batch/{backup|ros_update|fw_update} |
{"device_ids": [...]} и/или {"group_id": N} — групповая операция |
| PUT | /api/v1/batch/channel |
{"device_ids": [...] или "group_id": N, "channel": "..."} |
| GET/DELETE | /api/v1/backups, /backups/download?key= |
бэкапы в бакете (фильтры device_id, group, kind, date_from, date_to, q) |
| POST | /api/v1/backups/delete |
групповое удаление файлов: {"keys": [...]} → {"deleted": N, "failed": M} |
| GET | /api/v1/jobs, /jobs/{id} |
состояние задач |
Внешние справочники: RouterOS REST API, Yandex Object Storage S3 API.
Структура
app/ros— клиент RouterOS REST и операции (статус, бэкап, скачивание файлов, обновления)app/s3.py— бакет;app/security.py— шифрование и авторизация;app/models.py,app/db.py— БД и миграцииapp/services— устройства и фильтры, группы, бэкапы, операции, задачи, фоновый опрос (poller.py)app/api— JSON API;app/ui— WEB UI (Jinja2 + HTMX,static/со стилями, скриптом и шрифтами)tests— минимальный набор;docs/changes— планы и итоги каждого изменения
История изменений
Планы и итоги — в docs/changes/:
001-initial-implementation— первая версия.002-http-support-and-fixes— HTTP-подключение, исправления по итогам теста на реальном устройстве.003-download-via-rest-upload-from-server— бэкап: скачивание файлов через REST, загрузка в S3 сервером.004-upgrade-columns-and-actions-menu— колонки Upgrade ROS/FW, меню действий «⋯».005-groups-and-filters— группы устройств, фильтры устройств и бэкапов.006-ui-redesign— новый интерфейс по утверждённому макету; исправлены перезагрузка после обновления FW и привязка группы при добавлении устройства.007-fast-offline-detection— фоновый опрос устройств (30 с), быстрый таймаут соединения, автообновление страницы.008-device-note— примечание к устройству.009-dialog-label-alignment— выравнивание полей в окне устройства.010-export-show-sensitive—.rscсоздаётся сshow-sensitive(секреты в файле).011-dropdown-clipping— выпадающие меню не обрезаются в узком окне.012-clickable-device-name— клик по имени устройства открывает окно изменения.013-bulk-delete-backups— выбор файлов чекбоксами и групповое удаление бэкапов.014-chr-support— поддержка CHR (нет/system/routerboard), строгое сравнение версий ROS, причина недоступности в таблице.015-immutable-device-name— имя устройства задаётся только при создании.