mirror of
https://github.com/RuoJi6/ARTEX.git
synced 2026-10-27 17:03:49 +08:00
修复计划任务立即运行并区分状态颜色
This commit is contained in:
1 parent
a5cfec025f
commit
8067f3286b
4 files changed
+69
-12
No files matched your search
@@ -311,6 +311,11 @@ func (d *DB) TouchTaskSchedule(id int64, status, windowKey, lastError string) er
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) MarkTaskScheduleManual(id int64) error {
|
||||
_, err := d.Exec(`UPDATE task_schedules SET status='running',last_window_key='manual',last_transition_at=now(),last_error='' WHERE id=$1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ScheduleRunStats summarizes completed schedule window entries for list views.
|
||||
// The scheduler records one "running" history entry whenever a new window opens.
|
||||
type ScheduleRunStats struct {
|
||||
|
||||
+37
-5
@@ -199,23 +199,55 @@ func (s *Server) runScheduleNow(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, 404, "计划不存在")
|
||||
return
|
||||
}
|
||||
s.runScheduleTasks(item)
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
if err := s.m.pg.MarkTaskScheduleManual(id); err != nil {
|
||||
writeErr(w, 500, "无法启动计划任务")
|
||||
return
|
||||
}
|
||||
_ = s.m.pg.RecordScheduleHistory(id, 0, "running", true, "手动立即运行")
|
||||
started, errors := s.runScheduleTasks(item)
|
||||
writeJSON(w, 200, map[string]any{"ok": true, "started": started, "errors": errors})
|
||||
}
|
||||
|
||||
func (s *Server) runScheduleTasks(item *db.TaskSchedule) {
|
||||
func (s *Server) runScheduleTasks(item *db.TaskSchedule) (int, []string) {
|
||||
started := 0
|
||||
errors := []string{}
|
||||
for _, taskID := range item.TaskIDs {
|
||||
t, ok := s.m.Task(strconv.FormatInt(taskID, 10))
|
||||
if !ok {
|
||||
errors = append(errors, fmt.Sprintf("任务 #%d 未找到", taskID))
|
||||
continue
|
||||
}
|
||||
state := t.lifecycleSnapshot()
|
||||
if db.IsTerminal(state.Status) || !state.Paused {
|
||||
if db.IsTerminal(state.Status) {
|
||||
errors = append(errors, fmt.Sprintf("任务 #%d 已结束,无法立即运行", taskID))
|
||||
continue
|
||||
}
|
||||
_, _ = s.applyTaskControlWithCause(t, "resume", agent.AbortPausedByOrchestrator)
|
||||
if !state.Paused {
|
||||
started++
|
||||
continue
|
||||
}
|
||||
managed, err := s.m.pg.SchedulePausedTasks(taskID)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("任务 #%d 状态读取失败", taskID))
|
||||
_ = s.m.pg.RecordScheduleHistory(item.ID, taskID, "resume", false, "状态读取失败")
|
||||
continue
|
||||
}
|
||||
if !managed {
|
||||
errors = append(errors, fmt.Sprintf("任务 #%d 由人工暂停,计划不能强制恢复", taskID))
|
||||
_ = s.m.pg.RecordScheduleHistory(item.ID, taskID, "resume", false, "人工暂停优先")
|
||||
continue
|
||||
}
|
||||
_, err = s.applyTaskControlWithCause(t, "resume", agent.AbortPausedByOrchestrator)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Sprintf("任务 #%d:%s", taskID, err.Error()))
|
||||
_ = s.m.pg.RecordScheduleHistory(item.ID, taskID, "resume", false, err.Error())
|
||||
continue
|
||||
}
|
||||
started++
|
||||
_ = s.m.pg.SetSchedulePaused(item.ID, taskID, false)
|
||||
_ = s.m.pg.RecordScheduleHistory(item.ID, taskID, "resume", true, "立即运行")
|
||||
}
|
||||
return started, errors
|
||||
}
|
||||
|
||||
// scheduleRequestFromTask is used by the task creation handler without making
|
||||
|
||||
@@ -220,10 +220,20 @@ function ScheduleTimeline({ schedule, loading }: { schedule?: TaskSchedule; load
|
||||
);
|
||||
}
|
||||
|
||||
function statusVariant(status: TaskSchedule["status"]): "default" | "destructive" | "secondary" {
|
||||
if (status === "running") return "default";
|
||||
if (status === "error") return "destructive";
|
||||
return "secondary";
|
||||
function statusClass(status: TaskSchedule["status"]) {
|
||||
return (
|
||||
{
|
||||
waiting: "border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-900 dark:bg-sky-950/40 dark:text-sky-300",
|
||||
running:
|
||||
"border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900 dark:bg-emerald-950/40 dark:text-emerald-300",
|
||||
outside:
|
||||
"border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900 dark:bg-amber-950/40 dark:text-amber-300",
|
||||
completed: "border-blue-200 bg-blue-50 text-blue-700 dark:border-blue-900 dark:bg-blue-950/40 dark:text-blue-300",
|
||||
error: "border-red-200 bg-red-50 text-red-700 dark:border-red-900 dark:bg-red-950/40 dark:text-red-300",
|
||||
paused:
|
||||
"border-slate-200 bg-slate-100 text-slate-700 dark:border-slate-800 dark:bg-slate-900/60 dark:text-slate-300",
|
||||
} as Record<TaskSchedule["status"], string>
|
||||
)[status];
|
||||
}
|
||||
|
||||
function ScheduleForm({
|
||||
@@ -515,7 +525,14 @@ export default function SchedulesPage() {
|
||||
try {
|
||||
if (type === "pause") await api.pauseSchedule(id);
|
||||
else if (type === "resume") await api.resumeSchedule(id);
|
||||
else await api.runScheduleNow(id);
|
||||
else {
|
||||
const result = await api.runScheduleNow(id);
|
||||
if (result.errors?.length) {
|
||||
toast.warning(`已启动 ${result.started} 个任务,但有 ${result.errors.length} 个任务未启动`);
|
||||
} else {
|
||||
toast.success(`已立即运行 ${result.started} 个任务`);
|
||||
}
|
||||
}
|
||||
await load();
|
||||
} catch (e) {
|
||||
toast.error((e as Error).message);
|
||||
@@ -633,7 +650,9 @@ export default function SchedulesPage() {
|
||||
{item.task_ids.length ? item.task_ids.map((id) => `#${id}`).join("、") : "无"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={statusVariant(item.status)}>{statusText(item)}</Badge>
|
||||
<Badge variant="outline" className={statusClass(item.status)}>
|
||||
{statusText(item)}
|
||||
</Badge>
|
||||
<div className="mt-1 text-muted-foreground text-xs">{timezoneLabel(item)}</div>
|
||||
<div className="mt-1 text-muted-foreground text-xs">
|
||||
最近:{formatTime(item.last_transition_at)}
|
||||
|
||||
+2
-1
@@ -328,7 +328,8 @@ export const api = {
|
||||
deleteSchedule: (id: number) => del<{ ok: boolean }>(`/schedules/${id}`),
|
||||
pauseSchedule: (id: number) => post<{ ok: boolean }>(`/schedules/${id}/pause`, {}),
|
||||
resumeSchedule: (id: number) => post<{ ok: boolean }>(`/schedules/${id}/resume`, {}),
|
||||
runScheduleNow: (id: number) => post<{ ok: boolean }>(`/schedules/${id}/run-now`, {}),
|
||||
runScheduleNow: (id: number) =>
|
||||
post<{ ok: boolean; started: number; errors?: string[] }>(`/schedules/${id}/run-now`, {}),
|
||||
taskCategories: () => get<{ categories: TaskCategory[] }>("/task-categories").then((r) => arr(r.categories)),
|
||||
updateTask: (id: string, input: { name?: string; pinned?: boolean }) => patch<Task>(`/tasks/${id}`, input),
|
||||
renameTask: (id: string, name: string) => patch<Task>(`/tasks/${id}`, { name }),
|
||||
|
||||
Reference in new issue
Block a user