[功能] 服务状态页新增「视频分割」状态 - Oracle /api/oracle/activity 加 segment(运动片段 done/pending/failed/文件数/最近分割活动), ServiceStatus.vue 加视频分割卡片

This commit is contained in:
ericwyuan
2026-08-22 13:18:57 +08:00
parent 01a8ac2a3f
commit cbcc5d0325
3 changed files with 50 additions and 0 deletions

View File

@@ -153,9 +153,20 @@ def activity():
model_calls = db._conn.execute( model_calls = db._conn.execute(
"SELECT id, provider, model, filename, started_at, duration_sec, " "SELECT id, provider, model, filename, started_at, duration_sec, "
"success, error FROM model_calls ORDER BY id DESC LIMIT 5").fetchall() "success, error FROM model_calls ORDER BY id DESC LIMIT 5").fetchall()
# 运动片段分割状态(服务状态页「视频分割」卡)
segment = db.get_segment_status()
try:
import os
from ..config_loader import load_config
seg_dir = load_config().get('motion_segment', {}).get(
'clips_dir', '/opt/fam-edge/motion_clips')
segment['clips_files'] = len(os.listdir(seg_dir)) if os.path.isdir(seg_dir) else 0
except Exception:
segment['clips_files'] = 0
return jsonify({ return jsonify({
"queue": q_status, "queue": q_status,
"db": db.get_queue_status(), "db": db.get_queue_status(),
"segment": segment,
"rclone": _last_activity('rclone'), "rclone": _last_activity('rclone'),
"person": _last_activity('person'), "person": _last_activity('person'),
"model_calls": [dict(m) for m in model_calls], "model_calls": [dict(m) for m in model_calls],

View File

@@ -369,6 +369,35 @@ class OracleDB:
"recent": [dict(r) for r in recent], "recent": [dict(r) for r in recent],
} }
def get_segment_status(self) -> Dict:
"""运动片段分割状态(前端服务状态页「视频分割」卡)。
统计 motion_event_id 非空的运动片段记录 + 最近分割活动。
"""
seg_sql = "FROM videos WHERE motion_event_id IS NOT NULL"
total = self._conn.execute(f"SELECT COUNT(*) c {seg_sql}").fetchone()['c']
done = self._conn.execute(
f"SELECT COUNT(*) c {seg_sql} AND status='done'").fetchone()['c']
pending = self._conn.execute(
f"SELECT COUNT(*) c {seg_sql} AND status='pending'").fetchone()['c']
failed = self._conn.execute(
f"SELECT COUNT(*) c {seg_sql} AND status='failed'").fetchone()['c']
# 已分割事件数videos.motion_event_id 去重与待分割事件数ss_motion_events 未生成片段)
motion_total = self._conn.execute(
"SELECT COUNT(*) c FROM ss_motion_events WHERE event_type=10 AND COALESCE(duration,0)>0"
).fetchone()['c']
last = self._conn.execute(
"SELECT service, action, detail, ts FROM service_activity "
"WHERE service='segment' ORDER BY id DESC LIMIT 1").fetchone()
return {
"total": total,
"done": done,
"pending": pending,
"failed": failed,
"motion_events": motion_total,
"last": dict(last) if last else None,
}
def set_video_file_status(self, video_id: int, valid: bool, def set_video_file_status(self, video_id: int, valid: bool,
error: str = '', media_meta: dict = None): error: str = '', media_meta: dict = None):
"""登记/更新文件校验结果valid / file_error / media_meta_json""" """登记/更新文件校验结果valid / file_error / media_meta_json"""

View File

@@ -38,6 +38,7 @@ const qs = computed(() => queue.value.stats || {})
const rclone = computed(() => oracle.value.rclone) const rclone = computed(() => oracle.value.rclone)
const person = computed(() => oracle.value.person) const person = computed(() => oracle.value.person)
const segment = computed(() => oracle.value.segment)
const nasSync = computed(() => data.value?.nas_sync) const nasSync = computed(() => data.value?.nas_sync)
const modelCalls = computed(() => oracle.value.model_calls || []) const modelCalls = computed(() => oracle.value.model_calls || [])
const activities = computed(() => oracle.value.activities || []) const activities = computed(() => oracle.value.activities || [])
@@ -101,6 +102,15 @@ const SVC_BADGE = {
近5次 成功{{ modelCalls.filter(m => m.success).length }}/{{ modelCalls.length }} 近5次 成功{{ modelCalls.filter(m => m.success).length }}/{{ modelCalls.length }}
</template> </template>
</ServiceCard> </ServiceCard>
<ServiceCard icon="🎬" name="视频分割" :value-color="segment?.total ? '#f472b6' : undefined">
{{ segment && segment.total ? `运动片段 ${segment.done ?? 0}/${segment.total} · 文件 ${segment.clips_files ?? 0}` : '暂无片段' }}
<template #sub v-if="segment && segment.total">
待处理 {{ segment.pending ?? 0 }} · 失败 {{ segment.failed ?? 0 }} · 运动事件 {{ segment.motion_events ?? 0 }}
<span v-if="segment.last" class="mt-0.5 block text-text-mute">
{{ (segment.last.ts || '').slice(0, 19) }} · {{ (segment.last.detail || '').slice(0, 42) }}
</span>
</template>
</ServiceCard>
</div> </div>
<div class="mb-3 text-[15px] font-bold text-[#f7f9fc]">最近活动</div> <div class="mb-3 text-[15px] font-bold text-[#f7f9fc]">最近活动</div>