mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on the hidde restore action if already a restauration is waiting.
This commit is contained in:
@@ -21,7 +21,6 @@ export default async function Layout({children}: { children: React.ReactNode })
|
|||||||
}
|
}
|
||||||
if (!user) redirect('/login')
|
if (!user) redirect('/login')
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<div className="flex flex-col lg:flex-row w-full">
|
<div className="flex flex-col lg:flex-row w-full">
|
||||||
|
|||||||
@@ -3,55 +3,54 @@ import {prisma} from "@/prisma";
|
|||||||
import {notFound} from "next/navigation";
|
import {notFound} from "next/navigation";
|
||||||
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
import {Page, PageActions, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
|
||||||
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
|
import {BackupButton} from "@/components/wrappers/BackupButton/BackupButton";
|
||||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
|
||||||
import {DatabaseTabs} from "@/components/wrappers/Dashboard/Projects/Database/DatabaseTabs";
|
import {DatabaseTabs} from "@/components/wrappers/Dashboard/Projects/Database/DatabaseTabs";
|
||||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
import {DatabaseKpi} from "@/components/wrappers/Dashboard/Projects/Database/DatabaseKpi";
|
||||||
|
|
||||||
|
|
||||||
export default async function RoutePage(props: PageParams<{ databaseId: string }>) {
|
export default async function RoutePage(props: PageParams<{ databaseId: string }>) {
|
||||||
|
|
||||||
const {databaseId} = await props.params
|
const {databaseId} = await props.params
|
||||||
|
|
||||||
const database = await prisma.database.findUnique({
|
const database = await prisma.database.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: databaseId,
|
id: databaseId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
console.log("database", database)
|
|
||||||
|
|
||||||
if (!database) {
|
if (!database) {
|
||||||
notFound()
|
notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const backups = await prisma.backup.findMany({
|
const backups = await prisma.backup.findMany({
|
||||||
where: {
|
where: {
|
||||||
databaseId: database.id
|
databaseId: database.id
|
||||||
|
},
|
||||||
|
include:{
|
||||||
|
restaurations: {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log(backups)
|
|
||||||
|
|
||||||
|
const restaurations = await prisma.restauration.findMany({
|
||||||
|
where: {
|
||||||
|
databaseId: databaseId,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const isAlreadyBackup = !!backups.find(backup => backup.status === "waiting");
|
||||||
|
const isAlreadyRestore = !!restaurations.find(restoration => restoration.status === "waiting");
|
||||||
const totalBackups = await prisma.backup.count({
|
const totalBackups = await prisma.backup.count({
|
||||||
where: {
|
where: {
|
||||||
databaseId: databaseId,
|
databaseId: databaseId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const successfulBackups = await prisma.backup.count({
|
const successfulBackups = await prisma.backup.count({
|
||||||
where: {
|
where: {
|
||||||
databaseId: databaseId,
|
databaseId: databaseId,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null
|
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null
|
||||||
|
|
||||||
// const restaurations = await prisma.restauration.findMany({
|
|
||||||
// where: {
|
|
||||||
// databaseId: databaseId,
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="justify-between gap-2 sm:flex">
|
<div className="justify-between gap-2 sm:flex">
|
||||||
@@ -59,39 +58,19 @@ export default async function RoutePage(props: PageParams<{ databaseId: string }
|
|||||||
{database.name}
|
{database.name}
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
<PageActions className="justify-between">
|
<PageActions className="justify-between">
|
||||||
<BackupButton databaseId={databaseId} />
|
<BackupButton disable={isAlreadyBackup} databaseId={databaseId}/>
|
||||||
</PageActions>
|
</PageActions>
|
||||||
</div>
|
</div>
|
||||||
<PageDescription className="mt-5 sm:mt-0">{database.description}</PageDescription>
|
<PageDescription className="mt-5 sm:mt-0">{database.description}</PageDescription>
|
||||||
<PageContent className="flex flex-col w-full h-full">
|
<PageContent className="flex flex-col w-full h-full">
|
||||||
<div className="flex flex-col sm:flex-row sm:justify-between gap-8 mb-6">
|
<DatabaseKpi
|
||||||
<Card className="w-full sm:w-auto flex-1">
|
successRate={successRate}
|
||||||
<CardHeader className="font-bold text-xl">
|
database={database}
|
||||||
Backups
|
/>
|
||||||
</CardHeader>
|
|
||||||
<CardContent></CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="w-full sm:w-auto flex-1">
|
|
||||||
<CardHeader className="font-bold text-xl">
|
|
||||||
Success rate
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{successRate ?? "Unavailable for now."}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<Card className="w-full sm:w-auto flex-1">
|
|
||||||
<CardHeader className="font-bold text-xl">
|
|
||||||
Last contact
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{formatDateLastContact(database.lastContact)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DatabaseTabs
|
<DatabaseTabs
|
||||||
|
isAlreadyRestore={isAlreadyRestore}
|
||||||
backups={backups}
|
backups={backups}
|
||||||
// restaurations={restaurations}
|
restaurations={restaurations}
|
||||||
/>
|
/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -16,77 +16,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@layer base {*/
|
|
||||||
/* :root {*/
|
|
||||||
/* --background: 0 0% 100%;*/
|
|
||||||
/* --foreground: 0 0% 3.9%;*/
|
|
||||||
/* --card: 0 0% 100%;*/
|
|
||||||
/* --card-foreground: 0 0% 3.9%;*/
|
|
||||||
/* --popover: 0 0% 100%;*/
|
|
||||||
/* --popover-foreground: 0 0% 3.9%;*/
|
|
||||||
/* --primary: 0 0% 9%;*/
|
|
||||||
/* --primary-foreground: 0 0% 98%;*/
|
|
||||||
/* --secondary: 0 0% 96.1%;*/
|
|
||||||
/* --secondary-foreground: 0 0% 9%;*/
|
|
||||||
/* --muted: 0 0% 96.1%;*/
|
|
||||||
/* --muted-foreground: 0 0% 45.1%;*/
|
|
||||||
/* --accent: 0 0% 96.1%;*/
|
|
||||||
/* --accent-foreground: 0 0% 9%;*/
|
|
||||||
/* --destructive: 0 84.2% 60.2%;*/
|
|
||||||
/* --destructive-foreground: 0 0% 98%;*/
|
|
||||||
/* --border: 0 0% 89.8%;*/
|
|
||||||
/* --input: 0 0% 89.8%;*/
|
|
||||||
/* --ring: 0 0% 3.9%;*/
|
|
||||||
/* --chart-1: 12 76% 61%;*/
|
|
||||||
/* --chart-2: 173 58% 39%;*/
|
|
||||||
/* --chart-3: 197 37% 24%;*/
|
|
||||||
/* --chart-4: 43 74% 66%;*/
|
|
||||||
/* --chart-5: 27 87% 67%;*/
|
|
||||||
/* --radius: 0.5rem;*/
|
|
||||||
/* --sidebar-background: 0 0% 98%;*/
|
|
||||||
/* --sidebar-foreground: 240 5.3% 26.1%;*/
|
|
||||||
/* --sidebar-primary: 240 5.9% 10%;*/
|
|
||||||
/* --sidebar-primary-foreground: 0 0% 98%;*/
|
|
||||||
/* --sidebar-accent: 240 4.8% 95.9%;*/
|
|
||||||
/* --sidebar-accent-foreground: 240 5.9% 10%;*/
|
|
||||||
/* --sidebar-border: 220 13% 91%;*/
|
|
||||||
/* --sidebar-ring: 217.2 91.2% 59.8%;*/
|
|
||||||
/* }*/
|
|
||||||
/* .dark {*/
|
|
||||||
/* --background: 0 0% 3.9%;*/
|
|
||||||
/* --foreground: 0 0% 98%;*/
|
|
||||||
/* --card: 0 0% 3.9%;*/
|
|
||||||
/* --card-foreground: 0 0% 98%;*/
|
|
||||||
/* --popover: 0 0% 3.9%;*/
|
|
||||||
/* --popover-foreground: 0 0% 98%;*/
|
|
||||||
/* --primary: 0 0% 98%;*/
|
|
||||||
/* --primary-foreground: 0 0% 9%;*/
|
|
||||||
/* --secondary: 0 0% 14.9%;*/
|
|
||||||
/* --secondary-foreground: 0 0% 98%;*/
|
|
||||||
/* --muted: 0 0% 14.9%;*/
|
|
||||||
/* --muted-foreground: 0 0% 63.9%;*/
|
|
||||||
/* --accent: 0 0% 14.9%;*/
|
|
||||||
/* --accent-foreground: 0 0% 98%;*/
|
|
||||||
/* --destructive: 0 62.8% 30.6%;*/
|
|
||||||
/* --destructive-foreground: 0 0% 98%;*/
|
|
||||||
/* --border: 0 0% 14.9%;*/
|
|
||||||
/* --input: 0 0% 14.9%;*/
|
|
||||||
/* --ring: 0 0% 83.1%;*/
|
|
||||||
/* --chart-1: 220 70% 50%;*/
|
|
||||||
/* --chart-2: 160 60% 45%;*/
|
|
||||||
/* --chart-3: 30 80% 55%;*/
|
|
||||||
/* --chart-4: 280 65% 60%;*/
|
|
||||||
/* --chart-5: 340 75% 55%;*/
|
|
||||||
/* --sidebar-background: 240 5.9% 10%;*/
|
|
||||||
/* --sidebar-foreground: 240 4.8% 95.9%;*/
|
|
||||||
/* --sidebar-primary: 224.3 76.3% 48%;*/
|
|
||||||
/* --sidebar-primary-foreground: 0 0% 100%;*/
|
|
||||||
/* --sidebar-accent: 240 3.7% 15.9%;*/
|
|
||||||
/* --sidebar-accent-foreground: 240 4.8% 95.9%;*/
|
|
||||||
/* --sidebar-border: 240 3.7% 15.9%;*/
|
|
||||||
/* --sidebar-ring: 217.2 91.2% 59.8%;*/
|
|
||||||
/* }*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -2,16 +2,14 @@
|
|||||||
import {useMutation} from "@tanstack/react-query";
|
import {useMutation} from "@tanstack/react-query";
|
||||||
import {backupButtonAction} from "@/components/wrappers/BackupButton/backup-button.action";
|
import {backupButtonAction} from "@/components/wrappers/BackupButton/backup-button.action";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {Backup} from "@prisma/client";
|
|
||||||
import {useRouter} from "next/navigation";
|
import {useRouter} from "next/navigation";
|
||||||
import {ActionResult} from "@/types/action-type";
|
|
||||||
import {ButtonWithLoading} from "@/components/wrappers/Button/ButtonWithLoading/ButtonWithLoading";
|
import {ButtonWithLoading} from "@/components/wrappers/Button/ButtonWithLoading/ButtonWithLoading";
|
||||||
|
|
||||||
export type BackupButtonProps = {
|
export type BackupButtonProps = {
|
||||||
databaseId: string;
|
databaseId: string;
|
||||||
|
disable: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const BackupButton = (props: BackupButtonProps) => {
|
export const BackupButton = (props: BackupButtonProps) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
@@ -30,9 +28,9 @@ export const BackupButton = (props: BackupButtonProps) => {
|
|||||||
await mutation.mutateAsync(props.databaseId)
|
await mutation.mutateAsync(props.databaseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonWithLoading
|
<ButtonWithLoading
|
||||||
|
disabled={props.disable}
|
||||||
text="Backup"
|
text="Backup"
|
||||||
isPending={mutation.isPending}
|
isPending={mutation.isPending}
|
||||||
size={"default"}
|
size={"default"}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||||
|
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||||
|
import {Database} from "@prisma/client";
|
||||||
|
|
||||||
|
export type DatabaseKpiPro = {
|
||||||
|
successRate: any,
|
||||||
|
database: Database
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const DatabaseKpi = (props: DatabaseKpiPro) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col sm:flex-row sm:justify-between gap-8 mb-6">
|
||||||
|
<Card className="w-full sm:w-auto flex-1">
|
||||||
|
<CardHeader className="font-bold text-xl">
|
||||||
|
Backups
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent></CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="w-full sm:w-auto flex-1">
|
||||||
|
<CardHeader className="font-bold text-xl">
|
||||||
|
Success rate
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{props.successRate ?? "Unavailable for now."}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
<Card className="w-full sm:w-auto flex-1">
|
||||||
|
<CardHeader className="font-bold text-xl">
|
||||||
|
Last contact
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{formatDateLastContact(props.database.lastContact)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,7 +6,8 @@ import {Backup, Restauration} from "@prisma/client";
|
|||||||
|
|
||||||
export type DatabaseTabsProps = {
|
export type DatabaseTabsProps = {
|
||||||
backups: Backup[]
|
backups: Backup[]
|
||||||
// restaurations: Restauration[]
|
restaurations: Restauration[]
|
||||||
|
isAlreadyRestore: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
||||||
@@ -19,12 +20,12 @@ export const DatabaseTabs = (props: DatabaseTabsProps) => {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent className="h-full justify-between" value="backup">
|
<TabsContent className="h-full justify-between" value="backup">
|
||||||
<DataTableWithPagination columns={backupColumns} data={props.backups}/>
|
<DataTableWithPagination columns={backupColumns} data={props.backups} extendedProps={props.isAlreadyRestore}/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/*<TabsContent className="h-full justify-between" value="restore">*/}
|
<TabsContent className="h-full justify-between" value="restore">
|
||||||
{/* <DataTableWithPagination columns={restoreColumns} data={props.restaurations}/>*/}
|
<DataTableWithPagination columns={restoreColumns} data={props.restaurations}/>
|
||||||
{/*</TabsContent>*/}
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipProvider,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
|
import {PropsWithChildren} from "react";
|
||||||
|
|
||||||
|
export type TooltipCustomProps = PropsWithChildren<{
|
||||||
|
text: string
|
||||||
|
disabled?: any
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function TooltipCustom(props: TooltipCustomProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.disabled ?
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger className="w-full">
|
||||||
|
{props.children}
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>{props.text}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
:
|
||||||
|
props.children
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,9 +11,10 @@ import {TablePagination} from "@/components/wrappers/table/table-pagination";
|
|||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData, TValue> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
data: TData[]
|
data: TData[]
|
||||||
|
extendedProps?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTableWithPagination<TData, TValue>({columns, data}: DataTableProps<TData, TValue>) {
|
export function DataTableWithPagination<TData, TValue>({columns, data, extendedProps}: DataTableProps<TData, TValue>) {
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([])
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
|
|
||||||
@@ -27,11 +28,14 @@ export function DataTableWithPagination<TData, TValue>({columns, data}: DataTabl
|
|||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
},
|
},
|
||||||
|
meta: {
|
||||||
|
extendedProps: extendedProps,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-between h-full">
|
<div className="flex flex-col justify-between h-full">
|
||||||
<DataTable table={table}/>
|
<DataTable table={table} />
|
||||||
<TablePagination table={table} pageSizeOptions={[5, 10, 20, 50, 100]}/>
|
<TablePagination table={table} pageSizeOptions={[5, 10, 20, 50, 100]}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/c
|
|||||||
import {flexRender} from "@tanstack/react-table";
|
import {flexRender} from "@tanstack/react-table";
|
||||||
|
|
||||||
export type dataTableProps = {
|
export type dataTableProps = {
|
||||||
table: any
|
table: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DataTable = ({table}: dataTableProps) => {
|
export const DataTable = ({table}: dataTableProps) => {
|
||||||
@@ -10,7 +10,6 @@ export const DataTable = ({table}: dataTableProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="rounded-md border w-full ">
|
<div className="rounded-md border w-full ">
|
||||||
<Table className="w-full">
|
<Table className="w-full">
|
||||||
|
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{table.getHeaderGroups().map((headerGroup) => (
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
@@ -38,7 +37,10 @@ export const DataTable = ({table}: dataTableProps) => {
|
|||||||
>
|
>
|
||||||
{row.getVisibleCells().map((cell) => (
|
{row.getVisibleCells().map((cell) => (
|
||||||
<TableCell key={cell.id}>
|
<TableCell key={cell.id}>
|
||||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -55,55 +57,3 @@ export const DataTable = ({table}: dataTableProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// import { useMemo } from "react";
|
|
||||||
// import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
||||||
// import { flexRender } from "@tanstack/react-table";
|
|
||||||
//
|
|
||||||
// export type dataTableProps = {
|
|
||||||
// table: any;
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// export const DataTable = ({ table }: dataTableProps) => {
|
|
||||||
// // Memoize the header and rows to prevent unnecessary re-renders
|
|
||||||
// const headerGroups = useMemo(() => table.getHeaderGroups(), [table]);
|
|
||||||
// const rows = useMemo(() => table.getRowModel().rows, [table]);
|
|
||||||
//
|
|
||||||
// return (
|
|
||||||
// <div className="rounded-md border w-full ">
|
|
||||||
// <Table className="w-full">
|
|
||||||
// <TableHeader>
|
|
||||||
// {headerGroups.map((headerGroup) => (
|
|
||||||
// <TableRow key={headerGroup.id}>
|
|
||||||
// {headerGroup.headers.map((header) => (
|
|
||||||
// <TableHead key={header.id}>
|
|
||||||
// {header.isPlaceholder
|
|
||||||
// ? null
|
|
||||||
// : flexRender(header.column.columnDef.header, header.getContext())}
|
|
||||||
// </TableHead>
|
|
||||||
// ))}
|
|
||||||
// </TableRow>
|
|
||||||
// ))}
|
|
||||||
// </TableHeader>
|
|
||||||
// <TableBody>
|
|
||||||
// {rows?.length ? (
|
|
||||||
// rows.map((row) => (
|
|
||||||
// <TableRow key={row.id} data-state={row.getIsSelected() && "selected"}>
|
|
||||||
// {row.getVisibleCells().map((cell) => (
|
|
||||||
// <TableCell key={cell.id}>
|
|
||||||
// {flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
// </TableCell>
|
|
||||||
// ))}
|
|
||||||
// </TableRow>
|
|
||||||
// ))
|
|
||||||
// ) : (
|
|
||||||
// <TableRow>
|
|
||||||
// <TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">
|
|
||||||
// No results.
|
|
||||||
// </TableCell>
|
|
||||||
// </TableRow>
|
|
||||||
// )}
|
|
||||||
// </TableBody>
|
|
||||||
// </Table>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|||||||
@@ -13,7 +13,12 @@ import {Button} from "@/components/ui/button";
|
|||||||
import {Download, MoreHorizontal, Trash2} from "lucide-react";
|
import {Download, MoreHorizontal, Trash2} from "lucide-react";
|
||||||
import {ReloadIcon} from "@radix-ui/react-icons";
|
import {ReloadIcon} from "@radix-ui/react-icons";
|
||||||
import {Backup} from "@prisma/client";
|
import {Backup} from "@prisma/client";
|
||||||
|
import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action";
|
||||||
|
import {useMutation} from "@tanstack/react-query";
|
||||||
|
import {createRestaurationAction} from "@/features/restore/restore.action";
|
||||||
|
import {toast} from "sonner";
|
||||||
|
import {useRouter} from "next/navigation";
|
||||||
|
import {TooltipCustom} from "@/components/wrappers/TooltipCustom/TooltipCustom";
|
||||||
|
|
||||||
|
|
||||||
export const backupColumns: ColumnDef<Backup>[] = [
|
export const backupColumns: ColumnDef<Backup>[] = [
|
||||||
@@ -37,7 +42,41 @@ export const backupColumns: ColumnDef<Backup>[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
cell: ({row}) => {
|
cell: ({row, table}) => {
|
||||||
|
const status = row.getValue("status")
|
||||||
|
const rowData: Backup = row.original
|
||||||
|
const fileName = rowData.file
|
||||||
|
// @ts-ignore
|
||||||
|
const {extendedProps} = table.options.meta;
|
||||||
|
|
||||||
|
|
||||||
|
const isRestauring = !!rowData.restaurations.find(restauration => restauration.status === "waiting");
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleDownload = async (fileName: string) => {
|
||||||
|
const url = await getFileUrlPresignedLocal(fileName)
|
||||||
|
window.open(url, '_self');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const mutationRestore = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const restauration = await createRestaurationAction({
|
||||||
|
backupId: rowData.id,
|
||||||
|
databaseId: rowData.databaseId
|
||||||
|
})
|
||||||
|
if (restauration.data.success) {
|
||||||
|
toast.success(restauration.data.actionSuccess?.message || "Restauration created successfully!");
|
||||||
|
router.refresh()
|
||||||
|
} else {
|
||||||
|
toast.error(restauration.serverError || "Failed to create restauration.");
|
||||||
|
}
|
||||||
|
}})
|
||||||
|
|
||||||
|
const handleRestore = async () => {
|
||||||
|
await mutationRestore.mutateAsync()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -49,24 +88,28 @@ export const backupColumns: ColumnDef<Backup>[] = [
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
|
{status == "success" ?
|
||||||
<DropdownMenuItem onClick={() => {
|
<>
|
||||||
|
<TooltipCustom disabled={extendedProps} text="Already a restauration waiting">
|
||||||
|
<DropdownMenuItem disabled={mutationRestore.isPending || extendedProps}
|
||||||
|
onClick={async () => {
|
||||||
|
await handleRestore()
|
||||||
}}>
|
}}>
|
||||||
<ReloadIcon/> Restore
|
<ReloadIcon/> Restore
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
</TooltipCustom>
|
||||||
<DropdownMenuItem onClick={() => {
|
<DropdownMenuItem onClick={async () => {
|
||||||
|
await handleDownload(fileName)
|
||||||
}}>
|
}}>
|
||||||
<Download/> Download
|
<Download/> Download
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
<DropdownMenuSeparator/>
|
<DropdownMenuSeparator/>
|
||||||
|
|
||||||
<DropdownMenuItem className="text-red-600" onClick={() => {
|
<DropdownMenuItem className="text-red-600" onClick={() => {
|
||||||
}}>
|
}}>
|
||||||
<Trash2/> Delete
|
<Trash2/> Delete
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"use server"
|
||||||
|
import {userAction} from "@/safe-actions";
|
||||||
|
import {z} from "zod";
|
||||||
|
import {prisma} from "@/prisma";
|
||||||
|
import {ServerActionResult} from "@/types/action-type";
|
||||||
|
import {Restauration} from "@prisma/client";
|
||||||
|
|
||||||
|
|
||||||
|
export const createRestaurationAction = userAction
|
||||||
|
.schema(z.object({
|
||||||
|
backupId: z.string(),
|
||||||
|
databaseId: z.string(),
|
||||||
|
}))
|
||||||
|
.action(async ({parsedInput, ctx}): Promise<ServerActionResult<Restauration>> => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const restauration = await prisma.restauration.create({
|
||||||
|
data: {
|
||||||
|
databaseId: parsedInput.databaseId,
|
||||||
|
backupId: parsedInput.backupId,
|
||||||
|
status: "waiting",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
value: restauration,
|
||||||
|
actionSuccess: {
|
||||||
|
message: "Restauration has been successfully created.",
|
||||||
|
messageParams: { restaurationId: restauration.id },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating restauration:", error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
actionError: {
|
||||||
|
message: "Failed to create backup.",
|
||||||
|
status: 500,
|
||||||
|
cause: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
messageParams: { message: "Error creating the restauration" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@ export function timeAgo(rawDate: string | number | Date) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function formatDateLastContact(lastContact: string | number | Date | null) {
|
export function formatDateLastContact(lastContact: string | number | Date | null) {
|
||||||
|
console.log(lastContact)
|
||||||
return lastContact
|
return lastContact
|
||||||
? format(new Date(lastContact), 'dd/MM/yyyy HH:mm')
|
? format(new Date(lastContact), 'dd/MM/yyyy HH:mm')
|
||||||
: "Never connected."
|
: "Never connected."
|
||||||
|
|||||||
Reference in New Issue
Block a user