mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into feat/cloud
This commit is contained in:
@@ -32,6 +32,7 @@ Dokploy include multiples features to make your life easier.
|
|||||||
- **Docker Management**: Easily deploy and manage Docker containers.
|
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||||
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
||||||
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
|
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
|
||||||
|
- **Multi Server**: Deploy and manager your applications remotely to external servers.
|
||||||
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||||
|
|
||||||
## 🚀 Getting Started
|
## 🚀 Getting Started
|
||||||
|
|||||||
@@ -15,20 +15,25 @@ import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
BookIcon,
|
BookIcon,
|
||||||
|
CircuitBoard,
|
||||||
|
ExternalLink,
|
||||||
ExternalLinkIcon,
|
ExternalLinkIcon,
|
||||||
FolderInput,
|
FolderInput,
|
||||||
MoreHorizontalIcon,
|
MoreHorizontalIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Fragment } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { UpdateProject } from "./update";
|
import { UpdateProject } from "./update";
|
||||||
|
|
||||||
@@ -45,6 +50,7 @@ export const ShowProjects = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const { mutateAsync } = api.project.remove.useMutation();
|
const { mutateAsync } = api.project.remove.useMutation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{data?.length === 0 && (
|
{data?.length === 0 && (
|
||||||
@@ -74,17 +80,87 @@ export const ShowProjects = () => {
|
|||||||
project?.redis.length +
|
project?.redis.length +
|
||||||
project?.applications.length +
|
project?.applications.length +
|
||||||
project?.compose.length;
|
project?.compose.length;
|
||||||
|
|
||||||
|
const flattedDomains = [
|
||||||
|
...project.applications.flatMap((a) => a.domains),
|
||||||
|
...project.compose.flatMap((a) => a.domains),
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderDomainsDropdown = (
|
||||||
|
item: typeof project.compose | typeof project.applications,
|
||||||
|
) =>
|
||||||
|
item[0] ? (
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel>
|
||||||
|
{"applicationId" in item[0] ? "Applications" : "Compose"}
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
{item.map((a) => (
|
||||||
|
<Fragment
|
||||||
|
key={"applicationId" in a ? a.applicationId : a.composeId}
|
||||||
|
>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuLabel className="font-normal capitalize text-xs ">
|
||||||
|
{a.name}
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{a.domains.map((domain) => (
|
||||||
|
<DropdownMenuItem key={domain.domainId} asChild>
|
||||||
|
<Link
|
||||||
|
className="space-x-4 text-xs cursor-pointer justify-between"
|
||||||
|
target="_blank"
|
||||||
|
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
|
||||||
|
>
|
||||||
|
<span>{domain.host}</span>
|
||||||
|
<ExternalLink className="size-4 shrink-0" />
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={project.projectId} className="w-full lg:max-w-md">
|
<div key={project.projectId} className="w-full lg:max-w-md">
|
||||||
<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">
|
<Card className="group relative w-full bg-transparent transition-colors hover:bg-card">
|
||||||
<Button
|
{flattedDomains.length > 1 ? (
|
||||||
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"
|
<DropdownMenu>
|
||||||
size="sm"
|
<DropdownMenuTrigger asChild>
|
||||||
variant="default"
|
<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"
|
||||||
<ExternalLinkIcon className="size-3.5" />
|
size="sm"
|
||||||
</Button>
|
variant="default"
|
||||||
|
>
|
||||||
|
<ExternalLinkIcon className="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
className="w-[200px] space-y-2"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{renderDomainsDropdown(project.applications)}
|
||||||
|
{renderDomainsDropdown(project.compose)}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
) : flattedDomains[0] ? (
|
||||||
|
<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"
|
||||||
|
size="sm"
|
||||||
|
variant="default"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={`${flattedDomains[0].https ? "https" : "http"}://${flattedDomains[0].host}${flattedDomains[0].path}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<ExternalLinkIcon className="size-3.5" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<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">
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export const applicationRouter = createTRPCRouter({
|
|||||||
if (ctx.user.rol === "user") {
|
if (ctx.user.rol === "user") {
|
||||||
await addNewService(ctx.user.authId, newApplication.applicationId);
|
await addNewService(ctx.user.authId, newApplication.applicationId);
|
||||||
}
|
}
|
||||||
|
return newApplication;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof TRPCError) {
|
if (error instanceof TRPCError) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
applications.applicationId,
|
applications.applicationId,
|
||||||
accesedServices,
|
accesedServices,
|
||||||
),
|
),
|
||||||
|
with: { domains: true },
|
||||||
},
|
},
|
||||||
mariadb: {
|
mariadb: {
|
||||||
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
|
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
|
||||||
@@ -150,6 +151,7 @@ export const projectRouter = createTRPCRouter({
|
|||||||
},
|
},
|
||||||
compose: {
|
compose: {
|
||||||
where: buildServiceFilter(compose.composeId, accesedServices),
|
where: buildServiceFilter(compose.composeId, accesedServices),
|
||||||
|
with: { domains: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
orderBy: desc(projects.createdAt),
|
orderBy: desc(projects.createdAt),
|
||||||
@@ -160,13 +162,21 @@ export const projectRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return await db.query.projects.findMany({
|
return await db.query.projects.findMany({
|
||||||
with: {
|
with: {
|
||||||
applications: true,
|
applications: {
|
||||||
|
with: {
|
||||||
|
domains: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
mariadb: true,
|
mariadb: true,
|
||||||
mongo: true,
|
mongo: true,
|
||||||
mysql: true,
|
mysql: true,
|
||||||
postgres: true,
|
postgres: true,
|
||||||
redis: true,
|
redis: true,
|
||||||
compose: true,
|
compose: {
|
||||||
|
with: {
|
||||||
|
domains: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
where: eq(projects.adminId, ctx.user.adminId),
|
where: eq(projects.adminId, ctx.user.adminId),
|
||||||
orderBy: desc(projects.createdAt),
|
orderBy: desc(projects.createdAt),
|
||||||
|
|||||||
Reference in New Issue
Block a user