fix
This commit is contained in:
+5
-1
@@ -2,7 +2,7 @@ FROM python:3.12-slim
|
|||||||
|
|
||||||
# ffmpeg/ffprobe — движок записи и перекодирования
|
# ffmpeg/ffprobe — движок записи и перекодирования
|
||||||
RUN apt-get update \
|
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/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -12,6 +12,10 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
COPY backend/app ./app
|
COPY backend/app ./app
|
||||||
COPY frontend ./frontend
|
COPY frontend ./frontend
|
||||||
|
COPY backend/entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
EXPOSE 8090
|
EXPOSE 8090
|
||||||
|
# entrypoint генерирует self-signed TLS-сертификат при отсутствии, затем запускает CMD/команду
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8090"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8090"]
|
||||||
|
|||||||
@@ -280,3 +280,18 @@ async def delete_camera(cam_id: str, request: Request) -> dict:
|
|||||||
await runtime.supervisor.remove_camera(cam_id)
|
await runtime.supervisor.remove_camera(cam_id)
|
||||||
await go2rtc.remove_camera_streams(runtime.config, cam_id)
|
await go2rtc.remove_camera_streams(runtime.config, cam_id)
|
||||||
return {"ok": True}
|
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)}
|
||||||
|
|||||||
@@ -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 "$@"
|
||||||
+1
-1
@@ -12,7 +12,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "443:8090" # дашборд на https://<сервер>
|
- "443:8090" # дашборд на https://<сервер>
|
||||||
volumes:
|
volumes:
|
||||||
- ./certs:/app/certs:ro # self-signed TLS-сертификат
|
- ./certs:/app/certs # self-signed TLS-сертификат (rw: entrypoint генерирует при отсутствии)
|
||||||
- ./VERSION:/app/VERSION:ro # номер версии (показывается в UI)
|
- ./VERSION:/app/VERSION:ro # номер версии (показывается в UI)
|
||||||
- ./cameras.yaml:/app/cameras.yaml # rw: редактирование камер из UI
|
- ./cameras.yaml:/app/cameras.yaml # rw: редактирование камер из UI
|
||||||
- ./go2rtc.yaml:/app/go2rtc.yaml # rw: бэкенд регенерирует live-потоки
|
- ./go2rtc.yaml:/app/go2rtc.yaml # rw: бэкенд регенерирует live-потоки
|
||||||
|
|||||||
@@ -1100,6 +1100,8 @@ const CamSettings = {
|
|||||||
if (be) be.onclick = () => this.bulk ? this.saveBulk() : this.enterBulk();
|
if (be) be.onclick = () => this.bulk ? this.saveBulk() : this.enterBulk();
|
||||||
const bc = document.getElementById("bulk-cancel");
|
const bc = document.getElementById("bulk-cancel");
|
||||||
if (bc) bc.onclick = () => this.exitBulk();
|
if (bc) bc.onclick = () => this.exitBulk();
|
||||||
|
const cc = document.getElementById("clear-cams");
|
||||||
|
if (cc) cc.onclick = () => this.clearAll();
|
||||||
await this.load();
|
await this.load();
|
||||||
connectStatusWS(() => { if (!this.editing) this.load(); });
|
connectStatusWS(() => { if (!this.editing) this.load(); });
|
||||||
},
|
},
|
||||||
@@ -1108,10 +1110,12 @@ const CamSettings = {
|
|||||||
const be = document.getElementById("bulk-edit");
|
const be = document.getElementById("bulk-edit");
|
||||||
const bc = document.getElementById("bulk-cancel");
|
const bc = document.getElementById("bulk-cancel");
|
||||||
const add = document.getElementById("add-cam");
|
const add = document.getElementById("add-cam");
|
||||||
|
const cc = document.getElementById("clear-cams");
|
||||||
if (be) be.textContent = this.bulk ? "Сохранить" : "Изменить";
|
if (be) be.textContent = this.bulk ? "Сохранить" : "Изменить";
|
||||||
if (be) be.classList.toggle("primary", this.bulk);
|
if (be) be.classList.toggle("primary", this.bulk);
|
||||||
if (bc) bc.hidden = !this.bulk;
|
if (bc) bc.hidden = !this.bulk;
|
||||||
if (add) add.hidden = this.bulk; // во время массового редактирования не добавляем
|
if (add) add.hidden = this.bulk; // во время массового редактирования не добавляем
|
||||||
|
if (cc) cc.hidden = this.bulk;
|
||||||
},
|
},
|
||||||
|
|
||||||
enterBulk() {
|
enterBulk() {
|
||||||
@@ -1127,6 +1131,23 @@ const CamSettings = {
|
|||||||
await this.load();
|
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) {
|
bulkRowHtml(slotNo, cam) {
|
||||||
const v = (x) => this.esc(x);
|
const v = (x) => this.esc(x);
|
||||||
|
|||||||
Reference in New Issue
Block a user