mirror of
https://github.com/crocofied/CoreControl.git
synced 2025-12-18 16:07:10 +00:00
Action Buttons Layout improvements
This commit is contained in:
parent
e631d39b75
commit
75ca05454d
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user