mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Some refactoring in database and projects side.
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { TablePaginationNavigation } from "@/components/wrappers/common/table/table-pagination-navigation";
|
||||
import { TablePaginationSize } from "@/components/wrappers/common/table/table-pagination-size";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {TablePaginationNavigation} from "@/components/wrappers/common/table/table-pagination-navigation";
|
||||
import {TablePaginationSize} from "@/components/wrappers/common/table/table-pagination-size";
|
||||
import {cn} from "@/lib/utils";
|
||||
|
||||
interface tablePaginationProps {
|
||||
className?: string;
|
||||
@@ -12,12 +12,14 @@ interface tablePaginationProps {
|
||||
}
|
||||
|
||||
export function TablePagination(props: tablePaginationProps) {
|
||||
const { className, table, maxVisiblePages = 3, pageSizeOptions = [10, 20, 30, 40, 50] } = props;
|
||||
const {className, table, maxVisiblePages = 3, pageSizeOptions = [10, 20, 30, 40, 50]} = props;
|
||||
|
||||
return (
|
||||
<div className={cn("flex gap-x-4 flex-row w-full", className)}>
|
||||
<TablePaginationSize table={table} pageSizeOptions={pageSizeOptions} />
|
||||
<TablePaginationNavigation table={table} maxVisiblePages={maxVisiblePages} className="justify-end mt-0" />
|
||||
<div
|
||||
className={cn("flex gap-x-4", className)}
|
||||
>
|
||||
<TablePaginationSize table={table} pageSizeOptions={pageSizeOptions}/>
|
||||
<TablePaginationNavigation table={table} maxVisiblePages={maxVisiblePages} className="justify-end mt-0"/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {Form, FormControl, FormField, FormItem, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
import {Plus, Trash2} from "lucide-react";
|
||||
import {InfoIcon, Plus, Trash2} from "lucide-react";
|
||||
import {useFieldArray} from "react-hook-form";
|
||||
|
||||
import {
|
||||
AlertPoliciesSchema, AlertPoliciesType, AlertPolicySchema, AlertPolicyType,
|
||||
AlertPoliciesSchema, AlertPoliciesType, AlertPolicyType,
|
||||
EVENT_KIND_OPTIONS
|
||||
} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy.schema";
|
||||
import {DatabaseWith} from "@/db/schema/07_database";
|
||||
@@ -16,13 +15,14 @@ import {Separator} from "@/components/ui/separator";
|
||||
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select";
|
||||
import {MultiSelect} from "@/components/wrappers/common/multiselect/multi-select";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {deleteBackupAction} from "@/features/dashboard/restore/restore.action";
|
||||
import {toast} from "sonner";
|
||||
import {
|
||||
createAlertPoliciesAction, deleteAlertPoliciesAction,
|
||||
updateAlertPoliciesAction
|
||||
} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy.action";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Switch} from "@/components/ui/switch";
|
||||
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
|
||||
|
||||
type AlertPolicyFormProps = {
|
||||
onSuccess?: () => void;
|
||||
@@ -33,13 +33,13 @@ type AlertPolicyFormProps = {
|
||||
|
||||
export const AlertPolicyForm = ({database, notificationChannels, organizationId, onSuccess}: AlertPolicyFormProps) => {
|
||||
const router = useRouter()
|
||||
console.log("database",database)
|
||||
const organizationNotificationChannels = notificationChannels.map(channel => channel.id) ?? [];
|
||||
|
||||
const formattedAlertPoliciesList = (alertPolicies: AlertPolicy[]) => {
|
||||
return alertPolicies.map((alertPolicy) => ({
|
||||
notificationChannelId: alertPolicy.notificationChannelId,
|
||||
eventKinds: alertPolicy.eventKinds
|
||||
eventKinds: alertPolicy.eventKinds,
|
||||
enabled: alertPolicy.enabled
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -49,6 +49,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
alertPolicies: database.alertPolicies && database.alertPolicies.length > 0 ? formattedAlertPoliciesList(database.alertPolicies) : [{
|
||||
notificationChannelId: "",
|
||||
eventKinds: [],
|
||||
enabled: true,
|
||||
}],
|
||||
},
|
||||
});
|
||||
@@ -60,7 +61,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
|
||||
|
||||
const addAlertPolicy = () => {
|
||||
append({id: "", eventKinds: []})
|
||||
append({id: "", eventKinds: [], enabled: true});
|
||||
}
|
||||
|
||||
const removeAlertPolicy = (index: number) => {
|
||||
@@ -76,6 +77,9 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
const mutation = useMutation({
|
||||
mutationFn: async ({alertPolicies}: AlertPoliciesType) => {
|
||||
|
||||
|
||||
|
||||
|
||||
console.log(alertPolicies)
|
||||
|
||||
const defaultFormatedAlertPolicies = formattedAlertPoliciesList(database?.alertPolicies ?? []);
|
||||
@@ -138,7 +142,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
);
|
||||
|
||||
|
||||
console.log("failedActions",failedActions);
|
||||
console.log("failedActions", failedActions);
|
||||
if (failedActions.length > 0) {
|
||||
const firstError = failedActions[0].data.actionError;
|
||||
const message = firstError?.message || "One or more operations failed";
|
||||
@@ -149,7 +153,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
},
|
||||
onSuccess: () => {
|
||||
toast.success("Alert policies saved successfully");
|
||||
onSuccess?.();
|
||||
// onSuccess?.();
|
||||
router.refresh();
|
||||
},
|
||||
onError: (error: any) => {
|
||||
@@ -199,8 +203,6 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
control={form.control}
|
||||
name={`alertPolicies.${index}.notificationChannelId`}
|
||||
render={({field}) => {
|
||||
|
||||
|
||||
const selectedIds = form
|
||||
.watch("alertPolicies")
|
||||
.map((a: AlertPolicyType) => a.notificationChannelId)
|
||||
@@ -219,7 +221,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
>
|
||||
|
||||
<FormControl>
|
||||
<SelectTrigger className="h-10 w-full">
|
||||
<SelectTrigger className="h-10 w-full ">
|
||||
<SelectValue
|
||||
placeholder="Select notification channel"/>
|
||||
</SelectTrigger>
|
||||
@@ -239,6 +241,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`alertPolicies.${index}.eventKinds`}
|
||||
@@ -251,19 +254,58 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
defaultValue={field.value ?? []}
|
||||
placeholder="Select event kinds"
|
||||
variant="inverted"
|
||||
animation={2}
|
||||
animation={0}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex flex-col gap-3 justify-between items-center ">
|
||||
<Button type="button" variant="outline"
|
||||
onClick={() => removeAlertPolicy(index)}>
|
||||
<Trash2 className="w-4 h-4"/>
|
||||
</Button>
|
||||
<div className="flex h-full justify-center items-center">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div tabIndex={0} className="inline-flex rounded-md">
|
||||
{/*<Switch*/}
|
||||
{/* checked={true}*/}
|
||||
{/* onCheckedChange={async () => {*/}
|
||||
{/* }}*/}
|
||||
{/*/>*/}
|
||||
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={`alertPolicies.${index}.enabled`}
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-64 text-pretty">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<InfoIcon className="size-4"/>
|
||||
<p>This is for activating the alert policy</p>
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -279,7 +321,7 @@ export const AlertPolicyForm = ({database, notificationChannels, organizationId,
|
||||
Cancel
|
||||
</ButtonWithLoading>
|
||||
<ButtonWithLoading isPending={false}>
|
||||
Submit
|
||||
Save
|
||||
</ButtonWithLoading>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
@@ -27,6 +27,7 @@ export const createAlertPoliciesAction = userAction
|
||||
databaseId: parsedInput.databaseId,
|
||||
notificationChannelId: policy.notificationChannelId,
|
||||
eventKinds: policy.eventKinds,
|
||||
enabled: policy.enabled,
|
||||
}));
|
||||
|
||||
const insertedPolicies = await db
|
||||
|
||||
@@ -5,6 +5,7 @@ export const AlertPolicySchema =
|
||||
z.object({
|
||||
notificationChannelId: z.string().min(1, "Please select a notification channel"),
|
||||
eventKinds: z.enum(['error_backup', 'error_restore', 'success_restore', 'success_backup', 'weekly_report']).array().nonempty(),
|
||||
enabled: z.boolean().default(true),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+12
-3
@@ -4,19 +4,28 @@ import {userAction} from "@/lib/safe-actions/actions";
|
||||
import {z} from "zod";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {ServerActionResult} from "@/types/action-type";
|
||||
import {and, eq} from "drizzle-orm";
|
||||
import {and, eq, inArray} from "drizzle-orm";
|
||||
import {db} from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
|
||||
export const deleteProjectAction = userAction.schema(z.string()).action(async ({parsedInput}): Promise<ServerActionResult<typeof drizzleDb.schemas.project.$inferSelect>> => {
|
||||
try {
|
||||
const uuid = uuidv4();
|
||||
await db
|
||||
const databasesUpdated = await db
|
||||
.update(drizzleDb.schemas.database)
|
||||
.set({
|
||||
projectId: null,
|
||||
backupPolicy: null
|
||||
})
|
||||
.where(eq(drizzleDb.schemas.database.projectId, parsedInput));
|
||||
.where(eq(drizzleDb.schemas.database.projectId, parsedInput)).returning();
|
||||
|
||||
|
||||
const databasesToRemove = databasesUpdated.map((db) => db.id);
|
||||
console.log(databasesUpdated);
|
||||
console.log(databasesToRemove);
|
||||
|
||||
await db.delete(drizzleDb.schemas.retentionPolicy)
|
||||
.where(inArray(drizzleDb.schemas.retentionPolicy.databaseId, databasesToRemove)).execute();
|
||||
|
||||
const updatedProjects = await db
|
||||
.update(drizzleDb.schemas.project)
|
||||
|
||||
@@ -109,7 +109,6 @@ export const DatabaseBackupList = (props: DatabaseBackupListProps) => {
|
||||
enablePagination
|
||||
selectedActions={(rows) => (
|
||||
<>
|
||||
|
||||
<div className="flex justify-start md:justify-between gap-3 md:gap-0 items-center w-full ml-0">
|
||||
<div className="flex gap-2">
|
||||
{!isMember && (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use server";
|
||||
|
||||
import {userAction} from "@/lib/safe-actions/actions";
|
||||
import { ProjectSchema } from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
|
||||
import { z } from "zod";
|
||||
import { ServerActionResult } from "@/types/action-type";
|
||||
import { db } from "@/db";
|
||||
import {ProjectSchema} from "@/components/wrappers/dashboard/projects/project-form/project-form.schema";
|
||||
import {z} from "zod";
|
||||
import {ServerActionResult} from "@/types/action-type";
|
||||
import {db} from "@/db";
|
||||
import {and, eq, inArray} from "drizzle-orm";
|
||||
import {Project} from "@/db/schema/06_project";
|
||||
import * as drizzleDb from "@/db";
|
||||
@@ -18,12 +18,12 @@ export const createProjectAction = userAction
|
||||
organizationId: z.string(),
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }): Promise<ServerActionResult<Project>> => {
|
||||
.action(async ({parsedInput}): Promise<ServerActionResult<Project>> => {
|
||||
try {
|
||||
const slug = slugify(parsedInput.data.name);
|
||||
|
||||
const existingProject = await db.query.project.findFirst({
|
||||
where: and(eq(drizzleDb.schemas.project.name, parsedInput.data.name) ),
|
||||
where: and(eq(drizzleDb.schemas.project.name, parsedInput.data.name)),
|
||||
})
|
||||
|
||||
if (existingProject) {
|
||||
@@ -32,7 +32,7 @@ export const createProjectAction = userAction
|
||||
actionError: {
|
||||
message: "A project with this name already exists.",
|
||||
status: 400,
|
||||
messageParams: { projectName: parsedInput.data.name },
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export const createProjectAction = userAction
|
||||
if (parsedInput.data.databases.length > 0) {
|
||||
await db
|
||||
.update(drizzleDb.schemas.database)
|
||||
.set({ projectId: createdProject.id })
|
||||
.set({projectId: createdProject.id})
|
||||
.where(inArray(drizzleDb.schemas.database.id, parsedInput.data.databases));
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export const createProjectAction = userAction
|
||||
value: createdProject,
|
||||
actionSuccess: {
|
||||
message: "Project has been successfully created.",
|
||||
messageParams: { projectName: parsedInput.data.name },
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -69,15 +69,13 @@ export const createProjectAction = userAction
|
||||
message: "Failed to create project.",
|
||||
status: 500,
|
||||
cause: error instanceof Error ? error.message : "Unknown error",
|
||||
messageParams: { projectName: parsedInput.data.name },
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
export const updateProjectAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
@@ -86,7 +84,7 @@ export const updateProjectAction = userAction
|
||||
projectId: z.string(),
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }): Promise<ServerActionResult<Project>> => {
|
||||
.action(async ({parsedInput}): Promise<ServerActionResult<Project>> => {
|
||||
try {
|
||||
const existing = await db.query.project.findFirst({
|
||||
where: eq(drizzleDb.schemas.project.id, parsedInput.projectId),
|
||||
@@ -106,11 +104,18 @@ export const updateProjectAction = userAction
|
||||
const databasesToRemove = existingDbIds.filter((id: string) => !newDbIds.includes(id));
|
||||
|
||||
if (databasesToAdd.length > 0) {
|
||||
await db.update(drizzleDb.schemas.database).set({ projectId: parsedInput.projectId }).where(inArray(drizzleDb.schemas.database.id, databasesToAdd));
|
||||
await db.update(drizzleDb.schemas.database).set({projectId: parsedInput.projectId}).where(inArray(drizzleDb.schemas.database.id, databasesToAdd));
|
||||
}
|
||||
|
||||
if (databasesToRemove.length > 0) {
|
||||
await db.update(drizzleDb.schemas.database).set({ projectId: null }).where(inArray(drizzleDb.schemas.database.id, databasesToRemove));
|
||||
await db.update(drizzleDb.schemas.database).set({
|
||||
projectId: null,
|
||||
backupPolicy: null
|
||||
}).where(inArray(drizzleDb.schemas.database.id, databasesToRemove));
|
||||
|
||||
await db.delete(drizzleDb.schemas.retentionPolicy)
|
||||
.where(inArray(drizzleDb.schemas.retentionPolicy.databaseId, databasesToRemove)).execute();
|
||||
|
||||
}
|
||||
// const slug = slugify(parsedInput.data.name);
|
||||
|
||||
@@ -128,7 +133,7 @@ export const updateProjectAction = userAction
|
||||
value: updatedProject,
|
||||
actionSuccess: {
|
||||
message: "Project has been successfully updated.",
|
||||
messageParams: { projectName: parsedInput.data.name },
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -138,7 +143,7 @@ export const updateProjectAction = userAction
|
||||
message: "Failed to update project.",
|
||||
status: 500,
|
||||
cause: error instanceof Error ? error.message : "Unknown error",
|
||||
messageParams: { projectName: parsedInput.data.name },
|
||||
messageParams: {projectName: parsedInput.data.name},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user