mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-07-18 04:15:23 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe7349889 | ||
|
|
5c1c969873 | ||
|
|
2f6f1b19e7 | ||
|
|
03e04b7bce | ||
|
|
b920e7c0f1 | ||
|
|
15fde820a3 | ||
|
|
64293fce79 | ||
|
|
526f249d0e |
@@ -62,6 +62,8 @@ export const ShowDeployments = ({
|
|||||||
|
|
||||||
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
const { mutateAsync: rollback, isLoading: isRollingBack } =
|
||||||
api.rollback.rollback.useMutation();
|
api.rollback.rollback.useMutation();
|
||||||
|
const { mutateAsync: killProcess, isLoading: isKillingProcess } =
|
||||||
|
api.deployment.killProcess.useMutation();
|
||||||
|
|
||||||
const [url, setUrl] = React.useState("");
|
const [url, setUrl] = React.useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -170,6 +172,32 @@ export const ShowDeployments = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row items-center gap-2">
|
<div className="flex flex-row items-center gap-2">
|
||||||
|
{deployment.pid && deployment.status === "running" && (
|
||||||
|
<DialogAction
|
||||||
|
title="Kill Process"
|
||||||
|
description="Are you sure you want to kill the process?"
|
||||||
|
type="default"
|
||||||
|
onClick={async () => {
|
||||||
|
await killProcess({
|
||||||
|
deploymentId: deployment.deploymentId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Process killed successfully");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error killing process");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
isLoading={isKillingProcess}
|
||||||
|
>
|
||||||
|
Kill Process
|
||||||
|
</Button>
|
||||||
|
</DialogAction>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveLog(deployment);
|
setActiveLog(deployment);
|
||||||
|
|||||||
@@ -166,12 +166,16 @@ export const ShowSchedules = ({ id, scheduleType = "application" }: Props) => {
|
|||||||
|
|
||||||
await runManually({
|
await runManually({
|
||||||
scheduleId: schedule.scheduleId,
|
scheduleId: schedule.scheduleId,
|
||||||
}).then(async () => {
|
})
|
||||||
await new Promise((resolve) =>
|
.then(async () => {
|
||||||
setTimeout(resolve, 1500),
|
await new Promise((resolve) =>
|
||||||
);
|
setTimeout(resolve, 1500),
|
||||||
refetchSchedules();
|
);
|
||||||
});
|
refetchSchedules();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error running schedule");
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Play className="size-4 transition-colors" />
|
<Play className="size-4 transition-colors" />
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
|
|||||||
Show Swarm Nodes
|
Show Swarm Nodes
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen ">
|
<DialogContent className="min-w-[70vw] overflow-y-auto max-h-screen">
|
||||||
<div className="grid w-full gap-1">
|
<div className="grid w-full gap-1">
|
||||||
<ShowNodes serverId={serverId} />
|
<ShowNodes serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type * as React from "react";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const badgeVariants = cva(
|
const badgeVariants = cva(
|
||||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold whitespace-nowrap transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0098_conscious_chat.sql
Normal file
1
apps/dokploy/drizzle/0098_conscious_chat.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "deployment" ADD COLUMN "pid" text;
|
||||||
5832
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
5832
apps/dokploy/drizzle/meta/0098_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -687,6 +687,13 @@
|
|||||||
"when": 1750567641441,
|
"when": 1750567641441,
|
||||||
"tag": "0097_hard_lizard",
|
"tag": "0097_hard_lizard",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 98,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1751233265357,
|
||||||
|
"tag": "0098_conscious_chat",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.23.5",
|
"version": "v0.23.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -7,16 +7,21 @@ import {
|
|||||||
deployments,
|
deployments,
|
||||||
} from "@/server/db/schema";
|
} from "@/server/db/schema";
|
||||||
import {
|
import {
|
||||||
|
execAsync,
|
||||||
|
execAsyncRemote,
|
||||||
findAllDeploymentsByApplicationId,
|
findAllDeploymentsByApplicationId,
|
||||||
findAllDeploymentsByComposeId,
|
findAllDeploymentsByComposeId,
|
||||||
findAllDeploymentsByServerId,
|
findAllDeploymentsByServerId,
|
||||||
findApplicationById,
|
findApplicationById,
|
||||||
findComposeById,
|
findComposeById,
|
||||||
|
findDeploymentById,
|
||||||
findServerById,
|
findServerById,
|
||||||
|
updateDeploymentStatus,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { desc, eq } from "drizzle-orm";
|
import { desc, eq } from "drizzle-orm";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
export const deploymentRouter = createTRPCRouter({
|
export const deploymentRouter = createTRPCRouter({
|
||||||
all: protectedProcedure
|
all: protectedProcedure
|
||||||
@@ -72,4 +77,30 @@ export const deploymentRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return deploymentsList;
|
return deploymentsList;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
killProcess: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
deploymentId: z.string().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const deployment = await findDeploymentById(input.deploymentId);
|
||||||
|
|
||||||
|
if (!deployment.pid) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Deployment is not running",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = `kill -9 ${deployment.pid}`;
|
||||||
|
if (deployment.schedule?.serverId) {
|
||||||
|
await execAsyncRemote(deployment.schedule.serverId, command);
|
||||||
|
} else {
|
||||||
|
await execAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const deployments = pgTable("deployment", {
|
|||||||
description: text("description"),
|
description: text("description"),
|
||||||
status: deploymentStatus("status").default("running"),
|
status: deploymentStatus("status").default("running"),
|
||||||
logPath: text("logPath").notNull(),
|
logPath: text("logPath").notNull(),
|
||||||
|
pid: text("pid"),
|
||||||
applicationId: text("applicationId").references(
|
applicationId: text("applicationId").references(
|
||||||
() => applications.applicationId,
|
() => applications.applicationId,
|
||||||
{ onDelete: "cascade" },
|
{ onDelete: "cascade" },
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const findDeploymentById = async (deploymentId: string) => {
|
|||||||
where: eq(deployments.deploymentId, deploymentId),
|
where: eq(deployments.deploymentId, deploymentId),
|
||||||
with: {
|
with: {
|
||||||
application: true,
|
application: true,
|
||||||
|
schedule: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!deployment) {
|
if (!deployment) {
|
||||||
@@ -537,9 +538,11 @@ const removeLastTenDeployments = async (
|
|||||||
await removeRollbackById(oldDeployment.rollbackId);
|
await removeRollbackById(oldDeployment.rollbackId);
|
||||||
}
|
}
|
||||||
|
|
||||||
command += `
|
if (logPath !== ".") {
|
||||||
rm -rf ${logPath};
|
command += `
|
||||||
`;
|
rm -rf ${logPath};
|
||||||
|
`;
|
||||||
|
}
|
||||||
await removeDeployment(oldDeployment.deploymentId);
|
await removeDeployment(oldDeployment.deploymentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,7 +553,11 @@ const removeLastTenDeployments = async (
|
|||||||
await removeRollbackById(oldDeployment.rollbackId);
|
await removeRollbackById(oldDeployment.rollbackId);
|
||||||
}
|
}
|
||||||
const logPath = path.join(oldDeployment.logPath);
|
const logPath = path.join(oldDeployment.logPath);
|
||||||
if (existsSync(logPath) && !oldDeployment.errorMessage) {
|
if (
|
||||||
|
existsSync(logPath) &&
|
||||||
|
!oldDeployment.errorMessage &&
|
||||||
|
logPath !== "."
|
||||||
|
) {
|
||||||
await fsPromises.unlink(logPath);
|
await fsPromises.unlink(logPath);
|
||||||
}
|
}
|
||||||
await removeDeployment(oldDeployment.deploymentId);
|
await removeDeployment(oldDeployment.deploymentId);
|
||||||
|
|||||||
@@ -109,7 +109,12 @@ export const updateSchedule = async (
|
|||||||
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 || "");
|
|
||||||
|
// Add PID and Schedule ID echo by default to all scripts
|
||||||
|
const scriptWithPid = `echo "PID: $$ | Schedule ID: ${schedule.scheduleId}"
|
||||||
|
${schedule?.script || ""}`;
|
||||||
|
|
||||||
|
const encodedContent = encodeBase64(scriptWithPid);
|
||||||
const script = `
|
const script = `
|
||||||
mkdir -p ${fullPath}
|
mkdir -p ${fullPath}
|
||||||
rm -f ${fullPath}/script.sh
|
rm -f ${fullPath}/script.sh
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { createWriteStream } from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { paths } from "@dokploy/server/constants";
|
import { paths } from "@dokploy/server/constants";
|
||||||
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
import type { Schedule } from "@dokploy/server/db/schema/schedule";
|
||||||
import { createDeploymentSchedule } from "@dokploy/server/services/deployment";
|
import {
|
||||||
|
createDeploymentSchedule,
|
||||||
|
updateDeployment,
|
||||||
|
} from "@dokploy/server/services/deployment";
|
||||||
import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
|
import { updateDeploymentStatus } from "@dokploy/server/services/deployment";
|
||||||
import { findScheduleById } from "@dokploy/server/services/schedule";
|
import { findScheduleById } from "@dokploy/server/services/schedule";
|
||||||
import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule";
|
import { scheduleJob as scheduleJobNode, scheduledJobs } from "node-schedule";
|
||||||
@@ -113,8 +116,16 @@ export const runCommand = async (scheduleId: string) => {
|
|||||||
await spawnAsync(
|
await spawnAsync(
|
||||||
"bash",
|
"bash",
|
||||||
["-c", "./script.sh"],
|
["-c", "./script.sh"],
|
||||||
(data) => {
|
async (data) => {
|
||||||
if (writeStream.writable) {
|
if (writeStream.writable) {
|
||||||
|
// we need to extract the PID and Schedule ID from the data
|
||||||
|
const pid = data?.match(/PID: (\d+)/)?.[1];
|
||||||
|
|
||||||
|
if (pid) {
|
||||||
|
await updateDeployment(deployment.deploymentId, {
|
||||||
|
pid,
|
||||||
|
});
|
||||||
|
}
|
||||||
writeStream.write(data);
|
writeStream.write(data);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -133,13 +144,21 @@ export const runCommand = async (scheduleId: string) => {
|
|||||||
const command = `
|
const command = `
|
||||||
set -e
|
set -e
|
||||||
echo "Running script" >> ${deployment.logPath};
|
echo "Running script" >> ${deployment.logPath};
|
||||||
bash -c ${fullPath}/script.sh >> ${deployment.logPath} 2>> ${deployment.logPath} || {
|
bash -c ${fullPath}/script.sh 2>&1 | tee -a ${deployment.logPath} || {
|
||||||
echo "❌ Command failed" >> ${deployment.logPath};
|
echo "❌ Command failed" >> ${deployment.logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
echo "✅ Command executed successfully" >> ${deployment.logPath};
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command, async (data) => {
|
||||||
|
// we need to extract the PID and Schedule ID from the data
|
||||||
|
const pid = data?.match(/PID: (\d+)/)?.[1];
|
||||||
|
if (pid) {
|
||||||
|
await updateDeployment(deployment.deploymentId, {
|
||||||
|
pid,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await updateDeploymentStatus(deployment.deploymentId, "error");
|
await updateDeploymentStatus(deployment.deploymentId, "error");
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user