feat(web): split plan page from tasks (FE-6)

- Nav overview group: 首页 / 任务 / 计划 / Agent 会话; tasks page drops the plan tab
- /console/tasks?tab=plan[&job=] redirects to /console/schedules[?job=]
- Plan page: 自动任务 compact grouped list (switch, interval preset/custom, last result,
  countdown, failure reason, run now/skip/run records) and Agent 巡检 cards (goal,
  allowlist, budget, upstream/model, trigger, cost per run, recent sessions); split stats
- Home plan band: two counts linking to #auto / #checks
- Docs: console-ia §7.4a, glossary, schedules runbook; smoke routes

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 17:13:54 +08:00
1 parent c3aaf901ad
commit baf2d6ea04
22 files changed
+1030 -417

No files matched your search

+16 -30
View File
@@ -5,7 +5,6 @@ import { useEngineRun, useEngineStats } from "~/api/engine.ts";
import { DetailSheet } from "~/components/console/detail-sheet.tsx";
import { ErrorState, LoadingState } from "~/components/console/empty-state.tsx";
import { PageHeader } from "~/components/console/page-header.tsx";
import { ScheduleBoard } from "~/components/schedules/schedule-board.tsx";
import { runTitle } from "~/components/tasks/labels.ts";
import { RunActions, TaskDetailBody, TaskStatusLine } from "~/components/tasks/task-detail.tsx";
import { type StreamFilters, type StreamTab, TaskStream } from "~/components/tasks/task-stream.tsx";
@@ -16,11 +15,11 @@ import { legacyRedirectLoader } from "./redirects.ts";
export const meta = () => [{ title: "任务 · UHD 媒体自动化控制台" }];
// 旧的 ?tab=downloads / plans / business 跳到下载面板 / 下载计划 / 任务流
// 旧的 ?tab=downloads / plans / business / plan 跳到下载面板 / 下载计划 / 任务流 / 计划页
export const clientLoader = legacyRedirectLoader;
type Tab = StreamTab | "plan";
const TABS: Tab[] = ["all", "active", "failed", "plan"];
type Tab = StreamTab;
const TABS: Tab[] = ["all", "active", "failed"];
const FILTER_KEYS: (keyof StreamFilters)[] = ["status", "kind", "ref", "source", "since", "workflow"];
function Count({ n }: { n: number | undefined }) {
@@ -102,14 +101,7 @@ export default function TasksPage() {
title="任务"
description="每次运行都是一条任务,不论定时、事件、手动还是 Agent 触发。"
actions={
<Button
variant="outline"
size="sm"
onClick={() => {
qc.invalidateQueries({ queryKey: qk.all("engine") });
qc.invalidateQueries({ queryKey: qk.all("schedules") });
}}
>
<Button variant="outline" size="sm" onClick={() => qc.invalidateQueries({ queryKey: qk.all("engine") })}>
<RotateCw />
刷新
</Button>
@@ -120,7 +112,6 @@ export default function TasksPage() {
onValueChange={(v) =>
update((p) => {
p.set("tab", v);
p.delete("job");
if (v !== "all") p.delete("status");
})
}
@@ -135,26 +126,21 @@ export default function TasksPage() {
失败
<Count n={by?.failed} />
</TabsTrigger>
<TabsTrigger value="plan">计划</TabsTrigger>
</TabsList>
</Tabs>
<div className="mt-4">
{tab === "plan" ? (
<ScheduleBoard highlightJob={params.get("job") ?? undefined} />
) : (
<TaskStream
tab={tab}
filters={filters}
setFilter={setFilter}
resetFilters={() =>
update((p) => {
for (const k of FILTER_KEYS) p.delete(k);
})
}
onOpen={(id) => update((p) => p.set("run", id))}
selectedId={runId}
/>
)}
<TaskStream
tab={tab}
filters={filters}
setFilter={setFilter}
resetFilters={() =>
update((p) => {
for (const k of FILTER_KEYS) p.delete(k);
})
}
onOpen={(id) => update((p) => p.set("run", id))}
selectedId={runId}
/>
</div>
<RunSheet
runId={runId}