@open-mercato/scheduler 0.7.1-develop.7106.1.53fe9dd1b1 → 0.7.1-develop.7112.1.d486e6f1b2

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { useParams, useRouter } from "next/navigation";
4
+ import { useRouter } from "next/navigation";
5
5
  import { Page, PageBody, PageHeader } from "@open-mercato/ui/backend/Page";
6
6
  import { Button } from "@open-mercato/ui/primitives/button";
7
7
  import { Badge } from "@open-mercato/ui/primitives/badge";
@@ -17,12 +17,11 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
17
17
  import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
18
18
  import { useT } from "@open-mercato/shared/lib/i18n/context";
19
19
  import { formatDistanceToNow } from "date-fns";
20
- function ScheduleDetailPage() {
21
- const params = useParams();
20
+ function ScheduleDetailPage({ params }) {
22
21
  const router = useRouter();
23
22
  const t = useT();
24
23
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
25
- const scheduleId = params.id ? Array.isArray(params.id) ? params.id[0] : params.id : Array.isArray(params.slug) && params.slug.length >= 3 ? params.slug[2] : void 0;
24
+ const scheduleId = params?.id;
26
25
  const [schedule, setSchedule] = React.useState(null);
27
26
  const [runs, setRuns] = React.useState([]);
28
27
  const [loading, setLoading] = React.useState(true);
@@ -65,10 +64,13 @@ function ScheduleDetailPage() {
65
64
  }
66
65
  }, [scheduleId, isAsyncStrategy]);
