feat(web): scheduled jobs board (/console/schedules)
- cards grouped by collect / download / organize / maintain / agent checks: switch, in-place interval or cron edit, last result and duration, next-run countdown, failure reason, run now, skip next, reset to default, history drawer - top stats (enabled / failed / running / total) and a scheduler-offline notice; refreshed by the schedule.changed SSE event with a 30s fallback - agent check cards show goal template, trigger, budget and allowlist size; create / edit dialog (read-only capabilities, budget, trigger, interval, report to inbox) - nav entry in the system group, route and smoke route Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
a68fcea13b
commit
d800987403
11 files changed
+1024
-3
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type {
|
||||
AgentCheckCreate,
|
||||
ScheduleBoard,
|
||||
ScheduledJob,
|
||||
ScheduledJobRun,
|
||||
ScheduleRunNowResult,
|
||||
ScheduleUpdate,
|
||||
} from "@umt/contracts";
|
||||
import { apiData } from "~/lib/api.ts";
|
||||
import { qk } from "~/lib/query-keys.ts";
|
||||
|
||||
// 定时任务看板(W7-C):GET /schedules 一次取全部卡片;写操作成功后整块失效(SSE schedule.changed 也会触发)。
|
||||
|
||||
const AREA = "schedules";
|
||||
|
||||
export function useScheduleBoard() {
|
||||
return useQuery({
|
||||
queryKey: qk.list(AREA),
|
||||
queryFn: ({ signal }) => apiData<ScheduleBoard>("/schedules", { signal }),
|
||||
// SSE 是主路径,30 秒兜底
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
export function useScheduleRuns(id: string | null, enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: qk.sub(AREA, "runs", id ?? ""),
|
||||
queryFn: ({ signal }) =>
|
||||
apiData<{ items: ScheduledJobRun[] }>("/schedules/{id}/runs", {
|
||||
pathParams: { id: id ?? "" },
|
||||
params: { limit: 50 },
|
||||
signal,
|
||||
}),
|
||||
enabled: !!id && enabled,
|
||||
});
|
||||
}
|
||||
|
||||
function useInvalidate() {
|
||||
const qc = useQueryClient();
|
||||
return () => qc.invalidateQueries({ queryKey: qk.all(AREA) });
|
||||
}
|
||||
|
||||
export function useUpdateSchedule() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, body }: { id: string; body: ScheduleUpdate }) =>
|
||||
apiData<ScheduledJob>("/schedules/{id}", { method: "PATCH", pathParams: { id }, body }),
|
||||
onSettled: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useRunScheduleNow() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id }: { id: string }) =>
|
||||
apiData<ScheduleRunNowResult>("/schedules/{id}/run-now", { method: "POST", pathParams: { id } }),
|
||||
onSettled: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useSkipSchedule() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id }: { id: string }) =>
|
||||
apiData<ScheduledJob>("/schedules/{id}/skip", { method: "POST", pathParams: { id } }),
|
||||
onSettled: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateAgentCheck() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: (body: AgentCheckCreate) => apiData<ScheduledJob>("/schedules", { method: "POST", body }),
|
||||
onSettled: invalidate,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteSchedule() {
|
||||
const invalidate = useInvalidate();
|
||||
return useMutation({
|
||||
mutationFn: ({ id }: { id: string }) =>
|
||||
apiData<{ ok: true }>("/schedules/{id}", { method: "DELETE", pathParams: { id } }),
|
||||
onSettled: invalidate,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user