feat(cluster): enhance node management UI with loading indicators and improved tab content

- Added loading indicators in AddManager and AddWorker components to enhance user experience during data fetching.
- Updated AddNode component to include overflow handling for tab content.
- Renamed "Show Nodes" to "Show Swarm Nodes" in ShowNodesModal for clarity.
This commit is contained in:
Mauricio Siu
2025-03-18 21:11:50 -06:00
parent 172694be30
commit 891dc840f5
4 changed files with 108 additions and 96 deletions

View File

@@ -56,10 +56,10 @@ export const AddNode = ({ serverId }: Props) => {
<TabsTrigger value="worker">Worker</TabsTrigger> <TabsTrigger value="worker">Worker</TabsTrigger>
<TabsTrigger value="manager">Manager</TabsTrigger> <TabsTrigger value="manager">Manager</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="worker" className="pt-4"> <TabsContent value="worker" className="pt-4 overflow-hidden">
<AddWorker serverId={serverId} /> <AddWorker serverId={serverId} />
</TabsContent> </TabsContent>
<TabsContent value="manager" className="pt-4"> <TabsContent value="manager" className="pt-4 overflow-hidden">
<AddManager serverId={serverId} /> <AddManager serverId={serverId} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>

View File

@@ -6,7 +6,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import { CopyIcon } from "lucide-react"; import { CopyIcon, Loader2 } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
@@ -14,56 +14,63 @@ interface Props {
} }
export const AddManager = ({ serverId }: Props) => { export const AddManager = ({ serverId }: Props) => {
const { data } = api.cluster.addManager.useQuery({ serverId }); const { data, isLoading } = api.cluster.addManager.useQuery({ serverId });
return ( return (
<> <>
<div> <CardContent className="sm:max-w-4xl flex flex-col gap-4 px-0">
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0"> <DialogHeader>
<DialogHeader> <DialogTitle>Add a new manager</DialogTitle>
<DialogTitle>Add a new manager</DialogTitle> <DialogDescription>Add a new manager</DialogDescription>
<DialogDescription>Add a new manager</DialogDescription> </DialogHeader>
</DialogHeader> {isLoading ? (
<div className="flex flex-col gap-2.5 text-sm"> <Loader2 className="w-full animate-spin text-muted-foreground" />
<span>1. Go to your new server and run the following command</span> ) : (
<span className="bg-muted rounded-lg p-2 flex justify-between"> <>
curl https://get.docker.com | sh -s -- --version {data?.version} <div className="flex flex-col gap-2.5 text-sm">
<button <span>
type="button" 1. Go to your new server and run the following command
className="self-center" </span>
onClick={() => { <span className="bg-muted rounded-lg p-2 flex justify-between">
copy( curl https://get.docker.com | sh -s -- --version {data?.version}
`curl https://get.docker.com | sh -s -- --version ${data?.version}`, <button
); type="button"
toast.success("Copied to clipboard"); className="self-center"
}} onClick={() => {
> copy(
<CopyIcon className="h-4 w-4 cursor-pointer" /> `curl https://get.docker.com | sh -s -- --version ${data?.version}`,
</button> );
</span> toast.success("Copied to clipboard");
</div> }}
>
<CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm"> <div className="flex flex-col gap-2.5 text-sm">
<span> <span>
2. Run the following command to add the node(manager) to your 2. Run the following command to add the node(manager) to your
cluster cluster
</span> </span>
<span className="bg-muted rounded-lg p-2 flex">
{data?.command} <span className="bg-muted rounded-lg p-2 flex">
<button {data?.command}
type="button" <button
className="self-start" type="button"
onClick={() => { className="self-start"
copy(data?.command || ""); onClick={() => {
toast.success("Copied to clipboard"); copy(data?.command || "");
}} toast.success("Copied to clipboard");
> }}
<CopyIcon className="h-4 w-4 cursor-pointer" /> >
</button> <CopyIcon className="h-4 w-4 cursor-pointer" />
</span> </button>
</div> </span>
</CardContent> </div>
</div> </>
)}
</CardContent>
</> </>
); );
}; };

View File

@@ -17,7 +17,7 @@ export const ShowNodesModal = ({ serverId }: Props) => {
className="w-full cursor-pointer " className="w-full cursor-pointer "
onSelect={(e) => e.preventDefault()} onSelect={(e) => e.preventDefault()}
> >
Show Nodes Show Swarm Nodes
</DropdownMenuItem> </DropdownMenuItem>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen "> <DialogContent className="sm:max-w-5xl overflow-y-auto max-h-screen ">

View File

@@ -6,7 +6,7 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import { CopyIcon } from "lucide-react"; import { CopyIcon, Loader2 } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
interface Props { interface Props {
@@ -14,54 +14,59 @@ interface Props {
} }
export const AddWorker = ({ serverId }: Props) => { export const AddWorker = ({ serverId }: Props) => {
const { data } = api.cluster.addWorker.useQuery({ serverId }); const { data, isLoading } = api.cluster.addWorker.useQuery({ serverId });
return ( return (
<div> <CardContent className="sm:max-w-4xl flex flex-col gap-4 px-0">
<CardContent className="sm:max-w-4xl max-h-screen overflow-y-auto flex flex-col gap-4 px-0"> <DialogHeader>
<DialogHeader> <DialogTitle>Add a new worker</DialogTitle>
<DialogTitle>Add a new worker</DialogTitle> <DialogDescription>Add a new worker</DialogDescription>
<DialogDescription>Add a new worker</DialogDescription> </DialogHeader>
</DialogHeader> {isLoading ? (
<div className="flex flex-col gap-2.5 text-sm"> <Loader2 className="w-full animate-spin text-muted-foreground" />
<span>1. Go to your new server and run the following command</span> ) : (
<span className="bg-muted rounded-lg p-2 flex justify-between"> <>
curl https://get.docker.com | sh -s -- --version {data?.version} <div className="flex flex-col gap-2.5 text-sm">
<button <span>1. Go to your new server and run the following command</span>
type="button" <span className="bg-muted rounded-lg p-2 flex justify-between">
className="self-center" curl https://get.docker.com | sh -s -- --version {data?.version}
onClick={() => { <button
copy( type="button"
`curl https://get.docker.com | sh -s -- --version ${data?.version}`, className="self-center"
); onClick={() => {
toast.success("Copied to clipboard"); copy(
}} `curl https://get.docker.com | sh -s -- --version ${data?.version}`,
> );
<CopyIcon className="h-4 w-4 cursor-pointer" /> toast.success("Copied to clipboard");
</button> }}
</span> >
</div> <CopyIcon className="h-4 w-4 cursor-pointer" />
</button>
</span>
</div>
<div className="flex flex-col gap-2.5 text-sm"> <div className="flex flex-col gap-2.5 text-sm">
<span> <span>
2. Run the following command to add the node(worker) to your cluster 2. Run the following command to add the node(worker) to your
</span> cluster
</span>
<span className="bg-muted rounded-lg p-2 flex"> <span className="bg-muted rounded-lg p-2 flex">
{data?.command} {data?.command}
<button <button
type="button" type="button"
className="self-start" className="self-start"
onClick={() => { onClick={() => {
copy(data?.command || ""); copy(data?.command || "");
toast.success("Copied to clipboard"); toast.success("Copied to clipboard");
}} }}
> >
<CopyIcon className="h-4 w-4 cursor-pointer" /> <CopyIcon className="h-4 w-4 cursor-pointer" />
</button> </button>
</span> </span>
</div> </div>
</CardContent> </>
</div> )}
</CardContent>
); );
}; };