mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Enhance schedule initialization and management features
- Introduced a new `initSchedules` function to initialize and schedule active schedules from the database, improving the management of scheduled tasks. - Updated the `createSchedule` and `updateSchedule` functions to handle scheduling jobs based on the enabled status of schedules, ensuring proper job management. - Refactored the `removeScheduleJob` utility to cancel existing scheduled jobs, enhancing the flexibility of schedule updates. - Improved the `HandleSchedules` and `ShowSchedules` components by removing unused imports and enhancing the user interface for better clarity and usability.
This commit is contained in:
@@ -14,8 +14,6 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
Clock,
|
|
||||||
Terminal,
|
|
||||||
Info,
|
Info,
|
||||||
PlusCircle,
|
PlusCircle,
|
||||||
PenBoxIcon,
|
PenBoxIcon,
|
||||||
@@ -49,7 +47,8 @@ import type { CacheType } from "../../compose/domains/add-domain";
|
|||||||
import { AlertBlock } from "@/components/shared/alert-block";
|
import { AlertBlock } from "@/components/shared/alert-block";
|
||||||
import { CodeEditor } from "@/components/shared/code-editor";
|
import { CodeEditor } from "@/components/shared/code-editor";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
const commonCronExpressions = [
|
|
||||||
|
export const commonCronExpressions = [
|
||||||
{ label: "Every minute", value: "* * * * *" },
|
{ label: "Every minute", value: "* * * * *" },
|
||||||
{ label: "Every hour", value: "0 * * * *" },
|
{ label: "Every hour", value: "0 * * * *" },
|
||||||
{ label: "Every day at midnight", value: "0 0 * * *" },
|
{ label: "Every day at midnight", value: "0 0 * * *" },
|
||||||
@@ -361,7 +360,6 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex items-center gap-2">
|
<FormLabel className="flex items-center gap-2">
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
Task Name
|
Task Name
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -381,7 +379,6 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex items-center gap-2">
|
<FormLabel className="flex items-center gap-2">
|
||||||
<Clock className="w-4 h-4" />
|
|
||||||
Schedule
|
Schedule
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -444,7 +441,6 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex items-center gap-2">
|
<FormLabel className="flex items-center gap-2">
|
||||||
<Terminal className="w-4 h-4" />
|
|
||||||
Shell Type
|
Shell Type
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -474,7 +470,6 @@ export const HandleSchedules = ({ id, scheduleId, scheduleType }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex items-center gap-2">
|
<FormLabel className="flex items-center gap-2">
|
||||||
<Terminal className="w-4 h-4" />
|
|
||||||
Command
|
Command
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-0">
|
<CardContent className="px-0">
|
||||||
{isLoadingSchedules ? (
|
{isLoadingSchedules ? (
|
||||||
<div className="flex gap-4 w-full items-center justify-center text-center mx-auto">
|
<div className="flex gap-4 w-full items-center justify-center text-center mx-auto min-h-[45vh]">
|
||||||
<Loader2 className="size-4 text-muted-foreground/70 transition-colors animate-spin self-center" />
|
<Loader2 className="size-4 text-muted-foreground/70 transition-colors animate-spin self-center" />
|
||||||
<span className="text-sm text-muted-foreground/70">
|
<span className="text-sm text-muted-foreground/70">
|
||||||
Loading scheduled tasks...
|
Loading scheduled tasks...
|
||||||
@@ -146,7 +146,7 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<ShowSchedulesLogs
|
<ShowSchedulesLogs
|
||||||
deployments={deployments || []}
|
deployments={deployments || []}
|
||||||
serverId={serverId}
|
serverId={serverId || undefined}
|
||||||
>
|
>
|
||||||
<Button variant="ghost" size="icon">
|
<Button variant="ghost" size="icon">
|
||||||
<ClipboardList className="size-4 transition-colors " />
|
<ClipboardList className="size-4 transition-colors " />
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
createDefaultServerTraefikConfig,
|
createDefaultServerTraefikConfig,
|
||||||
createDefaultTraefikConfig,
|
createDefaultTraefikConfig,
|
||||||
initCronJobs,
|
initCronJobs,
|
||||||
|
initSchedules,
|
||||||
initializeNetwork,
|
initializeNetwork,
|
||||||
sendDokployRestartNotifications,
|
sendDokployRestartNotifications,
|
||||||
setupDirectories,
|
setupDirectories,
|
||||||
@@ -49,6 +50,7 @@ void app.prepare().then(async () => {
|
|||||||
createDefaultServerTraefikConfig();
|
createDefaultServerTraefikConfig();
|
||||||
await migration();
|
await migration();
|
||||||
await initCronJobs();
|
await initCronJobs();
|
||||||
|
await initSchedules();
|
||||||
await sendDokployRestartNotifications();
|
await sendDokployRestartNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,3 +128,4 @@ export {
|
|||||||
} from "./utils/access-log/handler";
|
} from "./utils/access-log/handler";
|
||||||
|
|
||||||
export * from "./utils/schedules/utils";
|
export * from "./utils/schedules/utils";
|
||||||
|
export * from "./utils/schedules/index";
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import type {
|
|||||||
updateScheduleSchema,
|
updateScheduleSchema,
|
||||||
} from "../db/schema/schedule";
|
} from "../db/schema/schedule";
|
||||||
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
|
||||||
import { paths } from "../constants";
|
import { IS_CLOUD, paths } from "../constants";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { encodeBase64 } from "../utils/docker/utils";
|
||||||
|
import { scheduleJob, removeScheduleJob } from "../utils/schedules/utils";
|
||||||
|
|
||||||
export type ScheduleExtended = Awaited<ReturnType<typeof findScheduleById>>;
|
export type ScheduleExtended = Awaited<ReturnType<typeof findScheduleById>>;
|
||||||
|
|
||||||
@@ -26,6 +28,11 @@ export const createSchedule = async (
|
|||||||
) {
|
) {
|
||||||
await handleScript(newSchedule);
|
await handleScript(newSchedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newSchedule?.enabled) {
|
||||||
|
scheduleJob(newSchedule);
|
||||||
|
}
|
||||||
|
|
||||||
return newSchedule;
|
return newSchedule;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,12 +57,16 @@ export const findScheduleById = async (scheduleId: string) => {
|
|||||||
|
|
||||||
export const deleteSchedule = async (scheduleId: string) => {
|
export const deleteSchedule = async (scheduleId: string) => {
|
||||||
const schedule = await findScheduleById(scheduleId);
|
const schedule = await findScheduleById(scheduleId);
|
||||||
|
const serverId =
|
||||||
|
schedule?.serverId ||
|
||||||
|
schedule?.application?.serverId ||
|
||||||
|
schedule?.compose?.serverId;
|
||||||
|
const { SCHEDULES_PATH } = paths(!!serverId);
|
||||||
|
|
||||||
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
|
||||||
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
||||||
const command = `rm -rf ${fullPath}`;
|
const command = `rm -rf ${fullPath}`;
|
||||||
if (schedule.serverId) {
|
if (serverId) {
|
||||||
await execAsyncRemote(schedule.serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} else {
|
} else {
|
||||||
await execAsync(command);
|
await execAsync(command);
|
||||||
}
|
}
|
||||||
@@ -89,19 +100,32 @@ export const updateSchedule = async (
|
|||||||
) {
|
) {
|
||||||
await handleScript(updatedSchedule);
|
await handleScript(updatedSchedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("updatedSchedule", updatedSchedule);
|
||||||
|
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
// scheduleJob(updatedSchedule);
|
||||||
|
} else {
|
||||||
|
if (updatedSchedule?.enabled) {
|
||||||
|
removeScheduleJob(scheduleId);
|
||||||
|
scheduleJob(updatedSchedule);
|
||||||
|
} else {
|
||||||
|
removeScheduleJob(scheduleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
return updatedSchedule;
|
return updatedSchedule;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScript = async (schedule: Schedule) => {
|
const handleScript = async (schedule: Schedule) => {
|
||||||
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
const { SCHEDULES_PATH } = paths(!!schedule?.serverId);
|
||||||
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
const fullPath = path.join(SCHEDULES_PATH, schedule?.appName || "");
|
||||||
|
const encodedContent = encodeBase64(schedule?.script || "");
|
||||||
const script = `
|
const script = `
|
||||||
mkdir -p ${fullPath}
|
mkdir -p ${fullPath}
|
||||||
rm -f ${fullPath}/script.sh
|
rm -f ${fullPath}/script.sh
|
||||||
touch ${fullPath}/script.sh
|
touch ${fullPath}/script.sh
|
||||||
chmod +x ${fullPath}/script.sh
|
chmod +x ${fullPath}/script.sh
|
||||||
echo "${schedule?.script}" > ${fullPath}/script.sh
|
echo "${encodedContent}" | base64 -d > ${fullPath}/script.sh
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (schedule?.scheduleType === "dokploy-server") {
|
if (schedule?.scheduleType === "dokploy-server") {
|
||||||
|
|||||||
28
packages/server/src/utils/schedules/index.ts
Normal file
28
packages/server/src/utils/schedules/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { db } from "../../db/index";
|
||||||
|
import { schedules } from "@dokploy/server/db/schema";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { scheduleJob } from "./utils";
|
||||||
|
|
||||||
|
export const initSchedules = async () => {
|
||||||
|
try {
|
||||||
|
const schedulesResult = await db.query.schedules.findMany({
|
||||||
|
where: eq(schedules.enabled, true),
|
||||||
|
with: {
|
||||||
|
server: true,
|
||||||
|
application: true,
|
||||||
|
compose: true,
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Initializing ${schedulesResult.length} schedules`);
|
||||||
|
for (const schedule of schedulesResult) {
|
||||||
|
scheduleJob(schedule);
|
||||||
|
console.log(
|
||||||
|
`Initialized schedule: ${schedule.name} ${schedule.scheduleType} ✅`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error initializing schedules: ${error}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
||||||
import { findScheduleById } from "@dokploy/server/services/schedule";
|
import { findScheduleById } from "@dokploy/server/services/schedule";
|
||||||
import { scheduleJob as scheduleJobNode } from "node-schedule";
|
import { scheduledJobs, scheduleJob as scheduleJobNode } from "node-schedule";
|
||||||
import { getComposeContainer, getServiceContainerIV2 } from "../docker/utils";
|
import { getComposeContainer, getServiceContainerIV2 } from "../docker/utils";
|
||||||
import { execAsyncRemote } from "../process/execAsync";
|
import { execAsyncRemote } from "../process/execAsync";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
@@ -18,6 +18,11 @@ export const scheduleJob = (schedule: Schedule) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeScheduleJob = (scheduleId: string) => {
|
||||||
|
const currentJob = scheduledJobs[scheduleId];
|
||||||
|
currentJob?.cancel();
|
||||||
|
};
|
||||||
|
|
||||||
export const runCommand = async (scheduleId: string) => {
|
export const runCommand = async (scheduleId: string) => {
|
||||||
const {
|
const {
|
||||||
application,
|
application,
|
||||||
|
|||||||
Reference in New Issue
Block a user