From 43dee9f79638ef8b02b37d24c552d0dbb81d78ba Mon Sep 17 00:00:00 2001 From: core Date: Sat, 30 May 2026 00:15:16 +0500 Subject: [PATCH] fix --- backend/Dockerfile | 6 +++++- backend/app/api/cameras.py | 15 +++++++++++++++ backend/entrypoint.sh | 16 ++++++++++++++++ docker-compose.yml | 2 +- frontend/static/app.js | 21 +++++++++++++++++++++ 5 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 backend/entrypoint.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index ae69205..3ba2cb0 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.12-slim # ffmpeg/ffprobe — движок записи и перекодирования RUN apt-get update \ - && apt-get install -y --no-install-recommends ffmpeg \ + && apt-get install -y --no-install-recommends ffmpeg openssl \ && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -12,6 +12,10 @@ RUN pip install --no-cache-dir -r requirements.txt COPY backend/app ./app COPY frontend ./frontend +COPY backend/entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh EXPOSE 8090 +# entrypoint генерирует self-signed TLS-сертификат при отсутствии, затем запускает CMD/команду +ENTRYPOINT ["/entrypoint.sh"] CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8090"] diff --git a/backend/app/api/cameras.py b/backend/app/api/cameras.py index 0c5c56e..3f960d8 100644 --- a/backend/app/api/cameras.py +++ b/backend/app/api/cameras.py @@ -280,3 +280,18 @@ async def delete_camera(cam_id: str, request: Request) -> dict: await runtime.supervisor.remove_camera(cam_id) await go2rtc.remove_camera_streams(runtime.config, cam_id) return {"ok": True} + + +@router.post("/clear") +async def clear_cameras(request: Request) -> dict: + """Удаляет ВСЕ камеры: чистит cameras.yaml, останавливает запись и потоки go2rtc. + + Архив (nvr.db + файлы .mp4) НЕ трогает — очищается только список камер.""" + require_role(request, "admin") + ids = [c.id for c in runtime.config.cameras] + runtime.config.cameras = [] + save_config(runtime.config) + for cam_id in ids: + await runtime.supervisor.remove_camera(cam_id) + await go2rtc.remove_camera_streams(runtime.config, cam_id) + return {"ok": True, "removed": len(ids)} diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh new file mode 100644 index 0000000..b352a5e --- /dev/null +++ b/backend/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Гарантирует наличие self-signed TLS-сертификата перед стартом uvicorn. +# Если cert.pem/key.pem отсутствуют (свежий деплой) — генерирует их; +# иначе использует существующие. Затем запускает переданную команду (CMD). +set -e + +CERT_DIR=/app/certs +if [ ! -f "$CERT_DIR/cert.pem" ] || [ ! -f "$CERT_DIR/key.pem" ]; then + echo "[entrypoint] TLS cert not found in $CERT_DIR — generating self-signed (CN=${NVR_TLS_CN:-localhost})" + mkdir -p "$CERT_DIR" + openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout "$CERT_DIR/key.pem" -out "$CERT_DIR/cert.pem" \ + -days 3650 -subj "/CN=${NVR_TLS_CN:-localhost}" +fi + +exec "$@" diff --git a/docker-compose.yml b/docker-compose.yml index a04b0b3..919a001 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: ports: - "443:8090" # дашборд на https://<сервер> volumes: - - ./certs:/app/certs:ro # self-signed TLS-сертификат + - ./certs:/app/certs # self-signed TLS-сертификат (rw: entrypoint генерирует при отсутствии) - ./VERSION:/app/VERSION:ro # номер версии (показывается в UI) - ./cameras.yaml:/app/cameras.yaml # rw: редактирование камер из UI - ./go2rtc.yaml:/app/go2rtc.yaml # rw: бэкенд регенерирует live-потоки diff --git a/frontend/static/app.js b/frontend/static/app.js index b77aa1c..36a5e30 100644 --- a/frontend/static/app.js +++ b/frontend/static/app.js @@ -1100,6 +1100,8 @@ const CamSettings = { if (be) be.onclick = () => this.bulk ? this.saveBulk() : this.enterBulk(); const bc = document.getElementById("bulk-cancel"); if (bc) bc.onclick = () => this.exitBulk(); + const cc = document.getElementById("clear-cams"); + if (cc) cc.onclick = () => this.clearAll(); await this.load(); connectStatusWS(() => { if (!this.editing) this.load(); }); }, @@ -1108,10 +1110,12 @@ const CamSettings = { const be = document.getElementById("bulk-edit"); const bc = document.getElementById("bulk-cancel"); const add = document.getElementById("add-cam"); + const cc = document.getElementById("clear-cams"); if (be) be.textContent = this.bulk ? "Сохранить" : "Изменить"; if (be) be.classList.toggle("primary", this.bulk); if (bc) bc.hidden = !this.bulk; if (add) add.hidden = this.bulk; // во время массового редактирования не добавляем + if (cc) cc.hidden = this.bulk; }, enterBulk() { @@ -1127,6 +1131,23 @@ const CamSettings = { await this.load(); }, + // удалить ВСЕ камеры (архив не трогается) — POST /api/cameras/clear + async clearAll() { + if (this.bulk) return; + const n = this.cams.length; + if (!n) { alert("Камер нет"); return; } + if (!confirm(`Удалить ВСЕ камеры (${n})?\nЗаписи архива НЕ затрагиваются. Действие необратимо.`)) return; + const r = await fetch("/api/cameras/clear", { method: "POST" }); + if (!r.ok) { + const d = await r.json().catch(() => ({})); + alert("Не удалось очистить: " + (d.detail || r.status)); + return; + } + const d = await r.json().catch(() => ({})); + await this.load(); + alert(`Удалено камер: ${d.removed != null ? d.removed : n}`); + }, + // ── строка камеры в режиме массового редактирования ── bulkRowHtml(slotNo, cam) { const v = (x) => this.esc(x);