feat(server,contracts,web): dashboard library counts and engine runs triggeredBy/multi-status filters

- GET /dashboard: automation.library {entities, movies, series, seasons,
  episodes, assets} and automation.slotSatisfiedRate; home media overview
  shows the counts instead of placeholders.
- GET /engine/runs: `status` accepts comma-separated values; new
  `triggeredBy` filter (comma-separated) evaluated server-side by scanning
  batches and paging with the last inspected row's cursor (scan cap 2000).
- Task stream: source filter and the "active" tab (running,pending) are now
  one paginated server query.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 16:49:11 +08:00
1 parent 615917bb45
commit 363f85b1a1
13 files changed
+165 -250

No files matched your search

+26 -1
View File
@@ -208,6 +208,28 @@ export type AuditEventQuery = z.infer<typeof AuditEventQuery>;
// ── 引擎面板(只读直查 openworkflow schema,仅 admin;docs/design/openworkflow-dashboard-reference.md) ──
/** 逗号分隔的枚举多选(查询串):`a,b` → ["a","b"],未知值报错 */
function csvEnum<T extends string>(e: z.ZodType<T>, name: string) {
return z
.string()
.max(200)
.transform((s, ctx) => {
const out: T[] = [];
for (const p of s
.split(",")
.map((x) => x.trim())
.filter(Boolean)) {
const r = e.safeParse(p);
if (!r.success) {
ctx.addIssue({ code: "custom", message: `未知 ${name}: ${p}` });
return z.NEVER;
}
if (!out.includes(r.data)) out.push(r.data);
}
return out;
});
}
/** 引擎 run 状态(含 legacy sleeping/succeeded) */
export const EngineRunStatus = z.enum([
"pending",
@@ -273,7 +295,8 @@ export type EngineRun = z.infer<typeof EngineRun>;
export const EngineRunQuery = z.object({
cursor: z.string().min(1).optional(),
limit: z.coerce.number().int().min(1).max(200).default(50),
status: EngineRunStatus.optional(),
/** 单值或逗号分隔多值,如 status=running,pending */
status: csvEnum(EngineRunStatus, "status").optional(),
workflowName: z.string().max(200).optional(),
/** created_at 下界(含) */
from: Timestamp.optional(),
@@ -283,6 +306,8 @@ export const EngineRunQuery = z.object({
businessKind: EngineBusinessKind.optional(),
/** 按关联业务对象 id 筛选(可与 businessKind 同用) */
businessRefId: z.string().min(1).max(200).optional(),
/** 按触发来源筛选(单值或逗号分隔多值);来源在服务端逐页推断,一页可能少于 limit,以 nextCursor 为准 */
triggeredBy: csvEnum(EngineTriggeredBy, "triggeredBy").optional(),
});
export type EngineRunQuery = z.infer<typeof EngineRunQuery>;
+11
View File
@@ -469,6 +469,17 @@ export const Dashboard = z.object({
}),
activeSubscriptions: NonNegInt,
slotCounts: SlotStatusCounts,
/** 版本目标满足率 = satisfied / 全部版本目标(0–1);没有版本目标时为 null */
slotSatisfiedRate: z.number().min(0).max(1).nullable(),
/** 本地账本计数:作品不含已合并;季 = content_unit.kind=season(不含 retired);资产 = active 的视频 / 原盘目录 */
library: z.object({
entities: NonNegInt,
movies: NonNegInt,
series: NonNegInt,
seasons: NonNegInt,
episodes: NonNegInt,
assets: NonNegInt,
}),
}),
currentWork: z.object({
downloading: NonNegInt,