From c31ed2b2b0e72fab988e215c420ddc5a235e7230 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:38:08 +0100 Subject: [PATCH 01/59] feat: add iproute2 as dependency to dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 51be6469..5fa0ee2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ WORKDIR /app # Set production ENV NODE_ENV=production -RUN apt-get update && apt-get install -y curl unzip apache2-utils && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y curl unzip apache2-utils iproute2 && rm -rf /var/lib/apt/lists/* # Copy only the necessary files COPY --from=build /prod/dokploy/.next ./.next From aa3541b67b2717a4e08bd50c50665c850ec7fe3d Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:39:19 +0100 Subject: [PATCH 02/59] feat: add utils to get docker host and check if running on WSL --- apps/dokploy/server/utils/docker.ts | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 apps/dokploy/server/utils/docker.ts diff --git a/apps/dokploy/server/utils/docker.ts b/apps/dokploy/server/utils/docker.ts new file mode 100644 index 00000000..7577c844 --- /dev/null +++ b/apps/dokploy/server/utils/docker.ts @@ -0,0 +1,40 @@ +import { execAsync } from "@dokploy/server"; + +/** Returns if the current operating system is Windows Subsystem for Linux (WSL). */ +export const isWSL = async () => { + try { + const { stdout } = await execAsync("uname -r"); + const isWSL = stdout.includes("microsoft"); + return isWSL; + } catch (error) { + return false; + } +}; + +/** Returns the Docker host IP address. */ +export const getDockerHost = async (): Promise => { + if (process.env.NODE_ENV === "production") { + if (process.platform === "linux" && !isWSL()) { + try { + // Try to get the Docker bridge IP first + const { stdout } = await execAsync( + "ip route | awk '/default/ {print $3}'", + ); + + const hostIp = stdout.trim(); + if (!hostIp) { + throw new Error("Failed to get Docker host IP"); + } + + return hostIp; + } catch (error) { + console.error("Failed to get Docker host IP:", error); + return "172.17.0.1"; // Default Docker bridge network IP + } + } + + return "host.docker.internal"; + } + + return "localhost"; +}; From d0a5427c668d118d223df57baa55948e01ec0433 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 11:41:34 +0100 Subject: [PATCH 03/59] fix: connecting to local server terminal --- apps/dokploy/server/wss/terminal.ts | 54 +++++++++++++++++++------ apps/dokploy/server/wss/utils.ts | 62 +++++++++-------------------- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 8c661c8a..87b096c0 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -8,6 +8,17 @@ import { publicIpv4, publicIpv6 } from "public-ip"; import { Client, type ConnectConfig } from "ssh2"; import { WebSocketServer } from "ws"; import { setupLocalServerSSHKey } from "./utils"; +import { getDockerHost } from "../utils/docker"; + +const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` +# ---------------------------------------- + mkdir -p $HOME/.ssh && \\ + chmod 700 $HOME/.ssh && \\ + touch $HOME/.ssh/authorized_keys && \\ + chmod 600 $HOME/.ssh/authorized_keys && \\ + cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ + echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." +# ----------------------------------------`; export const getPublicIpWithFallback = async () => { // @ts-ignore @@ -73,20 +84,31 @@ export const setupTerminalWebSocketServer = ( return; } - ws.send("Setting up private SSH key...\n"); - const privateKey = await setupLocalServerSSHKey(); + try { + ws.send("Setting up private SSH key...\n"); + const privateKey = await setupLocalServerSSHKey(); - if (!privateKey) { + if (!privateKey) { + ws.close(); + return; + } + + const dockerHost = await getDockerHost(); + + ws.send(`Found Docker host: ${dockerHost}\n`); + + connectionDetails = { + host: dockerHost, + port, + username, + privateKey, + }; + } catch (error) { + console.error(`Error setting up private SSH key: ${error}`); + ws.send(`Error setting up private SSH key: ${error}`); ws.close(); return; } - - connectionDetails = { - host: "localhost", - port, - username, - privateKey, - }; } else { const server = await findServerById(serverId); @@ -161,9 +183,15 @@ export const setupTerminalWebSocketServer = ( }) .on("error", (err) => { if (err.level === "client-authentication") { - ws.send( - `Authentication failed: Unauthorized ${isLocalServer ? "" : "private SSH key or "}username.\n❌ Error: ${err.message} ${err.level}`, - ); + if (isLocalServer) { + ws.send( + `Authentication failed: Please run the command below on your server to allow access. Make sure to run it as the same user as the one configured in connection settings:${COMMAND_TO_ALLOW_LOCAL_ACCESS}\nAfter running the command, reopen this window to reconnect. This procedure is required only once.`, + ); + } else { + ws.send( + `Authentication failed: Unauthorized private SSH key or username.\n❌ Error: ${err.message} ${err.level}`, + ); + } } else { ws.send(`SSH connection error: ${err.message} ❌ `); } diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index 9c78869d..b593595b 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -1,18 +1,8 @@ -import { execAsync } from "@dokploy/server"; +import { execAsync, paths } from "@dokploy/server"; import os from "node:os"; import path from "node:path"; import fs from "node:fs"; -const HOME_PATH = process.env.HOME || process.env.USERPROFILE || "/"; - -const LOCAL_SSH_KEY_PATH = path.join( - HOME_PATH, - ".ssh", - "auto_generated-dokploy-local", -); - -const AUTHORIZED_KEYS_PATH = path.join(HOME_PATH, ".ssh", "authorized_keys"); - export const getShell = () => { switch (os.platform()) { case "win32": @@ -24,39 +14,25 @@ export const getShell = () => { } }; -/** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key, also automatically appends the public key to `authorized_keys`, creating the file if needed. */ +/** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key. + * + * In case of permission failures when running locally, run the command below: + +``` +sudo chown -R $USER:$USER /etc/dokploy/ssh +``` + +*/ export const setupLocalServerSSHKey = async () => { - try { - if (!fs.existsSync(LOCAL_SSH_KEY_PATH)) { - // Generate new SSH key if it hasn't been created yet - await execAsync( - `ssh-keygen -t rsa -b 4096 -f ${LOCAL_SSH_KEY_PATH} -N ""`, - ); - } + const { SSH_PATH } = paths(true); + const sshKeyPath = path.join(SSH_PATH, "auto_generated-dokploy-local"); - const privateKey = fs.readFileSync(LOCAL_SSH_KEY_PATH, "utf8"); - const publicKey = fs.readFileSync(`${LOCAL_SSH_KEY_PATH}.pub`, "utf8"); - const authKeyContent = `${publicKey}\n`; - - if (!fs.existsSync(AUTHORIZED_KEYS_PATH)) { - // Create authorized_keys if it doesn't exist yet - fs.writeFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { mode: 0o600 }); - return privateKey; - } - - const existingAuthKeys = fs.readFileSync(AUTHORIZED_KEYS_PATH, "utf8"); - if (existingAuthKeys.includes(publicKey)) { - return privateKey; - } - - // Append the public key to authorized_keys - fs.appendFileSync(AUTHORIZED_KEYS_PATH, authKeyContent, { - mode: 0o600, - }); - - return privateKey; - } catch (error) { - console.error("Error getting private SSH key for local terminal:", error); - return ""; + if (!fs.existsSync(sshKeyPath)) { + // Generate new SSH key if it hasn't been created yet + await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N ""`); } + + const privateKey = fs.readFileSync(sshKeyPath, "utf8"); + + return privateKey; }; From 76c6d0256613aff49b619b5a5e34053808c32157 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:09:15 +0100 Subject: [PATCH 04/59] feat: add ssh key comment for auto generated key --- apps/dokploy/server/wss/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index b593595b..e98e5016 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -29,7 +29,7 @@ export const setupLocalServerSSHKey = async () => { if (!fs.existsSync(sshKeyPath)) { // Generate new SSH key if it hasn't been created yet - await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N ""`); + await execAsync(`ssh-keygen -t rsa -b 4096 -f ${sshKeyPath} -N "" -C "dokploy-local-access"`); } const privateKey = fs.readFileSync(sshKeyPath, "utf8"); From 41a970c526ed588cae852aa5608957af5fb33db7 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:09:54 +0100 Subject: [PATCH 05/59] feat: add permission grant command when key generation fails --- apps/dokploy/server/wss/terminal.ts | 18 +++++++++++++++++- apps/dokploy/server/wss/utils.ts | 7 ------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 87b096c0..9dafba22 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -20,6 +20,12 @@ const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." # ----------------------------------------`; +const COMMAND_TO_GRANT_PERMISSION_ACCESS = ` +# ---------------------------------------- + sudo chown -R $USER:$USER /etc/dokploy/ssh +# ---------------------------------------- +`; + export const getPublicIpWithFallback = async () => { // @ts-ignore let ip = null; @@ -105,7 +111,17 @@ export const setupTerminalWebSocketServer = ( }; } catch (error) { console.error(`Error setting up private SSH key: ${error}`); - ws.send(`Error setting up private SSH key: ${error}`); + ws.send(`Error setting up private SSH key: ${error}\n`); + + if ( + error instanceof Error && + error.message.includes("Permission denied") + ) { + ws.send( + `Please run the following command on your server to grant permission access and then reopen this window to reconnect:${COMMAND_TO_GRANT_PERMISSION_ACCESS}`, + ); + } + ws.close(); return; } diff --git a/apps/dokploy/server/wss/utils.ts b/apps/dokploy/server/wss/utils.ts index e98e5016..cd50130c 100644 --- a/apps/dokploy/server/wss/utils.ts +++ b/apps/dokploy/server/wss/utils.ts @@ -15,13 +15,6 @@ export const getShell = () => { }; /** Returns private SSH key for dokploy local server terminal. Uses already created SSH key or generates a new SSH key. - * - * In case of permission failures when running locally, run the command below: - -``` -sudo chown -R $USER:$USER /etc/dokploy/ssh -``` - */ export const setupLocalServerSSHKey = async () => { const { SSH_PATH } = paths(true); From ee9f4796c3ee80dac546df48bee65281e51c3bb4 Mon Sep 17 00:00:00 2001 From: UndefinedPony Date: Mon, 6 Jan 2025 12:12:23 +0100 Subject: [PATCH 06/59] style: remove spaces from commands --- apps/dokploy/server/wss/terminal.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts index 9dafba22..3f0d6a80 100644 --- a/apps/dokploy/server/wss/terminal.ts +++ b/apps/dokploy/server/wss/terminal.ts @@ -12,17 +12,17 @@ import { getDockerHost } from "../utils/docker"; const COMMAND_TO_ALLOW_LOCAL_ACCESS = ` # ---------------------------------------- - mkdir -p $HOME/.ssh && \\ - chmod 700 $HOME/.ssh && \\ - touch $HOME/.ssh/authorized_keys && \\ - chmod 600 $HOME/.ssh/authorized_keys && \\ - cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ - echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." +mkdir -p $HOME/.ssh && \\ +chmod 700 $HOME/.ssh && \\ +touch $HOME/.ssh/authorized_keys && \\ +chmod 600 $HOME/.ssh/authorized_keys && \\ +cat /etc/dokploy/ssh/auto_generated-dokploy-local.pub >> $HOME/.ssh/authorized_keys && \\ +echo "✓ Dokploy SSH key added successfully. Reopen the terminal in Dokploy to reconnect." # ----------------------------------------`; const COMMAND_TO_GRANT_PERMISSION_ACCESS = ` # ---------------------------------------- - sudo chown -R $USER:$USER /etc/dokploy/ssh +sudo chown -R $USER:$USER /etc/dokploy/ssh # ---------------------------------------- `; From 4a9d7225c9307024ef9edca865519a7c4f7bb74d Mon Sep 17 00:00:00 2001 From: Tam Nguyen Date: Wed, 8 Jan 2025 13:44:59 +1100 Subject: [PATCH 07/59] chore: typo "accesed" changed to "accessed" for TS code --- .../settings/users/add-permissions.tsx | 22 +++++----- apps/dokploy/server/api/routers/project.ts | 42 +++++++++---------- packages/server/src/db/schema/user.ts | 12 +++--- packages/server/src/services/user.ts | 20 ++++----- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx index bb82c982..7c1f5037 100644 --- a/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx +++ b/apps/dokploy/components/dashboard/settings/users/add-permissions.tsx @@ -30,8 +30,8 @@ import { toast } from "sonner"; import { z } from "zod"; const addPermissions = z.object({ - accesedProjects: z.array(z.string()).optional(), - accesedServices: z.array(z.string()).optional(), + accessedProjects: z.array(z.string()).optional(), + accessedServices: z.array(z.string()).optional(), canCreateProjects: z.boolean().optional().default(false), canCreateServices: z.boolean().optional().default(false), canDeleteProjects: z.boolean().optional().default(false), @@ -66,8 +66,8 @@ export const AddUserPermissions = ({ userId }: Props) => { const form = useForm({ defaultValues: { - accesedProjects: [], - accesedServices: [], + accessedProjects: [], + accessedServices: [], }, resolver: zodResolver(addPermissions), }); @@ -75,8 +75,8 @@ export const AddUserPermissions = ({ userId }: Props) => { useEffect(() => { if (data) { form.reset({ - accesedProjects: data.accesedProjects || [], - accesedServices: data.accesedServices || [], + accessedProjects: data.accessedProjects || [], + accessedServices: data.accessedServices || [], canCreateProjects: data.canCreateProjects, canCreateServices: data.canCreateServices, canDeleteProjects: data.canDeleteProjects, @@ -98,8 +98,8 @@ export const AddUserPermissions = ({ userId }: Props) => { canDeleteServices: data.canDeleteServices, canDeleteProjects: data.canDeleteProjects, canAccessToTraefikFiles: data.canAccessToTraefikFiles, - accesedProjects: data.accesedProjects || [], - accesedServices: data.accesedServices || [], + accessedProjects: data.accessedProjects || [], + accessedServices: data.accessedServices || [], canAccessToDocker: data.canAccessToDocker, canAccessToAPI: data.canAccessToAPI, canAccessToSSHKeys: data.canAccessToSSHKeys, @@ -318,7 +318,7 @@ export const AddUserPermissions = ({ userId }: Props) => { /> (
@@ -339,7 +339,7 @@ export const AddUserPermissions = ({ userId }: Props) => { { return ( { { return ( { if (ctx.user.rol === "user") { - const { accesedServices } = await findUserByAuthId(ctx.user.authId); + const { accessedServices } = await findUserByAuthId(ctx.user.authId); await checkProjectAccess(ctx.user.authId, "access", input.projectId); @@ -79,28 +79,28 @@ export const projectRouter = createTRPCRouter({ ), with: { compose: { - where: buildServiceFilter(compose.composeId, accesedServices), + where: buildServiceFilter(compose.composeId, accessedServices), }, applications: { where: buildServiceFilter( applications.applicationId, - accesedServices, + accessedServices, ), }, mariadb: { - where: buildServiceFilter(mariadb.mariadbId, accesedServices), + where: buildServiceFilter(mariadb.mariadbId, accessedServices), }, mongo: { - where: buildServiceFilter(mongo.mongoId, accesedServices), + where: buildServiceFilter(mongo.mongoId, accessedServices), }, mysql: { - where: buildServiceFilter(mysql.mysqlId, accesedServices), + where: buildServiceFilter(mysql.mysqlId, accessedServices), }, postgres: { - where: buildServiceFilter(postgres.postgresId, accesedServices), + where: buildServiceFilter(postgres.postgresId, accessedServices), }, redis: { - where: buildServiceFilter(redis.redisId, accesedServices), + where: buildServiceFilter(redis.redisId, accessedServices), }, }, }); @@ -125,18 +125,18 @@ export const projectRouter = createTRPCRouter({ }), all: protectedProcedure.query(async ({ ctx }) => { if (ctx.user.rol === "user") { - const { accesedProjects, accesedServices } = await findUserByAuthId( + const { accessedProjects, accessedServices } = await findUserByAuthId( ctx.user.authId, ); - if (accesedProjects.length === 0) { + if (accessedProjects.length === 0) { return []; } const query = await db.query.projects.findMany({ where: and( sql`${projects.projectId} IN (${sql.join( - accesedProjects.map((projectId) => sql`${projectId}`), + accessedProjects.map((projectId) => sql`${projectId}`), sql`, `, )})`, eq(projects.adminId, ctx.user.adminId), @@ -145,27 +145,27 @@ export const projectRouter = createTRPCRouter({ applications: { where: buildServiceFilter( applications.applicationId, - accesedServices, + accessedServices, ), with: { domains: true }, }, mariadb: { - where: buildServiceFilter(mariadb.mariadbId, accesedServices), + where: buildServiceFilter(mariadb.mariadbId, accessedServices), }, mongo: { - where: buildServiceFilter(mongo.mongoId, accesedServices), + where: buildServiceFilter(mongo.mongoId, accessedServices), }, mysql: { - where: buildServiceFilter(mysql.mysqlId, accesedServices), + where: buildServiceFilter(mysql.mysqlId, accessedServices), }, postgres: { - where: buildServiceFilter(postgres.postgresId, accesedServices), + where: buildServiceFilter(postgres.postgresId, accessedServices), }, redis: { - where: buildServiceFilter(redis.redisId, accesedServices), + where: buildServiceFilter(redis.redisId, accessedServices), }, compose: { - where: buildServiceFilter(compose.composeId, accesedServices), + where: buildServiceFilter(compose.composeId, accessedServices), with: { domains: true }, }, }, @@ -239,10 +239,10 @@ export const projectRouter = createTRPCRouter({ } }), }); -function buildServiceFilter(fieldName: AnyPgColumn, accesedServices: string[]) { - return accesedServices.length > 0 +function buildServiceFilter(fieldName: AnyPgColumn, accessedServices: string[]) { + return accessedServices.length > 0 ? sql`${fieldName} IN (${sql.join( - accesedServices.map((serviceId) => sql`${serviceId}`), + accessedServices.map((serviceId) => sql`${serviceId}`), sql`, `, )})` : sql`1 = 0`; diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts index fec3d127..735898f9 100644 --- a/packages/server/src/db/schema/user.ts +++ b/packages/server/src/db/schema/user.ts @@ -40,11 +40,11 @@ export const users = pgTable("user", { canAccessToTraefikFiles: boolean("canAccessToTraefikFiles") .notNull() .default(false), - accesedProjects: text("accesedProjects") + accessedProjects: text("accesedProjects") .array() .notNull() .default(sql`ARRAY[]::text[]`), - accesedServices: text("accesedServices") + accessedServices: text("accesedServices") .array() .notNull() .default(sql`ARRAY[]::text[]`), @@ -73,8 +73,8 @@ const createSchema = createInsertSchema(users, { token: z.string().min(1), isRegistered: z.boolean().optional(), adminId: z.string(), - accesedProjects: z.array(z.string()).optional(), - accesedServices: z.array(z.string()).optional(), + accessedProjects: z.array(z.string()).optional(), + accessedServices: z.array(z.string()).optional(), canCreateProjects: z.boolean().optional(), canCreateServices: z.boolean().optional(), canDeleteProjects: z.boolean().optional(), @@ -106,8 +106,8 @@ export const apiAssignPermissions = createSchema canCreateServices: true, canDeleteProjects: true, canDeleteServices: true, - accesedProjects: true, - accesedServices: true, + accessedProjects: true, + accessedServices: true, canAccessToTraefikFiles: true, canAccessToDocker: true, canAccessToAPI: true, diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts index 1cfe1260..c8a9849c 100644 --- a/packages/server/src/services/user.ts +++ b/packages/server/src/services/user.ts @@ -54,7 +54,7 @@ export const addNewProject = async (authId: string, projectId: string) => { await db .update(users) .set({ - accesedProjects: [...user.accesedProjects, projectId], + accessedProjects: [...user.accessedProjects, projectId], }) .where(eq(users.authId, authId)); }; @@ -64,7 +64,7 @@ export const addNewService = async (authId: string, serviceId: string) => { await db .update(users) .set({ - accesedServices: [...user.accesedServices, serviceId], + accessedServices: [...user.accessedServices, serviceId], }) .where(eq(users.authId, authId)); }; @@ -73,8 +73,8 @@ export const canPerformCreationService = async ( userId: string, projectId: string, ) => { - const { accesedProjects, canCreateServices } = await findUserByAuthId(userId); - const haveAccessToProject = accesedProjects.includes(projectId); + const { accessedProjects, canCreateServices } = await findUserByAuthId(userId); + const haveAccessToProject = accessedProjects.includes(projectId); if (canCreateServices && haveAccessToProject) { return true; @@ -87,8 +87,8 @@ export const canPerformAccessService = async ( userId: string, serviceId: string, ) => { - const { accesedServices } = await findUserByAuthId(userId); - const haveAccessToService = accesedServices.includes(serviceId); + const { accessedServices } = await findUserByAuthId(userId); + const haveAccessToService = accessedServices.includes(serviceId); if (haveAccessToService) { return true; @@ -101,8 +101,8 @@ export const canPeformDeleteService = async ( authId: string, serviceId: string, ) => { - const { accesedServices, canDeleteServices } = await findUserByAuthId(authId); - const haveAccessToService = accesedServices.includes(serviceId); + const { accessedServices, canDeleteServices } = await findUserByAuthId(authId); + const haveAccessToService = accessedServices.includes(serviceId); if (canDeleteServices && haveAccessToService) { return true; @@ -135,9 +135,9 @@ export const canPerformAccessProject = async ( authId: string, projectId: string, ) => { - const { accesedProjects } = await findUserByAuthId(authId); + const { accessedProjects } = await findUserByAuthId(authId); - const haveAccessToProject = accesedProjects.includes(projectId); + const haveAccessToProject = accessedProjects.includes(projectId); if (haveAccessToProject) { return true; From 553ae7065684ff9f19afefc9725ac5a672f3ae2a Mon Sep 17 00:00:00 2001 From: shiqocred <90097870+shiqocred@users.noreply.github.com> Date: Sat, 11 Jan 2025 11:01:53 +0700 Subject: [PATCH 08/59] feat(i18n): indonesian language (#1082) * Update languages.ts * Create common.json * Create settings.json --- apps/dokploy/lib/languages.ts | 1 + apps/dokploy/public/locales/id/common.json | 1 + apps/dokploy/public/locales/id/settings.json | 58 ++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 apps/dokploy/public/locales/id/common.json create mode 100644 apps/dokploy/public/locales/id/settings.json diff --git a/apps/dokploy/lib/languages.ts b/apps/dokploy/lib/languages.ts index 74113cb2..8765057b 100644 --- a/apps/dokploy/lib/languages.ts +++ b/apps/dokploy/lib/languages.ts @@ -16,6 +16,7 @@ export const Languages = { spanish: { code: "es", name: "Español" }, norwegian: { code: "no", name: "Norsk" }, azerbaijani: { code: "az", name: "Azərbaycan" }, + indonesian: {code: "id", name: "Bahasa Indonesia"} }; export type Language = keyof typeof Languages; diff --git a/apps/dokploy/public/locales/id/common.json b/apps/dokploy/public/locales/id/common.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/apps/dokploy/public/locales/id/common.json @@ -0,0 +1 @@ +{} diff --git a/apps/dokploy/public/locales/id/settings.json b/apps/dokploy/public/locales/id/settings.json new file mode 100644 index 00000000..489ddc01 --- /dev/null +++ b/apps/dokploy/public/locales/id/settings.json @@ -0,0 +1,58 @@ +{ + "settings.common.save": "Simpan", + "settings.common.enterTerminal": "Buka Terminal", + "settings.server.domain.title": "Domain Server", + "settings.server.domain.description": "Tambahkan domain ke aplikasi server anda.", + "settings.server.domain.form.domain": "Domain", + "settings.server.domain.form.letsEncryptEmail": "Email Let's Encrypt", + "settings.server.domain.form.certificate.label": "Penyedia Sertifikat", + "settings.server.domain.form.certificate.placeholder": "Pilih sertifikat", + "settings.server.domain.form.certificateOptions.none": "Tidak ada", + "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt", + + "settings.server.webServer.title": "Server Web", + "settings.server.webServer.description": "Muat ulang atau bersihkan server web.", + "settings.server.webServer.actions": "Opsi", + "settings.server.webServer.reload": "Muat ulang", + "settings.server.webServer.watchLogs": "Lihat log", + "settings.server.webServer.updateServerIp": "Perbarui IP Server", + "settings.server.webServer.server.label": "Server", + "settings.server.webServer.traefik.label": "Traefik", + "settings.server.webServer.traefik.modifyEnv": "Ubah Environment", + "settings.server.webServer.traefik.managePorts": "Pengaturan Port Tambahan", + "settings.server.webServer.traefik.managePortsDescription": "Tambahkan atau hapus port tambahan untuk Traefik", + "settings.server.webServer.traefik.targetPort": "Port Tujuan", + "settings.server.webServer.traefik.publishedPort": "Port saai ini", + "settings.server.webServer.traefik.addPort": "Tambah Port", + "settings.server.webServer.traefik.portsUpdated": "Port berhasil diperbarui", + "settings.server.webServer.traefik.portsUpdateError": "Gagal memperbarui Port", + "settings.server.webServer.traefik.publishMode": "Pilihan mode Port", + "settings.server.webServer.storage.label": "Penyimpanan", + "settings.server.webServer.storage.cleanUnusedImages": "Hapus Image tidak terpakai", + "settings.server.webServer.storage.cleanUnusedVolumes": "Hapus Volume tidak terpakai", + "settings.server.webServer.storage.cleanStoppedContainers": "Hapus Container tidak aktif", + "settings.server.webServer.storage.cleanDockerBuilder": "Bersihkan Docker Builder & System", + "settings.server.webServer.storage.cleanMonitoring": "Bersihkan Monitoring", + "settings.server.webServer.storage.cleanAll": "Bersihkan", + + "settings.profile.title": "Akun", + "settings.profile.description": "Ubah detail profil Anda di sini.", + "settings.profile.email": "Email", + "settings.profile.password": "Kata Sandi", + "settings.profile.avatar": "Avatar", + + "settings.appearance.title": "Tampilan", + "settings.appearance.description": "Sesuaikan tema dasbor Anda.", + "settings.appearance.theme": "Tema", + "settings.appearance.themeDescription": "Pilih tema untuk dasbor Anda", + "settings.appearance.themes.light": "Terang", + "settings.appearance.themes.dark": "Gelap", + "settings.appearance.themes.system": "Sistem", + "settings.appearance.language": "Bahasa", + "settings.appearance.languageDescription": "Pilih bahasa untuk dasbor Anda", + + "settings.terminal.connectionSettings": "Pengaturan koneksi", + "settings.terminal.ipAddress": "Alamat IP", + "settings.terminal.port": "Port", + "settings.terminal.username": "Username" +} From 9db979e43f1a155917282ae3c925839e259521b9 Mon Sep 17 00:00:00 2001 From: Khiet Tam Nguyen <86177399+nktnet1@users.noreply.github.com> Date: Sat, 11 Jan 2025 15:02:40 +1100 Subject: [PATCH 09/59] fix(ui): full width for body tag (#1078) --- apps/dokploy/pages/_document.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/pages/_document.tsx b/apps/dokploy/pages/_document.tsx index c305be20..120bb827 100644 --- a/apps/dokploy/pages/_document.tsx +++ b/apps/dokploy/pages/_document.tsx @@ -6,7 +6,7 @@ export default function Document() { - +
From dd3618bfd95e5e6eef5c71ec31a24c5b7d0bdcaf Mon Sep 17 00:00:00 2001 From: shiqocred <90097870+shiqocred@users.noreply.github.com> Date: Sun, 12 Jan 2025 00:55:31 +0700 Subject: [PATCH 10/59] Add inline button telegram (#3) * Update utils.ts add type inline button * Update dokploy-restart.ts fixing format massage and adding [] for inline button type * Update docker-cleanup.ts fixing telegram message * Update database-backup.ts fixing telegram message * Update build-error.ts fixing message and adding button logs view * Update build-success.ts fixing message, adding domains props, adding inline button * Update compose.ts adding get domains compose and send to notif * Update application.ts adding get domains and send it to notif --- packages/server/src/services/application.ts | 4 +++ packages/server/src/services/compose.ts | 4 +++ .../src/utils/notifications/build-error.ts | 25 +++++++------ .../src/utils/notifications/build-success.ts | 36 +++++++++++++------ .../utils/notifications/database-backup.ts | 21 +++++------ .../src/utils/notifications/docker-cleanup.ts | 8 ++--- .../utils/notifications/dokploy-restart.ts | 7 ++-- .../server/src/utils/notifications/utils.ts | 7 ++++ 8 files changed, 68 insertions(+), 44 deletions(-) diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts index e2ed407f..fa690952 100644 --- a/packages/server/src/services/application.ts +++ b/packages/server/src/services/application.ts @@ -176,6 +176,7 @@ export const deployApplication = async ({ }) => { const application = await findApplicationById(applicationId); const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`; + const domains = application.domains.map(({ host, https }) => ({ host, https })); const deployment = await createDeployment({ applicationId: applicationId, title: titleLog, @@ -213,6 +214,7 @@ export const deployApplication = async ({ applicationType: "application", buildLink, adminId: application.project.adminId, + domains }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -285,6 +287,7 @@ export const deployRemoteApplication = async ({ }) => { const application = await findApplicationById(applicationId); const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`; + const domains = application.domains.map(({ host, https }) => ({ host, https })); const deployment = await createDeployment({ applicationId: applicationId, title: titleLog, @@ -332,6 +335,7 @@ export const deployRemoteApplication = async ({ applicationType: "application", buildLink, adminId: application.project.adminId, + domains }); } catch (error) { // @ts-ignore diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts index 50459c45..2ed3d462 100644 --- a/packages/server/src/services/compose.ts +++ b/packages/server/src/services/compose.ts @@ -209,6 +209,7 @@ export const deployCompose = async ({ const buildLink = `${await getDokployUrl()}/dashboard/project/${ compose.projectId }/services/compose/${compose.composeId}?tab=deployments`; + const domains = compose.domains.map(({ host, https }) => ({ host, https })); const deployment = await createDeploymentCompose({ composeId: composeId, title: titleLog, @@ -243,6 +244,7 @@ export const deployCompose = async ({ applicationType: "compose", buildLink, adminId: compose.project.adminId, + domains }); } catch (error) { await updateDeploymentStatus(deployment.deploymentId, "error"); @@ -313,6 +315,7 @@ export const deployRemoteCompose = async ({ const buildLink = `${await getDokployUrl()}/dashboard/project/${ compose.projectId }/services/compose/${compose.composeId}?tab=deployments`; + const domains = compose.domains.map(({ host, https }) => ({ host, https })); const deployment = await createDeploymentCompose({ composeId: composeId, title: titleLog, @@ -366,6 +369,7 @@ export const deployRemoteCompose = async ({ applicationType: "compose", buildLink, adminId: compose.project.adminId, + domains }); } catch (error) { // @ts-ignore diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts index 695b3786..ef4c16cc 100644 --- a/packages/server/src/utils/notifications/build-error.ts +++ b/packages/server/src/utils/notifications/build-error.ts @@ -3,6 +3,7 @@ import { notifications } from "@dokploy/server/db/schema"; import BuildFailedEmail from "@dokploy/server/emails/emails/build-failed"; import { renderAsync } from "@react-email/components"; import { and, eq } from "drizzle-orm"; +import { format } from "date-fns"; import { sendDiscordNotification, sendEmailNotification, @@ -113,21 +114,19 @@ export const sendBuildErrorNotifications = async ({ } if (telegram) { + const inlineButton = [ + [ + { + text: "Deployment Logs", + url: buildLink, + }, + ], + ]; + await sendTelegramNotification( telegram, - ` - ⚠️ Build Failed - - Project: ${projectName} - Application: ${applicationName} - Type: ${applicationType} - Time: ${date.toLocaleString()} - - Error: -
${errorMessage}
- - Build Details: ${buildLink} - `, + `⚠️ Build Failed\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}\n\nError:\n
${errorMessage}
`, + inlineButton ); } diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index 402b0cd2..3bfa18a8 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -16,6 +16,10 @@ interface Props { applicationType: string; buildLink: string; adminId: string; + domains: { + host: string; + https: boolean; + }[]; } export const sendBuildSuccessNotifications = async ({ @@ -24,6 +28,7 @@ export const sendBuildSuccessNotifications = async ({ applicationType, buildLink, adminId, + domains }: Props) => { const date = new Date(); const unixDate = ~~(Number(date) / 1000); @@ -107,18 +112,29 @@ export const sendBuildSuccessNotifications = async ({ } if (telegram) { + const chunkArray = (array: T[], chunkSize: number): T[][] => + Array.from({ length: Math.ceil(array.length / chunkSize) }, (_, i) => array.slice(i * chunkSize, i * chunkSize + chunkSize) + ); + + const inlineButton = [ + [ + { + text: "Deployment Logs", + url: buildLink, + }, + ], + ...chunkArray(domains, 2).map((chunk) => + chunk.map((data) => ({ + text: data.host, + url: `${data.https ? "https" : "http"}://${data.host}`, + })) + ), + ]; + await sendTelegramNotification( telegram, - ` - ✅ Build Success - - Project: ${projectName} - Application: ${applicationName} - Type: ${applicationType} - Time: ${date.toLocaleString()} - - Build Details: ${buildLink} - `, + `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}Type: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + inlineButton ); } diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts index 3aec6f3d..6594f87c 100644 --- a/packages/server/src/utils/notifications/database-backup.ts +++ b/packages/server/src/utils/notifications/database-backup.ts @@ -3,6 +3,7 @@ import { notifications } from "@dokploy/server/db/schema"; import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup"; import { renderAsync } from "@react-email/components"; import { and, eq } from "drizzle-orm"; +import { format } from "date-fns"; import { sendDiscordNotification, sendEmailNotification, @@ -121,19 +122,15 @@ export const sendDatabaseBackupNotifications = async ({ } if (telegram) { + const isError = type === "error" && errorMessage; + const statusEmoji = type === "success" ? "✅" : "❌"; - const messageText = ` - ${statusEmoji} Database Backup ${type === "success" ? "Successful" : "Failed"} - - Project: ${projectName} - Application: ${applicationName} - Type: ${databaseType} - Time: ${date.toLocaleString()} - - Status: ${type === "success" ? "Successful" : "Failed"} - ${type === "error" && errorMessage ? `Error: ${errorMessage}` : ""} - `; - await sendTelegramNotification(telegram, messageText); + const typeStatus = type === "success" ? "Successful" : "Failed"; + const errorMsg = isError ? `\n\nError:\n
${errorMessage}
` : ""; + + const messageText = `${statusEmoji} Database Backup ${typeStatus}\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${databaseType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}${isError ? errorMsg : ""}`; + + await sendTelegramNotification(telegram, messageText, []); } if (slack) { diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts index c95c7906..8623867f 100644 --- a/packages/server/src/utils/notifications/docker-cleanup.ts +++ b/packages/server/src/utils/notifications/docker-cleanup.ts @@ -3,6 +3,7 @@ import { notifications } from "@dokploy/server/db/schema"; import DockerCleanupEmail from "@dokploy/server/emails/emails/docker-cleanup"; import { renderAsync } from "@react-email/components"; import { and, eq } from "drizzle-orm"; +import { format } from "date-fns"; import { sendDiscordNotification, sendEmailNotification, @@ -82,11 +83,8 @@ export const sendDockerCleanupNotifications = async ( if (telegram) { await sendTelegramNotification( telegram, - ` - ✅ Docker Cleanup - Message: ${message} - Time: ${date.toLocaleString()} - `, + `✅ Docker Cleanup\n\nMessage: ${message}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + [] ); } diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts index 16170349..ffa838b7 100644 --- a/packages/server/src/utils/notifications/dokploy-restart.ts +++ b/packages/server/src/utils/notifications/dokploy-restart.ts @@ -9,6 +9,7 @@ import { sendSlackNotification, sendTelegramNotification, } from "./utils"; +import { format } from "date-fns"; export const sendDokployRestartNotifications = async () => { const date = new Date(); @@ -67,10 +68,8 @@ export const sendDokployRestartNotifications = async () => { if (telegram) { await sendTelegramNotification( telegram, - ` - ✅ Dokploy Serverd Restarted - Time: ${date.toLocaleString()} - `, + `✅ Dokploy Serverd Restarted\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + [] ); } diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts index 8327c33d..96301156 100644 --- a/packages/server/src/utils/notifications/utils.ts +++ b/packages/server/src/utils/notifications/utils.ts @@ -55,6 +55,10 @@ export const sendDiscordNotification = async ( export const sendTelegramNotification = async ( connection: typeof telegram.$inferInsert, messageText: string, + inlineButton: { + text: string; + url: string; + }[][] ) => { try { const url = `https://api.telegram.org/bot${connection.botToken}/sendMessage`; @@ -66,6 +70,9 @@ export const sendTelegramNotification = async ( text: messageText, parse_mode: "HTML", disable_web_page_preview: true, + reply_markup: { + inline_keyboard: inlineButton, + }, }), }); } catch (err) { From 1d8db07fa1a2841ec899f76eb969d513da908865 Mon Sep 17 00:00:00 2001 From: shiqocred <90097870+shiqocred@users.noreply.github.com> Date: Sun, 12 Jan 2025 01:20:39 +0700 Subject: [PATCH 11/59] Add inline button telegram (#4) * Update utils.ts add type inline button * Update dokploy-restart.ts fixing format massage and adding [] for inline button type * Update docker-cleanup.ts fixing telegram message * Update database-backup.ts fixing telegram message * Update build-error.ts fixing message and adding button logs view * Update build-success.ts fixing message, adding domains props, adding inline button * Update compose.ts adding get domains compose and send to notif * Update application.ts adding get domains and send it to notif * Update build-success.ts fix space * Update dokploy-restart.ts fixing space --- packages/server/src/utils/notifications/build-success.ts | 2 +- packages/server/src/utils/notifications/dokploy-restart.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index 3bfa18a8..7b03d0c1 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -133,7 +133,7 @@ export const sendBuildSuccessNotifications = async ({ await sendTelegramNotification( telegram, - `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}Type: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + `✅ Build Success\n\nProject: ${projectName}\nApplication: ${applicationName}\nType: ${applicationType}\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, inlineButton ); } diff --git a/packages/server/src/utils/notifications/dokploy-restart.ts b/packages/server/src/utils/notifications/dokploy-restart.ts index ffa838b7..7ee50b6a 100644 --- a/packages/server/src/utils/notifications/dokploy-restart.ts +++ b/packages/server/src/utils/notifications/dokploy-restart.ts @@ -68,7 +68,7 @@ export const sendDokployRestartNotifications = async () => { if (telegram) { await sendTelegramNotification( telegram, - `✅ Dokploy Serverd Restarted\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, + `✅ Dokploy Serverd Restarted\n\nDate: ${format(date, "PP")}\nTime: ${format(date, "pp")}`, [] ); } From c0b8a411bd5a75a0cbeba3b7447a8726c2e993ba Mon Sep 17 00:00:00 2001 From: shiqocred <90097870+shiqocred@users.noreply.github.com> Date: Sun, 12 Jan 2025 02:04:10 +0700 Subject: [PATCH 12/59] Update build-success.ts (#5) add format --- packages/server/src/utils/notifications/build-success.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts index 7b03d0c1..366b4591 100644 --- a/packages/server/src/utils/notifications/build-success.ts +++ b/packages/server/src/utils/notifications/build-success.ts @@ -3,6 +3,7 @@ import { notifications } from "@dokploy/server/db/schema"; import BuildSuccessEmail from "@dokploy/server/emails/emails/build-success"; import { renderAsync } from "@react-email/components"; import { and, eq } from "drizzle-orm"; +import { format } from "date-fns"; import { sendDiscordNotification, sendEmailNotification, From d2094d6d76821b65c95c91bb3c987e5567574364 Mon Sep 17 00:00:00 2001 From: shiqocred <90097870+shiqocred@users.noreply.github.com> Date: Sun, 12 Jan 2025 02:14:09 +0700 Subject: [PATCH 13/59] Update notification.ts (#6) fix router notification --- apps/dokploy/server/api/routers/notification.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts index f8869503..61a1310e 100644 --- a/apps/dokploy/server/api/routers/notification.ts +++ b/apps/dokploy/server/api/routers/notification.ts @@ -134,7 +134,7 @@ export const notificationRouter = createTRPCRouter({ .input(apiTestTelegramConnection) .mutation(async ({ input }) => { try { - await sendTelegramNotification(input, "Hi, From Dokploy 👋"); + await sendTelegramNotification(input, "Hi, From Dokploy 👋", []); return true; } catch (error) { throw new TRPCError({ From 87f4c7b71b9cd41d68a8f26cf84503e5d2b32f63 Mon Sep 17 00:00:00 2001 From: Tobias Barsnes Date: Sun, 12 Jan 2025 00:42:05 +0100 Subject: [PATCH 14/59] refactor: better focus-visible a11y (#1017) * refactor: better focus-visible a11y * style: fix tree leaf width * style: input focus ring size * refactor: focus a11y on project pages * fix: project-environment import statement * style: `ring-border` on input * refactor: use ring border --------- Co-authored-by: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> --- .../dashboard/docker/logs/docker-logs-id.tsx | 2 +- .../dashboard/docker/show/show-containers.tsx | 2 +- .../dashboard/project/add-template.tsx | 8 ++++---- .../components/dashboard/projects/show.tsx | 12 ++++++++---- .../dashboard/settings/profile/profile-form.tsx | 4 ++-- apps/dokploy/components/layouts/navbar.tsx | 15 ++++++++++----- .../components/layouts/navigation-tabs.tsx | 10 +++++++--- apps/dokploy/components/ui/button.tsx | 4 ++-- apps/dokploy/components/ui/file-tree.tsx | 13 +++++++------ apps/dokploy/components/ui/input.tsx | 2 +- apps/dokploy/components/ui/select.tsx | 2 +- apps/dokploy/components/ui/tabs.tsx | 3 ++- apps/dokploy/components/ui/textarea.tsx | 2 +- apps/dokploy/components/ui/tooltip.tsx | 16 ++++++++++++++-- .../pages/dashboard/project/[projectId].tsx | 7 +++---- 15 files changed, 64 insertions(+), 38 deletions(-) diff --git a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx index 1fd8cea4..b638991c 100644 --- a/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx +++ b/apps/dokploy/components/dashboard/docker/logs/docker-logs-id.tsx @@ -226,7 +226,7 @@ export const DockerLogsId: React.FC = ({ return (
-
+
diff --git a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx index e55e6271..128009eb 100644 --- a/apps/dokploy/components/dashboard/docker/show/show-containers.tsx +++ b/apps/dokploy/components/dashboard/docker/show/show-containers.tsx @@ -72,7 +72,7 @@ export const ShowContainers = ({ serverId }: Props) => { return (
-
+
{ href={template.links.github} target="_blank" className={ - "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors" + "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" } > @@ -236,7 +236,7 @@ export const AddTemplate = ({ projectId }: Props) => { href={template.links.website} target="_blank" className={ - "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors" + "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" } > @@ -247,7 +247,7 @@ export const AddTemplate = ({ projectId }: Props) => { href={template.links.docs} target="_blank" className={ - "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors" + "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" } > @@ -257,7 +257,7 @@ export const AddTemplate = ({ projectId }: Props) => { href={`https://github.com/Dokploy/dokploy/tree/canary/apps/dokploy/templates/${template.id}`} target="_blank" className={ - "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors" + "text-sm text-muted-foreground p-3 rounded-full hover:bg-border items-center flex transition-colors focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none" } > diff --git a/apps/dokploy/components/dashboard/projects/show.tsx b/apps/dokploy/components/dashboard/projects/show.tsx index 59262183..be85d6f1 100644 --- a/apps/dokploy/components/dashboard/projects/show.tsx +++ b/apps/dokploy/components/dashboard/projects/show.tsx @@ -126,13 +126,16 @@ export const ShowProjects = () => { return (
- + {flattedDomains.length > 1 ? ( ); }); diff --git a/apps/dokploy/components/ui/input.tsx b/apps/dokploy/components/ui/input.tsx index 8fe7ab28..18b713af 100644 --- a/apps/dokploy/components/ui/input.tsx +++ b/apps/dokploy/components/ui/input.tsx @@ -14,7 +14,7 @@ const Input = React.forwardRef( type={type} className={cn( // bg-gray - "flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + "flex h-10 w-full rounded-md bg-input px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50", className, )} ref={ref} diff --git a/apps/dokploy/components/ui/select.tsx b/apps/dokploy/components/ui/select.tsx index e9e5b35b..cbf47dca 100644 --- a/apps/dokploy/components/ui/select.tsx +++ b/apps/dokploy/components/ui/select.tsx @@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef< span]:line-clamp-1", + "flex h-10 w-full items-center justify-between rounded-md border border-input bg-input px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className, )} {...props} diff --git a/apps/dokploy/components/ui/tabs.tsx b/apps/dokploy/components/ui/tabs.tsx index e54c215a..ffbbaed1 100644 --- a/apps/dokploy/components/ui/tabs.tsx +++ b/apps/dokploy/components/ui/tabs.tsx @@ -38,13 +38,14 @@ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; const TabsContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( +>(({ className, tabIndex, ...props }, ref) => ( )); diff --git a/apps/dokploy/components/ui/textarea.tsx b/apps/dokploy/components/ui/textarea.tsx index 5b434e4a..41561953 100644 --- a/apps/dokploy/components/ui/textarea.tsx +++ b/apps/dokploy/components/ui/textarea.tsx @@ -10,7 +10,7 @@ const Textarea = React.forwardRef( return (