Files
FastGPT/projects/app/src/components/Layout/index.tsx
Archer c75f154728 Password security policy (#4765)
* Psw (#4748)

* feat: 添加重置密码功能及相关接口

- 在用户模型中新增 passwordUpdateTime 字段以记录密码更新时间。
- 更新用户模式以支持密码更新时间的存储。
- 新增重置密码的模态框组件,允许用户重置密码。
- 实现重置密码的 API 接口,支持根据用户 ID 更新密码。
- 更新相关国际化文件,添加重置密码的提示信息。

* 更新国际化文件,添加重置密码相关提示信息,并优化重置密码模态框的实现。修复部分代码逻辑,确保用户体验流畅。

* 更新国际化文件,添加重置密码相关提示信息,优化重置密码模态框的实现,修复部分代码逻辑,确保用户体验流畅。新增获取用户密码更新时间的API接口,并调整相关逻辑以支持密码重置功能。

* update

* fix

* fix

* Added environment variables NEXT_PUBLIC_PASSWORD_UPDATETIME to support password update time configuration, update related logic to implement password mandatory update function, and optimize the implementation of reset password modal box to improve user experience.

* update index

* 更新用户密码重置功能,调整相关API接口,优化重置密码模态框的实现,确保用户体验流畅。修复部分代码逻辑,更新国际化提示信息。

* 删除获取用户密码更新时间的API接口,并在布局组件中移除不必要的重置密码模态框。优化代码结构,提升可维护性。

* update

* perf: reset expired password code

* perf: layout child components

* doc

* remove invalid env

* perf: update password code

---------

Co-authored-by: dreamer6680 <1468683855@qq.com>
2025-05-08 12:11:08 +08:00

188 lines
5.8 KiB
TypeScript

import React, { useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/web/support/user/useUserStore';
import { getUnreadCount } from '@/web/support/user/inform/api';
import dynamic from 'next/dynamic';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import Auth from './auth';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { useDebounceEffect, useMount } from 'ahooks';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
import WorkorderButton from './WorkorderButton';
const Navbar = dynamic(() => import('./navbar'));
const NavbarPhone = dynamic(() => import('./navbarPhone'));
const ResetExpiredPswModal = dynamic(
() => import('@/components/support/user/safe/ResetExpiredPswModal'),
{ ssr: false }
);
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'), {
ssr: false
});
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'), {
ssr: false
});
const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'), {
ssr: false
});
const UpdateContact = dynamic(() => import('@/components/support/user/inform/UpdateContactModal'), {
ssr: false
});
const ManualCopyModal = dynamic(
() => import('@fastgpt/web/hooks/useCopyData').then((mod) => mod.ManualCopyModal),
{ ssr: false }
);
const pcUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true,
'/login/provider': true,
'/login/fastlogin': true,
'/chat/share': true,
'/chat/team': true,
'/app/edit': true,
'/chat': true,
'/tools/price': true,
'/price': true
};
const phoneUnShowLayoutRoute: Record<string, boolean> = {
'/': true,
'/login': true,
'/login/provider': true,
'/login/fastlogin': true,
'/chat/share': true,
'/chat/team': true,
'/tools/price': true,
'/price': true
};
export const navbarWidth = '64px';
const Layout = ({ children }: { children: JSX.Element }) => {
const router = useRouter();
const { toast } = useToast();
const { t } = useTranslation();
const { Loading } = useLoading();
const { loading, feConfigs, llmModelList, embeddingModelList } = useSystemStore();
const { isPc } = useSystem();
const { userInfo, isUpdateNotification, setIsUpdateNotification } = useUserStore();
const { setUserDefaultLng } = useI18nLng();
const isChatPage = useMemo(
() => router.pathname === '/chat' && Object.values(router.query).join('').length !== 0,
[router.pathname, router.query]
);
const isHideNavbar = !!pcUnShowLayoutRoute[router.pathname];
// System hook
const { data, refetch: refetchUnRead } = useQuery(['getUnreadCount'], getUnreadCount, {
enabled: !!userInfo && !!feConfigs.isPlus,
refetchInterval: 30000
});
const unread = data?.unReadCount || 0;
const importantInforms = data?.importantInforms || [];
const showUpdateNotification =
isUpdateNotification &&
feConfigs?.bind_notification_method &&
feConfigs?.bind_notification_method.length > 0 &&
!userInfo?.contact &&
!!userInfo?.team.permission.isOwner;
useMount(() => {
setUserDefaultLng();
});
// Check model invalid
useDebounceEffect(
() => {
if (userInfo?.username === 'root') {
if (llmModelList.length === 0) {
toast({
status: 'warning',
title: t('common:llm_model_not_config')
});
router.pathname !== '/account/model' && router.push('/account/model');
} else if (embeddingModelList.length === 0) {
toast({
status: 'warning',
title: t('common:embedding_model_not_config')
});
router.pathname !== '/account/model' && router.push('/account/model');
}
}
},
[embeddingModelList.length, llmModelList.length, userInfo?.username],
{
wait: 2000
}
);
return (
<>
<Box h={'100%'} bg={'myGray.100'}>
{isPc === true && (
<>
{isHideNavbar ? (
<Auth>{children}</Auth>
) : (
<Auth>
<Box h={'100%'} position={'fixed'} left={0} top={0} w={navbarWidth}>
<Navbar unread={unread} />
</Box>
<Box h={'100%'} ml={navbarWidth} overflow={'overlay'}>
{children}
</Box>
</Auth>
)}
</>
)}
{isPc === false && (
<>
{phoneUnShowLayoutRoute[router.pathname] || isChatPage ? (
<Auth>{children}</Auth>
) : (
<Auth>
<Flex h={'100%'} flexDirection={'column'}>
<Box flex={'1 0 0'} h={0}>
{children}
</Box>
<Box h={'50px'} borderTop={'1px solid rgba(0,0,0,0.1)'}>
<NavbarPhone unread={unread} />
</Box>
</Flex>
</Auth>
)}
</>
)}
</Box>
{feConfigs?.isPlus && (
<>
<NotSufficientModal />
<SystemMsgModal />
{showUpdateNotification && (
<UpdateContact onClose={() => setIsUpdateNotification(false)} mode="contact" />
)}
{!!userInfo && importantInforms.length > 0 && (
<ImportantInform informs={importantInforms} refetch={refetchUnRead} />
)}
<ResetExpiredPswModal />
<WorkorderButton />
</>
)}
<ManualCopyModal />
<Loading loading={loading} zIndex={999999} />
</>
);
};
export default Layout;