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