mirror of
https://github.com/crocofied/CoreControl.git
synced 2025-12-17 15:36:50 +00:00
Basic VM functionality
This commit is contained in:
parent
c266296c4f
commit
406091fdcb
@ -28,6 +28,8 @@ export async function PUT(request: NextRequest) {
|
|||||||
const updatedServer = await prisma.server.update({
|
const updatedServer = await prisma.server.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
|
host,
|
||||||
|
hostServer,
|
||||||
name,
|
name,
|
||||||
os,
|
os,
|
||||||
ip,
|
ip,
|
||||||
|
|||||||
@ -6,24 +6,39 @@ interface GetRequest {
|
|||||||
ITEMS_PER_PAGE?: number;
|
ITEMS_PER_PAGE?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body: GetRequest = await request.json();
|
const body: GetRequest = await request.json();
|
||||||
const page = Math.max(1, body.page || 1);
|
const page = Math.max(1, body.page || 1);
|
||||||
const ITEMS_PER_PAGE = body.ITEMS_PER_PAGE || 4;
|
const ITEMS_PER_PAGE = body.ITEMS_PER_PAGE || 4;
|
||||||
|
|
||||||
const servers = await prisma.server.findMany({
|
// Host-Server mit Paginierung holen
|
||||||
|
const hosts = await prisma.server.findMany({
|
||||||
|
where: { hostServer: null },
|
||||||
skip: (page - 1) * ITEMS_PER_PAGE,
|
skip: (page - 1) * ITEMS_PER_PAGE,
|
||||||
take: ITEMS_PER_PAGE,
|
take: ITEMS_PER_PAGE,
|
||||||
orderBy: { name: 'asc' }
|
orderBy: { name: 'asc' }
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalCount = await prisma.server.count();
|
// VMs für alle Hosts sammeln
|
||||||
const maxPage = Math.ceil(totalCount / ITEMS_PER_PAGE);
|
const hostsWithVms = await Promise.all(
|
||||||
|
hosts.map(async (host) => ({
|
||||||
|
...host,
|
||||||
|
hostedVMs: await prisma.server.findMany({
|
||||||
|
where: { hostServer: host.id },
|
||||||
|
orderBy: { name: 'asc' }
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalHosts = await prisma.server.count({
|
||||||
|
where: { hostServer: null }
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxPage = Math.ceil(totalHosts / ITEMS_PER_PAGE);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
servers,
|
servers: hostsWithVms,
|
||||||
maxPage
|
maxPage
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
13
app/api/servers/hosts/route.ts
Normal file
13
app/api/servers/hosts/route.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const servers = await prisma.server.findMany({
|
||||||
|
where: { host: true },
|
||||||
|
});
|
||||||
|
return NextResponse.json({ servers });
|
||||||
|
} catch (error: any) {
|
||||||
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ import {
|
|||||||
Microchip,
|
Microchip,
|
||||||
MemoryStick,
|
MemoryStick,
|
||||||
HardDrive,
|
HardDrive,
|
||||||
|
Server,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -77,10 +78,15 @@ 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";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
interface Server {
|
interface Server {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
host: boolean;
|
||||||
|
hostServer: number | null;
|
||||||
os?: string;
|
os?: string;
|
||||||
ip?: string;
|
ip?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
@ -88,6 +94,7 @@ interface Server {
|
|||||||
gpu?: string;
|
gpu?: string;
|
||||||
ram?: string;
|
ram?: string;
|
||||||
disk?: string;
|
disk?: string;
|
||||||
|
hostedVMs: Server[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetServersResponse {
|
interface GetServersResponse {
|
||||||
@ -96,6 +103,8 @@ interface GetServersResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
|
const [host, setHost] = useState<boolean>(false);
|
||||||
|
const [hostServer, setHostServer] = useState<number>(0);
|
||||||
const [name, setName] = useState<string>("");
|
const [name, setName] = useState<string>("");
|
||||||
const [os, setOs] = useState<string>("");
|
const [os, setOs] = useState<string>("");
|
||||||
const [ip, setIp] = useState<string>("");
|
const [ip, setIp] = useState<string>("");
|
||||||
@ -113,6 +122,8 @@ export default function Dashboard() {
|
|||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
const [editId, setEditId] = useState<number | null>(null);
|
const [editId, setEditId] = useState<number | null>(null);
|
||||||
|
const [editHost, setEditHost] = useState<boolean>(false);
|
||||||
|
const [editHostServer, setEditHostServer] = useState<number | null>(0);
|
||||||
const [editName, setEditName] = useState<string>("");
|
const [editName, setEditName] = useState<string>("");
|
||||||
const [editOs, setEditOs] = useState<string>("");
|
const [editOs, setEditOs] = useState<string>("");
|
||||||
const [editIp, setEditIp] = useState<string>("");
|
const [editIp, setEditIp] = useState<string>("");
|
||||||
@ -125,6 +136,9 @@ export default function Dashboard() {
|
|||||||
const [searchTerm, setSearchTerm] = useState<string>("");
|
const [searchTerm, setSearchTerm] = useState<string>("");
|
||||||
const [isSearching, setIsSearching] = useState<boolean>(false);
|
const [isSearching, setIsSearching] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [hostServers, setHostServers] = useState<Server[]>([]);
|
||||||
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedLayout = Cookies.get("layoutPreference-servers");
|
const savedLayout = Cookies.get("layoutPreference-servers");
|
||||||
const layout_bool = savedLayout === "grid";
|
const layout_bool = savedLayout === "grid";
|
||||||
@ -146,6 +160,8 @@ export default function Dashboard() {
|
|||||||
const add = async () => {
|
const add = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post("/api/servers/add", {
|
await axios.post("/api/servers/add", {
|
||||||
|
host,
|
||||||
|
hostServer,
|
||||||
name,
|
name,
|
||||||
os,
|
os,
|
||||||
ip,
|
ip,
|
||||||
@ -155,6 +171,18 @@ export default function Dashboard() {
|
|||||||
ram,
|
ram,
|
||||||
disk,
|
disk,
|
||||||
});
|
});
|
||||||
|
setIsAddDialogOpen(false);
|
||||||
|
setHost(false);
|
||||||
|
setHostServer(0);
|
||||||
|
|
||||||
|
setName("");
|
||||||
|
setOs("");
|
||||||
|
setIp("");
|
||||||
|
setUrl("");
|
||||||
|
setCpu("");
|
||||||
|
setGpu("");
|
||||||
|
setRam("");
|
||||||
|
setDisk("");
|
||||||
getServers();
|
getServers();
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error.response.data);
|
console.log(error.response.data);
|
||||||
@ -171,6 +199,10 @@ export default function Dashboard() {
|
|||||||
ITEMS_PER_PAGE: itemsPerPage,
|
ITEMS_PER_PAGE: itemsPerPage,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
for (const server of response.data.servers) {
|
||||||
|
console.log("Host Server:" + server.hostServer);
|
||||||
|
console.log("ID:" + server.id);
|
||||||
|
}
|
||||||
setServers(response.data.servers);
|
setServers(response.data.servers);
|
||||||
setMaxPage(response.data.maxPage);
|
setMaxPage(response.data.maxPage);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -202,6 +234,8 @@ export default function Dashboard() {
|
|||||||
|
|
||||||
const openEditDialog = (server: Server) => {
|
const openEditDialog = (server: Server) => {
|
||||||
setEditId(server.id);
|
setEditId(server.id);
|
||||||
|
setEditHost(server.host);
|
||||||
|
setEditHostServer(server.hostServer || null);
|
||||||
setEditName(server.name);
|
setEditName(server.name);
|
||||||
setEditOs(server.os || "");
|
setEditOs(server.os || "");
|
||||||
setEditIp(server.ip || "");
|
setEditIp(server.ip || "");
|
||||||
@ -218,6 +252,8 @@ export default function Dashboard() {
|
|||||||
try {
|
try {
|
||||||
await axios.put("/api/servers/edit", {
|
await axios.put("/api/servers/edit", {
|
||||||
id: editId,
|
id: editId,
|
||||||
|
host: editHost,
|
||||||
|
hostServer: editHostServer,
|
||||||
name: editName,
|
name: editName,
|
||||||
os: editOs,
|
os: editOs,
|
||||||
ip: editIp,
|
ip: editIp,
|
||||||
@ -261,6 +297,23 @@ export default function Dashboard() {
|
|||||||
return () => clearTimeout(delayDebounce);
|
return () => clearTimeout(delayDebounce);
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchHostServers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get<{ servers: Server[] }>(
|
||||||
|
"/api/servers/hosts"
|
||||||
|
);
|
||||||
|
setHostServers(response.data.servers);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching host servers:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isAddDialogOpen || editId !== null) {
|
||||||
|
fetchHostServers();
|
||||||
|
}
|
||||||
|
}, [isAddDialogOpen, editId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
@ -312,7 +365,7 @@ export default function Dashboard() {
|
|||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
<AlertDialog>
|
<AlertDialog onOpenChange={setIsAddDialogOpen}>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button variant="outline" size="icon">
|
<Button variant="outline" size="icon">
|
||||||
<Plus />
|
<Plus />
|
||||||
@ -326,6 +379,9 @@ export default function Dashboard() {
|
|||||||
<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>
|
||||||
|
<TabsTrigger value="virtualization">
|
||||||
|
Virtualization
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="general">
|
<TabsContent value="general">
|
||||||
<div className="space-y-4 pt-4">
|
<div className="space-y-4 pt-4">
|
||||||
@ -459,6 +515,47 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="virtualization">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="hostCheckbox"
|
||||||
|
checked={host}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setHost(checked === true)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="hostCheckbox">
|
||||||
|
Mark as host server
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{!host && (
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label>Host Server</Label>
|
||||||
|
<Select
|
||||||
|
value={hostServer?.toString()}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setHostServer(Number(value))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a host server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{hostServers.map((server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.id}
|
||||||
|
value={server.id.toString()}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
@ -487,7 +584,9 @@ export default function Dashboard() {
|
|||||||
: "space-y-4"
|
: "space-y-4"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{servers.map((server) => (
|
{servers
|
||||||
|
.filter((server) => server.hostServer === null)
|
||||||
|
.map((server) => (
|
||||||
<Card
|
<Card
|
||||||
key={server.id}
|
key={server.id}
|
||||||
className={
|
className={
|
||||||
@ -606,9 +705,26 @@ export default function Dashboard() {
|
|||||||
<TabsTrigger value="hardware">
|
<TabsTrigger value="hardware">
|
||||||
Hardware
|
Hardware
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="virtualization">
|
||||||
|
Virtualization
|
||||||
|
</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">
|
||||||
|
<Label htmlFor="editName">
|
||||||
|
Name
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editName"
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. Server1"
|
||||||
|
value={editName}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEditName(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="grid w-full items-center gap-1.5">
|
<div className="grid w-full items-center gap-1.5">
|
||||||
<Label htmlFor="editOs">
|
<Label htmlFor="editOs">
|
||||||
Operating System
|
Operating System
|
||||||
@ -667,7 +783,9 @@ export default function Dashboard() {
|
|||||||
<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}
|
||||||
@ -677,7 +795,9 @@ export default function Dashboard() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid w-full items-center gap-1.5">
|
<div className="grid w-full items-center gap-1.5">
|
||||||
<Label htmlFor="editGpu">GPU</Label>
|
<Label htmlFor="editGpu">
|
||||||
|
GPU
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="editGpu"
|
id="editGpu"
|
||||||
value={editGpu}
|
value={editGpu}
|
||||||
@ -687,7 +807,9 @@ export default function Dashboard() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid w-full items-center gap-1.5">
|
<div className="grid w-full items-center gap-1.5">
|
||||||
<Label htmlFor="editRam">RAM</Label>
|
<Label htmlFor="editRam">
|
||||||
|
RAM
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="editRam"
|
id="editRam"
|
||||||
value={editRam}
|
value={editRam}
|
||||||
@ -710,15 +832,495 @@ export default function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="virtualization">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="editHostCheckbox"
|
||||||
|
checked={editHost}
|
||||||
|
onCheckedChange={(checked) =>
|
||||||
|
setEditHost(checked === true)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="editHostCheckbox">
|
||||||
|
Mark as host server
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{!editHost && (
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label>Host Server</Label>
|
||||||
|
<Select
|
||||||
|
value={editHostServer?.toString()}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setEditHostServer(
|
||||||
|
Number(value)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a host server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{hostServers.map(
|
||||||
|
(server) => (
|
||||||
|
<SelectItem
|
||||||
|
key={server.id}
|
||||||
|
value={server.id.toString()}
|
||||||
|
>
|
||||||
|
{server.name}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
<Button onClick={edit}>Save</Button>
|
<Button onClick={edit}>Save</Button>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
{server.hostedVMs.length > 0 && (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9"
|
||||||
|
>
|
||||||
|
<Server className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Hosted VMs
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{server.host && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<ScrollArea className="h-[500px] w-fzull rounded-md border p-4">
|
||||||
|
<div className="space-y-2 mt-2">
|
||||||
|
{server.hostedVMs?.map(
|
||||||
|
(hostedVM) => (
|
||||||
|
<div
|
||||||
|
key={hostedVM.id}
|
||||||
|
className="flex flex-col gap-2 border border-muted py-2 px-4 rounded-md"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="text-base font-md text-muted-foreground">
|
||||||
|
{hostedVM.name}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="gap-2"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
hostedVM.url,
|
||||||
|
"_blank"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Link className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9"
|
||||||
|
onClick={() =>
|
||||||
|
deleteApplication(
|
||||||
|
hostedVM.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
className="h-9 w-9"
|
||||||
|
onClick={() =>
|
||||||
|
openEditDialog(
|
||||||
|
hostedVM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Pencil className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>
|
||||||
|
Edit VM
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
<Tabs
|
||||||
|
defaultValue="general"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<TabsList className="w-full">
|
||||||
|
<TabsTrigger value="general">
|
||||||
|
General
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="hardware">
|
||||||
|
Hardware
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="virtualization">
|
||||||
|
Virtualization
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="general">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editName">
|
||||||
|
Name
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editName"
|
||||||
|
type="text"
|
||||||
|
placeholder="e.g. Server1"
|
||||||
|
value={
|
||||||
|
editName
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
setEditName(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editOs">
|
||||||
|
Operating
|
||||||
|
System
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
editOs
|
||||||
|
}
|
||||||
|
onValueChange={
|
||||||
|
setEditOs
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editCpu">
|
||||||
|
CPU
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editCpu"
|
||||||
|
value={
|
||||||
|
editCpu
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
setEditCpu(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editGpu">
|
||||||
|
GPU
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editGpu"
|
||||||
|
value={
|
||||||
|
editGpu
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
setEditGpu(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editRam">
|
||||||
|
RAM
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editRam"
|
||||||
|
value={
|
||||||
|
editRam
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
setEditRam(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="editDisk">
|
||||||
|
Disk
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="editDisk"
|
||||||
|
value={
|
||||||
|
editDisk
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
setEditDisk(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="virtualization">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="editHostCheckbox"
|
||||||
|
checked={
|
||||||
|
editHost
|
||||||
|
}
|
||||||
|
onCheckedChange={(
|
||||||
|
checked
|
||||||
|
) =>
|
||||||
|
setEditHost(
|
||||||
|
checked ===
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="editHostCheckbox">
|
||||||
|
Mark as
|
||||||
|
host
|
||||||
|
server
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
{!editHost && (
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label>
|
||||||
|
Host
|
||||||
|
Server
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={editHostServer?.toString()}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) =>
|
||||||
|
setEditHostServer(
|
||||||
|
Number(
|
||||||
|
value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a host server" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{hostServers.map(
|
||||||
|
(
|
||||||
|
server
|
||||||
|
) => (
|
||||||
|
<SelectItem
|
||||||
|
key={
|
||||||
|
server.id
|
||||||
|
}
|
||||||
|
value={server.id.toString()}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
server.name
|
||||||
|
}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>
|
||||||
|
Cancel
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<Button
|
||||||
|
onClick={edit}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-span-fullpb-2">
|
||||||
|
<Separator />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-5 pb-2">
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<MonitorCog className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>OS:</b>{" "}
|
||||||
|
{hostedVM.os || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<FileDigit className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>IP:</b>{" "}
|
||||||
|
{hostedVM.ip ||
|
||||||
|
"Not set"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<Cpu className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>CPU:</b>{" "}
|
||||||
|
{hostedVM.cpu || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<Microchip className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>GPU:</b>{" "}
|
||||||
|
{hostedVM.gpu || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<MemoryStick className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>RAM:</b>{" "}
|
||||||
|
{hostedVM.ram || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-foreground/80">
|
||||||
|
<HardDrive className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span>
|
||||||
|
<b>Disk:</b>{" "}
|
||||||
|
{hostedVM.disk || "-"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>
|
||||||
|
Close
|
||||||
|
</AlertDialogCancel>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -762,7 +1364,9 @@ export default function Dashboard() {
|
|||||||
<PaginationPrevious
|
<PaginationPrevious
|
||||||
onClick={handlePrevious}
|
onClick={handlePrevious}
|
||||||
isActive={currentPage > 1}
|
isActive={currentPage > 1}
|
||||||
style={{ cursor: currentPage === 1 ? 'not-allowed' : 'pointer' }}
|
style={{
|
||||||
|
cursor: currentPage === 1 ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
|
||||||
@ -774,7 +1378,10 @@ export default function Dashboard() {
|
|||||||
<PaginationNext
|
<PaginationNext
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
isActive={currentPage < maxPage}
|
isActive={currentPage < maxPage}
|
||||||
style={{ cursor: currentPage === maxPage ? 'not-allowed' : 'pointer' }}
|
style={{
|
||||||
|
cursor:
|
||||||
|
currentPage === maxPage ? "not-allowed" : "pointer",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
|
|||||||
46
components/ui/badge.tsx
Normal file
46
components/ui/badge.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Badge({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
asChild = false,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span"> &
|
||||||
|
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
||||||
|
const Comp = asChild ? Slot : "span"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
data-slot="badge"
|
||||||
|
className={cn(badgeVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants }
|
||||||
58
components/ui/scroll-area.tsx
Normal file
58
components/ui/scroll-area.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function ScrollArea({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
data-slot="scroll-area"
|
||||||
|
className={cn("relative", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
data-slot="scroll-area-viewport"
|
||||||
|
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollBar />
|
||||||
|
<ScrollAreaPrimitive.Corner />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScrollBar({
|
||||||
|
className,
|
||||||
|
orientation = "vertical",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
||||||
|
return (
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
|
data-slot="scroll-area-scrollbar"
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"flex touch-none p-px transition-colors select-none",
|
||||||
|
orientation === "vertical" &&
|
||||||
|
"h-full w-2.5 border-l border-l-transparent",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 flex-col border-t border-t-transparent",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.ScrollAreaThumb
|
||||||
|
data-slot="scroll-area-thumb"
|
||||||
|
className="bg-border relative flex-1 rounded-full"
|
||||||
|
/>
|
||||||
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ScrollArea, ScrollBar }
|
||||||
47
package-lock.json
generated
47
package-lock.json
generated
@ -17,6 +17,7 @@
|
|||||||
"@radix-ui/react-dialog": "^1.1.7",
|
"@radix-ui/react-dialog": "^1.1.7",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||||
"@radix-ui/react-label": "^2.1.3",
|
"@radix-ui/react-label": "^2.1.3",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.4",
|
||||||
"@radix-ui/react-select": "^2.1.7",
|
"@radix-ui/react-select": "^2.1.7",
|
||||||
"@radix-ui/react-separator": "^1.1.3",
|
"@radix-ui/react-separator": "^1.1.3",
|
||||||
"@radix-ui/react-slot": "^1.2.0",
|
"@radix-ui/react-slot": "^1.2.0",
|
||||||
@ -1720,6 +1721,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-scroll-area": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-G9rdWTQjOR4sk76HwSdROhPU0jZWpfozn9skU1v4N0/g9k7TmswrJn8W8WMU+aYktnLLpk5LX6fofj2bGe5NFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/number": "1.1.1",
|
||||||
|
"@radix-ui/primitive": "1.1.2",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.2",
|
||||||
|
"@radix-ui/react-context": "1.1.2",
|
||||||
|
"@radix-ui/react-direction": "1.1.1",
|
||||||
|
"@radix-ui/react-presence": "1.1.3",
|
||||||
|
"@radix-ui/react-primitive": "2.0.3",
|
||||||
|
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||||
|
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-select": {
|
"node_modules/@radix-ui/react-select": {
|
||||||
"version": "2.1.7",
|
"version": "2.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.7.tgz",
|
||||||
@ -4577,21 +4609,6 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
|
||||||
"version": "15.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.0.tgz",
|
|
||||||
"integrity": "sha512-vHUQS4YVGJPmpjn7r5lEZuMhK5UQBNBRSB+iGDvJjaNk649pTIcRluDWNb9siunyLLiu/LDPHfvxBtNamyuLTw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@
|
|||||||
"@radix-ui/react-dialog": "^1.1.7",
|
"@radix-ui/react-dialog": "^1.1.7",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||||
"@radix-ui/react-label": "^2.1.3",
|
"@radix-ui/react-label": "^2.1.3",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.4",
|
||||||
"@radix-ui/react-select": "^2.1.7",
|
"@radix-ui/react-select": "^2.1.7",
|
||||||
"@radix-ui/react-separator": "^1.1.3",
|
"@radix-ui/react-separator": "^1.1.3",
|
||||||
"@radix-ui/react-slot": "^1.2.0",
|
"@radix-ui/react-slot": "^1.2.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user