-
diff --git a/components/shared/toggle-visibility-input.tsx b/components/shared/toggle-visibility-input.tsx
new file mode 100644
index 00000000..d3a2b5e3
--- /dev/null
+++ b/components/shared/toggle-visibility-input.tsx
@@ -0,0 +1,26 @@
+import { useState } from "react";
+import { EyeIcon, EyeOffIcon } from "lucide-react";
+import { Input, type InputProps } from "../ui/input";
+import { Button } from "../ui/button";
+
+export const ToggleVisibilityInput = ({ ...props }: InputProps) => {
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false);
+
+ const togglePasswordVisibility = () => {
+ setIsPasswordVisible((prevVisibility) => !prevVisibility);
+ };
+
+ const inputType = isPasswordVisible ? "text" : "password";
+ return (
+
+
+
+
+ );
+};
diff --git a/lib/slug.ts b/lib/slug.ts
new file mode 100644
index 00000000..a4982a0e
--- /dev/null
+++ b/lib/slug.ts
@@ -0,0 +1,15 @@
+import slug from "slugify";
+
+export const slugify = (text: string | undefined) => {
+ if (!text) {
+ return "";
+ }
+
+ const cleanedText = text.trim().replace(/[^a-zA-Z0-9\s]/g, "");
+
+ return slug(cleanedText, {
+ lower: true,
+ trim: true,
+ strict: true,
+ });
+};
diff --git a/package.json b/package.json
index f85c0af6..36b8dab3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "dokploy",
- "version": "v0.2.1",
+ "version": "v0.2.2",
"private": true,
"license": "AGPL-3.0-only",
"type": "module",
@@ -31,11 +31,11 @@
"test": "vitest --config __test__/vitest.config.ts"
},
"dependencies": {
- "@codemirror/language":"^6.10.1",
"@aws-sdk/client-s3": "3.515.0",
- "@codemirror/legacy-modes":"6.4.0",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-yaml": "^6.1.1",
+ "@codemirror/language": "^6.10.1",
+ "@codemirror/legacy-modes": "6.4.0",
"@faker-js/faker": "^8.4.1",
"@hookform/resolvers": "^3.3.4",
"@lucia-auth/adapter-drizzle": "1.0.7",
@@ -76,6 +76,7 @@
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"copy-to-clipboard": "^3.3.3",
+ "copy-webpack-plugin": "^12.0.2",
"date-fns": "3.6.0",
"dockerode": "4.0.2",
"dockerode-compose": "^1.4.0",
@@ -105,6 +106,7 @@
"react-dom": "18.2.0",
"react-hook-form": "^7.49.3",
"recharts": "^2.12.3",
+ "slugify": "^1.6.6",
"sonner": "^1.4.0",
"superjson": "^2.2.1",
"tailwind-merge": "^2.2.0",
@@ -113,9 +115,7 @@
"use-resize-observer": "9.1.0",
"ws": "8.16.0",
"xterm-addon-fit": "^0.8.0",
- "zod": "^3.23.4",
- "copy-webpack-plugin": "^12.0.2"
-
+ "zod": "^3.23.4"
},
"devDependencies": {
"@biomejs/biome": "1.7.1",
diff --git a/pages/dashboard/project/[projectId].tsx b/pages/dashboard/project/[projectId].tsx
index c4752b19..acc4a191 100644
--- a/pages/dashboard/project/[projectId].tsx
+++ b/pages/dashboard/project/[projectId].tsx
@@ -210,9 +210,12 @@ const Project = (
Actions
-
-
-
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7c44c7cf..f7af8ad9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -230,6 +230,9 @@ dependencies:
recharts:
specifier: ^2.12.3
version: 2.12.3(react-dom@18.2.0)(react@18.2.0)
+ slugify:
+ specifier: ^1.6.6
+ version: 1.6.6
sonner:
specifier: ^1.4.0
version: 1.4.3(react-dom@18.2.0)(react@18.2.0)
@@ -8015,6 +8018,11 @@ packages:
engines: {node: '>=14.16'}
dev: false
+ /slugify@1.6.6:
+ resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
+ engines: {node: '>=8.0.0'}
+ dev: false
+
/sonner@1.4.3(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-SArYlHbkjqRuLiR0iGY2ZSr09oOrxw081ZZkQPfXrs8aZQLIBOLOdzTYxGJB5yIZ7qL56UEPmrX1YqbODwG0Lw==}
peerDependencies:
diff --git a/server/api/routers/application.ts b/server/api/routers/application.ts
index 69fa11cb..df5f2b50 100644
--- a/server/api/routers/application.ts
+++ b/server/api/routers/application.ts
@@ -65,7 +65,10 @@ export const applicationRouter = createTRPCRouter({
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newApplication.applicationId);
}
- } catch (error) {
+ } catch (error: unknown) {
+ if (error instanceof TRPCError) {
+ throw error;
+ }
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the application",
diff --git a/server/api/routers/compose.ts b/server/api/routers/compose.ts
index 6da5fe86..f23550b4 100644
--- a/server/api/routers/compose.ts
+++ b/server/api/routers/compose.ts
@@ -34,13 +34,18 @@ import { nanoid } from "nanoid";
import { removeDeploymentsByComposeId } from "../services/deployment";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { createCommand } from "@/server/utils/builders/compose";
-import { loadTemplateModule, readComposeFile } from "@/templates/utils";
+import {
+ generatePassword,
+ loadTemplateModule,
+ readComposeFile,
+} from "@/templates/utils";
import { findAdmin } from "../services/admin";
import { TRPCError } from "@trpc/server";
-import { findProjectById, slugifyProjectName } from "../services/project";
+import { findProjectById } from "../services/project";
import { createMount } from "../services/mount";
import type { TemplatesKeys } from "@/templates/types/templates-data.type";
import { templates } from "@/templates/templates";
+import { slugify } from "@/lib/slug";
export const composeRouter = createTRPCRouter({
create: protectedProcedure
@@ -229,7 +234,7 @@ export const composeRouter = createTRPCRouter({
const project = await findProjectById(input.projectId);
- const projectName = slugifyProjectName(`${project.name}-${input.id}`);
+ const projectName = slugify(`${project.name} ${input.id}`);
const { envs, mounts } = generate({
serverIp: admin.serverIp,
projectName: projectName,
@@ -241,6 +246,7 @@ export const composeRouter = createTRPCRouter({
env: envs.join("\n"),
name: input.id,
sourceType: "raw",
+ appName: `${projectName}-${generatePassword(6)}`,
});
if (ctx.user.rol === "user") {
diff --git a/server/api/routers/mariadb.ts b/server/api/routers/mariadb.ts
index 59e57748..2ab8dd6a 100644
--- a/server/api/routers/mariadb.ts
+++ b/server/api/routers/mariadb.ts
@@ -49,6 +49,9 @@ export const mariadbRouter = createTRPCRouter({
return true;
} catch (error) {
+ if (error instanceof TRPCError) {
+ throw error;
+ }
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mariadb database",
diff --git a/server/api/routers/mongo.ts b/server/api/routers/mongo.ts
index 705549b6..d9ddd2c2 100644
--- a/server/api/routers/mongo.ts
+++ b/server/api/routers/mongo.ts
@@ -49,6 +49,9 @@ export const mongoRouter = createTRPCRouter({
return true;
} catch (error) {
+ if (error instanceof TRPCError) {
+ throw error;
+ }
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mongo database",
diff --git a/server/api/routers/mysql.ts b/server/api/routers/mysql.ts
index 02f683ba..f520064b 100644
--- a/server/api/routers/mysql.ts
+++ b/server/api/routers/mysql.ts
@@ -50,6 +50,9 @@ export const mysqlRouter = createTRPCRouter({
return true;
} catch (error) {
+ if (error instanceof TRPCError) {
+ throw error;
+ }
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mysql database",
diff --git a/server/api/routers/postgres.ts b/server/api/routers/postgres.ts
index 45bd88a1..4dc7ff5d 100644
--- a/server/api/routers/postgres.ts
+++ b/server/api/routers/postgres.ts
@@ -49,6 +49,9 @@ export const postgresRouter = createTRPCRouter({
return true;
} catch (error) {
+ if (error instanceof TRPCError) {
+ throw error;
+ }
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting postgresql database",
diff --git a/server/api/services/application.ts b/server/api/services/application.ts
index a7e36533..3decd281 100644
--- a/server/api/services/application.ts
+++ b/server/api/services/application.ts
@@ -15,11 +15,23 @@ import { findAdmin } from "./admin";
import { createTraefikConfig } from "@/server/utils/traefik/application";
import { docker } from "@/server/constants";
import { getAdvancedStats } from "@/server/monitoring/utilts";
+import { validUniqueServerAppName } from "./project";
export type Application = typeof applications.$inferSelect;
export const createApplication = async (
input: typeof apiCreateApplication._type,
) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Application with this 'AppName' already exists",
+ });
+ }
+ }
+
return await db.transaction(async (tx) => {
const newApplication = await tx
.insert(applications)
diff --git a/server/api/services/compose.ts b/server/api/services/compose.ts
index 8f519e8a..3a773918 100644
--- a/server/api/services/compose.ts
+++ b/server/api/services/compose.ts
@@ -13,10 +13,21 @@ import { join } from "node:path";
import { COMPOSE_PATH } from "@/server/constants";
import { cloneGithubRepository } from "@/server/utils/providers/github";
import { cloneGitRepository } from "@/server/utils/providers/git";
+import { validUniqueServerAppName } from "./project";
export type Compose = typeof compose.$inferSelect;
export const createCompose = async (input: typeof apiCreateCompose._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
const newDestination = await db
.insert(compose)
.values({
@@ -39,6 +50,16 @@ export const createCompose = async (input: typeof apiCreateCompose._type) => {
export const createComposeByTemplate = async (
input: typeof compose.$inferInsert,
) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
const newDestination = await db
.insert(compose)
.values({
diff --git a/server/api/services/docker.ts b/server/api/services/docker.ts
index 57f60c36..8c951e8d 100644
--- a/server/api/services/docker.ts
+++ b/server/api/services/docker.ts
@@ -46,9 +46,7 @@ export const getContainers = async () => {
.filter((container) => !container.name.includes("dokploy"));
return containers;
- } catch (error) {
- console.error(`Execution error: ${error}`);
- }
+ } catch (error) {}
};
export const getConfig = async (containerId: string) => {
@@ -65,9 +63,7 @@ export const getConfig = async (containerId: string) => {
const config = JSON.parse(stdout);
return config;
- } catch (error) {
- console.error(`Execution error: ${error}`);
- }
+ } catch (error) {}
};
export const getContainersByAppNameMatch = async (appName: string) => {
@@ -103,9 +99,7 @@ export const getContainersByAppNameMatch = async (appName: string) => {
});
return containers || [];
- } catch (error) {
- console.error(`Execution error: ${error}`);
- }
+ } catch (error) {}
return [];
};
@@ -144,9 +138,7 @@ export const getContainersByAppLabel = async (appName: string) => {
});
return containers || [];
- } catch (error) {
- console.error(`Execution error: ${error}`);
- }
+ } catch (error) {}
return [];
};
diff --git a/server/api/services/mariadb.ts b/server/api/services/mariadb.ts
index 7545087f..1ebd3525 100644
--- a/server/api/services/mariadb.ts
+++ b/server/api/services/mariadb.ts
@@ -5,10 +5,22 @@ import { buildMariadb } from "@/server/utils/databases/mariadb";
import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
+import { validUniqueServerAppName } from "./project";
export type Mariadb = typeof mariadb.$inferSelect;
export const createMariadb = async (input: typeof apiCreateMariaDB._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
+
const newMariadb = await db
.insert(mariadb)
.values({
diff --git a/server/api/services/mongo.ts b/server/api/services/mongo.ts
index a7605ffe..e6114ef4 100644
--- a/server/api/services/mongo.ts
+++ b/server/api/services/mongo.ts
@@ -5,10 +5,22 @@ import { buildMongo } from "@/server/utils/databases/mongo";
import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
+import { validUniqueServerAppName } from "./project";
export type Mongo = typeof mongo.$inferSelect;
export const createMongo = async (input: typeof apiCreateMongo._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
+
const newMongo = await db
.insert(mongo)
.values({
diff --git a/server/api/services/mysql.ts b/server/api/services/mysql.ts
index b09aadaa..3482968d 100644
--- a/server/api/services/mysql.ts
+++ b/server/api/services/mysql.ts
@@ -5,11 +5,22 @@ import { buildMysql } from "@/server/utils/databases/mysql";
import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
-import { nanoid } from "nanoid";
+import { validUniqueServerAppName } from "./project";
export type MySql = typeof mysql.$inferSelect;
export const createMysql = async (input: typeof apiCreateMySql._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
+
const newMysql = await db
.insert(mysql)
.values({
diff --git a/server/api/services/postgres.ts b/server/api/services/postgres.ts
index 9575ac51..11ac1085 100644
--- a/server/api/services/postgres.ts
+++ b/server/api/services/postgres.ts
@@ -5,10 +5,22 @@ import { buildPostgres } from "@/server/utils/databases/postgres";
import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq, getTableColumns } from "drizzle-orm";
+import { validUniqueServerAppName } from "./project";
export type Postgres = typeof postgres.$inferSelect;
export const createPostgres = async (input: typeof apiCreatePostgres._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
+
const newPostgres = await db
.insert(postgres)
.values({
diff --git a/server/api/services/project.ts b/server/api/services/project.ts
index 48275607..56aecce5 100644
--- a/server/api/services/project.ts
+++ b/server/api/services/project.ts
@@ -1,5 +1,14 @@
import { db } from "@/server/db";
-import { type apiCreateProject, projects } from "@/server/db/schema";
+import {
+ type apiCreateProject,
+ applications,
+ mariadb,
+ mongo,
+ mysql,
+ postgres,
+ projects,
+ redis,
+} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { findAdmin } from "./admin";
@@ -75,12 +84,40 @@ export const updateProjectById = async (
return result;
};
-export const slugifyProjectName = (projectName: string): string => {
- return projectName
- .toLowerCase()
- .replace(/[0-9]/g, "")
- .replace(/[^a-z\s-]/g, "")
- .replace(/\s+/g, "-")
- .replace(/-+/g, "-")
- .replace(/^-+|-+$/g, "");
+export const validUniqueServerAppName = async (appName: string) => {
+ const query = await db.query.projects.findMany({
+ with: {
+ applications: {
+ where: eq(applications.appName, appName),
+ },
+ mariadb: {
+ where: eq(mariadb.appName, appName),
+ },
+ mongo: {
+ where: eq(mongo.appName, appName),
+ },
+ mysql: {
+ where: eq(mysql.appName, appName),
+ },
+ postgres: {
+ where: eq(postgres.appName, appName),
+ },
+ redis: {
+ where: eq(redis.appName, appName),
+ },
+ },
+ });
+
+ // Filter out items with non-empty fields
+ const nonEmptyProjects = query.filter(
+ (project) =>
+ project.applications.length > 0 ||
+ project.mariadb.length > 0 ||
+ project.mongo.length > 0 ||
+ project.mysql.length > 0 ||
+ project.postgres.length > 0 ||
+ project.redis.length > 0,
+ );
+
+ return nonEmptyProjects.length === 0;
};
diff --git a/server/api/services/redis.ts b/server/api/services/redis.ts
index e04bf41b..6137b922 100644
--- a/server/api/services/redis.ts
+++ b/server/api/services/redis.ts
@@ -5,11 +5,23 @@ import { buildRedis } from "@/server/utils/databases/redis";
import { pullImage } from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
+import { validUniqueServerAppName } from "./project";
export type Redis = typeof redis.$inferSelect;
// https://github.com/drizzle-team/drizzle-orm/discussions/1483#discussioncomment-7523881
export const createRedis = async (input: typeof apiCreateRedis._type) => {
+ if (input.appName) {
+ const valid = await validUniqueServerAppName(input.appName);
+
+ if (!valid) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Service with this 'AppName' already exists",
+ });
+ }
+ }
+
const newRedis = await db
.insert(redis)
.values({
diff --git a/server/db/schema/application.ts b/server/db/schema/application.ts
index 45d34673..2b9f7196 100644
--- a/server/db/schema/application.ts
+++ b/server/db/schema/application.ts
@@ -20,6 +20,7 @@ import {
} from "drizzle-orm/pg-core";
import { generateAppName } from "./utils";
import { registry } from "./registry";
+import { generatePassword } from "@/templates/utils";
export const sourceType = pgEnum("sourceType", ["docker", "git", "github"]);
@@ -307,11 +308,17 @@ const createSchema = createInsertSchema(applications, {
networkSwarm: NetworkSwarmSchema.nullable(),
});
-export const apiCreateApplication = createSchema.pick({
- name: true,
- description: true,
- projectId: true,
-});
+export const apiCreateApplication = createSchema
+ .pick({
+ name: true,
+ appName: true,
+ description: true,
+ projectId: true,
+ })
+ .transform((data) => ({
+ ...data,
+ appName: `${data.appName}-${generatePassword(6)}` || generateAppName("app"),
+ }));
export const apiFindOneApplication = createSchema
.pick({
diff --git a/server/db/schema/compose.ts b/server/db/schema/compose.ts
index f94711e0..bc1e641f 100644
--- a/server/db/schema/compose.ts
+++ b/server/db/schema/compose.ts
@@ -8,6 +8,7 @@ import { deployments } from "./deployment";
import { generateAppName } from "./utils";
import { applicationStatus } from "./shared";
import { mounts } from "./mount";
+import { generatePassword } from "@/templates/utils";
export const sourceTypeCompose = pgEnum("sourceTypeCompose", [
"git",
@@ -74,12 +75,19 @@ const createSchema = createInsertSchema(compose, {
composeType: z.enum(["docker-compose", "stack"]).optional(),
});
-export const apiCreateCompose = createSchema.pick({
- name: true,
- description: true,
- projectId: true,
- composeType: true,
-});
+export const apiCreateCompose = createSchema
+ .pick({
+ name: true,
+ description: true,
+ projectId: true,
+ composeType: true,
+ appName: true,
+ })
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("compose"),
+ }));
export const apiCreateComposeByTemplate = createSchema
.pick({
diff --git a/server/db/schema/mariadb.ts b/server/db/schema/mariadb.ts
index 256dfbfb..83ec2898 100644
--- a/server/db/schema/mariadb.ts
+++ b/server/db/schema/mariadb.ts
@@ -8,6 +8,7 @@ import { projects } from "./project";
import { backups } from "./backups";
import { mounts } from "./mount";
import { generateAppName } from "./utils";
+import { generatePassword } from "@/templates/utils";
export const mariadb = pgTable("mariadb", {
mariadbId: text("mariadbId")
@@ -79,6 +80,7 @@ const createSchema = createInsertSchema(mariadb, {
export const apiCreateMariaDB = createSchema
.pick({
name: true,
+ appName: true,
dockerImage: true,
databaseRootPassword: true,
projectId: true,
@@ -87,7 +89,12 @@ export const apiCreateMariaDB = createSchema
databaseUser: true,
databasePassword: true,
})
- .required();
+ .required()
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("mariadb"),
+ }));
export const apiFindOneMariaDB = createSchema
.pick({
diff --git a/server/db/schema/mongo.ts b/server/db/schema/mongo.ts
index 7406580e..2dd1cbb7 100644
--- a/server/db/schema/mongo.ts
+++ b/server/db/schema/mongo.ts
@@ -8,6 +8,7 @@ import { projects } from "./project";
import { backups } from "./backups";
import { mounts } from "./mount";
import { generateAppName } from "./utils";
+import { generatePassword } from "@/templates/utils";
export const mongo = pgTable("mongo", {
mongoId: text("mongoId")
@@ -73,13 +74,19 @@ const createSchema = createInsertSchema(mongo, {
export const apiCreateMongo = createSchema
.pick({
name: true,
+ appName: true,
dockerImage: true,
projectId: true,
description: true,
databaseUser: true,
databasePassword: true,
})
- .required();
+ .required()
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
+ }));
export const apiFindOneMongo = createSchema
.pick({
diff --git a/server/db/schema/mysql.ts b/server/db/schema/mysql.ts
index 9e0c8c77..0efbf28a 100644
--- a/server/db/schema/mysql.ts
+++ b/server/db/schema/mysql.ts
@@ -8,6 +8,7 @@ import { projects } from "./project";
import { backups } from "./backups";
import { mounts } from "./mount";
import { generateAppName } from "./utils";
+import { generatePassword } from "@/templates/utils";
export const mysql = pgTable("mysql", {
mysqlId: text("mysqlId")
@@ -77,6 +78,7 @@ const createSchema = createInsertSchema(mysql, {
export const apiCreateMySql = createSchema
.pick({
name: true,
+ appName: true,
dockerImage: true,
projectId: true,
description: true,
@@ -85,7 +87,12 @@ export const apiCreateMySql = createSchema
databasePassword: true,
databaseRootPassword: true,
})
- .required();
+ .required()
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("mysql"),
+ }));
export const apiFindOneMySql = createSchema
.pick({
diff --git a/server/db/schema/postgres.ts b/server/db/schema/postgres.ts
index 7cf0f34d..5e9077da 100644
--- a/server/db/schema/postgres.ts
+++ b/server/db/schema/postgres.ts
@@ -8,6 +8,7 @@ import { projects } from "./project";
import { backups } from "./backups";
import { mounts } from "./mount";
import { generateAppName } from "./utils";
+import { generatePassword } from "@/templates/utils";
export const postgres = pgTable("postgres", {
postgresId: text("postgresId")
@@ -74,6 +75,7 @@ const createSchema = createInsertSchema(postgres, {
export const apiCreatePostgres = createSchema
.pick({
name: true,
+ appName: true,
databaseName: true,
databaseUser: true,
databasePassword: true,
@@ -81,7 +83,12 @@ export const apiCreatePostgres = createSchema
projectId: true,
description: true,
})
- .required();
+ .required()
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("postgres"),
+ }));
export const apiFindOnePostgres = createSchema
.pick({
diff --git a/server/db/schema/redis.ts b/server/db/schema/redis.ts
index 842fe809..eb919764 100644
--- a/server/db/schema/redis.ts
+++ b/server/db/schema/redis.ts
@@ -7,6 +7,7 @@ import { integer, pgTable, text } from "drizzle-orm/pg-core";
import { projects } from "./project";
import { mounts } from "./mount";
import { generateAppName } from "./utils";
+import { generatePassword } from "@/templates/utils";
export const redis = pgTable("redis", {
redisId: text("redisId")
@@ -69,13 +70,18 @@ const createSchema = createInsertSchema(redis, {
export const apiCreateRedis = createSchema
.pick({
name: true,
+ appName: true,
databasePassword: true,
dockerImage: true,
projectId: true,
description: true,
})
-
- .required();
+ .required()
+ .transform((data) => ({
+ ...data,
+ appName:
+ `${data.appName}-${generatePassword(6)}` || generateAppName("redis"),
+ }));
export const apiFindOneRedis = createSchema
.pick({
diff --git a/server/setup/traefik-setup.ts b/server/setup/traefik-setup.ts
index a6a8b783..889988d6 100644
--- a/server/setup/traefik-setup.ts
+++ b/server/setup/traefik-setup.ts
@@ -7,6 +7,10 @@ import type { MainTraefikConfig } from "../utils/traefik/types";
import type { FileConfig } from "../utils/traefik/file-types";
import type { CreateServiceOptions } from "dockerode";
+const TRAEFIK_SSL_PORT =
+ Number.parseInt(process.env.TRAEFIK_SSL_PORT ?? "", 10) || 443;
+const TRAEFIK_PORT = Number.parseInt(process.env.TRAEFIK_PORT ?? "", 10) || 80;
+
export const initializeTraefik = async () => {
const imageName = "traefik:v2.5";
const containerName = "dokploy-traefik";
@@ -47,12 +51,12 @@ export const initializeTraefik = async () => {
Ports: [
{
TargetPort: 443,
- PublishedPort: 443,
+ PublishedPort: TRAEFIK_SSL_PORT,
PublishMode: "host",
},
{
TargetPort: 80,
- PublishedPort: 80,
+ PublishedPort: TRAEFIK_PORT,
PublishMode: "host",
},
{
@@ -146,10 +150,10 @@ export const createDefaultTraefikConfig = () => {
},
entryPoints: {
web: {
- address: ":80",
+ address: `:${TRAEFIK_PORT}`,
},
websecure: {
- address: ":443",
+ address: `:${TRAEFIK_SSL_PORT}`,
...(process.env.NODE_ENV === "production" && {
http: {
tls: {
diff --git a/server/utils/builders/index.ts b/server/utils/builders/index.ts
index e67ad9be..ce8cad39 100644
--- a/server/utils/builders/index.ts
+++ b/server/utils/builders/index.ts
@@ -148,7 +148,6 @@ export const mechanizeDockerContainer = async (
},
});
} catch (error) {
- console.log(error);
await docker.createService(settings);
}
};
diff --git a/server/utils/providers/docker.ts b/server/utils/providers/docker.ts
index 997648d1..c77a6721 100644
--- a/server/utils/providers/docker.ts
+++ b/server/utils/providers/docker.ts
@@ -3,47 +3,47 @@ import { type ApplicationNested, mechanizeDockerContainer } from "../builders";
import { pullImage } from "../docker/utils";
interface RegistryAuth {
- username: string;
- password: string;
- serveraddress: string;
+ username: string;
+ password: string;
+ serveraddress: string;
}
export const buildDocker = async (
- application: ApplicationNested,
- logPath: string,
+ application: ApplicationNested,
+ logPath: string,
): Promise
=> {
- const { buildType, dockerImage, username, password } = application;
- const authConfig: Partial = {
- username: username || "",
- password: password || "",
- };
+ const { buildType, dockerImage, username, password } = application;
+ const authConfig: Partial = {
+ username: username || "",
+ password: password || "",
+ };
- const writeStream = createWriteStream(logPath, { flags: "a" });
+ const writeStream = createWriteStream(logPath, { flags: "a" });
- writeStream.write(`\nBuild ${buildType}\n`);
+ writeStream.write(`\nBuild ${buildType}\n`);
- writeStream.write(`Pulling ${dockerImage}: ✅\n`);
+ writeStream.write(`Pulling ${dockerImage}: ✅\n`);
- try {
- if (!dockerImage) {
- throw new Error("Docker image not found");
- }
+ try {
+ if (!dockerImage) {
+ throw new Error("Docker image not found");
+ }
- await pullImage(
- dockerImage,
- (data) => {
- if (writeStream.writable) {
- writeStream.write(`${data.status}\n`);
- }
- },
- authConfig,
- );
- await mechanizeDockerContainer(application);
- writeStream.write("\nDocker Deployed: ✅\n");
- } catch (error) {
- writeStream.write(`ERROR: ${error}: ❌`);
- throw error;
- } finally {
- writeStream.end();
- }
+ await pullImage(
+ dockerImage,
+ (data) => {
+ if (writeStream.writable) {
+ writeStream.write(`${data.status}\n`);
+ }
+ },
+ authConfig,
+ );
+ await mechanizeDockerContainer(application);
+ writeStream.write("\nDocker Deployed: ✅\n");
+ } catch (error) {
+ writeStream.write(`ERROR: ${error}: ❌`);
+ throw error;
+ } finally {
+ writeStream.end();
+ }
};
diff --git a/utils/api.ts b/utils/api.ts
index 98806f93..625f8008 100644
--- a/utils/api.ts
+++ b/utils/api.ts
@@ -11,50 +11,39 @@ import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
import superjson from "superjson";
const getBaseUrl = () => {
- if (typeof window !== "undefined") return ""; // browser should use relative url
- return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
+ if (typeof window !== "undefined") return ""; // browser should use relative url
+ return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost
};
/** A set of type-safe react-query hooks for your tRPC API. */
export const api = createTRPCNext({
- config() {
- return {
- /**
- * Transformer used for data de-serialization from the server.
- *
- * @see https://trpc.io/docs/data-transformers
- */
- transformer: superjson,
+ config() {
+ return {
+ /**
+ * Transformer used for data de-serialization from the server.
+ *
+ * @see https://trpc.io/docs/data-transformers
+ */
+ transformer: superjson,
- /**
- * Links used to determine request flow from client to server.
- *
- * @see https://trpc.io/docs/links
- */
- links: [
- httpBatchLink({
- url: `${getBaseUrl()}/api/trpc`,
- }),
- // createWSClient({
- // url: `ws://localhost:3000`,
- // }),
- // loggerLink({
- // enabled: (opts) =>
- // process.env.NODE_ENV === "development" ||
- // (opts.direction === "down" && opts.result instanceof Error),
- // }),
- // httpBatchLink({
- // url: `${getBaseUrl()}/api/trpc`,
- // }),
- ],
- };
- },
- /**
- * Whether tRPC should await queries when server rendering pages.
- *
- * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
- */
- ssr: false,
+ /**
+ * Links used to determine request flow from client to server.
+ *
+ * @see https://trpc.io/docs/links
+ */
+ links: [
+ httpBatchLink({
+ url: `${getBaseUrl()}/api/trpc`,
+ }),
+ ],
+ };
+ },
+ /**
+ * Whether tRPC should await queries when server rendering pages.
+ *
+ * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false
+ */
+ ssr: false,
});
/**