feat(web): unified task stream with plan tab, task detail drawer and audit links (FE-1)

Tasks page merges engine runs (with W9-I business fields) into one stream:
all / active / failed / plan tabs, filters for source, business kind, status
and time; detail drawer with business summary, timeline, collapsed engine
steps, cancel/retry and related audit links. The schedule board moves into
the plan tab; card history jumps to the stream filtered by schedule key.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 16:10:29 +08:00
1 parent f8362a3f4c
commit d8b9bd9662
10 files changed
+1189 -30

No files matched your search

@@ -1,5 +1,6 @@
import type { AgentCheckConfig, ScheduledJob, ScheduleSpecInput } from "@umt/contracts";
import { History, LoaderCircle, Pencil, Play, RotateCcw, SkipForward, Trash2 } from "lucide-react";
import { useEffect, useRef } from "react";
import { toast } from "sonner";
import { useDeleteSchedule, useRunScheduleNow, useSkipSchedule, useUpdateSchedule } from "~/api/schedules.ts";
import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx";
@@ -8,6 +9,7 @@ import { Button } from "~/components/ui/button.tsx";
import { Switch } from "~/components/ui/switch.tsx";
import { errorMessage } from "~/lib/api.ts";
import { formatRelative } from "~/lib/labels.ts";
import { cn } from "~/lib/utils.ts";
import { IntervalEditor } from "./interval-editor.tsx";
import { CHECK_TRIGGER_LABEL, countdown, formatMs, RUN_STATUS } from "./labels.ts";
@@ -54,16 +56,23 @@ export function ScheduleCard({
now,
onHistory,
onEdit,
highlight = false,
}: {
job: ScheduledJob;
now: number;
onHistory: (job: ScheduledJob) => void;
onEdit: (job: ScheduledJob) => void;
/** 从任务流跳来时高亮并滚到可见 */
highlight?: boolean;
}) {
const update = useUpdateSchedule();
const runNow = useRunScheduleNow();
const skip = useSkipSchedule();
const del = useDeleteSchedule();
const ref = useRef<HTMLElement>(null);
useEffect(() => {
if (highlight) ref.current?.scrollIntoView({ block: "center" });
}, [highlight]);
const busy = !!job.runningSince || !!job.runRequestedAt;
const last = job.lastStatus ? RUN_STATUS[job.lastStatus] : null;
@@ -74,7 +83,15 @@ export function ScheduleCard({
);
return (
<article className="pg-slab grid min-w-0 gap-3 rounded-xl px-4 py-3.5" aria-label={job.name}>
<article
id={`job-${job.id}`}
ref={ref}
className={cn(
"pg-slab grid min-w-0 scroll-mt-20 gap-3 rounded-xl px-4 py-3.5",
highlight && "ring-2 ring-ring/50",
)}
aria-label={job.name}
>
<header className="flex items-start justify-between gap-3">
<div className="min-w-0">
<h3 className="truncate text-lead font-medium text-foreground" title={job.name}>