mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Working on settings Page. Adding init function on next.js startup.
This commit is contained in:
@@ -6,15 +6,18 @@ import {prisma} from "@/prisma";
|
|||||||
export default async function Layout({children}: { children: React.ReactNode }) {
|
export default async function Layout({children}: { children: React.ReactNode }) {
|
||||||
|
|
||||||
const user = await currentUser()
|
const user = await currentUser()
|
||||||
// const userInfo = await prisma.user.findUnique({
|
|
||||||
// where: {
|
|
||||||
// email: user?.email
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
if(user){
|
if(user){
|
||||||
|
const userInfo = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
email: user?.email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if(userInfo){
|
||||||
redirect('/dashboard')
|
redirect('/dashboard')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutAdmin>
|
<LayoutAdmin>
|
||||||
|
|||||||
@@ -16,20 +16,35 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
<PageTitle>
|
<PageTitle>
|
||||||
Agents
|
Agents
|
||||||
</PageTitle>
|
</PageTitle>
|
||||||
|
{agents.length > 0 && (
|
||||||
<PageActions>
|
<PageActions>
|
||||||
<Link href={"/dashboard/agents/new"}>
|
<Link href={"/dashboard/agents/new"}>
|
||||||
<Button>+ Create Agent</Button>
|
<Button>+ Create Agent</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</PageActions>
|
</PageActions>
|
||||||
|
)}
|
||||||
|
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
<PageContent className="mt-10">
|
<PageContent className="mt-10">
|
||||||
|
|
||||||
|
{agents.length > 0 ?
|
||||||
<CardsWithPagination
|
<CardsWithPagination
|
||||||
data={agents}
|
data={agents}
|
||||||
cardItem={AgentCard}
|
cardItem={AgentCard}
|
||||||
cardsPerPage={4}
|
cardsPerPage={4}
|
||||||
numberOfColumns={1}
|
numberOfColumns={1}
|
||||||
/>
|
/>
|
||||||
|
:
|
||||||
|
<Link
|
||||||
|
href="/dashboard/agents/new"
|
||||||
|
className=" flex item-center justify-center border-2 border-dashed transition-colors border-primary p-8 lg:p-12 w-full rounded-md">
|
||||||
|
Create new Agent
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const settings = await prisma.settings.findUnique({
|
||||||
|
where:{
|
||||||
|
name: "system"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
@@ -31,7 +36,7 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
Manage your Portabase settings
|
Manage your Portabase settings
|
||||||
</PageDescription>
|
</PageDescription>
|
||||||
<PageContent>
|
<PageContent>
|
||||||
<SettingsTabs users={users}/>
|
<SettingsTabs settings={settings} users={users}/>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import {init} from "@/utils/init";
|
||||||
|
|
||||||
|
|
||||||
|
export async function register() {
|
||||||
|
init()
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `name` to the `Settings` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Settings" ADD COLUMN "name" TEXT NOT NULL;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[name]` on the table `Settings` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Settings_name_key" ON "Settings"("name");
|
||||||
@@ -135,6 +135,7 @@ enum TypeStorage {
|
|||||||
model Settings {
|
model Settings {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
storage TypeStorage @default(local)
|
storage TypeStorage @default(local)
|
||||||
|
name String @unique
|
||||||
s3EndPointUrl String?
|
s3EndPointUrl String?
|
||||||
s3AccessKeyId String?
|
s3AccessKeyId String?
|
||||||
s3SecretAccessKey String?
|
s3SecretAccessKey String?
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
"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 {
|
||||||
|
EmailFormSchema,
|
||||||
|
EmailFormType
|
||||||
|
} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/email-form.schema";
|
||||||
|
|
||||||
|
export type EmailFormProps = {
|
||||||
|
defaultValues?: EmailFormType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailForm = (props: EmailFormProps) => {
|
||||||
|
const form = useZodForm({
|
||||||
|
schema: EmailFormSchema,
|
||||||
|
});
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (values: EmailFormType) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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="smtpFrom"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>From Email *</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
placeholder={"exemple@portabase.com"} {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>{"The email from where the email will be send"}</FormDescription>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import {z} from "zod";
|
||||||
|
|
||||||
|
export const EmailFormSchema = z.object({
|
||||||
|
smtpPassword: z.string(),
|
||||||
|
smtpFrom: z.string(),
|
||||||
|
smtpHost: z.string(),
|
||||||
|
smtpPort: z.string(),
|
||||||
|
smtpUser: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type EmailFormType = z.infer<typeof EmailFormSchema>;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import {EmailForm} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/EmailForm/EmailForm";
|
||||||
|
|
||||||
|
|
||||||
|
export type SettingsEmailTabProps = {}
|
||||||
|
|
||||||
|
export const SettingsEmailTab = (props:SettingsEmailTabProps) => {
|
||||||
|
return(
|
||||||
|
<div>
|
||||||
|
<EmailForm/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
|
||||||
|
import {Info} from "lucide-react";
|
||||||
|
import {Switch} from "@/components/ui/switch";
|
||||||
|
import {Label} from "@/components/ui/label";
|
||||||
|
import {StorageS3Form} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/StorageS3Form/StorageS3Form";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {Settings} from "@prisma/client";
|
||||||
|
|
||||||
|
export type SettingsStorageTabProps = {
|
||||||
|
settings: Settings
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettingsStorageTab = (props: SettingsStorageTabProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [isSwitched, setIsSwitched] = useState<boolean>(props.settings.storage !== "local");
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col h-full py-4">
|
||||||
|
<h1>Settings for Portabase storage</h1>
|
||||||
|
<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 .dump files in another.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<div className="flex flex-col h-full py-4 ">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Label htmlFor="storage-mode">Storage Mode (Local/s3 compatible)</Label>
|
||||||
|
<Switch
|
||||||
|
checked={isSwitched}
|
||||||
|
onCheckedChange={() => {
|
||||||
|
setIsSwitched(!isSwitched);
|
||||||
|
}}
|
||||||
|
id="storage-mode"/>
|
||||||
|
</div>
|
||||||
|
{isSwitched && (
|
||||||
|
<div className="mt-5">
|
||||||
|
<StorageS3Form/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
"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/Settings/SettingsStorageTab/StorageS3Form/s3-form.schema";
|
||||||
|
|
||||||
|
export type S3FormProps = {
|
||||||
|
defaultValues?: S3FormType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StorageS3Form = (props: S3FormProps) => {
|
||||||
|
const form = useZodForm({
|
||||||
|
schema: S3FormSchema,
|
||||||
|
});
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async (values: S3FormType) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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>
|
||||||
|
<Input
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
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>;
|
||||||
@@ -2,19 +2,19 @@
|
|||||||
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
import {Tabs, TabsContent, TabsList, TabsTrigger} from "@/components/ui/tabs";
|
||||||
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
import {DataTableWithPagination} from "@/components/wrappers/table/data-table-with-pagination";
|
||||||
import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users";
|
import {usersColumns} from "@/components/wrappers/Dashboard/Settings/SettingsTabs/columns-users";
|
||||||
import {User} from "@prisma/client";
|
import {User, Settings} from "@prisma/client";
|
||||||
import {backupColumns} from "@/features/backup/columns";
|
import {backupColumns} from "@/features/backup/columns";
|
||||||
|
import {SettingsEmailTab} from "@/components/wrappers/Dashboard/Settings/SettingsEmailTab/SettingsEmailTab";
|
||||||
|
import {SettingsStorageTab} from "@/components/wrappers/Dashboard/Settings/SettingsStorageTab/SettingsStorageTab";
|
||||||
|
|
||||||
|
|
||||||
export type SettingsTabsProps = {
|
export type SettingsTabsProps = {
|
||||||
users: User[];
|
users: User[];
|
||||||
|
settings: Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsTabs = (props: SettingsTabsProps) => {
|
export const SettingsTabs = (props: SettingsTabsProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<Tabs defaultValue="informations" >
|
<Tabs defaultValue="informations" >
|
||||||
<TabsList className="w-full" >
|
<TabsList className="w-full" >
|
||||||
@@ -24,13 +24,24 @@ export const SettingsTabs = (props: SettingsTabsProps) => {
|
|||||||
<TabsTrigger className="w-full" value="storage">Storage</TabsTrigger>
|
<TabsTrigger className="w-full" value="storage">Storage</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="informations">
|
<TabsContent value="informations">
|
||||||
ok
|
<div className="flex flex-1 flex-col gap-4 py-4">
|
||||||
|
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||||
|
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||||
|
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||||
|
<div className="aspect-video rounded-xl bg-muted/50"/>
|
||||||
|
</div>
|
||||||
|
<div className="min-h-[100vh] flex-1 rounded-xl bg-muted/50 md:min-h-min"/>
|
||||||
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="users" className="h-full justify-between">
|
<TabsContent value="users" className="h-full justify-between">
|
||||||
<DataTableWithPagination columns={usersColumns} data={props.users}/>
|
<DataTableWithPagination columns={usersColumns} data={props.users}/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="email">Change your password here.</TabsContent>
|
<TabsContent value="email">
|
||||||
<TabsContent value="storage">Change your password here.</TabsContent>
|
<SettingsEmailTab/>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="storage">
|
||||||
|
<SettingsStorageTab settings={props.settings}/>
|
||||||
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import {prisma} from "@/prisma";
|
||||||
|
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
consoleAscii()
|
||||||
|
console.log("====Init Functions====")
|
||||||
|
createSettingsIfNotExist()
|
||||||
|
.then(() => {
|
||||||
|
console.log('====Initialization completed====');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Error during initialization:', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createSettingsIfNotExist() {
|
||||||
|
|
||||||
|
const settings = await prisma.settings.findUnique({
|
||||||
|
where: {
|
||||||
|
name: "system",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(!settings){
|
||||||
|
console.log("====Init Setting====")
|
||||||
|
await prisma.settings.create({
|
||||||
|
data: {
|
||||||
|
name: "system",
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function consoleAscii(){
|
||||||
|
console.log("\n" +
|
||||||
|
" ____ __ __ _____ \n" +
|
||||||
|
" / __ \\ ____ _____ / /_ ____ _ / /_ ____ _ _____ ___ / ___/ ___ _____ _ __ ___ _____\n" +
|
||||||
|
" / /_/ // __ \\ / ___// __// __ `// __ \\ / __ `// ___// _ \\ \\__ \\ / _ \\ / ___/| | / // _ \\ / ___/\n" +
|
||||||
|
" / ____// /_/ // / / /_ / /_/ // /_/ // /_/ /(__ )/ __/ ___/ // __// / | |/ // __// / \n" +
|
||||||
|
"/_/ \\____//_/ \\__/ \\__,_//_.___/ \\__,_//____/ \\___/ /____/ \\___//_/ |___/ \\___//_/ \n" +
|
||||||
|
" \n")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user