mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
add dropdown link
This commit is contained in:
@@ -15,14 +15,18 @@ 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,
|
||||||
@@ -31,6 +35,7 @@ import {
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { UpdateProject } from "./update";
|
import { UpdateProject } from "./update";
|
||||||
|
import { Fragment } from "react";
|
||||||
|
|
||||||
export const ShowProjects = () => {
|
export const ShowProjects = () => {
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
@@ -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,85 @@ 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}`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
{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">
|
||||||
|
|||||||
@@ -125,6 +125,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),
|
||||||
@@ -143,6 +144,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),
|
||||||
@@ -153,13 +155,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