67
66
  React.useEffect(() => {
68
- if (scheduleId) {
69
- fetchScheduleAndRuns();
67
+ if (!scheduleId) {
68
+ setLoading(false);
69
+ setError(t("scheduler.error.missing_schedule_id", "No schedule selected."));
70
+ return;
70
71
  }
71
- }, [scheduleId, fetchScheduleAndRuns]);
72
+ fetchScheduleAndRuns();
73
+ }, [scheduleId, fetchScheduleAndRuns, t]);
72
74
  const handleTriggerNow = async () => {
73
75
  if (!scheduleId || !schedule) return;
74
76
  const targetTypeLabel = schedule.targetType === "queue" ? t("scheduler.target.queue", "queue job") : t("scheduler.target.command", "command");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/scheduler/backend/config/scheduled-jobs/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useParams, useRouter } from 'next/navigation'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Card, CardContent, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { JobLogsModal } from '../../../../components/JobLogsModal'\nimport { ExecutionDetailsDialog } from '../../../../components/ExecutionDetailsDialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { formatDistanceToNow } from 'date-fns'\n\ntype ScheduleDetail = {\n id: string\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 requireFeature?: string\n isEnabled: boolean\n lastRunAt?: string\n nextRunAt?: string\n sourceType: 'user' | 'module'\n sourceModule?: string\n}\n\ntype ExecutionRun = {\n id: string\n startedAt: string\n finishedAt?: string\n status: 'running' | 'completed' | 'failed' | 'skipped'\n triggerType: 'scheduled' | 'manual'\n queueJobId?: string\n queueName?: string\n errorMessage?: string\n errorStack?: string\n durationMs?: number\n}\n\nexport default function ScheduleDetailPage() {\n const params = useParams()\n const router = useRouter()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n // Extract schedule ID from either params.id or params.slug\n // When using catch-all routes, the ID is in params.slug[2]\n const scheduleId = params.id \n ? (Array.isArray(params.id) ? params.id[0] : params.id)\n : (Array.isArray(params.slug) && params.slug.length >= 3 ? params.slug[2] : undefined)\n\n const [schedule, setSchedule] = React.useState<ScheduleDetail | null>(null)\n const [runs, setRuns] = React.useState<ExecutionRun[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [logsModalOpen, setLogsModalOpen] = React.useState(false)\n const [detailsModalOpen, setDetailsModalOpen] = React.useState(false)\n const [selectedRun, setSelectedRun] = React.useState<ExecutionRun | null>(null)\n const [triggering, setTriggering] = React.useState(false)\n const [toggling, setToggling] = React.useState(false)\n\n const queueStrategy = process.env.NEXT_PUBLIC_QUEUE_STRATEGY || 'local'\n const isAsyncStrategy = queueStrategy === 'async'\n\n const fetchScheduleAndRuns = React.useCallback(async () => {\n if (!scheduleId) return\n try {\n // Fetch schedule details via list API with ID filter\n const { result: listData } = await apiCallOrThrow(\n `/api/scheduler/jobs?id=${scheduleId}&page=1&pageSize=1`\n )\n const schedules = (listData as { items?: unknown[] })?.items || []\n if (schedules.length === 0) {\n setIsNotFound(true)\n setLoading(false)\n return\n }\n setSchedule(schedules[0] as ScheduleDetail)\n\n // Fetch recent runs \u2014 only available with async strategy\n if (isAsyncStrategy) {\n try {\n const { result: runsData } = await apiCallOrThrow(\n `/api/scheduler/jobs/${scheduleId}/executions?pageSize=10`\n )\n setRuns((runsData as { items?: ExecutionRun[] }).items || [])\n } catch {\n setRuns([])\n }\n }\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }, [scheduleId, isAsyncStrategy])\n\n React.useEffect(() => {\n if (scheduleId) {\n fetchScheduleAndRuns()\n }\n }, [scheduleId, fetchScheduleAndRuns])\n\n const handleTriggerNow = async () => {\n if (!scheduleId || !schedule) return\n \n // Confirm before triggering\n const targetTypeLabel =\n schedule.targetType === 'queue'\n ? t('scheduler.target.queue', 'queue job')\n : t('scheduler.target.command', 'command')\n const triggerConfirmText = t(\n 'scheduler.confirm.trigger',\n 'Are you sure you want to trigger \"{name}\" now?\\n\\nThis will execute the {targetType} immediately.',\n { name: schedule.name, targetType: targetTypeLabel }\n )\n const confirmed = await confirm({\n title: t('scheduler.action.trigger', 'Trigger Now'),\n text: triggerConfirmText,\n })\n \n if (!confirmed) return\n \n setTriggering(true)\n try {\n await apiCallOrThrow(`/api/scheduler/trigger`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: scheduleId }),\n })\n flash(t('scheduler.success.triggered', 'Schedule triggered successfully'), 'success')\n await fetchScheduleAndRuns()\n } catch (err: unknown) {\n flash(err instanceof Error ? err.message : t('scheduler.error.trigger_failed', 'Failed to trigger schedule'), 'error')\n } finally {\n setTriggering(false)\n }\n }\n\n const handleToggleEnabled = async (enabled: boolean) => {\n if (!schedule || !scheduleId) return\n setToggling(true)\n try {\n await apiCallOrThrow(`/api/scheduler/jobs`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: scheduleId, isEnabled: enabled }),\n })\n flash(enabled ? t('scheduler.success.enabled', 'Schedule enabled') : t('scheduler.success.disabled', 'Schedule disabled'), 'success')\n await fetchScheduleAndRuns()\n } catch (err: unknown) {\n flash(err instanceof Error ? err.message : t('scheduler.error.update_failed', 'Failed to update schedule'), 'error')\n } finally {\n setToggling(false)\n }\n }\n\n const handleViewLogs = (run: ExecutionRun) => {\n setSelectedRun(run)\n setLogsModalOpen(true)\n }\n\n const handleViewDetails = (run: ExecutionRun) => {\n setSelectedRun(run)\n setDetailsModalOpen(true)\n }\n\n const runsColumns: ColumnDef<ExecutionRun>[] = [\n {\n accessorKey: 'startedAt',\n header: t('scheduler.execution.started', 'Started'),\n cell: ({ row }) => (\n <div>\n <div className=\"text-sm\">{new Date(row.original.startedAt).toLocaleString()}</div>\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(row.original.startedAt), { addSuffix: true })}\n </div>\n </div>\n ),\n },\n {\n accessorKey: 'status',\n header: t('scheduler.execution.status', 'Status'),\n cell: ({ row }) => (\n <Badge variant={\n row.original.status === 'completed' ? 'default' :\n row.original.status === 'failed' ? 'destructive' :\n row.original.status === 'skipped' ? 'secondary' : 'outline'\n }>\n {t(`scheduler.execution_status.${row.original.status}`, row.original.status)}\n </Badge>\n ),\n },\n {\n accessorKey: 'durationMs',\n header: t('scheduler.execution.duration', 'Duration'),\n cell: ({ row }) => \n row.original.durationMs \n ? `${(row.original.durationMs / 1000).toFixed(2)}s` \n : '\u2014',\n },\n {\n accessorKey: 'triggerType',\n header: t('scheduler.execution.trigger', 'Trigger'),\n cell: ({ row }) => t(`scheduler.trigger_type.${row.original.triggerType}`, row.original.triggerType),\n },\n {\n id: 'actions',\n header: t('scheduler.execution.actions', 'Actions'),\n cell: ({ row }) => {\n const canViewLogs = isAsyncStrategy && \n row.original.queueJobId && \n row.original.queueName\n const hasDetails = row.original.errorMessage || row.original.finishedAt\n\n return (\n <div className=\"flex items-center gap-2\">\n {hasDetails && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => handleViewDetails(row.original)}\n >\n {t('scheduler.execution.view_details', 'View Details')}\n </Button>\n )}\n {canViewLogs && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => handleViewLogs(row.original)}\n >\n {t('scheduler.execution.view_logs', 'View Logs')}\n </Button>\n )}\n {!hasDetails && !canViewLogs && (\n <span className=\"text-sm text-muted-foreground\">\u2014</span>\n )}\n </div>\n )\n },\n },\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 if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('scheduler.error.not_found', 'Schedule not found')}\n backHref=\"/backend/config/scheduled-jobs\"\n backLabel={t('scheduler.nav.backToList', 'Back to scheduled jobs')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('scheduler.details.error', 'Error')} description={error} />\n </PageBody>\n </Page>\n )\n }\n if (!schedule) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('scheduler.details.not_found', 'Not Found')} description={t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n {ConfirmDialogElement}\n <PageHeader\n title={schedule.name}\n description={schedule.description}\n actions={\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"schedule-enabled\" className=\"text-sm font-medium cursor-pointer\">\n {schedule.isEnabled ? t('scheduler.status.enabled', 'Enabled') : t('scheduler.status.disabled', 'Disabled')}\n </Label>\n <Switch\n id=\"schedule-enabled\"\n checked={schedule.isEnabled}\n onCheckedChange={handleToggleEnabled}\n disabled={toggling}\n />\n </div>\n <Button\n variant=\"outline\"\n onClick={handleTriggerNow}\n disabled={triggering || !schedule.isEnabled}\n >\n {triggering ? t('scheduler.details.triggering', 'Triggering...') : t('scheduler.action.trigger', 'Trigger Now')}\n </Button>\n <Button onClick={() => router.push(`/backend/config/scheduled-jobs/${scheduleId}/edit`)}>\n {t('scheduler.action.edit', 'Edit')}\n </Button>\n </div>\n }\n />\n\n <PageBody>\n <div className=\"space-y-6\">\n {/* Configuration Card */}\n <Card>\n <CardHeader>\n <CardTitle>{t('scheduler.details.configuration', 'Configuration')}</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"grid grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.status', 'Status')}</dt>\n <dd className=\"mt-1\">\n <Badge variant={schedule.isEnabled ? 'default' : 'secondary'}>\n {schedule.isEnabled ? t('scheduler.status.enabled', 'Enabled') : t('scheduler.status.disabled', 'Disabled')}\n </Badge>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.schedule', 'Schedule')}</dt>\n <dd className=\"text-sm mt-1\">\n {schedule.scheduleValue} \n <span className=\"text-muted-foreground ml-1\">({schedule.scheduleType})</span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.timezone', 'Timezone')}</dt>\n <dd className=\"text-sm mt-1\">{schedule.timezone}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.target', 'Target')}</dt>\n <dd className=\"text-sm mt-1\">\n <Badge variant=\"outline\">\n {schedule.targetType === 'queue' ? schedule.targetQueue : schedule.targetCommand}\n </Badge>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.scope', 'Scope')}</dt>\n <dd className=\"text-sm mt-1\">{schedule.scopeType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.source', 'Source')}</dt>\n <dd className=\"text-sm mt-1\">\n {schedule.sourceType === 'module' && schedule.sourceModule \n ? `${schedule.sourceModule} (${t('scheduler.details.source_module', 'module')})` \n : t('scheduler.details.source_user', 'User-created')}\n </dd>\n </div>\n {schedule.nextRunAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.next_run', 'Next Run')}</dt>\n <dd className=\"text-sm mt-1\">\n {new Date(schedule.nextRunAt).toLocaleString()}\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(schedule.nextRunAt), { addSuffix: true })}\n </div>\n </dd>\n </div>\n )}\n {schedule.lastRunAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.last_run', 'Last Run')}</dt>\n <dd className=\"text-sm mt-1\">\n {new Date(schedule.lastRunAt).toLocaleString()}\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(schedule.lastRunAt), { addSuffix: true })}\n </div>\n </dd>\n </div>\n )}\n {schedule.requireFeature && (\n <div className=\"col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.required_feature', 'Required Feature')}</dt>\n <dd className=\"text-sm mt-1\">\n <Badge variant=\"outline\">{schedule.requireFeature}</Badge>\n </dd>\n </div>\n )}\n {schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0 && (\n <div className=\"col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.payload', 'Payload')}</dt>\n <dd className=\"mt-1\">\n <pre className=\"bg-muted p-2 rounded text-xs overflow-auto max-h-32\">\n {JSON.stringify(schedule.targetPayload, null, 2)}\n </pre>\n </dd>\n </div>\n )}\n </dl>\n </CardContent>\n </Card>\n\n {/* Recent Executions Card */}\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('scheduler.details.recent_executions', 'Recent Executions')}</CardTitle>\n </div>\n </CardHeader>\n <CardContent>\n {!isAsyncStrategy ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('scheduler.details.executions_async_only', 'Execution history and manual triggers require async queue strategy (QUEUE_STRATEGY=async).')}\n </p>\n ) : runs.length > 0 ? (\n <DataTable\n columns={runsColumns}\n data={runs}\n />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('scheduler.details.no_executions', 'No executions yet')}</p>\n )}\n </CardContent>\n </Card>\n </div>\n </PageBody>\n\n <ExecutionDetailsDialog\n open={detailsModalOpen}\n onClose={() => setDetailsModalOpen(false)}\n run={selectedRun}\n scheduleName={schedule.name}\n />\n\n <JobLogsModal\n open={logsModalOpen}\n onClose={() => setLogsModalOpen(false)}\n queueJobId={selectedRun?.queueJobId || null}\n queueName={selectedRun?.queueName || null}\n scheduleName={schedule.name}\n />\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Card, CardContent, CardHeader, CardTitle } from '@open-mercato/ui/primitives/card'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { JobLogsModal } from '../../../../components/JobLogsModal'\nimport { ExecutionDetailsDialog } from '../../../../components/ExecutionDetailsDialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { formatDistanceToNow } from 'date-fns'\n\ntype ScheduleDetail = {\n id: string\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 requireFeature?: string\n isEnabled: boolean\n lastRunAt?: string\n nextRunAt?: string\n sourceType: 'user' | 'module'\n sourceModule?: string\n}\n\ntype ExecutionRun = {\n id: string\n startedAt: string\n finishedAt?: string\n status: 'running' | 'completed' | 'failed' | 'skipped'\n triggerType: 'scheduled' | 'manual'\n queueJobId?: string\n queueName?: string\n errorMessage?: string\n errorStack?: string\n durationMs?: number\n}\n\nexport default function ScheduleDetailPage({ params }: { params?: { id?: string } }) {\n const router = useRouter()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scheduleId = params?.id\n\n const [schedule, setSchedule] = React.useState<ScheduleDetail | null>(null)\n const [runs, setRuns] = React.useState<ExecutionRun[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [logsModalOpen, setLogsModalOpen] = React.useState(false)\n const [detailsModalOpen, setDetailsModalOpen] = React.useState(false)\n const [selectedRun, setSelectedRun] = React.useState<ExecutionRun | null>(null)\n const [triggering, setTriggering] = React.useState(false)\n const [toggling, setToggling] = React.useState(false)\n\n const queueStrategy = process.env.NEXT_PUBLIC_QUEUE_STRATEGY || 'local'\n const isAsyncStrategy = queueStrategy === 'async'\n\n const fetchScheduleAndRuns = React.useCallback(async () => {\n if (!scheduleId) return\n try {\n // Fetch schedule details via list API with ID filter\n const { result: listData } = await apiCallOrThrow(\n `/api/scheduler/jobs?id=${scheduleId}&page=1&pageSize=1`\n )\n const schedules = (listData as { items?: unknown[] })?.items || []\n if (schedules.length === 0) {\n setIsNotFound(true)\n setLoading(false)\n return\n }\n setSchedule(schedules[0] as ScheduleDetail)\n\n // Fetch recent runs \u2014 only available with async strategy\n if (isAsyncStrategy) {\n try {\n const { result: runsData } = await apiCallOrThrow(\n `/api/scheduler/jobs/${scheduleId}/executions?pageSize=10`\n )\n setRuns((runsData as { items?: ExecutionRun[] }).items || [])\n } catch {\n setRuns([])\n }\n }\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : t('scheduler.error.load_failed', 'Failed to load schedule'))\n } finally {\n setLoading(false)\n }\n }, [scheduleId, isAsyncStrategy])\n\n React.useEffect(() => {\n if (!scheduleId) {\n setLoading(false)\n setError(t('scheduler.error.missing_schedule_id', 'No schedule selected.'))\n return\n }\n fetchScheduleAndRuns()\n }, [scheduleId, fetchScheduleAndRuns, t])\n\n const handleTriggerNow = async () => {\n if (!scheduleId || !schedule) return\n \n // Confirm before triggering\n const targetTypeLabel =\n schedule.targetType === 'queue'\n ? t('scheduler.target.queue', 'queue job')\n : t('scheduler.target.command', 'command')\n const triggerConfirmText = t(\n 'scheduler.confirm.trigger',\n 'Are you sure you want to trigger \"{name}\" now?\\n\\nThis will execute the {targetType} immediately.',\n { name: schedule.name, targetType: targetTypeLabel }\n )\n const confirmed = await confirm({\n title: t('scheduler.action.trigger', 'Trigger Now'),\n text: triggerConfirmText,\n })\n \n if (!confirmed) return\n \n setTriggering(true)\n try {\n await apiCallOrThrow(`/api/scheduler/trigger`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: scheduleId }),\n })\n flash(t('scheduler.success.triggered', 'Schedule triggered successfully'), 'success')\n await fetchScheduleAndRuns()\n } catch (err: unknown) {\n flash(err instanceof Error ? err.message : t('scheduler.error.trigger_failed', 'Failed to trigger schedule'), 'error')\n } finally {\n setTriggering(false)\n }\n }\n\n const handleToggleEnabled = async (enabled: boolean) => {\n if (!schedule || !scheduleId) return\n setToggling(true)\n try {\n await apiCallOrThrow(`/api/scheduler/jobs`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: scheduleId, isEnabled: enabled }),\n })\n flash(enabled ? t('scheduler.success.enabled', 'Schedule enabled') : t('scheduler.success.disabled', 'Schedule disabled'), 'success')\n await fetchScheduleAndRuns()\n } catch (err: unknown) {\n flash(err instanceof Error ? err.message : t('scheduler.error.update_failed', 'Failed to update schedule'), 'error')\n } finally {\n setToggling(false)\n }\n }\n\n const handleViewLogs = (run: ExecutionRun) => {\n setSelectedRun(run)\n setLogsModalOpen(true)\n }\n\n const handleViewDetails = (run: ExecutionRun) => {\n setSelectedRun(run)\n setDetailsModalOpen(true)\n }\n\n const runsColumns: ColumnDef<ExecutionRun>[] = [\n {\n accessorKey: 'startedAt',\n header: t('scheduler.execution.started', 'Started'),\n cell: ({ row }) => (\n <div>\n <div className=\"text-sm\">{new Date(row.original.startedAt).toLocaleString()}</div>\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(row.original.startedAt), { addSuffix: true })}\n </div>\n </div>\n ),\n },\n {\n accessorKey: 'status',\n header: t('scheduler.execution.status', 'Status'),\n cell: ({ row }) => (\n <Badge variant={\n row.original.status === 'completed' ? 'default' :\n row.original.status === 'failed' ? 'destructive' :\n row.original.status === 'skipped' ? 'secondary' : 'outline'\n }>\n {t(`scheduler.execution_status.${row.original.status}`, row.original.status)}\n </Badge>\n ),\n },\n {\n accessorKey: 'durationMs',\n header: t('scheduler.execution.duration', 'Duration'),\n cell: ({ row }) => \n row.original.durationMs \n ? `${(row.original.durationMs / 1000).toFixed(2)}s` \n : '\u2014',\n },\n {\n accessorKey: 'triggerType',\n header: t('scheduler.execution.trigger', 'Trigger'),\n cell: ({ row }) => t(`scheduler.trigger_type.${row.original.triggerType}`, row.original.triggerType),\n },\n {\n id: 'actions',\n header: t('scheduler.execution.actions', 'Actions'),\n cell: ({ row }) => {\n const canViewLogs = isAsyncStrategy && \n row.original.queueJobId && \n row.original.queueName\n const hasDetails = row.original.errorMessage || row.original.finishedAt\n\n return (\n <div className=\"flex items-center gap-2\">\n {hasDetails && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => handleViewDetails(row.original)}\n >\n {t('scheduler.execution.view_details', 'View Details')}\n </Button>\n )}\n {canViewLogs && (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => handleViewLogs(row.original)}\n >\n {t('scheduler.execution.view_logs', 'View Logs')}\n </Button>\n )}\n {!hasDetails && !canViewLogs && (\n <span className=\"text-sm text-muted-foreground\">\u2014</span>\n )}\n </div>\n )\n },\n },\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 if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('scheduler.error.not_found', 'Schedule not found')}\n backHref=\"/backend/config/scheduled-jobs\"\n backLabel={t('scheduler.nav.backToList', 'Back to scheduled jobs')}\n />\n </PageBody>\n </Page>\n )\n }\n if (error) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('scheduler.details.error', 'Error')} description={error} />\n </PageBody>\n </Page>\n )\n }\n if (!schedule) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={t('scheduler.details.not_found', 'Not Found')} description={t('scheduler.error.not_found', 'Schedule not found')} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n {ConfirmDialogElement}\n <PageHeader\n title={schedule.name}\n description={schedule.description}\n actions={\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <Label htmlFor=\"schedule-enabled\" className=\"text-sm font-medium cursor-pointer\">\n {schedule.isEnabled ? t('scheduler.status.enabled', 'Enabled') : t('scheduler.status.disabled', 'Disabled')}\n </Label>\n <Switch\n id=\"schedule-enabled\"\n checked={schedule.isEnabled}\n onCheckedChange={handleToggleEnabled}\n disabled={toggling}\n />\n </div>\n <Button\n variant=\"outline\"\n onClick={handleTriggerNow}\n disabled={triggering || !schedule.isEnabled}\n >\n {triggering ? t('scheduler.details.triggering', 'Triggering...') : t('scheduler.action.trigger', 'Trigger Now')}\n </Button>\n <Button onClick={() => router.push(`/backend/config/scheduled-jobs/${scheduleId}/edit`)}>\n {t('scheduler.action.edit', 'Edit')}\n </Button>\n </div>\n }\n />\n\n <PageBody>\n <div className=\"space-y-6\">\n {/* Configuration Card */}\n <Card>\n <CardHeader>\n <CardTitle>{t('scheduler.details.configuration', 'Configuration')}</CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"grid grid-cols-2 gap-4\">\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.status', 'Status')}</dt>\n <dd className=\"mt-1\">\n <Badge variant={schedule.isEnabled ? 'default' : 'secondary'}>\n {schedule.isEnabled ? t('scheduler.status.enabled', 'Enabled') : t('scheduler.status.disabled', 'Disabled')}\n </Badge>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.schedule', 'Schedule')}</dt>\n <dd className=\"text-sm mt-1\">\n {schedule.scheduleValue} \n <span className=\"text-muted-foreground ml-1\">({schedule.scheduleType})</span>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.timezone', 'Timezone')}</dt>\n <dd className=\"text-sm mt-1\">{schedule.timezone}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.target', 'Target')}</dt>\n <dd className=\"text-sm mt-1\">\n <Badge variant=\"outline\">\n {schedule.targetType === 'queue' ? schedule.targetQueue : schedule.targetCommand}\n </Badge>\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.scope', 'Scope')}</dt>\n <dd className=\"text-sm mt-1\">{schedule.scopeType}</dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.source', 'Source')}</dt>\n <dd className=\"text-sm mt-1\">\n {schedule.sourceType === 'module' && schedule.sourceModule \n ? `${schedule.sourceModule} (${t('scheduler.details.source_module', 'module')})` \n : t('scheduler.details.source_user', 'User-created')}\n </dd>\n </div>\n {schedule.nextRunAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.field.next_run', 'Next Run')}</dt>\n <dd className=\"text-sm mt-1\">\n {new Date(schedule.nextRunAt).toLocaleString()}\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(schedule.nextRunAt), { addSuffix: true })}\n </div>\n </dd>\n </div>\n )}\n {schedule.lastRunAt && (\n <div>\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.last_run', 'Last Run')}</dt>\n <dd className=\"text-sm mt-1\">\n {new Date(schedule.lastRunAt).toLocaleString()}\n <div className=\"text-xs text-muted-foreground\">\n {formatDistanceToNow(new Date(schedule.lastRunAt), { addSuffix: true })}\n </div>\n </dd>\n </div>\n )}\n {schedule.requireFeature && (\n <div className=\"col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.required_feature', 'Required Feature')}</dt>\n <dd className=\"text-sm mt-1\">\n <Badge variant=\"outline\">{schedule.requireFeature}</Badge>\n </dd>\n </div>\n )}\n {schedule.targetPayload && Object.keys(schedule.targetPayload).length > 0 && (\n <div className=\"col-span-2\">\n <dt className=\"text-sm font-medium text-muted-foreground\">{t('scheduler.details.payload', 'Payload')}</dt>\n <dd className=\"mt-1\">\n <pre className=\"bg-muted p-2 rounded text-xs overflow-auto max-h-32\">\n {JSON.stringify(schedule.targetPayload, null, 2)}\n </pre>\n </dd>\n </div>\n )}\n </dl>\n </CardContent>\n </Card>\n\n {/* Recent Executions Card */}\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle>{t('scheduler.details.recent_executions', 'Recent Executions')}</CardTitle>\n </div>\n </CardHeader>\n <CardContent>\n {!isAsyncStrategy ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('scheduler.details.executions_async_only', 'Execution history and manual triggers require async queue strategy (QUEUE_STRATEGY=async).')}\n </p>\n ) : runs.length > 0 ? (\n <DataTable\n columns={runsColumns}\n data={runs}\n />\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('scheduler.details.no_executions', 'No executions yet')}</p>\n )}\n </CardContent>\n </Card>\n </div>\n </PageBody>\n\n <ExecutionDetailsDialog\n open={detailsModalOpen}\n onClose={() => setDetailsModalOpen(false)}\n run={selectedRun}\n scheduleName={schedule.name}\n />\n\n <JobLogsModal\n open={logsModalOpen}\n onClose={() => setLogsModalOpen(false)}\n queueJobId={selectedRun?.queueJobId || null}\n queueName={selectedRun?.queueName || null}\n scheduleName={schedule.name}\n />\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Ungültiger Zeitplanwert.",
