mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
Adjust styles to match the design system (#11118)
### What problem does this PR solve? - Modify and adjust styles (CSS vars, components) to match the design system - Adjust file and directory structure of admin UI ### Type of change - [x] Refactoring
This commit is contained in:
@ -15,15 +15,18 @@ const ThemeSwitch = forwardRef<
|
||||
return (
|
||||
<Root
|
||||
ref={ref}
|
||||
className={cn('relative rounded-full', className)}
|
||||
className={cn(
|
||||
'group/theme-switch relative rounded-full outline-none self-center focus-visible:ring-1 focus-visible:ring-accent-primary',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
checked={isDark}
|
||||
onCheckedChange={(value) =>
|
||||
setTheme(value ? ThemeEnum.Dark : ThemeEnum.Light)
|
||||
}
|
||||
>
|
||||
<div className="px-3 py-2 rounded-full border border-border-button bg-bg-card transition-[background-color] duration-200">
|
||||
<div className="flex items-center justify-between gap-4 relative z-[1] text-text-disabled transition-[text-color] duration-200">
|
||||
<div className="self-center p-3 py-2 rounded-full bg-bg-card transition-[background-color] duration-300">
|
||||
<div className="h-full flex items-center justify-between gap-4 relative z-[1] text-text-disabled transition-[text-color] duration-300 delay-75">
|
||||
<LucideSun
|
||||
className={cn('size-[1em]', !isDark && 'text-text-primary')}
|
||||
/>
|
||||
@ -35,11 +38,22 @@ const ThemeSwitch = forwardRef<
|
||||
|
||||
<Thumb
|
||||
className={cn(
|
||||
'absolute top-0 left-0 w-[calc(50%+.25rem)] h-full rounded-full bg-bg-base border border-border-button',
|
||||
'transition-all duration-200',
|
||||
{ 'left-[calc(50%-.25rem)]': isDark },
|
||||
'absolute top-0 left-0 w-[calc(50%+.25rem)] p-0.5 h-full rounded-full overflow-hidden',
|
||||
'transition-all ease-out duration-300',
|
||||
'group-hover/theme-switch:w-[calc(50%+.66rem)] group-focus-visible/theme-switch:w-[calc(50%+.66rem)]',
|
||||
{
|
||||
'left-[calc(50%-.25rem)] group-hover/theme-switch:left-[calc(50%-.66rem)] group-focus-visible/theme-switch:left-[calc(50%-.66rem)]':
|
||||
isDark,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<div
|
||||
className="
|
||||
size-full rounded-full bg-bg-base shadow-md
|
||||
transition-colors ease-out duration-300 delay-75
|
||||
"
|
||||
/>
|
||||
</Thumb>
|
||||
</Root>
|
||||
);
|
||||
});
|
||||
|
||||
@ -18,12 +18,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/ui/tabs-underlined';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
|
||||
import { listResources } from '@/services/admin-service';
|
||||
import { PERMISSION_TYPES, formMergeDefaultValues } from '../utils';
|
||||
@ -106,12 +101,12 @@ export const CreateRoleForm = ({
|
||||
<Label>{t('admin.resources')}</Label>
|
||||
|
||||
<Tabs defaultValue={resourceTypes?.[0]} className="w-full mt-2">
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||
{resourceTypes?.map((resourceType) => (
|
||||
<TabsTrigger
|
||||
key={resourceType}
|
||||
value={resourceType}
|
||||
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
|
||||
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||
>
|
||||
{t(`admin.resourceType.${resourceType.toLowerCase()}`)}
|
||||
</TabsTrigger>
|
||||
|
||||
@ -153,7 +153,11 @@ export const CreateUserForm = ({
|
||||
<SelectItem key={role.id} value={role.role_name}>
|
||||
{role.role_name}
|
||||
</SelectItem>
|
||||
))}
|
||||
)) ?? (
|
||||
<div className="text-text-secondary px-2 py-6 text-sm text-center">
|
||||
{t('common.noData')}
|
||||
</div>
|
||||
)}
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import message from '@/components/ui/message';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { logout } from '@/services/admin-service';
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavLink, Outlet, useNavigate } from 'umi';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import {
|
||||
LucideMonitor,
|
||||
LucideServerCrash,
|
||||
@ -12,13 +11,18 @@ import {
|
||||
LucideUserCog,
|
||||
LucideUserStar,
|
||||
} from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavLink, Outlet, useNavigate } from 'umi';
|
||||
import ThemeSwitch from './components/theme-switch';
|
||||
import { IS_ENTERPRISE } from './utils';
|
||||
|
||||
const AdminLayout = () => {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { logout } from '@/services/admin-service';
|
||||
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
|
||||
import ThemeSwitch from '../components/theme-switch';
|
||||
import { IS_ENTERPRISE } from '../utils';
|
||||
|
||||
const AdminNavigationLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -61,8 +65,6 @@ const AdminLayout = () => {
|
||||
mutationKey: ['adminLogout'],
|
||||
mutationFn: async () => {
|
||||
await logout();
|
||||
|
||||
message.success(t('message.logout'));
|
||||
authorizationUtil.removeAll();
|
||||
navigate(Routes.Admin);
|
||||
},
|
||||
@ -90,6 +92,7 @@ const AdminLayout = () => {
|
||||
'hover:bg-bg-card focus:bg-bg-card focus-visible:bg-bg-card',
|
||||
'hover:text-text-primary focus:text-text-primary focus-visible:text-text-primary',
|
||||
'active:text-text-primary',
|
||||
'transition-colors',
|
||||
{
|
||||
'bg-bg-card text-text-primary': isActive,
|
||||
},
|
||||
@ -105,14 +108,18 @@ const AdminLayout = () => {
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto space-y-4">
|
||||
<div className="text-right">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-accent-primary">
|
||||
vmm.ss.rr-nnn-commithash
|
||||
</span>
|
||||
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
variant="transparent"
|
||||
className="block w-full dark:border-border-button"
|
||||
block
|
||||
onClick={() => logoutMutation.mutate()}
|
||||
>
|
||||
{t('header.logout')}
|
||||
@ -127,4 +134,4 @@ const AdminLayout = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminLayout;
|
||||
export default AdminNavigationLayout;
|
||||
7
web/src/pages/admin/layouts/root-layout.tsx
Normal file
7
web/src/pages/admin/layouts/root-layout.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import { Outlet } from 'umi';
|
||||
|
||||
const AdminRootLayout = () => {
|
||||
return <Outlet />;
|
||||
};
|
||||
|
||||
export default AdminRootLayout;
|
||||
@ -1,18 +1,16 @@
|
||||
import { type AxiosResponseHeaders } from 'axios';
|
||||
import { useEffect, useId, useState } from 'react';
|
||||
import { useEffect, useId } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'umi';
|
||||
|
||||
import { LucideEye, LucideEyeOff } from 'lucide-react';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { ButtonLoading } from '@/components/ui/button';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
@ -43,8 +41,6 @@ function AdminLogin() {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'login' });
|
||||
const { isLogin } = useAuth();
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const loginMutation = useMutation({
|
||||
mutationKey: ['adminLogin'],
|
||||
mutationFn: async (params: { email: string; password: string }) => {
|
||||
@ -113,7 +109,7 @@ function AdminLogin() {
|
||||
|
||||
return (
|
||||
<ScrollArea className="w-screen h-screen">
|
||||
<div className="relative">
|
||||
<div className="relative h-max min-h-[100vh]">
|
||||
<Spotlight opcity={0.4} coverage={60} color="rgb(128, 255, 248)" />
|
||||
<Spotlight
|
||||
opcity={0.3}
|
||||
@ -143,9 +139,9 @@ function AdminLogin() {
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-center w-screen min-h-[1050px]">
|
||||
<div className="w-full max-w-[540px]">
|
||||
<Card className="w-full bg-bg-component backdrop-blur-sm rounded-2xl border border-border-button">
|
||||
<div className="flex items-center justify-center w-screen">
|
||||
<div className="w-full max-w-[540px] mt-72 mb-48">
|
||||
<Card className="w-full bg-bg-component rounded-2xl shadow-none backdrop-blur-sm">
|
||||
<CardContent className="px-10 pt-14 pb-10">
|
||||
<Form {...form}>
|
||||
<form
|
||||
@ -164,7 +160,7 @@ function AdminLogin() {
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
className="h-10 px-2.5"
|
||||
className="h-10"
|
||||
placeholder={t('emailPlaceholder')}
|
||||
autoComplete="email"
|
||||
{...field}
|
||||
@ -184,26 +180,13 @@ function AdminLogin() {
|
||||
<FormLabel required>{t('passwordLabel')}</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
className="h-10 px-2.5"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
autoComplete="password"
|
||||
{...field}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-y-0 right-0 pr-3 flex items-center"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<LucideEyeOff className="h-4 w-4 text-gray-500" />
|
||||
) : (
|
||||
<LucideEye className="h-4 w-4 text-gray-500" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<Input
|
||||
{...field}
|
||||
className="h-10"
|
||||
type="password"
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
autoComplete="password"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
@ -218,10 +201,10 @@ function AdminLogin() {
|
||||
<FormItem className="!mt-5">
|
||||
<FormLabel
|
||||
className={cn(
|
||||
'flex items-center hover:text-text-primary',
|
||||
'transition-colors',
|
||||
field.value
|
||||
? 'text-text-primary'
|
||||
: 'text-text-disabled',
|
||||
: 'text-text-secondary',
|
||||
)}
|
||||
>
|
||||
<FormControl>
|
||||
@ -241,19 +224,17 @@ function AdminLogin() {
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="px-10 pt-8 pb-14">
|
||||
<ButtonLoading
|
||||
<Button
|
||||
form={formId}
|
||||
variant="highlighted"
|
||||
size="lg"
|
||||
className="
|
||||
w-full h-10
|
||||
bg-metallic-gradient border-b-[#00BEB4] border-b-2
|
||||
hover:bg-metallic-gradient hover:border-b-[#02bcdd]
|
||||
"
|
||||
block
|
||||
type="submit"
|
||||
className="font-medium"
|
||||
loading={loading}
|
||||
>
|
||||
{t('login')}
|
||||
</ButtonLoading>
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
|
||||
@ -3,11 +3,14 @@ import { Card, CardContent } from '@/components/ui/card';
|
||||
|
||||
function AdminMonitoring() {
|
||||
return (
|
||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||
<Card className="!shadow-none relative h-full border-0.5 border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||
<Spotlight />
|
||||
|
||||
<CardContent className="size-full p-0">
|
||||
<iframe />
|
||||
<iframe
|
||||
className="size-full"
|
||||
src={`${location.protocol}//${location.hostname}:9090/alerts`}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@ -18,15 +18,9 @@ import {
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/ui/tabs-underlined';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { LucideEdit3, LucideTrash2, LucideUserPlus } from 'lucide-react';
|
||||
|
||||
import {
|
||||
@ -149,7 +143,7 @@ function AdminRoles() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="!shadow-none relative w-full h-full border border-border-button bg-transparent rounded-xl">
|
||||
<Card className="!shadow-none relative w-full h-full border-0.5 border-border-button bg-transparent rounded-xl">
|
||||
<Spotlight />
|
||||
|
||||
<ScrollArea className="size-full">
|
||||
@ -170,9 +164,9 @@ function AdminRoles() {
|
||||
roleList.map((role) => (
|
||||
<Card
|
||||
key={role.id}
|
||||
className="group border border-border-default bg-transparent dark:hover:bg-bg-card transition-color duration-150"
|
||||
className="group/role border-0.5 border-border-default bg-transparent dark:hover:bg-bg-card transition-color duration-150"
|
||||
>
|
||||
<CardHeader className="space-y-0 flex flex-row gap-4 items-center border-b border-border-button">
|
||||
<CardHeader className="space-y-0 flex flex-row gap-4 items-center border-b-0.5 border-border-button">
|
||||
<div className="space-y-1.5 w-0 flex-1">
|
||||
<CardTitle className="font-normal text-xl">
|
||||
{role.role_name}
|
||||
@ -187,7 +181,10 @@ function AdminRoles() {
|
||||
|
||||
<Button
|
||||
variant="transparent"
|
||||
className="ml-2 p-0 border-0 size-[1em] align-middle opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
className="
|
||||
ml-2 p-0 border-0 size-[1em] align-middle opacity-0
|
||||
group-hover/role:opacity-100 group-focus-within/role:opacity-100
|
||||
"
|
||||
onClick={() => {
|
||||
setEditRoleDescriptionModalOpen(true);
|
||||
setRoleToMakeAction(role);
|
||||
@ -202,7 +199,7 @@ function AdminRoles() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="ml-auto opacity-0 group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
className="ml-auto opacity-0 group-hover/role:opacity-100 group-focus-within/role:opacity-100"
|
||||
disabled={deleteRoleMutation.isPending}
|
||||
onClick={() => {
|
||||
setDeleteModalOpen(true);
|
||||
@ -218,12 +215,12 @@ function AdminRoles() {
|
||||
className="h-full flex flex-col"
|
||||
defaultValue={resourceTypes?.[0]}
|
||||
>
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||
{resourceTypes?.map((resourceName) => (
|
||||
<TabsTrigger
|
||||
key={resourceName}
|
||||
value={resourceName}
|
||||
className="text-text-secondary !border-border-button data-[state=active]:bg-bg-card data-[state=active]:text-text-primary"
|
||||
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||
>
|
||||
{t(
|
||||
`admin.resourceType.${resourceName.toLowerCase()}`,
|
||||
@ -290,7 +287,7 @@ function AdminRoles() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -309,14 +306,14 @@ function AdminRoles() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
type="submit"
|
||||
form={createRoleForm.id}
|
||||
className="px-4 h-10"
|
||||
variant="default"
|
||||
>
|
||||
{t('admin.confirm')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -334,7 +331,7 @@ function AdminRoles() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -372,13 +369,13 @@ function AdminRoles() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
type="submit"
|
||||
form={editRoleDescriptionFormId}
|
||||
className="px-4 h-10"
|
||||
>
|
||||
{t('admin.confirm')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -393,7 +390,7 @@ function AdminRoles() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -402,7 +399,7 @@ function AdminRoles() {
|
||||
{t('admin.deleteRoleConfirmation')}
|
||||
</DialogDescription>
|
||||
|
||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
||||
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||
{roleToMakeAction?.role_name}
|
||||
</div>
|
||||
</section>
|
||||
@ -417,7 +414,7 @@ function AdminRoles() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
className="px-4 h-10"
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
@ -428,7 +425,7 @@ function AdminRoles() {
|
||||
loading={deleteRoleMutation.isPending}
|
||||
>
|
||||
{t('admin.delete')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -44,15 +44,18 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
||||
|
||||
if (isPlainObject(content)) {
|
||||
return (
|
||||
<dl className="text-sm text-text-primary grid grid-cols-[auto,1fr] border border-card rounded-xl overflow-hidden bg-bg-card">
|
||||
<dl className="text-sm text-text-primary grid grid-cols-[minmax(20%,auto),1fr] rounded-xl overflow-hidden bg-bg-card">
|
||||
{Object.entries<any>(content).map(([key, value]) => (
|
||||
<div key={key} className="contents">
|
||||
<dt className="px-3 py-2 bg-bg-card">
|
||||
<div
|
||||
key={key}
|
||||
className="contents [:not(:last-child)]>*]border-b-0.5 [:not(:last-child)>*]:border-border-button"
|
||||
>
|
||||
<dt className="px-4 py-2.5 bg-bg-card">
|
||||
<pre>
|
||||
<code>{key}</code>
|
||||
</pre>
|
||||
</dt>
|
||||
<dd className="px-3 py-2">
|
||||
<dd className="px-4 py-2.5">
|
||||
<pre>
|
||||
<code>{JSON.stringify(value)}</code>
|
||||
</pre>
|
||||
@ -65,8 +68,8 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
||||
|
||||
if (typeof content === 'string') {
|
||||
return (
|
||||
<div className="rounded-lg p-4 border border-border-button bg-bg-input">
|
||||
<pre className="text-sm">
|
||||
<div className="rounded-lg p-4 bg-bg-card text-sm text-text-primary">
|
||||
<pre>
|
||||
<code>
|
||||
{typeof content === 'string'
|
||||
? content
|
||||
|
||||
@ -15,17 +15,14 @@ import {
|
||||
LucideClipboardList,
|
||||
LucideDot,
|
||||
LucideFilter,
|
||||
LucideSearch,
|
||||
LucideSettings2,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { TableEmpty } from '@/components/table-skeleton';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Badge, BadgeProps } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
@ -41,7 +38,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Popover,
|
||||
@ -127,17 +124,17 @@ function AdminServiceStatus() {
|
||||
}),
|
||||
columnHelper.accessor('host', {
|
||||
header: t('admin.host'),
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="secondary" className="font-normal text-text-primary">
|
||||
<i>{row.getValue('host')}</i>
|
||||
cell: ({ cell }) => (
|
||||
<Badge variant="secondary">
|
||||
<i>{cell.getValue()}</i>
|
||||
</Badge>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor('port', {
|
||||
header: t('admin.port'),
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="secondary" className="font-normal text-text-primary">
|
||||
<i>{row.getValue('port')}</i>
|
||||
cell: ({ cell }) => (
|
||||
<Badge variant="secondary">
|
||||
<i>{cell.getValue()}</i>
|
||||
</Badge>
|
||||
),
|
||||
}),
|
||||
@ -145,15 +142,14 @@ function AdminServiceStatus() {
|
||||
header: t('admin.status'),
|
||||
cell: ({ cell }) => (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'pl-2 font-normal text-sm text-text-primary capitalize',
|
||||
variant={
|
||||
{
|
||||
alive: 'bg-state-success-5 text-state-success',
|
||||
timeout: 'bg-state-error-5 text-state-error',
|
||||
fail: 'bg-gray-500/5 text-text-disable',
|
||||
}[cell.getValue()],
|
||||
)}
|
||||
alive: 'success',
|
||||
timeout: 'destructive',
|
||||
fail: 'grey',
|
||||
}[cell.getValue()] as BadgeProps['variant']
|
||||
}
|
||||
className="pl-[.5em] capitalize"
|
||||
>
|
||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||
{t(`admin.${cell.getValue()}`)}
|
||||
@ -165,7 +161,7 @@ function AdminServiceStatus() {
|
||||
id: 'actions',
|
||||
header: t('admin.actions'),
|
||||
cell: ({ row }) => (
|
||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
||||
<div className="opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
@ -206,6 +202,8 @@ function AdminServiceStatus() {
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@ -216,7 +214,7 @@ function AdminServiceStatus() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl">
|
||||
<Card className="!shadow-none relative h-full bg-transparent rounded-xl overflow-hidden">
|
||||
<Spotlight />
|
||||
|
||||
<ScrollArea className="size-full">
|
||||
@ -229,16 +227,20 @@ function AdminServiceStatus() {
|
||||
<Button
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="dark:bg-bg-input dark:border-border-button text-text-secondary"
|
||||
className="border-0.5"
|
||||
// className="
|
||||
// text-text-secondary
|
||||
// dark:bg-bg-input dark:border-border-button
|
||||
// hover:bg-border-button dark:hover:bg-border-button
|
||||
// focus-visible:ring-0 focus-visible:text-text-primary
|
||||
// focus-visible:bg-border-button focus-visible:border-border-button
|
||||
// "
|
||||
>
|
||||
<LucideFilter className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
align="end"
|
||||
className="bg-bg-base text-text-secondary"
|
||||
>
|
||||
<PopoverContent align="end">
|
||||
<div className="p-2 space-y-6">
|
||||
<section>
|
||||
<div className="font-bold mb-3">
|
||||
@ -247,9 +249,9 @@ function AdminServiceStatus() {
|
||||
|
||||
<RadioGroup
|
||||
value={
|
||||
table
|
||||
(table
|
||||
.getColumn('service_type')!
|
||||
?.getFilterValue() as string
|
||||
?.getFilterValue() as string) ?? ''
|
||||
}
|
||||
onValueChange={
|
||||
table.getColumn('service_type')!?.setFilterValue
|
||||
@ -291,15 +293,12 @@ function AdminServiceStatus() {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<div className="relative w-56">
|
||||
<LucideSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
className="pl-10 h-10 bg-bg-input border-border-button"
|
||||
placeholder={t('header.search')}
|
||||
value={table.getState().globalFilter}
|
||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<SearchInput
|
||||
className="w-56 h-10 bg-bg-input border-border-button"
|
||||
placeholder={t('header.search')}
|
||||
value={table.getState().globalFilter}
|
||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
@ -345,7 +344,7 @@ function AdminServiceStatus() {
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} className="group">
|
||||
<TableRow key={row.id} className="group/row">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
@ -389,12 +388,12 @@ function AdminServiceStatus() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 pt-6 pb-4">
|
||||
<div className="rounded-lg p-4 border border-border-button bg-bg-input">
|
||||
<div className="rounded-lg p-4 bg-bg-input">
|
||||
<pre className="text-sm">
|
||||
<code>
|
||||
{JSON.stringify(itemToMakeAction?.extra ?? {}, null, 2)}
|
||||
@ -425,7 +424,7 @@ function AdminServiceStatus() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>
|
||||
<Trans i18nKey="admin.serviceDetail">
|
||||
{{ name: itemToMakeAction?.name }}
|
||||
|
||||
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate, useParams } from 'umi';
|
||||
|
||||
import { LucideArrowLeft, LucideDot, LucideUser2 } from 'lucide-react';
|
||||
import { LucideArrowLeft, LucideDot } from 'lucide-react';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import {
|
||||
@ -13,11 +13,10 @@ import {
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
|
||||
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { Avatar } from '@/components/ui/avatar';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
@ -27,16 +26,11 @@ import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
// TableHead,
|
||||
// TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from '@/components/ui/tabs-underlined';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
|
||||
import {
|
||||
getUserDetails,
|
||||
@ -44,8 +38,12 @@ import {
|
||||
listUserDatasets,
|
||||
} from '@/services/admin-service';
|
||||
|
||||
import { TableEmpty } from '@/components/table-skeleton';
|
||||
import EnterpriseFeature from './components/enterprise-feature';
|
||||
import { getSortIcon, parseBooleanish } from './utils';
|
||||
import {
|
||||
// getSortIcon,
|
||||
parseBooleanish,
|
||||
} from './utils';
|
||||
|
||||
const ASSET_NAMES = ['dataset', 'flow'];
|
||||
|
||||
@ -60,6 +58,20 @@ function UserDatasetTable(props: {
|
||||
|
||||
const columnDefs = useMemo(
|
||||
() => [
|
||||
datasetColumnHelper.accessor('name', {
|
||||
header: t('admin.name'),
|
||||
cell: ({ row, cell }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<RAGFlowAvatar
|
||||
avatar={row.original.avatar}
|
||||
name={cell.getValue()}
|
||||
/>
|
||||
<span>{cell.getValue()}</span>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
// #region
|
||||
/*
|
||||
datasetColumnHelper.accessor('name', {
|
||||
header: t('admin.name'),
|
||||
enableSorting: false,
|
||||
@ -69,20 +81,15 @@ function UserDatasetTable(props: {
|
||||
cell: ({ cell }) => {
|
||||
return (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'font-normal text-sm pl-2',
|
||||
parseBooleanish(cell.getValue())
|
||||
? 'bg-state-success-5 text-state-success'
|
||||
: 'bg-state-error-5 text-state-error',
|
||||
)}
|
||||
variant={parseBooleanish(cell.getValue()) ? 'success' : 'destructive'}
|
||||
className="pl-[.35em]"
|
||||
>
|
||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||
{t(
|
||||
parseBooleanish(cell.getValue())
|
||||
? 'admin.active'
|
||||
: 'admin.inactive',
|
||||
)}
|
||||
)}"
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
@ -111,6 +118,8 @@ function UserDatasetTable(props: {
|
||||
header: t('admin.permission'),
|
||||
enableSorting: false,
|
||||
}),
|
||||
*/
|
||||
// #endregion
|
||||
],
|
||||
[t],
|
||||
);
|
||||
@ -121,12 +130,14 @@ function UserDatasetTable(props: {
|
||||
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{/* <TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
@ -152,7 +163,8 @@ function UserDatasetTable(props: {
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
</TableHeader> */}
|
||||
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
@ -165,14 +177,7 @@ function UserDatasetTable(props: {
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={table.getAllColumns().length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{t('common.noData')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty columnsLength={columnDefs.length} />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@ -197,6 +202,20 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
||||
|
||||
const columnDefs = useMemo(
|
||||
() => [
|
||||
agentColumnHelper.accessor('title', {
|
||||
header: t('admin.agentTitle'),
|
||||
cell: ({ row, cell }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<RAGFlowAvatar
|
||||
avatar={row.original.avatar}
|
||||
name={cell.getValue()}
|
||||
/>
|
||||
<span>{cell.getValue()}</span>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
// #region
|
||||
/*
|
||||
agentColumnHelper.accessor('title', {
|
||||
header: t('admin.agentTitle'),
|
||||
}),
|
||||
@ -206,6 +225,8 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
||||
agentColumnHelper.accessor('canvas_category', {
|
||||
header: t('admin.canvasCategory'),
|
||||
}),
|
||||
*/
|
||||
// #endregion
|
||||
],
|
||||
[t],
|
||||
);
|
||||
@ -216,12 +237,14 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
||||
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{/* <TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
@ -232,20 +255,13 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
{/* {header.column.getCanFilter() && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
>
|
||||
<LucideFilter />
|
||||
</Button>
|
||||
)} */}
|
||||
</>
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
</TableHeader> */}
|
||||
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
@ -259,14 +275,7 @@ function UserAgentTable(props: { data?: AdminService.ListUserAgentItem[] }) {
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow key="empty">
|
||||
<TableCell
|
||||
colSpan={table.getAllColumns().length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{t('common.noData')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty columnsLength={columnDefs.length} />
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
@ -319,32 +328,28 @@ function AdminUserDetail() {
|
||||
onClick={() => navigate(`${Routes.AdminUserManagement}`)}
|
||||
>
|
||||
<LucideArrowLeft />
|
||||
<span>{t('admin.userManagement')}</span>
|
||||
<span>{t('admin.back')}</span>
|
||||
</Button>
|
||||
</nav>
|
||||
|
||||
<Card className="!shadow-none relative h-0 basis-0 grow flex flex-col bg-transparent border dark:border-border-button overflow-hidden">
|
||||
<Card className="!shadow-none relative h-0 basis-0 grow flex flex-col bg-transparent border-0.5 border-border-button overflow-hidden">
|
||||
<Spotlight />
|
||||
|
||||
<CardHeader className="pb-10 border-b dark:border-border-button space-y-8">
|
||||
<CardHeader className="pb-10 border-b-0.5 dark:border-border-button space-y-8">
|
||||
<section className="flex items-center gap-4 text-base">
|
||||
<Avatar className="justify-center items-center bg-bg-group uppercase">
|
||||
{detail?.email
|
||||
.split('@')[0]
|
||||
.replace(/[^0-9a-z]/gi, '')
|
||||
.slice(0, 2) || <LucideUser2 />}
|
||||
</Avatar>
|
||||
<RAGFlowAvatar
|
||||
avatar={detail?.avatar}
|
||||
name={detail?.email}
|
||||
isPerson
|
||||
/>
|
||||
|
||||
<span>{detail?.email}</span>
|
||||
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'font-normal text-sm pl-2',
|
||||
parseBooleanish(detail?.is_active)
|
||||
? 'bg-state-success-5 text-state-success'
|
||||
: '',
|
||||
)}
|
||||
variant={
|
||||
parseBooleanish(detail?.is_active) ? 'success' : 'destructive'
|
||||
}
|
||||
className="pl-[.5em]"
|
||||
>
|
||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||
{t(
|
||||
@ -355,11 +360,11 @@ function AdminUserDetail() {
|
||||
</Badge>
|
||||
|
||||
<EnterpriseFeature>
|
||||
{() => (
|
||||
<Badge variant="secondary" className="font-normal text-sm">
|
||||
{detail?.role}
|
||||
</Badge>
|
||||
)}
|
||||
{() =>
|
||||
detail?.role && (
|
||||
<Badge variant="secondary">{detail?.role}</Badge>
|
||||
)
|
||||
}
|
||||
</EnterpriseFeature>
|
||||
</section>
|
||||
|
||||
@ -398,16 +403,23 @@ function AdminUserDetail() {
|
||||
</div>
|
||||
<div>{t(detail?.is_anonymous ? 'admin.yes' : 'admin.no')}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-sm text-text-secondary mb-2">
|
||||
{t('admin.isSuperuser')}
|
||||
</div>
|
||||
<div>{t(detail?.is_superuser ? 'admin.yes' : 'admin.no')}</div>
|
||||
</div>
|
||||
</section>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="h-0 basis-0 grow pt-6">
|
||||
<Tabs className="h-full flex flex-col" defaultValue="dataset">
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent">
|
||||
<TabsList className="p-0 mb-2 gap-4 bg-transparent justify-start">
|
||||
{ASSET_NAMES.map((name) => (
|
||||
<TabsTrigger
|
||||
key={name}
|
||||
className="border-border-button data-[state=active]:bg-bg-card"
|
||||
className="text-text-secondary border-0.5 border-border-button data-[state=active]:bg-bg-card"
|
||||
value={name}
|
||||
>
|
||||
{t(`header.${name}`)}
|
||||
|
||||
@ -22,7 +22,6 @@ import {
|
||||
LucideUserPlus,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
import { rsaPsw } from '@/utils';
|
||||
|
||||
import Spotlight from '@/components/spotlight';
|
||||
@ -39,14 +38,18 @@ import {
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
@ -55,13 +58,6 @@ import {
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Table,
|
||||
@ -213,6 +209,15 @@ function AdminUserManagement() {
|
||||
}),
|
||||
columnHelper.accessor('nickname', {
|
||||
header: t('admin.nickname'),
|
||||
cell: ({ row, cell }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{cell.getValue()}</span>
|
||||
|
||||
{row.original.is_superuser ? (
|
||||
<Badge variant="secondary">{t('admin.superuser')}</Badge>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
|
||||
...(IS_ENTERPRISE
|
||||
@ -220,30 +225,29 @@ function AdminUserManagement() {
|
||||
columnHelper.accessor('role', {
|
||||
header: t('admin.role'),
|
||||
cell: ({ row, cell }) => (
|
||||
<Select
|
||||
value={cell.getValue()}
|
||||
onValueChange={(value) => {
|
||||
if (!updateUserRoleMutation.isPending) {
|
||||
updateUserRoleMutation.mutate({
|
||||
email: row.original.email,
|
||||
role: value,
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={updateUserRoleMutation.isPending}
|
||||
>
|
||||
<SelectTrigger className="h-10">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="min-w-16">
|
||||
{cell.getValue()}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<SelectContent className="bg-bg-base">
|
||||
<DropdownMenuContent>
|
||||
{roleList?.map(({ id, role_name }) => (
|
||||
<SelectItem key={id} value={role_name}>
|
||||
<DropdownMenuItem
|
||||
key={id}
|
||||
onClick={() => {
|
||||
updateUserRoleMutation.mutate({
|
||||
email: row.original.email,
|
||||
role: role_name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{role_name}
|
||||
</SelectItem>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
filterFn: createColumnFilterFn(
|
||||
(row, id, filterValue) => row.getValue(id) === filterValue,
|
||||
@ -275,13 +279,8 @@ function AdminUserManagement() {
|
||||
header: t('admin.status'),
|
||||
cell: ({ cell }) => (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={cn(
|
||||
'pl-2 font-normal text-sm',
|
||||
parseBooleanish(cell.getValue())
|
||||
? 'bg-state-success-5 text-state-success'
|
||||
: '',
|
||||
)}
|
||||
variant={parseBooleanish(cell.getValue()) ? 'success' : 'secondary'}
|
||||
className="pl-[.5em]"
|
||||
>
|
||||
<LucideDot className="size-[1em] stroke-[8] mr-1" />
|
||||
{t(
|
||||
@ -304,11 +303,11 @@ function AdminUserManagement() {
|
||||
id: 'actions',
|
||||
header: t('admin.actions'),
|
||||
cell: ({ row }) => (
|
||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
||||
<div className="opacity-0 group-hover/row:opacity-100 group-focus-within/row:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="border-0 text-text-secondary"
|
||||
className="border-0"
|
||||
onClick={() =>
|
||||
navigate(`${Routes.AdminUserManagement}/${row.original.email}`)
|
||||
}
|
||||
@ -318,7 +317,7 @@ function AdminUserManagement() {
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="border-0 text-text-secondary"
|
||||
className="border-0"
|
||||
onClick={() => {
|
||||
setUserToMakeAction(row.original);
|
||||
setPasswordModalOpen(true);
|
||||
@ -327,9 +326,9 @@ function AdminUserManagement() {
|
||||
<LucideUserLock />
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
variant="danger"
|
||||
size="icon"
|
||||
className="border-0 text-text-secondary"
|
||||
className="border-0"
|
||||
onClick={() => {
|
||||
setUserToMakeAction(row.original);
|
||||
setDeleteModalOpen(true);
|
||||
@ -358,7 +357,7 @@ function AdminUserManagement() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||
<Card className="!shadow-none relative h-full bg-transparent overflow-hidden">
|
||||
<Spotlight />
|
||||
|
||||
<ScrollArea className="size-full">
|
||||
@ -493,8 +492,8 @@ function AdminUserManagement() {
|
||||
{() => <col className="w-[12%]" />}
|
||||
</EnterpriseFeature>
|
||||
|
||||
<col className="w-[10%]" />
|
||||
<col className="w-[12%]" />
|
||||
<col className="w-[8%]" />
|
||||
<col className="w-[15%]" />
|
||||
<col className="w-52" />
|
||||
</colgroup>
|
||||
|
||||
@ -518,7 +517,7 @@ function AdminUserManagement() {
|
||||
<TableBody>
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow key={row.id} className="group">
|
||||
<TableRow key={row.id} className="group/row">
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
@ -555,18 +554,16 @@ function AdminUserManagement() {
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||
<DialogContent className="p-0 border-border-button">
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<DialogDescription className="text-text-primary">
|
||||
{t('admin.deleteUserConfirmation')}
|
||||
<section className="px-12 py-4 text-text-primary text-sm">
|
||||
{t('admin.deleteUserConfirmation')}
|
||||
|
||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
||||
{userToMakeAction?.email}
|
||||
</div>
|
||||
</DialogDescription>
|
||||
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||
{userToMakeAction?.email}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
@ -579,7 +576,7 @@ function AdminUserManagement() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
className="px-4 h-10"
|
||||
variant="destructive"
|
||||
onClick={() =>
|
||||
@ -590,7 +587,7 @@ function AdminUserManagement() {
|
||||
loading={deleteUserMutation.isPending}
|
||||
>
|
||||
{t('admin.delete')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -605,7 +602,7 @@ function AdminUserManagement() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -637,7 +634,7 @@ function AdminUserManagement() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
form={changePasswordForm.id}
|
||||
className="px-4 h-10"
|
||||
variant="default"
|
||||
@ -646,7 +643,7 @@ function AdminUserManagement() {
|
||||
loading={changePasswordMutation.isPending}
|
||||
>
|
||||
{t('admin.changePassword')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -660,7 +657,7 @@ function AdminUserManagement() {
|
||||
}}
|
||||
>
|
||||
<DialogContent className="p-0 border-border-button">
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -684,7 +681,7 @@ function AdminUserManagement() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
form={createUserForm.id}
|
||||
type="submit"
|
||||
className="px-4 h-10"
|
||||
@ -693,7 +690,7 @@ function AdminUserManagement() {
|
||||
loading={createUserMutation.isPending}
|
||||
>
|
||||
{t('admin.confirm')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -40,8 +40,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
@ -63,6 +62,7 @@ import {
|
||||
|
||||
import { EMPTY_DATA, createFuzzySearchFn, getSortIcon } from './utils';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import useCreateEmailForm from './forms/email-form';
|
||||
import useImportExcelForm, {
|
||||
ImportExcelFormData,
|
||||
@ -157,18 +157,14 @@ function AdminWhitelist() {
|
||||
email: item.email,
|
||||
}));
|
||||
|
||||
const now = new Date();
|
||||
const YYYY = String(now.getFullYear()).padStart(4, '0');
|
||||
const MM = String(now.getMonth()).padStart(2, '0');
|
||||
const dd = String(now.getDate()).padStart(2, '0');
|
||||
const HH = String(now.getHours()).padStart(2, '0');
|
||||
const mm = String(now.getMinutes()).padStart(2, '0');
|
||||
const ss = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(columnData);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
|
||||
XLSX.writeFile(workbook, `whitelist_${YYYY}${MM}${dd}${HH}${mm}${ss}.xlsx`);
|
||||
XLSX.writeFile(
|
||||
workbook,
|
||||
`whitelist_${dayjs(new Date()).format('YYYYMMDDHHmmss')}.xlsx`,
|
||||
);
|
||||
};
|
||||
|
||||
const columnDefs = useMemo(
|
||||
@ -187,11 +183,11 @@ function AdminWhitelist() {
|
||||
id: 'actions',
|
||||
header: t('admin.actions'),
|
||||
cell: ({ row }) => (
|
||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-100">
|
||||
<div className="opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity">
|
||||
<Button
|
||||
variant="transparent"
|
||||
size="icon"
|
||||
className="border-0 text-text-secondary"
|
||||
className="border-0"
|
||||
onClick={() => {
|
||||
setItemToMakeAction(row.original);
|
||||
setEditModalOpen(true);
|
||||
@ -200,9 +196,9 @@ function AdminWhitelist() {
|
||||
<LucideUserPen />
|
||||
</Button>
|
||||
<Button
|
||||
variant="transparent"
|
||||
variant="danger"
|
||||
size="icon"
|
||||
className="border-0 text-text-secondary"
|
||||
className="border-0"
|
||||
onClick={() => {
|
||||
setItemToMakeAction(row.original);
|
||||
setDeleteModalOpen(true);
|
||||
@ -227,11 +223,13 @@ function AdminWhitelist() {
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
|
||||
enableSorting: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card className="!shadow-none relative h-full border border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||
<Card className="!shadow-none relative h-full border-0.5 border-border-button bg-transparent rounded-xl overflow-x-hidden overflow-y-auto">
|
||||
<Spotlight />
|
||||
|
||||
<ScrollArea className="size-full">
|
||||
@ -239,15 +237,13 @@ function AdminWhitelist() {
|
||||
<CardTitle>{t('admin.whitelistManagement')}</CardTitle>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="relative w-56">
|
||||
<LucideSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||
<Input
|
||||
className="pl-10 h-10 bg-bg-input border-border-button"
|
||||
placeholder={t('header.search')}
|
||||
value={table.getState().globalFilter}
|
||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<SearchInput
|
||||
className="w-56 h-10 bg-bg-input border-border-button"
|
||||
placeholder={t('header.search')}
|
||||
value={table.getState().globalFilter}
|
||||
onChange={(e) => table.setGlobalFilter(e.target.value)}
|
||||
prefix={<LucideSearch className="size-3.5" />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
@ -272,7 +268,7 @@ function AdminWhitelist() {
|
||||
onClick={() => setCreateModalOpen(true)}
|
||||
>
|
||||
<LucidePlus />
|
||||
{t('admin.createEmail')}
|
||||
{t('admin.newUser')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@ -360,18 +356,18 @@ function AdminWhitelist() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<DialogDescription className="text-text-primary">
|
||||
{t('admin.deleteWhitelistEmailConfirmation')}
|
||||
|
||||
<div className="rounded-lg mt-6 p-4 border border-border-button">
|
||||
{itemToMakeAction?.email}
|
||||
</div>
|
||||
</DialogDescription>
|
||||
|
||||
<div className="rounded-lg mt-6 p-4 border-0.5 border-border-button">
|
||||
{itemToMakeAction?.email}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
@ -384,7 +380,7 @@ function AdminWhitelist() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
className="px-4 h-10"
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
@ -398,7 +394,7 @@ function AdminWhitelist() {
|
||||
loading={deleteWhitelistEntryMutation.isPending}
|
||||
>
|
||||
{t('admin.delete')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -413,7 +409,7 @@ function AdminWhitelist() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -434,7 +430,7 @@ function AdminWhitelist() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
form={createEmailForm.id}
|
||||
type="submit"
|
||||
className="px-4 h-10"
|
||||
@ -443,7 +439,7 @@ function AdminWhitelist() {
|
||||
loading={createWhitelistEntryMutation.isPending}
|
||||
>
|
||||
{t('admin.confirm')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -459,7 +455,7 @@ function AdminWhitelist() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -487,7 +483,7 @@ function AdminWhitelist() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
form={editEmailForm.id}
|
||||
type="submit"
|
||||
className="px-4 h-10"
|
||||
@ -496,7 +492,7 @@ function AdminWhitelist() {
|
||||
loading={updateWhitelistEntryMutation.isPending}
|
||||
>
|
||||
{t('admin.confirm')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@ -511,7 +507,7 @@ function AdminWhitelist() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -532,7 +528,7 @@ function AdminWhitelist() {
|
||||
{t('admin.cancel')}
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
<Button
|
||||
form={importExcelForm.id}
|
||||
type="submit"
|
||||
className="px-4 h-10"
|
||||
@ -541,7 +537,7 @@ function AdminWhitelist() {
|
||||
loading={importExcelMutation.isPending}
|
||||
>
|
||||
{t('admin.import')}
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
9
web/src/pages/admin/wrappers/authorized.tsx
Normal file
9
web/src/pages/admin/wrappers/authorized.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { Routes } from '@/routes';
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
import { Navigate, Outlet } from 'umi';
|
||||
|
||||
export default function AuthorizedAdminWrapper() {
|
||||
const isLogin = !!authorizationUtil.getAuthorization();
|
||||
|
||||
return isLogin ? <Outlet /> : <Navigate to={Routes.Admin} />;
|
||||
}
|
||||
Reference in New Issue
Block a user