ShadCN Vaults

Back to Blocks

Animated VPS Admin Panel

Unknown Block

UnknownComponent

Animated VPS Admin Panel

Feature-rich VPS admin panel with animated transitions, resource usage indicators, and advanced controls for shared and dedicated VPS instances.

Preview

Full width desktop view

Code

monitoring-7.tsx
1"use client";
2
3import React, { useState, useEffect } from "react";
4import { motion, AnimatePresence } from "framer-motion";
5import {
6  Server,
7  Plus,
8  Settings,
9  Activity,
10  Users,
11  HardDrive,
12  Cpu,
13  MemoryStick,
14  MapPin,
15  Play,
16  Square,
17  Trash2,
18  Edit,
19  MoreHorizontal,
20  Search,
21  Filter,
22  RefreshCw,
23  AlertTriangle,
24  CheckCircle,
25  Clock,
26  Globe,
27  Shield,
28  Database,
29  Network,
30  Monitor,
31  Zap,
32  Eye,
33  X,
34  ChevronDown,
35  ChevronRight,
36} from "lucide-react";
37import { Button } from "@/components/ui/button";
38import { Input } from "@/components/ui/input";
39import { Badge } from "@/components/ui/badge";
40import {
41  Card,
42  CardContent,
43  CardDescription,
44  CardFooter,
45  CardHeader,
46  CardTitle,
47} from "@/components/ui/card";
48import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
49import { Progress } from "@/components/ui/progress";
50import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
51import { ScrollArea } from "@/components/ui/scroll-area";
52import {
53  Dialog,
54  DialogContent,
55  DialogDescription,
56  DialogFooter,
57  DialogHeader,
58  DialogTitle,
59  DialogTrigger,
60} from "@/components/ui/dialog";
61import {
62  Table,
63  TableBody,
64  TableCell,
65  TableHead,
66  TableHeader,
67  TableRow,
68} from "@/components/ui/table";
69import { cn } from "@/lib/utils";
70
71interface VPSParent {
72  id: string;
73  name: string;
74  region: string;
75  cpu: number;
76  ram: number;
77  storage: number;
78  status: "online" | "offline" | "maintenance";
79  ipAddress: string;
80  createdAt: string;
81  sharedVPS: SharedVPS[];
82  dedicatedVPS: DedicatedVPS[];
83  usage: {
84    cpu: number;
85    ram: number;
86    storage: number;
87  };
88}
89
90interface SharedVPS {
91  id: string;
92  name: string;
93  parentId: string;
94  customer: string;
95  cpu: number;
96  ram: number;
97  storage: number;
98  status: "running" | "stopped" | "suspended";
99  domain?: string;
100  createdAt: string;
101  usage: {
102    cpu: number;
103    ram: number;
104    storage: number;
105  };
106}
107
108interface DedicatedVPS {
109  id: string;
110  name: string;
111  customer: string;
112  region: string;
113  cpu: number;
114  ram: number;
115  storage: number;
116  status: "running" | "stopped" | "suspended";
117  ipAddress: string;
118  domain?: string;
119  createdAt: string;
120  usage: {
121    cpu: number;
122    ram: number;
123    storage: number;
124  };
125}
126
127const mockVPSParents: VPSParent[] = [
128  {
129    id: "parent-1",
130    name: "US-East-1-Parent",
131    region: "US East (Virginia)",
132    cpu: 32,
133    ram: 128,
134    storage: 2000,
135    status: "online",
136    ipAddress: "192.168.1.100",
137    createdAt: "2024-01-15",
138    sharedVPS: [],
139    dedicatedVPS: [],
140    usage: { cpu: 65, ram: 78, storage: 45 },
141  },
142  {
143    id: "parent-2",
144    name: "EU-West-1-Parent",
145    region: "EU West (Ireland)",
146    cpu: 24,
147    ram: 96,
148    storage: 1500,
149    status: "online",
150    ipAddress: "192.168.2.100",
151    createdAt: "2024-01-20",
152    sharedVPS: [],
153    dedicatedVPS: [],
154    usage: { cpu: 45, ram: 60, storage: 30 },
155  },
156];
157
158const mockSharedVPS: SharedVPS[] = [
159  {
160    id: "shared-1",
161    name: "WebStore-Alpha",
162    parentId: "parent-1",
163    customer: "John Doe",
164    cpu: 2,
165    ram: 4,
166    storage: 50,
167    status: "running",
168    domain: "alpha-store.com",
169    createdAt: "2024-02-01",
170    usage: { cpu: 35, ram: 60, storage: 25 },
171  },
172  {
173    id: "shared-2",
174    name: "WebStore-Beta",
175    parentId: "parent-1",
176    customer: "Jane Smith",
177    cpu: 1,
178    ram: 2,
179    storage: 25,
180    status: "running",
181    domain: "beta-store.com",
182    createdAt: "2024-02-05",
183    usage: { cpu: 20, ram: 40, storage: 15 },
184  },
185];
186
187const mockDedicatedVPS: DedicatedVPS[] = [
188  {
189    id: "dedicated-1",
190    name: "Enterprise-Store-1",
191    customer: "Acme Corp",
192    region: "US East (Virginia)",
193    cpu: 8,
194    ram: 32,
195    storage: 500,
196    status: "running",
197    ipAddress: "203.0.113.10",
198    domain: "acme-store.com",
199    createdAt: "2024-01-25",
200    usage: { cpu: 55, ram: 70, storage: 40 },
201  },
202];
203
204const regions = [
205  "US East (Virginia)",
206  "US West (California)",
207  "EU West (Ireland)",
208  "EU Central (Frankfurt)",
209  "Asia Pacific (Singapore)",
210  "Asia Pacific (Tokyo)",
211];
212
213const StatusBadge = ({ status }: { status: string }) => {
214  const variants = {
215    online: "bg-green-500/10 text-green-500 border-green-500/20",
216    running: "bg-green-500/10 text-green-500 border-green-500/20",
217    offline: "bg-red-500/10 text-red-500 border-red-500/20",
218    stopped: "bg-red-500/10 text-red-500 border-red-500/20",
219    maintenance: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20",
220    suspended: "bg-orange-500/10 text-orange-500 border-orange-500/20",
221  };
222
223  return (
224    <Badge className={cn("border", variants[status as keyof typeof variants])}>
225      {status}
226    </Badge>
227  );
228};
229
230const UsageIndicator = ({
231  label,
232  value,
233  max,
234  unit,
235}: {
236  label: string;
237  value: number;
238  max: number;
239  unit: string;
240}) => {
241  const percentage = (value / max) * 100;
242
243  return (
244    <div className="space-y-1">
245      <div className="flex justify-between text-sm">
246        <span className="text-muted-foreground">{label}</span>
247        <span className="font-medium">
248          {value}
249          {unit} / {max}
250          {unit}
251        </span>
252      </div>
253      <Progress value={percentage} className="h-2" />
254    </div>
255  );
256};
257
258const VPSParentCard = ({
259  parent,
260  onEdit,
261  onDelete,
262  onViewShared,
263  sharedCount,
264}: {
265  parent: VPSParent;
266  onEdit: (parent: VPSParent) => void;
267  onDelete: (id: string) => void;
268  onViewShared: (parentId: string) => void;
269  sharedCount: number;
270}) => {
271  return (
272    <Card className="hover:shadow-lg transition-shadow">
273      <CardHeader className="pb-3">
274        <div className="flex items-center justify-between">
275          <div className="flex items-center gap-3">
276            <div className="p-2 bg-blue-500/10 rounded-lg">
277              <Server className="h-5 w-5 text-blue-500" />
278            </div>
279            <div>
280              <CardTitle className="text-lg">{parent.name}</CardTitle>
281              <CardDescription className="flex items-center gap-1">
282                <MapPin className="h-3 w-3" />
283                {parent.region}
284              </CardDescription>
285            </div>
286          </div>
287          <div className="flex items-center gap-2">
288            <StatusBadge status={parent.status} />
289            <Button variant="ghost" size="sm" onClick={() => onEdit(parent)}>
290              <Edit className="h-4 w-4" />
291            </Button>
292            <Button
293              variant="ghost"
294              size="sm"
295              onClick={() => onDelete(parent.id)}
296            >
297              <Trash2 className="h-4 w-4" />
298            </Button>
299          </div>
300        </div>
301      </CardHeader>
302      <CardContent className="space-y-4">
303        <div className="grid grid-cols-3 gap-4 text-sm">
304          <div className="text-center">
305            <div className="font-semibold">{parent.cpu}</div>
306            <div className="text-muted-foreground">CPU Cores</div>
307          </div>
308          <div className="text-center">
309            <div className="font-semibold">{parent.ram}GB</div>
310            <div className="text-muted-foreground">RAM</div>
311          </div>
312          <div className="text-center">
313            <div className="font-semibold">{parent.storage}GB</div>
314            <div className="text-muted-foreground">Storage</div>
315          </div>
316        </div>
317
318        <div className="space-y-3">
319          <UsageIndicator
320            label="CPU"
321            value={parent.usage.cpu}
322            max={100}
323            unit="%"
324          />
325          <UsageIndicator
326            label="RAM"
327            value={parent.usage.ram}
328            max={100}
329            unit="%"
330          />
331          <UsageIndicator
332            label="Storage"
333            value={parent.usage.storage}
334            max={100}
335            unit="%"
336          />
337        </div>
338
339        <div className="flex items-center justify-between text-sm text-muted-foreground">
340          <span>IP: {parent.ipAddress}</span>
341          <span>Created: {parent.createdAt}</span>
342        </div>
343      </CardContent>
344      <CardFooter>
345        <Button
346          variant="outline"
347          className="w-full"
348          onClick={() => onViewShared(parent.id)}
349        >
350          <Users className="h-4 w-4 mr-2" />
351          View Shared VPS ({sharedCount})
352        </Button>
353      </CardFooter>
354    </Card>
355  );
356};
357
358const SharedVPSCard = ({
359  vps,
360  onEdit,
361  onDelete,
362  onStart,
363  onStop,
364}: {
365  vps: SharedVPS;
366  onEdit: (vps: SharedVPS) => void;
367  onDelete: (id: string) => void;
368  onStart: (id: string) => void;
369  onStop: (id: string) => void;
370}) => {
371  return (
372    <Card className="hover:shadow-lg transition-shadow">
373      <CardHeader className="pb-3">
374        <div className="flex items-center justify-between">
375          <div className="flex items-center gap-3">
376            <div className="p-2 bg-green-500/10 rounded-lg">
377              <Globe className="h-5 w-5 text-green-500" />
378            </div>
379            <div>
380              <CardTitle className="text-lg">{vps.name}</CardTitle>
381              <CardDescription>{vps.customer}</CardDescription>
382            </div>
383          </div>
384          <div className="flex items-center gap-2">
385            <StatusBadge status={vps.status} />
386            <Button
387              variant="ghost"
388              size="sm"
389              onClick={() =>
390                vps.status === "running" ? onStop(vps.id) : onStart(vps.id)
391              }
392            >
393              {vps.status === "running" ? (
394                <Square className="h-4 w-4" />
395              ) : (
396                <Play className="h-4 w-4" />
397              )}
398            </Button>
399            <Button variant="ghost" size="sm" onClick={() => onEdit(vps)}>
400              <Edit className="h-4 w-4" />
401            </Button>
402            <Button variant="ghost" size="sm" onClick={() => onDelete(vps.id)}>
403              <Trash2 className="h-4 w-4" />
404            </Button>
405          </div>
406        </div>
407      </CardHeader>
408      <CardContent className="space-y-4">
409        <div className="grid grid-cols-3 gap-4 text-sm">
410          <div className="text-center">
411            <div className="font-semibold">{vps.cpu}</div>
412            <div className="text-muted-foreground">CPU Cores</div>
413          </div>
414          <div className="text-center">
415            <div className="font-semibold">{vps.ram}GB</div>
416            <div className="text-muted-foreground">RAM</div>
417          </div>
418          <div className="text-center">
419            <div className="font-semibold">{vps.storage}GB</div>
420            <div className="text-muted-foreground">Storage</div>
421          </div>
422        </div>
423
424        <div className="space-y-3">
425          <UsageIndicator
426            label="CPU"
427            value={vps.usage.cpu}
428            max={100}
429            unit="%"
430          />
431          <UsageIndicator
432            label="RAM"
433            value={vps.usage.ram}
434            max={100}
435            unit="%"
436          />
437          <UsageIndicator
438            label="Storage"
439            value={vps.usage.storage}
440            max={100}
441            unit="%"
442          />
443        </div>
444
445        {vps.domain && (
446          <div className="text-sm text-muted-foreground">
447            Domain: {vps.domain}
448          </div>
449        )}
450      </CardContent>
451    </Card>
452  );
453};
454
455const DedicatedVPSCard = ({
456  vps,
457  onEdit,
458  onDelete,
459  onStart,
460  onStop,
461}: {
462  vps: DedicatedVPS;
463  onEdit: (vps: DedicatedVPS) => void;
464  onDelete: (id: string) => void;
465  onStart: (id: string) => void;
466  onStop: (id: string) => void;
467}) => {
468  return (
469    <Card className="hover:shadow-lg transition-shadow">
470      <CardHeader className="pb-3">
471        <div className="flex items-center justify-between">
472          <div className="flex items-center gap-3">
473            <div className="p-2 bg-purple-500/10 rounded-lg">
474              <Shield className="h-5 w-5 text-purple-500" />
475            </div>
476            <div>
477              <CardTitle className="text-lg">{vps.name}</CardTitle>
478              <CardDescription className="flex items-center gap-1">
479                <MapPin className="h-3 w-3" />
480                {vps.region}
481              </CardDescription>
482            </div>
483          </div>
484          <div className="flex items-center gap-2">
485            <StatusBadge status={vps.status} />
486            <Button
487              variant="ghost"
488              size="sm"
489              onClick={() =>
490                vps.status === "running" ? onStop(vps.id) : onStart(vps.id)
491              }
492            >
493              {vps.status === "running" ? (
494                <Square className="h-4 w-4" />
495              ) : (
496                <Play className="h-4 w-4" />
497              )}
498            </Button>
499            <Button variant="ghost" size="sm" onClick={() => onEdit(vps)}>
500              <Edit className="h-4 w-4" />
501            </Button>
502            <Button variant="ghost" size="sm" onClick={() => onDelete(vps.id)}>
503              <Trash2 className="h-4 w-4" />
504            </Button>
505          </div>
506        </div>
507      </CardHeader>
508      <CardContent className="space-y-4">
509        <div className="grid grid-cols-3 gap-4 text-sm">
510          <div className="text-center">
511            <div className="font-semibold">{vps.cpu}</div>
512            <div className="text-muted-foreground">CPU Cores</div>
513          </div>
514          <div className="text-center">
515            <div className="font-semibold">{vps.ram}GB</div>
516            <div className="text-muted-foreground">RAM</div>
517          </div>
518          <div className="text-center">
519            <div className="font-semibold">{vps.storage}GB</div>
520            <div className="text-muted-foreground">Storage</div>
521          </div>
522        </div>
523
524        <div className="space-y-3">
525          <UsageIndicator
526            label="CPU"
527            value={vps.usage.cpu}
528            max={100}
529            unit="%"
530          />
531          <UsageIndicator
532            label="RAM"
533            value={vps.usage.ram}
534            max={100}
535            unit="%"
536          />
537          <UsageIndicator
538            label="Storage"
539            value={vps.usage.storage}
540            max={100}
541            unit="%"
542          />
543        </div>
544
545        <div className="space-y-1 text-sm text-muted-foreground">
546          <div>Customer: {vps.customer}</div>
547          <div>IP: {vps.ipAddress}</div>
548          {vps.domain && <div>Domain: {vps.domain}</div>}
549        </div>
550      </CardContent>
551    </Card>
552  );
553};
554
555const CreateVPSParentDialog = ({
556  open,
557  onOpenChange,
558  onSubmit,
559}: {
560  open: boolean;
561  onOpenChange: (open: boolean) => void;
562  onSubmit: (
563    data: Omit<VPSParent, "id" | "sharedVPS" | "dedicatedVPS" | "usage">
564  ) => void;
565}) => {
566  const [formData, setFormData] = useState({
567    name: "",
568    region: "",
569    cpu: 8,
570    ram: 32,
571    storage: 500,
572    ipAddress: "",
573  });
574
575  const handleSubmit = (e: React.FormEvent) => {
576    e.preventDefault();
577    onSubmit({
578      ...formData,
579      status: "online" as const,
580      createdAt: new Date().toISOString().split("T")[0],
581    });
582    setFormData({
583      name: "",
584      region: "",
585      cpu: 8,
586      ram: 32,
587      storage: 500,
588      ipAddress: "",
589    });
590    onOpenChange(false);
591  };
592
593  return (
594    <Dialog open={open} onOpenChange={onOpenChange}>
595      <DialogContent className="max-w-md">
596        <DialogHeader>
597          <DialogTitle>Create VPS Parent</DialogTitle>
598          <DialogDescription>
599            Create a new VPS parent server that can host multiple shared VPS
600            instances.
601          </DialogDescription>
602        </DialogHeader>
603        <form onSubmit={handleSubmit} className="space-y-4">
604          <div>
605            <label className="text-sm font-medium">Name</label>
606            <Input
607              value={formData.name}
608              onChange={(e) =>
609                setFormData((prev) => ({ ...prev, name: e.target.value }))
610              }
611              placeholder="e.g., US-East-1-Parent"
612              required
613            />
614          </div>
615          <div>
616            <label className="text-sm font-medium">Region</label>
617            <select
618              value={formData.region}
619              onChange={(e) =>
620                setFormData((prev) => ({ ...prev, region: e.target.value }))
621              }
622              className="w-full p-2 border rounded-md"
623              required
624            >
625              <option value="">Select Region</option>
626              {regions.map((region) => (
627                <option key={region} value={region}>
628                  {region}
629                </option>
630              ))}
631            </select>
632          </div>
633          <div className="grid grid-cols-3 gap-4">
634            <div>
635              <label className="text-sm font-medium">CPU Cores</label>
636              <Input
637                type="number"
638                value={formData.cpu}
639                onChange={(e) =>
640                  setFormData((prev) => ({
641                    ...prev,
642                    cpu: parseInt(e.target.value),
643                  }))
644                }
645                min="1"
646                required
647              />
648            </div>
649            <div>
650              <label className="text-sm font-medium">RAM (GB)</label>
651              <Input
652                type="number"
653                value={formData.ram}
654                onChange={(e) =>
655                  setFormData((prev) => ({
656                    ...prev,
657                    ram: parseInt(e.target.value),
658                  }))
659                }
660                min="1"
661                required
662              />
663            </div>
664            <div>
665              <label className="text-sm font-medium">Storage (GB)</label>
666              <Input
667                type="number"
668                value={formData.storage}
669                onChange={(e) =>
670                  setFormData((prev) => ({
671                    ...prev,
672                    storage: parseInt(e.target.value),
673                  }))
674                }
675                min="1"
676                required
677              />
678            </div>
679          </div>
680          <div>
681            <label className="text-sm font-medium">IP Address</label>
682            <Input
683              value={formData.ipAddress}
684              onChange={(e) =>
685                setFormData((prev) => ({ ...prev, ipAddress: e.target.value }))
686              }
687              placeholder="e.g., 192.168.1.100"
688              required
689            />
690          </div>
691          <DialogFooter>
692            <Button
693              type="button"
694              variant="outline"
695              onClick={() => onOpenChange(false)}
696            >
697              Cancel
698            </Button>
699            <Button type="submit">Create VPS Parent</Button>
700          </DialogFooter>
701        </form>
702      </DialogContent>
703    </Dialog>
704  );
705};
706
707const CreateSharedVPSDialog = ({
708  open,
709  onOpenChange,
710  onSubmit,
711  vpsParents,
712}: {
713  open: boolean;
714  onOpenChange: (open: boolean) => void;
715  onSubmit: (data: Omit<SharedVPS, "id" | "usage">) => void;
716  vpsParents: VPSParent[];
717}) => {
718  const [formData, setFormData] = useState({
719    name: "",
720    parentId: "",
721    customer: "",
722    cpu: 2,
723    ram: 4,
724    storage: 50,
725    domain: "",
726  });
727
728  const handleSubmit = (e: React.FormEvent) => {
729    e.preventDefault();
730    onSubmit({
731      ...formData,
732      status: "running" as const,
733      createdAt: new Date().toISOString().split("T")[0],
734    });
735    setFormData({
736      name: "",
737      parentId: "",
738      customer: "",
739      cpu: 2,
740      ram: 4,
741      storage: 50,
742      domain: "",
743    });
744    onOpenChange(false);
745  };
746
747  return (
748    <Dialog open={open} onOpenChange={onOpenChange}>
749      <DialogContent className="max-w-md">
750        <DialogHeader>
751          <DialogTitle>Create Shared VPS</DialogTitle>
752          <DialogDescription>
753            Create a new shared VPS instance under a VPS parent server.
754          </DialogDescription>
755        </DialogHeader>
756        <form onSubmit={handleSubmit} className="space-y-4">
757          <div>
758            <label className="text-sm font-medium">Name</label>
759            <Input
760              value={formData.name}
761              onChange={(e) =>
762                setFormData((prev) => ({ ...prev, name: e.target.value }))
763              }
764              placeholder="e.g., WebStore-Alpha"
765              required
766            />
767          </div>
768          <div>
769            <label className="text-sm font-medium">VPS Parent</label>
770            <select
771              value={formData.parentId}
772              onChange={(e) =>
773                setFormData((prev) => ({ ...prev, parentId: e.target.value }))
774              }
775              className="w-full p-2 border rounded-md"
776              required
777            >
778              <option value="">Select VPS Parent</option>
779              {vpsParents.map((parent) => (
780                <option key={parent.id} value={parent.id}>
781                  {parent.name}
782                </option>
783              ))}
784            </select>
785          </div>
786          <div>
787            <label className="text-sm font-medium">Customer</label>
788            <Input
789              value={formData.customer}
790              onChange={(e) =>
791                setFormData((prev) => ({ ...prev, customer: e.target.value }))
792              }
793              placeholder="Customer name"
794              required
795            />
796          </div>
797          <div className="grid grid-cols-3 gap-4">
798            <div>
799              <label className="text-sm font-medium">CPU Cores</label>
800              <Input
801                type="number"
802                value={formData.cpu}
803                onChange={(e) =>
804                  setFormData((prev) => ({
805                    ...prev,
806                    cpu: parseInt(e.target.value),
807                  }))
808                }
809                min="1"
810                required
811              />
812            </div>
813            <div>
814              <label className="text-sm font-medium">RAM (GB)</label>
815              <Input
816                type="number"
817                value={formData.ram}
818                onChange={(e) =>
819                  setFormData((prev) => ({
820                    ...prev,
821                    ram: parseInt(e.target.value),
822                  }))
823                }
824                min="1"
825                required
826              />
827            </div>
828            <div>
829              <label className="text-sm font-medium">Storage (GB)</label>
830              <Input
831                type="number"
832                value={formData.storage}
833                onChange={(e) =>
834                  setFormData((prev) => ({
835                    ...prev,
836                    storage: parseInt(e.target.value),
837                  }))
838                }
839                min="1"
840                required
841              />
842            </div>
843          </div>
844          <div>
845            <label className="text-sm font-medium">Domain (Optional)</label>
846            <Input
847              value={formData.domain}
848              onChange={(e) =>
849                setFormData((prev) => ({ ...prev, domain: e.target.value }))
850              }
851              placeholder="e.g., example.com"
852            />
853          </div>
854          <DialogFooter>
855            <Button
856              type="button"
857              variant="outline"
858              onClick={() => onOpenChange(false)}
859            >
860              Cancel
861            </Button>
862            <Button type="submit">Create Shared VPS</Button>
863          </DialogFooter>
864        </form>
865      </DialogContent>
866    </Dialog>
867  );
868};
869
870const CreateDedicatedVPSDialog = ({
871  open,
872  onOpenChange,
873  onSubmit,
874}: {
875  open: boolean;
876  onOpenChange: (open: boolean) => void;
877  onSubmit: (data: Omit<DedicatedVPS, "id" | "usage">) => void;
878}) => {
879  const [formData, setFormData] = useState({
880    name: "",
881    customer: "",
882    region: "",
883    cpu: 8,
884    ram: 32,
885    storage: 500,
886    ipAddress: "",
887    domain: "",
888  });
889
890  const handleSubmit = (e: React.FormEvent) => {
891    e.preventDefault();
892    onSubmit({
893      ...formData,
894      status: "running" as const,
895      createdAt: new Date().toISOString().split("T")[0],
896    });
897    setFormData({
898      name: "",
899      customer: "",
900      region: "",
901      cpu: 8,
902      ram: 32,
903      storage: 500,
904      ipAddress: "",
905      domain: "",
906    });
907    onOpenChange(false);
908  };
909
910  return (
911    <Dialog open={open} onOpenChange={onOpenChange}>
912      <DialogContent className="max-w-md">
913        <DialogHeader>
914          <DialogTitle>Create Dedicated VPS</DialogTitle>
915          <DialogDescription>
916            Create a new dedicated VPS instance with full server resources.
917          </DialogDescription>
918        </DialogHeader>
919        <form onSubmit={handleSubmit} className="space-y-4">
920          <div>
921            <label className="text-sm font-medium">Name</label>
922            <Input
923              value={formData.name}
924              onChange={(e) =>
925                setFormData((prev) => ({ ...prev, name: e.target.value }))
926              }
927              placeholder="e.g., Enterprise-Store-1"
928              required
929            />
930          </div>
931          <div>
932            <label className="text-sm font-medium">Customer</label>
933            <Input
934              value={formData.customer}
935              onChange={(e) =>
936                setFormData((prev) => ({ ...prev, customer: e.target.value }))
937              }
938              placeholder="Customer name"
939              required
940            />
941          </div>
942          <div>
943            <label className="text-sm font-medium">Region</label>
944            <select
945              value={formData.region}
946              onChange={(e) =>
947                setFormData((prev) => ({ ...prev, region: e.target.value }))
948              }
949              className="w-full p-2 border rounded-md"
950              required
951            >
952              <option value="">Select Region</option>
953              {regions.map((region) => (
954                <option key={region} value={region}>
955                  {region}
956                </option>
957              ))}
958            </select>
959          </div>
960          <div className="grid grid-cols-3 gap-4">
961            <div>
962              <label className="text-sm font-medium">CPU Cores</label>
963              <Input
964                type="number"
965                value={formData.cpu}
966                onChange={(e) =>
967                  setFormData((prev) => ({
968                    ...prev,
969                    cpu: parseInt(e.target.value),
970                  }))
971                }
972                min="1"
973                required
974              />
975            </div>
976            <div>
977              <label className="text-sm font-medium">RAM (GB)</label>
978              <Input
979                type="number"
980                value={formData.ram}
981                onChange={(e) =>
982                  setFormData((prev) => ({
983                    ...prev,
984                    ram: parseInt(e.target.value),
985                  }))
986                }
987                min="1"
988                required
989              />
990            </div>
991            <div>
992              <label className="text-sm font-medium">Storage (GB)</label>
993              <Input
994                type="number"
995                value={formData.storage}
996                onChange={(e) =>
997                  setFormData((prev) => ({
998                    ...prev,
999                    storage: parseInt(e.target.value),
1000                  }))
1001                }
1002                min="1"
1003                required
1004              />
1005            </div>
1006          </div>
1007          <div>
1008            <label className="text-sm font-medium">IP Address</label>
1009            <Input
1010              value={formData.ipAddress}
1011              onChange={(e) =>
1012                setFormData((prev) => ({ ...prev, ipAddress: e.target.value }))
1013              }
1014              placeholder="e.g., 203.0.113.10"
1015              required
1016            />
1017          </div>
1018          <div>
1019            <label className="text-sm font-medium">Domain (Optional)</label>
1020            <Input
1021              value={formData.domain}
1022              onChange={(e) =>
1023                setFormData((prev) => ({ ...prev, domain: e.target.value }))
1024              }
1025              placeholder="e.g., example.com"
1026            />
1027          </div>
1028          <DialogFooter>
1029            <Button
1030              type="button"
1031              variant="outline"
1032              onClick={() => onOpenChange(false)}
1033            >
1034              Cancel
1035            </Button>
1036            <Button type="submit">Create Dedicated VPS</Button>
1037          </DialogFooter>
1038        </form>
1039      </DialogContent>
1040    </Dialog>
1041  );
1042};
1043
1044const VPSAdminDashboard = () => {
1045  const [vpsParents, setVpsParents] = useState<VPSParent[]>(mockVPSParents);
1046  const [sharedVPS, setSharedVPS] = useState<SharedVPS[]>(mockSharedVPS);
1047  const [dedicatedVPS, setDedicatedVPS] =
1048    useState<DedicatedVPS[]>(mockDedicatedVPS);
1049  const [activeTab, setActiveTab] = useState("overview");
1050  const [searchTerm, setSearchTerm] = useState("");
1051  const [selectedParentId, setSelectedParentId] = useState<string | null>(null);
1052
1053  const [createParentOpen, setCreateParentOpen] = useState(false);
1054  const [createSharedOpen, setCreateSharedOpen] = useState(false);
1055  const [createDedicatedOpen, setCreateDedicatedOpen] = useState(false);
1056
1057  const handleCreateVPSParent = (
1058    data: Omit<VPSParent, "id" | "sharedVPS" | "dedicatedVPS" | "usage">
1059  ) => {
1060    const newParent: VPSParent = {
1061      ...data,
1062      id: `parent-${Date.now()}`,
1063      sharedVPS: [],
1064      dedicatedVPS: [],
1065      usage: {
1066        cpu: Math.floor(Math.random() * 50),
1067        ram: Math.floor(Math.random() * 50),
1068        storage: Math.floor(Math.random() * 50),
1069      },
1070    };
1071    setVpsParents((prev) => [...prev, newParent]);
1072  };
1073
1074  const handleCreateSharedVPS = (data: Omit<SharedVPS, "id" | "usage">) => {
1075    const newSharedVPS: SharedVPS = {
1076      ...data,
1077      id: `shared-${Date.now()}`,
1078      usage: {
1079        cpu: Math.floor(Math.random() * 50),
1080        ram: Math.floor(Math.random() * 50),
1081        storage: Math.floor(Math.random() * 50),
1082      },
1083    };
1084    setSharedVPS((prev) => [...prev, newSharedVPS]);
1085  };
1086
1087  const handleCreateDedicatedVPS = (
1088    data: Omit<DedicatedVPS, "id" | "usage">
1089  ) => {
1090    const newDedicatedVPS: DedicatedVPS = {
1091      ...data,
1092      id: `dedicated-${Date.now()}`,
1093      usage: {
1094        cpu: Math.floor(Math.random() * 50),
1095        ram: Math.floor(Math.random() * 50),
1096        storage: Math.floor(Math.random() * 50),
1097      },
1098    };
1099    setDedicatedVPS((prev) => [...prev, newDedicatedVPS]);
1100  };
1101
1102  const handleDeleteVPSParent = (id: string) => {
1103    setVpsParents((prev) => prev.filter((p) => p.id !== id));
1104    setSharedVPS((prev) => prev.filter((s) => s.parentId !== id));
1105  };
1106
1107  const handleDeleteSharedVPS = (id: string) => {
1108    setSharedVPS((prev) => prev.filter((s) => s.id !== id));
1109  };
1110
1111  const handleDeleteDedicatedVPS = (id: string) => {
1112    setDedicatedVPS((prev) => prev.filter((d) => d.id !== id));
1113  };
1114
1115  const handleStartVPS = (id: string, type: "shared" | "dedicated") => {
1116    if (type === "shared") {
1117      setSharedVPS((prev) =>
1118        prev.map((s) => (s.id === id ? { ...s, status: "running" } : s))
1119      );
1120    } else {
1121      setDedicatedVPS((prev) =>
1122        prev.map((d) => (d.id === id ? { ...d, status: "running" } : d))
1123      );
1124    }
1125  };
1126
1127  const handleStopVPS = (id: string, type: "shared" | "dedicated") => {
1128    if (type === "shared") {
1129      setSharedVPS((prev) =>
1130        prev.map((s) => (s.id === id ? { ...s, status: "stopped" } : s))
1131      );
1132    } else {
1133      setDedicatedVPS((prev) =>
1134        prev.map((d) => (d.id === id ? { ...d, status: "stopped" } : d))
1135      );
1136    }
1137  };
1138
1139  const getSharedVPSForParent = (parentId: string) => {
1140    return sharedVPS.filter((s) => s.parentId === parentId);
1141  };
1142
1143  const filteredVPSParents = vpsParents.filter(
1144    (parent) =>
1145      parent.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
1146      parent.region.toLowerCase().includes(searchTerm.toLowerCase())
1147  );
1148
1149  const filteredSharedVPS = selectedParentId
1150    ? getSharedVPSForParent(selectedParentId)
1151    : sharedVPS.filter(
1152        (vps) =>
1153          vps.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
1154          vps.customer.toLowerCase().includes(searchTerm.toLowerCase())
1155      );
1156
1157  const filteredDedicatedVPS = dedicatedVPS.filter(
1158    (vps) =>
1159      vps.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
1160      vps.customer.toLowerCase().includes(searchTerm.toLowerCase()) ||
1161      vps.region.toLowerCase().includes(searchTerm.toLowerCase())
1162  );
1163
1164  const totalStats = {
1165    vpsParents: vpsParents.length,
1166    sharedVPS: sharedVPS.length,
1167    dedicatedVPS: dedicatedVPS.length,
1168    totalCustomers: new Set([
1169      ...sharedVPS.map((s) => s.customer),
1170      ...dedicatedVPS.map((d) => d.customer),
1171    ]).size,
1172  };
1173
1174  return (
1175    <div className="min-h-screen bg-background">
1176      <div className="border-b">
1177        <div className="container mx-auto px-4 py-4">
1178          <div className="flex items-center justify-between">
1179            <div>
1180              <h1 className="text-3xl font-bold">VPS Management Dashboard</h1>
1181              <p className="text-muted-foreground">
1182                Manage and monitor your VPS infrastructure
1183              </p>
1184            </div>
1185            <div className="flex items-center gap-2">
1186              <Button variant="outline" size="sm">
1187                <RefreshCw className="h-4 w-4 mr-2" />
1188                Refresh
1189              </Button>
1190              <Button variant="outline" size="sm">
1191                <Settings className="h-4 w-4 mr-2" />
1192                Settings
1193              </Button>
1194            </div>
1195          </div>
1196        </div>
1197      </div>
1198
1199      <div className="container mx-auto px-4 py-6">
1200        <Tabs
1201          value={activeTab}
1202          onValueChange={setActiveTab}
1203          className="space-y-6"
1204        >
1205          <TabsList className="grid w-full grid-cols-4">
1206            <TabsTrigger value="overview">Overview</TabsTrigger>
1207            <TabsTrigger value="vps-parents">VPS Parents</TabsTrigger>
1208            <TabsTrigger value="shared-vps">Shared VPS</TabsTrigger>
1209            <TabsTrigger value="dedicated-vps">Dedicated VPS</TabsTrigger>
1210          </TabsList>
1211
1212          <TabsContent value="overview" className="space-y-6">
1213            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
1214              <Card>
1215                <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1216                  <CardTitle className="text-sm font-medium">
1217                    VPS Parents
1218                  </CardTitle>
1219                  <Server className="h-4 w-4 text-muted-foreground" />
1220                </CardHeader>
1221                <CardContent>
1222                  <div className="text-2xl font-bold">
1223                    {totalStats.vpsParents}
1224                  </div>
1225                  <p className="text-xs text-muted-foreground">
1226                    Physical servers
1227                  </p>
1228                </CardContent>
1229              </Card>
1230              <Card>
1231                <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1232                  <CardTitle className="text-sm font-medium">
1233                    Shared VPS
1234                  </CardTitle>
1235                  <Globe className="h-4 w-4 text-muted-foreground" />
1236                </CardHeader>
1237                <CardContent>
1238                  <div className="text-2xl font-bold">
1239                    {totalStats.sharedVPS}
1240                  </div>
1241                  <p className="text-xs text-muted-foreground">
1242                    Shared instances
1243                  </p>
1244                </CardContent>
1245              </Card>
1246              <Card>
1247                <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1248                  <CardTitle className="text-sm font-medium">
1249                    Dedicated VPS
1250                  </CardTitle>
1251                  <Shield className="h-4 w-4 text-muted-foreground" />
1252                </CardHeader>
1253                <CardContent>
1254                  <div className="text-2xl font-bold">
1255                    {totalStats.dedicatedVPS}
1256                  </div>
1257                  <p className="text-xs text-muted-foreground">
1258                    Dedicated instances
1259                  </p>
1260                </CardContent>
1261              </Card>
1262              <Card>
1263                <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
1264                  <CardTitle className="text-sm font-medium">
1265                    Total Customers
1266                  </CardTitle>
1267                  <Users className="h-4 w-4 text-muted-foreground" />
1268                </CardHeader>
1269                <CardContent>
1270                  <div className="text-2xl font-bold">
1271                    {totalStats.totalCustomers}
1272                  </div>
1273                  <p className="text-xs text-muted-foreground">
1274                    Active customers
1275                  </p>
1276                </CardContent>
1277              </Card>
1278            </div>
1279
1280            <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
1281              <Card>
1282                <CardHeader>
1283                  <CardTitle>Recent Activity</CardTitle>
1284                </CardHeader>
1285                <CardContent>
1286                  <div className="space-y-4">
1287                    <div className="flex items-center gap-3">
1288                      <div className="p-2 bg-green-500/10 rounded-lg">
1289                        <CheckCircle className="h-4 w-4 text-green-500" />
1290                      </div>
1291                      <div className="flex-1">
1292                        <p className="text-sm font-medium">
1293                          WebStore-Alpha started
1294                        </p>
1295                        <p className="text-xs text-muted-foreground">
1296                          2 minutes ago
1297                        </p>
1298                      </div>
1299                    </div>
1300                    <div className="flex items-center gap-3">
1301                      <div className="p-2 bg-blue-500/10 rounded-lg">
1302                        <Plus className="h-4 w-4 text-blue-500" />
1303                      </div>
1304                      <div className="flex-1">
1305                        <p className="text-sm font-medium">
1306                          New VPS Parent created
1307                        </p>
1308                        <p className="text-xs text-muted-foreground">
1309                          1 hour ago
1310                        </p>
1311                      </div>
1312                    </div>
1313                    <div className="flex items-center gap-3">
1314                      <div className="p-2 bg-yellow-500/10 rounded-lg">
1315                        <AlertTriangle className="h-4 w-4 text-yellow-500" />
1316                      </div>
1317                      <div className="flex-1">
1318                        <p className="text-sm font-medium">
1319                          High CPU usage detected
1320                        </p>
1321                        <p className="text-xs text-muted-foreground">
1322                          3 hours ago
1323                        </p>
1324                      </div>
1325                    </div>
1326                  </div>
1327                </CardContent>
1328              </Card>
1329
1330              <Card>
1331                <CardHeader>
1332                  <CardTitle>System Health</CardTitle>
1333                </CardHeader>
1334                <CardContent>
1335                  <div className="space-y-4">
1336                    <div>
1337                      <div className="flex justify-between text-sm mb-1">
1338                        <span>Overall CPU Usage</span>
1339                        <span>65%</span>
1340                      </div>
1341                      <Progress value={65} className="h-2" />
1342                    </div>
1343                    <div>
1344                      <div className="flex justify-between text-sm mb-1">
1345                        <span>Memory Usage</span>
1346                        <span>78%</span>
1347                      </div>
1348                      <Progress value={78} className="h-2" />
1349                    </div>
1350                    <div>
1351                      <div className="flex justify-between text-sm mb-1">
1352                        <span>Storage Usage</span>
1353                        <span>45%</span>
1354                      </div>
1355                      <Progress value={45} className="h-2" />
1356                    </div>
1357                    <div>
1358                      <div className="flex justify-between text-sm mb-1">
1359                        <span>Network I/O</span>
1360                        <span>32%</span>
1361                      </div>
1362                      <Progress value={32} className="h-2" />
1363                    </div>
1364                  </div>
1365                </CardContent>
1366              </Card>
1367            </div>
1368          </TabsContent>
1369
1370          <TabsContent value="vps-parents" className="space-y-6">
1371            <div className="flex items-center justify-between">
1372              <div className="flex items-center gap-4">
1373                <div className="relative">
1374                  <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
1375                  <Input
1376                    placeholder="Search VPS Parents..."
1377                    value={searchTerm}
1378                    onChange={(e) => setSearchTerm(e.target.value)}
1379                    className="pl-10 w-80"
1380                  />
1381                </div>
1382                <Button variant="outline" size="sm">
1383                  <Filter className="h-4 w-4 mr-2" />
1384                  Filter
1385                </Button>
1386              </div>
1387              <Button onClick={() => setCreateParentOpen(true)}>
1388                <Plus className="h-4 w-4 mr-2" />
1389                Add VPS Parent
1390              </Button>
1391            </div>
1392
1393            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1394              {filteredVPSParents.map((parent) => (
1395                <VPSParentCard
1396                  key={parent.id}
1397                  parent={parent}
1398                  onEdit={() => {}}
1399                  onDelete={handleDeleteVPSParent}
1400                  onViewShared={(parentId) => {
1401                    setSelectedParentId(parentId);
1402                    setActiveTab("shared-vps");
1403                  }}
1404                  sharedCount={getSharedVPSForParent(parent.id).length}
1405                />
1406              ))}
1407            </div>
1408          </TabsContent>
1409
1410          <TabsContent value="shared-vps" className="space-y-6">
1411            <div className="flex items-center justify-between">
1412              <div className="flex items-center gap-4">
1413                <div className="relative">
1414                  <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
1415                  <Input
1416                    placeholder="Search Shared VPS..."
1417                    value={searchTerm}
1418                    onChange={(e) => setSearchTerm(e.target.value)}
1419                    className="pl-10 w-80"
1420                  />
1421                </div>
1422                {selectedParentId && (
1423                  <Button
1424                    variant="outline"
1425                    size="sm"
1426                    onClick={() => setSelectedParentId(null)}
1427                  >
1428                    <X className="h-4 w-4 mr-2" />
1429                    Clear Filter
1430                  </Button>
1431                )}
1432              </div>
1433              <Button onClick={() => setCreateSharedOpen(true)}>
1434                <Plus className="h-4 w-4 mr-2" />
1435                Add Shared VPS
1436              </Button>
1437            </div>
1438
1439            {selectedParentId && (
1440              <div className="bg-muted/50 p-4 rounded-lg">
1441                <p className="text-sm text-muted-foreground">
1442                  Showing Shared VPS for:{" "}
1443                  <strong>
1444                    {vpsParents.find((p) => p.id === selectedParentId)?.name}
1445                  </strong>
1446                </p>
1447              </div>
1448            )}
1449
1450            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1451              {filteredSharedVPS.map((vps) => (
1452                <SharedVPSCard
1453                  key={vps.id}
1454                  vps={vps}
1455                  onEdit={() => {}}
1456                  onDelete={handleDeleteSharedVPS}
1457                  onStart={(id) => handleStartVPS(id, "shared")}
1458                  onStop={(id) => handleStopVPS(id, "shared")}
1459                />
1460              ))}
1461            </div>
1462          </TabsContent>
1463
1464          <TabsContent value="dedicated-vps" className="space-y-6">
1465            <div className="flex items-center justify-between">
1466              <div className="flex items-center gap-4">
1467                <div className="relative">
1468                  <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
1469                  <Input
1470                    placeholder="Search Dedicated VPS..."
1471                    value={searchTerm}
1472                    onChange={(e) => setSearchTerm(e.target.value)}
1473                    className="pl-10 w-80"
1474                  />
1475                </div>
1476                <Button variant="outline" size="sm">
1477                  <Filter className="h-4 w-4 mr-2" />
1478                  Filter
1479                </Button>
1480              </div>
1481              <Button onClick={() => setCreateDedicatedOpen(true)}>
1482                <Plus className="h-4 w-4 mr-2" />
1483                Add Dedicated VPS
1484              </Button>
1485            </div>
1486
1487            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
1488              {filteredDedicatedVPS.map((vps) => (
1489                <DedicatedVPSCard
1490                  key={vps.id}
1491                  vps={vps}
1492                  onEdit={() => {}}
1493                  onDelete={handleDeleteDedicatedVPS}
1494                  onStart={(id) => handleStartVPS(id, "dedicated")}
1495                  onStop={(id) => handleStopVPS(id, "dedicated")}
1496                />
1497              ))}
1498            </div>
1499          </TabsContent>
1500        </Tabs>
1501      </div>
1502
1503      <CreateVPSParentDialog
1504        open={createParentOpen}
1505        onOpenChange={setCreateParentOpen}
1506        onSubmit={handleCreateVPSParent}
1507      />
1508
1509      <CreateSharedVPSDialog
1510        open={createSharedOpen}
1511        onOpenChange={setCreateSharedOpen}
1512        onSubmit={handleCreateSharedVPS}
1513        vpsParents={vpsParents}
1514      />
1515
1516      <CreateDedicatedVPSDialog
1517        open={createDedicatedOpen}
1518        onOpenChange={setCreateDedicatedOpen}
1519        onSubmit={handleCreateDedicatedVPS}
1520      />
1521    </div>
1522  );
1523};
1524
1525export default VPSAdminDashboard;

Dependencies

External Libraries

framer-motionlucide-reactreact

Shadcn/UI Components

avatarbadgebuttoncarddialoginputprogressscroll-areatabletabs

Local Components

/lib/utils

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.