41
41
  "scheduler.error.job_not_found": "Job in BullMQ nicht gefunden (möglicherweise entfernt)",
42
42
  "scheduler.error.load_failed": "Fehler beim Laden des Zeitplans",
43
+ "scheduler.error.missing_schedule_id": "Kein Zeitplan ausgewählt.",
43
44
  "scheduler.error.not_found": "Zeitplan nicht gefunden",
44
45
  "scheduler.error.queue_param_required": "Queue-Parameter erforderlich",
45
46
  "scheduler.error.trigger_async_hint": "Ausführungsverlauf und manuelle Auslösung sind nur mit BullMQ (Async-Strategie) verfügbar",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Invalid schedule value.",
41
41
  "scheduler.error.job_not_found": "Job not found in BullMQ (may have been removed)",
42
42
  "scheduler.error.load_failed": "Failed to load schedule",
43
+ "scheduler.error.missing_schedule_id": "No schedule selected.",
43
44
  "scheduler.error.not_found": "Schedule not found",
44
45
  "scheduler.error.queue_param_required": "queue parameter required",
45
46
  "scheduler.error.trigger_async_hint": "Execution history and manual triggers are only available with BullMQ (async strategy)",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Valor de programación no válido.",
41
41
  "scheduler.error.job_not_found": "Trabajo no encontrado en BullMQ (puede haber sido eliminado)",
