feat(监控): 实时服务状态界面 - Oracle 新增 service_activity 活动表(只保留7天,写入时清理)与 /api/oracle/activity 接口(队列实时状态/当前处理视频/模型调用/rclone/人物合并/最近50条活动);VideoQueue 打点+当前处理跟踪+status();PersonService 打点;rclone_sync.sh 同步结果写活动表;fam-ui 新增'🖥 服务状态'页(状态卡+活动时间流,直连 Oracle 实时拉取)

This commit is contained in:
ericwyuan
2026-08-21 15:52:36 +08:00
parent da7631976d
commit 0d0a7f6ef8
7 changed files with 311 additions and 9 deletions

View File

@@ -99,9 +99,17 @@ class OracleDB:
error TEXT,
created_at TEXT
);
CREATE TABLE IF NOT EXISTS service_activity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
service TEXT,
action TEXT,
detail TEXT,
ts TEXT
);
CREATE INDEX IF NOT EXISTS idx_videos_updated ON videos(updated_at);
CREATE INDEX IF NOT EXISTS idx_events_video ON events(video_id);
CREATE INDEX IF NOT EXISTS idx_model_calls_created ON model_calls(created_at);
CREATE INDEX IF NOT EXISTS idx_activity_ts ON service_activity(ts);
""")
# 兼容旧库:补 retry_count / file_valid / media 等列(生产-消费队列用)
cols = [r[1] for r in c.execute("PRAGMA table_info(videos)").fetchall()]
@@ -141,6 +149,53 @@ class OracleDB:
duration_sec, 1 if success else 0, error or '', now))
self._conn.commit()
# ------------------------------------------------------------------
# 服务活动日志(实时状态界面用;只保留最近 7 天)
# ------------------------------------------------------------------
def record_activity(self, service: str, action: str, detail: str = ''):
"""记录一条服务活动queue/rclone/person/model...)。
写入时顺带清理 7 天前的旧记录(用户要求只保留最近七天)。
"""
now = _now_iso()
with self._write_lock:
self._conn.execute(
"INSERT INTO service_activity (service, action, detail, ts) "
"VALUES (?,?,?,?)",
(service, action, str(detail or '')[:500], now))
# 只保留最近 7 天
self._conn.execute(
"DELETE FROM service_activity WHERE ts < ?",
((datetime.now(timezone(timedelta(hours=8))) - timedelta(days=7))
.strftime('%Y-%m-%d %H:%M:%S'),))
self._conn.commit()
def get_recent_activities(self, limit: int = 50) -> List[Dict]:
"""最近活动(时间倒序)。"""
rows = self._conn.execute(
"SELECT id, service, action, detail, ts FROM service_activity "
"ORDER BY id DESC LIMIT ?", (int(limit),)).fetchall()
return [dict(r) for r in rows]
def get_queue_status(self) -> Dict:
"""实时队列/处理状态(前端服务状态卡用)。"""
total = self._conn.execute("SELECT COUNT(*) c FROM videos").fetchone()['c']
by_status = {}
for r in self._conn.execute(
"SELECT status, COUNT(*) c FROM videos GROUP BY status").fetchall():
by_status[r['status']] = r['c']
# 最近处理的视频done/failed按 updated_at 倒序)
recent = self._conn.execute(
"SELECT id, filename, status, compute_provider, updated_at, "
"processed_at, event_start_time FROM videos "
"ORDER BY COALESCE(updated_at, created_at) DESC LIMIT 5"
).fetchall()
return {
"total": total,
"by_status": by_status,
"recent": [dict(r) for r in recent],
}
def set_video_file_status(self, video_id: int, valid: bool,
error: str = '', media_meta: dict = None):
"""登记/更新文件校验结果valid / file_error / media_meta_json"""