mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
@@ -26,7 +26,7 @@ Dokploy include multiples features to make your life easier.
|
|||||||
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
* **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||||
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
* **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||||
* **Docker Management**: Easily deploy and manage Docker containers.
|
* **Docker Management**: Easily deploy and manage Docker containers.
|
||||||
* **CLI (Soon⌛)**: Manage your applications and databases using the command line.
|
* **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||||
* **Self-Hosted**: Self-host Dokploy on your VPS.
|
* **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,11 @@ export const ShowDeployments = ({ applicationId }: Props) => {
|
|||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{deployment.title}
|
{deployment.title}
|
||||||
</span>
|
</span>
|
||||||
|
{deployment.description && (
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{deployment.description}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-end gap-2">
|
<div className="flex flex-col items-end gap-2">
|
||||||
<div className="text-sm capitalize text-muted-foreground">
|
<div className="text-sm capitalize text-muted-foreground">
|
||||||
|
|||||||
79
components/dashboard/application/domains/generate-domain.tsx
Normal file
79
components/dashboard/application/domains/generate-domain.tsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
applicationId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GenerateDomain = ({ applicationId }: Props) => {
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger className="" asChild>
|
||||||
|
<Button variant="secondary">
|
||||||
|
Generate Domain
|
||||||
|
<RefreshCcw className="size-4 text-muted-foreground " />
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Generate Domain</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Generate Domains for your applications
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-4 w-full">
|
||||||
|
<ul className="flex flex-col gap-4">
|
||||||
|
<li className="flex flex-row items-center gap-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="text-base font-bold">
|
||||||
|
1. Generate TraefikMe Domain
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
This option generates a free domain provided by{" "}
|
||||||
|
<Link
|
||||||
|
href="https://traefik.me"
|
||||||
|
className="text-primary"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
TraefikMe
|
||||||
|
</Link>
|
||||||
|
. We recommend using this for quick domain testing or if you
|
||||||
|
don't have a domain yet.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{/* <li className="flex flex-row items-center gap-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="text-base font-bold">
|
||||||
|
2. Use Wildcard Domain
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -141,7 +141,13 @@ export const DockerMonitoring = ({
|
|||||||
network: data.network[data.network.length - 1] ?? currentData.network,
|
network: data.network[data.network.length - 1] ?? currentData.network,
|
||||||
disk: data.disk[data.disk.length - 1] ?? currentData.disk,
|
disk: data.disk[data.disk.length - 1] ?? currentData.disk,
|
||||||
});
|
});
|
||||||
setAcummulativeData(data);
|
setAcummulativeData({
|
||||||
|
block: data?.block || [],
|
||||||
|
cpu: data?.cpu || [],
|
||||||
|
disk: data?.disk || [],
|
||||||
|
memory: data?.memory || [],
|
||||||
|
network: data?.network || [],
|
||||||
|
});
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ export const ShowProjects = () => {
|
|||||||
project?.compose.length;
|
project?.compose.length;
|
||||||
return (
|
return (
|
||||||
<div key={project.projectId} className="w-full lg:max-w-md">
|
<div key={project.projectId} className="w-full lg:max-w-md">
|
||||||
<Card className="group relative w-full bg-transparent transition-colors hover:bg-card">
|
<Link href={`/dashboard/project/${project.projectId}`}>
|
||||||
<Link href={`/dashboard/project/${project.projectId}`}>
|
<Card className="group relative w-full bg-transparent transition-colors hover:bg-card">
|
||||||
<Button
|
<Button
|
||||||
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
className="absolute -right-3 -top-3 size-9 translate-y-1 rounded-full p-0 opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -85,113 +85,122 @@ export const ShowProjects = () => {
|
|||||||
>
|
>
|
||||||
<ExternalLinkIcon className="size-3.5" />
|
<ExternalLinkIcon className="size-3.5" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle className="flex items-center justify-between gap-2">
|
||||||
<CardTitle className="flex items-center justify-between gap-2">
|
<span className="flex flex-col gap-1.5">
|
||||||
<span className="flex flex-col gap-1.5">
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<BookIcon className="size-4 text-muted-foreground" />
|
||||||
<BookIcon className="size-4 text-muted-foreground" />
|
<span className="text-base font-medium leading-none">
|
||||||
<Link
|
{project.name}
|
||||||
className="text-base font-medium leading-none"
|
</span>
|
||||||
href={`/dashboard/project/${project.projectId}`}
|
</div>
|
||||||
>
|
|
||||||
{project.name}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className="text-sm font-medium text-muted-foreground">
|
<span className="text-sm font-medium text-muted-foreground">
|
||||||
{project.description}
|
{project.description}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
<div className="flex self-start space-x-1">
|
||||||
<div className="flex self-start space-x-1">
|
<DropdownMenu>
|
||||||
<DropdownMenu>
|
<DropdownMenuTrigger asChild>
|
||||||
<DropdownMenuTrigger asChild>
|
<Button
|
||||||
<Button variant="ghost" size="icon" className="px-2">
|
variant="ghost"
|
||||||
<MoreHorizontalIcon className="size-5" />
|
size="icon"
|
||||||
</Button>
|
className="px-2"
|
||||||
</DropdownMenuTrigger>
|
>
|
||||||
<DropdownMenuContent className="w-[200px] space-y-2">
|
<MoreHorizontalIcon className="size-5" />
|
||||||
<DropdownMenuLabel className="font-normal">
|
</Button>
|
||||||
Actions
|
</DropdownMenuTrigger>
|
||||||
</DropdownMenuLabel>
|
<DropdownMenuContent className="w-[200px] space-y-2">
|
||||||
|
<DropdownMenuLabel className="font-normal">
|
||||||
|
Actions
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
|
||||||
<UpdateProject projectId={project.projectId} />
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<UpdateProject projectId={project.projectId} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{(auth?.rol === "admin" ||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
user?.canDeleteProjects) && (
|
{(auth?.rol === "admin" ||
|
||||||
<AlertDialog>
|
user?.canDeleteProjects) && (
|
||||||
<AlertDialogTrigger className="w-full">
|
<AlertDialog>
|
||||||
<DropdownMenuItem
|
<AlertDialogTrigger className="w-full">
|
||||||
className="w-full cursor-pointer space-x-3"
|
<DropdownMenuItem
|
||||||
onSelect={(e) => e.preventDefault()}
|
className="w-full cursor-pointer space-x-3"
|
||||||
>
|
onSelect={(e) => e.preventDefault()}
|
||||||
<TrashIcon className="size-4" />
|
>
|
||||||
<span>Delete</span>
|
<TrashIcon className="size-4" />
|
||||||
</DropdownMenuItem>
|
<span>Delete</span>
|
||||||
</AlertDialogTrigger>
|
</DropdownMenuItem>
|
||||||
<AlertDialogContent>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogHeader>
|
<AlertDialogContent>
|
||||||
<AlertDialogTitle>
|
<AlertDialogHeader>
|
||||||
Are you sure to delete this project?
|
<AlertDialogTitle>
|
||||||
</AlertDialogTitle>
|
Are you sure to delete this project?
|
||||||
{!emptyServices ? (
|
</AlertDialogTitle>
|
||||||
<div className="flex flex-row gap-4 rounded-lg bg-yellow-50 p-2 dark:bg-yellow-950">
|
{!emptyServices ? (
|
||||||
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
|
<div className="flex flex-row gap-4 rounded-lg bg-yellow-50 p-2 dark:bg-yellow-950">
|
||||||
<span className="text-sm text-yellow-600 dark:text-yellow-400">
|
<AlertTriangle className="text-yellow-600 dark:text-yellow-400" />
|
||||||
You have active services, please delete
|
<span className="text-sm text-yellow-600 dark:text-yellow-400">
|
||||||
them first
|
You have active services, please
|
||||||
</span>
|
delete them first
|
||||||
</div>
|
</span>
|
||||||
) : (
|
</div>
|
||||||
<AlertDialogDescription>
|
) : (
|
||||||
This action cannot be undone
|
<AlertDialogDescription>
|
||||||
</AlertDialogDescription>
|
This action cannot be undone
|
||||||
)}
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
)}
|
||||||
<AlertDialogFooter>
|
</AlertDialogHeader>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogFooter>
|
||||||
<AlertDialogAction
|
<AlertDialogCancel>
|
||||||
disabled={!emptyServices}
|
Cancel
|
||||||
onClick={async () => {
|
</AlertDialogCancel>
|
||||||
await mutateAsync({
|
<AlertDialogAction
|
||||||
projectId: project.projectId,
|
disabled={!emptyServices}
|
||||||
})
|
onClick={async () => {
|
||||||
.then(() => {
|
await mutateAsync({
|
||||||
toast.success(
|
projectId: project.projectId,
|
||||||
"Project delete succesfully",
|
})
|
||||||
);
|
.then(() => {
|
||||||
})
|
toast.success(
|
||||||
.catch(() => {
|
"Project delete succesfully",
|
||||||
toast.error(
|
);
|
||||||
"Error to delete this project",
|
})
|
||||||
);
|
.catch(() => {
|
||||||
})
|
toast.error(
|
||||||
.finally(() => {
|
"Error to delete this project",
|
||||||
utils.project.all.invalidate();
|
);
|
||||||
});
|
})
|
||||||
}}
|
.finally(() => {
|
||||||
>
|
utils.project.all.invalidate();
|
||||||
Delete
|
});
|
||||||
</AlertDialogAction>
|
}}
|
||||||
</AlertDialogFooter>
|
>
|
||||||
</AlertDialogContent>
|
Delete
|
||||||
</AlertDialog>
|
</AlertDialogAction>
|
||||||
)}
|
</AlertDialogFooter>
|
||||||
</DropdownMenuContent>
|
</AlertDialogContent>
|
||||||
</DropdownMenu>
|
</AlertDialog>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="pt-4">
|
||||||
|
<div className="space-y-1 text-sm flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
||||||
|
<DateTooltip date={project.createdAt}>
|
||||||
|
Created
|
||||||
|
</DateTooltip>
|
||||||
|
<span>
|
||||||
|
{totalServices}{" "}
|
||||||
|
{totalServices === 1 ? "service" : "services"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardFooter>
|
||||||
</CardHeader>
|
</Card>
|
||||||
<CardFooter className="pt-4">
|
</Link>
|
||||||
<div className="space-y-1 text-sm flex flex-row justify-between max-sm:flex-wrap w-full gap-2 sm:gap-4">
|
|
||||||
<DateTooltip date={project.createdAt}>Created</DateTooltip>
|
|
||||||
<span>
|
|
||||||
{totalServices}{" "}
|
|
||||||
{totalServices === 1 ? "service" : "services"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
1
drizzle/0017_minor_post.sql
Normal file
1
drizzle/0017_minor_post.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "deployment" ADD COLUMN "description" text;
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "user" ALTER COLUMN "token" DROP NOT NULL;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "7610c85e-c3e4-4a32-8ce9-7f48b298f956",
|
"id": "ec852f38-886a-43b4-9295-73984ed8ef45",
|
||||||
"prevId": "2d8d7670-b942-4573-9c44-6e81d2a2fa16",
|
"prevId": "2d8d7670-b942-4573-9c44-6e81d2a2fa16",
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"dialect": "postgresql",
|
"dialect": "postgresql",
|
||||||
@@ -465,7 +465,7 @@
|
|||||||
"name": "token",
|
"name": "token",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": true
|
||||||
},
|
},
|
||||||
"isRegistered": {
|
"isRegistered": {
|
||||||
"name": "isRegistered",
|
"name": "isRegistered",
|
||||||
@@ -1585,6 +1585,12 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"name": "status",
|
"name": "status",
|
||||||
"type": "deploymentStatus",
|
"type": "deploymentStatus",
|
||||||
|
|||||||
@@ -124,8 +124,8 @@
|
|||||||
{
|
{
|
||||||
"idx": 17,
|
"idx": 17,
|
||||||
"version": "6",
|
"version": "6",
|
||||||
"when": 1719109531147,
|
"when": 1719547174326,
|
||||||
"tag": "0017_yummy_norrin_radd",
|
"tag": "0017_minor_post",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.2.4",
|
"version": "v0.2.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
29
pages/api/[...trpc].ts
Normal file
29
pages/api/[...trpc].ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { createOpenApiNextHandler } from "@dokploy/trpc-openapi";
|
||||||
|
import { appRouter } from "@/server/api/root";
|
||||||
|
import { createTRPCContext } from "@/server/api/trpc";
|
||||||
|
import { validateBearerToken } from "@/server/auth/token";
|
||||||
|
import { validateRequest } from "@/server/auth/auth";
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
let { session, user } = await validateBearerToken(req);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
const cookieResult = await validateRequest(req, res);
|
||||||
|
session = cookieResult.session;
|
||||||
|
user = cookieResult.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || !session) {
|
||||||
|
res.status(401).json({ message: "Unauthorized" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return createOpenApiNextHandler({
|
||||||
|
router: appRouter,
|
||||||
|
createContext: createTRPCContext,
|
||||||
|
})(req, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default handler;
|
||||||
@@ -33,6 +33,7 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
||||||
|
const deploymentHash = extractHash(req.headers, req.body);
|
||||||
|
|
||||||
const sourceType = application.sourceType;
|
const sourceType = application.sourceType;
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ export default async function handler(
|
|||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: application.applicationId as string,
|
applicationId: application.applicationId as string,
|
||||||
titleLog: deploymentTitle,
|
titleLog: deploymentTitle,
|
||||||
|
descriptionLog: `Hash: ${deploymentHash}`,
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
};
|
};
|
||||||
@@ -166,6 +168,37 @@ export const extractCommitMessage = (headers: any, body: any) => {
|
|||||||
return "NEW CHANGES";
|
return "NEW CHANGES";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extractHash = (headers: any, body: any) => {
|
||||||
|
// GitHub
|
||||||
|
if (headers["x-github-event"]) {
|
||||||
|
return body.head_commit ? body.head_commit.id : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitLab
|
||||||
|
if (headers["x-gitlab-event"]) {
|
||||||
|
return (
|
||||||
|
body.checkout_sha ||
|
||||||
|
(body.commits && body.commits.length > 0
|
||||||
|
? body.commits[0].id
|
||||||
|
: "NEW COMMIT")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitbucket
|
||||||
|
if (headers["x-event-key"]?.includes("repo:push")) {
|
||||||
|
return body.push.changes && body.push.changes.length > 0
|
||||||
|
? body.push.changes[0].new.target.hash
|
||||||
|
: "NEW COMMIT";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gitea
|
||||||
|
if (headers["x-gitea-event"]) {
|
||||||
|
return body.after || "NEW COMMIT";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
export const extractBranchName = (headers: any, body: any) => {
|
export const extractBranchName = (headers: any, body: any) => {
|
||||||
if (headers["x-github-event"] || headers["x-gitea-event"]) {
|
if (headers["x-github-event"] || headers["x-gitea-event"]) {
|
||||||
return body?.ref?.replace("refs/heads/", "");
|
return body?.ref?.replace("refs/heads/", "");
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import type { DeploymentJob } from "@/server/queues/deployments-queue";
|
|||||||
import { myQueue } from "@/server/queues/queueSetup";
|
import { myQueue } from "@/server/queues/queueSetup";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { extractBranchName, extractCommitMessage } from "../[refreshToken]";
|
import {
|
||||||
|
extractBranchName,
|
||||||
|
extractCommitMessage,
|
||||||
|
extractHash,
|
||||||
|
} from "../[refreshToken]";
|
||||||
import { updateCompose } from "@/server/api/services/compose";
|
import { updateCompose } from "@/server/api/services/compose";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
@@ -34,7 +38,7 @@ export default async function handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
const deploymentTitle = extractCommitMessage(req.headers, req.body);
|
||||||
|
const deploymentHash = extractHash(req.headers, req.body);
|
||||||
const sourceType = composeResult.sourceType;
|
const sourceType = composeResult.sourceType;
|
||||||
|
|
||||||
if (sourceType === "github") {
|
if (sourceType === "github") {
|
||||||
@@ -61,6 +65,7 @@ export default async function handler(
|
|||||||
titleLog: deploymentTitle,
|
titleLog: deploymentTitle,
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
|
descriptionLog: `Hash: ${deploymentHash}`,
|
||||||
};
|
};
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
"deployments",
|
"deployments",
|
||||||
|
|||||||
@@ -6,15 +6,32 @@ import type { GetServerSidePropsContext, NextPage } from "next";
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import "swagger-ui-react/swagger-ui.css";
|
import "swagger-ui-react/swagger-ui.css";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false });
|
const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false });
|
||||||
|
|
||||||
const Home: NextPage = () => {
|
const Home: NextPage = () => {
|
||||||
const { data } = api.settings.getOpenApiDocument.useQuery();
|
const { data } = api.settings.getOpenApiDocument.useQuery();
|
||||||
|
const [spec, setSpec] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Esto solo se ejecutará en el cliente
|
||||||
|
if (data) {
|
||||||
|
const protocolAndHost = `${window.location.protocol}//${window.location.host}/api`;
|
||||||
|
const newSpec = {
|
||||||
|
...data,
|
||||||
|
servers: [{ url: protocolAndHost }],
|
||||||
|
externalDocs: {
|
||||||
|
url: `${protocolAndHost}/settings.getOpenApiDocument`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setSpec(newSpec);
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-white">
|
<div className="h-screen bg-white">
|
||||||
<SwaggerUI spec={data || {}} />
|
<SwaggerUI spec={spec} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
85
pnpm-lock.yaml
generated
85
pnpm-lock.yaml
generated
@@ -100,7 +100,7 @@ dependencies:
|
|||||||
version: 10.45.2(@trpc/server@10.45.2)
|
version: 10.45.2(@trpc/server@10.45.2)
|
||||||
'@trpc/next':
|
'@trpc/next':
|
||||||
specifier: ^10.43.6
|
specifier: ^10.43.6
|
||||||
version: 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/react-query@10.45.2)(@trpc/server@10.45.2)(next@14.2.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/react-query@10.45.2)(@trpc/server@10.45.2)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/react-query':
|
'@trpc/react-query':
|
||||||
specifier: ^10.43.6
|
specifier: ^10.43.6
|
||||||
version: 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/server@10.45.2)(react-dom@18.2.0)(react@18.2.0)
|
version: 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/server@10.45.2)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -193,10 +193,10 @@ dependencies:
|
|||||||
version: 3.3.7
|
version: 3.3.7
|
||||||
next:
|
next:
|
||||||
specifier: ^14.1.3
|
specifier: ^14.1.3
|
||||||
version: 14.2.3(react-dom@18.2.0)(react@18.2.0)
|
version: 14.2.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.2.1
|
specifier: ^0.2.1
|
||||||
version: 0.2.1(next@14.2.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 0.2.1(next@14.2.4)(react-dom@18.2.0)(react@18.2.0)
|
||||||
node-os-utils:
|
node-os-utils:
|
||||||
specifier: 1.3.7
|
specifier: 1.3.7
|
||||||
version: 1.3.7
|
version: 1.3.7
|
||||||
@@ -2059,12 +2059,12 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/env@14.2.3:
|
/@next/env@14.2.4:
|
||||||
resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==}
|
resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@next/swc-darwin-arm64@14.2.3:
|
/@next/swc-darwin-arm64@14.2.4:
|
||||||
resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==}
|
resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -2072,8 +2072,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64@14.2.3:
|
/@next/swc-darwin-x64@14.2.4:
|
||||||
resolution: {integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==}
|
resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
@@ -2081,48 +2081,44 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu@14.2.3:
|
/@next/swc-linux-arm64-gnu@14.2.4:
|
||||||
resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==}
|
resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl@14.2.3:
|
/@next/swc-linux-arm64-musl@14.2.4:
|
||||||
resolution: {integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==}
|
resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu@14.2.3:
|
/@next/swc-linux-x64-gnu@14.2.4:
|
||||||
resolution: {integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==}
|
resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl@14.2.3:
|
/@next/swc-linux-x64-musl@14.2.4:
|
||||||
resolution: {integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==}
|
resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc@14.2.3:
|
/@next/swc-win32-arm64-msvc@14.2.4:
|
||||||
resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==}
|
resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -2130,8 +2126,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc@14.2.3:
|
/@next/swc-win32-ia32-msvc@14.2.4:
|
||||||
resolution: {integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==}
|
resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -2139,8 +2135,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc@14.2.3:
|
/@next/swc-win32-x64-msvc@14.2.4:
|
||||||
resolution: {integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==}
|
resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
@@ -4952,7 +4948,7 @@ packages:
|
|||||||
'@trpc/server': 10.45.2
|
'@trpc/server': 10.45.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@trpc/next@10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/react-query@10.45.2)(@trpc/server@10.45.2)(next@14.2.3)(react-dom@18.2.0)(react@18.2.0):
|
/@trpc/next@10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/react-query@10.45.2)(@trpc/server@10.45.2)(next@14.2.4)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-RSORmfC+/nXdmRY1pQ0AalsVgSzwNAFbZLYHiTvPM5QQ8wmMEHilseCYMXpu0se/TbPt9zVR6Ka2d7O6zxKkXg==}
|
resolution: {integrity: sha512-RSORmfC+/nXdmRY1pQ0AalsVgSzwNAFbZLYHiTvPM5QQ8wmMEHilseCYMXpu0se/TbPt9zVR6Ka2d7O6zxKkXg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^4.18.0
|
'@tanstack/react-query': ^4.18.0
|
||||||
@@ -4967,7 +4963,7 @@ packages:
|
|||||||
'@trpc/client': 10.45.2(@trpc/server@10.45.2)
|
'@trpc/client': 10.45.2(@trpc/server@10.45.2)
|
||||||
'@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/server@10.45.2)(react-dom@18.2.0)(react@18.2.0)
|
'@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1)(@trpc/client@10.45.2)(@trpc/server@10.45.2)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/server': 10.45.2
|
'@trpc/server': 10.45.2
|
||||||
next: 14.2.3(react-dom@18.2.0)(react@18.2.0)
|
next: 14.2.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
@@ -8104,14 +8100,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next-themes@0.2.1(next@14.2.3)(react-dom@18.2.0)(react@18.2.0):
|
/next-themes@0.2.1(next@14.2.4)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: '*'
|
next: '*'
|
||||||
react: '*'
|
react: '*'
|
||||||
react-dom: '*'
|
react-dom: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 14.2.3(react-dom@18.2.0)(react@18.2.0)
|
next: 14.2.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
@@ -8120,8 +8116,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next@14.2.3(react-dom@18.2.0)(react@18.2.0):
|
/next@14.2.4(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==}
|
resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==}
|
||||||
engines: {node: '>=18.17.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -8138,7 +8134,7 @@ packages:
|
|||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 14.2.3
|
'@next/env': 14.2.4
|
||||||
'@swc/helpers': 0.5.5
|
'@swc/helpers': 0.5.5
|
||||||
busboy: 1.6.0
|
busboy: 1.6.0
|
||||||
caniuse-lite: 1.0.30001598
|
caniuse-lite: 1.0.30001598
|
||||||
@@ -8148,15 +8144,15 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
styled-jsx: 5.1.1(react@18.2.0)
|
styled-jsx: 5.1.1(react@18.2.0)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@next/swc-darwin-arm64': 14.2.3
|
'@next/swc-darwin-arm64': 14.2.4
|
||||||
'@next/swc-darwin-x64': 14.2.3
|
'@next/swc-darwin-x64': 14.2.4
|
||||||
'@next/swc-linux-arm64-gnu': 14.2.3
|
'@next/swc-linux-arm64-gnu': 14.2.4
|
||||||
'@next/swc-linux-arm64-musl': 14.2.3
|
'@next/swc-linux-arm64-musl': 14.2.4
|
||||||
'@next/swc-linux-x64-gnu': 14.2.3
|
'@next/swc-linux-x64-gnu': 14.2.4
|
||||||
'@next/swc-linux-x64-musl': 14.2.3
|
'@next/swc-linux-x64-musl': 14.2.4
|
||||||
'@next/swc-win32-arm64-msvc': 14.2.3
|
'@next/swc-win32-arm64-msvc': 14.2.4
|
||||||
'@next/swc-win32-ia32-msvc': 14.2.3
|
'@next/swc-win32-ia32-msvc': 14.2.4
|
||||||
'@next/swc-win32-x64-msvc': 14.2.3
|
'@next/swc-win32-x64-msvc': 14.2.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@@ -8551,7 +8547,7 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
nanoid: 3.3.7
|
nanoid: 3.3.7
|
||||||
picocolors: 1.0.0
|
picocolors: 1.0.0
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/postcss@8.4.35:
|
/postcss@8.4.35:
|
||||||
@@ -9404,7 +9400,6 @@ packages:
|
|||||||
/source-map-js@1.2.0:
|
/source-map-js@1.2.0:
|
||||||
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/source-map-support@0.5.21:
|
/source-map-support@0.5.21:
|
||||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: "Rebuild deployment",
|
titleLog: "Rebuild deployment",
|
||||||
|
descriptionLog: "",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
};
|
};
|
||||||
@@ -294,6 +295,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
const jobData: DeploymentJob = {
|
const jobData: DeploymentJob = {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
titleLog: "Manual deployment",
|
titleLog: "Manual deployment",
|
||||||
|
descriptionLog: "",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "application",
|
applicationType: "application",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
titleLog: "Manual deployment",
|
titleLog: "Manual deployment",
|
||||||
type: "deploy",
|
type: "deploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
|
descriptionLog: "",
|
||||||
};
|
};
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
"deployments",
|
"deployments",
|
||||||
@@ -156,6 +157,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
titleLog: "Rebuild deployment",
|
titleLog: "Rebuild deployment",
|
||||||
type: "redeploy",
|
type: "redeploy",
|
||||||
applicationType: "compose",
|
applicationType: "compose",
|
||||||
|
descriptionLog: "",
|
||||||
};
|
};
|
||||||
await myQueue.add(
|
await myQueue.add(
|
||||||
"deployments",
|
"deployments",
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
createDomain,
|
createDomain,
|
||||||
findDomainById,
|
findDomainById,
|
||||||
findDomainsByApplicationId,
|
findDomainsByApplicationId,
|
||||||
|
generateDomain,
|
||||||
|
generateWildcard,
|
||||||
removeDomainById,
|
removeDomainById,
|
||||||
updateDomainById,
|
updateDomainById,
|
||||||
} from "../services/domain";
|
} from "../services/domain";
|
||||||
@@ -35,6 +37,16 @@ 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);
|
||||||
|
}),
|
||||||
|
generateWildcard: protectedProcedure
|
||||||
|
.input(apiFindDomainByApplication)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
return generateWildcard(input);
|
||||||
|
}),
|
||||||
update: protectedProcedure
|
update: protectedProcedure
|
||||||
.input(apiUpdateDomain)
|
.input(apiUpdateDomain)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
getOpenApiDocument: protectedProcedure.query(
|
getOpenApiDocument: protectedProcedure.query(
|
||||||
async ({ ctx }): Promise<unknown> => {
|
async ({ ctx }): Promise<unknown> => {
|
||||||
const protocol = ctx.req.headers["x-forwarded-proto"];
|
const protocol = ctx.req.headers["x-forwarded-proto"];
|
||||||
const url = `${protocol}://${ctx.req.headers.host}/api/trpc`;
|
const url = `${protocol}://${ctx.req.headers.host}/api`;
|
||||||
const openApiDocument = generateOpenApiDocument(appRouter, {
|
const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: "tRPC OpenAPI",
|
title: "tRPC OpenAPI",
|
||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
|
|||||||
@@ -130,15 +130,18 @@ export const updateApplicationStatus = async (
|
|||||||
export const deployApplication = async ({
|
export const deployApplication = async ({
|
||||||
applicationId,
|
applicationId,
|
||||||
titleLog = "Manual deployment",
|
titleLog = "Manual deployment",
|
||||||
|
descriptionLog = "",
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const application = await findApplicationById(applicationId);
|
const application = await findApplicationById(applicationId);
|
||||||
const admin = await findAdmin();
|
const admin = await findAdmin();
|
||||||
const deployment = await createDeployment({
|
const deployment = await createDeployment({
|
||||||
applicationId: applicationId,
|
applicationId: applicationId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
|
description: descriptionLog,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -173,14 +176,17 @@ export const deployApplication = async ({
|
|||||||
export const rebuildApplication = async ({
|
export const rebuildApplication = async ({
|
||||||
applicationId,
|
applicationId,
|
||||||
titleLog = "Rebuild deployment",
|
titleLog = "Rebuild deployment",
|
||||||
|
descriptionLog = "",
|
||||||
}: {
|
}: {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const application = await findApplicationById(applicationId);
|
const application = await findApplicationById(applicationId);
|
||||||
const deployment = await createDeployment({
|
const deployment = await createDeployment({
|
||||||
applicationId: applicationId,
|
applicationId: applicationId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
|
description: descriptionLog,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -134,15 +134,18 @@ export const updateCompose = async (
|
|||||||
export const deployCompose = async ({
|
export const deployCompose = async ({
|
||||||
composeId,
|
composeId,
|
||||||
titleLog = "Manual deployment",
|
titleLog = "Manual deployment",
|
||||||
|
descriptionLog = "",
|
||||||
}: {
|
}: {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const compose = await findComposeById(composeId);
|
const compose = await findComposeById(composeId);
|
||||||
const admin = await findAdmin();
|
const admin = await findAdmin();
|
||||||
const deployment = await createDeploymentCompose({
|
const deployment = await createDeploymentCompose({
|
||||||
composeId: composeId,
|
composeId: composeId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
|
description: descriptionLog,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -170,14 +173,17 @@ export const deployCompose = async ({
|
|||||||
export const rebuildCompose = async ({
|
export const rebuildCompose = async ({
|
||||||
composeId,
|
composeId,
|
||||||
titleLog = "Rebuild deployment",
|
titleLog = "Rebuild deployment",
|
||||||
|
descriptionLog = "",
|
||||||
}: {
|
}: {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
}) => {
|
}) => {
|
||||||
const compose = await findComposeById(composeId);
|
const compose = await findComposeById(composeId);
|
||||||
const deployment = await createDeploymentCompose({
|
const deployment = await createDeploymentCompose({
|
||||||
composeId: composeId,
|
composeId: composeId,
|
||||||
title: titleLog,
|
title: titleLog,
|
||||||
|
description: descriptionLog,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export const createDeployment = async (
|
|||||||
title: deployment.title || "Deployment",
|
title: deployment.title || "Deployment",
|
||||||
status: "running",
|
status: "running",
|
||||||
logPath: logFilePath,
|
logPath: logFilePath,
|
||||||
|
description: deployment.description || "",
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
if (deploymentCreate.length === 0 || !deploymentCreate[0]) {
|
||||||
@@ -100,6 +101,7 @@ export const createDeploymentCompose = async (
|
|||||||
.values({
|
.values({
|
||||||
composeId: deployment.composeId,
|
composeId: deployment.composeId,
|
||||||
title: deployment.title || "Deployment",
|
title: deployment.title || "Deployment",
|
||||||
|
description: deployment.description || "",
|
||||||
status: "running",
|
status: "running",
|
||||||
logPath: logFilePath,
|
logPath: logFilePath,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,58 @@ 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: 3000,
|
||||||
|
certificateType: "none",
|
||||||
|
https: false,
|
||||||
|
path: "/",
|
||||||
|
});
|
||||||
|
|
||||||
|
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),
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const deployments = pgTable("deployment", {
|
|||||||
.primaryKey()
|
.primaryKey()
|
||||||
.$defaultFn(() => nanoid()),
|
.$defaultFn(() => nanoid()),
|
||||||
title: text("title").notNull(),
|
title: text("title").notNull(),
|
||||||
|
description: text("description"),
|
||||||
status: deploymentStatus("status").default("running"),
|
status: deploymentStatus("status").default("running"),
|
||||||
logPath: text("logPath").notNull(),
|
logPath: text("logPath").notNull(),
|
||||||
applicationId: text("applicationId").references(
|
applicationId: text("applicationId").references(
|
||||||
@@ -49,6 +50,7 @@ const schema = createInsertSchema(deployments, {
|
|||||||
logPath: z.string().min(1),
|
logPath: z.string().min(1),
|
||||||
applicationId: z.string(),
|
applicationId: z.string(),
|
||||||
composeId: z.string(),
|
composeId: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const apiCreateDeployment = schema
|
export const apiCreateDeployment = schema
|
||||||
@@ -57,6 +59,7 @@ export const apiCreateDeployment = schema
|
|||||||
status: true,
|
status: true,
|
||||||
logPath: true,
|
logPath: true,
|
||||||
applicationId: true,
|
applicationId: true,
|
||||||
|
description: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
applicationId: z.string().min(1),
|
applicationId: z.string().min(1),
|
||||||
@@ -68,6 +71,7 @@ export const apiCreateDeploymentCompose = schema
|
|||||||
status: true,
|
status: true,
|
||||||
logPath: true,
|
logPath: true,
|
||||||
composeId: true,
|
composeId: true,
|
||||||
|
description: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
composeId: z.string().min(1),
|
composeId: z.string().min(1),
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ type DeployJob =
|
|||||||
| {
|
| {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
type: "deploy" | "redeploy";
|
type: "deploy" | "redeploy";
|
||||||
applicationType: "application";
|
applicationType: "application";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
composeId: string;
|
composeId: string;
|
||||||
titleLog: string;
|
titleLog: string;
|
||||||
|
descriptionLog: string;
|
||||||
type: "deploy" | "redeploy";
|
type: "deploy" | "redeploy";
|
||||||
applicationType: "compose";
|
applicationType: "compose";
|
||||||
};
|
};
|
||||||
@@ -31,11 +33,13 @@ export const deploymentWorker = new Worker(
|
|||||||
await rebuildApplication({
|
await rebuildApplication({
|
||||||
applicationId: job.data.applicationId,
|
applicationId: job.data.applicationId,
|
||||||
titleLog: job.data.titleLog,
|
titleLog: job.data.titleLog,
|
||||||
|
descriptionLog: job.data.descriptionLog,
|
||||||
});
|
});
|
||||||
} else if (job.data.type === "deploy") {
|
} else if (job.data.type === "deploy") {
|
||||||
await deployApplication({
|
await deployApplication({
|
||||||
applicationId: job.data.applicationId,
|
applicationId: job.data.applicationId,
|
||||||
titleLog: job.data.titleLog,
|
titleLog: job.data.titleLog,
|
||||||
|
descriptionLog: job.data.descriptionLog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (job.data.applicationType === "compose") {
|
} else if (job.data.applicationType === "compose") {
|
||||||
@@ -43,11 +47,13 @@ export const deploymentWorker = new Worker(
|
|||||||
await deployCompose({
|
await deployCompose({
|
||||||
composeId: job.data.composeId,
|
composeId: job.data.composeId,
|
||||||
titleLog: job.data.titleLog,
|
titleLog: job.data.titleLog,
|
||||||
|
descriptionLog: job.data.descriptionLog,
|
||||||
});
|
});
|
||||||
} else if (job.data.type === "redeploy") {
|
} else if (job.data.type === "redeploy") {
|
||||||
await rebuildCompose({
|
await rebuildCompose({
|
||||||
composeId: job.data.composeId,
|
composeId: job.data.composeId,
|
||||||
titleLog: job.data.titleLog,
|
titleLog: job.data.titleLog,
|
||||||
|
descriptionLog: job.data.descriptionLog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ export const buildMariadb = async (mariadb: MariadbWithMounts) => {
|
|||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
|
Placement: {
|
||||||
|
Constraints: ["node.role==manager"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ export const buildMongo = async (mongo: MongoWithMounts) => {
|
|||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
|
Placement: {
|
||||||
|
Constraints: ["node.role==manager"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ export const buildMysql = async (mysql: MysqlWithMounts) => {
|
|||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
|
Placement: {
|
||||||
|
Constraints: ["node.role==manager"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ export const buildPostgres = async (postgres: PostgresWithMounts) => {
|
|||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
|
Placement: {
|
||||||
|
Constraints: ["node.role==manager"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
|
|||||||
@@ -50,17 +50,19 @@ export const buildRedis = async (redis: RedisWithMounts) => {
|
|||||||
Image: dockerImage,
|
Image: dockerImage,
|
||||||
Env: envVariables,
|
Env: envVariables,
|
||||||
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
Mounts: [...volumesMount, ...bindsMount, ...filesMount],
|
||||||
...(command
|
Command: ["/bin/sh"],
|
||||||
? {
|
Args: [
|
||||||
Command: ["/bin/sh"],
|
"-c",
|
||||||
Args: ["-c", command],
|
command ? command : `redis-server --requirepass ${databasePassword}`,
|
||||||
}
|
],
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
Networks: [{ Target: "dokploy-network" }],
|
Networks: [{ Target: "dokploy-network" }],
|
||||||
Resources: {
|
Resources: {
|
||||||
...resources,
|
...resources,
|
||||||
},
|
},
|
||||||
|
Placement: {
|
||||||
|
Constraints: ["node.role==manager"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Mode: {
|
Mode: {
|
||||||
Replicated: {
|
Replicated: {
|
||||||
|
|||||||
Reference in New Issue
Block a user