mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
refactor: improve interface for cluster
This commit is contained in:
parent
eda33e095e
commit
667067811c
67
components/dashboard/settings/cluster/nodes/add-node.tsx
Normal file
67
components/dashboard/settings/cluster/nodes/add-node.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ExternalLink, PlusIcon } from "lucide-react";
|
||||
import { AddWorker } from "./workers/add-worker";
|
||||
import { AddManager } from "./manager/add-manager";
|
||||
import Link from "next/link";
|
||||
|
||||
export const AddNode = () => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full cursor-pointer space-x-3">
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Node
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add Node</DialogTitle>
|
||||
<DialogDescription className="flex flex-col gap-2">
|
||||
Follow the steps to add a new node to your cluster, before you start
|
||||
working with this feature, you need to understand how docker swarm
|
||||
works.{" "}
|
||||
<Link
|
||||
href="https://docs.docker.com/engine/swarm/"
|
||||
target="_blank"
|
||||
className="text-primary flex flex-row gap-2 items-center"
|
||||
>
|
||||
Docker Swarm
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Link>
|
||||
<Link
|
||||
href="https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/"
|
||||
target="_blank"
|
||||
className="text-primary flex flex-row gap-2 items-center"
|
||||
>
|
||||
Architecture
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</Link>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Tabs defaultValue="worker">
|
||||
<TabsList>
|
||||
<TabsTrigger value="worker">Worker</TabsTrigger>
|
||||
<TabsTrigger value="manager">Manager</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="worker" className="pt-4">
|
||||
<AddWorker />
|
||||
</TabsContent>
|
||||
<TabsContent value="manager" className="pt-4">
|
||||
<AddManager />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -1,49 +1,63 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { api } from "@/utils/api";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import { CopyIcon } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const AddManager = () => {
|
||||
const { data } = api.cluster.addManager.useQuery();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer flex flex-row gap-2 items-center"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Add Manager
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-4xl max-h-screen overflow-y-auto ">
|
||||
<div>
|
||||
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a new manager</DialogTitle>
|
||||
<DialogDescription>Add a new manager</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 text-sm">
|
||||
<div className="flex flex-col gap-2.5 text-sm">
|
||||
<span>1. Go to your new server and run the following command</span>
|
||||
<span className="bg-muted rounded-lg p-2">
|
||||
<span className="bg-muted rounded-lg p-2 flex justify-between">
|
||||
curl https://get.docker.com | sh -s -- --version 24.0
|
||||
<button
|
||||
type="button"
|
||||
className="self-center"
|
||||
onClick={() => {
|
||||
copy("curl https://get.docker.com | sh -s -- --version 24.0");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 cursor-pointer" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 text-sm">
|
||||
<div className="flex flex-col gap-2.5 text-sm">
|
||||
<span>
|
||||
2. Run the following command to add the node(server) to your
|
||||
2. Run the following command to add the node(manager) to your
|
||||
cluster
|
||||
</span>
|
||||
<span className="bg-muted rounded-lg p-2 ">{data}</span>
|
||||
<span className="bg-muted rounded-lg p-2 flex">
|
||||
{data}
|
||||
<button
|
||||
type="button"
|
||||
className="self-start"
|
||||
onClick={() => {
|
||||
copy(data || "");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 cursor-pointer" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { api } from "@/utils/api";
|
||||
import { AddWorker } from "./workers/add-worker";
|
||||
import { DateTooltip } from "@/components/shared/date-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { DeleteWorker } from "./workers/delete-worker";
|
||||
@ -20,7 +19,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { MoreHorizontal, PlusIcon } from "lucide-react";
|
||||
import { HelpCircle, LockIcon, MoreHorizontal } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -29,10 +28,19 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ShowNodeData } from "./show-node-data";
|
||||
import { AddManager } from "./manager/add-manager";
|
||||
import { AddNode } from "./add-node";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
export const ShowNodes = () => {
|
||||
const { data, isLoading } = api.cluster.getNodes.useQuery();
|
||||
const { data: registry } = api.registry.all.useQuery();
|
||||
|
||||
const haveAtLeastOneRegistry = !!(registry && registry?.length > 0);
|
||||
return (
|
||||
<Card className="bg-transparent h-full">
|
||||
<CardHeader className="flex flex-row gap-2 justify-between w-full items-center flex-wrap">
|
||||
@ -40,91 +48,114 @@ export const ShowNodes = () => {
|
||||
<CardTitle className="text-xl">Cluster</CardTitle>
|
||||
<CardDescription>Add nodes to your cluster</CardDescription>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost">
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<AddWorker />
|
||||
<AddManager />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
{haveAtLeastOneRegistry && (
|
||||
<div className="flex flex-row gap-2">
|
||||
<AddNode />
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-4">
|
||||
<div className="grid md:grid-cols-1 gap-4">
|
||||
{isLoading && <div>Loading...</div>}
|
||||
<Table>
|
||||
<TableCaption>A list of your managers / workers.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Hostname</TableHead>
|
||||
<TableHead className="text-right">Status</TableHead>
|
||||
<TableHead className="text-right">Role</TableHead>
|
||||
<TableHead className="text-right">Availability</TableHead>
|
||||
<TableHead className="text-right">Engine Version</TableHead>
|
||||
<TableHead className="text-right">Created</TableHead>
|
||||
{haveAtLeastOneRegistry ? (
|
||||
<div className="grid md:grid-cols-1 gap-4">
|
||||
{isLoading && <div>Loading...</div>}
|
||||
<Table>
|
||||
<TableCaption>A list of your managers / workers.</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[100px]">Hostname</TableHead>
|
||||
<TableHead className="text-right">Status</TableHead>
|
||||
<TableHead className="text-right">Role</TableHead>
|
||||
<TableHead className="text-right">Availability</TableHead>
|
||||
<TableHead className="text-right">Engine Version</TableHead>
|
||||
<TableHead className="text-right">Created</TableHead>
|
||||
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map((node) => {
|
||||
const isManager = node.Spec.Role === "manager";
|
||||
return (
|
||||
<TableRow key={node.ID}>
|
||||
<TableCell className="w-[100px]">
|
||||
{node.Description.Hostname}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{node.Status.State}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Badge variant={isManager ? "default" : "secondary"}>
|
||||
{node?.Spec?.Role}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{node.Spec.Availability}
|
||||
</TableCell>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{data?.map((node) => {
|
||||
const isManager = node.Spec.Role === "manager";
|
||||
return (
|
||||
<TableRow key={node.ID}>
|
||||
<TableCell className="w-[100px]">
|
||||
{node.Description.Hostname}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{node.Status.State}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Badge variant={isManager ? "default" : "secondary"}>
|
||||
{node?.Spec?.Role}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{node.Spec.Availability}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-right">
|
||||
{node?.Description.Engine.EngineVersion}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{node?.Description.Engine.EngineVersion}
|
||||
</TableCell>
|
||||
|
||||
<TableCell className="text-right">
|
||||
<DateTooltip date={node.CreatedAt} className="text-sm">
|
||||
Created{" "}
|
||||
</DateTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-right flex justify-end">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<ShowNodeData data={node} />
|
||||
{!node?.ManagerStatus?.Leader && (
|
||||
<DeleteWorker nodeId={node.ID} />
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<TableCell className="text-right">
|
||||
<DateTooltip date={node.CreatedAt} className="text-sm">
|
||||
Created{" "}
|
||||
</DateTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="text-right flex justify-end">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<ShowNodeData data={node} />
|
||||
{!node?.ManagerStatus?.Leader && (
|
||||
<DeleteWorker nodeId={node.ID} />
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<LockIcon className="size-8 text-muted-foreground" />
|
||||
<div className="flex flex-row gap-2">
|
||||
<span className="text-base text-muted-foreground ">
|
||||
To add nodes to your cluster, you need to configure at least one
|
||||
registry.
|
||||
</span>
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="self-center">
|
||||
<HelpCircle className="size-5 text-muted-foreground " />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Nodes need a registry to pull images from.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
|
||||
<ul className="list-disc list-inside text-sm text-muted-foreground border p-4 rounded-lg flex flex-col gap-1.5 mt-2.5">
|
||||
<li>
|
||||
<strong>Docker Registry:</strong> Use custom registries like
|
||||
Docker Hub, DigitalOcean Registry, etc.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Self-Hosted Docker Registry:</strong> Automatically set
|
||||
up a local registry to store all images.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
@ -1,47 +1,61 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { api } from "@/utils/api";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { CopyIcon } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const AddWorker = () => {
|
||||
const { data } = api.cluster.addWorker.useQuery();
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer flex flex-row gap-2 items-center"
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Add Worker
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-4xl max-h-screen overflow-y-auto ">
|
||||
<div>
|
||||
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add a new worker</DialogTitle>
|
||||
<DialogDescription>Add a new worker</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 text-sm">
|
||||
<div className="flex flex-col gap-2.5 text-sm">
|
||||
<span>1. Go to your new server and run the following command</span>
|
||||
<span className="bg-muted rounded-lg p-2">
|
||||
<span className="bg-muted rounded-lg p-2 flex justify-between">
|
||||
curl https://get.docker.com | sh -s -- --version 24.0
|
||||
<button
|
||||
type="button"
|
||||
className="self-center"
|
||||
onClick={() => {
|
||||
copy("curl https://get.docker.com | sh -s -- --version 24.0");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 cursor-pointer" />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 text-sm">
|
||||
<div className="flex flex-col gap-2.5 text-sm">
|
||||
<span>
|
||||
2. Run the following command to add the node(server) to your cluster
|
||||
2. Run the following command to add the node(worker) to your cluster
|
||||
</span>
|
||||
|
||||
<span className="bg-muted rounded-lg p-2 flex">
|
||||
{data}
|
||||
<button
|
||||
type="button"
|
||||
className="self-start"
|
||||
onClick={() => {
|
||||
copy(data || "");
|
||||
toast.success("Copied to clipboard");
|
||||
}}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 cursor-pointer" />
|
||||
</button>
|
||||
</span>
|
||||
<span className="bg-muted rounded-lg p-2 ">{data}</span>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardContent>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ export const ShowRegistry = () => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Card className="bg-transparent h-full">
|
||||
<CardHeader className="flex flex-row gap-2 justify-between w-full items-center">
|
||||
<CardHeader className="flex flex-row gap-2 flex-wrap justify-between w-full items-center">
|
||||
<div className="flex flex-col gap-2">
|
||||
<CardTitle className="text-xl">Registry</CardTitle>
|
||||
<CardDescription>Add registry to your application.</CardDescription>
|
||||
|
Loading…
Reference in New Issue
Block a user