fix: data-loading (#364)

* chore: remove dead comment in database page

* fix(data-table): manualPagination support

* fix(hooks): useServerDataTable for server-side pagination

* fix(database):  paginated backup and restoration list actions

* fix(database): server-paginate backup list

* fix(database): server-paginate restoration list

* fix(database): switch tabs via History API instead of router.push

* fix(logs): load job logs on demand when the modal opens

* perf(database): slim metadata poll and drop eager backup/log loading

* fix(database): invalidate backups/restorations list keys on mutations for instant refresh

* fix(logs): disable log button when row has no logs

* fix(logs): fetch logs on button click, open modal already populated

* fix: loading log modal

---------

Co-authored-by: charles-gauthereau <charles.gauthereau@soluce-technologies.com>
This commit is contained in:
Charles GTE
2026-07-07 19:39:25 +02:00
committed by GitHub
co-authored by charles-gauthereau
parent 141abbae9d
commit 47d78943d8
20 changed files with 511 additions and 166 deletions
@@ -2,7 +2,7 @@ import { PageParams } from "@/types/next";
import { notFound, redirect } from "next/navigation";
import { Page } from "@/features/layout/components/page";
import { db } from "@/db";
import { eq, and, inArray } from "drizzle-orm";
import { eq, and, inArray, isNull } from "drizzle-orm";
import * as drizzleDb from "@/db";
import { getOrganizationProjectDatabases } from "@/db/services/project";
import { getActiveMember, getOrganization } from "@/lib/auth/auth";
@@ -49,38 +49,22 @@ export default async function RoutePage(
redirect("/dashboard/projects");
}
const backups = await db.query.backup.findMany({
where: eq(drizzleDb.schemas.backup.databaseId, dbItem.id),
with: {
restorations: true,
storages: {
with: {
storageChannel: true,
},
},
logs: true,
},
orderBy: (b, { desc }) => [desc(b.createdAt)],
});
const restorations = await db.query.restoration.findMany({
where: eq(drizzleDb.schemas.restoration.databaseId, dbItem.id),
with: {
logs: true,
},
orderBy: (r, { desc }) => [desc(r.createdAt)],
});
//const isAlreadyBackup = backups.some((b) => b.status === "waiting" || b.status === "ongoing");
const isAlreadyRestore = restorations.some((r) => r.status === "waiting");
const totalBackups = await db
.select({ count: drizzleDb.schemas.backup.id })
.from(drizzleDb.schemas.backup)
.where(eq(drizzleDb.schemas.backup.databaseId, dbItem.id))
.then((rows) => rows.length);
const availableBackups = backups.filter((b) => !b.deletedAt).length;
const availableBackups = await db
.select({ count: drizzleDb.schemas.backup.id })
.from(drizzleDb.schemas.backup)
.where(
and(
eq(drizzleDb.schemas.backup.databaseId, dbItem.id),
isNull(drizzleDb.schemas.backup.deletedAt),
),
)
.then((rows) => rows.length);
const successfulBackups = await db
.select({ count: drizzleDb.schemas.backup.id })
@@ -93,6 +77,17 @@ export default async function RoutePage(
)
.then((rows) => rows.length);
const isAlreadyRestore = await db
.select({ count: drizzleDb.schemas.restoration.id })
.from(drizzleDb.schemas.restoration)
.where(
and(
eq(drizzleDb.schemas.restoration.databaseId, dbItem.id),
eq(drizzleDb.schemas.restoration.status, "waiting"),
),
)
.then((rows) => rows.length > 0);
const [settings] = await db
.select()
.from(drizzleDb.schemas.setting)
@@ -109,8 +104,6 @@ export default async function RoutePage(
const successRate =
totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;
//const isMember = activeMember?.role === "member";
return (
<Page>
<LogsModalProvider>
@@ -121,8 +114,6 @@ export default async function RoutePage(
database={dbItem}
databaseHealthLogs={databaseHealthLogs}
isAlreadyRestore={isAlreadyRestore}
restorations={restorations}
backups={backups}
totalBackups={totalBackups}
availableBackups={availableBackups}
successRate={successRate}