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