mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat(cluster-nodes): enhance node management by adding serverId prop to components and implementing ShowNodesModal
This commit is contained in:
parent
60c03e1ca7
commit
35ff8dcfe6
@ -13,7 +13,11 @@ import Link from "next/link";
|
|||||||
import { AddManager } from "./manager/add-manager";
|
import { AddManager } from "./manager/add-manager";
|
||||||
import { AddWorker } from "./workers/add-worker";
|
import { AddWorker } from "./workers/add-worker";
|
||||||
|
|
||||||
export const AddNode = () => {
|
interface Props {
|
||||||
|
serverId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddNode = ({ serverId }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@ -53,10 +57,10 @@ export const AddNode = () => {
|
|||||||
<TabsTrigger value="manager">Manager</TabsTrigger>
|
<TabsTrigger value="manager">Manager</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="worker" className="pt-4">
|
<TabsContent value="worker" className="pt-4">
|
||||||
<AddWorker />
|
<AddWorker serverId={serverId} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="manager" className="pt-4">
|
<TabsContent value="manager" className="pt-4">
|
||||||
<AddManager />
|
<AddManager serverId={serverId} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,8 +9,12 @@ import copy from "copy-to-clipboard";
|
|||||||
import { CopyIcon } from "lucide-react";
|
import { CopyIcon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export const AddManager = () => {
|
interface Props {
|
||||||
const { data } = api.cluster.addManager.useQuery();
|
serverId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddManager = ({ serverId }: Props) => {
|
||||||
|
const { data } = api.cluster.addManager.useQuery({ serverId });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ShowNodes } from "./show-nodes";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
serverId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowNodesModal = ({ serverId }: Props) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="w-full cursor-pointer "
|
||||||
|
onSelect={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
Show Nodes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen ">
|
||||||
|
<div className="grid w-full gap-1">
|
||||||
|
<ShowNodes serverId={serverId} />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
@ -32,13 +32,25 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { Boxes, HelpCircle, LockIcon, MoreHorizontal } from "lucide-react";
|
import {
|
||||||
|
Boxes,
|
||||||
|
HelpCircle,
|
||||||
|
LockIcon,
|
||||||
|
MoreHorizontal,
|
||||||
|
Loader2,
|
||||||
|
} from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { AddNode } from "./add-node";
|
import { AddNode } from "./add-node";
|
||||||
import { ShowNodeData } from "./show-node-data";
|
import { ShowNodeData } from "./show-node-data";
|
||||||
|
|
||||||
export const ShowNodes = () => {
|
interface Props {
|
||||||
const { data, isLoading, refetch } = api.cluster.getNodes.useQuery();
|
serverId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShowNodes = ({ serverId }: Props) => {
|
||||||
|
const { data, isLoading, refetch } = api.cluster.getNodes.useQuery({
|
||||||
|
serverId,
|
||||||
|
});
|
||||||
const { data: registry } = api.registry.all.useQuery();
|
const { data: registry } = api.registry.all.useQuery();
|
||||||
|
|
||||||
const { mutateAsync: deleteNode } = api.cluster.removeWorker.useMutation();
|
const { mutateAsync: deleteNode } = api.cluster.removeWorker.useMutation();
|
||||||
@ -58,14 +70,17 @@ export const ShowNodes = () => {
|
|||||||
</div>
|
</div>
|
||||||
{haveAtLeastOneRegistry && (
|
{haveAtLeastOneRegistry && (
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<AddNode />
|
<AddNode serverId={serverId} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-2 py-8 border-t min-h-[35vh]">
|
<CardContent className="space-y-2 py-8 border-t min-h-[35vh]">
|
||||||
{haveAtLeastOneRegistry ? (
|
{isLoading ? (
|
||||||
|
<div className="flex items-center justify-center w-full h-[40vh]">
|
||||||
|
<Loader2 className="size-8 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
) : haveAtLeastOneRegistry ? (
|
||||||
<div className="grid md:grid-cols-1 gap-4">
|
<div className="grid md:grid-cols-1 gap-4">
|
||||||
{isLoading && <div>Loading...</div>}
|
|
||||||
<Table>
|
<Table>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
A list of your managers / workers.
|
A list of your managers / workers.
|
||||||
@ -137,6 +152,7 @@ export const ShowNodes = () => {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await deleteNode({
|
await deleteNode({
|
||||||
nodeId: node.ID,
|
nodeId: node.ID,
|
||||||
|
serverId,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
|
@ -9,8 +9,12 @@ import copy from "copy-to-clipboard";
|
|||||||
import { CopyIcon } from "lucide-react";
|
import { CopyIcon } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export const AddWorker = () => {
|
interface Props {
|
||||||
const { data } = api.cluster.addWorker.useQuery();
|
serverId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddWorker = ({ serverId }: Props) => {
|
||||||
|
const { data } = api.cluster.addWorker.useQuery({ serverId });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -42,6 +42,7 @@ import { ShowMonitoringModal } from "./show-monitoring-modal";
|
|||||||
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
|
import { ShowSwarmOverviewModal } from "./show-swarm-overview-modal";
|
||||||
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
import { ShowTraefikFileSystemModal } from "./show-traefik-file-system-modal";
|
||||||
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
import { WelcomeSuscription } from "./welcome-stripe/welcome-suscription";
|
||||||
|
import { ShowNodesModal } from "../cluster/nodes/show-nodes-modal";
|
||||||
|
|
||||||
export const ShowServers = () => {
|
export const ShowServers = () => {
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
@ -328,6 +329,9 @@ export const ShowServers = () => {
|
|||||||
<ShowSwarmOverviewModal
|
<ShowSwarmOverviewModal
|
||||||
serverId={server.serverId}
|
serverId={server.serverId}
|
||||||
/>
|
/>
|
||||||
|
<ShowNodesModal
|
||||||
|
serverId={server.serverId}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
@ -1,22 +1,35 @@
|
|||||||
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
import { getPublicIpWithFallback } from "@/server/wss/terminal";
|
||||||
import { type DockerNode, IS_CLOUD, docker, execAsync } from "@dokploy/server";
|
import {
|
||||||
|
type DockerNode,
|
||||||
|
IS_CLOUD,
|
||||||
|
execAsync,
|
||||||
|
getRemoteDocker,
|
||||||
|
} from "@dokploy/server";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
|
|
||||||
export const clusterRouter = createTRPCRouter({
|
export const clusterRouter = createTRPCRouter({
|
||||||
getNodes: protectedProcedure.query(async () => {
|
getNodes: protectedProcedure
|
||||||
if (IS_CLOUD) {
|
.input(
|
||||||
return [];
|
z.object({
|
||||||
}
|
serverId: z.string().optional(),
|
||||||
const workers: DockerNode[] = await docker.listNodes();
|
}),
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return workers;
|
const docker = await getRemoteDocker(input.serverId);
|
||||||
}),
|
const workers: DockerNode[] = await docker.listNodes();
|
||||||
|
|
||||||
|
return workers;
|
||||||
|
}),
|
||||||
removeWorker: protectedProcedure
|
removeWorker: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
nodeId: z.string(),
|
nodeId: z.string(),
|
||||||
|
serverId: z.string().optional(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
@ -40,37 +53,51 @@ export const clusterRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
addWorker: protectedProcedure.query(async () => {
|
addWorker: protectedProcedure
|
||||||
if (IS_CLOUD) {
|
.input(
|
||||||
return {
|
z.object({
|
||||||
command: "",
|
serverId: z.string().optional(),
|
||||||
version: "",
|
}),
|
||||||
};
|
)
|
||||||
}
|
.query(async ({ input }) => {
|
||||||
const result = await docker.swarmInspect();
|
if (IS_CLOUD) {
|
||||||
const docker_version = await docker.version();
|
return {
|
||||||
|
command: "",
|
||||||
|
version: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const docker = await getRemoteDocker(input.serverId);
|
||||||
|
const result = await docker.swarmInspect();
|
||||||
|
const docker_version = await docker.version();
|
||||||
|
|
||||||
return {
|
|
||||||
command: `docker swarm join --token ${
|
|
||||||
result.JoinTokens.Worker
|
|
||||||
} ${await getPublicIpWithFallback()}:2377`,
|
|
||||||
version: docker_version.Version,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
addManager: protectedProcedure.query(async () => {
|
|
||||||
if (IS_CLOUD) {
|
|
||||||
return {
|
return {
|
||||||
command: "",
|
command: `docker swarm join --token ${
|
||||||
version: "",
|
result.JoinTokens.Worker
|
||||||
|
} ${await getPublicIpWithFallback()}:2377`,
|
||||||
|
version: docker_version.Version,
|
||||||
};
|
};
|
||||||
}
|
}),
|
||||||
const result = await docker.swarmInspect();
|
addManager: protectedProcedure
|
||||||
const docker_version = await docker.version();
|
.input(
|
||||||
return {
|
z.object({
|
||||||
command: `docker swarm join --token ${
|
serverId: z.string().optional(),
|
||||||
result.JoinTokens.Manager
|
}),
|
||||||
} ${await getPublicIpWithFallback()}:2377`,
|
)
|
||||||
version: docker_version.Version,
|
.query(async ({ input }) => {
|
||||||
};
|
if (IS_CLOUD) {
|
||||||
}),
|
return {
|
||||||
|
command: "",
|
||||||
|
version: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const docker = await getRemoteDocker(input.serverId);
|
||||||
|
const result = await docker.swarmInspect();
|
||||||
|
const docker_version = await docker.version();
|
||||||
|
return {
|
||||||
|
command: `docker swarm join --token ${
|
||||||
|
result.JoinTokens.Manager
|
||||||
|
} ${await getPublicIpWithFallback()}:2377`,
|
||||||
|
version: docker_version.Version,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user