Merge pull request #166 from Dokploy/feat/wildcard-domains-generation

feat(#40):  traefik.me modals
This commit is contained in:
Mauricio Siu
2024-06-27 20:07:22 -06:00
committed by GitHub
5 changed files with 242 additions and 56 deletions

View File

@@ -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>
); );
}; };

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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 }) => {

View File

@@ -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),