mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-08 12:24:48 +08:00
Feat: Admin UI whitelist management and role management (#10910)
### What problem does this PR solve? Add whitelist management and role management in Admin UI ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -14,8 +13,8 @@ import { useForm } from 'react-hook-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface ImportExcelFormData {
|
||||
file: FileList;
|
||||
export interface ImportExcelFormData {
|
||||
file: File;
|
||||
overwriteExisting: boolean;
|
||||
}
|
||||
|
||||
@@ -43,6 +42,7 @@ export const ImportExcelForm = ({
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="file"
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
render={({ field: { onChange, value, ...field } }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium">
|
||||
@@ -56,7 +56,7 @@ export const ImportExcelForm = ({
|
||||
className="mt-2 px-3 h-10 bg-bg-input border-border-button file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-bg-accent file:text-text-primary hover:file:bg-bg-accent/80"
|
||||
onChange={(e) => {
|
||||
const files = e.target.files;
|
||||
onChange(files);
|
||||
onChange(files?.[0]);
|
||||
}}
|
||||
{...field}
|
||||
/>
|
||||
@@ -66,27 +66,7 @@ export const ImportExcelForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Overwrite checkbox */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="overwriteExisting"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2 text-sm font-medium">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
{t('admin.importOverwriteExistingEmails')}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<p className="text-xs text-text-secondary">
|
||||
<p className="text-sm text-text-secondary">
|
||||
<Trans
|
||||
i18nKey="admin.importFileTips"
|
||||
components={{ code: <code /> }}
|
||||
@@ -105,21 +85,14 @@ function useImportExcelForm() {
|
||||
const schema = useMemo(() => {
|
||||
return z.object({
|
||||
file: z
|
||||
.any()
|
||||
.refine((files) => files && files.length > 0, {
|
||||
message: t('admin.importFileRequired'),
|
||||
})
|
||||
.instanceof(File, { message: t('admin.importFileRequired') })
|
||||
.refine(
|
||||
(files) => {
|
||||
if (!files || files.length === 0) return false;
|
||||
const [file] = files;
|
||||
(file) => {
|
||||
return (
|
||||
file.type ===
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
// || file.type === 'application/vnd.ms-excel'
|
||||
file.name.endsWith('.xlsx')
|
||||
);
|
||||
// || file.name.endsWith('.xls');
|
||||
},
|
||||
{
|
||||
message: t('admin.invalidExcelFile'),
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { useCallback, useId, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
Form,
|
||||
@@ -16,15 +24,11 @@ import {
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/ui/tabs-underlined';
|
||||
import { AdminService, listResources } from '@/services/admin-service';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useId, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
|
||||
interface CreateRoleFormData {
|
||||
import { listResources } from '@/services/admin-service';
|
||||
import { PERMISSION_TYPES, formMergeDefaultValues } from '../utils';
|
||||
|
||||
export interface CreateRoleFormData {
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Record<string, AdminService.PermissionData>;
|
||||
@@ -36,8 +40,6 @@ interface CreateRoleFormProps {
|
||||
onSubmit?: (data: CreateRoleFormData) => void;
|
||||
}
|
||||
|
||||
const PERMISSION_TYPES = ['enable', 'read', 'write', 'share'] as const;
|
||||
|
||||
export const CreateRoleForm = ({
|
||||
id,
|
||||
form,
|
||||
@@ -48,6 +50,7 @@ export const CreateRoleForm = ({
|
||||
const { data: resourceTypes } = useQuery({
|
||||
queryKey: ['admin/resourceTypes'],
|
||||
queryFn: async () => (await listResources()).data.data.resource_types,
|
||||
retry: false,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -108,9 +111,9 @@ export const CreateRoleForm = ({
|
||||
<TabsTrigger
|
||||
key={resourceType}
|
||||
value={resourceType}
|
||||
className="text-text-secondary border-border-button dark:data-[state=active]:bg-bg-input"
|
||||
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
|
||||
>
|
||||
{t(`admin.resourceType.${resourceType}`)}
|
||||
{t(`admin.resourceType.${resourceType.toLowerCase()}`)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
@@ -121,7 +124,7 @@ export const CreateRoleForm = ({
|
||||
value={resourceType}
|
||||
className="space-y-4"
|
||||
>
|
||||
<Card className="border-0 bg-bg-card">
|
||||
<Card className="border-0 bg-bg-card !shadow-none">
|
||||
<CardContent className="p-6">
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
{PERMISSION_TYPES.map((permissionType) => (
|
||||
@@ -129,8 +132,8 @@ export const CreateRoleForm = ({
|
||||
key={permissionType}
|
||||
name={`permissions.${resourceType}.${permissionType}`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2">
|
||||
<FormItem className="space-y-0 inline-flex items-center gap-2">
|
||||
<FormLabel>
|
||||
{t(`admin.permissionType.${permissionType}`)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
@@ -158,42 +161,46 @@ export const CreateRoleForm = ({
|
||||
|
||||
// Export the form validation state for parent component
|
||||
function useCreateRoleForm(props?: {
|
||||
defaultValues: Partial<CreateRoleFormData>;
|
||||
defaultValues:
|
||||
| Partial<CreateRoleFormData>
|
||||
| (() => Promise<CreateRoleFormData>);
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const id = useId();
|
||||
|
||||
const schema = useMemo(() => {
|
||||
return z.object({
|
||||
name: z.string().min(1, { message: 'Role name is required' }),
|
||||
name: z.string().min(1, { message: t('admin.roleNameRequired') }),
|
||||
description: z.string().optional(),
|
||||
permissions: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
enable: z.boolean(),
|
||||
read: z.boolean(),
|
||||
write: z.boolean(),
|
||||
share: z.boolean(),
|
||||
enable: z.boolean().optional(),
|
||||
read: z.boolean().optional(),
|
||||
write: z.boolean().optional(),
|
||||
share: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
}, [t]);
|
||||
|
||||
const form = useForm<CreateRoleFormData>({
|
||||
defaultValues: {
|
||||
name: '',
|
||||
description: '',
|
||||
permissions: {},
|
||||
...(props?.defaultValues ?? {}),
|
||||
},
|
||||
defaultValues: formMergeDefaultValues(
|
||||
{
|
||||
name: '',
|
||||
description: '',
|
||||
permissions: {},
|
||||
},
|
||||
props?.defaultValues,
|
||||
),
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
|
||||
const FormComponent = useCallback(
|
||||
(props: Partial<CreateRoleFormProps>) => (
|
||||
<CreateRoleForm id="create-role-form" form={form} {...props} />
|
||||
<CreateRoleForm id={id} form={form} {...props} />
|
||||
),
|
||||
[form],
|
||||
[id, form],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user