ShadCN Vaults

Back to Blocks

VPS Management & Monitoring Dashboard

Unknown Block

UnknownComponent

VPS Management & Monitoring Dashboard

Comprehensive dashboard for managing and monitoring VPS instances, including resource usage, customer details, and real-time status with detailed dialogs.

Preview

Full width desktop view

Code

monitoring-10.tsx
1"use client";
2import React, { useState } from "react";
3import {
4  Card,
5  CardContent,
6  CardDescription,
7  CardHeader,
8  CardTitle,
9} from "@/components/ui/card";
10import { Button } from "@/components/ui/button";
11import { Input } from "@/components/ui/input";
12import { Label } from "@/components/ui/label";
13import {
14  Select,
15  SelectContent,
16  SelectItem,
17  SelectTrigger,
18  SelectValue,
19} from "@/components/ui/select";
20import { Badge } from "@/components/ui/badge";
21import {
22  Table,
23  TableBody,
24  TableCell,
25  TableHead,
26  TableHeader,
27  TableRow,
28} from "@/components/ui/table";
29import {
30  Dialog,
31  DialogContent,
32  DialogDescription,
33  DialogHeader,
34  DialogTitle,
35  DialogTrigger,
36} from "@/components/ui/dialog";
37import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
38import { Switch } from "@/components/ui/switch";
39import { Textarea } from "@/components/ui/textarea";
40import { Progress } from "@/components/ui/progress";
41import {
42  Server,
43  Plus,
44  Settings,
45  Activity,
46  HardDrive,
47  Cpu,
48  MemoryStick,
49  Network,
50  Power,
51  PowerOff,
52  Trash2,
53  Edit,
54  Eye,
55  AlertTriangle,
56  CheckCircle,
57  Clock,
58} from "lucide-react";
59
60interface VPSInstance {
61  id: string;
62  name: string;
63  type: "shared" | "dedicated";
64  status: "running" | "stopped" | "maintenance" | "error";
65  customer: string;
66  plan: string;
67  cpu: number;
68  ram: number;
69  storage: number;
70  bandwidth: number;
71  ipAddress: string;
72  location: string;
73  createdAt: string;
74  cpuUsage: number;
75  ramUsage: number;
76  storageUsage: number;
77  uptime: string;
78}
79
80interface NewVPSForm {
81  name: string;
82  type: "shared" | "dedicated";
83  customer: string;
84  plan: string;
85  cpu: number;
86  ram: number;
87  storage: number;
88  bandwidth: number;
89  location: string;
90  autoBackup: boolean;
91  notes: string;
92}
93
94const VPSAdminDashboard: React.FC = () => {
95  const [instances, setInstances] = useState<VPSInstance[]>([
96    {
97      id: "vps-001",
98      name: "WebStore-Primary",
99      type: "dedicated",
100      status: "running",
101      customer: "John Doe Store",
102      plan: "Premium",
103      cpu: 4,
104      ram: 8,
105      storage: 200,
106      bandwidth: 1000,
107      ipAddress: "192.168.1.100",
108      location: "US-East",
109      createdAt: "2024-01-15",
110      cpuUsage: 45,
111      ramUsage: 62,
112      storageUsage: 78,
113      uptime: "99.9%",
114    },
115    {
116      id: "vps-002",
117      name: "ECommerce-Dev",
118      type: "shared",
119      status: "running",
120      customer: "Tech Solutions Inc",
121      plan: "Standard",
122      cpu: 2,
123      ram: 4,
124      storage: 100,
125      bandwidth: 500,
126      ipAddress: "192.168.1.101",
127      location: "EU-West",
128      createdAt: "2024-01-20",
129      cpuUsage: 23,
130      ramUsage: 41,
131      storageUsage: 34,
132      uptime: "99.5%",
133    },
134    {
135      id: "vps-003",
136      name: "Store-Backup",
137      type: "shared",
138      status: "stopped",
139      customer: "Small Business Co",
140      plan: "Basic",
141      cpu: 1,
142      ram: 2,
143      storage: 50,
144      bandwidth: 250,
145      ipAddress: "192.168.1.102",
146      location: "US-West",
147      createdAt: "2024-02-01",
148      cpuUsage: 0,
149      ramUsage: 0,
150      storageUsage: 12,
151      uptime: "98.2%",
152    },
153  ]);
154
155  const [newVPS, setNewVPS] = useState<NewVPSForm>({
156    name: "",
157    type: "shared",
158    customer: "",
159    plan: "",
160    cpu: 1,
161    ram: 2,
162    storage: 50,
163    bandwidth: 250,
164    location: "",
165    autoBackup: false,
166    notes: "",
167  });
168
169  const [isDialogOpen, setIsDialogOpen] = useState(false);
170  const [selectedInstance, setSelectedInstance] = useState<VPSInstance | null>(
171    null
172  );
173
174  const getStatusColor = (status: string) => {
175    switch (status) {
176      case "running":
177        return "bg-green-500";
178      case "stopped":
179        return "bg-red-500";
180      case "maintenance":
181        return "bg-yellow-500";
182      case "error":
183        return "bg-orange-500";
184      default:
185        return "bg-gray-500";
186    }
187  };
188
189  const getStatusIcon = (status: string) => {
190    switch (status) {
191      case "running":
192        return <CheckCircle className="h-4 w-4" />;
193      case "stopped":
194        return <PowerOff className="h-4 w-4" />;
195      case "maintenance":
196        return <Clock className="h-4 w-4" />;
197      case "error":
198        return <AlertTriangle className="h-4 w-4" />;
199      default:
200        return <Power className="h-4 w-4" />;
201    }
202  };
203
204  const handleCreateVPS = () => {
205    const newInstance: VPSInstance = {
206      id: `vps-${String(instances.length + 1).padStart(3, "0")}`,
207      name: newVPS.name,
208      type: newVPS.type,
209      status: "running",
210      customer: newVPS.customer,
211      plan: newVPS.plan,
212      cpu: newVPS.cpu,
213      ram: newVPS.ram,
214      storage: newVPS.storage,
215      bandwidth: newVPS.bandwidth,
216      ipAddress: `192.168.1.${103 + instances.length}`,
217      location: newVPS.location,
218      createdAt: new Date().toISOString().split("T")[0],
219      cpuUsage: Math.floor(Math.random() * 50),
220      ramUsage: Math.floor(Math.random() * 60),
221      storageUsage: Math.floor(Math.random() * 40),
222      uptime: "100%",
223    };
224
225    setInstances([...instances, newInstance]);
226    setNewVPS({
227      name: "",
228      type: "shared",
229      customer: "",
230      plan: "",
231      cpu: 1,
232      ram: 2,
233      storage: 50,
234      bandwidth: 250,
235      location: "",
236      autoBackup: false,
237      notes: "",
238    });
239    setIsDialogOpen(false);
240  };
241
242  const handleStatusChange = (id: string, newStatus: "running" | "stopped") => {
243    setInstances(
244      instances.map((instance) =>
245        instance.id === id ? { ...instance, status: newStatus } : instance
246      )
247    );
248  };
249
250  const handleDeleteInstance = (id: string) => {
251    setInstances(instances.filter((instance) => instance.id !== id));
252  };
253
254  const totalInstances = instances.length;
255  const runningInstances = instances.filter(
256    (i) => i.status === "running"
257  ).length;
258  const totalCPU = instances.reduce((sum, i) => sum + i.cpu, 0);
259  const totalRAM = instances.reduce((sum, i) => sum + i.ram, 0);
260
261  return (
262    <div className="min-h-screen bg-background p-6">
263      <div className="max-w-7xl mx-auto space-y-6">
264        {/* Header */}
265        <div className="flex items-center justify-between">
266          <div>
267            <h1 className="text-3xl font-bold text-foreground">
268              VPS Management Dashboard
269            </h1>
270            <p className="text-muted-foreground">
271              Manage and monitor VPS instances for webstore deployments
272            </p>
273          </div>
274          <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
275            <DialogTrigger asChild>
276              <Button className="flex items-center gap-2">
277                <Plus className="h-4 w-4" />
278                Add New VPS
279              </Button>
280            </DialogTrigger>
281            <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
282              <DialogHeader>
283                <DialogTitle>Create New VPS Instance</DialogTitle>
284                <DialogDescription>
285                  Configure a new VPS instance for webstore deployment
286                </DialogDescription>
287              </DialogHeader>
288              <div className="grid gap-4 py-4">
289                <div className="grid grid-cols-2 gap-4">
290                  <div className="space-y-2">
291                    <Label htmlFor="name">Instance Name</Label>
292                    <Input
293                      id="name"
294                      value={newVPS.name}
295                      onChange={(e) =>
296                        setNewVPS({ ...newVPS, name: e.target.value })
297                      }
298                      placeholder="e.g., WebStore-Primary"
299                    />
300                  </div>
301                  <div className="space-y-2">
302                    <Label htmlFor="type">VPS Type</Label>
303                    <Select
304                      value={newVPS.type}
305                      onValueChange={(value: "shared" | "dedicated") =>
306                        setNewVPS({ ...newVPS, type: value })
307                      }
308                    >
309                      <SelectTrigger>
310                        <SelectValue />
311                      </SelectTrigger>
312                      <SelectContent>
313                        <SelectItem value="shared">Shared VPS</SelectItem>
314                        <SelectItem value="dedicated">Dedicated VPS</SelectItem>
315                      </SelectContent>
316                    </Select>
317                  </div>
318                </div>
319
320                <div className="grid grid-cols-2 gap-4">
321                  <div className="space-y-2">
322                    <Label htmlFor="customer">Customer Name</Label>
323                    <Input
324                      id="customer"
325                      value={newVPS.customer}
326                      onChange={(e) =>
327                        setNewVPS({ ...newVPS, customer: e.target.value })
328                      }
329                      placeholder="Customer or company name"
330                    />
331                  </div>
332                  <div className="space-y-2">
333                    <Label htmlFor="plan">Plan</Label>
334                    <Select
335                      value={newVPS.plan}
336                      onValueChange={(value) =>
337                        setNewVPS({ ...newVPS, plan: value })
338                      }
339                    >
340                      <SelectTrigger>
341                        <SelectValue placeholder="Select plan" />
342                      </SelectTrigger>
343                      <SelectContent>
344                        <SelectItem value="basic">Basic</SelectItem>
345                        <SelectItem value="standard">Standard</SelectItem>
346                        <SelectItem value="premium">Premium</SelectItem>
347                        <SelectItem value="enterprise">Enterprise</SelectItem>
348                      </SelectContent>
349                    </Select>
350                  </div>
351                </div>
352
353                <div className="grid grid-cols-2 gap-4">
354                  <div className="space-y-2">
355                    <Label htmlFor="cpu">CPU Cores</Label>
356                    <Select
357                      value={String(newVPS.cpu)}
358                      onValueChange={(value) =>
359                        setNewVPS({ ...newVPS, cpu: parseInt(value) })
360                      }
361                    >
362                      <SelectTrigger>
363                        <SelectValue />
364                      </SelectTrigger>
365                      <SelectContent>
366                        <SelectItem value="1">1 Core</SelectItem>
367                        <SelectItem value="2">2 Cores</SelectItem>
368                        <SelectItem value="4">4 Cores</SelectItem>
369                        <SelectItem value="8">8 Cores</SelectItem>
370                        <SelectItem value="16">16 Cores</SelectItem>
371                      </SelectContent>
372                    </Select>
373                  </div>
374                  <div className="space-y-2">
375                    <Label htmlFor="ram">RAM (GB)</Label>
376                    <Select
377                      value={String(newVPS.ram)}
378                      onValueChange={(value) =>
379                        setNewVPS({ ...newVPS, ram: parseInt(value) })
380                      }
381                    >
382                      <SelectTrigger>
383                        <SelectValue />
384                      </SelectTrigger>
385                      <SelectContent>
386                        <SelectItem value="2">2 GB</SelectItem>
387                        <SelectItem value="4">4 GB</SelectItem>
388                        <SelectItem value="8">8 GB</SelectItem>
389                        <SelectItem value="16">16 GB</SelectItem>
390                        <SelectItem value="32">32 GB</SelectItem>
391                        <SelectItem value="64">64 GB</SelectItem>
392                      </SelectContent>
393                    </Select>
394                  </div>
395                </div>
396
397                <div className="grid grid-cols-2 gap-4">
398                  <div className="space-y-2">
399                    <Label htmlFor="storage">Storage (GB)</Label>
400                    <Select
401                      value={String(newVPS.storage)}
402                      onValueChange={(value) =>
403                        setNewVPS({ ...newVPS, storage: parseInt(value) })
404                      }
405                    >
406                      <SelectTrigger>
407                        <SelectValue />
408                      </SelectTrigger>
409                      <SelectContent>
410                        <SelectItem value="50">50 GB</SelectItem>
411                        <SelectItem value="100">100 GB</SelectItem>
412                        <SelectItem value="200">200 GB</SelectItem>
413                        <SelectItem value="500">500 GB</SelectItem>
414                        <SelectItem value="1000">1 TB</SelectItem>
415                      </SelectContent>
416                    </Select>
417                  </div>
418                  <div className="space-y-2">
419                    <Label htmlFor="bandwidth">Bandwidth (GB/month)</Label>
420                    <Select
421                      value={String(newVPS.bandwidth)}
422                      onValueChange={(value) =>
423                        setNewVPS({ ...newVPS, bandwidth: parseInt(value) })
424                      }
425                    >
426                      <SelectTrigger>
427                        <SelectValue />
428                      </SelectTrigger>
429                      <SelectContent>
430                        <SelectItem value="250">250 GB</SelectItem>
431                        <SelectItem value="500">500 GB</SelectItem>
432                        <SelectItem value="1000">1 TB</SelectItem>
433                        <SelectItem value="2000">2 TB</SelectItem>
434                        <SelectItem value="0">Unlimited</SelectItem>
435                      </SelectContent>
436                    </Select>
437                  </div>
438                </div>
439
440                <div className="space-y-2">
441                  <Label htmlFor="location">Location</Label>
442                  <Select
443                    value={newVPS.location}
444                    onValueChange={(value) =>
445                      setNewVPS({ ...newVPS, location: value })
446                    }
447                  >
448                    <SelectTrigger>
449                      <SelectValue placeholder="Select location" />
450                    </SelectTrigger>
451                    <SelectContent>
452                      <SelectItem value="US-East">
453                        US East (Virginia)
454                      </SelectItem>
455                      <SelectItem value="US-West">
456                        US West (California)
457                      </SelectItem>
458                      <SelectItem value="EU-West">EU West (Ireland)</SelectItem>
459                      <SelectItem value="EU-Central">
460                        EU Central (Frankfurt)
461                      </SelectItem>
462                      <SelectItem value="Asia-Pacific">
463                        Asia Pacific (Singapore)
464                      </SelectItem>
465                    </SelectContent>
466                  </Select>
467                </div>
468
469                <div className="flex items-center space-x-2">
470                  <Switch
471                    id="autoBackup"
472                    checked={newVPS.autoBackup}
473                    onCheckedChange={(checked) =>
474                      setNewVPS({ ...newVPS, autoBackup: checked })
475                    }
476                  />
477                  <Label htmlFor="autoBackup">Enable automatic backups</Label>
478                </div>
479
480                <div className="space-y-2">
481                  <Label htmlFor="notes">Notes (Optional)</Label>
482                  <Textarea
483                    id="notes"
484                    value={newVPS.notes}
485                    onChange={(e) =>
486                      setNewVPS({ ...newVPS, notes: e.target.value })
487                    }
488                    placeholder="Additional notes or requirements..."
489                    rows={3}
490                  />
491                </div>
492
493                <div className="flex justify-end space-x-2 pt-4">
494                  <Button
495                    variant="outline"
496                    onClick={() => setIsDialogOpen(false)}
497                  >
498                    Cancel
499                  </Button>
500                  <Button
501                    onClick={handleCreateVPS}
502                    disabled={
503                      !newVPS.name ||
504                      !newVPS.customer ||
505                      !newVPS.plan ||
506                      !newVPS.location
507                    }
508                  >
509                    Create VPS Instance
510                  </Button>
511                </div>
512              </div>
513            </DialogContent>
514          </Dialog>
515        </div>
516
517        {/* Stats Cards */}
518        <div className="grid grid-cols-1 md:grid-cols-4 gap-6">
519          <Card>
520            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
521              <CardTitle className="text-sm font-medium">
522                Total Instances
523              </CardTitle>
524              <Server className="h-4 w-4 text-muted-foreground" />
525            </CardHeader>
526            <CardContent>
527              <div className="text-2xl font-bold">{totalInstances}</div>
528              <p className="text-xs text-muted-foreground">
529                {runningInstances} running
530              </p>
531            </CardContent>
532          </Card>
533
534          <Card>
535            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
536              <CardTitle className="text-sm font-medium">
537                Total CPU Cores
538              </CardTitle>
539              <Cpu className="h-4 w-4 text-muted-foreground" />
540            </CardHeader>
541            <CardContent>
542              <div className="text-2xl font-bold">{totalCPU}</div>
543              <p className="text-xs text-muted-foreground">
544                Across all instances
545              </p>
546            </CardContent>
547          </Card>
548
549          <Card>
550            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
551              <CardTitle className="text-sm font-medium">Total RAM</CardTitle>
552              <MemoryStick className="h-4 w-4 text-muted-foreground" />
553            </CardHeader>
554            <CardContent>
555              <div className="text-2xl font-bold">{totalRAM} GB</div>
556              <p className="text-xs text-muted-foreground">Memory allocated</p>
557            </CardContent>
558          </Card>
559
560          <Card>
561            <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
562              <CardTitle className="text-sm font-medium">
563                Active Customers
564              </CardTitle>
565              <Activity className="h-4 w-4 text-muted-foreground" />
566            </CardHeader>
567            <CardContent>
568              <div className="text-2xl font-bold">
569                {new Set(instances.map((i) => i.customer)).size}
570              </div>
571              <p className="text-xs text-muted-foreground">Unique customers</p>
572            </CardContent>
573          </Card>
574        </div>
575
576        {/* Main Content */}
577        <Tabs defaultValue="instances" className="space-y-4">
578          <TabsList>
579            <TabsTrigger value="instances">VPS Instances</TabsTrigger>
580            <TabsTrigger value="monitoring">Monitoring</TabsTrigger>
581          </TabsList>
582
583          <TabsContent value="instances" className="space-y-4">
584            <Card>
585              <CardHeader>
586                <CardTitle>VPS Instances</CardTitle>
587                <CardDescription>
588                  Manage all VPS instances and their configurations
589                </CardDescription>
590              </CardHeader>
591              <CardContent>
592                <Table>
593                  <TableHeader>
594                    <TableRow>
595                      <TableHead>Instance</TableHead>
596                      <TableHead>Type</TableHead>
597                      <TableHead>Status</TableHead>
598                      <TableHead>Customer</TableHead>
599                      <TableHead>Resources</TableHead>
600                      <TableHead>Location</TableHead>
601                      <TableHead>IP Address</TableHead>
602                      <TableHead>Actions</TableHead>
603                    </TableRow>
604                  </TableHeader>
605                  <TableBody>
606                    {instances.map((instance) => (
607                      <TableRow key={instance.id}>
608                        <TableCell>
609                          <div>
610                            <div className="font-medium">{instance.name}</div>
611                            <div className="text-sm text-muted-foreground">
612                              {instance.id}
613                            </div>
614                          </div>
615                        </TableCell>
616                        <TableCell>
617                          <Badge
618                            variant={
619                              instance.type === "dedicated"
620                                ? "default"
621                                : "secondary"
622                            }
623                          >
624                            {instance.type}
625                          </Badge>
626                        </TableCell>
627                        <TableCell>
628                          <div className="flex items-center gap-2">
629                            <div
630                              className={`w-2 h-2 rounded-full ${getStatusColor(
631                                instance.status
632                              )}`}
633                            />
634                            <span className="capitalize">
635                              {instance.status}
636                            </span>
637                          </div>
638                        </TableCell>
639                        <TableCell>
640                          <div>
641                            <div className="font-medium">
642                              {instance.customer}
643                            </div>
644                            <div className="text-sm text-muted-foreground">
645                              {instance.plan}
646                            </div>
647                          </div>
648                        </TableCell>
649                        <TableCell>
650                          <div className="text-sm">
651                            <div>
652                              {instance.cpu} CPU • {instance.ram}GB RAM
653                            </div>
654                            <div className="text-muted-foreground">
655                              {instance.storage}GB Storage
656                            </div>
657                          </div>
658                        </TableCell>
659                        <TableCell>{instance.location}</TableCell>
660                        <TableCell className="font-mono text-sm">
661                          {instance.ipAddress}
662                        </TableCell>
663                        <TableCell>
664                          <div className="flex items-center gap-2">
665                            <Button
666                              size="sm"
667                              variant="outline"
668                              onClick={() => setSelectedInstance(instance)}
669                            >
670                              <Eye className="h-4 w-4" />
671                            </Button>
672                            <Button
673                              size="sm"
674                              variant="outline"
675                              onClick={() =>
676                                handleStatusChange(
677                                  instance.id,
678                                  instance.status === "running"
679                                    ? "stopped"
680                                    : "running"
681                                )
682                              }
683                            >
684                              {instance.status === "running" ? (
685                                <PowerOff className="h-4 w-4" />
686                              ) : (
687                                <Power className="h-4 w-4" />
688                              )}
689                            </Button>
690                            <Button
691                              size="sm"
692                              variant="outline"
693                              onClick={() => handleDeleteInstance(instance.id)}
694                            >
695                              <Trash2 className="h-4 w-4" />
696                            </Button>
697                          </div>
698                        </TableCell>
699                      </TableRow>
700                    ))}
701                  </TableBody>
702                </Table>
703              </CardContent>
704            </Card>
705          </TabsContent>
706
707          <TabsContent value="monitoring" className="space-y-4">
708            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
709              {instances
710                .filter((i) => i.status === "running")
711                .map((instance) => (
712                  <Card key={instance.id}>
713                    <CardHeader>
714                      <CardTitle className="text-lg">{instance.name}</CardTitle>
715                      <CardDescription>{instance.customer}</CardDescription>
716                    </CardHeader>
717                    <CardContent className="space-y-4">
718                      <div className="space-y-2">
719                        <div className="flex justify-between text-sm">
720                          <span>CPU Usage</span>
721                          <span>{instance.cpuUsage}%</span>
722                        </div>
723                        <Progress value={instance.cpuUsage} className="h-2" />
724                      </div>
725
726                      <div className="space-y-2">
727                        <div className="flex justify-between text-sm">
728                          <span>RAM Usage</span>
729                          <span>{instance.ramUsage}%</span>
730                        </div>
731                        <Progress value={instance.ramUsage} className="h-2" />
732                      </div>
733
734                      <div className="space-y-2">
735                        <div className="flex justify-between text-sm">
736                          <span>Storage Usage</span>
737                          <span>{instance.storageUsage}%</span>
738                        </div>
739                        <Progress
740                          value={instance.storageUsage}
741                          className="h-2"
742                        />
743                      </div>
744
745                      <div className="flex justify-between items-center pt-2">
746                        <span className="text-sm text-muted-foreground">
747                          Uptime
748                        </span>
749                        <Badge variant="outline">{instance.uptime}</Badge>
750                      </div>
751                    </CardContent>
752                  </Card>
753                ))}
754            </div>
755          </TabsContent>
756        </Tabs>
757
758        {/* Instance Details Dialog */}
759        {selectedInstance && (
760          <Dialog
761            open={!!selectedInstance}
762            onOpenChange={() => setSelectedInstance(null)}
763          >
764            <DialogContent className="max-w-2xl">
765              <DialogHeader>
766                <DialogTitle>{selectedInstance.name} Details</DialogTitle>
767                <DialogDescription>
768                  Detailed information about this VPS instance
769                </DialogDescription>
770              </DialogHeader>
771              <div className="grid gap-4 py-4">
772                <div className="grid grid-cols-2 gap-4">
773                  <div>
774                    <Label className="text-sm font-medium">Instance ID</Label>
775                    <p className="text-sm text-muted-foreground">
776                      {selectedInstance.id}
777                    </p>
778                  </div>
779                  <div>
780                    <Label className="text-sm font-medium">Type</Label>
781                    <p className="text-sm text-muted-foreground capitalize">
782                      {selectedInstance.type}
783                    </p>
784                  </div>
785                </div>
786
787                <div className="grid grid-cols-2 gap-4">
788                  <div>
789                    <Label className="text-sm font-medium">Customer</Label>
790                    <p className="text-sm text-muted-foreground">
791                      {selectedInstance.customer}
792                    </p>
793                  </div>
794                  <div>
795                    <Label className="text-sm font-medium">Plan</Label>
796                    <p className="text-sm text-muted-foreground">
797                      {selectedInstance.plan}
798                    </p>
799                  </div>
800                </div>
801
802                <div className="grid grid-cols-2 gap-4">
803                  <div>
804                    <Label className="text-sm font-medium">IP Address</Label>
805                    <p className="text-sm text-muted-foreground font-mono">
806                      {selectedInstance.ipAddress}
807                    </p>
808                  </div>
809                  <div>
810                    <Label className="text-sm font-medium">Location</Label>
811                    <p className="text-sm text-muted-foreground">
812                      {selectedInstance.location}
813                    </p>
814                  </div>
815                </div>
816
817                <div className="grid grid-cols-4 gap-4">
818                  <div>
819                    <Label className="text-sm font-medium">CPU</Label>
820                    <p className="text-sm text-muted-foreground">
821                      {selectedInstance.cpu} cores
822                    </p>
823                  </div>
824                  <div>
825                    <Label className="text-sm font-medium">RAM</Label>
826                    <p className="text-sm text-muted-foreground">
827                      {selectedInstance.ram} GB
828                    </p>
829                  </div>
830                  <div>
831                    <Label className="text-sm font-medium">Storage</Label>
832                    <p className="text-sm text-muted-foreground">
833                      {selectedInstance.storage} GB
834                    </p>
835                  </div>
836                  <div>
837                    <Label className="text-sm font-medium">Bandwidth</Label>
838                    <p className="text-sm text-muted-foreground">
839                      {selectedInstance.bandwidth} GB
840                    </p>
841                  </div>
842                </div>
843
844                <div>
845                  <Label className="text-sm font-medium">Created</Label>
846                  <p className="text-sm text-muted-foreground">
847                    {selectedInstance.createdAt}
848                  </p>
849                </div>
850              </div>
851            </DialogContent>
852          </Dialog>
853        )}
854      </div>
855    </div>
856  );
857};
858
859export default VPSAdminDashboard;

