fix: policies reload

This commit is contained in:
Théo LAGACHE
2026-02-13 11:24:53 +01:00
parent 1c97c97fa1
commit ca9bc7ae94
10 changed files with 49 additions and 12 deletions
@@ -6,8 +6,6 @@ import {eq, and, inArray} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {getOrganizationProjectDatabases} from "@/lib/services"; import {getOrganizationProjectDatabases} from "@/lib/services";
import {getActiveMember, getOrganization} from "@/lib/auth/auth"; import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {getOrganizationChannels} from "@/db/services/notification-channel";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
import {BackupModalProvider} from "@/components/wrappers/dashboard/database/backup/backup-modal-context"; import {BackupModalProvider} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
import {DatabaseContent} from "@/components/wrappers/dashboard/projects/database/database-content"; import {DatabaseContent} from "@/components/wrappers/dashboard/projects/database/database-content";
@@ -85,12 +83,6 @@ export default async function RoutePage(props: PageParams<{
notFound(); notFound();
} }
const organizationChannels = await getOrganizationChannels(organization.id);
const activeOrganizationChannels = organizationChannels.filter(channel => channel.enabled);
const organizationStorageChannels = await getOrganizationStorageChannels(organization.id);
const activeOrganizationStorageChannels = organizationStorageChannels.filter(channel => channel.enabled);
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null; const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;
@@ -110,8 +102,8 @@ export default async function RoutePage(props: PageParams<{
availableBackups={availableBackups} availableBackups={availableBackups}
successRate={successRate} successRate={successRate}
organizationId={organization.id} organizationId={organization.id}
activeOrganizationChannels={activeOrganizationChannels} activeOrganizationChannels={[]}
activeOrganizationStorageChannels={activeOrganizationStorageChannels} activeOrganizationStorageChannels={[]}
/> />
</BackupModalProvider> </BackupModalProvider>
</Page> </Page>
@@ -1,6 +1,7 @@
"use client"; "use client";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import {useMutation, useQueryClient} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {toast} from "sonner"; import {toast} from "sonner";
import {backupButtonAction} from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action"; import {backupButtonAction} from "@/components/wrappers/dashboard/backup/backup-button/backup-button.action";
@@ -15,6 +16,7 @@ export type BackupButtonProps = {
export const BackupButton = (props: BackupButtonProps) => { export const BackupButton = (props: BackupButtonProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const isMobile = useIsMobile() const isMobile = useIsMobile()
const mutation = useMutation({ const mutation = useMutation({
@@ -23,6 +25,7 @@ export const BackupButton = (props: BackupButtonProps) => {
if (backup?.data?.success) { if (backup?.data?.success) {
toast.success(backup.data.actionSuccess?.message || "Backup created successfully!"); toast.success(backup.data.actionSuccess?.message || "Backup created successfully!");
queryClient.invalidateQueries({queryKey: ["database-data", props.databaseId]}); queryClient.invalidateQueries({queryKey: ["database-data", props.databaseId]});
router.refresh();
} else { } else {
toast.error(backup?.serverError || "Failed to create backup."); toast.error(backup?.serverError || "Failed to create backup.");
} }
@@ -18,6 +18,7 @@ import {TooltipProvider} from "@/components/ui/tooltip";
import {getChannelIcon} from "@/components/wrappers/dashboard/admin/channels/helpers/common"; import {getChannelIcon} from "@/components/wrappers/dashboard/admin/channels/helpers/common";
import {Badge} from "@/components/ui/badge"; import {Badge} from "@/components/ui/badge";
import {getStatusColor, getStatusIcon} from "@/components/wrappers/dashboard/admin/notifications/logs/columns"; import {getStatusColor, getStatusIcon} from "@/components/wrappers/dashboard/admin/notifications/logs/columns";
import {useRouter} from "next/navigation";
import { import {
createRestorationBackupAction, deleteBackupAction, deleteBackupStorageAction, createRestorationBackupAction, deleteBackupAction, deleteBackupStorageAction,
downloadBackupAction downloadBackupAction
@@ -39,6 +40,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
const filteredBackupStorages = backup.storages?.filter((storage) => storage.deletedAt === null) ?? [] const filteredBackupStorages = backup.storages?.filter((storage) => storage.deletedAt === null) ?? []
const {closeModal} = useBackupModal(); const {closeModal} = useBackupModal();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const form = useZodForm({ const form = useZodForm({
schema: BackupActionsSchema, schema: BackupActionsSchema,
@@ -72,6 +74,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (inner?.success) { if (inner?.success) {
toast.success(inner.actionSuccess?.message); toast.success(inner.actionSuccess?.message);
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]}); queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
router.refresh();
if (action === "download") { if (action === "download") {
const url = inner.value const url = inner.value
if (typeof url === "string") { if (typeof url === "string") {
@@ -89,6 +92,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (action === "delete") { if (action === "delete") {
toast.success("Backup deleted successfully.") toast.success("Backup deleted successfully.")
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]}); queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
router.refresh();
closeModal() closeModal()
} else { } else {
toast.error(inner?.actionError?.message ?? "An error occurred."); toast.error(inner?.actionError?.message ?? "An error occurred.");
@@ -110,6 +114,7 @@ export const BackupActionsForm = ({backup, action}: BackupActionsFormProps) => {
if (inner?.success) { if (inner?.success) {
toast.success(inner.actionSuccess?.message); toast.success(inner.actionSuccess?.message);
queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]}); queryClient.invalidateQueries({queryKey: ["database-data", backup.databaseId]});
router.refresh();
closeModal() closeModal()
} else { } else {
toast.error(inner?.actionError?.message); toast.error(inner?.actionError?.message);
@@ -5,6 +5,8 @@ import {db} from "@/db";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
import * as drizzleDb from "@/db"; import * as drizzleDb from "@/db";
import {BackupWith, Restoration} from "@/db/schema/07_database"; import {BackupWith, Restoration} from "@/db/schema/07_database";
import {getOrganizationChannels} from "@/db/services/notification-channel";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
export const getDatabaseDataAction = userAction export const getDatabaseDataAction = userAction
.schema( .schema(
@@ -19,6 +21,9 @@ export const getDatabaseDataAction = userAction
where: eq(drizzleDb.schemas.database.id, databaseId), where: eq(drizzleDb.schemas.database.id, databaseId),
with: { with: {
project: true, project: true,
retentionPolicy: true,
alertPolicies: true,
storagePolicies: true
} }
}); });
@@ -45,10 +50,23 @@ export const getDatabaseDataAction = userAction
const successfulBackups = backups.filter(b => b.status === "success").length; const successfulBackups = backups.filter(b => b.status === "success").length;
const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null; const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;
let activeOrganizationChannels = [];
let activeOrganizationStorageChannels = [];
if (database?.project?.organizationId) {
const organizationChannels = await getOrganizationChannels(database.project.organizationId);
activeOrganizationChannels = organizationChannels.filter(channel => channel.enabled);
const organizationStorageChannels = await getOrganizationStorageChannels(database.project.organizationId);
activeOrganizationStorageChannels = organizationStorageChannels.filter(channel => channel.enabled);
}
return { return {
database, database,
backups, backups,
restorations, restorations,
activeOrganizationChannels,
activeOrganizationStorageChannels,
stats: { stats: {
totalBackups, totalBackups,
availableBackups, availableBackups,
@@ -14,6 +14,7 @@ import {Switch} from "@/components/ui/switch";
import {Card} from "@/components/ui/card"; import {Card} from "@/components/ui/card";
import Link from "next/link"; import Link from "next/link";
import {useIsMobile} from "@/hooks/use-mobile"; import {useIsMobile} from "@/hooks/use-mobile";
import {useRouter} from "next/navigation";
import { import {
ChannelKind, ChannelKind,
getChannelIcon, getChannelIcon,
@@ -48,6 +49,7 @@ export const ChannelPoliciesForm = ({
kind kind
}: ChannelPoliciesFormProps) => { }: ChannelPoliciesFormProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const channelText = getChannelTextBasedOnKind(kind); const channelText = getChannelTextBasedOnKind(kind);
@@ -130,6 +132,7 @@ export const ChannelPoliciesForm = ({
onSuccess: () => { onSuccess: () => {
toast.success("Policies saved successfully"); toast.success("Policies saved successfully");
queryClient.invalidateQueries({queryKey: ["database-data", database.id]}); queryClient.invalidateQueries({queryKey: ["database-data", database.id]});
router.refresh();
}, },
onError: (error: any) => { toast.error(error.message || "Failed to save policies"); }, onError: (error: any) => { toast.error(error.message || "Failed to save policies"); },
}); });
@@ -8,6 +8,7 @@ import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { useState } from "react"; import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { toast } from "sonner"; import { toast } from "sonner";
import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action"; import { updateDatabaseBackupPolicyAction } from "@/components/wrappers/dashboard/database/cron-button/cron.action";
import {Database} from "@/db/schema/07_database"; import {Database} from "@/db/schema/07_database";
@@ -18,6 +19,7 @@ export type CronButtonProps = {
export const CronButton = (props: CronButtonProps) => { export const CronButton = (props: CronButtonProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const [isSwitched, setIsSwitched] = useState(props.database.backupPolicy !== null); const [isSwitched, setIsSwitched] = useState(props.database.backupPolicy !== null);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -27,6 +29,7 @@ export const CronButton = (props: CronButtonProps) => {
onSuccess: () => { onSuccess: () => {
toast.success(`Method updated successfully.`); toast.success(`Method updated successfully.`);
queryClient.invalidateQueries({ queryKey: ["database-data", props.database.id] }); queryClient.invalidateQueries({ queryKey: ["database-data", props.database.id] });
router.refresh();
}, },
onError: () => { onError: () => {
toast.error(`An error occurred while updating backup method.`); toast.error(`An error occurred while updating backup method.`);
@@ -1,6 +1,7 @@
import {AdvancedCronSelect} from "./advanced-cron-select"; import {AdvancedCronSelect} from "./advanced-cron-select";
import {updateDatabaseBackupPolicyAction} from "@/components/wrappers/dashboard/database/cron-button/cron.action"; import {updateDatabaseBackupPolicyAction} from "@/components/wrappers/dashboard/database/cron-button/cron.action";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import {useMutation, useQueryClient} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {useState} from "react"; import {useState} from "react";
import {toast} from "sonner"; import {toast} from "sonner";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button";
@@ -15,6 +16,7 @@ export type CronInputProps = {
export const CronInput = ({database, onSuccess}: CronInputProps) => { export const CronInput = ({database, onSuccess}: CronInputProps) => {
const [cron, setCron] = useState<string>(database.backupPolicy ?? "* * * * *"); const [cron, setCron] = useState<string>(database.backupPolicy ?? "* * * * *");
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const updateBackupPolicy = useMutation({ const updateBackupPolicy = useMutation({
mutationFn: (value: string) => updateDatabaseBackupPolicyAction({databaseId: database.id, backupPolicy: value}), mutationFn: (value: string) => updateDatabaseBackupPolicyAction({databaseId: database.id, backupPolicy: value}),
@@ -22,6 +24,7 @@ export const CronInput = ({database, onSuccess}: CronInputProps) => {
toast.success(`Cron updated successfully.`); toast.success(`Cron updated successfully.`);
onSuccess?.() onSuccess?.()
queryClient.invalidateQueries({queryKey: ["database-data", database.id]}); queryClient.invalidateQueries({queryKey: ["database-data", database.id]});
router.refresh();
}, },
onError: () => { onError: () => {
toast.error(`An error occurred while updating cron value.`); toast.error(`An error occurred while updating cron value.`);
@@ -2,6 +2,7 @@
import {DropZoneFile} from "@/components/wrappers/common/dropzone/dropzone-file"; import {DropZoneFile} from "@/components/wrappers/common/dropzone/dropzone-file";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import {useMutation, useQueryClient} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {useState} from "react"; import {useState} from "react";
import {Loader2} from "lucide-react"; import {Loader2} from "lucide-react";
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading"; import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
@@ -16,6 +17,7 @@ type UploadRetentionZoneProps = {
export const UploadBackupZone = ({onSuccessAction, databaseId}: UploadRetentionZoneProps) => { export const UploadBackupZone = ({onSuccessAction, databaseId}: UploadRetentionZoneProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const [isProcessing, setIsProcessing] = useState(false); const [isProcessing, setIsProcessing] = useState(false);
const [file, setFile] = useState<File | null>(null); const [file, setFile] = useState<File | null>(null);
@@ -37,6 +39,7 @@ export const UploadBackupZone = ({onSuccessAction, databaseId}: UploadRetentionZ
toast.success(inner.actionSuccess?.message); toast.success(inner.actionSuccess?.message);
onSuccessAction?.() onSuccessAction?.()
queryClient.invalidateQueries({queryKey: ["database-data", databaseId]}); queryClient.invalidateQueries({queryKey: ["database-data", databaseId]});
router.refresh();
} else { } else {
toast.error(inner?.actionError?.message); toast.error(inner?.actionError?.message);
} }
@@ -11,6 +11,7 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import {RetentionSettings, RetentionSettingsSchema} from "./backup-retention-settings.schema"; import {RetentionSettings, RetentionSettingsSchema} from "./backup-retention-settings.schema";
import {useMutation, useQueryClient} from "@tanstack/react-query"; import {useMutation, useQueryClient} from "@tanstack/react-query";
import {useRouter} from "next/navigation";
import {updateOrCreateBackupRetentionPolicyAction} from "./backup-retention-settings.action"; import {updateOrCreateBackupRetentionPolicyAction} from "./backup-retention-settings.action";
import {DatabaseWith, RetentionPolicy} from "@/db/schema/07_database"; import {DatabaseWith, RetentionPolicy} from "@/db/schema/07_database";
import {toast} from "sonner"; import {toast} from "sonner";
@@ -28,6 +29,7 @@ export type BackupRetentionSettingsFormProps = {
export const BackupRetentionSettingsForm = ({defaultValues, database}: BackupRetentionSettingsFormProps) => { export const BackupRetentionSettingsForm = ({defaultValues, database}: BackupRetentionSettingsFormProps) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter();
const defaultValuesFormatted: RetentionSettings = { const defaultValuesFormatted: RetentionSettings = {
type: defaultValues?.type, type: defaultValues?.type,
@@ -55,6 +57,7 @@ export const BackupRetentionSettingsForm = ({defaultValues, database}: BackupRet
onSuccess: () => { onSuccess: () => {
toast.success("Retention policy updated successfully."); toast.success("Retention policy updated successfully.");
queryClient.invalidateQueries({queryKey: ["database-data", database.id]}); queryClient.invalidateQueries({queryKey: ["database-data", database.id]});
router.refresh();
}, },
onError: () => { onError: () => {
toast.error("An error occurred while updating retention policy."); toast.error("An error occurred while updating retention policy.");
@@ -50,6 +50,8 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
}, },
backups: props.backups, backups: props.backups,
restorations: props.restorations, restorations: props.restorations,
activeOrganizationChannels: props.activeOrganizationChannels,
activeOrganizationStorageChannels: props.activeOrganizationStorageChannels,
stats: { stats: {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
@@ -64,6 +66,8 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
const database = data?.database ?? props.database; const database = data?.database ?? props.database;
const backups = data?.backups ?? props.backups; const backups = data?.backups ?? props.backups;
const restorations = data?.restorations ?? props.restorations; const restorations = data?.restorations ?? props.restorations;
const activeOrganizationChannels = data?.activeOrganizationChannels ?? props.activeOrganizationChannels;
const activeOrganizationStorageChannels = data?.activeOrganizationStorageChannels ?? props.activeOrganizationStorageChannels;
const stats = data?.stats ?? { const stats = data?.stats ?? {
totalBackups: props.totalBackups, totalBackups: props.totalBackups,
availableBackups: props.availableBackups, availableBackups: props.availableBackups,
@@ -91,14 +95,14 @@ export const DatabaseContent = (props: DatabaseContentProps) => {
database={database} database={database}
kind={"notification"} kind={"notification"}
icon={<Megaphone/>} icon={<Megaphone/>}
channels={props.activeOrganizationChannels} channels={activeOrganizationChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ChannelPoliciesModal <ChannelPoliciesModal
database={database} database={database}
icon={<HardDrive/>} icon={<HardDrive/>}
kind={"storage"} kind={"storage"}
channels={props.activeOrganizationStorageChannels} channels={activeOrganizationStorageChannels}
organizationId={props.organizationId} organizationId={props.organizationId}
/> />
<ImportModal database={database}/> <ImportModal database={database}/>