mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on notification system.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
import { z } from "zod";
|
||||
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
|
||||
import { EmailFormSchema } from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "@/db";
|
||||
import * as drizzleDb from "@/db";
|
||||
|
||||
@@ -19,11 +19,11 @@ import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
import {
|
||||
EmailFormSchema,
|
||||
EmailFormType
|
||||
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
|
||||
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {
|
||||
updateEmailSettingsAction
|
||||
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.action";
|
||||
} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.action";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import {EmailForm} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form";
|
||||
import {EmailForm} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form";
|
||||
import {Setting} from "@/db/schema/01_setting";
|
||||
import {EmailFormType} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
|
||||
import {EmailFormType} from "@/components/wrappers/dashboard/admin/settings/email/email-form/email-form.schema";
|
||||
|
||||
|
||||
export type SettingsEmailSectionProps = {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {Download} from "lucide-react";
|
||||
import {getFileUrlPresignedLocal} from "@/features/upload/private/upload.action";
|
||||
import {toast} from "sonner";
|
||||
|
||||
export type AdminSettingsTabProps = {};
|
||||
|
||||
export const AdminSettingsSection = (props: AdminSettingsTabProps) => {
|
||||
|
||||
const handleDownloadKey = async () => {
|
||||
|
||||
let url: string = "";
|
||||
const data = await getFileUrlPresignedLocal({dir: "private/keys/", fileName: "server_public.pem"})
|
||||
if (data?.data?.success) {
|
||||
url = data.data.value ?? "";
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const errorMessage = data?.data?.actionError?.message || "Failed to get file!";
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
window.open(url, "_self");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-4 h-full py-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Instance settings</CardTitle>
|
||||
<CardDescription>Manage portabase settings</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Download Public Key</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Used for encrypting communications with this instance.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={handleDownloadKey} variant="outline" size="sm">
|
||||
<Download className="h-4 w-4 mr-2"/>
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
"use client"
|
||||
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||
import {Info, ShieldCheck} from "lucide-react";
|
||||
import {Switch} from "@/components/ui/switch";
|
||||
import {Label} from "@/components/ui/label";
|
||||
import {StorageS3Form} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/storage-s3-form";
|
||||
import {useState} from "react";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {checkConnexionToS3} from "@/features/upload/public/upload.action";
|
||||
import {toast} from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {
|
||||
updateStorageSettingsAction
|
||||
} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.action";
|
||||
import {Setting} from "@/db/schema/01_setting";
|
||||
import {S3FormType} from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
|
||||
|
||||
export type SettingsStorageSectionProps = {
|
||||
settings: Setting;
|
||||
};
|
||||
|
||||
export const SettingsStorageSection = (props: SettingsStorageSectionProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const result = await checkConnexionToS3();
|
||||
if (result.error) {
|
||||
toast.error("An error occured during the connexion !");
|
||||
} else {
|
||||
toast.success("Connexion succeed!");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [isSwitched, setIsSwitched] = useState<boolean>(props.settings.storage !== "local");
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: () => updateStorageSettingsAction({name: "system", data: {storage: isSwitched ? "s3" : "local"}}),
|
||||
onSuccess: () => {
|
||||
toast.success(`Settings updated successfully.`);
|
||||
router.refresh();
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(`An error occurred while updating settings information.`);
|
||||
},
|
||||
});
|
||||
|
||||
const HandleSwitchStorage = async () => {
|
||||
setIsSwitched(!isSwitched);
|
||||
await updateMutation.mutateAsync();
|
||||
};
|
||||
|
||||
const extractS3FormValues = (settings: Setting): S3FormType | undefined => {
|
||||
if (!settings.s3EndPointUrl) return undefined;
|
||||
return {
|
||||
s3EndPointUrl: settings.s3EndPointUrl,
|
||||
s3AccessKeyId: settings.s3AccessKeyId!,
|
||||
s3SecretAccessKey: settings.s3SecretAccessKey!,
|
||||
S3BucketName: settings.S3BucketName!,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<Alert className="mt-3">
|
||||
<Info className="h-4 w-4"/>
|
||||
<AlertTitle>Informations</AlertTitle>
|
||||
<AlertDescription>
|
||||
Actually you can only store you data in one place : s3 compatible or in local. For exemple you
|
||||
cannot choose to store images in one place
|
||||
and backups files in another.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<div className="flex flex-col h-full py-4 ">
|
||||
<div className="flex items-center justify-between space-x-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="storage-mode">Storage Mode (Local/s3 compatible)</Label>
|
||||
<Switch
|
||||
checked={isSwitched}
|
||||
onCheckedChange={async () => {
|
||||
await HandleSwitchStorage();
|
||||
}}
|
||||
id="storage-mode"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ButtonWithLoading
|
||||
size={"default"}
|
||||
disabled={!isSwitched}
|
||||
isPending={mutation.isPending}
|
||||
onClick={async () => {
|
||||
await mutation.mutateAsync();
|
||||
}}
|
||||
icon={<ShieldCheck/>}>Test connexion</ButtonWithLoading>
|
||||
</div>
|
||||
</div>
|
||||
{isSwitched && (
|
||||
<div className="mt-5">
|
||||
<StorageS3Form defaultValues={extractS3FormValues(props.settings)}/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { db } from "@/db";
|
||||
import { S3FormSchema, StorageSwitchSchema } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import * as drizzleDb from "@/db";
|
||||
import {userAction} from "@/lib/safe-actions/actions";
|
||||
|
||||
export const updateS3SettingsAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
data: S3FormSchema,
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }) => {
|
||||
const { name, data } = parsedInput;
|
||||
|
||||
const [updatedSettings] = await db
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set({ ...data })
|
||||
.where(eq(drizzleDb.schemas.setting.name, name))
|
||||
.returning();
|
||||
|
||||
return {
|
||||
data: updatedSettings,
|
||||
};
|
||||
});
|
||||
|
||||
export const updateStorageSettingsAction = userAction
|
||||
.schema(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
data: StorageSwitchSchema,
|
||||
})
|
||||
)
|
||||
.action(async ({ parsedInput }) => {
|
||||
const { name, data } = parsedInput;
|
||||
|
||||
const [updatedSettings] = await db
|
||||
.update(drizzleDb.schemas.setting)
|
||||
.set({ ...data })
|
||||
.where(eq(drizzleDb.schemas.setting.name, name))
|
||||
.returning();
|
||||
|
||||
return {
|
||||
data: updatedSettings,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const S3FormSchema = z.object({
|
||||
s3EndPointUrl: z.string(),
|
||||
s3AccessKeyId: z.string(),
|
||||
s3SecretAccessKey: z.string(),
|
||||
S3BucketName: z.string(),
|
||||
});
|
||||
|
||||
export type S3FormType = z.infer<typeof S3FormSchema>;
|
||||
|
||||
export const StorageSwitchSchema = z.object({
|
||||
storage: z.enum(["local", "s3"]),
|
||||
});
|
||||
|
||||
export type StorageType = z.infer<typeof StorageSwitchSchema>;
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
|
||||
import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.schema";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/settings/storage/storage-s3/s3-form.action";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
|
||||
export type S3FormProps = {
|
||||
defaultValues?: S3FormType;
|
||||
};
|
||||
|
||||
export const StorageS3Form = (props: S3FormProps) => {
|
||||
const form = useZodForm({
|
||||
schema: S3FormSchema,
|
||||
defaultValues: props.defaultValues,
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: S3FormType) => {
|
||||
console.log(values);
|
||||
const updateS3Settings = await updateS3SettingsAction({ name: "system", data: values });
|
||||
const data = updateS3Settings?.data?.data;
|
||||
if (updateS3Settings?.serverError || !data) {
|
||||
console.log(updateS3Settings?.serverError);
|
||||
toast.error(updateS3Settings?.serverError);
|
||||
return;
|
||||
}
|
||||
toast.success(`Success updating storage informations`);
|
||||
router.refresh();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Form
|
||||
form={form}
|
||||
className="flex flex-col gap-4 mt-3"
|
||||
onSubmit={async (values) => {
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="s3EndPointUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Endpoint Url *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={"s3.eu-west-3.amazonaws.com"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Your s3 compatible url"}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="s3AccessKeyId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Access Key *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={"The access key token"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Add your access key"}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="s3SecretAccessKey"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Secret Key *</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder={"The secret key token"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"Add your secret key"}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="S3BucketName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Bucket name *</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={"my-bucket"} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>{"The bucket name where you want to store your data"}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button>Save</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user