mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-25 16:26:51 +08:00
Add task executor bar chart, add system version string (#11155)
### What problem does this PR solve? - Add task executor bar chart - Add read version string ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { NavLink, Outlet, useNavigate } from 'umi';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
|
||||
import {
|
||||
LucideMonitor,
|
||||
@ -15,7 +15,7 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { logout } from '@/services/admin-service';
|
||||
import { getSystemVersion, logout } from '@/services/admin-service';
|
||||
|
||||
import authorizationUtil from '@/utils/authorization-util';
|
||||
|
||||
@ -26,6 +26,11 @@ const AdminNavigationLayout = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: version } = useQuery({
|
||||
queryKey: ['admin/version'],
|
||||
queryFn: async () => (await getSystemVersion())?.data?.data?.version,
|
||||
});
|
||||
|
||||
const navItems = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -109,8 +114,8 @@ const AdminNavigationLayout = () => {
|
||||
|
||||
<div className="mt-auto space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-accent-primary">
|
||||
vmm.ss.rr-nnn-commithash
|
||||
<span className="leading-none text-xs text-accent-primary">
|
||||
{version}
|
||||
</span>
|
||||
|
||||
<ThemeSwitch />
|
||||
|
||||
@ -280,24 +280,24 @@ function AdminRoles() {
|
||||
{/* Add role modal */}
|
||||
<Dialog open={isAddRoleModalOpen} onOpenChange={setAddRoleModalOpen}>
|
||||
<DialogContent
|
||||
className="max-w-2xl p-0 border-border-button"
|
||||
className="max-w-2xl"
|
||||
onAnimationEnd={() => {
|
||||
if (!isAddRoleModalOpen) {
|
||||
createRoleForm.form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.addNewRole')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<section className="px-6">
|
||||
<createRoleForm.FormComponent
|
||||
onSubmit={createRoleMutation.mutate}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -324,18 +324,17 @@ function AdminRoles() {
|
||||
onOpenChange={setEditRoleDescriptionModalOpen}
|
||||
>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!isEditRoleDescriptionModalOpen) {
|
||||
setRoleToMakeAction(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.editRoleDescription')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<section className="px-6">
|
||||
<form
|
||||
id={editRoleDescriptionFormId}
|
||||
onSubmit={(evt) => {
|
||||
@ -360,7 +359,7 @@ function AdminRoles() {
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -383,18 +382,17 @@ function AdminRoles() {
|
||||
{/* Delete role modal */}
|
||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!deleteModalOpen) {
|
||||
setRoleToMakeAction(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.deleteRole')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<section className="px-6">
|
||||
<DialogDescription className="text-text-primary">
|
||||
{t('admin.deleteRoleConfirmation')}
|
||||
</DialogDescription>
|
||||
@ -404,7 +402,7 @@ function AdminRoles() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
|
||||
@ -68,7 +68,7 @@ function ServiceDetail({ content }: ServiceDetailProps) {
|
||||
|
||||
if (typeof content === 'string') {
|
||||
return (
|
||||
<div className="rounded-lg p-4 bg-bg-card text-sm text-text-primary">
|
||||
<div className="rounded-xl p-4 bg-bg-card text-sm text-text-primary">
|
||||
<pre>
|
||||
<code>
|
||||
{typeof content === 'string'
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
@ -66,7 +67,9 @@ import {
|
||||
getSortIcon,
|
||||
} from './utils';
|
||||
|
||||
import JsonView from 'react18-json-view';
|
||||
import ServiceDetail from './service-detail';
|
||||
import TaskExecutorDetail from './task-executor-detail';
|
||||
|
||||
const columnHelper = createColumnHelper<AdminService.ListServicesItem>();
|
||||
const globalFilterFn = createFuzzySearchFn<AdminService.ListServicesItem>([
|
||||
@ -381,7 +384,7 @@ function AdminServiceStatus() {
|
||||
{/* Extra info modal*/}
|
||||
<Dialog open={extraInfoModalOpen} onOpenChange={setExtraInfoModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
className="flex flex-col max-h-[calc(100vh-4rem)] p-0 overflow-hidden"
|
||||
onAnimationEnd={() => {
|
||||
if (!extraInfoModalOpen) {
|
||||
setItemToMakeAction(null);
|
||||
@ -392,15 +395,16 @@ function AdminServiceStatus() {
|
||||
<DialogTitle>{t('admin.extraInfo')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 pt-6 pb-4">
|
||||
<div className="rounded-lg p-4 bg-bg-input">
|
||||
<pre className="text-sm">
|
||||
<code>
|
||||
{JSON.stringify(itemToMakeAction?.extra ?? {}, null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
<DialogDescription className="sr-only" />
|
||||
|
||||
<ScrollArea className="h-0 flex-1 grid">
|
||||
<div className="px-12">
|
||||
<JsonView
|
||||
src={itemToMakeAction?.extra ?? {}}
|
||||
className="rounded-lg p-4 bg-bg-card break-words text-text-secondary"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<Button
|
||||
@ -417,7 +421,7 @@ function AdminServiceStatus() {
|
||||
{/* Service details modal */}
|
||||
<Dialog open={detailModalOpen} onOpenChange={setDetailModalOpen}>
|
||||
<DialogContent
|
||||
className="flex flex-col max-h-[calc(100vh-4rem)] max-w-6xl p-0 border-border-button"
|
||||
className="flex flex-col max-h-[calc(100vh-4rem)] max-w-6xl p-0 overflow-hidden"
|
||||
onAnimationEnd={() => {
|
||||
if (!detailModalOpen) {
|
||||
setItemToMakeAction(null);
|
||||
@ -426,14 +430,30 @@ function AdminServiceStatus() {
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogTitle>
|
||||
<Trans i18nKey="admin.serviceDetail">
|
||||
{{ name: itemToMakeAction?.name }}
|
||||
</Trans>
|
||||
{itemToMakeAction?.service_type === 'task_executor' ? (
|
||||
t('admin.taskExecutorDetail')
|
||||
) : (
|
||||
<Trans i18nKey="admin.serviceDetail">
|
||||
{{ name: itemToMakeAction?.name }}
|
||||
</Trans>
|
||||
)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<ScrollArea className="pt-6 pb-4 px-12 h-0 flex-1 text-text-secondary flex flex-col">
|
||||
<ServiceDetail content={serviceDetails?.message} />
|
||||
<DialogDescription className="sr-only" />
|
||||
|
||||
<ScrollArea className="h-0 flex-1 text-text-secondary grid">
|
||||
<div className="px-12">
|
||||
{itemToMakeAction?.service_type === 'task_executor' ? (
|
||||
<TaskExecutorDetail
|
||||
content={
|
||||
serviceDetails?.message as AdminService.TaskExecutorInfo
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ServiceDetail content={serviceDetails?.message} />
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
|
||||
162
web/src/pages/admin/task-executor-detail.tsx
Normal file
162
web/src/pages/admin/task-executor-detail.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import dayjs from 'dayjs';
|
||||
import JsonView from 'react18-json-view';
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Rectangle,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
} from 'recharts';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { formatDate, formatTime } from '@/utils/date';
|
||||
|
||||
interface TaskExecutorDetailProps {
|
||||
content?: AdminService.TaskExecutorInfo;
|
||||
}
|
||||
|
||||
function CustomTooltip({ active, payload, ...restProps }: any) {
|
||||
if (active && payload && payload.length) {
|
||||
const item = payload.at(0)?.payload ?? {};
|
||||
|
||||
return (
|
||||
<Card className="border bg-bg-base overflow-hidden shadow-xl">
|
||||
<CardHeader className="px-3 py-2 text-text-primary bg-bg-title">
|
||||
<CardTitle className="text-lg">
|
||||
{formatDate(restProps.label)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="p-0">
|
||||
<ScrollArea className="px-3">
|
||||
<div className="max-h-56">
|
||||
<JsonView
|
||||
src={item}
|
||||
displaySize={30}
|
||||
className="py-2 break-words text-text-secondary"
|
||||
/>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function CustomAxisTick({ x, y, payload }: any) {
|
||||
return (
|
||||
<g transform={`translate(${x},${y})`}>
|
||||
<text
|
||||
x={0}
|
||||
y={0}
|
||||
dy={8}
|
||||
transform="rotate(60)"
|
||||
textAnchor="start"
|
||||
fill="rgb(var(--text-secondary))"
|
||||
>
|
||||
{formatTime(payload.value)}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskExecutorDetail({ content }: TaskExecutorDetailProps) {
|
||||
return (
|
||||
<section className="space-y-8">
|
||||
{Object.entries(content ?? {}).map(([name, data]) => {
|
||||
const items = data.map((x) => ({
|
||||
...x,
|
||||
done: Math.floor(Math.random() * 100),
|
||||
failed: Math.floor(Math.random() * 100),
|
||||
now: dayjs(x.now).valueOf(),
|
||||
}));
|
||||
|
||||
const lastItem = items.at(-1);
|
||||
|
||||
return (
|
||||
<Card key={name} className="border-0">
|
||||
<CardHeader className="text-text-primary">
|
||||
<CardTitle>{name}</CardTitle>
|
||||
|
||||
<div className="text-sm text-text-secondary space-x-4">
|
||||
<span>Lag: {lastItem?.lag}</span>
|
||||
<span>Pending: {lastItem?.pending}</span>
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<ResponsiveContainer className="min-h-40 w-full">
|
||||
<BarChart
|
||||
data={items}
|
||||
margin={{ bottom: 32 }}
|
||||
className="text-sm text-text-secondary"
|
||||
>
|
||||
<XAxis
|
||||
dataKey="now"
|
||||
type="number"
|
||||
scale="time"
|
||||
domain={['dataMin', 'dataMax']}
|
||||
tick={CustomAxisTick}
|
||||
interval="equidistantPreserveStart"
|
||||
angle={60}
|
||||
minTickGap={16}
|
||||
allowDataOverflow
|
||||
padding={{ left: 24, right: 24 }}
|
||||
/>
|
||||
|
||||
{/* <YAxis
|
||||
type="number"
|
||||
tick={{ fill: 'rgb(var(--text-secondary))' }}
|
||||
/> */}
|
||||
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
|
||||
<Tooltip
|
||||
trigger="click"
|
||||
content={CustomTooltip}
|
||||
wrapperStyle={{
|
||||
pointerEvents: 'auto',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Legend wrapperStyle={{ bottom: 0 }} />
|
||||
|
||||
<Bar
|
||||
dataKey="done"
|
||||
fill="rgb(var(--state-success))"
|
||||
activeBar={
|
||||
<Rectangle
|
||||
fill="rgb(var(--state-success))"
|
||||
stroke="var(--bg-base)"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Bar
|
||||
dataKey="failed"
|
||||
fill="rgb(var(--state-error))"
|
||||
activeBar={
|
||||
<Rectangle
|
||||
fill="rgb(var(--state-error))"
|
||||
stroke="var(--bg-base)"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default TaskExecutorDetail;
|
||||
@ -91,6 +91,7 @@ import {
|
||||
parseBooleanish,
|
||||
} from './utils';
|
||||
|
||||
import { DialogDescription } from '@radix-ui/react-dialog';
|
||||
import EnterpriseFeature from './components/enterprise-feature';
|
||||
|
||||
const columnHelper = createColumnHelper<AdminService.ListUsersItem>();
|
||||
@ -210,8 +211,8 @@ function AdminUserManagement() {
|
||||
columnHelper.accessor('nickname', {
|
||||
header: t('admin.nickname'),
|
||||
cell: ({ row, cell }) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{cell.getValue()}</span>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 empty:hidden">{cell.getValue()}</span>
|
||||
|
||||
{row.original.is_superuser ? (
|
||||
<Badge variant="secondary">{t('admin.superuser')}</Badge>
|
||||
@ -553,20 +554,22 @@ function AdminUserManagement() {
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||
<DialogContent className="p-0 border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.deleteUser')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4 text-text-primary text-sm">
|
||||
{t('admin.deleteUserConfirmation')}
|
||||
<section className="px-6">
|
||||
<DialogDescription>
|
||||
{t('admin.deleteUserConfirmation')}
|
||||
</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">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -595,18 +598,17 @@ function AdminUserManagement() {
|
||||
{/* Change Password Modal */}
|
||||
<Dialog open={passwordModalOpen} onOpenChange={setPasswordModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!passwordModalOpen) {
|
||||
changePasswordForm.form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.changePassword')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4 text-text-secondary">
|
||||
<section className="px-6">
|
||||
<changePasswordForm.FormComponent
|
||||
key="changePasswordForm"
|
||||
email={userToMakeAction?.email || ''}
|
||||
@ -621,7 +623,7 @@ function AdminUserManagement() {
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -656,19 +658,19 @@ function AdminUserManagement() {
|
||||
createUserForm.form.reset();
|
||||
}}
|
||||
>
|
||||
<DialogContent className="p-0 border-border-button">
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.createNewUser')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<section className="px-6">
|
||||
<createUserForm.FormComponent
|
||||
id={createUserForm.id}
|
||||
onSubmit={createUserMutation.mutate}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
|
||||
@ -349,18 +349,17 @@ function AdminWhitelist() {
|
||||
{/* Delete Confirmation Modal */}
|
||||
<Dialog open={deleteModalOpen} onOpenChange={setDeleteModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!deleteModalOpen) {
|
||||
setItemToMakeAction(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.deleteEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4">
|
||||
<section className="px-6">
|
||||
<DialogDescription className="text-text-primary">
|
||||
{t('admin.deleteWhitelistEmailConfirmation')}
|
||||
</DialogDescription>
|
||||
@ -370,7 +369,7 @@ function AdminWhitelist() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -402,25 +401,24 @@ function AdminWhitelist() {
|
||||
{/* Create Email Modal */}
|
||||
<Dialog open={createModalOpen} onOpenChange={setCreateModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!createModalOpen) {
|
||||
createEmailForm.form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.createEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4 text-text-secondary">
|
||||
<section className="px-6">
|
||||
<createEmailForm.FormComponent
|
||||
id={createEmailForm.id}
|
||||
onSubmit={createWhitelistEntryMutation.mutate}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -447,7 +445,6 @@ function AdminWhitelist() {
|
||||
{/* Edit Email Modal */}
|
||||
<Dialog open={editModalOpen} onOpenChange={setEditModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!editModalOpen) {
|
||||
setItemToMakeAction(null);
|
||||
@ -455,11 +452,11 @@ function AdminWhitelist() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.editEmail')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4 text-text-secondary">
|
||||
<section className="px-6">
|
||||
<editEmailForm.FormComponent
|
||||
id={editEmailForm.id}
|
||||
onSubmit={(value) => {
|
||||
@ -473,7 +470,7 @@ function AdminWhitelist() {
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
@ -500,25 +497,24 @@ function AdminWhitelist() {
|
||||
{/* Import Excel Modal */}
|
||||
<Dialog open={importModalOpen} onOpenChange={setImportModalOpen}>
|
||||
<DialogContent
|
||||
className="p-0 border-border-button"
|
||||
onAnimationEnd={() => {
|
||||
if (!importModalOpen) {
|
||||
importExcelForm.form.reset();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogHeader className="p-6 border-b-0.5 border-border-button">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.importWhitelist')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<section className="px-12 py-4 text-text-secondary">
|
||||
<section className="px-6">
|
||||
<importExcelForm.FormComponent
|
||||
id={importExcelForm.id}
|
||||
onSubmit={importExcelMutation.mutate}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<DialogFooter className="flex justify-end gap-4 px-12 pt-4 pb-8">
|
||||
<DialogFooter className="gap-4 px-6 py-4">
|
||||
<Button
|
||||
className="px-4 h-10 dark:border-border-button"
|
||||
variant="outline"
|
||||
|
||||
Reference in New Issue
Block a user