video_monitor/app/views/AnalysisView.py
2026-09-04 18:16:14 +08:00

515 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Monitor · 分析模块 Web 层
页面:
- /control/index 布控管理(见 ControlView
API
- /alarm/openIndex/openDel/openBatchDel/openClearAlarms
- /analysis/openStatus/openStart/openStop/openReloadZones
"""
from app.views.ViewsBase import *
from app.utils.Utils import buildPageLabels
from django.shortcuts import render
import json
from datetime import datetime
from app.models import StreamModel, ZoneModel, AlarmModel
def _preview_owner(request):
if not request.session.session_key:
request.session.save()
return "%s:%s" % (request.user.pk, request.session.session_key)
def _parse_page_params(request, default_ps=12):
page = request.GET.get('p', 1)
page_size = request.GET.get('ps', default_ps)
try:
page = int(page)
if page < 1:
page = 1
except Exception:
page = 1
try:
page_size = int(page_size)
if page_size < 1:
page_size = default_ps
elif page_size > 100:
page_size = 100
except Exception:
page_size = default_ps
return page, page_size
def _build_page_data(request, page, page_size, count):
page_num = int(count / page_size)
if count % page_size > 0:
page_num += 1
if page_num < 1:
page_num = 1
if page > page_num:
page = page_num
page_labels = buildPageLabels(page=page, page_num=page_num, lang=f_parseRequestLang(request))
return {
"page": page,
"page_size": page_size,
"page_num": page_num,
"count": count,
"pageLabels": page_labels,
}
def _alarm_abs_path(rel_path):
"""将 metadata 中的相对路径(相对 static/)转为绝对路径"""
if not rel_path:
return ""
try:
from django.conf import settings
base = str(getattr(settings, "BASE_DIR", ""))
if base:
return os.path.join(base, "static", rel_path)
except Exception:
pass
return ""
def _delete_snapshot_file(rel_path):
"""删除单个快照文件(相对 static/ 的路径)"""
p = _alarm_abs_path(rel_path)
if p and os.path.isfile(p):
try:
os.remove(p)
return True
except Exception:
return False
return False
# ===================== Alarm =====================
def _alarm_to_dict(a):
"""将 AlarmModel 转为前端字典"""
try:
meta = json.loads(a.metadata) if a.metadata else {}
except Exception:
meta = {}
if not isinstance(meta, dict):
meta = {}
reason = meta.get("alarm_reason") or a.description or ""
return {
"id": a.id,
"stream_id": a.stream_id,
"stream_name": (a.stream.nickname if a.stream else ""),
"event_type": a.event_type,
"timestamp": str(a.timestamp),
"metadata": a.metadata,
"biz_algorithm_id": meta.get("biz_algorithm_id"),
"biz_algorithm_name": meta.get("biz_algorithm_name") or "",
"alarm_reason": reason,
"zone_name": meta.get("zone_name") or "",
"snapshot_path": meta.get("snapshot_path") or "",
"description": reason or a.description or "",
}
def alarm_openIndex(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = []
page_data = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
page, page_size = _parse_page_params(request, default_ps=20)
qs = AlarmModel.objects.all().order_by('-id')
stream_id = request.GET.get('stream_id')
if stream_id:
qs = qs.filter(stream_id=int(stream_id))
event_type = request.GET.get('event_type')
if event_type:
qs = qs.filter(event_type=event_type)
count = qs.count()
skip = (page - 1) * page_size
data = [_alarm_to_dict(a) for a in qs[skip:skip + page_size]]
page_data = _build_page_data(request, page, page_size, count)
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg, "data": data, "pageData": page_data})
def alarm_openDel(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
aid = int(params.get("id", 0))
if aid <= 0:
raise ValueError(LANG_VIEWS_T(request, "msg_invalid_parameter"))
obj = AlarmModel.objects.filter(id=aid).first()
if not obj:
raise ValueError(LANG_VIEWS_T(request, "msg_not_found"))
try:
meta = json.loads(obj.metadata) if obj.metadata else {}
snap = meta.get("snapshot_path", "")
if snap:
_delete_snapshot_file(snap)
except Exception:
pass
obj.delete()
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def alarm_openBatchDel(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
deleted = 0
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
ids_raw = params.get("ids", "[]")
ids = json.loads(ids_raw) if isinstance(ids_raw, str) else ids_raw
if not isinstance(ids, list) or not ids:
raise ValueError(LANG_VIEWS_T(request, "msg_invalid_parameter"))
qs = AlarmModel.objects.filter(id__in=ids)
for obj in qs:
try:
meta = json.loads(obj.metadata) if obj.metadata else {}
snap = meta.get("snapshot_path", "")
if snap:
_delete_snapshot_file(snap)
except Exception:
pass
deleted, _ = qs.delete()
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg, "data": {"deleted": deleted}})
def alarm_openClearAlarms(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
deleted = 0
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
qs = AlarmModel.objects.all()
for obj in qs:
try:
meta = json.loads(obj.metadata) if obj.metadata else {}
snap = meta.get("snapshot_path", "")
if snap:
_delete_snapshot_file(snap)
except Exception:
pass
deleted, _ = qs.delete()
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg, "data": {"deleted": deleted}})
# ===================== 分析控制 =====================
def analysis_openStatus(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
lite = str(request.GET.get("lite", "")).lower() in ("1", "true", "yes")
from app.views.ControlView import build_analysis_status_data
data = build_analysis_status_data(lite=lite)
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg, "data": data})
def analysis_openPreviewStart(request):
if request.method != "POST":
return f_responseJson({"code": 0, "msg": LANG_VIEWS_T(request, "msg_method_not_supported")})
ok, check_msg = f_checkRequestSafe(request)
if not ok:
return f_responseJson({"code": 0, "msg": check_msg})
try:
params = f_parsePostParams(request)
zone = ZoneModel.objects.select_related("stream").prefetch_related(
"algorithms", "algorithms__small_model", "algorithms__detector_model"
).get(id=int(params.get("zone_id", 0)))
from app.analysis.preview_sessions import get_preview_registry
data = get_preview_registry().start(_preview_owner(request), zone)
return f_responseJson({"code": 1000, "msg": "ok", "data": data})
except Exception as e:
return f_responseJson({"code": 0, "msg": str(e)})
def analysis_openPreviewData(request):
if request.method != "GET":
return f_responseJson({"code": 0, "msg": LANG_VIEWS_T(request, "msg_method_not_supported")})
ok, check_msg = f_checkRequestSafe(request)
if not ok:
return f_responseJson({"code": 0, "msg": check_msg})
try:
session_id = request.GET.get("session_id", "")
since = request.GET.get("since")
from app.analysis.preview_sessions import get_preview_registry
data = get_preview_registry().data(_preview_owner(request), session_id, since)
return f_responseJson({"code": 1000, "msg": "ok", "data": data})
except Exception as e:
return f_responseJson({"code": 0, "msg": str(e)})
def analysis_openPreviewStop(request):
if request.method != "POST":
return f_responseJson({"code": 0, "msg": LANG_VIEWS_T(request, "msg_method_not_supported")})
ok, check_msg = f_checkRequestSafe(request)
if not ok:
return f_responseJson({"code": 0, "msg": check_msg})
try:
params = f_parsePostParams(request)
from app.analysis.preview_sessions import get_preview_registry
stopped = get_preview_registry().stop(_preview_owner(request), params.get("session_id", ""))
return f_responseJson({"code": 1000 if stopped else 0,
"msg": "ok" if stopped else "预览会话不存在"})
except Exception as e:
return f_responseJson({"code": 0, "msg": str(e)})
def analysis_openStart(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
sid = int(params.get("stream_id", 0))
if sid <= 0:
raise ValueError(LANG_VIEWS_T(request, "msg_invalid_parameter"))
enabled = ZoneModel.objects.filter(stream_id=sid, state=1).exists()
if not enabled:
raise ValueError(LANG_VIEWS_T(request, "zone_analysis_need_enabled"))
stream = StreamModel.objects.get(id=sid)
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().start(stream)
ret = ok
msg = info
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
msg = LANG_VIEWS_T(request, "zone_analysis_started")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openStop(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
sid = int(params.get("stream_id", 0))
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().stop(sid)
ret = ok
msg = info if not ok else LANG_VIEWS_T(request, "zone_analysis_stopped")
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openReloadZones(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
sid = int(params.get("stream_id", 0))
from app.analysis.manager import AnalysisManager
ok = AnalysisManager().reload_zones(sid)
ret = ok
msg = LANG_VIEWS_T(request, "msg_success") if ok else "pipeline not running"
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openUpdateInferenceConfig(request):
"""热更新推理引擎配置(不持久化到 config.json
POST 参数:
- shared: 0/1 切换共享推理开关
- workers: int 调整共享推理 worker 数
两者至少传一个;切换 shared 会重启所有运行中的 pipeline。
"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
shared = params.get("shared", None)
workers = params.get("workers", None)
if shared is None and workers is None:
raise ValueError("至少需要传 shared 或 workers 参数")
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().set_inference_config(
shared=shared, workers=workers)
ret = ok
msg = info
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openToggleAlgoInstance(request):
"""切换业务算法的实例化开关(内存,重启丢失,立即生效)。
POST 参数:
- algorithm_id: int 业务算法绑定的小模型 ID
- enabled: 0/1
"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
algo_id = int(params.get("algorithm_id", 0))
enabled = int(params.get("enabled", 1)) == 1
if algo_id <= 0:
raise ValueError(LANG_VIEWS_T(request, "msg_invalid_parameter"))
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().set_algo_instance_enabled(algo_id, enabled)
ret = ok
msg = info
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openRestartAlgoInstance(request):
"""重启使用指定算法的所有 pipeline重新加载引擎
POST 参数:
- algorithm_id: int 小模型 ID
"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
algo_id = int(params.get("algorithm_id", 0))
if algo_id <= 0:
raise ValueError(LANG_VIEWS_T(request, "msg_invalid_parameter"))
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().restart_algo_instance(algo_id)
ret = ok
msg = info
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})
def analysis_openRestartInferencePool(request):
"""重启整个推理池(清除所有引擎缓存)。"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
try:
from app.analysis.manager import AnalysisManager
ok, info = AnalysisManager().restart_inference_pool()
ret = ok
msg = info
if ok:
from app.views.ControlView import invalidate_analysis_status_cache
invalidate_analysis_status_cache()
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
return f_responseJson({"code": 1000 if ret else 0, "msg": msg})