Preview
Full width desktop view
Code
monitoring-11.tsx
1"use client";
2
3import React, { useState, useEffect } from "react";
4import { cn } from "@/lib/utils";
5import {
6 Server,
7 Plus,
8 Edit,
9 Trash2,
10 Power,
11 PowerOff,
12 Activity,
13 HardDrive,
14 Cpu,
15 MemoryStick,
16 Globe,
17 Users,
18 Calendar,
19 DollarSign,
20 Search,
21 Filter,
22 MoreHorizontal,
23 CheckCircle,
24 XCircle,
25 AlertCircle,
26 Settings,
27 Eye,
28 Download,
29 Upload,
30} from "lucide-react";
31import { Button } from "@/components/ui/button";
32import { Input } from "@/components/ui/input";
33import { Label } from "@/components/ui/label";
34import {
35 Card,
36 CardContent,
37 CardDescription,
38 CardHeader,
39 CardTitle,
40} from "@/components/ui/card";
41import { Badge } from "@/components/ui/badge";
42import {
43 Select,
44 SelectContent,
45 SelectItem,
46 SelectTrigger,
47 SelectValue,
48} from "@/components/ui/select";
49import { Textarea } from "@/components/ui/textarea";
50import { Switch } from "@/components/ui/switch";
51import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
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 {
70 DropdownMenu,
71 DropdownMenuContent,
72 DropdownMenuItem,
73 DropdownMenuTrigger,
74} from "@/components/ui/dropdown-menu";
75import { Progress } from "@/components/ui/progress";
76import { Separator } from "@/components/ui/separator";
77
78// Types
79interface VPSInstance {
80 id: string;
81 name: string;
82 type: "shared" | "dedicated";
83 status: "running" | "stopped" | "maintenance" | "error";
84 ipAddress: string;
85 domain?: string;
86 plan: string;
87 cpu: number;
88 ram: number;
89 storage: number;
90 bandwidth: number;
91 monthlyPrice: number;
92 createdAt: string;
93 lastUpdated: string;
94 customerId: string;
95 customerName: string;
96 customerEmail: string;
97 location: string;
98 os: string;
99 cpuUsage: number;
100 ramUsage: number;
101 storageUsage: number;
102 uptime: number;
103}
104
105interface Customer {
106 id: string;
107 name: string;
108 email: string;
109 company?: string;
110 phone?: string;
111 createdAt: string;
112}
113
114// Mock data
115const mockCustomers: Customer[] = [
116 {
117 id: "1",
118 name: "John Doe",
119 email: "john@example.com",
120 company: "Tech Corp",
121 phone: "+1234567890",
122 createdAt: "2024-01-15",
123 },
124 {
125 id: "2",
126 name: "Jane Smith",
127 email: "jane@example.com",
128 company: "Design Studio",
129 phone: "+1234567891",
130 createdAt: "2024-02-20",
131 },
132 {
133 id: "3",
134 name: "Bob Wilson",
135 email: "bob@example.com",
136 company: "E-commerce Inc",
137 phone: "+1234567892",
138 createdAt: "2024-03-10",
139 },
140];
141
142const mockVPSInstances: VPSInstance[] = [
143 {
144 id: "vps-001",
145 name: "WebStore-Primary",
146 type: "dedicated",
147 status: "running",
148 ipAddress: "192.168.1.100",
149 domain: "shop.example.com",
150 plan: "Business Pro",
151 cpu: 8,
152 ram: 32,
153 storage: 500,
154 bandwidth: 1000,
155 monthlyPrice: 299,
156 createdAt: "2024-01-15",
157 lastUpdated: "2024-01-20",
158 customerId: "1",
159 customerName: "John Doe",
160 customerEmail: "john@example.com",
161 location: "US-East",
162 os: "Ubuntu 22.04",
163 cpuUsage: 45,
164 ramUsage: 68,
165 storageUsage: 32,
166 uptime: 99.9,
167 },
168 {
169 id: "vps-002",
170 name: "Dev-Environment",
171 type: "shared",
172 status: "running",
173 ipAddress: "192.168.1.101",
174 domain: "dev.example.com",
175 plan: "Starter",
176 cpu: 2,
177 ram: 8,
178 storage: 100,
179 bandwidth: 500,
180 monthlyPrice: 49,
181 createdAt: "2024-02-20",
182 lastUpdated: "2024-02-25",
183 customerId: "2",
184 customerName: "Jane Smith",
185 customerEmail: "jane@example.com",
186 location: "EU-West",
187 os: "CentOS 8",
188 cpuUsage: 23,
189 ramUsage: 41,
190 storageUsage: 15,
191 uptime: 98.5,
192 },
193 {
194 id: "vps-003",
195 name: "E-commerce-Main",
196 type: "dedicated",
197 status: "maintenance",
198 ipAddress: "192.168.1.102",
199 domain: "store.example.com",
200 plan: "Enterprise",
201 cpu: 16,
202 ram: 64,
203 storage: 1000,
204 bandwidth: 2000,
205 monthlyPrice: 599,
206 createdAt: "2024-03-10",
207 lastUpdated: "2024-03-15",
208 customerId: "3",
209 customerName: "Bob Wilson",
210 customerEmail: "bob@example.com",
211 location: "Asia-Pacific",
212 os: "Ubuntu 22.04",
213 cpuUsage: 0,
214 ramUsage: 0,
215 storageUsage: 78,
216 uptime: 95.2,
217 },
218];
219
220const VPSAdminDashboard = () => {
221 const [vpsInstances, setVpsInstances] =
222 useState<VPSInstance[]>(mockVPSInstances);
223 const [customers] = useState<Customer[]>(mockCustomers);
224 const [searchTerm, setSearchTerm] = useState("");
225 const [filterType, setFilterType] = useState<string>("all");
226 const [filterStatus, setFilterStatus] = useState<string>("all");
227 const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
228 const [editingInstance, setEditingInstance] = useState<VPSInstance | null>(
229 null
230 );
231 const [selectedTab, setSelectedTab] = useState("overview");
232
233 // Form state for adding/editing VPS
234 const [formData, setFormData] = useState({
235 name: "",
236 type: "shared" as "shared" | "dedicated",
237 customerId: "",
238 plan: "",
239 cpu: 2,
240 ram: 8,
241 storage: 100,
242 bandwidth: 500,
243 monthlyPrice: 49,
244 location: "US-East",
245 os: "Ubuntu 22.04",
246 domain: "",
247 ipAddress: "",
248 });
249
250 // Filter instances
251 const filteredInstances = vpsInstances.filter((instance) => {
252 const matchesSearch =
253 instance.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
254 instance.customerName.toLowerCase().includes(searchTerm.toLowerCase()) ||
255 instance.ipAddress.includes(searchTerm);
256 const matchesType = filterType === "all" || instance.type === filterType;
257 const matchesStatus =
258 filterStatus === "all" || instance.status === filterStatus;
259
260 return matchesSearch && matchesType && matchesStatus;
261 });
262
263 // Status badge component
264 const StatusBadge = ({ status }: { status: string }) => {
265 const variants = {
266 running: "bg-green-100 text-green-800 border-green-200",
267 stopped: "bg-red-100 text-red-800 border-red-200",
268 maintenance: "bg-yellow-100 text-yellow-800 border-yellow-200",
269 error: "bg-red-100 text-red-800 border-red-200",
270 };
271
272 const icons = {
273 running: <CheckCircle className="w-3 h-3" />,
274 stopped: <XCircle className="w-3 h-3" />,
275 maintenance: <AlertCircle className="w-3 h-3" />,
276 error: <XCircle className="w-3 h-3" />,
277 };
278
279 return (
280 <Badge
281 className={cn(
282 "flex items-center gap-1",
283 variants[status as keyof typeof variants]
284 )}
285 >
286 {icons[status as keyof typeof icons]}
287 {status.charAt(0).toUpperCase() + status.slice(1)}
288 </Badge>
289 );
290 };
291
292 // Reset form
293 const resetForm = () => {
294 setFormData({
295 name: "",
296 type: "shared",
297 customerId: "",
298 plan: "",
299 cpu: 2,
300 ram: 8,
301 storage: 100,
302 bandwidth: 500,
303 monthlyPrice: 49,
304 location: "US-East",
305 os: "Ubuntu 22.04",
306 domain: "",
307 ipAddress: "",
308 });
309 };
310
311 // Handle form submission
312 const handleSubmit = () => {
313 if (!formData.name || !formData.customerId) return;
314
315 const customer = customers.find((c) => c.id === formData.customerId);
316 if (!customer) return;
317
318 const newInstance: VPSInstance = {
319 id: editingInstance ? editingInstance.id : `vps-${Date.now()}`,
320 ...formData,
321 status: "stopped" as const,
322 ipAddress:
323 formData.ipAddress || `192.168.1.${Math.floor(Math.random() * 255)}`,
324 createdAt: editingInstance
325 ? editingInstance.createdAt
326 : new Date().toISOString().split("T")[0],
327 lastUpdated: new Date().toISOString().split("T")[0],
328 customerName: customer.name,
329 customerEmail: customer.email,
330 cpuUsage: 0,
331 ramUsage: 0,
332 storageUsage: 0,
333 uptime: 100,
334 };
335
336 if (editingInstance) {
337 setVpsInstances((prev) =>
338 prev.map((instance) =>
339 instance.id === editingInstance.id ? newInstance : instance
340 )
341 );
342 } else {
343 setVpsInstances((prev) => [...prev, newInstance]);
344 }
345
346 setIsAddDialogOpen(false);
347 setEditingInstance(null);
348 resetForm();
349 };
350
351 // Handle edit
352 const handleEdit = (instance: VPSInstance) => {
353 setEditingInstance(instance);
354 setFormData({
355 name: instance.name,
356 type: instance.type,
357 customerId: instance.customerId,
358 plan: instance.plan,
359 cpu: instance.cpu,
360 ram: instance.ram,
361 storage: instance.storage,
362 bandwidth: instance.bandwidth,
363 monthlyPrice: instance.monthlyPrice,
364 location: instance.location,
365 os: instance.os,
366 domain: instance.domain || "",
367 ipAddress: instance.ipAddress,
368 });
369 setIsAddDialogOpen(true);
370 };
371
372 // Handle delete
373 const handleDelete = (id: string) => {
374 setVpsInstances((prev) => prev.filter((instance) => instance.id !== id));
375 };
376
377 // Handle status change
378 const handleStatusChange = (id: string, newStatus: VPSInstance["status"]) => {
379 setVpsInstances((prev) =>
380 prev.map((instance) =>
381 instance.id === id
382 ? {
383 ...instance,
384 status: newStatus,
385 lastUpdated: new Date().toISOString().split("T")[0],
386 }
387 : instance
388 )
389 );
390 };
391
392 // Calculate stats
393 const stats = {
394 total: vpsInstances.length,
395 running: vpsInstances.filter((i) => i.status === "running").length,
396 stopped: vpsInstances.filter((i) => i.status === "stopped").length,
397 maintenance: vpsInstances.filter((i) => i.status === "maintenance").length,
398 totalRevenue: vpsInstances.reduce((sum, i) => sum + i.monthlyPrice, 0),
399 sharedInstances: vpsInstances.filter((i) => i.type === "shared").length,
400 dedicatedInstances: vpsInstances.filter((i) => i.type === "dedicated")
401 .length,
402 };
403
404 return (
405 <div className="min-h-screen bg-background p-4 space-y-6">
406 {/* Header */}
407 <div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
408 <div>
409 <h1 className="text-3xl font-bold tracking-tight">
410 VPS Management Dashboard
411 </h1>
412 <p className="text-muted-foreground">
413 Manage and monitor VPS instances for webstore deployments
414 </p>
415 </div>
416 <Dialog open={isAddDialogOpen} onOpenChange={setIsAddDialogOpen}>
417 <DialogTrigger asChild>
418 <Button onClick={resetForm} className="flex items-center gap-2">
419 <Plus className="w-4 h-4" />
420 Add VPS Instance
421 </Button>
422 </DialogTrigger>
423 <DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
424 <DialogHeader>
425 <DialogTitle>
426 {editingInstance ? "Edit VPS Instance" : "Add New VPS Instance"}
427 </DialogTitle>
428 <DialogDescription>
429 Configure the VPS instance settings for webstore deployment
430 </DialogDescription>
431 </DialogHeader>
432
433 <div className="grid gap-4 py-4">
434 <div className="grid grid-cols-2 gap-4">
435 <div className="space-y-2">
436 <Label htmlFor="name">Instance Name *</Label>
437 <Input
438 id="name"
439 value={formData.name}
440 onChange={(e) =>
441 setFormData((prev) => ({ ...prev, name: e.target.value }))
442 }
443 placeholder="e.g., WebStore-Primary"
444 />
445 </div>
446 <div className="space-y-2">
447 <Label htmlFor="type">VPS Type *</Label>
448 <Select
449 value={formData.type}
450 onValueChange={(value: "shared" | "dedicated") =>
451 setFormData((prev) => ({ ...prev, type: value }))
452 }
453 >
454 <SelectTrigger>
455 <SelectValue />
456 </SelectTrigger>
457 <SelectContent>
458 <SelectItem value="shared">Shared VPS</SelectItem>
459 <SelectItem value="dedicated">Dedicated VPS</SelectItem>
460 </SelectContent>
461 </Select>
462 </div>
463 </div>
464
465 <div className="grid grid-cols-2 gap-4">
466 <div className="space-y-2">
467 <Label htmlFor="customer">Customer *</Label>
468 <Select
469 value={formData.customerId}
470 onValueChange={(value) =>
471 setFormData((prev) => ({ ...prev, customerId: value }))
472 }
473 >
474 <SelectTrigger>
475 <SelectValue placeholder="Select customer" />
476 </SelectTrigger>
477 <SelectContent>
478 {customers.map((customer) => (
479 <SelectItem key={customer.id} value={customer.id}>
480 {customer.name} ({customer.email})
481 </SelectItem>
482 ))}
483 </SelectContent>
484 </Select>
485 </div>
486 <div className="space-y-2">
487 <Label htmlFor="plan">Plan Name</Label>
488 <Input
489 id="plan"
490 value={formData.plan}
491 onChange={(e) =>
492 setFormData((prev) => ({ ...prev, plan: e.target.value }))
493 }
494 placeholder="e.g., Business Pro"
495 />
496 </div>
497 </div>
498
499 <div className="grid grid-cols-2 gap-4">
500 <div className="space-y-2">
501 <Label htmlFor="domain">Domain (Optional)</Label>
502 <Input
503 id="domain"
504 value={formData.domain}
505 onChange={(e) =>
506 setFormData((prev) => ({
507 ...prev,
508 domain: e.target.value,
509 }))
510 }
511 placeholder="e.g., shop.example.com"
512 />
513 </div>
514 <div className="space-y-2">
515 <Label htmlFor="ipAddress">IP Address</Label>
516 <Input
517 id="ipAddress"
518 value={formData.ipAddress}
519 onChange={(e) =>
520 setFormData((prev) => ({
521 ...prev,
522 ipAddress: e.target.value,
523 }))
524 }
525 placeholder="Auto-generated if empty"
526 />
527 </div>
528 </div>
529
530 <Separator />
531
532 <div className="grid grid-cols-2 gap-4">
533 <div className="space-y-2">
534 <Label htmlFor="cpu">CPU Cores</Label>
535 <Input
536 id="cpu"
537 type="number"
538 min="1"
539 max="32"
540 value={formData.cpu}
541 onChange={(e) =>
542 setFormData((prev) => ({
543 ...prev,
544 cpu: parseInt(e.target.value) || 1,
545 }))
546 }
547 />
548 </div>
549 <div className="space-y-2">
550 <Label htmlFor="ram">RAM (GB)</Label>
551 <Input
552 id="ram"
553 type="number"
554 min="1"
555 max="128"
556 value={formData.ram}
557 onChange={(e) =>
558 setFormData((prev) => ({
559 ...prev,
560 ram: parseInt(e.target.value) || 1,
561 }))
562 }
563 />
564 </div>
565 </div>
566
567 <div className="grid grid-cols-2 gap-4">
568 <div className="space-y-2">
569 <Label htmlFor="storage">Storage (GB)</Label>
570 <Input
571 id="storage"
572 type="number"
573 min="10"
574 max="2000"
575 value={formData.storage}
576 onChange={(e) =>
577 setFormData((prev) => ({
578 ...prev,
579 storage: parseInt(e.target.value) || 10,
580 }))
581 }
582 />
583 </div>
584 <div className="space-y-2">
585 <Label htmlFor="bandwidth">Bandwidth (GB/month)</Label>
586 <Input
587 id="bandwidth"
588 type="number"
589 min="100"
590 max="10000"
591 value={formData.bandwidth}
592 onChange={(e) =>
593 setFormData((prev) => ({
594 ...prev,
595 bandwidth: parseInt(e.target.value) || 100,
596 }))
597 }
598 />
599 </div>
600 </div>
601
602 <div className="grid grid-cols-2 gap-4">
603 <div className="space-y-2">
604 <Label htmlFor="location">Location</Label>
605 <Select
606 value={formData.location}
607 onValueChange={(value) =>
608 setFormData((prev) => ({ ...prev, location: value }))
609 }
610 >
611 <SelectTrigger>
612 <SelectValue />
613 </SelectTrigger>
614 <SelectContent>
615 <SelectItem value="US-East">US East</SelectItem>
616 <SelectItem value="US-West">US West</SelectItem>
617 <SelectItem value="EU-West">EU West</SelectItem>
618 <SelectItem value="EU-Central">EU Central</SelectItem>
619 <SelectItem value="Asia-Pacific">Asia Pacific</SelectItem>
620 </SelectContent>
621 </Select>
622 </div>
623 <div className="space-y-2">
624 <Label htmlFor="os">Operating System</Label>
625 <Select
626 value={formData.os}
627 onValueChange={(value) =>
628 setFormData((prev) => ({ ...prev, os: value }))
629 }
630 >
631 <SelectTrigger>
632 <SelectValue />
633 </SelectTrigger>
634 <SelectContent>
635 <SelectItem value="Ubuntu 22.04">Ubuntu 22.04</SelectItem>
636 <SelectItem value="Ubuntu 20.04">Ubuntu 20.04</SelectItem>
637 <SelectItem value="CentOS 8">CentOS 8</SelectItem>
638 <SelectItem value="Debian 11">Debian 11</SelectItem>
639 <SelectItem value="Windows Server 2022">
640 Windows Server 2022
641 </SelectItem>
642 </SelectContent>
643 </Select>
644 </div>
645 </div>
646
647 <div className="space-y-2">
648 <Label htmlFor="price">Monthly Price ($)</Label>
649 <Input
650 id="price"
651 type="number"
652 min="0"
653 step="0.01"
654 value={formData.monthlyPrice}
655 onChange={(e) =>
656 setFormData((prev) => ({
657 ...prev,
658 monthlyPrice: parseFloat(e.target.value) || 0,
659 }))
660 }
661 />
662 </div>
663 </div>
664
665 <DialogFooter>
666 <Button
667 variant="outline"
668 onClick={() => setIsAddDialogOpen(false)}
669 >
670 Cancel
671 </Button>
672 <Button
673 onClick={handleSubmit}
674 disabled={!formData.name || !formData.customerId}
675 >
676 {editingInstance ? "Update Instance" : "Create Instance"}
677 </Button>
678 </DialogFooter>
679 </DialogContent>
680 </Dialog>
681 </div>
682
683 {/* Stats Cards */}
684 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
685 <Card>
686 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
687 <CardTitle className="text-sm font-medium">
688 Total Instances
689 </CardTitle>
690 <Server className="h-4 w-4 text-muted-foreground" />
691 </CardHeader>
692 <CardContent>
693 <div className="text-2xl font-bold">{stats.total}</div>
694 <p className="text-xs text-muted-foreground">
695 {stats.sharedInstances} shared, {stats.dedicatedInstances}{" "}
696 dedicated
697 </p>
698 </CardContent>
699 </Card>
700
701 <Card>
702 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
703 <CardTitle className="text-sm font-medium">Running</CardTitle>
704 <Activity className="h-4 w-4 text-green-600" />
705 </CardHeader>
706 <CardContent>
707 <div className="text-2xl font-bold text-green-600">
708 {stats.running}
709 </div>
710 <p className="text-xs text-muted-foreground">
711 {stats.stopped} stopped, {stats.maintenance} maintenance
712 </p>
713 </CardContent>
714 </Card>
715
716 <Card>
717 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
718 <CardTitle className="text-sm font-medium">
719 Monthly Revenue
720 </CardTitle>
721 <DollarSign className="h-4 w-4 text-muted-foreground" />
722 </CardHeader>
723 <CardContent>
724 <div className="text-2xl font-bold">${stats.totalRevenue}</div>
725 <p className="text-xs text-muted-foreground">
726 From {stats.total} active instances
727 </p>
728 </CardContent>
729 </Card>
730
731 <Card>
732 <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
733 <CardTitle className="text-sm font-medium">Customers</CardTitle>
734 <Users className="h-4 w-4 text-muted-foreground" />
735 </CardHeader>
736 <CardContent>
737 <div className="text-2xl font-bold">{customers.length}</div>
738 <p className="text-xs text-muted-foreground">Active customers</p>
739 </CardContent>
740 </Card>
741 </div>
742
743 {/* Filters and Search */}
744 <Card>
745 <CardHeader>
746 <CardTitle>VPS Instances</CardTitle>
747 <CardDescription>
748 Manage and monitor all VPS instances
749 </CardDescription>
750 </CardHeader>
751 <CardContent>
752 <div className="flex flex-col sm:flex-row gap-4 mb-6">
753 <div className="flex-1">
754 <div className="relative">
755 <Search className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
756 <Input
757 placeholder="Search by name, customer, or IP address..."
758 value={searchTerm}
759 onChange={(e) => setSearchTerm(e.target.value)}
760 className="pl-10"
761 />
762 </div>
763 </div>
764 <Select value={filterType} onValueChange={setFilterType}>
765 <SelectTrigger className="w-[180px]">
766 <SelectValue placeholder="Filter by type" />
767 </SelectTrigger>
768 <SelectContent>
769 <SelectItem value="all">All Types</SelectItem>
770 <SelectItem value="shared">Shared VPS</SelectItem>
771 <SelectItem value="dedicated">Dedicated VPS</SelectItem>
772 </SelectContent>
773 </Select>
774 <Select value={filterStatus} onValueChange={setFilterStatus}>
775 <SelectTrigger className="w-[180px]">
776 <SelectValue placeholder="Filter by status" />
777 </SelectTrigger>
778 <SelectContent>
779 <SelectItem value="all">All Status</SelectItem>
780 <SelectItem value="running">Running</SelectItem>
781 <SelectItem value="stopped">Stopped</SelectItem>
782 <SelectItem value="maintenance">Maintenance</SelectItem>
783 <SelectItem value="error">Error</SelectItem>
784 </SelectContent>
785 </Select>
786 </div>
787
788 {/* Instances Table */}
789 <div className="rounded-md border">
790 <Table>
791 <TableHeader>
792 <TableRow>
793 <TableHead>Instance</TableHead>
794 <TableHead>Customer</TableHead>
795 <TableHead>Type</TableHead>
796 <TableHead>Status</TableHead>
797 <TableHead>Resources</TableHead>
798 <TableHead>Usage</TableHead>
799 <TableHead>Price</TableHead>
800 <TableHead>Actions</TableHead>
801 </TableRow>
802 </TableHeader>
803 <TableBody>
804 {filteredInstances.map((instance) => (
805 <TableRow key={instance.id}>
806 <TableCell>
807 <div className="space-y-1">
808 <div className="font-medium">{instance.name}</div>
809 <div className="text-sm text-muted-foreground">
810 {instance.ipAddress}
811 {instance.domain && (
812 <div className="flex items-center gap-1">
813 <Globe className="w-3 h-3" />
814 {instance.domain}
815 </div>
816 )}
817 </div>
818 </div>
819 </TableCell>
820 <TableCell>
821 <div className="space-y-1">
822 <div className="font-medium">
823 {instance.customerName}
824 </div>
825 <div className="text-sm text-muted-foreground">
826 {instance.customerEmail}
827 </div>
828 </div>
829 </TableCell>
830 <TableCell>
831 <Badge
832 variant={
833 instance.type === "dedicated"
834 ? "default"
835 : "secondary"
836 }
837 >
838 {instance.type === "dedicated" ? "Dedicated" : "Shared"}
839 </Badge>
840 </TableCell>
841 <TableCell>
842 <StatusBadge status={instance.status} />
843 </TableCell>
844 <TableCell>
845 <div className="space-y-1 text-sm">
846 <div className="flex items-center gap-1">
847 <Cpu className="w-3 h-3" />
848 {instance.cpu} cores
849 </div>
850 <div className="flex items-center gap-1">
851 <MemoryStick className="w-3 h-3" />
852 {instance.ram}GB RAM
853 </div>
854 <div className="flex items-center gap-1">
855 <HardDrive className="w-3 h-3" />
856 {instance.storage}GB
857 </div>
858 </div>
859 </TableCell>
860 <TableCell>
861 <div className="space-y-2">
862 <div className="space-y-1">
863 <div className="flex justify-between text-xs">
864 <span>CPU</span>
865 <span>{instance.cpuUsage}%</span>
866 </div>
867 <Progress value={instance.cpuUsage} className="h-1" />
868 </div>
869 <div className="space-y-1">
870 <div className="flex justify-between text-xs">
871 <span>RAM</span>
872 <span>{instance.ramUsage}%</span>
873 </div>
874 <Progress value={instance.ramUsage} className="h-1" />
875 </div>
876 </div>
877 </TableCell>
878 <TableCell>
879 <div className="font-medium">
880 ${instance.monthlyPrice}/mo
881 </div>
882 <div className="text-sm text-muted-foreground">
883 {instance.plan}
884 </div>
885 </TableCell>
886 <TableCell>
887 <div className="flex items-center gap-2">
888 <DropdownMenu>
889 <DropdownMenuTrigger asChild>
890 <Button variant="ghost" size="sm">
891 <MoreHorizontal className="w-4 h-4" />
892 </Button>
893 </DropdownMenuTrigger>
894 <DropdownMenuContent align="end">
895 <DropdownMenuItem
896 onClick={() => handleEdit(instance)}
897 >
898 <Edit className="w-4 h-4 mr-2" />
899 Edit
900 </DropdownMenuItem>
901 <DropdownMenuItem>
902 <Eye className="w-4 h-4 mr-2" />
903 View Details
904 </DropdownMenuItem>
905 <DropdownMenuItem>
906 <Settings className="w-4 h-4 mr-2" />
907 Configure
908 </DropdownMenuItem>
909 {instance.status === "running" ? (
910 <DropdownMenuItem
911 onClick={() =>
912 handleStatusChange(instance.id, "stopped")
913 }
914 >
915 <PowerOff className="w-4 h-4 mr-2" />
916 Stop
917 </DropdownMenuItem>
918 ) : (
919 <DropdownMenuItem
920 onClick={() =>
921 handleStatusChange(instance.id, "running")
922 }
923 >
924 <Power className="w-4 h-4 mr-2" />
925 Start
926 </DropdownMenuItem>
927 )}
928 <DropdownMenuItem
929 onClick={() =>
930 handleStatusChange(instance.id, "maintenance")
931 }
932 >
933 <AlertCircle className="w-4 h-4 mr-2" />
934 Maintenance
935 </DropdownMenuItem>
936 <DropdownMenuItem
937 onClick={() => handleDelete(instance.id)}
938 className="text-red-600"
939 >
940 <Trash2 className="w-4 h-4 mr-2" />
941 Delete
942 </DropdownMenuItem>
943 </DropdownMenuContent>
944 </DropdownMenu>
945 </div>
946 </TableCell>
947 </TableRow>
948 ))}
949 </TableBody>
950 </Table>
951 </div>
952
953 {filteredInstances.length === 0 && (
954 <div className="text-center py-8">
955 <Server className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
956 <h3 className="text-lg font-medium mb-2">
957 No VPS instances found
958 </h3>
959 <p className="text-muted-foreground mb-4">
960 {searchTerm || filterType !== "all" || filterStatus !== "all"
961 ? "Try adjusting your search or filters"
962 : "Get started by creating your first VPS instance"}
963 </p>
964 {!searchTerm &&
965 filterType === "all" &&
966 filterStatus === "all" && (
967 <Button onClick={() => setIsAddDialogOpen(true)}>
968 <Plus className="w-4 h-4 mr-2" />
969 Add VPS Instance
970 </Button>
971 )}
972 </div>
973 )}
974 </CardContent>
975 </Card>
976 </div>
977 );
978};
979
980export default VPSAdminDashboard;
Dependencies
External Libraries
lucide-reactreact
Shadcn/UI Components
badgebuttoncarddialogdropdown-menuinputlabelprogressselectseparatorswitchtabletabstextarea
Local Components
/lib/utils