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

+7
View File
@@ -25,6 +25,13 @@ export const SOURCE_LABEL: Record<TaskSource, string> = {
manual: "手动",
agent: "Agent",
};
/** 来源 → 服务端 triggeredBy 筛选值(逗号分隔多值) */
export const SOURCE_TRIGGERED_BY: Record<TaskSource, string> = {
schedule: "schedule",
event: "event",
manual: "user,api_key",
agent: "agent",
};
export function sourceOf(t: EngineTriggeredBy | null | undefined): TaskSource | null {
if (!t) return null;
if (t === "user" || t === "api_key") return "manual";
+14 -24
View File
@@ -3,7 +3,7 @@ import type { EngineBusinessKind, EngineRun, ScheduledJob } from "@umt/contracts
import { CalendarClock, ExternalLink, History, ListTodo, X } from "lucide-react";
import { useMemo, useRef, useState } from "react";
import { Link } from "react-router";
import { useEngineRuns, useEngineRunsPage } from "~/api/engine.ts";
import { useEngineRuns } from "~/api/engine.ts";
import { useScheduleBoard } from "~/api/schedules.ts";
import { DataTable } from "~/components/console/data-table.tsx";
import { FilterBar, FilterSelect } from "~/components/console/filter-bar.tsx";
@@ -22,6 +22,7 @@ import {
runStatus,
runTitle,
SOURCE_LABEL,
SOURCE_TRIGGERED_BY,
sourceOf,
type TaskSource,
workflowLabel,
@@ -132,26 +133,20 @@ export function TaskStream({
businessKind: (filters.kind || undefined) as EngineBusinessKind | undefined,
businessRefId: filters.ref || undefined,
workflowName: filters.workflow || undefined,
triggeredBy: SOURCE_TRIGGERED_BY[filters.source as TaskSource] ?? undefined,
from,
};
const active = tab === "active";
// 进行中 = 进行中 + 排队(status 多值,服务端分页);来源筛选同样在服务端做
const list = useEngineRuns(
{
...base,
status: tab === "failed" ? "failed" : filters.status || undefined,
status: active ? "running,pending" : tab === "failed" ? "failed" : filters.status || undefined,
},
tab !== "active",
true,
active ? 10_000 : false,
);
// 进行中 = 进行中 + 排队(两次取第一页合并)
const running = useEngineRunsPage({ ...base, status: "running" }, 100, 10_000, tab === "active");
const pending = useEngineRunsPage({ ...base, status: "pending" }, 100, 10_000, tab === "active");
const active = tab === "active";
const loaded = active
? running.data && pending.data
? [...running.data.items, ...pending.data.items]
: undefined
: list.data?.pages.flatMap((p) => p.items);
const rows = filters.source ? loaded?.filter((r) => sourceOf(r.business?.triggeredBy) === filters.source) : loaded;
const rows = list.data?.pages.flatMap((p) => p.items);
const filtered = Object.values(filters).some(Boolean);
return (
@@ -212,17 +207,12 @@ export function TaskStream({
<DataTable<EngineRun>
rows={rows}
rowKey={(r) => r.id}
loading={active ? running.isPending || pending.isPending : list.isPending}
error={active ? (running.error ?? pending.error) : list.error}
onRetry={() => {
if (active) {
void running.refetch();
void pending.refetch();
} else void list.refetch();
}}
loading={list.isPending}
error={list.error}
onRetry={() => void list.refetch()}
onRowClick={(r) => onOpen(r.id)}
selectedKey={selectedId}
hasMore={!active && !!list.hasNextPage}
hasMore={!!list.hasNextPage}
onLoadMore={() => list.fetchNextPage()}
loadingMore={list.isFetchingNextPage}
empty={{
@@ -234,7 +224,7 @@ export function TaskStream({
: tab === "failed"
? "没有失败的任务"
: "还没有任务",
description: filters.source && loaded?.length ? "来源筛选只作用于已加载的记录,可加载更多再看。" : undefined,
description: filters.source && list.hasNextPage ? "按来源筛选时一页可能较少,可加载更多继续查找。" : undefined,
}}
columns={[
{