42
42
  "scheduler.error.load_failed": "Error al cargar la programación",
43
+ "scheduler.error.missing_schedule_id": "Ningún programa seleccionado.",
43
44
  "scheduler.error.not_found": "Programación no encontrada",
44
45
  "scheduler.error.queue_param_required": "Se requiere el parámetro queue",
45
46
  "scheduler.error.trigger_async_hint": "El historial de ejecución y los disparadores manuales solo están disponibles con BullMQ (estrategia async)",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "일정 값이 잘못되었습니다.",
41
41
  "scheduler.error.job_not_found": "BullMQ에서 작업을 찾을 수 없습니다 (제거되었을 수 있음)",
42
42
  "scheduler.error.load_failed": "일정을 불러오지 못했습니다",
43
+ "scheduler.error.missing_schedule_id": "선택된 일정이 없습니다.",
43
44
  "scheduler.error.not_found": "일정을 찾을 수 없습니다",
44
45
  "scheduler.error.queue_param_required": "queue 매개변수가 필요합니다",
45
46
  "scheduler.error.trigger_async_hint": "실행 기록 및 수동 실행은 BullMQ(비동기 전략)에서만 사용할 수 있습니다",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Nieprawidłowa wartość harmonogramu.",
41
41
  "scheduler.error.job_not_found": "Zadanie nie zostało znalezione w BullMQ (mogło zostać usunięte)",
