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