From 2c90103823d0392db99f1ee9d0083faf84101756 Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Fri, 2 May 2025 15:56:40 -0600 Subject: [PATCH] Update schedule management with shell type support and version upgrades - Updated the `drizzle-zod` package to version 0.7.1 across the project for enhanced schema validation. - Introduced a new `shellType` column in the `schedule` schema to specify the shell used for executing commands, defaulting to 'bash'. - Enhanced the `HandleSchedules` component to include the new `shellType` field, allowing users to select the shell type when creating or updating schedules. - Updated the `runCommand` utility to utilize the selected shell type when executing commands, improving flexibility in command execution. - Refactored the API to accommodate the new `shellType` in schedule creation and updates, ensuring proper handling of the new field. --- .../schedules/handle-schedules.tsx | 365 +- .../application/schedules/show-schedules.tsx | 62 +- .../drizzle/0092_safe_scarlet_witch.sql | 2 + apps/dokploy/drizzle/meta/0092_snapshot.json | 5518 +++++++++++++++++ apps/dokploy/drizzle/meta/_journal.json | 7 + apps/dokploy/package.json | 2 +- apps/dokploy/server/api/routers/schedule.ts | 6 +- packages/server/package.json | 2 +- packages/server/src/db/schema/schedule.ts | 11 +- packages/server/src/utils/schedules/utils.ts | 12 +- pnpm-lock.yaml | 18 +- 11 files changed, 5766 insertions(+), 239 deletions(-) create mode 100644 apps/dokploy/drizzle/0092_safe_scarlet_witch.sql create mode 100644 apps/dokploy/drizzle/meta/0092_snapshot.json diff --git a/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx index 2799953e..c5c4d47e 100644 --- a/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx +++ b/apps/dokploy/components/dashboard/application/schedules/handle-schedules.tsx @@ -13,7 +13,7 @@ import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { Clock, Terminal, Info } from "lucide-react"; +import { Clock, Terminal, Info, PlusCircle, PenBoxIcon } from "lucide-react"; import { Select, SelectContent, @@ -28,6 +28,15 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { Switch } from "@/components/ui/switch"; +import { useEffect, useState } from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { toast } from "sonner"; const commonCronExpressions = [ { label: "Every minute", value: "* * * * *" }, @@ -47,26 +56,16 @@ const formSchema = z.object({ }); interface Props { - applicationId: string; - onSuccess?: () => void; - defaultValues?: { - name: string; - cronExpression: string; - command: string; - }; + applicationId?: string; scheduleId?: string; } -export const HandleSchedules = ({ - applicationId, - onSuccess, - defaultValues, - scheduleId, -}: Props) => { - const utils = api.useContext(); +export const HandleSchedules = ({ applicationId, scheduleId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const utils = api.useUtils(); const form = useForm>({ resolver: zodResolver(formSchema), - defaultValues: defaultValues || { + defaultValues: { name: "", cronExpression: "", command: "", @@ -74,166 +73,198 @@ export const HandleSchedules = ({ }, }); - const { mutate: createSchedule, isLoading: isCreating } = - api.schedule.create.useMutation({ - onSuccess: () => { - utils.schedule.list.invalidate({ applicationId }); - form.reset(); - onSuccess?.(); - }, - }); + const { data: schedule } = api.schedule.one.useQuery( + { scheduleId: scheduleId || "" }, + { enabled: !!scheduleId }, + ); - const { mutate: updateSchedule, isLoading: isUpdating } = - api.schedule.update.useMutation({ - onSuccess: () => { - utils.schedule.list.invalidate({ applicationId }); - onSuccess?.(); - }, - }); - - const isLoading = isCreating || isUpdating; - - const onSubmit = (values: z.infer) => { + useEffect(() => { if (scheduleId) { - updateSchedule({ - ...values, - scheduleId, - applicationId, - }); - } else { - createSchedule({ - ...values, - applicationId, + form.reset({ + name: schedule?.name, + cronExpression: schedule?.cronExpression, + command: schedule?.command, + enabled: schedule?.enabled, }); } + }, [form, form.reset, schedule]); + + const { mutateAsync, isLoading } = scheduleId + ? api.schedule.update.useMutation() + : api.schedule.create.useMutation(); + + const onSubmit = async (values: z.infer) => { + await mutateAsync({ + ...values, + ...(scheduleId && { scheduleId }), + ...(applicationId && { applicationId }), + }) + .then(() => { + toast.success( + `Schedule ${scheduleId ? "updated" : "created"} successfully`, + ); + utils.schedule.list.invalidate({ applicationId }); + setIsOpen(false); + }) + .catch((error) => { + toast.error( + error instanceof Error ? error.message : "An unknown error occurred", + ); + }); }; return ( -
- - ( - - - - Task Name - - - - - - A descriptive name for your scheduled task - - - - )} - /> + + + {scheduleId ? ( + + ) : ( + + )} + + + {scheduleId} + + {scheduleId ? "Edit" : "Create"} Schedule + + + + ( + + + + Task Name + + + + + + A descriptive name for your scheduled task + + + + )} + /> - ( - - - - Schedule - - - - - - -

- Cron expression format: minute hour day month weekday -

-

Example: 0 0 * * * (daily at midnight)

-
-
-
-
- - - - - - Choose a predefined schedule or enter a custom cron expression - - -
- )} - /> + ( + + + + Schedule + + + + + + +

+ Cron expression format: minute hour day month + weekday +

+

Example: 0 0 * * * (daily at midnight)

+
+
+
+
+ + + + + + Choose a predefined schedule or enter a custom cron + expression + + +
+ )} + /> - ( - - - - Command - - - - - - The command to execute in your container - - - - )} - /> + ( + + + + Command + + + + + + The command to execute in your container + + + + )} + /> - ( - - - - Enabled - - - )} - /> - - - + ( + + + + Enabled + + + )} + /> + + + +
+
); }; diff --git a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx index ef82f745..e3f826bb 100644 --- a/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx +++ b/apps/dokploy/components/dashboard/application/schedules/show-schedules.tsx @@ -1,11 +1,4 @@ import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; import { Table, TableBody, @@ -15,9 +8,8 @@ import { TableRow, } from "@/components/ui/table"; import { api } from "@/utils/api"; -import { useState } from "react"; import { HandleSchedules } from "./handle-schedules"; -import { PlusCircle, Clock, Terminal, Trash2, Edit2 } from "lucide-react"; +import { Clock, Terminal, Trash2 } from "lucide-react"; import { Card, CardContent, @@ -34,14 +26,6 @@ interface Props { } export const ShowSchedules = ({ applicationId }: Props) => { - const [isOpen, setIsOpen] = useState(false); - const [editingSchedule, setEditingSchedule] = useState<{ - scheduleId: string; - name: string; - cronExpression: string; - command: string; - } | null>(null); - const { data: schedules } = api.schedule.list.useQuery({ applicationId, }); @@ -60,11 +44,6 @@ export const ShowSchedules = ({ applicationId }: Props) => { const utils = api.useContext(); - const onClose = () => { - setIsOpen(false); - setEditingSchedule(null); - }; - return ( @@ -78,27 +57,7 @@ export const ShowSchedules = ({ applicationId }: Props) => { - - - - - - - - {editingSchedule ? "Edit" : "Create"} Schedule - - - - - + @@ -146,7 +105,7 @@ export const ShowSchedules = ({ applicationId }: Props) => {
- + + +