Dependencies

External Libraries

lucide-reactreact

Shadcn/UI Components

badgebuttoncarddialoginputlabelprogressselectswitchtabletabstextarea

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Shadcn Vaults Project (CC BY-NC 4.0 with Internal Use Exception)

All user-submitted components in the '/blocks' directory are licensed under the Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0),
with the following clarification and exception:

You are free to:
- Share — copy and redistribute the material in any medium or format
- Adapt — remix, transform, and build upon the material

Under these conditions:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made.
- NonCommercial — You may NOT use the material for commercial redistribution, resale, or monetization.

🚫 You MAY NOT:
- Sell or redistribute the components individually or as part of a product (e.g. a UI kit, template marketplace, SaaS component library)
- Offer the components or derivative works in any paid tool, theme pack, or design system

✅ You MAY:
- Use the components in internal company tools, dashboards, or applications that are not sold as products
- Remix or adapt components for private or enterprise projects
- Use them in open-source non-commercial projects

This license encourages sharing, learning, and internal innovation — but prohibits using these components as a basis for monetized products.

Full license text: https://creativecommons.org/licenses/by-nc/4.0/

By submitting a component, contributors agree to these terms.

Contributors

Ramiro Godoy@milogodoy

Review Form Block

Aldhaneka@Aldhanekaa

Project Creator

For questions about licensing, please contact the project maintainers.