diff --git a/web/src/app/(main)/function/schedules/page.tsx b/web/src/app/(main)/function/schedules/page.tsx index 2d8c12e..6b9a3ce 100644 --- a/web/src/app/(main)/function/schedules/page.tsx +++ b/web/src/app/(main)/function/schedules/page.tsx @@ -162,14 +162,23 @@ function historyActionText(action: string) { ); } -function timelinePosition(event: ScheduleHistoryEntry, index: number, events: ScheduleHistoryEntry[]) { - if (events.length <= 1) return 50; - const timestamps = events.map((item) => Date.parse(item.created_at)).filter(Number.isFinite); - const time = Date.parse(event.created_at); - const min = Math.min(...timestamps); - const max = Math.max(...timestamps); - if (!Number.isFinite(time) || !Number.isFinite(min) || max <= min) return (index / (events.length - 1)) * 100; - return ((time - min) / (max - min)) * 100; +function historyEventClass(event: ScheduleHistoryEntry) { + if (!event.success) + return "border-red-200 bg-red-50 text-red-700 dark:border-red-900 dark:bg-red-950/40 dark:text-red-300"; + if (event.action === "running" || event.action === "resume") { + return "border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-300"; + } + if (event.action === "outside" || event.action === "pause" || event.action === "paused") { + return "border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-300"; + } + return "border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-800 dark:bg-slate-900/60 dark:text-slate-300"; +} + +function historyDotClass(event: ScheduleHistoryEntry) { + if (!event.success) return "bg-red-500"; + if (event.action === "running" || event.action === "resume") return "bg-emerald-500"; + if (event.action === "outside" || event.action === "pause" || event.action === "paused") return "bg-amber-500"; + return "bg-slate-400"; } function ScheduleTimeline({ schedule, loading }: { schedule?: TaskSchedule; loading: boolean }) { @@ -181,38 +190,42 @@ function ScheduleTimeline({ schedule, loading }: { schedule?: TaskSchedule; load ); } - const events = [...(schedule?.history ?? [])].reverse(); + // The API returns newest first. Keep that order so the latest transition is + // visible immediately when a row is expanded. + const events = schedule?.history ?? []; if (!events.length) { return
暂无运行记录,计划进入第一个窗口后会显示时间线。
; } return (
-
运行时间线
-
-
- {events.map((event, index) => ( -
-
-
- {historyActionText(event.action)} -
-
- ))} +
+
运行时间线
+ 最近 {Math.min(events.length, 20)} 条记录
-
- {events.slice(-20).map((event) => ( -
- {historyActionText(event.action)} - {formatTime(event.created_at)} - {event.task_id ? 任务 #{event.task_id} : null} - {event.message ? ( - {event.message} +
+ {events.slice(0, 20).map((event, index) => ( +
+ {index < Math.min(events.length, 20) - 1 ? ( +
))}