From f9f302dd2a599cb3ca38514712321b545bdb2afc Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 30 Mar 2026 11:41:18 +0800 Subject: [PATCH] feat(web): api key & space config ui upgrade --- web/src/assets/images/common/eye.svg | 16 +-- web/src/assets/images/menuNew/arrow_t_r.svg | 16 +++ web/src/assets/images/menuNew/logout_red.svg | 17 +++ web/src/assets/images/menuNew/settings.svg | 19 +++ web/src/assets/images/menuNew/userInfo.svg | 13 +++ web/src/components/Header/index.module.css | 22 ++++ web/src/components/Header/index.tsx | 64 +++++++--- web/src/i18n/en.ts | 4 +- web/src/i18n/zh.ts | 4 +- web/src/styles/index.css | 5 + web/src/views/ApiKeyManagement/index.tsx | 116 ++++++++++++------- web/src/views/MemberManagement/index.tsx | 7 +- web/src/views/Prompt/index.tsx | 4 +- web/src/views/SpaceConfig/index.tsx | 61 ++++------ web/src/views/UserManagement/index.tsx | 2 +- 15 files changed, 256 insertions(+), 114 deletions(-) create mode 100644 web/src/assets/images/menuNew/arrow_t_r.svg create mode 100644 web/src/assets/images/menuNew/logout_red.svg create mode 100644 web/src/assets/images/menuNew/settings.svg create mode 100644 web/src/assets/images/menuNew/userInfo.svg diff --git a/web/src/assets/images/common/eye.svg b/web/src/assets/images/common/eye.svg index df2af1cf..c7b531b8 100644 --- a/web/src/assets/images/common/eye.svg +++ b/web/src/assets/images/common/eye.svg @@ -1,13 +1,13 @@ - - 编辑 + + link-outlined - - - - - - + + + + + + diff --git a/web/src/assets/images/menuNew/arrow_t_r.svg b/web/src/assets/images/menuNew/arrow_t_r.svg new file mode 100644 index 00000000..884e46c1 --- /dev/null +++ b/web/src/assets/images/menuNew/arrow_t_r.svg @@ -0,0 +1,16 @@ + + + 编组 51 + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/logout_red.svg b/web/src/assets/images/menuNew/logout_red.svg new file mode 100644 index 00000000..7057b974 --- /dev/null +++ b/web/src/assets/images/menuNew/logout_red.svg @@ -0,0 +1,17 @@ + + + 退出 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/settings.svg b/web/src/assets/images/menuNew/settings.svg new file mode 100644 index 00000000..9a64bb29 --- /dev/null +++ b/web/src/assets/images/menuNew/settings.svg @@ -0,0 +1,19 @@ + + + 设置-界面设置 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/assets/images/menuNew/userInfo.svg b/web/src/assets/images/menuNew/userInfo.svg new file mode 100644 index 00000000..0e67a919 --- /dev/null +++ b/web/src/assets/images/menuNew/userInfo.svg @@ -0,0 +1,13 @@ + + + 账户 + + + + + + + + + + \ No newline at end of file diff --git a/web/src/components/Header/index.module.css b/web/src/components/Header/index.module.css index ca7c79cf..d39c91ec 100644 --- a/web/src/components/Header/index.module.css +++ b/web/src/components/Header/index.module.css @@ -24,4 +24,26 @@ .header :global(.ant-breadcrumb .ant-breadcrumb-item:last-child a) { color: #212332; font-weight: 600; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu) { + padding: 12px 8px; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item), +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-submenu-title), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-submenu-title) { + padding-left: 8px; + padding-right: 4px; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover) { + background-color: #F6F6F6; + color: #FF5D34; +} +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item-divider), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item-divider), +.userDropdown:global(.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-submenu-title-divider), +.userDropdown:global(.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-submenu-title-divider) { + margin: 10px 8px; } \ No newline at end of file diff --git a/web/src/components/Header/index.tsx b/web/src/components/Header/index.tsx index 7a5d17b9..ac59fcc2 100644 --- a/web/src/components/Header/index.tsx +++ b/web/src/components/Header/index.tsx @@ -13,12 +13,12 @@ * @component */ -import { type FC, useRef } from 'react'; -import { Layout, Dropdown, Breadcrumb } from 'antd'; +import { type FC, useRef, useState } from 'react'; +import { Layout, Dropdown, Breadcrumb, Flex } from 'antd'; import type { MenuProps, BreadcrumbProps } from 'antd'; -import { UserOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom'; +import clsx from 'clsx'; import { useUser } from '@/store/user'; import { useMenu } from '@/store/menu'; @@ -76,27 +76,39 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { const userMenuItems: MenuProps['items'] = [ { key: '1', + icon: + {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(0, 2) : user.username[0]} + , label: (<> -
{user.username}
-
{user.email}
+
{user.username}
+
{user.email}
), }, { key: '2', type: 'divider', + className: 'rb:bg-[#EBEBEB]!' }, { key: '3', - icon: , - label: t('header.userInfo'), + icon:
, + label: + {t('header.userInfo')} +
+
, + className: 'rb:text-[#212332]!', onClick: () => { userInfoModalRef.current?.handleOpen() }, }, { key: '4', - icon: , - label: t('header.settings'), + icon:
, + label: + {t('header.settings')} +
+
, + className: 'rb:text-[#212332]!', onClick: () => { settingModalRef.current?.handleOpen() }, @@ -104,12 +116,14 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { { key: '5', type: 'divider', + className: 'rb:bg-[#EBEBEB]!' }, { key: '6', - icon: , + icon:
, label: t('header.logout'), danger: true, + className: 'rb:hover:rb:bg-transparent rb:hover:text-[#FF5D34]!', onClick: handleLogout, }, ]; @@ -147,18 +161,34 @@ const AppHeader: FC<{source?: 'space' | 'manage';}> = ({source = 'manage'}) => { }); } + const [open, setOpen] = useState(false); + const handleOpenChange = (open: boolean) => { + setOpen(open); + } return (
{/* Breadcrumb navigation */} {/* User info dropdown menu */} - -
{user.username}
-
+ {user.username && ( + + + + {/[\u4e00-\u9fa5]/.test(user.username) ? user.username.slice(0, 2) : user.username[0]} + + {user.username} +
+
+
+ )} diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 9b957a84..2975796a 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -980,6 +980,7 @@ export const en = { scene_id: 'Ontology Scenario', }, member: { + memberList: 'Member List', username: 'Username', account: 'Account', role: 'Role', @@ -1908,7 +1909,8 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re permissionInfo: 'Permission Information', is_expired: 'Status', active: 'Active', - inactive: 'Expired' + inactive: 'Expired', + noScopes: 'There is no permission information here…', }, tool: { mcp: 'MCP Services', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 862ed5d4..3edd84e3 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1375,6 +1375,7 @@ export const zh = { scene_id: '本体场景', }, member: { + memberList: '成员列表', username: '用户名', account: '账号', role: '角色', @@ -1905,7 +1906,8 @@ export const zh = { permissionInfo: '授权信息', is_expired: '状态', active: '活跃', - inactive: '过期' + inactive: '过期', + noScopes: '暂无权限信息…', }, tool: { mcp: 'MCP 服务', diff --git a/web/src/styles/index.css b/web/src/styles/index.css index 71a6cce4..684a4ba6 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -396,6 +396,11 @@ body { color: #FFFFFF; background-color: #171719; } +.ant-dropdown .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover, +.ant-dropdown-menu-submenu .ant-dropdown-menu .ant-dropdown-menu-item.ant-dropdown-menu-item-danger:not(.ant-dropdown-menu-item-disabled):hover { + background-color: #F6F6F6; + color: #FF5D34; +} .spin.ant-spin-nested-loading .ant-spin-container::after { background: transparent; diff --git a/web/src/views/ApiKeyManagement/index.tsx b/web/src/views/ApiKeyManagement/index.tsx index 7bb417c5..071b9ef5 100644 --- a/web/src/views/ApiKeyManagement/index.tsx +++ b/web/src/views/ApiKeyManagement/index.tsx @@ -6,20 +6,22 @@ */ import React, { useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { Button, App, Space } from 'antd'; +import { Button, App, Dropdown, Flex } from 'antd'; import clsx from 'clsx'; import { DeleteOutlined, EditOutlined, EyeOutlined } from '@ant-design/icons'; import copy from 'copy-to-clipboard' +import type { MenuInfo } from 'rc-menu/lib/interface'; import type { ApiKey, ApiKeyModalRef } from './types'; import ApiKeyModal from './components/ApiKeyModal'; import ApiKeyDetailModal from './components/ApiKeyDetailModal'; -import RbCard from '@/components/RbCard/Card' +import RbCard from '@/components/RbCard' import { getApiKeyListUrl, deleteApiKey } from '@/api/apiKey'; import PageScrollList, { type PageScrollListRef } from '@/components/PageScrollList' import { formatDateTime } from '@/utils/format'; import Tag from '@/components/Tag' import { maskApiKeys } from '@/utils/apiKeyReplacer'; +import RbDescriptions from '@/components/RbDescriptions'; /** * API Key Management page component @@ -87,59 +89,85 @@ const ApiKeyManagement: React.FC = () => { } return ( <> -
+ -
+ ref={scrollListRef} url={getApiKeyListUrl} query={{ is_active: true, type: 'service' }} - column={2} + column={3} renderItem={(apiKeyItem) => { return ( - - {['id', 'is_expired', 'created_at'].map((key, index) => ( -
- {t(`apiKey.${key}`)} - - { key === 'created_at' - ? formatDateTime(apiKeyItem[key], 'YYYY-MM-DD HH:mm:ss') - : key === 'is_expired' - ? {apiKeyItem[key] ? t('apiKey.inactive') : t('apiKey.active')} - : String(apiKeyItem[key as keyof ApiKey]) - } - -
- ))} + + + {apiKeyItem.name} + + {apiKeyItem.scopes?.includes('memory') && {t('apiKey.memoryEngine')}} + {apiKeyItem.scopes?.includes('rag') && {t('apiKey.knowledgeBase')}} + {!apiKeyItem.scopes?.includes('memory') && !apiKeyItem.scopes?.includes('rag') &&
{t('apiKey.noScopes')}
} +
+
+ , + label: t('common.edit'), + onClick: () => handleEdit(apiKeyItem), + }, + { + key: 'view', + icon:
, + label: t('common.view'), + onClick: () => handleView(apiKeyItem), + }, + { + key: 'delete', + danger: true, + icon:
, + label: t('common.delete'), + onClick: () => handleDelete(apiKeyItem), + }, + ] + }} + placement="bottomRight" + > +
+ + + } + isNeedTooltip={false} + headerClassName="rb:min-h-[78px]!" + > + ({ + key, + label: t(`apiKey.${key}`), + children: + {key === 'created_at' + ? formatDateTime(apiKeyItem[key], 'YYYY-MM-DD HH:mm:ss') + : key === 'is_expired' + ? {apiKeyItem[key] ? t('apiKey.inactive') : t('apiKey.active')} + : String(apiKeyItem[key as keyof ApiKey]) + } + + }))} + /> -
- {maskApiKeys(apiKeyItem.api_key)} - - -
- - - {apiKeyItem.scopes?.includes('memory') && {t('apiKey.memoryEngine')}} - {apiKeyItem.scopes?.includes('rag') && {t('apiKey.knowledgeBase')}} - - -
- - - -
+ + {maskApiKeys(apiKeyItem.api_key)} + +
handleCopy(apiKeyItem.api_key)} className="rb:cursor-pointer rb:rounded-md rb:size-6 rb:bg-[url('@/assets/images/common/copy_dark.svg')] rb:bg-size-[16px_16px] rb:bg-center rb:bg-no-repeat" style={{ backgroundColor: 'rgba(0,0,0,0.08)' }}>
+
); }} diff --git a/web/src/views/MemberManagement/index.tsx b/web/src/views/MemberManagement/index.tsx index f846a310..b9b392ff 100644 --- a/web/src/views/MemberManagement/index.tsx +++ b/web/src/views/MemberManagement/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 16:42:12 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-03-26 15:48:43 + * @Last Modified time: 2026-03-30 11:38:42 */ /** * Member Management Page @@ -106,9 +106,10 @@ const MemberManagement: React.FC = () => { return (
- + +
{t('member.memberList')}
diff --git a/web/src/views/Prompt/index.tsx b/web/src/views/Prompt/index.tsx index 469b1e39..095a8daa 100644 --- a/web/src/views/Prompt/index.tsx +++ b/web/src/views/Prompt/index.tsx @@ -188,7 +188,7 @@ const Prompt: FC = () => { @@ -249,7 +249,7 @@ const Prompt: FC = () => {
{ const { t } = useTranslation(); @@ -63,7 +61,9 @@ const SpaceConfig: FC = () => { } return ( -
+
+
{t('menu.spaceConfig')}
+
{t('space.configAlert')}
{pageLoading ? :
{ layout="vertical" > - - - - {t('space.configAlert')} - - - - +
}
diff --git a/web/src/views/UserManagement/index.tsx b/web/src/views/UserManagement/index.tsx index 2ffdc91b..ed09994f 100644 --- a/web/src/views/UserManagement/index.tsx +++ b/web/src/views/UserManagement/index.tsx @@ -144,7 +144,7 @@ const UserManagement: React.FC = () => { return (
-
{t('user.userList')}
+
{t('user.userList')}