mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(#40): add domain generation by traefik.me
This commit is contained in:
69
components/dashboard/application/domains/generate-domain.tsx
Normal file
69
components/dashboard/application/domains/generate-domain.tsx
Normal 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, TrashIcon } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
export const GenerateDomain = ({ 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { ExternalLink, GlobeIcon } from "lucide-react";
|
import { ExternalLink, GlobeIcon, RefreshCcw } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -14,6 +14,7 @@ import { DeleteDomain } from "./delete-domain";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { AddDomain } from "./add-domain";
|
import { AddDomain } from "./add-domain";
|
||||||
import { UpdateDomain } from "./update-domain";
|
import { UpdateDomain } from "./update-domain";
|
||||||
|
import { GenerateDomain } from "./generate-domain";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
@@ -31,7 +32,7 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-5 ">
|
<div className="flex w-full flex-col gap-5 ">
|
||||||
<Card className="bg-background">
|
<Card className="bg-background">
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center flex-wrap gap-4 justify-between">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<CardTitle className="text-xl">Domains</CardTitle>
|
<CardTitle className="text-xl">Domains</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
@@ -39,11 +40,16 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{data && data?.length > 0 && (
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
<AddDomain applicationId={applicationId}>
|
{data && data?.length > 0 && (
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<AddDomain applicationId={applicationId}>
|
||||||
</AddDomain>
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
)}
|
</AddDomain>
|
||||||
|
)}
|
||||||
|
{data && data?.length > 0 && (
|
||||||
|
<GenerateDomain applicationId={applicationId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-row gap-4">
|
<CardContent className="flex w-full flex-row gap-4">
|
||||||
{data?.length === 0 ? (
|
{data?.length === 0 ? (
|
||||||
@@ -53,9 +59,13 @@ export const ShowDomains = ({ applicationId }: Props) => {
|
|||||||
To access to the application is required to set at least 1
|
To access to the application is required to set at least 1
|
||||||
domain
|
domain
|
||||||
</span>
|
</span>
|
||||||
<AddDomain applicationId={applicationId}>
|
<div className="flex flex-row gap-4 flex-wrap">
|
||||||
<GlobeIcon className="size-4" /> Add Domain
|
<AddDomain applicationId={applicationId}>
|
||||||
</AddDomain>
|
<GlobeIcon className="size-4" /> Add Domain
|
||||||
|
</AddDomain>
|
||||||
|
|
||||||
|
<GenerateDomain applicationId={applicationId} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex w-full flex-col gap-4">
|
<div className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
createDomain,
|
createDomain,
|
||||||
findDomainById,
|
findDomainById,
|
||||||
findDomainsByApplicationId,
|
findDomainsByApplicationId,
|
||||||
|
generateDomain,
|
||||||
removeDomainById,
|
removeDomainById,
|
||||||
updateDomainById,
|
updateDomainById,
|
||||||
} from "../services/domain";
|
} from "../services/domain";
|
||||||
@@ -35,6 +36,11 @@ export const domainRouter = createTRPCRouter({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await findDomainsByApplicationId(input.applicationId);
|
return await findDomainsByApplicationId(input.applicationId);
|
||||||
}),
|
}),
|
||||||
|
generateDomain: protectedProcedure
|
||||||
|
.input(apiFindDomainByApplication)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return generateDomain(input);
|
||||||
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateDomain)
|
.input(apiUpdateDomain)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { db } from "@/server/db";
|
import { db } from "@/server/db";
|
||||||
import { type apiCreateDomain, domains } from "@/server/db/schema";
|
import {
|
||||||
|
type apiCreateDomain,
|
||||||
|
type apiFindDomainByApplication,
|
||||||
|
domains,
|
||||||
|
} from "@/server/db/schema";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { findApplicationById } from "./application";
|
import { findApplicationById } from "./application";
|
||||||
import { manageDomain } from "@/server/utils/traefik/domain";
|
import { manageDomain } from "@/server/utils/traefik/domain";
|
||||||
|
import { findAdmin } from "./admin";
|
||||||
|
import { generateRandomDomain } from "@/templates/utils";
|
||||||
|
|
||||||
export type Domain = typeof domains.$inferSelect;
|
export type Domain = typeof domains.$inferSelect;
|
||||||
|
|
||||||
@@ -29,6 +35,26 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
|
|||||||
await manageDomain(application, domain);
|
await manageDomain(application, domain);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateDomain = async (
|
||||||
|
input: typeof apiFindDomainByApplication._type,
|
||||||
|
) => {
|
||||||
|
const application = await findApplicationById(input.applicationId);
|
||||||
|
const admin = await findAdmin();
|
||||||
|
const domain = await createDomain({
|
||||||
|
applicationId: application.applicationId,
|
||||||
|
host: generateRandomDomain({
|
||||||
|
serverIp: admin.serverIp || "",
|
||||||
|
projectName: application.appName,
|
||||||
|
}),
|
||||||
|
port: process.env.NODE_ENV === "development" ? 3000 : 80,
|
||||||
|
certificateType: "none",
|
||||||
|
https: false,
|
||||||
|
path: "/",
|
||||||
|
});
|
||||||
|
|
||||||
|
return domain;
|
||||||
|
};
|
||||||
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