mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #166 from Dokploy/feat/wildcard-domains-generation
feat(#40): traefik.me modals
This commit is contained in:
@@ -1,69 +1,79 @@
|
|||||||
import React from "react";
|
|
||||||
import {
|
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
AlertDialogTrigger,
|
|
||||||
} from "@/components/ui/alert-dialog";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { RefreshCcw } from "lucide-react";
|
||||||
|
import { GenerateTraefikMe } from "./generate-traefikme";
|
||||||
|
import { GenerateWildCard } from "./generate-wildcard";
|
||||||
|
import Link from "next/link";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { RefreshCcw, TrashIcon } from "lucide-react";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GenerateDomain = ({ applicationId }: Props) => {
|
export const GenerateDomain = ({ applicationId }: Props) => {
|
||||||
const { mutateAsync, isLoading } = api.domain.generateDomain.useMutation();
|
|
||||||
const utils = api.useUtils();
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<Dialog>
|
||||||
<AlertDialogTrigger asChild>
|
<DialogTrigger className="" asChild>
|
||||||
<Button variant="secondary" isLoading={isLoading}>
|
<Button variant="secondary">
|
||||||
Generate Domain
|
Generate Domain
|
||||||
<RefreshCcw className="size-4 text-muted-foreground " />
|
<RefreshCcw className="size-4 text-muted-foreground " />
|
||||||
</Button>
|
</Button>
|
||||||
</AlertDialogTrigger>
|
</DialogTrigger>
|
||||||
<AlertDialogContent>
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
<AlertDialogHeader>
|
<DialogHeader>
|
||||||
<AlertDialogTitle>
|
<DialogTitle>Generate Domain</DialogTitle>
|
||||||
Are you sure to generate a new domain?
|
<DialogDescription>
|
||||||
</AlertDialogTitle>
|
Generate Domains for your applications
|
||||||
<AlertDialogDescription>
|
</DialogDescription>
|
||||||
This will generate a new domain and will be used to access to the
|
</DialogHeader>
|
||||||
application
|
|
||||||
</AlertDialogDescription>
|
<div className="flex flex-col gap-4 w-full">
|
||||||
</AlertDialogHeader>
|
<ul className="flex flex-col gap-4">
|
||||||
<AlertDialogFooter>
|
<li className="flex flex-row items-center gap-4">
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<div className="flex flex-col gap-2">
|
||||||
<AlertDialogAction
|
<div className="text-base font-bold">
|
||||||
onClick={async () => {
|
1. Generate TraefikMe Domain
|
||||||
await mutateAsync({
|
</div>
|
||||||
applicationId,
|
<div className="text-sm text-muted-foreground">
|
||||||
})
|
This option generates a free domain provided by{" "}
|
||||||
.then((data) => {
|
<Link
|
||||||
utils.domain.byApplicationId.invalidate({
|
href="https://traefik.me"
|
||||||
applicationId: applicationId,
|
className="text-primary"
|
||||||
});
|
target="_blank"
|
||||||
utils.application.readTraefikConfig.invalidate({
|
>
|
||||||
applicationId: applicationId,
|
TraefikMe
|
||||||
});
|
</Link>
|
||||||
toast.success("Generated Domain succesfully");
|
. We recommend using this for quick domain testing or if you
|
||||||
})
|
don't have a domain yet.
|
||||||
.catch(() => {
|
</div>
|
||||||
toast.error("Error to generate Domain");
|
</div>
|
||||||
});
|
</li>
|
||||||
}}
|
{/* <li className="flex flex-row items-center gap-4">
|
||||||
>
|
<div className="flex flex-col gap-2">
|
||||||
Confirm
|
<div className="text-base font-bold">
|
||||||
</AlertDialogAction>
|
2. Use Wildcard Domain
|
||||||
</AlertDialogFooter>
|
</div>
|
||||||
</AlertDialogContent>
|
<div className="text-sm text-muted-foreground">
|
||||||
</AlertDialog>
|
To use this option, you need to set up an 'A' record in your
|
||||||
|
domain provider. For example, create a record for
|
||||||
|
*.yourdomain.com.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li> */}
|
||||||
|
</ul>
|
||||||
|
<div className="flex flex-row gap-4 w-full">
|
||||||
|
<GenerateTraefikMe applicationId={applicationId} />
|
||||||
|
{/* <GenerateWildCard applicationId={applicationId} /> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import React from "react";
|
||||||
|
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 { toast } from "sonner";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
export const GenerateTraefikMe = ({ applicationId }: Props) => {
|
||||||
|
const { mutateAsync, isLoading } = api.domain.generateDomain.useMutation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="secondary" isLoading={isLoading}>
|
||||||
|
Generate Domain
|
||||||
|
<RefreshCcw className="size-4 text-muted-foreground " />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Are you sure to generate a new domain?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will generate a new domain and will be used to access to the
|
||||||
|
application
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={async () => {
|
||||||
|
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
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import React from "react";
|
||||||
|
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 { toast } from "sonner";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
export const GenerateWildCard = ({ applicationId }: Props) => {
|
||||||
|
const { mutateAsync, isLoading } = api.domain.generateWildcard.useMutation();
|
||||||
|
const utils = api.useUtils();
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="secondary" isLoading={isLoading}>
|
||||||
|
Generate Wildcard Domain
|
||||||
|
<SquareAsterisk className="size-4 text-muted-foreground " />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Are you sure to generate a new wildcard domain?
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will generate a new domain and will be used to access to the
|
||||||
|
application
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={async () => {
|
||||||
|
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
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
findDomainById,
|
findDomainById,
|
||||||
findDomainsByApplicationId,
|
findDomainsByApplicationId,
|
||||||
generateDomain,
|
generateDomain,
|
||||||
|
generateWildcard,
|
||||||
removeDomainById,
|
removeDomainById,
|
||||||
updateDomainById,
|
updateDomainById,
|
||||||
} from "../services/domain";
|
} from "../services/domain";
|
||||||
@@ -41,6 +42,11 @@ export const domainRouter = createTRPCRouter({
|
|||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
return generateDomain(input);
|
return generateDomain(input);
|
||||||
}),
|
}),
|
||||||
|
generateWildcard: protectedProcedure
|
||||||
|
.input(apiFindDomainByApplication)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return generateWildcard(input);
|
||||||
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateDomain)
|
.input(apiUpdateDomain)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|||||||
@@ -55,6 +55,38 @@ export const generateDomain = async (
|
|||||||
|
|
||||||
return domain;
|
return domain;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateWildcard = async (
|
||||||
|
input: typeof apiFindDomainByApplication._type,
|
||||||
|
) => {
|
||||||
|
const application = await findApplicationById(input.applicationId);
|
||||||
|
const admin = await findAdmin();
|
||||||
|
|
||||||
|
if (!admin.host) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "We need a host to generate a wildcard domain",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const domain = await createDomain({
|
||||||
|
applicationId: application.applicationId,
|
||||||
|
host: generateWildcardDomain(application.appName, admin.host || ""),
|
||||||
|
port: 3000,
|
||||||
|
certificateType: "none",
|
||||||
|
https: false,
|
||||||
|
path: "/",
|
||||||
|
});
|
||||||
|
|
||||||
|
return domain;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateWildcardDomain = (
|
||||||
|
appName: string,
|
||||||
|
serverDomain: string,
|
||||||
|
) => {
|
||||||
|
return `${appName}-${serverDomain}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const findDomainById = async (domainId: string) => {
|
export const findDomainById = async (domainId: string) => {
|
||||||
const domain = await db.query.domains.findFirst({
|
const domain = await db.query.domains.findFirst({
|
||||||
where: eq(domains.domainId, domainId),
|
where: eq(domains.domainId, domainId),
|
||||||
|
|||||||
Reference in New Issue
Block a user