mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-09-12 19:51:00 +05:00
Adds a new Events tab to /dashboard/docker showing Docker daemon events (equivalent to `docker events`), polled via docker.getEvents with a time-range selector and refresh button. Also fixes a stale isCloud redirect that forced the Networks tab back to Containers even though it is now shown in cloud.
446 lines
12 KiB
TypeScript
446 lines
12 KiB
TypeScript
import {
|
|
containerKill,
|
|
containerRemove,
|
|
containerRestart,
|
|
containerStart,
|
|
containerStop,
|
|
deleteContainerFile,
|
|
findServerById,
|
|
getConfig,
|
|
getContainers,
|
|
getContainersByAppLabel,
|
|
getContainersByAppNameMatch,
|
|
getDockerEvents,
|
|
getServiceContainersByAppName,
|
|
getStackContainersByAppName,
|
|
listContainerFiles,
|
|
readContainerFile,
|
|
uploadFileToContainer,
|
|
writeContainerFile,
|
|
} from "@dokploy/server";
|
|
import { TRPCError } from "@trpc/server";
|
|
import { z } from "zod";
|
|
import { audit } from "@/server/api/utils/audit";
|
|
import { uploadFileToContainerSchema } from "@/utils/schema";
|
|
import { createTRPCRouter, withPermission } from "../trpc";
|
|
|
|
export const containerIdRegex = /^[a-zA-Z0-9.\-_]+$/;
|
|
|
|
const containerPathSchema = z
|
|
.string()
|
|
.min(1)
|
|
.max(4096)
|
|
.refine(
|
|
(path) => path.startsWith("/") && !path.includes("\0"),
|
|
"Path must be absolute.",
|
|
);
|
|
|
|
export const dockerRouter = createTRPCRouter({
|
|
getContainers: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getContainers(input.serverId);
|
|
}),
|
|
|
|
restartContainer: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await containerRestart(input.containerId, input.serverId);
|
|
await audit(ctx, {
|
|
action: "start",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: input.containerId,
|
|
});
|
|
}),
|
|
|
|
startContainer: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await containerStart(input.containerId, input.serverId);
|
|
await audit(ctx, {
|
|
action: "start",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: input.containerId,
|
|
});
|
|
}),
|
|
|
|
stopContainer: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await containerStop(input.containerId, input.serverId);
|
|
await audit(ctx, {
|
|
action: "stop",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: input.containerId,
|
|
});
|
|
}),
|
|
|
|
killContainer: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await containerKill(input.containerId, input.serverId);
|
|
await audit(ctx, {
|
|
action: "stop",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: input.containerId,
|
|
});
|
|
}),
|
|
|
|
removeContainer: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await containerRemove(input.containerId, input.serverId);
|
|
await audit(ctx, {
|
|
action: "delete",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: input.containerId,
|
|
});
|
|
}),
|
|
|
|
getConfig: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getConfig(input.containerId, input.serverId);
|
|
}),
|
|
|
|
getContainersByAppNameMatch: withPermission("service", "read")
|
|
.input(
|
|
z.object({
|
|
appType: z.enum(["stack", "docker-compose"]).optional(),
|
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getContainersByAppNameMatch(
|
|
input.appName,
|
|
input.appType,
|
|
input.serverId,
|
|
);
|
|
}),
|
|
|
|
getContainersByAppLabel: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
|
serverId: z.string().optional(),
|
|
type: z.enum(["standalone", "swarm"]),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getContainersByAppLabel(
|
|
input.appName,
|
|
input.type,
|
|
input.serverId,
|
|
);
|
|
}),
|
|
|
|
getStackContainersByAppName: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getStackContainersByAppName(input.appName, input.serverId);
|
|
}),
|
|
|
|
getServiceContainersByAppName: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
appName: z.string().min(1).regex(containerIdRegex, "Invalid app name."),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getServiceContainersByAppName(input.appName, input.serverId);
|
|
}),
|
|
|
|
uploadFileToContainer: withPermission("docker", "read")
|
|
.input(uploadFileToContainerSchema)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
|
|
const file = input.file;
|
|
if (!(file instanceof File)) {
|
|
throw new TRPCError({
|
|
code: "BAD_REQUEST",
|
|
message: "Invalid file provided",
|
|
});
|
|
}
|
|
|
|
// Convert File to Buffer
|
|
const arrayBuffer = await file.arrayBuffer();
|
|
const fileBuffer = Buffer.from(arrayBuffer);
|
|
|
|
await uploadFileToContainer(
|
|
input.containerId,
|
|
fileBuffer,
|
|
file.name,
|
|
input.destinationPath,
|
|
input.serverId || null,
|
|
);
|
|
|
|
return { success: true, message: "File uploaded successfully" };
|
|
}),
|
|
|
|
listContainerFiles: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
path: containerPathSchema,
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await listContainerFiles(
|
|
input.containerId,
|
|
input.path,
|
|
input.serverId,
|
|
);
|
|
}),
|
|
|
|
readContainerFile: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
path: containerPathSchema,
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await readContainerFile(
|
|
input.containerId,
|
|
input.path,
|
|
input.serverId,
|
|
);
|
|
}),
|
|
|
|
writeContainerFile: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
path: containerPathSchema,
|
|
content: z.string(),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await writeContainerFile(
|
|
input.containerId,
|
|
input.path,
|
|
input.content,
|
|
input.serverId,
|
|
);
|
|
await audit(ctx, {
|
|
action: "update",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: `${input.containerId}:${input.path}`,
|
|
});
|
|
}),
|
|
|
|
deleteContainerFile: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
containerId: z
|
|
.string()
|
|
.min(1)
|
|
.regex(containerIdRegex, "Invalid container id."),
|
|
path: containerPathSchema.refine(
|
|
(path) => path !== "/",
|
|
"Cannot delete the container root.",
|
|
),
|
|
serverId: z.string().optional(),
|
|
}),
|
|
)
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
await deleteContainerFile(input.containerId, input.path, input.serverId);
|
|
await audit(ctx, {
|
|
action: "delete",
|
|
resourceType: "docker",
|
|
resourceId: input.containerId,
|
|
resourceName: `${input.containerId}:${input.path}`,
|
|
});
|
|
}),
|
|
|
|
getEvents: withPermission("docker", "read")
|
|
.input(
|
|
z.object({
|
|
serverId: z.string().optional(),
|
|
minutes: z.number().min(1).max(1440).default(15),
|
|
}),
|
|
)
|
|
.query(async ({ input, ctx }) => {
|
|
if (input.serverId) {
|
|
const server = await findServerById(input.serverId);
|
|
if (server.organizationId !== ctx.session?.activeOrganizationId) {
|
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
}
|
|
}
|
|
return await getDockerEvents(input.serverId, input.minutes);
|
|
}),
|
|
});
|