42
42
  "scheduler.error.load_failed": "Nie udało się załadować harmonogramu",
43
+ "scheduler.error.missing_schedule_id": "Nie wybrano harmonogramu.",
43
44
  "scheduler.error.not_found": "Harmonogram nie znaleziony",
44
45
  "scheduler.error.queue_param_required": "Parametr queue jest wymagany",
45
46
  "scheduler.error.trigger_async_hint": "Historia wykonań i ręczne wyzwalanie są dostępne tylko z BullMQ (strategia async)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/scheduler",
3
- "version": "0.7.1-develop.7106.1.53fe9dd1b1",
3
+ "version": "0.7.1-develop.7112.1.d486e6f1b2",
4
4
  "license": "MIT",
5
5
  "description": "Database-managed scheduled jobs with admin UI",
6
6
  "type": "module",
@@ -59,13 +59,13 @@
59
59
  "./*/*/*/*/*/*.json": "./src/*/*/*/*/*/*.json"
60
60
  },
61
61
  "dependencies": {
62
- "@open-mercato/events": "0.7.1-develop.7106.1.53fe9dd1b1",
63
- "@open-mercato/queue": "0.7.1-develop.7106.1.53fe9dd1b1",
62
+ "@open-mercato/events": "0.7.1-develop.7112.1.d486e6f1b2",
63
+ "@open-mercato/queue": "0.7.1-develop.7112.1.d486e6f1b2",
64
64
  "cron-parser": "^5.6.2"
65
65
  },
