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