diff --git a/Dockerfile b/Dockerfile index 40f1dbeef..8da1db459 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ RUN curl -sSL https://nixpacks.com/install.sh -o install.sh \ && pnpm install -g tsx # Install buildpacks -RUN curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.35.0/pack-v0.35.0-linux.tgz" | tar -C /usr/local/bin/ --no-same-owner -xzv pack +COPY --from=buildpacksio/pack:0.35.0 /usr/local/bin/pack /usr/local/bin/pack EXPOSE 3000 CMD [ "pnpm", "start" ] diff --git a/apps/dokploy/__test__/compose/domain/labels.test.ts b/apps/dokploy/__test__/compose/domain/labels.test.ts new file mode 100644 index 000000000..9d5c0b52a --- /dev/null +++ b/apps/dokploy/__test__/compose/domain/labels.test.ts @@ -0,0 +1,89 @@ +import type { Domain } from "@/server/api/services/domain"; +import { createDomainLabels } from "@/server/utils/docker/domain"; +import { describe, expect, it } from "vitest"; + +describe("createDomainLabels", () => { + const appName = "test-app"; + const baseDomain: Domain = { + host: "example.com", + port: 8080, + https: false, + uniqueConfigKey: 1, + certificateType: "none", + applicationId: "", + composeId: "", + domainType: "compose", + serviceName: "test-app", + domainId: "", + path: "/", + createdAt: "", + }; + + it("should create basic labels for web entrypoint", async () => { + const labels = await createDomainLabels(appName, baseDomain, "web"); + expect(labels).toEqual([ + "traefik.http.routers.test-app-1-web.rule=Host(`example.com`)", + "traefik.http.routers.test-app-1-web.entrypoints=web", + "traefik.http.services.test-app-1-web.loadbalancer.server.port=8080", + "traefik.http.routers.test-app-1-web.service=test-app-1-web", + ]); + }); + + it("should create labels for websecure entrypoint", async () => { + const labels = await createDomainLabels(appName, baseDomain, "websecure"); + expect(labels).toEqual([ + "traefik.http.routers.test-app-1-websecure.rule=Host(`example.com`)", + "traefik.http.routers.test-app-1-websecure.entrypoints=websecure", + "traefik.http.services.test-app-1-websecure.loadbalancer.server.port=8080", + "traefik.http.routers.test-app-1-websecure.service=test-app-1-websecure", + ]); + }); + + it("should add redirect middleware for https on web entrypoint", async () => { + const httpsBaseDomain = { ...baseDomain, https: true }; + const labels = await createDomainLabels(appName, httpsBaseDomain, "web"); + expect(labels).toContain( + "traefik.http.routers.test-app-1-web.middlewares=redirect-to-https@file", + ); + }); + + it("should add Let's Encrypt configuration for websecure with letsencrypt certificate", async () => { + const letsencryptDomain = { + ...baseDomain, + https: true, + certificateType: "letsencrypt" as const, + }; + const labels = await createDomainLabels( + appName, + letsencryptDomain, + "websecure", + ); + expect(labels).toContain( + "traefik.http.routers.test-app-1-websecure.tls.certresolver=letsencrypt", + ); + }); + + it("should not add Let's Encrypt configuration for non-letsencrypt certificate", async () => { + const nonLetsencryptDomain = { + ...baseDomain, + https: true, + certificateType: "none" as const, + }; + const labels = await createDomainLabels( + appName, + nonLetsencryptDomain, + "websecure", + ); + expect(labels).not.toContain( + "traefik.http.routers.test-app-1-websecure.tls.certresolver=letsencrypt", + ); + }); + + it("should handle different ports correctly", async () => { + const customPortDomain = { ...baseDomain, port: 3000 }; + const labels = await createDomainLabels(appName, customPortDomain, "web"); + expect(labels).toContain( + "traefik.http.services.test-app-1-web.loadbalancer.server.port=3000", + ); + }); +}); diff --git a/apps/dokploy/__test__/compose/domain/network-root.test.ts b/apps/dokploy/__test__/compose/domain/network-root.test.ts new file mode 100644 index 000000000..ca8b16550 --- /dev/null +++ b/apps/dokploy/__test__/compose/domain/network-root.test.ts @@ -0,0 +1,29 @@ +import { addDokployNetworkToRoot } from "@/server/utils/docker/domain"; +import { describe, expect, it } from "vitest"; + +describe("addDokployNetworkToRoot", () => { + it("should create network object if networks is undefined", () => { + const result = addDokployNetworkToRoot(undefined); + expect(result).toEqual({ "dokploy-network": { external: true } }); + }); + + it("should add network to an empty object", () => { + const result = addDokployNetworkToRoot({}); + expect(result).toEqual({ "dokploy-network": { external: true } }); + }); + + it("should not modify existing network configuration", () => { + const existing = { "dokploy-network": { external: false } }; + const result = addDokployNetworkToRoot(existing); + expect(result).toEqual({ "dokploy-network": { external: true } }); + }); + + it("should add network alongside existing networks", () => { + const existing = { "other-network": { external: true } }; + const result = addDokployNetworkToRoot(existing); + expect(result).toEqual({ + "other-network": { external: true }, + "dokploy-network": { external: true }, + }); + }); +}); diff --git a/apps/dokploy/__test__/compose/domain/network-service.test.ts b/apps/dokploy/__test__/compose/domain/network-service.test.ts new file mode 100644 index 000000000..18faf5645 --- /dev/null +++ b/apps/dokploy/__test__/compose/domain/network-service.test.ts @@ -0,0 +1,24 @@ +import { addDokployNetworkToService } from "@/server/utils/docker/domain"; +import { describe, expect, it } from "vitest"; + +describe("addDokployNetworkToService", () => { + it("should add network to an empty array", () => { + const result = addDokployNetworkToService([]); + expect(result).toEqual(["dokploy-network"]); + }); + + it("should not add duplicate network to an array", () => { + const result = addDokployNetworkToService(["dokploy-network"]); + expect(result).toEqual(["dokploy-network"]); + }); + + it("should add network to an existing array with other networks", () => { + const result = addDokployNetworkToService(["other-network"]); + expect(result).toEqual(["other-network", "dokploy-network"]); + }); + + it("should add network to an object if networks is an object", () => { + const result = addDokployNetworkToService({ "other-network": {} }); + expect(result).toEqual({ "other-network": {}, "dokploy-network": {} }); + }); +}); diff --git a/apps/dokploy/__test__/traefik/traefik.test.ts b/apps/dokploy/__test__/traefik/traefik.test.ts index 5c490f7ce..70dbe5494 100644 --- a/apps/dokploy/__test__/traefik/traefik.test.ts +++ b/apps/dokploy/__test__/traefik/traefik.test.ts @@ -67,6 +67,9 @@ const baseDomain: Domain = { https: false, path: null, port: null, + serviceName: "", + composeId: "", + domainType: "application", uniqueConfigKey: 1, }; diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx index 873baa679..1b613704a 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/add-port.tsx @@ -28,7 +28,7 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { PlusIcon } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -52,6 +52,7 @@ export const AddPort = ({ applicationId, children = , }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { mutateAsync, isLoading, error, isError } = @@ -82,6 +83,7 @@ export const AddPort = ({ await utils.application.one.invalidate({ applicationId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to create the port"); @@ -89,7 +91,7 @@ export const AddPort = ({ }; return ( - + diff --git a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx b/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx index 8f9d9cd7f..a068ce18c 100644 --- a/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/ports/update-port.tsx @@ -28,7 +28,7 @@ import { import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -49,6 +49,7 @@ interface Props { } export const UpdatePort = ({ portId }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { data } = api.port.one.useQuery( { @@ -89,6 +90,7 @@ export const UpdatePort = ({ portId }: Props) => { await utils.application.one.invalidate({ applicationId: response?.applicationId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the port"); @@ -96,7 +98,7 @@ export const UpdatePort = ({ portId }: Props) => { }; return ( - + diff --git a/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx b/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx index 855f5c8c0..52ff310d2 100644 --- a/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/redirects/update-redirect.tsx @@ -23,7 +23,7 @@ import { Switch } from "@/components/ui/switch"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -41,6 +41,7 @@ interface Props { export const UpdateRedirect = ({ redirectId }: Props) => { const utils = api.useUtils(); + const [isOpen, setIsOpen] = useState(false); const { data } = api.redirects.one.useQuery( { redirectId, @@ -84,6 +85,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => { await utils.application.one.invalidate({ applicationId: response?.applicationId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the redirect"); @@ -91,7 +93,7 @@ export const UpdateRedirect = ({ redirectId }: Props) => { }; return ( - + diff --git a/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx b/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx index bb6e59aeb..1e5af95ff 100644 --- a/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/security/update-security.tsx @@ -21,7 +21,7 @@ import { Input } from "@/components/ui/input"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { PenBoxIcon, Pencil } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -38,6 +38,7 @@ interface Props { } export const UpdateSecurity = ({ securityId }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { data } = api.security.one.useQuery( { @@ -79,6 +80,7 @@ export const UpdateSecurity = ({ securityId }: Props) => { await utils.application.one.invalidate({ applicationId: response?.applicationId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the security"); @@ -86,7 +88,7 @@ export const UpdateSecurity = ({ securityId }: Props) => { }; return ( - + diff --git a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx index 4bb6851c2..2d847fbe2 100644 --- a/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx +++ b/apps/dokploy/components/dashboard/application/advanced/volumes/update-volume.tsx @@ -2,6 +2,7 @@ import { AlertBlock } from "@/components/shared/alert-block"; import { Button } from "@/components/ui/button"; import { Dialog, + DialogClose, DialogContent, DialogDescription, DialogFooter, @@ -22,7 +23,7 @@ import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { Pencil } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -76,6 +77,7 @@ export const UpdateVolume = ({ refetch, serviceType, }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { data } = api.mounts.one.useQuery( { @@ -135,6 +137,7 @@ export const UpdateVolume = ({ }) .then(() => { toast.success("Mount Update"); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the Bind mount"); @@ -148,6 +151,7 @@ export const UpdateVolume = ({ }) .then(() => { toast.success("Mount Update"); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the Volume mount"); @@ -162,6 +166,7 @@ export const UpdateVolume = ({ }) .then(() => { toast.success("Mount Update"); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the File mount"); @@ -171,7 +176,7 @@ export const UpdateVolume = ({ }; return ( - + + + + diff --git a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx index 71e44f929..ab7db1ef5 100644 --- a/apps/dokploy/components/dashboard/application/domains/add-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/add-domain.tsx @@ -27,13 +27,20 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { api } from "@/utils/api"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; -import { domain } from "@/server/db/validations"; +import { domain } from "@/server/db/validations/domain"; import { zodResolver } from "@hookform/resolvers/zod"; +import { Dices } from "lucide-react"; import type z from "zod"; type Domain = z.infer; @@ -60,10 +67,22 @@ export const AddDomain = ({ }, ); + const { data: application } = api.application.one.useQuery( + { + applicationId, + }, + { + enabled: !!applicationId, + }, + ); + const { mutateAsync, isError, error, isLoading } = domainId ? api.domain.update.useMutation() : api.domain.create.useMutation(); + const { mutateAsync: generateDomain, isLoading: isLoadingGenerate } = + api.domain.generateDomain.useMutation(); + const form = useForm({ resolver: zodResolver(domain), }); @@ -142,9 +161,42 @@ export const AddDomain = ({ render={({ field }) => ( Host - - - +
+ + + + + + + + + +

