Action Buttons Layout improvements

This commit is contained in:
headlessdev 2025-04-14 15:35:32 +02:00
parent e631d39b75
commit 75ca05454d
2 changed files with 548 additions and 380 deletions

View File

@ -474,140 +474,142 @@ export default function Dashboard() {
</Button> </Button>
)} )}
</div> </div>
<Button <div className="flex flex-col gap-2">
variant="destructive" <Button
size="icon" variant="destructive"
className="h-[72px] w-10" size="icon"
onClick={() => deleteApplication(app.id)} className="h-9 w-9"
> onClick={() => deleteApplication(app.id)}
<Trash2 className="h-4 w-4" /> >
</Button> <Trash2 className="h-4 w-4" />
<AlertDialog> </Button>
<AlertDialogTrigger asChild> <AlertDialog>
<Button <AlertDialogTrigger asChild>
size="icon" <Button
className="h-[72px] w-10" size="icon"
onClick={() => openEditDialog(app)} className="h-9 w-9"
> onClick={() => openEditDialog(app)}
<Pencil className="h-4 w-4" />
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Edit Application
</AlertDialogTitle>
<AlertDialogDescription>
<div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5">
<Label>Name</Label>
<Input
placeholder="e.g. Portainer"
value={editName}
onChange={(e) =>
setEditName(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>Server</Label>
<Select
value={
editServerId !== null
? String(editServerId)
: undefined
}
onValueChange={(v) =>
setEditServerId(Number(v))
}
required
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select server" />
</SelectTrigger>
<SelectContent>
{servers.map((server) => (
<SelectItem
key={server.id}
value={String(server.id)}
>
{server.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Description{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Textarea
placeholder="Application description"
value={editDescription}
onChange={(e) =>
setEditDescription(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Icon URL{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
placeholder="https://example.com/icon.png"
value={editIcon}
onChange={(e) =>
setEditIcon(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>Public URL</Label>
<Input
placeholder="https://example.com"
value={editPublicURL}
onChange={(e) =>
setEditPublicURL(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Local URL{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
placeholder="http://localhost:3000"
value={editLocalURL}
onChange={(e) =>
setEditLocalURL(e.target.value)
}
/>
</div>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={edit}
disabled={
!editName || !editPublicURL || !editServerId
}
> >
Save Changes <Pencil className="h-4 w-4" />
</AlertDialogAction> </Button>
</AlertDialogFooter> </AlertDialogTrigger>
</AlertDialogContent> <AlertDialogContent>
</AlertDialog> <AlertDialogHeader>
<AlertDialogTitle>
Edit Application
</AlertDialogTitle>
<AlertDialogDescription>
<div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5">
<Label>Name</Label>
<Input
placeholder="e.g. Portainer"
value={editName}
onChange={(e) =>
setEditName(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>Server</Label>
<Select
value={
editServerId !== null
? String(editServerId)
: undefined
}
onValueChange={(v) =>
setEditServerId(Number(v))
}
required
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select server" />
</SelectTrigger>
<SelectContent>
{servers.map((server) => (
<SelectItem
key={server.id}
value={String(server.id)}
>
{server.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Description{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Textarea
placeholder="Application description"
value={editDescription}
onChange={(e) =>
setEditDescription(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Icon URL{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
placeholder="https://example.com/icon.png"
value={editIcon}
onChange={(e) =>
setEditIcon(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>Public URL</Label>
<Input
placeholder="https://example.com"
value={editPublicURL}
onChange={(e) =>
setEditPublicURL(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label>
Local URL{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
placeholder="http://localhost:3000"
value={editLocalURL}
onChange={(e) =>
setEditLocalURL(e.target.value)
}
/>
</div>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={edit}
disabled={
!editName || !editPublicURL || !editServerId
}
>
Save Changes
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -16,7 +16,20 @@ import {
SidebarTrigger, SidebarTrigger,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Plus, Link, MonitorCog, FileDigit, Trash2, LayoutGrid, List, Pencil, Cpu, Microchip, MemoryStick, HardDrive } from "lucide-react"; import {
Plus,
Link,
MonitorCog,
FileDigit,
Trash2,
LayoutGrid,
List,
Pencil,
Cpu,
Microchip,
MemoryStick,
HardDrive,
} from "lucide-react";
import { import {
Card, Card,
CardContent, CardContent,
@ -62,7 +75,7 @@ import {
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import axios from 'axios'; import axios from "axios";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
interface Server { interface Server {
@ -112,72 +125,75 @@ export default function Dashboard() {
const [isSearching, setIsSearching] = useState<boolean>(false); const [isSearching, setIsSearching] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
const savedLayout = Cookies.get('layoutPreference-servers'); const savedLayout = Cookies.get("layoutPreference-servers");
setIsGridLayout(savedLayout === 'grid'); setIsGridLayout(savedLayout === "grid");
}, []); }, []);
const toggleLayout = () => { const toggleLayout = () => {
const newLayout = !isGridLayout; const newLayout = !isGridLayout;
setIsGridLayout(newLayout); setIsGridLayout(newLayout);
Cookies.set('layoutPreference-servers', newLayout ? 'grid' : 'standard', { Cookies.set("layoutPreference-servers", newLayout ? "grid" : "standard", {
expires: 365, expires: 365,
path: '/', path: "/",
sameSite: 'strict' sameSite: "strict",
}); });
}; };
const add = async () => { const add = async () => {
try { try {
await axios.post('/api/servers/add', { await axios.post("/api/servers/add", {
name, name,
os, os,
ip, ip,
url, url,
cpu, cpu,
gpu, gpu,
ram, ram,
disk disk,
}); });
getServers(); getServers();
} catch (error: any) { } catch (error: any) {
console.log(error.response.data); console.log(error.response.data);
} }
} };
const getServers = async () => { const getServers = async () => {
try { try {
setLoading(true); setLoading(true);
const response = await axios.post<GetServersResponse>('/api/servers/get', { const response = await axios.post<GetServersResponse>(
page: currentPage "/api/servers/get",
}); {
page: currentPage,
}
);
setServers(response.data.servers); setServers(response.data.servers);
setMaxPage(response.data.maxPage); setMaxPage(response.data.maxPage);
setLoading(false); setLoading(false);
} catch (error: any) { } catch (error: any) {
console.log(error.response); console.log(error.response);
} }
} };
useEffect(() => { useEffect(() => {
getServers(); getServers();
}, [currentPage]); }, [currentPage]);
const handlePrevious = () => { const handlePrevious = () => {
setCurrentPage(prev => Math.max(1, prev - 1)); setCurrentPage((prev) => Math.max(1, prev - 1));
} };
const handleNext = () => { const handleNext = () => {
setCurrentPage(prev => Math.min(maxPage, prev + 1)); setCurrentPage((prev) => Math.min(maxPage, prev + 1));
} };
const deleteApplication = async (id: number) => { const deleteApplication = async (id: number) => {
try { try {
await axios.post('/api/servers/delete', { id }); await axios.post("/api/servers/delete", { id });
getServers(); getServers();
} catch (error: any) { } catch (error: any) {
console.log(error.response.data); console.log(error.response.data);
} }
} };
const openEditDialog = (server: Server) => { const openEditDialog = (server: Server) => {
setEditId(server.id); setEditId(server.id);
@ -193,9 +209,9 @@ export default function Dashboard() {
const edit = async () => { const edit = async () => {
if (!editId) return; if (!editId) return;
try { try {
await axios.put('/api/servers/edit', { await axios.put("/api/servers/edit", {
id: editId, id: editId,
name: editName, name: editName,
os: editOs, os: editOs,
@ -204,14 +220,14 @@ export default function Dashboard() {
cpu: editCpu, cpu: editCpu,
gpu: editGpu, gpu: editGpu,
ram: editRam, ram: editRam,
disk: editDisk disk: editDisk,
}); });
getServers(); getServers();
setEditId(null); setEditId(null);
} catch (error: any) { } catch (error: any) {
console.log(error.response.data); console.log(error.response.data);
} }
} };
const searchServers = async () => { const searchServers = async () => {
try { try {
@ -272,8 +288,8 @@ export default function Dashboard() {
<TooltipProvider> <TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger asChild> <TooltipTrigger asChild>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
onClick={toggleLayout} onClick={toggleLayout}
> >
@ -285,9 +301,9 @@ export default function Dashboard() {
</Button> </Button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{isGridLayout ? {isGridLayout
"Switch to list view" : ? "Switch to list view"
"Switch to grid view"} : "Switch to grid view"}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@ -301,70 +317,144 @@ export default function Dashboard() {
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Add an server</AlertDialogTitle> <AlertDialogTitle>Add an server</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
<Tabs defaultValue="general" className="w-full"> <Tabs defaultValue="general" className="w-full">
<TabsList className="w-full"> <TabsList className="w-full">
<TabsTrigger value="general">General</TabsTrigger> <TabsTrigger value="general">General</TabsTrigger>
<TabsTrigger value="hardware">Hardware</TabsTrigger> <TabsTrigger value="hardware">Hardware</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="general"> <TabsContent value="general">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">Name</Label> <Label htmlFor="name">Name</Label>
<Input id="name" type="text" placeholder="e.g. Server1" onChange={(e) => setName(e.target.value)}/> <Input
</div> id="name"
<div className="grid w-full items-center gap-1.5"> type="text"
<Label htmlFor="description">Operating System <span className="text-stone-600">(optional)</span></Label> placeholder="e.g. Server1"
<Select onValueChange={(value) => setOs(value)}> onChange={(e) => setName(e.target.value)}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="description">
Operating System{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Select onValueChange={(value) => setOs(value)}>
<SelectTrigger className="w-full"> <SelectTrigger className="w-full">
<SelectValue placeholder="Select OS" /> <SelectValue placeholder="Select OS" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="Windows">Windows</SelectItem> <SelectItem value="Windows">
<SelectItem value="Linux">Linux</SelectItem> Windows
<SelectItem value="MacOS">MacOS</SelectItem> </SelectItem>
<SelectItem value="Linux">Linux</SelectItem>
<SelectItem value="MacOS">MacOS</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="icon">IP Adress <span className="text-stone-600">(optional)</span></Label> <Label htmlFor="icon">
<Input id="icon" type="text" placeholder="e.g. 192.168.100.2" onChange={(e) => setIp(e.target.value)}/> IP Adress{" "}
</div> <span className="text-stone-600">
<div className="grid w-full items-center gap-1.5"> (optional)
<TooltipProvider> </span>
</Label>
<Input
id="icon"
type="text"
placeholder="e.g. 192.168.100.2"
onChange={(e) => setIp(e.target.value)}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<TooltipProvider>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>
<Label htmlFor="publicURL">Management URL <span className="text-stone-600">(optional)</span></Label> <Label htmlFor="publicURL">
</TooltipTrigger> Management URL{" "}
<TooltipContent> <span className="text-stone-600">
Link to a web interface (e.g. Proxmox or Portainer) with which the server can be managed (optional)
</TooltipContent> </span>
</Label>
</TooltipTrigger>
<TooltipContent>
Link to a web interface (e.g. Proxmox or
Portainer) with which the server can be
managed
</TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
<Input id="publicURL" type="text" placeholder="e.g. https://proxmox.server1.com" onChange={(e) => setUrl(e.target.value)}/> <Input
id="publicURL"
type="text"
placeholder="e.g. https://proxmox.server1.com"
onChange={(e) => setUrl(e.target.value)}
/>
</div>
</div> </div>
</div> </TabsContent>
</TabsContent> <TabsContent value="hardware">
<TabsContent value="hardware"> <div className="space-y-4 pt-4">
<div className="space-y-4 pt-4"> <div className="grid w-full items-center gap-1.5">
<div className="grid w-full items-center gap-1.5"> <Label htmlFor="name">
<Label htmlFor="name">CPU <span className="text-stone-600">(optional)</span></Label> CPU{" "}
<Input id="name" type="text" placeholder="e.g. AMD Ryzen™ 7 7800X3D" onChange={(e) => setCpu(e.target.value)}/> <span className="text-stone-600">
(optional)
</span>
</Label>
<Input
id="name"
type="text"
placeholder="e.g. AMD Ryzen™ 7 7800X3D"
onChange={(e) => setCpu(e.target.value)}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">
GPU{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
id="name"
type="text"
placeholder="e.g. AMD Radeon™ Graphics"
onChange={(e) => setGpu(e.target.value)}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">
RAM{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
id="name"
type="text"
placeholder="e.g. 64GB DDR5"
onChange={(e) => setRam(e.target.value)}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">
Disk{" "}
<span className="text-stone-600">
(optional)
</span>
</Label>
<Input
id="name"
type="text"
placeholder="e.g. 2TB SSD"
onChange={(e) => setDisk(e.target.value)}
/>
</div>
</div> </div>
<div className="grid w-full items-center gap-1.5"> </TabsContent>
<Label htmlFor="name">GPU <span className="text-stone-600">(optional)</span></Label> </Tabs>
<Input id="name" type="text" placeholder="e.g. AMD Radeon™ Graphics" onChange={(e) => setGpu(e.target.value)}/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">RAM <span className="text-stone-600">(optional)</span></Label>
<Input id="name" type="text" placeholder="e.g. 64GB DDR5" onChange={(e) => setRam(e.target.value)}/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="name">Disk <span className="text-stone-600">(optional)</span></Label>
<Input id="name" type="text" placeholder="e.g. 2TB SSD" onChange={(e) => setDisk(e.target.value)}/>
</div>
</div>
</TabsContent>
</Tabs>
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
@ -384,30 +474,48 @@ export default function Dashboard() {
/> />
</div> </div>
<br /> <br />
{!loading ? {!loading ? (
<div className={isGridLayout ? <div
"grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 gap-4" : className={
"space-y-4"}> isGridLayout
? "grid grid-cols-1 md:grid-cols-1 lg:grid-cols-2 gap-4"
: "space-y-4"
}
>
{servers.map((server) => ( {servers.map((server) => (
<Card <Card
key={server.id} key={server.id}
className={isGridLayout ? className={
"h-full flex flex-col justify-between" : isGridLayout
"w-full mb-4"} ? "h-full flex flex-col justify-between"
: "w-full mb-4"
}
> >
<CardHeader> <CardHeader>
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-between w-full">
<div className="flex items-center"> <div className="flex items-center">
<div className="ml-4"> <div className="ml-4">
<CardTitle className="text-2xl font-bold">{server.name}</CardTitle> <CardTitle className="text-2xl font-bold">
<CardDescription className={`text-sm mt-1 grid gap-y-1 ${isGridLayout ? "grid-cols-1" : "grid-cols-2 gap-x-4"}`}> {server.name}
</CardTitle>
<CardDescription
className={`text-sm mt-1 grid gap-y-1 ${
isGridLayout
? "grid-cols-1"
: "grid-cols-2 gap-x-4"
}`}
>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MonitorCog className="h-4 w-4 text-muted-foreground" /> <MonitorCog className="h-4 w-4 text-muted-foreground" />
<span><b>OS:</b> {server.os || '-'}</span> <span>
<b>OS:</b> {server.os || "-"}
</span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<FileDigit className="h-4 w-4 text-muted-foreground" /> <FileDigit className="h-4 w-4 text-muted-foreground" />
<span><b>IP:</b> {server.ip || 'Nicht angegeben'}</span> <span>
<b>IP:</b> {server.ip || "Nicht angegeben"}
</span>
</div> </div>
<div className="col-span-full pt-2 pb-2"> <div className="col-span-full pt-2 pb-2">
@ -416,150 +524,197 @@ export default function Dashboard() {
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Cpu className="h-4 w-4 text-muted-foreground" /> <Cpu className="h-4 w-4 text-muted-foreground" />
<span><b>CPU:</b> {server.cpu || '-'}</span> <span>
<b>CPU:</b> {server.cpu || "-"}
</span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<Microchip className="h-4 w-4 text-muted-foreground" /> <Microchip className="h-4 w-4 text-muted-foreground" />
<span><b>GPU:</b> {server.gpu || '-'}</span> <span>
<b>GPU:</b> {server.gpu || "-"}
</span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<MemoryStick className="h-4 w-4 text-muted-foreground" /> <MemoryStick className="h-4 w-4 text-muted-foreground" />
<span><b>RAM:</b> {server.ram || '-'}</span> <span>
<b>RAM:</b> {server.ram || "-"}
</span>
</div> </div>
<div className="flex items-center gap-2 text-foreground/80"> <div className="flex items-center gap-2 text-foreground/80">
<HardDrive className="h-4 w-4 text-muted-foreground" /> <HardDrive className="h-4 w-4 text-muted-foreground" />
<span><b>Disk:</b> {server.disk || '-'}</span> <span>
<b>Disk:</b> {server.disk || "-"}
</span>
</div> </div>
</CardDescription> </CardDescription>
</div> </div>
</div> </div>
<div className="flex flex-col items-end justify-start space-y-2 w-[405px]"> <div className="flex flex-col items-end justify-start space-y-2 w-[405px]">
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
<div className="flex flex-col space-y-2 flex-grow"> <div className="flex flex-col space-y-2 flex-grow">
{server.url && ( {server.url && (
<Button <Button
variant="outline" variant="outline"
className="gap-2 w-full" className="gap-2 w-full"
onClick={() => window.open(server.url, "_blank")} onClick={() =>
> window.open(server.url, "_blank")
}
>
<Link className="h-4 w-4" /> <Link className="h-4 w-4" />
Open Management URL Open Management URL
</Button>
)}
</div>
<Button
variant="destructive"
size="icon"
className="w-10"
onClick={() => deleteApplication(server.id)}
>
<Trash2 className="h-4 w-4" />
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
size="icon"
className="w-10"
onClick={() => openEditDialog(server)}
>
<Pencil className="h-4 w-4" />
</Button> </Button>
</AlertDialogTrigger> )}
<AlertDialogContent> </div>
<AlertDialogHeader> <div className="flex flex-col gap-2">
<AlertDialogTitle>Edit Server</AlertDialogTitle> <Button
<AlertDialogDescription> variant="destructive"
<Tabs defaultValue="general" className="w-full"> size="icon"
<TabsList className="w-full"> className="h-9 w-9"
<TabsTrigger value="general">General</TabsTrigger> onClick={() => deleteApplication(server.id)}
<TabsTrigger value="hardware">Hardware</TabsTrigger> >
</TabsList> <Trash2 className="h-4 w-4" />
<TabsContent value="general"> </Button>
<div className="space-y-4 pt-4"> <AlertDialog>
<div className="grid w-full items-center gap-1.5"> <AlertDialogTrigger asChild>
<Label htmlFor="editOs">Operating System</Label> <Button
<Select size="icon"
value={editOs} className="h-9 w-9"
onValueChange={setEditOs} onClick={() => openEditDialog(server)}
> >
<SelectTrigger className="w-full"> <Pencil className="h-4 w-4" />
<SelectValue placeholder="Select OS" /> </Button>
</SelectTrigger> </AlertDialogTrigger>
<SelectContent> <AlertDialogContent>
<SelectItem value="Windows">Windows</SelectItem> <AlertDialogHeader>
<SelectItem value="Linux">Linux</SelectItem> <AlertDialogTitle>
<SelectItem value="MacOS">MacOS</SelectItem> Edit Server
</SelectContent> </AlertDialogTitle>
</Select> <AlertDialogDescription>
</div> <Tabs
<div className="grid w-full items-center gap-1.5"> defaultValue="general"
<Label htmlFor="editIp">IP Adress</Label> className="w-full"
<Input >
id="editIp" <TabsList className="w-full">
type="text" <TabsTrigger value="general">
placeholder="e.g. 192.168.100.2" General
value={editIp} </TabsTrigger>
onChange={(e) => setEditIp(e.target.value)} <TabsTrigger value="hardware">
/> Hardware
</div> </TabsTrigger>
<div className="grid w-full items-center gap-1.5"> </TabsList>
<Label htmlFor="editUrl">Management URL</Label> <TabsContent value="general">
<Input <div className="space-y-4 pt-4">
id="editUrl" <div className="grid w-full items-center gap-1.5">
type="text" <Label htmlFor="editOs">
placeholder="e.g. https://proxmox.server1.com" Operating System
value={editUrl} </Label>
onChange={(e) => setEditUrl(e.target.value)} <Select
/> value={editOs}
</div> onValueChange={setEditOs}
</div> >
</TabsContent> <SelectTrigger className="w-full">
<SelectValue placeholder="Select OS" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Windows">
Windows
</SelectItem>
<SelectItem value="Linux">
Linux
</SelectItem>
<SelectItem value="MacOS">
MacOS
</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="editIp">
IP Adress
</Label>
<Input
id="editIp"
type="text"
placeholder="e.g. 192.168.100.2"
value={editIp}
onChange={(e) =>
setEditIp(e.target.value)
}
/>
</div>
<div className="grid w-full items-center gap-1.5">
<Label htmlFor="editUrl">
Management URL
</Label>
<Input
id="editUrl"
type="text"
placeholder="e.g. https://proxmox.server1.com"
value={editUrl}
onChange={(e) =>
setEditUrl(e.target.value)
}
/>
</div>
</div>
</TabsContent>
<TabsContent value="hardware"> <TabsContent value="hardware">
<div className="space-y-4 pt-4"> <div className="space-y-4 pt-4">
<div className="grid w-full items-center gap-1.5"> <div className="grid w-full items-center gap-1.5">
<Label htmlFor="editCpu">CPU</Label> <Label htmlFor="editCpu">CPU</Label>
<Input <Input
id="editCpu" id="editCpu"
value={editCpu} value={editCpu}
onChange={(e) => setEditCpu(e.target.value)} onChange={(e) =>
/> setEditCpu(e.target.value)
</div> }
<div className="grid w-full items-center gap-1.5"> />
<Label htmlFor="editGpu">GPU</Label> </div>
<Input <div className="grid w-full items-center gap-1.5">
id="editGpu" <Label htmlFor="editGpu">GPU</Label>
value={editGpu} <Input
onChange={(e) => setEditGpu(e.target.value)} id="editGpu"
/> value={editGpu}
</div> onChange={(e) =>
<div className="grid w-full items-center gap-1.5"> setEditGpu(e.target.value)
<Label htmlFor="editRam">RAM</Label> }
<Input />
id="editRam" </div>
value={editRam} <div className="grid w-full items-center gap-1.5">
onChange={(e) => setEditRam(e.target.value)} <Label htmlFor="editRam">RAM</Label>
/> <Input
</div> id="editRam"
<div className="grid w-full items-center gap-1.5"> value={editRam}
<Label htmlFor="editDisk">Disk</Label> onChange={(e) =>
<Input setEditRam(e.target.value)
id="editDisk" }
value={editDisk} />
onChange={(e) => setEditDisk(e.target.value)} </div>
/> <div className="grid w-full items-center gap-1.5">
</div> <Label htmlFor="editDisk">
</div> Disk
</TabsContent> </Label>
</Tabs> <Input
</AlertDialogDescription> id="editDisk"
</AlertDialogHeader> value={editDisk}
<AlertDialogFooter> onChange={(e) =>
<AlertDialogCancel>Cancel</AlertDialogCancel> setEditDisk(e.target.value)
<Button onClick={edit}>Save</Button> }
</AlertDialogFooter> />
</AlertDialogContent> </div>
</AlertDialog> </div>
</TabsContent>
</Tabs>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button onClick={edit}>Save</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -567,41 +722,52 @@ export default function Dashboard() {
</Card> </Card>
))} ))}
</div> </div>
: ) : (
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<div className='inline-block' role='status' aria-label='loading'> <div className="inline-block" role="status" aria-label="loading">
<svg className='w-6 h-6 stroke-white animate-spin ' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'> <svg
<g clip-path='url(#clip0_9023_61563)'> className="w-6 h-6 stroke-white animate-spin "
<path d='M14.6437 2.05426C11.9803 1.2966 9.01686 1.64245 6.50315 3.25548C1.85499 6.23817 0.504864 12.4242 3.48756 17.0724C6.47025 21.7205 12.6563 23.0706 17.3044 20.088C20.4971 18.0393 22.1338 14.4793 21.8792 10.9444' stroke='stroke-current' stroke-width='1.4' stroke-linecap='round' className='my-path'></path> viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_9023_61563)">
<path
d="M14.6437 2.05426C11.9803 1.2966 9.01686 1.64245 6.50315 3.25548C1.85499 6.23817 0.504864 12.4242 3.48756 17.0724C6.47025 21.7205 12.6563 23.0706 17.3044 20.088C20.4971 18.0393 22.1338 14.4793 21.8792 10.9444"
stroke="stroke-current"
stroke-width="1.4"
stroke-linecap="round"
className="my-path"
></path>
</g> </g>
<defs> <defs>
<clipPath id='clip0_9023_61563'> <clipPath id="clip0_9023_61563">
<rect width='24' height='24' fill='white'></rect> <rect width="24" height="24" fill="white"></rect>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
<span className='sr-only'>Loading...</span> <span className="sr-only">Loading...</span>
</div>
</div> </div>
} </div>
)}
<div className="pt-4"> <div className="pt-4">
<Pagination> <Pagination>
<PaginationContent> <PaginationContent>
<PaginationItem> <PaginationItem>
<PaginationPrevious <PaginationPrevious
href="#" href="#"
onClick={handlePrevious} onClick={handlePrevious}
isActive={currentPage > 1} isActive={currentPage > 1}
/> />
</PaginationItem> </PaginationItem>
<PaginationItem> <PaginationItem>
<PaginationLink isActive>{currentPage}</PaginationLink> <PaginationLink isActive>{currentPage}</PaginationLink>
</PaginationItem> </PaginationItem>
<PaginationItem> <PaginationItem>
<PaginationNext <PaginationNext
href="#" href="#"
onClick={handleNext} onClick={handleNext}
isActive={currentPage < maxPage} isActive={currentPage < maxPage}
/> />
@ -612,5 +778,5 @@ export default function Dashboard() {
</div> </div>
</SidebarInset> </SidebarInset>
</SidebarProvider> </SidebarProvider>
) );
} }