diff --git a/.gitignore b/.gitignore index 61e00971..4e788d91 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ yarn-error.log* *.lockb *.rdb +.idea diff --git a/components/dashboard/application/environment/show.tsx b/components/dashboard/application/environment/show.tsx index 29b536cb..72f25d2e 100644 --- a/components/dashboard/application/environment/show.tsx +++ b/components/dashboard/application/environment/show.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Card, CardContent, @@ -19,8 +19,9 @@ import { } from "@/components/ui/form"; import { api } from "@/utils/api"; import { toast } from "sonner"; -import { Textarea } from "@/components/ui/textarea"; +import { Toggle } from "@/components/ui/toggle"; import { CodeEditor } from "@/components/shared/code-editor"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; const addEnvironmentSchema = z.object({ environment: z.string(), @@ -33,6 +34,7 @@ interface Props { } export const ShowEnvironment = ({ applicationId }: Props) => { + const [isEnvVisible, setIsEnvVisible] = useState(true); const { mutateAsync, isLoading } = api.application.saveEnvironment.useMutation(); @@ -72,15 +74,50 @@ export const ShowEnvironment = ({ applicationId }: Props) => { toast.error("Error to add environment"); }); }; + useEffect(() => { + if (isEnvVisible) { + if (data?.env) { + const maskedLines = data.env + .split("\n") + .map((line) => "*".repeat(line.length)) + .join("\n"); + form.reset({ + environment: maskedLines, + }); + } else { + form.reset({ + environment: "", + }); + } + } else { + form.reset({ + environment: data?.env || "", + }); + } + }, [form.reset, data, form, isEnvVisible]); return (
- - Environment Settings - - You can add environment variables to your resource. - + +
+ Environment Settings + + You can add environment variables to your resource. + +
+ + + {isEnvVisible ? ( + + ) : ( + + )} +
@@ -97,6 +134,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
-
diff --git a/components/dashboard/compose/enviroment/show.tsx b/components/dashboard/compose/enviroment/show.tsx index 824e3ce8..c897ac6a 100644 --- a/components/dashboard/compose/enviroment/show.tsx +++ b/components/dashboard/compose/enviroment/show.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { Card, CardContent, @@ -20,6 +20,8 @@ import { import { api } from "@/utils/api"; import { toast } from "sonner"; import { CodeEditor } from "@/components/shared/code-editor"; +import { Toggle } from "@/components/ui/toggle"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; const addEnvironmentSchema = z.object({ environment: z.string(), @@ -32,6 +34,7 @@ interface Props { } export const ShowEnvironmentCompose = ({ composeId }: Props) => { + const [isEnvVisible, setIsEnvVisible] = useState(true); const { mutateAsync, isLoading } = api.compose.update.useMutation(); const { data, refetch } = api.compose.one.useQuery( @@ -71,14 +74,50 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => { }); }; + useEffect(() => { + if (isEnvVisible) { + if (data?.env) { + const maskedLines = data.env + .split("\n") + .map((line) => "*".repeat(line.length)) + .join("\n"); + form.reset({ + environment: maskedLines, + }); + } else { + form.reset({ + environment: "", + }); + } + } else { + form.reset({ + environment: data?.env || "", + }); + } + }, [form.reset, data, form, isEnvVisible]); + return (
- - Environment Settings - - You can add environment variables to your resource. - + +
+ Environment Settings + + You can add environment variables to your resource. + +
+ + + {isEnvVisible ? ( + + ) : ( + + )} +
@@ -95,6 +134,7 @@ export const ShowEnvironmentCompose = ({ composeId }: Props) => {
-
diff --git a/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx b/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx index 83f730fa..2f19d78b 100644 --- a/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx +++ b/components/dashboard/mariadb/general/show-external-mariadb-credentials.tsx @@ -22,6 +22,7 @@ import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; const DockerProviderSchema = z.object({ externalPort: z.preprocess((a) => { @@ -136,7 +137,7 @@ export const ShowExternalMariadbCredentials = ({ mariadbId }: Props) => {
{/* jdbc:mariadb://5.161.59.207:3306/pixel-calculate?user=mariadb&password=HdVXfq6hM7W7F1 */} - +
)} diff --git a/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx b/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx index 7dc61772..869409d5 100644 --- a/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx +++ b/components/dashboard/mariadb/general/show-internal-mariadb-credentials.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; interface Props { mariadbId: string; @@ -29,20 +30,18 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
-
-
@@ -58,7 +57,7 @@ export const ShowInternalMariadbCredentials = ({ mariadbId }: Props) => {
- diff --git a/components/dashboard/mongo/general/show-external-mongo-credentials.tsx b/components/dashboard/mongo/general/show-external-mongo-credentials.tsx index 77218857..c8a3fa23 100644 --- a/components/dashboard/mongo/general/show-external-mongo-credentials.tsx +++ b/components/dashboard/mongo/general/show-external-mongo-credentials.tsx @@ -1,3 +1,4 @@ +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; import { Card, @@ -136,7 +137,7 @@ export const ShowExternalMongoCredentials = ({ mongoId }: Props) => {
- +
)} diff --git a/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx b/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx index a57933fe..9fab4a8a 100644 --- a/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx +++ b/components/dashboard/mongo/general/show-internal-mongo-credentials.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; interface Props { mongoId: string; @@ -26,10 +27,9 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
-
@@ -46,7 +46,7 @@ export const ShowInternalMongoCredentials = ({ mongoId }: Props) => {
- diff --git a/components/dashboard/mysql/general/show-external-mysql-credentials.tsx b/components/dashboard/mysql/general/show-external-mysql-credentials.tsx index ab9388cd..ce184a98 100644 --- a/components/dashboard/mysql/general/show-external-mysql-credentials.tsx +++ b/components/dashboard/mysql/general/show-external-mysql-credentials.tsx @@ -1,3 +1,4 @@ +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; import { Card, @@ -136,7 +137,7 @@ export const ShowExternalMysqlCredentials = ({ mysqlId }: Props) => {
- +
)} diff --git a/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx b/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx index 80a71bfe..c48fe95d 100644 --- a/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx +++ b/components/dashboard/mysql/general/show-internal-mysql-credentials.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; interface Props { mysqlId: string; @@ -29,20 +30,18 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
-
-
@@ -58,7 +57,7 @@ export const ShowInternalMysqlCredentials = ({ mysqlId }: Props) => {
- diff --git a/components/dashboard/postgres/general/show-external-postgres-credentials.tsx b/components/dashboard/postgres/general/show-external-postgres-credentials.tsx index 215356ce..edb128bf 100644 --- a/components/dashboard/postgres/general/show-external-postgres-credentials.tsx +++ b/components/dashboard/postgres/general/show-external-postgres-credentials.tsx @@ -1,3 +1,4 @@ +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; import { Button } from "@/components/ui/button"; import { Card, @@ -137,7 +138,7 @@ export const ShowExternalPostgresCredentials = ({ postgresId }: Props) => {
- +
)} diff --git a/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx b/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx index def7f9c0..a8b5270d 100644 --- a/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx +++ b/components/dashboard/postgres/general/show-internal-postgres-credentials.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; interface Props { postgresId: string; @@ -29,10 +30,9 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
-
@@ -48,7 +48,7 @@ export const ShowInternalPostgresCredentials = ({ postgresId }: Props) => {
- diff --git a/components/dashboard/project/add-application.tsx b/components/dashboard/project/add-application.tsx index 145c601d..ecf2a1af 100644 --- a/components/dashboard/project/add-application.tsx +++ b/components/dashboard/project/add-application.tsx @@ -22,16 +22,26 @@ import { AlertBlock } from "@/components/shared/alert-block"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Folder } from "lucide-react"; -import { useEffect } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; import { Textarea } from "@/components/ui/textarea"; +import { slugify } from "@/lib/slug"; const AddTemplateSchema = z.object({ name: z.string().min(1, { message: "Name is required", }), + appName: z + .string() + .min(1, { + message: "App name is required", + }) + .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { + message: + "App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", + }), description: z.string().optional(), }); @@ -39,10 +49,13 @@ type AddTemplate = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddApplication = ({ projectId }: Props) => { +export const AddApplication = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); + const [visible, setVisible] = useState(false); + const slug = slugify(projectName); const { mutateAsync, isLoading, error, isError } = api.application.create.useMutation(); @@ -50,34 +63,34 @@ export const AddApplication = ({ projectId }: Props) => { const form = useForm({ defaultValues: { name: "", + appName: `${slug}-`, description: "", }, resolver: zodResolver(AddTemplateSchema), }); - useEffect(() => { - form.reset(); - }, [form, form.reset, form.formState.isSubmitSuccessful]); - const onSubmit = async (data: AddTemplate) => { await mutateAsync({ name: data.name, + appName: data.appName, description: data.description, projectId, }) .then(async () => { toast.success("Service Created"); + form.reset(); + setVisible(false); await utils.project.one.invalidate({ projectId, }); }) - .catch(() => { + .catch((e) => { toast.error("Error to create the service"); }); }; return ( - + { {isError && {error?.message}} - -
- ( - - Name - - - - - - - )} - /> -
+ ( + + Name + + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${slug}-${val}`); + field.onChange(val); + }} + /> + + + + )} + /> + ( + + AppName + + + + + + )} + /> { )} /> - - {/* ( - - Build Type - - - - - - - - Dockerfile - - - - - - - Nixpacks - - - - - - - Heroku Buildpacks - - - - - - - )} - /> */} diff --git a/components/dashboard/project/add-compose.tsx b/components/dashboard/project/add-compose.tsx index 97df55ef..fd769c91 100644 --- a/components/dashboard/project/add-compose.tsx +++ b/components/dashboard/project/add-compose.tsx @@ -34,12 +34,22 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { slugify } from "@/lib/slug"; const AddComposeSchema = z.object({ composeType: z.enum(["docker-compose", "stack"]).optional(), name: z.string().min(1, { message: "Name is required", }), + appName: z + .string() + .min(1, { + message: "App name is required", + }) + .regex(/^[a-z](?!.*--)([a-z0-9-]*[a-z])?$/, { + message: + "App name supports letters, numbers, '-' and can only start and end letters, and does not support continuous '-'", + }), description: z.string().optional(), }); @@ -47,11 +57,12 @@ type AddCompose = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddCompose = ({ projectId }: Props) => { +export const AddCompose = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); - + const slug = slugify(projectName); const { mutateAsync, isLoading, error, isError } = api.compose.create.useMutation(); @@ -60,6 +71,7 @@ export const AddCompose = ({ projectId }: Props) => { name: "", description: "", composeType: "docker-compose", + appName: `${slug}-`, }, resolver: zodResolver(AddComposeSchema), }); @@ -74,6 +86,7 @@ export const AddCompose = ({ projectId }: Props) => { description: data.description, projectId, composeType: data.composeType, + appName: data.appName, }) .then(async () => { toast.success("Compose Created"); @@ -120,14 +133,34 @@ export const AddCompose = ({ projectId }: Props) => { Name - + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${slug}-${val}`); + field.onChange(val); + }} + /> - )} />
+ ( + + AppName + + + + + + )} + /> , + label: "PostgreSQL", + }, + mongo: { + icon: , + label: "MongoDB", + }, + mariadb: { + icon: , + label: "MariaDB", + }, + mysql: { + icon: , + label: "MySQL", + }, + redis: { + icon: , + label: "Redis", + }, +}; + type AddDatabase = z.infer; interface Props { projectId: string; + projectName?: string; } -export const AddDatabase = ({ projectId }: Props) => { +export const AddDatabase = ({ projectId, projectName }: Props) => { const utils = api.useUtils(); - - const { mutateAsync: createPostgresql } = api.postgres.create.useMutation(); - - const { mutateAsync: createMongo } = api.mongo.create.useMutation(); - - const { mutateAsync: createRedis } = api.redis.create.useMutation(); - - const { mutateAsync: createMariadb } = api.mariadb.create.useMutation(); - - const { mutateAsync: createMysql } = api.mysql.create.useMutation(); + const [visible, setVisible] = useState(false); + const slug = slugify(projectName); + const postgresMutation = api.postgres.create.useMutation(); + const mongoMutation = api.mongo.create.useMutation(); + const redisMutation = api.redis.create.useMutation(); + const mariadbMutation = api.mariadb.create.useMutation(); + const mysqlMutation = api.mysql.create.useMutation(); const form = useForm({ defaultValues: { type: "postgres", dockerImage: "", name: "", + appName: `${slug}-`, databasePassword: "", description: "", databaseName: "", @@ -133,76 +165,65 @@ export const AddDatabase = ({ projectId }: Props) => { resolver: zodResolver(mySchema), }); const type = form.watch("type"); - - useEffect(() => { - form.reset({ - type: "postgres", - dockerImage: "", - name: "", - databasePassword: "", - description: "", - databaseName: "", - databaseUser: "", - }); - }, [form, form.reset, form.formState.isSubmitSuccessful]); + const activeMutation = { + postgres: postgresMutation, + mongo: mongoMutation, + redis: redisMutation, + mariadb: mariadbMutation, + mysql: mysqlMutation, + }; const onSubmit = async (data: AddDatabase) => { const defaultDockerImage = data.dockerImage || dockerImageDefaultPlaceholder[data.type]; let promise: Promise | null = null; + const commonParams = { + name: data.name, + appName: data.appName, + dockerImage: defaultDockerImage, + projectId, + description: data.description, + }; + if (data.type === "postgres") { - promise = createPostgresql({ - name: data.name, - dockerImage: defaultDockerImage, + promise = postgresMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, - description: data.description, }); } else if (data.type === "mongo") { - promise = createMongo({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mongoMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, - description: data.description, }); } else if (data.type === "redis") { - promise = createRedis({ - name: data.name, - dockerImage: defaultDockerImage, + promise = redisMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, projectId, - description: data.description, }); } else if (data.type === "mariadb") { - promise = createMariadb({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mariadbMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, - projectId, databaseRootPassword: data.databaseRootPassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - description: data.description, }); } else if (data.type === "mysql") { - promise = createMysql({ - name: data.name, - dockerImage: defaultDockerImage, + promise = mysqlMutation.mutateAsync({ + ...commonParams, databasePassword: data.databasePassword, databaseName: data.databaseName, databaseUser: data.databaseUser || databasesUserDefaultPlaceholder[data.type], - projectId, databaseRootPassword: data.databaseRootPassword, - description: data.description, }); } @@ -210,6 +231,17 @@ export const AddDatabase = ({ projectId }: Props) => { await promise .then(async () => { toast.success("Database Created"); + form.reset({ + type: "postgres", + dockerImage: "", + name: "", + appName: `${projectName}-`, + databasePassword: "", + description: "", + databaseName: "", + databaseUser: "", + }); + setVisible(false); await utils.project.one.invalidate({ projectId, }); @@ -220,7 +252,7 @@ export const AddDatabase = ({ projectId }: Props) => { } }; return ( - + { Database - + Databases - {/* {isError && ( -
- - - {error?.message} - -
- )} */}
{ defaultValue={field.value} className="grid w-full grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4" > - - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
- - -
- - -
-
-
+ {Object.entries(databasesMap).map(([key, value]) => ( + + +
+ + +
+
+
+ ))} + {activeMutation[field.value].isError && ( +
+ + + {activeMutation[field.value].error?.message} + +
+ )} )} /> @@ -372,13 +337,34 @@ export const AddDatabase = ({ projectId }: Props) => { Name - + { + const val = e.target.value?.trim() || ""; + form.setValue("appName", `${slug}-${val}`); + field.onChange(val); + }} + /> )} /> + ( + + AppName + + + + + + )} + /> {
- +
)} diff --git a/components/dashboard/redis/general/show-internal-redis-credentials.tsx b/components/dashboard/redis/general/show-internal-redis-credentials.tsx index 3f44b37a..1f798144 100644 --- a/components/dashboard/redis/general/show-internal-redis-credentials.tsx +++ b/components/dashboard/redis/general/show-internal-redis-credentials.tsx @@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { api } from "@/utils/api"; +import { ToggleVisibilityInput } from "@/components/shared/toggle-visibility-input"; interface Props { redisId: string; @@ -25,10 +26,9 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
-
@@ -44,7 +44,7 @@ export const ShowInternalRedisCredentials = ({ redisId }: Props) => {
- 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, }); /**