ShadCN Vaults

Back to Blocks

Advanced VPS Admin Panel

System Monitoring Block

System MonitoringComponent

Advanced VPS Admin Panel

Feature-rich VPS admin panel with customer management, resource tracking, status controls, and advanced table features for shared and dedicated VPS.

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

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

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

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

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

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

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

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

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

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

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

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

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

By submitting a component, contributors agree to these terms.

For questions about licensing, please contact the project maintainers.