@open-mercato/scheduler 0.4.6-develop-9ff1d4a9a2 → 0.4.6-develop-219dae16c5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/scheduler/backend/config/scheduled-jobs/[id]/edit/page.js +21 -213
- package/dist/modules/scheduler/backend/config/scheduled-jobs/[id]/edit/page.js.map +2 -2
- package/dist/modules/scheduler/backend/config/scheduled-jobs/new/page.js +20 -211
- package/dist/modules/scheduler/backend/config/scheduled-jobs/new/page.js.map +2 -2
- package/dist/modules/scheduler/lib/scheduledJobFormConfig.js +233 -0
- package/dist/modules/scheduler/lib/scheduledJobFormConfig.js.map +7 -0
- package/package.json +4 -4
- package/src/modules/scheduler/backend/config/scheduled-jobs/[id]/edit/page.tsx +24 -247
- package/src/modules/scheduler/backend/config/scheduled-jobs/new/page.tsx +22 -244
- package/src/modules/scheduler/lib/scheduledJobFormConfig.tsx +308 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
5
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
@@ -9,21 +9,15 @@ import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall"
|
|
|
9
9
|
import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
|
|
10
10
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
11
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
value: value || {},
|
|
22
|
-
onChange: setValue,
|
|
23
|
-
disabled
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
}
|
|
12
|
+
import {
|
|
13
|
+
createTargetOptionsLoader,
|
|
14
|
+
loadTimezoneOptions,
|
|
15
|
+
scheduledJobFormSchema,
|
|
16
|
+
scheduledJobFields,
|
|
17
|
+
scheduledJobGroups,
|
|
18
|
+
ScheduledJobEnabledSwitch,
|
|
19
|
+
buildScheduledJobPayload
|
|
20
|
+
} from "../../../../../lib/scheduledJobFormConfig.js";
|
|
27
21
|
function EditSchedulePage({ params }) {
|
|
28
22
|
const t = useT();
|
|
29
23
|
const router = useRouter();
|
|
@@ -31,42 +25,10 @@ function EditSchedulePage({ params }) {
|
|
|
31
25
|
const [error, setError] = React.useState(null);
|
|
32
26
|
const [initialData, setInitialData] = React.useState(null);
|
|
33
27
|
const [isEnabled, setIsEnabled] = React.useState(false);
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const { result } = await apiCall("/api/scheduler/targets");
|
|
39
|
-
const options = result ?? { queues: [], commands: [] };
|
|
40
|
-
targetOptionsRef.current = options;
|
|
41
|
-
return options;
|
|
42
|
-
} catch {
|
|
43
|
-
return { queues: [], commands: [] };
|
|
44
|
-
}
|
|
45
|
-
}, []);
|
|
46
|
-
const loadQueueOptions = React.useCallback(async (query) => {
|
|
47
|
-
const options = await loadTargetOptions();
|
|
48
|
-
if (!query) return options.queues;
|
|
49
|
-
const lower = query.toLowerCase();
|
|
50
|
-
return options.queues.filter((q) => q.label.toLowerCase().includes(lower));
|
|
51
|
-
}, [loadTargetOptions]);
|
|
52
|
-
const loadCommandOptions = React.useCallback(async (query) => {
|
|
53
|
-
const options = await loadTargetOptions();
|
|
54
|
-
if (!query) return options.commands;
|
|
55
|
-
const lower = query.toLowerCase();
|
|
56
|
-
return options.commands.filter((c) => c.label.toLowerCase().includes(lower));
|
|
57
|
-
}, [loadTargetOptions]);
|
|
58
|
-
const loadTimezoneOptions = React.useCallback(async (query) => {
|
|
59
|
-
try {
|
|
60
|
-
const allTz = Intl.supportedValuesOf("timeZone");
|
|
61
|
-
const filtered = query ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase())) : allTz;
|
|
62
|
-
return filtered.slice(0, 100).map((tz) => ({
|
|
63
|
-
value: tz,
|
|
64
|
-
label: tz
|
|
65
|
-
}));
|
|
66
|
-
} catch {
|
|
67
|
-
return [{ value: "UTC", label: "UTC" }];
|
|
68
|
-
}
|
|
69
|
-
}, []);
|
|
28
|
+
const { loadQueueOptions, loadCommandOptions } = React.useMemo(
|
|
29
|
+
() => createTargetOptionsLoader(apiCall),
|
|
30
|
+
[]
|
|
31
|
+
);
|
|
70
32
|
React.useEffect(() => {
|
|
71
33
|
async function fetchSchedule() {
|
|
72
34
|
try {
|
|
@@ -98,151 +60,12 @@ function EditSchedulePage({ params }) {
|
|
|
98
60
|
}
|
|
99
61
|
fetchSchedule();
|
|
100
62
|
}, [params.id, t]);
|
|
101
|
-
const formSchema = React.useMemo(
|
|
102
|
-
() => z.object({
|
|
103
|
-
name: z.string().min(1, t("scheduler.form.name.required", "Name is required")),
|
|
104
|
-
description: z.string().optional(),
|
|
105
|
-
scopeType: z.enum(["system", "organization", "tenant"]),
|
|
106
|
-
scheduleType: z.enum(["cron", "interval"]),
|
|
107
|
-
scheduleValue: z.string().min(1, t("scheduler.form.schedule.required", "Schedule is required")),
|
|
108
|
-
timezone: z.string(),
|
|
109
|
-
targetType: z.enum(["queue", "command"]),
|
|
110
|
-
targetQueue: z.string().optional(),
|
|
111
|
-
targetCommand: z.string().optional(),
|
|
112
|
-
targetPayload: z.record(z.string(), z.unknown()).optional(),
|
|
113
|
-
isEnabled: z.boolean()
|
|
114
|
-
}),
|
|
115
|
-
[t]
|
|
116
|
-
);
|
|
63
|
+
const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t]);
|
|
117
64
|
const fields = React.useMemo(
|
|
118
|
-
() =>
|
|
119
|
-
|
|
120
|
-
id: "name",
|
|
121
|
-
type: "text",
|
|
122
|
-
label: t("scheduler.form.name", "Name"),
|
|
123
|
-
required: true
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
id: "description",
|
|
127
|
-
type: "textarea",
|
|
128
|
-
label: t("scheduler.form.description", "Description")
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "scopeType",
|
|
132
|
-
type: "select",
|
|
133
|
-
label: t("scheduler.form.scope_type", "Scope"),
|
|
134
|
-
required: true,
|
|
135
|
-
options: [
|
|
136
|
-
{ value: "system", label: t("scheduler.scope.system", "System") },
|
|
137
|
-
{ value: "organization", label: t("scheduler.scope.organization", "Organization") },
|
|
138
|
-
{ value: "tenant", label: t("scheduler.scope.tenant", "Tenant") }
|
|
139
|
-
]
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
id: "scheduleType",
|
|
143
|
-
type: "select",
|
|
144
|
-
label: t("scheduler.form.schedule_type", "Schedule Type"),
|
|
145
|
-
required: true,
|
|
146
|
-
options: [
|
|
147
|
-
{ value: "cron", label: t("scheduler.type.cron", "Cron Expression") },
|
|
148
|
-
{ value: "interval", label: t("scheduler.type.interval", "Simple Interval") }
|
|
149
|
-
]
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
id: "scheduleValue",
|
|
153
|
-
type: "text",
|
|
154
|
-
label: t("scheduler.form.schedule_value", "Schedule Value"),
|
|
155
|
-
placeholder: t("scheduler.form.schedule_value.placeholder", "e.g. 0 */6 * * * or 15m"),
|
|
156
|
-
description: t("scheduler.form.schedule_value.description", 'For cron: use cron expression (e.g., "0 0 * * *"). For interval: use format like "15m", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days)'),
|
|
157
|
-
required: true
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
id: "timezone",
|
|
161
|
-
type: "combobox",
|
|
162
|
-
label: t("scheduler.form.timezone", "Timezone"),
|
|
163
|
-
placeholder: t("scheduler.form.timezone.placeholder", "Search timezone..."),
|
|
164
|
-
required: true,
|
|
165
|
-
loadOptions: loadTimezoneOptions,
|
|
166
|
-
allowCustomValues: false
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
id: "targetType",
|
|
170
|
-
type: "select",
|
|
171
|
-
label: t("scheduler.form.target_type", "Target Type"),
|
|
172
|
-
required: true,
|
|
173
|
-
options: [
|
|
174
|
-
{ value: "queue", label: t("scheduler.target.queue", "Queue") },
|
|
175
|
-
{ value: "command", label: t("scheduler.target.command", "Command") }
|
|
176
|
-
]
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: "targetFields",
|
|
180
|
-
type: "custom",
|
|
181
|
-
label: "",
|
|
182
|
-
component: ({ values, setFormValue }) => {
|
|
183
|
-
const targetType = values?.targetType;
|
|
184
|
-
const targetQueue = values?.targetQueue || "";
|
|
185
|
-
const targetCommand = values?.targetCommand || "";
|
|
186
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
187
|
-
targetType === "queue" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
188
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "targetQueue", children: t("scheduler.form.target_queue", "Target Queue") }),
|
|
189
|
-
/* @__PURE__ */ jsx(
|
|
190
|
-
ComboboxInput,
|
|
191
|
-
{
|
|
192
|
-
value: targetQueue,
|
|
193
|
-
onChange: (next) => setFormValue && setFormValue("targetQueue", next),
|
|
194
|
-
placeholder: t("scheduler.form.target_queue.placeholder", "Search queues..."),
|
|
195
|
-
loadSuggestions: loadQueueOptions,
|
|
196
|
-
allowCustomValues: true
|
|
197
|
-
}
|
|
198
|
-
)
|
|
199
|
-
] }),
|
|
200
|
-
targetType === "command" && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
201
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "targetCommand", children: t("scheduler.form.target_command", "Target Command") }),
|
|
202
|
-
/* @__PURE__ */ jsx(
|
|
203
|
-
ComboboxInput,
|
|
204
|
-
{
|
|
205
|
-
value: targetCommand,
|
|
206
|
-
onChange: (next) => setFormValue && setFormValue("targetCommand", next),
|
|
207
|
-
placeholder: t("scheduler.form.target_command.placeholder", "Search commands..."),
|
|
208
|
-
loadSuggestions: loadCommandOptions,
|
|
209
|
-
allowCustomValues: false
|
|
210
|
-
}
|
|
211
|
-
)
|
|
212
|
-
] })
|
|
213
|
-
] });
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
id: "targetPayload",
|
|
218
|
-
type: "custom",
|
|
219
|
-
label: t("scheduler.form.target_payload", "Job Arguments (JSON)"),
|
|
220
|
-
description: t("scheduler.form.target_payload.description", "Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time."),
|
|
221
|
-
component: (props) => /* @__PURE__ */ jsx(PayloadJsonEditor, { ...props })
|
|
222
|
-
}
|
|
223
|
-
],
|
|
224
|
-
[t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]
|
|
225
|
-
);
|
|
226
|
-
const groups = React.useMemo(
|
|
227
|
-
() => [
|
|
228
|
-
{
|
|
229
|
-
id: "basic",
|
|
230
|
-
title: t("scheduler.form.group.basic", "Basic Information"),
|
|
231
|
-
fields: ["name", "description", "scopeType"]
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
id: "schedule",
|
|
235
|
-
title: t("scheduler.form.group.schedule", "Schedule Configuration"),
|
|
236
|
-
fields: ["scheduleType", "scheduleValue", "timezone"]
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
id: "target",
|
|
240
|
-
title: t("scheduler.form.group.target", "Target Configuration"),
|
|
241
|
-
fields: ["targetType", "targetFields", "targetPayload"]
|
|
242
|
-
}
|
|
243
|
-
],
|
|
244
|
-
[t]
|
|
65
|
+
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
|
|
66
|
+
[t, loadQueueOptions, loadCommandOptions]
|
|
245
67
|
);
|
|
68
|
+
const groups = React.useMemo(() => scheduledJobGroups(t), [t]);
|
|
246
69
|
if (loading) {
|
|
247
70
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("scheduler.loading", "Loading schedule...") }) }) });
|
|
248
71
|
}
|
|
@@ -260,27 +83,12 @@ function EditSchedulePage({ params }) {
|
|
|
260
83
|
submitLabel: t("scheduler.form.save", "Save Changes"),
|
|
261
84
|
cancelHref: "/backend/config/scheduled-jobs",
|
|
262
85
|
schema: formSchema,
|
|
263
|
-
extraActions: /* @__PURE__ */
|
|
264
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "enabled-switch", className: "text-sm font-medium cursor-pointer", children: isEnabled ? t("scheduler.form.enabled", "Enabled") : t("scheduler.form.disabled", "Disabled") }),
|
|
265
|
-
/* @__PURE__ */ jsx(
|
|
266
|
-
Switch,
|
|
267
|
-
{
|
|
268
|
-
id: "enabled-switch",
|
|
269
|
-
checked: isEnabled,
|
|
270
|
-
onCheckedChange: setIsEnabled
|
|
271
|
-
}
|
|
272
|
-
)
|
|
273
|
-
] }),
|
|
86
|
+
extraActions: /* @__PURE__ */ jsx(ScheduledJobEnabledSwitch, { isEnabled, setIsEnabled, t }),
|
|
274
87
|
onSubmit: async (values) => {
|
|
275
|
-
const
|
|
88
|
+
const payload = buildScheduledJobPayload(values, isEnabled);
|
|
276
89
|
await updateCrud(
|
|
277
90
|
"scheduler/jobs",
|
|
278
|
-
{
|
|
279
|
-
id: params.id,
|
|
280
|
-
...values,
|
|
281
|
-
targetPayload,
|
|
282
|
-
isEnabled
|
|
283
|
-
}
|
|
91
|
+
{ id: params.id, ...payload }
|
|
284
92
|
);
|
|
285
93
|
flash(t("scheduler.success.updated", "Schedule updated successfully"), "success");
|
|
286
94
|
router.push("/backend/config/scheduled-jobs");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/scheduler/backend/config/scheduled-jobs/%5Bid%5D/edit/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { z } from 'zod'\nimport { ComboboxInput, type ComboboxOption } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\n\ntype ScheduleFormValues = {\n name: string\n description?: string\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone?: string\n targetType: 'queue' | 'command'\n targetQueue?: string\n targetCommand?: string\n targetPayload?: Record<string, unknown>\n isEnabled: boolean\n}\n\ntype ScheduleData = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n targetPayload?: Record<string, unknown> | null\n isEnabled: boolean\n}\n\ntype TargetOptions = {\n queues: ComboboxOption[]\n commands: ComboboxOption[]\n}\n\nfunction PayloadJsonEditor({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n return (\n <JsonBuilder\n value={value || {}}\n onChange={setValue}\n disabled={disabled}\n />\n )\n}\n\nexport default function EditSchedulePage({ params }: { params: { id: string } }) {\n const t = useT()\n const router = useRouter()\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [initialData, setInitialData] = React.useState<Partial<ScheduleFormValues> | null>(null)\n const [isEnabled, setIsEnabled] = React.useState(false)\n const targetOptionsRef = React.useRef<TargetOptions | null>(null)\n\n const loadTargetOptions = React.useCallback(async (): Promise<TargetOptions> => {\n if (targetOptionsRef.current) return targetOptionsRef.current\n try {\n const { result } = await apiCall<TargetOptions>('/api/scheduler/targets')\n const options = result ?? { queues: [], commands: [] }\n targetOptionsRef.current = options\n return options\n } catch {\n return { queues: [], commands: [] }\n }\n }, [])\n\n const loadQueueOptions = React.useCallback(async (query?: string): Promise<ComboboxOption[]> => {\n const options = await loadTargetOptions()\n if (!query) return options.queues\n const lower = query.toLowerCase()\n return options.queues.filter((q) => q.label.toLowerCase().includes(lower))\n }, [loadTargetOptions])\n\n const loadCommandOptions = React.useCallback(async (query?: string): Promise<ComboboxOption[]> => {\n const options = await loadTargetOptions()\n if (!query) return options.commands\n const lower = query.toLowerCase()\n return options.commands.filter((c) => c.label.toLowerCase().includes(lower))\n }, [loadTargetOptions])\n\n // Load timezone options - filtering on query for better performance\n const loadTimezoneOptions = React.useCallback(async (query?: string) => {\n try {\n const allTz = Intl.supportedValuesOf('timeZone')\n const filtered = query\n ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase()))\n : allTz\n return filtered.slice(0, 100).map((tz) => ({\n value: tz,\n label: tz,\n }))\n } catch {\n return [{ value: 'UTC', label: 'UTC' }]\n }\n }, [])\n\n React.useEffect(() => {\n async function fetchSchedule() {\n try {\n const { result } = await apiCallOrThrow<{ items: ScheduleData[] }>(\n `/api/scheduler/jobs?id=${params.id}`\n )\n\n const schedule = result?.items?.[0]\n if (schedule) {\n setIsEnabled(schedule.isEnabled)\n \n setInitialData({\n name: schedule.name,\n description: schedule.description || undefined,\n scopeType: schedule.scopeType,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue || undefined,\n targetCommand: schedule.targetCommand || undefined,\n targetPayload: (schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0)\n ? schedule.targetPayload\n : undefined,\n isEnabled: schedule.isEnabled,\n })\n }\n } catch (err) {\n setError(t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }\n\n fetchSchedule()\n }, [params.id, t])\n\n const formSchema = React.useMemo(\n () =>\n z.object({\n name: z.string().min(1, t('scheduler.form.name.required', 'Name is required')),\n description: z.string().optional(),\n scopeType: z.enum(['system', 'organization', 'tenant']),\n scheduleType: z.enum(['cron', 'interval']),\n scheduleValue: z.string().min(1, t('scheduler.form.schedule.required', 'Schedule is required')),\n timezone: z.string(),\n targetType: z.enum(['queue', 'command']),\n targetQueue: z.string().optional(),\n targetCommand: z.string().optional(),\n targetPayload: z.record(z.string(), z.unknown()).optional(),\n isEnabled: z.boolean(),\n }),\n [t]\n )\n\n const fields = React.useMemo<CrudField[]>(\n () => [\n {\n id: 'name',\n type: 'text',\n label: t('scheduler.form.name', 'Name'),\n required: true,\n },\n {\n id: 'description',\n type: 'textarea',\n label: t('scheduler.form.description', 'Description'),\n },\n {\n id: 'scopeType',\n type: 'select',\n label: t('scheduler.form.scope_type', 'Scope'),\n required: true,\n options: [\n { value: 'system', label: t('scheduler.scope.system', 'System') },\n { value: 'organization', label: t('scheduler.scope.organization', 'Organization') },\n { value: 'tenant', label: t('scheduler.scope.tenant', 'Tenant') },\n ],\n },\n {\n id: 'scheduleType',\n type: 'select',\n label: t('scheduler.form.schedule_type', 'Schedule Type'),\n required: true,\n options: [\n { value: 'cron', label: t('scheduler.type.cron', 'Cron Expression') },\n { value: 'interval', label: t('scheduler.type.interval', 'Simple Interval') },\n ],\n },\n {\n id: 'scheduleValue',\n type: 'text',\n label: t('scheduler.form.schedule_value', 'Schedule Value'),\n placeholder: t('scheduler.form.schedule_value.placeholder', 'e.g. 0 */6 * * * or 15m'),\n description: t('scheduler.form.schedule_value.description', 'For cron: use cron expression (e.g., \"0 0 * * *\"). For interval: use format like \"15m\", \"2h\", \"1d\" (s=seconds, m=minutes, h=hours, d=days)'),\n required: true,\n },\n {\n id: 'timezone',\n type: 'combobox',\n label: t('scheduler.form.timezone', 'Timezone'),\n placeholder: t('scheduler.form.timezone.placeholder', 'Search timezone...'),\n required: true,\n loadOptions: loadTimezoneOptions,\n allowCustomValues: false,\n },\n {\n id: 'targetType',\n type: 'select',\n label: t('scheduler.form.target_type', 'Target Type'),\n required: true,\n options: [\n { value: 'queue', label: t('scheduler.target.queue', 'Queue') },\n { value: 'command', label: t('scheduler.target.command', 'Command') },\n ],\n },\n {\n id: 'targetFields',\n type: 'custom',\n label: '',\n component: ({ values, setFormValue }) => {\n const targetType = values?.targetType as string | undefined\n const targetQueue = (values?.targetQueue as string) || ''\n const targetCommand = (values?.targetCommand as string) || ''\n\n return (\n <div className=\"space-y-4\">\n {targetType === 'queue' && (\n <div className=\"space-y-1\">\n <Label htmlFor=\"targetQueue\">\n {t('scheduler.form.target_queue', 'Target Queue')}\n </Label>\n <ComboboxInput\n value={targetQueue}\n onChange={(next) => setFormValue && setFormValue('targetQueue', next)}\n placeholder={t('scheduler.form.target_queue.placeholder', 'Search queues...')}\n loadSuggestions={loadQueueOptions}\n allowCustomValues={true}\n />\n </div>\n )}\n {targetType === 'command' && (\n <div className=\"space-y-1\">\n <Label htmlFor=\"targetCommand\">\n {t('scheduler.form.target_command', 'Target Command')}\n </Label>\n <ComboboxInput\n value={targetCommand}\n onChange={(next) => setFormValue && setFormValue('targetCommand', next)}\n placeholder={t('scheduler.form.target_command.placeholder', 'Search commands...')}\n loadSuggestions={loadCommandOptions}\n allowCustomValues={false}\n />\n </div>\n )}\n </div>\n )\n },\n },\n {\n id: 'targetPayload',\n type: 'custom',\n label: t('scheduler.form.target_payload', 'Job Arguments (JSON)'),\n description: t('scheduler.form.target_payload.description', 'Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time.'),\n component: (props) => <PayloadJsonEditor {...props} />,\n },\n ],\n [t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n title: t('scheduler.form.group.basic', 'Basic Information'),\n fields: ['name', 'description', 'scopeType'],\n },\n {\n id: 'schedule',\n title: t('scheduler.form.group.schedule', 'Schedule Configuration'),\n fields: ['scheduleType', 'scheduleValue', 'timezone'],\n },\n {\n id: 'target',\n title: t('scheduler.form.group.target', 'Target Configuration'),\n fields: ['targetType', 'targetFields', 'targetPayload'],\n },\n ],\n [t]\n )\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('scheduler.loading', 'Loading schedule...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !initialData) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error || t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<ScheduleFormValues>\n title={t('scheduler.edit.title', 'Edit Schedule')}\n backHref=\"/backend/config/scheduled-jobs\"\n fields={fields}\n groups={groups}\n initialValues={initialData}\n submitLabel={t('scheduler.form.save', 'Save Changes')}\n cancelHref=\"/backend/config/scheduled-jobs\"\n schema={formSchema}\n extraActions={\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"enabled-switch\" className=\"text-sm font-medium cursor-pointer\">\n {isEnabled ? t('scheduler.form.enabled', 'Enabled') : t('scheduler.form.disabled', 'Disabled')}\n </Label>\n <Switch\n id=\"enabled-switch\"\n checked={isEnabled}\n onCheckedChange={setIsEnabled}\n />\n </div>\n }\n onSubmit={async (values) => {\n const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0\n ? values.targetPayload\n : null\n\n await updateCrud(\n 'scheduler/jobs',\n { \n id: params.id, \n ...values, \n targetPayload,\n isEnabled \n }\n )\n\n flash(t('scheduler.success.updated', 'Schedule updated successfully'), 'success')\n router.push('/backend/config/scheduled-jobs')\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport {\n type ScheduleFormValues,\n createTargetOptionsLoader,\n loadTimezoneOptions,\n scheduledJobFormSchema,\n scheduledJobFields,\n scheduledJobGroups,\n ScheduledJobEnabledSwitch,\n buildScheduledJobPayload,\n} from '../../../../../lib/scheduledJobFormConfig'\n\ntype ScheduleData = {\n id: string\n name: string\n description?: string | null\n scopeType: 'system' | 'organization' | 'tenant'\n scheduleType: 'cron' | 'interval'\n scheduleValue: string\n timezone: string\n targetType: 'queue' | 'command'\n targetQueue?: string | null\n targetCommand?: string | null\n targetPayload?: Record<string, unknown> | null\n isEnabled: boolean\n}\n\nexport default function EditSchedulePage({ params }: { params: { id: string } }) {\n const t = useT()\n const router = useRouter()\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [initialData, setInitialData] = React.useState<Partial<ScheduleFormValues> | null>(null)\n const [isEnabled, setIsEnabled] = React.useState(false)\n\n const { loadQueueOptions, loadCommandOptions } = React.useMemo(\n () => createTargetOptionsLoader(apiCall),\n []\n )\n\n React.useEffect(() => {\n async function fetchSchedule() {\n try {\n const { result } = await apiCallOrThrow<{ items: ScheduleData[] }>(\n `/api/scheduler/jobs?id=${params.id}`\n )\n\n const schedule = result?.items?.[0]\n if (schedule) {\n setIsEnabled(schedule.isEnabled)\n\n setInitialData({\n name: schedule.name,\n description: schedule.description || undefined,\n scopeType: schedule.scopeType,\n scheduleType: schedule.scheduleType,\n scheduleValue: schedule.scheduleValue,\n timezone: schedule.timezone,\n targetType: schedule.targetType,\n targetQueue: schedule.targetQueue || undefined,\n targetCommand: schedule.targetCommand || undefined,\n targetPayload: (schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0)\n ? schedule.targetPayload\n : undefined,\n isEnabled: schedule.isEnabled,\n })\n }\n } catch (err) {\n setError(t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }\n\n fetchSchedule()\n }, [params.id, t])\n\n const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t])\n\n const fields = React.useMemo(\n () => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),\n [t, loadQueueOptions, loadCommandOptions]\n )\n\n const groups = React.useMemo(() => scheduledJobGroups(t), [t])\n\n if (loading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('scheduler.loading', 'Loading schedule...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !initialData) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error || t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<ScheduleFormValues>\n title={t('scheduler.edit.title', 'Edit Schedule')}\n backHref=\"/backend/config/scheduled-jobs\"\n fields={fields}\n groups={groups}\n initialValues={initialData}\n submitLabel={t('scheduler.form.save', 'Save Changes')}\n cancelHref=\"/backend/config/scheduled-jobs\"\n schema={formSchema}\n extraActions={\n <ScheduledJobEnabledSwitch isEnabled={isEnabled} setIsEnabled={setIsEnabled} t={t} />\n }\n onSubmit={async (values) => {\n const payload = buildScheduledJobPayload(values, isEnabled)\n\n await updateCrud(\n 'scheduler/jobs',\n { id: params.id, ...payload }\n )\n\n flash(t('scheduler.success.updated', 'Schedule updated successfully'), 'success')\n router.push('/backend/config/scheduled-jobs')\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { useRouter } from "next/navigation";
|
|
5
5
|
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
@@ -7,207 +7,30 @@ import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
|
7
7
|
import { createCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
8
8
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
9
9
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
10
|
-
import { Switch } from "@open-mercato/ui/primitives/switch";
|
|
11
|
-
import { Label } from "@open-mercato/ui/primitives/label";
|
|
12
|
-
import { z } from "zod";
|
|
13
|
-
import { ComboboxInput } from "@open-mercato/ui/backend/inputs/ComboboxInput";
|
|
14
10
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
25
|
-
}
|
|
11
|
+
import {
|
|
12
|
+
createTargetOptionsLoader,
|
|
13
|
+
loadTimezoneOptions,
|
|
14
|
+
scheduledJobFormSchema,
|
|
15
|
+
scheduledJobFields,
|
|
16
|
+
scheduledJobGroups,
|
|
17
|
+
ScheduledJobEnabledSwitch,
|
|
18
|
+
buildScheduledJobPayload
|
|
19
|
+
} from "../../../../lib/scheduledJobFormConfig.js";
|
|
26
20
|
function NewSchedulePage() {
|
|
27
21
|
const t = useT();
|
|
28
22
|
const router = useRouter();
|
|
29
23
|
const [isEnabled, setIsEnabled] = React.useState(true);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const { result } = await apiCall("/api/scheduler/targets");
|
|
35
|
-
const options = result ?? { queues: [], commands: [] };
|
|
36
|
-
targetOptionsRef.current = options;
|
|
37
|
-
return options;
|
|
38
|
-
} catch {
|
|
39
|
-
return { queues: [], commands: [] };
|
|
40
|
-
}
|
|
41
|
-
}, []);
|
|
42
|
-
const loadQueueOptions = React.useCallback(async (query) => {
|
|
43
|
-
const options = await loadTargetOptions();
|
|
44
|
-
if (!query) return options.queues;
|
|
45
|
-
const lower = query.toLowerCase();
|
|
46
|
-
return options.queues.filter((q) => q.label.toLowerCase().includes(lower));
|
|
47
|
-
}, [loadTargetOptions]);
|
|
48
|
-
const loadCommandOptions = React.useCallback(async (query) => {
|
|
49
|
-
const options = await loadTargetOptions();
|
|
50
|
-
if (!query) return options.commands;
|
|
51
|
-
const lower = query.toLowerCase();
|
|
52
|
-
return options.commands.filter((c) => c.label.toLowerCase().includes(lower));
|
|
53
|
-
}, [loadTargetOptions]);
|
|
54
|
-
const loadTimezoneOptions = React.useCallback(async (query) => {
|
|
55
|
-
try {
|
|
56
|
-
const allTz = Intl.supportedValuesOf("timeZone");
|
|
57
|
-
const filtered = query ? allTz.filter((tz) => tz.toLowerCase().includes(query.toLowerCase())) : allTz;
|
|
58
|
-
return filtered.slice(0, 100).map((tz) => ({
|
|
59
|
-
value: tz,
|
|
60
|
-
label: tz
|
|
61
|
-
}));
|
|
62
|
-
} catch {
|
|
63
|
-
return [{ value: "UTC", label: "UTC" }];
|
|
64
|
-
}
|
|
65
|
-
}, []);
|
|
66
|
-
const formSchema = React.useMemo(
|
|
67
|
-
() => z.object({
|
|
68
|
-
name: z.string().min(1, t("scheduler.form.name.required", "Name is required")),
|
|
69
|
-
description: z.string().optional(),
|
|
70
|
-
scopeType: z.enum(["system", "organization", "tenant"]),
|
|
71
|
-
scheduleType: z.enum(["cron", "interval"]),
|
|
72
|
-
scheduleValue: z.string().min(1, t("scheduler.form.schedule.required", "Schedule is required")),
|
|
73
|
-
timezone: z.string(),
|
|
74
|
-
targetType: z.enum(["queue", "command"]),
|
|
75
|
-
targetQueue: z.string().optional(),
|
|
76
|
-
targetCommand: z.string().optional(),
|
|
77
|
-
targetPayload: z.record(z.string(), z.unknown()).optional(),
|
|
78
|
-
isEnabled: z.boolean()
|
|
79
|
-
}),
|
|
80
|
-
[t]
|
|
24
|
+
const { loadQueueOptions, loadCommandOptions } = React.useMemo(
|
|
25
|
+
() => createTargetOptionsLoader(apiCall),
|
|
26
|
+
[]
|
|
81
27
|
);
|
|
28
|
+
const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t]);
|
|
82
29
|
const fields = React.useMemo(
|
|
83
|
-
() =>
|
|
84
|
-
|
|
85
|
-
id: "name",
|
|
86
|
-
type: "text",
|
|
87
|
-
label: t("scheduler.form.name", "Name"),
|
|
88
|
-
required: true
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: "description",
|
|
92
|
-
type: "textarea",
|
|
93
|
-
label: t("scheduler.form.description", "Description")
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
id: "scopeType",
|
|
97
|
-
type: "select",
|
|
98
|
-
label: t("scheduler.form.scope_type", "Scope"),
|
|
99
|
-
required: true,
|
|
100
|
-
options: [
|
|
101
|
-
{ value: "system", label: t("scheduler.scope.system", "System") },
|
|
102
|
-
{ value: "organization", label: t("scheduler.scope.organization", "Organization") },
|
|
103
|
-
{ value: "tenant", label: t("scheduler.scope.tenant", "Tenant") }
|
|
104
|
-
]
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: "scheduleType",
|
|
108
|
-
type: "select",
|
|
109
|
-
label: t("scheduler.form.schedule_type", "Schedule Type"),
|
|
110
|
-
required: true,
|
|
111
|
-
options: [
|
|
112
|
-
{ value: "cron", label: t("scheduler.type.cron", "Cron Expression") },
|
|
113
|
-
{ value: "interval", label: t("scheduler.type.interval", "Simple Interval") }
|
|
114
|
-
]
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: "scheduleValue",
|
|
118
|
-
type: "text",
|
|
119
|
-
label: t("scheduler.form.schedule_value", "Schedule Value"),
|
|
120
|
-
placeholder: t("scheduler.form.schedule_value.placeholder", "e.g. 0 */6 * * * or 15m"),
|
|
121
|
-
description: t("scheduler.form.schedule_value.description", 'For cron: use cron expression (e.g., "0 0 * * *"). For interval: use format like "15m", "2h", "1d" (s=seconds, m=minutes, h=hours, d=days)'),
|
|
122
|
-
required: true
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
id: "timezone",
|
|
126
|
-
type: "combobox",
|
|
127
|
-
label: t("scheduler.form.timezone", "Timezone"),
|
|
128
|
-
placeholder: t("scheduler.form.timezone.placeholder", "Search timezone..."),
|
|
129
|
-
required: true,
|
|
130
|
-
loadOptions: loadTimezoneOptions,
|
|
131
|
-
allowCustomValues: false
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
id: "targetType",
|
|
135
|
-
type: "select",
|
|
136
|
-
label: t("scheduler.form.target_type", "Target Type"),
|
|
137
|
-
required: true,
|
|
138
|
-
options: [
|
|
139
|
-
{ value: "queue", label: t("scheduler.target.queue", "Queue") },
|
|
140
|
-
{ value: "command", label: t("scheduler.target.command", "Command") }
|
|
141
|
-
]
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
id: "targetFields",
|
|
145
|
-
type: "custom",
|
|
146
|
-
label: "",
|
|
147
|
-
component: ({ values, setFormValue }) => {
|
|
148
|
-
const targetType = values?.targetType;
|
|
149
|
-
const targetQueue = values?.targetQueue || "";
|
|
150
|
-
const targetCommand = values?.targetCommand || "";
|
|
151
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
152
|
-
targetType === "queue" && /* @__PURE__ */ jsxs("div", { children: [
|
|
153
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "targetQueue", children: t("scheduler.form.target_queue", "Target Queue") }),
|
|
154
|
-
/* @__PURE__ */ jsx(
|
|
155
|
-
ComboboxInput,
|
|
156
|
-
{
|
|
157
|
-
value: targetQueue,
|
|
158
|
-
onChange: (next) => setFormValue && setFormValue("targetQueue", next),
|
|
159
|
-
placeholder: t("scheduler.form.target_queue.placeholder", "Search queues..."),
|
|
160
|
-
loadSuggestions: loadQueueOptions,
|
|
161
|
-
allowCustomValues: true
|
|
162
|
-
}
|
|
163
|
-
)
|
|
164
|
-
] }),
|
|
165
|
-
targetType === "command" && /* @__PURE__ */ jsxs("div", { children: [
|
|
166
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "targetCommand", children: t("scheduler.form.target_command", "Target Command") }),
|
|
167
|
-
/* @__PURE__ */ jsx(
|
|
168
|
-
ComboboxInput,
|
|
169
|
-
{
|
|
170
|
-
value: targetCommand,
|
|
171
|
-
onChange: (next) => setFormValue && setFormValue("targetCommand", next),
|
|
172
|
-
placeholder: t("scheduler.form.target_command.placeholder", "Search commands..."),
|
|
173
|
-
loadSuggestions: loadCommandOptions,
|
|
174
|
-
allowCustomValues: false
|
|
175
|
-
}
|
|
176
|
-
)
|
|
177
|
-
] })
|
|
178
|
-
] });
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
id: "targetPayload",
|
|
183
|
-
type: "custom",
|
|
184
|
-
label: t("scheduler.form.target_payload", "Job Arguments (JSON)"),
|
|
185
|
-
description: t("scheduler.form.target_payload.description", "Optional JSON payload. Fields tenantId and organizationId are injected automatically at execution time."),
|
|
186
|
-
component: (props) => /* @__PURE__ */ jsx(PayloadJsonEditor, { ...props })
|
|
187
|
-
}
|
|
188
|
-
],
|
|
189
|
-
[t, loadTimezoneOptions, loadQueueOptions, loadCommandOptions]
|
|
190
|
-
);
|
|
191
|
-
const groups = React.useMemo(
|
|
192
|
-
() => [
|
|
193
|
-
{
|
|
194
|
-
id: "basic",
|
|
195
|
-
title: t("scheduler.form.group.basic", "Basic Information"),
|
|
196
|
-
fields: ["name", "description", "scopeType"]
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
id: "schedule",
|
|
200
|
-
title: t("scheduler.form.group.schedule", "Schedule Configuration"),
|
|
201
|
-
fields: ["scheduleType", "scheduleValue", "timezone"]
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
id: "target",
|
|
205
|
-
title: t("scheduler.form.group.target", "Target Configuration"),
|
|
206
|
-
fields: ["targetType", "targetFields", "targetPayload"]
|
|
207
|
-
}
|
|
208
|
-
],
|
|
209
|
-
[t]
|
|
30
|
+
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
|
|
31
|
+
[t, loadQueueOptions, loadCommandOptions]
|
|
210
32
|
);
|
|
33
|
+
const groups = React.useMemo(() => scheduledJobGroups(t), [t]);
|
|
211
34
|
const initialValues = React.useMemo(
|
|
212
35
|
() => ({
|
|
213
36
|
scopeType: "tenant",
|
|
@@ -229,26 +52,12 @@ function NewSchedulePage() {
|
|
|
229
52
|
submitLabel: t("scheduler.form.submit", "Create Schedule"),
|
|
230
53
|
cancelHref: "/backend/config/scheduled-jobs",
|
|
231
54
|
schema: formSchema,
|
|
232
|
-
extraActions: /* @__PURE__ */
|
|
233
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "enabled-switch", className: "text-sm font-medium cursor-pointer", children: isEnabled ? t("scheduler.form.enabled", "Enabled") : t("scheduler.form.disabled", "Disabled") }),
|
|
234
|
-
/* @__PURE__ */ jsx(
|
|
235
|
-
Switch,
|
|
236
|
-
{
|
|
237
|
-
id: "enabled-switch",
|
|
238
|
-
checked: isEnabled,
|
|
239
|
-
onCheckedChange: setIsEnabled
|
|
240
|
-
}
|
|
241
|
-
)
|
|
242
|
-
] }),
|
|
55
|
+
extraActions: /* @__PURE__ */ jsx(ScheduledJobEnabledSwitch, { isEnabled, setIsEnabled, t }),
|
|
243
56
|
onSubmit: async (values) => {
|
|
244
|
-
const
|
|
57
|
+
const payload = buildScheduledJobPayload(values, isEnabled);
|
|
245
58
|
await createCrud(
|
|
246
59
|
"scheduler/jobs",
|
|
247
|
-
|
|
248
|
-
...values,
|
|
249
|
-
targetPayload,
|
|
250
|
-
isEnabled
|
|
251
|
-
}
|
|
60
|
+
payload
|
|
252
61
|
);
|
|
253
62
|
flash(t("scheduler.success.created", "Schedule created successfully"), "success");
|
|
254
63
|
router.push("/backend/config/scheduled-jobs");
|