@open-mercato/scheduler 0.6.5-develop.4516.1.88e6ab71a9 → 0.6.5-develop.4544.1.71c003c861
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 +1 -1
- package/dist/modules/scheduler/backend/config/scheduled-jobs/[id]/edit/page.js.map +2 -2
- package/dist/modules/scheduler/i18n/de.json +1 -0
- package/dist/modules/scheduler/i18n/en.json +1 -0
- package/dist/modules/scheduler/i18n/es.json +1 -0
- package/dist/modules/scheduler/i18n/pl.json +1 -0
- package/dist/modules/scheduler/lib/scheduledJobFormConfig.js +8 -1
- package/dist/modules/scheduler/lib/scheduledJobFormConfig.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/scheduler/backend/config/scheduled-jobs/[id]/edit/page.tsx +1 -1
- package/src/modules/scheduler/data/__tests__/validators.test.ts +17 -0
- package/src/modules/scheduler/i18n/de.json +1 -0
- package/src/modules/scheduler/i18n/en.json +1 -0
- package/src/modules/scheduler/i18n/es.json +1 -0
- package/src/modules/scheduler/i18n/pl.json +1 -0
- package/src/modules/scheduler/lib/__tests__/scheduledJobFormConfig.test.ts +24 -0
- package/src/modules/scheduler/lib/scheduledJobFormConfig.tsx +11 -1
|
@@ -62,7 +62,7 @@ function EditSchedulePage({ params }) {
|
|
|
62
62
|
}, [params.id, t]);
|
|
63
63
|
const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t]);
|
|
64
64
|
const fields = React.useMemo(
|
|
65
|
-
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
|
|
65
|
+
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }, { lockScope: true }),
|
|
66
66
|
[t, loadQueueOptions, loadCommandOptions]
|
|
67
67
|
);
|
|
68
68
|
const groups = React.useMemo(() => scheduledJobGroups(t), [t]);
|
|
@@ -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 } 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": ";AAoGU;AAlGV,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,sBAAsB;AACxC,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiBQ,SAAR,iBAAkC,EAAE,OAAO,GAA+B;AAC/E,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAA6C,IAAI;AAC7F,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,QAAM,EAAE,kBAAkB,mBAAmB,IAAI,MAAM;AAAA,IACrD,MAAM,0BAA0B,OAAO;AAAA,IACvC,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,MAAM;AACpB,mBAAe,gBAAgB;AAC7B,UAAI;AACF,cAAM,EAAE,OAAO,IAAI,MAAM;AAAA,UACvB,0BAA0B,OAAO,EAAE;AAAA,QACrC;AAEA,cAAM,WAAW,QAAQ,QAAQ,CAAC;AAClC,YAAI,UAAU;AACZ,uBAAa,SAAS,SAAS;AAE/B,yBAAe;AAAA,YACb,MAAM,SAAS;AAAA,YACf,aAAa,SAAS,eAAe;AAAA,YACrC,WAAW,SAAS;AAAA,YACpB,cAAc,SAAS;AAAA,YACvB,eAAe,SAAS;AAAA,YACxB,UAAU,SAAS;AAAA,YACnB,YAAY,SAAS;AAAA,YACrB,aAAa,SAAS,eAAe;AAAA,YACrC,eAAe,SAAS,iBAAiB;AAAA,YACzC,eAAgB,SAAS,iBAAiB,OAAO,KAAK,SAAS,aAAa,EAAE,SAAS,IACnF,SAAS,gBACT;AAAA,YACJ,WAAW,SAAS;AAAA,UACtB,CAAC;AAAA,QACH;AAAA,MACF,SAAS,KAAK;AACZ,iBAAS,EAAE,+BAA+B,yBAAyB,CAAC;AAAA,MACtE,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AAEA,kBAAc;AAAA,EAChB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;AAEjB,QAAM,aAAa,MAAM,QAAQ,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC;AAErE,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,mBAAmB,GAAG,EAAE,kBAAkB,oBAAoB,oBAAoB,CAAC;AAAA,
|
|
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 }, { lockScope: true }),\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
|
}
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Für Cron: verwenden Sie Cron-Ausdruck (z.B. \"0 0 * * *\"). Für Intervall: verwenden Sie Format wie \"15m\", \"2h\", \"1d\" (s=Sekunden, m=Minuten, h=Stunden, d=Tage)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "z.B. 0 */6 * * * oder 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Bereich",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Der Bereich wird bei der Erstellung des Zeitplans festgelegt und kann danach nicht mehr geändert werden.",
|
|
102
103
|
"scheduler.form.submit": "Zeitplan erstellen",
|
|
103
104
|
"scheduler.form.target_command": "Zielbefehl",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Befehle suchen...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"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)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "e.g. 0 */6 * * * or 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Scope",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Scope is set when the schedule is created and cannot be changed afterwards.",
|
|
102
103
|
"scheduler.form.submit": "Create Schedule",
|
|
103
104
|
"scheduler.form.target_command": "Target Command",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Search commands...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Para cron: use expresión cron (ej. \"0 0 * * *\"). Para intervalo: use formato como \"15m\", \"2h\", \"1d\" (s=segundos, m=minutos, h=horas, d=días)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "ej. 0 */6 * * * o 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Ámbito",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "El ámbito se establece al crear la programación y no se puede cambiar posteriormente.",
|
|
102
103
|
"scheduler.form.submit": "Crear programación",
|
|
103
104
|
"scheduler.form.target_command": "Comando de destino",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Buscar comandos...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Dla cron: użyj wyrażenia cron (np. \"0 0 * * *\"). Dla interwału: użyj formatu jak \"15m\", \"2h\", \"1d\" (s=sekundy, m=minuty, h=godziny, d=dni)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "np. 0 */6 * * * lub 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Zakres",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Zakres jest ustalany podczas tworzenia harmonogramu i nie można go później zmienić.",
|
|
102
103
|
"scheduler.form.submit": "Utwórz harmonogram",
|
|
103
104
|
"scheduler.form.target_command": "Polecenie docelowe",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Szukaj poleceń...",
|
|
@@ -85,7 +85,8 @@ function scheduledJobFormSchema(t) {
|
|
|
85
85
|
isEnabled: z.boolean()
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
|
-
function scheduledJobFields(t, loaders) {
|
|
88
|
+
function scheduledJobFields(t, loaders, options) {
|
|
89
|
+
const lockScope = options?.lockScope ?? false;
|
|
89
90
|
return [
|
|
90
91
|
{
|
|
91
92
|
id: "name",
|
|
@@ -103,6 +104,12 @@ function scheduledJobFields(t, loaders) {
|
|
|
103
104
|
type: "select",
|
|
104
105
|
label: t("scheduler.form.scope_type", "Scope"),
|
|
105
106
|
required: true,
|
|
107
|
+
// Scope is derived from the creator's auth context at create time and is
|
|
108
|
+
// immutable afterwards (the update schema/command never persist it). Lock
|
|
109
|
+
// the field on edit so it does not deceptively accept input that is then
|
|
110
|
+
// silently dropped on save.
|
|
111
|
+
disabled: lockScope,
|
|
112
|
+
description: lockScope ? t("scheduler.form.scope_type.locked_description", "Scope is set when the schedule is created and cannot be changed afterwards.") : void 0,
|
|
106
113
|
options: [
|
|
107
114
|
{ value: "system", label: t("scheduler.scope.system", "System") },
|
|
108
115
|
{ value: "organization", label: t("scheduler.scope.organization", "Organization") },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/scheduler/lib/scheduledJobFormConfig.tsx"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { z } from 'zod'\nimport { ComboboxInput, type ComboboxOption } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\nimport type { CrudField, CrudFormGroup, CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type 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\nexport type TargetOptions = {\n queues: ComboboxOption[]\n commands: ComboboxOption[]\n}\n\n// ---------------------------------------------------------------------------\n// Components\n// ---------------------------------------------------------------------------\n\nexport function PayloadJsonEditor({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n return (\n <JsonBuilder\n value={value || {}}\n onChange={setValue}\n disabled={disabled}\n />\n )\n}\n\nexport function ScheduledJobEnabledSwitch({\n isEnabled,\n setIsEnabled,\n t,\n}: {\n isEnabled: boolean\n setIsEnabled: (value: boolean) => void\n t: (key: string, fallback: string) => string\n}) {\n return (\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}\n\n// ---------------------------------------------------------------------------\n// Option loaders\n// ---------------------------------------------------------------------------\n\nexport function createTargetOptionsLoader(\n apiCallFn: <T>(url: string, init?: RequestInit, options?: Record<string, unknown>) => Promise<{ result: T | null }>\n) {\n const targetOptionsRef = { current: null as TargetOptions | null }\n\n async function loadTargetOptions(): Promise<TargetOptions> {\n if (targetOptionsRef.current) return targetOptionsRef.current\n try {\n const { result } = await apiCallFn<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 async function loadQueueOptions(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 }\n\n async function loadCommandOptions(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 }\n\n return { loadTargetOptions, loadQueueOptions, loadCommandOptions, targetOptionsRef }\n}\n\nexport async function loadTimezoneOptions(query?: string): Promise<ComboboxOption[]> {\n try {\n const allTz = Array.from(new Set(['UTC', ...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// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobFormSchema(t: (key: string, fallback: string) => string) {\n return 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}\n\n// ---------------------------------------------------------------------------\n// Fields\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobFields(\n t: (key: string, fallback: string) => string,\n loaders: {\n loadQueueOptions: (query?: string) => Promise<ComboboxOption[]>\n loadCommandOptions: (query?: string) => Promise<ComboboxOption[]>\n loadTimezoneOptions: (query?: string) => Promise<ComboboxOption[]>\n }\n): CrudField[] {\n return [\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: loaders.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 <span className=\"text-status-error-icon ml-0.5\" aria-hidden=\"true\">*</span>\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={loaders.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 <span className=\"text-status-error-icon ml-0.5\" aria-hidden=\"true\">*</span>\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={loaders.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}\n\n// ---------------------------------------------------------------------------\n// Groups\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobGroups(t: (key: string, fallback: string) => string): CrudFormGroup[] {\n return [\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}\n\n// ---------------------------------------------------------------------------\n// Payload builder\n// ---------------------------------------------------------------------------\n\nexport function buildScheduledJobPayload(\n values: ScheduleFormValues,\n isEnabled: boolean\n): Record<string, unknown> {\n const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0\n ? values.targetPayload\n : null\n\n return {\n ...values,\n targetPayload,\n isEnabled,\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { z } from 'zod'\nimport { ComboboxInput, type ComboboxOption } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { JsonBuilder } from '@open-mercato/ui/backend/JsonBuilder'\nimport type { CrudField, CrudFormGroup, CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type 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\nexport type TargetOptions = {\n queues: ComboboxOption[]\n commands: ComboboxOption[]\n}\n\n// ---------------------------------------------------------------------------\n// Components\n// ---------------------------------------------------------------------------\n\nexport function PayloadJsonEditor({ value, setValue, disabled }: CrudCustomFieldRenderProps) {\n return (\n <JsonBuilder\n value={value || {}}\n onChange={setValue}\n disabled={disabled}\n />\n )\n}\n\nexport function ScheduledJobEnabledSwitch({\n isEnabled,\n setIsEnabled,\n t,\n}: {\n isEnabled: boolean\n setIsEnabled: (value: boolean) => void\n t: (key: string, fallback: string) => string\n}) {\n return (\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}\n\n// ---------------------------------------------------------------------------\n// Option loaders\n// ---------------------------------------------------------------------------\n\nexport function createTargetOptionsLoader(\n apiCallFn: <T>(url: string, init?: RequestInit, options?: Record<string, unknown>) => Promise<{ result: T | null }>\n) {\n const targetOptionsRef = { current: null as TargetOptions | null }\n\n async function loadTargetOptions(): Promise<TargetOptions> {\n if (targetOptionsRef.current) return targetOptionsRef.current\n try {\n const { result } = await apiCallFn<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 async function loadQueueOptions(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 }\n\n async function loadCommandOptions(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 }\n\n return { loadTargetOptions, loadQueueOptions, loadCommandOptions, targetOptionsRef }\n}\n\nexport async function loadTimezoneOptions(query?: string): Promise<ComboboxOption[]> {\n try {\n const allTz = Array.from(new Set(['UTC', ...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// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobFormSchema(t: (key: string, fallback: string) => string) {\n return 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}\n\n// ---------------------------------------------------------------------------\n// Fields\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobFields(\n t: (key: string, fallback: string) => string,\n loaders: {\n loadQueueOptions: (query?: string) => Promise<ComboboxOption[]>\n loadCommandOptions: (query?: string) => Promise<ComboboxOption[]>\n loadTimezoneOptions: (query?: string) => Promise<ComboboxOption[]>\n },\n options?: { lockScope?: boolean }\n): CrudField[] {\n const lockScope = options?.lockScope ?? false\n return [\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 // Scope is derived from the creator's auth context at create time and is\n // immutable afterwards (the update schema/command never persist it). Lock\n // the field on edit so it does not deceptively accept input that is then\n // silently dropped on save.\n disabled: lockScope,\n description: lockScope\n ? t('scheduler.form.scope_type.locked_description', 'Scope is set when the schedule is created and cannot be changed afterwards.')\n : undefined,\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: loaders.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 <span className=\"text-status-error-icon ml-0.5\" aria-hidden=\"true\">*</span>\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={loaders.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 <span className=\"text-status-error-icon ml-0.5\" aria-hidden=\"true\">*</span>\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={loaders.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}\n\n// ---------------------------------------------------------------------------\n// Groups\n// ---------------------------------------------------------------------------\n\nexport function scheduledJobGroups(t: (key: string, fallback: string) => string): CrudFormGroup[] {\n return [\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}\n\n// ---------------------------------------------------------------------------\n// Payload builder\n// ---------------------------------------------------------------------------\n\nexport function buildScheduledJobPayload(\n values: ScheduleFormValues,\n isEnabled: boolean\n): Record<string, unknown> {\n const targetPayload = values.targetPayload && Object.keys(values.targetPayload).length > 0\n ? values.targetPayload\n : null\n\n return {\n ...values,\n targetPayload,\n isEnabled,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/scheduler",
|
|
3
|
-
"version": "0.6.5-develop.
|
|
3
|
+
"version": "0.6.5-develop.4544.1.71c003c861",
|
|
4
4
|
"description": "Database-managed scheduled jobs with admin UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
"./*/*/*/*/*/*.json": "./src/*/*/*/*/*/*.json"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@open-mercato/events": "0.6.5-develop.
|
|
62
|
-
"@open-mercato/queue": "0.6.5-develop.
|
|
61
|
+
"@open-mercato/events": "0.6.5-develop.4544.1.71c003c861",
|
|
62
|
+
"@open-mercato/queue": "0.6.5-develop.4544.1.71c003c861",
|
|
63
63
|
"cron-parser": "^5.5.0"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
66
|
"@mikro-orm/core": "^7.0.14",
|
|
67
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
67
|
+
"@open-mercato/shared": "0.6.5-develop.4544.1.71c003c861",
|
|
68
68
|
"bullmq": "^5.0.0",
|
|
69
69
|
"ioredis": "^5.0.0",
|
|
70
70
|
"zod": ">=3.23.0"
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
|
-
"@open-mercato/shared": "0.6.5-develop.
|
|
81
|
+
"@open-mercato/shared": "0.6.5-develop.4544.1.71c003c861",
|
|
82
82
|
"@types/jest": "^30.0.0",
|
|
83
83
|
"@types/node": "^25.9.1",
|
|
84
84
|
"jest": "^30.4.2",
|
|
@@ -88,7 +88,7 @@ export default function EditSchedulePage({ params }: { params: { id: string } })
|
|
|
88
88
|
const formSchema = React.useMemo(() => scheduledJobFormSchema(t), [t])
|
|
89
89
|
|
|
90
90
|
const fields = React.useMemo(
|
|
91
|
-
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }),
|
|
91
|
+
() => scheduledJobFields(t, { loadQueueOptions, loadCommandOptions, loadTimezoneOptions }, { lockScope: true }),
|
|
92
92
|
[t, loadQueueOptions, loadCommandOptions]
|
|
93
93
|
)
|
|
94
94
|
|
|
@@ -506,6 +506,23 @@ describe('scheduleUpdateSchema', () => {
|
|
|
506
506
|
|
|
507
507
|
expect(result.targetPayload).toBeNull()
|
|
508
508
|
})
|
|
509
|
+
|
|
510
|
+
it('should strip scope fields so scope stays immutable after creation', () => {
|
|
511
|
+
const result = scheduleUpdateSchema.parse({
|
|
512
|
+
id: scheduleId,
|
|
513
|
+
name: 'Updated name',
|
|
514
|
+
scopeType: 'organization',
|
|
515
|
+
organizationId,
|
|
516
|
+
tenantId,
|
|
517
|
+
})
|
|
518
|
+
|
|
519
|
+
// Scope is derived from the creator's auth context at create time and must
|
|
520
|
+
// not be mutable via update. The edit form locks the Scope field to mirror
|
|
521
|
+
// this contract (see scheduledJobFormConfig lockScope).
|
|
522
|
+
expect((result as Record<string, unknown>).scopeType).toBeUndefined()
|
|
523
|
+
expect((result as Record<string, unknown>).organizationId).toBeUndefined()
|
|
524
|
+
expect((result as Record<string, unknown>).tenantId).toBeUndefined()
|
|
525
|
+
})
|
|
509
526
|
})
|
|
510
527
|
|
|
511
528
|
describe('scheduleDeleteSchema', () => {
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Für Cron: verwenden Sie Cron-Ausdruck (z.B. \"0 0 * * *\"). Für Intervall: verwenden Sie Format wie \"15m\", \"2h\", \"1d\" (s=Sekunden, m=Minuten, h=Stunden, d=Tage)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "z.B. 0 */6 * * * oder 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Bereich",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Der Bereich wird bei der Erstellung des Zeitplans festgelegt und kann danach nicht mehr geändert werden.",
|
|
102
103
|
"scheduler.form.submit": "Zeitplan erstellen",
|
|
103
104
|
"scheduler.form.target_command": "Zielbefehl",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Befehle suchen...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"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)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "e.g. 0 */6 * * * or 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Scope",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Scope is set when the schedule is created and cannot be changed afterwards.",
|
|
102
103
|
"scheduler.form.submit": "Create Schedule",
|
|
103
104
|
"scheduler.form.target_command": "Target Command",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Search commands...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Para cron: use expresión cron (ej. \"0 0 * * *\"). Para intervalo: use formato como \"15m\", \"2h\", \"1d\" (s=segundos, m=minutos, h=horas, d=días)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "ej. 0 */6 * * * o 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Ámbito",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "El ámbito se establece al crear la programación y no se puede cambiar posteriormente.",
|
|
102
103
|
"scheduler.form.submit": "Crear programación",
|
|
103
104
|
"scheduler.form.target_command": "Comando de destino",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Buscar comandos...",
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"scheduler.form.schedule_value.description": "Dla cron: użyj wyrażenia cron (np. \"0 0 * * *\"). Dla interwału: użyj formatu jak \"15m\", \"2h\", \"1d\" (s=sekundy, m=minuty, h=godziny, d=dni)",
|
|
100
100
|
"scheduler.form.schedule_value.placeholder": "np. 0 */6 * * * lub 15m",
|
|
101
101
|
"scheduler.form.scope_type": "Zakres",
|
|
102
|
+
"scheduler.form.scope_type.locked_description": "Zakres jest ustalany podczas tworzenia harmonogramu i nie można go później zmienić.",
|
|
102
103
|
"scheduler.form.submit": "Utwórz harmonogram",
|
|
103
104
|
"scheduler.form.target_command": "Polecenie docelowe",
|
|
104
105
|
"scheduler.form.target_command.placeholder": "Szukaj poleceń...",
|
|
@@ -83,6 +83,30 @@ describe('scheduledJobFormConfig target fields', () => {
|
|
|
83
83
|
})
|
|
84
84
|
})
|
|
85
85
|
|
|
86
|
+
describe('scheduledJobFormConfig scope field', () => {
|
|
87
|
+
const t = (_key: string, fallback: string) => fallback
|
|
88
|
+
const loaders = {
|
|
89
|
+
loadQueueOptions: async () => [],
|
|
90
|
+
loadCommandOptions: async () => [],
|
|
91
|
+
loadTimezoneOptions: async () => [],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
it('keeps the scope field editable on create (no lockScope)', () => {
|
|
95
|
+
const fields = scheduledJobFields(t, loaders)
|
|
96
|
+
const scope = fields.find((f) => f.id === 'scopeType')
|
|
97
|
+
expect(scope).toBeDefined()
|
|
98
|
+
expect(scope?.disabled).toBeFalsy()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('locks the scope field on edit so the value cannot be deceptively changed then silently dropped', () => {
|
|
102
|
+
const fields = scheduledJobFields(t, loaders, { lockScope: true })
|
|
103
|
+
const scope = fields.find((f) => f.id === 'scopeType')
|
|
104
|
+
expect(scope).toBeDefined()
|
|
105
|
+
expect(scope?.disabled).toBe(true)
|
|
106
|
+
expect(scope?.description).toBeTruthy()
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
86
110
|
describe('loadTimezoneOptions', () => {
|
|
87
111
|
it('includes UTC even when Intl.supportedValuesOf does not list it', async () => {
|
|
88
112
|
const options = await loadTimezoneOptions('utc')
|
|
@@ -149,8 +149,10 @@ export function scheduledJobFields(
|
|
|
149
149
|
loadQueueOptions: (query?: string) => Promise<ComboboxOption[]>
|
|
150
150
|
loadCommandOptions: (query?: string) => Promise<ComboboxOption[]>
|
|
151
151
|
loadTimezoneOptions: (query?: string) => Promise<ComboboxOption[]>
|
|
152
|
-
}
|
|
152
|
+
},
|
|
153
|
+
options?: { lockScope?: boolean }
|
|
153
154
|
): CrudField[] {
|
|
155
|
+
const lockScope = options?.lockScope ?? false
|
|
154
156
|
return [
|
|
155
157
|
{
|
|
156
158
|
id: 'name',
|
|
@@ -168,6 +170,14 @@ export function scheduledJobFields(
|
|
|
168
170
|
type: 'select',
|
|
169
171
|
label: t('scheduler.form.scope_type', 'Scope'),
|
|
170
172
|
required: true,
|
|
173
|
+
// Scope is derived from the creator's auth context at create time and is
|
|
174
|
+
// immutable afterwards (the update schema/command never persist it). Lock
|
|
175
|
+
// the field on edit so it does not deceptively accept input that is then
|
|
176
|
+
// silently dropped on save.
|
|
177
|
+
disabled: lockScope,
|
|
178
|
+
description: lockScope
|
|
179
|
+
? t('scheduler.form.scope_type.locked_description', 'Scope is set when the schedule is created and cannot be changed afterwards.')
|
|
180
|
+
: undefined,
|
|
171
181
|
options: [
|
|
172
182
|
{ value: 'system', label: t('scheduler.scope.system', 'System') },
|
|
173
183
|
{ value: 'organization', label: t('scheduler.scope.organization', 'Organization') },
|