66
66
  "peerDependencies": {
67
67
  "@mikro-orm/core": "^7.0.14",
68
- "@open-mercato/shared": "0.7.1-develop.7106.1.53fe9dd1b1",
68
+ "@open-mercato/shared": "0.7.1-develop.7112.1.d486e6f1b2",
69
69
  "bullmq": "^5.0.0 || ^6.0.0",
70
70
  "ioredis": "^5.0.0 || ^6.0.0",
71
71
  "zod": ">=3.23.0"
@@ -79,7 +79,7 @@
79
79
  }
80
80
  },
81
81
  "devDependencies": {
82
- "@open-mercato/shared": "0.7.1-develop.7106.1.53fe9dd1b1",
82
+ "@open-mercato/shared": "0.7.1-develop.7112.1.d486e6f1b2",
83
83
  "@types/jest": "^30.0.0",
84
84
  "@types/node": "^26.1.2",
85
85
  "jest": "^30.4.2",
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { useParams, useRouter } from 'next/navigation'
4
+ import { useRouter } from 'next/navigation'
5
5
  import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
7
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -52,16 +52,11 @@ type ExecutionRun = {
52
52
  durationMs?: number
53
53
  }
54
54
 
55
- export default function ScheduleDetailPage() {
56
- const params = useParams()
55
+ export default function ScheduleDetailPage({ params }: { params?: { id?: string } }) {
57
56
  const router = useRouter()
58
57
  const t = useT()
59
58
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
60
- // Extract schedule ID from either params.id or params.slug
61
- // When using catch-all routes, the ID is in params.slug[2]
62
- const scheduleId = params.id
63
- ? (Array.isArray(params.id) ? params.id[0] : params.id)
64
- : (Array.isArray(params.slug) && params.slug.length >= 3 ? params.slug[2] : undefined)
59
+ const scheduleId = params?.id
65
60
 
66
61
  const [schedule, setSchedule] = React.useState<ScheduleDetail | null>(null)
67
62
  const [runs, setRuns] = React.useState<ExecutionRun[]>([])
@@ -111,10 +106,13 @@ export default function ScheduleDetailPage() {
111
106
  }, [scheduleId, isAsyncStrategy])
112
107
 
113
108
  React.useEffect(() => {
114
- if (scheduleId) {
115
- fetchScheduleAndRuns()
109
+ if (!scheduleId) {
110
+ setLoading(false)
111
+ setError(t('scheduler.error.missing_schedule_id', 'No schedule selected.'))
112
+ return
116
113
  }
117
- }, [scheduleId, fetchScheduleAndRuns])
114
+ fetchScheduleAndRuns()
115
+ }, [scheduleId, fetchScheduleAndRuns, t])
118
116
 
119
117
  const handleTriggerNow = async () => {
120
118
  if (!scheduleId || !schedule) return
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Ungültiger Zeitplanwert.",
41
41
  "scheduler.error.job_not_found": "Job in BullMQ nicht gefunden (möglicherweise entfernt)",
42
42
  "scheduler.error.load_failed": "Fehler beim Laden des Zeitplans",
43
+ "scheduler.error.missing_schedule_id": "Kein Zeitplan ausgewählt.",
43
44
  "scheduler.error.not_found": "Zeitplan nicht gefunden",
44
45
  "scheduler.error.queue_param_required": "Queue-Parameter erforderlich",
45
46
  "scheduler.error.trigger_async_hint": "Ausführungsverlauf und manuelle Auslösung sind nur mit BullMQ (Async-Strategie) verfügbar",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Invalid schedule value.",
41
41
  "scheduler.error.job_not_found": "Job not found in BullMQ (may have been removed)",
42
42
  "scheduler.error.load_failed": "Failed to load schedule",
43
+ "scheduler.error.missing_schedule_id": "No schedule selected.",
43
44
  "scheduler.error.not_found": "Schedule not found",
44
45
  "scheduler.error.queue_param_required": "queue parameter required",
45
46
  "scheduler.error.trigger_async_hint": "Execution history and manual triggers are only available with BullMQ (async strategy)",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Valor de programación no válido.",
41
41
  "scheduler.error.job_not_found": "Trabajo no encontrado en BullMQ (puede haber sido eliminado)",
42
42
  "scheduler.error.load_failed": "Error al cargar la programación",
43
+ "scheduler.error.missing_schedule_id": "Ningún programa seleccionado.",
43
44
  "scheduler.error.not_found": "Programación no encontrada",
44
45
  "scheduler.error.queue_param_required": "Se requiere el parámetro queue",
45
46
  "scheduler.error.trigger_async_hint": "El historial de ejecución y los disparadores manuales solo están disponibles con BullMQ (estrategia async)",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "일정 값이 잘못되었습니다.",
41
41
  "scheduler.error.job_not_found": "BullMQ에서 작업을 찾을 수 없습니다 (제거되었을 수 있음)",
42
42
  "scheduler.error.load_failed": "일정을 불러오지 못했습니다",
43
+ "scheduler.error.missing_schedule_id": "선택된 일정이 없습니다.",
43
44
  "scheduler.error.not_found": "일정을 찾을 수 없습니다",
44
45
  "scheduler.error.queue_param_required": "queue 매개변수가 필요합니다",
45
46
  "scheduler.error.trigger_async_hint": "실행 기록 및 수동 실행은 BullMQ(비동기 전략)에서만 사용할 수 있습니다",
@@ -40,6 +40,7 @@
40
40
  "scheduler.error.invalid_schedule_value": "Nieprawidłowa wartość harmonogramu.",
41
41
  "scheduler.error.job_not_found": "Zadanie nie zostało znalezione w BullMQ (mogło zostać usunięte)",
42
42
  "scheduler.error.load_failed": "Nie udało się załadować harmonogramu",
43
+ "scheduler.error.missing_schedule_id": "Nie wybrano harmonogramu.",
43
44
  "scheduler.error.not_found": "Harmonogram nie znaleziony",
44
45
  "scheduler.error.queue_param_required": "Parametr queue jest wymagany",
45
46
  "scheduler.error.trigger_async_hint": "Historia wykonań i ręczne wyzwalanie są dostępne tylko z BullMQ (strategia async)",