Files
ros_control/app/services/backups.py
T
ayurishchevandClaude Sonnet 5 4c1841b61b ros_control: централизованное управление парком MikroTik RouterOS
Control Server (FastAPI) с WEB UI (Jinja2 + HTMX) и JSON API для группового
администрирования устройств RouterOS 7 через REST.

- Устройства: список, статус (модель, канал, версии ROS/FW, uptime, доступные
  обновления), примечания, пароли шифруются (Fernet); фоновый опрос каждые 30 с
  и быстрое обнаружение недоступности (таймаут соединения 4 с).
- Группы устройств и фильтры (устройства, резервные копии).
- Резервные копии: .backup и .rsc (show-sensitive) создаются через REST,
  скачиваются сервером и загружаются в S3 (Yandex Object Storage).
- Обновление ROS/FW и выбор канала, групповые операции задачами.
- Интерфейс по утверждённому макету: светлая/тёмная темы, окна, шрифты IBM Plex
  локально. Docker Compose, SQLite в томе, минимальный набор тестов.
- Планы и итоги каждого изменения — в docs/changes/.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 13:13:29 +03:00

50 lines
2.5 KiB
Python

"""Список бэкапов из бакета с фильтрами (устройство, группа, тип, даты, текст)."""
from datetime import date
from app import s3
from app.services import devices, groups
def _kind(key: str) -> str:
return "backup" if key.endswith(".backup") else "rsc" if key.endswith(".rsc") else "other"
async def search(device: str = "", group: str = "", kind: str = "", date_from: date | None = None,
date_to: date | None = None, q: str = "") -> tuple[list[dict], dict]:
"""Отфильтрованные бэкапы и итоги по всему бакету ({"total": N, "size": байты}).
Ключи вида backups/<устройство>/<файл>: устройство берётся из ключа, группа — по имени устройства.
group: "" — все, "none" — без группы (в т.ч. бэкапы удалённых устройств), иначе id группы.
Даты — по времени изменения объекта (UTC), включительно."""
group_names = {g["id"]: g["name"] for g in groups.list_groups()}
device_group = {d.name: group_names.get(d.group_id) for d in devices.list_devices()}
want_group = group_names.get(int(group)) if group.isdigit() else None
everything = await s3.list_backups(None)
stats = {"total": len(everything), "size": sum(i["size"] for i in everything)}
q = q.strip().lower()
out = []
for item in everything:
parts = item["key"].split("/")
name = parts[1] if len(parts) >= 3 else ""
item = {**item, "device": name, "group": device_group.get(name), "kind": _kind(item["key"])}
if device and name != device:
continue
if group == "none" and item["group"] is not None:
continue
if group.isdigit() and (want_group is None or item["group"] != want_group):
continue
if kind and item["kind"] != kind:
continue
day = item["last_modified"].date()
if (date_from and day < date_from) or (date_to and day > date_to):
continue
if q and q not in item["key"].lower():
continue
out.append(item)
return out, stats
async def list_backups(device: str = "", group: str = "", kind: str = "", date_from: date | None = None,
date_to: date | None = None, q: str = "") -> list[dict]:
return (await search(device, group, kind, date_from, date_to, q))[0]