Generate traefik.me domain

+
+
+
+
diff --git a/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx b/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx index 63bd3f305..5933a99a6 100644 --- a/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx +++ b/apps/dokploy/components/dashboard/application/domains/delete-domain.tsx @@ -44,12 +44,19 @@ export const DeleteDomain = ({ domainId }: Props) => { domainId, }) .then((data) => { - utils.domain.byApplicationId.invalidate({ - applicationId: data?.applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: data?.applicationId, - }); + if (data?.applicationId) { + utils.domain.byApplicationId.invalidate({ + applicationId: data?.applicationId, + }); + utils.application.readTraefikConfig.invalidate({ + applicationId: data?.applicationId, + }); + } else if (data?.composeId) { + utils.domain.byComposeId.invalidate({ + composeId: data?.composeId, + }); + } + toast.success("Domain delete succesfully"); }) .catch(() => { diff --git a/apps/dokploy/components/dashboard/application/domains/generate-domain.tsx b/apps/dokploy/components/dashboard/application/domains/generate-domain.tsx deleted file mode 100644 index 9ebe8e30c..000000000 --- a/apps/dokploy/components/dashboard/application/domains/generate-domain.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import Link from "next/link"; -import { GenerateTraefikMe } from "./generate-traefikme"; -import { GenerateWildCard } from "./generate-wildcard"; - -interface Props { - applicationId: string; -} - -export const GenerateDomain = ({ applicationId }: Props) => { - return ( - - - - - - - Generate Domain - - Generate Domains for your applications - - - -
-
    -
  • -
    -
    - 1. Generate TraefikMe Domain -
    -
    - This option generates a free domain provided by{" "} - - TraefikMe - - . We recommend using this for quick domain testing or if you - don't have a domain yet. -
    -
    -
  • - {/*
  • -
    -
    - 2. Use Wildcard Domain -
    -
    - To use this option, you need to set up an 'A' record in your - domain provider. For example, create a record for - *.yourdomain.com. -
    -
    -
  • */} -
-
- - {/* */} -
-
-
-
- ); -}; diff --git a/apps/dokploy/components/dashboard/application/domains/generate-traefikme.tsx b/apps/dokploy/components/dashboard/application/domains/generate-traefikme.tsx deleted file mode 100644 index 3085b3a88..000000000 --- a/apps/dokploy/components/dashboard/application/domains/generate-traefikme.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { RefreshCcw } from "lucide-react"; -import React from "react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} -export const GenerateTraefikMe = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.domain.generateDomain.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to generate a new domain? - - - This will generate a new domain and will be used to access to the - application - - - - Cancel - { - await mutateAsync({ - applicationId, - }) - .then((data) => { - utils.domain.byApplicationId.invalidate({ - applicationId: applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: applicationId, - }); - toast.success("Generated Domain succesfully"); - }) - .catch(() => { - toast.error("Error to generate Domain"); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/domains/generate-wildcard.tsx b/apps/dokploy/components/dashboard/application/domains/generate-wildcard.tsx deleted file mode 100644 index da4445527..000000000 --- a/apps/dokploy/components/dashboard/application/domains/generate-wildcard.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; -import { api } from "@/utils/api"; -import { SquareAsterisk } from "lucide-react"; -import React from "react"; -import { toast } from "sonner"; - -interface Props { - applicationId: string; -} -export const GenerateWildCard = ({ applicationId }: Props) => { - const { mutateAsync, isLoading } = api.domain.generateWildcard.useMutation(); - const utils = api.useUtils(); - return ( - - - - - - - - Are you sure to generate a new wildcard domain? - - - This will generate a new domain and will be used to access to the - application - - - - Cancel - { - await mutateAsync({ - applicationId, - }) - .then((data) => { - utils.domain.byApplicationId.invalidate({ - applicationId: applicationId, - }); - utils.application.readTraefikConfig.invalidate({ - applicationId: applicationId, - }); - toast.success("Generated Domain succesfully"); - }) - .catch((e) => { - toast.error(`Error to generate Domain: ${e.message}`); - }); - }} - > - Confirm - - - - - ); -}; diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index d7724ce72..46c05f150 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -12,7 +12,6 @@ import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react"; import Link from "next/link"; import { AddDomain } from "./add-domain"; import { DeleteDomain } from "./delete-domain"; -import { GenerateDomain } from "./generate-domain"; interface Props { applicationId: string; @@ -46,9 +45,6 @@ export const ShowDomains = ({ applicationId }: Props) => { )} - {data && data?.length > 0 && ( - - )} @@ -65,8 +61,6 @@ export const ShowDomains = ({ applicationId }: Props) => { Add Domain - - ) : ( diff --git a/apps/dokploy/components/dashboard/application/update-application.tsx b/apps/dokploy/components/dashboard/application/update-application.tsx index a769804da..f40a00203 100644 --- a/apps/dokploy/components/dashboard/application/update-application.tsx +++ b/apps/dokploy/components/dashboard/application/update-application.tsx @@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { AlertTriangle, SquarePen } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -41,6 +41,7 @@ interface Props { } export const UpdateApplication = ({ applicationId }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { mutateAsync, error, isError, isLoading } = api.application.update.useMutation(); @@ -79,6 +80,7 @@ export const UpdateApplication = ({ applicationId }: Props) => { utils.application.one.invalidate({ applicationId: applicationId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the application"); @@ -87,7 +89,7 @@ export const UpdateApplication = ({ applicationId }: Props) => { }; return ( - + + + +

+ Fetch: Will clone the repository and load the + services +

+
+ + + + + + + + +

+ Cache: If you previously deployed this + compose, it will read the services from the + last deployment/fetch from the repository +

+
+
+
+ + + + + )} + /> + + + ( + + Host +
+ + + + + + + + + +

Generate traefik.me domain

+
+
+
+
+ + +
+ )} + /> + + { + return ( + + Path + + + + + + ); + }} + /> + + { + return ( + + Container Port + + { + field.onChange(Number.parseInt(e.target.value)); + }} + /> + + + + ); + }} + /> + {https && ( + ( + + Certificate + + + + )} + /> + )} + + ( + +
+ HTTPS + + Automatically provision SSL Certificate. + + +
+ + + +
+ )} + /> + + + + + + + + + +
+ ); +}; diff --git a/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx new file mode 100644 index 000000000..01e6f1abc --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/domains/show-domains.tsx @@ -0,0 +1,111 @@ +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { api } from "@/utils/api"; +import { ExternalLink, GlobeIcon, PenBoxIcon } from "lucide-react"; +import Link from "next/link"; +import { DeleteDomain } from "../../application/domains/delete-domain"; +import { AddDomainCompose } from "./add-domain"; + +interface Props { + composeId: string; +} + +export const ShowDomainsCompose = ({ composeId }: Props) => { + const { data } = api.domain.byComposeId.useQuery( + { + composeId, + }, + { + enabled: !!composeId, + }, + ); + + return ( +
+ + +
+ Domains + + Domains are used to access to the application + +
+ +
+ {data && data?.length > 0 && ( + + + + )} +
+
+ + {data?.length === 0 ? ( +
+ + + To access to the application is required to set at least 1 + domain + +
+ + + +
+
+ ) : ( +
+ {data?.map((item) => { + return ( +
+ + + + + + + + +
+ + + + +
+
+ ); + })} +
+ )} +
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx b/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx index 035d6c417..bb896e9ac 100644 --- a/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx +++ b/apps/dokploy/components/dashboard/compose/general/compose-file-editor.tsx @@ -72,7 +72,7 @@ export const ComposeFileEditor = ({ composeId }: Props) => { .then(async () => { toast.success("Compose config Updated"); refetch(); - await utils.compose.allServices.invalidate({ + await utils.compose.getConvertedCompose.invalidate({ composeId, }); }) diff --git a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx index 2db4248d5..93a2d7698 100644 --- a/apps/dokploy/components/dashboard/compose/general/generic/show.tsx +++ b/apps/dokploy/components/dashboard/compose/general/generic/show.tsx @@ -5,6 +5,7 @@ import { GitBranch, LockIcon } from "lucide-react"; import Link from "next/link"; import { useState } from "react"; import { ComposeFileEditor } from "../compose-file-editor"; +import { ShowConvertedCompose } from "../show-converted-compose"; import { SaveGitProviderCompose } from "./save-git-provider-compose"; import { SaveGithubProviderCompose } from "./save-github-provider-compose"; @@ -29,7 +30,8 @@ export const ShowProviderFormCompose = ({ composeId }: Props) => { Select the source of your code

-
+
+
diff --git a/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx new file mode 100644 index 000000000..bb01badc5 --- /dev/null +++ b/apps/dokploy/components/dashboard/compose/general/show-converted-compose.tsx @@ -0,0 +1,87 @@ +import { AlertBlock } from "@/components/shared/alert-block"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { api } from "@/utils/api"; +import { Puzzle, RefreshCw } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; + +interface Props { + composeId: string; +} + +export const ShowConvertedCompose = ({ composeId }: Props) => { + const [isOpen, setIsOpen] = useState(false); + const { + data: compose, + error, + isError, + refetch, + } = api.compose.getConvertedCompose.useQuery( + { composeId }, + { + retry: false, + }, + ); + + const { mutateAsync, isLoading } = api.compose.fetchSourceType.useMutation(); + + return ( + + + + + + + Converted Compose + + Preview your docker-compose file with added domains. Note: At least + one domain must be specified for this conversion to take effect. + + + {isError && {error?.message}} + +
+ +
+ +
+					
+				
+
+
+ ); +}; diff --git a/apps/dokploy/components/dashboard/compose/update-compose.tsx b/apps/dokploy/components/dashboard/compose/update-compose.tsx index 391801792..5991c03da 100644 --- a/apps/dokploy/components/dashboard/compose/update-compose.tsx +++ b/apps/dokploy/components/dashboard/compose/update-compose.tsx @@ -22,7 +22,7 @@ import { Textarea } from "@/components/ui/textarea"; import { api } from "@/utils/api"; import { zodResolver } from "@hookform/resolvers/zod"; import { SquarePen } from "lucide-react"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -41,6 +41,7 @@ interface Props { } export const UpdateCompose = ({ composeId }: Props) => { + const [isOpen, setIsOpen] = useState(false); const utils = api.useUtils(); const { mutateAsync, error, isError, isLoading } = api.compose.update.useMutation(); @@ -79,6 +80,7 @@ export const UpdateCompose = ({ composeId }: Props) => { utils.compose.one.invalidate({ composeId: composeId, }); + setIsOpen(false); }) .catch(() => { toast.error("Error to update the Compose"); @@ -87,7 +89,7 @@ export const UpdateCompose = ({ composeId }: Props) => { }; return ( - +