ShadcnUI Vaults

Back to Blocks

Enterprise Backup Manager

Unknown Block

UnknownComponent

Enterprise Backup Manager

Enterprise-grade backup management system with advanced table features, filtering, and detailed backup analytics.

Preview

Full width desktop view

Code

monitoring-5.tsx
1"use client";
2
3import * as React from "react";
4import {
5  ColumnDef,
6  ColumnFiltersState,
7  SortingState,
8  VisibilityState,
9  flexRender,
10  getCoreRowModel,
11  getFilteredRowModel,
12  getPaginationRowModel,
13  getSortedRowModel,
14  useReactTable,
15} from "@tanstack/react-table";
16import {
17  ArrowUpDown,
18  Database,
19  Server,
20  Clock,
21  CheckCircle,
22  XCircle,
23  Download,
24  Trash2,
25  Eye,
26  RefreshCw,
27  Filter,
28  Search,
29} from "lucide-react";
30import { format, getDate } from "date-fns";
31import { cn } from "@/lib/utils";
32
33// Button Component
34const buttonVariants = {
35  default:
36    "bg-primary text-primary-foreground shadow-sm shadow-black/5 hover:bg-primary/90",
37  destructive:
38    "bg-destructive text-destructive-foreground shadow-sm shadow-black/5 hover:bg-destructive/90",
39  outline:
40    "border border-input bg-background shadow-sm shadow-black/5 hover:bg-accent hover:text-accent-foreground",
41  secondary:
42    "bg-secondary text-secondary-foreground shadow-sm shadow-black/5 hover:bg-secondary/80",
43  ghost: "hover:bg-accent hover:text-accent-foreground",
44  link: "text-primary underline-offset-4 hover:underline",
45};
46
47const buttonSizes = {
48  default: "h-9 px-4 py-2",
49  sm: "h-8 rounded-lg px-3 text-xs",
50  lg: "h-10 rounded-lg px-8",
51  icon: "h-9 w-9",
52};
53
54interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
55  variant?: keyof typeof buttonVariants;
56  size?: keyof typeof buttonSizes;
57}
58
59const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
60  ({ className, variant = "default", size = "default", ...props }, ref) => {
61    return (
62      <button
63        className={cn(
64          "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
65          buttonVariants[variant],
66          buttonSizes[size],
67          className
68        )}
69        ref={ref}
70        {...props}
71      />
72    );
73  }
74);
75Button.displayName = "Button";
76
77// Input Component
78const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
79  ({ className, type, ...props }, ref) => {
80    return (
81      <input
82        type={type}
83        className={cn(
84          "flex h-9 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-sm shadow-black/5 transition-shadow placeholder:text-muted-foreground/70 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50",
85          className
86        )}
87        ref={ref}
88        {...props}
89      />
90    );
91  }
92);
93Input.displayName = "Input";
94
95// Badge Component
96interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
97  variant?:
98    | "default"
99    | "secondary"
100    | "destructive"
101    | "outline"
102    | "success"
103    | "warning";
104}
105
106const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
107  ({ className, variant = "default", ...props }, ref) => {
108    const variants = {
109      default:
110        "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
111      secondary:
112        "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
113      destructive:
114        "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
115      outline: "text-foreground",
116      success:
117        "border-transparent bg-green-500 text-white shadow hover:bg-green-500/80",
118      warning:
119        "border-transparent bg-yellow-500 text-white shadow hover:bg-yellow-500/80",
120    };
121
122    return (
123      <div
124        ref={ref}
125        className={cn(
126          "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
127          variants[variant],
128          className
129        )}
130        {...props}
131      />
132    );
133  }
134);
135Badge.displayName = "Badge";
136
137// Card Components
138const Card = React.forwardRef<
139  HTMLDivElement,
140  React.HTMLAttributes<HTMLDivElement>
141>(({ className, ...props }, ref) => (
142  <div
143    ref={ref}
144    className={cn(
145      "rounded-xl border bg-card text-card-foreground shadow",
146      className
147    )}
148    {...props}
149  />
150));
151Card.displayName = "Card";
152
153const CardHeader = React.forwardRef<
154  HTMLDivElement,
155  React.HTMLAttributes<HTMLDivElement>
156>(({ className, ...props }, ref) => (
157  <div
158    ref={ref}
159    className={cn("flex flex-col space-y-1.5 p-6", className)}
160    {...props}
161  />
162));
163CardHeader.displayName = "CardHeader";
164
165const CardTitle = React.forwardRef<
166  HTMLParagraphElement,
167  React.HTMLAttributes<HTMLHeadingElement>
168>(({ className, ...props }, ref) => (
169  <h3
170    ref={ref}
171    className={cn("font-semibold leading-none tracking-tight", className)}
172    {...props}
173  />
174));
175CardTitle.displayName = "CardTitle";
176
177const CardContent = React.forwardRef<
178  HTMLDivElement,
179  React.HTMLAttributes<HTMLDivElement>
180>(({ className, ...props }, ref) => (
181  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
182));
183CardContent.displayName = "CardContent";
184
185// Table Components
186const Table = React.forwardRef<
187  HTMLTableElement,
188  React.HTMLAttributes<HTMLTableElement>
189>(({ className, ...props }, ref) => (
190  <div className="relative w-full overflow-auto">
191    <table
192      ref={ref}
193      className={cn("w-full caption-bottom text-sm", className)}
194      {...props}
195    />
196  </div>
197));
198Table.displayName = "Table";
199
200const TableHeader = React.forwardRef<
201  HTMLTableSectionElement,
202  React.HTMLAttributes<HTMLTableSectionElement>
203>(({ className, ...props }, ref) => (
204  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
205));
206TableHeader.displayName = "TableHeader";
207
208const TableBody = React.forwardRef<
209  HTMLTableSectionElement,
210  React.HTMLAttributes<HTMLTableSectionElement>
211>(({ className, ...props }, ref) => (
212  <tbody
213    ref={ref}
214    className={cn("[&_tr:last-child]:border-0", className)}
215    {...props}
216  />
217));
218TableBody.displayName = "TableBody";
219
220const TableRow = React.forwardRef<
221  HTMLTableRowElement,
222  React.HTMLAttributes<HTMLTableRowElement>
223>(({ className, ...props }, ref) => (
224  <tr
225    ref={ref}
226    className={cn(
227      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
228      className
229    )}
230    {...props}
231  />
232));
233TableRow.displayName = "TableRow";
234
235const TableHead = React.forwardRef<
236  HTMLTableCellElement,
237  React.ThHTMLAttributes<HTMLTableCellElement>
238>(({ className, ...props }, ref) => (
239  <th
240    ref={ref}
241    className={cn(
242      "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
243      className
244    )}
245    {...props}
246  />
247));
248TableHead.displayName = "TableHead";
249
250const TableCell = React.forwardRef<
251  HTMLTableCellElement,
252  React.TdHTMLAttributes<HTMLTableCellElement>
253>(({ className, ...props }, ref) => (
254  <td
255    ref={ref}
256    className={cn(
257      "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
258      className
259    )}
260    {...props}
261  />
262));
263TableCell.displayName = "TableCell";
264
265// Date Badge Component
266function DateBadge({ date: rawDate }: { date: string | Date }) {
267  const date = getDate(rawDate);
268  const month = format(rawDate, "LLL");
269
270  return (
271    <div className="bg-background/40 flex size-10 shrink-0 cursor-default flex-col items-center justify-center rounded-md border text-center">
272      <span className="text-sm font-semibold leading-snug">{date}</span>
273      <span className="text-muted-foreground text-xs leading-none">
274        {month}
275      </span>
276    </div>
277  );
278}
279
280// Types
281interface DatabaseBackup {
282  id: string;
283  database_name: string;
284  backup_date: Date;
285  backup_time: string;
286  vps_region: string;
287  status: "success" | "failed" | "in_progress" | "scheduled";
288  size: number;
289  backup_type: "full" | "incremental" | "differential";
290  retention_days: number;
291  logs: string[];
292  file_path: string;
293  checksum: string;
294}
295
296// Sample data
297const sampleBackups: DatabaseBackup[] = [
298  {
299    id: "bkp_001",
300    database_name: "production_db",
301    backup_date: new Date("2024-01-15T02:30:00Z"),
302    backup_time: "02:30:00",
303    vps_region: "us-east-1",
304    status: "success",
305    size: 2.4,
306    backup_type: "full",
307    retention_days: 30,
308    logs: [
309      "Backup started",
310      "Database locked",
311      "Backup completed successfully",
312    ],
313    file_path: "/backups/production_db_20240115_023000.sql.gz",
314    checksum: "sha256:a1b2c3d4e5f6",
315  },
316  {
317    id: "bkp_002",
318    database_name: "staging_db",
319    backup_date: new Date("2024-01-15T01:15:00Z"),
320    backup_time: "01:15:00",
321    vps_region: "eu-west-1",
322    status: "failed",
323    size: 0,
324    backup_type: "incremental",
325    retention_days: 7,
326    logs: ["Backup started", "Connection timeout", "Backup failed"],
327    file_path: "",
328    checksum: "",
329  },
330  {
331    id: "bkp_003",
332    database_name: "analytics_db",
333    backup_date: new Date("2024-01-14T23:45:00Z"),
334    backup_time: "23:45:00",
335    vps_region: "ap-southeast-1",
336    status: "in_progress",
337    size: 1.2,
338    backup_type: "differential",
339    retention_days: 14,
340    logs: ["Backup started", "Processing tables", "50% completed"],
341    file_path: "/backups/analytics_db_20240114_234500.sql.gz",
342    checksum: "",
343  },
344  {
345    id: "bkp_004",
346    database_name: "user_data_db",
347    backup_date: new Date("2024-01-16T03:00:00Z"),
348    backup_time: "03:00:00",
349    vps_region: "us-west-2",
350    status: "scheduled",
351    size: 0,
352    backup_type: "full",
353    retention_days: 90,
354    logs: ["Backup scheduled"],
355    file_path: "",
356    checksum: "",
357  },
358  {
359    id: "bkp_005",
360    database_name: "inventory_db",
361    backup_date: new Date("2024-01-14T22:30:00Z"),
362    backup_time: "22:30:00",
363    vps_region: "eu-central-1",
364    status: "success",
365    size: 0.8,
366    backup_type: "incremental",
367    retention_days: 21,
368    logs: [
369      "Backup started",
370      "Incremental changes detected",
371      "Backup completed",
372    ],
373    file_path: "/backups/inventory_db_20240114_223000.sql.gz",
374    checksum: "sha256:f6e5d4c3b2a1",
375  },
376];
377
378// Status Badge Component
379function StatusBadge({ status }: { status: DatabaseBackup["status"] }) {
380  const statusConfig = {
381    success: {
382      variant: "success" as const,
383      icon: CheckCircle,
384      text: "Success",
385    },
386    failed: { variant: "destructive" as const, icon: XCircle, text: "Failed" },
387    in_progress: {
388      variant: "warning" as const,
389      icon: RefreshCw,
390      text: "In Progress",
391    },
392    scheduled: {
393      variant: "secondary" as const,
394      icon: Clock,
395      text: "Scheduled",
396    },
397  };
398
399  const config = statusConfig[status];
400  const Icon = config.icon;
401
402  return (
403    <Badge variant={config.variant} className="flex items-center gap-1">
404      <Icon
405        size={12}
406        className={status === "in_progress" ? "animate-spin" : ""}
407      />
408      {config.text}
409    </Badge>
410  );
411}
412
413// Region Badge Component
414function RegionBadge({ region }: { region: string }) {
415  const regionNames: Record<string, string> = {
416    "us-east-1": "N. Virginia",
417    "us-west-2": "Oregon",
418    "eu-west-1": "Ireland",
419    "eu-central-1": "Frankfurt",
420    "ap-southeast-1": "Singapore",
421  };
422
423  return (
424    <Badge variant="outline" className="flex items-center gap-1">
425      <Server size={12} />
426      {regionNames[region] || region}
427    </Badge>
428  );
429}
430
431// Backup Type Badge Component
432function BackupTypeBadge({ type }: { type: DatabaseBackup["backup_type"] }) {
433  const typeConfig = {
434    full: { color: "bg-blue-500", text: "Full" },
435    incremental: { color: "bg-green-500", text: "Incremental" },
436    differential: { color: "bg-orange-500", text: "Differential" },
437  };
438
439  const config = typeConfig[type];
440
441  return (
442    <div className="flex items-center gap-2">
443      <div className={cn("w-2 h-2 rounded-full", config.color)} />
444      <span className="text-sm text-muted-foreground">{config.text}</span>
445    </div>
446  );
447}
448
449// Logs Modal Component
450function LogsModal({
451  logs,
452  isOpen,
453  onClose,
454}: {
455  logs: string[];
456  isOpen: boolean;
457  onClose: () => void;
458}) {
459  if (!isOpen) return null;
460
461  return (
462    <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
463      <div className="bg-background border rounded-lg p-6 max-w-2xl w-full mx-4 max-h-96 overflow-auto">
464        <div className="flex justify-between items-center mb-4">
465          <h3 className="text-lg font-semibold">Backup Logs</h3>
466          <Button variant="ghost" size="icon" onClick={onClose}>
467            <XCircle size={16} />
468          </Button>
469        </div>
470        <div className="space-y-2">
471          {logs.map((log, index) => (
472            <div key={index} className="flex items-center gap-2 text-sm">
473              <span className="text-muted-foreground">{index + 1}.</span>
474              <span>{log}</span>
475            </div>
476          ))}
477        </div>
478      </div>
479    </div>
480  );
481}
482
483// Main Database Backup Management Component
484function DatabaseBackupManager() {
485  const [sorting, setSorting] = React.useState<SortingState>([]);
486  const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
487    []
488  );
489  const [columnVisibility, setColumnVisibility] =
490    React.useState<VisibilityState>({});
491  const [rowSelection, setRowSelection] = React.useState({});
492  const [selectedLogs, setSelectedLogs] = React.useState<string[]>([]);
493  const [isLogsModalOpen, setIsLogsModalOpen] = React.useState(false);
494
495  const columns: ColumnDef<DatabaseBackup>[] = [
496    {
497      accessorKey: "database_name",
498      header: ({ column }) => (
499        <Button
500          variant="ghost"
501          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
502          className="h-auto p-0 font-medium"
503        >
504          Database
505          <ArrowUpDown className="ml-2 h-4 w-4" />
506        </Button>
507      ),
508      cell: ({ row }) => (
509        <div className="flex items-center gap-2">
510          <Database size={16} className="text-muted-foreground" />
511          <span className="font-medium">{row.getValue("database_name")}</span>
512        </div>
513      ),
514    },
515    {
516      accessorKey: "backup_date",
517      header: ({ column }) => (
518        <Button
519          variant="ghost"
520          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
521          className="h-auto p-0 font-medium"
522        >
523          Date
524          <ArrowUpDown className="ml-2 h-4 w-4" />
525        </Button>
526      ),
527      cell: ({ row }) => (
528        <div className="flex items-center gap-2">
529          <DateBadge date={row.getValue("backup_date")} />
530          <div className="flex flex-col">
531            <span className="text-sm font-medium">
532              {format(row.getValue("backup_date"), "MMM dd, yyyy")}
533            </span>
534            <span className="text-xs text-muted-foreground">
535              {row.original.backup_time}
536            </span>
537          </div>
538        </div>
539      ),
540    },
541    {
542      accessorKey: "status",
543      header: "Status",
544      cell: ({ row }) => <StatusBadge status={row.getValue("status")} />,
545    },
546    {
547      accessorKey: "vps_region",
548      header: "Region",
549      cell: ({ row }) => <RegionBadge region={row.getValue("vps_region")} />,
550    },
551    {
552      accessorKey: "backup_type",
553      header: "Type",
554      cell: ({ row }) => <BackupTypeBadge type={row.getValue("backup_type")} />,
555    },
556    {
557      accessorKey: "size",
558      header: ({ column }) => (
559        <Button
560          variant="ghost"
561          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
562          className="h-auto p-0 font-medium"
563        >
564          Size
565          <ArrowUpDown className="ml-2 h-4 w-4" />
566        </Button>
567      ),
568      cell: ({ row }) => {
569        const size = row.getValue("size") as number;
570        return size > 0 ? `${size.toFixed(1)} GB` : "-";
571      },
572    },
573    {
574      accessorKey: "retention_days",
575      header: "Retention",
576      cell: ({ row }) => (
577        <span className="text-sm">{row.getValue("retention_days")} days</span>
578      ),
579    },
580    {
581      id: "actions",
582      header: "Actions",
583      cell: ({ row }) => {
584        const backup = row.original;
585
586        return (
587          <div className="flex items-center gap-1">
588            <Button
589              variant="ghost"
590              size="icon"
591              onClick={() => {
592                setSelectedLogs(backup.logs);
593                setIsLogsModalOpen(true);
594              }}
595              className="h-8 w-8"
596            >
597              <Eye size={14} />
598            </Button>
599            {backup.status === "success" && (
600              <Button variant="ghost" size="icon" className="h-8 w-8">
601                <Download size={14} />
602              </Button>
603            )}
604            <Button
605              variant="ghost"
606              size="icon"
607              className="h-8 w-8 text-destructive"
608            >
609              <Trash2 size={14} />
610            </Button>
611          </div>
612        );
613      },
614    },
615  ];
616
617  const table = useReactTable({
618    data: sampleBackups,
619    columns,
620    onSortingChange: setSorting,
621    onColumnFiltersChange: setColumnFilters,
622    getCoreRowModel: getCoreRowModel(),
623    getPaginationRowModel: getPaginationRowModel(),
624    getSortedRowModel: getSortedRowModel(),
625    getFilteredRowModel: getFilteredRowModel(),
626    onColumnVisibilityChange: setColumnVisibility,
627    onRowSelectionChange: setRowSelection,
628    state: {
629      sorting,
630      columnFilters,
631      columnVisibility,
632      rowSelection,
633    },
634  });
635
636  const stats = React.useMemo(() => {
637    const total = sampleBackups.length;
638    const successful = sampleBackups.filter(
639      (b) => b.status === "success"
640    ).length;
641    const failed = sampleBackups.filter((b) => b.status === "failed").length;
642    const totalSize = sampleBackups.reduce((acc, b) => acc + b.size, 0);
643
644    return { total, successful, failed, totalSize };
645  }, []);
646
647  return (
648    <div className="space-y-6 p-6">
649      {/* Header */}
650      <div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
651        <div>
652          <h1 className="text-3xl font-bold tracking-tight">
653            Database Backups
654          </h1>
655          <p className="text-muted-foreground">
656            Manage and monitor your database backup operations
657          </p>
658        </div>
659        <div className="flex gap-2">
660          <Button>
661            <RefreshCw size={16} className="mr-2" />
662            Refresh
663          </Button>
664          <Button>
665            <Database size={16} className="mr-2" />
666            New Backup
667          </Button>
668        </div>
669      </div>
670
671      {/* Stats Cards */}
672      <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
673        <Card>
674          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
675            <CardTitle className="text-sm font-medium">Total Backups</CardTitle>
676            <Database className="h-4 w-4 text-muted-foreground" />
677          </CardHeader>
678          <CardContent>
679            <div className="text-2xl font-bold">{stats.total}</div>
680          </CardContent>
681        </Card>
682        <Card>
683          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
684            <CardTitle className="text-sm font-medium">Successful</CardTitle>
685            <CheckCircle className="h-4 w-4 text-green-500" />
686          </CardHeader>
687          <CardContent>
688            <div className="text-2xl font-bold text-green-600">
689              {stats.successful}
690            </div>
691          </CardContent>
692        </Card>
693        <Card>
694          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
695            <CardTitle className="text-sm font-medium">Failed</CardTitle>
696            <XCircle className="h-4 w-4 text-red-500" />
697          </CardHeader>
698          <CardContent>
699            <div className="text-2xl font-bold text-red-600">
700              {stats.failed}
701            </div>
702          </CardContent>
703        </Card>
704        <Card>
705          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
706            <CardTitle className="text-sm font-medium">Total Size</CardTitle>
707            <Server className="h-4 w-4 text-muted-foreground" />
708          </CardHeader>
709          <CardContent>
710            <div className="text-2xl font-bold">
711              {stats.totalSize.toFixed(1)} GB
712            </div>
713          </CardContent>
714        </Card>
715      </div>
716
717      {/* Filters */}
718      <div className="flex items-center gap-4">
719        <div className="relative flex-1 max-w-sm">
720          <Search
721            className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground"
722            size={16}
723          />
724          <Input
725            placeholder="Search databases..."
726            value={
727              (table.getColumn("database_name")?.getFilterValue() as string) ??
728              ""
729            }
730            onChange={(event) =>
731              table
732                .getColumn("database_name")
733                ?.setFilterValue(event.target.value)
734            }
735            className="pl-9"
736          />
737        </div>
738        <Button variant="outline">
739          <Filter size={16} className="mr-2" />
740          Filters
741        </Button>
742      </div>
743
744      {/* Table */}
745      <Card>
746        <div className="rounded-md border">
747          <Table>
748            <TableHeader>
749              {table.getHeaderGroups().map((headerGroup) => (
750                <TableRow key={headerGroup.id}>
751                  {headerGroup.headers.map((header) => (
752                    <TableHead key={header.id}>
753                      {header.isPlaceholder
754                        ? null
755                        : flexRender(
756                            header.column.columnDef.header,
757                            header.getContext()
758                          )}
759                    </TableHead>
760                  ))}
761                </TableRow>
762              ))}
763            </TableHeader>
764            <TableBody>
765              {table.getRowModel().rows?.length ? (
766                table.getRowModel().rows.map((row) => (
767                  <TableRow
768                    key={row.id}
769                    data-state={row.getIsSelected() && "selected"}
770                  >
771                    {row.getVisibleCells().map((cell) => (
772                      <TableCell key={cell.id}>
773                        {flexRender(
774                          cell.column.columnDef.cell,
775                          cell.getContext()
776                        )}
777                      </TableCell>
778                    ))}
779                  </TableRow>
780                ))
781              ) : (
782                <TableRow>
783                  <TableCell
784                    colSpan={columns.length}
785                    className="h-24 text-center"
786                  >
787                    No backups found.
788                  </TableCell>
789                </TableRow>
790              )}
791            </TableBody>
792          </Table>
793        </div>
794      </Card>
795
796      {/* Pagination */}
797      <div className="flex items-center justify-end space-x-2">
798        <div className="flex-1 text-sm text-muted-foreground">
799          {table.getFilteredSelectedRowModel().rows.length} of{" "}
800          {table.getFilteredRowModel().rows.length} row(s) selected.
801        </div>
802        <div className="space-x-2">
803          <Button
804            variant="outline"
805            size="sm"
806            onClick={() => table.previousPage()}
807            disabled={!table.getCanPreviousPage()}
808          >
809            Previous
810          </Button>
811          <Button
812            variant="outline"
813            size="sm"
814            onClick={() => table.nextPage()}
815            disabled={!table.getCanNextPage()}
816          >
817            Next
818          </Button>
819        </div>
820      </div>
821
822      {/* Logs Modal */}
823      <LogsModal
824        logs={selectedLogs}
825        isOpen={isLogsModalOpen}
826        onClose={() => setIsLogsModalOpen(false)}
827      />
828    </div>
829  );
830}
831
832export default function Component() {
833  return <DatabaseBackupManager />;
834}

Dependencies

External Libraries

@tanstack/react-tabledate-fnslucide-reactreact

Local Components

/lib/utils

LICENSE

MIT License

Copyright (c) 2025 Aldhaneka

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

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

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

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

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

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

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

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

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

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

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

By submitting a component, contributors agree to these terms.

Contributors

Ramiro Godoy@milogodoy

Review Form Block

Aldhaneka@Aldhanekaa

Project Creator

For questions about licensing, please contact the project maintainers.