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:
Jimmy Ben Klieve
2025-11-10 10:05:19 +08:00
committed by GitHub
parent 660386d3b5
commit 1cd54832b5
42 changed files with 685 additions and 539 deletions

View File

@ -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>
);
});

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -0,0 +1,7 @@
import { Outlet } from 'umi';
const AdminRootLayout = () => {
return <Outlet />;
};
export default AdminRootLayout;

View File

@ -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>

View File

@ -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>
);

View File

@ -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>

View File

@ -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

View File

@ -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 }}

View File

@ -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}`)}

View File

@ -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>

View File

@ -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>

View 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} />;
}