diff --git a/.gitignore b/.gitignore index 830ed6b3..c23d454b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ /config.yml /build/ /apipark -.gitlab-ci.yml \ No newline at end of file +.gitlab-ci.yml diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e605da20..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "cSpell.words": [ - "Antd", - "apinto", - "Apipark", - "logsettings", - "resourcesettings" - ] -} \ No newline at end of file diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 00000000..e06d3c9b --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,7 @@ +node_modules +dist +build +coverage +.next +*.d.ts +*.js \ No newline at end of file diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index fc718a97..26b9c6af 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -18,13 +18,18 @@ "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": ["react", "@typescript-eslint", "prettier"], + "plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"], "rules": { "react/react-in-jsx-scope": "off", "prettier/prettier": "error", "@typescript-eslint/no-explicit-any": "warn", "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["warn"] + "@typescript-eslint/no-unused-vars": "off", + "unused-imports/no-unused-imports": "error", + "unused-imports/no-unused-vars": [ + "warn", + { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } + ] }, "settings": { "react": { diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json index 0260651f..99797c96 100644 --- a/frontend/.prettierrc.json +++ b/frontend/.prettierrc.json @@ -1,10 +1,11 @@ { - "semi": true, - "singleQuote": true, - "trailingComma": "es5", + "printWidth": 120, + "useTabs": false, "tabWidth": 2, - "printWidth": 100, + "semi": false, "bracketSpacing": true, - "arrowParens": "avoid", - "jsxSingleQuote": false + "arrowParens": "always", + "trailingComma": "none", + "singleQuote": true, + "bracketLine": true } diff --git a/frontend/.windsurfrules b/frontend/.windsurfrules new file mode 100644 index 00000000..baa0cc3e --- /dev/null +++ b/frontend/.windsurfrules @@ -0,0 +1,45 @@ +Create detailed components with these requirements: +1. Use 'use client' directive for client-side components +2. Style with Tailwind CSS utility classes for responsive design +3. Use React Router for navigation +4. Use Ant Design for UI components +5. Use iconify React for icons (from @iconify/react package). Do NOT use other UI libraries unless requested +6. Use local photos from public folder where appropriate, only valid URLs you know exist +7. Create root layout.tsx page that wraps necessary navigation items to all pages +8. MUST implement the navigation elements items in their rightful place i.e. Left sidebar, Top header +9. Accurately implement necessary grid layouts +10. Follow proper import practices: + - Use @/ path aliases + - Keep component imports organized + - Update current packages/core/src/pages/Root.tsx with new comprehensive code + - Don't forget root route (page.tsx) handling + - You MUST complete the entire prompt before stopping +​11. Table component should use `import PageList from "@common/components/aoplatform/PageList.tsx"` +12. PageList component MUST use addNewBtnTitle for add button, NOT toolBarRender. Example: +getTeamList()} + showPagination={false} + addNewBtnTitle={$t('添加团队')} + addNewBtnAccess = "system.organization.team.add" + searchPlaceholder={$t("输入名称、ID、负责人查找团队")} + onAddNewBtnClick={()=>{openModal('add')}} + onSearchWordChange={(e)=>{setSearchWord(e.target.value)}} + onRowClick={(row:TeamTableListItem)=>(navigate(`../inside/${row.id}/setting`))} +/> +13. use `const { fetchData } = useFetch()` to fetch http data,such as +```tsx +fetchData>('account/profile', { method: 'GET' }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setUserInfo(data.profile) + dispatch({ type: 'UPDATE_USERDATA', userData: data.profile }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } +}) +``` +14. can't not import new package! diff --git a/frontend/i18next-scanner.config.js b/frontend/i18next-scanner.config.js index a2ad5e71..1529283e 100644 --- a/frontend/i18next-scanner.config.js +++ b/frontend/i18next-scanner.config.js @@ -6,7 +6,7 @@ const systemLanguage = { en_US: 'en-US', zh_CN: 'zh-CN', ja_JP: 'ja-JP', - zh_TW: 'zh-TW' + zh_TW: 'zh-TW', }; const localesDir = 'packages/common/src/locales/scan'; const newJsonDir = 'packages/common/src/locales/scan/newJson'; @@ -19,7 +19,7 @@ fs.readdirSync(localesDir).forEach(file => { const lang = path.basename(file, '.json'); const filePath = path.join(localesDir, file); try { - console.log('Current working directory:', process.cwd(),filePath); + console.log('Current working directory:', process.cwd(), filePath); const existJsonData = fs.readFileSync(filePath); existData[lang] = JSON.parse(existJsonData); } catch (error) { @@ -36,20 +36,18 @@ fs.readdirSync(localesDir).forEach(file => { const keyList = Object.keys(existData); - // 清空 newJson 目录下的所有语言文件 Object.values(systemLanguage).forEach(lng => { const newJsonPath = path.join(newJsonDir, `${lng}.json`); - fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件 + fs.writeFileSync(newJsonPath, JSON.stringify({})); // 清空文件 }); - module.exports = { input: [ 'packages/*/src/**/*.{js,jsx,tsx,ts}', // 不需要扫描的文件加! '!packages/*/src/locales/**', - '!**/node_modules/**' + '!**/node_modules/**', ], output: 'packages/common/src/locales/scan', // 输出目录 options: { @@ -62,15 +60,15 @@ module.exports = { loadPath: './newJson/{{lng}}.json', // 输入路径 (手动新建目录) savePath: './newJson/{{lng}}.json', // 输出路径 (输出会根据输入路径内容自增, 不会覆盖已有的key) jsonIndent: 2, - lineEnding: '\n' + lineEnding: '\n', }, removeUnusedKeys: true, nsSeparator: false, // namespace separator keySeparator: false, // key separator interpolation: { prefix: '{{', - suffix: '}}' - } + suffix: '}}', + }, }, // 这里我们要实现将中文转换成crc格式, 通过crc格式key作为索引, 最终实现语言包的切换. transform: function (file, enc, done) { @@ -80,11 +78,10 @@ module.exports = { parser.parseFuncFromString(content, { list: ['t'] }, (key, options) => { options.defaultValue = key; const hashKey = `K${crc32(key).toString(16)}`; // crc32转换格式 - keyHashMap[key] = hashKey; - + keyHashMap[key] = hashKey; // 遍历每种语言,逐个语言检查翻译是否存在 - keyList.forEach((lng) => { + keyList.forEach(lng => { const langData = existData[lng] || {}; // 如果某语言没有翻译该字段,则记录到该语言的 newJson 文件中 @@ -116,13 +113,12 @@ module.exports = { }); done(); }, - flush: function(done) { + flush: function (done) { // 将 keyHashMap 写入文件 fs.writeFileSync(keyHashFile, JSON.stringify(keyHashMap, null, 2)); - // 遍历每种语言,处理旧字段 - keyList.forEach((lng) => { + keyList.forEach(lng => { const localeFilePath = path.join(localesDir, `${lng}.json`); const oldJsonPath = path.join(oldJsonDir, `${lng}.json`); const langData = existData[lng] || {}; @@ -132,7 +128,7 @@ module.exports = { // 将不存在于 keyHashMap 中的键移动到 oldJson 文件中 Object.keys(langData).forEach(hashKey => { if (!Object.values(keyHashMap).includes(hashKey)) { - oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中 + oldJsonData[hashKey] = langData[hashKey]; // 将旧的 key 移到 oldJson 中 } }); @@ -142,5 +138,5 @@ module.exports = { } }); done(); - } -}; \ No newline at end of file + }, +}; diff --git a/frontend/package.json b/frontend/package.json index 5fa0b6c5..7217f6e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,7 +13,9 @@ "serve:remotes": "lerna run serve --scope=remote --parallel", "dev": "lerna run dev --scope=core --stream", "stop": "kill-port --port 5000", - "scan": "i18next-scanner --config i18next-scanner.config.js" + "scan": "i18next-scanner --config i18next-scanner.config.js", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ." }, "keywords": [], "author": "", @@ -65,8 +67,12 @@ "antd": "^5.19.4", "babel-jest": "^29.7.0", "eslint": "^8.53.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.2", + "eslint-plugin-react": "7.37.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.4", + "eslint-plugin-unused-imports": "^4.1.4", "file-saver": "^2.0.5", "i18next-scanner": "^4.5.0", "jest": "^29.7.0", @@ -78,6 +84,7 @@ "lodash": "^4.17.21", "lodash-es": "^4.17.21", "postcss-nested": "^6.0.1", + "prettier": "^3.1.1", "react-test-renderer": "^18.3.1", "ts-jest": "^29.1.2", "typescript": "^5.2.2", diff --git a/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx b/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx index ab431f20..786ff5bb 100644 --- a/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx +++ b/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx @@ -1,39 +1,37 @@ -import React from "react" -import SwaggerUI from 'swagger-ui-react'; -import 'swagger-ui-react/swagger-ui.css'; +import React from 'react' +import SwaggerUI from 'swagger-ui-react' +import 'swagger-ui-react/swagger-ui.css' -export default function ApiDocument({spec}:{spec?:string|object}) { - - class OperationsLayout extends React.Component { - render() { - const { - getComponent - } = this.props - const Operations = getComponent("operations", true) - - return ( -
- -
- ) - } - } - - // Create the plugin that provides our layout component - const OperationsLayoutPlugin = () => { - return { - components: { - OperationsLayout: OperationsLayout - } - } +export default function ApiDocument({ spec }: { spec?: string | object }) { + class OperationsLayout extends React.Component { + render() { + const { getComponent } = this.props + const Operations = getComponent('operations', true) + + return ( +
+ +
+ ) } - - return( - null}} - layout="OperationsLayout" - plugins={[OperationsLayoutPlugin ]} /> - ) -} \ No newline at end of file + } + + // Create the plugin that provides our layout component + const OperationsLayoutPlugin = () => { + return { + components: { + OperationsLayout: OperationsLayout + } + } + } + + return ( + null }} + layout="OperationsLayout" + plugins={[OperationsLayoutPlugin]} + /> + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx index b46732a7..931bf7f0 100644 --- a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx +++ b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx @@ -1,29 +1,22 @@ -import { - MenuProps, - App, - Button, - ConfigProvider, - Dropdown -} from 'antd'; -import { Outlet, useLocation, useNavigate } from "react-router-dom"; -import Logo from '@common/assets/layout-logo.png'; +import { ProConfigProvider, ProLayout } from '@ant-design/pro-components' import AvatarPic from '@common/assets/default-avatar.png' -import { useCallback, useEffect, useMemo, useState} from "react"; -import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'; -import { PERMISSION_DEFINITION } from '@common/const/permissions.ts'; -import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx'; -import { UserInfoType } from '@common/const/type.ts'; -import { useFetch } from '@common/hooks/http.ts'; -import { ProjectFilled } from '@ant-design/icons'; -import { getNavItem, transformMenuData } from '@common/utils/navigation'; -import { Icon } from '@iconify/react'; -import { $t } from '@common/locales'; -import { ProConfigProvider, ProLayout } from '@ant-design/pro-components'; -import LanguageSetting from './LanguageSetting'; -import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext'; +import Logo from '@common/assets/layout-logo.png' +import { BasicResponse, RESPONSE_TIPS, routerKeyMap, STATUS_CODE } from '@common/const/const.tsx' +import { PERMISSION_DEFINITION } from '@common/const/permissions.ts' +import { UserInfoType } from '@common/const/type.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { transformMenuData } from '@common/utils/navigation' +import { Icon } from '@iconify/react' +import { App, Button, ConfigProvider, Dropdown, MenuProps } from 'antd' +import { useEffect, useMemo, useState } from 'react' +import { Outlet, useLocation, useNavigate } from 'react-router-dom' +import LanguageSetting from './LanguageSetting' -const APP_MODE = import.meta.env.VITE_APP_MODE; -export type MenuItem = Required['items'][number]; +const APP_MODE = import.meta.env.VITE_APP_MODE +export type MenuItem = Required['items'][number] const themeToken = { bgLayout: '#17163E;', @@ -32,92 +25,99 @@ const themeToken = { }, pageContainer: { paddingBlockPageContainerContent: 0, - paddingInlinePageContainerContent: 0, + paddingInlinePageContainerContent: 0 } } +function BasicLayout({ project = 'core' }: { project: string }) { + const navigator = useNavigate() + const location = useLocation() + const currentUrl = location.pathname + const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } = + useGlobalContext() + const [pathname, setPathname] = useState(currentUrl) + const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list' + const [menuItems, setMenuItems] = useState() + const pluginSlotHub = usePluginSlotHub() - function BasicLayout({project = 'core'}:{project:string}){ - const navigator = useNavigate() - const location = useLocation() - const currentUrl = location.pathname - const { state,accessData,checkPermission,accessInit,dispatch,resetAccess,getGlobalAccessData, menuList} = useGlobalContext() - const [pathname, setPathname] = useState(currentUrl); - const mainPage = project === 'core' ?'/service/list':'/serviceHub/list' - const [menuItems, setMenuItems] = useState(); - const pluginSlotHub = usePluginSlotHub() - - useEffect(()=>{ + useEffect(() => { const newMenu = transformMenuData(menuList) - setMenuItems(newMenu); - },[menuList, state.language,accessInit]) + setMenuItems(newMenu) + }, [menuList, state.language, accessInit]) useEffect(() => { if (currentUrl === '/') { navigator(mainPage) } - - }, [currentUrl]); + }, [currentUrl]) const headerMenuData = useMemo(() => { // 判断权限 - const hasAccess = (access: unknown) => checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]); + const hasAccess = (access: unknown) => checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0]) // 过滤菜单项 const filterMenu = (menu: Array<{ [k: string]: unknown }>) => { return [...menu] - .filter(x => x) // 过滤掉空数据 + .filter((x) => x) // 过滤掉空数据 .map((item: any) => { if (item.routes && item.routes.length > 0) { // 递归处理子菜单 - const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes); + const filteredRoutes: Array<{ [k: string]: unknown }> = filterMenu(item.routes) if (filteredRoutes.length === 0) { return false } - return { ...item,routes: filteredRoutes,name:$t(item.name) }; + return { ...item, routes: filteredRoutes, name: $t(item.name) } } // 处理没有 routes 的菜单项 if (item.access) { - return (item.access === 'all' || hasAccess(item.access)) ? {...item,name:$t(item.name)} : null; + return item.access === 'all' || hasAccess(item.access) ? { ...item, name: $t(item.name) } : null } - // 如果没有 access 和 routes,则保留 - return {...item,name:$t(item.name) }; - }) - .filter(x => x); // 过滤掉处理后为 null 的项 - }; - - // 初始过滤操作 - const res = [...(menuItems || [])]!.filter(x => x).map((x: any) => (x.routes ? { ...x,name:$t(x.name), routes: filterMenu(x.routes) } : {...x,name:$t(x.name)})); - // 返回处理后的数据 - return { path: '/', routes: res.map(x=> ({...x, routes: x.routes?.filter(x=> (x.access || x.routes?.length > 0))})).filter(x=> (x.access || x.routes?.length > 0)) }; - }, [accessData, state.language,menuItems]); - - const { message } = App.useApp() - const [userInfo,setUserInfo] = useState() - const {fetchData} = useFetch() - const navigate = useNavigate(); + // 如果没有 access 和 routes,则保留 + return { ...item, name: $t(item.name) } + }) + .filter((x) => x) // 过滤掉处理后为 null 的项 + } + + // 初始过滤操作 + const res = [...(menuItems || [])]! + .filter((x) => x) + .map((x: any) => + x.routes ? { ...x, name: $t(x.name), routes: filterMenu(x.routes) } : { ...x, name: $t(x.name) } + ) + // 返回处理后的数据 + return { + path: '/', + routes: res + .map((x) => ({ ...x, routes: x.routes?.filter((x) => x.access || x.routes?.length > 0) })) + .filter((x) => x.access || x.routes?.length > 0) + } + }, [accessData, state.language, menuItems]) + + const { message } = App.useApp() + const [userInfo, setUserInfo] = useState() + const { fetchData } = useFetch() + const navigate = useNavigate() const getUserInfo = () => { - fetchData>('account/profile', { method: 'GET' }) - .then(response => { - const { code, data, msg } = response - if (code === STATUS_CODE.SUCCESS) { - setUserInfo(data.profile) - dispatch({ type: 'UPDATE_USERDATA', userData: data.profile }) - } else { - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) + fetchData>('account/profile', { method: 'GET' }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setUserInfo(data.profile) + dispatch({ type: 'UPDATE_USERDATA', userData: data.profile }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) } useEffect(() => { getUserInfo() getGlobalAccessData() - }, []); + }, []) const logOut = () => { - fetchData>('account/logout', { method: 'GET' }).then(response => { + fetchData>('account/logout', { method: 'GET' }).then((response) => { const { code, msg } = response if (code === STATUS_CODE.SUCCESS) { dispatch({ type: 'LOGOUT' }) @@ -130,133 +130,162 @@ const themeToken = { }) } - const items: MenuProps['items'] = useMemo(() => [ - userInfo?.type !== 'guest' && { - key: '2', - label: ( - ) - }, - { - key: '3', - label: ( - ) - }, - ].filter(Boolean), [userInfo]); + const items: MenuProps['items'] = useMemo( + () => + [ + userInfo?.type !== 'guest' && { + key: '2', + label: ( + + ) + }, + { + key: '3', + label: ( + + ) + } + ].filter(Boolean), + [userInfo] + ) + const actionRender = useMemo(() => { + return [ + , + , + ...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[]) || []) + ] + }, [pluginSlotHub.getSlot('basicLayoutAfterBtns')]) - const actionRender =useMemo( ()=>{ - return [ - , - , - ...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[] )||[] ) - ] - },[pluginSlotHub.getSlot('basicLayoutAfterBtns') ]) - - - return( -
+ + { + return document.getElementById('test-pro-layout') || document.body + }} > - - { - return document.getElementById('test-pro-layout') || document.body; + { + return ( + - { - return ( - -
{dom} -
-
- ); - }, - }} - actionsRender={(props) => { - if (props.isMobile) return []; - if (typeof window === 'undefined') return []; - return actionRender; - }} - headerTitleRender={() => ( -
- navigator(mainPage)} - /> -
- )} - logo={Logo} - pageTitleRender={()=>$t('APIPark')} - menuFooterRender={(props) => { - if (props?.collapsed) return undefined; - }} - menuItemRender={(item, dom) => ( -
{ - // 同级目录点击无效 - if(item.key && routerKeyMap.get(item.key) && routerKeyMap.get(item.key).length > 0 && routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1){ - return - } - if(item.key === pathname.split('/')[1]){ - return - } - - if(item.path){ - navigator(item.path) - } - setPathname(item.path || ''); - }} - > - {dom} -
- )} - fixSiderbar={true} - layout='mix' - splitMenus={true} - collapsed={false} - collapsedButtonRender={false} - > -
- -
-
-
-
-
- ) + > +
{dom}
+ + ) + } + }} + actionsRender={(props) => { + if (props.isMobile) return [] + if (typeof window === 'undefined') return [] + return actionRender + }} + headerTitleRender={() => ( +
+ navigator(mainPage)} /> +
+ )} + logo={Logo} + pageTitleRender={() => $t('APIPark')} + menuFooterRender={(props) => { + if (props?.collapsed) return undefined + }} + menuItemRender={(item, dom) => ( +
{ + // 同级目录点击无效 + if ( + item.key && + routerKeyMap.get(item.key) && + routerKeyMap.get(item.key).length > 0 && + routerKeyMap.get(item.key)?.indexOf(pathname.split('/')[1]) !== -1 + ) { + return + } + if (item.key === pathname.split('/')[1]) { + return + } + + if (item.path) { + navigator(item.path) + } + setPathname(item.path || '') + }} + > + {dom} +
+ )} + fixSiderbar={true} + layout="mix" + splitMenus={true} + collapsed={false} + collapsedButtonRender={false} + > +
+ +
+ + + + + ) } -export default BasicLayout \ No newline at end of file +export default BasicLayout diff --git a/frontend/packages/common/src/components/aoplatform/Breadcrumb.tsx b/frontend/packages/common/src/components/aoplatform/Breadcrumb.tsx index a375cd44..be44cfe3 100644 --- a/frontend/packages/common/src/components/aoplatform/Breadcrumb.tsx +++ b/frontend/packages/common/src/components/aoplatform/Breadcrumb.tsx @@ -1,15 +1,11 @@ -import { Breadcrumb } from "antd" -import { useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {FC,useEffect} from "react"; - +import { Breadcrumb } from 'antd' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { FC, useEffect } from 'react' const TopBreadcrumb: FC = () => { - const { breadcrumb } = useBreadcrumb() - useEffect(() => { - }, [breadcrumb]); - return ( - - ) + const { breadcrumb } = useBreadcrumb() + useEffect(() => {}, [breadcrumb]) + return } -export default TopBreadcrumb \ No newline at end of file +export default TopBreadcrumb diff --git a/frontend/packages/common/src/components/aoplatform/CodePage.tsx b/frontend/packages/common/src/components/aoplatform/CodePage.tsx index e0061adf..a133d990 100644 --- a/frontend/packages/common/src/components/aoplatform/CodePage.tsx +++ b/frontend/packages/common/src/components/aoplatform/CodePage.tsx @@ -1,34 +1,32 @@ -import { FC } from 'react'; -import { Table } from 'antd'; -import type { ColumnsType } from 'antd/es/table'; -import { $t } from '@common/locales'; +import { FC } from 'react' +import { Table } from 'antd' +import type { ColumnsType } from 'antd/es/table' +import { $t } from '@common/locales' interface DataType { - httpStatusCode: string; - systemStatusCode: string; - description: string; - + httpStatusCode: string + systemStatusCode: string + description: string } const columns: ColumnsType = [ { - title:$t('HTTP 状态码'), + title: $t('HTTP 状态码'), dataIndex: 'httpStatusCode', - key: 'httpStatusCode', + key: 'httpStatusCode' }, { - title:$t('系统状态码'), + title: $t('系统状态码'), dataIndex: 'systemStatusCode', - key: 'systemStatusCode', + key: 'systemStatusCode' }, { title: $t('描述'), dataIndex: 'description', key: 'description', - ellipsis:true - }, - -]; + ellipsis: true + } +] const data: DataType[] = [ // { @@ -44,12 +42,12 @@ const data: DataType[] = [ { httpStatusCode: '413', systemStatusCode: '10003', - description: '请求频率过高', + description: '请求频率过高' }, { httpStatusCode: '403', systemStatusCode: '10004', - description: '请求来源非法,不在白名单中', + description: '请求来源非法,不在白名单中' }, // { // httpStatusCode: '416', @@ -59,7 +57,7 @@ const data: DataType[] = [ { httpStatusCode: '504', systemStatusCode: '10006', - description: '网关超时', + description: '网关超时' }, // { // httpStatusCode: '504', @@ -69,7 +67,7 @@ const data: DataType[] = [ { httpStatusCode: '404', systemStatusCode: '10007', - description: '接口不存在', + description: '接口不存在' }, // { // httpStatusCode: '416', @@ -84,42 +82,43 @@ const data: DataType[] = [ { httpStatusCode: '400', systemStatusCode: '10010', - description: '无法识别请求内容,请检查请求体是否正确', + description: '无法识别请求内容,请检查请求体是否正确' }, { httpStatusCode: '400', systemStatusCode: '10011', - description: '请求头部缺少 Content-Type 字段', + description: '请求头部缺少 Content-Type 字段' }, { httpStatusCode: '400', systemStatusCode: '10011', - description: '请求头部 Content-Type 字段错误', + description: '请求头部 Content-Type 字段错误' }, { httpStatusCode: '400', systemStatusCode: '10014', - description: '批量参数超出单次批量数量的最大限制', + description: '批量参数超出单次批量数量的最大限制' }, { httpStatusCode: '400', systemStatusCode: '10016', - description: '参数缺少内容', + description: '参数缺少内容' }, { httpStatusCode: '500', systemStatusCode: '10017', - description: '参数类型错误', - }, -]; + description: '参数类型错误' + } +] -const CodePage: FC = () => - ( +
({...item, key: index})) || []} + columns={columns} + className="table-border border-b-0 rounded" + dataSource={data?.map((item, index) => ({ ...item, key: index })) || []} pagination={false} - />; + /> +) -export default CodePage; \ No newline at end of file +export default CodePage diff --git a/frontend/packages/common/src/components/aoplatform/CopyAddrList.tsx b/frontend/packages/common/src/components/aoplatform/CopyAddrList.tsx index 910b6534..67e5614a 100644 --- a/frontend/packages/common/src/components/aoplatform/CopyAddrList.tsx +++ b/frontend/packages/common/src/components/aoplatform/CopyAddrList.tsx @@ -1,33 +1,32 @@ - -import { useState,FC } from 'react'; -import { Tooltip, Button } from 'antd'; -import useCopyToClipboard from '@common/hooks/copy'; -import { Icon } from '@iconify/react/dist/iconify.js'; +import { useState, FC } from 'react' +import { Tooltip, Button } from 'antd' +import useCopyToClipboard from '@common/hooks/copy' +import { Icon } from '@iconify/react/dist/iconify.js' type AddressItem = { - expand?: boolean; - [key: string]: unknown; + expand?: boolean + [key: string]: unknown } -type CopyAddrListProps = { - addrItem: AddressItem; - onAddrItemChange?: (addrItem: AddressItem) => void; - keyName: string; +type CopyAddrListProps = { + addrItem: AddressItem + onAddrItemChange?: (addrItem: AddressItem) => void + keyName: string } const CopyAddrList: FC = ({ addrItem, onAddrItemChange, keyName }) => { - const [localAddrItem, setLocalAddrItem] = useState(addrItem); - const { copyToClipboard } = useCopyToClipboard(); + const [localAddrItem, setLocalAddrItem] = useState(addrItem) + const { copyToClipboard } = useCopyToClipboard() const toggleExpand = () => { - const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand }; - setLocalAddrItem(updatedAddrItem); - onAddrItemChange?.(updatedAddrItem); - }; + const updatedAddrItem = { ...localAddrItem, expand: !localAddrItem.expand } + setLocalAddrItem(updatedAddrItem) + onAddrItemChange?.(updatedAddrItem) + } const renderTooltipTitle = () => { // 假设keyName对应的值是一个字符串数组 - const addresses:string[] = localAddrItem[keyName] as string[] + const addresses: string[] = localAddrItem[keyName] as string[] return (
{addresses?.map((addr, index) => ( @@ -36,29 +35,41 @@ const CopyAddrList: FC = ({ addrItem, onAddrItemChange, keyNa
))} - ); - }; - + ) + } const renderAddresses = () => { if (!localAddrItem.expand) { return ( - - 1) ? 'w-5/6' : 'w-full'}`}> + + 1 ? 'w-5/6' : 'w-full'}`} + > {(localAddrItem[keyName] as string[]).join(',')} {(localAddrItem[keyName] as string[]).length === 1 && ( - - } - { showLastStep && } - { extraBtn } - - - } - onClose={onClose} - open={open} - > - {children} - - ) -} \ No newline at end of file + useEffect(() => { + !open && setSubmitLoading(false) + }, [open]) + return ( + <> + + {showOkBtn && ( + + + + )} + {showLastStep && } + {extraBtn} + + + } + onClose={onClose} + open={open} + > + {children} + + + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/DynamicKeyValueInput.tsx b/frontend/packages/common/src/components/aoplatform/DynamicKeyValueInput.tsx index e822a35d..08ea4849 100644 --- a/frontend/packages/common/src/components/aoplatform/DynamicKeyValueInput.tsx +++ b/frontend/packages/common/src/components/aoplatform/DynamicKeyValueInput.tsx @@ -1,91 +1,93 @@ - -import {FC } from 'react'; -import { Input, Space } from 'antd'; -import { Icon } from '@iconify/react/dist/iconify.js'; +import { FC } from 'react' +import { Input, Space } from 'antd' +import { Icon } from '@iconify/react/dist/iconify.js' type KeyValueInput = { - key: string; - value: string; -}; + key: string + value: string +} type DynamicKeyValueInputProps = { - value?: KeyValueInput[]; - onChange?: (newValue: KeyValueInput[]) => void; -}; + value?: KeyValueInput[] + onChange?: (newValue: KeyValueInput[]) => void +} - -export function transferToList (rawData:unknown):Array<{key:string, value:string}> { - const res:Array<{key:string, value:string}> = [] - if(!rawData) - return res - const keys:Array = Object.keys(rawData) +export function transferToList(rawData: unknown): Array<{ key: string; value: string }> { + const res: Array<{ key: string; value: string }> = [] + if (!rawData) return res + const keys: Array = Object.keys(rawData) if (keys?.length > 0) { - for (const key of keys) { - res.push({ key: key, value: rawData[key] }) + for (const key of keys) { + res.push({ key: key, value: rawData[key] }) + } + return [...res, { key: '', value: '' }] } - return [...res, { key: '', value: '' }] -} -return [{ key: '', value: '' }] + return [{ key: '', value: '' }] } -export function transferToMap (rawData:Array<{key:string, value:string}>):{[key:string]:string} { - const res:{[key:string]:string} = {} +export function transferToMap(rawData: Array<{ key: string; value: string }>): { [key: string]: string } { + const res: { [key: string]: string } = {} for (const kv of rawData) { - if (kv.key && kv.value) { res[kv.key] = kv.value } + if (kv.key && kv.value) { + res[kv.key] = kv.value + } } return res } -export const DynamicKeyValueInput: FC = ({value = [{key:'',value:''}],onChange}) => { +export const DynamicKeyValueInput: FC = ({ value = [{ key: '', value: '' }], onChange }) => { // const [keyValuePairs, setKeyValuePairs] = useState([{ key: '', value: '' }]); -// Define a handler for when the inputs change + // Define a handler for when the inputs change const handleInputChange = (index: number, type: 'key' | 'value', newValue: string) => { // Create a new array with the updated value - const newKeyValuePairs = value ? [...value] : []; + const newKeyValuePairs = value ? [...value] : [] if (newKeyValuePairs[index]) { - newKeyValuePairs[index][type] = newValue; + newKeyValuePairs[index][type] = newValue // If we're changing the last input and it's not empty, add a new pair if (index === newKeyValuePairs.length - 1 && (newKeyValuePairs[index].key || newKeyValuePairs[index].value)) { - newKeyValuePairs.push({ key: '', value: '' }); + newKeyValuePairs.push({ key: '', value: '' }) } // Call the onChange handler if it exists - onChange?.(newKeyValuePairs); + onChange?.(newKeyValuePairs) } - }; + } const addNewPair = () => { - const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }]; - onChange?.(newKeyValuePairs); - }; + const newKeyValuePairs = value ? [...value, { key: '', value: '' }] : [{ key: '', value: '' }] + onChange?.(newKeyValuePairs) + } const removePair = (index: number) => { - const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || []; - onChange?.(newKeyValuePairs); - }; + const newKeyValuePairs = value?.filter((_, idx) => idx !== index) || [] + onChange?.(newKeyValuePairs) + } return ( - <> - {value && value?.map((pair, index) => ( - - handleInputChange(index, 'key', e.target.value)} - style={{ width: 162 }} /> - handleInputChange(index, 'value', e.target.value)} - style={{ width: 162 }} /> - {index !== value.length - 1 && ( - <> - removePair(index)} width="14" height="14"/> - - - )} - + <> + {value && + value?.map((pair, index) => ( + + handleInputChange(index, 'key', e.target.value)} + style={{ width: 162 }} + /> + handleInputChange(index, 'value', e.target.value)} + style={{ width: 162 }} + /> + {index !== value.length - 1 && ( + <> + removePair(index)} width="14" height="14" /> + + + )} + ))} - - ); -}; + + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/EditableTable.tsx b/frontend/packages/common/src/components/aoplatform/EditableTable.tsx index f5037bc2..f15887c4 100644 --- a/frontend/packages/common/src/components/aoplatform/EditableTable.tsx +++ b/frontend/packages/common/src/components/aoplatform/EditableTable.tsx @@ -1,116 +1,130 @@ -import { EditableProTable } from "@ant-design/pro-components"; -import { useState, useEffect, useMemo } from "react"; -import { v4 as uuidv4} from 'uuid'; -import { PageProColumns } from "./PageList"; -import TableBtnWithPermission from "./TableBtnWithPermission"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; - +import { EditableProTable } from '@ant-design/pro-components' +import { useState, useEffect, useMemo } from 'react' +import { v4 as uuidv4 } from 'uuid' +import { PageProColumns } from './PageList' +import TableBtnWithPermission from './TableBtnWithPermission' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' + interface EditableTableProps { - configFields: PageProColumns[]; - value?: T[]; // 外部传入的值 - className?: string; - onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 - // tableProps?: TableProps; - disabled?:boolean - extendsId?:string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id + configFields: PageProColumns[] + value?: T[] // 外部传入的值 + className?: string + onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数 + // tableProps?: TableProps; + disabled?: boolean + extendsId?: string[] // 自增一行时,需要和上一行数据一致的字段,比如集群id } const EditableTable = ({ - configFields, - value, // value 现在是外部传入的配置项数组 - onChange, // onChange 现在是当配置项数组变化时的回调函数 - // tableProps, - disabled, - className, - extendsId, - }: EditableTableProps) => { - const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]); - const {state} = useGlobalContext() + configFields, + value, // value 现在是外部传入的配置项数组 + onChange, // onChange 现在是当配置项数组变化时的回调函数 + // tableProps, + disabled, + className, + extendsId +}: EditableTableProps) => { + const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }]) + const { state } = useGlobalContext() - const [editableKeys, setEditableRowKeys] = useState(() => - value?.map((item) => item._id) || ['1234'] - ); + const [editableKeys, setEditableRowKeys] = useState(() => value?.map((item) => item._id) || ['1234']) - useEffect(() => { - setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}]); - }, [value]); + useEffect(() => { + setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }]) + }, [value]) - const getNotEmptyValue = (value:unknown)=>{ - return value - } + const getNotEmptyValue = (value: unknown) => { + return value + } - const translatedColumns = useMemo(()=>configFields.map((x)=>({...x, title:$t(x.title as string)})),[state.language,configFields]) + const translatedColumns = useMemo( + () => configFields.map((x) => ({ ...x, title: $t(x.title as string) })), + [state.language, configFields] + ) - return ( - - className={className} - columns={translatedColumns} - rowKey="_id" - value={configurations as T[]} - size="small" - bordered={true} - recordCreatorProps={false} - editable={ { - type: 'multiple', - editableKeys:disabled ? [] : configurations?.map(x=>x._id), - actionRender: (row, config) => { - return [ - { - const newId = uuidv4(); - setConfigurations((prev)=>{ - const tmpPreData = [...prev]; - const newId = uuidv4() - const lastRecord:{[k:string]:unknown} = tmpPreData[tmpPreData.length - 1]; - const newRecord :{[k:string]:unknown, _id:string}= { _id: newId }; - - // 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值 - if(extendsId && extendsId.length > 0) { - extendsId.forEach(field => { - newRecord[field] = lastRecord[field]; - }); - } - tmpPreData.splice(Number(config.index) + 1, 0,newRecord); - onChange?.(getNotEmptyValue(tmpPreData)); - return tmpPreData}); - setEditableRowKeys((prev)=>([...prev,newId])) - }} - btnTitle="增加"/>, + return ( + + className={className} + columns={translatedColumns} + rowKey="_id" + value={configurations as T[]} + size="small" + bordered={true} + recordCreatorProps={false} + editable={{ + type: 'multiple', + editableKeys: disabled ? [] : configurations?.map((x) => x._id), + actionRender: (row, config) => { + return [ + { + const newId = uuidv4() + setConfigurations((prev) => { + const tmpPreData = [...prev] + const newId = uuidv4() + const lastRecord: { [k: string]: unknown } = tmpPreData[tmpPreData.length - 1] + const newRecord: { [k: string]: unknown; _id: string } = { _id: newId } - (config.index !== configurations.length - 1 )&& { - setConfigurations((prev)=>{ - const tmpPreData = [...prev]; - tmpPreData.splice(Number(config.index), 1); - onChange?.(tmpPreData); - return tmpPreData}); - setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id))) - }}/>,, - ]; - }, - onValuesChange: (record, recordList) => { - if(record._id === recordList[recordList.length - 1]._id){ - const newId = uuidv4() - const lastRecord:{[k:string]:unknown} = recordList[recordList.length - 1]; - const newRecord :{[k:string]:unknown, _id:string}= { _id: newId }; - - // 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值 - if(extendsId && extendsId.length > 0) { - extendsId.forEach(field => { - newRecord[field] = lastRecord[field]; - }); - } - - recordList = ([...recordList, newRecord as T]); - setEditableRowKeys((prev)=>[...prev, newId]) - } - setConfigurations(recordList); - onChange?.(recordList); - }, - onChange: setEditableRowKeys, - }} - /> - ) - } + // 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值 + if (extendsId && extendsId.length > 0) { + extendsId.forEach((field) => { + newRecord[field] = lastRecord[field] + }) + } + tmpPreData.splice(Number(config.index) + 1, 0, newRecord) + onChange?.(getNotEmptyValue(tmpPreData)) + return tmpPreData + }) + setEditableRowKeys((prev) => [...prev, newId]) + }} + btnTitle="增加" + />, -export default EditableTable; \ No newline at end of file + config.index !== configurations.length - 1 && ( + { + setConfigurations((prev) => { + const tmpPreData = [...prev] + tmpPreData.splice(Number(config.index), 1) + onChange?.(tmpPreData) + return tmpPreData + }) + setEditableRowKeys((prev) => prev.filter((x) => x !== config._id)) + }} + /> + ), + , + ] + }, + onValuesChange: (record, recordList) => { + if (record._id === recordList[recordList.length - 1]._id) { + const newId = uuidv4() + const lastRecord: { [k: string]: unknown } = recordList[recordList.length - 1] + const newRecord: { [k: string]: unknown; _id: string } = { _id: newId } + + // 当extendsId的长度大于0时,根据extendsId指定的字段从最后一个record中复制值 + if (extendsId && extendsId.length > 0) { + extendsId.forEach((field) => { + newRecord[field] = lastRecord[field] + }) + } + + recordList = [...recordList, newRecord as T] + setEditableRowKeys((prev) => [...prev, newId]) + } + setConfigurations(recordList) + onChange?.(recordList) + }, + onChange: setEditableRowKeys + }} + /> + ) +} + +export default EditableTable diff --git a/frontend/packages/common/src/components/aoplatform/EditableTableNotAutoGen.tsx b/frontend/packages/common/src/components/aoplatform/EditableTableNotAutoGen.tsx index 9ccac47c..1f6debda 100644 --- a/frontend/packages/common/src/components/aoplatform/EditableTableNotAutoGen.tsx +++ b/frontend/packages/common/src/components/aoplatform/EditableTableNotAutoGen.tsx @@ -1,105 +1,119 @@ -import { EditableFormInstance, EditableProTable } from "@ant-design/pro-components"; -import { useState, useEffect, useMemo, useRef, MutableRefObject } from "react"; -import { v4 as uuidv4} from 'uuid'; -import { PageProColumns } from "./PageList"; -import TableBtnWithPermission from "./TableBtnWithPermission"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { Form } from "antd"; -import { debounce } from "lodash-es"; - +import { EditableFormInstance, EditableProTable } from '@ant-design/pro-components' +import { useState, useEffect, useMemo, useRef, MutableRefObject } from 'react' +import { v4 as uuidv4 } from 'uuid' +import { PageProColumns } from './PageList' +import TableBtnWithPermission from './TableBtnWithPermission' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { Form } from 'antd' +import { debounce } from 'lodash-es' + interface EditableTableProps { - configFields: PageProColumns[]; - value?: T[]; // 外部传入的值 - className?: string; - onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 - // tableProps?: TableProps; - disabled?:boolean - getFromRef?:(form:MutableRefObject | undefined>)=>void + configFields: PageProColumns[] + value?: T[] // 外部传入的值 + className?: string + onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数 + // tableProps?: TableProps; + disabled?: boolean + getFromRef?: (form: MutableRefObject | undefined>) => void } const EditableTableNotAutoGen = ({ - configFields, - value, // value 现在是外部传入的配置项数组 - onChange, // onChange 现在是当配置项数组变化时的回调函数 - // tableProps, - disabled, - className, - getFromRef - }: EditableTableProps) => { - const [configurations, setConfigurations] = useState<(T | {_id:string})[]>(value ||[{_id:'1234'}]); - const {state} = useGlobalContext() - const form =useRef>(); - const [tableForm] = Form.useForm(); - const [editableKeys, setEditableRowKeys] = useState(() => - value?.map((item) => item._id) || ['1234'] - ); + configFields, + value, // value 现在是外部传入的配置项数组 + onChange, // onChange 现在是当配置项数组变化时的回调函数 + // tableProps, + disabled, + className, + getFromRef +}: EditableTableProps) => { + const [configurations, setConfigurations] = useState<(T | { _id: string })[]>(value || [{ _id: '1234' }]) + const { state } = useGlobalContext() + const form = useRef>() + const [tableForm] = Form.useForm() + const [editableKeys, setEditableRowKeys] = useState(() => value?.map((item) => item._id) || ['1234']) - useEffect(()=>{ - getFromRef?.(form) - },[form]) + useEffect(() => { + getFromRef?.(form) + }, [form]) - useEffect(() => { - const newValue = value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || [{_id:uuidv4()}] - setConfigurations(newValue); - setTimeout(()=>validateForm(),1000) - }, [value]); + useEffect(() => { + const newValue = value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || [{ _id: uuidv4() }] + setConfigurations(newValue) + setTimeout(() => validateForm(), 1000) + }, [value]) - const validateForm = async ()=>{ - await tableForm.validateFields(); - } + const validateForm = async () => { + await tableForm.validateFields() + } - const translatedColumns = useMemo(()=>configFields.map((x)=>( - {...x, - title:$t(x.title as string), - formItemProps:{ - ...(x. formItemProps || {}), - rules:[...(x.formItemProps?.rules || []).map((r:Record)=>{ - if(r.message){ - r.message = $t(r.message) - } - return r - })], - }})),[state.language,configFields]) - - const debouncedOnChange = useMemo(() => debounce((value) => { - onChange?.(value); - }, 500), [onChange]); + const translatedColumns = useMemo( + () => + configFields.map((x) => ({ + ...x, + title: $t(x.title as string), + formItemProps: { + ...(x.formItemProps || {}), + rules: [ + ...(x.formItemProps?.rules || []).map((r: Record) => { + if (r.message) { + r.message = $t(r.message) + } + return r + }) + ] + } + })), + [state.language, configFields] + ) - return ( - - className={className} - columns={translatedColumns} - onChange={debouncedOnChange} - controlled={true} - rowKey="_id" - value={configurations as T[]} - size="small" - editableFormRef={form} - bordered={true} - recordCreatorProps={false} - editable={ { - type: 'multiple', - form: tableForm, - // errorType:'default', - editableKeys:disabled ? [] : configurations?.map(x=>x._id), - actionRender: (row, config) => { - return [ - { - setConfigurations((prev)=>{ - const tmpPreData = [...prev]; - tmpPreData.splice(Number(config.index), 1); - onChange?.(tmpPreData); - return tmpPreData}); - setEditableRowKeys((prev)=>(prev.filter(x=>x !== config._id))) - }}/>, - ]; - }, - onChange: setEditableRowKeys - }} - /> - ) - } + const debouncedOnChange = useMemo( + () => + debounce((value) => { + onChange?.(value) + }, 500), + [onChange] + ) -export default EditableTableNotAutoGen; \ No newline at end of file + return ( + + className={className} + columns={translatedColumns} + onChange={debouncedOnChange} + controlled={true} + rowKey="_id" + value={configurations as T[]} + size="small" + editableFormRef={form} + bordered={true} + recordCreatorProps={false} + editable={{ + type: 'multiple', + form: tableForm, + // errorType:'default', + editableKeys: disabled ? [] : configurations?.map((x) => x._id), + actionRender: (row, config) => { + return [ + { + setConfigurations((prev) => { + const tmpPreData = [...prev] + tmpPreData.splice(Number(config.index), 1) + onChange?.(tmpPreData) + return tmpPreData + }) + setEditableRowKeys((prev) => prev.filter((x) => x !== config._id)) + }} + /> + ] + }, + onChange: setEditableRowKeys + }} + /> + ) +} + +export default EditableTableNotAutoGen diff --git a/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx b/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx index 8cc994c9..144277db 100644 --- a/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx +++ b/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx @@ -1,165 +1,195 @@ -import {useEffect, useMemo, useState} from 'react'; -import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd'; -import { v4 as uuidv4 } from 'uuid'; -import { ColumnsType } from 'antd/es/table'; -import WithPermission from './WithPermission'; -import { $t } from '@common/locales'; -import { COLUMNS_TITLE, VALIDATE_MESSAGE } from '@common/const/const'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; -import TableBtnWithPermission from './TableBtnWithPermission'; +import { useEffect, useMemo, useState } from 'react' +import { Button, Modal, Form, Table, FormInstance, TableProps, Divider } from 'antd' +import { v4 as uuidv4 } from 'uuid' +import WithPermission from './WithPermission' +import { $t } from '@common/locales' +import { COLUMNS_TITLE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import TableBtnWithPermission from './TableBtnWithPermission' export interface ConfigField { - title: string; - key: keyof T; - component: React.ReactNode; - renderText?: (value: unknown, record: T) => string; - required?: boolean; - ellipsis?:boolean - unRender?:(form:FormInstance)=>boolean + title: string + key: keyof T + component: React.ReactNode + renderText?: (value: unknown, record: T) => string + required?: boolean + ellipsis?: boolean + unRender?: (form: FormInstance) => boolean } interface EditableTableWithModalProps { - configFields: ConfigField[]; - value?: T[]; // 外部传入的值 - className?: string; - onChange?: (newConfigItems: T[]) => void; // 当配置项变化时,外部传入的回调函数 - tableProps?: TableProps; - disabled?:boolean + configFields: ConfigField[] + value?: T[] // 外部传入的值 + className?: string + onChange?: (newConfigItems: T[]) => void // 当配置项变化时,外部传入的回调函数 + tableProps?: TableProps + disabled?: boolean } const EditableTableWithModal = ({ - configFields, - value, // value 现在是外部传入的配置项数组 - onChange, // onChange 现在是当配置项数组变化时的回调函数 - tableProps, - disabled, - className - }: EditableTableWithModalProps) => { - const [form] = Form.useForm(); - const [isModalVisible, setIsModalVisible] = useState(false); - const [configurations, setConfigurations] = useState(value ||[]); - const [editingConfig, setEditingConfig] = useState(null); - const {state} = useGlobalContext() - const [formsValue, setFormsValue] = useState>() + configFields, + value, // value 现在是外部传入的配置项数组 + onChange, // onChange 现在是当配置项数组变化时的回调函数 + tableProps, + disabled, + className +}: EditableTableWithModalProps) => { + const [form] = Form.useForm() + const [isModalVisible, setIsModalVisible] = useState(false) + const [configurations, setConfigurations] = useState(value || []) + const [editingConfig, setEditingConfig] = useState(null) + const { state } = useGlobalContext() + const [formsValue, setFormsValue] = useState>() - const showModal = (config?: T) => { - if (config) { - form.setFieldsValue(config as Record); - setEditingConfig(config); + const showModal = (config?: T) => { + if (config) { + form.setFieldsValue(config as Record) + setEditingConfig(config) + } else { + form.resetFields() + setEditingConfig(null) + } + setIsModalVisible(true) + } + + const handleCancel = () => { + setIsModalVisible(false) + } + + const handleDelete = (_id: string) => { + const newConfigurations = configurations.filter((config) => config._id !== _id) + setConfigurations(newConfigurations) + onChange?.(newConfigurations) + } + + const handleOk = () => { + form + .validateFields() + .then((values) => { + let newConfigurations = [...configurations] + if (editingConfig && editingConfig._id) { + newConfigurations = newConfigurations?.map((config) => + config._id === editingConfig._id ? { ...config, ...values } : config + ) } else { - form.resetFields(); - setEditingConfig(null); + const newConfig = { _id: uuidv4(), ...values } as Record + newConfigurations.push(newConfig as T) } - setIsModalVisible(true); - }; + setConfigurations(newConfigurations) + onChange?.(newConfigurations) + setIsModalVisible(false) + }) + .catch((info) => { + console.log('Validate Failed:', info) + }) + } - const handleCancel = () => { - setIsModalVisible(false); - }; + useEffect(() => { + setConfigurations(value?.map((x) => (x._id ? x : { ...x, _id: uuidv4() })) || []) + }, [value]) - const handleDelete = (_id: string) => { - const newConfigurations = configurations.filter(config => config._id !== _id); - setConfigurations(newConfigurations); - onChange?.(newConfigurations); - }; - - const handleOk = () => { - form.validateFields() - .then(values => { - let newConfigurations = [...configurations]; - if (editingConfig && editingConfig._id) { - newConfigurations = newConfigurations?.map(config => - config._id === editingConfig._id ? { ...config, ...values } : config - ); - } else { - const newConfig = { _id: uuidv4(), ...values } as Record; - newConfigurations.push(newConfig as T); - } - setConfigurations(newConfigurations); - onChange?.(newConfigurations); - setIsModalVisible(false); - }) - .catch(info => { - console.log('Validate Failed:', info); - }); - }; - - useEffect(() => { - setConfigurations(value?.map((x)=>x._id ? x : {...x,_id:uuidv4()}) || []); - }, [value]); - - const columns = useMemo(()=>[ - ...configFields.map(({ title, key, renderText }) => ({ - title:$t(title), - dataIndex: key as string, - key: key as string, - render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined, - ellipsis:true - })), - ...(disabled ? []:[{ - title: COLUMNS_TITLE.operate, - key: 'action', - btnNums:2, - render: (_: unknown, record: T) => ( + const columns = useMemo( + () => [ + ...configFields.map(({ title, key, renderText }) => ({ + title: $t(title), + dataIndex: key as string, + key: key as string, + render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined, + ellipsis: true + })), + ...(disabled + ? [] + : [ + { + title: COLUMNS_TITLE.operate, + key: 'action', + btnNums: 2, + render: (_: unknown, record: T) => ( <> -
- {showModal(record)}} btnTitle='编辑'/> +
+ { + showModal(record) + }} + btnTitle="编辑" + /> - {handleDelete(record._id || '')}} btnTitle='删除'/> -
+ { + handleDelete(record._id || '') + }} + btnTitle="删除" + /> +
- ), - }] ) - ],[state.language, disabled, configFields]) + ) + } + ]) + ], + [state.language, disabled, configFields] + ) - - const formItems = useMemo(()=>{ - return configFields.map(({ title,key, component, required,unRender }) => { - return ( - unRender && unRender(formsValue) ? null : - - {component} - - ) - }) - } - ,[formsValue]) + const formItems = useMemo(() => { + return configFields.map(({ title, key, component, required, unRender }) => { + return unRender && unRender(formsValue) ? null : ( + + {component} + + ) + }) + }, [formsValue]) + return ( + <> + {!disabled && ( + + )} + {configurations.length > 0 && ( +
+ )} + + +
{ + setFormsValue(form.getFieldsValue()) + }} + // labelCol={{ span: 7 }} + // wrapperCol={{ span: 17}} + autoComplete="off" + > + {formItems} + +
+
+ + ) +} - - return ( - <> - {!disabled && } - {configurations.length > 0 && -
} - -
{ - setFormsValue(form.getFieldsValue()) - })} - // labelCol={{ span: 7 }} - // wrapperCol={{ span: 17}} - autoComplete="off"> - {formItems} -
-
- - ); -}; - -export default EditableTableWithModal; +export default EditableTableWithModal diff --git a/frontend/packages/common/src/components/aoplatform/ErrorBoundary.tsx b/frontend/packages/common/src/components/aoplatform/ErrorBoundary.tsx index ee998b34..b1268457 100644 --- a/frontend/packages/common/src/components/aoplatform/ErrorBoundary.tsx +++ b/frontend/packages/common/src/components/aoplatform/ErrorBoundary.tsx @@ -1,23 +1,23 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect } from 'react' function ErrorBoundary({ children }) { - const [error, setError] = useState(null); - - useEffect(() => { - window.addEventListener("error", (event) => { - setError(event.error); - }); - }, []); - - if (error) { - return ( -
-

An error occurred

-
{error.message}
-
- ); - } - - return children; + const [error, setError] = useState(null) + + useEffect(() => { + window.addEventListener('error', (event) => { + setError(event.error) + }) + }, []) + + if (error) { + return ( +
+

An error occurred

+
{error.message}
+
+ ) } - - export default ErrorBoundary \ No newline at end of file + + return children +} + +export default ErrorBoundary diff --git a/frontend/packages/common/src/components/aoplatform/InsidePage.tsx b/frontend/packages/common/src/components/aoplatform/InsidePage.tsx index 696fcee4..3260e886 100644 --- a/frontend/packages/common/src/components/aoplatform/InsidePage.tsx +++ b/frontend/packages/common/src/components/aoplatform/InsidePage.tsx @@ -1,66 +1,108 @@ - -import { Button, Tag } from "antd" -import {useNavigate} from "react-router-dom"; -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { FC, ReactNode } from "react"; -import { ArrowLeftOutlined, LeftOutlined } from "@ant-design/icons"; -import { $t } from "@common/locales"; - +import { ArrowLeftOutlined } from '@ant-design/icons' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { $t } from '@common/locales' +import { Button, Tag } from 'antd' +import { FC, ReactNode } from 'react' +import { useNavigate } from 'react-router-dom' class InsidePageProps { - showBanner?:boolean = true - pageTitle:string| React.ReactNode = '' - tagList?:Array<{label:string|ReactNode}> = [] - children:React.ReactNode - showBtn?:boolean = false - btnTitle?:string = '' - description?:string | React.ReactNode= '' - onBtnClick?:()=>void - backUrl?:string = '/' - btnAccess?:string - showBorder?:boolean = true - className?:string = '' - contentClassName?:string='' - headerClassName?:string='' - /** 整个页面滚动 */ - scrollPage?:boolean = true - customBtn?:ReactNode + showBanner?: boolean = true + pageTitle: string | React.ReactNode = '' + tagList?: Array<{ label: string | ReactNode }> = [] + children: React.ReactNode + showBtn?: boolean = false + btnTitle?: string = '' + description?: string | React.ReactNode = '' + onBtnClick?: () => void + backUrl?: string = '/' + btnAccess?: string + showBorder?: boolean = true + className?: string = '' + contentClassName?: string = '' + headerClassName?: string = '' + /** 整个页面滚动 */ + scrollPage?: boolean = true + customBtn?: ReactNode } -const InsidePage:FC = ({showBanner=true,pageTitle,tagList,showBtn,btnTitle,btnAccess,description,children,onBtnClick,backUrl,showBorder=true,className='',contentClassName='',headerClassName='',scrollPage=true,customBtn})=>{ - const navigate = useNavigate(); +const InsidePage: FC = ({ + showBanner = true, + pageTitle, + tagList, + showBtn, + btnTitle, + btnAccess, + description, + children, + onBtnClick, + backUrl, + showBorder = true, + className = '', + contentClassName = '', + headerClassName = '', + scrollPage = true, + customBtn +}) => { + const navigate = useNavigate() - const goBack = () => { - navigate(backUrl || '/'); - }; - return ( - //
-
- { showBanner &&
- {!pageTitle && !description && !backUrl &&!customBtn ? <>:
- {backUrl &&
- -
} -
-
-
{pageTitle}
- {tagList && tagList?.length > 0 && tagList?.map((tag)=>{ - return ( {tag.label}) - })} -
- {showBtn && } - {customBtn} -
-
- {description} -
-
} -
} -
{children}
+ const goBack = () => { + navigate(backUrl || '/') + } + return ( +
+ {showBanner && ( +
+ {!pageTitle && !description && !backUrl && !customBtn ? ( + <> + ) : ( +
+ {backUrl && ( +
+ +
+ )} +
+
+
{pageTitle}
+ {tagList && + tagList?.length > 0 && + tagList?.map((tag) => { + return ( + + {tag.label} + + ) + })} +
+ {showBtn && ( + + + + )} + {customBtn} +
+
{description}
+
+ )}
- ) + )} +
+ {children} +
+
+ ) } -export default InsidePage \ No newline at end of file +export default InsidePage diff --git a/frontend/packages/common/src/components/aoplatform/LanguageSetting.tsx b/frontend/packages/common/src/components/aoplatform/LanguageSetting.tsx index 49cf8308..90a20efa 100644 --- a/frontend/packages/common/src/components/aoplatform/LanguageSetting.tsx +++ b/frontend/packages/common/src/components/aoplatform/LanguageSetting.tsx @@ -1,11 +1,11 @@ -import { Dropdown, Row, Col, Button } from 'antd'; -import i18n from '@common/locales'; -import { memo, useEffect, useMemo } from 'react'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; -import { Icon } from '@iconify/react/dist/iconify.js'; +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import i18n from '@common/locales' +import { Icon } from '@iconify/react/dist/iconify.js' +import { Button, Dropdown } from 'antd' +import { memo, useEffect, useMemo } from 'react' const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => { - const { dispatch, state } = useGlobalContext(); + const { dispatch, state } = useGlobalContext() const items = [ { key: 'en-US', @@ -14,7 +14,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => { English ), - title: 'English', + title: 'English' }, { key: 'ja-JP', @@ -23,7 +23,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => { 日本語 ), - title: '日本語', + title: '日本語' }, { key: 'zh-TW', @@ -32,7 +32,7 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => { 繁體中文 ), - title: '繁體中文', + title: '繁體中文' }, { key: 'zh-CN', @@ -41,40 +41,41 @@ const LanguageSetting = ({ mode = 'light' }: { mode?: 'dark' | 'light' }) => { 简体中文 ), - title: '简体中文', - }, - ]; + title: '简体中文' + } + ] - const langLabel = useMemo( - () => items.find(item => item?.key === state.language)?.title, - [state.language] - ); + const langLabel = useMemo(() => items.find((item) => item?.key === state.language)?.title, [state.language]) useEffect(() => { - const savedLang = sessionStorage.getItem('i18nextLng'); - const browserLang = navigator.language || navigator.userLanguage; - if (savedLang) return; + const savedLang = i18n.language || sessionStorage.getItem('i18nextLng') + if (savedLang && state.language !== savedLang) { + dispatch({ type: 'UPDATE_LANGUAGE', language: savedLang }) + } else if (!savedLang) { + const browserLang = navigator.language + const supportedLang = items.find((item) => item.key === browserLang) ? browserLang : 'zh-CN' + dispatch({ type: 'UPDATE_LANGUAGE', language: supportedLang }) + i18n.changeLanguage(supportedLang) + } + }, []) - dispatch({ type: 'UPDATE_LANGUAGE', language: browserLang }); - }, []); return ( { - const { key } = e; - dispatch({ type: 'UPDATE_LANGUAGE', language: key }); - i18n.changeLanguage(key); - }, + onClick: (e) => { + const { key } = e + dispatch({ type: 'UPDATE_LANGUAGE', language: key }) + i18n.changeLanguage(key) + sessionStorage.setItem('i18nextLng', key) + } }} > - ); -}; -export default memo(LanguageSetting); + ) +} +export default memo(LanguageSetting) diff --git a/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx b/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx index ac3b513d..eb3e7958 100644 --- a/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx +++ b/frontend/packages/common/src/components/aoplatform/MemberTransfer.tsx @@ -1,171 +1,199 @@ - -import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from "antd"; -import { DataNode } from "antd/es/tree"; -import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; -import { ApartmentOutlined, LoadingOutlined, UserOutlined } from "@ant-design/icons"; -import { ColumnsType } from "antd/es/table"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +import { TransferProps, TreeDataNode, Tree, Spin, Input, Empty } from 'antd' +import { DataNode } from 'antd/es/tree' +import { Ref, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { ApartmentOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons' +import { ColumnsType } from 'antd/es/table' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' export type TransferTableProps = { - request?:(k?:string)=>Promise<{data:T[],success:boolean}> + request?: (k?: string) => Promise<{ data: T[]; success: boolean }> columns: ColumnsType - primaryKey:string - onSelect:(selectedData:string[])=>void - tableType?:'member'|'api' - disabledData:string[] - searchPlaceholder?:string + primaryKey: string + onSelect: (selectedData: string[]) => void + tableType?: 'member' | 'api' + disabledData: string[] + searchPlaceholder?: string } export type TransferTableHandle = { - selectedRowKeys: () => React.Key[]; + selectedRowKeys: () => React.Key[] } interface TreeTransferProps { - dataSource: TreeDataNode[]; - targetKeys: TransferProps['targetKeys']; - onChange: TransferProps['onChange']; + dataSource: TreeDataNode[] + targetKeys: TransferProps['targetKeys'] + onChange: TransferProps['onChange'] } - + const generateTree = ( treeNodes: TreeDataNode[] = [], - checkedKeys: TreeTransferProps['targetKeys'] = [], + checkedKeys: TreeTransferProps['targetKeys'] = [], filterUnchecked: boolean = false, - disabledData:string[], - filteredItems?:Set + disabledData: string[], + filteredItems?: Set ): TreeDataNode[] => { - const checkedKeysSet = new Set(checkedKeys); + const checkedKeysSet = new Set(checkedKeys) return treeNodes .map(({ children, ...props }) => { - const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems); - const isDisabled = (!filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1) - ? true - : (filterUnchecked ? false : checkedKeysSet.has(props.id as string)); - const hasEnabledChild = childNodes.some(node => !node.disabled); + const childNodes = generateTree(children, checkedKeys, filterUnchecked, disabledData, filteredItems) + const isDisabled = + !filterUnchecked && disabledData && disabledData.indexOf(props.id as string) !== -1 + ? true + : filterUnchecked + ? false + : checkedKeysSet.has(props.id as string) + const hasEnabledChild = childNodes.some((node) => !node.disabled) return { ...props, title: {props.name}, key: props.id, disabled: isDisabled && !hasEnabledChild, - children: childNodes, - }; - }) - .filter(node => { - let res:boolean= true - if(filterUnchecked){ - res =(!disabledData || disabledData.indexOf(node.key as string) === -1) && (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0) ) + children: childNodes } - - if(filterUnchecked && filteredItems &&((filteredItems.size && !filteredItems.has(node.key as string))&& !(node.children && node.children.length > 0) )){ + }) + .filter((node) => { + let res: boolean = true + if (filterUnchecked) { + res = + (!disabledData || disabledData.indexOf(node.key as string) === -1) && + (checkedKeysSet.has(node.key as string) || (node.children && node.children.length > 0)) + } + + if ( + filterUnchecked && + filteredItems && + filteredItems.size && + !filteredItems.has(node.key as string) && + !(node.children && node.children.length > 0) + ) { return false } - return res - } - ) -}; + return res + }) +} +const MemberTransfer = forwardRef< + TransferTableHandle<{ [k: string]: unknown }>, + TransferTableProps<{ [k: string]: unknown }> +>((props: TransferTableProps, ref: Ref>) => { + const { request, columns, primaryKey, onSelect, tableType, disabledData = [], searchPlaceholder } = props + const [targetKeys, setTargetKeys] = useState([]) + const [dataSource, setDataSource] = useState([]) + const parentRef = useRef(null) + const [loading, setLoading] = useState(false) + const { state } = useGlobalContext() + const [expandedKeys, setExpandedKeys] = useState([]) + const [searchWord, setSearchWord] = useState('') + useEffect(() => { + setTargetKeys(disabledData) + }, [disabledData]) - const MemberTransfer= forwardRef, TransferTableProps<{[k:string]:unknown}>>( - (props: TransferTableProps, ref:Ref>) => { - const {request,columns,primaryKey,onSelect,tableType,disabledData = [],searchPlaceholder} = props - const [targetKeys, setTargetKeys] = useState([]); - const [dataSource, setDataSource] = useState([]) - const parentRef = useRef(null); - const [loading, setLoading] = useState(false) - const {state} = useGlobalContext() - const [expandedKeys, setExpandedKeys] = useState([]); - const [searchWord, setSearchWord] = useState('') - useEffect(()=>{ - setTargetKeys(disabledData) - },[disabledData]) + useImperativeHandle(ref, () => ({ + selectedRowKeys: () => targetKeys + })) - useImperativeHandle(ref, () =>({ - selectedRowKeys: () => targetKeys,})) - - const translatedDataSource = useMemo(()=>{ - + const translatedDataSource = useMemo(() => { const loop = (data: DataNode[]): DataNode[] => data?.map((item) => { - const strTitle:string = item.name === '所有成员' ? $t(item.name) as string : item.name as string; - const index = strTitle.indexOf(searchWord); - const beforeStr = strTitle.substring(0, index); - const afterStr = strTitle.slice(index + searchWord.length); - const title = - index > -1 ? ( - - {beforeStr} - {searchWord} - {afterStr} - - ) : ( - {strTitle} - ) - if (item.children) { - return { - ...item, - title, - disableCheckbox:disabledData.indexOf(item.key as string) !== -1, - icon:, - children: loop(item.children as T[]) }; - } - + const strTitle: string = item.name === '所有成员' ? ($t(item.name) as string) : (item.name as string) + const index = strTitle.indexOf(searchWord) + const beforeStr = strTitle.substring(0, index) + const afterStr = strTitle.slice(index + searchWord.length) + const title = + index > -1 ? ( + + {beforeStr} + {searchWord} + {afterStr} + + ) : ( + + {strTitle} + + ) + if (item.children) { return { - ...item, - title, - icon:, - isLeaf:true, - disableCheckbox:disabledData.indexOf(item.key as string) !== -1 - }; - }); - return loop(dataSource); - },[dataSource, state.language, searchWord]) + ...item, + title, + disableCheckbox: disabledData.indexOf(item.key as string) !== -1, + icon: , + children: loop(item.children as T[]) + } + } + return { + ...item, + title, + icon: , + isLeaf: true, + disableCheckbox: disabledData.indexOf(item.key as string) !== -1 + } + }) + return loop(dataSource) + }, [dataSource, state.language, searchWord]) - const getInitExpandKeys = (data:T[], expandKeys:string[] = [])=>{ - data.forEach((item)=>{ - if(item.children?.length){ + const getInitExpandKeys = (data: T[], expandKeys: string[] = []) => { + data.forEach((item) => { + if (item.children?.length) { expandKeys.push(item.key as string) - getInitExpandKeys(item.children,expandKeys) + getInitExpandKeys(item.children, expandKeys) } }) return expandKeys } - - const getDataSource = ()=>{ - setLoading(true) - request && request().then((res)=>{ - const {data,success} = res - setDataSource(success? data : []) - setExpandedKeys(getInitExpandKeys(success? data:[])) - }).finally(()=>{setLoading(false)}) - } + const getDataSource = () => { + setLoading(true) + request && + request() + .then((res) => { + const { data, success } = res + setDataSource(success ? data : []) + setExpandedKeys(getInitExpandKeys(success ? data : [])) + }) + .finally(() => { + setLoading(false) + }) + } useEffect(() => { getDataSource() - }, []); + }, []) - return ( -
- } spinning={loading} className=''> - setSearchWord(e.target.value)} value={searchWord} /> - <>{ translatedDataSource && translatedDataSource.length > 0 ? {setTargetKeys(e); - onSelect(((e as string[])?.filter(x=>disabledData.indexOf(x as string) === -1))||[])}} - onExpand={setExpandedKeys} - treeData={translatedDataSource} - blockNode - showIcon - /> - : } - -
- ); + return ( +
+ } spinning={loading} className=""> + setSearchWord(e.target.value)} + value={searchWord} + /> + <> + {translatedDataSource && translatedDataSource.length > 0 ? ( + { + setTargetKeys(e) + onSelect((e as string[])?.filter((x) => disabledData.indexOf(x as string) === -1) || []) + }} + onExpand={setExpandedKeys} + treeData={translatedDataSource} + blockNode + showIcon + /> + ) : ( + + )} + + +
+ ) }) -export default MemberTransfer; \ No newline at end of file +export default MemberTransfer diff --git a/frontend/packages/common/src/components/aoplatform/NotFound.tsx b/frontend/packages/common/src/components/aoplatform/NotFound.tsx index fc30bb0d..6d8e84a8 100644 --- a/frontend/packages/common/src/components/aoplatform/NotFound.tsx +++ b/frontend/packages/common/src/components/aoplatform/NotFound.tsx @@ -1,22 +1,22 @@ -import React, { useEffect, useState } from 'react'; -import { Result, Skeleton } from 'antd'; +import React, { useEffect, useState } from 'react' +import { Result, Skeleton } from 'antd' const NotFound: React.FC = () => { - const [showPage, setShowPage] = useState(false) + const [showPage, setShowPage] = useState(false) - useEffect(()=>{ - setTimeout(()=>setShowPage(true), 1000) - },[]) + useEffect(() => { + setTimeout(() => setShowPage(true), 1000) + }, []) - return ( -
- { showPage ? : } -
-)} + return ( +
+ {showPage ? ( + + ) : ( + + )} +
+ ) +} -export default NotFound; \ No newline at end of file +export default NotFound diff --git a/frontend/packages/common/src/components/aoplatform/PageList.tsx b/frontend/packages/common/src/components/aoplatform/PageList.tsx index 70fdcb9c..4575904a 100644 --- a/frontend/packages/common/src/components/aoplatform/PageList.tsx +++ b/frontend/packages/common/src/components/aoplatform/PageList.tsx @@ -1,243 +1,370 @@ - -import {Button, Dropdown, Input, MenuProps, TablePaginationConfig} from 'antd'; -import {ChangeEvent, RefAttributes, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import type {ActionType, ParamsType, ProColumns, ProTableProps} from '@ant-design/pro-components'; -import { - DragSortTable, - ProTable, -} from '@ant-design/pro-components'; -import './PageList.module.css' -import {SearchOutlined} from "@ant-design/icons"; +import { SearchOutlined } from '@ant-design/icons' +import type { ActionType, ParamsType, ProColumns, ProTableProps } from '@ant-design/pro-components' +import { DragSortTable, ProTable } from '@ant-design/pro-components' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { withMinimumDelay } from '@common/utils/ux' +import { Button, Dropdown, Input, MenuProps, TablePaginationConfig } from 'antd' +import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface' import { debounce } from 'lodash-es' -import WithPermission from '@common/components/aoplatform/WithPermission'; -import { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface'; -import { useGlobalContext } from '../../contexts/GlobalStateContext'; -import { PERMISSION_DEFINITION } from '@common/const/permissions'; -import { withMinimumDelay } from '@common/utils/ux'; +import { + ChangeEvent, + RefAttributes, + forwardRef, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState +} from 'react' +import { useGlobalContext } from '../../contexts/GlobalStateContext' +import './PageList.module.css' -export type PageProColumns = ProColumns & {btnNums? : number} +export type PageProColumns = ProColumns & { btnNums?: number } -interface PageListProps extends ProTableProps, RefAttributes { - id?:string - columns: PageProColumns[] - request?:(params: (ParamsType & {pageSize?: number | undefined, current?: number | undefined, keyword?: string | undefined}), sorter: unknown, filter: unknown)=>Promise<{data:T[], success:boolean}> - dropMenu?:MenuProps - searchPlaceholder?:string - showPagination?:boolean - primaryKey?:string - addNewBtnTitle?:string - addNewBtnAccess?:string - tableClickAccess?:string - onAddNewBtnClick?:()=>void - beforeSearchNode?:React.ReactNode[] - onSearchWordChange?:(e:ChangeEvent) => void - afterNewBtn?:React.ReactNode[] - dragSortKey?:string - onDragSortEnd?:(beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise - tableTitle?:string - dataSource?:T[] - onRowClick?:(record:T)=>void - showColSetting?:boolean - minVirtualHeight?:number - besidesTableHeight?:number - noTop?:boolean - tableClass?:string - tableTitleClass?:string - addNewBtnWrapperClass?:string - delayLoading?:boolean - noScroll?:boolean +interface PageListProps extends ProTableProps, RefAttributes { + id?: string + columns: PageProColumns[] + request?: ( + params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined }, + sorter: unknown, + filter: unknown + ) => Promise<{ data: T[]; success: boolean }> + dropMenu?: MenuProps + searchPlaceholder?: string + showPagination?: boolean + primaryKey?: string + addNewBtnTitle?: string + addNewBtnAccess?: string + tableClickAccess?: string + onAddNewBtnClick?: () => void + beforeSearchNode?: React.ReactNode[] + onSearchWordChange?: (e: ChangeEvent) => void + afterNewBtn?: React.ReactNode[] + dragSortKey?: string + onDragSortEnd?: (beforeIndex: number, afterIndex: number, newDataSource: T[]) => void | Promise + tableTitle?: string + dataSource?: T[] + onRowClick?: (record: T) => void + showColSetting?: boolean + minVirtualHeight?: number + besidesTableHeight?: number + noTop?: boolean + tableClass?: string + tableTitleClass?: string + addNewBtnWrapperClass?: string + delayLoading?: boolean + noScroll?: boolean /* 前端分页的表格,需要传入该字段以支持后端搜索 */ - manualReloadTable?:()=>void + manualReloadTable?: () => void } - - -const PageList = >(props: React.PropsWithChildren>,ref: React.Ref) => { - const {id,columns,request,dropMenu,searchPlaceholder,showPagination=true,primaryKey='id',addNewBtnTitle,addNewBtnAccess,tableClickAccess,tableClass,onAddNewBtnClick,beforeSearchNode,onSearchWordChange,manualReloadTable,afterNewBtn,dragSortKey,onDragSortEnd,tableTitle,rowSelection,onChange,dataSource,onRowClick,showColSetting=false,minVirtualHeight,noTop,addNewBtnWrapperClass = '',tableTitleClass,delayLoading = true,besidesTableHeight, noScroll} = props - const parentRef = useRef(null); - const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight); - const [tableWidth, setTableWidth] = useState(undefined); - const actionRef = useRef(); - const [allowTableClick,setAllowTableClick] = useState(false) - const {accessData,checkPermission,accessInit,state} = useGlobalContext() +const PageList = >( + props: React.PropsWithChildren>, + ref: React.Ref +) => { + const { + id, + columns, + request, + dropMenu, + searchPlaceholder, + showPagination = true, + primaryKey = 'id', + addNewBtnTitle, + addNewBtnAccess, + tableClickAccess, + tableClass, + onAddNewBtnClick, + beforeSearchNode, + onSearchWordChange, + manualReloadTable, + afterNewBtn, + dragSortKey, + onDragSortEnd, + tableTitle, + rowSelection, + onChange, + dataSource, + onRowClick, + showColSetting = false, + minVirtualHeight, + noTop, + addNewBtnWrapperClass = '', + tableTitleClass, + delayLoading = true, + besidesTableHeight, + noScroll + } = props + const parentRef = useRef(null) + const [tableHeight, setTableHeight] = useState(minVirtualHeight || window.innerHeight) + const [tableWidth, setTableWidth] = useState(undefined) + const actionRef = useRef() + const [allowTableClick, setAllowTableClick] = useState(false) + const { accessData, checkPermission, accessInit, state } = useGlobalContext() const [minTableWidth, setMinTableWidth] = useState(0) - // 使用useImperativeHandle来自定义暴露给父组件的实例值 - useImperativeHandle(ref, () => actionRef.current!); + useImperativeHandle(ref, () => actionRef.current!) - useEffect(()=>{ + useEffect(() => { actionRef?.current?.reload?.() - },[state.language]) - - const lastAccess = useMemo(()=>{ - if(!tableClickAccess) return true - return checkPermission(tableClickAccess as keyof typeof PERMISSION_DEFINITION[0]) -},[allowTableClick, accessData,accessInit]) + }, [state.language]) - useEffect(()=>{ - tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true) - },[accessData]) - - const resizeObserverRef = useRef(null); + const lastAccess = useMemo(() => { + if (!tableClickAccess) return true + return checkPermission(tableClickAccess as keyof (typeof PERMISSION_DEFINITION)[0]) + }, [allowTableClick, accessData, accessInit]) + + useEffect(() => { + tableClickAccess ? setAllowTableClick(lastAccess) : setAllowTableClick(true) + }, [accessData]) + + const resizeObserverRef = useRef(null) useEffect(() => { const handleResize = () => { if (parentRef.current && !noScroll) { - const res = parentRef.current.getBoundingClientRect(); - const height = res.height - ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) +( besidesTableHeight ?? 0) + 1); // 减去顶部按钮、底部分页、表头高度 - setTableWidth(minTableWidth - 5> res.width ? minTableWidth : undefined); - height && setTableHeight(minVirtualHeight === undefined ? height : (height > minVirtualHeight ? height : minVirtualHeight)); + const res = parentRef.current.getBoundingClientRect() + const height = + res.height - + ((noTop ? 0 : 59) + 54 + (showPagination && !dragSortKey ? 52 : 0) + (besidesTableHeight ?? 0) + 1) // 减去顶部按钮、底部分页、表头高度 + setTableWidth(minTableWidth - 5 > res.width ? minTableWidth : undefined) + height && + setTableHeight( + minVirtualHeight === undefined ? height : height > minVirtualHeight ? height : minVirtualHeight + ) } - }; + } - const debouncedHandleResize = debounce(handleResize, 200); + const debouncedHandleResize = debounce(handleResize, 200) if (!resizeObserverRef.current && !noScroll) { // 创建一个 ResizeObserver 来监听高度变化,只创建一次 - resizeObserverRef.current = new ResizeObserver(debouncedHandleResize); + resizeObserverRef.current = new ResizeObserver(debouncedHandleResize) // 开始监听 if (parentRef.current && !minVirtualHeight) { - resizeObserverRef.current.observe(parentRef.current); + resizeObserverRef.current.observe(parentRef.current) } } // 在 minTableWidth 变化时手动触发 handleResize - handleResize(); + handleResize() // 清理函数 return () => { if (resizeObserverRef.current) { - resizeObserverRef.current.disconnect(); - resizeObserverRef.current = null; + resizeObserverRef.current.disconnect() + resizeObserverRef.current = null } - }; - }, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]); // 将相关依赖项作为 useEffect 的依赖项 + } + }, [minTableWidth, parentRef, noTop, showPagination, dragSortKey, minVirtualHeight]) // 将相关依赖项作为 useEffect 的依赖项 - - - - const newColumns = useMemo(()=>{ - let width:number = 0 - const res = columns?.map( - (x, index)=>{ - const sorter = localStorage.getItem(`${id}_sorter`) - const filters = localStorage.getItem(`${id}_filters`) - x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email') - if(sorter && x.sorter){ - const sorterObj = JSON.parse(sorter) - const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex - x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined - // x.showSorterTooltip = {target:'sorter-icon'} - } - if(filters && x.filters){ - const filtersObj = JSON.parse(filters) - const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(','):x.dataIndex - x.defaultFilteredValue = filtersObj?.[xName as string] - } - if((index === columns.length -1 || x.key === 'option') && x.btnNums){ - const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21 - x.width = Math.max(optionWidth, 54) - } - width += Number(x.width ?? ((x.filters || x.sorter) ? 120 : 100)) - return x}) - setMinTableWidth(width) + const newColumns = useMemo(() => { + let width: number = 0 + const res = columns?.map((x, index) => { + const sorter = localStorage.getItem(`${id}_sorter`) + const filters = localStorage.getItem(`${id}_filters`) + x.copyable = x.copyable ?? (index === 0 || x.dataIndex === 'id' || x.dataIndex === 'email') + if (sorter && x.sorter) { + const sorterObj = JSON.parse(sorter) + const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex + x.defaultSortOrder = sorterObj?.columnKey === xName ? sorterObj?.order : undefined + // x.showSorterTooltip = {target:'sorter-icon'} + } + if (filters && x.filters) { + const filtersObj = JSON.parse(filters) + const xName = Array.isArray(x.dataIndex) ? x.dataIndex.join(',') : x.dataIndex + x.defaultFilteredValue = filtersObj?.[xName as string] + } + if ((index === columns.length - 1 || x.key === 'option') && x.btnNums) { + const optionWidth = 24 + 18 * x.btnNums + (x.btnNums - 1) * 21 + x.width = Math.max(optionWidth, 54) + } + width += Number(x.width ?? (x.filters || x.sorter ? 120 : 100)) + return x + }) + setMinTableWidth(width) return res - },[columns]) + }, [columns]) - const headerTitle = ()=>{ + const headerTitle = () => { return ( - <>{ - tableTitle ? {tableTitle} : ( - addNewBtnTitle ? : undefined - ) - - } - {afterNewBtn ? afterNewBtn as React.ReactNode[] :undefined} - - ) + <> + {tableTitle ? ( + {tableTitle} + ) : addNewBtnTitle ? ( + + + + ) : undefined} + {afterNewBtn ? (afterNewBtn as React.ReactNode[]) : undefined} + + ) } - const requestWithDelay = (params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined;}, sort: unknown, filter: unknown) => { - return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false? 0 : undefined); - }; + const getTableActions = () => { + return [ + ...(beforeSearchNode ? [beforeSearchNode] : []), + ...(searchPlaceholder + ? [ + debounce(onSearchWordChange, 100)(e) : undefined} + onPressEnter={() => { + if (manualReloadTable) { + manualReloadTable() + return + } + if (actionRef.current) { + actionRef.current.reset?.() + actionRef.current.reload?.() + } + }} + allowClear + placeholder={searchPlaceholder} + prefix={ + { + if (actionRef.current) { + actionRef.current.reset?.() + actionRef.current.reload?.() + } + }} + /> + } + /> + ] + : []) + ] + } + + const requestWithDelay = ( + params: ParamsType & { pageSize?: number | undefined; current?: number | undefined; keyword?: string | undefined }, + sort: unknown, + filter: unknown + ) => { + return withMinimumDelay(() => request!(params, sort, filter), delayLoading === false ? 0 : undefined) + } return ( -
- {dragSortKey? +
+ {dragSortKey ? ( + actionRef={actionRef} columns={newColumns} rowKey={primaryKey} search={false} - pagination={false} + pagination={ + showPagination + ? { + showSizeChanger: true, + showQuickJumper: true, + size: 'default' + } + : false + } request={request} dragSortKey={dragSortKey} onDragSortEnd={onDragSortEnd} - scroll={noScroll ? undefined :{ y: tableHeight }} + scroll={noScroll ? undefined : { y: tableHeight }} options={{ reload: false, density: false, - setting: false, + setting: false }} - headerTitle={ - headerTitle() - } - /> : + toolbar={{ + actions: getTableActions() + }} + headerTitle={headerTitle()} + /> + ) : ( + actionRef={actionRef} columns={newColumns} virtual - scroll={noScroll ? undefined : {x:tableWidth,y: tableHeight }} + scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }} size="middle" rowSelection={rowSelection} tableAlertRender={false} tableAlertOptionRender={false} request={request ? requestWithDelay : undefined} toolBarRender={() => [ - dropMenu ? ( - - ):null, + dropMenu ? ( + + + + ) : null ]} toolbar={{ - actions:[...[beforeSearchNode],...[searchPlaceholder? debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>manualReloadTable ? manualReloadTable():actionRef.current?.reload?.()} allowClear placeholder={searchPlaceholder} prefix={{actionRef.current?.reload?.()}}/>}/>:null]], + actions: getTableActions() }} options={{ reload: false, density: false, - setting: showColSetting ? { - draggable:false, - showListItemOption:false - } :false, + setting: showColSetting + ? { + draggable: false, + showListItemOption: false + } + : false }} showSorterTooltip={false} - columnsState={{persistenceType:'localStorage',persistenceKey:id}} - pagination={showPagination ? { - showSizeChanger: true, - showQuickJumper: true, - size:'default' - }:false} + columnsState={{ persistenceType: 'localStorage', persistenceKey: id }} + pagination={ + showPagination + ? { + showSizeChanger: true, + showQuickJumper: true, + size: 'default' + } + : false + } + onChange={( + pagination: TablePaginationConfig, + filters: Record, + sorter: SorterResult | SorterResult[], + extra: TableCurrentDataSource + ) => { + localStorage.setItem(`${id}_filters`, JSON.stringify(filters)) + !Array.isArray(sorter) && + localStorage.setItem( + `${id}_sorter`, + JSON.stringify({ columnKey: sorter?.columnKey, order: sorter?.order }) + ) + onChange?.(pagination, filters, sorter, extra) + }} rowKey={primaryKey} - onChange={(pagination: TablePaginationConfig, filters: Record, sorter: SorterResult | SorterResult[],extra:TableCurrentDataSource) =>{ - localStorage.setItem(`${id}_filters`,JSON.stringify(filters)) - !Array.isArray(sorter) && localStorage.setItem(`${id}_sorter`,JSON.stringify({columnKey:sorter?.columnKey, order: sorter?.order})) - onChange?.(pagination,filters,sorter,extra)}} dataSource={dataSource} search={false} - headerTitle={ - headerTitle() + headerTitle={headerTitle()} + onRow={ + onRowClick && allowTableClick + ? (record) => ({ + onClick: () => { + onRowClick(record) + } + }) + : undefined } - onRow={onRowClick && allowTableClick ? (record) => ({ - onClick: () => { - onRowClick(record); - } - }):undefined} - rowClassName={()=>onRowClick && allowTableClick ?"cursor-pointer":''} - />} + rowClassName={() => (onRowClick && allowTableClick ? 'cursor-pointer' : '')} + /> + )}
- ); -}; + ) +} -export default forwardRef(PageList) as >(props: React.PropsWithChildren> & { ref?: React.Ref }) => ReturnType; \ No newline at end of file +export default forwardRef(PageList) as >( + props: React.PropsWithChildren> & { ref?: React.Ref } +) => ReturnType diff --git a/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx b/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx index e058d11f..ccb8a99a 100644 --- a/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx +++ b/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx @@ -1,95 +1,100 @@ -import {App, Form, Input, Row, Table} from "antd"; -import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react"; -import {useFetch} from "@common/hooks/http.ts"; -import {BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { PolicyPublishModalHandle, PolicyPublishModalProps } from "@common/const/type"; +import { App, Form, Input, Row, Table } from 'antd' +import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react' +import { useFetch } from '@common/hooks/http.ts' +import { BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { PolicyPublishModalHandle, PolicyPublishModalProps } from '@common/const/type' +export const PolicyPublishModalContent = forwardRef((props, ref) => { + const { message } = App.useApp() + const { data } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() + const { state } = useGlobalContext() -export const PolicyPublishModalContent = forwardRef((props, ref) => { - const { message } = App.useApp() - const { data} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const {state} = useGlobalContext() - - const publish:()=>Promise> = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - const body = {...value, source:data.source} - fetchData>('strategy/global/data-masking/publish',{method: 'POST',eoBody:body,eoTransformKeys:['versionName']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(response) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - useImperativeHandle(ref, ()=>({ - publish, + const publish: () => Promise> = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + const body = { ...value, source: data.source } + fetchData>('strategy/global/data-masking/publish', { + method: 'POST', + eoBody: body, + eoTransformKeys: ['versionName'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(response) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) }) - ) + .catch((errorInfo) => reject(errorInfo)) + }) + } - useEffect(()=>{ - form.setFieldsValue(data) - },[data]) + useImperativeHandle(ref, () => ({ + publish + })) - const translatedPolicyColumns = useMemo(()=>PolicyPublishColumns.map((x)=>({ - ...x, - title: typeof x.title === 'string' ? $t(x.title) : x.title, -})),[state.language]) + useEffect(() => { + form.setFieldsValue(data) + }, [data]) + const translatedPolicyColumns = useMemo( + () => + PolicyPublishColumns.map((x) => ({ + ...x, + title: typeof x.title === 'string' ? $t(x.title) : x.title + })), + [state.language] + ) - return ( - <> -
+ return ( + <> + + + + + - - - - - - - - {$t('策略列表')}: - -
- {!data?.isPublish&& data?.unpublishMsg&&

{data.unpublishMsg}

} - - - - - ) -}) \ No newline at end of file + + + + + {$t('策略列表')}: + + +
+ {!data?.isPublish && data?.unpublishMsg &&

{data.unpublishMsg}

} + + + + + ) +}) diff --git a/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx b/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx index 3e1393da..6272fd42 100644 --- a/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx +++ b/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx @@ -1,257 +1,402 @@ -import {App, Col, Form, Input, Row, Table, Tooltip} from "antd"; -import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react"; -import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR} from "@common/const/const.tsx"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx"; -import { $t } from "@common/locales"; -import { ApprovalPolicyColumns, ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { LoadingOutlined } from "@ant-design/icons"; -import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline"; +import { App, Col, Form, Input, Row, Table, Tooltip } from 'antd' +import { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react' +import { + PublishApprovalInfoType, + PublishApprovalModalHandle, + PublishApprovalModalProps, + PublishVersionTableListItem +} from '@common/const/approval/type.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { + BasicResponse, + FORM_ERROR_TIPS, + PLACEHOLDER, + RESPONSE_TIPS, + STATUS_CODE, + STATUS_COLOR +} from '@common/const/const.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '@core/const/system/const.tsx' +import { $t } from '@common/locales' +import { + ApprovalPolicyColumns, + ApprovalRouteColumns, + ApprovalStatusColorClass, + ApprovalUpstreamColumns, + ChangeTypeEnum +} from '@common/const/approval/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { LoadingOutlined } from '@ant-design/icons' +import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline' - -export const PublishApprovalModalContent = forwardRef((props, ref) => { +export const PublishApprovalModalContent = forwardRef( + (props, ref) => { const { message } = App.useApp() - const { type,data,insidePage = false, serviceType = 'rest', serviceId, teamId} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const {state} = useGlobalContext() + const { type, data, insidePage = false, serviceType = 'rest', serviceId, teamId } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() + const { state } = useGlobalContext() - const save:(operate:'pass'|'refuse')=>Promise = (operate)=>{ - if(type === 'view'){ + const save: (operate: 'pass' | 'refuse') => Promise = (operate) => { + if (type === 'view') { + return Promise.resolve(true) + } + return form + .validateFields() + .then((value) => { + if (operate === 'refuse' && form.getFieldValue('opinion') === '') { + form.setFields([ + { + name: 'opinion', + errors: [$t(FORM_ERROR_TIPS.refuseOpinion)] + } + ]) + form.scrollToField('opinion') + return Promise.reject($t(RESPONSE_TIPS.refuseOpinion)) + } + return fetchData>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`, { + method: 'PUT', + eoBody: { comments: value.opinion }, + eoParams: { id: data!.id, project: serviceId }, + eoTransformKeys: ['versionRemark'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) return Promise.resolve(true) - } - return form.validateFields().then((value)=>{ - if(operate === 'refuse' && form.getFieldValue('opinion') === '' ){ - form.setFields([{ - name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)] - }]) - form.scrollToField('opinion') - return Promise.reject($t(RESPONSE_TIPS.refuseOpinion)) - } - return fetchData>(`service/publish/${operate === 'pass' ? 'accept' : 'refuse'}`,{method: 'PUT',eoBody:({comments:value.opinion}), eoParams:{id:data!.id, project:serviceId},eoTransformKeys:['versionRemark']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - return Promise.resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> Promise.reject(errorInfo)) - }).catch((err)=> {form.scrollToField(err.errorFields[0].name[0]); return Promise.reject(err)}) - } - - const publish:(notSave?:boolean)=>Promise> = (notSave)=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - const body = {...value, ...(type === 'publish'&&{release:data.id})} - fetchData>( - notSave ? 'service/publish/apply' : 'service/publish/release/do',{method: 'POST',eoBody:body, eoParams:{service:serviceId, team:teamId},eoTransformKeys:['versionRemark']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(response) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const online:()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then(()=>{ - fetchData>('service/publish/execute',{method: 'PUT', eoParams:{project:serviceId,id:(data as PublishVersionTableListItem).flowId},eoTransformKeys:['versionRemark']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - useImperativeHandle(ref, ()=>({ - save, - publish, - online + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => Promise.reject(errorInfo)) }) + .catch((err) => { + form.scrollToField(err.errorFields[0].name[0]) + return Promise.reject(err) + }) + } + + const publish: (notSave?: boolean) => Promise> = (notSave) => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + const body = { ...value, ...(type === 'publish' && { release: data.id }) } + fetchData>(notSave ? 'service/publish/apply' : 'service/publish/release/do', { + method: 'POST', + eoBody: body, + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['versionRemark'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(response) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const online: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then(() => { + fetchData>('service/publish/execute', { + method: 'PUT', + eoParams: { project: serviceId, id: (data as PublishVersionTableListItem).flowId }, + eoTransformKeys: ['versionRemark'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + useImperativeHandle(ref, () => ({ + save, + publish, + online + })) + + useEffect(() => { + form.setFieldsValue({ opinion: '', ...data }) + }, []) + + const translatedUpstreamColumns = useMemo( + () => + ApprovalUpstreamColumns.map((x) => ({ + ...x, + ...(x.dataIndex === 'type' + ? { + valueEnum: { + static: { + text: $t('静态上游') + } + } + } + : {}), + ...(x.dataIndex === 'change' + ? { + render: (_, entity) => ( + + + {$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')} + {entity.change === 'error' + ? $t('该 API 缺失(0)(1)(2)请先补充', [ + entity.proxyStatus == 1 && $t('转发信息,'), + entity.docStatus == 1 && $t('文档信息,'), + entity.upstreamStatus == 1 && $t('上游信息,') + ]) + : ''} + + + ) + } + : {}), + title: typeof x.title === 'string' ? $t(x.title) : x.title + })), + [state.language] ) - useEffect(()=>{ - form.setFieldsValue({ opinion:'',...data}) - },[]) - - const translatedUpstreamColumns = useMemo(()=>ApprovalUpstreamColumns.map((x)=>({ - ...x, - ...(x.dataIndex === 'type' ? {valueEnum:{ - 'static':{ - text:$t('静态上游') - } - }}:{}), - ...(x.dataIndex === 'change' ? { - render:(_,entity)=>( - - {$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')} - {entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''} - ) - }:{}), - title: typeof x.title === 'string' ? $t(x.title) : x.title, - })),[state.language]) - - - const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.filter(x=> serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods').map((x)=>({ - ...x, - ...(x.dataIndex === 'change' ? { - render:(_,entity)=>( - - - {$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')} - {entity.change === 'error' ?$t('该 API 缺失(0)(1)(2)请先补充',[entity.proxyStatus == 1 && $t('转发信息,'),entity.docStatus == 1 && $t('文档信息,'),entity.upstreamStatus == 1 && $t('上游信息,')]):''} - - ) - }:{} - ), - title: typeof x.title === 'string' ? $t(x.title) : x.title, -})),[state.language, serviceType]) - - const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{ - if(x.dataIndex === 'status'){ - return {...x,title:$t(x.title), - render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{ - switch(entity.status){ - case 'done': - return {$t('成功')} - case 'error': - return {$t('失败')} {entity.error} - default: - return + const translatedRouteColumns = useMemo( + () => + ApprovalRouteColumns.filter((x) => + serviceType === 'rest' ? x.dataIndex !== 'name' : x.dataIndex !== 'methods' + ).map((x) => ({ + ...x, + ...(x.dataIndex === 'change' + ? { + render: (_, entity) => ( + + + {$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')} + {entity.change === 'error' + ? $t('该 API 缺失(0)(1)(2)请先补充', [ + entity.proxyStatus == 1 && $t('转发信息,'), + entity.docStatus == 1 && $t('文档信息,'), + entity.upstreamStatus == 1 && $t('上游信息,') + ]) + : ''} + + + ) + } + : {}), + title: typeof x.title === 'string' ? $t(x.title) : x.title + })), + [state.language, serviceType] + ) - - const translatedPolicyColumns = useMemo(()=>ApprovalPolicyColumns.map((x)=>{ - return { + const translatedPublishColumns = useMemo( + () => + SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x) => { + if (x.dataIndex === 'status') { + return { + ...x, + title: $t(x.title), + render: (_: unknown, entity: SystemInsidePublishOnlineItems) => { + switch (entity.status) { + case 'done': + return ( + {$t('成功')} + ) + case 'error': + return ( + + + {$t('失败')} {entity.error} + + + ) + default: + return + } + } + } + } + }), + [state.language] + ) + + const translatedPolicyColumns = useMemo( + () => + ApprovalPolicyColumns.map((x) => { + return { ...x, title: typeof x.title === 'string' ? $t(x.title) : x.title, - ...(x.dataIndex === 'status' ? { - render:(_,entity)=> ( - - {$t(ChangeTypeEnum[entity.change as (keyof typeof ChangeTypeEnum)] || '-')} + ...(x.dataIndex === 'status' + ? { + render: (_, entity) => ( + + {$t(ChangeTypeEnum[entity.change as keyof typeof ChangeTypeEnum] || '-')} - ) - }:{}) - } - }),[state.language]) - + ) + } + : {}) + } + }), + [state.language] + ) return ( - <> - {!insidePage && <> + <> + {!insidePage && ( + <> - {$t('申请系统')}: - {(data as PublishApprovalInfoType).project || '-'} + + {$t('申请系统')}: + + {(data as PublishApprovalInfoType).project || '-'} - {$t('所属团队')}: - {(data as PublishApprovalInfoType).team || '-'} + + {$t('所属团队')}: + + {(data as PublishApprovalInfoType).team || '-'} - {$t('申请人')}: - {(data as PublishApprovalInfoType).applier || '-'} + + {$t('申请人')}: + + {(data as PublishApprovalInfoType).applier || '-'} - {$t('申请时间')}: - {(data as PublishApprovalInfoType).applyTime || '-'} + + {$t('申请时间')}: + + {(data as PublishApprovalInfoType).applyTime || '-'} - } -
+ + )} + + + {insidePage && ( + <> + + + - { - insidePage && - <> - - - - - - - - - } - {$t('路由列表')}: - -
- { - serviceType === 'rest' && <> - {$t('上游列表')}: - -
- - } - {$t('策略列表')}: - -
- {/* + + + + )} + + {$t('路由列表')}: + + +
+ + {serviceType === 'rest' && ( + <> + + {$t('上游列表')}: + + +
+ + + )} + + {$t('策略列表')}: + + +
+ + {/* */} -{/* + {/* {type !== 'add' && type !== 'publish' && } */} - - {['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <> - {$t('上线情况')}: - -
- } - - - ) -}) \ No newline at end of file + + {['error', 'done'].indexOf(data.status) !== -1 && + data.clusterPublishStatus && + data.clusterPublishStatus.length > 0 && ( + <> + + {$t('上线情况')}: + + +
+ + + )} + + + + ) + } +) diff --git a/frontend/packages/common/src/components/aoplatform/ScrollableSection.tsx b/frontend/packages/common/src/components/aoplatform/ScrollableSection.tsx index 32b3c944..93e2fc10 100644 --- a/frontend/packages/common/src/components/aoplatform/ScrollableSection.tsx +++ b/frontend/packages/common/src/components/aoplatform/ScrollableSection.tsx @@ -1,64 +1,64 @@ - -import {FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react'; +import { FC, useRef, useEffect, Children, cloneElement, isValidElement } from 'react' interface ScrollableSectionProps { - children: React.ReactNode; + children: React.ReactNode } const ScrollableSection: FC = ({ children }) => { - const scrollAreaRef = useRef(null); + const scrollAreaRef = useRef(null) useEffect(() => { const handleScroll = () => { if (scrollAreaRef.current) { - const scrollTop = scrollAreaRef.current.scrollTop; - const scrollHeight = scrollAreaRef.current.scrollHeight; - const clientHeight = scrollAreaRef.current.clientHeight; + const scrollTop = scrollAreaRef.current.scrollTop + const scrollHeight = scrollAreaRef.current.scrollHeight + const clientHeight = scrollAreaRef.current.clientHeight - // 如果滚动到顶部,.content-before 应该显示阴影 - const showTopShadow = scrollTop > 0; - // 如果滚动到底部,.content-after 应该显示阴影 - const showBottomShadow = scrollHeight - scrollTop < clientHeight; - // 这里我们不直接更新状态,而是通过ref来设置样式 - if (showTopShadow && !showBottomShadow) { - setElementShadow('.content-before', true); - setElementShadow('.content-after', false); - } else if (!showTopShadow && showBottomShadow) { - setElementShadow('.content-before', false); - setElementShadow('.content-after', true); - } else { - setElementShadow('.content-before', false); - setElementShadow('.content-after', false); - } + // 如果滚动到顶部,.content-before 应该显示阴影 + const showTopShadow = scrollTop > 0 + // 如果滚动到底部,.content-after 应该显示阴影 + const showBottomShadow = scrollHeight - scrollTop < clientHeight + // 这里我们不直接更新状态,而是通过ref来设置样式 + if (showTopShadow && !showBottomShadow) { + setElementShadow('.content-before', true) + setElementShadow('.content-after', false) + } else if (!showTopShadow && showBottomShadow) { + setElementShadow('.content-before', false) + setElementShadow('.content-after', true) + } else { + setElementShadow('.content-before', false) + setElementShadow('.content-after', false) + } } - }; + } - scrollAreaRef.current?.addEventListener('scroll', handleScroll); + scrollAreaRef.current?.addEventListener('scroll', handleScroll) return () => { - scrollAreaRef.current?.removeEventListener('scroll', handleScroll); - }; - }, []); + scrollAreaRef.current?.removeEventListener('scroll', handleScroll) + } + }, []) const setElementShadow = (elementSelector: string, showShadow: boolean) => { - const element = document.querySelector(elementSelector); + const element = document.querySelector(elementSelector) if (element) { - element.style.boxShadow = showShadow ? ( elementSelector === '.content-before' ? '0 2px 2px #0000000d':'0 -2px 2px -2px var(--border-color)') : 'none'; + element.style.boxShadow = showShadow + ? elementSelector === '.content-before' + ? '0 2px 2px #0000000d' + : '0 -2px 2px -2px var(--border-color)' + : 'none' } } const childrenWithRef = Children.toArray(children).map((child) => { if (isValidElement(child) && child.props.className && child.props.className.includes('scroll-area')) { // 将 ref 附加到具有 'scroll-area' 类名的子元素 - return cloneElement(child, { ref: scrollAreaRef }); + return cloneElement(child, { ref: scrollAreaRef }) } - return child; - }); + return child + }) - return ( - <> {childrenWithRef} - - ); -}; + return <> {childrenWithRef} +} -export default ScrollableSection; \ No newline at end of file +export default ScrollableSection diff --git a/frontend/packages/common/src/components/aoplatform/SubscribeApprovalModalContent.tsx b/frontend/packages/common/src/components/aoplatform/SubscribeApprovalModalContent.tsx index df982c22..023e72e7 100644 --- a/frontend/packages/common/src/components/aoplatform/SubscribeApprovalModalContent.tsx +++ b/frontend/packages/common/src/components/aoplatform/SubscribeApprovalModalContent.tsx @@ -1,115 +1,129 @@ -import {App, Col, Form, Input, Row} from "antd"; -import { forwardRef, useEffect, useImperativeHandle} from "react"; -import {SubscribeApprovalInfoType} from "@common/const/approval/type.tsx"; -import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { SubscribeApprovalList } from "@common/const/approval/const"; -import { $t } from "@common/locales"; +import { App, Col, Form, Input, Row } from 'antd' +import { forwardRef, useEffect, useImperativeHandle } from 'react' +import { SubscribeApprovalInfoType } from '@common/const/approval/type.tsx' +import { BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { SubscribeApprovalList } from '@common/const/approval/const' +import { $t } from '@common/locales' type SubscribeApprovalModalProps = { - type:'approval'|'view' - data?:SubscribeApprovalInfoType - inSystem?:boolean - serviceId:string - teamId:string + type: 'approval' | 'view' + data?: SubscribeApprovalInfoType + inSystem?: boolean + serviceId: string + teamId: string } export type SubscribeApprovalModalHandle = { - save:(operate:'pass'|'refuse') =>Promise + save: (operate: 'pass' | 'refuse') => Promise } type FieldType = { - reason?:string; - opinion?:string; -}; + reason?: string + opinion?: string +} -export const SubscribeApprovalModalContent = forwardRef((props, ref) => { +export const SubscribeApprovalModalContent = forwardRef( + (props, ref) => { const { message } = App.useApp() - const {data, type,inSystem=false, teamId, serviceId} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() + const { data, type, inSystem = false, teamId, serviceId } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() - const save:(operate:'pass'|'refuse')=>Promise = (operate)=>{ - return new Promise((resolve, reject)=>{ - if(type === 'view'){ - resolve(true) - return - } - form.validateFields().then((value)=>{ - if(operate === 'refuse' && form.getFieldValue('opinion') === ''){ - form.setFields([{ - name:'opinion',errors:[$t(FORM_ERROR_TIPS.refuseOpinion)] - }]) - form.scrollToField('opinion') - reject($t(RESPONSE_TIPS.refuseOpinion)) - return + const save: (operate: 'pass' | 'refuse') => Promise = (operate) => { + return new Promise((resolve, reject) => { + if (type === 'view') { + resolve(true) + return + } + form + .validateFields() + .then((value) => { + if (operate === 'refuse' && form.getFieldValue('opinion') === '') { + form.setFields([ + { + name: 'opinion', + errors: [$t(FORM_ERROR_TIPS.refuseOpinion)] } - fetchData>(`${inSystem?'service/':''}approval/subscribe`,{method: 'POST',eoBody:({opinion:value.opinion,operate}), eoParams:(inSystem ? {apply:data!.id, team:teamId} : {id:data!.id,team:teamId})}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) + ]) + form.scrollToField('opinion') + reject($t(RESPONSE_TIPS.refuseOpinion)) + return + } + fetchData>(`${inSystem ? 'service/' : ''}approval/subscribe`, { + method: 'POST', + eoBody: { opinion: value.opinion, operate }, + eoParams: inSystem ? { apply: data!.id, team: teamId } : { id: data!.id, team: teamId } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) } - useImperativeHandle(ref, ()=>({ - save - }) - ) + useImperativeHandle(ref, () => ({ + save + })) - useEffect(()=>{ - form.setFieldsValue({opinion:'',...data}) - },[]) + useEffect(() => { + form.setFieldsValue({ opinion: '', ...data }) + }, []) return ( -
{ - SubscribeApprovalList?.map((x)=>( - -
{$t(x.title)}: - {(data as {[k:string]:unknown})?.[x.key]?.name || (data as {[k:string]:unknown})?.[x.key] || '-'} - - )) - } +
+ {SubscribeApprovalList?.map((x) => ( + +
+ {$t(x.title)}: + + + {(data as { [k: string]: unknown })?.[x.key]?.name || (data as { [k: string]: unknown })?.[x.key] || '-'} + + + ))} -
- - - label={$t("申请原因")} - name="reason" - > - - - - label={$t("审核意见")} - name="opinion" - extra={$t(FORM_ERROR_TIPS.refuseOpinion)} - > - { form.setFields([ - { - name: 'opinion', - errors: [], // 设置为空数组来移除错误信息 - }, - ])}} /> - - -
- + + label={$t('申请原因')} name="reason"> + + + label={$t('审核意见')} name="opinion" extra={$t(FORM_ERROR_TIPS.refuseOpinion)}> + { + form.setFields([ + { + name: 'opinion', + errors: [] // 设置为空数组来移除错误信息 + } + ]) + }} + /> + + + + ) -}) \ No newline at end of file + } +) diff --git a/frontend/packages/common/src/components/aoplatform/TableBtnWithPermission.tsx b/frontend/packages/common/src/components/aoplatform/TableBtnWithPermission.tsx index b6087974..b951a7c5 100644 --- a/frontend/packages/common/src/components/aoplatform/TableBtnWithPermission.tsx +++ b/frontend/packages/common/src/components/aoplatform/TableBtnWithPermission.tsx @@ -1,41 +1,51 @@ - -import { Button, Tooltip } from "antd" -import { useState, useMemo, useEffect, useCallback } from "react" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import { useNavigate } from "react-router-dom" -import { PERMISSION_DEFINITION } from "@common/const/permissions" -import { Icon } from "@iconify/react/dist/iconify.js" -import { $t } from "@common/locales" +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { Icon } from '@iconify/react/dist/iconify.js' +import { Button, Tooltip } from 'antd' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { useNavigate } from 'react-router-dom' type TableBtnWithPermissionProps = { btnTitle: string - access?: keyof typeof PERMISSION_DEFINITION[0], - tooltip?: string, - disabled?: boolean, - navigateTo?: string, + access?: keyof (typeof PERMISSION_DEFINITION)[0] + tooltip?: string + disabled?: boolean + navigateTo?: string onClick?: (args?: unknown) => void className?: string btnType: string } const TableIconName = { - 'add': 'ic:baseline-add', - 'edit': 'ic:baseline-edit', - 'delete': 'ic:baseline-delete', - 'remove': 'ic:baseline-minus', - 'copy': 'ic:baseline-file-copy', - 'view': 'ic:baseline-remove-red-eye', - 'publish': 'ic:baseline-publish', - 'approval': 'ic:baseline-approval', - 'stop': 'ic:baseline-stop-circle', - 'online': 'ic:baseline-check-circle', - 'cancel': 'ic:baseline-cancel-schedule-send', - 'refresh': 'ic:baseline-refresh', - 'logs': 'hugeicons:google-doc' + add: 'ic:baseline-add', + edit: 'ic:baseline-edit', + delete: 'ic:baseline-delete', + remove: 'ic:baseline-minus', + copy: 'ic:baseline-file-copy', + view: 'ic:baseline-remove-red-eye', + publish: 'ic:baseline-publish', + offline: 'ic:baseline-file-download-off', + approval: 'ic:baseline-approval', + stop: 'ic:baseline-stop-circle', + online: 'ic:baseline-check-circle', + cancel: 'ic:baseline-cancel-schedule-send', + refresh: 'ic:baseline-refresh', + logs: 'hugeicons:google-doc', + disable: 'ic:baseline-pause-circle', + enable: 'ic:baseline-play-circle' } // 表格操作栏按钮,受权限控制 -const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateTo, onClick, className, btnType }: TableBtnWithPermissionProps) => { - +const TableBtnWithPermission = ({ + btnTitle, + access, + tooltip, + disabled, + navigateTo, + onClick, + className, + btnType +}: TableBtnWithPermissionProps) => { const [btnAccess, setBtnAccess] = useState(false) const [btnStatus, setBtnStatus] = useState(false) const [closeToolTip, setCloseToolTip] = useState(false) @@ -51,16 +61,18 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT access ? setBtnAccess(lastAccess) : setBtnAccess(true) }, [access, lastAccess]) + const handleClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + setTimeout(() => { + setBtnStatus(false) + setCloseToolTip(true) + }) - const handleClick = useCallback((e: React.MouseEvent) => { - e.stopPropagation() - setTimeout(() => { - setBtnStatus(false) - setCloseToolTip(true) - }) - - navigateTo ? navigate(navigateTo) : onClick?.() - }, [navigateTo, navigate, onClick]) + navigateTo ? navigate(navigateTo) : onClick?.() + }, + [navigateTo, navigate, onClick] + ) const changeTooltipStatus = (open: boolean) => { setBtnStatus(open) if (closeToolTip) { @@ -68,18 +80,42 @@ const TableBtnWithPermission = ({ btnTitle, access, tooltip, disabled, navigateT setCloseToolTip(false) } } - return (<>{ - !btnAccess || (disabled && tooltip) ? - - - - : - - - - - } - ); + return ( + <> + {!btnAccess || (disabled && tooltip) ? ( + + + + ) : ( + + + + )} + + ) } -export default TableBtnWithPermission \ No newline at end of file +export default TableBtnWithPermission diff --git a/frontend/packages/common/src/components/aoplatform/TagWithPermission.tsx b/frontend/packages/common/src/components/aoplatform/TagWithPermission.tsx index 4c6c4b92..8aeb2e79 100644 --- a/frontend/packages/common/src/components/aoplatform/TagWithPermission.tsx +++ b/frontend/packages/common/src/components/aoplatform/TagWithPermission.tsx @@ -1,37 +1,38 @@ +import { Tag, TagProps } from 'antd' +import { useState, useMemo, useEffect } from 'react' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' -import { Tag, TagProps } from "antd"; -import { useState, useMemo, useEffect } from "react"; -import { PERMISSION_DEFINITION } from "@common/const/permissions"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; - -export interface TagWithPermission extends TagProps{ - access?:string +export interface TagWithPermission extends TagProps { + access?: string } -export default function TagWithPermission(props:TagWithPermission){ - const {access,onClose} = props - const [editAccess, setEditAccess] = useState(access ? false:true) - const {accessData,checkPermission,accessInit} = useGlobalContext() - const lastAccess = useMemo(()=>{ - if(!access) return true - return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]) - },[access, accessData,checkPermission,accessInit]) +export default function TagWithPermission(props: TagWithPermission) { + const { access, onClose } = props + const [editAccess, setEditAccess] = useState(access ? false : true) + const { accessData, checkPermission, accessInit } = useGlobalContext() + const lastAccess = useMemo(() => { + if (!access) return true + return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0]) + }, [access, accessData, checkPermission, accessInit]) - useEffect(()=>{ - access ? setEditAccess(lastAccess) : setEditAccess(true) - },[lastAccess]) - - const handleTagClose = (e: React.MouseEvent)=>{ - e.preventDefault(); - if(!editAccess) return - onClose?.(e) - } + useEffect(() => { + access ? setEditAccess(lastAccess) : setEditAccess(true) + }, [lastAccess]) - return - {props.children} - + const handleTagClose = (e: React.MouseEvent) => { + e.preventDefault() + if (!editAccess) return + onClose?.(e) + } -} \ No newline at end of file + return ( + + {props.children} + + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/ThemeSwitcher.tsx b/frontend/packages/common/src/components/aoplatform/ThemeSwitcher.tsx index 367bea96..38a92c20 100644 --- a/frontend/packages/common/src/components/aoplatform/ThemeSwitcher.tsx +++ b/frontend/packages/common/src/components/aoplatform/ThemeSwitcher.tsx @@ -1,34 +1,33 @@ - -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react' const ThemeSwitcher = () => { - const [darkMode, setDarkMode] = useState(true); + const [darkMode, setDarkMode] = useState(true) useEffect(() => { - let isDarkMode = localStorage.getItem('dark-mode'); - if(isDarkMode !== undefined && isDarkMode !== null){ - setDarkMode(isDarkMode === 'true') - }else{ - localStorage.setItem('dark-mode', (darkMode).toString()); + const isDarkMode = localStorage.getItem('dark-mode') + if (isDarkMode !== undefined && isDarkMode !== null) { + setDarkMode(isDarkMode === 'true') + } else { + localStorage.setItem('dark-mode', darkMode.toString()) } - }, []); + }, []) - useEffect(()=>{ - document.documentElement.classList.toggle('dark', darkMode); - },[darkMode]) + useEffect(() => { + document.documentElement.classList.toggle('dark', darkMode) + }, [darkMode]) const toggleDarkMode = () => { - setDarkMode(!darkMode); - localStorage.setItem('dark-mode', (!darkMode).toString()); - document.documentElement.classList.toggle('dark', !darkMode); - }; + setDarkMode(!darkMode) + localStorage.setItem('dark-mode', (!darkMode).toString()) + document.documentElement.classList.toggle('dark', !darkMode) + } return ( // <> - ); -}; + ) +} -export default ThemeSwitcher; \ No newline at end of file +export default ThemeSwitcher diff --git a/frontend/packages/common/src/components/aoplatform/TimeRangeSelector.tsx b/frontend/packages/common/src/components/aoplatform/TimeRangeSelector.tsx index 9908f023..f2724dd8 100644 --- a/frontend/packages/common/src/components/aoplatform/TimeRangeSelector.tsx +++ b/frontend/packages/common/src/components/aoplatform/TimeRangeSelector.tsx @@ -1,25 +1,24 @@ +import { useEffect, useState } from 'react' +import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd' +import dayjs, { Dayjs } from 'dayjs' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import '../../index.css' +import { $t } from '@common/locales' -import { useEffect, useState } from 'react'; -import { Radio, DatePicker, GetProps, RadioChangeEvent } from 'antd'; -import dayjs, { Dayjs } from 'dayjs'; -import customParseFormat from 'dayjs/plugin/customParseFormat'; -import "../../index.css" -import { $t } from '@common/locales'; +type RangePickerProps = GetProps +export type RangeValue = [Dayjs | null, Dayjs | null] | null -type RangePickerProps = GetProps; -export type RangeValue = [Dayjs | null, Dayjs | null] | null; - -dayjs.extend(customParseFormat); +dayjs.extend(customParseFormat) export type TimeRange = { start: number | null end: number | null } -export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'; +export type TimeRangeButton = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays' type TimeRangeSelectorProps = { - initialTimeButton?: TimeRangeButton, + initialTimeButton?: TimeRangeButton initialDatePickerValue?: RangeValue onTimeRangeChange?: (timeRange: TimeRange) => void hideTitle?: boolean @@ -30,17 +29,27 @@ type TimeRangeSelectorProps = { defaultTimeButton?: TimeRangeButton } const TimeRangeSelector = (props: TimeRangeSelectorProps) => { - const { initialTimeButton, initialDatePickerValue, onTimeRangeChange, hideTitle, onTimeButtonChange, labelSize = 'default', bindRef, hideBtns = [], defaultTimeButton = 'hour' } = props - const [timeButton, setTimeButton] = useState(initialTimeButton || ''); - const [datePickerValue, setDatePickerValue] = useState(initialDatePickerValue || [null, null]); + const { + initialTimeButton, + initialDatePickerValue, + onTimeRangeChange, + hideTitle, + onTimeButtonChange, + labelSize = 'default', + bindRef, + hideBtns = [], + defaultTimeButton = 'hour' + } = props + const [timeButton, setTimeButton] = useState(initialTimeButton || '') + const [datePickerValue, setDatePickerValue] = useState(initialDatePickerValue || [null, null]) useEffect(() => { if (bindRef) { - bindRef({ reset }); + bindRef({ reset }) } }, [bindRef]) // 根据选择的时间范围计算开始和结束时间 const calculateTimeRange = (curBtn: TimeRangeButton) => { - const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳 + const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳 let startMin = currentSecond - 60 * 60 switch (curBtn) { case 'hour': { @@ -52,30 +61,26 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => { break } case 'threeDays': { - startMin = - Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - - 2 * 24 * 60 * 60 + startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 2 * 24 * 60 * 60 break } case 'sevenDays': { - startMin = - Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - - 6 * 24 * 60 * 60 + startMin = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000) - 6 * 24 * 60 * 60 break } } if (onTimeRangeChange) { - onTimeRangeChange({ start: startMin, end: currentSecond }); + onTimeRangeChange({ start: startMin, end: currentSecond }) } - }; + } // 处理单选按钮的变化 const handleRadioChange = (e: RadioChangeEvent) => { - setTimeButton(e.target.value); + setTimeButton(e.target.value) onTimeButtonChange?.(e.target.value) setDatePickerValue(null) - calculateTimeRange(e.target.value); - }; + calculateTimeRange(e.target.value) + } const reset = () => { setTimeButton(defaultTimeButton) calculateTimeRange(defaultTimeButton) @@ -86,35 +91,43 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => { const handleDatePickerChange = (dates: RangeValue) => { setTimeButton(dates ? '' : defaultTimeButton) onTimeButtonChange?.(dates ? '' : defaultTimeButton) - setDatePickerValue(dates); + setDatePickerValue(dates) if (dates && Array.isArray(dates) && dates.length === 2) { - const [startDate, endDate] = dates; - const start = startDate!.startOf('day').unix(); // 开始日期的00:00:00 - const end = endDate!.endOf('day').unix(); // 结束日期的23:59:59 + const [startDate, endDate] = dates + const start = startDate!.startOf('day').unix() // 开始日期的00:00:00 + const end = endDate!.endOf('day').unix() // 结束日期的23:59:59 if (onTimeRangeChange) { - onTimeRangeChange({ start, end }); + onTimeRangeChange({ start, end }) } } if (!dates) { calculateTimeRange(defaultTimeButton) } - }; - - + } const disabledDate: RangePickerProps['disabledDate'] = (current) => { // Can not select days before today and today - return current && current.valueOf() > dayjs().startOf('day').valueOf(); - }; + return current && current.valueOf() > dayjs().startOf('day').valueOf() + } return (
{!hideTitle && } - {hideBtns?.length && hideBtns.includes('hour') ? null : {$t('近1小时')}} - {hideBtns?.length && hideBtns.includes('day') ? null : {$t('近24小时')}} - {hideBtns?.length && hideBtns.includes('threeDays') ? null : {$t('近3天')}} - {hideBtns?.length && hideBtns.includes('sevenDays') ? null : {$t('近7天')}} + {hideBtns?.length && hideBtns.includes('hour') ? null : ( + {$t('近1小时')} + )} + {hideBtns?.length && hideBtns.includes('day') ? null : ( + {$t('近24小时')} + )} + {hideBtns?.length && hideBtns.includes('threeDays') ? null : ( + {$t('近3天')} + )} + {hideBtns?.length && hideBtns.includes('sevenDays') ? null : ( + + {$t('近7天')} + + )} { }} />
- ); -}; + ) +} -export default TimeRangeSelector; \ No newline at end of file +export default TimeRangeSelector diff --git a/frontend/packages/common/src/components/aoplatform/TreeWithMore.tsx b/frontend/packages/common/src/components/aoplatform/TreeWithMore.tsx index e2f41992..df50473f 100644 --- a/frontend/packages/common/src/components/aoplatform/TreeWithMore.tsx +++ b/frontend/packages/common/src/components/aoplatform/TreeWithMore.tsx @@ -1,45 +1,94 @@ - -import {CheckOutlined, LoadingOutlined, MoreOutlined} from "@ant-design/icons"; -import {Dropdown, Input, InputRef, MenuProps} from "antd"; -import { ReactNode, useEffect, useRef, useState} from "react"; +import { CheckOutlined, LoadingOutlined, MoreOutlined } from '@ant-design/icons' +import { Dropdown, Input, InputRef, MenuProps } from 'antd' +import { ReactNode, useEffect, useRef, useState } from 'react' export type TreeWithMoreProp = { - children:ReactNode, - dropdownMenu:MenuProps['items'] - editable?:boolean - editingId?:string - afterEdit?:(val:string)=>Promise - editKey?:string - entity?:{id:string,[k:string]:unknown | string} - onBlur?:()=>void - stopClick?:boolean + children: ReactNode + dropdownMenu: MenuProps['items'] + editable?: boolean + editingId?: string + afterEdit?: (val: string) => Promise + editKey?: string + entity?: { id: string; [k: string]: unknown | string } + onBlur?: () => void + stopClick?: boolean } -const TreeWithMore = ({children,dropdownMenu,editable,editingId,entity,editKey='name',afterEdit,onBlur,stopClick=true}:TreeWithMoreProp)=>{ - const [editValue, setEditValue] = useState(entity?.[editKey] as string) - const [submitting, setSubmitting] = useState(false) - const inputRef = useRef(null) +const TreeWithMore = ({ + children, + dropdownMenu, + editable, + editingId, + entity, + editKey = 'name', + afterEdit, + onBlur, + stopClick = true +}: TreeWithMoreProp) => { + const [editValue, setEditValue] = useState(entity?.[editKey] as string) + const [submitting, setSubmitting] = useState(false) + const inputRef = useRef(null) - const handleSubmit = (val:string)=>{ - if(submitting) return - setSubmitting(true) - afterEdit && afterEdit(val).finally(()=>setSubmitting(false)) - } + const handleSubmit = (val: string) => { + if (submitting) return + setSubmitting(true) + afterEdit && afterEdit(val).finally(() => setSubmitting(false)) + } - useEffect(()=>{inputRef.current?.focus()},[inputRef]) + useEffect(() => { + inputRef.current?.focus() + }, [inputRef]) - return (<> - { - editable && editingId && entity?.id && editingId === entity.id ? {setEditValue(e.target.value)}} onBlur={()=>{onBlur?.()}} onClick={(e)=>stopClick&&e?.stopPropagation()} onPressEnter={()=>{handleSubmit(editValue)}} suffix={submitting ? :{handleSubmit(editValue)}}/>} />: - -
{children} - { stopClick && e.stopPropagation();}}> - - { stopClick && e.stopPropagation(); }} /> - - -
+ return ( + <> + {editable && editingId && entity?.id && editingId === entity.id ? ( + { + setEditValue(e.target.value) + }} + onBlur={() => { + onBlur?.() + }} + onClick={(e) => stopClick && e?.stopPropagation()} + onPressEnter={() => { + handleSubmit(editValue) + }} + suffix={ + submitting ? ( + + ) : ( + { + handleSubmit(editValue) + }} + /> + ) + } + /> + ) : ( + +
+ {children} + { + stopClick && e.stopPropagation() + }} + > + + { + stopClick && e.stopPropagation() + }} + /> + + +
- }) + )} + + ) } -export default TreeWithMore \ No newline at end of file +export default TreeWithMore diff --git a/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx b/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx index 0c1bd16f..b93160b3 100644 --- a/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx +++ b/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx @@ -1,190 +1,190 @@ -import { $t } from "@common/locales" +import { $t } from '@common/locales' /* 本组件不在页面渲染,只是为了让i18next-scanner能找到从接口传递的、需要翻译的字段 , 此处的字段除非确认不在页面上渲染了,否则不应删除,容易导致翻译遗漏*/ -export const TranslateWord = ()=>{ - return ( - <> - {$t('文件日志')} - {$t('HTTP日志')} - {$t('Kafka日志')} - {$t('NSQ日志')} - {$t('Syslog日志')} - {$t('未分配')} - {$t('超级管理员')} - {$t('团队管理员')} - {$t('运维管理员')} - {$t('普通成员')} - {$t('只读成员')} - {$t('服务管理员')} - {$t('服务开发者')} - {$t('消费者开发者')} - {$t('消费者管理员')} - {$t('驱动名称')} - {$t('请求失败数')} - {$t('转发失败数')} - {$t('作用范围')} - {$t('添加条目')} - {$t('添加地址')} - {$t('文件名称')} - {$t('存放目录')} - {$t('日志分割周期')} - {$t('过期时间')} - {$t('单位:天')} - {$t('输出格式')} - {$t('格式化配置')} - {$t('服务器地址')} - {$t('Access日志')} - {$t('NSQD地址列表')} - {$t('鉴权Secret')} - {$t('网络协议')} - {$t('日志等级')} - {$t('单行')} - {$t('小时')} - {$t('天')} - {$t('未发布')} - {$t('待发布')} - {$t('单位:s,最小值:1')} - {$t('上传文件')} - {$t('替换文件')} - {$t('是否放行')} - {$t('监控')} - {$t('必填')} - {$t('字符非法,仅支持英文')} - {$t('上传 OpenAPI 文档 (.json/.yaml)')} - {$t('替换 OpenAPI 文档 (.json/.yaml)')} - {$t('打开 OpenAPI YAML 编辑器')} - {$t('无需审核:允许任何消费者调用该服务')} - {$t('人工审核:仅允许通过人工审核的消费者调用该服务')} - {$t('永久')} - {$t('否')} - {$t('是')} - {$t('无需审核')} - {$t('需要审核')} - {$t('创建时间')} - {$t('协议')} - {$t('方法')} - {$t('地址(IP 端口或域名)')} - {$t('权重(0-999)')} - {$t('带权轮询')} - {$t('发布版本')} - {$t('发布申请记录')} - {$t('创建版本时间')} - {$t('版本状态')} - {$t('创建人')} - {$t('审核时间')} - {$t('申请方-消费者')} - {$t('审核状态')} - {$t('申请人')} - {$t('审核人')} - {$t('审核时间')} - {$t('来源')} - {$t('订阅时间')} - {$t('请输入')} - {$t('请选择')} - {$t('创建者')} - {$t('服务数量')} - {$t('负责人')} - {$t('姓名')} - {$t('团队角色')} - {$t('添加日期')} - {$t('请求成功数')} - {$t('转发成功数')} - {$t('API 名称')} - {$t('失败状态码数')} - {$t('所属服务')} - {$t('平均响应时间(ms)')} - {$t('最大响应时间(ms)')} - {$t('最小响应时间(ms)')} - {$t('平均请求流量(KB)')} - {$t('最大请求流量(KB)')} - {$t('最小请求流量(KB)')} - {$t('所有成员')} - {$t('状态')} - {$t('角色名称')} - {$t('绑定域名')} - {$t('过期日期')} - {$t('支持字母开头、英文数字中横线下划线组合')} - {$t('英文数字下划线任意一种,首字母必须为英文')} - {$t('字符非法,仅支持英文')} - {$t('无法连接集群,请检查集群地址是否正确或防火墙配置')} - {$t('选择拒绝时,审核意见为必填')} - {$t('操作成功')} - {$t('操作失败')} - {$t('正在操作')} - {$t('正在加载数据')} - {$t('获取数据失败')} - {$t('登录成功')} - {$t('退出成功,将跳转至登录页')} - {$t('未填写审核意见')} - {$t('复制成功')} - {$t('复制失败,请手动复制')} - {$t('服务所属团队')} - {$t('在线')} - {$t('已拒绝')} - {$t('中止')} - {$t('发布异常')} - {$t('发布中')} - {$t('申请方所属团队')} - {$t('发布状态')} - {$t(' 次')} - {$t('每分钟')} - {$t('每5分钟')} - {$t('每小时')} - {$t('每天')} - {$t('每周')} - {$t('上线结果')} - {$t('订阅服务数量')} - {$t('鉴权数量')} - {$t('列表')} - {$t('块')} - {$t('HTTP 请求头')} - {$t('全等匹配')} - {$t('前缀匹配')} - {$t('后缀匹配')} - {$t('子串匹配')} - {$t('非等匹配')} - {$t('空值匹配')} - {$t('存在匹配')} - {$t('不存在匹配')} - {$t('区分大小写的正则匹配')} - {$t('不区分大小写的正则匹配')} - {$t('任意匹配')} - {$t('驳回')} - {$t('已订阅')} - {$t('取消申请')} - {$t('透传客户端请求 Host')} - {$t('使用上游服务 Host')} - {$t('重写 Host')} - {$t('动态服务发现')} - {$t('地址')} - {$t('新增')} - {$t('申请方消费者')} - {$t('策略名称')} - {$t('优先级')} - {$t('筛选条件')} - {$t('处理数')} - {$t('数据格式')} - {$t('关键字')} - {$t('正则表达式')} - {$t('手机号')} - {$t('身份证号')} - {$t('银行卡号')} - {$t('金额')} - {$t('日期')} - {$t('局部显示')} - {$t('局部遮蔽')} - {$t('截取')} - {$t('替换')} - {$t('乱序')} - {$t('随机字符串')} - {$t('自定义字符串')} - {$t('请输入IP地址或CIDR范围,每条以换行分割')} - {$t('待更新')} - {$t('待删除')} - {$t('内容')} - {$t('调用地址')} - {$t('消费者 IP')} - {$t('鉴权名称')} - - ) -} \ No newline at end of file +export const TranslateWord = () => { + return ( + <> + {$t('文件日志')} + {$t('HTTP日志')} + {$t('Kafka日志')} + {$t('NSQ日志')} + {$t('Syslog日志')} + {$t('未分配')} + {$t('超级管理员')} + {$t('团队管理员')} + {$t('运维管理员')} + {$t('普通成员')} + {$t('只读成员')} + {$t('服务管理员')} + {$t('服务开发者')} + {$t('消费者开发者')} + {$t('消费者管理员')} + {$t('驱动名称')} + {$t('请求失败数')} + {$t('转发失败数')} + {$t('作用范围')} + {$t('添加条目')} + {$t('添加地址')} + {$t('文件名称')} + {$t('存放目录')} + {$t('日志分割周期')} + {$t('过期时间')} + {$t('单位:天')} + {$t('输出格式')} + {$t('格式化配置')} + {$t('服务器地址')} + {$t('Access日志')} + {$t('NSQD地址列表')} + {$t('鉴权Secret')} + {$t('网络协议')} + {$t('日志等级')} + {$t('单行')} + {$t('小时')} + {$t('天')} + {$t('未发布')} + {$t('待发布')} + {$t('单位:s,最小值:1')} + {$t('上传文件')} + {$t('替换文件')} + {$t('是否放行')} + {$t('监控')} + {$t('必填')} + {$t('字符非法,仅支持英文')} + {$t('上传 OpenAPI 文档 (.json/.yaml)')} + {$t('替换 OpenAPI 文档 (.json/.yaml)')} + {$t('打开 OpenAPI YAML 编辑器')} + {$t('无需审核:允许任何消费者调用该服务')} + {$t('人工审核:仅允许通过人工审核的消费者调用该服务')} + {$t('永久')} + {$t('否')} + {$t('是')} + {$t('无需审核')} + {$t('需要审核')} + {$t('创建时间')} + {$t('协议')} + {$t('方法')} + {$t('地址(IP 端口或域名)')} + {$t('权重(0-999)')} + {$t('带权轮询')} + {$t('发布版本')} + {$t('发布申请记录')} + {$t('创建版本时间')} + {$t('版本状态')} + {$t('创建人')} + {$t('审核时间')} + {$t('申请方-消费者')} + {$t('审核状态')} + {$t('申请人')} + {$t('审核人')} + {$t('审核时间')} + {$t('来源')} + {$t('订阅时间')} + {$t('请输入')} + {$t('请选择')} + {$t('创建者')} + {$t('服务数量')} + {$t('负责人')} + {$t('姓名')} + {$t('团队角色')} + {$t('添加日期')} + {$t('请求成功数')} + {$t('转发成功数')} + {$t('API 名称')} + {$t('失败状态码数')} + {$t('所属服务')} + {$t('平均响应时间(ms)')} + {$t('最大响应时间(ms)')} + {$t('最小响应时间(ms)')} + {$t('平均请求流量(KB)')} + {$t('最大请求流量(KB)')} + {$t('最小请求流量(KB)')} + {$t('所有成员')} + {$t('状态')} + {$t('角色名称')} + {$t('绑定域名')} + {$t('过期日期')} + {$t('支持字母开头、英文数字中横线下划线组合')} + {$t('英文数字下划线任意一种,首字母必须为英文')} + {$t('字符非法,仅支持英文')} + {$t('无法连接集群,请检查集群地址是否正确或防火墙配置')} + {$t('选择拒绝时,审核意见为必填')} + {$t('操作成功')} + {$t('操作失败')} + {$t('正在操作')} + {$t('正在加载数据')} + {$t('获取数据失败')} + {$t('登录成功')} + {$t('退出成功,将跳转至登录页')} + {$t('未填写审核意见')} + {$t('复制成功')} + {$t('复制失败,请手动复制')} + {$t('服务所属团队')} + {$t('在线')} + {$t('已拒绝')} + {$t('中止')} + {$t('发布异常')} + {$t('发布中')} + {$t('申请方所属团队')} + {$t('发布状态')} + {$t(' 次')} + {$t('每分钟')} + {$t('每5分钟')} + {$t('每小时')} + {$t('每天')} + {$t('每周')} + {$t('上线结果')} + {$t('订阅服务数量')} + {$t('鉴权数量')} + {$t('列表')} + {$t('块')} + {$t('HTTP 请求头')} + {$t('全等匹配')} + {$t('前缀匹配')} + {$t('后缀匹配')} + {$t('子串匹配')} + {$t('非等匹配')} + {$t('空值匹配')} + {$t('存在匹配')} + {$t('不存在匹配')} + {$t('区分大小写的正则匹配')} + {$t('不区分大小写的正则匹配')} + {$t('任意匹配')} + {$t('驳回')} + {$t('已订阅')} + {$t('取消申请')} + {$t('透传客户端请求 Host')} + {$t('使用上游服务 Host')} + {$t('重写 Host')} + {$t('动态服务发现')} + {$t('地址')} + {$t('新增')} + {$t('申请方消费者')} + {$t('策略名称')} + {$t('优先级')} + {$t('筛选条件')} + {$t('处理数')} + {$t('数据格式')} + {$t('关键字')} + {$t('正则表达式')} + {$t('手机号')} + {$t('身份证号')} + {$t('银行卡号')} + {$t('金额')} + {$t('日期')} + {$t('局部显示')} + {$t('局部遮蔽')} + {$t('截取')} + {$t('替换')} + {$t('乱序')} + {$t('随机字符串')} + {$t('自定义字符串')} + {$t('请输入IP地址或CIDR范围,每条以换行分割')} + {$t('待更新')} + {$t('待删除')} + {$t('内容')} + {$t('调用地址')} + {$t('消费者 IP')} + {$t('鉴权名称')} + + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/WithPermission.tsx b/frontend/packages/common/src/components/aoplatform/WithPermission.tsx index 2b1a37d7..719cd5bd 100644 --- a/frontend/packages/common/src/components/aoplatform/WithPermission.tsx +++ b/frontend/packages/common/src/components/aoplatform/WithPermission.tsx @@ -1,49 +1,47 @@ - -import { Button, Tooltip, Upload } from "antd"; -import { ReactElement, cloneElement, useEffect, useMemo, useState } from "react"; -import { useGlobalContext } from "../../contexts/GlobalStateContext"; -import { PERMISSION_DEFINITION } from "@common/const/permissions"; -import { $t } from "@common/locales"; -import { last } from "lodash-es"; +import { Button, Tooltip, Upload } from 'antd' +import { ReactElement, cloneElement, useEffect, useMemo, useState } from 'react' +import { useGlobalContext } from '../../contexts/GlobalStateContext' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { $t } from '@common/locales' type WithPermissionProps = { - access?:string | string[] - tooltip?:string - children:ReactElement - disabled?:boolean - showDisabled?:boolean + access?: string | string[] + tooltip?: string + children: ReactElement + disabled?: boolean + showDisabled?: boolean } // 权限控制的高阶组件 -const WithPermission = ({access, tooltip, children,disabled, showDisabled = true}:WithPermissionProps) => { - - const [editAccess, setEditAccess] = useState(access ? false:true) - const {accessData,checkPermission,accessInit} = useGlobalContext() +const WithPermission = ({ access, tooltip, children, disabled, showDisabled = true }: WithPermissionProps) => { + const [editAccess, setEditAccess] = useState(access ? false : true) + const { accessData, checkPermission, accessInit } = useGlobalContext() - const lastAccess = useMemo(()=>{ - if(!access) return true - return checkPermission(access as keyof typeof PERMISSION_DEFINITION[0]) - },[access, accessData,checkPermission,accessInit]) + const lastAccess = useMemo(() => { + if (!access) return true + return checkPermission(access as keyof (typeof PERMISSION_DEFINITION)[0]) + }, [access, accessData, checkPermission, accessInit]) - useEffect(()=>{ - // 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture - access && setEditAccess(lastAccess) - },[lastAccess,disabled]) + useEffect(() => { + // 先判断权限,无论权限是否为true,如果disabled为true时则必须为ture + access && setEditAccess(lastAccess) + }, [lastAccess, disabled]) + return ( + <> + {} + {editAccess && !disabled && cloneElement(children)} + {editAccess && disabled && {cloneElement(children, { disabled: true })}} + {!editAccess && children?.type !== Button && children?.type !== Upload && showDisabled && ( + + {cloneElement(children, { + disabled: true, + onClick: (e) => e.preventDefault(), + okButtonProps: { disabled: true } + })} + + )} + + ) +} - - return ( - <>{ - } - {editAccess && !disabled && cloneElement(children)} - {editAccess && disabled && - { cloneElement(children, {disabled:true})} - } - {!editAccess && (children?.type !== Button && children?.type !== Upload && showDisabled) && - { cloneElement(children, {disabled:true, onClick:(e)=>e.preventDefault(),okButtonProps:{disabled:true}})} - } - - - ); - } - -export default WithPermission \ No newline at end of file +export default WithPermission diff --git a/frontend/packages/common/src/components/aoplatform/WithRouteGuard.tsx b/frontend/packages/common/src/components/aoplatform/WithRouteGuard.tsx index 7edd1f59..361bb427 100644 --- a/frontend/packages/common/src/components/aoplatform/WithRouteGuard.tsx +++ b/frontend/packages/common/src/components/aoplatform/WithRouteGuard.tsx @@ -1,79 +1,85 @@ -import { set } from 'lodash-es'; -import { ExoticComponent, JSXElementConstructor, ReactElement, useEffect, useState } from 'react'; -import { useBlocker, useLocation, useNavigate } from 'react-router-dom'; -import { JSX } from 'react/jsx-runtime'; +import { ExoticComponent, JSXElementConstructor, useEffect, useState } from 'react' +import { useBlocker, useLocation } from 'react-router-dom' +import { JSX } from 'react/jsx-runtime' -const withRouteGuard = (WrappedComponent: ExoticComponent | JSXElementConstructor, { - canActivate, - canLoad , - canDeactivate, - deactivated, - pathPrefix -}: { pathPrefix?:string, canActivate?: () => Promise; canLoad?: () => Promise; canDeactivate?: () => Promise; deactivated?: () => Promise; } = {}) => { +const withRouteGuard = ( + WrappedComponent: ExoticComponent | JSXElementConstructor, + { + canActivate, + canLoad, + canDeactivate, + deactivated, + pathPrefix + }: { + pathPrefix?: string + canActivate?: () => Promise + canLoad?: () => Promise + canDeactivate?: () => Promise + deactivated?: () => Promise + } = {} +) => { return function RouteGuard(props: JSX.IntrinsicAttributes) { - const [isActivated, setIsActivated] = useState(false); - const location = useLocation(); + const [isActivated, setIsActivated] = useState(false) + const location = useLocation() // check canActivate - const startLifecycle = async ()=>{ - if(canActivate){ - const activateRes = await canActivate(); - setIsActivated(activateRes); - }else{ - setIsActivated(true); + const startLifecycle = async () => { + if (canActivate) { + const activateRes = await canActivate() + setIsActivated(activateRes) + } else { + setIsActivated(true) + } + } + + // check canDeactivate + const handleBeforeUnload = async (event: { preventDefault: () => void; returnValue: string }) => { + const deactivateRes = canDeactivate ? await canDeactivate() : true + if (!deactivateRes) { + event.preventDefault() + event.returnValue = '' } } - - // check canDeactivate - const handleBeforeUnload =async (event: { preventDefault: () => void; returnValue: string; }) => { - const deactivateRes = canDeactivate? await canDeactivate():true; - if (!deactivateRes) { - event.preventDefault(); - event.returnValue = ''; - } - }; // 激活组件时的检查 useEffect(() => { - startLifecycle(); - window.addEventListener('beforeunload', handleBeforeUnload); + startLifecycle() + window.addEventListener('beforeunload', handleBeforeUnload) return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); - deactivated?.(); - }; - }, []); - + window.removeEventListener('beforeunload', handleBeforeUnload) + deactivated?.() + } + }, []) const blocker = useBlocker((tx) => { - const currentPath = location.pathname; - const targetPath = tx.nextLocation.pathname; + const currentPath = location.pathname + const targetPath = tx.nextLocation.pathname - if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) { + if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) { canDeactivate().then((res) => { - if(res){ - return false; - }else{ - return true; + if (res) { + return false + } else { + return true } }) } else { return false } - }); + }) - - const checkCanLoad = async()=>{ - const loadRes = await canLoad!(); - !loadRes && setIsActivated(false); + const checkCanLoad = async () => { + const loadRes = await canLoad!() + !loadRes && setIsActivated(false) } useEffect(() => { if (isActivated && canLoad) { checkCanLoad() } - }, [isActivated]); + }, [isActivated]) - return isActivated ? : null; - }; + return isActivated ? : null + } } -export default withRouteGuard; \ No newline at end of file +export default withRouteGuard diff --git a/frontend/packages/common/src/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx b/frontend/packages/common/src/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx index 79266618..0685dcbe 100644 --- a/frontend/packages/common/src/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx +++ b/frontend/packages/common/src/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx @@ -1,150 +1,143 @@ - -import {forwardRef, useImperativeHandle, useState} from 'react' +import { forwardRef, useImperativeHandle, useState } from 'react' import { Input } from 'antd' import { Icon } from '@iconify/react/dist/iconify.js' -export const ArrayItemBlankComponent = forwardRef( - (props: { [k: string]: any }, ref) => { - const { onChange, value, dataFormat } = props +export const ArrayItemBlankComponent = forwardRef((props: { [k: string]: any }, ref) => { + const { onChange, value, dataFormat } = props - const getDefaultListItem = () => { - const defaultData: { [k: string]: unknown } = {} + const getDefaultListItem = () => { + const defaultData: { [k: string]: unknown } = {} - for (const data of dataFormat) { - defaultData[data.key] = '' - } - - return [defaultData] + for (const data of dataFormat) { + defaultData[data.key] = '' } - const [resList, setResList] = useState( - value && Object.keys(value).length > 0 - ? [ - ...value - ?.filter((v: string) => { - return v - }) - ?.map((v: string) => { - const vTmp = v - const newValue: { [k: string]: unknown } = {} - for (let index = 0; index < dataFormat.length; index++) { - if (dataFormat[index]?.hideName) { - newValue[dataFormat[index].key] = vTmp.split(' ')[index] - } else { - const vTmp2: string | string[] | undefined = - vTmp.indexOf(' ') === -1 - ? vTmp - : vTmp.split(' ')[index] + return [defaultData] + } + + const [resList, setResList] = useState( + value && Object.keys(value).length > 0 + ? [ + ...value + ?.filter((v: string) => { + return v + }) + ?.map((v: string) => { + const vTmp = v + const newValue: { [k: string]: unknown } = {} + for (let index = 0; index < dataFormat.length; index++) { + if (dataFormat[index]?.hideName) { + newValue[dataFormat[index].key] = vTmp.split(' ')[index] + } else { + const vTmp2: string | string[] | undefined = + vTmp.indexOf(' ') === -1 + ? vTmp + : vTmp.split(' ')[index] ? vTmp.split(' ')[index].indexOf('=') === -1 ? '' : vTmp.split(' ')[index].split('=') : '' - if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) { - vTmp2.shift() - } - newValue[dataFormat[index].key] = - vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2 + if (vTmp2 && vTmp2 instanceof Array && vTmp2.length > 0) { + vTmp2.shift() } + newValue[dataFormat[index].key] = vTmp2 instanceof Array ? vTmp2?.join('=') : vTmp2 } - return newValue - }), - ...getDefaultListItem() - ] - : [...getDefaultListItem()] - ) + } + return newValue + }), + ...getDefaultListItem() + ] + : [...getDefaultListItem()] + ) - useImperativeHandle(ref, () => ({})) + useImperativeHandle(ref, () => ({})) - const emitNewArr = () => { - const newArr: Array = [] - for (const r of resList) { - if (r[dataFormat[0].key]) { - newArr.push( - dataFormat?.map((format: { key: string; hideName: boolean }) => { - return format?.hideName - ? r[format.key] - : `${format.key}=${r[format.key]}` - }) - .join(' ') - ) - } - } - onChange(newArr) - } - - const changeInputValue = ( - newValue: string, - index: number, - keyName: string, - dataFormat: unknown - ) => { - const newArr = [...resList] - newArr[index][keyName] = newValue - newArr[index].status = - (dataFormat.required && !newValue) || - (dataFormat.pattern && !dataFormat.pattern.test(newValue)) - ? 'error' - : '' - setResList(newArr) - emitNewArr() - if (index === resList.length - 1) { - setResList([...newArr, ...getDefaultListItem()]) + const emitNewArr = () => { + const newArr: Array = [] + for (const r of resList) { + if (r[dataFormat[0].key]) { + newArr.push( + dataFormat + ?.map((format: { key: string; hideName: boolean }) => { + return format?.hideName ? r[format.key] : `${format.key}=${r[format.key]}` + }) + .join(' ') + ) } } - - const addLine = (index: number) => { - resList.splice(index + 1, 0, ...getDefaultListItem()) - const newKvList = [...resList] - setResList(newKvList) - emitNewArr() - } - - const removeLine = (index: number) => { - resList.splice(index, 1) - const newKvList = [...resList] - setResList([...newKvList]) - emitNewArr() - } - - return ( -
- {resList?.map((n: unknown, index: unknown) => { - return ( -
- {dataFormat?.map((data: unknown, index2: unknown) => { - return ( - { - changeInputValue(e.target.value, index, data.key, data) - }} - placeholder={data.placeholder || `请输入${data.key}`} - status={n.status} - type={data.type || 'text'} - /> - ) - })} - - {index !== resList.length - 1 && ( -
- {n[dataFormat[0].key] && ( - addLine(index as unknown as number)} width="14" height="14"/> - )} - removeLine(index as unknown as number)} width="14" height="14"/> -
- )} -
- ) - })} -
- ) + onChange(newArr) } -) + + const changeInputValue = (newValue: string, index: number, keyName: string, dataFormat: unknown) => { + const newArr = [...resList] + newArr[index][keyName] = newValue + newArr[index].status = + (dataFormat.required && !newValue) || (dataFormat.pattern && !dataFormat.pattern.test(newValue)) ? 'error' : '' + setResList(newArr) + emitNewArr() + if (index === resList.length - 1) { + setResList([...newArr, ...getDefaultListItem()]) + } + } + + const addLine = (index: number) => { + resList.splice(index + 1, 0, ...getDefaultListItem()) + const newKvList = [...resList] + setResList(newKvList) + emitNewArr() + } + + const removeLine = (index: number) => { + resList.splice(index, 1) + const newKvList = [...resList] + setResList([...newKvList]) + emitNewArr() + } + + return ( +
+ {resList?.map((n: unknown, index: unknown) => { + return ( +
+ {dataFormat?.map((data: unknown, index2: unknown) => { + return ( + { + changeInputValue(e.target.value, index, data.key, data) + }} + placeholder={data.placeholder || `请输入${data.key}`} + status={n.status} + type={data.type || 'text'} + /> + ) + })} + + {index !== resList.length - 1 && ( +
+ {n[dataFormat[0].key] && ( + addLine(index as unknown as number)} + width="14" + height="14" + /> + )} + removeLine(index as unknown as number)} + width="14" + height="14" + /> +
+ )} +
+ ) + })} +
+ ) +}) diff --git a/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx b/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx index 43853bb4..ca1e8259 100644 --- a/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx +++ b/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx @@ -1,47 +1,35 @@ - -import {forwardRef, useImperativeHandle, useState} from 'react' +import { forwardRef, useImperativeHandle, useState } from 'react' import { Codebox } from '@common/components/postcat/api/Codebox' -export const CustomCodeboxComponent = forwardRef( - (props: { [k: string]: unknown }, ref) => { - const { - mode = 'yaml', - theme = 'xcode', - fontSize, - height, - width = '100%', - onChange, - value - } = props - const [code, setCode] = useState( - mode === 'json' ? JSON.stringify(value) : value - ) - useImperativeHandle(ref, () => ({})) - const handleChange = (value: string) => { - setCode(value) - let res = value - if (mode === 'json') { - try { - res = JSON.parse(value) - } catch { - console.warn(' 输入的json语句格式有误') - } +export const CustomCodeboxComponent = forwardRef((props: { [k: string]: unknown }, ref) => { + const { mode = 'yaml', theme = 'xcode', fontSize, height, width = '100%', onChange, value } = props + const [code, setCode] = useState(mode === 'json' ? JSON.stringify(value) : value) + useImperativeHandle(ref, () => ({})) + const handleChange = (value: string) => { + setCode(value) + let res = value + if (mode === 'json') { + try { + res = JSON.parse(value) + } catch { + console.warn(' 输入的json语句格式有误') } - onChange(res) } - - return ( -
- -
- ) + onChange(res) } -) + + return ( +
+ +
+ ) +}) diff --git a/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomDialogComponent.tsx b/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomDialogComponent.tsx index c7bdd611..13d9301f 100644 --- a/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomDialogComponent.tsx +++ b/frontend/packages/common/src/components/aoplatform/formily2-customize/CustomDialogComponent.tsx @@ -1,5 +1,4 @@ - -import {forwardRef,useImperativeHandle} from 'react' +import { forwardRef, useImperativeHandle } from 'react' import { createSchemaField } from '@formily/react' import { FormItem, @@ -82,57 +81,53 @@ const SchemaField = createSchemaField({ } }) -export const CustomDialogComponent = forwardRef( - (props: { [k: string]: unknown }, ref) => { - const { onChange, title, value, render } = props - useImperativeHandle(ref, () => ({})) - let editPage: boolean = false - try { - editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0 - } catch {} +export const CustomDialogComponent = forwardRef((props: { [k: string]: unknown }, ref) => { + const { onChange, title, value, render } = props + useImperativeHandle(ref, () => ({})) + let editPage: boolean = false + try { + editPage = Object.keys(JSON.parse(JSON.stringify(value))).length > 0 + } catch {} - return ( - - { - const dialog = FormDialog( - editPage ? $t('编辑(0)',[title||'']) : $t('添加(0)',[title||'']), - () => { - return ( - - - - ) - } + return ( + + { + const dialog = FormDialog(editPage ? $t('编辑(0)', [title || '']) : $t('添加(0)', [title || '']), () => { + return ( + + + ) - dialog - .forOpen((payload, next) => { - next({ - initialValues: value - }) + }) + dialog + .forOpen((payload, next) => { + next({ + initialValues: value }) - .forConfirm((payload, next) => { - next(payload) - }) - .forCancel((payload, next) => { - next(payload) - }) - .open() - .then(onChange) - }} - > - - - - - - ) - } -) + }) + .forConfirm((payload, next) => { + next(payload) + }) + .forCancel((payload, next) => { + next(payload) + }) + .open() + .then(onChange) + }} + > + + + + + + ) +}) diff --git a/frontend/packages/common/src/components/aoplatform/formily2-customize/SimpleMapComponent.tsx b/frontend/packages/common/src/components/aoplatform/formily2-customize/SimpleMapComponent.tsx index 926a47d1..0408ea81 100644 --- a/frontend/packages/common/src/components/aoplatform/formily2-customize/SimpleMapComponent.tsx +++ b/frontend/packages/common/src/components/aoplatform/formily2-customize/SimpleMapComponent.tsx @@ -1,104 +1,99 @@ -import {forwardRef, useImperativeHandle, useState} from 'react' +import { forwardRef, useImperativeHandle, useState } from 'react' import { Input } from '@formily/antd-v5' import { $t } from '@common/locales' import { Icon } from '@iconify/react/dist/iconify.js' -export const SimpleMapComponent = forwardRef( - (props: { [k: string]: unknown }, ref) => { - const { - onChange, - value, - placeholderKey = $t('请输入Key'), - placeholderValue = $t('请输入Value') - } = props +export const SimpleMapComponent = forwardRef((props: { [k: string]: unknown }, ref) => { + const { onChange, value, placeholderKey = $t('请输入Key'), placeholderValue = $t('请输入Value') } = props - const [kvList, setKvList] = useState( - value && Object.keys(value).length > 0 - ? [ - ...Object.keys(value)?.map((k: string) => { - return { key: k, value: value[k] } - }), - { key: '', value: '' } - ] - : [{ key: '', value: '' }] - ) + const [kvList, setKvList] = useState( + value && Object.keys(value).length > 0 + ? [ + ...Object.keys(value)?.map((k: string) => { + return { key: k, value: value[k] } + }), + { key: '', value: '' } + ] + : [{ key: '', value: '' }] + ) - useImperativeHandle(ref, () => ({})) + useImperativeHandle(ref, () => ({})) - const emitNewArr = () => { - const res: { [k: string]: unknown } = {} - for (const kv of kvList) { - res[kv.key] = kv.value - } - onChange(res) + const emitNewArr = () => { + const res: { [k: string]: unknown } = {} + for (const kv of kvList) { + res[kv.key] = kv.value } - - const changeInputValue = ( - newValue: string, - index: number, - type: 'key' | 'value' - ) => { - const newArr = [...kvList] - newArr[index][type] = newValue - setKvList(newArr) - emitNewArr() - if (index === kvList.length - 1) { - setKvList([...newArr, { key: '', value: '' }]) - } - } - - const addLine = (index: number) => { - kvList.splice(index + 1, 0, { key: '', value: '' }) - const newKvList = [...kvList] - setKvList(newKvList) - emitNewArr() - } - - const removeLine = (index: number) => { - kvList.splice(index, 1) - const newKvList = [...kvList] - setKvList(newKvList) - emitNewArr() - } - - return ( -
- {kvList?.map((n: unknown, index: unknown) => { - return ( -
- { - changeInputValue(e.target.value, index, 'key') - }} - placeholder={placeholderKey} - /> - { - changeInputValue(e.target.value, index, 'value') - }} - placeholder={placeholderValue} - /> - {index !== kvList.length - 1 && ( -
- {n.key && ( - addLine(index as unknown as number)} width="14" height="14"/> - )} - removeLine(index as unknown as number)} width="14" height="14"/> -
- )} -
- ) - })} -
- ) + onChange(res) } -) + + const changeInputValue = (newValue: string, index: number, type: 'key' | 'value') => { + const newArr = [...kvList] + newArr[index][type] = newValue + setKvList(newArr) + emitNewArr() + if (index === kvList.length - 1) { + setKvList([...newArr, { key: '', value: '' }]) + } + } + + const addLine = (index: number) => { + kvList.splice(index + 1, 0, { key: '', value: '' }) + const newKvList = [...kvList] + setKvList(newKvList) + emitNewArr() + } + + const removeLine = (index: number) => { + kvList.splice(index, 1) + const newKvList = [...kvList] + setKvList(newKvList) + emitNewArr() + } + + return ( +
+ {kvList?.map((n: unknown, index: unknown) => { + return ( +
+ { + changeInputValue(e.target.value, index, 'key') + }} + placeholder={placeholderKey} + /> + { + changeInputValue(e.target.value, index, 'value') + }} + placeholder={placeholderValue} + /> + {index !== kvList.length - 1 && ( +
+ {n.key && ( + addLine(index as unknown as number)} + width="14" + height="14" + /> + )} + removeLine(index as unknown as number)} + width="14" + height="14" + /> +
+ )} +
+ ) + })} +
+ ) +}) diff --git a/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginConfig.tsx b/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginConfig.tsx index aa3c3e8a..217677ba 100644 --- a/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginConfig.tsx +++ b/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginConfig.tsx @@ -1,327 +1,333 @@ -import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react"; +import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react' import { action } from '@formily/reactive' import { - FormItem, - Space, - ArrayItems, - DatePicker, - Editable, - FormButtonGroup, - Input, - Radio, - Select, - Submit, - Cascader, - Form, - FormGrid, - FormLayout, - Upload, + FormItem, + Space, + ArrayItems, + DatePicker, + Editable, + FormButtonGroup, + Input, + Radio, + Select, + Submit, + Cascader, + Form, + FormGrid, + FormLayout, + Upload, + ArrayCollapse, + ArrayTable, + ArrayTabs, + Checkbox, + FormCollapse, + FormDialog, + FormDrawer, + FormStep, + FormTab, + NumberPicker, + Password, + PreviewText, + Reset, + SelectTable, + Switch, + TimePicker, + Transfer, + TreeSelect, + ArrayCards +} from '@formily/antd-v5' +import { createForm } from '@formily/core' +import { CustomCodeboxComponent } from '@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx' +import { SimpleMapComponent } from '@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx' +import { CustomDialogComponent } from '@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx' +import { ArrayItemBlankComponent } from '@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx' +import { DefaultOptionType } from 'antd/es/cascader' +import { createSchemaField, FormProvider, RecursionField, useField, useForm } from '@formily/react' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { App } from 'antd' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { setValidateLanguage } from '@formily/core' + +export const DynamicRender = (props) => { + const { schema } = props + const field = useField() + const form = useForm() + const [renderSchema, setRenderSchema] = useState({}) + const { state } = useGlobalContext() + + useEffect(() => { + form.clearFormGraph(`${field.address}.*`) + try { + const parsedSchema = JSON.parse(schema) + setRenderSchema(parsedSchema[form?.values?.driver]) + } catch (e) { + console.error('渲染出错', e?.message) + } + }, [form.values.driver]) + + const translateSchema = (render) => { + const res1 = { + ...render, + ...(render.title ? { title: $t(render.title) } : {}), + ...(render.description ? { description: $t(render.description) } : {}), + ...(render.label ? { label: $t(render.label) } : {}), + ...(render.properties + ? { + properties: Object.keys(render.properties).reduce((total, cur) => { + try { + total[cur] = translateSchema(render.properties[cur]) + } catch (error) { + console.error(`Error translating schema for property ${cur}:`, error) + } + return total + }, {}) + } + : {}), + ...(render.items && Array.isArray(render.items) ? { items: render.items.map((x) => translateSchema(x)) } : {}), + ...(render.items && !Array.isArray(render.items) ? { items: translateSchema(render.items) } : {}), + ...(render.additionalProperties ? { additionalProperties: translateSchema(render.additionalProperties) } : {}), + ...(render.enum ? { enum: render.enum.map((x) => ({ ...x, label: $t(x.label) })) } : {}) + } + return res1 + } + + const translatedRenderSchema = useMemo(() => { + const res = renderSchema && translateSchema(renderSchema) + return res + }, [state.language, renderSchema]) + + return +} + +export type IntelligentPluginConfigProps = { + type: 'add' | 'edit' + renderSchema: unknown + tabData: DefaultOptionType[] + moduleId: string + driverSelectionOptions: DefaultOptionType[] + entityId?: string + initFormValue: { [k: string]: unknown } +} + +export type IntelligentPluginConfigHandle = { + save: () => Promise +} + +const SchemaField = createSchemaField({ + components: { + ArrayCards, ArrayCollapse, + ArrayItems, ArrayTable, ArrayTabs, + Cascader, Checkbox, + DatePicker, + Editable, + Form, + FormButtonGroup, FormCollapse, + // @ts-ignore FormDialog, + // @ts-ignore FormDrawer, + FormGrid, + FormItem, + FormLayout, FormStep, FormTab, + Input, NumberPicker, Password, PreviewText, + Radio, Reset, + Select, SelectTable, + Space, + Submit, Switch, TimePicker, Transfer, TreeSelect, - ArrayCards -} from '@formily/antd-v5' -import { createForm } from '@formily/core' -import {CustomCodeboxComponent} from "@common/components/aoplatform/formily2-customize/CustomCodeboxComponent.tsx"; -import {SimpleMapComponent} from "@common/components/aoplatform/formily2-customize/SimpleMapComponent.tsx"; -import {CustomDialogComponent} from "@common/components/aoplatform/formily2-customize/CustomDialogComponent.tsx"; -import {ArrayItemBlankComponent} from "@common/components/aoplatform/formily2-customize/ArrayItemBlankComponent.tsx"; -import {DefaultOptionType} from "antd/es/cascader"; -import {createSchemaField, FormProvider, RecursionField, useField, useForm} from "@formily/react"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {App, Descriptions} from "antd"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { setValidateLanguage } from '@formily/core' - -export const DynamicRender = (props) => { - const {schema} = props - const field = useField() - const form = useForm() - const [renderSchema, setRenderSchema] = useState({}) - const {state} = useGlobalContext() - - useEffect(() => { - form.clearFormGraph(`${field.address}.*`) - try{ - const parsedSchema = JSON.parse(schema) - setRenderSchema(parsedSchema[form?.values?.driver]) - }catch(e){ - console.error('渲染出错',e?.message) - } - }, [form.values.driver]) - - - const translateSchema = (render) =>{ - const res1 = { - ...render, - ...(render.title ? {title:$t(render.title)} : {}), - ...(render.description) ? {description:$t(render.description)} : {}, - ...(render.label ? {label:$t(render.label)} : {}), - ...(render.properties ? {properties: Object.keys(render.properties).reduce((total, cur) => { - try { - total[cur] = translateSchema(render.properties[cur]); - } catch (error) { - console.error(`Error translating schema for property ${cur}:`, error); - } - return total; - }, {})} : {}), - ...(render.items && Array.isArray(render.items) ? {items:render.items.map(x=>translateSchema(x))} : {}), - ...(render.items && !Array.isArray(render.items) ? {items:translateSchema(render.items)} : {}), - ...(render.additionalProperties ? {additionalProperties: translateSchema(render.additionalProperties)} : {}), - ...(render.enum ? {enum: render.enum.map(x=>({...x, label:$t(x.label)}))} : {}), - - } - return res1 - } - - const translatedRenderSchema = useMemo(()=>{ - const res = renderSchema && translateSchema(renderSchema) - return res - },[state.language,renderSchema]) - - return ( - - ) -} - - - -export type IntelligentPluginConfigProps = { - type:'add'|'edit' - renderSchema:unknown - tabData:DefaultOptionType[] - moduleId:string - driverSelectionOptions:DefaultOptionType[] - entityId?:string - initFormValue:{[k:string]:unknown} -} - -export type IntelligentPluginConfigHandle = { - save:()=>Promise -} - -const SchemaField = createSchemaField({ - components: { - ArrayCards, - ArrayCollapse, - ArrayItems, - ArrayTable, - ArrayTabs, - Cascader, - Checkbox, - DatePicker, - Editable, - Form, - FormButtonGroup, - FormCollapse, - // @ts-ignore - FormDialog, - // @ts-ignore - FormDrawer, - FormGrid, - FormItem, - FormLayout, - FormStep, - FormTab, - Input, - NumberPicker, - Password, - PreviewText, - Radio, - Reset, - Select, - SelectTable, - Space, - Submit, - Switch, - TimePicker, - Transfer, - TreeSelect, - Upload, - CustomCodeboxComponent, - SimpleMapComponent, - CustomDialogComponent, - ArrayItemBlankComponent, - DynamicRender - } + Upload, + CustomCodeboxComponent, + SimpleMapComponent, + CustomDialogComponent, + ArrayItemBlankComponent, + DynamicRender + } }) -export const IntelligentPluginConfig = forwardRef((props,ref)=>{ - const { type,renderSchema,moduleId,driverSelectionOptions,initFormValue} = props +export const IntelligentPluginConfig = forwardRef( + (props, ref) => { + const { type, renderSchema, moduleId, driverSelectionOptions, initFormValue } = props const { message } = App.useApp() - const {fetchData} = useFetch() + const { fetchData } = useFetch() const form = createForm({ validateFirst: type === 'edit' }) form.setInitialValues(initFormValue || {}) const { state } = useGlobalContext() - useEffect(()=>{ - setValidateLanguage(state.language) - },[state.language]) + useEffect(() => { + setValidateLanguage(state.language) + }, [state.language]) const pluginEditSchema = { - type: 'object', - properties: { - layout: { - type: 'void', - 'x-component': 'FormLayout', - 'x-component-props': { - labelCol: 6, - wrapperCol: 10, - layout: 'vertical', + type: 'object', + properties: { + layout: { + type: 'void', + 'x-component': 'FormLayout', + 'x-component-props': { + labelCol: 6, + wrapperCol: 10, + layout: 'vertical' + }, + properties: { + id: { + type: 'string', + title: $t('ID'), + required: true, + pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelCol: 4, + wrapperCol: 20, + labelAlign: 'left' }, - properties: { - id: { - type: 'string', - title: $t('ID'), - required: true, - pattern: /^[a-zA-Z][a-zA-Z0-9-_]*$/, - 'x-decorator': 'FormItem', - 'x-decorator-props': { - labelCol:4, - wrapperCol: 20, - labelAlign:'left' - }, - 'x-component': 'Input', - 'x-component-props': { - placeholder: $t(PLACEHOLDER.specialStartWithAlphabet), - }, - 'x-disabled': type === 'edit' - }, - title: { - type: 'string', - title: $t('名称'), - required: true, - 'x-decorator': 'FormItem', - 'x-decorator-props': { - labelCol:4, - wrapperCol: 20, - labelAlign:'left' - }, - 'x-component': 'Input', - 'x-component-props': { - placeholder: $t(PLACEHOLDER.input), - } - }, - driver: { - type: 'string', - title: $t('Driver'), - required: true, - 'x-decorator': 'FormItem', - 'x-decorator-props': { - labelCol:4, - wrapperCol: 20, - labelAlign:'left' - }, - 'x-component': 'Select', - 'x-component-props': { - disabled: type === 'edit' - }, - 'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden', - enum: [...driverSelectionOptions] - }, - description: { - type: 'string', - title: $t('描述'), - 'x-decorator': 'FormItem', - 'x-decorator-props': { - labelCol:4, - wrapperCol: 20, - labelAlign:'left' - }, - 'x-component': 'Input.TextArea', - 'x-component-props': { - placeholder: $t(PLACEHOLDER.input), - } - }, - config: { - type: 'object', - 'x-component': 'DynamicRender', - 'x-component-props': { - schema: JSON.stringify(renderSchema), - } - } + 'x-component': 'Input', + 'x-component-props': { + placeholder: $t(PLACEHOLDER.specialStartWithAlphabet) + }, + 'x-disabled': type === 'edit' + }, + title: { + type: 'string', + title: $t('名称'), + required: true, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelCol: 4, + wrapperCol: 20, + labelAlign: 'left' + }, + 'x-component': 'Input', + 'x-component-props': { + placeholder: $t(PLACEHOLDER.input) + } + }, + driver: { + type: 'string', + title: $t('Driver'), + required: true, + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelCol: 4, + wrapperCol: 20, + labelAlign: 'left' + }, + 'x-component': 'Select', + 'x-component-props': { + disabled: type === 'edit' + }, + 'x-display': driverSelectionOptions.length > 1 ? 'visible' : 'hidden', + enum: [...driverSelectionOptions] + }, + description: { + type: 'string', + title: $t('描述'), + 'x-decorator': 'FormItem', + 'x-decorator-props': { + labelCol: 4, + wrapperCol: 20, + labelAlign: 'left' + }, + 'x-component': 'Input.TextArea', + 'x-component-props': { + placeholder: $t(PLACEHOLDER.input) + } + }, + config: { + type: 'object', + 'x-component': 'DynamicRender', + 'x-component-props': { + schema: JSON.stringify(renderSchema) + } } + } } - } -} - - - const save :()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validate().then(()=>{ - fetchData>(type === 'add'?`dynamic/${moduleId}`:`dynamic/${moduleId}/config`,{method:type === 'add'? 'POST' : 'PUT',eoBody:form.values, eoParams:{...(type !== 'add' && {id:initFormValue.id})}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo:unknown)=> reject(errorInfo)) - }) + } } - useImperativeHandle(ref, ()=>({ - save - }) - ) + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validate() + .then(() => { + fetchData>(type === 'add' ? `dynamic/${moduleId}` : `dynamic/${moduleId}/config`, { + method: type === 'add' ? 'POST' : 'PUT', + eoBody: form.values, + eoParams: { ...(type !== 'add' && { id: initFormValue.id }) } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo: unknown) => reject(errorInfo)) + }) + } + useImperativeHandle(ref, () => ({ + save + })) const getSkillData = async (skill: string) => { - return new Promise((resolve,reject) => { - fetchData}>>(`api/common/provider/${skill}`,{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - resolve(data[skill]?.map((x:{name:string,title:string})=>{return{label:x.title, value:x.name}}) || []) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }) + return new Promise((resolve, reject) => { + fetchData }>>( + `api/common/provider/${skill}`, + { method: 'GET' } + ).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + resolve( + data[skill]?.map((x: { name: string; title: string }) => { + return { label: x.title, value: x.name } + }) || [] + ) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) + }) } - const useAsyncDataSource = - (service: unknown, skill: string) => (field: unknown) => { - field.loading = true - service(skill).then( - action.bound && - action.bound((data: unknown) => { - field.dataSource = data - field.loading = false - }) - ) - } + const useAsyncDataSource = (service: unknown, skill: string) => (field: unknown) => { + field.loading = true + service(skill).then( + action.bound && + action.bound((data: unknown) => { + field.dataSource = data + field.loading = false + }) + ) + } return ( -
- - - -
) -}) \ No newline at end of file +
+ + + +
+ ) + } +) diff --git a/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx b/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx index 4232e75d..4d1a3c49 100644 --- a/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx +++ b/frontend/packages/common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx @@ -1,349 +1,434 @@ -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"; -import {App, Divider, Spin} from "antd"; -import {useEffect, useMemo, useRef, useState} from "react"; -import { useLocation, useOutletContext, useParams} from "react-router-dom"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {ActionType, ParamsType} from "@ant-design/pro-components"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import {DefaultOptionType} from "antd/es/cascader"; -import {IntelligentPluginConfig, IntelligentPluginConfigHandle} from "./IntelligentPluginConfig.tsx"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {EntityItem} from "@common/const/type.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx"; -import { LoadingOutlined } from "@ant-design/icons"; -import { $t } from "@common/locales/index.ts"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; +import { LoadingOutlined } from '@ant-design/icons' +import { ActionType, ParamsType } from '@ant-design/pro-components' +import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter.tsx' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { EntityItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { App, Divider, Spin } from 'antd' +import { DefaultOptionType } from 'antd/es/cascader' +import { useEffect, useMemo, useRef, useState } from 'react' +import { useLocation, useOutletContext, useParams } from 'react-router-dom' +import { IntelligentPluginConfig, IntelligentPluginConfigHandle } from './IntelligentPluginConfig.tsx' - type DynamicTableField = { - name: string, - title: string, - attr: string, - enum: Array +type DynamicTableField = { + name: string + title: string + attr: string + enum: Array } - type DynamicDriverData = { - name:string, title:string +type DynamicDriverData = { + name: string + title: string } export type DynamicTableConfig = { - basic:{ - id:string, - name: string, - title: string, - drivers: Array, - fields: Array, - } - list: Array, - total:number + basic: { + id: string + name: string + title: string + drivers: Array + fields: Array + } + list: Array + total: number } export type DynamicRender = { - render:unknown, - basic:{ - id:string, - name:string, - title:string - } + render: unknown + basic: { + id: string + name: string + title: string + } } export type DynamicPublishCluster = { - name:string, - title:string, - status:string, - updater:EntityItem, - update_time:string, - checked?:boolean + name: string + title: string + status: string + updater: EntityItem + update_time: string + checked?: boolean } export type DynamicPublishData = { - id:string, - name:string, - title:string, - description:string - clusters:DynamicPublishCluster[] + id: string + name: string + title: string + description: string + clusters: DynamicPublishCluster[] } -export type DynamicTableItem = {[k:string]:unknown} +export type DynamicTableItem = { [k: string]: unknown } export const StatusColorClass = { - "已发布":'text-[#03a9f4]', - "待发布":'text-[#46BE11]', - "未发布":'text-[#03a9f4]' + 已发布: 'text-[#03a9f4]', + 待发布: 'text-[#46BE11]', + 未发布: 'text-[#03a9f4]' } - export type DynamicPublish = { - code:number, - msg:string, - data:{ - success:Array, - fail:Array - } + code: number + msg: string + data: { + success: Array + fail: Array + } } -export default function IntelligentPluginList(){ - const { modal,message } = App.useApp() - const [searchWord, setSearchWord] = useState('') - const { moduleId } = useParams(); - const [pluginName,setPluginName] = useState('-') - const [partitionOptions] = useState([{label:'default', value:'default'}]) - const { setBreadcrumb } = useBreadcrumb() - const [renderSchema ,setRenderSchema] = useState<{[k:string]:unknown}>({}) - const drawerFormRef = useRef(null); - const [driverOptions, setDriverOptions] = useState([]) - const [tableListDataSource, setTableListDataSource] = useState([]); +export default function IntelligentPluginList() { + const { modal, message } = App.useApp() + const [searchWord, setSearchWord] = useState('') + const { moduleId } = useParams() + const [pluginName, setPluginName] = useState('-') + const [partitionOptions] = useState([{ label: 'default', value: 'default' }]) + const { setBreadcrumb } = useBreadcrumb() + const [renderSchema, setRenderSchema] = useState<{ [k: string]: unknown }>({}) + const drawerFormRef = useRef(null) + const [driverOptions, setDriverOptions] = useState([]) + const [tableListDataSource, setTableListDataSource] = useState([]) - const [tableHttpReload, setTableHttpReload] = useState(true); - const [columns,setColumns] = useState([]) - const {fetchData} = useFetch() - const pageListRef = useRef(null); - const [publishBtnLoading, setPublishBtnLoading] = useState(false) - const [curDetail,setCurDetail] = useState<{[k: string]: unknown;}|undefined>() - const [drawerType, setDrawerType] = useState<'add'|'edit'>('add') - const [drawerOpen, setDrawerOpen] = useState(false) - const [drawerLoading, setDrawerLoading] = useState(false) - const location = useLocation().pathname - const {accessPrefix} = useOutletContext<{accessPrefix:string}>() - const {state} = useGlobalContext() + const [tableHttpReload, setTableHttpReload] = useState(true) + const [columns, setColumns] = useState([]) + const { fetchData } = useFetch() + const pageListRef = useRef(null) + const [publishBtnLoading, setPublishBtnLoading] = useState(false) + const [curDetail, setCurDetail] = useState<{ [k: string]: unknown } | undefined>() + const [drawerType, setDrawerType] = useState<'add' | 'edit'>('add') + const [drawerOpen, setDrawerOpen] = useState(false) + const [drawerLoading, setDrawerLoading] = useState(false) + const location = useLocation().pathname + const { accessPrefix } = useOutletContext<{ accessPrefix: string }>() + const { state } = useGlobalContext() - - const getIntelligentPluginTableList=(params:ParamsType & { - pageSize?: number | undefined; - current?: number | undefined; - keyword?: string | undefined; - }): Promise<{ data: DynamicTableItem[], success: boolean }>=> { - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); + const getIntelligentPluginTableList = ( + params: ParamsType & { + pageSize?: number | undefined + current?: number | undefined + keyword?: string | undefined + } + ): Promise<{ data: DynamicTableItem[]; success: boolean }> => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) + } + const query = { + page: params.current, + pageSize: params.pageSize, + keyword: searchWord + } + return fetchData>(`dynamic/${moduleId}/list`, { + method: 'GET', + eoParams: query, + eoTransformKeys: ['pageSize'] + }) + .then((res) => { + message.destroy() + if (res.code === STATUS_CODE.SUCCESS) { + getConfig(res.data) + setColumns(res.data.basic.fields) + setTableListDataSource(res.data.list) + return { data: res.data.list, success: true, total: res.data.total } + } else { + setTableListDataSource([]) + return { data: [], success: false } } - const query = { - page:params.current, - pageSize:params.pageSize, - keyword:searchWord, - } - return fetchData>( - `dynamic/${moduleId}/list`, - {method:'GET',eoParams:query,eoTransformKeys:['pageSize']}).then((res)=>{ - message.destroy(); - if(res.code === STATUS_CODE.SUCCESS){ - getConfig(res.data) - setColumns(res.data.basic.fields) - setTableListDataSource(res.data.list); - return ({ data: res.data.list, success: true,total:res.data.total }); - }else{ - setTableListDataSource([]); - return ({ data: [], success: false }); - } - }).catch((e)=>{console.warn(e); - return ({ data: [], success: false });}) - } + }) + .catch((e) => { + console.warn(e) + return { data: [], success: false } + }) + } - const translatedCol = useMemo(()=>columns.map((field:DynamicTableField, index:number)=>({ - title: typeof field.title === 'string' ? $t(field.title as string): field.title, - dataIndex:field.name, - fixed:field.name === 'title' ? 'left' : undefined, - ellipsis:true, - width:field.name === 'title' ? 150 : undefined, - ...(field.enum?.length > 0 ?{ - onFilter: (value: string, record: { [x: string]: string | string[]; }) => record[field.name].indexOf(value) === 0, - filters:field.enum?.map((x:string)=>{return {text:$t(x), value:x}}), - render:(_: unknown, entity: { [x: string]: string; })=> { - return {$t(entity[field.name] as string)} - }, - }:{}), - })),[state.language,columns]) - - - - const getConfig = (data:DynamicTableConfig)=>{ - const {basic,list } = data - const {title,drivers} = basic - - setBreadcrumb([ - {title:location.includes('resourcesettings') ? $t('资源'): $t('日志')}, - { - title - } - ]) - - setPluginName(title) - setDriverOptions(drivers?.map((driver:DynamicDriverData) => { - return { label: driver.title, value: driver.name } - }) || []) - - } - - const getRender = ()=>{ - return fetchData>(`dynamic/${moduleId}/render`,{method:'GET'}).then((resp) => { - if (resp.code === STATUS_CODE.SUCCESS) { - setRenderSchema(resp.data.render) - return Promise.resolve(resp.data.render) - } - return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error)) - }) - } - - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - fixed:'right', - valueType: 'option', - btnNums:3, - render: (_: React.ReactNode, entity: DynamicTableItem) => [ - {openModal('publish',entity)}} btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')}/>, - , - {openDrawer('edit',entity)}} btnTitle={$t("查看")}/>, - , - {openModal('delete',entity)}} btnTitle={$t("删除")}/>, - ], - } - ] - const handleClusterChange = (e:string[])=>{ - setTableHttpReload(true) - pageListRef.current?.reload() - } - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - const deleteInstance = (entity:DynamicTableItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>(`dynamic/${moduleId}/batch`,{method:'DELETE',eoParams:{ids:JSON.stringify([entity!.id])}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }) - }) - } - - const openDrawer = async (type:'add'|'edit', entity?:DynamicTableItem)=>{ - switch (type){ - case 'add': - setCurDetail({driver:driverOptions[0].value || '',config:{}}) - break; - case 'edit':{ - setDrawerLoading(true) - fetchData>( - `dynamic/${moduleId}/info`, - {method:'GET',eoParams:{id:entity!.id}}).then((res)=>{ - const {code, data, msg } = res - if(code === STATUS_CODE.SUCCESS){ - if(data.info.config){ - } - setCurDetail(data.info) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>setDrawerLoading(false)) - break; - } - } - setDrawerType(type) - setDrawerOpen(true) - } - - const openModal = async (type:'publish'|'delete', entity?:DynamicTableItem)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'publish':{ - message.loading($t(RESPONSE_TIPS.operating)) - await fetchData>(`dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline':'online'}`, { - method: 'PUT', - eoParams:{id:entity!.id}, - }).then(response => { - const {code, msg} = response - if (code === STATUS_CODE.SUCCESS) { - message.success(msg || $t(RESPONSE_TIPS.success)) - return Promise.resolve(true) - } else { - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> Promise.reject(errorInfo)) - message.destroy() - return;} - case 'delete': - title='删除' - content={$t(DELETE_TIPS.default)} - break; - } - - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ // case 'publish': - // return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) - case 'delete': - return deleteInstance(entity!).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width: type === 'delete'? 600 : 900, - okText:$t('确认'), - okButtonProps:{ - disabled:false - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - footer:(_, { OkBtn, CancelBtn }) =>{ + const translatedCol = useMemo( + () => + columns.map((field: DynamicTableField, index: number) => ({ + title: typeof field.title === 'string' ? $t(field.title as string) : field.title, + dataIndex: field.name, + fixed: field.name === 'title' ? 'left' : undefined, + ellipsis: true, + width: field.name === 'title' ? 150 : undefined, + ...(field.enum?.length > 0 + ? { + onFilter: (value: string, record: { [x: string]: string | string[] }) => + record[field.name].indexOf(value) === 0, + filters: field.enum?.map((x: string) => { + return { text: $t(x), value: x } + }), + render: (_: unknown, entity: { [x: string]: string }) => { return ( - <> - - - - ); - }, + + {$t(entity[field.name] as string)} + + ) + } + } + : {}) + })), + [state.language, columns] + ) + + const getConfig = (data: DynamicTableConfig) => { + const { basic, list } = data + const { title, drivers } = basic + + setBreadcrumb([ + { title: location.includes('resourcesettings') ? $t('资源') : $t('日志') }, + { + title + } + ]) + + setPluginName(title) + setDriverOptions( + drivers?.map((driver: DynamicDriverData) => { + return { label: driver.title, value: driver.name } + }) || [] + ) + } + + const getRender = () => { + return fetchData>(`dynamic/${moduleId}/render`, { method: 'GET' }).then((resp) => { + if (resp.code === STATUS_CODE.SUCCESS) { + setRenderSchema(resp.data.render) + return Promise.resolve(resp.data.render) + } + return Promise.reject(resp.msg || $t(RESPONSE_TIPS.error)) + }) + } + + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + fixed: 'right', + valueType: 'option', + btnNums: 3, + render: (_: React.ReactNode, entity: DynamicTableItem) => [ + { + openModal('publish', entity) + }} + btnTitle={entity.status === $t('已发布') ? $t('下线') : $t('上线')} + />, + , + { + openDrawer('edit', entity) + }} + btnTitle={$t('查看 ')} + />, + , + { + openModal('delete', entity) + }} + btnTitle={$t('删除')} + /> + ] + } + ] + const handleClusterChange = (e: string[]) => { + setTableHttpReload(true) + pageListRef.current?.reload() + } + + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } + + const deleteInstance = (entity: DynamicTableItem) => { + return new Promise((resolve, reject) => { + fetchData>(`dynamic/${moduleId}/batch`, { + method: 'DELETE', + eoParams: { ids: JSON.stringify([entity!.id]) } + }).then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + }) + } + + const openDrawer = async (type: 'add' | 'edit', entity?: DynamicTableItem) => { + switch (type) { + case 'add': + setCurDetail({ driver: driverOptions[0].value || '', config: {} }) + break + case 'edit': { + setDrawerLoading(true) + fetchData>(`dynamic/${moduleId}/info`, { + method: 'GET', + eoParams: { id: entity!.id } }) + .then((res) => { + const { code, data, msg } = res + if (code === STATUS_CODE.SUCCESS) { + if (data.info.config) { + } + setCurDetail(data.info) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => setDrawerLoading(false)) + break + } + } + setDrawerType(type) + setDrawerOpen(true) + } + + const openModal = async (type: 'publish' | 'delete', entity?: DynamicTableItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'publish': { + message.loading($t(RESPONSE_TIPS.operating)) + await fetchData>( + `dynamic/${moduleId}/${entity!.status === $t('已发布') ? 'offline' : 'online'}`, + { + method: 'PUT', + eoParams: { id: entity!.id } + } + ) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + manualReloadTable() + return Promise.resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => Promise.reject(errorInfo)) + return + } + case 'delete': + title = '删除' + content = {$t(DELETE_TIPS.default)} + break } - useEffect(() => { - getRender() - pageListRef.current?.reload() - }, [moduleId]); + modal.confirm({ + title, + content, + onOk: () => { + switch ( + type // case 'publish': + ) { + // return editRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) + case 'delete': + return deleteInstance(entity!).then((res) => { + if (res === true) manualReloadTable() + }) + } + }, + width: type === 'delete' ? 600 : 900, + okText: $t('确认'), + okButtonProps: { + disabled: false + }, + cancelText: $t('取消'), + closable: true, + icon: <>, + footer: (_, { OkBtn, CancelBtn }) => { + return ( + <> + + + + + + + + ) + } + }) + } + useEffect(() => { + getRender() + pageListRef.current?.reload() + }, [moduleId]) - return (<> - getIntelligentPluginTableList(params)} - addNewBtnTitle={$t('添加(0)',[$t(pluginName)])} - searchPlaceholder={$t('搜索(0)名称',[$t(pluginName)])} - onChange={() => { - setTableHttpReload(false) - }} - addNewBtnAccess={`${accessPrefix}.add`} - onAddNewBtnClick={()=>{openDrawer('add')}} - onSearchWordChange={(e)=>{setSearchWord(e.target.value);setTableHttpReload(true);setTableHttpReload(true)}} - /> - - {setCurDetail(undefined);setDrawerOpen(false)}} onSubmit={()=>drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} submitAccess=''> - } spinning={drawerLoading}> - - - - ) -} \ No newline at end of file + return ( + <> + getIntelligentPluginTableList(params)} + addNewBtnTitle={$t('添加(0)', [$t(pluginName)])} + searchPlaceholder={$t('搜索(0)名称', [$t(pluginName)])} + onChange={() => { + setTableHttpReload(false) + }} + addNewBtnAccess={`${accessPrefix}.add`} + onAddNewBtnClick={() => { + openDrawer('add') + }} + onSearchWordChange={(e) => { + setSearchWord(e.target.value) + setTableHttpReload(true) + setTableHttpReload(true) + }} + /> + + { + setCurDetail(undefined) + setDrawerOpen(false) + }} + onSubmit={() => + drawerFormRef.current?.save()?.then((res) => { + res && manualReloadTable() + return res + }) + } + submitAccess="" + > + } spinning={drawerLoading}> + + + + + ) +} diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditor.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditor.tsx index fcf85646..8aaaa69c 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditor.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditor.tsx @@ -1,14 +1,9 @@ 'use client' import type { FC } from 'react' -import { useEffect, useMemo } from 'react' -import type { - EditorState, -} from 'lexical' -import { - $getRoot, - TextNode, -} from 'lexical' +import { useEffect } from 'react' +import type { EditorState } from 'lexical' +import { $getRoot, TextNode } from 'lexical' import { CodeNode } from '@lexical/code' import { LexicalComposer } from '@lexical/react/LexicalComposer' import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin' @@ -19,25 +14,13 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin' // import TreeView from './plugins/tree-view' import Placeholder from './plugins/placeholder' import ComponentPickerBlock from './plugins/component-picker-block/index' -import { - ContextBlock, - ContextBlockNode, - ContextBlockReplacementBlock, -} from './plugins/context-block/index' -import { - QueryBlock, - QueryBlockNode, - QueryBlockReplacementBlock, -} from './plugins/query-block/index' -import { - HistoryBlock, - HistoryBlockNode, - HistoryBlockReplacementBlock, -} from './plugins/history-block/index' +import { ContextBlock, ContextBlockNode, ContextBlockReplacementBlock } from './plugins/context-block/index' +import { QueryBlock, QueryBlockNode, QueryBlockReplacementBlock } from './plugins/query-block/index' +import { HistoryBlock, HistoryBlockNode, HistoryBlockReplacementBlock } from './plugins/history-block/index' import { WorkflowVariableBlock, WorkflowVariableBlockNode, - WorkflowVariableBlockReplacementBlock, + WorkflowVariableBlockReplacementBlock } from './plugins/workflow-variable-block/index' import VariableBlock from './plugins/variable-block/index' import VariableValueBlock from './plugins/variable-value-block/index' @@ -52,12 +35,9 @@ import type { HistoryBlockType, QueryBlockType, VariableBlockType, - WorkflowVariableBlockType, + WorkflowVariableBlockType } from './types' -import { - UPDATE_DATASETS_EVENT_EMITTER, - UPDATE_HISTORY_EVENT_EMITTER, -} from './constants' +import { UPDATE_DATASETS_EVENT_EMITTER, UPDATE_HISTORY_EVENT_EMITTER } from './constants' import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext' export type PromptEditorProps = { @@ -97,7 +77,7 @@ const PromptEditor: FC = ({ historyBlock, variableBlock, externalToolBlock, - workflowVariableBlock, + workflowVariableBlock }) => { const { eventEmitter } = useEventEmitterContextContext() const initialConfig = { @@ -107,51 +87,58 @@ const PromptEditor: FC = ({ CustomTextNode, { replace: TextNode, - with: (node: TextNode) => new CustomTextNode(node.__text), + with: (node: TextNode) => new CustomTextNode(node.__text) }, ContextBlockNode, HistoryBlockNode, QueryBlockNode, WorkflowVariableBlockNode, - VariableValueBlockNode, + VariableValueBlockNode ], editorState: textToEditorState(value || ''), onError: (error: Error) => { throw error - }, + } } - + const handleEditorChange = (editorState: EditorState) => { const text = editorState.read(() => { - return $getRoot().getChildren().map(p => p.getTextContent()).join('\n') + return $getRoot() + .getChildren() + .map((p) => p.getTextContent()) + .join('\n') }) - if (onChange) - onChange(text) + if (onChange) onChange(text) } useEffect(() => { eventEmitter?.emit({ type: UPDATE_DATASETS_EVENT_EMITTER, - payload: contextBlock?.datasets, + payload: contextBlock?.datasets } as any) }, [eventEmitter, contextBlock?.datasets]) useEffect(() => { eventEmitter?.emit({ type: UPDATE_HISTORY_EVENT_EMITTER, - payload: historyBlock?.history, + payload: historyBlock?.history } as any) }, [eventEmitter, historyBlock?.history]) return ( -
+
} + contentEditable={ + + } placeholder={} ErrorBoundary={LexicalErrorBoundary} /> = ({ workflowVariableBlock={workflowVariableBlock} /> = ({ externalToolBlock={externalToolBlock} workflowVariableBlock={workflowVariableBlock} /> - { - contextBlock?.show && ( - <> - - - - ) - } - { - queryBlock?.show && ( - <> - - - - ) - } - { - historyBlock?.show && ( - <> - - - - ) - } - { - (variableBlock?.show || externalToolBlock?.show) && ( - <> - - - - ) - } - { - workflowVariableBlock?.show && ( - <> - - - - ) - } + {contextBlock?.show && ( + <> + + + + )} + {queryBlock?.show && ( + <> + + + + )} + {historyBlock?.show && ( + <> + + + + )} + {(variableBlock?.show || externalToolBlock?.show) && ( + <> + + + + )} + {workflowVariableBlock?.show && ( + <> + + + + )} diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditorResizable.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditorResizable.tsx index 24e37ebf..d93491b5 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditorResizable.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/PromptEditorResizable.tsx @@ -1,82 +1,94 @@ +import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx' +import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx' +import { useState } from 'react' +import { getVars } from './utils' +import { VariableItems } from '@core/const/ai-service/type' -import PromptEditor from '@common/components/aoplatform/prompt-editor/PromptEditor.tsx'; -import PromptEditorHeightResizeWrap from '@common/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx'; -import { useEffect, useState } from 'react'; -import { getVars } from './utils'; -import { VariableItems } from '@core/const/ai-service/type'; +const PromptEditorResizable = (props: { + value?: string + onChange?: (value: string) => void + variablesChange?: (keys: string[]) => void + promptVariables: VariableItems[] + disabled?: boolean +}) => { + const { value, onChange, variablesChange, promptVariables, disabled } = props + const minHeight = 68 + const [editorHeight, setEditorHeight] = useState(minHeight) + const [previousKeys, setPreviousKeys] = useState([]) + const handleChange = (newTemplates: string, keys: string[]) => { + onChange?.(newTemplates) + } -const PromptEditorResizable = (props:{value?:string, onChange?:(value:string)=>void, variablesChange?:(keys:string[])=>void,promptVariables:VariableItems[]}) =>{ - const {value , onChange,variablesChange,promptVariables} = props - const minHeight = 68 - const [editorHeight, setEditorHeight] = useState(minHeight) - const [previousKeys, setPreviousKeys] = useState([]) - const handleChange = (newTemplates: string, keys: string[]) => { - onChange?.(newTemplates) + return ( + +
+ {value?.length || 0} +
+
} - - return ( -
{value?.length || 0}
-
+ > + <> + {value !== undefined && ( + ({ + // id: item.id, + // name: item.name, + // type: item.data_source_type, + // })), + // onAddContext: ()=>{console.log('?onAddContext')}, + }} + variableBlock={{ + show: true, + variables: promptVariables?.map((x) => ({ name: x.key, value: x.key })) || [] + }} + externalToolBlock={{ + show: false, + externalTools: [] + // onAddExternalTool: handleOpenExternalDataToolModal, + }} + historyBlock={{ + show: false, + selectable: false, + history: { + user: '', + assistant: '' + }, + onEditRole: () => {} + }} + queryBlock={{ + show: false, + selectable: true + }} + onChange={(value) => { + handleChange?.(value, []) + }} + onBlur={() => { + const keys = getVars(value) + handleChange(value, keys) + if (keys.filter((key) => !previousKeys.includes(key)).length > 0) { + variablesChange?.(keys) + setPreviousKeys(keys) + } + }} + editable={disabled ? false : true} + /> )} - ><> - {value !== undefined && ({ - // id: item.id, - // name: item.name, - // type: item.data_source_type, - // })), - // onAddContext: ()=>{console.log('?onAddContext')}, - }} - variableBlock={{ - show: true, - variables:promptVariables?.map(x=>({name:x.key, value:x.key})) || [], - }} - externalToolBlock={{ - show: false, - externalTools: [], - // onAddExternalTool: handleOpenExternalDataToolModal, - }} - historyBlock={{ - show: false, - selectable: false, - history: { - user: '', - assistant: '', - }, - onEditRole: () => { }, - }} - queryBlock={{ - show: false, - selectable: true, - }} - onChange={(value) => { - handleChange?.(value, []) - }} - onBlur={() => { - const keys = getVars(value) - handleChange(value, keys) - if(keys.filter(key => !previousKeys.includes(key)).length > 0){ - variablesChange?.(keys) - setPreviousKeys(keys) - } - }} - editable={true} - /> - } - ) + + + ) } -export default PromptEditorResizable \ No newline at end of file +export default PromptEditorResizable diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/constants.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/constants.tsx index f7fd5204..4f542729 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/constants.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/constants.tsx @@ -9,40 +9,35 @@ export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update- export const MAX_VAR_KEY_LENGTH = 30 export const checkHasContextBlock = (text: string) => { - if (!text) - return false + if (!text) return false return text.includes(CONTEXT_PLACEHOLDER_TEXT) } export const checkHasHistoryBlock = (text: string) => { - if (!text) - return false + if (!text) return false return text.includes(HISTORY_PLACEHOLDER_TEXT) } export const checkHasQueryBlock = (text: string) => { - if (!text) - return false + if (!text) return false return text.includes(QUERY_PLACEHOLDER_TEXT) } /* -* {{#1711617514996.name#}} => [1711617514996, name] -* {{#1711617514996.sys.query#}} => [sys, query] -*/ + * {{#1711617514996.name#}} => [1711617514996, name] + * {{#1711617514996.sys.query#}} => [sys, query] + */ export const getInputVars = (text: string) => { - if (!text) - return [] + if (!text) return [] const allVars = text.match(/{{#([^#]*)#}}/g) if (allVars && allVars?.length > 0) { // {{#context#}}, {{#query#}} is not input vars const inputVars = allVars - .filter(item => item.includes('.')) + .filter((item) => item.includes('.')) .map((item) => { const valueSelector = item.replace('{{#', '').replace('#}}', '').split('.') - if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0])) - return valueSelector.slice(1) + if (valueSelector[1] === 'sys' && /^\d+$/.test(valueSelector[0])) return valueSelector.slice(1) return valueSelector }) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/hooks.ts b/frontend/packages/common/src/components/aoplatform/prompt-editor/hooks.ts index c9e4cc12..992dfae1 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/hooks.ts +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/hooks.ts @@ -1,16 +1,6 @@ -import { - useCallback, - useEffect, - useRef, - useState, -} from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import type { Dispatch, RefObject, SetStateAction } from 'react' -import type { - Klass, - LexicalCommand, - LexicalEditor, - TextNode, -} from 'lexical' +import type { Klass, LexicalCommand, LexicalEditor, TextNode } from 'lexical' import { $getNodeByKey, $getSelection, @@ -18,12 +8,10 @@ import { $isNodeSelection, COMMAND_PRIORITY_LOW, KEY_BACKSPACE_COMMAND, - KEY_DELETE_COMMAND, + KEY_DELETE_COMMAND } from 'lexical' import type { EntityMatch } from '@lexical/text' -import { - mergeRegister, -} from '@lexical/utils' +import { mergeRegister } from '@lexical/utils' import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { $isContextBlockNode } from './plugins/context-block/node' @@ -35,7 +23,10 @@ import { DELETE_QUERY_BLOCK_COMMAND } from './plugins/query-block' import type { CustomTextNode } from './plugins/custom-text/node' import { registerLexicalTextEntity } from './utils' -export type UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand) => [RefObject, boolean] +export type UseSelectOrDeleteHandler = ( + nodeKey: string, + command?: LexicalCommand +) => [RefObject, boolean] export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, command?: LexicalCommand) => { const ref = useRef(null) const [editor] = useLexicalComposerContext() @@ -46,13 +37,11 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com const selection = $getSelection() const nodes = selection?.getNodes() if ( - !isSelected - && nodes?.length === 1 - && ( - ($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND) - || ($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND) - || ($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND) - ) + !isSelected && + nodes?.length === 1 && + (($isContextBlockNode(nodes[0]) && command === DELETE_CONTEXT_BLOCK_COMMAND) || + ($isHistoryBlockNode(nodes[0]) && command === DELETE_HISTORY_BLOCK_COMMAND) || + ($isQueryBlockNode(nodes[0]) && command === DELETE_QUERY_BLOCK_COMMAND)) ) editor.dispatchCommand(command, undefined) @@ -60,8 +49,7 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com event.preventDefault() const node = $getNodeByKey(nodeKey) if ($isDecoratorNode(node)) { - if (command) - editor.dispatchCommand(command, undefined) + if (command) editor.dispatchCommand(command, undefined) node.remove() return true @@ -70,38 +58,31 @@ export const useSelectOrDelete: UseSelectOrDeleteHandler = (nodeKey: string, com return false }, - [isSelected, nodeKey, command, editor], + [isSelected, nodeKey, command, editor] ) - const handleSelect = useCallback((e: MouseEvent) => { - e.stopPropagation() - clearSelection() - setSelected(true) - }, [setSelected, clearSelection]) + const handleSelect = useCallback( + (e: MouseEvent) => { + e.stopPropagation() + clearSelection() + setSelected(true) + }, + [setSelected, clearSelection] + ) useEffect(() => { const ele = ref.current - if (ele) - ele.addEventListener('click', handleSelect) + if (ele) ele.addEventListener('click', handleSelect) return () => { - if (ele) - ele.removeEventListener('click', handleSelect) + if (ele) ele.removeEventListener('click', handleSelect) } }, [handleSelect]) useEffect(() => { return mergeRegister( - editor.registerCommand( - KEY_DELETE_COMMAND, - handleDelete, - COMMAND_PRIORITY_LOW, - ), - editor.registerCommand( - KEY_BACKSPACE_COMMAND, - handleDelete, - COMMAND_PRIORITY_LOW, - ), + editor.registerCommand(KEY_DELETE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW), + editor.registerCommand(KEY_BACKSPACE_COMMAND, handleDelete, COMMAND_PRIORITY_LOW) ) }, [editor, clearSelection, handleDelete]) @@ -114,17 +95,15 @@ export const useTrigger: UseTriggerHandler = () => { const [open, setOpen] = useState(false) const handleOpen = useCallback((e: MouseEvent) => { e.stopPropagation() - setOpen(v => !v) + setOpen((v) => !v) }, []) useEffect(() => { const trigger = triggerRef.current - if (trigger) - trigger.addEventListener('click', handleOpen) + if (trigger) trigger.addEventListener('click', handleOpen) return () => { - if (trigger) - trigger.removeEventListener('click', handleOpen) + if (trigger) trigger.removeEventListener('click', handleOpen) } }, [handleOpen]) @@ -134,7 +113,7 @@ export const useTrigger: UseTriggerHandler = () => { export function useLexicalTextEntity( getMatch: (text: string) => null | EntityMatch, targetNode: Klass, - createNode: (textNode: CustomTextNode) => T, + createNode: (textNode: CustomTextNode) => T ) { const [editor] = useLexicalComposerContext() @@ -148,24 +127,16 @@ export type MenuTextMatch = { matchingString: string replaceableString: string } -export type TriggerFn = ( - text: string, - editor: LexicalEditor, -) => MenuTextMatch | null +export type TriggerFn = (text: string, editor: LexicalEditor) => MenuTextMatch | null export const PUNCTUATION = '\\.,\\+\\*\\?\\$\\@\\|#{}\\(\\)\\^\\-\\[\\]\\\\/!%\'"~=<>_:;' export function useBasicTypeaheadTriggerMatch( trigger: string, - { minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number }, + { minLength = 1, maxLength = 75 }: { minLength?: number; maxLength?: number } ): TriggerFn { return useCallback( (text: string) => { const validChars = `[${PUNCTUATION}\\s]` - const TypeaheadTriggerRegex = new RegExp( - '(.*)(' - + `[${trigger}]` - + `((?:${validChars}){0,${maxLength}})` - + ')$', - ) + const TypeaheadTriggerRegex = new RegExp('(.*)(' + `[${trigger}]` + `((?:${validChars}){0,${maxLength}})` + ')$') const match = TypeaheadTriggerRegex.exec(text) if (match !== null) { const maybeLeadingWhitespace = match[1] @@ -174,12 +145,12 @@ export function useBasicTypeaheadTriggerMatch( return { leadOffset: match.index + maybeLeadingWhitespace.length, matchingString, - replaceableString: match[2], + replaceableString: match[2] } } } return null }, - [maxLength, minLength, trigger], + [maxLength, minLength, trigger] ) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/hooks.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/hooks.tsx index e09a83ed..b788c8d7 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/hooks.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/hooks.tsx @@ -8,7 +8,7 @@ import type { HistoryBlockType, QueryBlockType, VariableBlockType, - WorkflowVariableBlockType, + WorkflowVariableBlockType } from '../../types' import { INSERT_CONTEXT_BLOCK_COMMAND } from '../context-block' import { INSERT_HISTORY_BLOCK_COMMAND } from '../history-block' @@ -32,32 +32,35 @@ import { $t } from '@common/locales' export const usePromptOptions = ( contextBlock?: ContextBlockType, queryBlock?: QueryBlockType, - historyBlock?: HistoryBlockType, + historyBlock?: HistoryBlockType ) => { const [editor] = useLexicalComposerContext() const promptOptions: PickerBlockMenuOption[] = [] if (contextBlock?.show) { - promptOptions.push(new PickerBlockMenuOption({ - key: $t('上下文'), - group: 'prompt context', - render: ({ isSelected, onSelect, onSetHighlight }) => { - return } - // icon={} - disabled={!contextBlock.selectable} - isSelected={isSelected} - onClick={onSelect} - onMouseEnter={onSetHighlight} - /> - }, - onSelect: () => { - if (!contextBlock?.selectable) - return - editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined) - }, - })) + promptOptions.push( + new PickerBlockMenuOption({ + key: $t('上下文'), + group: 'prompt context', + render: ({ isSelected, onSelect, onSetHighlight }) => { + return ( + } + // icon={} + disabled={!contextBlock.selectable} + isSelected={isSelected} + onClick={onSelect} + onMouseEnter={onSetHighlight} + /> + ) + }, + onSelect: () => { + if (!contextBlock?.selectable) return + editor.dispatchCommand(INSERT_CONTEXT_BLOCK_COMMAND, undefined) + } + }) + ) } if (queryBlock?.show) { @@ -79,11 +82,10 @@ export const usePromptOptions = ( ) }, onSelect: () => { - if (!queryBlock?.selectable) - return + if (!queryBlock?.selectable) return editor.dispatchCommand(INSERT_QUERY_BLOCK_COMMAND, undefined) - }, - }), + } + }) ) } @@ -98,8 +100,7 @@ export const usePromptOptions = ( title={$t('会话历史')} icon={<>} // icon={} - disabled={!historyBlock.selectable - } + disabled={!historyBlock.selectable} isSelected={isSelected} onClick={onSelect} onMouseEnter={onSetHighlight} @@ -107,11 +108,10 @@ export const usePromptOptions = ( ) }, onSelect: () => { - if (!historyBlock?.selectable) - return + if (!historyBlock?.selectable) return editor.dispatchCommand(INSERT_HISTORY_BLOCK_COMMAND, undefined) - }, - }), + } + }) ) } return promptOptions @@ -119,16 +119,15 @@ export const usePromptOptions = ( export const useVariableOptions = ( variableBlock?: VariableBlockType, - queryString?: string, + queryString?: string ): PickerBlockMenuOption[] => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() const options = useMemo(() => { - if (!variableBlock?.variables) - return [] + if (!variableBlock?.variables) return [] - const baseOptions = (variableBlock.variables).map((item) => { + const baseOptions = variableBlock.variables.map((item) => { return new PickerBlockMenuOption({ key: item.value, group: 'prompt variable', @@ -147,15 +146,14 @@ export const useVariableOptions = ( }, onSelect: () => { editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.value}}}`) - }, + } }) }) - if (!queryString) - return baseOptions + if (!queryString) return baseOptions const regex = new RegExp(queryString, 'i') - return baseOptions.filter(option => regex.test(option.key)) + return baseOptions.filter((option) => regex.test(option.key)) }, [editor, queryString, variableBlock]) const addOption = useMemo(() => { @@ -182,7 +180,7 @@ export const useVariableOptions = ( $insertNodes([prefixNode, suffixNode]) prefixNode.select() }) - }, + } }) }, [editor, t]) @@ -191,17 +189,13 @@ export const useVariableOptions = ( }, [options, addOption, variableBlock?.show]) } -export const useExternalToolOptions = ( - externalToolBlockType?: ExternalToolBlockType, - queryString?: string, -) => { +export const useExternalToolOptions = (externalToolBlockType?: ExternalToolBlockType, queryString?: string) => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() const options = useMemo(() => { - if (!externalToolBlockType?.externalTools) - return [] - const baseToolOptions = (externalToolBlockType.externalTools).map((item) => { + if (!externalToolBlockType?.externalTools) return [] + const baseToolOptions = externalToolBlockType.externalTools.map((item) => { return new PickerBlockMenuOption({ key: item.name, group: 'external tool', @@ -217,7 +211,7 @@ export const useExternalToolOptions = ( // background={item.icon_background} // /> // } - extraElement={
{item.variableName}
} + extraElement={
{item.variableName}
} queryString={queryString} isSelected={isSelected} onClick={onSelect} @@ -227,15 +221,14 @@ export const useExternalToolOptions = ( }, onSelect: () => { editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${item.variableName}}}`) - }, + } }) }) - if (!queryString) - return baseToolOptions + if (!queryString) return baseToolOptions const regex = new RegExp(queryString, 'i') - return baseToolOptions.filter(option => regex.test(option.key)) + return baseToolOptions.filter((option) => regex.test(option.key)) }, [editor, queryString, externalToolBlockType]) const addOption = useMemo(() => { @@ -257,7 +250,7 @@ export const useExternalToolOptions = ( }, onSelect: () => { externalToolBlockType?.onAddExternalTool?.() - }, + } }) }, [externalToolBlockType, t]) @@ -273,14 +266,13 @@ export const useOptions = ( variableBlock?: VariableBlockType, externalToolBlockType?: ExternalToolBlockType, workflowVariableBlockType?: WorkflowVariableBlockType, - queryString?: string, + queryString?: string ) => { const promptOptions = usePromptOptions(contextBlock, queryBlock, historyBlock) const variableOptions = useVariableOptions(variableBlock, queryString) const externalToolOptions = useExternalToolOptions(externalToolBlockType, queryString) const workflowVariableOptions = useMemo(() => { - if (!workflowVariableBlockType?.show) - return [] + if (!workflowVariableBlockType?.show) return [] return workflowVariableBlockType.variables || [] }, [workflowVariableBlockType]) @@ -288,7 +280,7 @@ export const useOptions = ( return useMemo(() => { return { workflowVariableOptions, - allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions], + allFlattenOptions: [...promptOptions, ...variableOptions, ...externalToolOptions] } }, [promptOptions, variableOptions, externalToolOptions, workflowVariableOptions]) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/index.tsx index 8b137349..900f3973 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/index.tsx @@ -1,16 +1,6 @@ -import { - Fragment, - memo, - useCallback, - useState, -} from 'react' +import { Fragment, memo, useCallback, useState } from 'react' import ReactDOM from 'react-dom' -import { - flip, - offset, - shift, - useFloating, -} from '@floating-ui/react' +import { flip, offset, shift, useFloating } from '@floating-ui/react' import type { TextNode } from 'lexical' import type { MenuRenderFn } from '@lexical/react/LexicalTypeaheadMenuPlugin' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' @@ -21,7 +11,7 @@ import type { HistoryBlockType, QueryBlockType, VariableBlockType, - WorkflowVariableBlockType, + WorkflowVariableBlockType } from '../../types' import { useBasicTypeaheadTriggerMatch } from '../../hooks' import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block' @@ -48,7 +38,7 @@ const ComponentPicker = ({ historyBlock, variableBlock, externalToolBlock, - workflowVariableBlock, + workflowVariableBlock }: ComponentPickerProps) => { const { eventEmitter } = useEventEmitterContextContext() const { refs, floatingStyles, isPositioned } = useFloating({ @@ -56,15 +46,15 @@ const ComponentPicker = ({ middleware: [ offset(0), // fix hide cursor shift({ - padding: 8, + padding: 8 }), - flip(), - ], + flip() + ] }) const [editor] = useLexicalComposerContext() const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(triggerString, { minLength: 0, - maxLength: 0, + maxLength: 0 }) const [queryString, setQueryString] = useState(null) @@ -74,123 +64,114 @@ const ComponentPicker = ({ editor.dispatchCommand(INSERT_VARIABLE_VALUE_BLOCK_COMMAND, `{{${v.payload}}}`) }) - const { - allFlattenOptions, - workflowVariableOptions, - } = useOptions( + const { allFlattenOptions, workflowVariableOptions } = useOptions( contextBlock, queryBlock, historyBlock, variableBlock, externalToolBlock, - workflowVariableBlock, + workflowVariableBlock ) const onSelectOption = useCallback( - ( - selectedOption: PickerBlockMenuOption, - nodeToRemove: TextNode | null, - closeMenu: () => void, - ) => { + (selectedOption: PickerBlockMenuOption, nodeToRemove: TextNode | null, closeMenu: () => void) => { editor.update(() => { - if (nodeToRemove && selectedOption?.key) - nodeToRemove.remove() + if (nodeToRemove && selectedOption?.key) nodeToRemove.remove() selectedOption.onSelectMenuOption() closeMenu() }) }, - [editor], + [editor] ) - const handleSelectWorkflowVariable = useCallback((variables: string[]) => { - editor.update(() => { - const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!) - if (needRemove) - needRemove.remove() - }) + const handleSelectWorkflowVariable = useCallback( + (variables: string[]) => { + editor.update(() => { + const needRemove = $splitNodeContainingQuery(checkForTriggerMatch(triggerString, editor)!) + if (needRemove) needRemove.remove() + }) - if (variables[1] === 'sys.query' || variables[1] === 'sys.files') - editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) - else - editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) - }, [editor, checkForTriggerMatch, triggerString]) + if (variables[1] === 'sys.query' || variables[1] === 'sys.files') + editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, [variables[1]]) + else editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variables) + }, + [editor, checkForTriggerMatch, triggerString] + ) - const renderMenu = useCallback>(( - anchorElementRef, - { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }, - ) => { - if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) - return null - refs.setReference(anchorElementRef.current) + const renderMenu = useCallback>( + (anchorElementRef, { options, selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => { + if (!(anchorElementRef.current && (allFlattenOptions.length || workflowVariableBlock?.show))) return null + refs.setReference(anchorElementRef.current) - return ( - <> - { - ReactDOM.createPortal( + return ( + <> + {ReactDOM.createPortal( // The `LexicalMenu` will try to calculate the position of the floating menu based on the first child. // Since we use floating ui, we need to wrap it with a div to prevent the position calculation being affected. // See https://github.com/facebook/lexical/blob/ac97dfa9e14a73ea2d6934ff566282d7f758e8bb/packages/lexical-react/src/shared/LexicalMenu.ts#L493 -
+
- { - options.map((option, index) => ( - - { - // Divider - index !== 0 && options.at(index - 1)?.group !== option.group && ( -
- ) + {options.map((option, index) => ( + + { + // Divider + index !== 0 && options.at(index - 1)?.group !== option.group && ( +
+ ) + } + {option.renderMenuOption({ + queryString, + isSelected: selectedIndex === index, + onSelect: () => { + selectOptionAndCleanUp(option) + }, + onSetHighlight: () => { + setHighlightedIndex(index) } - {option.renderMenuOption({ - queryString, - isSelected: selectedIndex === index, - onSelect: () => { - selectOptionAndCleanUp(option) - }, - onSetHighlight: () => { - setHighlightedIndex(index) - }, - })} -
- )) - } - { - workflowVariableBlock?.show && ( - <> - { - (!!options.length) && ( -
- ) - } -
- {/* + ))} + {workflowVariableBlock?.show && ( + <> + {!!options.length &&
} +
+ {/* { handleSelectWorkflowVariable(variables) }} /> */} -
- - ) - } +
+ + )}
, - anchorElementRef.current, - ) - } - - ) - }, [allFlattenOptions.length, workflowVariableBlock?.show, refs, isPositioned, floatingStyles, queryString, workflowVariableOptions, handleSelectWorkflowVariable]) + anchorElementRef.current + )} + + ) + }, + [ + allFlattenOptions.length, + workflowVariableBlock?.show, + refs, + isPositioned, + floatingStyles, + queryString, + workflowVariableOptions, + handleSelectWorkflowVariable + ] + ) return ( diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/menu.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/menu.tsx index d8c71569..6e81f9f0 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/menu.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/menu.tsx @@ -20,12 +20,14 @@ export class PickerBlockMenuOption extends MenuOption { group?: string onSelect?: () => void render: (menuRenderProps: MenuOptionRenderProps) => JSX.Element - }, + } ) { super(data.key) this.group = data.group } public onSelectMenuOption = () => this.data.onSelect?.() - public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => {this.data.render(menuRenderProps)} + public renderMenuOption = (menuRenderProps: MenuOptionRenderProps) => ( + {this.data.render(menuRenderProps)} + ) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/prompt-option.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/prompt-option.tsx index 7aabbe4b..a309bfa2 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/prompt-option.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/prompt-option.tsx @@ -9,37 +9,30 @@ type PromptMenuItemMenuItemProps = { onMouseEnter: () => void setRefElement?: (element: HTMLDivElement) => void } -export const PromptMenuItem = memo(({ - icon, - title, - disabled, - isSelected, - onClick, - onMouseEnter, - setRefElement, -}: PromptMenuItemMenuItemProps) => { - return ( -
{ + return ( +
{ - if (disabled) - return - onMouseEnter() - }} - onClick={() => { - if (disabled) - return - onClick() - }}> - {icon} -
{title}
-
- ) -}) + tabIndex={-1} + ref={setRefElement} + onMouseEnter={() => { + if (disabled) return + onMouseEnter() + }} + onClick={() => { + if (disabled) return + onClick() + }} + > + {icon} +
{title}
+
+ ) + } +) PromptMenuItem.displayName = 'PromptMenuItem' diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/variable-option.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/variable-option.tsx index bb5290c8..d963dd29 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/variable-option.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/component-picker-block/variable-option.tsx @@ -10,51 +10,52 @@ type VariableMenuItemProps = { onMouseEnter: () => void setRefElement?: (element: HTMLDivElement) => void } -export const VariableMenuItem = memo(({ - title, - icon, - extraElement, - isSelected, - queryString, - onClick, - onMouseEnter, - setRefElement, -}: VariableMenuItemProps) => { - let before = title - let middle = '' - let after = '' +export const VariableMenuItem = memo( + ({ + title, + icon, + extraElement, + isSelected, + queryString, + onClick, + onMouseEnter, + setRefElement + }: VariableMenuItemProps) => { + let before = title + let middle = '' + let after = '' - if (queryString) { - const regex = new RegExp(queryString, 'i') - const match = regex.exec(title) + if (queryString) { + const regex = new RegExp(queryString, 'i') + const match = regex.exec(title) - if (match) { - before = title.substring(0, match.index) - middle = match[0] - after = title.substring(match.index + match[0].length) + if (match) { + before = title.substring(0, match.index) + middle = match[0] + after = title.substring(match.index + match[0].length) + } } - } - return ( -
-
- {icon} + tabIndex={-1} + ref={setRefElement} + onMouseEnter={onMouseEnter} + onClick={onClick} + > +
{icon}
+
+ {before} + {middle} + {after} +
+ {extraElement}
-
- {before} - {middle} - {after} -
- {extraElement} -
- ) -}) + ) + } +) VariableMenuItem.displayName = 'VariableMenuItem' diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/component.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/component.tsx index 7a8db454..f7342df2 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/component.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/component.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import { useState } from 'react' -import { useTranslation } from 'react-i18next' // import { // RiAddLine, // } from '@remixicon/react' @@ -28,7 +27,7 @@ const ContextBlockComponent: FC = ({ nodeKey, datasets = [], onAddContext, - canNotAddContext, + canNotAddContext }) => { const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_CONTEXT_BLOCK_COMMAND) const [triggerRef, open, setOpen] = useTrigger() @@ -36,19 +35,20 @@ const ContextBlockComponent: FC = ({ const [localDatasets, setLocalDatasets] = useState(datasets) eventEmitter?.useSubscription((v: any) => { - if (v?.type === UPDATE_DATASETS_EVENT_EMITTER) - setLocalDatasets(v.payload) + if (v?.type === UPDATE_DATASETS_EVENT_EMITTER) setLocalDatasets(v.payload) }) return ( -
+ `} + ref={ref} + > {/* */} -
{$t('上下文')}
- +
{$t('上下文')}
) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/context-block-replacement-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/context-block-replacement-block.tsx index 7470edac..1e7886a5 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/context-block-replacement-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/context-block-replacement-block.tsx @@ -1,18 +1,11 @@ -import { - memo, - useCallback, - useEffect, -} from 'react' +import { memo, useCallback, useEffect } from 'react' import { $applyNodeReplacement } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { decoratorTransform } from '../../utils' import { CONTEXT_PLACEHOLDER_TEXT } from '../../constants' import type { ContextBlockType } from '../../types' -import { - $createContextBlockNode, - ContextBlockNode, -} from './node' +import { $createContextBlockNode, ContextBlockNode } from './node' import { CustomTextNode } from '../custom-text/node' const REGEX = new RegExp(CONTEXT_PLACEHOLDER_TEXT) @@ -21,7 +14,7 @@ const ContextBlockReplacementBlock = ({ datasets = [], onAddContext = () => {}, onInsert, - canNotAddContext, + canNotAddContext }: ContextBlockType) => { const [editor] = useLexicalComposerContext() @@ -31,29 +24,29 @@ const ContextBlockReplacementBlock = ({ }, [editor]) const createContextBlockNode = useCallback((): ContextBlockNode => { - if (onInsert) - onInsert() + if (onInsert) onInsert() return $applyNodeReplacement($createContextBlockNode(datasets, onAddContext, canNotAddContext)) }, [datasets, onAddContext, onInsert, canNotAddContext]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) - if (matchArr === null) - return null + if (matchArr === null) return null const startOffset = matchArr.index const endOffset = startOffset + CONTEXT_PLACEHOLDER_TEXT.length return { end: endOffset, - start: startOffset, + start: startOffset } }, []) useEffect(() => { REGEX.lastIndex = 0 return mergeRegister( - editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createContextBlockNode)), + editor.registerNodeTransform(CustomTextNode, (textNode) => + decoratorTransform(textNode, getMatch, createContextBlockNode) + ) ) }, []) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/index.tsx index 5be4f1fa..0a0f2d0c 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/index.tsx @@ -1,19 +1,9 @@ -import { - memo, - useEffect, -} from 'react' -import { - $insertNodes, - COMMAND_PRIORITY_EDITOR, - createCommand, -} from 'lexical' +import { memo, useEffect } from 'react' +import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import type { ContextBlockType } from '../../types' -import { - $createContextBlockNode, - ContextBlockNode, -} from './node' +import { $createContextBlockNode, ContextBlockNode } from './node' export const INSERT_CONTEXT_BLOCK_COMMAND = createCommand('INSERT_CONTEXT_BLOCK_COMMAND') export const DELETE_CONTEXT_BLOCK_COMMAND = createCommand('DELETE_CONTEXT_BLOCK_COMMAND') @@ -24,49 +14,43 @@ export type Dataset = { type: string } -const ContextBlock = memo(({ - datasets = [], - onAddContext = () => {}, - onInsert, - onDelete, - canNotAddContext, -}: ContextBlockType) => { - const [editor] = useLexicalComposerContext() +const ContextBlock = memo( + ({ datasets = [], onAddContext = () => {}, onInsert, onDelete, canNotAddContext }: ContextBlockType) => { + const [editor] = useLexicalComposerContext() - useEffect(() => { - if (!editor.hasNodes([ContextBlockNode])) - throw new Error('ContextBlockPlugin: ContextBlock not registered on editor') + useEffect(() => { + if (!editor.hasNodes([ContextBlockNode])) + throw new Error('ContextBlockPlugin: ContextBlock not registered on editor') - return mergeRegister( - editor.registerCommand( - INSERT_CONTEXT_BLOCK_COMMAND, - () => { - const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext) + return mergeRegister( + editor.registerCommand( + INSERT_CONTEXT_BLOCK_COMMAND, + () => { + const contextBlockNode = $createContextBlockNode(datasets, onAddContext, canNotAddContext) - $insertNodes([contextBlockNode]) + $insertNodes([contextBlockNode]) - if (onInsert) - onInsert() + if (onInsert) onInsert() - return true - }, - COMMAND_PRIORITY_EDITOR, - ), - editor.registerCommand( - DELETE_CONTEXT_BLOCK_COMMAND, - () => { - if (onDelete) - onDelete() + return true + }, + COMMAND_PRIORITY_EDITOR + ), + editor.registerCommand( + DELETE_CONTEXT_BLOCK_COMMAND, + () => { + if (onDelete) onDelete() - return true - }, - COMMAND_PRIORITY_EDITOR, - ), - ) - }, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext]) + return true + }, + COMMAND_PRIORITY_EDITOR + ) + ) + }, [editor, datasets, onAddContext, onInsert, onDelete, canNotAddContext]) - return null -}) + return null + } +) ContextBlock.displayName = 'ContextBlock' export { ContextBlock } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/node.tsx index 3800b9bb..4fc3e18d 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/context-block/node.tsx @@ -3,7 +3,11 @@ import { DecoratorNode } from 'lexical' import ContextBlockComponent from './component' import type { Dataset } from './index' -export type SerializedNode = SerializedLexicalNode & { datasets: Dataset[]; onAddContext: () => void; canNotAddContext: boolean } +export type SerializedNode = SerializedLexicalNode & { + datasets: Dataset[] + onAddContext: () => void + canNotAddContext: boolean +} export class ContextBlockNode extends DecoratorNode { __datasets: Dataset[] @@ -70,7 +74,11 @@ export class ContextBlockNode extends DecoratorNode { } static importJSON(serializedNode: SerializedNode): ContextBlockNode { - const node = $createContextBlockNode(serializedNode.datasets, serializedNode.onAddContext, serializedNode.canNotAddContext) + const node = $createContextBlockNode( + serializedNode.datasets, + serializedNode.onAddContext, + serializedNode.canNotAddContext + ) return node } @@ -81,7 +89,7 @@ export class ContextBlockNode extends DecoratorNode { version: 1, datasets: this.getDatasets(), onAddContext: this.getOnAddContext(), - canNotAddContext: this.getCanNotAddContext(), + canNotAddContext: this.getCanNotAddContext() } } @@ -89,12 +97,14 @@ export class ContextBlockNode extends DecoratorNode { return '{{#context#}}' } } -export function $createContextBlockNode(datasets: Dataset[], onAddContext: () => void, canNotAddContext?: boolean): ContextBlockNode { +export function $createContextBlockNode( + datasets: Dataset[], + onAddContext: () => void, + canNotAddContext?: boolean +): ContextBlockNode { return new ContextBlockNode(datasets, onAddContext, undefined, canNotAddContext) } -export function $isContextBlockNode( - node: ContextBlockNode | LexicalNode | null | undefined, -): boolean { +export function $isContextBlockNode(node: ContextBlockNode | LexicalNode | null | undefined): boolean { return node instanceof ContextBlockNode } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/custom-text/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/custom-text/node.tsx index 5df4894c..0d7405a9 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/custom-text/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/custom-text/node.tsx @@ -37,13 +37,12 @@ export class CustomTextNode extends TextNode { style: this.getStyle(), text: this.getTextContent(), type: 'custom-text', - version: 1, + version: 1 } } isSimpleText() { - return ( - (this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0) + return (this.__type === 'text' || this.__type === 'custom-text') && this.__mode === 0 } } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/component.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/component.tsx index d333b712..6017ecfa 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/component.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/component.tsx @@ -1,6 +1,5 @@ import type { FC } from 'react' import { useState } from 'react' -import { useTranslation } from 'react-i18next' // import { // RiMoreFill, // } from '@remixicon/react' @@ -26,7 +25,7 @@ type HistoryBlockComponentProps = { const HistoryBlockComponent: FC = ({ nodeKey, roleName = { user: '', assistant: '' }, - onEditRole, + onEditRole }) => { const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_HISTORY_BLOCK_COMMAND) const [triggerRef, open, setOpen] = useTrigger() @@ -34,18 +33,20 @@ const HistoryBlockComponent: FC = ({ const [localRoleName, setLocalRoleName] = useState(roleName) eventEmitter?.useSubscription((v: any) => { - if (v?.type === UPDATE_HISTORY_EVENT_EMITTER) - setLocalRoleName(v.payload) + if (v?.type === UPDATE_HISTORY_EVENT_EMITTER) setLocalRoleName(v.payload) }) return ( -
+ `} + ref={ref} + > {/* */} -
{$t('会话历史')}
+
{$t('会话历史')}
) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/history-block-replacement-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/history-block-replacement-block.tsx index 414d027c..72cc0146 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/history-block-replacement-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/history-block-replacement-block.tsx @@ -1,17 +1,11 @@ -import { - useCallback, - useEffect, -} from 'react' +import { useCallback, useEffect } from 'react' import { $applyNodeReplacement } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { decoratorTransform } from '../../utils' import { HISTORY_PLACEHOLDER_TEXT } from '../../constants' import type { HistoryBlockType } from '../../types' -import { - $createHistoryBlockNode, - HistoryBlockNode, -} from './node' +import { $createHistoryBlockNode, HistoryBlockNode } from './node' import { CustomTextNode } from '../custom-text/node' const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT) @@ -19,7 +13,7 @@ const REGEX = new RegExp(HISTORY_PLACEHOLDER_TEXT) const HistoryBlockReplacementBlock = ({ history = { user: '', assistant: '' }, onEditRole = () => {}, - onInsert, + onInsert }: HistoryBlockType) => { const [editor] = useLexicalComposerContext() @@ -29,29 +23,29 @@ const HistoryBlockReplacementBlock = ({ }, [editor]) const createHistoryBlockNode = useCallback((): HistoryBlockNode => { - if (onInsert) - onInsert() + if (onInsert) onInsert() return $applyNodeReplacement($createHistoryBlockNode(history, onEditRole)) }, [history, onEditRole, onInsert]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) - if (matchArr === null) - return null + if (matchArr === null) return null const startOffset = matchArr.index const endOffset = startOffset + HISTORY_PLACEHOLDER_TEXT.length return { end: endOffset, - start: startOffset, + start: startOffset } }, []) useEffect(() => { REGEX.lastIndex = 0 return mergeRegister( - editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createHistoryBlockNode)), + editor.registerNodeTransform(CustomTextNode, (textNode) => + decoratorTransform(textNode, getMatch, createHistoryBlockNode) + ) ) }, []) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/index.tsx index 78c73495..fd5b7e7c 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/index.tsx @@ -1,19 +1,9 @@ -import { - memo, - useEffect, -} from 'react' -import { - $insertNodes, - COMMAND_PRIORITY_EDITOR, - createCommand, -} from 'lexical' +import { memo, useEffect } from 'react' +import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import type { HistoryBlockType } from '../../types' -import { - $createHistoryBlockNode, - HistoryBlockNode, -} from './node' +import { $createHistoryBlockNode, HistoryBlockNode } from './node' export const INSERT_HISTORY_BLOCK_COMMAND = createCommand('INSERT_HISTORY_BLOCK_COMMAND') export const DELETE_HISTORY_BLOCK_COMMAND = createCommand('DELETE_HISTORY_BLOCK_COMMAND') @@ -30,48 +20,43 @@ export type HistoryBlockProps = { onDelete?: () => void } -const HistoryBlock = memo(({ - history = { user: '', assistant: '' }, - onEditRole = () => {}, - onInsert, - onDelete, -}: HistoryBlockType) => { - const [editor] = useLexicalComposerContext() +const HistoryBlock = memo( + ({ history = { user: '', assistant: '' }, onEditRole = () => {}, onInsert, onDelete }: HistoryBlockType) => { + const [editor] = useLexicalComposerContext() - useEffect(() => { - if (!editor.hasNodes([HistoryBlockNode])) - throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor') + useEffect(() => { + if (!editor.hasNodes([HistoryBlockNode])) + throw new Error('HistoryBlockPlugin: HistoryBlock not registered on editor') - return mergeRegister( - editor.registerCommand( - INSERT_HISTORY_BLOCK_COMMAND, - () => { - const historyBlockNode = $createHistoryBlockNode(history, onEditRole) + return mergeRegister( + editor.registerCommand( + INSERT_HISTORY_BLOCK_COMMAND, + () => { + const historyBlockNode = $createHistoryBlockNode(history, onEditRole) - $insertNodes([historyBlockNode]) + $insertNodes([historyBlockNode]) - if (onInsert) - onInsert() + if (onInsert) onInsert() - return true - }, - COMMAND_PRIORITY_EDITOR, - ), - editor.registerCommand( - DELETE_HISTORY_BLOCK_COMMAND, - () => { - if (onDelete) - onDelete() + return true + }, + COMMAND_PRIORITY_EDITOR + ), + editor.registerCommand( + DELETE_HISTORY_BLOCK_COMMAND, + () => { + if (onDelete) onDelete() - return true - }, - COMMAND_PRIORITY_EDITOR, - ), - ) - }, [editor, history, onEditRole, onInsert, onDelete]) + return true + }, + COMMAND_PRIORITY_EDITOR + ) + ) + }, [editor, history, onEditRole, onInsert, onDelete]) - return null -}) + return null + } +) HistoryBlock.displayName = 'HistoryBlock' export { HistoryBlock } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/node.tsx index 4112b736..08baa79a 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/history-block/node.tsx @@ -40,11 +40,7 @@ export class HistoryBlockNode extends DecoratorNode { decorate(): JSX.Element { return ( - + ) } @@ -71,7 +67,7 @@ export class HistoryBlockNode extends DecoratorNode { type: 'history-block', version: 1, roleName: this.getRoleName(), - onEditRole: this.getOnEditRole, + onEditRole: this.getOnEditRole } } @@ -83,8 +79,6 @@ export function $createHistoryBlockNode(roleName: RoleName, onEditRole: () => vo return new HistoryBlockNode(roleName, onEditRole) } -export function $isHistoryBlockNode( - node: HistoryBlockNode | LexicalNode | null | undefined, -): node is HistoryBlockNode { +export function $isHistoryBlockNode(node: HistoryBlockNode | LexicalNode | null | undefined): node is HistoryBlockNode { return node instanceof HistoryBlockNode } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/on-blur-or-focus-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/on-blur-or-focus-block.tsx index 2e3adc15..d16e2c23 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/on-blur-or-focus-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/on-blur-or-focus-block.tsx @@ -1,11 +1,6 @@ import type { FC } from 'react' import { useEffect, useRef } from 'react' -import { - BLUR_COMMAND, - COMMAND_PRIORITY_EDITOR, - FOCUS_COMMAND, - KEY_ESCAPE_COMMAND, -} from 'lexical' +import { BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { CLEAR_HIDE_MENU_TIMEOUT } from './workflow-variable-block' @@ -14,10 +9,7 @@ type OnBlurBlockProps = { onBlur?: () => void onFocus?: () => void } -const OnBlurBlock: FC = ({ - onBlur, - onFocus, -}) => { +const OnBlurBlock: FC = ({ onBlur, onFocus }) => { const [editor] = useLexicalComposerContext() const ref = useRef(null) @@ -33,7 +25,7 @@ const OnBlurBlock: FC = ({ } return true }, - COMMAND_PRIORITY_EDITOR, + COMMAND_PRIORITY_EDITOR ), editor.registerCommand( BLUR_COMMAND, @@ -42,22 +34,20 @@ const OnBlurBlock: FC = ({ editor.dispatchCommand(KEY_ESCAPE_COMMAND, new KeyboardEvent('keydown', { key: 'Escape' })) }, 200) - if (onBlur) - onBlur() + if (onBlur) onBlur() return true }, - COMMAND_PRIORITY_EDITOR, + COMMAND_PRIORITY_EDITOR ), editor.registerCommand( FOCUS_COMMAND, () => { - if (onFocus) - onFocus() + if (onFocus) onFocus() return true }, - COMMAND_PRIORITY_EDITOR, - ), + COMMAND_PRIORITY_EDITOR + ) ) }, [editor, onBlur, onFocus]) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/placeholder.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/placeholder.tsx index d0ed3c9a..1adc2211 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/placeholder.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/placeholder.tsx @@ -1,18 +1,10 @@ import { $t } from '@common/locales' import { memo } from 'react' -const Placeholder = ({ - compact, - value -}: { - compact?: boolean - value?: string - className?: string -}) => { - +const Placeholder = ({ compact, value }: { compact?: boolean; value?: string; className?: string }) => { return ( -
{value || $t('AI 模型调用默认仅使用 Query 变量,可输入 “{” 增加新变量。')}
diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/component.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/component.tsx index 115e51f1..77d11fe2 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/component.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/component.tsx @@ -1,5 +1,4 @@ import type { FC } from 'react' -import { useTranslation } from 'react-i18next' import { useSelectOrDelete } from '../../hooks' import { DELETE_QUERY_BLOCK_COMMAND } from './index' import { $t } from '@common/locales' @@ -9,9 +8,7 @@ type QueryBlockComponentProps = { nodeKey: string } -const QueryBlockComponent: FC = ({ - nodeKey, -}) => { +const QueryBlockComponent: FC = ({ nodeKey }) => { const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_QUERY_BLOCK_COMMAND) return ( @@ -23,9 +20,9 @@ const QueryBlockComponent: FC = ({ ref={ref} > {/* */} -
{'{{'}
-
{$t('查询内容')}
-
{'}}'}
+
{'{{'}
+
{$t('查询内容')}
+
{'}}'}
) } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/index.tsx index 09461531..018497fa 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/index.tsx @@ -1,19 +1,9 @@ -import { - memo, - useEffect, -} from 'react' -import { - $insertNodes, - COMMAND_PRIORITY_EDITOR, - createCommand, -} from 'lexical' +import { memo, useEffect } from 'react' +import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import type { QueryBlockType } from '../../types' -import { - $createQueryBlockNode, - QueryBlockNode, -} from './node' +import { $createQueryBlockNode, QueryBlockNode } from './node' export const INSERT_QUERY_BLOCK_COMMAND = createCommand('INSERT_QUERY_BLOCK_COMMAND') export const DELETE_QUERY_BLOCK_COMMAND = createCommand('DELETE_QUERY_BLOCK_COMMAND') @@ -22,15 +12,11 @@ export type QueryBlockProps = { onInsert?: () => void onDelete?: () => void } -const QueryBlock = memo(({ - onInsert, - onDelete, -}: QueryBlockType) => { +const QueryBlock = memo(({ onInsert, onDelete }: QueryBlockType) => { const [editor] = useLexicalComposerContext() useEffect(() => { - if (!editor.hasNodes([QueryBlockNode])) - throw new Error('QueryBlockPlugin: QueryBlock not registered on editor') + if (!editor.hasNodes([QueryBlockNode])) throw new Error('QueryBlockPlugin: QueryBlock not registered on editor') return mergeRegister( editor.registerCommand( @@ -39,23 +25,21 @@ const QueryBlock = memo(({ const contextBlockNode = $createQueryBlockNode() $insertNodes([contextBlockNode]) - if (onInsert) - onInsert() + if (onInsert) onInsert() return true }, - COMMAND_PRIORITY_EDITOR, + COMMAND_PRIORITY_EDITOR ), editor.registerCommand( DELETE_QUERY_BLOCK_COMMAND, () => { - if (onDelete) - onDelete() + if (onDelete) onDelete() return true }, - COMMAND_PRIORITY_EDITOR, - ), + COMMAND_PRIORITY_EDITOR + ) ) }, [editor, onInsert, onDelete]) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/node.tsx index 3b5f6c0d..e015694d 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/node.tsx @@ -40,7 +40,7 @@ export class QueryBlockNode extends DecoratorNode { exportJSON(): SerializedNode { return { type: 'query-block', - version: 1, + version: 1 } } @@ -52,8 +52,6 @@ export function $createQueryBlockNode(): QueryBlockNode { return new QueryBlockNode() } -export function $isQueryBlockNode( - node: QueryBlockNode | LexicalNode | null | undefined, -): node is QueryBlockNode { +export function $isQueryBlockNode(node: QueryBlockNode | LexicalNode | null | undefined): node is QueryBlockNode { return node instanceof QueryBlockNode } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/query-block-replacement-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/query-block-replacement-block.tsx index 0959f93c..0a369007 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/query-block-replacement-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/query-block/query-block-replacement-block.tsx @@ -1,25 +1,16 @@ -import { - memo, - useCallback, - useEffect, -} from 'react' +import { memo, useCallback, useEffect } from 'react' import { $applyNodeReplacement } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { decoratorTransform } from '../../utils' import { QUERY_PLACEHOLDER_TEXT } from '../../constants' import type { QueryBlockType } from '../../types' -import { - $createQueryBlockNode, - QueryBlockNode, -} from './node' +import { $createQueryBlockNode, QueryBlockNode } from './node' import { CustomTextNode } from '../custom-text/node' const REGEX = new RegExp(QUERY_PLACEHOLDER_TEXT) -const QueryBlockReplacementBlock = ({ - onInsert, -}: QueryBlockType) => { +const QueryBlockReplacementBlock = ({ onInsert }: QueryBlockType) => { const [editor] = useLexicalComposerContext() useEffect(() => { @@ -28,29 +19,29 @@ const QueryBlockReplacementBlock = ({ }, [editor]) const createQueryBlockNode = useCallback((): QueryBlockNode => { - if (onInsert) - onInsert() + if (onInsert) onInsert() return $applyNodeReplacement($createQueryBlockNode()) }, [onInsert]) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) - if (matchArr === null) - return null + if (matchArr === null) return null const startOffset = matchArr.index const endOffset = startOffset + QUERY_PLACEHOLDER_TEXT.length return { end: endOffset, - start: startOffset, + start: startOffset } }, []) useEffect(() => { REGEX.lastIndex = 0 return mergeRegister( - editor.registerNodeTransform(CustomTextNode, textNode => decoratorTransform(textNode, getMatch, createQueryBlockNode)), + editor.registerNodeTransform(CustomTextNode, (textNode) => + decoratorTransform(textNode, getMatch, createQueryBlockNode) + ) ) }, []) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/update-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/update-block.tsx index e17a493b..72be43ea 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/update-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/update-block.tsx @@ -11,9 +11,7 @@ export const PROMPT_EDITOR_INSERT_QUICKLY = 'PROMPT_EDITOR_INSERT_QUICKLY' type UpdateBlockProps = { instanceId?: string } -const UpdateBlock = ({ - instanceId, -}: UpdateBlockProps) => { +const UpdateBlock = ({ instanceId }: UpdateBlockProps) => { const { eventEmitter } = useEventEmitterContextContext() const [editor] = useLexicalComposerContext() diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-block/index.tsx index 3c995d59..1f021452 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-block/index.tsx @@ -1,9 +1,5 @@ import { useEffect } from 'react' -import { - $insertNodes, - COMMAND_PRIORITY_EDITOR, - createCommand, -} from 'lexical' +import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { CustomTextNode } from '../custom-text/node' @@ -24,7 +20,7 @@ const VariableBlock = () => { return true }, - COMMAND_PRIORITY_EDITOR, + COMMAND_PRIORITY_EDITOR ), editor.registerCommand( INSERT_VARIABLE_VALUE_BLOCK_COMMAND, @@ -34,8 +30,8 @@ const VariableBlock = () => { return true }, - COMMAND_PRIORITY_EDITOR, - ), + COMMAND_PRIORITY_EDITOR + ) ) }, [editor]) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/index.tsx index e93c0d7f..11dcce12 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/index.tsx @@ -1,14 +1,8 @@ -import { - useCallback, - useEffect, -} from 'react' +import { useCallback, useEffect } from 'react' import type { TextNode } from 'lexical' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { useLexicalTextEntity } from '../../hooks' -import { - $createVariableValueBlockNode, - VariableValueBlockNode, -} from './node' +import { $createVariableValueBlockNode, VariableValueBlockNode } from './node' import { getHashtagRegexString } from './utils' const REGEX = new RegExp(getHashtagRegexString(), 'i') @@ -28,22 +22,21 @@ const VariableValueBlock = () => { const getVariableValueMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) - if (matchArr === null) - return null + if (matchArr === null) return null const hashtagLength = matchArr[0].length const startOffset = matchArr.index const endOffset = startOffset + hashtagLength return { end: endOffset, - start: startOffset, + start: startOffset } }, []) useLexicalTextEntity( getVariableValueMatch, VariableValueBlockNode, - createVariableValueBlockNode, + createVariableValueBlockNode ) return null diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/node.tsx index 163d4bfa..b65bd755 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/variable-value-block/node.tsx @@ -1,13 +1,5 @@ -import type { - EditorConfig, - LexicalNode, - NodeKey, - SerializedTextNode, -} from 'lexical' -import { - $applyNodeReplacement, - TextNode, -} from 'lexical' +import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from 'lexical' +import { $applyNodeReplacement, TextNode } from 'lexical' export class VariableValueBlockNode extends TextNode { static getType(): string { @@ -24,7 +16,15 @@ export class VariableValueBlockNode extends TextNode { createDOM(config: EditorConfig): HTMLElement { const element = super.createDOM(config) - element.classList.add('inline-flex', 'items-center', 'px-0.5', 'h-[22px]', 'text-[#155EEF]', 'rounded-[5px]', 'align-middle') + element.classList.add( + 'inline-flex', + 'items-center', + 'px-0.5', + 'h-[22px]', + 'text-[#155EEF]', + 'rounded-[5px]', + 'align-middle' + ) return element } @@ -45,7 +45,7 @@ export class VariableValueBlockNode extends TextNode { style: this.getStyle(), text: this.getTextContent(), type: 'variable-value-block', - version: 1, + version: 1 } } @@ -58,8 +58,6 @@ export function $createVariableValueBlockNode(text = ''): VariableValueBlockNode return $applyNodeReplacement(new VariableValueBlockNode(text)) } -export function $isVariableValueNodeBlock( - node: LexicalNode | null | undefined, -): node is VariableValueBlockNode { +export function $isVariableValueNodeBlock(node: LexicalNode | null | undefined): node is VariableValueBlockNode { return node instanceof VariableValueBlockNode } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/component.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/component.tsx index 51017894..a2c340b7 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -1,12 +1,6 @@ -import { - memo, - useEffect, - useState, -} from 'react' +import { memo, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { - COMMAND_PRIORITY_EDITOR, -} from 'lexical' +import { COMMAND_PRIORITY_EDITOR } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' // import { @@ -15,10 +9,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext import { useSelectOrDelete } from '../../hooks' import type { WorkflowNodesMap } from './node' import { WorkflowVariableBlockNode } from './node' -import { - DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, - UPDATE_WORKFLOW_NODES_MAP, -} from './index' +import { DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, UPDATE_WORKFLOW_NODES_MAP } from './index' // import cn from '@/utils/classnames' // import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development' // import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others' @@ -36,7 +27,7 @@ type WorkflowVariableBlockComponentProps = { const WorkflowVariableBlockComponent = ({ nodeKey, variables, - workflowNodesMap = {}, + workflowNodesMap = {} }: WorkflowVariableBlockComponentProps) => { const { t } = useTranslation() const [editor] = useLexicalComposerContext() @@ -66,14 +57,14 @@ const WorkflowVariableBlockComponent = ({ return true }, - COMMAND_PRIORITY_EDITOR, - ), + COMMAND_PRIORITY_EDITOR + ) ) }, [editor]) const Item = (
{/* {!isEnv && !isChatVar && ( @@ -93,7 +84,7 @@ const WorkflowVariableBlockComponent = ({
)} */} -
+
{/* {!isEnv && !isChatVar && } {isEnv && } {isChatVar && } @@ -107,7 +98,6 @@ const WorkflowVariableBlockComponent = ({
) - return Item } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/index.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/index.tsx index 8f0e2f48..5544164a 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/index.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/index.tsx @@ -1,19 +1,9 @@ -import { - memo, - useEffect, -} from 'react' -import { - $insertNodes, - COMMAND_PRIORITY_EDITOR, - createCommand, -} from 'lexical' +import { memo, useEffect } from 'react' +import { $insertNodes, COMMAND_PRIORITY_EDITOR, createCommand } from 'lexical' import { mergeRegister } from '@lexical/utils' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import type { WorkflowVariableBlockType } from '../../types' -import { - $createWorkflowVariableBlockNode, - WorkflowVariableBlockNode, -} from './node' +import { $createWorkflowVariableBlockNode, WorkflowVariableBlockNode } from './node' // import type { Node } from '@/app/components/workflow/types' export const INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND = createCommand('INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND') @@ -26,11 +16,7 @@ export type WorkflowVariableBlockProps = { onInsert?: () => void onDelete?: () => void } -const WorkflowVariableBlock = memo(({ - workflowNodesMap, - onInsert, - onDelete, -}: WorkflowVariableBlockType) => { +const WorkflowVariableBlock = memo(({ workflowNodesMap, onInsert, onDelete }: WorkflowVariableBlockType) => { const [editor] = useLexicalComposerContext() useEffect(() => { @@ -51,23 +37,21 @@ const WorkflowVariableBlock = memo(({ const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap) $insertNodes([workflowVariableBlockNode]) - if (onInsert) - onInsert() + if (onInsert) onInsert() return true }, - COMMAND_PRIORITY_EDITOR, + COMMAND_PRIORITY_EDITOR ), editor.registerCommand( DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND, () => { - if (onDelete) - onDelete() + if (onDelete) onDelete() return true }, - COMMAND_PRIORITY_EDITOR, - ), + COMMAND_PRIORITY_EDITOR + ) ) }, [editor, onInsert, onDelete, workflowNodesMap]) diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/node.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/node.tsx index e4154731..0157085e 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/node.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/node.tsx @@ -63,7 +63,7 @@ export class WorkflowVariableBlockNode extends DecoratorNode { type: 'workflow-variable-block', version: 1, variables: this.getVariables(), - workflowNodesMap: this.getWorkflowNodesMap(), + workflowNodesMap: this.getWorkflowNodesMap() } } @@ -81,12 +81,15 @@ export class WorkflowVariableBlockNode extends DecoratorNode { return `{{#${this.getVariables().join('.')}#}}` } } -export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap): WorkflowVariableBlockNode { +export function $createWorkflowVariableBlockNode( + variables: string[], + workflowNodesMap: WorkflowNodesMap +): WorkflowVariableBlockNode { return new WorkflowVariableBlockNode(variables, workflowNodesMap) } export function $isWorkflowVariableBlockNode( - node: WorkflowVariableBlockNode | LexicalNode | null | undefined, + node: WorkflowVariableBlockNode | LexicalNode | null | undefined ): node is WorkflowVariableBlockNode { return node instanceof WorkflowVariableBlockNode } diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx index 4571c18b..e95d13bb 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/plugins/workflow-variable-block/workflow-variable-block-replacement-block.tsx @@ -1,8 +1,4 @@ -import { - memo, - useCallback, - useEffect, -} from 'react' +import { memo, useCallback, useEffect } from 'react' import type { TextNode } from 'lexical' import { $applyNodeReplacement } from 'lexical' import { mergeRegister } from '@lexical/utils' @@ -16,10 +12,7 @@ import { WorkflowVariableBlockNode } from './index' export const REGEX = /\{\{(#[a-zA-Z0-9_-]{1,50}(\.[a-zA-Z_][a-zA-Z0-9_]{0,29}){1,10}#)\}\}/gi -const WorkflowVariableBlockReplacementBlock = ({ - workflowNodesMap, - onInsert, -}: WorkflowVariableBlockType) => { +const WorkflowVariableBlockReplacementBlock = ({ workflowNodesMap, onInsert }: WorkflowVariableBlockType) => { const [editor] = useLexicalComposerContext() useEffect(() => { @@ -27,37 +20,39 @@ const WorkflowVariableBlockReplacementBlock = ({ throw new Error('WorkflowVariableBlockNodePlugin: WorkflowVariableBlockNode not registered on editor') }, [editor]) - const createWorkflowVariableBlockNode = useCallback((textNode: TextNode): WorkflowVariableBlockNode => { - if (onInsert) - onInsert() + const createWorkflowVariableBlockNode = useCallback( + (textNode: TextNode): WorkflowVariableBlockNode => { + if (onInsert) onInsert() - const nodePathString = textNode.getTextContent().slice(3, -3) - return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap)) - }, [onInsert, workflowNodesMap]) + const nodePathString = textNode.getTextContent().slice(3, -3) + return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap)) + }, + [onInsert, workflowNodesMap] + ) const getMatch = useCallback((text: string) => { const matchArr = REGEX.exec(text) - if (matchArr === null) - return null + if (matchArr === null) return null const startOffset = matchArr.index const endOffset = startOffset + matchArr[0].length return { end: endOffset, - start: startOffset, + start: startOffset } }, []) - const transformListener = useCallback((textNode: any) => { - return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode) - }, [createWorkflowVariableBlockNode, getMatch]) + const transformListener = useCallback( + (textNode: any) => { + return decoratorTransform(textNode, getMatch, createWorkflowVariableBlockNode) + }, + [createWorkflowVariableBlockNode, getMatch] + ) useEffect(() => { REGEX.lastIndex = 0 - return mergeRegister( - editor.registerNodeTransform(CustomTextNode, transformListener), - ) + return mergeRegister(editor.registerNodeTransform(CustomTextNode, transformListener)) }, []) return null diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx b/frontend/packages/common/src/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx index 2376fb01..7188bb0a 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/prompt-editor-height-resize-wrap.tsx @@ -20,7 +20,7 @@ const PromptEditorHeightResizeWrap: FC = ({ onHeightChange, children, footer, - hideResize, + hideResize }) => { const [clientY, setClientY] = useState(0) const [isResizing, setIsResizing] = useState(false) @@ -38,19 +38,20 @@ const PromptEditorHeightResizeWrap: FC = ({ document.body.style.userSelect = prevUserSelectStyle }, [prevUserSelectStyle]) - const { run: didHandleResize } = useDebounceFn((e) => { - if (!isResizing) - return + const { run: didHandleResize } = useDebounceFn( + (e) => { + if (!isResizing) return - const offset = e.clientY - clientY - let newHeight = height + offset - setClientY(e.clientY) - if (newHeight < minHeight) - newHeight = minHeight - onHeightChange(newHeight) - }, { - wait: 0, - }) + const offset = e.clientY - clientY + let newHeight = height + offset + setClientY(e.clientY) + if (newHeight < minHeight) newHeight = minHeight + onHeightChange(newHeight) + }, + { + wait: 0 + } + ) const handleResize = useCallback(didHandleResize, [isResizing, height, minHeight, clientY]) @@ -69,12 +70,11 @@ const PromptEditorHeightResizeWrap: FC = ({ }, [handleStopResize]) return ( -
-
+
{children} @@ -83,9 +83,10 @@ const PromptEditorHeightResizeWrap: FC = ({ {footer} {!hideResize && (
-
+ className="absolute bottom-0 left-0 w-full flex justify-center h-2 cursor-row-resize" + onMouseDown={handleStartResize} + > +
)}
diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/types.ts b/frontend/packages/common/src/components/aoplatform/prompt-editor/types.ts index 8b7df07f..d153bfd9 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/types.ts +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/types.ts @@ -4,7 +4,6 @@ import type { RoleName } from './plugins/history-block/index' // Node, // } from '@/app/components/workflow/types' - export type NodeOutPutVar = { nodeId: string title: string @@ -12,7 +11,6 @@ export type NodeOutPutVar = { isStartNode?: boolean } - export type Option = { value: string name: string diff --git a/frontend/packages/common/src/components/aoplatform/prompt-editor/utils.ts b/frontend/packages/common/src/components/aoplatform/prompt-editor/utils.ts index f1cca6dc..95603e7d 100644 --- a/frontend/packages/common/src/components/aoplatform/prompt-editor/utils.ts +++ b/frontend/packages/common/src/components/aoplatform/prompt-editor/utils.ts @@ -1,45 +1,34 @@ import { $isAtNodeEnd } from '@lexical/selection' -import type { - ElementNode, - Klass, - LexicalEditor, - LexicalNode, - RangeSelection, - TextNode, -} from 'lexical' -import { - $createTextNode, - $getSelection, - $isRangeSelection, - $isTextNode, -} from 'lexical' +import type { ElementNode, Klass, LexicalEditor, LexicalNode, RangeSelection, TextNode } from 'lexical' +import { $createTextNode, $getSelection, $isRangeSelection, $isTextNode } from 'lexical' import type { EntityMatch } from '@lexical/text' import { CustomTextNode } from './plugins/custom-text/node' import type { MenuTextMatch } from './types' -import { CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT, MAX_VAR_KEY_LENGTH } from './constants' +import { + CONTEXT_PLACEHOLDER_TEXT, + HISTORY_PLACEHOLDER_TEXT, + QUERY_PLACEHOLDER_TEXT, + PRE_PROMPT_PLACEHOLDER_TEXT, + MAX_VAR_KEY_LENGTH +} from './constants' -export function getSelectedNode( - selection: RangeSelection, -): TextNode | ElementNode { +export function getSelectedNode(selection: RangeSelection): TextNode | ElementNode { const anchor = selection.anchor const focus = selection.focus const anchorNode = selection.anchor.getNode() const focusNode = selection.focus.getNode() - if (anchorNode === focusNode) - return anchorNode + if (anchorNode === focusNode) return anchorNode const isBackward = selection.isBackward() - if (isBackward) - return $isAtNodeEnd(focus) ? anchorNode : focusNode - else - return $isAtNodeEnd(anchor) ? anchorNode : focusNode + if (isBackward) return $isAtNodeEnd(focus) ? anchorNode : focusNode + else return $isAtNodeEnd(anchor) ? anchorNode : focusNode } export function registerLexicalTextEntity( editor: LexicalEditor, getMatch: (text: string) => null | EntityMatch, targetNode: Klass, - createNode: (textNode: TextNode) => T, + createNode: (textNode: TextNode) => T ) { const isTargetNode = (node: LexicalNode | null | undefined): node is T => { return node instanceof targetNode @@ -56,8 +45,7 @@ export function registerLexicalTextEntity( } const textNodeTransform = (node: TextNode) => { - if (!node.isSimpleText()) - return + if (!node.isSimpleText()) return const prevSibling = node.getPreviousSibling() let text = node.getTextContent() @@ -73,8 +61,7 @@ export function registerLexicalTextEntity( if (prevMatch === null || getMode(prevSibling) !== 0) { replaceWithSimpleText(prevSibling) return - } - else { + } else { const diff = prevMatch.end - previousText.length if (diff > 0) { @@ -85,8 +72,7 @@ export function registerLexicalTextEntity( if (diff === text.length) { node.remove() - } - else { + } else { const remainingText = text.slice(diff) node.setTextContent(remainingText) } @@ -94,8 +80,7 @@ export function registerLexicalTextEntity( return } } - } - else if (prevMatch === null || prevMatch.start < previousText.length) { + } else if (prevMatch === null || prevMatch.start < previousText.length) { return } } @@ -113,44 +98,34 @@ export function registerLexicalTextEntity( const nextMatch = getMatch(nextText) if (nextMatch === null) { - if (isTargetNode(nextSibling)) - replaceWithSimpleText(nextSibling) - else - nextSibling.markDirty() + if (isTargetNode(nextSibling)) replaceWithSimpleText(nextSibling) + else nextSibling.markDirty() return - } - else if (nextMatch.start !== 0) { + } else if (nextMatch.start !== 0) { return } } - } - else { + } else { const nextMatch = getMatch(nextText) - if (nextMatch !== null && nextMatch.start === 0) - return + if (nextMatch !== null && nextMatch.start === 0) return } - if (match === null) - return + if (match === null) return - if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) - continue + if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue let nodeToReplace - if (match.start === 0) - [nodeToReplace, currentNode] = currentNode.splitText(match.end) - else - [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end) + if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end) + else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end) const replacementNode = createNode(nodeToReplace) replacementNode.setFormat(nodeToReplace.getFormat()) nodeToReplace.replace(replacementNode) - if (currentNode == null) - return + if (currentNode == null) return } } @@ -181,8 +156,7 @@ export function registerLexicalTextEntity( if ($isTextNode(nextSibling) && nextSibling.isTextEntity()) { replaceWithSimpleText(nextSibling) // This may have already been converted in the previous block - if (isTargetNode(node)) - replaceWithSimpleText(node) + if (isTargetNode(node)) replaceWithSimpleText(node) } } @@ -194,10 +168,9 @@ export function registerLexicalTextEntity( export const decoratorTransform = ( node: CustomTextNode, getMatch: (text: string) => null | EntityMatch, - createNode: (textNode: TextNode) => LexicalNode, + createNode: (textNode: TextNode) => LexicalNode ) => { - if (!node.isSimpleText()) - return + if (!node.isSimpleText()) return const prevSibling = node.getPreviousSibling() let text = node.getTextContent() @@ -219,79 +192,56 @@ export const decoratorTransform = ( if (nextMatch === null) { nextSibling.markDirty() return - } - else if (nextMatch.start !== 0) { + } else if (nextMatch.start !== 0) { return } } - } - else { + } else { const nextMatch = getMatch(nextText) - if (nextMatch !== null && nextMatch.start === 0) - return + if (nextMatch !== null && nextMatch.start === 0) return } - if (match === null) - return + if (match === null) return - if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) - continue + if (match.start === 0 && $isTextNode(prevSibling) && prevSibling.isTextEntity()) continue let nodeToReplace - if (match.start === 0) - [nodeToReplace, currentNode] = currentNode.splitText(match.end) - else - [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end) + if (match.start === 0) [nodeToReplace, currentNode] = currentNode.splitText(match.end) + else [, nodeToReplace, currentNode] = currentNode.splitText(match.start, match.end) const replacementNode = createNode(nodeToReplace) nodeToReplace.replace(replacementNode) - if (currentNode == null) - return + if (currentNode == null) return } } -function getFullMatchOffset( - documentText: string, - entryText: string, - offset: number, -): number { +function getFullMatchOffset(documentText: string, entryText: string, offset: number): number { let triggerOffset = offset for (let i = triggerOffset; i <= entryText.length; i++) { - if (documentText.substr(-i) === entryText.substr(0, i)) - triggerOffset = i + if (documentText.substr(-i) === entryText.substr(0, i)) triggerOffset = i } return triggerOffset } export function $splitNodeContainingQuery(match: MenuTextMatch): TextNode | null { const selection = $getSelection() - if (!$isRangeSelection(selection) || !selection.isCollapsed()) - return null + if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null const anchor = selection.anchor - if (anchor.type !== 'text') - return null + if (anchor.type !== 'text') return null const anchorNode = anchor.getNode() - if (!anchorNode.isSimpleText()) - return null + if (!anchorNode.isSimpleText()) return null const selectionOffset = anchor.offset const textContent = anchorNode.getTextContent().slice(0, selectionOffset) const characterOffset = match.replaceableString.length - const queryOffset = getFullMatchOffset( - textContent, - match.matchingString, - characterOffset, - ) + const queryOffset = getFullMatchOffset(textContent, match.matchingString, characterOffset) const startOffset = selectionOffset - queryOffset - if (startOffset < 0) - return null + if (startOffset < 0) return null let newNode - if (startOffset === 0) - [newNode] = anchorNode.splitText(selectionOffset) - else - [, newNode] = anchorNode.splitText(startOffset, selectionOffset) + if (startOffset === 0) [newNode] = anchorNode.splitText(selectionOffset) + else [, newNode] = anchorNode.splitText(startOffset, selectionOffset) return newNode } @@ -303,49 +253,57 @@ export function textToEditorState(text: string) { root: { children: paragraph.map((p) => { return { - children: [{ - detail: 0, - format: 0, - mode: 'normal', - style: '', - text: p, - type: 'custom-text', - version: 1, - }], + children: [ + { + detail: 0, + format: 0, + mode: 'normal', + style: '', + text: p, + type: 'custom-text', + version: 1 + } + ], direction: 'ltr', format: '', indent: 0, type: 'paragraph', - version: 1, + version: 1 } }), direction: 'ltr', format: '', indent: 0, type: 'root', - version: 1, - }, + version: 1 + } }) } - - const varRegex = /\{\{(.+?)\}\}/g export const getVars = (value: string) => { - if (!value) - return [] + if (!value) return [] - const keys = value.match(varRegex)?.filter((item) => { - return ![CONTEXT_PLACEHOLDER_TEXT, HISTORY_PLACEHOLDER_TEXT, QUERY_PLACEHOLDER_TEXT, PRE_PROMPT_PLACEHOLDER_TEXT].includes(item) - }).map((item) => { - return item.replace('{{', '').replace('}}', '') - }).filter(key => key.length <= MAX_VAR_KEY_LENGTH) || [] + const keys = + value + .match(varRegex) + ?.filter((item) => { + return ![ + CONTEXT_PLACEHOLDER_TEXT, + HISTORY_PLACEHOLDER_TEXT, + QUERY_PLACEHOLDER_TEXT, + PRE_PROMPT_PLACEHOLDER_TEXT + ].includes(item) + }) + .map((item) => { + return item.replace('{{', '').replace('}}', '') + }) + .filter((key) => key.length <= MAX_VAR_KEY_LENGTH) || [] const keyObj: Record = {} // remove duplicate keys const res: string[] = [] keys.forEach((key) => { - if (keyObj[key]) - return + if (keyObj[key]) return keyObj[key] = true res.push(key) diff --git a/frontend/packages/common/src/components/apispace/code-snippet/code-snippets.type.ts b/frontend/packages/common/src/components/apispace/code-snippet/code-snippets.type.ts index 6a2314a6..31742b03 100644 --- a/frontend/packages/common/src/components/apispace/code-snippet/code-snippets.type.ts +++ b/frontend/packages/common/src/components/apispace/code-snippet/code-snippets.type.ts @@ -1,14 +1,6 @@ -import { $t } from "@common/locales" +import { $t } from '@common/locales' -export type PARAM_TYPE = - | 'string' - | 'float' - | 'integer' - | 'boolean' - | 'date' - | 'time' - | 'datatime' - | string +export type PARAM_TYPE = 'string' | 'float' | 'integer' | 'boolean' | 'date' | 'time' | 'datatime' | string export type PARAM_KEY_REF_TYPE = { key: string type: string @@ -17,7 +9,7 @@ export type PARAM_KEY_REF_TYPE = { attribute?: string description?: string filter?: string - arrayItemKey?:string + arrayItemKey?: string } export type PARAM_TYPE_REF_TYPE = { [key: string | number]: PARAM_TYPE diff --git a/frontend/packages/common/src/components/apispace/code-snippet/generate-code.ts b/frontend/packages/common/src/components/apispace/code-snippet/generate-code.ts index b45d20a7..db4d94ea 100644 --- a/frontend/packages/common/src/components/apispace/code-snippet/generate-code.ts +++ b/frontend/packages/common/src/components/apispace/code-snippet/generate-code.ts @@ -1,8 +1,17 @@ import { cloneDeep } from 'lodash-es' -import { parseFormData, parseFileValue, parseFileType, parseHeaders, parseRequestBodyToString, parseUri, payloadStr, goCodeParseFormData } from './transform' +import { + parseFormData, + parseFileValue, + parseFileType, + parseHeaders, + parseRequestBodyToString, + parseUri, + payloadStr, + goCodeParseFormData +} from './transform' // import { getJson } from '../.@common/utils/'; -import { ApiBodyType } from '@common/const/api-detail'; -import { $t } from '@common/locales'; +import { ApiBodyType } from '@common/const/api-detail' +import { $t } from '@common/locales' function sameNameToParams(params: unknown) { params = cloneDeep(params) @@ -54,9 +63,9 @@ function enrichParams(params: unknown) { export function generateCode( type: string, multipart: boolean, - { protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown, + { protocol, URL, headers, params, method, requestType, apiRequestParamJsonType, raw }: unknown ) { - requestType=ApiBodyType[requestType] + requestType = ApiBodyType[requestType] let code: string = '' const indent = ' ' let urlObj: unknown = {} @@ -243,9 +252,7 @@ export function generateCode( `${indent}"headers": {\r\n` + `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + `${indent}},\r\n` + - `${ - multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : '' - }` + + `${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` + `${indent}"data": data,\r\n` + `${indent}"crossDomain": true\r\n` + '})\r\n' + @@ -334,14 +341,10 @@ export function generateCode( 'var requestInfo={\r\n' + `${indent}"method": "${method}",\r\n` + `${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` + - `${ - urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : '' - }` + + `${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` + `${ urlObj.pathname || urlObj.search - ? `${indent}"path": "${urlObj.pathname || ''}${ - urlObj.search || '' - }",\r\n` + ? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n` : '' }` + `${indent}"headers": {\r\n` + @@ -366,19 +369,11 @@ export function generateCode( `var http = require("${urlObj.protocol.replace(':', '')}");\r\n` + 'var requestInfo={\r\n' + `${indent}"method": "${method}",\r\n` + - `${ - urlObj.hostname - ? `${indent}"hostname": "${urlObj.hostname}",\r\n` - : '' - }` + - `${ - urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : '' - }` + + `${urlObj.hostname ? `${indent}"hostname": "${urlObj.hostname}",\r\n` : ''}` + + `${urlObj.port ? `${indent}"port": "${urlObj.port}",\r\n` : ''}` + `${ urlObj.pathname || urlObj.search - ? `${indent}"path": "${urlObj.pathname || ''}${ - urlObj.search || '' - }",\r\n` + ? `${indent}"path": "${urlObj.pathname || ''}${urlObj.search || ''}",\r\n` : '' }` + `${indent}"headers": {\r\n` + @@ -439,11 +434,7 @@ export function generateCode( } } code = - `${ - (requestType || 'FORMDATA') === 'FORMDATA' && multipart - ? 'var fs = require("fs");\r\n' - : '' - }` + + `${(requestType || 'FORMDATA') === 'FORMDATA' && multipart ? 'var fs = require("fs");\r\n' : ''}` + 'var request = require("request");\r\n' + 'var requestInfo={\r\n' + ` method: "${method}",\r\n` + @@ -472,7 +463,7 @@ export function generateCode( params = sameNameToParams(params) let tmpOutput: unknown = '' params.map((val: unknown, key: number) => { - if(val.data_type === 'file') { + if (val.data_type === 'file') { tmpOutput += ` "${val.name}" =>array(\r\n` + ` "type" => "${parseFileType(val.value)}",\r\n` + @@ -481,11 +472,9 @@ export function generateCode( ` )${key === params.length - 1 ? '' : ','}\r\n` } else { tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r` - } }) - langTmp.paramsStr = - 'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);' + langTmp.paramsStr = 'addForm(' + `${tmpOutput.length ? `array(\r\n${tmpOutput})` : ''}` + '\r\n);' langTmp.fileValue = parseFileValue(params) } else { langTmp.paramsStr = `append(new http\\QueryString(array({\r\n${parseFormData(params, { @@ -516,9 +505,7 @@ export function generateCode( const tmpOutput: unknown = [] urlObj.searchParams.forEach((val: unknown, key: unknown) => { tmpOutput.push(` ${JSON.stringify(key)} => ${JSON.stringify(val)}`) - langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join( - ',\r\n' - )}\r\n)));` + langTmp.queryStr = `$request->setQuery(new http\\QueryString(array(\r\n${tmpOutput.join(',\r\n')}\r\n)));` }) if (multipart) { code = @@ -534,7 +521,7 @@ export function generateCode( '$request->setBody($body);\r\n\r\n' + `$request->getBody()->${langTmp.paramsStr}\r\n\r\n` + '$request->setHeaders(array(\r\n' + - `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + + `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + ' "Content-Type":"multipart/form-data"\r\n' + '));\r\n\r\n' + '$client->enqueue($request)->send();\r\n' + @@ -552,7 +539,7 @@ export function generateCode( '$request->setBody($body);\r\n\r\n' + `${langTmp.queryStr ? `${langTmp.queryStr}\r\n\r\n` : ''}` + '$request->setHeaders(array(\r\n' + - `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + + `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + '));\r\n\r\n' + '$client->enqueue($request)->send();\r\n' + '$response = $client->getResponse();\r\n\r\n' + @@ -572,9 +559,7 @@ export function generateCode( let tmpOutput = '' params.map((val: unknown, key: number) => { if (val.data_type === 'file') { - tmpOutput += ` "${val.name}" => new CURLFile($file_path)${ - key === params.length - 1 ? '' : ',' - }\r\n` + tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n` } else { tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r` } @@ -614,7 +599,7 @@ export function generateCode( ` CURLOPT_CUSTOMREQUEST => "${method}",\r\n` + ` CURLOPT_POSTFIELDS => ${langTmp.paramsStr},\r\n` + ' CURLOPT_HTTPHEADER => array(\r\n' + - `${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` + + `${langTmp.headerStr ? `${langTmp.headerStr},\r\n` : ''}` + ' "Content-Type:multipart/form-data"' + '\r\n ),\r\n' + '));\r\n\r\n' + @@ -769,36 +754,33 @@ export function generateCode( tmpOutput.push(`${JSON.stringify(key)} : ${JSON.stringify(val)}`) // langTmp.querystring = `querystring={${tmpOutput.join(',')}};` langTmp.querystring = `{${tmpOutput.join(',')}}` - }) - if(multipart) { - code = - 'import requests \r\n\r\n' + - 'headers = {\r\n' + - `${langTmp.headerStr}\r\n` + - '}\r\n' + - `url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` + - '//获取文件,需填路径 \r\n' + - "file_path = '' \r\n" + - `filename = "${langTmp.fileValue}" \r\n` + - `filetype = "${langTmp.fileType}" \r\n` + - 'data = { \r\n' + - `${langTmp.paramsStr}\r\n` + - '} \r\n' + - 'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' + - `response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` + - 'print(response.text)\r\n' + if (multipart) { + code = + 'import requests \r\n\r\n' + + 'headers = {\r\n' + + `${langTmp.headerStr}\r\n` + + '}\r\n' + + `url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n` + + '//获取文件,需填路径 \r\n' + + "file_path = '' \r\n" + + `filename = "${langTmp.fileValue}" \r\n` + + `filetype = "${langTmp.fileType}" \r\n` + + 'data = { \r\n' + + `${langTmp.paramsStr}\r\n` + + '} \r\n' + + 'files = {"file": (filename, open(file_path, "rb"), filetype)} \r\n' + + `response=requests.${method.toLowerCase()}(url, files=files, headers=headers, data=data)\r\n` + + 'print(response.text)\r\n' } else { - code = - 'import requests\r\n\r\n' + - `url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` + - `payload = ${ - langTmp.querystring ? langTmp.querystring : langTmp.paramsStr - }\r\n\r\n` + - `headers = {\r\n${langTmp.headerStr}` + - '\r\n}\r\n\r\n' + - `response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` + - 'print(response.text)\r\n' + code = + 'import requests\r\n\r\n' + + `url = "${method === 'GET' ? urlObj.origin + urlObj.pathname : urlObj.href}"\r\n\r\n` + + `payload = ${langTmp.querystring ? langTmp.querystring : langTmp.paramsStr}\r\n\r\n` + + `headers = {\r\n${langTmp.headerStr}` + + '\r\n}\r\n\r\n' + + `response=requests.request("${method}", url, ${payloadStr(method, headers)}, headers=headers)\r\n\r\n` + + 'print(response.text)\r\n' } break } @@ -815,9 +797,7 @@ export function generateCode( let tmpOutput = '' params.map((val: unknown, key: number) => { if (val.data_type === 'file') { - tmpOutput += ` "${val.name}" => new CURLFile($file_path)${ - key === params.length - 1 ? '' : ',' - }\r\n` + tmpOutput += ` "${val.name}" => new CURLFile($file_path)${key === params.length - 1 ? '' : ','}\r\n` } else { tmpOutput += ` ${JSON.stringify(val.name)} => ${JSON.stringify(val.value)}\r` } @@ -847,7 +827,7 @@ export function generateCode( `request = Net::HTTP::${method.toLowerCase().replace(/^\S/, (s: string) => { return s?.toUpperCase() })}.new(url)\r\n` + - `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + + `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + `request.body = ${langTmp.paramsStr}\r\n\r\n` + 'response = http.request(request)\r\n' + 'puts response.read_body' @@ -883,25 +863,19 @@ export function generateCode( } } code = - `${ - requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : '' - } http ${ - (requestType || 'FORMDATA').toString() === 'FORMDATA' && - !multipart + `${requestType === 'JSON' ? `printf '${langTmp.paramsStr}'|` : ''} http ${ + (requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart ? '--form' - : (requestType || 'JSON').toString() === 'JSON' && - !multipart - ? '--follow' - : '' + : (requestType || 'JSON').toString() === 'JSON' && !multipart + ? '--follow' + : '' } ${multipart ? '--ignore-stdin --form --follow' : ''} ${method} '${ urlObj.href }' ${multipart ? '\\\r' : '\\'}` + `${multipart ? langTmp.paramsStr : ''}\r` + `${langTmp.headerStr}` + `${ - (requestType || 'FORMDATA').toString() === 'FORMDATA' && - !multipart && - langTmp.paramsStr + (requestType || 'FORMDATA').toString() === 'FORMDATA' && !multipart && langTmp.paramsStr ? ` \\\r\n${langTmp.paramsStr}` : '' }` @@ -923,7 +897,6 @@ export function generateCode( } }) langTmp.paramsStr = tmpOutput - } else { const tmpOutput = parseFormData(params, { format: '${name}=${value}', @@ -1024,7 +997,7 @@ export function generateCode( ' if err != nil {\r\n' + ' return nil, err\r\n' + ' }\r\n' + - `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + + `${langTmp.headerStr ? `${langTmp.headerStr}\r\n` : ''}` + ' req.Header.Add("Content-Type", writer.FormDataContentType())\r\n\r\n' + ' client := &http.Client{}\r\n' + ' resp, err := client.Do(req)\r\n' + @@ -1071,7 +1044,7 @@ export function generateCode( } default: { langTmp.paramsStr = requestParam ? JSON.stringify(requestParam) : '' - code = + code = 'package main\r\n\r\n' + 'import (\r\n' + ' "bytes"\r\n' + @@ -1091,7 +1064,7 @@ export function generateCode( 'func request() ([]byte, error) {\r\n' + ` uri := "${urlObj.href}"\r\n\r\n` + ` ${ - langTmp.paramsStr + langTmp.paramsStr ? ` payload := map[string]interface{}${langTmp.paramsStr}` : ' payload := strings.NewReader("")' }\r\n\r\n` + @@ -1116,7 +1089,7 @@ export function generateCode( map: stringifyHeaders }) let mediaType = 'application/octet-stream' - switch ( (requestType || 'FORMDATA').toUpperCase()) { + switch ((requestType || 'FORMDATA').toUpperCase()) { case 'FORMDATA': { if (multipart) { mediaType = 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' @@ -1154,8 +1127,8 @@ export function generateCode( break } } - if(multipart) { - code = + if (multipart) { + code = 'OkHttpClient client = new OkHttpClient();\r\n\r\n' + '//获取文件,需填路径 \r\n' + 'File file = new File(""); \r\n\r\n' + @@ -1175,11 +1148,7 @@ export function generateCode( code = 'OkHttpClient client = new OkHttpClient().newBuilder().build();\r\n' + `MediaType mediaType = MediaType.parse("${mediaType}");\r\n` + - `${ - method === 'GET' - ? '' - : `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n` - }` + + `${method === 'GET' ? '' : `RequestBody body = RequestBody.create(mediaType, ${langTmp.paramsStr});\r\n`}` + 'Request request = new Request.Builder()\r\n' + ` .url("${urlObj.href}")\r\n` + ` .method("${method}",${method === 'GET' ? 'null' : 'body'})\r\n` + @@ -1188,7 +1157,7 @@ export function generateCode( 'Response response = client.newCall(request).execute();\r\n' + 'System.out.println(response.body().string());\r\n' } - + break } @@ -1233,9 +1202,7 @@ export function generateCode( `${indent}"header": {\r\n` + `${langTmp.headerStr}\r\n` + `${indent}},\r\n` + - `${ - multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : '' - }` + + `${multipart ? `${indent}"processData": false,\r\n${indent}"contentType": false,\r\n` : ''}` + `${langTmp.paramsStr ? `${indent}"data": data,\r\n` : ''}` + `${indent}"success": (response)=> {\r\n` + `${indent + indent}console.log(response.data)\r\n` + diff --git a/frontend/packages/common/src/components/apispace/code-snippet/index.tsx b/frontend/packages/common/src/components/apispace/code-snippet/index.tsx index 5fca8e4b..58fdbe1d 100644 --- a/frontend/packages/common/src/components/apispace/code-snippet/index.tsx +++ b/frontend/packages/common/src/components/apispace/code-snippet/index.tsx @@ -1,218 +1,228 @@ -import { useEffect, useMemo } from 'react'; -import { Cascader } from 'antd'; -import CODE_LANG from '@common/const/code/const'; -import type { DefaultOptionType } from 'antd/es/cascader'; -import { useState } from 'react'; -import { cloneDeep } from 'lodash-es'; -import { paramsJsonType } from './code-snippets.type'; -import { DOMAIN_SUFIX } from './code-example.type'; -import { transfromUrlParam } from './transform'; -import { generateCode } from './generate-code'; -import {ApiDetail} from "@common/const/api-detail"; -import {Codebox} from "@common/components/postcat//api/Codebox"; -import {Collapse} from "@common/components/postcat/api/Collapse"; -import {Box} from "@mui/material"; -import { $t } from '@common/locales'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; +import { useEffect, useMemo } from 'react' +import { Cascader } from 'antd' +import CODE_LANG from '@common/const/code/const' +import type { DefaultOptionType } from 'antd/es/cascader' +import { useState } from 'react' +import { cloneDeep } from 'lodash-es' +import { paramsJsonType } from './code-snippets.type' +import { DOMAIN_SUFIX } from './code-example.type' +import { transfromUrlParam } from './transform' +import { generateCode } from './generate-code' +import { ApiDetail } from '@common/const/api-detail' +import { Codebox } from '@common/components/postcat//api/Codebox' +import { Collapse } from '@common/components/postcat/api/Collapse' +import { Box } from '@mui/material' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' type CodeSnippetCompoType = { - title:string - api:ApiDetail, - extraTitle:unknown, - extraContent:unknown, - minLines:number + title: string + api: ApiDetail + extraTitle: unknown + extraContent: unknown + minLines: number } - const file: unknown[] = [] - const env: unknown = {} - const loading: boolean = false - const codeMode: string = 'rust' - const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search'] - const DOMAIN_REGEX: RegExp = new RegExp( - `^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join( - '|' - )})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))` - ) +const file: unknown[] = [] +const env: unknown = {} +const loading: boolean = false +const codeMode: string = 'rust' +const codeMens: string[] = ['reset', 'copy', 'download', 'newTab', 'search'] +const DOMAIN_REGEX: RegExp = new RegExp( + `^(((http|ftp|https):\/\/)|)(([\\\w\\\-_]+([\\\w\\\-\\\.]*)?(\\\.(${DOMAIN_SUFIX.join( + '|' + )})))|((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(localhost))((\\\/)|(\\\?)|(:)|($))` +) - let isMultipart: boolean = false +let isMultipart: boolean = false - export default function CodeSnippetCompo({title,api, extraTitle, extraContent, minLines=15}: CodeSnippetCompoType) { - const {state } = useGlobalContext() - // const [tokenState ] = useTokenBasicInfo() - const pretreatmentRequestInfo = (apiDoc: ApiDetail) =>{ - isMultipart = false - const result: ApiDetail = cloneDeep(apiDoc) - const files: unknown = file || [] - let isMuti: boolean = false - const headers: string[] = [] - let alreadyHadContentType: boolean = false - result.headers = [] - const originHeader = apiDoc.requestParams?.headerParams - //处理请求头部 - originHeader?.forEach((header: unknown) => { - // if ( - // tokenState?.selected_X_apibee_token && - // originHeader.length && - // originHeader[0].name == 'X-APISpace-Token' - // ) { - // originHeader[0].value = tokenState?.selected_X_apibee_token - // } - const { checkbox, name } = header - if ((checkbox || !header.hasOwnProperty('checkbox')) && name) { - headers.push(name?.toLowerCase()) - result.headers.push(header) - if (/content-type/i.test(name)) { - alreadyHadContentType = true - } +export default function CodeSnippetCompo({ + title, + api, + extraTitle, + extraContent, + minLines = 15 +}: CodeSnippetCompoType) { + const { state } = useGlobalContext() + // const [tokenState ] = useTokenBasicInfo() + const pretreatmentRequestInfo = (apiDoc: ApiDetail) => { + isMultipart = false + const result: ApiDetail = cloneDeep(apiDoc) + const files: unknown = file || [] + let isMuti: boolean = false + const headers: string[] = [] + let alreadyHadContentType: boolean = false + result.headers = [] + const originHeader = apiDoc.requestParams?.headerParams + //处理请求头部 + originHeader?.forEach((header: unknown) => { + // if ( + // tokenState?.selected_X_apibee_token && + // originHeader.length && + // originHeader[0].name == 'X-APISpace-Token' + // ) { + // originHeader[0].value = tokenState?.selected_X_apibee_token + // } + const { checkbox, name } = header + if ((checkbox || !header.hasOwnProperty('checkbox')) && name) { + headers.push(name?.toLowerCase()) + result.headers.push(header) + if (/content-type/i.test(name)) { + alreadyHadContentType = true } - }) - const query: unknown = {} + } + }) + const query: unknown = {} - apiDoc.requestParams?.queryParams?.forEach((query: unknown) => { - const { checkbox, name } = query - if ((checkbox || !query.hasOwnProperty('checkbox')) && name) { - query[name] = query?.paramAttr.example || '' + apiDoc.requestParams?.queryParams?.forEach((query: unknown) => { + const { checkbox, name } = query + if ((checkbox || !query.hasOwnProperty('checkbox')) && name) { + query[name] = query?.paramAttr.example || '' + } + }) + result.URL = transfromUrlParam(result.uri, query) + + //处理 restful 参数 + apiDoc.requestParams?.restParams?.forEach((rest: unknown) => { + if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) { + if (eval(`/:${rest.name}/`).test(result.URL.trim())) { + result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example) + } else if ( + result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 && + result.URL.trim().indexOf(`{${rest.name}}`) > -1 + ) { + result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example) } - }) - result.URL = transfromUrlParam(result.uri, query) + } + }) - //处理 restful 参数 - apiDoc.requestParams?.restParams?.forEach((rest: unknown) => { - if ((rest.checkbox || !rest.hasOwnProperty('checkbox')) && rest.name && rest.paramAttr.example) { - if (eval(`/:${rest.name}/`).test(result.URL.trim())) { - result.URL = result.URL.replaceAll(`:${rest.name}`, rest.paramAttr.example ) - } else if ( - result.URL.trim().indexOf(`{{${rest.name}}}`) == -1 && - result.URL.trim().indexOf(`{${rest.name}}`) > -1 - ) { - result.URL = result.URL.replaceAll(`{${rest.name}}`, rest.paramAttr.example) - } - } - }) - - result.params = [] - //为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值 - switch (result.requestParams?.bodyParams?.[0]?.contentType) { - case 0: { - result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => { - if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) { - if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) { - isMuti = true - body.files = files[key] || [] - } - result.params.push(body) + result.params = [] + //为请求参数 中的header、reset、body、query 添加 value 和 valueQuery 的值 + switch (result.requestParams?.bodyParams?.[0]?.contentType) { + case 0: { + result.requestParams?.bodyParams?.forEach((body: unknown, key: unknown) => { + if ((body.checkbox || !body.hasOwnProperty('checkbox')) && body.name) { + if (paramsJsonType[body.dataType] == 'string' && body.paramAttr.example) { + isMuti = true + body.files = files[key] || [] } + result.params.push(body) + } + }) + if (!alreadyHadContentType) { + if (isMuti) { + isMultipart = true + result.headers.push({ + name: 'Content-Type', + value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW', + checkbox: true + }) + } else { + result.headers.push({ + name: 'Content-Type', + value: 'application/x-www-form-urlencoded', + checkbox: true + }) + } + } + break + } + case 1: { + result.params = apiDoc.requestParams?.bodyParams + break + } + case 2: { + result.params = apiDoc.requestParams?.bodyParams + if (!alreadyHadContentType) { + result.headers.push({ + name: 'Content-Type', + value: 'application/json', + checkbox: true }) - if (!alreadyHadContentType) { - if (isMuti) { - isMultipart = true - result.headers.push({ - name: 'Content-Type', - value: 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW', - checkbox: true - }) - } else { - result.headers.push({ - name: 'Content-Type', - value: 'application/x-www-form-urlencoded', - checkbox: true - }) - } - } - break - } - case 1: { - result.params = apiDoc.requestParams?.bodyParams - break - } - case 2: { - result.params = apiDoc.requestParams?.bodyParams - if (!alreadyHadContentType) { - result.headers.push({ - name: 'Content-Type', - value: 'application/json', - checkbox: true - }) - } - break - } - case 3: { - result.params = apiDoc.requestParams?.bodyParams - if (!alreadyHadContentType) { - result.headers.push({ - name: 'Content-Type', - value: 'application/xml', - checkbox: true - }) - } - break } + break + } + case 3: { + result.params = apiDoc.requestParams?.bodyParams + if (!alreadyHadContentType) { + result.headers.push({ + name: 'Content-Type', + value: 'application/xml', + checkbox: true + }) + } + break } - - result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0 - return result } - const [code, setCode] = useState('') - const [lang, setLang] = useState([20]) + result.requestType = result.requestParams?.bodyParams?.[0]?.contentType || 0 + return result + } - let tempCode = '' - const getCode = (language: number | string) => { - if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) { - tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例') - setCode(tempCode) - return - } - tempCode = generateCode( - language.toString(), - isMultipart, - pretreatmentRequestInfo(cloneDeep(api)) - ) + const [code, setCode] = useState('') + const [lang, setLang] = useState([20]) + + let tempCode = '' + const getCode = (language: number | string) => { + if (!['HTTPS', 'HTTP'].includes(api.protocol?.toUpperCase())) { + tempCode = $t('暂不支持生成非 HTTPS 或非 HTTP 协议的代码示例') setCode(tempCode) + return } + tempCode = generateCode(language.toString(), isMultipart, pretreatmentRequestInfo(cloneDeep(api))) + setCode(tempCode) + } - useEffect(() => { - if(!Object.keys(api).length) return - getCode(lang[lang.length -1 ]) - }, [api]) + useEffect(() => { + if (!Object.keys(api).length) return + getCode(lang[lang.length - 1]) + }, [api]) + const onChange = (value: number[], record: DefaultOptionType[]) => { + const num = value[value.length - 1] + setLang(value) + if (!Object.keys(api).length) return + getCode(num) + } - const onChange = (value: number[],record:DefaultOptionType[]) => { - const num = value[value.length - 1] - setLang(value) - if(!Object.keys(api).length) return - getCode(num) - }; + const filter = (inputValue: string, path: DefaultOptionType[]) => + path.some((option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1) - const filter = (inputValue: string, path: DefaultOptionType[]) => - path.some( - (option) => (option.label as string).toLowerCase().indexOf(inputValue.toLowerCase()) > -1, - ); + const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...')) + const [selectItemTxt, setSelectItemTxt] = useState('') - const [placeholderTxt, setPlaceholderTxt] = useState($t('搜索编程语言...')) - const [selectItemTxt, setSelectItemTxt ] = useState('') - - const codeLangOptions = useMemo(()=>CODE_LANG.map(x=>({...x, label:$t(x.label as string)})),[state.language]) - return ( - - - - <> - {$t('编程语言')}: CODE_LANG.map((x) => ({ ...x, label: $t(x.label as string) })), + [state.language] + ) + return ( + + + <> + + {$t('编程语言')}: + onChange(value as unknown as number[],record)} + onChange={(value, record) => onChange(value as unknown as number[], record)} placeholder={placeholderTxt} value={lang} // 当前的值 showSearch={{ filter }} size="small" allowClear={false} // onDropdownVisibleChange={value => openChange(value)} - />} - language={'javascript'} value={code} readOnly={true} height={'250px'} width={'100%'}/> - - - - - ) - - } \ No newline at end of file + /> + + } + language={'javascript'} + value={code} + readOnly={true} + height={'250px'} + width={'100%'} + /> + + + + ) +} diff --git a/frontend/packages/common/src/components/apispace/code-snippet/transform.ts b/frontend/packages/common/src/components/apispace/code-snippet/transform.ts index 6069f895..d5430ebb 100644 --- a/frontend/packages/common/src/components/apispace/code-snippet/transform.ts +++ b/frontend/packages/common/src/components/apispace/code-snippet/transform.ts @@ -1,6 +1,6 @@ import { cloneDeep } from 'lodash-es' -import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type'; -import { tranformJson, tranformXml } from './util'; +import { PARAM_KEY_REF_TYPE, PARAM_TYPE_REF_TYPE } from './code-snippets.type' +import { tranformJson, tranformXml } from './util' type LANG_TYPE = 'Java' | 'HTTP' | 'shellHttpie' | 'go' | 'NodeJSNative' type PARAM_HEADER_TYPE = { name: string; value: string } type PARSE_OPTS_TYPE = { @@ -30,201 +30,199 @@ export const paramsJsonType: unknown = { NULL: 'null' } - - /** - * @description 拼接地址栏query参数,返回url - * @param {string} url 地址栏url - * @param {Object} query 地址栏query对象 - */ - export function transfromUrlParam(url: string, query: { [key: string]: string }) { - const querys: string[] = [] - Object.entries(query).forEach((item: string[]) => { - querys.push(`${item[0]}=${item[1]}`) - }) - if (!querys.length) return url - return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}` - } - export function parseUri(protocol: string, url: string) { - if (!/((http:\/\/)|(https:\/\/))/.test(url)) { - url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url - } - return url +/** + * @description 拼接地址栏query参数,返回url + * @param {string} url 地址栏url + * @param {Object} query 地址栏query对象 + */ +export function transfromUrlParam(url: string, query: { [key: string]: string }) { + const querys: string[] = [] + Object.entries(query).forEach((item: string[]) => { + querys.push(`${item[0]}=${item[1]}`) + }) + if (!querys.length) return url + return `${url}${url.includes('?') ? '&' : '?'}${querys.join('&')}` +} +export function parseUri(protocol: string, url: string) { + if (!/((http:\/\/)|(https:\/\/))/.test(url)) { + url = (protocol == 'HTTPS' ? 'https://' : 'http://') + url } + return url +} - /** - * @description 处理FormData格式请求参数 - * @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]} - * @param {Array} params 待拼接数组 - */ - export function parseFormData(params: unknown, { map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {}) { - if (map) params = cloneDeep(params) - if (init) params = init(params) - if (format) { - //x-www - const result: unknown = [] - params.map((val: unknown) => { - if (map) val = map(val) - result.push(format.replace('${name}', val.name).replace('${value}', val.value)) - }) - return result.join(separator || '&') - } - //multipart - let result: string = '' - const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW' - params.forEach((val: unknown) => { +/** + * @description 处理FormData格式请求参数 + * @param {Object} options {format:生成Formdata格式[option],separator:组合字符串的分割符[option]} + * @param {Array} params 待拼接数组 + */ +export function parseFormData( + params: unknown, + { map, init, format, separator, langType, hasFileParams }: PARSE_OPTS_TYPE = {} +) { + if (map) params = cloneDeep(params) + if (init) params = init(params) + if (format) { + //x-www + const result: unknown = [] + params.map((val: unknown) => { if (map) val = map(val) - if (val.files) { - if (val.files.length) { - result += `------${boundary}\r\n` - val.files.map((childVal: unknown) => { - result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n` - if (typeof childVal === 'string') { - result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '') - .replace(/^data:/, '') - .replace(/;$/, '')}\r\n` - } else { - result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '') - .replace(/^data:/, '') - .replace(/;$/, '')}\r\n` - } - result += '\r\n' - }) - } - } else { - result += `------${boundary}\r\n` - result += `content-disposition: form-data; name="${val.name}"\r\n` - result += '\r\n' - result += `${val.value}\r\n` - } + result.push(format.replace('${name}', val.name).replace('${value}', val.value)) }) - result += `------${boundary}--` - return result + return result.join(separator || '&') } - export function parseFileValue (params: unknown[]) { - const tmp = { - output: '' - } - if (params && params.length) { - for (let i = 0; i < params.length; i++) { - if (params[i].data_type === 'file') { - tmp.output = params[i].value || '' - break - } + //multipart + let result: string = '' + const boundary: string = 'WebKitFormBoundary7MA4YWxkTrZu0gW' + params.forEach((val: unknown) => { + if (map) val = map(val) + if (val.files) { + if (val.files.length) { + result += `------${boundary}\r\n` + val.files.map((childVal: unknown) => { + result += `content-disposition: form-data; name="${val.name}"; filename="${val.value}"\r\n` + if (typeof childVal === 'string') { + result += `Content-Type: ${((childVal.match(/data:(.*);/) || [])[0] || '') + .replace(/^data:/, '') + .replace(/;$/, '')}\r\n` + } else { + result += `Content-Type: ${(childVal.dataUrl.match(/data:(.*);/)[0] || '') + .replace(/^data:/, '') + .replace(/;$/, '')}\r\n` + } + result += '\r\n' + }) } - return tmp.output - } - } - - - export function payloadStr (method: string, headers: unknown[]) { - let tmpStr = '' - if (method === 'GET') { - tmpStr = 'params=payload' } else { - headers.forEach((item) => { - if (item.name === 'Content-Type') { - switch (item.description || item.value) { - case 'application/x-www-form-urlencoded': - tmpStr = 'data=payload' - break - case 'application/json': - tmpStr = 'data=json.dumps(payload)' - break - default: { - tmpStr = 'params=payload' - } + result += `------${boundary}\r\n` + result += `content-disposition: form-data; name="${val.name}"\r\n` + result += '\r\n' + result += `${val.value}\r\n` + } + }) + result += `------${boundary}--` + return result +} +export function parseFileValue(params: unknown[]) { + const tmp = { + output: '' + } + if (params && params.length) { + for (let i = 0; i < params.length; i++) { + if (params[i].data_type === 'file') { + tmp.output = params[i].value || '' + break + } + } + return tmp.output + } +} + +export function payloadStr(method: string, headers: unknown[]) { + let tmpStr = '' + if (method === 'GET') { + tmpStr = 'params=payload' + } else { + headers.forEach((item) => { + if (item.name === 'Content-Type') { + switch (item.description || item.value) { + case 'application/x-www-form-urlencoded': + tmpStr = 'data=payload' + break + case 'application/json': + tmpStr = 'data=json.dumps(payload)' + break + default: { + tmpStr = 'params=payload' } } - }) - } - return tmpStr - } - - export function goCodeParseFormData(params: unknown[]) { - if (!params.length) return - let output = '' - params.forEach((item) => { - output += `payload.Set("${item.name}", "${item.value}")\r\n ` - }) - return output - } - export function parseFileType (fileValue: string) { - const isPng = fileValue.endsWith('.png') - const isJpeg = fileValue.endsWith('.jpeg') - const isJpg = fileValue.endsWith('.jpg') - let result = 'image/jpeg' - if (isPng) { - result = 'image/png' - } else if (isJpg) { - result = 'image/jpeg' - } else if (isJpeg) { - result = 'image/jpeg' - } - return result - } - /** - * @description 处理请求头格式 - * @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]} - * @param {Array} headers 待拼接数组 - */ - export function parseHeaders( - headers: PARAM_HEADER_TYPE[], - { map, filter, format, separator }: PARSE_OPTS_TYPE = {} - ) { - const result = [] - if (map) { - headers = cloneDeep(headers) - } - for (const key in headers) { - let val = headers[key] - if (map) { - val = map(val) } - if (filter) { - if (filter(val)) { - result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value)) - } - } else { + }) + } + return tmpStr +} + +export function goCodeParseFormData(params: unknown[]) { + if (!params.length) return + let output = '' + params.forEach((item) => { + output += `payload.Set("${item.name}", "${item.value}")\r\n ` + }) + return output +} +export function parseFileType(fileValue: string) { + const isPng = fileValue.endsWith('.png') + const isJpeg = fileValue.endsWith('.jpeg') + const isJpg = fileValue.endsWith('.jpg') + let result = 'image/jpeg' + if (isPng) { + result = 'image/png' + } else if (isJpg) { + result = 'image/jpeg' + } else if (isJpeg) { + result = 'image/jpeg' + } + return result +} +/** + * @description 处理请求头格式 + * @param {Object} options {format:生成请求头格式[option],separator:组合字符串的分割符[option]} + * @param {Array} headers 待拼接数组 + */ +export function parseHeaders(headers: PARAM_HEADER_TYPE[], { map, filter, format, separator }: PARSE_OPTS_TYPE = {}) { + const result = [] + if (map) { + headers = cloneDeep(headers) + } + for (const key in headers) { + let val = headers[key] + if (map) { + val = map(val) + } + if (filter) { + if (filter(val)) { result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value)) } + } else { + result.push(format?.replace('${name}', val.name)?.replace('${value}', val.value)) } - return result.join(separator || '') - } - const keyRefs: PARAM_KEY_REF_TYPE = { - key: 'name', - type: 'data_type', - value: 'value', - childKey: 'child_list', - arrayItemKey: 'isArrItem' } + return result.join(separator || '') +} +const keyRefs: PARAM_KEY_REF_TYPE = { + key: 'name', + type: 'data_type', + value: 'value', + childKey: 'child_list', + arrayItemKey: 'isArrItem' +} - const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType +const typeRefs: PARAM_TYPE_REF_TYPE = paramsJsonType - export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => { - let result: string = '' - switch ((requestType || 'FORAMDATA').toString()) { - case 'RAW': { - //raw - // todo - result = raw - break - } - case 'JSON': { - //json - if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name' - result = tranformJson(params, keyRefs, typeRefs) - if (apiRequestParamJsonType === 'ARRAY') { - //array - result = `[${result}]` - } - break - } - case 'XML': { - //xml - if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name' - result = tranformXml(params, keyRefs, typeRefs) - break - } +export const parseRequestBodyToString = ({ requestType, params, apiRequestParamJsonType, raw }: unknown) => { + let result: string = '' + switch ((requestType || 'FORAMDATA').toString()) { + case 'RAW': { + //raw + // todo + result = raw + break } - return result - } \ No newline at end of file + case 'JSON': { + //json + if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name' + result = tranformJson(params, keyRefs, typeRefs) + if (apiRequestParamJsonType === 'ARRAY') { + //array + result = `[${result}]` + } + break + } + case 'XML': { + //xml + if (!params[0]?.hasOwnProperty('value')) keyRefs.value = 'name' + result = tranformXml(params, keyRefs, typeRefs) + break + } + } + return result +} diff --git a/frontend/packages/common/src/components/apispace/code-snippet/util.ts b/frontend/packages/common/src/components/apispace/code-snippet/util.ts index 3d1ac481..1e60bbf2 100644 --- a/frontend/packages/common/src/components/apispace/code-snippet/util.ts +++ b/frontend/packages/common/src/components/apispace/code-snippet/util.ts @@ -1,19 +1,25 @@ -import {Random} from 'mockjs' -import { PARAM_KEY_REF_TYPE, PARAM_LIST_TYPE, PARAM_LIS_ITEM_TYPE, PARAM_TYPE, PARAM_TYPE_REF_TYPE } from "./code-snippets.type" +import { Random } from 'mockjs' +import { + PARAM_KEY_REF_TYPE, + PARAM_LIST_TYPE, + PARAM_LIS_ITEM_TYPE, + PARAM_TYPE, + PARAM_TYPE_REF_TYPE +} from './code-snippets.type' const DEFAULT_PARAM_KEY_REF: PARAM_KEY_REF_TYPE = { key: 'key', type: 'type', value: 'value' } - /** - * 将自定义列表转换为 xml - * @param list 列表 - * @param keyRefs 关键词映射 - * @param random 是否随机值 - * @returns xml 字符串 - */ - export function tranformXml( +/** + * 将自定义列表转换为 xml + * @param list 列表 + * @param keyRefs 关键词映射 + * @param random 是否随机值 + * @returns xml 字符串 + */ +export function tranformXml( list: PARAM_LIST_TYPE, keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF, typeRefs: PARAM_TYPE_REF_TYPE = {}, @@ -74,40 +80,40 @@ export function tranformJson( return `{${result.join(',')}}` } - /** - * 将自定义列表转换为地址栏参数 - * @param list 列表 - * @param keyRefs 关键词映射 - * @param random 是否随机值 - * @returns key-value 结构字符串 - */ - export function tranformUrlParam( - list: PARAM_LIST_TYPE, - keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF, - typeRefs: PARAM_TYPE_REF_TYPE = {}, - random: boolean = false - ) { - const { key, value, filter, type } = keyRefs - const result: string[] = [] - list.forEach((item: unknown) => { - if (filter && item[filter]) return - const tab: string = item[key] - if (!tab) return - const itemType: PARAM_TYPE = typeRefs[item[type]] +/** + * 将自定义列表转换为地址栏参数 + * @param list 列表 + * @param keyRefs 关键词映射 + * @param random 是否随机值 + * @returns key-value 结构字符串 + */ +export function tranformUrlParam( + list: PARAM_LIST_TYPE, + keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF, + typeRefs: PARAM_TYPE_REF_TYPE = {}, + random: boolean = false +) { + const { key, value, filter, type } = keyRefs + const result: string[] = [] + list.forEach((item: unknown) => { + if (filter && item[filter]) return + const tab: string = item[key] + if (!tab) return + const itemType: PARAM_TYPE = typeRefs[item[type]] - const text: string = random === true ? getRandomDataByType(itemType) : item[value] - result.push(`${tab}=${text}`) - }) - return result.join('&') //分隔符为& - } - /** - * 将自定义列表转换为 key-value 结构 - * @param list 列表 - * @param keyRefs 关键词映射 - * @param random 是否随机值 - * @returns - */ - export function tranformKeyValue( + const text: string = random === true ? getRandomDataByType(itemType) : item[value] + result.push(`${tab}=${text}`) + }) + return result.join('&') //分隔符为& +} +/** + * 将自定义列表转换为 key-value 结构 + * @param list 列表 + * @param keyRefs 关键词映射 + * @param random 是否随机值 + * @returns + */ +export function tranformKeyValue( list: PARAM_LIST_TYPE, keyRefs: PARAM_KEY_REF_TYPE = DEFAULT_PARAM_KEY_REF, typeRefs: PARAM_TYPE_REF_TYPE = {}, diff --git a/frontend/packages/common/src/components/apispace/response-example/index.tsx b/frontend/packages/common/src/components/apispace/response-example/index.tsx index df2a4de3..f0845443 100644 --- a/frontend/packages/common/src/components/apispace/response-example/index.tsx +++ b/frontend/packages/common/src/components/apispace/response-example/index.tsx @@ -1,112 +1,147 @@ -import {useState, useEffect, useImperativeHandle} from 'react'; -import {AutoComplete, Empty, Tabs} from 'antd'; -import { ResultListType} from "@common/const/api-detail"; -import {Collapse} from "@common/components/postcat/api/Collapse"; -import {Box} from "@mui/material"; -import {Codebox} from "@common/components/postcat/api/Codebox"; -import { cloneDeep } from 'lodash-es'; -import { $t } from '@common/locales'; +import { useState, useEffect, useImperativeHandle } from 'react' +import { AutoComplete, Empty, Tabs } from 'antd' +import { ResultListType } from '@common/const/api-detail' +import { Collapse } from '@common/components/postcat/api/Collapse' +import { Box } from '@mui/material' +import { Codebox } from '@common/components/postcat/api/Codebox' +import { cloneDeep } from 'lodash-es' +import { $t } from '@common/locales' export interface ResponseExampleCompoEditorApi { - getData: () => ResultListType[] | [] + getData: () => ResultListType[] | [] } const DEFAULT_RESULT_LIST = [ - {id:'success',name:$t('成功示例'),httpCode:'200',content:''}, - {id:'failed',name:$t('失败示例'),httpCode:'200',content:''}, + { id: 'success', name: $t('成功示例'), httpCode: '200', content: '' }, + { id: 'failed', name: $t('失败示例'), httpCode: '200', content: '' } ] export const HTTP_STATUS_CODE = ['200', '403', '404', '410', '422', '500', '502', '503', '504'] export const CONTENT_TYPE_TYPE = [ - 'application/json', - 'application/x-www-form-urlencoded', - 'image/jpeg', - 'image/png', - 'multipart/form-data', - 'text/asp', - 'text/css', - 'text/html', - 'text/html; charset=UTF-8', - 'text/plain', - 'text/xml' + 'application/json', + 'application/x-www-form-urlencoded', + 'image/jpeg', + 'image/png', + 'multipart/form-data', + 'text/asp', + 'text/css', + 'text/html', + 'text/html; charset=UTF-8', + 'text/plain', + 'text/xml' ] -export function ResponseExampleCompo ({ editorRef,title,detail,mode='view' }: {editorRef?: React.RefObject,title:string, detail:resultList[]}) { - const [resultDemos, setResultDemos] = useState([]); +export function ResponseExampleCompo({ + editorRef, + title, + detail, + mode = 'view' +}: { + editorRef?: React.RefObject + title: string + detail: resultList[] +}) { + const [resultDemos, setResultDemos] = useState([]) - useImperativeHandle(editorRef, () => ({ - getData: () => { - return resultDemos||[] - } - })) + useImperativeHandle(editorRef, () => ({ + getData: () => { + return resultDemos || [] + } + })) useEffect(() => { - if(mode === 'view'){ - setResultDemos(detail); - }else{ - setResultDemos(detail?.length > 0 ? detail: cloneDeep(DEFAULT_RESULT_LIST)) - } - }, [detail]); - - const updateResultList = (id:string, type:'httpCode' | 'httpContentType'|'content',value:string) => { - setResultDemos((prevList)=>{ - for(let i = 0 ; i < prevList.length; i++){ - if(prevList[i].id === id){ - prevList[i][type] = value - return prevList - } - } - }) + if (mode === 'view') { + setResultDemos(detail) + } else { + setResultDemos(detail?.length > 0 ? detail : cloneDeep(DEFAULT_RESULT_LIST)) } + }, [detail]) + + const updateResultList = (id: string, type: 'httpCode' | 'httpContentType' | 'content', value: string) => { + setResultDemos((prevList) => { + for (let i = 0; i < prevList.length; i++) { + if (prevList[i].id === id) { + prevList[i][type] = value + return prevList + } + } + }) + } return ( - - + + - {resultDemos && resultDemos?.map((item:ResultListType) => ( - -
-
- {mode === 'view' ? - item.content ? HTTP Status Code: {item.httpCode}:'' - : ({label:code, value:code}))} - style={{ width: 200 }} - value={item.httpCode} - status={item.httpCode ? '' : 'error'} - onSelect={(value)=>updateResultList(item.id,'httpCode',value)} - placeholder={$t("HTTP 状态码")} - /> - } - {mode === 'view' ? - item.content ? Content-Type: {item.httpContentType || 'text/html;charset=UTF-8'}:'' - : ({label:type, value:type}))} - style={{ width: 200 }} - value={item.httpContentType || 'text/html;charset=UTF-8'} - onSelect={(value)=>updateResultList(item.id,'httpContentType',value)} - placeholder={$t("默认 text/html;charset=UTF-8")} - />} + {resultDemos && + resultDemos?.map((item: ResultListType) => ( + +
+
+ {mode === 'view' ? ( + item.content ? ( + + {' '} + HTTP Status Code: {item.httpCode} + + ) : ( + '' + ) + ) : ( + ({ label: code, value: code }))} + style={{ width: 200 }} + value={item.httpCode} + status={item.httpCode ? '' : 'error'} + onSelect={(value) => updateResultList(item.id, 'httpCode', value)} + placeholder={$t('HTTP 状态码')} + /> + )} + {mode === 'view' ? ( + item.content ? ( + + Content-Type: {item.httpContentType || 'text/html;charset=UTF-8'} + + ) : ( + '' + ) + ) : ( + ({ label: type, value: type }))} + style={{ width: 200 }} + value={item.httpContentType || 'text/html;charset=UTF-8'} + onSelect={(value) => updateResultList(item.id, 'httpContentType', value)} + placeholder={$t('默认 text/html;charset=UTF-8')} + /> + )} +
+ {mode === 'view' ? ( + <> + {item.content ? ( +
+                          {item.content}
+                        
+ ) : ( + + )} + + ) : ( + <> + updateResultList(item.id, 'content', value)} + /> + + )}
- {mode === 'view' ? - <> - { item.content ? -
{item.content}
- : - - } - : <> - updateResultList(item.id,'content',value)}/> - - } -
- - ))} + + ))} - - - ); + + + ) } - diff --git a/frontend/packages/common/src/components/postcat/ApiEdit.tsx b/frontend/packages/common/src/components/postcat/ApiEdit.tsx index 1fe375eb..9fb41e90 100644 --- a/frontend/packages/common/src/components/postcat/ApiEdit.tsx +++ b/frontend/packages/common/src/components/postcat/ApiEdit.tsx @@ -1,259 +1,297 @@ +import { Collapse } from './api/Collapse' +import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' +import { Select, Input, Space } from 'antd' +import { Box, Stack, ThemeProvider, createTheme } from '@mui/material' +import { ApiResponseEditor, ApiResponseEditorApi } from './api/ApiManager/components/ApiResponseEditor' +import { ApiRequestEditor, ApiRequestEditorApi } from './api/ApiManager/components/ApiRequestEditor' +import { ResponseExampleCompo, ResponseExampleCompoEditorApi } from '@common/components/apispace/response-example' +import { ResultListType } from '@common/const/api-detail' +import { SystemApiDetail, SystemInsideApiProxyHandle } from '@core/const/system/type' +import SystemInsideApiProxy from '@core/pages/system/api/SystemInsideApiProxy' +import ApiMatch from './api/ApiPreview/components/ApiMatch' +import { v4 as uuidv4 } from 'uuid' +import { PLACEHOLDER } from '@common/const/const' +import { $t } from '@common/locales' -import {Collapse} from "./api/Collapse"; -import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react"; -import {Select,Input,Space} from "antd"; -import { Box, Stack, ThemeProvider, createTheme } from "@mui/material" -import {ApiResponseEditor, ApiResponseEditorApi} from "./api/ApiManager/components/ApiResponseEditor"; -import {ApiRequestEditor, ApiRequestEditorApi} from "./api/ApiManager/components/ApiRequestEditor"; -import {ResponseExampleCompo, ResponseExampleCompoEditorApi} from "@common/components/apispace/response-example"; -import {ResultListType} from "@common/const/api-detail"; -import { SystemApiDetail, SystemInsideApiProxyHandle } from "@core/const/system/type"; -import SystemInsideApiProxy from "@core/pages/system/api/SystemInsideApiProxy"; -import ApiMatch from "./api/ApiPreview/components/ApiMatch"; -import {v4 as uuidv4} from 'uuid' -import { PLACEHOLDER } from "@common/const/const"; -import { $t } from "@common/locales"; - -const PROTOCOL_LIST = ['HTTP','HTTPS'] -const HTTP_METHOD_LIST = ['POST','GET','PUT', 'DELETE','HEAD','OPTIONS','PATCH'] -export interface ApiEditApi{ - getData:()=>(Promise<{apiInfo:Partial}|string|boolean> | undefined) +const PROTOCOL_LIST = ['HTTP', 'HTTPS'] +const HTTP_METHOD_LIST = ['POST', 'GET', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'] +export interface ApiEditApi { + getData: () => Promise<{ apiInfo: Partial } | string | boolean> | undefined } -interface DescriptionHandle{ - getData:()=>string +interface DescriptionHandle { + getData: () => string } -interface ApiNameProps{ - apiInfo:SystemApiDetail +interface ApiNameProps { + apiInfo: SystemApiDetail } -interface ApiNameHandle{ - getData:()=>string +interface ApiNameHandle { + getData: () => string } export const theme = createTheme({ - palette: { - primary: { - main: '#3D46F2', // 自定义主色调 - }, - text: { - primary: '#333', // 主要文字颜色 - secondary: '#333', // 次要文字颜色 - }, - // 添加其他颜色配置,如错误色、背景色等 - error: { - main: '#d32f2f', - }, - background: { - paper: '#fff', - default: '#f7f8fa', - }, + palette: { + primary: { + main: '#3D46F2' // 自定义主色调 }, - transitions:{ - create:()=>'none' + text: { + primary: '#333', // 主要文字颜色 + secondary: '#333' // 次要文字颜色 }, - components:{ - MuiInput: { - styleOverrides: { - root: { - '&::placeholder': { - color: '#BBB', // 设置 placeholder 的颜色 - }, - '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': { - borderColor: '#3D46F2', // 设置 hover 时的边框颜色 - borderWidth: '1px', // 设置边框粗细 - }, - '&.Mui-focused': { - borderColor: '#3D46F2', // 设置选中时的边框颜色 - borderWidth: '1px', // 设置边框粗细 - }, - }, + // 添加其他颜色配置,如错误色、背景色等 + error: { + main: '#d32f2f' + }, + background: { + paper: '#fff', + default: '#f7f8fa' + } + }, + transitions: { + create: () => 'none' + }, + components: { + MuiInput: { + styleOverrides: { + root: { + '&::placeholder': { + color: '#BBB' // 设置 placeholder 的颜色 }, - }, - MuiTextField: { - styleOverrides: { - root: { - '&::placeholder': { - color: '#BBB', // 设置 placeholder 的颜色 - }, - '&:hover .MuiOutlinedInput-notchedOutline':{ - borderColor: '#3D46F2', // 设置选中时的边框颜色 - borderWidth: '1px', // 设置边框粗细 - } + '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': { + borderColor: '#3D46F2', // 设置 hover 时的边框颜色 + borderWidth: '1px' // 设置边框粗细 + }, + '&.Mui-focused': { + borderColor: '#3D46F2', // 设置选中时的边框颜色 + borderWidth: '1px' // 设置边框粗细 + } + } + } + }, + MuiTextField: { + styleOverrides: { + root: { + '&::placeholder': { + color: '#BBB' // 设置 placeholder 的颜色 + }, + '&:hover .MuiOutlinedInput-notchedOutline': { + borderColor: '#3D46F2', // 设置选中时的边框颜色 + borderWidth: '1px' // 设置边框粗细 + } + } + } + }, + MuiCheckbox: { + styleOverrides: { + root: { + '&:hover': { + backgroundColor: 'transparent' // 设置 hover 时的背景色为透明 + }, + '&:hover:before': { + backgroundColor: 'transparent' // 确保不透明度也为透明 + }, + transition: 'none' // 取消过渡效果 + } + } + }, + MuiButton: { + styleOverrides: { + root: { + '&': { + marginLeft: '0px', + padding: '3px 12px', + borderRadius: '4px' + } + } + } + }, + MuiSelect: { + styleOverrides: { + root: { + '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': { + borderColor: '#3D46F2', + borderWidth: '1px' + }, + '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error) .MuiOutlinedInput-notchedOutline': { + borderColor: '#3D46F2', + borderWidth: '1px' + }, + '&.Mui-focused': { + borderColor: '#3D46F2', + borderWidth: '1px' + }, + '&.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: '#3D46F2', + borderWidth: '1px' + } + } + } + }, + MuiMenu: { + styleOverrides: { + root: { + '.MuiMenuItem-root:hover': { + backgroundColor: '#EBEEF2' + }, + '.MuiMenuItem-root.Mui-selected': { + backgroundColor: '#EBEEF2' + } + } + } + }, + MuiInputLabel: { + styleOverrides: { + root: { + color: '#BBB' // 设置 label 的颜色为灰色 + } + } + } + } +}) + +export default function ApiEdit({ + apiInfo, + editorRef, + loaded, + serviceId, + teamId +}: { + apiInfo: SystemApiDetail + editorRef?: React.RefObject + loaded: boolean + serviceId: string + teamId: string +}) { + const requestRef = useRef(null) + const responseRef = useRef(null) + const resultListRef = useRef(null) + const protocolOptionList = PROTOCOL_LIST.map((x) => ({ label: x, value: x })) + const methodOptionList = HTTP_METHOD_LIST.map((x) => ({ label: x, value: x })) + const [apiName, setApiName] = useState('') + const [resultList, setResultList] = useState([]) + const proxyRef = useRef(null) + const descriptionRef = useRef(null) + const apiNameRef = useRef(null) + + useImperativeHandle(editorRef, () => ({ + getData: () => { + return proxyRef.current + ?.validate() + .then((res) => { + const name = apiNameRef.current?.getData() + if (!name) return Promise.reject($t('请填写接口名称')) + const newData: { apiInfo: Partial } = { + apiInfo: { + info: { + name, + description: descriptionRef.current?.getData() }, - }, - }, - MuiCheckbox: { - styleOverrides: { - root: { - '&:hover': { - backgroundColor: 'transparent', // 设置 hover 时的背景色为透明 - }, - '&:hover:before': { - backgroundColor: 'transparent', // 确保不透明度也为透明 - }, - transition: 'none', // 取消过渡效果 - }, - }, - }, - MuiButton:{ - styleOverrides: { - root: { - '&':{ - marginLeft:'0px', - padding:'3px 12px', - borderRadius:'4px' - } + proxy: res, + doc: { + ...apiInfo?.doc, + requestParams: requestRef.current!.getData()!, + responseList: responseRef.current!.getData()!, + resultList: resultListRef.current!.getData()! } } - }, - MuiSelect: { - styleOverrides: { - root: { - '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error)': { - borderColor: '#3D46F2', - borderWidth: '1px', - }, - '&:hover:not(.Mui-disabled):not(.Mui-focused):not(.Mui-error) .MuiOutlinedInput-notchedOutline': { - borderColor: '#3D46F2', - borderWidth: '1px', - }, - '&.Mui-focused': { - borderColor: '#3D46F2', - borderWidth: '1px', - }, - '&.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: '#3D46F2', - borderWidth: '1px', - }, - }, - }, - }, - MuiMenu:{ - styleOverrides: { - root:{ - '.MuiMenuItem-root:hover':{ - backgroundColor: '#EBEEF2', - }, - '.MuiMenuItem-root.Mui-selected':{ - backgroundColor: '#EBEEF2', - } - } - } - }, - MuiInputLabel: { - styleOverrides: { - root: { - color: '#BBB', // 设置 label 的颜色为灰色 - }, - }, - }, + } + return Promise.resolve(newData) + }) + .catch((errInfo) => Promise.reject(errInfo)) } - }); - -export default function ApiEdit({apiInfo,editorRef,loaded,serviceId, teamId}:{apiInfo:SystemApiDetail,editorRef?:React.RefObject,loaded:boolean,serviceId:string, teamId:string}){ - const requestRef = useRef(null) - const responseRef = useRef(null) - const resultListRef = useRef(null) - const protocolOptionList = PROTOCOL_LIST.map((x)=>({label:x,value:x})) - const methodOptionList = HTTP_METHOD_LIST.map((x)=>({label:x,value:x})) - const [apiName,setApiName]=useState('') - const [resultList,setResultList] = useState([]) - const proxyRef = useRef(null) - const descriptionRef = useRef(null) - const apiNameRef = useRef(null) - - useImperativeHandle(editorRef, () => ({ - getData: () => { - return proxyRef.current?.validate().then((res)=>{ - const name = apiNameRef.current?.getData() - if(!name) return Promise.reject($t('请填写接口名称')) - const newData :{apiInfo:Partial}= { - apiInfo:{ - info:{ - name, - description:descriptionRef.current?.getData(), - }, - proxy:res, - doc:{ - ...apiInfo?.doc, - requestParams:requestRef.current!.getData()!, - responseList:responseRef.current!.getData()!, - resultList:resultListRef.current!.getData()! - } - } - } - return Promise.resolve(newData) - }).catch((errInfo)=>Promise.reject(errInfo)) - } + })) + + useEffect(() => { + if (!apiInfo || Object.keys(apiInfo).length === 0) return + setApiName(apiInfo.name!) + setResultList(apiInfo?.doc?.resultList || []) + }, [apiInfo]) + + const Description = forwardRef((props, ref) => { + const { initDescription } = props + const [description, setDescription] = useState(initDescription || '') + useImperativeHandle(ref, () => ({ + getData: () => description })) - - useEffect(() => { - if(!apiInfo || Object.keys(apiInfo).length === 0) return - setApiName(apiInfo.name!) - setResultList(apiInfo?.doc?.resultList || []) - }, [apiInfo]); - - const Description = forwardRef((props,ref)=>{ - - const { initDescription } = props - const [description, setDescription] = useState(initDescription||'') - useImperativeHandle(ref, ()=>({ - getData:()=>description - })) - return ( - setDescription(e.target.value)} placeholder={$t(PLACEHOLDER.input)}/> - ) - }) - - const ApiName = forwardRef((props,ref)=>{ - const {apiInfo} = props - const [apiName, setApiName] = useState(apiInfo?.name || '') - useImperativeHandle(ref, ()=>({ - getData:()=>apiName - })) - return ( - <> - - - - - setApiName(e.target.value) } status={apiName ? '' : 'error'}/> - - ) - }) - - return( - <> - - - - - - - - - { - apiInfo?.match && apiInfo.match?.length > 0 && - {x.id = uuidv4();return x})} /> - } - - - - - - - - - - - - - - - - - + return ( + setDescription(e.target.value)} + placeholder={$t(PLACEHOLDER.input)} + /> ) -} \ No newline at end of file + }) + + const ApiName = forwardRef((props, ref) => { + const { apiInfo } = props + const [apiName, setApiName] = useState(apiInfo?.name || '') + useImperativeHandle(ref, () => ({ + getData: () => apiName + })) + return ( + <> + + + + + setApiName(e.target.value)} status={apiName ? '' : 'error'} /> + + ) + }) + + return ( + <> + + + + + + + + + {apiInfo?.match && apiInfo.match?.length > 0 && ( + { + x.id = uuidv4() + return x + })} + /> + )} + + + + + + + + + + + + + + + + + + ) +} diff --git a/frontend/packages/common/src/components/postcat/ApiPreview.tsx b/frontend/packages/common/src/components/postcat/ApiPreview.tsx index f52fefa9..f0a6eb90 100644 --- a/frontend/packages/common/src/components/postcat/ApiPreview.tsx +++ b/frontend/packages/common/src/components/postcat/ApiPreview.tsx @@ -1,159 +1,169 @@ -import {useCallback, useEffect, useState} from "react"; -import Search from "antd/es/input/Search"; -import {Button, Space, Tooltip} from "antd"; -import CodeSnippetCompo from "@common/components/apispace/code-snippet"; -import {ApiDetail} from "@common/const/api-detail"; -import {flattenTree} from "@common/utils/postcat.tsx"; -import MessageBodyComponent, {RenderMessageBody} from "./api/ApiPreview/components/MessageBody"; -import HeaderFields from "./api/ApiPreview/components/HeaderFields"; -import {ResponseExampleCompo} from "@common/components/apispace/response-example"; -import {MoreSetting} from "./api/MoreSetting"; -import { - useMoreSettingHiddenConfig -} from "./api/ApiManager/components/MessageDataGrid/hooks/useMoreSettingHiddenConfig.ts"; -import {MessageType} from "./api/ApiManager/components/MessageDataGrid"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { ThemeProvider } from "@mui/material"; -import { theme } from "./ApiEdit.tsx"; -import { $t } from "@common/locales/index.ts"; +import { useCallback, useEffect, useState } from 'react' +import Search from 'antd/es/input/Search' +import { Button, Space, Tooltip } from 'antd' +import CodeSnippetCompo from '@common/components/apispace/code-snippet' +import { ApiDetail } from '@common/const/api-detail' +import { flattenTree } from '@common/utils/postcat.tsx' +import MessageBodyComponent, { RenderMessageBody } from './api/ApiPreview/components/MessageBody' +import HeaderFields from './api/ApiPreview/components/HeaderFields' +import { ResponseExampleCompo } from '@common/components/apispace/response-example' +import { MoreSetting } from './api/MoreSetting' +import { useMoreSettingHiddenConfig } from './api/ApiManager/components/MessageDataGrid/hooks/useMoreSettingHiddenConfig.ts' +import { MessageType } from './api/ApiManager/components/MessageDataGrid' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { ThemeProvider } from '@mui/material' +import { theme } from './ApiEdit.tsx' +import { $t } from '@common/locales/index.ts' -export const SearchBtn = ({entity}:{entity:unknown})=>{ - return ( - - {$t('测试 API')} - - ) +export const SearchBtn = ({ entity }: { entity: unknown }) => { + return ( + + {$t('测试 API')} + + ) } -export default function ApiPreview(props:{testClick?:()=>void, entity:ApiDetail}){ - const {testClick,entity} = props - const {requestParams,responseList,resultList} = entity - const [requestBodyList, setRequestBodyList] = useState([]) - const [responseBodyList, setResponseBodyList] = useState([]) - const [currentMoreSettingParam, setCurrentMoreSettingParam] = useState(null) +export default function ApiPreview(props: { testClick?: () => void; entity: ApiDetail }) { + const { testClick, entity } = props + const { requestParams, responseList, resultList } = entity + const [requestBodyList, setRequestBodyList] = useState([]) + const [responseBodyList, setResponseBodyList] = useState([]) + const [currentMoreSettingParam, setCurrentMoreSettingParam] = useState(null) - // const responseData = responseList?.[0] - // const responseParams = responseData?.responseParams?.headerParams + // const responseData = responseList?.[0] + // const responseParams = responseData?.responseParams?.headerParams - useEffect(() => { - // setTimeout(()=>{ - // const element = document.querySelectorAll('.MuiDataGrid-main'); - // if(element?.length > 0){ - // for(const x of element){ - // x.childNodes[x.childNodes.length - 1 ].textContent === 'MUI X Missing license key' ? x.childNodes[x.childNodes.length - 1 ].textContent = '' :null - // } - // } - // },500) + useEffect(() => { + // setTimeout(()=>{ + // const element = document.querySelectorAll('.MuiDataGrid-main'); + // if(element?.length > 0){ + // for(const x of element){ + // x.childNodes[x.childNodes.length - 1 ].textContent === 'MUI X Missing license key' ? x.childNodes[x.childNodes.length - 1 ].textContent = '' :null + // } + // } + // },500) + setRequestBodyList( + flattenTree(requestParams?.bodyParams || [], 'childList', 'name') as unknown as RenderMessageBody[] + ) + setResponseBodyList( + flattenTree( + responseList?.[0]?.responseParams?.bodyParams || [], + 'childList', + 'name' + ) as unknown as RenderMessageBody[] + ) + }, [requestParams, responseList, resultList]) - setRequestBodyList( - flattenTree(requestParams?.bodyParams || [], 'childList', 'name') as unknown as RenderMessageBody[] - ) - setResponseBodyList( - flattenTree( - responseList?.[0]?.responseParams?.bodyParams || [], - 'childList', - 'name' - ) as unknown as RenderMessageBody[] - ) - }, [requestParams,responseList,resultList]); + const handleCloseMoreSetting = useCallback(() => { + setCurrentMoreSettingParam(null) + }, []) + const moreSettingHiddenConfig = useMoreSettingHiddenConfig({ + param: currentMoreSettingParam as unknown as RenderMessageBody, + // TODO: + messageType: 'Header' as MessageType, + readOnly: true + }) - const handleCloseMoreSetting = useCallback(() => { - setCurrentMoreSettingParam(null) - }, []) + const handleTest = () => { + // testClick && testClick() + } - - const moreSettingHiddenConfig = useMoreSettingHiddenConfig({ - param: currentMoreSettingParam as unknown as RenderMessageBody, - // TODO: - messageType: 'Header' as MessageType, - readOnly: true - }) - - const handleTest = () => { - // testClick && testClick() - }; - - return (<> - - - {testClick && - + return ( + <> + + {testClick && ( + } - onSearch={handleTest} + readOnly + addonBefore={entity?.method} + value={entity?.uri} + // enterButton={} + onSearch={handleTest} /> - } + + )} - { - requestParams?.headerParams?.length > 0 && - - } + {requestParams?.headerParams?.length > 0 && ( + + )} - {requestBodyList?.length > 0 && - - } + {requestBodyList?.length > 0 && ( + + )} - { - requestParams?.queryParams?.length > 0 && - - } + {requestParams?.queryParams?.length > 0 && ( + + )} - { - requestParams?.restParams?.length > 0 && - - } + {requestParams?.restParams?.length > 0 && ( + + )} {/*

请求示例代码

*/} - - - - + title={$t('请求示例代码')} + api={entity} + extraContent={ + testClick ? ( +
+ + + + -
: undefined } +
+ ) : undefined + } /> - {resultList?.length > 0 && } + {resultList?.length > 0 && } - { - responseList?.[0]?.responseParams?.headerParams?.length > 0 && - - } + {responseList?.[0]?.responseParams?.headerParams?.length > 0 && ( + + )} - {responseBodyList?.length > 0 && - - } + {responseBodyList?.length > 0 && ( + + )} - - ) + + + ) } - diff --git a/frontend/packages/common/src/components/postcat/ApiTest.tsx b/frontend/packages/common/src/components/postcat/ApiTest.tsx index 8e2ec302..e30d3b66 100644 --- a/frontend/packages/common/src/components/postcat/ApiTest.tsx +++ b/frontend/packages/common/src/components/postcat/ApiTest.tsx @@ -1,287 +1,289 @@ -import { Box, Button, LinearProgress, Stack } from '@mui/material' +import { Box, LinearProgress, Stack } from '@mui/material' import { Allotment } from 'allotment' -import { memo, useCallback, useContext, useEffect, useImperativeHandle, useRef, useState } from 'react' +import { memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react' import { useAutoAnimate } from '@formkit/auto-animate/react' -import {HTTPMethod} from "./api/RequestMethod"; +import { HTTPMethod } from './api/RequestMethod' import { - ApiBodyType, - ApiDetail, - BodyParamsType, - HeaderParamsType, - QueryParamsType, ResponseList, RestParamsType, - TestApiBodyType -} from "@common/const/api-detail"; -import {extractBraceContent, mapContentTypeToApiBodyType, syncUrlAndQuery} from "@common/utils/postcat.tsx"; -import {generateRow} from "./api/ApiManager/components/MessageDataGrid/constants.ts"; -import {RawParams} from "./api/ApiManager/components/ApiMessageBody"; -import {ParseCurlResult} from "@common/utils/curl.ts"; -import {ApiRequestTester, ApiRequestTesterApi} from "./api/ApiTest/components/ApiRequestTester"; -import {TestResponse} from "@common/hooks/useTest.ts"; -import {getDefaultApiInfo} from "./api/ApiManager/constants.ts"; -import {UriInput} from "./api/ApiManager/components/UriInput"; -import {TestControl} from "./api/ApiTest/components/TestControl"; + ApiBodyType, + ApiDetail, + BodyParamsType, + HeaderParamsType, + QueryParamsType, + ResponseList, + RestParamsType, + TestApiBodyType +} from '@common/const/api-detail' +import { extractBraceContent, mapContentTypeToApiBodyType, syncUrlAndQuery } from '@common/utils/postcat.tsx' +import { generateRow } from './api/ApiManager/components/MessageDataGrid/constants.ts' +import { RawParams } from './api/ApiManager/components/ApiMessageBody' +import { ParseCurlResult } from '@common/utils/curl.ts' +import { ApiRequestTester, ApiRequestTesterApi } from './api/ApiTest/components/ApiRequestTester' +import { TestResponse } from '@common/hooks/useTest.ts' +import { getDefaultApiInfo } from './api/ApiManager/constants.ts' +import { UriInput } from './api/ApiManager/components/UriInput' +import { TestControl } from './api/ApiTest/components/TestControl' const Tester = memo(ApiRequestTester) import 'allotment/dist/style.css' -import {ApiResponse} from "./api/ApiTest/components/ApiResponse"; +import { ApiResponse } from './api/ApiTest/components/ApiResponse' type SafeAny = unknown export interface ApiTestApiRef { - getTestMeta: () => SafeAny + getTestMeta: () => SafeAny } -export default function ApiTest({ apiRef, apiInfo,loaded = true}: { apiRef?: React.RefObject ,apiInfo:ApiDetail,loaded?:boolean}) { - const [uri, setUri] = useState('') - const [httpMethod, setHttpMethod] = useState(HTTPMethod.POST) - const [testResponse, setTestResponse] = useState(null) - // const { apiInfo, loaded } = useContext>(ApiTabContext) - const testerApiRef = useRef(null) - const [parent] = useAutoAnimate() - // const testApiInfo:ApiDetail = apiInfo - const [isLoading, setIsLoading] = useState() - const [cancel,setCancel] = useState() +export default function ApiTest({ + apiRef, + apiInfo, + loaded = true +}: { + apiRef?: React.RefObject + apiInfo: ApiDetail + loaded?: boolean +}) { + const [uri, setUri] = useState('') + const [httpMethod, setHttpMethod] = useState(HTTPMethod.POST) + const [testResponse, setTestResponse] = useState(null) + // const { apiInfo, loaded } = useContext>(ApiTabContext) + const testerApiRef = useRef(null) + const [parent] = useAutoAnimate() + // const testApiInfo:ApiDetail = apiInfo + const [isLoading, setIsLoading] = useState() + const [cancel, setCancel] = useState() + // useEffect(() => { + // if (testApiInfo) { + // const data: SafeAny = testApiInfo + // const responseResult = { + // report: { + // general: { + // downloadRate: data.downloadRate, + // downloadSize: data.downloadSize, + // redirectTimes: data.redirectTimes, + // time: data.time, + // timingSummary: data.timingSummary + // }, + // request: { + // headers: testApiInfo.requestParams.headerParams?.map((item) => ({ + // value: item.paramAttr.example, + // key: item.name + // })), + // requestType: data.request.contentType, + // body: testApiInfo.requestParams.bodyParams, + // uri: testApiInfo.uri + // }, + // response: { + // headers: data.headers.map((item: SafeAny) => ({ key: item.name, value: item.value })), + // body: data.body, + // contentType: data.contentType, + // httpCode: data.statusCode, + // responseType: data.responseType + // } + // } + // } as unknown as TestResponse + // setTestResponse(responseResult) + // setHttpMethod(testApiInfo.method) + // testerApiRef.current?.updateHeaderDataGrid((testApiInfo.requestParams.headerParams as HeaderParamsType[]) || []) + // const apiBodyType: TestApiBodyType = testApiInfo.requestParams.bodyParams[0]?.contentType as TestApiBodyType + // const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded' + // testerApiRef.current?.updateRequestBody({ + // apiBodyType, + // contentType: contentType, + // data: + // apiBodyType === ApiBodyType.Raw + // ? testApiInfo.requestParams.bodyParams?.[0]?.binaryRawData + // : testApiInfo.requestParams.bodyParams + // }) + // setTimeout(() => { + // setUri(testApiInfo.uri) + // testerApiRef.current?.updateQueryDataGrid([]) + // testerApiRef.current?.updateRestDataGrid([]) + // }, 0) + // // updateTestApiInfo(null) + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [testApiInfo]) + useEffect(() => { + if (apiInfo && loaded) { + setUri(apiInfo.uri) + setHttpMethod(apiInfo.method) + testerApiRef.current?.updateHeaderDataGrid((apiInfo.requestParams.headerParams as HeaderParamsType[]) || []) + const apiBodyType: TestApiBodyType = apiInfo.requestParams?.bodyParams[0]?.contentType as TestApiBodyType + const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded' + testerApiRef.current?.updateRequestBody({ + apiBodyType, + contentType: contentType, + data: + apiBodyType === ApiBodyType.Raw + ? apiInfo.requestParams?.bodyParams?.[0]?.binaryRawData + : apiInfo.requestParams?.bodyParams + }) + } + }, [apiInfo, loaded]) - // useEffect(() => { - // if (testApiInfo) { - // const data: SafeAny = testApiInfo - // const responseResult = { - // report: { - // general: { - // downloadRate: data.downloadRate, - // downloadSize: data.downloadSize, - // redirectTimes: data.redirectTimes, - // time: data.time, - // timingSummary: data.timingSummary - // }, - // request: { - // headers: testApiInfo.requestParams.headerParams?.map((item) => ({ - // value: item.paramAttr.example, - // key: item.name - // })), - // requestType: data.request.contentType, - // body: testApiInfo.requestParams.bodyParams, - // uri: testApiInfo.uri - // }, - // response: { - // headers: data.headers.map((item: SafeAny) => ({ key: item.name, value: item.value })), - // body: data.body, - // contentType: data.contentType, - // httpCode: data.statusCode, - // responseType: data.responseType - // } - // } - // } as unknown as TestResponse - // setTestResponse(responseResult) - // setHttpMethod(testApiInfo.method) - // testerApiRef.current?.updateHeaderDataGrid((testApiInfo.requestParams.headerParams as HeaderParamsType[]) || []) - // const apiBodyType: TestApiBodyType = testApiInfo.requestParams.bodyParams[0]?.contentType as TestApiBodyType - // const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded' - // testerApiRef.current?.updateRequestBody({ - // apiBodyType, - // contentType: contentType, - // data: - // apiBodyType === ApiBodyType.Raw - // ? testApiInfo.requestParams.bodyParams?.[0]?.binaryRawData - // : testApiInfo.requestParams.bodyParams - // }) - // setTimeout(() => { - // setUri(testApiInfo.uri) - // testerApiRef.current?.updateQueryDataGrid([]) - // testerApiRef.current?.updateRestDataGrid([]) - // }, 0) - // // updateTestApiInfo(null) + useImperativeHandle(apiRef, () => ({ + getTestMeta: () => { + const { rest, query, headers, body } = testerApiRef.current?.getEditMeta() || {} + return { + uri, + restParams: rest || [], + headersParams: (headers as HeaderParamsType[]) || [], + bodyParams: (body?.data as BodyParamsType[]) || [], + queryParams: query || [], + method: httpMethod, + requestType: body!.apiBodyType + } + } + })) + + const handleTest = async () => { + const { rest, headers, body } = testerApiRef.current?.getEditMeta() || {} + // const response = await test( + // { apiId, workspaceId, projectId }, + // { + // uri, + // restParams: rest || [], + // headersParams: (headers as HeaderParamsType[]) || [], + // bodyParams: (body?.data as BodyParamsType[]) || [], + // method: httpMethod, + // requestType: body!.apiBodyType // } - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [testApiInfo]) + // ) + // const response = {data:{}} + // if (response.data?.report?.request) { + // response.data.report.request.uri = uri + // } + // setTestResponse(response.data) + } - useEffect(() => { - if (apiInfo && loaded) { - setUri(apiInfo.uri) - setHttpMethod(apiInfo.method) - testerApiRef.current?.updateHeaderDataGrid((apiInfo.requestParams.headerParams as HeaderParamsType[]) || []) - const apiBodyType: TestApiBodyType = apiInfo.requestParams?.bodyParams[0]?.contentType as TestApiBodyType - const contentType = apiBodyType === ApiBodyType.Raw ? 'application/json' : 'application/x-www-form-urlencoded' - testerApiRef.current?.updateRequestBody({ - apiBodyType, - contentType: contentType, - data: - apiBodyType === ApiBodyType.Raw - ? apiInfo.requestParams?.bodyParams?.[0]?.binaryRawData - : apiInfo.requestParams?.bodyParams - }) - } - }, [apiInfo, loaded]) - - useImperativeHandle(apiRef, () => ({ - getTestMeta: () => { - const { - rest, - query, - headers, - body, - } = testerApiRef.current?.getEditMeta() || {} - return { - uri, - restParams: rest || [], - headersParams: (headers as HeaderParamsType[]) || [], - bodyParams: (body?.data as BodyParamsType[]) || [], - queryParams: query || [], - method: httpMethod, - requestType: body!.apiBodyType - } - } - })) - - const handleTest = async () => { - const { rest, headers, body} = testerApiRef.current?.getEditMeta() || {} - // const response = await test( - // { apiId, workspaceId, projectId }, - // { - // uri, - // restParams: rest || [], - // headersParams: (headers as HeaderParamsType[]) || [], - // bodyParams: (body?.data as BodyParamsType[]) || [], - // method: httpMethod, - // requestType: body!.apiBodyType - // } - // ) - // const response = {data:{}} - // if (response.data?.report?.request) { - // response.data.report.request.uri = uri - // } - // setTestResponse(response.data) + const handleQueryChange = useCallback((queryList: QueryParamsType[]) => { + /** Can't use new URL due to potential non-standard URLs; reverting to pre-refactor code temporarily. */ + const result = syncUrlAndQuery(uri, queryList as SafeAny, { + nowOperate: 'query', + method: 'replace' + }) + if (result?.url) { + setUri(result.url) } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) - const handleQueryChange = useCallback((queryList: QueryParamsType[]) => { - /** Can't use new URL due to potential non-standard URLs; reverting to pre-refactor code temporarily. */ - const result = syncUrlAndQuery(uri, queryList as SafeAny, { - nowOperate: 'query', - method: 'replace' + const handleUriChange = (uri: string) => { + setUri(uri) + // if (activeTab?.path === quickTestRoute.path) { + // updateTab({ ...activeTab, name: uri || 'New Request', method: httpMethod } as TabRouteObject) + // } + if (uri) { + const restResult = extractBraceContent(uri) + const queryResult = syncUrlAndQuery(uri, []) + queryResult?.query?.length && testerApiRef.current?.updateQueryDataGrid(queryResult.query) + restResult?.length && + testerApiRef.current?.updateRestDataGrid(restResult.map((rest) => ({ name: rest })) as RestParamsType[]) + } + } + + const handleHttpMethodChange = (method: HTTPMethod) => { + setHttpMethod(method) + // if (activeTab?.path === quickTestRoute.path) { + // updateTab({ ...activeTab, name: uri || 'New Request', method } as TabRouteObject) + // } + } + + /** Execute this logic only during 'Quicktest' run. */ + const handleSaveApi = () => { + const { rest, query, headers, body, preScript = '', postScript = '' } = testerApiRef.current?.getEditMeta() || {} + const newApiInfo = getDefaultApiInfo() + const contentType = mapContentTypeToApiBodyType(body?.contentType ?? 'text/plain') + newApiInfo.uri = uri + newApiInfo.name = 'New Request' + newApiInfo.apiAttrInfo.requestMethod = httpMethod + newApiInfo.apiAttrInfo.contentType = contentType + newApiInfo.requestParams = { + bodyParams: body?.data, + headerParams: headers, + queryParams: query, + restParams: rest + } as SafeAny + if (testResponse) { + const response = testResponse.report.response + const responseHeader = response?.headers.map((header) => { + return generateRow({ + name: header.key, + paramAttr: { + example: header.value + } }) - if (result?.url) { - setUri(result.url) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - const handleUriChange = (uri: string) => { - setUri(uri) - // if (activeTab?.path === quickTestRoute.path) { - // updateTab({ ...activeTab, name: uri || 'New Request', method: httpMethod } as TabRouteObject) - // } - if (uri) { - const restResult = extractBraceContent(uri) - const queryResult = syncUrlAndQuery(uri, []) - queryResult?.query?.length && testerApiRef.current?.updateQueryDataGrid(queryResult.query) - restResult?.length && - testerApiRef.current?.updateRestDataGrid(restResult.map((rest) => ({ name: rest })) as RestParamsType[]) + }) + const responseList = [ + { + // TODO: response JSON? + contentType: ApiBodyType.Raw, + responseParams: { + bodyParams: [RawParams(response?.body || '')], + headerParams: responseHeader || [], + queryParams: [], + restParams: [] + } } + ] + newApiInfo.responseList = responseList as unknown as ResponseList[] } + } - const handleHttpMethodChange = (method: HTTPMethod) => { - setHttpMethod(method) - // if (activeTab?.path === quickTestRoute.path) { - // updateTab({ ...activeTab, name: uri || 'New Request', method } as TabRouteObject) - // } - } + const handleCURLParse = (cURLResult: ParseCurlResult) => { + setHttpMethod(HTTPMethod[cURLResult.method as keyof typeof HTTPMethod]) + /** cURLResult.body */ + const headers = Object.keys(cURLResult.headers).map((key) => ({ + name: key, + paramAttr: { + example: cURLResult.headers[key] + } + })) + testerApiRef.current?.updateHeaderDataGrid((headers as HeaderParamsType[]) || []) + testerApiRef.current?.updateRequestBodyWithCurlInfo(cURLResult) + setTimeout(() => { + setUri(cURLResult.url) + testerApiRef.current?.updateQueryDataGrid([]) + testerApiRef.current?.updateRestDataGrid([]) + }, 0) + } - /** Execute this logic only during 'Quicktest' run. */ - const handleSaveApi = () => { - const { rest, query, headers, body, preScript = '', postScript = '' } = testerApiRef.current?.getEditMeta() || {} - const newApiInfo = getDefaultApiInfo() - const contentType = mapContentTypeToApiBodyType(body?.contentType ?? 'text/plain') - newApiInfo.uri = uri - newApiInfo.name = 'New Request' - newApiInfo.apiAttrInfo.requestMethod = httpMethod - newApiInfo.apiAttrInfo.contentType = contentType - newApiInfo.requestParams = { - bodyParams: body?.data, - headerParams: headers, - queryParams: query, - restParams: rest - } as SafeAny - if (testResponse) { - const response = testResponse.report.response - const responseHeader = response?.headers.map((header) => { - return generateRow({ - name: header.key, - paramAttr: { - example: header.value - } - }) - }) - const responseList = [ - { - // TODO: response JSON? - contentType: ApiBodyType.Raw, - responseParams: { - bodyParams: [RawParams(response?.body || '')], - headerParams: responseHeader || [], - queryParams: [], - restParams: [] - } - } - ] - newApiInfo.responseList = responseList as unknown as ResponseList[] - } - } - - const handleCURLParse = (cURLResult: ParseCurlResult) => { - setHttpMethod(HTTPMethod[cURLResult.method as keyof typeof HTTPMethod]) - /** cURLResult.body */ - const headers = Object.keys(cURLResult.headers).map((key) => ({ - name: key, - paramAttr: { - example: cURLResult.headers[key] - } - })) - testerApiRef.current?.updateHeaderDataGrid((headers as HeaderParamsType[]) || []) - testerApiRef.current?.updateRequestBodyWithCurlInfo(cURLResult) - setTimeout(() => { - setUri(cURLResult.url) - testerApiRef.current?.updateQueryDataGrid([]) - testerApiRef.current?.updateRestDataGrid([]) - }, 0) - } - - - return ( - - - - - - - - - - - - - - - - - - - - - - {isLoading ? : null} - - - - + return ( + + + + + + + + + + + + + - + + + + + + + + {isLoading ? : null} + + + + - ) + + + ) } diff --git a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiMessageBody/components/Binary.tsx b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiMessageBody/components/Binary.tsx index 4d29c201..c19c1816 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiMessageBody/components/Binary.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiMessageBody/components/Binary.tsx @@ -1,11 +1,11 @@ -import { $t } from '@common/locales'; +import { $t } from '@common/locales' import { TextField } from '@mui/material' import { SyntheticEvent } from 'react' export function RequestBodyBinary({ value, onChange }: { value: string; onChange: (value: string) => void }) { return ( { @@ -43,7 +52,7 @@ export function RawParams(value: string) { } } -export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBodyParamsTypeProps) { +export function ApiMessageBody({ apiInfo = null, loaded, mode, bodyApiRef }: ApiBodyParamsTypeProps) { const [apiBodyTypeValue, setApiBodyTypeValue] = useState(ApiBodyType.JSON) const [apiFormData, setApiFormData] = useState([]) @@ -67,7 +76,9 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo }[apiBodyTypeValue as ApiBodyType.JSON | ApiBodyType.FormData | ApiBodyType.XML] bodyParams.push(...(targetRef.current?.getEditMeta() as BodyParamsType[])) } else if ([ApiBodyType.Raw, ApiBodyType.Binary].includes(apiBodyTypeValue)) { - bodyParams.push(RawParams(apiBodyTypeValue === ApiBodyType.Raw ? apiRaw : apiBinary) as unknown as BodyParamsType) + bodyParams.push( + RawParams(apiBodyTypeValue === ApiBodyType.Raw ? apiRaw : apiBinary) as unknown as BodyParamsType + ) } return { @@ -86,7 +97,7 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo // } // } // },500) - }, []); + }, []) useEffect(() => { if (loaded && (apiInfo || apiInfo === null)) { @@ -193,14 +204,12 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo { key: 'Raw', value: ApiBodyType.Raw, - element: + element: }, { key: 'Binary', value: ApiBodyType.Binary, - element: + element: } ].filter((type) => type) as ApiBodyTypeOption[] }, [apiBinary, apiFormData, apiJson, apiRaw, apiXml, mode]) @@ -214,23 +223,23 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo } return ( - - + + {apiBodyTypeList.map((apiBodyType) => ( } + control={} label={apiBodyType.key} /> ))} @@ -243,8 +252,7 @@ export function ApiMessageBody({ apiInfo=null, loaded, mode, bodyApiRef }: ApiBo onChange={handleJsonTypeChange} sx={{ height: '30px', - borderColor:'#EDEDED' - + borderColor: '#EDEDED' }} > Object diff --git a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiProxyEditor/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiProxyEditor/index.tsx index 4f95d2d8..8a225e78 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiProxyEditor/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiProxyEditor/index.tsx @@ -1,20 +1,19 @@ import { Box, useTheme } from '@mui/material' -import {ApiBodyType, BodyParamsType} from "@common/const/api-detail"; -import {RenderMessageBody} from "@common/components/postcat/api/apiManager/components/MessageDataGrid"; +import { BodyParamsType } from '@common/const/api-detail' +import { RenderMessageBody } from '@common/components/postcat/api/apiManager/components/MessageDataGrid' export interface ApiProxyEditorApi { getEditMeta: () => Partial[] } - interface ApiProxyEditorProps { -// onChange?: (rows: T[]) => void -// initialRows?: T[] | null -// onDirty?: () => void + // onChange?: (rows: T[]) => void + // initialRows?: T[] | null + // onDirty?: () => void loading?: boolean -// messageType?: MessageType -// contentType: ContentType -// isMoreSettingReadOnly?: boolean -// apiRef?: RefObject + // messageType?: MessageType + // contentType: ContentType + // isMoreSettingReadOnly?: boolean + // apiRef?: RefObject } export function ApiProxyEditor(props: ApiProxyEditorProps) { @@ -22,7 +21,7 @@ export function ApiProxyEditor(props: ApiProxyEditorProps) { // onChange, // initialRows, // onDirty, - loading = false, + loading = false // contentType, // messageType, // isMoreSettingReadOnly, @@ -31,7 +30,6 @@ export function ApiProxyEditor(props: ApiProxyEditorProps) { } = props const theme = useTheme() - return ( ) { borderRadius: `${theme.shape.borderRadius}px` }} > - - - - + ) } diff --git a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiRequestEditor/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiRequestEditor/index.tsx index 1f49c4ec..817d262e 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiRequestEditor/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiManager/components/ApiRequestEditor/index.tsx @@ -1,16 +1,17 @@ import { Box, Grow, Tab, Tabs, Typography, useTheme } from '@mui/material' -import {ReactNode, SyntheticEvent, useEffect, useImperativeHandle, useRef, useState} from 'react' +import { ReactNode, SyntheticEvent, useEffect, useImperativeHandle, useRef, useState } from 'react' import { - ApiBodyType, ApiDetail, + ApiBodyType, + ApiDetail, BodyParamsType, HeaderParamsType, QueryParamsType, RestParamsType -} from "@common/const/api-detail"; -import {MessageDataGrid, MessageDataGridApi} from "../MessageDataGrid"; -import {Indicator} from "../../../../Indicator"; -import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody'; -import { $t } from '@common/locales'; +} from '@common/const/api-detail' +import { MessageDataGrid, MessageDataGridApi } from '../MessageDataGrid' +import { Indicator } from '../../../../Indicator' +import { ApiMessageBody, ApiMessageBodyApi } from '../ApiMessageBody' +import { $t } from '@common/locales' export interface ApiRequestEditorApi { getData: () => { @@ -30,21 +31,31 @@ interface ApiRequestEditorTab { dirty: boolean } -export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef?: React.RefObject ,apiInfo:ApiDetail,loaded:boolean}) { - const [apiHeaders, setApiHeaders] = useState([]) - const [apiQuery, setApiQuery] = useState([]) - const [apiRest, setApiRest] = useState([]) +export function ApiRequestEditor({ + editorRef, + apiInfo = null, + loaded +}: { + editorRef?: React.RefObject + apiInfo: ApiDetail + loaded: boolean +}) { + const [apiHeaders, setApiHeaders] = useState([]) + const [apiQuery, setApiQuery] = useState([]) + const [apiRest, setApiRest] = useState([]) const headersRef = useRef(null) const bodyRef = useRef(null) const queryRef = useRef(null) const restRef = useRef(null) - const [innerLoaded,setInnerLoaded] = useState(false) + const [innerLoaded, setInnerLoaded] = useState(false) useImperativeHandle(editorRef, () => ({ getData: () => { return { - bodyParams: bodyRef.current?.getBodyMeta()?.bodyParams.map((x)=>({...x,contentType:bodyRef.current?.getBodyMeta().contentType})), + bodyParams: bodyRef.current + ?.getBodyMeta() + ?.bodyParams.map((x) => ({ ...x, contentType: bodyRef.current?.getBodyMeta().contentType })), headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || [], queryParams: (queryRef.current?.getEditMeta() as QueryParamsType[]) || [], restParams: (restRef.current?.getEditMeta() as RestParamsType[]) || [] @@ -77,8 +88,8 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef? dirty: false }, { - label:$t('请求体'), - element: , + label: $t('请求体'), + element: , dirty: false }, { @@ -123,12 +134,15 @@ export function ApiRequestEditor({ editorRef ,apiInfo=null,loaded}: { editorRef? const tabHeight = '30px' return ( - + - {tab.label} + {tab.label} }) { +export function ApiResponseEditor({ + editorRef, + apiInfo = null, + loaded +}: { + editorRef?: React.RefObject +}) { const [apiHeaders, setApiHeaders] = useState([]) const [innerLoaded, setInnerLoaded] = useState(false) const headersRef = useRef(null) @@ -40,16 +46,17 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe getData: () => { const bodyData = bodyRef.current?.getBodyMeta() const uuid = uuidv4() - return ([{ - id:uuid, - responseUuid:uuid, - httpCode:bodyData?.contentType, - responseParams:{ - bodyParams: bodyData?.bodyParams, - headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || [] + return [ + { + id: uuid, + responseUuid: uuid, + httpCode: bodyData?.contentType, + responseParams: { + bodyParams: bodyData?.bodyParams, + headerParams: (headersRef.current?.getEditMeta() as HeaderParamsType[]) || [] + } } - } - ]) + ] } })) @@ -70,8 +77,7 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe }, { label: $t('返回值'), - element: , + element: , dirty: false } ] @@ -88,17 +94,21 @@ export function ApiResponseEditor({ editorRef ,apiInfo=null, loaded}: { editorRe const tabHeight = '30px' return ( - + - {tab.label} + {tab.label} ) useEffect(() => { if (initialRows && loaded && !innerLoaded) { - let updateRows = [...initialRows,EmptyRow()] + let updateRows = [...initialRows, EmptyRow()] if (!updateRows?.length && contentType !== 'XML') { updateRows = [EmptyRow()] } @@ -171,7 +171,7 @@ export function MessageDataGrid(props: MessageDataGridProps) onChange?.(updateRows) setInnerLoaded(true) } - }, [EmptyRow, contentType, initialRows, loaded,innerLoaded, onChange, updateSelectAll]) + }, [EmptyRow, contentType, initialRows, loaded, innerLoaded, onChange, updateSelectAll]) useEffect(() => { const neoRenderRows = flattenTree( @@ -251,15 +251,13 @@ export function MessageDataGrid(props: MessageDataGridProps) const getActions = useCallback( (params: GridRowParams) => { - const actions = [ - handleOpenMoreSetting(params)} /> - ] + const actions = [ handleOpenMoreSetting(params)} />] const isXML = contentType === 'XML' const isRoot = params.row.__globalIndex__ === 0 if (['JSON', 'XML'].includes(contentType)) { actions.unshift( { const newRow = EmptyRow() @@ -289,7 +287,7 @@ export function MessageDataGrid(props: MessageDataGridProps) if (!(isXML && isRoot)) { actions.unshift( { const newRow = EmptyRow() @@ -302,11 +300,11 @@ export function MessageDataGrid(props: MessageDataGridProps) } } if (renderRows.length > 1) { - actions.push( handleRowDelete(params)} />) + actions.push( handleRowDelete(params)} />) } return actions }, - + [EmptyRow, tableApiRef, contentType, handleOpenMoreSetting, handleRowDelete, renderRows.length, rows] ) @@ -315,7 +313,7 @@ export function MessageDataGrid(props: MessageDataGridProps) field: 'name', headerName: $t('标签'), editable: true, - sortable:false, + sortable: false, renderEditCell: (params) => { const options = RequestHeaders.map((option) => option.key) return ( @@ -331,7 +329,7 @@ export function MessageDataGrid(props: MessageDataGridProps) const rowIndex = params.row.__globalIndex__ as number if (renderRows.length === rowIndex + 1) { const newRow = EmptyRow() - setRows((preRows)=>[...preRows,newRow]) + setRows((preRows) => [...preRows, newRow]) } }} /> @@ -386,7 +384,7 @@ export function MessageDataGrid(props: MessageDataGridProps) const newRow = EmptyRow() const currentLevelChildrenList = params.row.parent?.childList ?? rows currentLevelChildrenList?.splice((params.row.__levelIndex__ || 0) + 1, 0, newRow) - setRows((preRows)=>[...preRows]) + setRows((preRows) => [...preRows]) } }} /> @@ -444,7 +442,7 @@ export function MessageDataGrid(props: MessageDataGridProps) indeterminate={selectAll === 'indeterminate'} onChange={handleSelectAllChange} /> - {$t('必需')} + {$t('必需')} ) }, @@ -516,7 +514,8 @@ export function MessageDataGrid(props: MessageDataGridProps) paddingRight: theme.spacing(1) } }} - placeholder={$t('示例')} /> + placeholder={$t('示例')} + /> ) } }, diff --git a/frontend/packages/common/src/components/postcat/api/ApiManager/components/UriInput/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiManager/components/UriInput/index.tsx index c00d3ea1..4a5b3393 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiManager/components/UriInput/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiManager/components/UriInput/index.tsx @@ -1,9 +1,9 @@ -import { InputAdornment, TextField, Select, MenuItem, Divider, SelectChangeEvent, Typography, Box } from '@mui/material' +import { InputAdornment, TextField, Select, MenuItem, Divider, SelectChangeEvent } from '@mui/material' import { SyntheticEvent } from 'react' -import {ParseCurlResult} from "@common/const/api-detail"; -import {HTTPMethod, RequestMethod} from "../../../RequestMethod"; -import {ParseCurl} from "@common/utils/curl.ts"; -import { $t } from '@common/locales'; +import { ParseCurlResult } from '@common/const/api-detail' +import { HTTPMethod, RequestMethod } from '../../../RequestMethod' +import { ParseCurl } from '@common/utils/curl.ts' +import { $t } from '@common/locales' interface UriInputProps { inputValue?: string @@ -22,8 +22,6 @@ export function UriInput({ onCURLPaste, onTest }: UriInputProps) { - - const handleSelectChange = (event: SelectChangeEvent) => { onSelectChange?.(event.target.value as HTTPMethod) } @@ -52,14 +50,14 @@ export function UriInput({ return ( { diff --git a/frontend/packages/common/src/components/postcat/api/ApiManager/constants.ts b/frontend/packages/common/src/components/postcat/api/ApiManager/constants.ts index abc9bbcb..9bba8f93 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiManager/constants.ts +++ b/frontend/packages/common/src/components/postcat/api/ApiManager/constants.ts @@ -1,14 +1,15 @@ -import {generateId} from "@common/utils/postcat.tsx"; +import { generateId } from '@common/utils/postcat.tsx' import { ApiBodyType, BodyParamsType, HeaderParamsType, - QueryParamsType, ResponseList, + QueryParamsType, + ResponseList, RestParamsType -} from "@common/const/api-detail"; -import {Protocol} from "../RequestMethod"; +} from '@common/const/api-detail' +import { Protocol } from '../RequestMethod' -type SafeAny = unknown +type SafeAny = unknown type Timestamp = number declare interface HttpRequestMessage { @@ -68,4 +69,4 @@ export function getDefaultApiInfo(): ApiRequest { }, responseList: [{}] } as unknown as ApiRequest -} \ No newline at end of file +} diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiBasicInfoDisplay/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiBasicInfoDisplay/index.tsx index f12b81a7..17ce249f 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiBasicInfoDisplay/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiBasicInfoDisplay/index.tsx @@ -1,8 +1,7 @@ - import { Box, Chip, Stack, Typography, Skeleton } from '@mui/material' -import {HTTPMethod, Protocol,RequestMethod} from "../../../RequestMethod"; -import {Clipboard} from "../../../Clipboard" -import { $t } from '@common/locales'; +import { HTTPMethod, Protocol, RequestMethod } from '../../../RequestMethod' +import { Clipboard } from '../../../Clipboard' +import { $t } from '@common/locales' interface ApiBasicInfoDisplayProps { apiName: string @@ -39,15 +38,19 @@ export default function ApiBasicInfoDisplay(props: Partial - + {/*{selectedEnv ? selectedEnv.hostUri : ''}*/} {uri} diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiMatch/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiMatch/index.tsx index ae487b99..2cb686d6 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiMatch/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiMatch/index.tsx @@ -1,12 +1,11 @@ - -import { Box, useTheme } from "@mui/material" +import { Box, useTheme } from '@mui/material' import { DataGridPro, GridColDef } from '@mui/x-data-grid-pro' import { useMemo } from 'react' -import {collapseTableSx, previewTableHoverSx} from "../../../PreviewTable"; -import {Collapse} from "../../../Collapse"; -import { MatchPositionEnum, MatchTypeEnum } from "@core/const/system/const"; -import { MatchItem } from "@common/const/type"; -import { $t } from "@common/locales"; +import { collapseTableSx, previewTableHoverSx } from '../../../PreviewTable' +import { Collapse } from '../../../Collapse' +import { MatchPositionEnum, MatchTypeEnum } from '@core/const/system/const' +import { MatchItem } from '@common/const/type' +import { $t } from '@common/locales' interface ApiMatchProps { rows?: MatchItem[] @@ -32,24 +31,24 @@ export default function ApiMatch({ rows = [], title, loading = false }: ApiMatch field: 'key', headerName: $t('参数名'), hideable: false, - width:200 + width: 200 }, { field: 'position', headerName: $t('参数位置'), valueGetter: (params) => MatchPositionEnum[params.row.position], - width:160 + width: 160 }, { field: 'matchType', headerName: $t('匹配类型'), valueGetter: (params) => MatchTypeEnum[params.row.matchType], - width:160 + width: 160 }, { field: 'pattern', headerName: $t('参数值'), - flex:1 + flex: 1 } ] diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiProxy/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiProxy/index.tsx index 83d4a19e..f0d9b509 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiProxy/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/ApiProxy/index.tsx @@ -1,15 +1,15 @@ -import { useTheme, Box } from "@mui/material" -import { GridColDef, DataGridPro } from "@mui/x-data-grid-pro" -import { Descriptions } from "antd" -import { useState, useMemo, useEffect } from "react" -import { SystemApiProxyType, ProxyHeaderItem } from "@core/const/system/type" -import { previewTableHoverSx, collapseTableSx } from "../../../PreviewTable" -import { RenderMessageBody } from "../MessageBody" -import { Collapse } from "../../../Collapse" -import { $t } from "@common/locales" +import { useTheme, Box } from '@mui/material' +import { GridColDef, DataGridPro } from '@mui/x-data-grid-pro' +import { Descriptions } from 'antd' +import { useState, useMemo, useEffect } from 'react' +import { SystemApiProxyType, ProxyHeaderItem } from '@core/const/system/type' +import { previewTableHoverSx, collapseTableSx } from '../../../PreviewTable' +import { RenderMessageBody } from '../MessageBody' +import { Collapse } from '../../../Collapse' +import { $t } from '@common/locales' interface HeaderFieldsProps { - proxyInfo:SystemApiProxyType + proxyInfo: SystemApiProxyType loading?: boolean validating?: boolean title: string @@ -18,7 +18,7 @@ interface HeaderFieldsProps { export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSettingChange }: HeaderFieldsProps) { const theme = useTheme() - const [rows,setRows] = useState<[]>([]) + const [rows, setRows] = useState<[]>([]) const borderRadius = theme.shape.borderRadius const hoverSx = useMemo(() => { @@ -38,14 +38,14 @@ export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSett { field: 'optType', headerName: $t('操作类型'), - valueGetter: (params) => params.row.optType === 'ADD'?$t('新增或修改'):$t('删除'), + valueGetter: (params) => (params.row.optType === 'ADD' ? $t('新增或修改') : $t('删除')), width: 200 }, { field: 'value', headerName: $t('匹配参数值'), flex: 1 - }, + } ] const getBasicInfo = useMemo(() => { @@ -54,13 +54,13 @@ export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSett key: 'path', label: $t('转发上游路径'), children: proxyInfo?.path, - style: {paddingBottom: '10px'}, + style: { paddingBottom: '10px' } }, { key: 'timeout', label: $t('请求超时时间'), children: proxyInfo?.timeout, - style: {paddingBottom: '10px'}, + style: { paddingBottom: '10px' } }, // { // key: 'upstream', @@ -72,27 +72,38 @@ export default function ApiProxy({ proxyInfo, title, loading = false, onMoreSett key: 'retry', label: $t('重试时间'), children: proxyInfo?.retry, - style: {paddingBottom: '10px'}, + style: { paddingBottom: '10px' } }, - ...(proxyInfo.headers.length > 0 ? [{ - key: 'headers', - label: $t('转发上游请求头'), - children: '', - style: {paddingBottom: '10px'}, - }]:[]) - ]; - }, [proxyInfo]); + ...(proxyInfo.headers.length > 0 + ? [ + { + key: 'headers', + label: $t('转发上游请求头'), + children: '', + style: { paddingBottom: '10px' } + } + ] + : []) + ] + }, [proxyInfo]) useEffect(() => { setRows(proxyInfo?.headers || []) - }, [proxyInfo]); + }, [proxyInfo]) return ( - 0 ? 'border-0 border-b border-solid border-b-BORDER': ''} `} title="" items={getBasicInfo} column={2} labelStyle={{width:'120px',justifyContent:'flex-end',fontWeight:'bold'}} contentStyle={{color:'#333'}}/> + 0 ? 'border-0 border-b border-solid border-b-BORDER' : ''} `} + title="" + items={getBasicInfo} + column={2} + labelStyle={{ width: '120px', justifyContent: 'flex-end', fontWeight: 'bold' }} + contentStyle={{ color: '#333' }} + /> - {proxyInfo?.headers?.length > 0 && + {proxyInfo?.headers?.length > 0 && ( - } + )} ) diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/HeaderFields/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/HeaderFields/index.tsx index 69bfafda..0da13fc8 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/HeaderFields/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/HeaderFields/index.tsx @@ -1,11 +1,11 @@ -import { Box, LinearProgress, useTheme } from "@mui/material" +import { Box, LinearProgress, useTheme } from '@mui/material' import { DataGridPro, GridColDef } from '@mui/x-data-grid-pro' import { useMemo } from 'react' import { RenderMessageBody } from '../MessageBody' -import {HeaderParamsType} from "@common/const/api-detail"; -import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "../../../PreviewTable"; -import {Collapse} from "../../../Collapse"; -import { $t } from "@common/locales"; +import { HeaderParamsType } from '@common/const/api-detail' +import { collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx } from '../../../PreviewTable' +import { Collapse } from '../../../Collapse' +import { $t } from '@common/locales' interface HeaderFieldsProps { rows?: HeaderParamsType[] @@ -58,7 +58,7 @@ export default function HeaderFields({ rows = [], title, loading = false, onMore getActions: (params) => [ onMoreSettingChange?.(params.row as unknown as RenderMessageBody)} /> diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Binary.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Binary.tsx index 83bcad93..bbe170b4 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Binary.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Binary.tsx @@ -1,14 +1,5 @@ import { TextField } from '@mui/material' -export function PreviewBodyBinary({ value }: { value: string;}) { - return ( - - ) +export function PreviewBodyBinary({ value }: { value: string }) { + return } diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Raw.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Raw.tsx index 3b7acb14..94e60193 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Raw.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/components/Raw.tsx @@ -1,4 +1,4 @@ -import {Codebox} from "../../../../Codebox"; +import { Codebox } from '../../../../Codebox' interface RequestBodyRawProps { value: string diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/index.tsx index 740c0385..03c2058b 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/MessageBody/index.tsx @@ -1,12 +1,12 @@ import { Box, LinearProgress, useTheme } from '@mui/material' import { DataGridPro, GridColDef, useGridApiRef } from '@mui/x-data-grid-pro' import { useEffect, useMemo } from 'react' -import {ApiBodyType, ApiParamsType, BodyParamsType} from "@common/const/api-detail"; -import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "../../../PreviewTable"; -import {Collapse} from "../../../Collapse"; -import { PreviewBodyBinary } from './components/Binary'; -import { PreviewBodyRaw } from './components/Raw'; -import { $t } from '@common/locales'; +import { ApiBodyType, ApiParamsType, BodyParamsType } from '@common/const/api-detail' +import { collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx } from '../../../PreviewTable' +import { Collapse } from '../../../Collapse' +import { PreviewBodyBinary } from './components/Binary' +import { PreviewBodyRaw } from './components/Raw' +import { $t } from '@common/locales' export interface RenderMessageBody extends BodyParamsType { path: string[] @@ -41,7 +41,6 @@ export default function MessageBodyComponent({ } }, [borderRadius]) - useEffect(() => { rows.forEach((row) => { row?.childList?.length && apiRef.current.setRowChildrenExpansion(row.id, true) @@ -70,7 +69,7 @@ export default function MessageBodyComponent({ }, { field: 'paramAttr', - headerName:$t('示例'), + headerName: $t('示例'), valueGetter: (params) => params.row.paramAttr?.example, flex: 1 }, @@ -83,10 +82,10 @@ export default function MessageBodyComponent({ hideable: true, getActions: (params) => [ onMoreSettingChange?.(params.row)} + icon="more" + label={$t('More')} + key="more" + onClick={() => onMoreSettingChange?.(params.row)} /> ] } @@ -95,39 +94,41 @@ export default function MessageBodyComponent({ return ( - {contentType !== ApiBodyType.Binary && contentType !== ApiBodyType.Raw && row.path} - hideFooter - disableColumnMenu={true} - disableColumnReorder={true} - disableColumnPinning={true} - autosizeOptions={{ - expand: true, - includeHeaders: false - }} - loading={loading} - slots={{ - loadingOverlay: LinearProgress - }} - />} - { contentType ===ApiBodyType.Binary && } - { contentType === ApiBodyType.Raw && } + {contentType !== ApiBodyType.Binary && contentType !== ApiBodyType.Raw && ( + row.path} + hideFooter + disableColumnMenu={true} + disableColumnReorder={true} + disableColumnPinning={true} + autosizeOptions={{ + expand: true, + includeHeaders: false + }} + loading={loading} + slots={{ + loadingOverlay: LinearProgress + }} + /> + )} + {contentType === ApiBodyType.Binary && } + {contentType === ApiBodyType.Raw && } ) diff --git a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/QueryFields/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/QueryFields/index.tsx index 09e7c83d..1186fc97 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiPreview/components/QueryFields/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiPreview/components/QueryFields/index.tsx @@ -1,12 +1,11 @@ - import { Box, LinearProgress, useTheme } from '@mui/material' import { DataGridPro, GridColDef } from '@mui/x-data-grid-pro' import { useMemo } from 'react' import { RenderMessageBody } from '../MessageBody' -import {QueryParamsType} from "@common/const/api-detail"; -import {collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx} from "../../../PreviewTable"; -import {Collapse} from "../../../Collapse"; -import { $t } from '@common/locales'; +import { QueryParamsType } from '@common/const/api-detail' +import { collapseTableSx, PreviewGridActionsCellItem, previewTableHoverSx } from '../../../PreviewTable' +import { Collapse } from '../../../Collapse' +import { $t } from '@common/locales' interface QueryFieldsProps { rows?: QueryParamsType[] @@ -59,7 +58,7 @@ export default function QueryFields({ rows = [], title, loading = false, onMoreS getActions: (params) => [ onMoreSettingChange?.(params.row as unknown as RenderMessageBody)} /> diff --git a/frontend/packages/common/src/components/postcat/api/ApiTest/components/ApiRequestTester/ImportMessage/index.tsx b/frontend/packages/common/src/components/postcat/api/ApiTest/components/ApiRequestTester/ImportMessage/index.tsx index a501397d..796fa1b8 100644 --- a/frontend/packages/common/src/components/postcat/api/ApiTest/components/ApiRequestTester/ImportMessage/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/ApiTest/components/ApiRequestTester/ImportMessage/index.tsx @@ -1,10 +1,10 @@ import { Box, Button, DialogContent, Paper, Typography, useTheme } from '@mui/material' import { useEffect, useMemo, useState } from 'react' import { IconButton } from '../../../../IconButton' -import {BaseDialog} from "../../../../Dialog"; -import {Icon} from "../../../../Icon"; -import {Codebox} from "../../../../Codebox"; -import { $t } from '@common/locales'; +import { BaseDialog } from '../../../../Dialog' +import { Icon } from '../../../../Icon' +import { Codebox } from '../../../../Codebox' +import { $t } from '@common/locales' export type ImportMessageChangeType = 'replace-all' | 'insert-end' | 'replace-changed' @@ -90,7 +90,7 @@ export function ImportMessage({ type, onChange }: ImportMessageDialogProps) { {$t('取消')} ) : ( - {value?.length ? {$t('Files Selected')}: {value.length} : null} + {value?.length ? ( + + {$t('Files Selected')}: {value.length} + + ) : null} ) } diff --git a/frontend/packages/common/src/const/api-detail/default-header.ts b/frontend/packages/common/src/const/api-detail/default-header.ts index dc32fc23..9bbadf01 100644 --- a/frontend/packages/common/src/const/api-detail/default-header.ts +++ b/frontend/packages/common/src/const/api-detail/default-header.ts @@ -1,7 +1,7 @@ type DEFAULT_HEADER_OBJ_TYPE = { - [key: string]: { description: string; value: string }; + [key: string]: { description: string; value: string } // 索引签名接受任意字符串类型的参数,并返回具有指定属性的对象 -}; +} const DEFAULT_HEADER_OBJ: DEFAULT_HEADER_OBJ_TYPE = { // 'Authorization-Type': { // description: '鉴权方式,值为:apikey', @@ -13,4 +13,4 @@ const DEFAULT_HEADER_OBJ: DEFAULT_HEADER_OBJ_TYPE = { } } -export default DEFAULT_HEADER_OBJ \ No newline at end of file +export default DEFAULT_HEADER_OBJ diff --git a/frontend/packages/common/src/const/api-detail/index.ts b/frontend/packages/common/src/const/api-detail/index.ts index 1b1f4fd1..af434552 100644 --- a/frontend/packages/common/src/const/api-detail/index.ts +++ b/frontend/packages/common/src/const/api-detail/index.ts @@ -1,15 +1,13 @@ -import {extend} from "lodash-es"; -import {HTTPMethod, Protocol} from "@common/components/postcat/api/RequestMethod"; +import { HTTPMethod, Protocol } from '@common/components/postcat/api/RequestMethod' export interface MenuItem { - key?: string; - name?: string; - emoji?: string; - path?: string; + key?: string + name?: string + emoji?: string + path?: string content?: unknown } - export const DATA_TYPE = { JSON: '[json]', INT: '[int]', @@ -29,35 +27,35 @@ export const DATA_TYPE = { STRING: '[string]' } -export type DATA_TYPE_ITEM_TYPE = keyof typeof DATA_TYPE; +export type DATA_TYPE_ITEM_TYPE = keyof typeof DATA_TYPE // tdk和schema使用,商品详情内页的类型 export const PageTypeEnum = { - INTRODUCTION:1, - COMMON_QUESTION:3, - API_DOCUMENT:4, - PRICE:5, - GUIDANCE:6 + INTRODUCTION: 1, + COMMON_QUESTION: 3, + API_DOCUMENT: 4, + PRICE: 5, + GUIDANCE: 6 } export type ApiParamsBasicType = { - id: string, - parentId: number, - apiUuid: string, - responseUuid: string, - name: string, - paramType: number, - partType: number, - dataType: number, - dataTypeValue: string, - structureId: number, - structureParamId: string, - contentType: ApiBodyType , - isRequired: number, - binaryRawData: string, - description: string, - orderNo: number, - isDefault: number, + id: string + parentId: number + apiUuid: string + responseUuid: string + name: string + paramType: number + partType: number + dataType: number + dataTypeValue: string + structureId: number + structureParamId: string + contentType: ApiBodyType + isRequired: number + binaryRawData: string + description: string + orderNo: number + isDefault: number paramAttr: ParamAttrType childList: [] responseParams?: HttpResponseMessage @@ -72,73 +70,73 @@ export type QueryParamsType = ApiParamsBasicType export type RestParamsType = ApiParamsBasicType export type ParamAttrType = { - id: number, - apiParamId: number, - minLength: number, - maxLength: number, - minValue: {}, - maxValue: {}, - paramLimit: string, - paramValueList: string, - paramMock: string, - attr: string, - structureIsHide: number, - example: string, - dbArr: string, + id: number + apiParamId: number + minLength: number + maxLength: number + minValue: {} + maxValue: {} + paramLimit: string + paramValueList: string + paramMock: string + attr: string + structureIsHide: number + example: string + dbArr: string paramNote: string } export type ResultListType = { - id: string, - apiUuid: string, - name: string, - httpCode: string, - httpContentType: string, - type: number, - content: string, - createUserId: number, - updateUserId: number, - createTime: number, + id: string + apiUuid: string + name: string + httpCode: string + httpContentType: string + type: number + content: string + createUserId: number + updateUserId: number + createTime: number updateTime: number } export type ResponseList = { - id: number, - responseUuid: string, - apiUuid: string, - oldId: number, - name: string, - httpCode: string, - contentType: number, - isDefault: number, - updateUserId: number, - createUserId: number, - createTime: number, - updateTime: number, + id: number + responseUuid: string + apiUuid: string + oldId: number + name: string + httpCode: string + contentType: number + isDefault: number + updateUserId: number + createUserId: number + createTime: number + updateTime: number responseParams: { - headerParams: HeaderParamsType[], + headerParams: HeaderParamsType[] bodyParams: BodyParamsType[] - queryParams: QueryParamsType[], + queryParams: QueryParamsType[] restParams: RestParamsType[] } } export type ApiDetail = { - id: string, - service: string, - name: string, - protocol: Protocol, - method:HTTPMethod, - uri: string, - encoding: string, - tag: string, + id: string + service: string + name: string + protocol: Protocol + method: HTTPMethod + uri: string + encoding: string + tag: string requestParams: { - headerParams: HeaderParamsType[], - bodyParams: BodyParamsType[], - queryParams: QueryParamsType[], + headerParams: HeaderParamsType[] + bodyParams: BodyParamsType[] + queryParams: QueryParamsType[] restParams: RestParamsType[] - }, - resultList: ResultListType[], + } + resultList: ResultListType[] responseList: ResponseList[] } @@ -180,7 +178,6 @@ export enum ApiBodyType { export type TestApiBodyType = ApiBodyType.FormData | ApiBodyType.Raw | ApiBodyType.Binary - export type ParseCurlResult = { /* 请求地址 */ url: string @@ -204,77 +201,76 @@ declare interface HttpResponseMessage { headerParams?: ApiParamsBasicType[] } - export const commonTableSx = { - '.MuiDataGrid-columnHeaderTitle':{ - fontSize:'14px' + '.MuiDataGrid-columnHeaderTitle': { + fontSize: '14px' }, - '.MuiDataGrid-columnHeader':{ - background:'#f7f8fa', - + '.MuiDataGrid-columnHeader': { + background: '#f7f8fa' }, - '.MuiDataGrid-withBorderColor':{ - borderColor:'#EDEDED' + '.MuiDataGrid-withBorderColor': { + borderColor: '#EDEDED' }, - '& .MuiButtonBase-root.MuiIconButton-root':{ - borderRadius:'4px' + '& .MuiButtonBase-root.MuiIconButton-root': { + borderRadius: '4px' }, - '& .MuiButtonBase-root.MuiIconButton-root:hover':{ - backgroundColor:'#f7f8fa' + '& .MuiButtonBase-root.MuiIconButton-root:hover': { + backgroundColor: '#f7f8fa' }, - '& .MuiDataGrid-columnSeparator--resizable:hover':{ - color:'#EDEDED' + '& .MuiDataGrid-columnSeparator--resizable:hover': { + color: '#EDEDED' }, - '& .MuiDataGrid-columnHeader:focus-within':{ - outline:'none'} - , - '& .MuiDataGrid-cell:focus-within':{ - outline:'none'}, - '.MuiDataGrid-columnHeaderTitleContainer':{ - justifyContent:'space-between' + '& .MuiDataGrid-columnHeader:focus-within': { + outline: 'none' }, - '.MuiDataGrid-columnSeparator--resizable:hover':{ - color:'#EDEDED' + '& .MuiDataGrid-cell:focus-within': { + outline: 'none' }, - '& .MuiDataGrid-withBorderColor':{ - borderColor:'#EDEDED' + '.MuiDataGrid-columnHeaderTitleContainer': { + justifyContent: 'space-between' }, - '& .MuiDataGrid-row.Mui-selected ':{ - backgroundColor:'#f7f8fa', + '.MuiDataGrid-columnSeparator--resizable:hover': { + color: '#EDEDED' }, - '& .MuiDataGrid-columnHeader':{ - backgroundColor:'#f7f8fa', + '& .MuiDataGrid-withBorderColor': { + borderColor: '#EDEDED' }, - '& .MuiDataGrid-row.Mui-selected:hover':{ - backgroundColor:'#EBEEF2', + '& .MuiDataGrid-row.Mui-selected ': { + backgroundColor: '#f7f8fa' }, - '& .MuiDataGrid-row.Mui-selected.Mui-hovered':{ - backgroundColor:'#EBEEF2', + '& .MuiDataGrid-columnHeader': { + backgroundColor: '#f7f8fa' }, - '& .MuiDataGrid-columnHeader:focus':{ - outline:'none'} - , - '& .MuiDataGrid-cell:focus':{ - outline:'none' + '& .MuiDataGrid-row.Mui-selected:hover': { + backgroundColor: '#EBEEF2' }, - '& .MuiDataGrid-cell.MuiDataGrid-cell--editing:focus-within':{ - outline:'none' + '& .MuiDataGrid-row.Mui-selected.Mui-hovered': { + backgroundColor: '#EBEEF2' + }, + '& .MuiDataGrid-columnHeader:focus': { + outline: 'none' + }, + '& .MuiDataGrid-cell:focus': { + outline: 'none' + }, + '& .MuiDataGrid-cell.MuiDataGrid-cell--editing:focus-within': { + outline: 'none' }, '& .MuiDataGrid-row.Mui-hovered': { - backgroundColor:'#EBEEF2', + backgroundColor: '#EBEEF2', '.table-actions': { visibility: 'visible' } }, - '& .MuiButtonBase-root.MuiIconButton-root.MuiIconButton-sizeSmall':{ - borderRadius:'4px' + '& .MuiButtonBase-root.MuiIconButton-root.MuiIconButton-sizeSmall': { + borderRadius: '4px' }, - '& .MuiDataGrid-columnHeaderTitleContainer':{ - justifyContent:'space-between' + '& .MuiDataGrid-columnHeaderTitleContainer': { + justifyContent: 'space-between' }, - '& .MuiOutlinedInput-input':{ - color:'#333', - fontSize:'14px' + '& .MuiOutlinedInput-input': { + color: '#333', + fontSize: '14px' } -} \ No newline at end of file +} diff --git a/frontend/packages/common/src/const/approval/const.tsx b/frontend/packages/common/src/const/approval/const.tsx index 3d7186c0..af3c1bf5 100644 --- a/frontend/packages/common/src/const/approval/const.tsx +++ b/frontend/packages/common/src/const/approval/const.tsx @@ -1,480 +1,475 @@ -import { ApprovalTableListItem, PublishTableListItem } from "./type"; -import { PageProColumns } from "@common/components/aoplatform/PageList"; +import { ApprovalTableListItem, PublishTableListItem } from './type' +import { PageProColumns } from '@common/components/aoplatform/PageList' -export const TODO_LIST_COLUMN_NOT_INCLUDE_KEY:string[] = ['status','approver','approvalTime'] +export const TODO_LIST_COLUMN_NOT_INCLUDE_KEY: string[] = ['status', 'approver', 'approvalTime'] -export const SUBSCRIBE_APPROVAL_TABLE_COLUMN : PageProColumns[] = [ - { - title:('申请时间'), - dataIndex: 'applyTime', - ellipsis:true, - width:182, - fixed:'left', - sorter: (a,b)=> { - return a.applyTime.localeCompare(b.applyTime) - }, - }, - { - title:('申请方-消费者'), - dataIndex: ['application','name'], - ellipsis:true - }, - { - title:('申请服务'), - dataIndex: ['service','name'], - ellipsis:true - }, - { - title:('服务所属系统'), - dataIndex: ['service','name'], - ellipsis:true - }, - { - title:('服务所属团队'), - dataIndex: ['team','name'], - ellipsis:true - }, - { - title:('审核状态'), - dataIndex: 'status', - valueType: 'text', - }, - { - title:('申请人'), - dataIndex: ['applier','name'], - ellipsis: true, - width:88, - }, - { - title:('审核人'), - dataIndex: ['approver','name'], - ellipsis: true, - width:88 - }, - { - title:('审核时间'), - dataIndex: 'approvalTime', - ellipsis: true, - // sorter: true,, - width:182, - sorter: (a,b)=> { - return a.approvalTime.localeCompare(b.approvalTime) - }, - }, -]; +export const SUBSCRIBE_APPROVAL_TABLE_COLUMN: PageProColumns[] = [ + { + title: '申请时间', + dataIndex: 'applyTime', + ellipsis: true, + width: 182, + fixed: 'left', + sorter: (a, b) => { + return a.applyTime.localeCompare(b.applyTime) + } + }, + { + title: '申请方-消费者', + dataIndex: ['application', 'name'], + ellipsis: true + }, + { + title: '申请服务', + dataIndex: ['service', 'name'], + ellipsis: true + }, + { + title: '服务所属系统', + dataIndex: ['service', 'name'], + ellipsis: true + }, + { + title: '服务所属团队', + dataIndex: ['team', 'name'], + ellipsis: true + }, + { + title: '审核状态', + dataIndex: 'status', + valueType: 'text' + }, + { + title: '申请人', + dataIndex: ['applier', 'name'], + ellipsis: true, + width: 88 + }, + { + title: '审核人', + dataIndex: ['approver', 'name'], + ellipsis: true, + width: 88 + }, + { + title: '审核时间', + dataIndex: 'approvalTime', + ellipsis: true, + // sorter: true,, + width: 182, + sorter: (a, b) => { + return a.approvalTime.localeCompare(b.approvalTime) + } + } +] -export const SUBSCRIBE_APPROVAL_INNER_TODO_TABLE_COLUMN : PageProColumns[] = [ - { - title:('申请时间'), - dataIndex: 'applyTime', - // sorter: true, - ellipsis:true, - width:182, - fixed:'left', - sorter: (a,b)=> { - return a.applyTime.localeCompare(b.applyTime) - }, - }, - { - title:('申请方-消费者'), - dataIndex: ['application','name'], - ellipsis:true - }, - { - // title:('申请人', - title: ('申请人'), - dataIndex: ['applier','name'], - ellipsis: true, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true - }, - { - title:('申请服务'), - dataIndex: ['service','name'], - ellipsis:true - }, -]; +export const SUBSCRIBE_APPROVAL_INNER_TODO_TABLE_COLUMN: PageProColumns[] = [ + { + title: '申请时间', + dataIndex: 'applyTime', + // sorter: true, + ellipsis: true, + width: 182, + fixed: 'left', + sorter: (a, b) => { + return a.applyTime.localeCompare(b.applyTime) + } + }, + { + title: '申请方-消费者', + dataIndex: ['application', 'name'], + ellipsis: true + }, + { + // title:('申请人', + title: '申请人', + dataIndex: ['applier', 'name'], + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '申请服务', + dataIndex: ['service', 'name'], + ellipsis: true + } +] - -export const SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN : PageProColumns[] = [ - { - title:('申请时间'), - dataIndex: 'applyTime', - // sorter: true, - ellipsis:true, - width:182, - fixed:'left', - sorter: (a,b)=> { - return a.applyTime.localeCompare(b.applyTime) - }, - }, - { - title:('申请方-消费者'), - dataIndex: ['application','name'], - ellipsis:true - }, - { - // title:('申请人', - title: ('申请人'), - dataIndex: ['applier','name'], - ellipsis: true, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('申请服务'), - dataIndex: ['service','name'], - ellipsis:true - }, - { - title:('审核状态'), - dataIndex: 'status', - valueType: 'select', - ellipsis: true, - filters: true, - onFilter: true, - }, - { - title:('审核人'), - dataIndex: ['approver','name'], - ellipsis: true, - width:88, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('审核时间'), - dataIndex: 'approvalTime', - ellipsis: true, - // sorter: true,, - width:182, - sorter: (a,b)=> { - return a.approvalTime.localeCompare(b.approvalTime) - }, - }, -]; +export const SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN: PageProColumns[] = [ + { + title: '申请时间', + dataIndex: 'applyTime', + // sorter: true, + ellipsis: true, + width: 182, + fixed: 'left', + sorter: (a, b) => { + return a.applyTime.localeCompare(b.applyTime) + } + }, + { + title: '申请方-消费者', + dataIndex: ['application', 'name'], + ellipsis: true + }, + { + // title:('申请人', + title: '申请人', + dataIndex: ['applier', 'name'], + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '申请服务', + dataIndex: ['service', 'name'], + ellipsis: true + }, + { + title: '审核状态', + dataIndex: 'status', + valueType: 'select', + ellipsis: true, + filters: true, + onFilter: true + }, + { + title: '审核人', + dataIndex: ['approver', 'name'], + ellipsis: true, + width: 88, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '审核时间', + dataIndex: 'approvalTime', + ellipsis: true, + // sorter: true,, + width: 182, + sorter: (a, b) => { + return a.approvalTime.localeCompare(b.approvalTime) + } + } +] export type SubscribeApprovalTableListItem = { - applyTime: string; - id:string; - application:string; - service:string; - applier:string; - team?:string; - status:0|1; - approver:string; - approvalTime:string; -}; - - -export const PublishApplyStatusEnum = { - 'accept': ("审核完成"), - 'apply': ("发布审核中"), - 'running': ("在线"), - 'none': ("-"), - 'refuse': ("已拒绝"), - // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values - 'close' : ('-'), - 'stop' : ('中止'), - 'error' : ('发布异常'), - 'publishing' : ('发布中') + applyTime: string + id: string + application: string + service: string + applier: string + team?: string + status: 0 | 1 + approver: string + approvalTime: string } +export const PublishApplyStatusEnum = { + accept: '审核完成', + apply: '发布审核中', + running: '在线', + none: '-', + refuse: '已拒绝', + // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values + close: '-', + stop: '中止', + error: '发布异常', + publishing: '发布中' +} export const PublishTableStatusColorClass = { - 'success' : 'text-[#03a9f4]', - 'fail' : 'text-[#ff3b30]', - 'apply' : 'text-[#46BE11]', - 'refuse' : 'text-[#EF0020]', - 'running' : 'text-[#3D46F2]', - 'accept' : 'text-[#147AFE]', - 'none' : 'text-[var(--MAIN_TEXT)]', - 'approval' : 'text-[#03a9f4]', - 'done' : 'text-[#138913]', - 'stop' : 'text-[#ff3b30]', - 'close' : 'text-[var(--MAIN_TEXT)]', - 'error':'text-[#ff3b30]', - 'publishing':'text-[#46BE11]', + success: 'text-[#03a9f4]', + fail: 'text-[#ff3b30]', + apply: 'text-[#46BE11]', + refuse: 'text-[#EF0020]', + running: 'text-[#3D46F2]', + accept: 'text-[#147AFE]', + none: 'text-[var(--MAIN_TEXT)]', + approval: 'text-[#03a9f4]', + done: 'text-[#138913]', + stop: 'text-[#ff3b30]', + close: 'text-[var(--MAIN_TEXT)]', + error: 'text-[#ff3b30]', + publishing: 'text-[#46BE11]' } export const ApprovalStatusColorClass = { - new: 'text-[#138913]', // 使用 Tailwind 的 Arbitrary Properties - update: 'text-[#03a9f4]', - delete: 'text-[#ff3b30]', - none: 'text-[var(--MAIN_TEXT)]', // 假设你也有一个“none”的状态 - }; - - + new: 'text-[#138913]', // 使用 Tailwind 的 Arbitrary Properties + update: 'text-[#03a9f4]', + delete: 'text-[#ff3b30]', + none: 'text-[var(--MAIN_TEXT)]' // 假设你也有一个“none”的状态 +} export const ApprovalRouteColumns = [ - { - title:('请求方式'), - dataIndex:'methods', - ellipsis:true, - render:(value)=>value?.join(', ') - }, - { - title:('名称'), - dataIndex:'name', - ellipsis:true, - }, - { - title:('路径'), - dataIndex:'path', - ellipsis:true - }, - { - title:('描述'), - dataIndex:'description', - - }, - { - title:('类型'), - dataIndex:'change', - - } + { + title: '请求方式', + dataIndex: 'methods', + ellipsis: true, + render: (value) => value?.join(', ') + }, + { + title: '名称', + dataIndex: 'name', + ellipsis: true + }, + { + title: '路径', + dataIndex: 'path', + ellipsis: true + }, + { + title: '描述', + dataIndex: 'description' + }, + { + title: '类型', + dataIndex: 'change' + } ] - export const ApprovalPolicyColumns = [ - { - title:('名称'), - dataIndex:'name', - ellipsis:true, - }, - { - title:('优先级'), - dataIndex:'priority', - ellipsis:true - }, - { - title:('状态'), - dataIndex:'status', - - } + { + title: '名称', + dataIndex: 'name', + ellipsis: true + }, + { + title: '优先级', + dataIndex: 'priority', + ellipsis: true + }, + { + title: '状态', + dataIndex: 'status' + } ] - export const ApprovalUpstreamColumns = [ - { - title:('上游类型'), - dataIndex:'type', - ellipsis:true, - }, - { - title:('地址'), - dataIndex:'addr', - render:(text:string[])=>(<>{text.join(',')}), - ellipsis:true - }, - { - title:('类型'), - dataIndex:'change' - } + { + title: '上游类型', + dataIndex: 'type', + ellipsis: true + }, + { + title: '地址', + dataIndex: 'addr', + render: (text: string[]) => <>{text.join(',')}, + ellipsis: true + }, + { + title: '类型', + dataIndex: 'change' + } ] export const PublishStatusEnum = { - 'apply': ('待审核'), - 'accept' : ('审核通过'), - 'done' : ('已发布'), - 'stop': ('发布终止'), - 'close': ('已关闭'), - 'refuse' : ('已拒绝'), - 'error' : ('发布异常'), - 'publishing' : ('发布中') + apply: '待审核', + accept: '审核通过', + done: '已发布', + stop: '发布终止', + close: '已关闭', + refuse: '已拒绝', + error: '发布异常', + publishing: '发布中' } -export const PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN : PageProColumns[] = [ - { - title:('发布版本'), - dataIndex: 'version', - ellipsis:true, - width:160, - fixed:'left' - }, - { - title:('版本说明'), - dataIndex: 'remark', - ellipsis:true, - width:160, - }, - { - title:('创建版本时间'), - dataIndex: 'createTime', - ellipsis:true, - sorter: (a,b)=> { - return a.createTime.localeCompare(b.createTime) - }, - }, - { - title:('版本状态'), - dataIndex: 'status', - ellipsis:true, - filters: true, - onFilter: true, - valueType: 'select' - }, - { - title:('创建人'), - dataIndex: ['creator','name'], - ellipsis: true, - width:120, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, +export const PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN: PageProColumns[] = [ + { + title: '发布版本', + dataIndex: 'version', + ellipsis: true, + width: 160, + fixed: 'left' + }, + { + title: '版本说明', + dataIndex: 'remark', + ellipsis: true, + width: 160 + }, + { + title: '创建版本时间', + dataIndex: 'createTime', + ellipsis: true, + sorter: (a, b) => { + return a.createTime.localeCompare(b.createTime) } -]; + }, + { + title: '版本状态', + dataIndex: 'status', + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select' + }, + { + title: '创建人', + dataIndex: ['creator', 'name'], + ellipsis: true, + width: 120, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + } +] -export const PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN : PageProColumns[] = [ - { - title:('申请时间'), - dataIndex: 'applyTime', - ellipsis:true, - width:182, - fixed:'left', - }, - { - title:('审核时间'), - dataIndex: 'approveTime', - ellipsis:true, - width:182, - }, - { - title:('版本号'), - dataIndex: 'version', - ellipsis:true, - width:130, - }, - { - title:('版本说明'), - dataIndex: 'remark', - ellipsis:true, - width:160, - }, - { - title:('发布状态'), - dataIndex: 'status', - ellipsis:true, - }, - { - title:('申请人'), - dataIndex: ['applicant','name'], - ellipsis: true, - width:88, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('审核人'), - dataIndex: ['approver','name'], - ellipsis: true, - width:88, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, -]; +export const PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN: PageProColumns[] = [ + { + title: '申请时间', + dataIndex: 'applyTime', + ellipsis: true, + width: 182, + fixed: 'left' + }, + { + title: '审核时间', + dataIndex: 'approveTime', + ellipsis: true, + width: 182 + }, + { + title: '版本号', + dataIndex: 'version', + ellipsis: true, + width: 130 + }, + { + title: '版本说明', + dataIndex: 'remark', + ellipsis: true, + width: 160 + }, + { + title: '发布状态', + dataIndex: 'status', + ellipsis: true + }, + { + title: '申请人', + dataIndex: ['applicant', 'name'], + ellipsis: true, + width: 88, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '审核人', + dataIndex: ['approver', 'name'], + ellipsis: true, + width: 88, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + } +] -export const PUBLISH_APPROVAL_TABLE_COLUMN : PageProColumns[] = [ - { - title:('申请时间'), - dataIndex: 'applyTime', - ellipsis:true, - width:182, - fixed:'left', - sorter: (a,b)=> { - return a.applyTime.localeCompare(b.applyTime) - }, +export const PUBLISH_APPROVAL_TABLE_COLUMN: PageProColumns[] = [ + { + title: '申请时间', + dataIndex: 'applyTime', + ellipsis: true, + width: 182, + fixed: 'left', + sorter: (a, b) => { + return a.applyTime.localeCompare(b.applyTime) + } + }, + { + title: '申请系统', + dataIndex: ['service', 'name'], + ellipsis: true + }, + { + title: '所属团队', + dataIndex: ['team', 'name'], + ellipsis: true + }, + { + title: '审核状态', + dataIndex: 'status', + ellipsis: { + showTitle: true }, - { - title:('申请系统'), - dataIndex: ['service','name'], - ellipsis:true - }, - { - title:('所属团队'), - dataIndex: ['team','name'], - ellipsis:true - }, - { - title:('审核状态'), - dataIndex: 'status', - ellipsis:{ - showTitle:true - }, - filters: true, - onFilter: true, - valueType: 'select', - }, - { - title:('申请人'), - dataIndex: ['applier','name'], - ellipsis: true, - width:88, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('审核人'), - dataIndex: ['approver','name'], - ellipsis: true, - width:88, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('审核时间'), - dataIndex: 'approvalTime', - // sorter: true, - ellipsis:true, - hideInSearch: true, - width:182, - sorter: (a,b)=> { - return a.approvalTime.localeCompare(b.approvalTime) - }, - }, -]; + filters: true, + onFilter: true, + valueType: 'select' + }, + { + title: '申请人', + dataIndex: ['applier', 'name'], + ellipsis: true, + width: 88, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '审核人', + dataIndex: ['approver', 'name'], + ellipsis: true, + width: 88, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '审核时间', + dataIndex: 'approvalTime', + // sorter: true, + ellipsis: true, + hideInSearch: true, + width: 182, + sorter: (a, b) => { + return a.approvalTime.localeCompare(b.approvalTime) + } + } +] -export const ChangeTypeEnum = { - 'new': ('新增'), - 'update': ('变更'), - 'delete' : ('删除'), - 'none' : ('无变更'), - 'error' : ('缺失字段') +export const ChangeTypeEnum = { + new: '新增', + update: '变更', + delete: '删除', + none: '无变更', + error: '缺失字段' } - export const SubscribeApprovalList = [ - { - title:('申请方消费者'),key:'application' - }, - { - title:('申请方所属团队'),key:'applyTeam' - }, - { - title:('申请人'),key:'applier' - }, - { - title:('申请时间'),key:'applyTime' - }, - { - title:('申请服务'),key:'service' - }, - { - title:('服务所属团队'),key:'team' - } -] \ No newline at end of file + { + title: '申请方消费者', + key: 'application' + }, + { + title: '申请方所属团队', + key: 'applyTeam' + }, + { + title: '申请人', + key: 'applier' + }, + { + title: '申请时间', + key: 'applyTime' + }, + { + title: '申请服务', + key: 'service' + }, + { + title: '服务所属团队', + key: 'team' + } +] diff --git a/frontend/packages/common/src/const/approval/type.tsx b/frontend/packages/common/src/const/approval/type.tsx index 42242f73..8e4ccd42 100644 --- a/frontend/packages/common/src/const/approval/type.tsx +++ b/frontend/packages/common/src/const/approval/type.tsx @@ -1,121 +1,117 @@ -import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline"; -import { SystemReleaseStatus } from "@core/const/system/type"; -import { EntityItem } from "@common/const/type"; -import { SubscribeApprovalTableListItem, PublishApplyStatusEnum } from "./const"; +import { SystemInsidePublishOnlineItems } from '@core/pages/system/publish/SystemInsidePublishOnline' +import { SystemReleaseStatus } from '@core/const/system/type' +import { EntityItem } from '@common/const/type' +import { SubscribeApprovalTableListItem, PublishApplyStatusEnum } from './const' export type SubscribeApprovalInfoType = { - applyTime: string; - id:string; - application:string; - applier:string; - service:string; - applyTeam:string; - team:string; - status:string; - approver:string; - approvalTime:string; - reason:string - opinion?:string -}; - + applyTime: string + id: string + application: string + applier: string + service: string + applyTeam: string + team: string + status: string + approver: string + approvalTime: string + reason: string + opinion?: string +} export type PublishApprovalTableListItem = { - id:string; - applyTime:string; - service:string; - team:string; - status:string; - applier:string; - approver:string; - approvalTime:string; -}; - export type PublishApprovalApiItem = { - name:string - method:string - path:string - upstream:string - change:string - status:{ - upstreamStatus: SystemReleaseStatus, - docStatus: SystemReleaseStatus, - proxyStatus: SystemReleaseStatus} + id: string + applyTime: string + service: string + team: string + status: string + applier: string + approver: string + approvalTime: string +} +export type PublishApprovalApiItem = { + name: string + method: string + path: string + upstream: string + change: string + status: { + upstreamStatus: SystemReleaseStatus + docStatus: SystemReleaseStatus + proxyStatus: SystemReleaseStatus + } } export type PublishApprovalUpstreamItem = { - upstream:EntityItem - cluster:EntityItem - type:'static'|'dynamic' - addr:string[] - change:'add'|'update'|'delete'|'none', - status:SystemReleaseStatus + upstream: EntityItem + cluster: EntityItem + type: 'static' | 'dynamic' + addr: string[] + change: 'add' | 'update' | 'delete' | 'none' + status: SystemReleaseStatus } // 发布详情(版本) export type PublishApprovalInfoType = { - id:string; - applyTime:string; - service:EntityItem; - applyTeam:EntityItem; - team:EntityItem; - status:string; - applier:EntityItem; - approver:EntityItem; - approvalTime:string; - areas:Array - remark:string - opinion?:string - diffs:{ - apis:PublishApprovalApiItem[] - upstreams:PublishApprovalUpstreamItem[] - strategies:Array<{name:string, priority:number,statues:0|1}> - } - clusterPublishStatus?:SystemInsidePublishOnlineItems[], - error:string -}; - + id: string + applyTime: string + service: EntityItem + applyTeam: EntityItem + team: EntityItem + status: string + applier: EntityItem + approver: EntityItem + approvalTime: string + areas: Array + remark: string + opinion?: string + diffs: { + apis: PublishApprovalApiItem[] + upstreams: PublishApprovalUpstreamItem[] + strategies: Array<{ name: string; priority: number; statues: 0 | 1 }> + } + clusterPublishStatus?: SystemInsidePublishOnlineItems[] + error: string +} export type ApprovalTableListItem = SubscribeApprovalTableListItem | PublishApprovalTableListItem - export type PublishVersionTableListItem = { - id:string, - version:string - service:EntityItem - remark:string - createTime:string - creator:EntityItem - status:keyof typeof PublishApplyStatusEnum - canRollback:boolean - canDelete:boolean - flowId:string + id: string + version: string + service: EntityItem + remark: string + createTime: string + creator: EntityItem + status: keyof typeof PublishApplyStatusEnum + canRollback: boolean + canDelete: boolean + flowId: string } - export type PublishTableListItem = { - id:string, - version:string - applyTime:string, - approveTime: string, - createTime:string, - creator: EntityItem, - service:EntityItem - team:EntityItem - status:keyof typeof PublishApplyStatusEnum + id: string + version: string + applyTime: string + approveTime: string + createTime: string + creator: EntityItem + service: EntityItem + team: EntityItem + status: keyof typeof PublishApplyStatusEnum } - export type PublishApprovalModalProps = { - type:'approval'|'view'|'add'|'publish'|'online' - data:PublishApprovalInfoType | PublishApprovalInfoType &{id?:string} | PublishVersionTableListItem - insidePage?:boolean - serviceId:string - teamId:string - clusterPublishStatus?:SystemInsidePublishOnlineItems[] - serviceType?:'rest'|'ai' + type: 'approval' | 'view' | 'add' | 'publish' | 'online' + data: PublishApprovalInfoType | (PublishApprovalInfoType & { id?: string }) | PublishVersionTableListItem + insidePage?: boolean + serviceId: string + teamId: string + clusterPublishStatus?: SystemInsidePublishOnlineItems[] + serviceType?: 'rest' | 'ai' } export type PublishApprovalModalHandle = { - save:(operate:'pass'|'refuse') =>Promise - publish:(notSave?:boolean)=>Promise> - online:()=>Promise -} \ No newline at end of file + save: (operate: 'pass' | 'refuse') => Promise + publish: (notSave?: boolean) => Promise> + online: () => Promise +} diff --git a/frontend/packages/common/src/const/code/const.ts b/frontend/packages/common/src/const/code/const.ts index c064ea1e..82b2886d 100644 --- a/frontend/packages/common/src/const/code/const.ts +++ b/frontend/packages/common/src/const/code/const.ts @@ -1,5 +1,4 @@ - -const CODE_LANG = [ +const CODE_LANG = [ { label: 'Java(OK HTTP)', value: 20 @@ -96,4 +95,4 @@ const CODE_LANG = [ } ] -export default CODE_LANG \ No newline at end of file +export default CODE_LANG diff --git a/frontend/packages/common/src/const/const.tsx b/frontend/packages/common/src/const/const.tsx index 092671f6..4b1ae941 100644 --- a/frontend/packages/common/src/const/const.tsx +++ b/frontend/packages/common/src/const/const.tsx @@ -2,107 +2,108 @@ import { $t } from '@common/locales' import { StrategyStatusColorClass, StrategyStatusEnum } from './policy/consts' export type BasicResponse = { - code:number - data:T - msg:string + code: number + data: T + msg: string } export const STATUS_CODE = { - SUCCESS:0, - UNANTHORIZED:401, - FORBIDDEN:403 + SUCCESS: 0, + UNANTHORIZED: 401, + FORBIDDEN: 403 } export const STATUS_COLOR = { - 'done':'text-[#03a9f4]', - 'error':'text-[#ff3b30]' + done: 'text-[#03a9f4]', + error: 'text-[#ff3b30]' } - // TODO should be generated dynamically -export const routerKeyMap = new Map([ - ['workspace',['consumer','service','team','guide']], - ['my',['consumer','service','team']], - ['mainPage',['dashboard','systemrunning']], - ['operationCenter',['member','user','role','common']], - ['organization',['member','user','role']], - ['serviceHubSetting',['common']], - ['maintenanceCenter',['aisetting','datasourcing','cluster','cert','logsettings','resourcesettings','openapi'] - ]]) - - - - export const COLUMNS_TITLE = { - operate : '' - } - - export const VALIDATE_MESSAGE = { - required: ('必填项'), - email:('不是有效邮箱地址') - } - - export const PLACEHOLDER = { - input:('请输入'), - select:('请选择'), - startWithAlphabet:('英文数字下划线任意一种,首字母必须为英文'), - specialStartWithAlphabet:('支持字母开头、英文数字中横线下划线组合'), - onlyAlphabet:('字符非法,仅支持英文'), - ipAndCidr:'请输入IP地址或CIDR范围,每条以换行分割' - } - - export const FORM_ERROR_TIPS = { - refuseOpinion: ('选择拒绝时,审核意见为必填'), - clusterTest:('无法连接集群,请检查集群地址是否正确或防火墙配置'), - - } - - export const RESPONSE_TIPS = { - success: ('操作成功'), - error: ('操作失败'), - operating:('正在操作'), - loading:('正在加载数据'), - dataError:('获取数据失败'), - loginSuccess: ('登录成功'), - logoutSuccess:('退出成功,将跳转至登录页'), - refuseOpinion: ('未填写审核意见'), - copySuccess:('复制成功'), - copyError:('复制失败,请手动复制') - } - - export const DELETE_TIPS = { - default:('该数据删除后将无法找回,请确认是否删除?') - } - - export const DATA_SHOW_TYPE_OPTIONS = [ - {label:'列表', value:'list'}, - {label:'块', value:'block'}, +export const routerKeyMap = new Map([ + ['workspace', ['consumer', 'service', 'team', 'guide']], + ['my', ['consumer', 'service', 'team']], + ['mainPage', ['dashboard', 'systemrunning']], + ['operationCenter', ['member', 'user', 'role', 'common']], + ['organization', ['member', 'user', 'role']], + ['serviceHubSetting', ['common']], + [ + 'maintenanceCenter', + ['aisetting', 'keysetting', 'datasourcing', 'cluster', 'cert', 'logsettings', 'resourcesettings', 'openapi'] ] +]) + +export const COLUMNS_TITLE = { + operate: '' +} + +export const VALIDATE_MESSAGE = { + required: '必填项', + email: '不是有效邮箱地址' +} + +export const PLACEHOLDER = { + input: '请输入', + select: '请选择', + startWithAlphabet: '英文数字下划线任意一种,首字母必须为英文', + specialStartWithAlphabet: '支持字母开头、英文数字中横线下划线组合', + onlyAlphabet: '字符非法,仅支持英文', + ipAndCidr: '请输入IP地址或CIDR范围,每条以换行分割' +} + +export const FORM_ERROR_TIPS = { + refuseOpinion: '选择拒绝时,审核意见为必填', + clusterTest: '无法连接集群,请检查集群地址是否正确或防火墙配置' +} + +export const RESPONSE_TIPS = { + success: '操作成功', + error: '操作失败', + operating: '正在操作', + loading: '正在加载数据', + dataError: '获取数据失败', + loginSuccess: '登录成功', + logoutSuccess: '退出成功,将跳转至登录页', + refuseOpinion: '未填写审核意见', + copySuccess: '复制成功', + copyError: '复制失败,请手动复制' +} + +export const DELETE_TIPS = { + default: '该数据删除后将无法找回,请确认是否删除?' +} + +export const DATA_SHOW_TYPE_OPTIONS = [ + { label: '列表', value: 'list' }, + { label: '块', value: 'block' } +] - export const PolicyPublishColumns = [ - { - title: ('策略名称'), + title: '策略名称', dataIndex: 'name', ellipsis: true, width: 160 }, { - title: ('优先级'), + title: '优先级', dataIndex: 'priority', width: 140, ellipsis: true }, { - title: ('状态'), + title: '状态', dataIndex: 'status', width: 140, - render:(text:string)=> {$t(StrategyStatusEnum[text as keyof typeof StrategyStatusEnum])}, + render: (text: string) => ( + + {$t(StrategyStatusEnum[text as keyof typeof StrategyStatusEnum])} + + ) }, { - title: ('更新时间'), + title: '更新时间', dataIndex: 'optTime', width: 182, - ellipsis: true, - }, -] \ No newline at end of file + ellipsis: true + } +] diff --git a/frontend/packages/common/src/const/domain/const.ts b/frontend/packages/common/src/const/domain/const.ts index abaa0e9a..45ad4ef8 100644 --- a/frontend/packages/common/src/const/domain/const.ts +++ b/frontend/packages/common/src/const/domain/const.ts @@ -43,4 +43,4 @@ const DOMAIN_CONSTANT = [ 'localhost' ] -export default DOMAIN_CONSTANT \ No newline at end of file +export default DOMAIN_CONSTANT diff --git a/frontend/packages/common/src/const/permissions.ts b/frontend/packages/common/src/const/permissions.ts index abfff745..dbe2786e 100644 --- a/frontend/packages/common/src/const/permissions.ts +++ b/frontend/packages/common/src/const/permissions.ts @@ -3,596 +3,692 @@ // 维度 backend - 后端的权限字段; export const PERMISSION_DEFINITION = [ - { - "system.settings.account.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.view"] }] - } - }, - "system.organization.member.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.remove": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.block": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.department.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.department.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.organization.member.department.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.account.manager"] }] - } - }, - "system.workspace.team.view_all": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.view_all"] }] - } - }, - "system.organization.team.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.create"] }] - } - }, - "system.organization.team.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager"] }] - } - }, - "system.organization.team.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager"] }] - } - }, - "system.organization.team.running": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager"] }] - } - }, - "system.organization.role.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.role.view"] }] - } - }, - "system.organization.role.system.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.role.view"] }] - } - }, - "system.organization.role.system.add": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_system_role"] }] - } - }, - "system.organization.role.system.edit": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_system_role"] }] - } - }, - "system.organization.role.system.delete": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_system_role"] }] - } - }, - "system.organization.role.team.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.role.view"] }] - } - }, - "system.organization.role.team.add": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_team_role"] }] - } - }, - "system.organization.role.team.edit": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_team_role"] }] - } - }, - "system.organization.role.team.delete": { - "granted": { - "anyOf": [{ "backend": ["system.organization.role.manager_team_role"] }] - } - }, - "system.api_market.service_classification.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.view"] }] - } - }, - "system.api_market.service_classification.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.manager"] }] - } - }, - "system.api_market.service_classification.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.manager"] }] - } - }, - "system.api_market.service_classification.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.manager"] }] - } - }, - "system.devops.system_setting.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.view"] }] - } - }, - "system.devops.system_setting.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.general.manager"] }] - } - }, - "system.analysis.run_view.view":{ - "granted": { - "anyOf": [{ "backend": ['system.analysis.run_view.view'] }] - } - }, - "system.settings.data_source.view":{ - "granted":{ - "anyOf":[{"backend":['system.settings.data_source.view']}] - } - }, - "system.devops.data_source.edit":{ - "granted":{ - "anyOf":[{"backend":['system.settings.data_source.manager']}] - } - }, - "system.settings.api_gateway.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.api_gateway.view"] }] - } - }, - "system.devops.cluster.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.api_gateway.manager"] }] - } - }, - "system.devops.cluster.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.api_gateway.manager"] }] - } - }, - "system.devops.cluster.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.api_gateway.manager"] }] - } - }, - "system.settings.ai_provider.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ai_provider.view"] }] - } - }, - "system.devops.ai_provider.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ai_provider.manager"] }] - } - }, - "system.settings.ssl_certificate.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ssl_certificate.view"] }] - } - }, - "system.devops.ssl_certificate.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ssl_certificate.manager"] }] - } - }, - "system.devops.ssl_certificate.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ssl_certificate.manager"] }] - } - }, - "system.devops.ssl_certificate.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.ssl_certificate.manager"] }] - } - }, - "system.settings.log_configuration.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.log_configuration.view"] }] - } - }, - "system.devops.log_configuration.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.log_configuration.manager"] }] - } - }, - "system.devops.log_configuration.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.log_configuration.manager"] }] - } - }, - "system.devops.log_configuration.publish": { - "granted": { - "anyOf": [{ "backend": ["system.settings.log_configuration.manager"] }] - } - }, - "system.devops.log_configuration.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.log_configuration.manager"] }] - } - }, - "system.devops.policy.view": { - "granted": { - "anyOf": [{ "backend": ["system.settings.strategy.view"] }] - } - }, - "system.devops.policy.add": { - "granted": { - "anyOf": [{ "backend": ["system.settings.strategy.manager"] }] - } - }, - "system.devops.policy.edit": { - "granted": { - "anyOf": [{ "backend": ["system.settings.strategy.manager"] }] - } - }, - "system.devops.policy.publish": { - "granted": { - "anyOf": [{ "backend": ["system.settings.strategy.manager"] }] - } - }, - "system.devops.policy.delete": { - "granted": { - "anyOf": [{ "backend": ["system.settings.strategy.manager"] }] - } - }, - "system.workspace.application.view_all": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.view_all"] }] - } - }, - "system.workspace.application.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all"] }] - } - }, - "system.workspace.service.view_all": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all"] }] - } - }, - "system.workspace.service.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all"] }] - } - }, - "system.api_portal.api_portal.view": { - "granted": { - "anyOf": [{ "backend": ["system.api_portal.api_portal.view"] }] - } - }, - "system.dashboard.dashboard.view": { - "granted": { - "anyOf": [{ "backend": [] }] - } - }, - "system.dashboard.systemrunning.view": { - "granted": { - "anyOf": [{ "backend": [] }] - } - }, - "team.service.api_doc.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.api_doc.view"] }] - } - }, - "team.service.api_doc.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api_doc.manager"] }] - } - }, - "team.service.api_doc.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api_doc.manager"] }] - } - }, - "team.service.service_intro.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.service_intro.view"] }] - } - }, - "team.service.service_intro.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.service_intro.manager"] }] - } - }, - "team.service.service_intro.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.service_intro.manager"] }] - } - }, - "team.service.api_doc.import": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api_doc.manager"] }] - } - }, - "team.service.router.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.api.view"] }] - } - }, - "team.service.router.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api.manager"] }] - } - }, - "team.service.router.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api.manager"] }] - } - }, - "team.service.router.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.api.manager"] }] - } - }, - "team.service.upstream.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.upstream.view"] }] - } - }, - "team.service.upstream.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.upstream.manager"] }] - } - }, - "team.service.upstream.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.upstream.manager"] }] - } - }, - "team.service.upstream.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.upstream.manager"] }] - } - }, - "team.service.release.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.release.view"] }] - } - }, - "team.service.release.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.online": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.stop": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.cancel": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.rollback": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.release.approval": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.release.manager"] }] - } - }, - "team.service.subscription.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.view_all","team.service.subscription.view"] }] - } - }, - "team.service.subscription.approval": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.subscription.manager"] }] - } - }, - "team.service.subscription.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.subscription.manager"] }] - } - }, - "team.service.subscription.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.service.subscription.manager"] }] - } - }, - "team.service.service.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","system.workspace.service.view_all","team.team.service.view"] }] - } - }, - "team.service.service.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.team.service.manager","team.service.service.manager"] }] - } - }, - "team.service.service.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.team.service.manager","team.service.service.manager"] }] - } - }, - "team.service.service.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.service.manager_all","team.team.service.manager","team.service.service.manager"] }] - } - }, - "team.service.policy.view": { - "granted": { - "anyOf": [{ "backend": ["team.service.strategy.view"] }] - } - }, - "team.service.policy.add": { - "granted": { - "anyOf": [{ "backend": ["team.service.strategy.manager"] }] - } - }, - "team.service.policy.edit": { - "granted": { - "anyOf": [{ "backend": ["team.service.strategy.manager"] }] - } - }, - "team.service.policy.publish": { - "granted": { - "anyOf": [{ "backend": ["team.service.strategy.manager"] }] - } - }, - "team.service.policy.delete": { - "granted": { - "anyOf": [{ "backend": ["team.service.strategy.manager"] }] - } - }, - "team.application.subscription.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.view_all","team.consumer.subscription.view_subscribed_service"] }] - } - }, - "team.application.subscription.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.subscribe"] }] - } - }, - "team.application.subscription.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.subscription.manager_subscribed_services"] }] - } - }, - "team.application.subscription.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.team.consumer.subscription.manager_subscribed_services"] }] - } - }, - "team.application.application.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.view_all","team.team.consumer.view"] }] - } - }, - "team.application.application.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }] - } - }, - "team.application.application.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }] - } - }, - "team.application.application.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all",'team.team.consumer.manager',"team.consumer.application.manager"] }] - } - }, - "team.consumer.authorization.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","system.workspace.application.view_all","team.consumer.authorization.view"] }] - } - }, - "team.application.authorization.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }] - } - }, - "team.application.authorization.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }] - } - }, - "team.application.authorization.delete": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }] - } - }, - "team.application.authorization.cancelSubApply": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }] - } - }, - "team.application.authorization.cancelSub": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.application.manager_all","team.consumer.authorization.manager"] }] - } - }, - "team.team.team.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.view_all","team.team.team.view"] }] - } - }, - "team.team.team.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager","team.team.team.manager"] }] - } - }, - "team.team.member.view": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.view_all","team.team.member.view"] }] - } - }, - "team.team.member.add": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager","team.team.member.manager"] }] - } - }, - "team.team.member.edit": { - "granted": { - "anyOf": [{ "backend": ["system.workspace.team.manager","team.team.member.manager"] }] - } - }, - "project.mySystem.topology.view": { - "granted": { - "anyOf": [{ "backend": ["project.subscribe_approval"] }] - } - }, - "project.mySystem.access.view": { - "granted": { - "anyOf": [{ "backend": ["project.permission_manager"] }] - } - }, - "project.mySystem.access.edit": { - "granted": { - "anyOf": [{ "backend": ["project.permission_manager"] }] - } - }, - "project.mySystem.access.delete": { - "granted": { - "anyOf": [{ "backend": ["project.permission_manager"] }] - } + { + 'system.settings.account.view': { + granted: { + anyOf: [{ backend: ['system.settings.account.view'] }] + } + }, + 'system.organization.member.add': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.edit': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.remove': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.delete': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.block': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.department.add': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.department.edit': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.organization.member.department.delete': { + granted: { + anyOf: [{ backend: ['system.settings.account.manager'] }] + } + }, + 'system.workspace.team.view_all': { + granted: { + anyOf: [{ backend: ['system.workspace.team.view_all'] }] + } + }, + 'system.organization.team.add': { + granted: { + anyOf: [{ backend: ['system.workspace.team.create'] }] + } + }, + 'system.organization.team.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager'] }] + } + }, + 'system.organization.team.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager'] }] + } + }, + 'system.organization.team.running': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager'] }] + } + }, + 'system.organization.role.view': { + granted: { + anyOf: [{ backend: ['system.settings.role.view'] }] + } + }, + 'system.organization.role.system.view': { + granted: { + anyOf: [{ backend: ['system.settings.role.view'] }] + } + }, + 'system.organization.role.system.add': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_system_role'] }] + } + }, + 'system.organization.role.system.edit': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_system_role'] }] + } + }, + 'system.organization.role.system.delete': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_system_role'] }] + } + }, + 'system.organization.role.team.view': { + granted: { + anyOf: [{ backend: ['system.settings.role.view'] }] + } + }, + 'system.organization.role.team.add': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_team_role'] }] + } + }, + 'system.organization.role.team.edit': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_team_role'] }] + } + }, + 'system.organization.role.team.delete': { + granted: { + anyOf: [{ backend: ['system.organization.role.manager_team_role'] }] + } + }, + 'system.api_market.service_classification.view': { + granted: { + anyOf: [{ backend: ['system.settings.general.view'] }] + } + }, + 'system.api_market.service_classification.add': { + granted: { + anyOf: [{ backend: ['system.settings.general.manager'] }] + } + }, + 'system.api_market.service_classification.edit': { + granted: { + anyOf: [{ backend: ['system.settings.general.manager'] }] + } + }, + 'system.api_market.service_classification.delete': { + granted: { + anyOf: [{ backend: ['system.settings.general.manager'] }] + } + }, + 'system.devops.system_setting.view': { + granted: { + anyOf: [{ backend: ['system.settings.general.view'] }] + } + }, + 'system.devops.system_setting.edit': { + granted: { + anyOf: [{ backend: ['system.settings.general.manager'] }] + } + }, + 'system.analysis.run_view.view': { + granted: { + anyOf: [{ backend: ['system.analysis.run_view.view'] }] + } + }, + 'system.settings.data_source.view': { + granted: { + anyOf: [{ backend: ['system.settings.data_source.view'] }] + } + }, + 'system.devops.data_source.edit': { + granted: { + anyOf: [{ backend: ['system.settings.data_source.manager'] }] + } + }, + 'system.settings.api_gateway.view': { + granted: { + anyOf: [{ backend: ['system.settings.api_gateway.view'] }] + } + }, + 'system.devops.cluster.add': { + granted: { + anyOf: [{ backend: ['system.settings.api_gateway.manager'] }] + } + }, + 'system.devops.cluster.edit': { + granted: { + anyOf: [{ backend: ['system.settings.api_gateway.manager'] }] + } + }, + 'system.devops.cluster.delete': { + granted: { + anyOf: [{ backend: ['system.settings.api_gateway.manager'] }] + } + }, + 'system.settings.ai_provider.view': { + granted: { + anyOf: [{ backend: ['system.settings.ai_provider.view'] }] + } + }, + 'system.devops.ai_provider.edit': { + granted: { + anyOf: [{ backend: ['system.settings.ai_provider.manager'] }] + } + }, + 'system.settings.ssl_certificate.view': { + granted: { + anyOf: [{ backend: ['system.settings.ssl_certificate.view'] }] + } + }, + 'system.devops.ssl_certificate.add': { + granted: { + anyOf: [{ backend: ['system.settings.ssl_certificate.manager'] }] + } + }, + 'system.devops.ssl_certificate.edit': { + granted: { + anyOf: [{ backend: ['system.settings.ssl_certificate.manager'] }] + } + }, + 'system.devops.ssl_certificate.delete': { + granted: { + anyOf: [{ backend: ['system.settings.ssl_certificate.manager'] }] + } + }, + 'system.settings.log_configuration.view': { + granted: { + anyOf: [{ backend: ['system.settings.log_configuration.view'] }] + } + }, + 'system.settings.log_configuration.add': { + granted: { + anyOf: [{ backend: ['system.settings.log_configuration.manager'] }] + } + }, + 'system.settings.log_configuration.edit': { + granted: { + anyOf: [{ backend: ['system.settings.log_configuration.manager'] }] + } + }, + 'system.settings.log_configuration.publish': { + granted: { + anyOf: [{ backend: ['system.settings.log_configuration.manager'] }] + } + }, + 'system.settings.log_configuration.delete': { + granted: { + anyOf: [{ backend: ['system.settings.log_configuration.manager'] }] + } + }, + 'system.devops.policy.view': { + granted: { + anyOf: [{ backend: ['system.settings.strategy.view'] }] + } + }, + 'system.devops.policy.add': { + granted: { + anyOf: [{ backend: ['system.settings.strategy.manager'] }] + } + }, + 'system.devops.policy.edit': { + granted: { + anyOf: [{ backend: ['system.settings.strategy.manager'] }] + } + }, + 'system.devops.policy.publish': { + granted: { + anyOf: [{ backend: ['system.settings.strategy.manager'] }] + } + }, + 'system.devops.policy.delete': { + granted: { + anyOf: [{ backend: ['system.settings.strategy.manager'] }] + } + }, + 'system.workspace.application.view_all': { + granted: { + anyOf: [{ backend: ['system.workspace.application.view_all'] }] + } + }, + 'system.workspace.application.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all'] }] + } + }, + 'system.workspace.service.view_all': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all'] }] + } + }, + 'system.workspace.service.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all'] }] + } + }, + 'system.api_portal.api_portal.view': { + granted: { + anyOf: [{ backend: ['system.api_portal.api_portal.view'] }] + } + }, + 'system.dashboard.dashboard.view': { + granted: { + anyOf: [{ backend: [] }] + } + }, + 'system.dashboard.systemrunning.view': { + granted: { + anyOf: [{ backend: [] }] + } + }, + 'team.service.api_doc.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.api_doc.view'] }] + } + }, + 'team.service.api_doc.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api_doc.manager'] }] + } + }, + 'team.service.api_doc.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api_doc.manager'] }] + } + }, + 'team.service.service_intro.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.service_intro.view'] }] + } + }, + 'team.service.service_intro.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.service_intro.manager'] }] + } + }, + 'team.service.service_intro.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.service_intro.manager'] }] + } + }, + 'team.service.api_doc.import': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api_doc.manager'] }] + } + }, + 'team.service.router.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.api.view'] }] + } + }, + 'team.service.router.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api.manager'] }] + } + }, + 'team.service.router.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api.manager'] }] + } + }, + 'team.service.router.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.api.manager'] }] + } + }, + 'team.service.upstream.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.upstream.view'] }] + } + }, + 'team.service.upstream.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.upstream.manager'] }] + } + }, + 'team.service.upstream.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.upstream.manager'] }] + } + }, + 'team.service.upstream.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.upstream.manager'] }] + } + }, + 'team.service.release.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.release.view'] }] + } + }, + 'team.service.release.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.online': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.stop': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.cancel': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.rollback': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.release.approval': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.release.manager'] }] + } + }, + 'team.service.subscription.view': { + granted: { + anyOf: [{ backend: ['system.workspace.service.view_all', 'team.service.subscription.view'] }] + } + }, + 'team.service.subscription.approval': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.subscription.manager'] }] + } + }, + 'team.service.subscription.add': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.subscription.manager'] }] + } + }, + 'team.service.subscription.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.service.manager_all', 'team.service.subscription.manager'] }] + } + }, + 'team.service.service.view': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.service.manager_all', + 'system.workspace.service.view_all', + 'team.team.service.view' + ] + } + ] + } + }, + 'team.service.service.add': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.service.manager_all', + 'team.team.service.manager', + 'team.service.service.manager' + ] + } + ] + } + }, + 'team.service.service.edit': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.service.manager_all', + 'team.team.service.manager', + 'team.service.service.manager' + ] + } + ] + } + }, + 'team.service.service.delete': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.service.manager_all', + 'team.team.service.manager', + 'team.service.service.manager' + ] + } + ] + } + }, + 'team.service.policy.view': { + granted: { + anyOf: [{ backend: ['team.service.strategy.view'] }] + } + }, + 'team.service.policy.add': { + granted: { + anyOf: [{ backend: ['team.service.strategy.manager'] }] + } + }, + 'team.service.policy.edit': { + granted: { + anyOf: [{ backend: ['team.service.strategy.manager'] }] + } + }, + 'team.service.policy.publish': { + granted: { + anyOf: [{ backend: ['team.service.strategy.manager'] }] + } + }, + 'team.service.policy.delete': { + granted: { + anyOf: [{ backend: ['team.service.strategy.manager'] }] + } + }, + 'team.application.subscription.view': { + granted: { + anyOf: [ + { backend: ['system.workspace.application.view_all', 'team.consumer.subscription.view_subscribed_service'] } + ] + } + }, + 'team.application.subscription.add': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.subscription.subscribe'] }] + } + }, + 'team.application.subscription.edit': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'team.consumer.subscription.manager_subscribed_services' + ] + } + ] + } + }, + 'team.application.subscription.delete': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'team.team.consumer.subscription.manager_subscribed_services' + ] + } + ] + } + }, + 'team.application.application.view': { + granted: { + anyOf: [{ backend: ['system.workspace.application.view_all', 'team.team.consumer.view'] }] + } + }, + 'team.application.application.add': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'team.team.consumer.manager', + 'team.consumer.application.manager' + ] + } + ] + } + }, + 'team.application.application.edit': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'team.team.consumer.manager', + 'team.consumer.application.manager' + ] + } + ] + } + }, + 'team.application.application.delete': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'team.team.consumer.manager', + 'team.consumer.application.manager' + ] + } + ] + } + }, + 'team.consumer.authorization.view': { + granted: { + anyOf: [ + { + backend: [ + 'system.workspace.application.manager_all', + 'system.workspace.application.view_all', + 'team.consumer.authorization.view' + ] + } + ] + } + }, + 'team.application.authorization.add': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }] + } + }, + 'team.application.authorization.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }] + } + }, + 'team.application.authorization.delete': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }] + } + }, + 'team.application.authorization.cancelSubApply': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }] + } + }, + 'team.application.authorization.cancelSub': { + granted: { + anyOf: [{ backend: ['system.workspace.application.manager_all', 'team.consumer.authorization.manager'] }] + } + }, + 'team.team.team.view': { + granted: { + anyOf: [{ backend: ['system.workspace.team.view_all', 'team.team.team.view'] }] + } + }, + 'team.team.team.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager', 'team.team.team.manager'] }] + } + }, + 'team.team.member.view': { + granted: { + anyOf: [{ backend: ['system.workspace.team.view_all', 'team.team.member.view'] }] + } + }, + 'team.team.member.add': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager', 'team.team.member.manager'] }] + } + }, + 'team.team.member.edit': { + granted: { + anyOf: [{ backend: ['system.workspace.team.manager', 'team.team.member.manager'] }] + } + }, + 'project.mySystem.topology.view': { + granted: { + anyOf: [{ backend: ['project.subscribe_approval'] }] + } + }, + 'project.mySystem.access.view': { + granted: { + anyOf: [{ backend: ['project.permission_manager'] }] + } + }, + 'project.mySystem.access.edit': { + granted: { + anyOf: [{ backend: ['project.permission_manager'] }] + } + }, + 'project.mySystem.access.delete': { + granted: { + anyOf: [{ backend: ['project.permission_manager'] }] + } + }, + 'system.settings.ai_key_resource.view': { + granted: { + anyOf: [{ backend: ['system.settings.ai_key_resource.view'] }] + } + }, + 'system.settings.ai_key_resource.manager': { + granted: { + anyOf: [{ backend: ['system.settings.ai_key_resource.manager'] }] + } + }, + + 'system.settings.ai_api.view': { + granted: { + anyOf: [{ backend: ['system.settings.ai_api.view'] }] } } - ]; \ No newline at end of file + } +] diff --git a/frontend/packages/common/src/const/policy/consts.tsx b/frontend/packages/common/src/const/policy/consts.tsx index eb898388..3accaeb8 100644 --- a/frontend/packages/common/src/const/policy/consts.tsx +++ b/frontend/packages/common/src/const/policy/consts.tsx @@ -1,88 +1,76 @@ -import { codeBoxLanguagesType } from "@common/components/postcat/api/Codebox"; +import { codeBoxLanguagesType } from '@common/components/postcat/api/Codebox' export const MatchRules = [ - { value: 'inner', label: '数据格式' }, - { value: 'keyword', label: '关键字' }, - { value: 'regex', label: '正则表达式' }, - { value: 'json_path', label: 'JSON Path' } - ]; - - - export const DataFormatOptions = [ - { label: '姓名', value: 'name' }, - { label: '手机号', value: 'phone' }, - { label: '身份证号', value: 'id-card' }, - { label: '银行卡号', value: 'bank-card' }, - { label: '日期', value: 'date' }, - { label: '金额', value: 'amount' } - ]; - - - export const DataMaskBaseOptionOptions = [ - { value: 'partial-display', label: '局部显示' }, - { value: 'partial-masking', label: '局部遮蔽' }, - { value: 'truncation', label: '截取' }, - { value: 'replacement', label: '替换' }, - ]; - - - export const DataMaskOrderOptions = [ - ...DataMaskBaseOptionOptions, - { label: '乱序', value: 'shuffling' } - ] - - - export const DataMaskReplaceStrOptions = [ - { value: 'random', label: '随机字符串' }, - { value: 'custom', label: '自定义字符串' } - ]; - - -export const PolicyOptions = [ - {label:'数据脱敏',value:'data-masking'}, + { value: 'inner', label: '数据格式' }, + { value: 'keyword', label: '关键字' }, + { value: 'regex', label: '正则表达式' }, + { value: 'json_path', label: 'JSON Path' } ] -export const StrategyStatusEnum = { - 'update':'待更新', - 'online':'已发布', - 'offline':'未发布', - "delete":'待删除', - } - - export const StrategyStatusColorClass = { - "online":'text-status_success', - "update":'text-status_pending', - "offline":'text-status_fail', - "delete":'text-status_offline', - } +export const DataFormatOptions = [ + { label: '姓名', value: 'name' }, + { label: '手机号', value: 'phone' }, + { label: '身份证号', value: 'id-card' }, + { label: '银行卡号', value: 'bank-card' }, + { label: '日期', value: 'date' }, + { label: '金额', value: 'amount' } +] - export const contentTypeToLanguageMap: Record = { - // JSON - "application/json": "json", - - // XML - "application/xml": "xml", - "text/xml": "xml", - - // HTML - "text/html": "html", - - // Plain text - "text/plain": "plaintext", - - // JavaScript - "application/javascript": "javascript", - "text/javascript": "javascript", - - // CSS - "text/css": "css", - - // YAML - "application/x-yaml": "yaml", - "text/yaml": "yaml", - - // Others (fallback) - "*/*": "plaintext", // 任意类型默认处理为普通文本 - }; - - \ No newline at end of file +export const DataMaskBaseOptionOptions = [ + { value: 'partial-display', label: '局部显示' }, + { value: 'partial-masking', label: '局部遮蔽' }, + { value: 'truncation', label: '截取' }, + { value: 'replacement', label: '替换' } +] + +export const DataMaskOrderOptions = [...DataMaskBaseOptionOptions, { label: '乱序', value: 'shuffling' }] + +export const DataMaskReplaceStrOptions = [ + { value: 'random', label: '随机字符串' }, + { value: 'custom', label: '自定义字符串' } +] + +export const PolicyOptions = [{ label: '数据脱敏', value: 'data-masking' }] + +export const StrategyStatusEnum = { + update: '待更新', + online: '已发布', + offline: '未发布', + delete: '待删除' +} + +export const StrategyStatusColorClass = { + online: 'text-status_success', + update: 'text-status_pending', + offline: 'text-status_fail', + delete: 'text-status_offline' +} + +export const contentTypeToLanguageMap: Record = { + // JSON + 'application/json': 'json', + + // XML + 'application/xml': 'xml', + 'text/xml': 'xml', + + // HTML + 'text/html': 'html', + + // Plain text + 'text/plain': 'plaintext', + + // JavaScript + 'application/javascript': 'javascript', + 'text/javascript': 'javascript', + + // CSS + 'text/css': 'css', + + // YAML + 'application/x-yaml': 'yaml', + 'text/yaml': 'yaml', + + // Others (fallback) + '*/*': 'plaintext' // 任意类型默认处理为普通文本 +} diff --git a/frontend/packages/common/src/const/policy/type.ts b/frontend/packages/common/src/const/policy/type.ts index 492cd7cf..fa0f15f2 100644 --- a/frontend/packages/common/src/const/policy/type.ts +++ b/frontend/packages/common/src/const/policy/type.ts @@ -1,145 +1,141 @@ -import { DefaultOptionType } from "antd/es/select"; -import { StrategyStatusEnum } from "./consts"; -import { EntityItem } from "../type"; +import { DefaultOptionType } from 'antd/es/select' +import { StrategyStatusEnum } from './consts' +import { EntityItem } from '../type' export type DataMaskRuleTableProps = { - disabled?: boolean; - value?: MaskRuleData[]; - onChange?: (value: MaskRuleData[]) => void; - } - - -export type MaskRuleData = { - match: { - type: string; - value: string; - }; - mask: { - type: string; - begin?: number; - length?: number; - replace?: { - type: string; - value: string; - }; - }; - eoKey?: string; - } - - export type DataMaskRuleFormProps = { - editData?: MaskRuleData; - ruleList: MaskRuleData[]; - onSave: (ruleList: MaskRuleData[]) => void; - onClose: () => void; - modalVisible:boolean - } - - -export type DataMaskingConfigHandle = { - save: (values: any) => void + disabled?: boolean + value?: MaskRuleData[] + onChange?: (value: MaskRuleData[]) => void } -export type PolicyMatchType = {name:string, values:string[], label?:string, title?:string, type?:string} +export type MaskRuleData = { + match: { + type: string + value: string + } + mask: { + type: string + begin?: number + length?: number + replace?: { + type: string + value: string + } + } + eoKey?: string +} + +export type DataMaskRuleFormProps = { + editData?: MaskRuleData + ruleList: MaskRuleData[] + onSave: (ruleList: MaskRuleData[]) => void + onClose: () => void + modalVisible: boolean +} + +export type DataMaskingConfigHandle = { + save: (values: any) => void +} + +export type PolicyMatchType = { name: string; values: string[]; label?: string; title?: string; type?: string } export type DataMaskingRulesType = {} export type DataMaskingConfigFieldType = { - id:string - name:string - priority:number - description:string - filters:PolicyMatchType[] - config:{ - rules:DataMaskingRulesType - } + id: string + name: string + priority: number + description: string + filters: PolicyMatchType[] + config: { + rules: DataMaskingRulesType + } } export type DataMaskStrategyItem = { - id:string - name:string - priority:number - isStop:boolean - isDelete:boolean - publishStatus:keyof typeof StrategyStatusEnum - filters:string - conf:string - updater:EntityItem - updateTime:string - } + id: string + name: string + priority: number + isStop: boolean + isDelete: boolean + publishStatus: keyof typeof StrategyStatusEnum + filters: string + conf: string + updater: EntityItem + updateTime: string +} export type DataMaskLogItem = { - id:string - service: { - id:string - name:string - } - method:string - url:string - remote_ip:string - consumer: { - id:string - name:string - } - authorization:string - record_time:string + id: string + service: { + id: string + name: string } + method: string + url: string + remote_ip: string + consumer: { + id: string + name: string + } + authorization: string + record_time: string +} + +export type FilterFormField = { + name: string + values: string[] | string + label: string + title: string +} + +export type FilterOptionType = { + name: string + pattern: string + title: string + type: 'remote' | 'pattern' | 'static' + options: string[] +} + +export type FilterTableProps = { + disabled?: boolean + drawerTitle?: string + value?: FilterFormField[] + onChange?: (val: FilterFormField[]) => void +} - -export type FilterFormField= { - name: string; - values:string[] |string; - label:string - title:string - } - - export type FilterOptionType = { - name:string - pattern:string - title:string - type:'remote'|'pattern'|'static' - options:string[] - } - - - export type FilterTableProps = { - disabled?: boolean; - drawerTitle?: string; - value?:FilterFormField[]; - onChange?:(val:FilterFormField[])=>void - } - export type FilterFormType = { - name:string - values:unknown - type?:string - } - - export type FilterFormProps = { - filterForm: FilterFormType; - filterOptions:DefaultOptionType[]; - selectedOptionNameSet: Set; - disabled: boolean; - onFilterFormChange: (form: FilterFormType) => void; - setFormCanSubmit:(canSubmit:boolean)=>void - serviceId?:string - teamId?:string - } - - export type FilterFormHandle = { - clear:()=>void - save:()=>Promise - } - - export type FilterFormItemProps = { - value?: string[]; - onChange?: (value: string[]) => void; - disabled:boolean - option:unknown - onShowValueChange?:(value:string)=>void - serviceId?:string - teamId?:string - } - - export type RemoteTitleType = { - title:string - field:string - } \ No newline at end of file + name: string + values: unknown + type?: string +} + +export type FilterFormProps = { + filterForm: FilterFormType + filterOptions: DefaultOptionType[] + selectedOptionNameSet: Set + disabled: boolean + onFilterFormChange: (form: FilterFormType) => void + setFormCanSubmit: (canSubmit: boolean) => void + serviceId?: string + teamId?: string +} + +export type FilterFormHandle = { + clear: () => void + save: () => Promise +} + +export type FilterFormItemProps = { + value?: string[] + onChange?: (value: string[]) => void + disabled: boolean + option: unknown + onShowValueChange?: (value: string) => void + serviceId?: string + teamId?: string +} + +export type RemoteTitleType = { + title: string + field: string +} diff --git a/frontend/packages/common/src/const/type.ts b/frontend/packages/common/src/const/type.ts index e0cfe1bf..96f29cb5 100644 --- a/frontend/packages/common/src/const/type.ts +++ b/frontend/packages/common/src/const/type.ts @@ -1,214 +1,204 @@ -import { FC, ReactElement, ReactNode } from "react" -import { PERMISSION_DEFINITION } from "./permissions" -import { MatchPositionEnum, MatchTypeEnum } from "@core/const/system/const" -import usePluginLoader from "@common/hooks/pluginLoader" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import { StrategyStatusEnum } from "./policy/consts" +import { FC, ReactElement, ReactNode } from 'react' +import { PERMISSION_DEFINITION } from './permissions' +import { MatchPositionEnum, MatchTypeEnum } from '@core/const/system/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { StrategyStatusEnum } from './policy/consts' export type UserInfoType = { - username: string - nickname: string - email: string - phone: string - avatar: string - type:string + username: string + nickname: string + email: string + phone: string + avatar: string + type: string } export type UserProfileProps = { - entity?:UserInfoType + entity?: UserInfoType } export type UserProfileHandle = { - save:()=>Promise + save: () => Promise } export type ClusterSimpleOption = { - id:string - name:string - description:string + id: string + name: string + description: string } - export type ClusterEnumData = { - name:string, - uuid:string, - title:string + name: string + uuid: string + title: string } -export interface ClusterEnum{ - clusters:Array - name:string +export interface ClusterEnum { + clusters: Array + name: string } export type TeamSimpleMemberItem = { - user:EntityItem - mail:string - department:EntityItem + user: EntityItem + mail: string + department: EntityItem } export type MemberItem = { - id:string; - name:string; - email:string; - department:Array<{id:string,name:string}> + id: string + name: string + email: string + department: Array<{ id: string; name: string }> } export type DashboardPartitionItem = { - id:string; - name:string - enableMonitor:boolean + id: string + name: string + enableMonitor: boolean } - export type SimpleTeamItem = { - id:string - name:string - description:string - appNum:number + id: string + name: string + description: string + appNum: number } export type MatchItem = { - position:typeof MatchPositionEnum - matchType:typeof MatchTypeEnum - key:string - pattern:string - id?:string + position: typeof MatchPositionEnum + matchType: typeof MatchTypeEnum + key: string + pattern: string + id?: string } export type EntityItem = { - id:string - name:string + id: string + name: string } export type DynamicMenuItem = { - name:string - title:string - path:string + name: string + title: string + path: string } -export type AccessDataType = keyof typeof PERMISSION_DEFINITION[0] - - +export type AccessDataType = keyof (typeof PERMISSION_DEFINITION)[0] export type NewSimpleMemberItem = { - user:EntityItem - email:string - department:string - avatar:string + user: EntityItem + email: string + department: string + avatar: string } export type SimpleMemberItem = { - id:string - name:string - email:string - department:string - avatar:string + id: string + name: string + email: string + department: string + avatar: string } - export type RouteConfig = { - path:string - pathPrefix?:string - component?:ReactElement - children?:(RouteConfig|false)[] - key:string - provider?:FC<{ children: ReactNode; }> - lazy?:unknown - data?:Record - lifecycle?:{ - canActivate?:()=>Promise - canLoad?:()=>Promise - canDeactivate?:()=>Promise - deactivated?:()=>Promise - } + path: string + pathPrefix?: string + component?: ReactElement + children?: (RouteConfig | false)[] + key: string + provider?: FC<{ children: ReactNode }> + lazy?: unknown + data?: Record + lifecycle?: { + canActivate?: () => Promise + canLoad?: () => Promise + canDeactivate?: () => Promise + deactivated?: () => Promise + } } -export type RouterParams = { - teamId:string - apiId:string - serviceId:string - clusterId:string; - memberGroupId:string - userGroupId:string - pluginName:string - moduleId:string - accessType:'project'|'team'|'service' - categoryId:string - tagId:string - dashboardType:string - dashboardDetailId:string - topologyId:string - appId:string - roleType:string - roleId:string - routeId:string - policyId:string +export type RouterParams = { + teamId: string + apiId: string + serviceId: string + clusterId: string + memberGroupId: string + userGroupId: string + pluginName: string + moduleId: string + accessType: 'project' | 'team' | 'service' + categoryId: string + tagId: string + dashboardType: string + dashboardDetailId: string + topologyId: string + appId: string + roleType: string + roleId: string + routeId: string + policyId: string } - export type PluginRouterConfig = { - name:string - path:string; - type:string; - expose?:string - } + name: string + path: string + type: string + expose?: string +} export type CoreObj = { -routerConfig: RouteConfig[]; -setExecuteList: (param:unknown[])=>void; -pluginLoader: { - loadModule: (path: string, name: string, expose: string, pluginPath: string) => Promise; -}; -pluginProvider: ReturnType -// pluginLifecycleGuard: PluginLifecycleGuard; -builtInPluginLoader: (name: string) => any; + routerConfig: RouteConfig[] + setExecuteList: (param: unknown[]) => void + pluginLoader: { + loadModule: (path: string, name: string, expose: string, pluginPath: string) => Promise + } + pluginProvider: ReturnType + // pluginLifecycleGuard: PluginLifecycleGuard; + builtInPluginLoader: (name: string) => any } export type PluginConfigType = { - name: string; - router: Array; - path?: string; - driver:string - } - - -export type ApiparkPluginDriverType = { - [key:string]:{[key:string]:(coreObj?:CoreObj, pluginConfig?:PluginConfigType)=>(CoreObj|undefined)} - } - - -export type RouterMapConfig = { - type: 'component' | 'module', - component: ReactElement, - provider?: FC, - lazy?: FC - key?: string - children?: RouteConfig[] - data?:Record - pathMatch?:string + name: string + router: Array + path?: string + driver: string +} + +export type ApiparkPluginDriverType = { + [key: string]: { [key: string]: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => CoreObj | undefined } +} + +export type RouterMapConfig = { + type: 'component' | 'module' + component: ReactElement + provider?: FC + lazy?: FC + key?: string + children?: RouteConfig[] + data?: Record + pathMatch?: string } - export type PolichPublishItemType = { - name:string - priority:number - status:keyof typeof StrategyStatusEnum - optTime:string + name: string + priority: number + status: keyof typeof StrategyStatusEnum + optTime: string } // 发布详情(版本) export type PolicyPublishInfoType = { - source:string - strategies:Array - isPublish:boolean - versionName:string - unpublishMsg:string -}; + source: string + strategies: Array + isPublish: boolean + versionName: string + unpublishMsg: string +} export type PolicyPublishModalProps = { - data:PolicyPublishInfoType + data: PolicyPublishInfoType } export type PolicyPublishModalHandle = { - publish:()=>Promise> -} \ No newline at end of file + publish: () => Promise> +} diff --git a/frontend/packages/common/src/contexts/BreadcrumbContext.tsx b/frontend/packages/common/src/contexts/BreadcrumbContext.tsx index 8cf12b58..e41a4933 100644 --- a/frontend/packages/common/src/contexts/BreadcrumbContext.tsx +++ b/frontend/packages/common/src/contexts/BreadcrumbContext.tsx @@ -1,26 +1,22 @@ -import {createContext, useContext, useState} from "react"; -import {BreadcrumbItemType} from "antd/es/breadcrumb/Breadcrumb"; +import { createContext, useContext, useState } from 'react' +import { BreadcrumbItemType } from 'antd/es/breadcrumb/Breadcrumb' interface BreadcrumbContextType { - breadcrumb: BreadcrumbItemType[]; - setBreadcrumb: (newItems: BreadcrumbItemType[]) => void; + breadcrumb: BreadcrumbItemType[] + setBreadcrumb: (newItems: BreadcrumbItemType[]) => void } -const BreadcrumbContext = createContext(undefined); +const BreadcrumbContext = createContext(undefined) export const useBreadcrumb = () => { - const context = useContext(BreadcrumbContext); - if (!context) { - throw new Error('useBreadcrumb must be used within a BreadcrumbProvider'); - } - return context; -}; -export const BreadcrumbProvider = ({children}:unknown) =>{ - const [breadcrumb,setBreadcrumb] = useState([]) + const context = useContext(BreadcrumbContext) + if (!context) { + throw new Error('useBreadcrumb must be used within a BreadcrumbProvider') + } + return context +} +export const BreadcrumbProvider = ({ children }: unknown) => { + const [breadcrumb, setBreadcrumb] = useState([]) - return ( - - {children} - - ) -} \ No newline at end of file + return {children} +} diff --git a/frontend/packages/common/src/contexts/EventEmitterContext.tsx b/frontend/packages/common/src/contexts/EventEmitterContext.tsx index d31e32e8..c182e674 100644 --- a/frontend/packages/common/src/contexts/EventEmitterContext.tsx +++ b/frontend/packages/common/src/contexts/EventEmitterContext.tsx @@ -5,7 +5,7 @@ import { useEventEmitter } from 'ahooks' import type { EventEmitter } from 'ahooks/lib/useEventEmitter' const EventEmitterContext = createContext<{ eventEmitter: EventEmitter | null }>({ - eventEmitter: null, + eventEmitter: null }) export const useEventEmitterContextContext = () => useContext(EventEmitterContext) @@ -13,16 +13,10 @@ export const useEventEmitterContextContext = () => useContext(EventEmitterContex type EventEmitterContextProviderProps = { children: React.ReactNode } -export const EventEmitterContextProvider = ({ - children, -}: EventEmitterContextProviderProps) => { +export const EventEmitterContextProvider = ({ children }: EventEmitterContextProviderProps) => { const eventEmitter = useEventEmitter() - return ( - - {children} - - ) + return {children} } export default EventEmitterContext diff --git a/frontend/packages/common/src/contexts/GlobalStateContext.tsx b/frontend/packages/common/src/contexts/GlobalStateContext.tsx index 1c20f38c..da730fa6 100644 --- a/frontend/packages/common/src/contexts/GlobalStateContext.tsx +++ b/frontend/packages/common/src/contexts/GlobalStateContext.tsx @@ -1,487 +1,539 @@ -import {createContext, Dispatch, FC, ReactNode, useContext, useEffect, useReducer, useState} from "react"; -import { useFetch } from "@common/hooks/http"; -import { App } from "antd"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { checkAccess } from "@common/utils/permission"; -import { PERMISSION_DEFINITION } from "@common/const/permissions"; -import { $t } from "@common/locales"; -import { MenuItem } from "@common/utils/navigation"; -import { ErrorBoundary } from "@ant-design/pro-components"; -import NotFound from "@common/components/aoplatform/NotFound"; -import { RouteConfig } from "@common/const/type"; -import { ProtectedRoute } from "@core/components/aoplatform/RenderRoutes"; -import Login from "@core/pages/Login"; -import { useLocaleContext } from "./LocaleContext"; -import Root from "@core/pages/Root" -import DataMaskingCompare from "@core/pages/policy/dataMasking/DataMaskingCompare"; +import { ErrorBoundary } from '@ant-design/pro-components' +import NotFound from '@common/components/aoplatform/NotFound' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { RouteConfig } from '@common/const/type' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { MenuItem } from '@common/utils/navigation' +import { checkAccess } from '@common/utils/permission' +import { ProtectedRoute } from '@core/components/aoplatform/RenderRoutes' +import Login from '@core/pages/Login' +import Root from '@core/pages/Root' +import Playground from '@core/pages/playground' +import DataMaskingCompare from '@core/pages/policy/dataMasking/DataMaskingCompare' +import { App } from 'antd' +import { createContext, Dispatch, FC, ReactNode, useContext, useEffect, useReducer, useState } from 'react' +import { useLocaleContext } from './LocaleContext' interface GlobalState { - isAuthenticated: boolean; - userData: UserData | null; - version: string; - updateDate: string; - powered:string; - mainPage:string; - language:string; - pluginsLoaded:boolean + isAuthenticated: boolean + userData: UserData | null + version: string + updateDate: string + powered: string + mainPage: string + language: string + pluginsLoaded: boolean } interface UserData { - username: string; + username: string } export type GlobalAction = - | { type: 'LOGIN'} - | { type: 'LOGOUT' } - | { type: 'UPDATE_USERDATA'; userData: UserData } - | { type: 'UPDATE_VERSION'; version: string } - | { type: 'UPDATE_DATE'; updateDate: string } - | { type: 'UPDATE_POWER'; powered: string } - | { type: 'UPDATE_MAIN_PAGE'; mainPage: string } - | { type: 'UPDATE_LANGUAGE'; language: string } - | { type: 'SET_PLUGINS_LOADED'; pluginsLoaded: boolean } + | { type: 'LOGIN' } + | { type: 'LOGOUT' } + | { type: 'UPDATE_USERDATA'; userData: UserData } + | { type: 'UPDATE_VERSION'; version: string } + | { type: 'UPDATE_DATE'; updateDate: string } + | { type: 'UPDATE_POWER'; powered: string } + | { type: 'UPDATE_MAIN_PAGE'; mainPage: string } + | { type: 'UPDATE_LANGUAGE'; language: string } + | { type: 'SET_PLUGINS_LOADED'; pluginsLoaded: boolean } +const mockData = [ + { + name: '工作空间', + key: 'workspace', + path: '/guide', + icon: 'ic:baseline-space-dashboard', + children: [ + { + name: '首页', + key: 'guide', + path: '/guide', + icon: 'ic:baseline-home', + access: 'all' + }, + { + name: '服务', + key: 'service', + path: '/service', + icon: 'ic:baseline-blinds-closed', + access: 'all' + }, + { + name: '消费者', + key: 'consumer', + path: '/consumer', + icon: 'ic:baseline-apps', + access: 'all' + }, + { + name: '团队', + key: 'team', + path: '/team', + icon: 'ic:baseline-people-alt', + access: 'all' + } + // { + // "name": "路由组件", + // "key": "router", + // "path": "/router1", + // "icon": "ic:baseline-people-alt", + // "access": "all" + // } + ] + }, + { + name: 'API 市场', + key: 'serviceHub', + path: '/serviceHub', + icon: 'ic:baseline-hub', + access: 'system.api_portal.api_portal.view' + }, + { + name: '仪表盘', + key: 'analytics', + path: '/analytics', + icon: 'ic:baseline-bar-chart', + children: [ + { + name: '运行视图', + key: 'analytics', + path: '/analytics', + icon: 'ic:baseline-bar-chart', + access: 'system.analysis.run_view.view' + } + ], + access: 'system.analysis.run_view.view' + }, + { + name: '系统设置', + key: 'operationCenter', + path: '/commonsetting', + icon: 'ic:baseline-settings', + children: [ + { + name: '系统', + key: 'serviceHubSetting', + path: '/commonsetting', + children: [ + { + name: '常规', + key: 'commonsetting', + path: '/commonsetting', + icon: 'ic:baseline-hub', + access: 'system.api_market.service_classification.view' + }, + { + name: 'API 网关', + key: 'cluster', + path: '/cluster', + icon: 'ic:baseline-device-hub', + access: 'system.settings.api_gateway.view' + } + ] + }, + { + name: 'AI', + key: 'aiSettings', + path: '/aisetting', + children: [ + { + name: 'AI 模型', + key: 'aiProviders', + path: '/aisetting', + icon: 'hugeicons:ai-network', + access: 'system.settings.ai_provider.view' + }, + { + name: 'APIKey 资源池', + key: 'aiKeys', + path: '/keysetting', + icon: 'ic:baseline-key', + access: 'system.settings.ai_key_resource.view' + }, + { + name: 'AI API', + key: 'aiApiList', + path: '/aiApis', + icon: 'ic:baseline-api', + access: 'system.settings.ai_api.view' + } + ] + }, + { + name: '用户', + key: 'organization', + path: '/member', + children: [ + { + name: '账号', + key: 'member', + path: '/member', + icon: 'ic:baseline-people-alt', + access: 'system.settings.account.view' + }, + { + name: '角色', + key: 'role', + path: '/role', + icon: 'ic:baseline-verified-user', + access: 'system.organization.role.view' + } + ] + }, + { + name: '集成', + key: 'maintenanceCenter', + path: '/datasourcing', + children: [ + { + name: '数据源', + key: 'datasourcing', + path: '/datasourcing', + icon: 'ic:baseline-monitor-heart', + access: 'system.settings.data_source.view' + }, + { + name: '全局策略', + key: 'globalpolicy', + path: '/globalpolicy', + icon: 'icon-park-solid:exchange-three', + access: 'system.settings.data_source.view' + }, + { + name: '证书', + key: 'cert', + path: '/cert', + icon: 'ic:baseline-security', + access: 'system.settings.ssl_certificate.view' + }, + { + name: '日志', + key: 'logsettings', + path: '/logsettings', + icon: 'ic:baseline-sticky-note-2', + access: 'system.settings.log_configuration.view' + } + ] + } + ] + } +] - const mockData = [ - { - "name": "工作空间", - "key": "workspace", - "path": "/guide", - "icon": "ic:baseline-space-dashboard", - "children": [ - { - "name": "首页", - "key": "guide", - "path": "/guide", - "icon": "ic:baseline-home", - "access": "all" - }, - { - "name": "服务", - "key": "service", - "path": "/service", - "icon": "ic:baseline-blinds-closed", - "access": "all" - }, - { - "name": "消费者", - "key": "consumer", - "path": "/consumer", - "icon": "ic:baseline-apps", - "access": "all" - }, - { - "name": "团队", - "key": "team", - "path": "/team", - "icon": "ic:baseline-people-alt", - "access": "all" - }, - // { - // "name": "路由组件", - // "key": "router", - // "path": "/router1", - // "icon": "ic:baseline-people-alt", - // "access": "all" - // } - ] - }, - { - "name": "API 市场", - "key": "serviceHub", - "path": "/serviceHub", - "icon": "ic:baseline-hub", - "access": "system.api_portal.api_portal.view" - }, - { - "name": "仪表盘", - "key": "analytics", - "path": "/analytics", - "icon": "ic:baseline-bar-chart", - "children": [ - { - "name": "运行视图", - "key": "analytics", - "path": "/analytics", - "icon": "ic:baseline-bar-chart", - "access": "system.analysis.run_view.view" - } - ], - "access": "system.analysis.run_view.view" - }, - { - "name": "系统设置", - "key": "operationCenter", - "path": "/commonsetting", - "icon": "ic:baseline-settings", - "children": [ - { - "name": "系统", - "key": "serviceHubSetting", - "path": "/commonsetting", - "children": [ - { - "name": "常规", - "key": "commonsetting", - "path": "/commonsetting", - "icon": "ic:baseline-hub", - "access": "system.api_market.service_classification.view" - }, - { - "name": "API 网关", - "key": "cluster", - "path": "/cluster", - "icon": "ic:baseline-device-hub", - "access": "system.settings.api_gateway.view" - }, - { - "name": "AI 模型", - "key": "aisetting", - "path": "/aisetting", - "icon": "hugeicons:ai-network", - "access": "system.settings.ai_provider.view" - } - ], - }, - { - "name": "用户", - "key": "organization", - "path": "/member", - "children": [ - { - "name": "账号", - "key": "member", - "path": "/member", - "icon": "ic:baseline-people-alt", - "access": "system.settings.account.view" - }, - { - "name": "角色", - "key": "role", - "path": "/role", - "icon": "ic:baseline-verified-user", - "access": "system.organization.role.view" - } - ] - }, - { - "name": "集成", - "key": "maintenanceCenter", - "path": "/datasourcing", - "children": [ - { - "name": "数据源", - "key": "datasourcing", - "path": "/datasourcing", - "icon": "ic:baseline-monitor-heart", - "access": "system.settings.data_source.view" - }, - { - "name": "全局策略", - "key": "globalpolicy", - "path": "/globalpolicy", - "icon": "icon-park-solid:exchange-three", - "access": "system.settings.data_source.view" - }, - { - "name": "证书", - "key": "cert", - "path": "/cert", - "icon": "ic:baseline-security", - "access": "system.settings.ssl_certificate.view" - }, - { - "name": "日志", - "key": "logsettings", - "path": "/logsettings", - "icon": "ic:baseline-sticky-note-2", - "access": "system.settings.log_configuration.view" - }, - ] - } - ] - } - ] - - /* 存储用户登录、信息、权限等数据 */ -export const GlobalContext = createContext<{ - state: GlobalState; - dispatch: Dispatch; - accessData:Map; - pluginAccessDictionary:{[k:string]:string}; - menuList:MenuItem[]; - getGlobalAccessData:()=>Promise<{ access:string[]}>; - getTeamAccessData:(teamId:string)=>void; - getPluginAccessDictionary:(pluginData:{[k:string]:string})=>void - getMenuList:()=>void - resetAccess:()=>void - cleanTeamAccessData:()=>void - checkPermission:(access:keyof typeof PERMISSION_DEFINITION[0] | Array)=>boolean - teamDataFlushed:boolean - accessInit:boolean - aiConfigFlushed:boolean - setAiConfigFlushed:(flush:boolean)=>void - routeConfig: RouteConfig[]; - setRouterConfig: (isRoot: boolean, config: RouteConfig) => void; - addRouteConfig: (parentRoute: RouteConfig, config: RouteConfig) => void; - fetchData: ReturnType['fetchData']; - $t: typeof $t; -} | undefined>(undefined); +export const GlobalContext = createContext< + | { + state: GlobalState + dispatch: Dispatch + accessData: Map + pluginAccessDictionary: { [k: string]: string } + menuList: MenuItem[] + getGlobalAccessData: () => Promise<{ access: string[] }> + getTeamAccessData: (teamId: string) => void + getPluginAccessDictionary: (pluginData: { [k: string]: string }) => void + getMenuList: () => void + resetAccess: () => void + cleanTeamAccessData: () => void + checkPermission: ( + access: keyof (typeof PERMISSION_DEFINITION)[0] | Array + ) => boolean + teamDataFlushed: boolean + accessInit: boolean + aiConfigFlushed: boolean + setAiConfigFlushed: (flush: boolean) => void + routeConfig: RouteConfig[] + setRouterConfig: (isRoot: boolean, config: RouteConfig) => void + addRouteConfig: (parentRoute: RouteConfig, config: RouteConfig) => void + fetchData: ReturnType['fetchData'] + $t: typeof $t + } + | undefined +>(undefined) const globalReducer = (state: GlobalState, action: GlobalAction): GlobalState => { - switch (action.type) { - case 'LOGIN': - return { - ...state, - isAuthenticated: true, - }; - case 'LOGOUT': - return { - ...state, - isAuthenticated: false, - userData: null, - } - case 'UPDATE_USERDATA': - return { - ...state, - userData: action.userData, - }; - case 'UPDATE_VERSION': - return { - ...state, - version: action.version, - }; - case 'UPDATE_DATE': - return { - ...state, - updateDate: action.updateDate, - }; - case 'UPDATE_POWER': - return { - ...state, - powered: action.powered, - }; - case 'UPDATE_MAIN_PAGE': - return { - ...state, - mainPage: action.mainPage, - }; - case 'UPDATE_LANGUAGE': - return { - ...state, - language: action.language, - }; - case 'SET_PLUGINS_LOADED': - return { - ...state, - pluginsLoaded: action.pluginsLoaded, - }; - default: - return state; - } -}; - + switch (action.type) { + case 'LOGIN': + return { + ...state, + isAuthenticated: true + } + case 'LOGOUT': + return { + ...state, + isAuthenticated: false, + userData: null + } + case 'UPDATE_USERDATA': + return { + ...state, + userData: action.userData + } + case 'UPDATE_VERSION': + return { + ...state, + version: action.version + } + case 'UPDATE_DATE': + return { + ...state, + updateDate: action.updateDate + } + case 'UPDATE_POWER': + return { + ...state, + powered: action.powered + } + case 'UPDATE_MAIN_PAGE': + return { + ...state, + mainPage: action.mainPage + } + case 'UPDATE_LANGUAGE': + return { + ...state, + language: action.language + } + case 'SET_PLUGINS_LOADED': + return { + ...state, + pluginsLoaded: action.pluginsLoaded + } + default: + return state + } +} export const DefaultRouteConfig = [ - { path: '/', pathMatch: 'full', component: ,key:'root',}, - { path: '/login', component: ,key:'login'}, - { path: '/dataMaskCompare/:logId/:serviceId?/:teamId?', component: ,key:'dataMaskCompare'}, - { path: '/', pathMatch:'prefix',component: ,key:'basciLayout',children:[ - { path: '*', component: , key: 'errorBoundary' } - ]} + { path: '/', pathMatch: 'full', component: , key: 'root' }, + { path: '/login', component: , key: 'login' }, + { path: '/dataMaskCompare/:logId/:serviceId?/:teamId?', component: , key: 'dataMaskCompare' }, + { + path: '/', + pathMatch: 'prefix', + component: , + key: 'basciLayout', + children: [ + { + path: '*', + component: ( + + + + ), + key: 'errorBoundary' + } + ] + }, + { path: '/playground', component: , key: 'playground' } ] // Create a context provider component -export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => { - const { message } = App.useApp() - const { setLocale } = useLocaleContext(); - const [state, dispatch] = useReducer(globalReducer, { - isAuthenticated: false, //mock用 - userData: null, - version: '1.0.0', - updateDate: '2024-07-01', - powered:'Powered by https://apipark.com', - mainPage:'/guide/page', - language:'en-US', - pluginsLoaded:false - }); - const [accessData,setAccessData] = useState>(new Map()) - const [pluginAccessDictionary, setPluginAccessDictionary] = useState<{[k:string]:string}>({}) - const [teamDataFlushed, setTeamDataFlushed] = useState(false) - const [accessInit, setAccessInit] = useState(false) - const [aiConfigFlushed, setAiConfigFlushed] = useState(false) - let getGlobalAccessPromise: Promise> | null = null - const [menuList, setMenuList] = useState(mockData); - const [routeConfig, setRouteConfigState] = useState(DefaultRouteConfig) +export const GlobalProvider: FC<{ children: ReactNode }> = ({ children }) => { + const { message } = App.useApp() + const { setLocale } = useLocaleContext() + const [state, dispatch] = useReducer(globalReducer, { + isAuthenticated: false, //mock用 + userData: null, + version: '1.0.0', + updateDate: '2024-07-01', + powered: 'Powered by https://apipark.com', + mainPage: '/guide/page', + language: 'en-US', + pluginsLoaded: false + }) + const [accessData, setAccessData] = useState>(new Map()) + const [pluginAccessDictionary, setPluginAccessDictionary] = useState<{ [k: string]: string }>({}) + const [teamDataFlushed, setTeamDataFlushed] = useState(false) + const [accessInit, setAccessInit] = useState(false) + const [aiConfigFlushed, setAiConfigFlushed] = useState(false) + let getGlobalAccessPromise: Promise> | null = null + const [menuList, setMenuList] = useState(mockData) + const [routeConfig, setRouteConfigState] = useState(DefaultRouteConfig) - useEffect(() => { - setLocale(state.language); - }, [state.language, setLocale]); - - const { fetchData } = useFetch(); - - const setRouterConfig = (isRoot: boolean, config: RouteConfig) => { - setRouteConfigState(prevConfig => { - if (isRoot) { - return [config,...prevConfig]; - } else { - const rootRoute = prevConfig.find(route => route.path === '/' && route?.pathMatch === 'prefix') ; - if (rootRoute ) { - rootRoute.children = rootRoute.children ? [config, ...rootRoute.children] : [config]; + useEffect(() => { + setLocale(state.language) + }, [state.language, setLocale]) + + const { fetchData } = useFetch() + + const setRouterConfig = (isRoot: boolean, config: RouteConfig) => { + setRouteConfigState((prevConfig) => { + if (isRoot) { + return [config, ...prevConfig] + } else { + const rootRoute = prevConfig.find((route) => route.path === '/' && route?.pathMatch === 'prefix') + if (rootRoute) { + rootRoute.children = rootRoute.children ? [config, ...rootRoute.children] : [config] + } + return [...prevConfig] + } + }) + } + + const addRouteConfig = (parentRoute: RouteConfig, config: RouteConfig) => { + const addConfigToParent = (routes: RouteConfig[]): RouteConfig[] => { + return routes.map((route) => { + if (route.key === parentRoute.key) { + route.children = route.children ? [...route.children, config] : [config] + } else if (route.children) { + route.children = addConfigToParent(route.children) + } + return route + }) + } + + setRouteConfigState((prevConfig) => addConfigToParent(prevConfig)) + } + + const getGlobalAccessData = () => { + if (getGlobalAccessPromise) { + return getGlobalAccessPromise + } + getGlobalAccessPromise = new Promise((resolve, reject) => + fetchData>('profile/permission/system', { method: 'GET' }).then( + (response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setAccessInit(true) + setAccessData((prevData) => new Map(prevData).set('system', data.access)) + resolve(data.response) + getGlobalAccessPromise = null + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(data.msg || $t(RESPONSE_TIPS.error)) } - return [...prevConfig]; } - }); - }; - - const addRouteConfig = (parentRoute: RouteConfig, config: RouteConfig) => { - const addConfigToParent = (routes: RouteConfig[]): RouteConfig[] => { - return routes.map(route => { - if (route.key === parentRoute.key) { - route.children = route.children ? [...route.children, config] : [config]; - } else if (route.children) { - route.children = addConfigToParent(route.children); - } - return route; - }); - }; - - setRouteConfigState(prevConfig => addConfigToParent(prevConfig)); - }; + ) + ) + return getGlobalAccessData + } - const getGlobalAccessData = ()=>{ - if(getGlobalAccessPromise){ - return getGlobalAccessPromise - } - getGlobalAccessPromise = new Promise((resolve, reject) => fetchData>('profile/permission/system',{method:'GET'},).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAccessInit(true) - setAccessData(prevData => new Map(prevData).set('system', data.access)) - resolve(data.response) - getGlobalAccessPromise = null - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(data.msg || $t(RESPONSE_TIPS.error)) - } - }) - ) - return getGlobalAccessData + const getTeamAccessData = (teamId: string) => { + fetchData>('profile/permission/team', { + method: 'GET', + eoParams: { team: teamId } + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setAccessData((prevData) => new Map(prevData).set('team', data.access)) + setTeamDataFlushed(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const getMenuList = () => { + // TODO 等待对接后端接口 + // fetchData>('profile/permission/team',{method:'GET',eoParams:{team:teamId}},).then(response=>{ + // const {code,data,msg} = response + // if(code === STATUS_CODE.SUCCESS){ + // setMenuList(data.menus) + // }else{ + // message.error(msg || $t(RESPONSE_TIPS.error)) + // } + // }) + } + + const cleanTeamAccessData = () => { + setTeamDataFlushed(false) + setAccessData((prevData) => prevData.set('team', [])) + } + + const getPluginAccessDictionary = (pluginData: { [k: string]: string }) => { + setPluginAccessDictionary(pluginData) + } + + const resetAccess = () => { + setAccessData(new Map()) + setAccessInit(false) + setPluginAccessDictionary({}) + } + + const checkPermission = ( + access: keyof (typeof PERMISSION_DEFINITION)[0] | Array + ) => { + let revs = false + if (Array.isArray(access)) { + revs = access.some((item) => checkAccess(item, accessData)) + } else { + revs = checkAccess(access, accessData) } + return revs + } - const getTeamAccessData = (teamId:string)=>{ - fetchData>('profile/permission/team',{method:'GET',eoParams:{team:teamId}},).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAccessData(prevData => new Map(prevData).set('team', data.access)) - setTeamDataFlushed(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - const getMenuList = ()=>{ - // TODO 等待对接后端接口 - // fetchData>('profile/permission/team',{method:'GET',eoParams:{team:teamId}},).then(response=>{ - // const {code,data,msg} = response - // if(code === STATUS_CODE.SUCCESS){ - // setMenuList(data.menus) - // }else{ - // message.error(msg || $t(RESPONSE_TIPS.error)) - // } - // }) - } - - const cleanTeamAccessData = ()=>{ - setTeamDataFlushed(false) - setAccessData(prevData => prevData.set('team',[])) - } - - const getPluginAccessDictionary = (pluginData:{[k:string]:string})=>{ - setPluginAccessDictionary(pluginData) - } - - const resetAccess = ()=>{ - setAccessData(new Map()) - setAccessInit(false) - setPluginAccessDictionary({}) - } - - const checkPermission = (access:keyof typeof PERMISSION_DEFINITION[0] | Array)=>{ - let revs = false; - if (Array.isArray(access)) { - revs = access.some(item => checkAccess(item, accessData)); - } else { - revs = checkAccess(access, accessData); - } - return revs - } - - - - return ( - - {children} - - ); -}; + return ( + + {children} + + ) +} // Create a custom hook for accessing the global context export const useGlobalContext = () => { - const context = useContext(GlobalContext); - if (!context) { - - console.warn('useGlobalContext must be used within a GlobalProvider. Returning default context.'); - return { - state: { - isAuthenticated: false, - userData: null, - version: '1.0.0', - updateDate: '', - powered: '', - mainPage: '', - language: 'en-US', - pluginsLoaded: false, - }, - dispatch: () => {}, - accessData: new Map(), - pluginAccessDictionary: {}, - menuList: [], - getGlobalAccessData: async () => ({ access: [] }), - getTeamAccessData: () => {}, - getPluginAccessDictionary: () => {}, - getMenuList: () => {}, - resetAccess: () => {}, - cleanTeamAccessData: () => {}, - checkPermission: () => false, - teamDataFlushed: false, - accessInit: false, - aiConfigFlushed: false, - setAiConfigFlushed: () => {}, - routeConfig: [], - setRouterConfig: () => {}, - addRouteConfig: () => {}, - fetchData: async () => ({}), - $t: (key: string) => key, - }; + const context = useContext(GlobalContext) + if (!context) { + console.warn('useGlobalContext must be used within a GlobalProvider. Returning default context.') + return { + state: { + isAuthenticated: false, + userData: null, + version: '1.0.0', + updateDate: '', + powered: '', + mainPage: '', + language: 'en-US', + pluginsLoaded: false + }, + dispatch: () => {}, + accessData: new Map(), + pluginAccessDictionary: {}, + menuList: [], + getGlobalAccessData: async () => ({ access: [] }), + getTeamAccessData: () => {}, + getPluginAccessDictionary: () => {}, + getMenuList: () => {}, + resetAccess: () => {}, + cleanTeamAccessData: () => {}, + checkPermission: () => false, + teamDataFlushed: false, + accessInit: false, + aiConfigFlushed: false, + setAiConfigFlushed: () => {}, + routeConfig: [], + setRouterConfig: () => {}, + addRouteConfig: () => {}, + fetchData: async () => ({}), + $t: (key: string) => key } - return context; -}; \ No newline at end of file + } + return context +} diff --git a/frontend/packages/common/src/contexts/LocaleContext.tsx b/frontend/packages/common/src/contexts/LocaleContext.tsx index 497e1a79..3c061f55 100644 --- a/frontend/packages/common/src/contexts/LocaleContext.tsx +++ b/frontend/packages/common/src/contexts/LocaleContext.tsx @@ -1,47 +1,46 @@ -import React, { createContext, useContext, useState, useEffect, useMemo, FC, ReactNode } from 'react'; -import { ConfigProviderProps } from 'antd'; -import zhCN from 'antd/locale/zh_CN'; -import enUS from 'antd/locale/en_US'; -import zhTW from 'antd/locale/zh_TW'; -import jaJP from 'antd/locale/ja_JP'; -import dayjs from 'dayjs'; -import 'dayjs/locale/zh-cn'; -import 'dayjs/locale/zh-tw'; -import 'dayjs/locale/ja'; +import React, { createContext, useContext, useState, FC, ReactNode } from 'react' +import { ConfigProviderProps } from 'antd' +import zhCN from 'antd/locale/zh_CN' +import enUS from 'antd/locale/en_US' +import zhTW from 'antd/locale/zh_TW' +import jaJP from 'antd/locale/ja_JP' +import dayjs from 'dayjs' +import 'dayjs/locale/zh-cn' +import 'dayjs/locale/zh-tw' +import 'dayjs/locale/ja' -type Locale = ConfigProviderProps['locale']; +type Locale = ConfigProviderProps['locale'] const languageMap: Record = { 'zh-CN': zhCN, 'en-US': enUS, 'zh-TW': zhTW, - 'ja-JP': jaJP, -}; + 'ja-JP': jaJP +} -const LocaleContext = createContext<{ - locale: Locale; - setLocale: (locale: string) => void; -}|undefined>(undefined); +const LocaleContext = createContext< + | { + locale: Locale + setLocale: (locale: string) => void + } + | undefined +>(undefined) -export const LocaleProvider: FC<{children:ReactNode}> = ({ children }) => { - const [locale, setLocaleState] = useState(zhCN); +export const LocaleProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [locale, setLocaleState] = useState(zhCN) const setLocale = (language: string) => { - dayjs.locale(language); - setLocaleState(languageMap[language]); - }; + dayjs.locale(language) + setLocaleState(languageMap[language]) + } - return ( - - {children} - - ); -}; + return {children} +} export const useLocaleContext = () => { - const context = useContext(LocaleContext); - if (!context) { - throw new Error('useLocaleContext must be used within a LocaleContext'); - } - return context; -}; \ No newline at end of file + const context = useContext(LocaleContext) + if (!context) { + throw new Error('useLocaleContext must be used within a LocaleContext') + } + return context +} diff --git a/frontend/packages/common/src/contexts/PluginEventHubContext.tsx b/frontend/packages/common/src/contexts/PluginEventHubContext.tsx index 407b5993..a3a617e3 100644 --- a/frontend/packages/common/src/contexts/PluginEventHubContext.tsx +++ b/frontend/packages/common/src/contexts/PluginEventHubContext.tsx @@ -1,76 +1,73 @@ -import { createContext, FC, ReactNode, useContext, useState } from "react"; - +import { createContext, FC, ReactNode, useContext, useState } from 'react' class EventEmitter { - // 用来存放注册的事件与回调 - _events:any - constructor () { - this._events = {} - } - - on (eventName:string, callback:Function) { - // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列 - const callbacks = this._events[eventName] || [] - callbacks.push(callback) - this._events[eventName] = callbacks - } - - // 此处需要处理,emit时需要按顺序执行监听的函数,每个函数都会返回是否中止的参数,如果中止则不执行后续的函数 - // emit传入eventName 和 event, 返回 event - emit (eventName:string, event:any) { - return new Promise((resolve) => { - const callbacks = this._events[eventName] || [] - for (const cb of callbacks) { - const cbRes = cb(event.data) - if (cbRes.continue === false) { - resolve(cbRes) - break - } else { - event = cbRes - } - } - resolve(event.data) - }) - } - - // 取消订阅 - off (eventName:string, callback:Function) { - const callbacks = this._events[eventName] || [] - const newCallbacks = callbacks.filter((fn:any) => fn !== callback && fn.initialCallback !== callback /* 用于once的取消订阅 */) - this._events[eventName] = newCallbacks - } - - // 单次订阅,后台插件可以自行决定取消对事件的订阅 - once (eventName:string, callback:Function) { - // 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数 - const one = (...args:any) => { - callback(...args) - this.off(eventName, one) - } - - // 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数 - // 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现 - one.initialCallback = callback - this.on(eventName, one) - } + // 用来存放注册的事件与回调 + _events: any + constructor() { + this._events = {} } -export const PluginEventHubContext = createContext(undefined); + on(eventName: string, callback: Function) { + // 由于一个事件可能注册多个回调函数,所以使用数组来存储事件队列 + const callbacks = this._events[eventName] || [] + callbacks.push(callback) + this._events[eventName] = callbacks + } + + // 此处需要处理,emit时需要按顺序执行监听的函数,每个函数都会返回是否中止的参数,如果中止则不执行后续的函数 + // emit传入eventName 和 event, 返回 event + emit(eventName: string, event: any) { + return new Promise((resolve) => { + const callbacks = this._events[eventName] || [] + for (const cb of callbacks) { + const cbRes = cb(event.data) + if (cbRes.continue === false) { + resolve(cbRes) + break + } else { + event = cbRes + } + } + resolve(event.data) + }) + } + + // 取消订阅 + off(eventName: string, callback: Function) { + const callbacks = this._events[eventName] || [] + const newCallbacks = callbacks.filter( + (fn: any) => fn !== callback && fn.initialCallback !== callback /* 用于once的取消订阅 */ + ) + this._events[eventName] = newCallbacks + } + + // 单次订阅,后台插件可以自行决定取消对事件的订阅 + once(eventName: string, callback: Function) { + // 由于需要在回调函数执行后,取消订阅当前事件,所以需要对传入的回调函数做一层包装,然后绑定包装后的函数 + const one = (...args: any) => { + callback(...args) + this.off(eventName, one) + } + + // 由于:我们订阅事件的时候,修改了原回调函数的引用,所以,用户触发 off 的时候不能找到对应的回调函数 + // 所以,我们需要在当前函数与用户传入的回调函数做一个绑定,我们通过自定义属性来实现 + one.initialCallback = callback + this.on(eventName, one) + } +} + +export const PluginEventHubContext = createContext(undefined) export const PluginEventHubProvider: FC<{ children: ReactNode }> = ({ children }) => { - const [pluginEventHub] = useState(new EventEmitter()); + const [pluginEventHub] = useState(new EventEmitter()) - return ( - - {children} - - ); -}; + return {children} +} export const usePluginEventHub = () => { - const context = useContext(PluginEventHubContext); + const context = useContext(PluginEventHubContext) if (!context) { - throw new Error('usePluginEventHub must be used within a PluginEventHubProvider'); + throw new Error('usePluginEventHub must be used within a PluginEventHubProvider') } - return context; -}; \ No newline at end of file + return context +} diff --git a/frontend/packages/common/src/contexts/PluginSlotHubContext.tsx b/frontend/packages/common/src/contexts/PluginSlotHubContext.tsx index 7cdd47ac..55d1e24e 100644 --- a/frontend/packages/common/src/contexts/PluginSlotHubContext.tsx +++ b/frontend/packages/common/src/contexts/PluginSlotHubContext.tsx @@ -1,36 +1,45 @@ -import { createContext, FC, ReactNode, useContext, useState } from "react"; +import { createContext, FC, ReactNode, useContext, useState } from 'react' -export const PluginSlotHubContext = createContext<{ - addSlot: (name: string, content: unknown) => void; - addSlotArr: (name: string, content: unknown[]) => void; - removeSlot: (name: string) => void; - getSlot: (name: string) => unknown; -} | undefined>(undefined); +export const PluginSlotHubContext = createContext< + | { + addSlot: (name: string, content: unknown) => void + addSlotArr: (name: string, content: unknown[]) => void + removeSlot: (name: string) => void + getSlot: (name: string) => unknown + } + | undefined +>(undefined) export const PluginSlotHubProvider: FC<{ children: ReactNode }> = ({ children }) => { - const [pluginSlotHub] = useState>(new Map()); + const [pluginSlotHub] = useState>(new Map()) const pluginSlotHubService = { - addSlot: (name: string, content: any) => { - pluginSlotHub.set(name, pluginSlotHub.get(name) ? [...(pluginSlotHub.get(name) as Array), content] : [content] ); }, - addSlotArr: (name: string, content: any[]) => { pluginSlotHub.get(name) ? pluginSlotHub.set(name, (pluginSlotHub.get(name) as Array).push(content)) : pluginSlotHub.set(name, content); }, - removeSlot: (name: string) => { pluginSlotHub.delete(name); }, + addSlot: (name: string, content: any) => { + pluginSlotHub.set( + name, + pluginSlotHub.get(name) ? [...(pluginSlotHub.get(name) as Array), content] : [content] + ) + }, + addSlotArr: (name: string, content: any[]) => { + pluginSlotHub.get(name) + ? pluginSlotHub.set(name, (pluginSlotHub.get(name) as Array).push(content)) + : pluginSlotHub.set(name, content) + }, + removeSlot: (name: string) => { + pluginSlotHub.delete(name) + }, getSlot: (name: string) => { - - return pluginSlotHub.get(name) ; } - }; + return pluginSlotHub.get(name) + } + } - return ( - - {children} - - ); -}; + return {children} +} export const usePluginSlotHub = () => { - const context = useContext(PluginSlotHubContext); + const context = useContext(PluginSlotHubContext) if (!context) { - throw new Error('usePluginSlotHub must be used within a PluginSlotHubProvider'); + throw new Error('usePluginSlotHub must be used within a PluginSlotHubProvider') } - return context; -}; \ No newline at end of file + return context +} diff --git a/frontend/packages/common/src/hooks/copy.ts b/frontend/packages/common/src/hooks/copy.ts index 5d682628..15b4ab78 100644 --- a/frontend/packages/common/src/hooks/copy.ts +++ b/frontend/packages/common/src/hooks/copy.ts @@ -1,64 +1,65 @@ - -import { RESPONSE_TIPS } from '@common/const/const'; -import { message } from 'antd'; -import { $t } from "@common/locales" -import { useEffect, useState } from 'react'; +import { RESPONSE_TIPS } from '@common/const/const' +import { message } from 'antd' +import { $t } from '@common/locales' +import { useEffect, useState } from 'react' const useCopyToClipboard = () => { - const [isCopied, setIsCopied] = useState(false); + const [isCopied, setIsCopied] = useState(false) const copyToClipboard = (text: string) => { if (navigator.clipboard && window.isSecureContext) { - navigator.clipboard.writeText(text)?.then(() => { - message.success($t(RESPONSE_TIPS.copySuccess)) - setIsCopied(true) - }) - .catch((error) => { - console.error('Failed to copy text to clipboard:', error); - }); + navigator.clipboard + .writeText(text) + ?.then(() => { + message.success($t(RESPONSE_TIPS.copySuccess)) + setIsCopied(true) + }) + .catch((error) => { + console.error('Failed to copy text to clipboard:', error) + }) } else { - // 创建text partition - const textArea = document.createElement("textarea"); - textArea.value = text; - // 使text area不在viewport,同时设置不可见 - textArea.style.position = "absolute"; - textArea.style.opacity = 0 + ''; - textArea.style.left = "-999999px"; - textArea.style.top = "-999999px"; - document.body.appendChild(textArea); - // textArea.focus(); - textArea.select(); - new Promise((resolve, reject) => { - if(document.execCommand('copy')) { - message.success($t(RESPONSE_TIPS.copySuccess)) - setIsCopied(true) - resolve() - } else { - reject('Failed to copy text to clipboard:') - } - }).catch((error) => { - console.error('Failed to copy text to clipboard:', error); - }).finally(() => { - textArea.remove(); - + // 创建text partition + const textArea = document.createElement('textarea') + textArea.value = text + // 使text area不在viewport,同时设置不可见 + textArea.style.position = 'absolute' + textArea.style.opacity = 0 + '' + textArea.style.left = '-999999px' + textArea.style.top = '-999999px' + document.body.appendChild(textArea) + // textArea.focus(); + textArea.select() + new Promise((resolve, reject) => { + if (document.execCommand('copy')) { + message.success($t(RESPONSE_TIPS.copySuccess)) + setIsCopied(true) + resolve() + } else { + reject('Failed to copy text to clipboard:') + } + }) + .catch((error) => { + console.error('Failed to copy text to clipboard:', error) + }) + .finally(() => { + textArea.remove() }) } - }; - + } useEffect(() => { if (isCopied) { const timeout = setTimeout(() => { - setIsCopied(false); - }, 3000); + setIsCopied(false) + }, 3000) return () => { - clearTimeout(timeout); - }; + clearTimeout(timeout) + } } - }, [isCopied]); + }, [isCopied]) - return { isCopied, copyToClipboard }; -}; + return { isCopied, copyToClipboard } +} -export default useCopyToClipboard; +export default useCopyToClipboard diff --git a/frontend/packages/common/src/hooks/excel.ts b/frontend/packages/common/src/hooks/excel.ts index 391d565d..86dfda6d 100644 --- a/frontend/packages/common/src/hooks/excel.ts +++ b/frontend/packages/common/src/hooks/excel.ts @@ -1,60 +1,74 @@ - -import * as ExcelJS from 'exceljs'; -import { saveAs } from 'file-saver'; -import { ProColumnType } from '@ant-design/pro-components'; -import { $t } from '@common/locales'; +import * as ExcelJS from 'exceljs' +import { saveAs } from 'file-saver' +import { ProColumnType } from '@ant-design/pro-components' +import { $t } from '@common/locales' export const useExcelExport = () => { - const createExcel = (sheetTitle: string, columns: ExcelJS.Column[], tableData: T[]) => { const workBook = new ExcelJS.Workbook() - const sheet = workBook.addWorksheet(sheetTitle || $t('默认工作表')); - sheet.columns = columns; - sheet.addRows(tableData); + const sheet = workBook.addWorksheet(sheetTitle || $t('默认工作表')) + sheet.columns = columns + sheet.addRows(tableData) return workBook - }; + } - const exportExcel = async (fileTitle: string, date: [number, number], sheetTitle: string, tableId: string, tableColumnConfig: (ProColumnType&{eoTitle:string})[], tableData: T[]) => { - const workBook = createExcel(sheetTitle, getColumns(tableId, tableColumnConfig) as ExcelJS.Column[], tableData || []) - const fileName = getFileName(fileTitle, date); + const exportExcel = async ( + fileTitle: string, + date: [number, number], + sheetTitle: string, + tableId: string, + tableColumnConfig: (ProColumnType & { eoTitle: string })[], + tableData: T[] + ) => { + const workBook = createExcel( + sheetTitle, + getColumns(tableId, tableColumnConfig) as ExcelJS.Column[], + tableData || [] + ) + const fileName = getFileName(fileTitle, date) try { - const buffer = await workBook.xlsx.writeBuffer(); - saveAs(new Blob([buffer], { - type: 'application/octet-stream' - }), `${fileName}.xlsx`); + const buffer = await workBook.xlsx.writeBuffer() + saveAs( + new Blob([buffer], { + type: 'application/octet-stream' + }), + `${fileName}.xlsx` + ) } catch (error) { - console.error('Error exporting Excel file:', error); + console.error('Error exporting Excel file:', error) } - }; + } - const getColumns = (tableId: string, tableColumnConfig: (ProColumnType&{eoTitle:string})[]) => { - let tableConfig: Record | null; + const getColumns = (tableId: string, tableColumnConfig: (ProColumnType & { eoTitle: string })[]) => { + let tableConfig: Record | null try { - const storedConfig = localStorage.getItem(tableId); - tableConfig = storedConfig ? JSON.parse(storedConfig) : {}; + const storedConfig = localStorage.getItem(tableId) + tableConfig = storedConfig ? JSON.parse(storedConfig) : {} } catch (error) { - console.error('Error parsing localStorage config:', error); - tableConfig = {}; + console.error('Error parsing localStorage config:', error) + tableConfig = {} } return tableColumnConfig - .filter((head: ProColumnType&{eoTitle:string}) => tableConfig?.[head.dataIndex as string]?.show) - .map((head) => { return({ - header: $t(head.eoTitle), - key: head.dataIndex, - width: (head.eoTitle as string).length > 5 ? (head.eoTitle as string).length * 3 : 15, - style: (head.dataIndex as string).includes('Rate') ? { numFmt: '0.00%' } : undefined, - })}); - }; + .filter((head: ProColumnType & { eoTitle: string }) => tableConfig?.[head.dataIndex as string]?.show) + .map((head) => { + return { + header: $t(head.eoTitle), + key: head.dataIndex, + width: (head.eoTitle as string).length > 5 ? (head.eoTitle as string).length * 3 : 15, + style: (head.dataIndex as string).includes('Rate') ? { numFmt: '0.00%' } : undefined + } + }) + } const getFileName = (fileTitle: string, date: [number, number]): string => { - const [start, end] = date.map((time) => getDateFormat(time)); - return `${fileTitle}-${start}${$t('至')}${end}`; - }; + const [start, end] = date.map((time) => getDateFormat(time)) + return `${fileTitle}-${start}${$t('至')}${end}` + } const getDateFormat = (time: number): string => { - const date = new Date(time * 1000); - return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}-${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; - }; + const date = new Date(time * 1000) + return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}-${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}` + } - return { exportExcel }; -}; + return { exportExcel } +} diff --git a/frontend/packages/common/src/hooks/http.ts b/frontend/packages/common/src/hooks/http.ts index 05fe42cf..6688fdad 100644 --- a/frontend/packages/common/src/hooks/http.ts +++ b/frontend/packages/common/src/hooks/http.ts @@ -1,207 +1,205 @@ -import { BasicResponse, STATUS_CODE } from "@common/const/const"; -import { usePluginEventHub } from "@common/contexts/PluginEventHubContext"; -import EventEmitter from "events"; +import { STATUS_CODE } from '@common/const/const' +import { usePluginEventHub } from '@common/contexts/PluginEventHubContext' -const urlWhiteList = [/api.example.com\/users/, /api.example2.com\/products/]; // 正则白名单 +const urlWhiteList = [/api.example.com\/users/, /api.example2.com\/products/] // 正则白名单 -function shouldNotTransform(url:string) { - return urlWhiteList.some(regex => regex.test(url)); +function shouldNotTransform(url: string) { + return urlWhiteList.some((regex) => regex.test(url)) } -function toCamel(s:string) { - return s.replace(/(_\w)/g, k => k[1].toUpperCase()); +function toCamel(s: string) { + return s.replace(/(_\w)/g, (k) => k[1].toUpperCase()) } -function toSnake(s:string) { - return s.replace(/([A-Z])/g, '_$1').toLowerCase(); +function toSnake(s: string) { + return s.replace(/([A-Z])/g, '_$1').toLowerCase() } -function isObject(obj:unknown) { - return obj === Object(obj) && !Array.isArray(obj) && typeof obj !== 'function'; +function isObject(obj: unknown) { + return obj === Object(obj) && !Array.isArray(obj) && typeof obj !== 'function' } // 将对象的键从下划线转为驼峰 -function keysToCamel(o:unknown,transformKeys:string[]):unknown { - if (isObject(o)) { - const n:{[k:string]:unknown} = {}; - Object.keys(o as object).forEach(k => { - const newKey = transformKeys.includes(k) ? toCamel(k) : k; - n[newKey] = keysToCamel((o as {[k:string]:unknown})[k],transformKeys); - }); - return n; - } - else if (Array.isArray(o)) { - return o.map(i => keysToCamel(i,transformKeys)); - } - return o; +function keysToCamel(o: unknown, transformKeys: string[]): unknown { + if (isObject(o)) { + const n: { [k: string]: unknown } = {} + Object.keys(o as object).forEach((k) => { + const newKey = transformKeys.includes(k) ? toCamel(k) : k + n[newKey] = keysToCamel((o as { [k: string]: unknown })[k], transformKeys) + }) + return n + } else if (Array.isArray(o)) { + return o.map((i) => keysToCamel(i, transformKeys)) + } + return o } // 将对象的键从驼峰转为下划线 -function keysToSnake(o:unknown,transformKeys:string[]):unknown { - if (isObject(o)) { - const n:{[k:string]:unknown} = {}; - Object.keys(o as object).forEach(k => { - const newKey = transformKeys.includes(k) ? toSnake(k) : k; - n[newKey] = keysToSnake((o as {[k:string]:unknown})[k],transformKeys); - }); - return n; - }else if (Array.isArray(o)) { - return o.map(i => keysToSnake(i,transformKeys)); - } - return o; +function keysToSnake(o: unknown, transformKeys: string[]): unknown { + if (isObject(o)) { + const n: { [k: string]: unknown } = {} + Object.keys(o as object).forEach((k) => { + const newKey = transformKeys.includes(k) ? toSnake(k) : k + n[newKey] = keysToSnake((o as { [k: string]: unknown })[k], transformKeys) + }) + return n + } else if (Array.isArray(o)) { + return o.map((i) => keysToSnake(i, transformKeys)) + } + return o } // 将查询字符串的键从驼峰转换为下划线 function convertQueryParamsToSnake( - params: { [k: string]: unknown }, - shouldTransformKeys: boolean, - transformKeys: string[] - ) { - const newParams = new URLSearchParams(); - - for (const key in params) { - if (shouldTransformKeys && transformKeys?.includes(key)) { - const newKey = toSnake(key); - const value = params[key]; - appendParam(newParams, newKey, value as Array | string); - } else { - appendParam(newParams, key, params[key] as Array | string); - } - } - - return newParams; - } - - function appendParam(params: URLSearchParams, key: string, value: Array | string) { - if (value !== undefined && value !== null) { - if (Array.isArray(value)) { - value.forEach((item) => params.append(key, item)); - } else { - params.append(key, value as string); - } + params: { [k: string]: unknown }, + shouldTransformKeys: boolean, + transformKeys: string[] +) { + const newParams = new URLSearchParams() + + for (const key in params) { + if (shouldTransformKeys && transformKeys?.includes(key)) { + const newKey = toSnake(key) + const value = params[key] + appendParam(newParams, newKey, value as Array | string) + } else { + appendParam(newParams, key, params[key] as Array | string) } } + return newParams +} -function isJsonHttp(headers: Headers | {[k:string]:string}): boolean { - const contentType = headers instanceof Headers ? headers.get('Content-Type') : headers['Content-Type']; - return contentType?.includes('application/json') ?? false; +function appendParam(params: URLSearchParams, key: string, value: Array | string) { + if (value !== undefined && value !== null) { + if (Array.isArray(value)) { + value.forEach((item) => params.append(key, item)) + } else { + params.append(key, value as string) + } + } +} + +function isJsonHttp(headers: Headers | { [k: string]: string }): boolean { + const contentType = headers instanceof Headers ? headers.get('Content-Type') : headers['Content-Type'] + return contentType?.includes('application/json') ?? false } const trimStringValuesInObject = (obj: unknown): unknown => { - if (typeof obj === 'string') { - return (obj as string).trim(); - } - if (Array.isArray(obj)) { - return obj.map(trimStringValuesInObject); - } - if (typeof obj === 'object' && obj !== null) { - return Object.fromEntries( - Object.entries(obj).map(([key, value]) => [key, trimStringValuesInObject(value)]) - ); - } - return obj; + if (typeof obj === 'string') { + return (obj as string).trim() + } + if (Array.isArray(obj)) { + return obj.map(trimStringValuesInObject) + } + if (typeof obj === 'object' && obj !== null) { + return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, trimStringValuesInObject(value)])) + } + return obj } const processQueryParams = (url: string, options: EoRequest, shouldTransformKeys: boolean) => { - if (options.eoParams) { - const cleanParams = Object.fromEntries( - Object.entries(options.eoParams) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value]) - ); - const queryParams = convertQueryParamsToSnake(cleanParams, shouldTransformKeys, options.eoTransformKeys as string[]); - const queryString = queryParams.toString(); - url += (url.includes('?') ? '&' : '?') + queryString; // 添加查询字符串到URL - } - return url; + if (options.eoParams) { + const cleanParams = Object.fromEntries( + Object.entries(options.eoParams) + .filter(([, value]) => value !== undefined) + .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value]) + ) + const queryParams = convertQueryParamsToSnake(cleanParams, shouldTransformKeys, options.eoTransformKeys as string[]) + const queryString = queryParams.toString() + url += (url.includes('?') ? '&' : '?') + queryString // 添加查询字符串到URL + } + return url } - const processRequestBody = (options: EoRequest, headers: EoHeaders, shouldTransformKeys: boolean) => { - let newBody:{[k:string]:unknown}|undefined - if (shouldTransformKeys && isJsonHttp(headers) && options.eoBody) { - newBody = keysToSnake(options.eoBody, options.eoTransformKeys as string[]) as {[k:string]:unknown}; - } + let newBody: { [k: string]: unknown } | undefined + if (shouldTransformKeys && isJsonHttp(headers) && options.eoBody) { + newBody = keysToSnake(options.eoBody, options.eoTransformKeys as string[]) as { [k: string]: unknown } + } - if (isJsonHttp(headers) && (newBody || options.eoBody)) { - options.body = JSON.stringify(trimStringValuesInObject(newBody || options.eoBody)); - } - return options.body; + if (isJsonHttp(headers) && (newBody || options.eoBody)) { + options.body = JSON.stringify(trimStringValuesInObject(newBody || options.eoBody)) + } + return options.body } const DEFAULT_HEADERS = { - 'Content-Type': 'application/json', - namespace:'default' -}; + 'Content-Type': 'application/json', + namespace: 'default' +} -type EoRequest = RequestInit & {eoParams?:{[k:string]:unknown},eoTransformKeys?:string[],eoApiPrefix?:string,eoBody?:{[k:string]:unknown}|Array|string} +type EoRequest = RequestInit & { + eoParams?: { [k: string]: unknown } + eoTransformKeys?: string[] + eoApiPrefix?: string + eoBody?: { [k: string]: unknown } | Array | string +} -type EoHeaders = Headers | {[k:string]:string} +type EoHeaders = Headers | { [k: string]: string } export function useFetch() { - // plugin cannot use usePluginEventHub directly, so we need to pass it as a parameter - const pluginEventHub = usePluginEventHub() + // plugin cannot use usePluginEventHub directly, so we need to pass it as a parameter + const pluginEventHub = usePluginEventHub() - function fetchData(url:string, options: EoRequest ) { - // 合并传入的headers与默认headers - const headers = { ...(options.body ? {}:DEFAULT_HEADERS), ...options.headers }; + function fetchData(url: string, options: EoRequest) { + // 合并传入的headers与默认headers + const headers = { ...(options.body ? {} : DEFAULT_HEADERS), ...options.headers } - // 检查是否需要转换键 - const shouldTransformKeys = !shouldNotTransform(url) && options?.eoTransformKeys && options?.eoTransformKeys?.length > 0; - - // 处理URL查询参数 - url = processQueryParams(url, options, !!shouldTransformKeys); + // 检查是否需要转换键 + const shouldTransformKeys = + !shouldNotTransform(url) && options?.eoTransformKeys && options?.eoTransformKeys?.length > 0 - // 处理请求体, 当请求头为json时,fetch的body应当是json字符串 - options.body = processRequestBody(options, headers as EoHeaders, !!shouldTransformKeys); - // 全局请求前拦截 - const finalOptions = { - ...(options || {}), - headers: { - ...headers - // Authorization: 'Bearer your-token', // 示例:添加统一的Token认证 - }, - }; + // 处理URL查询参数 + url = processQueryParams(url, options, !!shouldTransformKeys) - return fetch(`${options?.eoApiPrefix === undefined? '/api/v1/':options.eoApiPrefix}${url}`, finalOptions) - .then(async response => { - if (response.status === STATUS_CODE.UNANTHORIZED) { - // 处理401未登录的逻辑,比如跳转到登录页面或弹出登录框 - console.log('Unauthorized access, redirecting to login...'); - window.location.href = '/login' // 示例:重定向到登录 - - return; // 返回或抛出错误,确保不继续执行后续的响应处理 - } - - if (response.status === STATUS_CODE.FORBIDDEN) { - // 处理403无权限,比如跳转到登录页面或弹出登录框 - console.log('Unauthorized access, redirecting to login...'); - // window.location.href = '/login' // 示例:重定向到登录 - - return; // 返回或抛出错误,确保不继续执行后续的响应处理 - } - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - // 如果响应体为JSON且指定了转换键,则转换响应数据 - if ( isJsonHttp(response.headers)) { - const data = await response.json(); - const newData = await pluginEventHub.emit('httpResponse', {data,continue:true}) as Response; - return shouldTransformKeys ? keysToCamel(newData,options.eoTransformKeys as string[]) as T:data - } - - - return response; - }) - .catch(error => { - // 全局错误处理 - console.error('Global error handler:', error); - throw error; // 可选择重新抛出错误,让组件处理或显示错误信息 - }); + // 处理请求体, 当请求头为json时,fetch的body应当是json字符串 + options.body = processRequestBody(options, headers as EoHeaders, !!shouldTransformKeys) + // 全局请求前拦截 + const finalOptions = { + ...(options || {}), + headers: { + ...headers + // Authorization: 'Bearer your-token', // 示例:添加统一的Token认证 + } } - return { fetchData } -} \ No newline at end of file + return fetch(`${options?.eoApiPrefix === undefined ? '/api/v1/' : options.eoApiPrefix}${url}`, finalOptions) + .then(async (response) => { + if (response.status === STATUS_CODE.UNANTHORIZED) { + // 处理401未登录的逻辑,比如跳转到登录页面或弹出登录框 + console.log('Unauthorized access, redirecting to login...') + window.location.href = '/login' // 示例:重定向到登录 + + return // 返回或抛出错误,确保不继续执行后续的响应处理 + } + + if (response.status === STATUS_CODE.FORBIDDEN) { + // 处理403无权限,比如跳转到登录页面或弹出登录框 + console.log('Unauthorized access, redirecting to login...') + // window.location.href = '/login' // 示例:重定向到登录 + + return // 返回或抛出错误,确保不继续执行后续的响应处理 + } + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + // 如果响应体为JSON且指定了转换键,则转换响应数据 + if (options?.eoApiPrefix||isJsonHttp(response.headers)) { + const data = await response.json() + const newData = (await pluginEventHub.emit('httpResponse', { data, continue: true })) as Response + return shouldTransformKeys ? (keysToCamel(newData, options.eoTransformKeys as string[]) as T) : data + } + + return response + }) + .catch((error) => { + // 全局错误处理 + console.error('Global error handler:', error) + throw error // 可选择重新抛出错误,让组件处理或显示错误信息 + }) + } + return { fetchData } +} diff --git a/frontend/packages/common/src/hooks/pluginLoader.ts b/frontend/packages/common/src/hooks/pluginLoader.ts index 375fe664..a33eb8e4 100644 --- a/frontend/packages/common/src/hooks/pluginLoader.ts +++ b/frontend/packages/common/src/hooks/pluginLoader.ts @@ -1,200 +1,225 @@ -import { useEffect, useState } from "react"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { DEFAULT_LOCAL_PLUGIN_PATH, generateRemoteModuleTemplate, loadRemoteModule, validateExportLifecycle } from "@common/utils/plugin.tsx"; -import { useFetch } from "@common/hooks/http"; -import { PluginConfigType, RouteConfig } from "@common/const/type.ts"; -import { ApiparkPluginDriverType ,RouterMapConfig} from "@common/const/type"; -import { usePluginEventHub } from "@common/contexts/PluginEventHubContext"; -import { usePluginSlotHub } from "@common/contexts/PluginSlotHubContext"; -import { App } from "antd"; +import { ApiparkPluginDriverType, RouterMapConfig } from '@common/const/type' +import { PluginConfigType } from '@common/const/type.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { usePluginEventHub } from '@common/contexts/PluginEventHubContext' +import { usePluginSlotHub } from '@common/contexts/PluginSlotHubContext' +import { useFetch } from '@common/hooks/http' +import { + DEFAULT_LOCAL_PLUGIN_PATH, + generateRemoteModuleTemplate, + loadRemoteModule, + validateExportLifecycle +} from '@common/utils/plugin.tsx' +import { App } from 'antd' +import { useEffect, useState } from 'react' const mockData = { - buildAt:'2024-09-13T03:51:25Z', - build_user:'gitlab-runner', - git_commint:'6438d5aaff146dc560ed0d8563788e64a49640a5', - goversion:'go version go1.21.4 linux/amd64', - guide:true, - plugins:[ + buildAt: '2024-09-13T03:51:25Z', + build_user: 'gitlab-runner', + git_commint: '6438d5aaff146dc560ed0d8563788e64a49640a5', + goversion: 'go version go1.21.4 linux/amd64', + guide: true, + plugins: [ { - driver:'apipark.builtIn.component', - name:'guide', - router:[ + driver: 'apipark.builtIn.component', + name: 'guide', + router: [ { - path:'guide/*', - type:'normal' + path: 'guide/*', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'team', - router:[ + driver: 'apipark.builtIn.component', + name: 'team', + router: [ { - path:'team', - type:'normal' + path: 'team', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'service', - router:[ + driver: 'apipark.builtIn.component', + name: 'service', + router: [ { - path:'service', - type:'normal' + path: 'service', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'datasourcing', - router:[ + driver: 'apipark.builtIn.component', + name: 'datasourcing', + router: [ { - path:'datasourcing', - type:'normal' + path: 'datasourcing', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'cluster', - router:[ + driver: 'apipark.builtIn.component', + name: 'cluster', + router: [ { - path:'cluster', - type:'normal' + path: 'cluster', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'aisetting', - router:[ + driver: 'apipark.builtIn.component', + name: 'aisetting', + router: [ { - path:'aisetting', - type:'normal' + path: 'aisetting', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'cert', - router:[ + driver: 'apipark.builtIn.component', + name: 'keysetting', + router: [ { - path:'cert', - type:'normal' + path: 'keysetting', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'serviceHub', - router:[ + driver: 'apipark.builtIn.component', + name: 'aiApis', + router: [ { - path:'serviceHub', - type:'normal' + path: 'aiApis', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'commonsetting', - router:[ + driver: 'apipark.builtIn.component', + name: 'cert', + router: [ { - path:'commonsetting', - type:'normal' + path: 'cert', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'consumer', - router:[ + driver: 'apipark.builtIn.component', + name: 'serviceHub', + router: [ { - path:'consumer', - type:'normal' + path: 'serviceHub', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'member', - router:[ + driver: 'apipark.builtIn.component', + name: 'commonsetting', + router: [ { - path:'member', - type:'normal' + path: 'commonsetting', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'role', - router:[ + driver: 'apipark.builtIn.component', + name: 'consumer', + router: [ { - path:'role', - type:'normal' + path: 'consumer', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'analytics', - router:[ + driver: 'apipark.builtIn.component', + name: 'member', + router: [ { - path:'analytics', - type:'normal' + path: 'member', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'template', - router:[ + driver: 'apipark.builtIn.component', + name: 'role', + router: [ { - path:'template/:moduleId', - type:'normal' + path: 'role', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'logsettings', - router:[ + driver: 'apipark.builtIn.component', + name: 'analytics', + router: [ { - path:'logsettings/*', - type:'normal' + path: 'analytics', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'resourcesettings', - router:[ + driver: 'apipark.builtIn.component', + name: 'template', + router: [ { - path:'resourcesettings/*', - type:'normal' + path: 'template/:moduleId', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'userProfile', - router:[ + driver: 'apipark.builtIn.component', + name: 'logsettings', + router: [ { - path:'userProfile', - type:'normal' + path: 'logsettings/*', + type: 'normal' } ] }, { - driver:'apipark.builtIn.component', - name:'globalPolicy', - router:[ + driver: 'apipark.builtIn.component', + name: 'resourcesettings', + router: [ { - path:'globalPolicy', - type:'normal' + path: 'resourcesettings/*', + type: 'normal' } ] }, + { + driver: 'apipark.builtIn.component', + name: 'userProfile', + router: [ + { + path: 'userProfile', + type: 'normal' + } + ] + }, + { + driver: 'apipark.builtIn.component', + name: 'globalPolicy', + router: [ + { + path: 'globalPolicy', + type: 'normal' + } + ] + } // { // "driver": "apipark.remote.normal", // "name": "remote", @@ -263,187 +288,204 @@ const mockData = { // ] // } ], - powered:'Powered by https://eolink.com', - product:'apipark', - version:'6438d5aa' + powered: 'Powered by https://eolink.com', + product: 'apipark', + version: '6438d5aa' } export type ExecutePluginType = PluginConfigType & { - expose:string, - bootstrap:string + expose: string + bootstrap: string } -const usePluginLoader = (apipark:ApiparkPluginDriverType,routerMap:Map) => { - const [modules, setModules] = useState(new Map()); - const [executeList, setExecuteList] = useState([]); - const [baseHref, setBaseHref] = useState(''); - const [pendingTasks, setPendingTasks] = useState(0); - const {fetchData} = useFetch(); - const pluginProvider = useGlobalContext(); - const pluginEventHub = usePluginEventHub(); - const pluginSlotHub = usePluginSlotHub(); - const { getMenuList,dispatch} = pluginProvider - const { modal,message } = App.useApp() - const [startLoadExecutePlugin, setStartLoadExecutePlugin] = useState(false) - const messageService = message; - const modalService = modal; - let startInstallPlugin = false +const usePluginLoader = (apipark: ApiparkPluginDriverType, routerMap: Map) => { + const [modules, setModules] = useState(new Map()) + const [executeList, setExecuteList] = useState([]) + const [baseHref, setBaseHref] = useState('') + const [pendingTasks, setPendingTasks] = useState(0) + const { fetchData } = useFetch() + const pluginProvider = useGlobalContext() + const pluginEventHub = usePluginEventHub() + const pluginSlotHub = usePluginSlotHub() + const { getMenuList, dispatch } = pluginProvider + const { modal, message } = App.useApp() + const [startLoadExecutePlugin, setStartLoadExecutePlugin] = useState(false) + const messageService = message + const modalService = modal + let startInstallPlugin = false - useEffect(()=>{ - if (startLoadExecutePlugin && pendingTasks === 0 && executeList.length > 0) { - loadExecutedPlugin(); - } - },[pendingTasks, executeList]) - + useEffect(() => { + if (startLoadExecutePlugin && pendingTasks === 0 && executeList.length > 0) { + loadExecutedPlugin() + } + }, [pendingTasks, executeList]) - const getModule = (routerPrefix:string, specific = false) => { - if (routerPrefix.startsWith('/')) { - routerPrefix = routerPrefix.substring(1); + const getModule = (routerPrefix: string, specific = false) => { + if (routerPrefix.startsWith('/')) { + routerPrefix = routerPrefix.substring(1) + } + if (specific) { + return modules.get(routerPrefix) + } + let matchedModule = null + let matchedLength = 0 + + modules.forEach((value, key) => { + if (routerPrefix.startsWith(key) && key.length > matchedLength) { + matchedModule = value + matchedLength = key.length } - if (specific) { - return modules.get(routerPrefix); - } - let matchedModule = null; - let matchedLength = 0; - - modules.forEach((value, key) => { - if (routerPrefix.startsWith(key) && key.length > matchedLength) { - matchedModule = value; - matchedLength = key.length; - } - }); - return matchedModule; - }; - - const loadModule = async (routerPrefix: string, pluginName: any, exposedModule: string , pluginPath: any) => { - if (!modules.get(routerPrefix)) { - try { - const loadedModule = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, exposedModule, pluginPath)); - const Module = loadedModule.default ?? loadedModule - let ModuleBootstrap; - try { - ModuleBootstrap = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, 'Bootstrap', pluginPath)); - } catch (error) { - console.warn('Bootstrap module not found:', error); - } - setModules(prevModules => new Map(prevModules).set(routerPrefix,Module[exposedModule] )); - if (!validateExportLifecycle(Module)) { - console.error('需要导出插件生命周期函数'); - return; - } - await Module.bootstrap?.({ - pluginProvider, - pluginEventHub, - pluginSlotHub - }); - return Module; - } catch (error) { - console.error('导入插件失败:', error); - } - } - return getModule(routerPrefix, true); - }; - - const loadExecutedPlugin = async () => { - setStartLoadExecutePlugin(true) - for (const plugin of executeList) { - try { - const Module = await loadRemoteModule(generateRemoteModuleTemplate(plugin.name, plugin?.expose || 'Bootstrap', plugin.path || `${DEFAULT_LOCAL_PLUGIN_PATH}${plugin.name}/apipark.js`)); - const bootstrap = Module.bootstrap; - if (!bootstrap) { - console.warn('立即执行插件未导出Bootstrap模块或bootstrap函数'); - } else { - await bootstrap({ - pluginEventHub, - pluginSlotHub, - pluginProvider, - platformProvider:null, - messageService, - modalService, - }); - } - } catch (error) { - console.error('执行插件失败:', error); - } - } - }; - - - const loadPlugins = () => { - return new Promise((resolve) => { - if(startInstallPlugin) { - return resolve(true) - } - startInstallPlugin = true - installPlugin().then(async (res)=>{ - // reset route after loading executed plugins - await loadExecutedPlugin(); - return resolve(res) - }) - }); - }; - - const installPlugin = () => { - return new Promise((resolve, reject) => { - // fetchData('system/plugins',{method:'GET'}).then((resp) => { - // if (resp.code === 0){ - const resp = {data:mockData} - dispatch({type:'UPDATE_VERSION',version:resp.data.version}) - dispatch({type:'UPDATE_DATE',updateDate:resp.data.buildAt}) - dispatch({type:'UPDATE_POWER',powered:resp.data.powered}) - const driverMethod = { apipark: apipark }; - const pluginConfigList = resp.data.plugins; - const pluginLoader = { loadModule }; - const pluginLifecycleGuard ={}; - const builtInPluginLoader = loadBuiltInModule; - pluginSlotHub.addSlot('renewMenu', () => { - getMenuList() - }); - for (const plugin of pluginConfigList) { - try { - const driverName = plugin.driver; - if (!driverName) { - console.error('no driver name'); - continue; - } - const driver = driverName.split('.').reduce((driverMethod: { [x: string]: any; }, driverName: string | number) => driverMethod[driverName], driverMethod); - if(driverName.split('.')[2] === 'preload'){ - setPendingTasks(prev => prev + 1); - } - ;(driver as Function )?.({ setExecuteList:(callback:ExecutePluginType[]) => { - setExecuteList(callback); - setPendingTasks(prev => prev - 1); - }, pluginLoader, pluginProvider, pluginLifecycleGuard, builtInPluginLoader }, plugin); - } catch (err) { - console.warn('安装插件出错:', err); - } - } - resolve(true); - // } else { - // messageService.error(resp.msg || '获取插件配置列表失败,请重试!'); - // reject(new Error(resp.msg || '获取插件配置列表失败')); - // } - // }); - } - ); - }; - - const loadBuiltInModule = (pluginName: any) => { + }) + return matchedModule + } + + const loadModule = async (routerPrefix: string, pluginName: any, exposedModule: string, pluginPath: any) => { + if (!modules.get(routerPrefix)) { try { - const { module } = routerMap.get(pluginName)!; - return module; - } catch (err) { - console.warn(`安装内置插件[${pluginName}]出错:`, err); + const loadedModule = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, exposedModule, pluginPath)) + const Module = loadedModule.default ?? loadedModule + let ModuleBootstrap + try { + ModuleBootstrap = await loadRemoteModule(generateRemoteModuleTemplate(pluginName, 'Bootstrap', pluginPath)) + } catch (error) { + console.warn('Bootstrap module not found:', error) + } + setModules((prevModules) => new Map(prevModules).set(routerPrefix, Module[exposedModule])) + if (!validateExportLifecycle(Module)) { + console.error('需要导出插件生命周期函数') + return + } + await Module.bootstrap?.({ + pluginProvider, + pluginEventHub, + pluginSlotHub + }) + return Module + } catch (error) { + console.error('导入插件失败:', error) } - }; - - return { - loadPlugins, - loadModule, - loadExecutedPlugin, - setBaseHref, - getModule - }; - }; - - export default usePluginLoader; \ No newline at end of file + } + return getModule(routerPrefix, true) + } + + const loadExecutedPlugin = async () => { + setStartLoadExecutePlugin(true) + for (const plugin of executeList) { + try { + const Module = await loadRemoteModule( + generateRemoteModuleTemplate( + plugin.name, + plugin?.expose || 'Bootstrap', + plugin.path || `${DEFAULT_LOCAL_PLUGIN_PATH}${plugin.name}/apipark.js` + ) + ) + const bootstrap = Module.bootstrap + if (!bootstrap) { + console.warn('立即执行插件未导出Bootstrap模块或bootstrap函数') + } else { + await bootstrap({ + pluginEventHub, + pluginSlotHub, + pluginProvider, + platformProvider: null, + messageService, + modalService + }) + } + } catch (error) { + console.error('执行插件失败:', error) + } + } + } + + const loadPlugins = () => { + return new Promise((resolve) => { + if (startInstallPlugin) { + return resolve(true) + } + startInstallPlugin = true + installPlugin().then(async (res) => { + // reset route after loading executed plugins + await loadExecutedPlugin() + return resolve(res) + }) + }) + } + + const installPlugin = () => { + return new Promise((resolve, reject) => { + // fetchData('system/plugins',{method:'GET'}).then((resp) => { + // if (resp.code === 0){ + const resp = { data: mockData } + dispatch({ type: 'UPDATE_VERSION', version: resp.data.version }) + dispatch({ type: 'UPDATE_DATE', updateDate: resp.data.buildAt }) + dispatch({ type: 'UPDATE_POWER', powered: resp.data.powered }) + const driverMethod = { apipark: apipark } + const pluginConfigList = resp.data.plugins + const pluginLoader = { loadModule } + const pluginLifecycleGuard = {} + const builtInPluginLoader = loadBuiltInModule + pluginSlotHub.addSlot('renewMenu', () => { + getMenuList() + }) + for (const plugin of pluginConfigList) { + try { + const driverName = plugin.driver + if (!driverName) { + console.error('no driver name') + continue + } + const driver = driverName + .split('.') + .reduce( + (driverMethod: { [x: string]: any }, driverName: string | number) => driverMethod[driverName], + driverMethod + ) + if (driverName.split('.')[2] === 'preload') { + setPendingTasks((prev) => prev + 1) + } + ;(driver as Function)?.( + { + setExecuteList: (callback: ExecutePluginType[]) => { + setExecuteList(callback) + setPendingTasks((prev) => prev - 1) + }, + pluginLoader, + pluginProvider, + pluginLifecycleGuard, + builtInPluginLoader + }, + plugin + ) + } catch (err) { + console.warn('安装插件出错:', err) + } + } + resolve(true) + // } else { + // messageService.error(resp.msg || '获取插件配置列表失败,请重试!'); + // reject(new Error(resp.msg || '获取插件配置列表失败')); + // } + // }); + }) + } + + const loadBuiltInModule = (pluginName: any) => { + try { + const { module } = routerMap.get(pluginName)! + return module + } catch (err) { + console.warn(`安装内置插件[${pluginName}]出错:`, err) + } + } + + return { + loadPlugins, + loadModule, + loadExecutedPlugin, + setBaseHref, + getModule + } +} + +export default usePluginLoader diff --git a/frontend/packages/common/src/hooks/tokens.ts b/frontend/packages/common/src/hooks/tokens.ts index bdc6d979..ab5a448b 100644 --- a/frontend/packages/common/src/hooks/tokens.ts +++ b/frontend/packages/common/src/hooks/tokens.ts @@ -1,7 +1,7 @@ import { Dispatch, SetStateAction, useEffect, useState } from 'react' type BasicInfoType = { - selected_X_apibee_token: string, + selected_X_apibee_token: string tokenId: number | string } diff --git a/frontend/packages/common/src/hooks/useInitializeMonaco.ts b/frontend/packages/common/src/hooks/useInitializeMonaco.ts index 10cf2125..06ee788e 100644 --- a/frontend/packages/common/src/hooks/useInitializeMonaco.ts +++ b/frontend/packages/common/src/hooks/useInitializeMonaco.ts @@ -1,18 +1,17 @@ - -import { useEffect,useState } from 'react'; -import { loader } from '@monaco-editor/react'; -import { monaco } from '../monacoConfig'; +import { useEffect, useState } from 'react' +import { loader } from '@monaco-editor/react' +import { monaco } from '../monacoConfig' const useInitializeMonaco = () => { - const [initialized, setInitialized] = useState(false); + const [initialized, setInitialized] = useState(false) - useEffect(() => { - if (!initialized) { - loader.config({ monaco }); - loader.init().then(() => { - setInitialized(true); - }); - } - }, [initialized]); -}; -export default useInitializeMonaco; + useEffect(() => { + if (!initialized) { + loader.config({ monaco }) + loader.init().then(() => { + setInitialized(true) + }) + } + }, [initialized]) +} +export default useInitializeMonaco diff --git a/frontend/packages/common/src/hooks/webSocket.ts b/frontend/packages/common/src/hooks/webSocket.ts index 7971cb03..9207d68d 100644 --- a/frontend/packages/common/src/hooks/webSocket.ts +++ b/frontend/packages/common/src/hooks/webSocket.ts @@ -1,47 +1,50 @@ -import {useState, useCallback} from 'react'; +import { useState, useCallback } from 'react' type WebSocketHookProps = { - onOpen:()=>void, - onClose:()=>void, - onMessage:(event:unknown)=>void, - onError:(event:unknown)=>void, + onOpen: () => void + onClose: () => void + onMessage: (event: unknown) => void + onError: (event: unknown) => void } const useWebSocket = () => { - const [ws, setWs] = useState(); + const [ws, setWs] = useState() - // 创建WebSocket连接的方法 - const createWs = (url:string,{onOpen, onClose, onMessage, onError}:WebSocketHookProps) => { - if(ws) { - ws.close(); - } - - const socket = new WebSocket(url); - setWs(socket); - - socket.onopen = () => onOpen && onOpen(); - socket.onclose = () => onClose && onClose(); - socket.onmessage = (event) => onMessage && onMessage(event); - socket.onerror = (error) => onError && onError(error); - - return socket + // 创建WebSocket连接的方法 + const createWs = (url: string, { onOpen, onClose, onMessage, onError }: WebSocketHookProps) => { + if (ws) { + ws.close() } - // 提供发送消息的方法 - const sendMessage = useCallback((message:string) => { - if (ws && ws.readyState === WebSocket.OPEN) { - ws.send(message); - } - }, [ws]); + const socket = new WebSocket(url) + setWs(socket) - // 断开连接的方法 - const disconnectWs = useCallback(() => { - if (ws) { - ws.close(); - setWs(null); - } - }, [ws]); + socket.onopen = () => onOpen && onOpen() + socket.onclose = () => onClose && onClose() + socket.onmessage = (event) => onMessage && onMessage(event) + socket.onerror = (error) => onError && onError(error) - return { createWs, sendMessage, disconnectWs }; -}; + return socket + } -export default useWebSocket; \ No newline at end of file + // 提供发送消息的方法 + const sendMessage = useCallback( + (message: string) => { + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(message) + } + }, + [ws] + ) + + // 断开连接的方法 + const disconnectWs = useCallback(() => { + if (ws) { + ws.close() + setWs(null) + } + }, [ws]) + + return { createWs, sendMessage, disconnectWs } +} + +export default useWebSocket diff --git a/frontend/packages/common/src/locales/index.ts b/frontend/packages/common/src/locales/index.ts index d819368b..8a98b857 100644 --- a/frontend/packages/common/src/locales/index.ts +++ b/frontend/packages/common/src/locales/index.ts @@ -1,17 +1,17 @@ -import i18n from 'i18next'; -import { initReactI18next, useTranslation } from 'react-i18next'; +import i18n from 'i18next' +import { initReactI18next } from 'react-i18next' // i18next-browser-languagedetector插件 这是一个 i18next 语言检测插件,用于检测浏览器中的用户语言, -import LanguageDetector from 'i18next-browser-languagedetector'; -import crc32 from 'crc/crc32'; +import crc32 from 'crc/crc32' +import LanguageDetector from 'i18next-browser-languagedetector' // 引入需要实现国际化的简体、繁体、英文三种数据的json文件 -import zhCN from 'antd/locale/zh_CN'; -import enUS from 'antd/locale/en_US'; -import jaJP from 'antd/locale/ja_JP'; -import zhTW from 'antd/locale/zh_TW'; -import localZh_CN from './scan/zh-CN.json'; // 本地翻译中文文件 -import localEn_US from './scan/en-US.json'; // 本地翻译英文文件 -import localZh_TW from './scan/zh-TW.json'; // 本地翻译英文文件 -import localJa_JP from './scan/ja-JP.json'; // 本地翻译英文文件 +import enUS from 'antd/locale/en_US' +import jaJP from 'antd/locale/ja_JP' +import zhCN from 'antd/locale/zh_CN' +import zhTW from 'antd/locale/zh_TW' +import localEn_US from './scan/en-US.json' // 本地翻译英文文件 +import localJa_JP from './scan/ja-JP.json' // 本地翻译英文文件 +import localZh_CN from './scan/zh-CN.json' // 本地翻译中文文件 +import localZh_TW from './scan/zh-TW.json' // 本地翻译英文文件 // import config from '../../../../i18next-scanner.config.js'; const resources = { @@ -31,7 +31,7 @@ const resources = { translation: localJa_JP, ...jaJP } -}; +} i18n .use(LanguageDetector) // 嗅探当前浏览器语言 zh-CN @@ -43,27 +43,26 @@ i18n detection: { caches: ['localStorage', 'sessionStorage', 'cookie'] } - }); + }) // --------这里是i18next-scanner新增的配置------------- export const $t = (key: string, params?: any[]): string => { - const hashKey = `K${crc32(key).toString(16)}`; // 将中文转换成crc32格式去匹配对应的json语言包 - let words = i18n.t(hashKey); + const hashKey = `K${crc32(key).toString(16)}` // 将中文转换成crc32格式去匹配对应的json语言包 + let words = i18n.t(hashKey) // const { t } = useTranslation(); // 通过hooks // let words = t(hashKey); if (words === hashKey) { - words = key; + words = key } // 配置传递参数的场景, 目前仅支持数组,可在此拓展 if (Array.isArray(params)) { - const reg = /\((\d)\)/g; + const reg = /\((\d)\)/g words = words.replace(reg, (a: string, b: number) => { - return params[b]; - }); + return params[b] + }) } - return words; -}; - -export default i18n; + return words +} +export default i18n diff --git a/frontend/packages/common/src/locales/keyHashMap.json b/frontend/packages/common/src/locales/keyHashMap.json index 12e1d338..14b2af88 100644 --- a/frontend/packages/common/src/locales/keyHashMap.json +++ b/frontend/packages/common/src/locales/keyHashMap.json @@ -45,8 +45,6 @@ "成功": "K43fcaf94", "上线失败": "Kc71c6a9", "失败": "K56c686f8", - "正常": "Ke039b9b5", - "无效": "K1da86266", "申请系统": "K1ff96ff", "所属团队": "K9bf855d6", "申请人": "K11b994ed", @@ -239,6 +237,10 @@ "请输入IP地址或CIDR范围,每条以换行分割": "K49731763", "待更新": "K3a34d49b", "待删除": "Kd2850420", + "内容": "K3e7aa0ad", + "调用地址": "K2f5fdf5e", + "消费者 IP": "K1bc5e0a3", + "鉴权名称": "K6f39ea21", "暂无操作权限,请联系管理员分配。": "K23fda291", "微信小程序": "K4618cb0a", "获取文件,需填路径": "Ka854f511", @@ -324,12 +326,25 @@ "响应 Body": "Kd2be51d1", "默认工作表": "K2a3f24ac", "至": "K7e1ab4b0", - "详情": "Kf1b166e7", "暂不支持带有双斜杠//的url": "K28555332", "输入的IP或CIDR不符合格式": "K83237c89", "请正确输入路径,如/usr/*或*/usr/*": "K5ae2c87a", "必填项": "K71661ee8", "不是有效邮箱地址": "Kcbee3f8", + "获取 AI providers 失败": "K94b48734", + "AI 供应商": "Kf23a8988", + "AI 服务": "Kd2c34e2c", + "模型": "Kfede1c7c", + "已用 Token": "K89f135a7", + "拦截": "Kb7df6ac1", + "放行": "K5c1722fe", + "编辑时间": "K1acc30b2", + "预览": "K4d81a657", + "查看详情": "K35f990b0", + "AI API 列表": "K91144ebd", + "重置": "K50d471b2", + "查询": "Kee8ae330", + "请输入 APIURL 搜索": "Kf8187c33", "最近一次更新者": "K617f34f1", "最近一次更新时间": "K6ebca204", "保存": "Kabfe9512", @@ -357,18 +372,18 @@ "变量": "K13ffbe88", "输入这个接口的描述": "K79c8cfaf", "重试次数": "K469e475a", + "拦截接口": "Kee4139c2", + "开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea", "模型配置": "K8a35059b", "路由": "Kf9dcef3a", "添加路由": "K6134bbe8", "输入 URL 查找路由": "Kf85b83a0", "模型供应商": "Kcf9f90b8", - "模型": "Kfede1c7c", "参数": "Ke99513a0", "审核": "Kb595f40", "通过": "K54e27f57", "拒绝": "K8582af3f", "发布结果": "Kd568e15c", - "查看详情": "K35f990b0", "申请发布": "Kdbc1f6cb", "回滚": "Kb6860a3f", "请确认是否回滚?": "Ka3494f4b", @@ -377,13 +392,31 @@ "终止发布": "Ke1b79b93", "请确认是否终止发布?": "Ka2449180", "新建版本": "K2cb02f38", - "从 (0) 获取 API KEY": "Kb3e34847", + "未配置 AI 模型": "Kd752a3a8", + "前往设置": "K8b7ac871", + "AI 模型": "K99935e6f", + "配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务": "K2260837a", + "已设置": "Kf97448b3", + "未设置": "K30d4d8df", + "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。": "Ke32702ac", + "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。": "Ka08c28d4", + "默认模型": "Kc2ee5223", + "负载优先级": "K608af899", + "负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。": "K65b7a96", + "优先级必须大于 0": "K9eccff16", + "请输入优先级": "Kfcf02780", + "API Key(默认 Key)": "K5c6dcf58", + "LLM 状态管理": "K59bf8ed9", + "当前调用状态:": "Kab8fe398", + "正常": "Ke039b9b5", + "停用": "Kedd64e4d", + "异常": "K23a3bd72", + "启用": "K52c8a730", "已配置": "K66a7d24c", "未配置": "Kaf074220", "默认": "Kd9a46c29", - "AI 模型管理": "K7ac2be34", - "配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务": "K2260837a", - "同步最新模型": "K18dccc1a", + "默认:": "K2a3aeb8d", + "从 (0) 获取 API KEY": "Kb3e34847", "待审核": "K35612f29", "已审核": "K47eaafde", "发布申请": "K56b4254f", @@ -437,6 +470,28 @@ "了解 APIPark 如何更好地管理 API 和 AI": "K1afaf20e", "了解更多功能": "K48f7e21f", "隐藏该教程": "K698296e2", + "请输入 APIKey": "K8b88ef63", + "API Key": "Kcbd30819", + "请填写 APIKey": "Kcb6e2d3e", + "永不过期": "K9dfa2c97", + "设置过期时间": "Ke13e332a", + "请选择过期时间": "K409aa8ba", + "超额": "Kba69594c", + "过期": "Kb9e7ceda", + "错误": "Kac405b50", + "请选择状态": "K3fde5b49", + "添加 (0) APIKey": "K4880fd04", + "编辑 APIKey": "K434b7e76", + "删除成功": "K28190dbc", + "停用成功": "Kb5fcf5b8", + "启用成功": "K5940d788", + "排序成功": "K8743bccd", + "编辑": "Kad207008", + "调用优先级": "K19590c2c", + "APIKey 资源池": "Kefb03657", + "支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡": "Kc0352e64", + "请输入名称搜索": "Kd25acba1", + "添加 APIKey": "K6d0388a0", "请输入账号": "Kf076f63c", "账号": "K80a560a1", "请输入密码": "K25c895d5", @@ -460,7 +515,6 @@ "加入部门": "Ke6f00b44", "确定删除成员?此操作无法恢复,确认操作?": "K501cb1e7", "成员与部门": "Kf20863b5", - "启用": "K52c8a730", "禁用": "K718c9310", "输入用户名、邮箱查找成员": "K5f27a546", "移出当前部门": "K7c97c5df", @@ -473,10 +527,13 @@ "成员": "K74aef1ad", "设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。": "K3f1077c9", "搜索部门": "Kdce62a6", + "操作成功,即将刷新页面": "K9ada4366", "数据源类型": "Ka46b9b24", "数据源地址": "Kbb0cdcd0", "Organization": "Kd9dfb884", "鉴权 Token": "K3e770a75", + "请求前缀": "K9b332ab1", + "HTTP 头部": "K3d78d483", "密钥": "K8ef69ee2", "上传密钥": "Kba3507d6", "密钥文件的后缀名一般为 .key 的文件内容": "K93ac0f23", @@ -490,7 +547,6 @@ "集群": "Ke93d36ed", "修改配置": "K877985b7", "设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。": "Kdf66a675", - "异常": "K23a3bd72", "私有网络": "Ke1b1865", "公共网络": "K4786c57c", "管理地址": "Kf12b3034", @@ -501,14 +557,19 @@ "数据源": "K8fa58214", "设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9", "统计图表": "K1358acf", + "数据日志": "K17dc3a62", "地址(IP:端口)": "K62dabdf6", "组织(Organization)": "K2db12335", "添加策略": "K34d0d409", "输入名称、筛选条件查找": "Kbb4298ac", + "处理日志": "Ke429194e", + "脱敏前": "K8c34c02f", + "脱敏后": "K8e3d388d", "编辑策略": "Kc82b8374", "策略类型": "K4b34a5e5", "匹配条件": "K57f0fee8", "数据脱敏规则": "K10650c58", + "输入调用地址、消费者IP和消费者条件查找": "K84ffb1dd", "配置脱敏规则": "K1b34a9ab", "匹配值": "K26d22405", "脱敏类型": "K1546e1fe", @@ -520,7 +581,6 @@ "脱敏规则": "K4cd91d61", "自定义字符串; 值:": "K8dcad979", "起始位置:(0)位;长度:(1)位": "K82e3f7b7", - "编辑": "Kad207008", "已选择(0)项(1)数据": "K49dfc123", "所有(0)": "K8457ea34", "属性名称": "K7ca9a795", @@ -543,13 +603,9 @@ "请求协议": "K6bc47edb", "请求方式": "K1365fe45", "转发规则设置": "K90f3c02f", - "拦截": "Kb7df6ac1", - "放行": "K5c1722fe", - "路由详情": "K28435c5c", "只允许上传PNG、JPG或SVG格式的图片": "Ka9c08390", "服务名称": "K413b9869", "服务类型": "K9919285b", - "AI 服务": "Kd2c34e2c", "REST 服务": "K62840d62", "默认 AI 供应商": "Kcef64f4d", "创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API": "K300c89d4", @@ -604,8 +660,6 @@ "选择API": "Kcc8265e1", "路径": "Kc380335f", "请输入请求路径进行搜索": "K8aefc1e4", - "重置": "K50d471b2", - "查询": "Kee8ae330", "导出": "Ka2c794a2", "退出全屏": "Kaf70c3b", "(0)调用详情": "Kd22841a4", @@ -695,7 +749,6 @@ "修改": "K9cbe1e0", "访问授权": "Kb6e9328f", "添加授权": "Kd23d1716", - "永不过期": "K9dfa2c97", "到期时间": "Kfa920c0", "订阅的服务": "Kcce1af60", "审核详情": "Kfefa9b58", @@ -710,7 +763,6 @@ "创建并管理自己的消费者实体,每个消费者可以订阅多个API服务,确保在调用之前已获得相应权限。你可以为消费者生成 API 密钥等鉴权方式,用于安全地调用 API 服务": "K5c4e2865", "订阅的服务数量:已通过 (0) 个,申请中 (1) 个": "K3c7b175f", "输入名称、ID 查找消费者": "K3a6f905d", - "退出测试": "Kbe3e9335", "服务市场": "K370a3eb2", "服务详情": "Kf7ec36d", "申请服务": "K58ca9485", diff --git a/frontend/packages/common/src/locales/scan/en-US.json b/frontend/packages/common/src/locales/scan/en-US.json index 1863cc04..c70caa9c 100644 --- a/frontend/packages/common/src/locales/scan/en-US.json +++ b/frontend/packages/common/src/locales/scan/en-US.json @@ -704,64 +704,109 @@ "Kbdec9fa": "Filter Criteria", "Kbcbb7391": "Processed Count", "Kad207008": "Edit", - "K630c9e6d": "APIPark", - "Ka3e9f580": "Release Name", - "Kb2480682": "Policy List", - "K118d8d74": "Data Format", - "Kfe7c7d2d": "Keyword", - "K2f57a694": "Regular Expression", - "K8953e0a6": "Mobile Number", - "K6f86a038": "ID Card Number", - "K7954e7c8": "Bank Card Number", - "K320fdb17": "Amount", - "K7867acda": "Date", - "K7d327ae8": "Partial Display", - "Kfbf38e3c": "Partial Masking", - "Kd8c1fbb0": "Truncate", - "K89829921": "Replace", - "K480a7165": "Shuffle", - "Kea0d69df": "Random String", - "Ke7c84d1d": "Custom String", - "K49731763": "Please enter IP address or CIDR range, each separated by a newline.", - "K83237c89": "The entered IP or CIDR does not meet the format.", - "K5ae2c87a": "Please enter the correct path, such as /usr/* or */usr/*.", - "Kc82b8374": "Edit Policy", - "K4b34a5e5": "Policy Type", - "K57f0fee8": "Match Conditions", - "K10650c58": "Data Masking Rules", - "K1b34a9ab": "Configure Masking Rules", - "K26d22405": "Match Value", - "K1546e1fe": "Masking Type", - "K9b9b0629": "Starting Position", - "K52c84fe1": "Length", - "Kde84409c": "Replace Type", - "K338653b4": "Replace Value", - "Kbaeed3b7": "JSON Path", - "K4cd91d61": "Masking Rules", - "K8dcad979": "Custom String; Value:", - "K82e3f7b7": "Starting Position: (0); Length: (1)", - "K49dfc123": "Selected (0) items (1) data", - "K8457ea34": "All (0)", - "K7ca9a795": "Attribute Name", - "Kc4391744": "Attribute Value", - "K678e13fc": "Configure (0)", - "K508d8bf4": "Integration Address", - "K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms", - "K1da86266": "Invalid", - "K3a34d49b": "Pending Update", - "Kd2850420": "Pending Deletion", - "K9ada4366": "Operation successful, the page will refresh shortly", - "K9b332ab1": "Request prefix", - "K3d78d483": "HTTP headers", - "K17dc3a62": "Data logs", - "Ke429194e": "Processing logs", - "K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search", - "Kb147fabc": "Create", - "K40ca4f2": "Update", - "K3e7aa0ad": "Content", - "K2f5fdf5e": "Call Address", - "K1bc5e0a3": "Consumer IP", - "K6f39ea21": "Authentication Name", - "K8c34c02f": "Before Masking", - "K8e3d388d": "After Masking" + "K630c9e6d": "APIPark", + "Ka3e9f580": "Release Name", + "Kb2480682": "Policy List", + "K118d8d74": "Data Format", + "Kfe7c7d2d": "Keyword", + "K2f57a694": "Regular Expression", + "K8953e0a6": "Mobile Number", + "K6f86a038": "ID Card Number", + "K7954e7c8": "Bank Card Number", + "K320fdb17": "Amount", + "K7867acda": "Date", + "K7d327ae8": "Partial Display", + "Kfbf38e3c": "Partial Masking", + "Kd8c1fbb0": "Truncate", + "K89829921": "Replace", + "K480a7165": "Shuffle", + "Kea0d69df": "Random String", + "Ke7c84d1d": "Custom String", + "K49731763": "Please enter IP address or CIDR range, each separated by a newline.", + "K83237c89": "The entered IP or CIDR does not meet the format.", + "K5ae2c87a": "Please enter the correct path, such as /usr/* or */usr/*.", + "Kc82b8374": "Edit Policy", + "K4b34a5e5": "Policy Type", + "K57f0fee8": "Match Conditions", + "K10650c58": "Data Masking Rules", + "K1b34a9ab": "Configure Masking Rules", + "K26d22405": "Match Value", + "K1546e1fe": "Masking Type", + "K9b9b0629": "Starting Position", + "K52c84fe1": "Length", + "Kde84409c": "Replace Type", + "K338653b4": "Replace Value", + "Kbaeed3b7": "JSON Path", + "K4cd91d61": "Masking Rules", + "K8dcad979": "Custom String; Value:", + "K82e3f7b7": "Starting Position: (0); Length: (1)", + "K49dfc123": "Selected (0) items (1) data", + "K8457ea34": "All (0)", + "K7ca9a795": "Attribute Name", + "Kc4391744": "Attribute Value", + "K678e13fc": "Configure (0)", + "K508d8bf4": "Integration Address", + "K67f4e9bb": "The domain name for obtaining API market documentation information when integrating with external platforms", + "K1da86266": "Invalid", + "K3a34d49b": "Pending Update", + "Kd2850420": "Pending Deletion", + "K9ada4366": "Operation successful, the page will refresh shortly", + "K9b332ab1": "Request prefix", + "K3d78d483": "HTTP headers", + "K17dc3a62": "Data logs", + "Ke429194e": "Processing logs", + "K84ffb1dd": "Enter the invocation address, consumer IP, and consumer condition to search", + "Kb147fabc": "Create", + "K40ca4f2": "Update", + "K3e7aa0ad": "Content", + "K2f5fdf5e": "Call Address", + "K1bc5e0a3": "Consumer IP", + "K6f39ea21": "Authentication Name", + "K8c34c02f": "Before Masking", + "K8e3d388d": "After Masking", + "K94b48734": "Failed to get AI providers", + "Kf23a8988": "AI Provider", + "K4d81a657": "Preview", + "K91144ebd": "AI API List", + "Kf8187c33": "Please enter API URL to search", + "Kee4139c2": "Intercept Interface", + "K3e38ea": "After enabling interception, the gateway will intercept all requests on this path.", + "Kd752a3a8": "AI Model Not Configured", + "K8b7ac871": "Go to Settings", + "Kf97448b3": "Configured", + "K30d4d8df": "Not Configured", + "Kc2ee5223": "Default Model", + "K608af899": "Load Priority", + "K65b7a96": "Load priority determines which provider to use first when the original provider is abnormal or disabled. The smaller the priority number, the higher the priority.", + "K9eccff16": "Priority must be greater than 0", + "Kfcf02780": "Please enter priority", + "K5c6dcf58": "API Key (Default Key)", + "K59bf8ed9": "LLM Status Management", + "Kedd64e4d": "Disable", + "K2a3aeb8d": "Default:", + "K8b88ef63": "Please enter APIKey", + "Kcbd30819": "API Key", + "Kcb6e2d3e": "Please fill in APIKey", + "Ke13e332a": "Set expiration time", + "K409aa8ba": "Please select expiration time", + "Kba69594c": "Overage", + "Kb9e7ceda": "Expired", + "Kac405b50": "Error", + "K3fde5b49": "Please select status", + "K434b7e76": "Edit APIKey", + "K28190dbc": "Deleted successfully", + "Kb5fcf5b8": "Disabled successfully", + "K5940d788": "Enabled successfully", + "K8743bccd": "Sorted successfully", + "K19590c2c": "Call Priority", + "K89f135a7": "Used Token", + "K1acc30b2": "Edit Time", + "Kefb03657": "APIKey Resource Pool", + "Kc0352e64": "Supports creating multiple APIKeys under a single API model provider for intelligent load balancing", + "Kd25acba1": "Please enter name to search", + "K6d0388a0": "Add APIKey", + "Ke32702ac": "After saving, the supplier status will become [Disabled]. APIs using this supplier will temporarily use the normal supplier with the highest load priority.", + "Ka08c28d4": "After saving, the supplier status will become [Normal], restoring the AI capabilities of this supplier.", + "Kab8fe398": "Current Call Status:", + "K4880fd04": "Add (0) APIKey" } diff --git a/frontend/packages/common/src/locales/scan/ja-JP.json b/frontend/packages/common/src/locales/scan/ja-JP.json index bd1ab3fc..efe1d341 100644 --- a/frontend/packages/common/src/locales/scan/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/ja-JP.json @@ -1,583 +1,583 @@ { - "Kc0e5ef9f": "Workspace", - "K4de11e23": "Home", - "Kfe93ef35": "Application", - "Kb58e0c3f": "Service", - "Kc9e489f5": "Team", - "K61c89f5f": "API Portal", - "K16d71239": "解析", - "K714c192d": "呼び出し統計", - "Kd57dfe97": "トポロジー図", - "K3fe97dcc": "設定", - "Kecbb0e45": "システム", - "Ka358e23d": "一般", - "K449058e9": "API ゲートウェイ", - "K99935e6f": "AI モデル", - "K1deaa2dd": "ユーザー", - "K80a560a1": "Account", - "Kf644225f": "Role", - "K4057391a": "統合", - "K8fa58214": "データソース", - "K481e8a05": "SSL 証明書", - "Kca53edd0": "ログ", - "Kb283e720": "リソース", - "K631d646f": "Open API", - "K6535ff9c": "Account 設定", - "Kf15499b4": "ログアウト", - "Kabbd6e6": "Docs", - "K1196b104": "APIPark", - "K1f42de3": "HTTP ステータスコード", - "K4770dff4": "システムステータスコード", - "Kf89e58f1": "説明", - "K9e53c664": "送信", - "Kf8e7294c": "前へ", - "Ka0451c97": "キャンセル", - "Kb1dedda3": "閉じる", - "Kb2fc7600": "設定を追加", - "K4e07217d": "設定を編集", - "K4ea968fe": "編集 (0)", - "Ka7aaaeb": "追加 (0)", - "Kaff78ecf": "Keyを入力してください", - "K65d46535": "Valueを入力してください", - "Kc14b2ea3": "戻る", - "K11d3633a": "ID", - "Kbff43de3": "名前", - "K16ca79ef": "ドライバー", - "K7a369eef": "公開済み", - "Kcfa1a4d2": "オフライン", - "K771dc3b7": "オンライン", - "K530f5951": "表示", - "Kecbd7449": "削除", - "K1cbe2507": "確認", - "K48325b6": "検索 (0) 名前", - "Kc6340091": "コンテキスト", - "K74ecb1fa": "検索内容", - "K79f2e2f9": "セッション履歴", - "K3a8912ee": "変数を追加", - "Kb291a19": "ツールを追加", - "K27ece71d": "AI モデルの呼び出しはデフォルトで Query 変数を使用します。「{key}」を入力して新しい変数を追加してください。", - "Kdeed8399": "固定の上流サーバー", - "K4ee62e8": "この API が欠けています (0) (1) (2) 先に補充してください", - "K385591f3": "フォワード情報", - "K68415c14": "ドキュメント情報", - "K133b75e9": "上流情報", - "K43fcaf94": "成功", - "Kc71c6a9": "オンラインに失敗しました", - "K56c686f8": "失敗", - "K1ff96ff": "システムを申請する", - "K9bf855d6": "所属チーム", - "K11b994ed": "申請者", - "K939baba7": "申請日時", - "Kdab2e63b": "バージョン番号", - "K8b29c460": "バージョン説明", - "K4758140d": "ルートリスト", - "K54e44357": "上流リスト", - "Kb8e8e6f5": "備考", - "K7e52ffa3": "オンライン状況", - "K1ab0ae5b": "申請理由", - "K53c00c3c": "レビューコメント", - "Kfd50704d": "権限がありません (0)、管理者にお問い合わせください。", - "K7edf331d": "時間", - "Kef45b208": "1時間", - "K9dbf22b8": "24時間", - "K820fbfab": "3日", - "Kd6d28fc": "7日", - "Ke00c858c": "ファイルをアップロード", - "K6d9dd1f5": "ファイルを置き換え", - "K71753476": "許可するかどうか", - "K597435c5": "監視", - "Ke66a17dd": "必須", - "K28b68036": "不正な文字です。英語のみサポートしています。", - "K6206e4ad": "OpenAPI ドキュメント (.json/.yaml) をアップロード", - "Kfba46e6d": "OpenAPI ドキュメント (.json/.yaml) を置き換え", - "Kdac8ce7e": "OpenAPI YAML エディターを開く", - "Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます", - "K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます", - "Kbfe02d7f": "永久", - "K1e9c479e": "いいえ", - "Kaddfcb6b": "はい", - "K23fda291": "操作権限がありません。管理者にお問い合わせください。", - "K4618cb0a": "WeChat ミニプログラム", - "Ka854f511": "ファイルを取得、パスを入力してください", - "Kaa11a695": "非 HTTP/HTTPS コードサンプルの生成はサポートされていません", - "Kbe46924e": "プログラミング言語を検索...", - "Ke8e4f258": "プログラミング言語", - "K29c07a47": "成功例", - "K1f5c814d": "失敗例", - "K4ef022d7": "デフォルト text/html;charset=UTF-8", - "Kd061b5bf": "サンプルが未入力です", - "Kc14cec33": "バイナリ", - "K48b4d9e3": "リクエスト Header", - "Kcd347eaf": "リクエスト Body", - "K9e100bfe": "リクエスト Query", - "K3e9f12fd": "リクエスト REST", - "K2bfa290c": "API リクエストエディター", - "Kb36d111a": "レスポンス Header", - "K980bde79": "レスポンス Body", - "Kb04d201a": "その他の設定", - "Kee74f5b4": "子パラメーターを追加", - "Kc7d3106c": "行を追加", - "Keaabd222": "タグ", - "K8ad2c50e": "Parameter Name", - "K67d68dd1": "Type", - "K29245f47": "必須", - "Ke32cbcd3": "例", - "Kc13936c6": "URL または cURL を入力", - "Ka1ede006": "HTTP", - "K152ac44e": "パラメーター位置", - "K1660ae72": "マッチタイプ", - "K91ced765": "パラメーター値", - "K5b265628": "操作タイプ", - "K1826982d": "新規作成または変更", - "Kd65b55f5": "パラメーター値を一致させる", - "K15f35bf2": "上流パスを転送", - "K79dec0dd": "リクエストのタイムアウト時間", - "K7d465645": "上流サービスをバインド", - "K63a6404d": "リトライ時間", - "K47740727": "上流リクエスト Header を転送", - "K2b605d42": "詳細", - "K1df9fbd5": "インポート", - "K5e85df18": "インポート形式", - "K9eaf7885": "すべて置き換え", - "Kf8c3a80b": "最後に挿入", - "Kd96b2d7d": "インクリメンタル更新", - "Kf2fc08eb": "リクエスト Header", - "Ka45f1d8": "リクエスト Rest パラメーター", - "K94bb113a": "サイズ", - "K359919b5": "ファイルとして保存", - "K38bf1b90": "レスポンス", - "K59f4186e": "レスポンス Header", - "K5f1e23fd": "本文", - "Kf404ef7d": "送信 (Enter)", - "K2dbfd648": "中止", - "Kacabc771": "秒", - "K13ae6a93": "コピー", - "Ke54a14a3": "フォーマット", - "K43934f6d": "検索", - "K741decac": "置換", - "Kd507abff": "確認", - "Kca2d1624": "(0) 負数にできません。", - "K792b255a": "(0) (1) 以上でなければなりません。", - "Kf0bed26d": "列挙値", - "K633a03ca": "列挙", - "Kd2766caf": "最小長", - "Kd6d52485": "最大長", - "Kea15f66c": "最小値", - "K1af340ff": "最大値", - "K68691e16": "ここにファイルをドラッグするか、ファイルを選択してアップロードします", - "Kcec46ae": "ファイルをアップロード", - "K760fb044": "選択されたファイル", - "Kea2bdee0": "API 名を入力してください", - "K49053438": "詳細な説明", - "K148f6fa4": "高度な一致", - "K3ae4c789": "フォワード設定", - "K2f4d0a37": "リクエストパラメーター", - "Kde2d6dbd": "戻り値サンプル", - "K70e6069c": "API のテスト", - "Ke4603448": "リクエスト Header", - "K89fd86b3": "リクエスト Body", - "K8747e3c4": "リクエストサンプルコード", - "K8613e6e7": "レスポンスサンプル", - "Kab1c2159": "レスポンス Header", - "Kd2be51d1": "レスポンス Body", - "K2a3f24ac": "デフォルトワークスペース", - "K7e1ab4b0": "まで", - "Kf1b166e7": "詳細", - "K28555332": "// が含まれる URL はサポートされていません", - "K71661ee8": "必須項目", - "Kcbee3f8": "有効なメールアドレスではありません", - "K617f34f1": "更新者", - "K6ebca204": "更新日時", - "Kabfe9512": "保存", - "K51d1eb5d": "API", - "Ka2b6d281": "API ドキュメント", - "Kdefa9caa": "説明ドキュメント", - "K36856e71": "公開", - "K6382bbfd": "サブスクリプション管理", - "K31af5b99": "サブスクリプションレビュー", - "Ka97bd9e5": "サブスクライバー管理", - "K5974bf24": "管理", - "K3fa5c4c3": "トポロジー図を呼び出す", - "Kb5c7b82d": "設定", - "K1e84ad04": "Service ID", - "K39ab0358": "サブスクライバーを新規作成", - "K18307d56": "手動追加", - "K705fe9f5": "サブスクリプション申請", - "K3a67ea90": "サブスクライバー", - "Kefa2a4cf": "API 設定", - "K66060758": "API 名", - "K5582ac8": "リクエストパス", - "K2bb86fb4": "プロンプト", - "K13ffbe88": "変数", - "K79c8cfaf": "インターフェース説明", - "K469e475a": "リトライ回数", - "K8a35059b": "モデル設定", - "Kf9dcef3a": "API", - "K6134bbe8": "API を追加", - "Kf85b83a0": "URL を入力して検索", - "Kcf9f90b8": "モデルプロバイダー", - "Kfede1c7c": "モデル", - "Ke99513a0": "パラメーター", - "Kb595f40": "レビュー", - "K54e27f57": "承認", - "K8582af3f": "拒否", - "Kd568e15c": "公開結果", - "K35f990b0": "詳細を見る", - "Kdbc1f6cb": "公開を申請する", - "Kb6860a3f": "公開をロールバック", - "Ka3494f4b": "公開をロールバックしますか?", - "Kb397a99f": "申請を撤回", - "K7d401c0f": "申請を撤回しますか?", - "Ke1b79b93": "公開を停止", - "Ka2449180": "公開を停止しますか?", - "K2cb02f38": "新しいバージョンを作成", - "Kb3e34847": "(0) から API KEY を取得", - "K66a7d24c": "設定済み", - "Kaf074220": "未設定", - "Kd9a46c29": "デフォルト", - "K7ac2be34": "AI モデル管理", - "K2260837a": "AI モデルを設定したら、そのモデルを使って AI サービスを作成できます", - "K18dccc1a": "最新モデルを同期", - "K35612f29": "レビュー中", - "K47eaafde": "承認済み", - "K56b4254f": "公開申請", - "Kea2f9279": "API 呼び出し先", - "K7fc496a1": "API Base URL は、通常 API ゲートウェイの外部ネットワークアクセスアドレス、または API ゲートウェイにバインドされたドメインを設定します。", - "K8ab0fc95": "一般設定", - "Kb66fec9d": "API リクエスト設定", - "K4de0af74": "サービスカテゴリ", - "Kb4ceecea": "サブカテゴリを追加", - "K67479e88": "カテゴリ名を変更", - "K2bc75e2c": "カテゴリを追加", - "Kab4aab44": "カテゴリをリネーム", - "Ke595a20a": "カテゴリ名", - "K9679728f": "親カテゴリ ID", - "K9b2d08fd": "サブカテゴリ名", - "K71671763": "AI への簡単な接続", - "Ka8a5ec5": "AI モデルを設定", - "K10d7e99f": "APIPark を使用して、さまざまな AI モデルに迅速に接続し、統一された形式で API を呼び出し、モデルを自由に切り替えることができます。", - "Kc057704a": "AI サービスと API を作成", - "K76bb4a09": "AI タイプのサービスを作成し、プロンプトを API に設定して、AI を簡単に利用できます。", - "K71b2c70f": "トークンを作成", - "K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。", - "Kc5738b6c": "呼び出し", - "Kd6d7ca1f": "これでトークンを使用してこれらの API を呼び出すことができます。", - "K86cf95f": "REST API への簡単な接続", - "K7a3a8417": "REST サービスと API を作成", - "K4a84214e": "API 呼び出しの統計", - "K297d8563": "ダッシュボードでは、API の動作を把握するための多くの統計グラフが提供されています。", - "K2cdbb773": "コア機能", - "K3378c50d": "アカウントと役割", - "Kda5bb930": "チームメンバーを APIPark に招待して、API を一緒に管理および呼び出すことができます。", - "Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。", - "Kd5be0cd7": "サービスには一連の API が含まれており、他のチームが使用できるように API マーケットに公開できます。", - "K62e89ee7": "権限管理", - "K8f7808e6": "サービスのサブスクリプション", - "Kf2410413": "あるサービスの API を呼び出すには、まずそのサービスにサブスクライブし、提供チームの承認を待ってから API リクエストを発行できます。", - "K6c2e44b8": "サブスクリプションをレビュー", - "Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。", - "K3453272": "APIPark は詳細な API 呼び出しログを提供し、企業が API の運用状況を監視、分析、監査するのに役立ちます。", - "Kd518ba3e": "こんにちは!APIPark へようこそ", - "K7e04ea16": "🦄 APIPark は、100 以上の AI モデルに簡単に接続できるオープンソースの AI ゲートウェイと API 開発者ポータルです。AI モデルとプロンプトを新しい API に組み合わせ、すべての AI リクエストのデータ形式を統一し、AI モデルを切り替えたりプロンプトを調整したりしても、アプリケーションやマイクロサービスに影響を与えません。また、APIPark の開発者ポータルを使用してチーム内で API を共有し、アプリケーションを管理し、API のセキュリティを確保し、AI API の使用状況を監視するための明確なグラフを提供します。", - "Kedd41c18": "✨ APIParkが好きなら、Githubでスターや製品フィードバックの意見を提供してください。", - "Kef02fd87": "クイックスタート", - "K43a3b38d": "APIPark をすばやく理解するためのいくつかのタスクを提供しています", - "K408bfcf1": "高度なチュートリアル", - "K1afaf20e": "APIPark が API と AI をどのようによりよく管理するかを学ぶ", - "K48f7e21f": "その他の機能を学ぶ", - "K698296e2": "このチュートリアルを非表示", - "Kd2c1a316": "ログイン", - "Kf076f63c": "Account を入力してください", - "K25c895d5": "パスワードを入力してください", - "K551b0348": "パスワード", - "K192b3e38": "ゲストモード", - "K91aa4801": "ゲストモードではすべてのページと機能を表示できますが、データの編集はできません。ゲストモードは製品の機能を理解するためにのみ使用され、正式な製品ではこの機能を無効にすることができます。", - "K480045ce": "バージョン (0)-(1)", - "Kadee8e49": "ログ設定", - "K2724314b": "企業が API の運用状況を監視、分析、監査するのに役立つ詳細な API 呼び出しログを提供します。", - "K33c76dbc": "部門名", - "K84829ca9": "親部門 ID", - "K4d7fc74b": "子部門名", - "Keb9fcdad": "ユーザー名", - "Kc654b275": "メール", - "Kbe2ecc69": "部門", - "Ka16e6c44": "無効または無効なメンバーは部門に参加できません", - "Ked03ba97": "メンバーを新しい部門に追加してください", - "K184d3473": "Account を追加", - "K1ecb35f2": "メンバー情報を編集", - "Ke6f00b44": "部門に参加", - "K501cb1e7": "この操作は元に戻せません。メンバーを削除しますか?", - "Kf20863b5": "メンバーと部門", - "K52c8a730": "有効", - "K718c9310": "無効", - "K5f27a546": "ユーザー名またはメールでメンバーを検索", - "K7c97c5df": "削除", - "K1362a512": "無効にする", - "K6e1289b1": "有効にする", - "K1f4b5385": "削除", - "K26c698bb": "部門を追加", - "Kb9cf2a7d": "子部門を追加", - "Kc83551f5": "名前を変更", - "K5cfdd950": "このデータを削除すると、復元できません。削除しますか?", - "K74aef1ad": "メンバー", - "K3f1077c9": "メンバーの役割を設定し、メンバーは役割権限の範囲内でのみ機能とデータを表示できます。", - "Kdce62a6": "部門を検索", - "Ka46b9b24": "データソースタイプ", - "Kbb0cdcd0": "データソースアドレス", - "Kd9dfb884": "組織", - "K3e770a75": "認証トークン", - "K8ef69ee2": "秘密鍵", - "Kba3507d6": "秘密鍵をアップロード", - "K93ac0f23": "秘密鍵ファイルの拡張子は通常 .key です", - "K7cdd1331": "証明書をアップロード", - "K6d91905d": "証明書ファイルの拡張子は通常 .crt または .pem です", - "Kd0f6ded7": "証明書を追加", - "Ke5732d60": "証明書を編集", - "K3ca07a70": "証明書管理", - "Kdb927f83": "API サービスの SSL 証明書を設定および管理することで、企業はデータ転送を暗号化し、機密情報の漏洩を防止できます。", - "Ke93d36ed": "クラスター", - "K877985b7": "設定を変更", - "Kdf66a675": "クラスターにアクセスするための設定を行い、API が分散環境で安定して動作できるようにします。ビジネスニーズに応じて柔軟に拡張および最適化することが可能です。", - "Ke039b9b5": "正常", - "K23a3bd72": "異常", - "Kf12b3034": "管理アドレス", - "K867e6faf": "サービスアドレス", - "K2a49373f": "同期アドレス", - "K5878440c": "クラスターアドレス", - "K5e9022f8": "次へ", - "Kdbafd6f9": "監視レポートのデータソースを設定し、設定が完了すると API 呼び出し統計グラフが表示されます。", - "K1358acf": "統計グラフ", - "K62dabdf6": "アドレス(IP:ポート)", - "K2db12335": "組織", - "K8e7a0f80": "リソース設定", - "K95c3fd8b": "役割の権限範囲を設定します。", - "K138facd3": "システムロール", - "K6eac768d": "Role を追加", - "Kb9c2cf02": "チームロール", - "K2a16c93b": "単位: ms、最小値: 1", - "Ka945cfb1": "API 設定", - "K2e050340": "API 基本情報", - "Kba92c499": "インターセプター", - "Kde9d6e8e": "インターセプトが有効になると、このパスのすべてのリクエストがインターセプトされます。", - "K6bc47edb": "リクエストプロトコル", - "K1365fe45": "リクエストメソッド", - "K90f3c02f": "フォワードルール設定", - "Kb7df6ac1": "インターセプト", - "K5c1722fe": "許可", - "K28435c5c": "API 詳細", - "Ka9c08390": "PNG、JPG、SVG 形式の画像のみをアップロードできます", - "K413b9869": "サービス名", - "K9919285b": "サービスタイプ", - "Kcef64f4d": "デフォルト AI プロバイダー", - "Kcab588a9": "AI モデルプロバイダーが設定されていません、", - "Kb9b56111": "今すぐ設定", - "Kcf756b7a": "API 呼び出しプレフィックス", - "K13edc043": "すべての API に適用されるプレフィックス(例: host/{service_name}/{api_path})、保存後は変更できません", - "Kf52a584d": "所属サービスカテゴリ", - "K72b21be5": "サービスをどのカテゴリに表示するかを設定", - "Kdc840242": "アイコン", - "K427a5bd5": "PNG、JPG、JPEG、SVG 形式の画像ファイルのみをサポートし、1KB を超えるファイルは圧縮されます", - "K44bc352d": "ロゴ", - "Kde6bae17": "サービスを削除", - "K885ea699": "削除操作は元に戻せません。慎重に操作してください!", - "Kda8d5ea1": "上流", - "K12f58863": "サービスは高性能な API ゲートウェイを提供し、複数の大規模 AI モデルにシームレスに接続できます。これにより、AI 機能を API にパッケージ化して呼び出すことができ、AI モデルの使用障壁が大幅に低減されます。同時に、API の作成、監視、アクセス制御など、包括的な API 管理機能も提供されており、開発者が効率的かつ安全に API サービスを開発および管理できるようにしています。", - "K2d6658ed": "サービスを追加", - "K7b8f623f": "名前、ID、所属チーム、責任者を入力してサービスを検索", - "Kad98e030": "上流タイプ", - "Kdd9b5008": "デフォルトのバックエンド IP アドレス", - "Kc9acdb25": "ロードバランシング", - "K632dba5c": "フォワードホスト", - "Kc1f08a63": "Rewrite Host", - "K628f6851": "タイムアウト時間", - "Kaff62621": "リトライ回数", - "Kf14d159b": "回", - "Kc41ca30e": "呼び出し頻度制限", - "K753e8aeb": "回/秒", - "K813e1c0a": "チーム名", - "K692f5aa6": "チーム ID", - "K5de0bc2": "チーム ID(team_id)はチームの検索に使用でき、保存後は変更できません。", - "Ka63dd985": "チーム責任者", - "Ka6bcd272": "責任者は、チーム内のチーム、サービス、メンバーを管理する権限を持ちます", - "Ka2012bdd": "チームを削除", - "Kbde1f3d": "サービスデータをクリアした後、削除できます", - "K395acc14": "メンバーを削除", - "Kec46a57f": "メンバーを追加", - "K48724410": "名前を入力して検索", - "Kb9052305": "ユーザー名またはメールを検索", - "K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。", - "K510cdd27": "チームを追加", - "K9244ae14": "名前、ID、責任者を入力してチームを検索", - "Kc7b24b4b": "チームを設定", - "Kecb51e2c": "旧パスワード", - "K8266bcf2": "新しいパスワード", - "Ka9aef039": "パスワードを確認", - "Kcf42dcda": "2回のパスワードが一致しません", - "Kf876a42d": "パスワードを変更", - "K8ed884f": "個人アカウントを管理", - "K9be8e1d7": "API 呼び出し統計", - "K521ab28e": "Service を選択", - "Kcc8265e1": "API を選択", - "Kc380335f": "パス", - "K8aefc1e4": "リクエストパスを入力して検索", - "K50d471b2": "リセット", - "Kee8ae330": "検索", - "Ka2c794a2": "エクスポート", - "Kaf70c3b": "フルスクリーン終了", - "Kd22841a4": "(0) 呼び出し詳細", - "K1512e983": "アプリケーション呼び出し統計", - "Kb4d2007f": "Application を選択", - "K8c7f2d2e": "呼び出しトレンド", - "K657c3452": "(0)-(1) 呼び出しトレンド", - "Kc04efb87": "呼び出し量の統計", - "Keb98266e": "総データとの比較に追加", - "K18c2ed46": "(0) 呼び出し量", - "Kc3741830": "(0) 成功率", - "Ka6aa5863": "リクエスト数", - "K9eaef42": "成功率", - "K7082a4af": "フォワード数", - "K1ce386fb": "成功率", - "K87d6877e": "4xx", - "K4c8a54db": "5xx", - "Kd566283e": "トレンド", - "K21ad4a6a": "(0) メッセージ", - "Kd23a0be6": "リクエストメッセージ", - "Kec3e8361": "レスポンスメッセージ", - "Ke6250744": "4XX 数", - "K2d79d4e1": "5XX 数", - "Kcf6553c6": "Service 呼び出し統計", - "Kffcfe375": "Service を選択", - "Ka65f739c": "呼び出し詳細", - "K89b7ac79": "API トップ10", - "Kc0915603": "Application トップ10", - "Kf90b54": "Service トップ10", - "Kfb26388": "リクエストデータがありません", - "Kc8cbd8f8": "リクエスト統計", - "K8dece48": "フォワード統計データがありません", - "K1ee32434": "フォワード統計", - "Kcd125e4d": "呼び出し量の統計データがありません", - "Kaa114e8b": "メッセージの統計データがありません", - "K3ad84406": "メッセージ統計", - "Kfa088d49": "クラスター設定と監視の開始", - "K3da3b9a0": "監視機能は、クラスター内の情報を管理するために使用されます。クラスターを設定し、監視情報を設定してから、現在のクラスターの監視状況を確認してください。", - "Kaddacfb": "クラスター", - "K4ac33975": "クラスターアドレスを設定して、監視システムがクラスターを正しく認識して接続できるようにします", - "Ke5ed9810": "クラスターを設定", - "K1a132228": "監視", - "K6af08c3c": "監視を設定", - "K4a1a14": "監視概要", - "K69741ea7": "Service 呼び出し", - "K9c8d9933": "API 呼び出し", - "K145e4941": "億", - "Ke6a935d": "万", - "Kd59290a2": "カテゴリまたはタグを検索", - "K6b75bdbc": "API データがありません", - "Kd8a7a689": "アプリケーションを検索または選択", - "K4b15d6f5": "申請理由", - "Kb7e869a4": "アプリケーション管理", - "Kb71b5a13": "認証タイプ", - "K4d1465ee": "ISS", - "K5dcd7ed8": "署名アルゴリズム", - "K5b0eedd3": "Secret", - "K44f4ffe1": "RSA 公開鍵", - "Kc5ecd7d9": "ユーザー名 JSONPath", - "K417d85cf": "検証フィールド", - "K3b82fe1d": "Base64 暗号化を使用するかどうか", - "K49b5f4a3": "AK", - "K31418470": "SK", - "Kbfeb5297": "API Key", - "K1a78e6f0": "有効期限", - "Ke64e43a": "認証情報を非表示", - "K5168eb63": "Application 名", - "K546e46f": "Application ID", - "K95764d1d": "Application を削除", - "K217cb125": "認証の詳細", - "K2bb63eca": "認証を追加", - "Kd74d69b7": "認証を編集", - "K9cbe1e0": "変更", - "Kb6e9328f": "アクセス許可", - "Kd23d1716": "アクセス許可を追加", - "K9dfa2c97": "期限なし", - "Kfa920c0": "有効期限", - "Kcce1af60": "サブスクライブされたサービス", - "Kfefa9b58": "レビューの詳細", - "K3118fdb0": "サブスクリプションをキャンセル", - "Ked811bb1": "サブスクリプションをキャンセルしますか?", - "K50c39a62": "サブスクリプション申請をキャンセル", - "K1856c229": "サブスクリプション申請をキャンセルしますか?", - "K66ea2f0": "Service を検索", - "K8adf7f8b": "レビュー中", - "K667bbbe7": "Application を追加", - "Ka4b45550": "サービス説明がありません", - "K3c7b175f": "サブスクライブサービス:承認済み (0)、レビュー中 (1)", - "Kbe3e9335": "テスト終了", - "K370a3eb2": "サービスマーケット", - "Kf7ec36d": "Service 詳細", - "K58ca9485": "サービスを申請", - "K59cdbec3": "サービス紹介", - "K4aa9ed2c": "申請", - "K6c060779": "サービス情報", - "K8723422e": "アプリケーションを接続", - "Kb97544cb": "サプライヤー", - "Kb32f0afe": "カテゴリ", - "K81634069": "バージョン", - "Keefda53d": "更新日時", - "K96a2f1c8": "タグなし", - "K72b0c0b3": "API 数量", - "K93d5a66e": "接続アプリケーション数", - "K96059c69": "関連タグ", - "K8b7c2592": "更新者", - "K32263abd": "OpenAPI を追加", - "K7829bb78": "OpenAPI を設定", - "Kcdf76005": "OpenAPI", - "Ke2601944": "サービス呼び出し", - "K8504bca8": "ズームイン", - "K693c1b41": "ズームアウト", - "K3d7465f7": "ファイルログ", - "Kc87167a0": "HTTP ログ", - "K54630fe8": "Kafka ログ", - "Kd5c3966e": "NSQ ログ", - "K2e3de2c1": "Syslog ログ", - "K48322168": "未割り当て", - "K98f247f9": "スーパー管理者", - "K9c8a571f": "チーム管理者", - "K929b485b": "運用管理者", - "K82cc5ec2": "一般メンバー", - "Ke41d7451": "読み取り専用メンバー", - "Kf99e8b66": "サービス管理者", - "Kda8db57a": "サービス開発者", - "K216a1ac7": "アプリケーション開発者", - "K27924db": "アプリケーション管理者", - "K8dc5c723": "ドライバ名", - "Kda249fe8": "リクエスト失敗数", - "Kcf2df651": "フォワード失敗数", - "K7e6a859d": "影響範囲", - "K3a008b34": "エントリを追加", - "Ke0599ef7": "アドレスを追加", - "K48d3b5c4": "ファイル名", - "Kafde0d2a": "ディレクトリ", - "Kfb2926ac": "ログ分割期間", - "Kd96c2c69": "単位: 日", - "Kc2b776fa": "出力形式", - "K7b7cdac2": "フォーマット設定", - "K2f59807a": "サーバーアドレス", - "Kb1cfa6e7": "Access ログ", - "K540488a8": "NSQD アドレスリスト", - "K8bc33a11": "認証 Secret", - "K1cd3002f": "ネットワークプロトコル", - "Kdfaa32c8": "ログレベル", - "Kc0408d9c": "1 行", - "Ke3db239d": "時間", - "K3509a9f8": "日", - "Kb3960e83": "未公開", - "K8bd1e18": "公開待ち", - "K225a6c43": "単位: s、最小値: 1", - "K593e0c7e": "レビュー不要", - "Ke2d747d9": "レビュー必要", - "Kc29dabf2": "Base URL", - "Kd55c6887": "レビュー", - "K300c89d4": "API 作成時にこのプロバイダーがデフォルトで選択されます。デフォルトプロバイダーを変更しても既存の API には影響しません。", - "Kefaf9956": "作成時間", + "Kc0e5ef9f": "ワークスペース", + "K4de11e23": "ホーム", + "Kfe93ef35": "アプリケーション", + "Kb58e0c3f": "サービス", + "Kc9e489f5": "チーム", + "K61c89f5f": "API ポータル", + "K16d71239": "解析", + "K714c192d": "呼び出し統計", + "Kd57dfe97": "トポロジー図", + "K3fe97dcc": "設定", + "Kecbb0e45": "システム", + "Ka358e23d": "一般", + "K449058e9": "API ゲートウェイ", + "K99935e6f": "AI モデル", + "K1deaa2dd": "ユーザー", + "K80a560a1": "アカウント", + "Kf644225f": "ロール", + "K4057391a": "統合", + "K8fa58214": "データソース", + "K481e8a05": "SSL 証明書", + "Kca53edd0": "ログ", + "Kb283e720": "リソース", + "K631d646f": "Open API", + "K6535ff9c": "Account 設定", + "Kf15499b4": "ログアウト", + "Kabbd6e6": "Docs", + "K1196b104": "APIPark", + "K1f42de3": "HTTP ステータスコード", + "K4770dff4": "システムステータスコード", + "Kf89e58f1": "説明", + "K9e53c664": "送信", + "Kf8e7294c": "前へ", + "Ka0451c97": "キャンセル", + "Kb1dedda3": "閉じる", + "Kb2fc7600": "設定を追加", + "K4e07217d": "設定を編集", + "K4ea968fe": "編集 (0)", + "Ka7aaaeb": "追加 (0)", + "Kaff78ecf": "Keyを入力してください", + "K65d46535": "Valueを入力してください", + "Kc14b2ea3": "戻る", + "K11d3633a": "ID", + "Kbff43de3": "名前", + "K16ca79ef": "ドライバー", + "K7a369eef": "公開済み", + "Kcfa1a4d2": "オフライン", + "K771dc3b7": "オンライン", + "K530f5951": "表示", + "Kecbd7449": "削除", + "K1cbe2507": "確認", + "K48325b6": "検索 (0) 名前", + "Kc6340091": "コンテキスト", + "K74ecb1fa": "検索内容", + "K79f2e2f9": "セッション履歴", + "K3a8912ee": "変数を追加", + "Kb291a19": "ツールを追加", + "K27ece71d": "AI モデルの呼び出しはデフォルトで Query 変数を使用します。「{key}」を入力して新しい変数を追加してください。", + "Kdeed8399": "固定の上流サーバー", + "K4ee62e8": "この API が欠けています (0) (1) (2) 先に補充してください", + "K385591f3": "フォワード情報", + "K68415c14": "ドキュメント情報", + "K133b75e9": "上流情報", + "K43fcaf94": "成功", + "Kc71c6a9": "オンラインに失敗しました", + "K56c686f8": "失敗", + "K1ff96ff": "システムを申請する", + "K9bf855d6": "所属チーム", + "K11b994ed": "申請者", + "K939baba7": "申請日時", + "Kdab2e63b": "バージョン番号", + "K8b29c460": "バージョン説明", + "K4758140d": "ルートリスト", + "K54e44357": "上流リスト", + "Kb8e8e6f5": "備考", + "K7e52ffa3": "オンライン状況", + "K1ab0ae5b": "申請理由", + "K53c00c3c": "レビューコメント", + "Kfd50704d": "権限がありません (0)、管理者にお問い合わせください。", + "K7edf331d": "時間", + "Kef45b208": "1時間", + "K9dbf22b8": "24時間", + "K820fbfab": "3日", + "Kd6d28fc": "7日", + "Ke00c858c": "ファイルをアップロード", + "K6d9dd1f5": "ファイルを置き換え", + "K71753476": "許可するかどうか", + "K597435c5": "監視", + "Ke66a17dd": "必須", + "K28b68036": "不正な文字です。英語のみサポートしています。", + "K6206e4ad": "OpenAPI ドキュメント (.json/.yaml) をアップロード", + "Kfba46e6d": "OpenAPI ドキュメント (.json/.yaml) を置き換え", + "Kdac8ce7e": "OpenAPI YAML エディターを開く", + "Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます", + "K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます", + "Kbfe02d7f": "永久", + "K1e9c479e": "いいえ", + "Kaddfcb6b": "はい", + "K23fda291": "操作権限がありません。管理者にお問い合わせください。", + "K4618cb0a": "WeChat ミニプログラム", + "Ka854f511": "ファイルを取得、パスを入力してください", + "Kaa11a695": "非 HTTP/HTTPS コードサンプルの生成はサポートされていません", + "Kbe46924e": "プログラミング言語を検索...", + "Ke8e4f258": "プログラミング言語", + "K29c07a47": "成功例", + "K1f5c814d": "失敗例", + "K4ef022d7": "デフォルト text/html;charset=UTF-8", + "Kd061b5bf": "サンプルが未入力です", + "Kc14cec33": "バイナリ", + "K48b4d9e3": "リクエスト Header", + "Kcd347eaf": "リクエスト Body", + "K9e100bfe": "リクエスト Query", + "K3e9f12fd": "リクエスト REST", + "K2bfa290c": "API リクエストエディター", + "Kb36d111a": "レスポンス Header", + "K980bde79": "レスポンス Body", + "Kb04d201a": "その他の設定", + "Kee74f5b4": "子パラメーターを追加", + "Kc7d3106c": "行を追加", + "Keaabd222": "タグ", + "K8ad2c50e": "Parameter Name", + "K67d68dd1": "Type", + "K29245f47": "必須", + "Ke32cbcd3": "例", + "Kc13936c6": "URL または cURL を入力", + "Ka1ede006": "HTTP", + "K152ac44e": "パラメーター位置", + "K1660ae72": "マッチタイプ", + "K91ced765": "パラメーター値", + "K5b265628": "操作タイプ", + "K1826982d": "新規作成または変更", + "Kd65b55f5": "パラメーター値を一致させる", + "K15f35bf2": "上流パスを転送", + "K79dec0dd": "リクエストのタイムアウト時間", + "K7d465645": "上流サービスをバインド", + "K63a6404d": "リトライ時間", + "K47740727": "上流リクエスト Header を転送", + "K2b605d42": "詳細", + "K1df9fbd5": "インポート", + "K5e85df18": "インポート形式", + "K9eaf7885": "すべて置き換え", + "Kf8c3a80b": "最後に挿入", + "Kd96b2d7d": "インクリメンタル更新", + "Kf2fc08eb": "リクエスト Header", + "Ka45f1d8": "リクエスト Rest パラメーター", + "K94bb113a": "サイズ", + "K359919b5": "ファイルとして保存", + "K38bf1b90": "レスポンス", + "K59f4186e": "レスポンス Header", + "K5f1e23fd": "本文", + "Kf404ef7d": "送信 (Enter)", + "K2dbfd648": "中止", + "Kacabc771": "秒", + "K13ae6a93": "コピー", + "Ke54a14a3": "フォーマット", + "K43934f6d": "検索", + "K741decac": "置換", + "Kd507abff": "確認", + "Kca2d1624": "(0) 負数にできません。", + "K792b255a": "(0) (1) 以上でなければなりません。", + "Kf0bed26d": "列挙値", + "K633a03ca": "列挙", + "Kd2766caf": "最小長", + "Kd6d52485": "最大長", + "Kea15f66c": "最小値", + "K1af340ff": "最大値", + "K68691e16": "ここにファイルをドラッグするか、ファイルを選択してアップロードします", + "Kcec46ae": "ファイルをアップロード", + "K760fb044": "選択されたファイル", + "Kea2bdee0": "API 名を入力してください", + "K49053438": "詳細な説明", + "K148f6fa4": "高度な一致", + "K3ae4c789": "フォワード設定", + "K2f4d0a37": "リクエストパラメーター", + "Kde2d6dbd": "戻り値サンプル", + "K70e6069c": "API のテスト", + "Ke4603448": "リクエスト Header", + "K89fd86b3": "リクエスト Body", + "K8747e3c4": "リクエストサンプルコード", + "K8613e6e7": "レスポンスサンプル", + "Kab1c2159": "レスポンス Header", + "Kd2be51d1": "レスポンス Body", + "K2a3f24ac": "デフォルトワークスペース", + "K7e1ab4b0": "まで", + "Kf1b166e7": "詳細", + "K28555332": "// が含まれる URL はサポートされていません", + "K71661ee8": "必須項目", + "Kcbee3f8": "有効なメールアドレスではありません", + "K617f34f1": "更新者", + "K6ebca204": "更新日時", + "Kabfe9512": "保存", + "K51d1eb5d": "API", + "Ka2b6d281": "API ドキュメント", + "Kdefa9caa": "説明ドキュメント", + "K36856e71": "公開", + "K6382bbfd": "サブスクリプション管理", + "K31af5b99": "サブスクリプションレビュー", + "Ka97bd9e5": "サブスクライバー管理", + "K5974bf24": "管理", + "K3fa5c4c3": "トポロジー図を呼び出す", + "Kb5c7b82d": "設定", + "K1e84ad04": "Service ID", + "K39ab0358": "サブスクライバーを新規作成", + "K18307d56": "手動追加", + "K705fe9f5": "サブスクリプション申請", + "K3a67ea90": "サブスクライバー", + "Kefa2a4cf": "API 設定", + "K66060758": "API 名", + "K5582ac8": "リクエストパス", + "K2bb86fb4": "プロンプト", + "K13ffbe88": "変数", + "K79c8cfaf": "インターフェース説明", + "K469e475a": "リトライ回数", + "K8a35059b": "モデル設定", + "Kf9dcef3a": "API", + "K6134bbe8": "API を追加", + "Kf85b83a0": "URL を入力して検索", + "Kcf9f90b8": "モデルプロバイダー", + "Kfede1c7c": "モデル", + "Ke99513a0": "パラメーター", + "Kb595f40": "レビュー", + "K54e27f57": "承認", + "K8582af3f": "拒否", + "Kd568e15c": "公開結果", + "K35f990b0": "詳細を見る", + "Kdbc1f6cb": "公開を申請する", + "Kb6860a3f": "公開をロールバック", + "Ka3494f4b": "公開をロールバックしますか?", + "Kb397a99f": "申請を撤回", + "K7d401c0f": "申請を撤回しますか?", + "Ke1b79b93": "公開を停止", + "Ka2449180": "公開を停止しますか?", + "K2cb02f38": "新しいバージョンを作成", + "Kb3e34847": "(0) から API KEY を取得", + "K66a7d24c": "設定済み", + "Kaf074220": "未設定", + "Kd9a46c29": "デフォルト", + "K7ac2be34": "AI モデル管理", + "K2260837a": "AI モデルを設定したら、そのモデルを使って AI サービスを作成できます", + "K18dccc1a": "最新モデルを同期", + "K35612f29": "レビュー中", + "K47eaafde": "承認済み", + "K56b4254f": "公開申請", + "Kea2f9279": "API 呼び出し先", + "K7fc496a1": "API Base URL は、通常 API ゲートウェイの外部ネットワークアクセスアドレス、または API ゲートウェイにバインドされたドメインを設定します。", + "K8ab0fc95": "一般設定", + "Kb66fec9d": "API リクエスト設定", + "K4de0af74": "サービスカテゴリ", + "Kb4ceecea": "サブカテゴリを追加", + "K67479e88": "カテゴリ名を変更", + "K2bc75e2c": "カテゴリを追加", + "Kab4aab44": "カテゴリをリネーム", + "Ke595a20a": "カテゴリ名", + "K9679728f": "親カテゴリ ID", + "K9b2d08fd": "サブカテゴリ名", + "K71671763": "AI への簡単な接続", + "Ka8a5ec5": "AI モデルを設定", + "K10d7e99f": "APIPark を使用して、さまざまな AI モデルに迅速に接続し、統一された形式で API を呼び出し、モデルを自由に切り替えることができます。", + "Kc057704a": "AI サービスと API を作成", + "K76bb4a09": "AI タイプのサービスを作成し、プロンプトを API に設定して、AI を簡単に利用できます。", + "K71b2c70f": "トークンを作成", + "K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。", + "Kc5738b6c": "呼び出し", + "Kd6d7ca1f": "これでトークンを使用してこれらの API を呼び出すことができます。", + "K86cf95f": "REST API への簡単な接続", + "K7a3a8417": "REST サービスと API を作成", + "K4a84214e": "API 呼び出しの統計", + "K297d8563": "ダッシュボードでは、API の動作を把握するための多くの統計グラフが提供されています。", + "K2cdbb773": "コア機能", + "K3378c50d": "アカウントと役割", + "Kda5bb930": "チームメンバーを APIPark に招待して、API を一緒に管理および呼び出すことができます。", + "Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。", + "Kd5be0cd7": "サービスには一連の API が含まれており、他のチームが使用できるように API マーケットに公開できます。", + "K62e89ee7": "権限管理", + "K8f7808e6": "サービスのサブスクリプション", + "Kf2410413": "あるサービスの API を呼び出すには、まずそのサービスにサブスクライブし、提供チームの承認を待ってから API リクエストを発行できます。", + "K6c2e44b8": "サブスクリプションをレビュー", + "Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。", + "K3453272": "APIPark は詳細な API 呼び出しログを提供し、企業が API の運用状況を監視、分析、監査するのに役立ちます。", + "Kd518ba3e": "こんにちは!APIPark へようこそ", + "K7e04ea16": "🦄 APIPark は、100 以上の AI モデルに簡単に接続できるオープンソースの AI ゲートウェイと API 開発者ポータルです。AI モデルとプロンプトを新しい API に組み合わせ、すべての AI リクエストのデータ形式を統一し、AI モデルを切り替えたりプロンプトを調整したりしても、アプリケーションやマイクロサービスに影響を与えません。また、APIPark の開発者ポータルを使用してチーム内で API を共有し、アプリケーションを管理し、API のセキュリティを確保し、AI API の使用状況を監視するための明確なグラフを提供します。", + "Kedd41c18": "✨ APIParkが好きなら、Githubでスターや製品フィードバックの意見を提供してください。", + "Kef02fd87": "クイックスタート", + "K43a3b38d": "APIPark をすばやく理解するためのいくつかのタスクを提供しています", + "K408bfcf1": "高度なチュートリアル", + "K1afaf20e": "APIPark が API と AI をどのようによりよく管理するかを学ぶ", + "K48f7e21f": "その他の機能を学ぶ", + "K698296e2": "このチュートリアルを非表示", + "Kd2c1a316": "ログイン", + "Kf076f63c": "Account を入力してください", + "K25c895d5": "パスワードを入力してください", + "K551b0348": "パスワード", + "K192b3e38": "ゲストモード", + "K91aa4801": "ゲストモードではすべてのページと機能を表示できますが、データの編集はできません。ゲストモードは製品の機能を理解するためにのみ使用され、正式な製品ではこの機能を無効にすることができます。", + "K480045ce": "バージョン (0)-(1)", + "Kadee8e49": "ログ設定", + "K2724314b": "企業が API の運用状況を監視、分析、監査するのに役立つ詳細な API 呼び出しログを提供します。", + "K33c76dbc": "部門名", + "K84829ca9": "親部門 ID", + "K4d7fc74b": "子部門名", + "Keb9fcdad": "ユーザー名", + "Kc654b275": "メール", + "Kbe2ecc69": "部門", + "Ka16e6c44": "無効または無効なメンバーは部門に参加できません", + "Ked03ba97": "メンバーを新しい部門に追加してください", + "K184d3473": "Account を追加", + "K1ecb35f2": "メンバー情報を編集", + "Ke6f00b44": "部門に参加", + "K501cb1e7": "この操作は元に戻せません。メンバーを削除しますか?", + "Kf20863b5": "メンバーと部門", + "K52c8a730": "有効", + "K718c9310": "無効", + "K5f27a546": "ユーザー名またはメールでメンバーを検索", + "K7c97c5df": "削除", + "K1362a512": "無効にする", + "K6e1289b1": "有効にする", + "K1f4b5385": "削除", + "K26c698bb": "部門を追加", + "Kb9cf2a7d": "子部門を追加", + "Kc83551f5": "名前を変更", + "K5cfdd950": "このデータを削除すると、復元できません。削除しますか?", + "K74aef1ad": "メンバー", + "K3f1077c9": "メンバーの役割を設定し、メンバーは役割権限の範囲内でのみ機能とデータを表示できます。", + "Kdce62a6": "部門を検索", + "Ka46b9b24": "データソースタイプ", + "Kbb0cdcd0": "データソースアドレス", + "Kd9dfb884": "組織", + "K3e770a75": "認証トークン", + "K8ef69ee2": "秘密鍵", + "Kba3507d6": "秘密鍵をアップロード", + "K93ac0f23": "秘密鍵ファイルの拡張子は通常 .key です", + "K7cdd1331": "証明書をアップロード", + "K6d91905d": "証明書ファイルの拡張子は通常 .crt または .pem です", + "Kd0f6ded7": "証明書を追加", + "Ke5732d60": "証明書を編集", + "K3ca07a70": "証明書管理", + "Kdb927f83": "API サービスの SSL 証明書を設定および管理することで、企業はデータ転送を暗号化し、機密情報の漏洩を防止できます。", + "Ke93d36ed": "クラスター", + "K877985b7": "設定を変更", + "Kdf66a675": "クラスターにアクセスするための設定を行い、API が分散環境で安定して動作できるようにします。ビジネスニーズに応じて柔軟に拡張および最適化することが可能です。", + "Ke039b9b5": "正常", + "K23a3bd72": "異常", + "Kf12b3034": "管理アドレス", + "K867e6faf": "サービスアドレス", + "K2a49373f": "同期アドレス", + "K5878440c": "クラスターアドレス", + "K5e9022f8": "次へ", + "Kdbafd6f9": "監視レポートのデータソースを設定し、設定が完了すると API 呼び出し統計グラフが表示されます。", + "K1358acf": "統計グラフ", + "K62dabdf6": "アドレス(IP:ポート)", + "K2db12335": "組織", + "K8e7a0f80": "リソース設定", + "K95c3fd8b": "役割の権限範囲を設定します。", + "K138facd3": "システムロール", + "K6eac768d": "Role を追加", + "Kb9c2cf02": "チームロール", + "K2a16c93b": "単位: ms、最小値: 1", + "Ka945cfb1": "API 設定", + "K2e050340": "API 基本情報", + "Kba92c499": "インターセプター", + "Kde9d6e8e": "インターセプトが有効になると、このパスのすべてのリクエストがインターセプトされます。", + "K6bc47edb": "リクエストプロトコル", + "K1365fe45": "リクエストメソッド", + "K90f3c02f": "フォワードルール設定", + "Kb7df6ac1": "インターセプト", + "K5c1722fe": "許可", + "K28435c5c": "API 詳細", + "Ka9c08390": "PNG、JPG、SVG 形式の画像のみをアップロードできます", + "K413b9869": "サービス名", + "K9919285b": "サービスタイプ", + "Kcef64f4d": "デフォルト AI プロバイダー", + "Kcab588a9": "AI モデルプロバイダーが設定されていません、", + "Kb9b56111": "今すぐ設定", + "Kcf756b7a": "API 呼び出しプレフィックス", + "K13edc043": "すべての API に適用されるプレフィックス(例: host/{service_name}/{api_path})、保存後は変更できません", + "Kf52a584d": "所属サービスカテゴリ", + "K72b21be5": "サービスをどのカテゴリに表示するかを設定", + "Kdc840242": "アイコン", + "K427a5bd5": "PNG、JPG、JPEG、SVG 形式の画像ファイルのみをサポートし、1KB を超えるファイルは圧縮されます", + "K44bc352d": "ロゴ", + "Kde6bae17": "サービスを削除", + "K885ea699": "削除操作は元に戻せません。慎重に操作してください!", + "Kda8d5ea1": "上流", + "K12f58863": "サービスは高性能な API ゲートウェイを提供し、複数の大規模 AI モデルにシームレスに接続できます。これにより、AI 機能を API にパッケージ化して呼び出すことができ、AI モデルの使用障壁が大幅に低減されます。同時に、API の作成、監視、アクセス制御など、包括的な API 管理機能も提供されており、開発者が効率的かつ安全に API サービスを開発および管理できるようにしています。", + "K2d6658ed": "サービスを追加", + "K7b8f623f": "名前、ID、所属チーム、責任者を入力してサービスを検索", + "Kad98e030": "上流タイプ", + "Kdd9b5008": "デフォルトのバックエンド IP アドレス", + "Kc9acdb25": "ロードバランシング", + "K632dba5c": "フォワードホスト", + "Kc1f08a63": "Rewrite Host", + "K628f6851": "タイムアウト時間", + "Kaff62621": "リトライ回数", + "Kf14d159b": "回", + "Kc41ca30e": "呼び出し頻度制限", + "K753e8aeb": "回/秒", + "K813e1c0a": "チーム名", + "K692f5aa6": "チーム ID", + "K5de0bc2": "チーム ID(team_id)はチームの検索に使用でき、保存後は変更できません。", + "Ka63dd985": "チーム責任者", + "Ka6bcd272": "責任者は、チーム内のチーム、サービス、メンバーを管理する権限を持ちます", + "Ka2012bdd": "チームを削除", + "Kbde1f3d": "サービスデータをクリアした後、削除できます", + "K395acc14": "メンバーを削除", + "Kec46a57f": "メンバーを追加", + "K48724410": "名前を入力して検索", + "Kb9052305": "ユーザー名またはメールを検索", + "K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。", + "K510cdd27": "チームを追加", + "K9244ae14": "名前、ID、責任者を入力してチームを検索", + "Kc7b24b4b": "チームを設定", + "Kecb51e2c": "旧パスワード", + "K8266bcf2": "新しいパスワード", + "Ka9aef039": "パスワードを確認", + "Kcf42dcda": "2回のパスワードが一致しません", + "Kf876a42d": "パスワードを変更", + "K8ed884f": "個人アカウントを管理", + "K9be8e1d7": "API 呼び出し統計", + "K521ab28e": "Service を選択", + "Kcc8265e1": "API を選択", + "Kc380335f": "パス", + "K8aefc1e4": "リクエストパスを入力して検索", + "K50d471b2": "リセット", + "Kee8ae330": "検索", + "Ka2c794a2": "エクスポート", + "Kaf70c3b": "フルスクリーン終了", + "Kd22841a4": "(0) 呼び出し詳細", + "K1512e983": "アプリケーション呼び出し統計", + "Kb4d2007f": "Application を選択", + "K8c7f2d2e": "呼び出しトレンド", + "K657c3452": "(0)-(1) 呼び出しトレンド", + "Kc04efb87": "呼び出し量の統計", + "Keb98266e": "総データとの比較に追加", + "K18c2ed46": "(0) 呼び出し量", + "Kc3741830": "(0) 成功率", + "Ka6aa5863": "リクエスト数", + "K9eaef42": "成功率", + "K7082a4af": "フォワード数", + "K1ce386fb": "成功率", + "K87d6877e": "4xx", + "K4c8a54db": "5xx", + "Kd566283e": "トレンド", + "K21ad4a6a": "(0) メッセージ", + "Kd23a0be6": "リクエストメッセージ", + "Kec3e8361": "レスポンスメッセージ", + "Ke6250744": "4XX 数", + "K2d79d4e1": "5XX 数", + "Kcf6553c6": "Service 呼び出し統計", + "Kffcfe375": "Service を選択", + "Ka65f739c": "呼び出し詳細", + "K89b7ac79": "API トップ10", + "Kc0915603": "Application トップ10", + "Kf90b54": "Service トップ10", + "Kfb26388": "リクエストデータがありません", + "Kc8cbd8f8": "リクエスト統計", + "K8dece48": "フォワード統計データがありません", + "K1ee32434": "フォワード統計", + "Kcd125e4d": "呼び出し量の統計データがありません", + "Kaa114e8b": "メッセージの統計データがありません", + "K3ad84406": "メッセージ統計", + "Kfa088d49": "クラスター設定と監視の開始", + "K3da3b9a0": "監視機能は、クラスター内の情報を管理するために使用されます。クラスターを設定し、監視情報を設定してから、現在のクラスターの監視状況を確認してください。", + "Kaddacfb": "クラスター", + "K4ac33975": "クラスターアドレスを設定して、監視システムがクラスターを正しく認識して接続できるようにします", + "Ke5ed9810": "クラスターを設定", + "K1a132228": "監視", + "K6af08c3c": "監視を設定", + "K4a1a14": "監視概要", + "K69741ea7": "Service 呼び出し", + "K9c8d9933": "API 呼び出し", + "K145e4941": "億", + "Ke6a935d": "万", + "Kd59290a2": "カテゴリまたはタグを検索", + "K6b75bdbc": "API データがありません", + "Kd8a7a689": "アプリケーションを検索または選択", + "K4b15d6f5": "申請理由", + "Kb7e869a4": "アプリケーション管理", + "Kb71b5a13": "認証タイプ", + "K4d1465ee": "ISS", + "K5dcd7ed8": "署名アルゴリズム", + "K5b0eedd3": "Secret", + "K44f4ffe1": "RSA 公開鍵", + "Kc5ecd7d9": "ユーザー名 JSONPath", + "K417d85cf": "検証フィールド", + "K3b82fe1d": "Base64 暗号化を使用するかどうか", + "K49b5f4a3": "AK", + "K31418470": "SK", + "Kbfeb5297": "API Key", + "K1a78e6f0": "有効期限", + "Ke64e43a": "認証情報を非表示", + "K5168eb63": "Application 名", + "K546e46f": "Application ID", + "K95764d1d": "Application を削除", + "K217cb125": "認証の詳細", + "K2bb63eca": "認証を追加", + "Kd74d69b7": "認証を編集", + "K9cbe1e0": "変更", + "Kb6e9328f": "アクセス許可", + "Kd23d1716": "アクセス許可を追加", + "K9dfa2c97": "期限なし", + "Kfa920c0": "有効期限", + "Kcce1af60": "サブスクライブされたサービス", + "Kfefa9b58": "レビューの詳細", + "K3118fdb0": "サブスクリプションをキャンセル", + "Ked811bb1": "サブスクリプションをキャンセルしますか?", + "K50c39a62": "サブスクリプション申請をキャンセル", + "K1856c229": "サブスクリプション申請をキャンセルしますか?", + "K66ea2f0": "Service を検索", + "K8adf7f8b": "レビュー中", + "K667bbbe7": "Application を追加", + "Ka4b45550": "サービス説明がありません", + "K3c7b175f": "サブスクライブサービス:承認済み (0)、レビュー中 (1)", + "Kbe3e9335": "テスト終了", + "K370a3eb2": "サービスマーケット", + "Kf7ec36d": "Service 詳細", + "K58ca9485": "サービスを申請", + "K59cdbec3": "サービス紹介", + "K4aa9ed2c": "申請", + "K6c060779": "サービス情報", + "K8723422e": "アプリケーションを接続", + "Kb97544cb": "サプライヤー", + "Kb32f0afe": "カテゴリ", + "K81634069": "バージョン", + "Keefda53d": "更新日時", + "K96a2f1c8": "タグなし", + "K72b0c0b3": "API 数量", + "K93d5a66e": "接続アプリケーション数", + "K96059c69": "関連タグ", + "K8b7c2592": "更新者", + "K32263abd": "OpenAPI を追加", + "K7829bb78": "OpenAPI を設定", + "Kcdf76005": "OpenAPI", + "Ke2601944": "サービス呼び出し", + "K8504bca8": "ズームイン", + "K693c1b41": "ズームアウト", + "K3d7465f7": "ファイルログ", + "Kc87167a0": "HTTP ログ", + "K54630fe8": "Kafka ログ", + "Kd5c3966e": "NSQ ログ", + "K2e3de2c1": "Syslog ログ", + "K48322168": "未割り当て", + "K98f247f9": "スーパー管理者", + "K9c8a571f": "チーム管理者", + "K929b485b": "運用管理者", + "K82cc5ec2": "一般メンバー", + "Ke41d7451": "読み取り専用メンバー", + "Kf99e8b66": "サービス管理者", + "Kda8db57a": "サービス開発者", + "K216a1ac7": "アプリケーション開発者", + "K27924db": "アプリケーション管理者", + "K8dc5c723": "ドライバ名", + "Kda249fe8": "リクエスト失敗数", + "Kcf2df651": "フォワード失敗数", + "K7e6a859d": "影響範囲", + "K3a008b34": "エントリを追加", + "Ke0599ef7": "アドレスを追加", + "K48d3b5c4": "ファイル名", + "Kafde0d2a": "ディレクトリ", + "Kfb2926ac": "ログ分割期間", + "Kd96c2c69": "単位: 日", + "Kc2b776fa": "出力形式", + "K7b7cdac2": "フォーマット設定", + "K2f59807a": "サーバーアドレス", + "Kb1cfa6e7": "Access ログ", + "K540488a8": "NSQD アドレスリスト", + "K8bc33a11": "認証 Secret", + "K1cd3002f": "ネットワークプロトコル", + "Kdfaa32c8": "ログレベル", + "Kc0408d9c": "1 行", + "Ke3db239d": "時間", + "K3509a9f8": "日", + "Kb3960e83": "未公開", + "K8bd1e18": "公開待ち", + "K225a6c43": "単位: s、最小値: 1", + "K593e0c7e": "レビュー不要", + "Ke2d747d9": "レビュー必要", + "Kc29dabf2": "Base URL", + "Kd55c6887": "レビュー", + "K300c89d4": "API 作成時にこのプロバイダーがデフォルトで選択されます。デフォルトプロバイダーを変更しても既存の API には影響しません。", + "Kefaf9956": "作成時間", "Kad1c674c": "プロトコル", "Kad01bc3e": "メソッド", "Ka29b346f": "アドレス(IP ポートまたはドメイン)", @@ -620,7 +620,7 @@ "K1cc07937": "有効期限", "K39686a7f": "先頭はアルファベットで、英数字とハイフン、アンダースコアの組み合わせをサポート", "Ka4ecfa40": "英数字またはアンダースコア、先頭は必ずアルファベット", - "K37318b68": "クラスタに接続できません。クラスタアドレスが正しいか、ファイアウォール設定を確認してください。", + "K37318b68": "クラスターに接続できません。クラスタアドレスが正しいか、ファイアウォール設定を確認してください。", "Kac172626": "申請を拒否する際は、拒否理由を記入してください。", "K7f0c746d": "成功", "K6a365d01": "失敗", @@ -785,5 +785,50 @@ "K1bc5e0a3": "コンシューマー IP", "K6f39ea21": "認証名", "K8c34c02f": "マスキング前", - "K8e3d388d": "マスキング後" + "K8e3d388d": "マスキング後", + "K94b48734": "AI providers の取得に失敗しました", + "Kf23a8988": "AI プロバイダー", + "K4d81a657": "プレビュー", + "K91144ebd": "AI API リスト", + "Kf8187c33": "API URL を入力して検索", + "Kee4139c2": "インターセプト API", + "K3e38ea": "インターセプトを有効にすると、ゲートウェイはこのパスのすべてのリクエストをインターセプトします。", + "Kd752a3a8": "AI モデルが設定されていません", + "K8b7ac871": "設定へ移動", + "Kf97448b3": "設定済み", + "K30d4d8df": "未設定", + "Kc2ee5223": "デフォルトモデル", + "K608af899": "負荷優先度", + "K65b7a96": "負荷優先度は、元のプロバイダーが異常または停止した場合に、どのプロバイダーを優先的に使用するかを決定します。優先度の数値が小さいほど、優先度が高くなります。", + "K9eccff16": "優先度は0より大きい必要があります", + "Kfcf02780": "優先度を入力してください", + "K5c6dcf58": "API キー(デフォルトキー)", + "K59bf8ed9": "LLM ステータス管理", + "Kedd64e4d": "停止", + "K2a3aeb8d": "デフォルト:", + "K8b88ef63": "API キーを入力してください", + "Kcbd30819": "API キー", + "Kcb6e2d3e": "API キーを入力してください", + "Ke13e332a": "有効期限を設定", + "K409aa8ba": "有効期限を選択してください", + "Kba69594c": "超過", + "Kb9e7ceda": "期限切れ", + "Kac405b50": "エラー", + "K3fde5b49": "ステータスを選択してください", + "K434b7e76": "API キーを編集", + "K28190dbc": "削除しました", + "Kb5fcf5b8": "停止しました", + "K5940d788": "有効化しました", + "K8743bccd": "並び替えが完了しました", + "K19590c2c": "呼び出し優先度", + "K89f135a7": "使用済みトークン", + "K1acc30b2": "編集時間", + "Kefb03657": "API キーリソースプール", + "Kc0352e64": "単一の API モデルプロバイダーで複数の API キーを作成し、インテリジェントな負荷分散を実現できます", + "Kd25acba1": "名前を入力して検索", + "K6d0388a0": "API キーを追加", + "Ke32702ac": "保存後、サプライヤーのステータスは【無効】となり、このサプライヤーのAPIは一時的に負荷優先度が最も高い正常なサプライヤーを使用します。", + "Ka08c28d4": "保存後、サプライヤーのステータスは【正常】となり、このサプライヤーのAI機能が復元されます。", + "Kab8fe398": "現在の呼び出し状態:", + "K4880fd04": "APIKeyを追加 (0)" } diff --git a/frontend/packages/common/src/locales/scan/newJson/en-US.json b/frontend/packages/common/src/locales/scan/newJson/en-US.json index 9e26dfee..3d1c19a5 100644 --- a/frontend/packages/common/src/locales/scan/newJson/en-US.json +++ b/frontend/packages/common/src/locales/scan/newJson/en-US.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "Ke32702ac": "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。", + "Ka08c28d4": "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。", + "Kab8fe398": "当前调用状态:", + "K4880fd04": "添加 (0) APIKey" +} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/newJson/ja-JP.json b/frontend/packages/common/src/locales/scan/newJson/ja-JP.json index 9e26dfee..3d1c19a5 100644 --- a/frontend/packages/common/src/locales/scan/newJson/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/newJson/ja-JP.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "Ke32702ac": "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。", + "Ka08c28d4": "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。", + "Kab8fe398": "当前调用状态:", + "K4880fd04": "添加 (0) APIKey" +} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/newJson/zh-CN.json b/frontend/packages/common/src/locales/scan/newJson/zh-CN.json index aa541d30..a14a907f 100644 --- a/frontend/packages/common/src/locales/scan/newJson/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/newJson/zh-CN.json @@ -2,7 +2,6 @@ "K630c9e6d": "APIPark", "Ka3e9f580": "发布名称", "Kb2480682": "策略列表", - "K1da86266": "无效", "K76036e25": "HTTP 请求头", "K44607e3f": "全等匹配", "Kc287500a": "前缀匹配", @@ -45,8 +44,12 @@ "Kd2850420": "待删除", "K83237c89": "输入的IP或CIDR不符合格式", "K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*", + "Ke32702ac": "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。", + "Ka08c28d4": "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。", + "Kab8fe398": "当前调用状态:", "K508d8bf4": "集成地址", "K67f4e9bb": "与外部平台集成时,获取 API 市场中文档信息的域名", + "K4880fd04": "添加 (0) APIKey", "Kc82b8374": "编辑策略", "K4b34a5e5": "策略类型", "K57f0fee8": "匹配条件", diff --git a/frontend/packages/common/src/locales/scan/newJson/zh-TW.json b/frontend/packages/common/src/locales/scan/newJson/zh-TW.json index 9e26dfee..3d1c19a5 100644 --- a/frontend/packages/common/src/locales/scan/newJson/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/newJson/zh-TW.json @@ -1 +1,6 @@ -{} \ No newline at end of file +{ + "Ke32702ac": "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。", + "Ka08c28d4": "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。", + "Kab8fe398": "当前调用状态:", + "K4880fd04": "添加 (0) APIKey" +} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/en-US.json b/frontend/packages/common/src/locales/scan/oldJson/en-US.json index 6599b2f7..ea1770de 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/en-US.json +++ b/frontend/packages/common/src/locales/scan/oldJson/en-US.json @@ -6,11 +6,18 @@ "Kecbb0e45": "System", "Ka358e23d": "General", "K449058e9": "API Gateway", - "K99935e6f": "AI Model", "K1deaa2dd": "User", "K631d646f": "Open API", "K1196b104": "APIPark", + "Kf1b166e7": "Details", + "K7ac2be34": "AI Model Management", + "K18dccc1a": "Sync Latest Model", "K5cfdd950": "This data cannot be recovered after deletion. Are you sure you want to delete?", + "K28435c5c": "API Details", "Kb9052305": "Search Username, Email", - "K40a89bd8": "Enter Name, ID to Search Member" + "Kbe3e9335": "Exit Test", + "K40a89bd8": "Enter Name, ID to Search Member", + "K1da86266": "Invalid", + "Kb147fabc": "Create", + "K40ca4f2": "Update" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json b/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json index d468aa53..9cce1c7b 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json @@ -1,22 +1,25 @@ { - "Kc0e5ef9f": "Workspace", - "K4de11e23": "Home", - "Kfe93ef35": "Application", - "K61c89f5f": "API Portal", + "Kc0e5ef9f": "ワークスペース", + "K4de11e23": "ホーム", + "Kfe93ef35": "アプリケーション", + "K61c89f5f": "API ポータル", "K3fe97dcc": "設定", "Kecbb0e45": "システム", "Ka358e23d": "一般", "K449058e9": "API ゲートウェイ", - "K99935e6f": "AI モデル", "K1deaa2dd": "ユーザー", "K631d646f": "Open API", "K1196b104": "APIPark", "Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます", "K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます", + "Kf1b166e7": "詳細", + "K7ac2be34": "AI モデル管理", + "K18dccc1a": "最新モデルを同期", "K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。", "Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。", "Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。", "K5cfdd950": "このデータを削除すると、復元できません。削除しますか?", + "K28435c5c": "API 詳細", "Kb9052305": "ユーザー名またはメールを検索", "K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。", "K1512e983": "アプリケーション呼び出し統計", @@ -28,11 +31,15 @@ "K546e46f": "Application ID", "K95764d1d": "Application を削除", "K667bbbe7": "Application を追加", + "Kbe3e9335": "テスト終了", "K8723422e": "アプリケーションを接続", "K93d5a66e": "接続アプリケーション数", "K216a1ac7": "アプリケーション開発者", "K27924db": "アプリケーション管理者", "Kd55c6887": "レビュー", "K831aa6c0": "申請元-アプリケーション", - "K40a89bd8": "名前または ID を入力してサービスを検索" + "K40a89bd8": "名前または ID を入力してサービスを検索", + "K1da86266": "無効", + "Kb147fabc": "新規作成", + "K40ca4f2": "更新" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json b/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json index 991aad93..5ad1a272 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json @@ -7,16 +7,19 @@ "Kecbb0e45": "系统", "Ka358e23d": "常规", "K449058e9": "API 网关", - "K99935e6f": "AI 模型", "K1deaa2dd": "用户", "K631d646f": "Open API", "K1196b104": "APIPark", "Kffd7e274": "无审核:允许所有消费者订阅该服务", "K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务", + "Kf1b166e7": "详情", + "K7ac2be34": "AI 模型管理", + "K18dccc1a": "同步最新模型", "K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。", "Kc8239422": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。", "Ka0a8840a": "审核其他消费者的订阅申请,审核通过后的才可发起 API 请求。", "K5cfdd950": "该数据删除后将无法找回,是否删除?", + "K28435c5c": "API 详情", "Kb9052305": "搜索用户名、邮箱", "K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。", "K1512e983": "消费者调用统计", @@ -28,11 +31,14 @@ "K546e46f": "消费者 ID", "K95764d1d": "删除消费者", "K667bbbe7": "添加消费者", + "Kbe3e9335": "退出测试", "K8723422e": "接入消费者", "K93d5a66e": "接入消费者数量", "K216a1ac7": "消费者开发者", "K27924db": "消费者管理员", "Kd55c6887": "审核", "K831aa6c0": "申请方-消费者", - "K40a89bd8": "输入名称、ID 查找服务" + "K40a89bd8": "输入名称、ID 查找服务", + "Kb147fabc": "新建", + "K40ca4f2": "更新" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json b/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json index 357d2bae..b77e4a48 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json @@ -7,16 +7,19 @@ "Kecbb0e45": "系統", "Ka358e23d": "常規", "K449058e9": "API 網關", - "K99935e6f": "AI 模型", "K1deaa2dd": "用戶", "K631d646f": "Open API", "K1196b104": "APIPark", "Kffd7e274": "無審核:允許所有應用程式訂閱該服務", "K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務", + "Kf1b166e7": "詳情", + "K7ac2be34": "AI 模型管理", + "K18dccc1a": "同步最新模型", "K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。", "Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。", "Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。", "K5cfdd950": "該數據刪除後將無法找回,是否刪除?", + "K28435c5c": "API 詳情", "Kb9052305": "搜索用戶名、電郵", "K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。", "K1512e983": "應用程式調用統計", @@ -28,11 +31,15 @@ "K546e46f": "應用程式 ID", "K95764d1d": "刪除應用程式", "K667bbbe7": "添加應用程式", + "Kbe3e9335": "退出測試", "K8723422e": "接入應用程式", "K93d5a66e": "接入應用程式數量", "K216a1ac7": "應用程式開發者", "K27924db": "應用程式管理員", "Kd55c6887": "審核", "K831aa6c0": "申請方-應用程式", - "K40a89bd8": "輸入名稱、ID 查找服務" + "K40a89bd8": "輸入名稱、ID 查找服務", + "K1da86266": "無效", + "Kb147fabc": "新建", + "K40ca4f2": "更新" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/zh-CN.json b/frontend/packages/common/src/locales/scan/zh-CN.json index 41ee0ce3..5b9563b7 100644 --- a/frontend/packages/common/src/locales/scan/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/zh-CN.json @@ -716,5 +716,50 @@ "K1bc5e0a3": "消费者 IP", "K6f39ea21": "鉴权名称", "K8c34c02f": "脱敏前", - "K8e3d388d": "脱敏后" - } \ No newline at end of file + "K8e3d388d": "脱敏后", + "K94b48734": "获取 AI providers 失败", + "Kf23a8988": "AI 供应商", + "K4d81a657": "预览", + "K91144ebd": "AI API 列表", + "Kf8187c33": "请输入 APIURL 搜索", + "Kee4139c2": "拦截接口", + "K3e38ea": "开启拦截后,网关会拦截所有该路径的请求。", + "Kd752a3a8": "未配置 AI 模型", + "K8b7ac871": "前往设置", + "Kf97448b3": "已设置", + "K30d4d8df": "未设置", + "Kc2ee5223": "默认模型", + "K608af899": "负载优先级", + "K65b7a96": "负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。", + "K9eccff16": "优先级必须大于 0", + "Kfcf02780": "请输入优先级", + "K5c6dcf58": "API Key(默认 Key)", + "K59bf8ed9": "LLM 状态管理", + "Kedd64e4d": "停用", + "K2a3aeb8d": "默认:", + "K8b88ef63": "请输入 APIKey", + "Kcbd30819": "API Key", + "Kcb6e2d3e": "请填写 APIKey", + "Ke13e332a": "设置过期时间", + "K409aa8ba": "请选择过期时间", + "Kba69594c": "超额", + "Kb9e7ceda": "过期", + "Kac405b50": "错误", + "K3fde5b49": "请选择状态", + "K434b7e76": "编辑 APIKey", + "K28190dbc": "删除成功", + "Kb5fcf5b8": "停用成功", + "K5940d788": "启用成功", + "K8743bccd": "排序成功", + "K19590c2c": "调用优先级", + "K89f135a7": "已用 Token", + "K1acc30b2": "编辑时间", + "Kefb03657": "APIKey 资源池", + "Kc0352e64": "支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡", + "Kd25acba1": "请输入名称搜索", + "K6d0388a0": "添加 APIKey", + "Ke32702ac": "保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。", + "Ka08c28d4": "保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。", + "Kab8fe398": "当前调用状态:", + "K4880fd04": "添加 (0) APIKey" +} diff --git a/frontend/packages/common/src/locales/scan/zh-TW.json b/frontend/packages/common/src/locales/scan/zh-TW.json index a7fda4a7..ca25b4e6 100644 --- a/frontend/packages/common/src/locales/scan/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/zh-TW.json @@ -1,583 +1,583 @@ { - "Kc0e5ef9f": "工作區", - "K4de11e23": "主頁", - "Kfe93ef35": "應用程式", - "Kb58e0c3f": "服務", - "Kc9e489f5": "團隊", - "K61c89f5f": "API 門戶", - "K16d71239": "分析報告", - "K714c192d": "調用統計", - "Kd57dfe97": "拓撲圖", - "K3fe97dcc": "系統設置", - "Kecbb0e45": "系統", - "Ka358e23d": "常規", - "K449058e9": "API 網關", - "K99935e6f": "AI 模型", - "K1deaa2dd": "用戶", - "K80a560a1": "帳戶", - "Kf644225f": "角色", - "K4057391a": "集成", - "K8fa58214": "數據源", - "K481e8a05": "SSL 證書", - "Kca53edd0": "日誌", - "Kb283e720": "資源", - "K631d646f": "Open API", - "K6535ff9c": "帳戶設置", - "Kf15499b4": "登出", - "Kabbd6e6": "文檔", - "K1196b104": "APIPark", - "K1f42de3": "HTTP 狀態碼", - "K4770dff4": "系統狀態碼", - "Kf89e58f1": "描述", - "K9e53c664": "提交", - "Kf8e7294c": "上一步", - "Ka0451c97": "取消", - "Kb1dedda3": "關閉", - "Kb2fc7600": "添加設置", - "K4e07217d": "編輯設置", - "K4ea968fe": "編輯(0)", - "Ka7aaaeb": "添加(0)", - "Kaff78ecf": "請輸入 Key", - "K65d46535": "請輸入 Value", - "Kc14b2ea3": "返回", - "K11d3633a": "ID", - "Kbff43de3": "名稱", - "K16ca79ef": "驅動", - "K7a369eef": "已發布", - "Kcfa1a4d2": "下線", - "K771dc3b7": "上線", - "K530f5951": "查看", - "Kecbd7449": "刪除", - "K1cbe2507": "確認", - "K48325b6": "搜尋(0)名稱", - "Kc6340091": "上下文", - "K74ecb1fa": "查詢內容", - "K79f2e2f9": "會話歷史", - "K3a8912ee": "添加變量", - "Kb291a19": "添加工具", - "K27ece71d": "AI 模型調用默認僅使用 Query 變量,可輸入 “{key}” 增加新變量。", - "Kdeed8399": "固定的上游服務器", - "K4ee62e8": "該 API 缺失(0)(1)(2)請先補充", - "K385591f3": "轉發信息,", - "K68415c14": "文檔信息,", - "K133b75e9": "上游信息,", - "K43fcaf94": "成功", - "Kc71c6a9": "上線失敗", - "K56c686f8": "失敗", - "K1ff96ff": "申請系統", - "K9bf855d6": "所屬團隊", - "K11b994ed": "申請人", - "K939baba7": "申請時間", - "Kdab2e63b": "版本號", - "K8b29c460": "版本說明", - "K4758140d": "路由列表", - "K54e44357": "上游列表", - "Kb8e8e6f5": "備註", - "K7e52ffa3": "上線情況", - "K1ab0ae5b": "申請原因", - "K53c00c3c": "審核意見", - "Kfd50704d": "無(0)權限,請聯絡管理員分配。", - "K7edf331d": "時間", - "Kef45b208": "1小時", - "K9dbf22b8": "24小時", - "K820fbfab": "3天", - "Kd6d28fc": "7天", - "Ke00c858c": "上傳文件", - "K6d9dd1f5": "替換文件", - "K71753476": "是否放行", - "K597435c5": "監控", - "Ke66a17dd": "必填", - "K28b68036": "字符非法,僅支持英文", - "K6206e4ad": "上傳 OpenAPI 文檔 (.json/.yaml)", - "Kfba46e6d": "替換 OpenAPI 文檔 (.json/.yaml)", - "Kdac8ce7e": "打開 OpenAPI YAML 編輯器", - "Kffd7e274": "無審核:允許所有應用程式訂閱該服務", - "K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務", - "Kbfe02d7f": "永久", - "K1e9c479e": "否", - "Kaddfcb6b": "是", - "K23fda291": "無操作權限,請聯絡管理員分配。", - "K4618cb0a": "微信小程序", - "Ka854f511": "獲取文件,需填路徑", - "Kaa11a695": "暫不支持生成非 HTTP/HTTPS 代碼示例", - "Kbe46924e": "搜索編程語言...", - "Ke8e4f258": "編程語言", - "K29c07a47": "成功示例", - "K1f5c814d": "失敗示例", - "K4ef022d7": "默認 text/html;charset=UTF-8", - "Kd061b5bf": "暫未填寫示例", - "Kc14cec33": "Binary", - "K48b4d9e3": "請求 Header", - "Kcd347eaf": "請求 Body", - "K9e100bfe": "請求 Query", - "K3e9f12fd": "請求 REST", - "K2bfa290c": "API 請求編輯器", - "Kb36d111a": "響應 Header", - "K980bde79": "響應 Body", - "Kb04d201a": "更多設置", - "Kee74f5b4": "添加子參數", - "Kc7d3106c": "添加行", - "Keaabd222": "標籤", - "K8ad2c50e": "參數名", - "K67d68dd1": "類型", - "K29245f47": "必需", - "Ke32cbcd3": "示例", - "Kc13936c6": "輸入 URL 或 cURL", - "Ka1ede006": "HTTP", - "K152ac44e": "參數位置", - "K1660ae72": "匹配類型", - "K91ced765": "參數值", - "K5b265628": "操作類型", - "K1826982d": "新增或修改", - "Kd65b55f5": "匹配參數值", - "K15f35bf2": "轉發上游路徑", - "K79dec0dd": "請求超時時間", - "K7d465645": "綁定上游服務", - "K63a6404d": "重試時間", - "K47740727": "轉發上游請求頭", - "K2b605d42": "更多", - "K1df9fbd5": "導入", - "K5e85df18": "導入格式", - "K9eaf7885": "全部替換", - "Kf8c3a80b": "在最後插入", - "Kd96b2d7d": "增量更新", - "Kf2fc08eb": "請求 Header", - "Ka45f1d8": "請求 Rest 參數", - "K94bb113a": "大小", - "K359919b5": "另存為文件", - "K38bf1b90": "響應", - "K59f4186e": "響應 Header", - "K5f1e23fd": "正文", - "Kf404ef7d": "發送(Enter)", - "K2dbfd648": "中止", - "Kacabc771": "秒", - "K13ae6a93": "複製", - "Ke54a14a3": "格式化", - "K43934f6d": "搜索", - "K741decac": "替代", - "Kd507abff": "確定", - "Kca2d1624": "(0) 不能為負數.", - "K792b255a": "(0) 必須大於或等於 (1).", - "Kf0bed26d": "枚舉值", - "K633a03ca": "枚舉", - "Kd2766caf": "最小長度", - "Kd6d52485": "最大長度", - "Kea15f66c": "最小值", - "K1af340ff": "最大值", - "K68691e16": "拖拽文件至此處,或選擇文件上傳", - "Kcec46ae": "上傳文件", - "K760fb044": "已選擇文件", - "Kea2bdee0": "請填寫接口名稱", - "K49053438": "詳細說明", - "K148f6fa4": "高級匹配", - "K3ae4c789": "轉發設置", - "K2f4d0a37": "請求參數", - "Kde2d6dbd": "返回示例", - "K70e6069c": "測試 API", - "Ke4603448": "請求 Header", - "K89fd86b3": "請求 Body", - "K8747e3c4": "請求示例代碼", - "K8613e6e7": "響應示例", - "Kab1c2159": "響應 Header", - "Kd2be51d1": "響應 Body", - "K2a3f24ac": "默認工作表", - "K7e1ab4b0": "至", - "Kf1b166e7": "詳情", - "K28555332": "不支持帶有雙斜槓 // 的 URL", - "K71661ee8": "必填項", - "Kcbee3f8": "不是有效電郵地址", - "K617f34f1": "更新者", - "K6ebca204": "更新時間", - "Kabfe9512": "保存", - "K51d1eb5d": "API", - "Ka2b6d281": "API 文檔", - "Kdefa9caa": "說明文檔", - "K36856e71": "發布", - "K6382bbfd": "訂閱管理", - "K31af5b99": "訂閱審核", - "Ka97bd9e5": "訂閱方管理", - "K5974bf24": "管理", - "K3fa5c4c3": "調用拓撲圖", - "Kb5c7b82d": "設置", - "K1e84ad04": "服務 ID", - "K39ab0358": "新增訂閱方", - "K18307d56": "手動添加", - "K705fe9f5": "訂閱申請", - "K3a67ea90": "訂閱方", - "Kefa2a4cf": "API 設置", - "K66060758": "API 名稱", - "K5582ac8": "請求路徑", - "K2bb86fb4": "提示詞", - "K13ffbe88": "變量", - "K79c8cfaf": "接口描述", - "K469e475a": "最大重試次數", - "K8a35059b": "模型設置", - "Kf9dcef3a": "API", - "K6134bbe8": "添加 API", - "Kf85b83a0": "輸入 URL 查找", - "Kcf9f90b8": "模型供應商", - "Kfede1c7c": "模型", - "Ke99513a0": "參數", - "Kb595f40": "審核", - "K54e27f57": "通過", - "K8582af3f": "拒絕", - "Kd568e15c": "發布結果", - "K35f990b0": "查看詳情", - "Kdbc1f6cb": "申請發布", - "Kb6860a3f": "回滾發布", - "Ka3494f4b": "是否回滾發布?", - "Kb397a99f": "撤銷申請", - "K7d401c0f": "是否撤銷申請?", - "Ke1b79b93": "終止發布", - "Ka2449180": "是否終止發布?", - "K2cb02f38": "新建版本", - "Kb3e34847": "從 (0) 獲取 API KEY", - "K66a7d24c": "已設置", - "Kaf074220": "未設置", - "Kd9a46c29": "默認", - "K7ac2be34": "AI 模型管理", - "K2260837a": "設置好 AI 模型後,你可以使用對應的大模型來創建 AI 服務", - "K18dccc1a": "同步最新模型", - "K35612f29": "待審核", - "K47eaafde": "已審核", - "K56b4254f": "發布申請", - "Kea2f9279": "API 調用地址", - "K7fc496a1": "API Base URL 一般設置為 API 網關的外部網絡訪問地址,或者是API網關綁定的域名。", - "K8ab0fc95": "常規設置", - "Kb66fec9d": "API 請求設置", - "K4de0af74": "服務分類", - "Kb4ceecea": "添加子分類", - "K67479e88": "修改分類名稱", - "K2bc75e2c": "添加分類", - "Kab4aab44": "重命名分類", - "Ke595a20a": "分類名稱", - "K9679728f": "父分類 ID", - "K9b2d08fd": "子分類名稱", - "K71671763": "快速接入 AI", - "Ka8a5ec5": "設置你的 AI 模型", - "K10d7e99f": "通過 APIPark 快速接入各種 AI 模型,使用統一的格式來調用API,並且可以隨意切換模型。", - "Kc057704a": "創建 AI 服務和 API", - "K76bb4a09": "創建 AI 類型的服務,並且你可以將 Prompt 提示詞設置為一個 API,簡化使用 AI 的流程。", - "K71b2c70f": "創建調用 Token", - "K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。", - "Kc5738b6c": "調用", - "Kd6d7ca1f": "現在你可以通過 Token 來調用這些 API。", - "K86cf95f": "快速接入 REST API", - "K7a3a8417": "創建 REST 服務和 API", - "K4a84214e": "統計 API 調用情況", - "K297d8563": "儀表板中提供了多種統計圖表,幫助我們了解 API 的運行情況。", - "K2cdbb773": "核心功能", - "K3378c50d": "帳戶與角色", - "Kda5bb930": "邀請你的團隊成員加入 APIPark,共同管理和調用 API。", - "Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。", - "Kd5be0cd7": "服務內包含一組 API,並且可以發布到 API 市場被其他團隊使用。", - "K62e89ee7": "權限管理", - "K8f7808e6": "訂閱服務", - "Kf2410413": "如果需要調用某個服務的 API,需要先訂閱該服務,並且等待提供服務的團隊審核後才可發起 API 請求。", - "K6c2e44b8": "審核訂閱", - "Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。", - "K3453272": "APIPark 提供詳盡的 API 調用日誌,幫助企業監控、分析和審計 API 的運行狀況。", - "Kd518ba3e": "Hello!歡迎使用 APIPark", - "K7e04ea16": "🦄 APIPark 是開源的一站式 AI 網關和 API 開發者門戶,幫助企業和開發者快速接入 100+ AI 模型,將 AI 模型和 Prompt 提示詞組合成新的 API,並且統一所有 AI 的請求數據格式,避免切換 AI 模型或調整提示詞時影響你的 APP 應用程式或者微服務。你還可以通過 APIPark 的開發者門戶在團隊內共享 API,管理調用的應用程式並保障你的 API 安全,通過清晰的圖表來監控你的 AI API 使用情況。", - "Kedd41c18": "✨ 如果你喜歡APIPark,歡迎在Github為我們Star或提供產品回饋意見。", - "Kef02fd87": "快速入門", - "K43a3b38d": "我們提供了一些任務來幫你快速了解 APIPark", - "K408bfcf1": "進階教程", - "K1afaf20e": "了解 APIPark 如何更好地管理 API 和 AI", - "K48f7e21f": "了解更多功能", - "K698296e2": "隱藏該教程", - "Kd2c1a316": "登入", - "Kf076f63c": "請輸入帳戶", - "K25c895d5": "請輸入密碼", - "K551b0348": "密碼", - "K192b3e38": "訪客模式", - "K91aa4801": "您可通過訪客模式查看所有頁面和功能,但是無法編輯數據。訪客模式僅用於了解產品功能,您可以在正式產品中關閉該功能。", - "K480045ce": "Version (0)-(1)", - "Kadee8e49": "日誌設置", - "K2724314b": "提供詳盡的 API 調用日誌,幫助企業監控、分析和審計 API 的運行狀況。", - "K33c76dbc": "部門名稱", - "K84829ca9": "父部門 ID", - "K4d7fc74b": "子部門名稱", - "Keb9fcdad": "用戶名", - "Kc654b275": "電郵", - "Kbe2ecc69": "部門", - "Ka16e6c44": "未激活或禁用的成員無法加入部門", - "Ked03ba97": "請選擇成員需要新加入的部門", - "K184d3473": "添加帳戶", - "K1ecb35f2": "編輯成員信息", - "Ke6f00b44": "加入部門", - "K501cb1e7": "此操作無法恢復,確定刪除成員?", - "Kf20863b5": "成員與部門", - "K52c8a730": "啟用", - "K718c9310": "禁用", - "K5f27a546": "輸入用戶名、電郵查找成員", - "K7c97c5df": "移出", - "K1362a512": "禁用", - "K6e1289b1": "啟用", - "K1f4b5385": "刪除", - "K26c698bb": "添加部門", - "Kb9cf2a7d": "添加子部門", - "Kc83551f5": "重命名", - "K5cfdd950": "該數據刪除後將無法找回,是否刪除?", - "K74aef1ad": "成員", - "K3f1077c9": "設置成員的角色,成員只能夠看到角色權限範圍內的功能和數據。", - "Kdce62a6": "搜索部門", - "Ka46b9b24": "數據源類型", - "Kbb0cdcd0": "數據源地址", - "Kd9dfb884": "Organization", - "K3e770a75": "鑒權 Token", - "K8ef69ee2": "密鑰", - "Kba3507d6": "上傳密鑰", - "K93ac0f23": "密鑰文件的後綴名一般是 .key", - "K7cdd1331": "上傳證書", - "K6d91905d": "證書文件的後綴名一般是 .crt 或 .pem", - "Kd0f6ded7": "添加證書", - "Ke5732d60": "修改證書", - "K3ca07a70": "證書管理", - "Kdb927f83": "通過為 API 服務設置和管理 SSL 證書,企業可以加密數據傳輸,防止敏感信息泄露。", - "Ke93d36ed": "集群", - "K877985b7": "修改設置", - "Kdf66a675": "設置訪問 API 的集群,讓 API 在分佈式環境中穩定運行,並且能夠根據業務需求進行靈活擴展和優化。", - "Ke039b9b5": "正常", - "K23a3bd72": "異常", - "Kf12b3034": "管理地址", - "K867e6faf": "服務地址", - "K2a49373f": "同步地址", - "K5878440c": "集群地址", - "K5e9022f8": "下一步", - "Kdbafd6f9": "設置監控報表的數據來源,設置完成之後即可獲得詳細的API調用統計圖表。", - "K1358acf": "統計圖表", - "K62dabdf6": "地址(IP:端口)", - "K2db12335": "組織", - "K8e7a0f80": "資源設置", - "K95c3fd8b": "設置角色的權限範圍。", - "K138facd3": "系統角色", - "K6eac768d": "添加角色", - "Kb9c2cf02": "團隊角色", - "K2a16c93b": "單位:ms,最小值:1", - "Ka945cfb1": "API 設置", - "K2e050340": "API 基礎信息", - "Kba92c499": "攔截接口", - "Kde9d6e8e": "開啟攔截後,網關會攔截所有該路徑的請求。", - "K6bc47edb": "請求協議", - "K1365fe45": "請求方式", - "K90f3c02f": "轉發規則設置", - "Kb7df6ac1": "攔截", - "K5c1722fe": "放行", - "K28435c5c": "API 詳情", - "Ka9c08390": "只允許上傳PNG、JPG或SVG格式的圖片", - "K413b9869": "服務名稱", - "K9919285b": "服務類型", - "Kcef64f4d": "默認 AI 供應商", - "Kcab588a9": "未設置任何 AI 模型供應商,", - "Kb9b56111": "立即設置", - "Kcf756b7a": "API 調用前綴", - "K13edc043": "作為服務內所有API的前綴,比如host/{service_name}/{api_path},一旦保存無法修改", - "Kf52a584d": "所屬服務分類", - "K72b21be5": "設置服務展示在服務市場中的哪個分類下", - "Kdc840242": "圖標", - "K427a5bd5": "僅支持 .png .jpg .jpeg .svg 格式的圖片文件, 大於 1KB 的文件將被壓縮", - "K44bc352d": "Logo", - "Kde6bae17": "刪除服務", - "K885ea699": "刪除操作不可恢復,請謹慎操作!", - "Kda8d5ea1": "上游", - "K12f58863": "服務提供了高性能 API 網關,並且可以無縫接入多種大型 AI 模型,並將這些 AI 能力打包成 API 進行調用,從而大幅簡化了 AI 模型的使用門檻。同時,我們的平台提供了完善的 API 管理功能,支持 API 的創建、監控、訪問控制等,保障開發者可以高效、安全地開發和管理 API 服務。", - "K2d6658ed": "添加服務", - "K7b8f623f": "輸入名稱、ID、所屬團隊、負責人查找服務", - "Kad98e030": "上游類型", - "Kdd9b5008": "後端默認使用的IP地址", - "Kc9acdb25": "負載均衡", - "K632dba5c": "轉發 Host", - "Kc1f08a63": "重寫 Host", - "K628f6851": "超時時間", - "Kaff62621": "超時重試次數", - "Kf14d159b": "次", - "Kc41ca30e": "調用頻率限制", - "K753e8aeb": "次/秒", - "K813e1c0a": "團隊名稱", - "K692f5aa6": "團隊 ID", - "K5de0bc2": "團隊 ID(team_id)可用於檢索團隊,一旦保存無法修改。", - "Ka63dd985": "團隊負責人", - "Ka6bcd272": "負責人對團隊內的團隊、服務、成員有管理權限", - "Ka2012bdd": "刪除團隊", - "Kbde1f3d": "服務數據清除後,方可刪除", - "K395acc14": "移除成員", - "Kec46a57f": "添加成員", - "K48724410": "輸入姓名查找", - "Kb9052305": "搜索用戶名、電郵", - "K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。", - "K510cdd27": "添加團隊", - "K9244ae14": "輸入名稱、ID、負責人查找團隊", - "Kc7b24b4b": "設置團隊", - "Kecb51e2c": "舊密碼", - "K8266bcf2": "新密碼", - "Ka9aef039": "確認密碼", - "Kcf42dcda": "兩次密碼不一致", - "Kf876a42d": "修改密碼", - "K8ed884f": "管理個人帳戶", - "K9be8e1d7": "API調用統計", - "K521ab28e": "選擇服務", - "Kcc8265e1": "選擇API", - "Kc380335f": "路徑", - "K8aefc1e4": "請輸入請求路徑進行搜索", - "K50d471b2": "重置", - "Kee8ae330": "查詢", - "Ka2c794a2": "導出", - "Kaf70c3b": "退出全屏", - "Kd22841a4": "(0)調用詳情", - "K1512e983": "應用程式調用統計", - "Kb4d2007f": "請選擇應用程式", - "K8c7f2d2e": "調用趨勢", - "K657c3452": "(0)-(1)調用趨勢", - "Kc04efb87": "調用量統計", - "Keb98266e": "加入總體數據對比", - "K18c2ed46": "(0)調用量", - "Kc3741830": "(0)成功率", - "Ka6aa5863": "請求數", - "K9eaef42": "成功率", - "K7082a4af": "轉發數", - "K1ce386fb": "成功率", - "K87d6877e": "4xx", - "K4c8a54db": "5xx", - "Kd566283e": "趨勢", - "K21ad4a6a": "(0)報文", - "Kd23a0be6": "請求報文", - "Kec3e8361": "響應報文", - "Ke6250744": "4XX數", - "K2d79d4e1": "5XX數", - "Kcf6553c6": "服務調用統計", - "Kffcfe375": "請選擇服務", - "Ka65f739c": "調用詳情", - "K89b7ac79": "API Top10", - "Kc0915603": "應用程式 Top10", - "Kf90b54": "服務 Top10", - "Kfb26388": "無請求數據", - "Kc8cbd8f8": "請求統計", - "K8dece48": "無轉發統計數據", - "K1ee32434": "轉發統計", - "Kcd125e4d": "無調用量統計數據", - "Kaa114e8b": "無報文統計數據", - "K3ad84406": "報文統計", - "Kfa088d49": "集群設置並開啟監控", - "K3da3b9a0": "監控功能用於輔助管理集群內信息,請設置集群、設置監控信息後查看當前集群監控情況;", - "Kaddacfb": "集群", - "K4ac33975": "設置集群地址,以確保監控系統能夠正確識別和連接到集群", - "Ke5ed9810": "設置集群", - "K1a132228": "監控", - "K6af08c3c": "設置監控", - "K4a1a14": "監控總覽", - "K69741ea7": "服務調用", - "K9c8d9933": "API 調用", - "K145e4941": "億", - "Ke6a935d": "萬", - "Kd59290a2": "搜索分類或標籤", - "K6b75bdbc": "無API數據", - "Kd8a7a689": "搜索或選擇應用程式", - "K4b15d6f5": "申請理由", - "Kb7e869a4": "應用程式管理", - "Kb71b5a13": "鑒權類型", - "K4d1465ee": "ISS", - "K5dcd7ed8": "簽名算法", - "K5b0eedd3": "Secret", - "K44f4ffe1": "RSA 公鑰", - "Kc5ecd7d9": "用戶名 JSONPath", - "K417d85cf": "校驗字段", - "K3b82fe1d": "是否 Base64 加密", - "K49b5f4a3": "AK", - "K31418470": "SK", - "Kbfeb5297": "API Key", - "K1a78e6f0": "過期時間", - "Ke64e43a": "隱藏認證信息", - "K5168eb63": "應用程式名稱", - "K546e46f": "應用程式 ID", - "K95764d1d": "刪除應用程式", - "K217cb125": "鑒權詳情", - "K2bb63eca": "添加鑒權", - "Kd74d69b7": "編輯鑒權", - "K9cbe1e0": "修改", - "Kb6e9328f": "訪問授權", - "Kd23d1716": "添加授權", - "K9dfa2c97": "永不過期", - "Kfa920c0": "過期時間", - "Kcce1af60": "訂閱的服務", - "Kfefa9b58": "審核詳情", - "K3118fdb0": "取消訂閱", - "Ked811bb1": "是否取消訂閱?", - "K50c39a62": "取消訂閱申請", - "K1856c229": "是否取消訂閱申請?", - "K66ea2f0": "搜索服務", - "K8adf7f8b": "審核中", - "K667bbbe7": "添加應用程式", - "Ka4b45550": "無服務描述", - "K3c7b175f": "訂閱服務:已通過 (0) ,審核中 (1)", - "Kbe3e9335": "退出測試", - "K370a3eb2": "服務市場", - "Kf7ec36d": "服務詳情", - "K58ca9485": "申請訂閱", - "K59cdbec3": "服務介紹", - "K4aa9ed2c": "申請訂閱", - "K6c060779": "服務信息", - "K8723422e": "接入應用程式", - "Kb97544cb": "供應方", - "Kb32f0afe": "分類", - "K81634069": "版本", - "Keefda53d": "更新時間", - "K96a2f1c8": "無標籤", - "K72b0c0b3": "API 數量", - "K93d5a66e": "接入應用程式數量", - "K96059c69": "關聯標籤", - "K8b7c2592": "更新者", - "K32263abd": "添加 Open Api", - "K7829bb78": "設置 Open Api", - "Kcdf76005": "Open Api", - "Ke2601944": "調用服務", - "K8504bca8": "放大", - "K693c1b41": "縮小", - "K3d7465f7": "文件日誌", - "Kc87167a0": "HTTP 日誌", - "K54630fe8": "Kafka 日誌", - "Kd5c3966e": "NSQ 日誌", - "K2e3de2c1": "Syslog 日誌", - "K48322168": "未分配", - "K98f247f9": "超級管理員", - "K9c8a571f": "團隊管理員", - "K929b485b": "運維管理員", - "K82cc5ec2": "普通成員", - "Ke41d7451": "只讀成員", - "Kf99e8b66": "服務管理員", - "Kda8db57a": "服務開發者", - "K216a1ac7": "應用程式開發者", - "K27924db": "應用程式管理員", - "K8dc5c723": "驅動名稱", - "Kda249fe8": "失敗", - "Kcf2df651": "失敗", - "K7e6a859d": "作用範圍", - "K3a008b34": "添加條目", - "Ke0599ef7": "添加地址", - "K48d3b5c4": "文件名稱", - "Kafde0d2a": "存放目錄", - "Kfb2926ac": "日誌分割週期", - "Kd96c2c69": "單位:天", - "Kc2b776fa": "輸出格式", - "K7b7cdac2": "格式化設置", - "K2f59807a": "服務器地址", - "Kb1cfa6e7": "Access 日誌", - "K540488a8": "NSQD 地址列表", - "K8bc33a11": "鑒權 Secret", - "K1cd3002f": "網絡協議", - "Kdfaa32c8": "日誌等級", - "Kc0408d9c": "單行", - "Ke3db239d": "小時", - "K3509a9f8": "天", - "Kb3960e83": "未發布", - "K8bd1e18": "待發布", - "K225a6c43": "單位:s,最小值:1", - "K593e0c7e": "無需審核", - "Ke2d747d9": "需要審核", - "Kc29dabf2": "Base URL", - "Kd55c6887": "審核", - "K300c89d4": "創建 API 時會默認選擇該供應商,修改默認供應商不會影響現有 API", - "Kefaf9956": "創建時間", + "Kc0e5ef9f": "工作區", + "K4de11e23": "主頁", + "Kfe93ef35": "應用程式", + "Kb58e0c3f": "服務", + "Kc9e489f5": "團隊", + "K61c89f5f": "API 門戶", + "K16d71239": "分析報告", + "K714c192d": "調用統計", + "Kd57dfe97": "拓撲圖", + "K3fe97dcc": "系統設置", + "Kecbb0e45": "系統", + "Ka358e23d": "常規", + "K449058e9": "API 網關", + "K99935e6f": "AI 模型", + "K1deaa2dd": "用戶", + "K80a560a1": "帳戶", + "Kf644225f": "角色", + "K4057391a": "集成", + "K8fa58214": "數據源", + "K481e8a05": "SSL 證書", + "Kca53edd0": "日誌", + "Kb283e720": "資源", + "K631d646f": "Open API", + "K6535ff9c": "帳戶設置", + "Kf15499b4": "登出", + "Kabbd6e6": "文檔", + "K1196b104": "APIPark", + "K1f42de3": "HTTP 狀態碼", + "K4770dff4": "系統狀態碼", + "Kf89e58f1": "描述", + "K9e53c664": "提交", + "Kf8e7294c": "上一步", + "Ka0451c97": "取消", + "Kb1dedda3": "關閉", + "Kb2fc7600": "添加設置", + "K4e07217d": "編輯設置", + "K4ea968fe": "編輯(0)", + "Ka7aaaeb": "添加(0)", + "Kaff78ecf": "請輸入 Key", + "K65d46535": "請輸入 Value", + "Kc14b2ea3": "返回", + "K11d3633a": "ID", + "Kbff43de3": "名稱", + "K16ca79ef": "驅動", + "K7a369eef": "已發布", + "Kcfa1a4d2": "下線", + "K771dc3b7": "上線", + "K530f5951": "查看", + "Kecbd7449": "刪除", + "K1cbe2507": "確認", + "K48325b6": "搜尋(0)名稱", + "Kc6340091": "上下文", + "K74ecb1fa": "查詢內容", + "K79f2e2f9": "會話歷史", + "K3a8912ee": "添加變量", + "Kb291a19": "添加工具", + "K27ece71d": "AI 模型調用默認僅使用 Query 變量,可輸入 “{key}” 增加新變量。", + "Kdeed8399": "固定的上游服務器", + "K4ee62e8": "該 API 缺失(0)(1)(2)請先補充", + "K385591f3": "轉發信息,", + "K68415c14": "文檔信息,", + "K133b75e9": "上游信息,", + "K43fcaf94": "成功", + "Kc71c6a9": "上線失敗", + "K56c686f8": "失敗", + "K1ff96ff": "申請系統", + "K9bf855d6": "所屬團隊", + "K11b994ed": "申請人", + "K939baba7": "申請時間", + "Kdab2e63b": "版本號", + "K8b29c460": "版本說明", + "K4758140d": "路由列表", + "K54e44357": "上游列表", + "Kb8e8e6f5": "備註", + "K7e52ffa3": "上線情況", + "K1ab0ae5b": "申請原因", + "K53c00c3c": "審核意見", + "Kfd50704d": "無(0)權限,請聯絡管理員分配。", + "K7edf331d": "時間", + "Kef45b208": "1小時", + "K9dbf22b8": "24小時", + "K820fbfab": "3天", + "Kd6d28fc": "7天", + "Ke00c858c": "上傳文件", + "K6d9dd1f5": "替換文件", + "K71753476": "是否放行", + "K597435c5": "監控", + "Ke66a17dd": "必填", + "K28b68036": "字符非法,僅支持英文", + "K6206e4ad": "上傳 OpenAPI 文檔 (.json/.yaml)", + "Kfba46e6d": "替換 OpenAPI 文檔 (.json/.yaml)", + "Kdac8ce7e": "打開 OpenAPI YAML 編輯器", + "Kffd7e274": "無審核:允許所有應用程式訂閱該服務", + "K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務", + "Kbfe02d7f": "永久", + "K1e9c479e": "否", + "Kaddfcb6b": "是", + "K23fda291": "無操作權限,請聯絡管理員分配。", + "K4618cb0a": "微信小程序", + "Ka854f511": "獲取文件,需填路徑", + "Kaa11a695": "暫不支持生成非 HTTP/HTTPS 代碼示例", + "Kbe46924e": "搜索編程語言...", + "Ke8e4f258": "編程語言", + "K29c07a47": "成功示例", + "K1f5c814d": "失敗示例", + "K4ef022d7": "默認 text/html;charset=UTF-8", + "Kd061b5bf": "暫未填寫示例", + "Kc14cec33": "Binary", + "K48b4d9e3": "請求 Header", + "Kcd347eaf": "請求 Body", + "K9e100bfe": "請求 Query", + "K3e9f12fd": "請求 REST", + "K2bfa290c": "API 請求編輯器", + "Kb36d111a": "響應 Header", + "K980bde79": "響應 Body", + "Kb04d201a": "更多設置", + "Kee74f5b4": "添加子參數", + "Kc7d3106c": "添加行", + "Keaabd222": "標籤", + "K8ad2c50e": "參數名", + "K67d68dd1": "類型", + "K29245f47": "必需", + "Ke32cbcd3": "示例", + "Kc13936c6": "輸入 URL 或 cURL", + "Ka1ede006": "HTTP", + "K152ac44e": "參數位置", + "K1660ae72": "匹配類型", + "K91ced765": "參數值", + "K5b265628": "操作類型", + "K1826982d": "新增或修改", + "Kd65b55f5": "匹配參數值", + "K15f35bf2": "轉發上游路徑", + "K79dec0dd": "請求超時時間", + "K7d465645": "綁定上游服務", + "K63a6404d": "重試時間", + "K47740727": "轉發上游請求頭", + "K2b605d42": "更多", + "K1df9fbd5": "導入", + "K5e85df18": "導入格式", + "K9eaf7885": "全部替換", + "Kf8c3a80b": "在最後插入", + "Kd96b2d7d": "增量更新", + "Kf2fc08eb": "請求 Header", + "Ka45f1d8": "請求 Rest 參數", + "K94bb113a": "大小", + "K359919b5": "另存為文件", + "K38bf1b90": "響應", + "K59f4186e": "響應 Header", + "K5f1e23fd": "正文", + "Kf404ef7d": "發送(Enter)", + "K2dbfd648": "中止", + "Kacabc771": "秒", + "K13ae6a93": "複製", + "Ke54a14a3": "格式化", + "K43934f6d": "搜索", + "K741decac": "替代", + "Kd507abff": "確定", + "Kca2d1624": "(0) 不能為負數.", + "K792b255a": "(0) 必須大於或等於 (1).", + "Kf0bed26d": "枚舉值", + "K633a03ca": "枚舉", + "Kd2766caf": "最小長度", + "Kd6d52485": "最大長度", + "Kea15f66c": "最小值", + "K1af340ff": "最大值", + "K68691e16": "拖拽文件至此處,或選擇文件上傳", + "Kcec46ae": "上傳文件", + "K760fb044": "已選擇文件", + "Kea2bdee0": "請填寫接口名稱", + "K49053438": "詳細說明", + "K148f6fa4": "高級匹配", + "K3ae4c789": "轉發設置", + "K2f4d0a37": "請求參數", + "Kde2d6dbd": "返回示例", + "K70e6069c": "測試 API", + "Ke4603448": "請求 Header", + "K89fd86b3": "請求 Body", + "K8747e3c4": "請求示例代碼", + "K8613e6e7": "響應示例", + "Kab1c2159": "響應 Header", + "Kd2be51d1": "響應 Body", + "K2a3f24ac": "默認工作表", + "K7e1ab4b0": "至", + "Kf1b166e7": "詳情", + "K28555332": "不支持帶有雙斜槓 // 的 URL", + "K71661ee8": "必填項", + "Kcbee3f8": "不是有效電郵地址", + "K617f34f1": "更新者", + "K6ebca204": "更新時間", + "Kabfe9512": "保存", + "K51d1eb5d": "API", + "Ka2b6d281": "API 文檔", + "Kdefa9caa": "說明文檔", + "K36856e71": "發布", + "K6382bbfd": "訂閱管理", + "K31af5b99": "訂閱審核", + "Ka97bd9e5": "訂閱方管理", + "K5974bf24": "管理", + "K3fa5c4c3": "調用拓撲圖", + "Kb5c7b82d": "設置", + "K1e84ad04": "服務 ID", + "K39ab0358": "新增訂閱方", + "K18307d56": "手動添加", + "K705fe9f5": "訂閱申請", + "K3a67ea90": "訂閱方", + "Kefa2a4cf": "API 設置", + "K66060758": "API 名稱", + "K5582ac8": "請求路徑", + "K2bb86fb4": "提示詞", + "K13ffbe88": "變量", + "K79c8cfaf": "接口描述", + "K469e475a": "最大重試次數", + "K8a35059b": "模型設置", + "Kf9dcef3a": "API", + "K6134bbe8": "添加 API", + "Kf85b83a0": "輸入 URL 查找", + "Kcf9f90b8": "模型供應商", + "Kfede1c7c": "模型", + "Ke99513a0": "參數", + "Kb595f40": "審核", + "K54e27f57": "通過", + "K8582af3f": "拒絕", + "Kd568e15c": "發布結果", + "K35f990b0": "查看詳情", + "Kdbc1f6cb": "申請發布", + "Kb6860a3f": "回滾發布", + "Ka3494f4b": "是否回滾發布?", + "Kb397a99f": "撤銷申請", + "K7d401c0f": "是否撤銷申請?", + "Ke1b79b93": "終止發布", + "Ka2449180": "是否終止發布?", + "K2cb02f38": "新建版本", + "Kb3e34847": "從 (0) 獲取 API KEY", + "K66a7d24c": "已設置", + "Kaf074220": "未設置", + "Kd9a46c29": "默認", + "K7ac2be34": "AI 模型管理", + "K2260837a": "設置好 AI 模型後,你可以使用對應的大模型來創建 AI 服務", + "K18dccc1a": "同步最新模型", + "K35612f29": "待審核", + "K47eaafde": "已審核", + "K56b4254f": "發布申請", + "Kea2f9279": "API 調用地址", + "K7fc496a1": "API Base URL 一般設置為 API 網關的外部網絡訪問地址,或者是API網關綁定的域名。", + "K8ab0fc95": "常規設置", + "Kb66fec9d": "API 請求設置", + "K4de0af74": "服務分類", + "Kb4ceecea": "添加子分類", + "K67479e88": "修改分類名稱", + "K2bc75e2c": "添加分類", + "Kab4aab44": "重命名分類", + "Ke595a20a": "分類名稱", + "K9679728f": "父分類 ID", + "K9b2d08fd": "子分類名稱", + "K71671763": "快速接入 AI", + "Ka8a5ec5": "設置你的 AI 模型", + "K10d7e99f": "通過 APIPark 快速接入各種 AI 模型,使用統一的格式來調用API,並且可以隨意切換模型。", + "Kc057704a": "創建 AI 服務和 API", + "K76bb4a09": "創建 AI 類型的服務,並且你可以將 Prompt 提示詞設置為一個 API,簡化使用 AI 的流程。", + "K71b2c70f": "創建調用 Token", + "K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。", + "Kc5738b6c": "調用", + "Kd6d7ca1f": "現在你可以通過 Token 來調用這些 API。", + "K86cf95f": "快速接入 REST API", + "K7a3a8417": "創建 REST 服務和 API", + "K4a84214e": "統計 API 調用情況", + "K297d8563": "儀表板中提供了多種統計圖表,幫助我們了解 API 的運行情況。", + "K2cdbb773": "核心功能", + "K3378c50d": "帳戶與角色", + "Kda5bb930": "邀請你的團隊成員加入 APIPark,共同管理和調用 API。", + "Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。", + "Kd5be0cd7": "服務內包含一組 API,並且可以發布到 API 市場被其他團隊使用。", + "K62e89ee7": "權限管理", + "K8f7808e6": "訂閱服務", + "Kf2410413": "如果需要調用某個服務的 API,需要先訂閱該服務,並且等待提供服務的團隊審核後才可發起 API 請求。", + "K6c2e44b8": "審核訂閱", + "Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。", + "K3453272": "APIPark 提供詳盡的 API 調用日誌,幫助企業監控、分析和審計 API 的運行狀況。", + "Kd518ba3e": "Hello!歡迎使用 APIPark", + "K7e04ea16": "🦄 APIPark 是開源的一站式 AI 網關和 API 開發者門戶,幫助企業和開發者快速接入 100+ AI 模型,將 AI 模型和 Prompt 提示詞組合成新的 API,並且統一所有 AI 的請求數據格式,避免切換 AI 模型或調整提示詞時影響你的 APP 應用程式或者微服務。你還可以通過 APIPark 的開發者門戶在團隊內共享 API,管理調用的應用程式並保障你的 API 安全,通過清晰的圖表來監控你的 AI API 使用情況。", + "Kedd41c18": "✨ 如果你喜歡APIPark,歡迎在Github為我們Star或提供產品回饋意見。", + "Kef02fd87": "快速入門", + "K43a3b38d": "我們提供了一些任務來幫你快速了解 APIPark", + "K408bfcf1": "進階教程", + "K1afaf20e": "了解 APIPark 如何更好地管理 API 和 AI", + "K48f7e21f": "了解更多功能", + "K698296e2": "隱藏該教程", + "Kd2c1a316": "登入", + "Kf076f63c": "請輸入帳戶", + "K25c895d5": "請輸入密碼", + "K551b0348": "密碼", + "K192b3e38": "訪客模式", + "K91aa4801": "您可通過訪客模式查看所有頁面和功能,但是無法編輯數據。訪客模式僅用於了解產品功能,您可以在正式產品中關閉該功能。", + "K480045ce": "Version (0)-(1)", + "Kadee8e49": "日誌設置", + "K2724314b": "提供詳盡的 API 調用日誌,幫助企業監控、分析和審計 API 的運行狀況。", + "K33c76dbc": "部門名稱", + "K84829ca9": "父部門 ID", + "K4d7fc74b": "子部門名稱", + "Keb9fcdad": "用戶名", + "Kc654b275": "電郵", + "Kbe2ecc69": "部門", + "Ka16e6c44": "未激活或禁用的成員無法加入部門", + "Ked03ba97": "請選擇成員需要新加入的部門", + "K184d3473": "添加帳戶", + "K1ecb35f2": "編輯成員信息", + "Ke6f00b44": "加入部門", + "K501cb1e7": "此操作無法恢復,確定刪除成員?", + "Kf20863b5": "成員與部門", + "K52c8a730": "啟用", + "K718c9310": "禁用", + "K5f27a546": "輸入用戶名、電郵查找成員", + "K7c97c5df": "移出", + "K1362a512": "禁用", + "K6e1289b1": "啟用", + "K1f4b5385": "刪除", + "K26c698bb": "添加部門", + "Kb9cf2a7d": "添加子部門", + "Kc83551f5": "重命名", + "K5cfdd950": "該數據刪除後將無法找回,是否刪除?", + "K74aef1ad": "成員", + "K3f1077c9": "設置成員的角色,成員只能夠看到角色權限範圍內的功能和數據。", + "Kdce62a6": "搜索部門", + "Ka46b9b24": "數據源類型", + "Kbb0cdcd0": "數據源地址", + "Kd9dfb884": "Organization", + "K3e770a75": "鑒權 Token", + "K8ef69ee2": "密鑰", + "Kba3507d6": "上傳密鑰", + "K93ac0f23": "密鑰文件的後綴名一般是 .key", + "K7cdd1331": "上傳證書", + "K6d91905d": "證書文件的後綴名一般是 .crt 或 .pem", + "Kd0f6ded7": "添加證書", + "Ke5732d60": "修改證書", + "K3ca07a70": "證書管理", + "Kdb927f83": "通過為 API 服務設置和管理 SSL 證書,企業可以加密數據傳輸,防止敏感信息泄露。", + "Ke93d36ed": "集群", + "K877985b7": "修改設置", + "Kdf66a675": "設置訪問 API 的集群,讓 API 在分佈式環境中穩定運行,並且能夠根據業務需求進行靈活擴展和優化。", + "Ke039b9b5": "正常", + "K23a3bd72": "異常", + "Kf12b3034": "管理地址", + "K867e6faf": "服務地址", + "K2a49373f": "同步地址", + "K5878440c": "集群地址", + "K5e9022f8": "下一步", + "Kdbafd6f9": "設置監控報表的數據來源,設置完成之後即可獲得詳細的API調用統計圖表。", + "K1358acf": "統計圖表", + "K62dabdf6": "地址(IP:端口)", + "K2db12335": "組織", + "K8e7a0f80": "資源設置", + "K95c3fd8b": "設置角色的權限範圍。", + "K138facd3": "系統角色", + "K6eac768d": "添加角色", + "Kb9c2cf02": "團隊角色", + "K2a16c93b": "單位:ms,最小值:1", + "Ka945cfb1": "API 設置", + "K2e050340": "API 基礎信息", + "Kba92c499": "攔截接口", + "Kde9d6e8e": "開啟攔截後,網關會攔截所有該路徑的請求。", + "K6bc47edb": "請求協議", + "K1365fe45": "請求方式", + "K90f3c02f": "轉發規則設置", + "Kb7df6ac1": "攔截", + "K5c1722fe": "放行", + "K28435c5c": "API 詳情", + "Ka9c08390": "只允許上傳PNG、JPG或SVG格式的圖片", + "K413b9869": "服務名稱", + "K9919285b": "服務類型", + "Kcef64f4d": "默認 AI 供應商", + "Kcab588a9": "未設置任何 AI 模型供應商,", + "Kb9b56111": "立即設置", + "Kcf756b7a": "API 調用前綴", + "K13edc043": "作為服務內所有API的前綴,比如host/{service_name}/{api_path},一旦保存無法修改", + "Kf52a584d": "所屬服務分類", + "K72b21be5": "設置服務展示在服務市場中的哪個分類下", + "Kdc840242": "圖標", + "K427a5bd5": "僅支持 .png .jpg .jpeg .svg 格式的圖片文件, 大於 1KB 的文件將被壓縮", + "K44bc352d": "Logo", + "Kde6bae17": "刪除服務", + "K885ea699": "刪除操作不可恢復,請謹慎操作!", + "Kda8d5ea1": "上游", + "K12f58863": "服務提供了高性能 API 網關,並且可以無縫接入多種大型 AI 模型,並將這些 AI 能力打包成 API 進行調用,從而大幅簡化了 AI 模型的使用門檻。同時,我們的平台提供了完善的 API 管理功能,支持 API 的創建、監控、訪問控制等,保障開發者可以高效、安全地開發和管理 API 服務。", + "K2d6658ed": "添加服務", + "K7b8f623f": "輸入名稱、ID、所屬團隊、負責人查找服務", + "Kad98e030": "上游類型", + "Kdd9b5008": "後端默認使用的IP地址", + "Kc9acdb25": "負載均衡", + "K632dba5c": "轉發 Host", + "Kc1f08a63": "重寫 Host", + "K628f6851": "超時時間", + "Kaff62621": "超時重試次數", + "Kf14d159b": "次", + "Kc41ca30e": "調用頻率限制", + "K753e8aeb": "次/秒", + "K813e1c0a": "團隊名稱", + "K692f5aa6": "團隊 ID", + "K5de0bc2": "團隊 ID(team_id)可用於檢索團隊,一旦保存無法修改。", + "Ka63dd985": "團隊負責人", + "Ka6bcd272": "負責人對團隊內的團隊、服務、成員有管理權限", + "Ka2012bdd": "刪除團隊", + "Kbde1f3d": "服務數據清除後,方可刪除", + "K395acc14": "移除成員", + "Kec46a57f": "添加成員", + "K48724410": "輸入姓名查找", + "Kb9052305": "搜索用戶名、電郵", + "K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。", + "K510cdd27": "添加團隊", + "K9244ae14": "輸入名稱、ID、負責人查找團隊", + "Kc7b24b4b": "設置團隊", + "Kecb51e2c": "舊密碼", + "K8266bcf2": "新密碼", + "Ka9aef039": "確認密碼", + "Kcf42dcda": "兩次密碼不一致", + "Kf876a42d": "修改密碼", + "K8ed884f": "管理個人帳戶", + "K9be8e1d7": "API調用統計", + "K521ab28e": "選擇服務", + "Kcc8265e1": "選擇API", + "Kc380335f": "路徑", + "K8aefc1e4": "請輸入請求路徑進行搜索", + "K50d471b2": "重置", + "Kee8ae330": "查詢", + "Ka2c794a2": "導出", + "Kaf70c3b": "退出全屏", + "Kd22841a4": "(0)調用詳情", + "K1512e983": "應用程式調用統計", + "Kb4d2007f": "請選擇應用程式", + "K8c7f2d2e": "調用趨勢", + "K657c3452": "(0)-(1)調用趨勢", + "Kc04efb87": "調用量統計", + "Keb98266e": "加入總體數據對比", + "K18c2ed46": "(0)調用量", + "Kc3741830": "(0)成功率", + "Ka6aa5863": "請求數", + "K9eaef42": "成功率", + "K7082a4af": "轉發數", + "K1ce386fb": "成功率", + "K87d6877e": "4xx", + "K4c8a54db": "5xx", + "Kd566283e": "趨勢", + "K21ad4a6a": "(0)報文", + "Kd23a0be6": "請求報文", + "Kec3e8361": "響應報文", + "Ke6250744": "4XX數", + "K2d79d4e1": "5XX數", + "Kcf6553c6": "服務調用統計", + "Kffcfe375": "請選擇服務", + "Ka65f739c": "調用詳情", + "K89b7ac79": "API Top10", + "Kc0915603": "應用程式 Top10", + "Kf90b54": "服務 Top10", + "Kfb26388": "無請求數據", + "Kc8cbd8f8": "請求統計", + "K8dece48": "無轉發統計數據", + "K1ee32434": "轉發統計", + "Kcd125e4d": "無調用量統計數據", + "Kaa114e8b": "無報文統計數據", + "K3ad84406": "報文統計", + "Kfa088d49": "集群設置並開啟監控", + "K3da3b9a0": "監控功能用於輔助管理集群內信息,請設置集群、設置監控信息後查看當前集群監控情況;", + "Kaddacfb": "集群", + "K4ac33975": "設置集群地址,以確保監控系統能夠正確識別和連接到集群", + "Ke5ed9810": "設置集群", + "K1a132228": "監控", + "K6af08c3c": "設置監控", + "K4a1a14": "監控總覽", + "K69741ea7": "服務調用", + "K9c8d9933": "API 調用", + "K145e4941": "億", + "Ke6a935d": "萬", + "Kd59290a2": "搜索分類或標籤", + "K6b75bdbc": "無API數據", + "Kd8a7a689": "搜索或選擇應用程式", + "K4b15d6f5": "申請理由", + "Kb7e869a4": "應用程式管理", + "Kb71b5a13": "鑒權類型", + "K4d1465ee": "ISS", + "K5dcd7ed8": "簽名算法", + "K5b0eedd3": "Secret", + "K44f4ffe1": "RSA 公鑰", + "Kc5ecd7d9": "用戶名 JSONPath", + "K417d85cf": "校驗字段", + "K3b82fe1d": "是否 Base64 加密", + "K49b5f4a3": "AK", + "K31418470": "SK", + "Kbfeb5297": "API Key", + "K1a78e6f0": "過期時間", + "Ke64e43a": "隱藏認證信息", + "K5168eb63": "應用程式名稱", + "K546e46f": "應用程式 ID", + "K95764d1d": "刪除應用程式", + "K217cb125": "鑒權詳情", + "K2bb63eca": "添加鑒權", + "Kd74d69b7": "編輯鑒權", + "K9cbe1e0": "修改", + "Kb6e9328f": "訪問授權", + "Kd23d1716": "添加授權", + "K9dfa2c97": "永不過期", + "Kfa920c0": "過期時間", + "Kcce1af60": "訂閱的服務", + "Kfefa9b58": "審核詳情", + "K3118fdb0": "取消訂閱", + "Ked811bb1": "是否取消訂閱?", + "K50c39a62": "取消訂閱申請", + "K1856c229": "是否取消訂閱申請?", + "K66ea2f0": "搜索服務", + "K8adf7f8b": "審核中", + "K667bbbe7": "添加應用程式", + "Ka4b45550": "無服務描述", + "K3c7b175f": "訂閱服務:已通過 (0) ,審核中 (1)", + "Kbe3e9335": "退出測試", + "K370a3eb2": "服務市場", + "Kf7ec36d": "服務詳情", + "K58ca9485": "申請訂閱", + "K59cdbec3": "服務介紹", + "K4aa9ed2c": "申請訂閱", + "K6c060779": "服務信息", + "K8723422e": "接入應用程式", + "Kb97544cb": "供應方", + "Kb32f0afe": "分類", + "K81634069": "版本", + "Keefda53d": "更新時間", + "K96a2f1c8": "無標籤", + "K72b0c0b3": "API 數量", + "K93d5a66e": "接入應用程式數量", + "K96059c69": "關聯標籤", + "K8b7c2592": "更新者", + "K32263abd": "添加 Open Api", + "K7829bb78": "設置 Open Api", + "Kcdf76005": "Open Api", + "Ke2601944": "調用服務", + "K8504bca8": "放大", + "K693c1b41": "縮小", + "K3d7465f7": "文件日誌", + "Kc87167a0": "HTTP 日誌", + "K54630fe8": "Kafka 日誌", + "Kd5c3966e": "NSQ 日誌", + "K2e3de2c1": "Syslog 日誌", + "K48322168": "未分配", + "K98f247f9": "超級管理員", + "K9c8a571f": "團隊管理員", + "K929b485b": "運維管理員", + "K82cc5ec2": "普通成員", + "Ke41d7451": "只讀成員", + "Kf99e8b66": "服務管理員", + "Kda8db57a": "服務開發者", + "K216a1ac7": "應用程式開發者", + "K27924db": "應用程式管理員", + "K8dc5c723": "驅動名稱", + "Kda249fe8": "失敗", + "Kcf2df651": "失敗", + "K7e6a859d": "作用範圍", + "K3a008b34": "添加條目", + "Ke0599ef7": "添加地址", + "K48d3b5c4": "文件名稱", + "Kafde0d2a": "存放目錄", + "Kfb2926ac": "日誌分割週期", + "Kd96c2c69": "單位:天", + "Kc2b776fa": "輸出格式", + "K7b7cdac2": "格式化設置", + "K2f59807a": "服務器地址", + "Kb1cfa6e7": "Access 日誌", + "K540488a8": "NSQD 地址列表", + "K8bc33a11": "鑒權 Secret", + "K1cd3002f": "網絡協議", + "Kdfaa32c8": "日誌等級", + "Kc0408d9c": "單行", + "Ke3db239d": "小時", + "K3509a9f8": "天", + "Kb3960e83": "未發布", + "K8bd1e18": "待發布", + "K225a6c43": "單位:s,最小值:1", + "K593e0c7e": "無需審核", + "Ke2d747d9": "需要審核", + "Kc29dabf2": "Base URL", + "Kd55c6887": "審核", + "K300c89d4": "創建 API 時會默認選擇該供應商,修改默認供應商不會影響現有 API", + "Kefaf9956": "創建時間", "Kad1c674c": "協議", "Kad01bc3e": "方法", "Ka29b346f": "地址(IP 端口或域名)", @@ -785,5 +785,50 @@ "K1bc5e0a3": "消費者 IP", "K6f39ea21": "鑑權名稱", "K8c34c02f": "脫敏前", - "K8e3d388d": "脫敏後" - } + "K8e3d388d": "脫敏後", + "K94b48734": "获取 AI providers 失败", + "Kf23a8988": "AI 供应商", + "K4d81a657": "预览", + "K91144ebd": "AI API 列表", + "Kf8187c33": "请输入 APIURL 搜索", + "Kee4139c2": "拦截接口", + "K3e38ea": "开启拦截后,网关会拦截所有该路径的请求。", + "Kd752a3a8": "未配置 AI 模型", + "K8b7ac871": "前往设置", + "Kf97448b3": "已设置", + "K30d4d8df": "未设置", + "Kc2ee5223": "默认模型", + "K608af899": "负载优先级", + "K65b7a96": "负载优先级决定在原供应商异常或停用后,优先使用哪一个供应商。优先级数字越小,优先级越高。", + "K9eccff16": "优先级必须大于 0", + "Kfcf02780": "请输入优先级", + "K5c6dcf58": "API Key(默认 Key)", + "K59bf8ed9": "LLM 状态管理", + "Kedd64e4d": "停用", + "K2a3aeb8d": "默认:", + "K8b88ef63": "请输入 APIKey", + "Kcbd30819": "API Key", + "Kcb6e2d3e": "请填写 APIKey", + "Ke13e332a": "设置过期时间", + "K409aa8ba": "请选择过期时间", + "Kba69594c": "超额", + "Kb9e7ceda": "过期", + "Kac405b50": "错误", + "K3fde5b49": "请选择状态", + "K434b7e76": "编辑 APIKey", + "K28190dbc": "删除成功", + "Kb5fcf5b8": "停用成功", + "K5940d788": "启用成功", + "K8743bccd": "排序成功", + "K19590c2c": "调用优先级", + "K89f135a7": "已用 Token", + "K1acc30b2": "编辑时间", + "Kefb03657": "APIKey 资源池", + "Kc0352e64": "支持单个 API 模型供应商下创建多个 APIKey APIKey 进行智能负载均衡", + "Kd25acba1": "请输入名称搜索", + "K6d0388a0": "添加 APIKey", + "Ke32702ac": "儲存後供應商狀態變為【停用】,使用本供應商的 API 將暫時使用負載優先級最高的正常供應商。", + "Ka08c28d4": "儲存後供應商狀態變為【正常】,恢復調用本供應商的 AI 能力。", + "Kab8fe398": "目前調用狀態:", + "K4880fd04": "新增 (0) APIKey" +} diff --git a/frontend/packages/common/src/monacoConfig.ts b/frontend/packages/common/src/monacoConfig.ts index 97b65136..5ecafca1 100644 --- a/frontend/packages/common/src/monacoConfig.ts +++ b/frontend/packages/common/src/monacoConfig.ts @@ -1,26 +1,26 @@ -import * as monaco from 'monaco-editor'; -import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'; -import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'; -import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'; -import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'; -import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'; +import * as monaco from 'monaco-editor' +import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' +import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker' +import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker' +import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker' +import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' self.MonacoEnvironment = { - getWorker(_, label) { - if (label === 'json') { - return new jsonWorker(); - } - if (label === 'css' || label === 'scss' || label === 'less') { - return new cssWorker(); - } - if (label === 'html' || label === 'handlebars' || label === 'razor') { - return new htmlWorker(); - } - if (label === 'typescript' || label === 'javascript') { - return new tsWorker(); - } - return new editorWorker(); - }, -}; + getWorker(_, label) { + if (label === 'json') { + return new jsonWorker() + } + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker() + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker() + } + if (label === 'typescript' || label === 'javascript') { + return new tsWorker() + } + return new editorWorker() + } +} -export { monaco }; \ No newline at end of file +export { monaco } diff --git a/frontend/packages/common/src/types/iconpark.d.ts b/frontend/packages/common/src/types/iconpark.d.ts new file mode 100644 index 00000000..4797b6f3 --- /dev/null +++ b/frontend/packages/common/src/types/iconpark.d.ts @@ -0,0 +1,10 @@ +declare namespace JSX { + interface IntrinsicElements { + 'iconpark-icon': { + name?: string; + size?: string | number; + color?: string; + [key: string]: any; + } + } +} diff --git a/frontend/packages/common/src/utils/curl.ts b/frontend/packages/common/src/utils/curl.ts index 36310edb..df78d6c7 100644 --- a/frontend/packages/common/src/utils/curl.ts +++ b/frontend/packages/common/src/utils/curl.ts @@ -71,9 +71,12 @@ export class ParseCurl { * @param curlStr curl 字符串 * @param options 配置项 */ - constructor(curlStr: string, options?: { - ignoreDisabledHeaders: boolean; -}) { + constructor( + curlStr: string, + options?: { + ignoreDisabledHeaders: boolean + } + ) { this.curlStr = curlStr this.options = options || this.options this.validateCurl() @@ -198,11 +201,12 @@ export class ParseCurl { */ case !!arg: switch (state) { - case 'header':{ + case 'header': { const field = parseField(arg) result.headers[field[0]] = field[1] state = '' - break} + break + } case 'user-agent': result.headers['User-Agent'] = arg state = '' diff --git a/frontend/packages/common/src/utils/dataTransfer.ts b/frontend/packages/common/src/utils/dataTransfer.ts index d5be17f7..3a365edc 100644 --- a/frontend/packages/common/src/utils/dataTransfer.ts +++ b/frontend/packages/common/src/utils/dataTransfer.ts @@ -1,25 +1,23 @@ - import { ColumnFilterItem } from 'antd/es/table/interface' -import {DepartmentListItem} from '@core/const/member/type' -import { RcFile } from 'antd/es/upload'; +import { DepartmentListItem } from '@core/const/member/type' +import { RcFile } from 'antd/es/upload' -export const handleDepartmentListToFilter:(departmentList:DepartmentListItem[])=>ColumnFilterItem[] = (departmentList:DepartmentListItem[])=>{ - return departmentList?.map((x:DepartmentListItem)=>( - { - text:x.name, - value:x.id, - children:x.children ? handleDepartmentListToFilter(x.children):null - } - )) +export const handleDepartmentListToFilter: (departmentList: DepartmentListItem[]) => ColumnFilterItem[] = ( + departmentList: DepartmentListItem[] +) => { + return departmentList?.map((x: DepartmentListItem) => ({ + text: x.name, + value: x.id, + children: x.children ? handleDepartmentListToFilter(x.children) : null + })) } export const getImgBase64 = (img: RcFile, callback: (url: string) => void) => { - const reader = new FileReader(); - reader.addEventListener('load', () => callback(reader.result as string)); - reader.readAsDataURL(img); -}; + const reader = new FileReader() + reader.addEventListener('load', () => callback(reader.result as string)) + reader.readAsDataURL(img) +} - -export const frontendTimeSorter = (a:{[k:string]:string},b: { [k: string]: string }, field:string) =>{ - return (new Date((a)[field])).getTime() - (new Date((b)[field])).getTime() -} \ No newline at end of file +export const frontendTimeSorter = (a: { [k: string]: string }, b: { [k: string]: string }, field: string) => { + return new Date(a[field]).getTime() - new Date(b[field]).getTime() +} diff --git a/frontend/packages/common/src/utils/ip.ts b/frontend/packages/common/src/utils/ip.ts index b27cc339..11ae080f 100644 --- a/frontend/packages/common/src/utils/ip.ts +++ b/frontend/packages/common/src/utils/ip.ts @@ -1,20 +1,21 @@ -export const extractIPFromURL = (url:string|string[]) =>{ - if (Array.isArray(url)) { - url = url[0]; // 获取第一个 URL - } - if (typeof url !== 'string' || !url.includes("://")) { - console.warn("Invalid URL format"); - return null; - } - const match = url.match(/https?:\/\/([\d.]+):\d+/); - return match ? match[1] : null; +export const extractIPFromURL = (url: string | string[]) => { + if (Array.isArray(url)) { + url = url[0] // 获取第一个 URL + } + if (typeof url !== 'string' || !url.includes('://')) { + console.warn('Invalid URL format') + return null + } + const match = url.match(/https?:\/\/([\d.]+):\d+/) + return match ? match[1] : null } -export const isPrivateIP = (ip:string) =>{ - if (typeof ip !== 'string') { - console.error("Invalid IP format"); - return false; - } - const privateIpRegex = /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3})$|^(172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3})$|^(192\.168\.\d{1,3}\.\d{1,3})$/; - return privateIpRegex.test(ip); -} \ No newline at end of file +export const isPrivateIP = (ip: string) => { + if (typeof ip !== 'string') { + console.error('Invalid IP format') + return false + } + const privateIpRegex = + /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3})$|^(172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3})$|^(192\.168\.\d{1,3}\.\d{1,3})$/ + return privateIpRegex.test(ip) +} diff --git a/frontend/packages/common/src/utils/navigation.tsx b/frontend/packages/common/src/utils/navigation.tsx index 5c4df895..cc36524c 100644 --- a/frontend/packages/common/src/utils/navigation.tsx +++ b/frontend/packages/common/src/utils/navigation.tsx @@ -1,10 +1,9 @@ -import { Icon } from "@iconify/react/dist/iconify.js"; -import { MenuProps } from "antd"; +import { Icon } from '@iconify/react/dist/iconify.js' +import { MenuProps } from 'antd' +export type MenuItem = Required['items'][number] -export type MenuItem = Required['items'][number]; - -export function getNavItem({ +export function getNavItem({ label, key, path, @@ -13,68 +12,67 @@ export function getNavItem({ type, access }: { - label: React.ReactNode; - key: React.Key; - path?: string; - icon?: React.ReactNode; - children?: MenuItem[]; - type?: 'group'; - access?: string[] | string; + label: React.ReactNode + key: React.Key + path?: string + icon?: React.ReactNode + children?: MenuItem[] + type?: 'group' + access?: string[] | string }): MenuItem { return { key, icon, - routes:children, - name:label, + routes: children, + name: label, type, access, path - } as MenuItem; + } as MenuItem } export function getItem( - label: React.ReactNode, - key: React.Key, - icon?: React.ReactNode, - children?: MenuItem[], - type?: 'group', - access?:string[] | string - ): MenuItem { - return { - key, - icon, - children, - label, - type, - access - } as MenuItem; - } + label: React.ReactNode, + key: React.Key, + icon?: React.ReactNode, + children?: MenuItem[], + type?: 'group', + access?: string[] | string +): MenuItem { + return { + key, + icon, + children, + label, + type, + access + } as MenuItem +} - export function getTabItem( - label: React.ReactNode, - key: React.Key, - children?: MenuItem[], - type?: 'group', - access?:string - ) { - return { - key, - label, - access - } +export function getTabItem( + label: React.ReactNode, + key: React.Key, + children?: MenuItem[], + type?: 'group', + access?: string +) { + return { + key, + label, + access } +} - export function transformMenuData(data: any[]): MenuItem[] { - return data.map(item => { - const { name, key, path, icon, children, access } = item; + return data.map((item) => { + const { name, key, path, icon, children, access } = item return getNavItem({ - label:name, + label: name, key, path, icon: icon ? : undefined, - children:children ? transformMenuData(children) : undefined, - access} - ); - }); -} \ No newline at end of file + children: children ? transformMenuData(children) : undefined, + access + }) + }) +} diff --git a/frontend/packages/common/src/utils/permission.ts b/frontend/packages/common/src/utils/permission.ts index ad02f7f7..f8317c28 100644 --- a/frontend/packages/common/src/utils/permission.ts +++ b/frontend/packages/common/src/utils/permission.ts @@ -1,33 +1,34 @@ +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { AccessDataType } from '@common/const/type' -import { PERMISSION_DEFINITION } from "@common/const/permissions" -import { AccessDataType } from "@common/const/type" - - -export const checkAccess:(access:AccessDataType, accessData:Map)=>boolean = (access, accessData)=>{ - if(!access){ - return true - } - const accLevel = access.split('.')[0] - if(['system','team'].indexOf(accLevel) === -1){ - console.warn('权限字段有误:',access) - return false - } - const neededBackendAccessArr = PERMISSION_DEFINITION[0]?.[access]?.granted?.anyOf[0].backend || [] - let accessSet = new Set(accessData.get('system')) - if(accLevel === 'team'){ - accessSet = new Set(Array.from(accessSet).concat(accessData?.get('team') || [])) - } - return accessSet!.size > 0 ? hasIntersection(neededBackendAccessArr, accessSet) : false +export const checkAccess: (access: AccessDataType, accessData: Map) => boolean = ( + access, + accessData +) => { + if (!access) { + return true + } + const accLevel = access.split('.')[0] + if (['system', 'team'].indexOf(accLevel) === -1) { + console.warn('权限字段有误:', access) + return false + } + const neededBackendAccessArr = PERMISSION_DEFINITION[0]?.[access]?.granted?.anyOf[0].backend || [] + let accessSet = new Set(accessData.get('system')) + if (accLevel === 'team') { + accessSet = new Set(Array.from(accessSet).concat(accessData?.get('team') || [])) + } + return accessSet!.size > 0 ? hasIntersection(neededBackendAccessArr, accessSet) : false } -const hasIntersection = (arr1:string[], set1:Set)=> { - const arr2 = Array.from(set1) - const set = new Set(arr1.length > arr2.length ? arr2:arr1) - const arr = arr1.length > arr2.length ? arr1:arr2 - for (const item of arr) { - if (set.has(item)) { - return true; // 发现交集 - } +const hasIntersection = (arr1: string[], set1: Set) => { + const arr2 = Array.from(set1) + const set = new Set(arr1.length > arr2.length ? arr2 : arr1) + const arr = arr1.length > arr2.length ? arr1 : arr2 + for (const item of arr) { + if (set.has(item)) { + return true // 发现交集 } - return false; // 没有交集 - } \ No newline at end of file + } + return false // 没有交集 +} diff --git a/frontend/packages/common/src/utils/plugin.tsx b/frontend/packages/common/src/utils/plugin.tsx index db23aafc..d2d1a33d 100644 --- a/frontend/packages/common/src/utils/plugin.tsx +++ b/frontend/packages/common/src/utils/plugin.tsx @@ -1,34 +1,36 @@ -import { CoreObj, PluginConfigType, PluginRouterConfig, RouteConfig, RouterMapConfig } from '@common/const/type'; +import { + ApiparkPluginDriverType, + CoreObj, + PluginConfigType, + PluginRouterConfig, + RouteConfig, + RouterMapConfig +} from '@common/const/type' import { isFunction } from 'lodash-es' -// @ts-expect-error module cannot find -import { __federation_method_setRemote,__federation_method_getRemote,__federation_method_unwrapDefault } from 'virtual:__federation__'; -import { ApiparkPluginDriverType } from '@common/const/type'; -import React from 'react'; +import React from 'react' +import { __federation_method_getRemote, __federation_method_setRemote } from 'virtual:__federation__' -interface RemoteModuleConfig{ - type:string - remoteEntry:string - exposedModule:string - remoteName:string +interface RemoteModuleConfig { + type: string + remoteEntry: string + exposedModule: string + remoteName: string } -export async function loadRemoteModule(remoteModuleConfig:RemoteModuleConfig){ - __federation_method_setRemote(remoteModuleConfig.remoteName, { - url: () => Promise.resolve(remoteModuleConfig.remoteEntry), - format: 'esm', - from: 'vite', - }); - return await __federation_method_getRemote( - remoteModuleConfig.remoteName, - `./${remoteModuleConfig.exposedModule}` - ) +export async function loadRemoteModule(remoteModuleConfig: RemoteModuleConfig) { + __federation_method_setRemote(remoteModuleConfig.remoteName, { + url: () => Promise.resolve(remoteModuleConfig.remoteEntry), + format: 'esm', + from: 'vite' + }) + return await __federation_method_getRemote(remoteModuleConfig.remoteName, `./${remoteModuleConfig.exposedModule}`) } export function generateRemoteModuleTemplate( pluginName: string, exposedModule: string, pluginPath: string -):RemoteModuleConfig { +): RemoteModuleConfig { return { type: 'module', remoteEntry: pluginPath, @@ -45,110 +47,144 @@ export function validateExportLifecycle(exports: unknown) { export const DEFAULT_LOCAL_PLUGIN_PATH = '/plugin-frontend/' -export const ApiparkPluginDriver = (routerMap: Map): ApiparkPluginDriverType => { return { - builtIn: { - component: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => { - if(!coreObj || !pluginConfig) return coreObj - for (const pluginRouter of pluginConfig.router) { - routerMap.get(pluginConfig.name) && coreObj.pluginProvider.setRouterConfig(pluginRouter.type === 'root' , { - ...routerMap.get(pluginConfig.name)!, - key:pluginConfig.name, - path:pluginRouter.path}) +export const ApiparkPluginDriver = (routerMap: Map): ApiparkPluginDriverType => { + return { + builtIn: { + component: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => { + if (!coreObj || !pluginConfig) return coreObj + for (const pluginRouter of pluginConfig.router) { + routerMap.get(pluginConfig.name) && + coreObj.pluginProvider.setRouterConfig(pluginRouter.type === 'root', { + ...routerMap.get(pluginConfig.name)!, + key: pluginConfig.name, + path: pluginRouter.path + }) } - return coreObj - } - }, - remote: { - normal: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => { - if(!coreObj || !pluginConfig) return coreObj - const routerToChanged:RouteConfig[] = coreObj.routerConfig.find((router: RouteConfig) => router.path === '/' && router?.pathMatch !== 'full')!.children as RouteConfig[] - const remoteRouter:RouteConfig[] = routerToChanged.find((item:RouteConfig) => item?.data?.['type'] === 'remotePlugin') as RouteConfig[] + return coreObj + } + }, + remote: { + normal: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => { + if (!coreObj || !pluginConfig) return coreObj + const routerToChanged: RouteConfig[] = coreObj.routerConfig.find( + (router: RouteConfig) => router.path === '/' && router?.pathMatch !== 'full' + )!.children as RouteConfig[] + const remoteRouter: RouteConfig[] = routerToChanged.find( + (item: RouteConfig) => item?.data?.['type'] === 'remotePlugin' + ) as RouteConfig[] if (!remoteRouter) { - routerMap.get('remote') && coreObj.pluginProvider.setRouterConfig(false,{ - ...routerMap.get('remote')!, key:'remote', path:'remote',type:'remotePlugin',children:[{ - path:':moduleName', - component: routerMap.get('remote')!.component} + routerMap.get('remote') && + coreObj.pluginProvider.setRouterConfig(false, { + ...routerMap.get('remote')!, + key: 'remote', + path: 'remote', + type: 'remotePlugin', + children: [ + { + path: ':moduleName', + component: routerMap.get('remote')!.component + } ] }) - } - return coreObj - } - }, - intelligent: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - normal: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => { - if(!coreObj || !pluginConfig) return coreObj - if(['logsettings','resourcesettings'].indexOf(pluginConfig.name) !== -1){ - const routerToChanged:RouteConfig[] = coreObj.routerConfig.find((router: RouteConfig) => router.path === '/' && router?.pathMatch !== 'full')!.children as RouteConfig[] - const remoteRouter:RouteConfig[] = routerToChanged.find((item:RouteConfig) => item?.data?.['key'] === pluginConfig.name) as RouteConfig[] - if(!remoteRouter){ - routerMap.get(pluginConfig.name) && routerToChanged.unshift({...routerMap.get(pluginConfig.name)!, key:pluginConfig.name, path:pluginConfig.path}) } - return + return coreObj } - const remoteRouter = coreObj.routerConfig.find((item:RouteConfig) => item?.data?.['type'] === 'intelligentPlugin') - if (!remoteRouter) { - // coreObj.pluginProvider.setRouterConfig(false, { - // path: 'template', - // loadChildren: coreObj.builtInPluginLoader('intelligent'), - // data: { - // type: 'intelligentPlugin' - // } - // }, coreObj.routerConfig) - } - return coreObj - } - }, - local: { - router: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => { - if(!coreObj || !pluginConfig) return coreObj - for (const pluginRouter of pluginConfig.router) { - if (pluginRouter.type === 'sub') { - continue - } - updateRouterConfigWithPlugin(coreObj, pluginRouter, pluginConfig) - } - return coreObj }, - preload: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => { - if(!coreObj || !pluginConfig) return coreObj - coreObj.setExecuteList(prev=>[...prev,{ ...pluginConfig, expose: 'Bootstrap', bootstrap: 'BootstrapModule.bootstrap' }]) - for (const pluginRouter of pluginConfig.router) { - updateRouterConfigWithPlugin(coreObj, pluginRouter, pluginConfig) + intelligent: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + normal: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => { + if (!coreObj || !pluginConfig) return coreObj + if (['logsettings', 'resourcesettings'].indexOf(pluginConfig.name) !== -1) { + const routerToChanged: RouteConfig[] = coreObj.routerConfig.find( + (router: RouteConfig) => router.path === '/' && router?.pathMatch !== 'full' + )!.children as RouteConfig[] + const remoteRouter: RouteConfig[] = routerToChanged.find( + (item: RouteConfig) => item?.data?.['key'] === pluginConfig.name + ) as RouteConfig[] + if (!remoteRouter) { + routerMap.get(pluginConfig.name) && + routerToChanged.unshift({ + ...routerMap.get(pluginConfig.name)!, + key: pluginConfig.name, + path: pluginConfig.path + }) + } + return + } + const remoteRouter = coreObj.routerConfig.find( + (item: RouteConfig) => item?.data?.['type'] === 'intelligentPlugin' + ) + if (!remoteRouter) { + // coreObj.pluginProvider.setRouterConfig(false, { + // path: 'template', + // loadChildren: coreObj.builtInPluginLoader('intelligent'), + // data: { + // type: 'intelligentPlugin' + // } + // }, coreObj.routerConfig) + } + return coreObj } - return coreObj + }, + local: { + router: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => { + if (!coreObj || !pluginConfig) return coreObj + for (const pluginRouter of pluginConfig.router) { + if (pluginRouter.type === 'sub') { + continue + } + updateRouterConfigWithPlugin(coreObj, pluginRouter, pluginConfig) + } + return coreObj + }, + preload: (coreObj?: CoreObj, pluginConfig?: PluginConfigType) => { + if (!coreObj || !pluginConfig) return coreObj + coreObj.setExecuteList((prev) => [ + ...prev, + { ...pluginConfig, expose: 'Bootstrap', bootstrap: 'BootstrapModule.bootstrap' } + ]) + for (const pluginRouter of pluginConfig.router) { + updateRouterConfigWithPlugin(coreObj, pluginRouter, pluginConfig) + } + return coreObj + } + // extender: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => {} } - // extender: (coreObj?:CoreObj, pluginConfig?:PluginConfigType) => {} } } -} -async function updateRouterConfigWithPlugin (coreObj: CoreObj, pluginRouter: PluginRouterConfig, pluginConfig: PluginConfigType) { +async function updateRouterConfigWithPlugin( + coreObj: CoreObj, + pluginRouter: PluginRouterConfig, + pluginConfig: PluginConfigType +) { if (!pluginRouter.expose) { throw new Error('pluginRouter.expose is required') } else { for (const pluginRouter of pluginConfig.router) { - const loadedModule = await coreObj.pluginLoader.loadModule( - pluginRouter.path, - pluginConfig.name, - pluginRouter.expose!, - pluginConfig.path || `${DEFAULT_LOCAL_PLUGIN_PATH}${pluginConfig.name}/apipark.js` - ) + const loadedModule = await coreObj.pluginLoader.loadModule( + pluginRouter.path, + pluginConfig.name, + pluginRouter.expose!, + pluginConfig.path || `${DEFAULT_LOCAL_PLUGIN_PATH}${pluginConfig.name}/apipark.js` + ) const loadedModulePage = loadedModule[pluginRouter.expose!] - const LazyComponent = React.lazy(() => Promise.resolve({ default: loadedModulePage?.default || loadedModulePage })); - + const LazyComponent = React.lazy(() => + Promise.resolve({ default: loadedModulePage?.default || loadedModulePage }) + ) + const newRouter: RouteConfig = { path: pluginRouter.path, key: pluginConfig.name, lazy: () => Promise.resolve({ default: (props: any) => }), pathPrefix: pluginRouter.path.endsWith('/*') ? pluginRouter.path.slice(0, -2) : pluginRouter.path, - lifecycle:{ + lifecycle: { canActivate: loadedModule?.beforeMount, canLoad: loadedModule?.mount, canDeactivate: loadedModule?.beforeUnmount, deactivated: loadedModule?.unmount } - }; - coreObj.pluginProvider.setRouterConfig(pluginRouter.type === 'root' , newRouter) + } + coreObj.pluginProvider.setRouterConfig(pluginRouter.type === 'root', newRouter) } } } diff --git a/frontend/packages/common/src/utils/postcat.tsx b/frontend/packages/common/src/utils/postcat.tsx index 9e438723..eacfbd14 100644 --- a/frontend/packages/common/src/utils/postcat.tsx +++ b/frontend/packages/common/src/utils/postcat.tsx @@ -1,165 +1,163 @@ -import {ReactNode} from "react"; -import {ApiBodyType} from "../const/api-detail"; -import {ContentType} from "@common/components/postcat/api/ApiTest/components/ApiRequestTester/TestBody/const.ts"; +import { ReactNode } from 'react' +import { ApiBodyType } from '../const/api-detail' +import { ContentType } from '@common/components/postcat/api/ApiTest/components/ApiRequestTester/TestBody/const.ts' declare type CheckedStatus = 'checked' | 'unchecked' | 'indeterminate' export interface TreeNode> { - id: string - children?: T[] - path?: string[] - parent?: T | null - __raw__?: T - __globalIndex__?: number - __levelIndex__?: number - __hasSiblingLeaf__?: boolean + id: string + children?: T[] + path?: string[] + parent?: T | null + __raw__?: T + __globalIndex__?: number + __levelIndex__?: number + __hasSiblingLeaf__?: boolean } - interface CommonTreeNode> { - children?: T[] - childList?: T[] - parent?: T + children?: T[] + childList?: T[] + parent?: T } /** * Flattens a hierarchical tree structure into a flat array of nodes, * each enhanced with a path property representing its location within the tree. */ export function flattenTree>( - tree: T[] = [], - childrenKey: keyof T = 'children', - pathKey: keyof T = 'id' + tree: T[] = [], + childrenKey: keyof T = 'children', + pathKey: keyof T = 'id' ): T[] { - const result: T[] = [] - let __globalIndex__ = 0 + const result: T[] = [] + let __globalIndex__ = 0 - const flatten = (node: T, path: string[], __levelIndex__: number, parent: T | null = null): void => { - const { [childrenKey]: children, ...restNode } = node - const nodeWithPath: T = { - ...restNode, - __raw__: node, - path: [...path, node[pathKey]] as string[], - __globalIndex__, - __levelIndex__ - } as T - nodeWithPath[childrenKey] = children - nodeWithPath.parent = parent ?? null - result.push(nodeWithPath) - __globalIndex__++ + const flatten = (node: T, path: string[], __levelIndex__: number, parent: T | null = null): void => { + const { [childrenKey]: children, ...restNode } = node + const nodeWithPath: T = { + ...restNode, + __raw__: node, + path: [...path, node[pathKey]] as string[], + __globalIndex__, + __levelIndex__ + } as T + nodeWithPath[childrenKey] = children + nodeWithPath.parent = parent ?? null + result.push(nodeWithPath) + __globalIndex__++ - const list: T[] = (children || []) as unknown as T[] - list.forEach((child: T, childIndex: number) => flatten(child, nodeWithPath.path || [], childIndex, node)) - } + const list: T[] = (children || []) as unknown as T[] + list.forEach((child: T, childIndex: number) => flatten(child, nodeWithPath.path || [], childIndex, node)) + } - tree.forEach((node, index) => flatten(node, [], index)) - return result + tree.forEach((node, index) => flatten(node, [], index)) + return result } - - export function byteToString(inputByteLength: number): string { - inputByteLength = inputByteLength || 0 + inputByteLength = inputByteLength || 0 - // Define thresholds for byte units - const KB = 1024 - const MB = 1024 * KB - const GB = 1024 * MB + // Define thresholds for byte units + const KB = 1024 + const MB = 1024 * KB + const GB = 1024 * MB - // Helper function to format the byte length into a string - const formatSize = (size: number, unit: string) => { - const formattedSize = size.toFixed(2) - // Remove unnecessary '.00' - if (formattedSize.endsWith('.00')) { - return `${parseInt(formattedSize, 10)} ${unit}` - } - return `${formattedSize} ${unit}` + // Helper function to format the byte length into a string + const formatSize = (size: number, unit: string) => { + const formattedSize = size.toFixed(2) + // Remove unnecessary '.00' + if (formattedSize.endsWith('.00')) { + return `${parseInt(formattedSize, 10)} ${unit}` } + return `${formattedSize} ${unit}` + } - // Convert and format byte length to appropriate unit - if (inputByteLength < 0.1 * KB) { - return formatSize(inputByteLength, 'B') - } else if (inputByteLength < 0.1 * MB) { - return formatSize(inputByteLength / KB, 'KB') - } else if (inputByteLength < 0.1 * GB) { - return formatSize(inputByteLength / MB, 'MB') - } else { - return formatSize(inputByteLength / GB, 'GB') - } + // Convert and format byte length to appropriate unit + if (inputByteLength < 0.1 * KB) { + return formatSize(inputByteLength, 'B') + } else if (inputByteLength < 0.1 * MB) { + return formatSize(inputByteLength / KB, 'KB') + } else if (inputByteLength < 0.1 * GB) { + return formatSize(inputByteLength / MB, 'MB') + } else { + return formatSize(inputByteLength / GB, 'GB') + } } export function determineCheckState(items: T[]): CheckedStatus { - let allChecked = true - let allUnchecked = true + let allChecked = true + let allUnchecked = true - for (const item of items) { - if (item.isRequired) { - allUnchecked = false - } else { - allChecked = false - } - - if (!allChecked && !allUnchecked) { - return 'indeterminate' - } + for (const item of items) { + if (item.isRequired) { + allUnchecked = false + } else { + allChecked = false } - return allChecked ? 'checked' : allUnchecked ? 'unchecked' : 'indeterminate' + if (!allChecked && !allUnchecked) { + return 'indeterminate' + } + } + + return allChecked ? 'checked' : allUnchecked ? 'unchecked' : 'indeterminate' } export function generateId(): string { - return Math.random().toString(36).slice(-8) + return Math.random().toString(36).slice(-8) } - export const getActionColWidth = (actionButtonCount: number) => { - if (actionButtonCount === 0) return 50 - return actionButtonCount * 30 + 20 + if (actionButtonCount === 0) return 50 + return actionButtonCount * 30 + 20 } export function renderComponent(content: ReactNode | null | undefined, fallbackComponent: ReactNode): ReactNode | null { - if (content === null) return null - return content ?? fallbackComponent + if (content === null) return null + return content ?? fallbackComponent } export function isNil(value: unknown): value is null | undefined { - return typeof value === 'undefined' || value === null + return typeof value === 'undefined' || value === null } export function traverse>( - node: T | T[] | null, - cb: (node: T, level: number) => void, - childrenKey: keyof T = 'children' + node: T | T[] | null, + cb: (node: T, level: number) => void, + childrenKey: keyof T = 'children' ): void { - if (!node) return; - const queue: { node: T; level: number }[] = Array.isArray(node) ? node.map(n => ({ node: n, level: 0 })) : [{ node, level: 0 }]; - while (queue.length) { - const { node: currentNode, level } = queue.shift()! - cb(currentNode, level); - const children = currentNode[childrenKey] as T[] | undefined; - if (children && children.length > 0) { - queue.push(...children.map(child => ({ node: child, level: level + 1 }))); - } + if (!node) return + const queue: { node: T; level: number }[] = Array.isArray(node) + ? node.map((n) => ({ node: n, level: 0 })) + : [{ node, level: 0 }] + while (queue.length) { + const { node: currentNode, level } = queue.shift()! + cb(currentNode, level) + const children = currentNode[childrenKey] as T[] | undefined + if (children && children.length > 0) { + queue.push(...children.map((child) => ({ node: child, level: level + 1 }))) } + } } export function generateNumberId(digit: number = 15): number { - let result = '' - for (let i = 0; i < digit; i++) { - result += Math.floor(Math.random() * 10).toString() - } - return +result + let result = '' + for (let i = 0; i < digit; i++) { + result += Math.floor(Math.random() * 10).toString() + } + return +result } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - type SafeAny = any +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type SafeAny = any export const getQueryFromURL = (url: string): { [key: string]: string } => { - const result: SafeAny = {}; - //? prevent double question mark - new URLSearchParams(url.split('?').slice(1).join('?')).forEach((val, name) => { - result[name] = val; - }); - return result; -}; + const result: SafeAny = {} + //? prevent double question mark + new URLSearchParams(url.split('?').slice(1).join('?')).forEach((val, name) => { + result[name] = val + }) + return result +} /** * Sync URL and Query @@ -171,99 +169,97 @@ export const getQueryFromURL = (url: string): { [key: string]: string } => { * @returns - {url:"",query:[]} */ export const syncUrlAndQuery = ( - url = '', - query = [], - opts: { - nowOperate?: 'url' | 'query'; - method: 'replace' | 'keepBoth'; - } = { - method: 'replace', - nowOperate: 'url' - } + url = '', + query = [], + opts: { + nowOperate?: 'url' | 'query' + method: 'replace' | 'keepBoth' + } = { + method: 'replace', + nowOperate: 'url' + } ) => { - const urlQuery: SafeAny[] = []; - const uiQuery = query; - //Get url query - const queryObj = getQueryFromURL(url); - Object.keys(queryObj).forEach(name => { - const value = queryObj[name]; - const item: SafeAny = { - isRequired: 1, - name, - paramAttr: { - example: value - } - }; - urlQuery.push(item); - }); - const pre = opts.nowOperate === 'url' ? uiQuery : urlQuery; - const next = opts.nowOperate === 'url' ? urlQuery : uiQuery; - const result: SafeAny = { - url, - query - }; - if (opts.method === 'replace') { - result.query = [...next, ...pre.filter(val => !val.isRequired)]; - } else { - result.query = [ - ...next.map(val => Object.assign(pre.find(val1 => val1.name === val.name) || {}, val)), - ...pre.filter((val: SafeAny) => urlQuery.every(val1 => val1.name !== val.name)) - ]; + const urlQuery: SafeAny[] = [] + const uiQuery = query + //Get url query + const queryObj = getQueryFromURL(url) + Object.keys(queryObj).forEach((name) => { + const value = queryObj[name] + const item: SafeAny = { + isRequired: 1, + name, + paramAttr: { + example: value + } } - result.url = jointQuery(url, result.query); - return result; -}; - -const jointQuery = (url = '', query: SafeAny[]) => { - //Joint query - let search = ''; - query.forEach(val => { - if (!(val.name && val.isRequired)) { - return; - } - search += `${val.name}=${val.paramAttr?.example || ''}&`; - }); - search = search ? `?${search.slice(0, -1)}` : ''; - return `${url.split('?')[0]}${search}`; -}; - - -export function extractBraceContent(uri: string): string[] | null { - // Regular expression to match content inside curly braces - const regex = /{([^}]+)}/g - let match: RegExpExecArray | null - const results: string[] = [] - - // Loop to find all matches - while ((match = regex.exec(uri)) !== null) { - // Add the matched content to the results array - results.push(match[1]) - } - - // Return null if no matches were found - return results.length > 0 ? results : null + urlQuery.push(item) + }) + const pre = opts.nowOperate === 'url' ? uiQuery : urlQuery + const next = opts.nowOperate === 'url' ? urlQuery : uiQuery + const result: SafeAny = { + url, + query + } + if (opts.method === 'replace') { + result.query = [...next, ...pre.filter((val) => !val.isRequired)] + } else { + result.query = [ + ...next.map((val) => Object.assign(pre.find((val1) => val1.name === val.name) || {}, val)), + ...pre.filter((val: SafeAny) => urlQuery.every((val1) => val1.name !== val.name)) + ] + } + result.url = jointQuery(url, result.query) + return result } +const jointQuery = (url = '', query: SafeAny[]) => { + //Joint query + let search = '' + query.forEach((val) => { + if (!(val.name && val.isRequired)) { + return + } + search += `${val.name}=${val.paramAttr?.example || ''}&` + }) + search = search ? `?${search.slice(0, -1)}` : '' + return `${url.split('?')[0]}${search}` +} + +export function extractBraceContent(uri: string): string[] | null { + // Regular expression to match content inside curly braces + const regex = /{([^}]+)}/g + let match: RegExpExecArray | null + const results: string[] = [] + + // Loop to find all matches + while ((match = regex.exec(uri)) !== null) { + // Add the matched content to the results array + results.push(match[1]) + } + + // Return null if no matches were found + return results.length > 0 ? results : null +} export function mapContentTypeToApiBodyType(type: ContentType): ApiBodyType { - const contentType = - { - 'text/plain': ApiBodyType.Raw, - 'application/json': ApiBodyType.JSON, - 'application/xml': ApiBodyType.XML, - 'text/html': ApiBodyType.XML, - 'application/javascript': ApiBodyType.Raw, - 'application/x-www-form-urlencoded': ApiBodyType.FormData, - 'multipart/form-data': ApiBodyType.FormData - }[type ?? 'text/plain'] ?? ApiBodyType.Raw - return contentType + const contentType = + { + 'text/plain': ApiBodyType.Raw, + 'application/json': ApiBodyType.JSON, + 'application/xml': ApiBodyType.XML, + 'text/html': ApiBodyType.XML, + 'application/javascript': ApiBodyType.Raw, + 'application/x-www-form-urlencoded': ApiBodyType.FormData, + 'multipart/form-data': ApiBodyType.FormData + }[type ?? 'text/plain'] ?? ApiBodyType.Raw + return contentType } export function file2Base64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = (): void => resolve(reader.result as string) - reader.onerror = (error): void => reject(error) - }) -} \ No newline at end of file + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(file) + reader.onload = (): void => resolve(reader.result as string) + reader.onerror = (error): void => reject(error) + }) +} diff --git a/frontend/packages/common/src/utils/router.ts b/frontend/packages/common/src/utils/router.ts index 92bf9676..779daad4 100644 --- a/frontend/packages/common/src/utils/router.ts +++ b/frontend/packages/common/src/utils/router.ts @@ -1,24 +1,24 @@ -export const objectToSearchParameters = (obj:Record, prefix?:string)=>{ - const params = new URLSearchParams(); - - for (const key in obj) { - const value = obj[key]; - const prefixedKey = prefix ? `${prefix}[${key}]` : key; - - if(value === undefined) continue - if (Array.isArray(value)) { - // 如果值是数组,展开数组每个元素为单独的键值对 - value.forEach((item, index) => { - params.append(`${prefixedKey}[${index}]`, item); - }); - } else if (value !== null && typeof value === 'object') { - // 如果值是对象,递归处理 - params.append(prefixedKey, JSON.stringify(value)); // 将嵌套对象转换为字符串 - } else { - // 否则,直接添加键值对 - params.append(prefixedKey, value); - } +export const objectToSearchParameters = (obj: Record, prefix?: string) => { + const params = new URLSearchParams() + + for (const key in obj) { + const value = obj[key] + const prefixedKey = prefix ? `${prefix}[${key}]` : key + + if (value === undefined) continue + if (Array.isArray(value)) { + // 如果值是数组,展开数组每个元素为单独的键值对 + value.forEach((item, index) => { + params.append(`${prefixedKey}[${index}]`, item) + }) + } else if (value !== null && typeof value === 'object') { + // 如果值是对象,递归处理 + params.append(prefixedKey, JSON.stringify(value)) // 将嵌套对象转换为字符串 + } else { + // 否则,直接添加键值对 + params.append(prefixedKey, value) } - - return params; -} \ No newline at end of file + } + + return params +} diff --git a/frontend/packages/common/src/utils/systemRunning.ts b/frontend/packages/common/src/utils/systemRunning.ts index 06d18004..eb1c5cf4 100644 --- a/frontend/packages/common/src/utils/systemRunning.ts +++ b/frontend/packages/common/src/utils/systemRunning.ts @@ -1,46 +1,54 @@ +/** + * @description 获取全局消费者关系视图(即空间下所有项目组的 api 关联关系) + * @param request + * @returns + */ +// export function getSpaceProjectGroupRelative(request: GetSpaceProjectGroupRelativeRequest) { +// return this.http.post( +// '/javaApi/topology/project/get-all-relations', +// request, +// { +// headers: { +// 'content-type': 'application/json' +// } +// } +// ) +// } - /** - * @description 获取全局消费者关系视图(即空间下所有项目组的 api 关联关系) - * @param request - * @returns - */ - // export function getSpaceProjectGroupRelative(request: GetSpaceProjectGroupRelativeRequest) { - // return this.http.post( - // '/javaApi/topology/project/get-all-relations', - // request, - // { - // headers: { - // 'content-type': 'application/json' - // } - // } - // ) - // } +import G6, { EdgeConfig } from '@antv/g6' +import { + OUT_SPACE_CONTENT_COLOR, + OUT_SPACE_THEME, + RELATIVE_PICTURE_NODE_FONTSIZE, + SELF_SPACE_CONTENT_COLOR, + SELF_SPACE_THEME +} from '@core/const/system-running/const' +import { NodeData } from '@core/const/system-running/type' +import { TopologyProjectItem, TopologyServiceItem } from '@core/pages/systemRunning/SystemRunning' -import G6, { EdgeConfig } from "@antv/g6" -import { SELF_SPACE_CONTENT_COLOR, OUT_SPACE_CONTENT_COLOR, RELATIVE_PICTURE_NODE_FONTSIZE, SELF_SPACE_THEME, OUT_SPACE_THEME } from "@core/const/system-running/const" -import { NodeData } from "@core/const/system-running/type" -import { TopologyProjectItem, TopologyServiceItem } from "@core/pages/systemRunning/SystemRunning" +/** + * @description 获取全局消费者关系视图(即空间下所有项目组的 api 关联关系) + * @param request + * @returns + */ +// export function getProjectGroupRelativeData(request: ProjectGroupRelativeRequest) { +// return this.http.post('/javaApi/topology/project/get-focus-relations', request, { +// headers: { +// 'content-type': 'application/json' +// } +// }) +// } - /** - * @description 获取全局消费者关系视图(即空间下所有项目组的 api 关联关系) - * @param request - * @returns - */ - // export function getProjectGroupRelativeData(request: ProjectGroupRelativeRequest) { - // return this.http.post('/javaApi/topology/project/get-focus-relations', request, { - // headers: { - // 'content-type': 'application/json' - // } - // }) - // } - - - - export const nodesFormatter:(nodes:TopologyProjectItem[], isSelfSpace?:boolean)=> NodeData[] = (nodes,isSelfSpace=true) =>{ +export const nodesFormatter: (nodes: TopologyProjectItem[], isSelfSpace?: boolean) => NodeData[] = ( + nodes, + isSelfSpace = true +) => { return nodes .filter((item) => item) .map((item) => { - if(isSelfSpace === undefined) {isSelfSpace = true} + if (isSelfSpace === undefined) { + isSelfSpace = true + } const theme = isSelfSpace ? SELF_SPACE_CONTENT_COLOR : OUT_SPACE_CONTENT_COLOR const name = `${item.name}` const nodeData: NodeData = { @@ -48,8 +56,8 @@ import { TopologyProjectItem, TopologyServiceItem } from "@core/pages/systemRun label: fittingString(name, 150, RELATIVE_PICTURE_NODE_FONTSIZE), name: name, isSelfSpace, - isApp:item.isApp, - isServer:item.isServer, + isApp: item.isApp, + isServer: item.isServer, x: 250, y: 150, title: name, @@ -69,231 +77,105 @@ import { TopologyProjectItem, TopologyServiceItem } from "@core/pages/systemRun return nodeData }) } - /** - * @description - */ - export function edgesFormatter(projectConnectMap:Map>) { - const edges: EdgeConfig[] = []; - for (const [projectKey, invokedMap] of projectConnectMap) { - for (const [invokedProjectId] of invokedMap) { // 这里使用了 for...of 遍历 Map - edges.push({ - source: projectKey, - target: invokedProjectId, - _projectInfo: invokedMap.get(invokedProjectId), - }); - } +/** + * @description + */ +export function edgesFormatter(projectConnectMap: Map>) { + const edges: EdgeConfig[] = [] + for (const [projectKey, invokedMap] of projectConnectMap) { + for (const [invokedProjectId] of invokedMap) { + // 这里使用了 for...of 遍历 Map + edges.push({ + source: projectKey, + target: invokedProjectId, + _projectInfo: invokedMap.get(invokedProjectId) + }) } - return edges; - } + return edges +} - /** - * @description 修正节点,过长省略 - * @param str - * @param maxWidth - * @param fontSize - * @returns - */ - export const fittingString = (str: string, maxWidth: number, fontSize: number) => { - const ellipsis = '...' - const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0] - let currentWidth = 0 - let res = str - const pattern = new RegExp('[\u4E00-\u9FA5]+') - str.split('').forEach((letter, i) => { - if (currentWidth > maxWidth - ellipsisLength) return - if (pattern.test(letter)) { - currentWidth += fontSize - } else { - currentWidth += G6.Util.getLetterWidth(letter, fontSize) - } - if (currentWidth > maxWidth - ellipsisLength) { - res = `${str.substr(0, i)}${ellipsis}` - } - }) - return res +/** + * @description 修正节点,过长省略 + * @param str + * @param maxWidth + * @param fontSize + * @returns + */ +export const fittingString = (str: string, maxWidth: number, fontSize: number) => { + const ellipsis = '...' + const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0] + let currentWidth = 0 + let res = str + const pattern = new RegExp('[\u4E00-\u9FA5]+') + str.split('').forEach((letter, i) => { + if (currentWidth > maxWidth - ellipsisLength) return + if (pattern.test(letter)) { + currentWidth += fontSize + } else { + currentWidth += G6.Util.getLetterWidth(letter, fontSize) + } + if (currentWidth > maxWidth - ellipsisLength) { + res = `${str.substr(0, i)}${ellipsis}` + } + }) + return res +} + +/** + * 动态获取节点距离 + */ +export const getNodeSpacing = (num: number, nodes?: unknown) => { + if (nodes) { + const base: number = getNodeSpacing(num) as number + return (d: { comboId: string }) => { + return d.comboId === 'none-combo' ? base / 2 : base + } } - - - /** - * 动态获取节点距离 - */ - export const getNodeSpacing= (num: number,nodes?:unknown) => { - if(nodes) { - const base:number = getNodeSpacing(num) as number - return (d:{comboId:string})=>{ - return d.comboId === 'none-combo' ? (base /2):base - } - } - if (num <= 15) { - return 100 - } - let result = 100 - let base = 15 - while (num > base) { - result = Math.ceil(result / 2) - base *= 15 - } - return result + if (num <= 15) { + return 100 } - -// const registerEdge = () => { -// G6.registerEdge( -// 'line-running', -// { -// options: { -// style: EDGE_STYLE -// }, - -// afterDraw: (cfg, group) => { -// const lineDash = [4, 2, 1, 2] -// if (!group) return -// const shape = group.get('children')[0] -// let index = 0 -// // Define the animation -// shape.animate( -// () => { -// index = index + 0.4 -// if (index > 1000) { -// index = 0 -// } -// const res = { -// lineDash, -// lineDashOffset: -index -// } -// return res -// }, -// { -// repeat: true, -// duration: 5000 -// } -// ) -// }, - -// setState: (name, value, item) => { -// if (!item || !name) return -// const shape = item.get('keyShape') -// const itemStatus = item.getStates() - -// if ( -// !['edge-success', 'edge-error', 'edge-transparent'].includes(name) && -// itemStatus.some((state) => ['edge-error', 'edge-success', 'edge-transparent'].includes(state)) -// ) -// return -// const theme = item?._cfg?.model?.style?.stroke || SELF_SPACE_THEME -// if (name === 'running') { -// if (value) { -// shape.attr({ -// lineWidth: 4, -// shadowColor: theme, -// shadowBlur: 2 -// }) -// } else { -// shape.attr(EDGE_STYLE) -// } -// } -// } -// }, -// 'quadratic' -// ) -// } - -// private initGraph = (container: ElementRef) => { -// const element = container.nativeElement -// this.graph = new G6.Graph({ -// container: container.nativeElement, -// plugins: [this.tooltip], -// layout: { -// type: 'force', -// // 稳定系数,初始动画的加载时长(稳定性)=节点数量/稳定系数 -// alphaDecay: 0.08, -// // 因为有分组的存在,整体布局需要往左偏移一点 -// center: [element.scrollWidth / 2 - 150, element.scrollHeight / 2], -// preventOverlap: true, -// linkDistance: (d: nodeAny) => { -// if (d.source.id === 'node0') { -// return 100 -// } -// return 30 -// }, -// nodeStrength: (d: nodeAny) => { -// if (d.isLeaf) { -// return -50 -// } -// return -10 -// }, -// edgeStrength: (d: nodeAny) => { -// if (d.source.id === 'node1' || d.source.id === 'node2' || d.source.id === 'node3') { -// return 0.7 -// } -// return 0.1 -// } -// }, -// modes: { -// default: ['drag-canvas', 'drag-node', 'zoom-canvas'] -// }, -// defaultNode: { -// size: [24, 24], -// style: { -// radius: 5, -// stroke: '#69c0ff', -// lineWidth: 1, -// fillOpacity: 1 -// }, -// labelCfg: { -// style: { -// fontSize: RELATIVE_PICTURE_NODE_FONTSIZE, -// fill: this.textColor -// }, -// position: 'bottom', -// offset: 12 -// } -// }, -// defaultEdge: { -// type: 'line-running', -// label: $t('详情', -// labelCfg: { -// style: { -// fill: '5B8FF9', -// opacity: 0 -// } -// } -// } -// }) -// } + let result = 100 + let base = 15 + while (num > base) { + result = Math.ceil(result / 2) + base *= 15 + } + return result +} export class UnionFind { - private parent: Record; - private rank: Record; + private parent: Record + private rank: Record constructor(initialNodes: string[]) { - this.parent = {}; - this.rank = {}; + this.parent = {} + this.rank = {} initialNodes.forEach((node) => { - this.parent[node] = node; - this.rank[node] = 0; - }); + this.parent[node] = node + this.rank[node] = 0 + }) } find(node: string): string { if (node !== this.parent[node]) { - this.parent[node] = this.find(this.parent[node]); + this.parent[node] = this.find(this.parent[node]) } - return this.parent[node]; + return this.parent[node] } union(node1: string, node2: string): void { - const root1 = this.find(node1); - const root2 = this.find(node2); + const root1 = this.find(node1) + const root2 = this.find(node2) if (root1 !== root2) { if (this.rank[root1] < this.rank[root2]) { - this.parent[root1] = root2; + this.parent[root1] = root2 } else if (this.rank[root1] > this.rank[root2]) { - this.parent[root2] = root1; + this.parent[root2] = root1 } else { - this.parent[root2] = root1; - this.rank[root1]++; + this.parent[root2] = root1 + this.rank[root1]++ } } } -} \ No newline at end of file +} diff --git a/frontend/packages/common/src/utils/uploadPic.ts b/frontend/packages/common/src/utils/uploadPic.ts index 4d9b00f5..ae1abf12 100644 --- a/frontend/packages/common/src/utils/uploadPic.ts +++ b/frontend/packages/common/src/utils/uploadPic.ts @@ -1,55 +1,54 @@ -import { RcFile } from "antd/es/upload"; +import { RcFile } from 'antd/es/upload' export const normFile = (e: unknown) => { - if (Array.isArray(e)) { - return e; + if (Array.isArray(e)) { + return e + } + return (e as { fileList: unknown })?.fileList +} + +export const compressImage = (file: RcFile, maxSize: number): Promise => { + const img = document.createElement('img') + const canvas = document.createElement('canvas') + const reader = new FileReader() + + return new Promise((resolve, reject) => { + reader.onload = (e) => { + img.src = e.target?.result as string + img.onload = () => { + let quality = 0.9 + let width = img.width + let height = img.height + + const ctx = canvas.getContext('2d') + + const compress = () => { + canvas.width = width + canvas.height = height + ctx?.clearRect(0, 0, width, height) + ctx?.drawImage(img, 0, 0, width, height) + + const dataUrl = canvas.toDataURL(file.type, quality) + const base64 = dataUrl.split(',')[1] + return { base64, size: base64.length * 0.75 } + } + + let { base64, size } = compress() + + while (size > maxSize && quality > 0.1) { + quality -= 0.1 + ;({ base64, size } = compress()) + } + + while (size > maxSize && (width > 50 || height > 50)) { + width *= 0.9 + height *= 0.9 + ;({ base64, size } = compress()) + } + resolve(base64) + } } - return( e as {fileList:unknown} )?.fileList; - }; - - - export const compressImage = (file: RcFile, maxSize: number): Promise => { - const img = document.createElement('img'); - const canvas = document.createElement('canvas'); - const reader = new FileReader(); - - return new Promise((resolve, reject) => { - reader.onload = (e) => { - img.src = e.target?.result as string; - img.onload = () => { - let quality = 0.9; - let width = img.width; - let height = img.height; - - const ctx = canvas.getContext('2d'); - - const compress = () => { - canvas.width = width; - canvas.height = height; - ctx?.clearRect(0, 0, width, height); - ctx?.drawImage(img, 0, 0, width, height); - - const dataUrl = canvas.toDataURL(file.type, quality); - const base64 = dataUrl.split(',')[1]; - return { base64, size: base64.length * 0.75 }; - }; - - let { base64, size } = compress(); - - while (size > maxSize && quality > 0.1) { - quality -= 0.1; - ({ base64, size } = compress()); - } - - while (size > maxSize && (width > 50 || height > 50)) { - width *= 0.9; - height *= 0.9; - ({ base64, size } = compress()); - } - resolve(base64); - }; - }; - reader.onerror = (e) => reject(e); - reader.readAsDataURL(file); - }); - }; \ No newline at end of file + reader.onerror = (e) => reject(e) + reader.readAsDataURL(file) + }) +} diff --git a/frontend/packages/common/src/utils/ux.ts b/frontend/packages/common/src/utils/ux.ts index 3f5b8d94..aa93d47c 100644 --- a/frontend/packages/common/src/utils/ux.ts +++ b/frontend/packages/common/src/utils/ux.ts @@ -1,12 +1,11 @@ - export const withMinimumDelay = (fn: () => Promise, delay: number = 100): Promise => { - const startTime = Date.now(); - return fn().then(async result => { - const endTime = Date.now(); - const elapsed = endTime - startTime; - if (elapsed < delay) { - await new Promise(resolve => setTimeout(resolve, delay - elapsed)); - } - return result; - }); - }; \ No newline at end of file + const startTime = Date.now() + return fn().then(async (result) => { + const endTime = Date.now() + const elapsed = endTime - startTime + if (elapsed < delay) { + await new Promise((resolve) => setTimeout(resolve, delay - elapsed)) + } + return result + }) +} diff --git a/frontend/packages/common/src/utils/validate.ts b/frontend/packages/common/src/utils/validate.ts index ca780fc4..659e5080 100644 --- a/frontend/packages/common/src/utils/validate.ts +++ b/frontend/packages/common/src/utils/validate.ts @@ -1,41 +1,41 @@ -import { $t } from "@common/locales"; +import { $t } from '@common/locales' export const validateUrlSlash = (_, value) => { - if (value && value.includes('//')) { - return Promise.reject(new Error($t('暂不支持带有双斜杠//的url'))); - } - return Promise.resolve(); - }; + if (value && value.includes('//')) { + return Promise.reject(new Error($t('暂不支持带有双斜杠//的url'))) + } + return Promise.resolve() +} - export const validateIPorCIDR = (rule, value) => { - if (!value) { - return Promise.resolve(); - } - - const lines = value.split('\n'); - const ipCidrRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/; - - for (const line of lines) { - if (line && !ipCidrRegex.test(line.trim())) { - return Promise.reject($t('输入的IP或CIDR不符合格式')); - } - } - - return Promise.resolve(); - }; +export const validateIPorCIDR = (rule, value) => { + if (!value) { + return Promise.resolve() + } - - export const validateApiPath = (rule, value) => { - if (!value) { - return Promise.resolve(); - } + const lines = value.split('\n') + const ipCidrRegex = + /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/ - const invalidCharsRegex = /[^a-zA-Z0-9\-\/\*]/; - const validPathRegex = /^(\/?\*?\/?[a-zA-Z0-9\-\/\*]*)$/; - - if (value && (invalidCharsRegex.test(value.trim()) || !validPathRegex.test(value.trim()))) { - return Promise.reject($t('请正确输入路径,如/usr/*或*/usr/*')); + for (const line of lines) { + if (line && !ipCidrRegex.test(line.trim())) { + return Promise.reject($t('输入的IP或CIDR不符合格式')) } - - return Promise.resolve(); - }; \ No newline at end of file + } + + return Promise.resolve() +} + +export const validateApiPath = (rule, value) => { + if (!value) { + return Promise.resolve() + } + + const invalidCharsRegex = /[^a-zA-Z0-9\-\/\*]/ + const validPathRegex = /^(\/?\*?\/?[a-zA-Z0-9\-\/\*]*)$/ + + if (value && (invalidCharsRegex.test(value.trim()) || !validPathRegex.test(value.trim()))) { + return Promise.reject($t('请正确输入路径,如/usr/*或*/usr/*')) + } + + return Promise.resolve() +} diff --git a/frontend/packages/common/vite.config.ts b/frontend/packages/common/vite.config.ts index d669dfc6..76151496 100644 --- a/frontend/packages/common/vite.config.ts +++ b/frontend/packages/common/vite.config.ts @@ -1,33 +1,34 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' -import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'; +import dynamicImportVars from '@rollup/plugin-dynamic-import-vars' export default defineConfig({ css: { preprocessorOptions: { less: { - javascriptEnabled: true, - }, + javascriptEnabled: true + } }, - modules:{ - localsConvention:"camelCase", - generateScopedName:"[local]_[hash:base64:2]" + modules: { + localsConvention: 'camelCase', + generateScopedName: '[local]_[hash:base64:2]' } }, - plugins: [react(), - dynamicImportVars({ - include:["src"], - exclude:[], - warnOnError:false - }), - ], + plugins: [ + react(), + dynamicImportVars({ + include: ['src'], + exclude: [], + warnOnError: false + }) + ], resolve: { alias: [ { find: /^~/, replacement: '' }, { find: '@common', replacement: path.resolve(__dirname, './src') }, { find: '@market', replacement: path.resolve(__dirname, '/./market/src') }, - { find: '@core', replacement: path.resolve(__dirname, '../core/src') }, + { find: '@core', replacement: path.resolve(__dirname, '../core/src') } ] }, server: { @@ -35,14 +36,14 @@ export default defineConfig({ '/api/v1': { // target: 'http://uat.apikit.com:11204/mockApi/aoplatform/', target: 'http://172.18.166.219:8288/', - changeOrigin: true, + changeOrigin: true }, '/api2/v1': { // target: 'http://uat.apikit.com:11204/mockApi/aoplatform/', target: 'http://172.18.166.219:8288/', - changeOrigin: true, + changeOrigin: true } } }, - logLevel:'info' + logLevel: 'info' }) diff --git a/frontend/packages/core/package.json b/frontend/packages/core/package.json index 7b9c483d..3e399c96 100644 --- a/frontend/packages/core/package.json +++ b/frontend/packages/core/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@tinymce/tinymce-react": "^4.3.2", + "@xyflow/react": "^12.3.6", "fs-extra": "^11.2.0", "highlight.js": "^11.9.0", "tinymce": "^6.8.1" diff --git a/frontend/packages/core/src/App.tsx b/frontend/packages/core/src/App.tsx index de83bc0f..aa5b4c40 100644 --- a/frontend/packages/core/src/App.tsx +++ b/frontend/packages/core/src/App.tsx @@ -1,47 +1,45 @@ - +import { StyleProvider } from '@ant-design/cssinjs' +import { BreadcrumbProvider } from '@common/contexts/BreadcrumbContext.tsx' +import { GlobalProvider } from '@common/contexts/GlobalStateContext' +import { useLocaleContext } from '@common/contexts/LocaleContext' +import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext' +import { PluginSlotHubProvider } from '@common/contexts/PluginSlotHubContext' +import useInitializeMonaco from '@common/hooks/useInitializeMonaco' +import { $t } from '@common/locales' +import RenderRoutes from '@core/components/aoplatform/RenderRoutes' +import { App as AppAntd, ConfigProvider } from 'antd' +import { useMemo } from 'react' import './App.css' -import { ConfigProvider, App as AppAntd } from 'antd'; -import RenderRoutes from '@core/components/aoplatform/RenderRoutes'; -import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx"; -import useInitializeMonaco from "@common/hooks/useInitializeMonaco"; -import { useMemo } from 'react'; -import { GlobalProvider } from '@common/contexts/GlobalStateContext'; -import { $t } from '@common/locales'; -import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext'; -import { PluginSlotHubProvider } from '@common/contexts/PluginSlotHubContext'; -import { useLocaleContext } from '@common/contexts/LocaleContext'; -import { StyleProvider } from '@ant-design/cssinjs'; - const antdComponentThemeToken = { token: { // Seed Token,影响范围大 colorPrimary: '#3D46F2', - colorLink:'#3D46F2', - colorBorder:'#ededed', - colorText:'#333', + colorLink: '#3D46F2', + colorBorder: '#ededed', + colorText: '#333', borderRadius: 4, // 派生变量,影响范围小 colorBgContainer: '#fff', - colorPrimaryBg:'#EBEEF2', - colorTextQuaternary:'#BBB', - colorTextTertiary:'#999' + colorPrimaryBg: '#EBEEF2', + colorTextQuaternary: '#BBB', + colorTextTertiary: '#999' }, - components:{ + components: { // 派生变量,影响范围小 - Input:{ - activeShadow:'none' + Input: { + activeShadow: 'none' }, - Select:{ - activeShadow:'none' + Select: { + activeShadow: 'none' }, - Checkbox:{ - activeShadow:'none' + Checkbox: { + activeShadow: 'none' }, - Cascader:{ - activeShadow:'none', - optionSelectedBg:'#EBEEF2', - optionHoverBg:'#EBEEF2' + Cascader: { + activeShadow: 'none', + optionSelectedBg: '#EBEEF2', + optionHoverBg: '#EBEEF2' }, Layout: { bodyBg: '#fff', @@ -50,122 +48,122 @@ const antdComponentThemeToken = { headerHeight: 50, headerPadding: '10 20px', lightSiderBg: '#fff', - siderBg: '#fff', + siderBg: '#fff' }, - Breadcrumb:{ - itemColor:'#666', - linkColor:'#666', - lastItemColor:'#333', + Breadcrumb: { + itemColor: '#666', + linkColor: '#666', + lastItemColor: '#333' }, - Table:{ - headerBorderRadius:0, - headerSplitColor:'#ededed', - borderColor:'#ededed', - cellPaddingBlockMD:'15px', - cellPaddingInlineMD:'12px', - cellPaddingBlockSM:'8px', - cellPaddingInlineSM:'12px', - headerFilterHoverBg:'#EBEEF2', - headerSortActiveBg:'#F7F8FA', - headerSortHoverBg:'#F7F8FA', - fixedHeaderSortActiveBg:'#F7F8FA', - headerBg:'#FAFAFA', - rowHoverBg:'#EBEEF2' - + Table: { + headerBorderRadius: 0, + headerSplitColor: '#ededed', + borderColor: '#ededed', + cellPaddingBlockMD: '15px', + cellPaddingInlineMD: '12px', + cellPaddingBlockSM: '8px', + cellPaddingInlineSM: '12px', + headerFilterHoverBg: '#EBEEF2', + headerSortActiveBg: '#F7F8FA', + headerSortHoverBg: '#F7F8FA', + fixedHeaderSortActiveBg: '#F7F8FA', + headerBg: '#FAFAFA', + rowHoverBg: '#EBEEF2' }, - Segmented:{ - itemColor:'#333', - itemSelectedColor:'#333', - trackBg:'#f7f8fa', - trackPadding:0, - // itemHoverColor:'#EBEEF2', - itemActiveBg:'#EBEEF2', - itemHoverBg:'#EBEEF2', - itemSelectedBg:'#EBEEF2', + Segmented: { + itemColor: '#333', + itemSelectedColor: '#333', + trackBg: '#f7f8fa', + trackPadding: 0, + // itemHoverColor:'#EBEEF2', + itemActiveBg: '#EBEEF2', + itemHoverBg: '#EBEEF2', + itemSelectedBg: '#EBEEF2' }, - Tree:{ - // titleHeight:30, - // fontSize:12, - directoryNodeSelectedBg:'#EBEEF2', - directoryNodeSelectedColor:'#333', - nodeSelectedBg:'#EBEEF2', - nodeHoverBg:'#EBEEF2' - }, - Collapse:{ - headerBg:'#f7f8fa', - headerPadding:"12px", - contentPadding:"0 10px 12px 10px" - }, - Button:{ - // paddingInline:8, - dangerShadow:'none', - defaultShadow:'none', - primaryShadow:'none' - }, - Tabs:{ - cardBg:'#EBEEF2', - cardHeight:42, - horizontalItemGutter:8, - horizontalItemPaddingSM:'12px 8px 8px 8px', - horizontalItemPadding:'12px 8px 8px 8px', - }, - Menu:{ - // itemBg:'#F7F8FA', - // subMenuItemBg:'#F7F8FA', - // itemMarginBlock:0, - // activeBarBorderWidth:0, - // itemSelectedColor:'#333', - // itemSelectedBg:'#EBEEF2', - // itemHoverBg:'#EBEEF2' - }, - List:{ - itemPadding:'8px 0' - }, - Form:{ - itemMarginBottom:10, - - }, - Alert:{ - defaultPadding:'12px 16px' - }, - Tag:{ - defaultBg:"#f7f8fa" - }, + Tree: { + // titleHeight:30, + // fontSize:12, + directoryNodeSelectedBg: '#EBEEF2', + directoryNodeSelectedColor: '#333', + nodeSelectedBg: '#EBEEF2', + nodeHoverBg: '#EBEEF2' + }, + Collapse: { + headerBg: '#f7f8fa', + headerPadding: '12px', + contentPadding: '0 10px 12px 10px' + }, + Button: { + // paddingInline:8, + dangerShadow: 'none', + defaultShadow: 'none', + primaryShadow: 'none' + }, + Tabs: { + cardBg: '#EBEEF2', + cardHeight: 42, + horizontalItemGutter: 8, + horizontalItemPaddingSM: '12px 8px 8px 8px', + horizontalItemPadding: '12px 8px 8px 8px' + }, + Menu: { + // itemBg:'#F7F8FA', + // subMenuItemBg:'#F7F8FA', + // itemMarginBlock:0, + // activeBarBorderWidth:0, + // itemSelectedColor:'#333', + // itemSelectedBg:'#EBEEF2', + // itemHoverBg:'#EBEEF2' + }, + List: { + itemPadding: '8px 0' + }, + Form: { + itemMarginBottom: 10 + }, + Alert: { + defaultPadding: '8px 12px' + }, + Tag: { + defaultBg: '#f7f8fa' + } } } - function App() { - const { locale } = useLocaleContext(); + const { locale } = useLocaleContext() useInitializeMonaco() - - - const validateMessages = useMemo(()=>({ - required: $t('必填项'), - email:$t('不是有效邮箱地址')} - ),[locale]) - + + const validateMessages = useMemo( + () => ({ + required: $t('必填项'), + email: $t('不是有效邮箱地址') + }), + [locale] + ) + return ( - - + - - - - - - - - - + form={{ validateMessages }} + > + + + + + + + + + - ); + ) } export default App diff --git a/frontend/packages/core/src/components/AIProviderSelect/index.tsx b/frontend/packages/core/src/components/AIProviderSelect/index.tsx new file mode 100644 index 00000000..84e5ce60 --- /dev/null +++ b/frontend/packages/core/src/components/AIProviderSelect/index.tsx @@ -0,0 +1,106 @@ +import { STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { ModelDetailData } from '@core/pages/aiSetting/types' +import { Select, Space, message } from 'antd' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' + +export interface AIProvider extends ModelDetailData { + default_config: string + backupName: string + backupModel: string +} + +interface AIProviderResponse { + code: number + msg: string + data: { + providers: AIProvider[] + backup: string + } + msg_zh: string +} + +interface AIProviderSelectProps { + value?: string + onChange?: (value: string, provider: AIProvider) => void + style?: React.CSSProperties +} + +const AIProviderSelect: React.FC = ({ value, onChange, style = { width: 200 } }) => { + const { t } = useTranslation() + const [providers, setProviders] = useState([]) + const [loading, setLoading] = useState(false) + const { fetchData } = useFetch() + + useEffect(() => { + let isMounted = true + const fetchProviders = async () => { + if (isMounted) setLoading(true) + try { + const endpoint = 'simple/ai/providers/configured' + const response = await fetchData(endpoint, { method: 'GET' }) + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const providers = data.providers.map((val) => ({ + ...val, + backupName: data.backup?.name, + backupModel: data.backup?.model?.name + })) + isMounted && setProviders(providers) + if (!data.providers?.length) return + const selectedProvider: AIProvider = value ? providers.find((p) => p.id === value) : providers[0] + onChange?.(selectedProvider.id, selectedProvider) + } else { + message.error(msg || t('获取 AI providers 失败')) + } + } catch (error) { + message.error(t('获取 AI providers 失败')) + } finally { + isMounted && setLoading(false) + } + } + + fetchProviders() + + return () => { + isMounted = false + } + }, []) + + return ( + + {$t('AI 供应商')}: + , - renderText: (value: unknown) => value, - required: true - }, { - title:('匹配类型'), - key: 'matchType', - renderText: (value:keyof typeof MatchTypeEnum) => { - return MatchTypeEnum[value] - }, - required: true - }, { - title:('参数值'), - key: 'pattern', - unRender:(formValue)=>{return formValue?.matchType === 'NULL' || formValue?.matchType==='EXIST' || formValue?.matchType === 'UNEXIST'}, - component: , - renderText: (value: string) => { - return value - }, - required: true + }, + { + title: '服务 ID', + dataIndex: 'id', + width: 140, + ellipsis: true + }, + { + title: '类型', + dataIndex: 'service_kind', + width: 140, + ellipsis: true + }, + { + title: '所属团队', + dataIndex: ['team', 'name'], + ellipsis: true + }, + { + title: 'API 数量', + dataIndex: 'apiNum', + ellipsis: true, + sorter: (a, b) => { + return a.apiNum - b.apiNum } + }, + { + title: '描述', + dataIndex: 'description', + ellipsis: true + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 182, + ellipsis: true, + sorter: (a, b) => frontendTimeSorter(a, b, 'createTime') + } ] +export const SYSTEM_SUBSCRIBER_TABLE_COLUMNS: PageProColumns[] = [ + { + title: '服务名称', + dataIndex: ['service', 'name'], + ellipsis: true, + width: 160, + fixed: 'left', + sorter: (a, b) => { + return a.service.name.localeCompare(b.service.name) + } + }, + { + title: '服务 ID', + dataIndex: 'id', + width: 140, + ellipsis: true + }, + { + title: '订阅方', + dataIndex: ['subscriber', 'name'], + ellipsis: true + }, + { + title: '所属团队', + dataIndex: ['team', 'name'], + ellipsis: true + }, + { + title: '来源', + dataIndex: 'from', + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select' + }, + { + title: '订阅时间', + dataIndex: 'applyTime', + ellipsis: true, + width: 182, + sorter: (a, b) => { + return a.applyTime.localeCompare(b.applyTime) + } + } +] + +export const SYSTEM_MEMBER_TABLE_COLUMN: PageProColumns[] = [ + { + title: '用户名', + dataIndex: ['user', 'name'], + ellipsis: true, + width: 160, + fixed: 'left', + sorter: (a, b) => { + return a.user.name.localeCompare(b.user.name) + } + }, + { + title: '邮箱', + dataIndex: 'email', + ellipsis: true + }, + { + title: '角色', + dataIndex: ['roles', 'name'], + ellipsis: true + } +] + +export const MATCH_CONFIG: ConfigField[] = [ + { + title: '参数位置', + key: 'position', + renderText: (value: keyof typeof MatchPositionEnum) => { + return MatchPositionEnum[value] + }, + required: true, + ellipsis: true + }, + { + title: '参数名', + key: 'key', + component: , + renderText: (value: unknown) => value, + required: true + }, + { + title: '匹配类型', + key: 'matchType', + renderText: (value: keyof typeof MatchTypeEnum) => { + return MatchTypeEnum[value] + }, + required: true + }, + { + title: '参数值', + key: 'pattern', + unRender: (formValue) => { + return formValue?.matchType === 'NULL' || formValue?.matchType === 'EXIST' || formValue?.matchType === 'UNEXIST' + }, + component: , + renderText: (value: string) => { + return value + }, + required: true + } +] export const SYSTEM_API_TABLE_COLUMNS: PageProColumns[] = [ - { - title:('URL'), - dataIndex: 'requestPath', - ellipsis:true - }, - { - title:('协议'), - dataIndex: 'protocols', - ellipsis:true, - renderText:(value)=>value?.join(', ') - }, - { - title:('方法'), - dataIndex: 'methods', - ellipsis:true, - renderText:(value)=>value?.join(', ') - }, - { - title:'是否放行', - dataIndex:'disable', - ellipsis:true, - filters: true, - onFilter: true, - valueType: 'select' - }, - { - title:('描述'), - dataIndex: 'description', - ellipsis:true - }, - { - title:('创建者'), - dataIndex: ['creator','name'], - ellipsis: true, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, - }, - { - title:('更新时间'), - dataIndex: 'updateTime', - ellipsis:true, - hideInSearch: true, - width:182, - sorter: (a,b)=>frontendTimeSorter(a,b,'updateTime') - }, -]; + { + title: 'URL', + dataIndex: 'requestPath', + ellipsis: true + }, + { + title: '协议', + dataIndex: 'protocols', + ellipsis: true, + renderText: (value) => value?.join(', ') + }, + { + title: '方法', + dataIndex: 'methods', + ellipsis: true, + renderText: (value) => value?.join(', ') + }, + { + title: '是否放行', + dataIndex: 'disabled', + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select' + }, + { + title: '描述', + dataIndex: 'description', + ellipsis: true + }, + { + title: '创建者', + dataIndex: ['creator', 'name'], + ellipsis: true, + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: '更新时间', + dataIndex: 'updateTime', + ellipsis: true, + hideInSearch: true, + width: 182, + sorter: (a, b) => frontendTimeSorter(a, b, 'updateTime') + } +] export const UpstreamDriverEnum = { - 'static':('静态上游'), - 'discoveries':('动态服务发现'), + static: '静态上游', + discoveries: '动态服务发现' } export const UPSTREAM_TYPE_OPTIONS = [ - { label: ('静态上游'), value: 'static' }, - // { label: ('动态服务发现', value: 'discoveries' }, -]; + { label: '静态上游', value: 'static' } + // { label: ('动态服务发现', value: 'discoveries' }, +] export const schemeOptions = [ - { label:('HTTPS'), value:'HTTPS'}, - { label:('HTTP'), value:'HTTP'}, + { label: 'HTTPS', value: 'HTTPS' }, + { label: 'HTTP', value: 'HTTP' } ] export const UPSTREAM_BALANCE_OPTIONS = [ - { label: ('带权轮询'), value: 'round-robin' }, - { label: ('IP Hash'), value: 'ip-hash' }, -]; + { label: '带权轮询', value: 'round-robin' }, + { label: 'IP Hash', value: 'ip-hash' } +] export const UPSTREAM_PASS_HOST_OPTIONS = [ - { label:('透传客户端请求 Host'), value:'pass'}, - { label:('使用上游服务 Host'), value:'node'}, - { label:('重写 Host'), value:'rewrite'}, + { label: '透传客户端请求 Host', value: 'pass' }, + { label: '使用上游服务 Host', value: 'node' }, + { label: '重写 Host', value: 'rewrite' } ] -export const UPSTREAM_PROXY_HEADER_TYPE_OPTIONS =[ - {label:('新增或修改'), value: 'ADD' }, - { label: ('删除'), value: 'DELETE' } +export const UPSTREAM_PROXY_HEADER_TYPE_OPTIONS = [ + { label: '新增或修改', value: 'ADD' }, + { label: '删除', value: 'DELETE' } ] -export const PROXY_HEADER_CONFIG:ConfigField[] = [ - { - title:('操作类型'), - key: 'optType', - renderText: (value: string) => { - return value === 'ADD' ? ('新增或修改'):('删除') - }, - required: true - }, { - title:('参数名'), - key: 'key', - component: , - renderText: (value: string) => { - return value - }, - required: true - }, { - title:('参数值'), - key: 'value', - component: , - renderText: (value: string) => { - return value - }, - required: true - } +export const PROXY_HEADER_CONFIG: ConfigField[] = [ + { + title: '操作类型', + key: 'optType', + renderText: (value: string) => { + return value === 'ADD' ? '新增或修改' : '删除' + }, + required: true + }, + { + title: '参数名', + key: 'key', + component: , + renderText: (value: string) => { + return value + }, + required: true + }, + { + title: '参数值', + key: 'value', + component: , + renderText: (value: string) => { + return value + }, + required: true + } ] - export const SERVICE_VISUALIZATION_OPTIONS = [ - {label:('内部服务:可通过网关访问,但不展示在服务广场'),value:'inner'}, - {label:('公开服务:可通过网关访问,展示在服务广场,可被其他消费者订阅'),value:'public'}]; + { label: '内部服务:可通过网关访问,但不展示在服务广场', value: 'inner' }, + { label: '公开服务:可通过网关访问,展示在服务广场,可被其他消费者订阅', value: 'public' } +] - export const SERVICE_APPROVAL_OPTIONS = [ - {label:('无需审核:允许任何消费者调用该服务'),value:'auto'}, - {label:('人工审核:仅允许通过人工审核的消费者调用该服务'),value:'manual'}]; + { label: '无需审核:允许任何消费者调用该服务', value: 'auto' }, + { label: '人工审核:仅允许通过人工审核的消费者调用该服务', value: 'manual' } +] export const SERVICE_KIND_OPTIONS = [ - {label:('REST'),value:'rest'}, - {label:('AI'),value:'ai'}]; + { label: 'REST', value: 'rest' }, + { label: 'AI', value: 'ai' } +] - - -export const SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS: PageProColumns[] = [ - { - title:('地址(IP 端口或域名)'), - dataIndex: 'address', - width: '50%', - formItemProps: { - className:'p-0 bg-transparent border-none', - rootClassName:'test', - rules: [ - { - required: true, - whitespace: true - }, - ], - }, - ellipsis:true +export const SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS: PageProColumns[] = [ + { + title: '地址(IP 端口或域名)', + dataIndex: 'address', + width: '50%', + formItemProps: { + className: 'p-0 bg-transparent border-none', + rootClassName: 'test', + rules: [ + { + required: true, + whitespace: true + } + ] }, - { - title:('权重(0-999)'), - dataIndex: 'weight', - valueType:'digit', - formItemProps: { - className:'p-0 bg-transparent border-none'} - }, - { - title: COLUMNS_TITLE.operate, - valueType: 'option', - btnNums:2, - render: ()=>null - }, - ]; - - -export const SYSTEM_INSIDE_APPROVAL_TAB_ITEMS: TabsProps['items'] = [ - { - key: '0', - label:('待审核'), - }, - { - key: '1', - label: ('已审核'), + ellipsis: true + }, + { + title: '权重(0-999)', + dataIndex: 'weight', + valueType: 'digit', + formItemProps: { + className: 'p-0 bg-transparent border-none' } -]; - + }, + { + title: COLUMNS_TITLE.operate, + valueType: 'option', + btnNums: 2, + render: () => null + } +] +export const SYSTEM_INSIDE_APPROVAL_TAB_ITEMS: TabsProps['items'] = [ + { + key: '0', + label: '待审核' + }, + { + key: '1', + label: '已审核' + } +] export const SYSTEM_PUBLISH_TAB_ITEMS: TabsProps['items'] = [ - { - key: '0', - label: ('发布版本'), - }, - { - key: '1', - label: ('发布申请记录'), - } -]; - + { + key: '0', + label: '发布版本' + }, + { + key: '1', + label: '发布申请记录' + } +] export const SYSTEM_SUBSCRIBE_APPROVAL_DETAIL_LIST = [ - { - title:('服务名称'),key:'service',nested:'name' - }, - { - title:('服务 ID'),key:'applyTeam',nested:'id' - }, - { - title:('所属团队'),key:'team',nested:'name' - }, - { - title:('所属系统'),key:'project',nested:'name' - }, - { - title:('申请状态'),key:'status',renderText:()=>{} - }, - { - title:('申请人'),key:'applier',nested:'name' - }, - { - title:('申请时间'),key:'applyTime' - }, + { + title: '服务名称', + key: 'service', + nested: 'name' + }, + { + title: '服务 ID', + key: 'applyTeam', + nested: 'id' + }, + { + title: '所属团队', + key: 'team', + nested: 'name' + }, + { + title: '所属系统', + key: 'project', + nested: 'name' + }, + { + title: '申请状态', + key: 'status', + renderText: () => {} + }, + { + title: '申请人', + key: 'applier', + nested: 'name' + }, + { + title: '申请时间', + key: 'applyTime' + } ] export const SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP = { - subscriberProject:{ - stroke:'#3291F8FF', - fill: '#3291F8FF', - name:('调用系统名称') - }, - subscriberService:{ - stroke:'#3D46F2', - fill: '#7371FC33', - name:('调用服务名称') - }, - curProject:{ - stroke:'#7371FCFF', - fill: '#7371FCFF', - name:('当前系统名称') - }, - invokeService:{ - stroke:'#3D46F2', - fill: '#7371FC33', - name:('被调用服务名称') - }, - invokeProject:{ - stroke:'#19C56BFF', - fill: '#19C56BFF', - name:('被调用系统名称') - }, - application:{ - stroke:'#ffa940', - fill: '#ffa94033', + subscriberProject: { + stroke: '#3291F8FF', + fill: '#3291F8FF', + name: '调用系统名称' + }, + subscriberService: { + stroke: '#3D46F2', + fill: '#7371FC33', + name: '调用服务名称' + }, + curProject: { + stroke: '#7371FCFF', + fill: '#7371FCFF', + name: '当前系统名称' + }, + invokeService: { + stroke: '#3D46F2', + fill: '#7371FC33', + name: '被调用服务名称' + }, + invokeProject: { + stroke: '#19C56BFF', + fill: '#19C56BFF', + name: '被调用系统名称' + }, + application: { + stroke: '#ffa940', + fill: '#ffa94033' + } +} + +export const SYSTEM_PUBLISH_ONLINE_COLUMNS = [ + { + title: '上线结果', + dataIndex: 'status', + ellipsis: { + showTitle: true } - }; - - - - export const SYSTEM_PUBLISH_ONLINE_COLUMNS = [ - { - title:('上线结果'), - dataIndex: 'status', - ellipsis:{ - showTitle:true - }, - }, - ] - - + } +] diff --git a/frontend/packages/core/src/const/system/type.ts b/frontend/packages/core/src/const/system/type.ts index dc34c2c9..c4e7742c 100644 --- a/frontend/packages/core/src/const/system/type.ts +++ b/frontend/packages/core/src/const/system/type.ts @@ -1,6 +1,6 @@ -import { FormInstance, UploadFile } from "antd"; import { EntityItem, MatchItem } from "@common/const/type"; +import { FormInstance, UploadFile } from "antd"; import { SubscribeEnum, SubscribeFromEnum } from "./const"; export type SystemTableListItem = { diff --git a/frontend/packages/core/src/contexts/TeamContext.tsx b/frontend/packages/core/src/contexts/TeamContext.tsx index f5ffd64d..0c917b77 100644 --- a/frontend/packages/core/src/contexts/TeamContext.tsx +++ b/frontend/packages/core/src/contexts/TeamContext.tsx @@ -1,24 +1,23 @@ - -import { FC, createContext, useContext, useState, ReactNode } from 'react'; -import { TeamConfigType } from '../const/team/type'; +import { FC, ReactNode, createContext, useContext, useState } from 'react' +import { TeamConfigType } from '../const/team/type' interface TeamContextProps { - teamInfo?:TeamConfigType - setTeamInfo?: React.Dispatch>; + teamInfo?: TeamConfigType + setTeamInfo?: React.Dispatch> } -const TeamContext = createContext(undefined); +const TeamContext = createContext(undefined) export const useTeamContext = () => { - const context = useContext(TeamContext); - if (!context) { - throw new Error('useArray must be used within a ArrayProvider'); - } - return context; -}; + const context = useContext(TeamContext) + if (!context) { + throw new Error('useArray must be used within a ArrayProvider') + } + return context +} export const TeamProvider: FC<{ children: ReactNode }> = ({ children }) => { - const [teamInfo, setTeamInfo] = useState() - - return {children}; -}; \ No newline at end of file + const [teamInfo, setTeamInfo] = useState() + + return {children} +} diff --git a/frontend/packages/core/src/pages/aiApis/aiApisLayout.tsx b/frontend/packages/core/src/pages/aiApis/aiApisLayout.tsx new file mode 100644 index 00000000..b956887a --- /dev/null +++ b/frontend/packages/core/src/pages/aiApis/aiApisLayout.tsx @@ -0,0 +1,15 @@ +import { useEffect } from 'react' +import { Outlet, useLocation, useNavigate } from 'react-router-dom' + +export default function GlobalPolicyLayout() { + const location = useLocation() + const pathName = location.pathname + const navigator = useNavigate() + useEffect(() => { + if (pathName === '/aiApis') { + const queryParams = new URLSearchParams(location.search).toString() + navigator(`/aiApis/list${queryParams ? `?${queryParams}` : ''}`) + } + }, [pathName]) + return +} diff --git a/frontend/packages/core/src/pages/aiApis/index.tsx b/frontend/packages/core/src/pages/aiApis/index.tsx new file mode 100644 index 00000000..c5cadf06 --- /dev/null +++ b/frontend/packages/core/src/pages/aiApis/index.tsx @@ -0,0 +1,322 @@ +import { ActionType } from '@ant-design/pro-components' +import InsidePage from '@common/components/aoplatform/InsidePage' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission' +import TimeRangeSelector, { TimeRangeButton } from '@common/components/aoplatform/TimeRangeSelector' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import AIProviderSelect, { AIProvider } from '@core/components/AIProviderSelect' +import { getTime } from '@dashboard/utils/dashboard' +import { Alert, App, Button, Typography } from 'antd' +import dayjs from 'dayjs' +import React, { useEffect, useRef, useState } from 'react' +import { useNavigate, useSearchParams } from 'react-router-dom' +import { APIs } from './types' + +const ApiSettings: React.FC = () => { + const pageListRef = useRef(null) + const { modal, message } = App.useApp() + const [searchParams] = useSearchParams() + const [selectedProvider, setSelectedProvider] = useState(searchParams.get('modelId') || '') + const [provider, setProvider] = useState() + const { fetchData } = useFetch() + const [searchWord, setSearchWord] = useState('') + const [columns, setColumns] = useState[]>([]) + const [total, setTotal] = useState(0) + const [timeButton, setTimeButton] = useState('day') + const navigate = useNavigate() + const [timeRange, setTimeRange] = useState<{ start: number | null; end: number | null }>({ + start: null, + end: null + }) + const [queryBtnLoading, setQueryBtnLoading] = useState(false) + + useEffect(() => { + pageListRef.current?.reload() + }, [selectedProvider]) + + const handlePreview = (record: APIs) => { + navigate(`../service/${record.team.id}/aiInside/${record.service.id}/route/${record.id}/apiDetail`) + } + const requestApis = async ( + params: any & { + pageSize: number + current: number + }, + sort: Record, + filter: Record + ) => { + if (!selectedProvider) return + setQueryBtnLoading(true) + try { + const eoParams = { + provider: selectedProvider, + page_size: params.pageSize, + keyword: searchWord, + sort: Object.keys(sort)?.length > 0 ? 'use_token' : undefined, + asc: Object.keys(sort)?.length > 0 ? Object.values(sort)?.[0] === 'ascend' : undefined, + models: filter?.model && filter?.model?.length ? JSON.stringify(filter.model) : undefined, + services: filter?.name && filter?.name?.length ? JSON.stringify(filter.name) : undefined, + disabled: filter?.disable && filter?.disable?.length ? filter.disable[0] : undefined, + page: params.current, + start: timeRange.start, + end: timeRange.end + } + if (!timeRange || !timeRange.start) { + const { startTime, endTime } = getTime(timeButton, []) + eoParams.start = startTime + eoParams.end = endTime + } + const response = await fetchData>('ai/apis', { + method: 'GET', + eoParams + }) + setQueryBtnLoading(false) + if (response.code === STATUS_CODE.SUCCESS) { + setTotal(response.data.total) + const modalMap: { + [key: string]: string + } = response.data?.condition?.models.reduce( + (acc: { [key: string]: string }, item: { id: string; name: string }) => { + acc[item.id] = $t(item.name) + return acc + }, + {} + ) + const serviceMap: { + [key: string]: string + } = response.data?.condition?.services.reduce( + (acc: { [key: string]: string }, item: { id: string; name: string }) => { + acc[item.id] = $t(item.name) + return acc + }, + {} + ) + setTableColumns(modalMap, serviceMap) + return { + data: response.data.apis || [], + success: true, + total: response.data.total + } + } else { + message.error(response.msg || $t(RESPONSE_TIPS.error)) + return { + data: [], + success: false, + total: response.data.total + } + } + } catch (error) { + return { + data: [], + success: false, + total: 0 + } + } + } + const setTableColumns = ( + modalMap: { + [key: string]: string + }, + serviceMap: { + [key: string]: string + } + ) => { + setColumns([ + { + title: $t('AI 服务'), + dataIndex: 'name', + key: 'name', + width: 180, + filters: true, + valueEnum: serviceMap || {} + }, + { + title: 'API URL', + dataIndex: 'request_path', + key: 'request_path', + ellipsis: true, + render: (text: string, record: APIs) => ( +

+ {record.method} + {text} +

+ ) + }, + { + title: $t('模型'), + dataIndex: ['model', 'name'], + key: 'model', + width: 150, + filters: true, + onFilter: true, + valueType: 'select', + valueEnum: modalMap || {} + }, + { + title: $t('已用 Token'), + dataIndex: 'use_token', + key: 'use_token', + width: 120, + sorter: (a: any, b: any) => { + return (a.priority as number) - (b.priority as number) + } + }, + { + title: $t('是否放行'), + dataIndex: 'disable', + ellipsis: true, + width: 120, + filters: true, + onFilter: true, + valueType: 'select', + valueEnum: { + true: { text: {$t('拦截')} }, + false: { text: {$t('放行')} } + } + }, + { + title: $t('编辑时间'), + dataIndex: 'update_time', + key: 'update_time', + width: 200, + render: (time: string) => {dayjs(time).format('YYYY-MM-DD HH:mm:ss')} + }, + ...operation + ]) + } + const operation: PageProColumns[] = [ + { + title: '', + key: 'option', + btnNums: 4, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: APIs) => [ + handlePreview(entity)} + btnTitle={$t('预览')} + /> + ] + } + ] + + const resetQuery = () => { + setTimeButton('day') + setTimeRange({ start: null, end: null }) + setSearchWord('') + } + + const getData = () => { + pageListRef.current?.reload() + } + + const renderProviderBanner = () => { + if (!provider) return null + if (provider.status === 'disabled' || provider.status === 'abnormal') { + const message = + provider.status === 'disabled' + ? $t(`当前供应商异常,以下API均临时调用 ${provider.backupName} 下的 ${provider.backupModel} 模型能力。`) + : $t(`当前供应商异常,以下API均临时调用 ${provider.backupName} 下的 ${provider.backupModel} 模型能力。`) + const type = provider.status === 'disabled' ? 'warning' : 'error' + return ( + { + navigate('/aisetting') + }} + > + {$t('查看详情')} + + } + /> + ) + } + return null + } + + return ( + +
+ { + setSelectedProvider(value) + setProvider(option) + }} + /> +
+ {renderProviderBanner()} + + } + showBorder={false} + scrollPage={false} + > +
+ + { + setTimeRange($event) + }} + /> +
+ + +
+
+ } + request={async ( + params: any & { + pageSize: number + current: number + }, + sort: Record, + filter: Record + ) => requestApis(params, sort, filter)} + onSearchWordChange={(e) => { + setSearchWord(e.target.value) + }} + onRowClick={(row: APIs) => handlePreview(row)} + showPagination={true} + searchPlaceholder={$t('请输入 APIURL 搜索')} + columns={columns} + /> +
+ + ) +} + +export default ApiSettings diff --git a/frontend/packages/core/src/pages/aiApis/types.ts b/frontend/packages/core/src/pages/aiApis/types.ts new file mode 100644 index 00000000..ee7368fd --- /dev/null +++ b/frontend/packages/core/src/pages/aiApis/types.ts @@ -0,0 +1,18 @@ +export interface APIs { + id: string; + name: string; + service: { + id: string; + name: string; + }; + team:{ + id: string; + name: string; + }, + method: string; + request_path: string; + model: string; + disabled: boolean; + update_time: string; + use_token: number; +} \ No newline at end of file diff --git a/frontend/packages/core/src/pages/aiService/AiServiceInsidePage.tsx b/frontend/packages/core/src/pages/aiService/AiServiceInsidePage.tsx index 3459f146..16178602 100644 --- a/frontend/packages/core/src/pages/aiService/AiServiceInsidePage.tsx +++ b/frontend/packages/core/src/pages/aiService/AiServiceInsidePage.tsx @@ -1,166 +1,262 @@ +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { PERMISSION_DEFINITION } from '@common/const/permissions.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { getItem } from '@common/utils/navigation.tsx' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts' +import { App, Menu, MenuProps } from 'antd' +import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/interface' +import Paragraph from 'antd/es/typography/Paragraph' +import { cloneDeep } from 'lodash-es' +import { FC, useEffect, useMemo, useState } from 'react' +import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom' +import { useAiServiceContext } from '../../contexts/AiServiceContext.tsx' +const APP_MODE = import.meta.env.VITE_APP_MODE -import {FC, useEffect, useMemo, useState} from "react"; -import {Link, Outlet, useLocation, useNavigate, useParams} from "react-router-dom"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import {App, Menu, MenuProps} from "antd"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { useAiServiceContext} from "../../contexts/AiServiceContext.tsx"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { PERMISSION_DEFINITION } from "@common/const/permissions.ts"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; -import Paragraph from "antd/es/typography/Paragraph"; -import { cloneDeep } from "lodash-es"; -import { $t } from "@common/locales/index.ts"; -import { getItem } from "@common/utils/navigation.tsx"; -import { AiServiceConfigFieldType } from "@core/const/ai-service/type.ts"; -import { MenuItemGroupType, MenuItemType, ItemType } from "antd/es/menu/interface"; -const APP_MODE = import.meta.env.VITE_APP_MODE; +const AiServiceInsidePage: FC = () => { + const { message } = App.useApp() + const { teamId, serviceId, apiId, routeId, policyId } = useParams() + const location = useLocation() + const currentUrl = location.pathname + const { fetchData } = useFetch() + const { setPrefixForce, setApiPrefix, aiServiceInfo, setAiServiceInfo } = useAiServiceContext() + const { accessData, checkPermission, accessInit, state } = useGlobalContext() + const [activeMenu, setActiveMenu] = useState() + const navigateTo = useNavigate() + const [showMenu, setShowMenu] = useState(false) -const AiServiceInsidePage:FC = ()=> { - const { message } = App.useApp() - const { teamId,serviceId,apiId, routeId,policyId } = useParams(); - const location = useLocation() - const currentUrl = location.pathname - const {fetchData} = useFetch() - const { setPrefixForce,setApiPrefix ,aiServiceInfo ,setAiServiceInfo} = useAiServiceContext() - const { accessData,checkPermission,accessInit,state} = useGlobalContext() - const [activeMenu, setActiveMenu] = useState() - const navigateTo = useNavigate() - const [showMenu, setShowMenu] = useState(false) + const getAiServiceInfo = () => { + fetchData>('service/info', { + method: 'GET', + eoParams: { team: teamId, service: serviceId } + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setAiServiceInfo(data.service) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - const getAiServiceInfo = ()=>{ - fetchData>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId}}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAiServiceInfo(data.service) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } + const getApiDefine = () => { + setApiPrefix('') + setPrefixForce(false) + fetchData>('service/router/define', { + method: 'GET', + eoParams: { service: serviceId, team: teamId } + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setApiPrefix(data.prefix) + setPrefixForce(data.force) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - - const getApiDefine = ()=>{ - setApiPrefix('') - setPrefixForce(false) - fetchData>('service/router/define',{method:'GET',eoParams:{service:serviceId,team:teamId}}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setApiPrefix(data.prefix) - setPrefixForce(data.force) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - - - const SYSTEM_PAGE_MENU_ITEMS = useMemo(()=>[ - getItem($t('服务'), 'assets', null, + const SYSTEM_PAGE_MENU_ITEMS = useMemo( + () => [ + getItem( + $t('服务'), + 'assets', + null, [ - getItem({$t('API 路由')}, 'route',undefined,undefined,undefined,'team.service.router.view'), - getItem({$t('API 文档')}, 'api',undefined,undefined,undefined,'team.service.api_doc.view'), - getItem({$t('使用说明')}, 'document',undefined,undefined,undefined,'team.service.service_intro.view'), - getItem({$t('服务策略')}, 'servicepolicy', undefined, undefined, undefined, 'team.service.policy.view'), - getItem({$t('发布')}, 'publish',undefined,undefined,undefined,'team.service.release.view'), - ], - 'group'), - getItem($t('订阅管理'), 'provideSer', null, - [ - getItem({$t('订阅审核')}, 'approval',undefined,undefined,undefined,'team.service.subscription.view'), - getItem({$t('订阅方管理')}, 'subscriber',undefined,undefined,undefined,'team.service.subscription.view'), + getItem( + {$t('API 路由')}, + 'route', + undefined, + undefined, + undefined, + 'team.service.router.view' + ), + getItem( + {$t('API 文档')}, + 'api', + undefined, + undefined, + undefined, + 'team.service.api_doc.view' + ), + getItem( + {$t('使用说明')}, + 'document', + undefined, + undefined, + undefined, + 'team.service.service_intro.view' + ), + getItem( + {$t('服务策略')}, + 'servicepolicy', + undefined, + undefined, + undefined, + 'team.service.policy.view' + ), + getItem( + {$t('发布')}, + 'publish', + undefined, + undefined, + undefined, + 'team.service.release.view' + ) ], - 'group'), - getItem($t('管理'), 'mng', null, + 'group' + ), + getItem( + $t('订阅管理'), + 'provideSer', + null, [ - APP_MODE === 'pro' ? getItem({$t('调用拓扑图')}, 'topology',undefined,undefined,undefined,'project.myAiService.topology.view'):null, - getItem({$t('设置')}, 'setting',undefined,undefined,undefined,'')], - 'group'), -],[state.language]) + getItem( + {$t('订阅审核')}, + 'approval', + undefined, + undefined, + undefined, + 'team.service.subscription.view' + ), + getItem( + {$t('订阅方管理')}, + 'subscriber', + undefined, + undefined, + undefined, + 'team.service.subscription.view' + ) + ], + 'group' + ), + getItem( + $t('管理'), + 'mng', + null, + [ + APP_MODE === 'pro' + ? getItem( + {$t('调用拓扑图')}, + 'topology', + undefined, + undefined, + undefined, + 'project.myAiService.topology.view' + ) + : null, + getItem({$t('设置')}, 'setting', undefined, undefined, undefined, '') + ], + 'group' + ) + ], + [state.language] + ) - - const menuData = useMemo(()=>{ - const filterMenu = (menu:MenuItemGroupType[])=>{ - const newMenu = cloneDeep(menu) - return newMenu!.filter((m:MenuItemGroupType )=>{ - if(m&&m.children && m.children.length > 0){ - m.children = m.children.filter( - (c)=>{ - if(!c) return false - return (((c as MenuItemType&{access:string} ).access ? - checkPermission((c as MenuItemType&{access:string} ).access as keyof typeof PERMISSION_DEFINITION[0]): - true))}) - } - return m.children && m.children.length > 0 - }) + const menuData = useMemo(() => { + const filterMenu = (menu: MenuItemGroupType[]) => { + const newMenu = cloneDeep(menu) + return newMenu!.filter((m: MenuItemGroupType) => { + if (m && m.children && m.children.length > 0) { + m.children = m.children.filter((c) => { + if (!c) return false + return (c as MenuItemType & { access: string }).access + ? checkPermission( + (c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0] + ) + : true + }) } - const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType[]) - const menu = activeMenu ?? filteredMenu[0]?.children ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key - if(menu && currentUrl.split('/')[-1] !== menu){ - navigateTo(`/service/${teamId}/aiInside/${serviceId}/${menu}`) - } - return filteredMenu || [] - },[accessData,accessInit, SYSTEM_PAGE_MENU_ITEMS]) - - const onMenuClick: MenuProps['onClick'] = ({key}) => { - setActiveMenu(key) - }; - - useEffect(() => { - // route edit and policy edit page don't need to show menu - setShowMenu(!routeId && !currentUrl.includes('route/create') && !policyId &&!currentUrl.includes('servicepolicy/datamasking/create')) + return m.children && m.children.length > 0 + }) + } + const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType[]) + const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key + if (menu && currentUrl.split('/')[-1] !== menu) { + navigateTo(`/service/${teamId}/aiInside/${serviceId}/${menu}`) + } + return filteredMenu || [] + }, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS]) - if(apiId !== undefined){ - setActiveMenu('api') - } else if(currentUrl.includes('servicepolicy')){ - setActiveMenu('servicepolicy') - } else if(serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]){ - setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1]) - }else{ - setActiveMenu('route') - } - }, [currentUrl]); + const onMenuClick: MenuProps['onClick'] = ({ key }) => { + setActiveMenu(key) + } - useEffect(()=>{ - if(accessData && checkPermission('team.service.router.view')){ - getApiDefine() - } - },[accessData]) - - useEffect(()=>{ - if( activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]){ - navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`) - } - },[activeMenu]) - - useEffect(() => { - serviceId && getAiServiceInfo() - }, [serviceId]); - - return ( - <>{showMenu ? - {$t('服务 ID')}:{serviceId || '-'} - }]} - backUrl="/service/list"> -
- [] } - /> -
- -
-
-
: } - + useEffect(() => { + // route edit and policy edit page don't need to show menu + setShowMenu( + !routeId && + !currentUrl.includes('route/create') && + !policyId && + !currentUrl.includes('servicepolicy/datamasking/create') ) + + if (apiId !== undefined) { + setActiveMenu('api') + } else if (currentUrl.includes('servicepolicy')) { + setActiveMenu('servicepolicy') + } else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) { + setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1]) + } else { + setActiveMenu('route') + } + }, [currentUrl]) + + useEffect(() => { + if (accessData && checkPermission('team.service.router.view')) { + getApiDefine() + } + }, [accessData]) + + useEffect(() => { + if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) { + navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`) + } + }, [activeMenu]) + + useEffect(() => { + serviceId && getAiServiceInfo() + }, [serviceId]) + + return ( + <> + {showMenu ? ( + + {$t('服务 ID')}:{serviceId || '-'} + + ) + } + ]} + backUrl="/service/list" + > +
+ []} + /> +
+ +
+
+
+ ) : ( + + )} + + ) } -export default AiServiceInsidePage \ No newline at end of file +export default AiServiceInsidePage diff --git a/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterCreate.tsx b/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterCreate.tsx index e983f90d..489ed981 100644 --- a/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterCreate.tsx +++ b/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterCreate.tsx @@ -1,354 +1,420 @@ -import {App, Button, Form, Input, InputNumber, Row, Select, Space, Spin, Tag} from "antd"; -import { MutableRefObject, useEffect, useMemo, useRef, useState} from "react"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { $t } from "@common/locales/index.ts"; -import { LoadingOutlined } from "@ant-design/icons"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; -import { useNavigate, useParams } from "react-router-dom"; -import { useAiServiceContext } from "@core/contexts/AiServiceContext.tsx"; -import EditableTableNotAutoGen from "@common/components/aoplatform/EditableTableNotAutoGen.tsx"; -import { AI_SERVICE_VARIABLES_TABLE_COLUMNS } from "@core/const/ai-service/const.tsx"; -import { VariableItems } from "@core/const/ai-service/type.ts"; -import PromptEditorResizable from '@common/components/aoplatform/prompt-editor/PromptEditorResizable.tsx'; -import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter"; -import AiServiceRouterModelConfig, { AiServiceRouterModelConfigHandle } from "./AiServiceInsideRouterModelConfig"; -import { AiProviderDefaultConfig, AiProviderLlmsItems } from "@core/pages/aiSetting/AiSettingList"; -import { EditableFormInstance } from "@ant-design/pro-components"; -import { validateUrlSlash } from "@common/utils/validate"; -import { API_PATH_MATCH_RULES } from "@core/const/system/const"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +import { LoadingOutlined } from '@ant-design/icons' +import { EditableFormInstance } from '@ant-design/pro-components' +import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter' +import EditableTableNotAutoGen from '@common/components/aoplatform/EditableTableNotAutoGen.tsx' +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import PromptEditorResizable from '@common/components/aoplatform/prompt-editor/PromptEditorResizable.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { validateUrlSlash } from '@common/utils/validate' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { AI_SERVICE_VARIABLES_TABLE_COLUMNS } from '@core/const/ai-service/const.tsx' +import { VariableItems } from '@core/const/ai-service/type.ts' +import { API_PATH_MATCH_RULES } from '@core/const/system/const' +import { useAiServiceContext } from '@core/contexts/AiServiceContext.tsx' +import { AiProviderDefaultConfig, AiProviderLlmsItems } from '@core/pages/aiSetting/AiSettingList' +import { Icon } from '@iconify/react/dist/iconify.js' +import { App, Button, Form, Input, InputNumber, Row, Space, Spin, Switch, Tag } from 'antd' +import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import AiServiceRouterModelConfig, { AiServiceRouterModelConfigHandle } from './AiServiceInsideRouterModelConfig' type AiServiceRouterField = { - name:string - path:string - prompt:string - variables:Array<{key:string, description:string, require:true}> - description:string - timeout:number - retry:number + name: string + path: string + prompt: string + variables: Array<{ key: string; description: string; require: true }> + description: string + timeout: number + retry: number + disabled: boolean } type AiServiceRouterConfig = { - name:string - path:string - aiPrompt:{ - prompt:string - variables:Array<{key:string, description:string, require:true}> - } - aiModel:{ - id:string - config:string - } - description:string - timeout:number - retry:number + name: string + path: string + aiPrompt: { + prompt: string + variables: Array<{ key: string; description: string; require: true }> + } + aiModel: { + id: string + config: string + } + description: string + timeout: number + retry: number } const AiServiceInsideRouterCreate = () => { - const navigator = useNavigate() - const { message } = App.useApp() - const {serviceId, teamId,routeId} = useParams() - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const [loading, setLoading] = useState(false) - const {apiPrefix,prefixForce ,aiServiceInfo} = useAiServiceContext() - const [variablesTable,setVariablesTable] = useState([]) - const [drawerType,setDrawerType]= useState<'edit'|undefined>() - const [open, setOpen] = useState(false); - const drawerAddFormRef = useRef(null) - const [defaultLlm, setDefaultLlm] = useState() - const [llmList, setLlmList] = useState([]) - const [variablesTableRef, setVariablesTableRef] = useState | undefined>>() - const {state} = useGlobalContext() - - const onFinish = ()=>{ - return variablesTableRef?.current?.validateFields().then(()=>{ - return form.validateFields().then((formValue)=>{ - const {name, path, description, variables, prompt, timeout, retry,pathMatch} = formValue - const body = { - name, - path: `${prefixForce ? apiPrefix + '/' : ''}${path.trim()}${pathMatch === 'prefix' ? '/*' : ''}`, - description,timeout, retry,aiPrompt:{variables:variables, prompt:prompt},aiModel:{id:defaultLlm?.id, provider:defaultLlm?.provider, config:defaultLlm?.config}} - return fetchData>('service/ai-router',{method: routeId ? 'PUT' : 'POST',eoBody:(body), eoParams: {service:serviceId,team:teamId, ...(routeId ? {router:routeId}: {})},eoTransformKeys:['aiPrompt','aiModel']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - navigator(`/service/${teamId}/aiInside/${serviceId}/route`) - return Promise.resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(errInfo=>Promise.reject(errInfo)) - }) - }) - .catch(errInfo=>Promise.reject(errInfo)) - } + const navigator = useNavigate() + const { message } = App.useApp() + const { serviceId, teamId, routeId, type } = useParams() + const [form] = Form.useForm() + const { fetchData } = useFetch() + const [loading, setLoading] = useState(false) + const { apiPrefix, prefixForce, aiServiceInfo } = useAiServiceContext() + const [variablesTable, setVariablesTable] = useState([]) + const [drawerType, setDrawerType] = useState<'edit' | undefined>() + const [open, setOpen] = useState(false) + const drawerAddFormRef = useRef(null) + const [defaultLlm, setDefaultLlm] = useState() + const [llmList, setLlmList] = useState([]) + const [variablesTableRef, setVariablesTableRef] = useState | undefined>>() + const { state } = useGlobalContext() - const openDrawer = (type:'edit')=>{ - setDrawerType(type) - } - - useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType]) - - const getRouterConfig = ()=>{ - setLoading(true) - fetchData>('service/ai-router',{method:'GET',eoParams:{service:serviceId,team:teamId, router:routeId}, eoTransformKeys:['ai_model', 'ai_prompt']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const {path, aiPrompt,aiModel} = data.api - let newPath = path - let pathMatch = 'full' - if(prefixForce && path?.startsWith(apiPrefix + '/')){ - newPath = path.slice((apiPrefix?.length || 0) + 1) - } - if(newPath.endsWith('/*')){ - newPath = newPath.slice(0,-2) - pathMatch = 'prefix' - } - form.setFieldsValue({ - ...data.api, - ...aiPrompt, - path:newPath, - pathMatch}) - setVariablesTable(aiPrompt.variables as VariableItems[]) - setDefaultLlm(prev => ({...prev, provider: aiModel?.provider, id:aiModel?.id, config:aiModel.config}) as (AiProviderDefaultConfig & { config: string; })) - getDefaultModelConfig(aiModel?.provider) - }else{ + const onFinish = () => { + return variablesTableRef?.current + ?.validateFields() + .then(() => { + return form.validateFields().then((formValue) => { + const { name, path, description, variables, prompt, timeout, retry, pathMatch, disabled } = formValue + const body = { + name, + path: `${prefixForce ? apiPrefix + '/' : ''}${path.trim()}${pathMatch === 'prefix' ? '/*' : ''}`, + description, + timeout, + retry, + aiPrompt: { variables: variables, prompt: prompt }, + aiModel: { id: defaultLlm?.id, provider: defaultLlm?.provider, config: defaultLlm?.config }, + disabled + } + return fetchData>('service/ai-router', { + method: routeId ? 'PUT' : 'POST', + eoBody: body, + eoParams: { service: serviceId, team: teamId, ...(routeId ? { router: routeId } : {}) }, + eoTransformKeys: ['aiPrompt', 'aiModel'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + navigator(`/service/${teamId}/aiInside/${serviceId}/route`) + return Promise.resolve(true) + } else { message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> console.error(errorInfo)) - .finally(()=>setLoading(false)) - } - - const getDefaultModelConfig = (provider?:string)=>{ - fetchData>('ai/provider/llms',{method:'GET',eoParams:{provider:provider ?? aiServiceInfo?.provider?.id}, eoTransformKeys:['default_llm']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setLlmList(data.llms) - setDefaultLlm(prev => { - const llmSetting = data.llms?.find((x:AiProviderLlmsItems)=>x.id ===( prev?.id ?? data.provider.defaultLlm)) - return {...prev, - defaultLlm:data.provider.defaultLlm, - provider:data.provider.id, - name:data.provider.name, - config:llmSetting?.config || '', - ...(llmSetting ?? {}) - } as (AiProviderDefaultConfig & { config: string; }) - }) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> console.error(errorInfo)) - } - - - useEffect(()=>{ - !routeId && aiServiceInfo?.provider && getDefaultModelConfig() - },[ - aiServiceInfo - ]) - - - useEffect(() => { - if(routeId){ - getRouterConfig() - }else{ - form.setFieldsValue({ - prefix:apiPrefix, - variables:[{key:'Query',value:'',require:true}], - prompt:'{{Query}}', - retry:0, - timeout:300000, - pathMatch:'prefix' + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } }) - } - return (form.setFieldsValue({})) - }, []); - - const addVariable = ()=>{ - form.setFieldsValue({ - variables:[...form.getFieldValue('variables'),{key:'',value:'',require:true}] + .catch((errInfo) => Promise.reject(errInfo)) }) - } + }) + .catch((errInfo) => Promise.reject(errInfo)) + } + const isAIApiPreview = type === 'apiDetail' + const backUrl = isAIApiPreview ? `/aiApis/list` : `/service/${teamId}/aiInside/${serviceId}/route` + const openDrawer = (type: 'edit') => { + setDrawerType(type) + } - const handleVariablesChange = (newKeys:string[])=>{ - const variables = form.getFieldValue('variables') || [] - const variablesKeys = variables?.map(({key}:{key:string})=>(key)) - for(const key of newKeys){ - if(!variablesKeys ||variablesKeys.indexOf(key) === -1){ - variables.push({key, value:'',require:true}) - } + useEffect(() => { + drawerType !== undefined ? setOpen(true) : setOpen(false) + }, [drawerType]) + + const getRouterConfig = () => { + setLoading(true) + fetchData>('service/ai-router', { + method: 'GET', + eoParams: { service: serviceId, team: teamId, router: routeId }, + eoTransformKeys: ['ai_model', 'ai_prompt'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const { path, aiPrompt, aiModel } = data.api + let newPath = path + let pathMatch = 'full' + if (prefixForce && path?.startsWith(apiPrefix + '/')) { + newPath = path.slice((apiPrefix?.length || 0) + 1) + } + if (newPath.endsWith('/*')) { + newPath = newPath.slice(0, -2) + pathMatch = 'prefix' + } + form.setFieldsValue({ + ...data.api, + ...aiPrompt, + path: newPath, + pathMatch + }) + setVariablesTable(aiPrompt.variables as VariableItems[]) + setDefaultLlm( + (prev) => + ({ + ...prev, + provider: aiModel?.provider, + id: aiModel?.id, + config: aiModel.config + }) as AiProviderDefaultConfig & { config: string } + ) + getDefaultModelConfig(aiModel?.provider) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) } - form.setFieldsValue({ - variables:[...variables] - }) - setVariablesTable(variables as VariableItems[]) - } + }) + .catch((errorInfo) => console.error(errorInfo)) + .finally(() => setLoading(false)) + } - - const handleValuesChange = (changedValues:Record) => { - if(changedValues.variables){ - setVariablesTable(changedValues.variables as VariableItems[]) + const getDefaultModelConfig = (provider?: string) => { + fetchData>('ai/provider/llms', { + method: 'GET', + eoParams: { provider: provider ?? aiServiceInfo?.provider?.id }, + eoTransformKeys: ['default_llm'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setLlmList(data.llms) + setDefaultLlm((prev) => { + const llmSetting = data.llms?.find( + (x: AiProviderLlmsItems) => x.id === (prev?.id ?? data.provider.defaultLlm) + ) + return { + ...prev, + defaultLlm: data.provider.defaultLlm, + provider: data.provider.id, + name: data.provider.name, + config: llmSetting?.config || '', + ...(llmSetting ?? {}) + } as AiProviderDefaultConfig & { config: string } + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) } - }; + }) + .catch((errorInfo) => console.error(errorInfo)) + } - - const handlerSubmit:() => Promise|undefined= ()=>{ - return drawerAddFormRef.current?.save()?.then((res:{id:string, config:string})=>{ - setDefaultLlm(prev => ({...prev, provider:res.provider, id:res.id, config:res.config, logo:llmList?.find((x:AiProviderLlmsItems)=>x.id === res.id)?.logo}) as (AiProviderDefaultConfig & { config: string; })) - return true}) + useEffect(() => { + !routeId && aiServiceInfo?.provider && getDefaultModelConfig() + }, [aiServiceInfo]) + + useEffect(() => { + if (routeId) { + getRouterConfig() + } else { + form.setFieldsValue({ + prefix: apiPrefix, + variables: [{ key: 'Query', value: '', require: true }], + prompt: '{{Query}}', + retry: 0, + timeout: 300000, + pathMatch: 'prefix' + }) } + return form.setFieldsValue({}) + }, []) - const onClose = () => { - setDrawerType(undefined); - }; + const addVariable = () => { + if (isAIApiPreview) return + form.setFieldsValue({ + variables: [...form.getFieldValue('variables'), { key: '', value: '', require: true }] + }) + } - const apiPathMatchRulesOptions = useMemo(()=>API_PATH_MATCH_RULES.map( - x=>({label:$t(x.label), value:x.value})),[state.language]) + const handleVariablesChange = (newKeys: string[]) => { + const variables = form.getFieldValue('variables') || [] + const variablesKeys = variables?.map(({ key }: { key: string }) => key) + for (const key of newKeys) { + if (!variablesKeys || variablesKeys.indexOf(key) === -1) { + variables.push({ key, value: '', require: true }) + } + } + form.setFieldsValue({ + variables: [...variables] + }) + setVariablesTable(variables as VariableItems[]) + } - return ( - - - - - -
- }> - } spinning={loading} wrapperClassName=' pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X'> - ) => { + if (changedValues.variables) { + setVariablesTable(changedValues.variables as VariableItems[]) + } + } + + const handlerSubmit: () => Promise | undefined = () => { + return drawerAddFormRef.current?.save()?.then((res: { id: string; config: string }) => { + setDefaultLlm( + (prev) => + ({ + ...prev, + provider: res.provider, + id: res.id, + config: res.config, + logo: llmList?.find((x: AiProviderLlmsItems) => x.id === res.id)?.logo + }) as AiProviderDefaultConfig & { config: string } + ) + return true + }) + } + + const onClose = () => { + setDrawerType(undefined) + } + + const apiPathMatchRulesOptions = useMemo( + () => API_PATH_MATCH_RULES.map((x) => ({ label: $t(x.label), value: x.value })), + [state.language] + ) + + return ( + + + {!isAIApiPreview && ( + + )} +
+ } + > + } + spinning={loading} + wrapperClassName=" pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X" + > + + +
+ + + className="flex-1" + label={$t('路由名称')} + name="name" + rules={[{ required: true, whitespace: true }]} > -
- - - className="flex-1" - label={$t("路由名称")} - name="name" - rules={[{ required: true,whitespace:true }]} - > - - - - - - - - { - if((e.target.value as string).endsWith('/*')){ - form.setFieldValue('path',e.target.value.slice(0,-2)) - form.setFieldValue('pathMatch','prefix') - } - }}/> - - - + + - - - - label={$t("提示词")} - name="prompt" - > - - - - - label={
{$t("变量")}New
} - name="variables" - className="[&>.ant-row>.ant-col>label]:w-full" - > - - getFromRef={setVariablesTableRef} - configFields={AI_SERVICE_VARIABLES_TABLE_COLUMNS} - /> - - - - label={$t("描述")} - name="description" - > - - - - - - className="flex-1" - label={$t("请求超时时间")} - name={'timeout'} - rules={[{required: true}]} - > - - - - className="flex-1" - label={$t("重试次数")} - name={'retry'} - rules={[{required: true}]} - > - - - - - -
- - - handlerSubmit()} + + + + name="path" + rules={[ + { required: true, whitespace: true }, + { + validator: validateUrlSlash + } + ]} + noStyle > - - - - ) + { + if ((e.target.value as string).endsWith('/*')) { + form.setFieldValue('path', e.target.value.slice(0, -2)) + form.setFieldValue('pathMatch', 'prefix') + } + }} + /> + + + +
+ + label={$t('提示词')} name="prompt"> + + + + + label={ +
+ {$t('变量')} + + + New + +
+ } + name="variables" + className="[&>.ant-row>.ant-col>label]:w-full" + > + + getFromRef={setVariablesTableRef} + configFields={AI_SERVICE_VARIABLES_TABLE_COLUMNS} + /> + + + label={$t('描述')} name="description"> + + + + + + className="flex-1" + label={$t('请求超时时间')} + name={'timeout'} + rules={[{ required: true }]} + > + + + + className="flex-1" + label={$t('重试次数')} + name={'retry'} + rules={[{ required: true }]} + > + + + + + label={$t('拦截接口')} + name="disabled" + extra={$t('开启拦截后,网关会拦截所有该路径的请求。')} + > + + +
+ +
+
+ handlerSubmit()}> + + + + ) } export default AiServiceInsideRouterCreate - - - \ No newline at end of file diff --git a/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterList.tsx b/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterList.tsx index 88a09f72..007f64f6 100644 --- a/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterList.tsx +++ b/frontend/packages/core/src/pages/aiService/api/AiServiceInsideRouterList.tsx @@ -1,182 +1,224 @@ -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx" -import {ActionType} from "@ant-design/pro-components"; -import {FC, useEffect, useMemo, useRef, useState} from "react"; -import {Link, useNavigate, useParams} from "react-router-dom"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {App, Divider} from "antd"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import { SimpleMemberItem} from '@common/const/type.ts' -import {useFetch} from "@common/hooks/http.ts"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { checkAccess } from "@common/utils/permission.ts"; -import { $t } from "@common/locales/index.ts"; -import { AiServiceRouterTableListItem } from "@core/const/ai-service/type.ts"; -import { AI_SERVICE_ROUTER_TABLE_COLUMNS } from "@core/const/ai-service/const.tsx"; +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { SimpleMemberItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { checkAccess } from '@common/utils/permission.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { AI_SERVICE_ROUTER_TABLE_COLUMNS } from '@core/const/ai-service/const.tsx' +import { AiServiceRouterTableListItem } from '@core/const/ai-service/type.ts' +import { App, Divider, Typography } from 'antd' +import { FC, useEffect, useMemo, useRef, useState } from 'react' +import { Link, useNavigate, useParams } from 'react-router-dom' -const AiServiceInsideRouterList:FC = ()=>{ - const [searchWord, setSearchWord] = useState('') - const { setBreadcrumb } = useBreadcrumb() - const { modal,message } = App.useApp() - const [tableListDataSource, setTableListDataSource] = useState([]); - const [tableHttpReload, setTableHttpReload] = useState(true); - const {fetchData} = useFetch() - const pageListRef = useRef(null); - const [memberValueEnum, setMemberValueEnum] = useState([]) - const {accessData,state} = useGlobalContext() - const {serviceId, teamId} = useParams() - const navigator = useNavigate() +const AiServiceInsideRouterList: FC = () => { + const [searchWord, setSearchWord] = useState('') + const { setBreadcrumb } = useBreadcrumb() + const { modal, message } = App.useApp() + const [tableListDataSource, setTableListDataSource] = useState([]) + const [tableHttpReload, setTableHttpReload] = useState(true) + const { fetchData } = useFetch() + const pageListRef = useRef(null) + const [memberValueEnum, setMemberValueEnum] = useState([]) + const { accessData, state } = useGlobalContext() + const { serviceId, teamId } = useParams() + const navigator = useNavigate() - const getRoutesList = (): Promise<{ data: AiServiceRouterTableListItem[], success: boolean }>=> { - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - - return fetchData>('service/ai-routers',{method:'GET',eoParams:{service:serviceId,team:teamId, keyword:searchWord},eoTransformKeys:['request_path','create_time','update_time','disable']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTableListDataSource(data.apis) - setTableHttpReload(false) - return {data:data.apis, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) + const getRoutesList = (): Promise<{ data: AiServiceRouterTableListItem[]; success: boolean }> => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) } - const deleteRoute = (entity:AiServiceRouterTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('service/ai-router',{method:'DELETE',eoParams:{service:serviceId,team:teamId, router:entity!.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const openModal = async (type: 'delete',entity:AiServiceRouterTableListItem) =>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; + return fetchData>('service/ai-routers', { + method: 'GET', + eoParams: { service: serviceId, team: teamId, keyword: searchWord }, + eoTransformKeys: ['request_path', 'create_time', 'update_time', 'disable'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.apis) + setTableHttpReload(false) + return { data: data.apis, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } + }) + .catch(() => { + return { data: [], success: false } + }) + } - modal.confirm({ - title, - content, - onOk:()=> { - switch (type){ - case 'delete': - return deleteRoute(entity).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - disabled : !checkAccess( `team.service.router.${type}`, accessData ) - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:2, - fixed:'right', - valueType: 'option', - render: (_: React.ReactNode, entity: AiServiceRouterTableListItem) => [ - {navigator(`/service/${teamId}/aiInside/${serviceId}/route/${entity.id}`)}} btnTitle="编辑"/>, - , - {openModal('delete',entity)}} btnTitle="删除"/>, - ], - } - ] - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - const getMemberList = async ()=>{ - setMemberValueEnum([]) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - setMemberValueEnum(data.members) - }else{ + const deleteRoute = (entity: AiServiceRouterTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>('service/ai-router', { + method: 'DELETE', + eoParams: { service: serviceId, team: teamId, router: entity!.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { message.error(msg || $t(RESPONSE_TIPS.error)) - } + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const openModal = async (type: 'delete', entity: AiServiceRouterTableListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break } - useEffect(() => { - setBreadcrumb([ - { - title:{$t('服务')} - }, - { - title:$t('路由') - } - ]) - getMemberList() - manualReloadTable() - }, [serviceId]); - - const columns = useMemo(()=>{ - return [...AI_SERVICE_ROUTER_TABLE_COLUMNS].map(x=>{ - if(x.filters &&((x.dataIndex as string[])?.indexOf('creator') !== -1) ){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - memberValueEnum?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - x.valueEnum = tmpValueEnum - } - - return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}}) - },[memberValueEnum,state.language]) + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'delete': + return deleteRoute(entity).then((res) => { + if (res === true) manualReloadTable() + }) + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: !checkAccess(`team.service.router.${type}`, accessData) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 2, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: AiServiceRouterTableListItem) => [ + { + navigator(`/service/${teamId}/aiInside/${serviceId}/route/${entity.id}`) + }} + btnTitle="编辑" + />, + , + { + openModal('delete', entity) + }} + btnTitle="删除" + /> + ] + } + ] - return ( - <> - getRoutesList()} - dataSource={tableListDataSource} - addNewBtnTitle={$t('添加路由')} - searchPlaceholder={$t('输入 URL 查找路由')} - onAddNewBtnClick={()=>{navigator(`/service/${teamId}/aiInside/${serviceId}/route/create`)}} - addNewBtnAccess="team.service.router.add" - tableClickAccess="team.service.router.view" - manualReloadTable={manualReloadTable} - onSearchWordChange={(e)=>{setSearchWord(e.target.value)}} - onChange={() => { - setTableHttpReload(false) - }} - onRowClick={(row:AiServiceRouterTableListItem)=>navigator(`/service/${teamId}/aiInside/${serviceId}/route/${row.id}`)} - tableClass="mr-PAGE_INSIDE_X " - /> - - ) + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } + const getMemberList = async () => { + setMemberValueEnum([]) + const { code, data, msg } = await fetchData>('simple/member', { + method: 'GET' + }) + if (code === STATUS_CODE.SUCCESS) { + setMemberValueEnum(data.members) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + useEffect(() => { + setBreadcrumb([ + { + title: {$t('服务')} + }, + { + title: $t('路由') + } + ]) + getMemberList() + manualReloadTable() + }, [serviceId]) + + const columns = useMemo(() => { + return [...AI_SERVICE_ROUTER_TABLE_COLUMNS].map((x) => { + if (x.filters && (x.dataIndex as string[])?.indexOf('creator') !== -1) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + memberValueEnum?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + x.valueEnum = tmpValueEnum + } + if (x.filters && (x.dataIndex as string[])?.indexOf('disabled') !== -1) { + x.valueEnum = { + true: { text: {$t('拦截')} }, + false: { text: {$t('放行')} } + } + } + + return { ...x, title: typeof x.title === 'string' ? $t(x.title as string) : x.title } + }) + }, [memberValueEnum, state.language]) + + return ( + <> + getRoutesList()} + dataSource={tableListDataSource} + addNewBtnTitle={$t('添加路由')} + searchPlaceholder={$t('输入 URL 查找路由')} + onAddNewBtnClick={() => { + navigator(`/service/${teamId}/aiInside/${serviceId}/route/create`) + }} + addNewBtnAccess="team.service.router.add" + tableClickAccess="team.service.router.view" + manualReloadTable={manualReloadTable} + onSearchWordChange={(e) => { + setSearchWord(e.target.value) + }} + onChange={() => { + setTableHttpReload(false) + }} + onRowClick={(row: AiServiceRouterTableListItem) => + navigator(`/service/${teamId}/aiInside/${serviceId}/route/${row.id}`) + } + tableClass="mr-PAGE_INSIDE_X " + /> + + ) } -export default AiServiceInsideRouterList \ No newline at end of file +export default AiServiceInsideRouterList diff --git a/frontend/packages/core/src/pages/aiService/publish/AiServiceInsidePublishOnline.tsx b/frontend/packages/core/src/pages/aiService/publish/AiServiceInsidePublishOnline.tsx index 44b93bb7..22f6fb6e 100644 --- a/frontend/packages/core/src/pages/aiService/publish/AiServiceInsidePublishOnline.tsx +++ b/frontend/packages/core/src/pages/aiService/publish/AiServiceInsidePublishOnline.tsx @@ -1,90 +1,120 @@ - -import { App, Table, Tooltip } from "antd"; -import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "../../../const/system/const"; -import { useEffect, useMemo, useState } from "react"; -import { useFetch } from "@common/hooks/http"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR } from "@common/const/const"; -import { EntityItem } from "@common/const/type"; -import { LoadingOutlined } from "@ant-design/icons"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +import { LoadingOutlined } from '@ant-design/icons' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR } from '@common/const/const' +import { EntityItem } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Table, Tooltip } from 'antd' +import { useEffect, useMemo, useState } from 'react' +import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from '../../../const/system/const' type AiServiceInsidePublishOnlineProps = { - serviceId:string - teamId:string - id:string + serviceId: string + teamId: string + id: string } export type AiServiceInsidePublishOnlineItems = { - cluster:EntityItem - status:'done' | 'error' | 'publishing' - error:string + cluster: EntityItem + status: 'done' | 'error' | 'publishing' + error: string } -export default function AiServiceInsidePublishOnline(props:AiServiceInsidePublishOnlineProps ){ - const {serviceId, teamId, id} = props - const {message} = App.useApp() - const [dataSource, setDataSource] = useState<[]>() - const {fetchData} = useFetch() - const [isStopped, setIsStopped] = useState(false); - const { state } = useGlobalContext() +export default function AiServiceInsidePublishOnline(props: AiServiceInsidePublishOnlineProps) { + const { serviceId, teamId, id } = props + const { message } = App.useApp() + const [dataSource, setDataSource] = useState<[]>() + const { fetchData } = useFetch() + const [isStopped, setIsStopped] = useState(false) + const { state } = useGlobalContext() - const getOnlineStatus = ()=>{ - fetchData>('service/publish/status',{method:'GET',eoParams:{service:serviceId,team:teamId, id}, eoTransformKeys:['publish_status_list']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setDataSource(data.publishStatusList) - if(data.publishStatusList.filter((x:AiServiceInsidePublishOnlineItems)=>x.status === 'publishing').length === 0){ - setIsStopped(true) - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> message.error(errorInfo)) + const getOnlineStatus = () => { + fetchData>( + 'service/publish/status', + { + method: 'GET', + eoParams: { service: serviceId, team: teamId, id }, + eoTransformKeys: ['publish_status_list'] + } + ) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setDataSource(data.publishStatusList) + if ( + data.publishStatusList.filter( + (x: AiServiceInsidePublishOnlineItems) => x.status === 'publishing' + ).length === 0 + ) { + setIsStopped(true) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(errorInfo => message.error(errorInfo)) + } + + useEffect(() => { + getOnlineStatus() + }, []) + + useEffect(() => { + let intervalId: NodeJS.Timeout + if (!isStopped) { + intervalId = setInterval(() => { + !isStopped && getOnlineStatus() + }, 5000) } - useEffect(()=>{ - getOnlineStatus(); - },[]) + return () => { + clearInterval(intervalId) + } + }, [isStopped]) - useEffect(() => { - let intervalId: NodeJS.Timeout; - if (!isStopped) { - intervalId = setInterval(() => { - !isStopped && getOnlineStatus(); - }, 5000); + const translatedPublishColumns = useMemo( + () => + SYSTEM_PUBLISH_ONLINE_COLUMNS.map(x => { + if (x.dataIndex === 'status') { + return { + ...x, + title: $t(x.title), + render: (_: unknown, entity: AiServiceInsidePublishOnlineItems) => { + switch (entity.status) { + case 'done': + return ( + + {$t('成功')} + + ) + case 'error': + return ( + + + {$t('失败')} {entity.error} + + + ) + default: + return + } + } + } } + }), + [state.language] + ) - return () => { - clearInterval(intervalId); - }; - }, [isStopped]); - - - const translatedPublishColumns = useMemo(()=>SYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{ - if(x.dataIndex === 'status'){ - return {...x,title:$t(x.title), - render:(_:unknown,entity:AiServiceInsidePublishOnlineItems)=>{ - switch(entity.status){ - case 'done': - return {$t('成功')} - case 'error': - return {$t('失败')} {entity.error} - default: - return - } - }} - } - }),[state.language]) - - return ( -
- ) -} \ No newline at end of file + return ( +
+ ) +} diff --git a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx new file mode 100644 index 00000000..dfd90d5c --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx @@ -0,0 +1,269 @@ +'use client' + +import { BasicResponse } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { + CoordinateExtent, + Edge, + EdgeTypes, + Node, + NodeTypes, + PanOnScrollMode, + ReactFlow, + useEdgesState, + useNodesState +} from '@xyflow/react' +import '@xyflow/react/dist/style.css' +import { Button, Space, Spin } from 'antd' +import { useCallback, useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import CustomEdge from './components/CustomEdge' +import { KeyStatusNode } from './components/KeyStatusNode' +import { ModelCardNode } from './components/ModelCardNode' +import { ServiceCardNode } from './components/NodeComponents' +import { LAYOUT } from './constants' +import './styles.css' +import { ModelListData } from './types' + +export type ApiResponse = BasicResponse<{ + backup: { + id: string + name: string + } + providers: ModelListData[] +}> + +const calculateNodePositions = (models: ModelListData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { + return models.reduce( + (acc, model, index) => { + const y = startY + index * gap + return { + ...acc, + [model.id]: { + x: LAYOUT.MODEL_NODE_X, + y + }, + [`${model.id}-keys`]: { + x: LAYOUT.KEY_NODE_X, + y: y + 16 + } + } + }, + {} as Record + ) +} + +const nodeTypes: NodeTypes = { + modelCard: ModelCardNode, + keyCard: KeyStatusNode, + serviceCard: ServiceCardNode +} as const + +const edgeTypes: EdgeTypes = { + custom: CustomEdge +} + +const AIFlowChart = () => { + const [modelData, setModelData] = useState([]) + const [loading, setLoading] = useState(false) + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + const { fetchData } = useFetch() + const { aiConfigFlushed } = useGlobalContext() + const navigate = useNavigate() + + useEffect(() => { + setLoading(true) + fetchData('ai/providers/configured', { + method: 'GET', + eoTransformKeys: ['default_llm'] + // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' + }) + .then((response) => { + const mockApiResponse: ApiResponse = response as ApiResponse + setModelData(mockApiResponse.data.providers) + }) + .finally(() => { + setLoading(false) + }) + }, [aiConfigFlushed]) + + useEffect(() => { + if (!modelData.length) return + + const positions = calculateNodePositions(modelData) + const firstSuccessModel = modelData.find((model) => model.status === 'enabled') + console.log(firstSuccessModel) + + // subtract 5 to make sure the service node is aligned with the top model node + const serviceY = positions[modelData[0].id].y - 5 + const newNodes = [ + { + id: 'apiService', + type: 'serviceCard', + position: { x: LAYOUT.SERVICE_NODE_X, y: serviceY }, + draggable: false, + data: { + title: 'API Services', + count: modelData.length + } + }, + ...modelData.map((model) => ({ + id: model.id, + type: 'modelCard', + position: positions[model.id], + data: { + name: model.name, + status: model.status, + defaultLlm: model.defaultLlm, + logo: model.logo, + id: model.id, + alternativeModel: firstSuccessModel + } + })), + ...modelData.map((model) => ({ + id: `${model.id}-keys`, + type: 'keyCard', + position: positions[`${model.id}-keys`], + data: { + title: '', + keys: (model.keys || []).map((key, index) => ({ + id: key.id, + status: key.status, + priority: index + 1 + })) + } + })) + ] + + const newEdges: any = [ + ...modelData.map((model) => ({ + id: `service-${model.id}`, + source: 'apiService', + target: model.id, + label: `${model.api_count} apis`, + data: { + id: model.id, + status: model.status + }, + animated: true, + style: { stroke: model.status === 'enabled' ? '#52c41a' : '#ff4d4f' } + })), + ...modelData.map((model) => ({ + id: `${model.id}-keys-edge`, + source: model.id, + target: `${model.id}-keys`, + label: `${model.key_count} keys`, + data: { id: model.id }, + animated: true + })) + ] + setNodes(newNodes) + setEdges(newEdges) + }, [modelData]) + + const calculateExtent = useCallback(() => { + const left = LAYOUT.SERVICE_NODE_X + const right = LAYOUT.KEY_NODE_X + const top = 0 // Allow slight negative scroll to reduce top padding + const bottom = LAYOUT.NODE_START_Y + modelData.length * LAYOUT.NODE_GAP + return [ + [left, top], + [right, bottom < 100 ? 5000 : bottom] + ] as CoordinateExtent + }, [modelData.length]) + + const updateProviderOrder = async (sortedProviderIds: string[]) => { + await fetchData('ai/provider/sort', { + method: 'PUT', + body: JSON.stringify({ + providers: sortedProviderIds + }) + }) + } + + const onNodeDragStop: any = useCallback((_: any, node: Node) => { + if (node.type !== 'modelCard') return + + setNodes((nds) => { + const modelNodes = nds.filter((n) => n.type === 'modelCard') + const sortedNodes = [...modelNodes].sort((a, b) => a.position.y - b.position.y) + const sortedProviderIds = sortedNodes.map((node) => node.id) + + // Update provider order outside of setNodes callback + updateProviderOrder(sortedProviderIds) + // Update all node positions in a single pass + return nds.map((n) => { + if (n.type === 'modelCard') { + const index = sortedNodes.findIndex((sn) => sn.id === n.id) + return { + ...n, + position: { + x: LAYOUT.MODEL_NODE_X, + y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP + } + } + } + if (n.type === 'keyCard') { + const modelId = n.id.replace('-keys', '') + const modelNode = sortedNodes.find((mn) => mn.id === modelId) + if (modelNode) { + const index = sortedNodes.findIndex((sn) => sn.id === modelId) + return { + ...n, + position: { + x: LAYOUT.KEY_NODE_X, + y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP + 16 + } + } + } + } + return n + }) + }) + }, []) + + return ( +
+ {loading ? ( +
+ +
+ ) : modelData.length === 0 ? ( + +
{$t('未配置 AI 模型')}
+ +
+ ) : ( + + )} +
+ ) +} + +export default AIFlowChart diff --git a/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx b/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx new file mode 100644 index 00000000..ba1ae334 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx @@ -0,0 +1,136 @@ +import Icon, { LoadingOutlined } from '@ant-design/icons' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Button, Card, Empty, Spin, Tag } from 'antd' +import { memo, useEffect, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useAiSetting } from './contexts/AiSettingContext' +import { AiSettingListItem } from './types' + +const CardBox = memo(({ provider }: { provider: AiSettingListItem }) => { + const { openConfigModal } = useAiSetting() + const navigate = useNavigate() + + const handleOpenModal = async (provider: AiSettingListItem) => { + await openConfigModal(provider) + navigate('/aisetting?status=configure') + } + + return ( + +
+ + {provider.name} +
+ + {provider.configured ? $t('已配置') : $t('未配置')} + + + } + className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible h-[156px] m-0 flex flex-col " + classNames={{ header: 'border-b-[0px] p-[20px] px-[24px]', body: 'pt-0 flex-1' }} + > +
+
+ {provider.configured && ( + <> + + {provider.defaultLlm} + + )} +
+ + + +
+
+ ) +}) +const ModelCardArea = ({ modelList, className }: { modelList: AiSettingListItem[]; className?: string }) => { + return ( + <> + {modelList.length > 0 ? ( +
+ {modelList.map((provider: AiSettingListItem) => ( + + ))} +
+ ) : ( + + )} + + ) +} + +const AIUnConfigure = () => { + const [modelData, setModelData] = useState([]) + const { fetchData } = useFetch() + const [loading, setLoading] = useState(false) + const { aiConfigFlushed } = useGlobalContext() + + useEffect(() => { + setLoading(true) + fetchData[] }>>(`ai/providers/unconfigured`, { + method: 'GET', + eoTransformKeys: ['default_llm', 'default_llm_logo'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setModelData(data.providers) + } else { + const { message } = App.useApp() + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => setLoading(false)) + }, [aiConfigFlushed]) + + return ( + } + spinning={loading} + > + {modelData && modelData.length > 0 ? ( +
+ {modelData.filter((item) => !item.configured).length > 0 && ( + <> + !item.configured) || []} /> + + )} +
+ ) : ( + + )} +
+ ) +} +export default AIUnConfigure diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx index 2b06cd0d..30694d5a 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx @@ -1,211 +1,71 @@ -import { LoadingOutlined } from "@ant-design/icons"; -import InsidePage from "@common/components/aoplatform/InsidePage"; -import { BasicResponse, STATUS_CODE, RESPONSE_TIPS } from "@common/const/const"; -import { useFetch } from "@common/hooks/http"; -import { $t } from "@common/locales"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import { App, Spin, Card, Tag, Button, Empty, Divider } from "antd"; -import { memo, useEffect, useRef, useState } from "react"; -import AiSettingModalContent, { AiSettingModalContentHandle } from "./AiSettingModal"; -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { checkAccess } from "@common/utils/permission"; +import InsidePage from '@common/components/aoplatform/InsidePage' +import { $t } from '@common/locales' +import { Tabs } from 'antd' +import { useEffect, useState } from 'react' +import { useSearchParams } from 'react-router-dom' +import AIFlowChart from './AIFlowChart' +import AIUnConfigure from './AIUnconfigure' +import { AiSettingProvider } from './contexts/AiSettingContext' -export type AiSettingListItem = { - name: string - id:string - logo:string - defaultLlm: string - defaultLlmLogo:string - enable:boolean - configured:boolean -} +const CONTENT_STYLE = { height: 'calc(-300px + 100vh)' } as const -export type AiProviderLlmsItems = { - id:string - logo:string - scopes:('chat'|'completions')[] - config:string -} +const AiSettingContent = () => { + const [searchParams, setSearchParams] = useSearchParams() + const [activeKey, setActiveKey] = useState(searchParams.get('status') === 'unconfigure' ? 'config' : 'flow') -export type AiProviderDefaultConfig = { - id:string - provider:string - name:string - logo:string - defaultLlm:string - scopes:string[] -} + useEffect(() => { + const newActiveKey = searchParams.get('status') === 'unconfigure' ? 'config' : 'flow' + setActiveKey(newActiveKey) + }, [searchParams]) -export type AiProviderConfig = { - id:string - name:string - config:string - getApikeyUrl:string -} -const AiSettingList = ()=>{ - const { modal,message } = App.useApp() - const {fetchData} = useFetch() - const [aiSettingList, setAiSettingList] = useState([]) - const [loading, setLoading] = useState(false) - // const [updateLoading, setUpdateLoading] = useState(false) - const modalRef = useRef() - const {setAiConfigFlushed,accessData} = useGlobalContext() - - const getAiSettingList = ()=>{ - setLoading(true) - return fetchData[]}>>(`ai/providers`,{method:'GET', eoTransformKeys:['default_llm','default_llm_logo']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAiSettingList(data.providers?.map((x:AiSettingListItem)=>({...x,name:$t(x.name),llmListStatus:'unload', availableLlms:[]}) - )) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) + return ( + +
+ { + setActiveKey(key) + setSearchParams({ status: key === 'config' ? 'unconfigure' : 'configure' }) + }} + className="sticky top-0 flex-shrink-0" + items={[ + { + key: 'flow', + label: $t('已设置'), + children: ( +
+ +
+ ) + }, + { + key: 'config', + label: $t('未设置'), + children: ( +
+ +
+ ) } - }).finally(()=>setLoading(false)) - } - - - // 第一期暂时隐藏 - // const updateModalList = ()=>{ - // setUpdateLoading(true) - // return fetchData>(`aisetting`,{method:'GET'}).then(response=>{ - // const {code,msg} = response - // if(code === STATUS_CODE.SUCCESS){ - // getAiSettingList() - // }else{ - // message.error(msg || $t(RESPONSE_TIPS.error)) - // } - // }).finally(()=>setUpdateLoading(false)) - // } - - const openModal = async (entity:AiSettingListItem)=>{ - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>('ai/provider/config',{method:'GET',eoParams:{provider:entity!.id}, eoTransformKeys:['get_apikey_url']}) - message.destroy() - if(code !== STATUS_CODE.SUCCESS){ - message.error(msg || $t(RESPONSE_TIPS.error)) - return - } - modal.confirm({ - title:$t('模型配置'), - content:, - onOk:()=>{ - return modalRef.current?.save().then((res)=>{if(res === true) - setAiConfigFlushed(true) - getAiSettingList()}) - }, - width:600, - okText:$t('确认'), - footer:(_, { OkBtn, CancelBtn }) =>{ - return ( -
- - {$t('从 (0) 获取 API KEY',[data.provider.name])} - - -
- - { - checkAccess('system.devops.ai_provider.edit', accessData) ? : null - } -
-
- ); - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - useEffect(() => { - getAiSettingList() - }, []); - - const CardBox = memo(({provider}:{provider:AiSettingListItem})=>{ - return ( - -
- - {provider.name} -
- - {provider.configured ? $t('已配置') : $t('未配置')} - -
} - className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible h-[156px] m-0 flex flex-col " - classNames={{header:'border-b-[0px] p-[20px] px-[24px]', body:"pt-0 flex-1"}} - > -
-
{ - provider.configured && <> - {provider.defaultLlm} - - } - -
- - - -
- - ) - }); - - const ModelCardArea = ({modelList,className}:{ modelList:AiSettingListItem[] ;className?:string})=>{ - return ( <> - { modelList.length > 0 ? -
- {modelList.map((provider:AiSettingListItem)=>)} -
: - } - ) - } - - return (<> - - // - // - // } - > - } spinning={loading}> - {aiSettingList && aiSettingList.length > 0 ?
-

{$t('已配置')}

- item.configured) || [] }/> - { - aiSettingList.filter((item)=>!item.configured).length > 0 && <> - -

{$t('未配置')}

- !item.configured) || [] }/> - - } -
:} -
-
- ) + ]} + /> + +
+ ) } -export default AiSettingList; \ No newline at end of file + +const AiSettingList = () => { + return ( + + + + ) +} + +export default AiSettingList diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx index 9155a703..a81a4c7d 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx @@ -1,124 +1,205 @@ -import { BasicResponse, STATUS_CODE, RESPONSE_TIPS, PLACEHOLDER } from "@common/const/const"; -import { useFetch } from "@common/hooks/http"; -import { $t } from "@common/locales"; -import { App, Form, Select, Tag } from "antd"; -import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { AiProviderConfig, AiProviderLlmsItems } from "./AiSettingList"; -import { Codebox } from "@common/components/postcat/api/Codebox"; - +import { QuestionCircleOutlined } from '@ant-design/icons' +import { Codebox } from '@common/components/postcat/api/Codebox' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Form, InputNumber, Select, Switch, Tag, Tooltip } from 'antd' +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { AiProviderLlmsItems, ModelDetailData } from './types' export type AiSettingModalContentProps = { - entity:AiProviderConfig & {defaultLlm:string} - readOnly:boolean + entity: ModelDetailData & { defaultLlm: string } + readOnly: boolean } export type AiSettingModalContentHandle = { - save:()=>Promise + save: () => Promise } -type AiSettingModalContentField = { - config:string - defaultLlm:string -} - -const AiSettingModalContent = forwardRef((props,ref)=>{ - const [form] = Form.useForm(); - const { message } = App.useApp() - const {entity,readOnly} = props - const {fetchData} = useFetch() - const [llmList, setLlmList] = useState() - const [loading, setLoading] = useState(false) - - - const getLlmList = ()=>{ - setLoading(true) - fetchData>(`ai/provider/llms`,{method:'GET',eoParams:{provider:entity.id}}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setLlmList(data.llms) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{ - setLoading(false) - }) - } - - - useEffect(() => { - getLlmList() - try{ - form.setFieldsValue({ - defaultLlm:entity.defaultLlm, - config:entity!.config ? JSON.stringify(JSON.parse(entity!.config),null,2) : '' - }) - }catch(e){ - form.setFieldsValue({ - defaultLlm:entity.defaultLlm, - config: '' - }) - } - }, []); - - const save: ()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>('ai/provider/config',{method:'PUT',eoParams:{provider:entity?.id}, eoBody:value, eoTransformKeys:['defaultLlm']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) +const AiSettingModalContent = forwardRef((props, ref) => { + const [form] = Form.useForm() + const { message } = App.useApp() + const { entity, readOnly } = props + const { fetchData } = useFetch() + const [llmList, setLlmList] = useState() + const [loading, setLoading] = useState(false) + const [enableState, setEnableState] = useState(entity.status === 'enabled') + const getLlmList = () => { + setLoading(true) + fetchData>(`ai/provider/llms`, { + method: 'GET', + eoParams: { provider: entity.id } }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setLlmList(data.llms) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setLoading(false) + }) + } + + useEffect(() => { + getLlmList() + try { + form.setFieldsValue({ + defaultLlm: entity.defaultLlm, + config: entity!.config ? JSON.stringify(JSON.parse(entity!.config), null, 2) : '', + priority: entity.priority || 1, + enable: entity.status === 'enabled' + }) + } catch (e) { + form.setFieldsValue({ + defaultLlm: entity.defaultLlm, + config: '', + priority: 1, + enable: true + }) } + }, []) - useImperativeHandle(ref, ()=>({ - save + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + const finalValue = { + ...value, + priority: Math.max(1, value.priority) + } + + fetchData>('ai/provider/config', { + method: 'PUT', + eoParams: { provider: entity?.id }, + eoBody: finalValue, + eoTransformKeys: ['defaultLlm'] + // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) }) - ) + .catch((errorInfo) => reject(errorInfo)) + }) + } - return ( -
+ const getTooltipText = (isChecked: boolean) => { + if (!isChecked) { + return $t('保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。') + } + return $t('保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。') + } - - label={$t("模型")} - name="defaultLlm" - rules={[{ required: true }]} - > - - + useImperativeHandle(ref, () => ({ + save + })) - - label={$t("参数")} - name="config" - > - - - + return ( +
+ label={$t('默认模型')} name="defaultLlm" rules={[{ required: true }]}> + + + + + label={ + + {$t('负载优先级')} + + + + + } + name="priority" + rules={[ + { required: true }, + { + validator: async (_, value) => { + if (value <= 0) { + throw new Error($t('优先级必须大于 0')) + } + return Promise.resolve() + } + } + ]} + initialValue={1} + > + + + + label={$t('API Key(默认 Key)')} name="config"> + + + + {entity.configured && ( + +
+
+ {$t('当前调用状态:')} + {entity.status === 'enabled' && {$t('正常')}} + {entity.status === 'disabled' && {$t('停用')}} + {entity.status === 'abnormal' && {$t('异常')}} +
+ + { + form.setFieldsValue({ enable: checked }) + setEnableState(checked) + }} + /> + +
+ {(entity.status === 'enabled' && !enableState) || (entity.status !== 'enabled' && enableState) ? ( +
* {getTooltipText(enableState)}
+ ) : null} +
+ )} + ) }) -export default AiSettingModalContent \ No newline at end of file +export default AiSettingModalContent diff --git a/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx b/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx new file mode 100644 index 00000000..35d585f5 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx @@ -0,0 +1,72 @@ +import { BaseEdge, EdgeLabelRenderer, EdgeProps, getSmoothStepPath, useStore } from '@xyflow/react' + +export default function CustomEdge({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + style = {}, + markerEnd, + label, + data, + source, + target +}: EdgeProps) { + // Get all edges to check for duplicates + const edges = useStore((state) => state.edges) + + // Find duplicate edges between the same source and target + const duplicateEdges = edges.filter((edge) => edge.source === source && edge.target === target) + const edgeIndex = duplicateEdges.findIndex((edge) => edge.id === id) + + // Adjust the path if this is a duplicate edge + const offset = edgeIndex * 20 // 20px offset for each duplicate edge + + const [edgePath] = getSmoothStepPath({ + sourceX, + sourceY: sourceY, + sourcePosition, + targetX, + targetY: targetY + offset, + targetPosition, + borderRadius: 16 + }) + + const modelId = data?.id + + return ( + <> + + {label && ( + + + {label} + + + )} + + ) +} diff --git a/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx new file mode 100644 index 00000000..5420e9fe --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx @@ -0,0 +1,52 @@ +import { Handle, Position } from '@xyflow/react' +import React from 'react' +import { KeyData } from '../types' + +interface KeyStatusNodeData { + id: string + title: string + keys: KeyData[] +} + +const KEY_SIZE = '1.25rem' // 20px +const KEY_GAP = '0.25rem' // 4px +const MAX_KEYS = 10 + +export const KeyStatusNode: React.FC<{ data: KeyStatusNodeData }> = ({ data }) => { + const { title, keys = [] } = data + const totalKeys = keys.length + const keyWidth = totalKeys > 5 ? `calc((100% - ${(totalKeys - 1) * 0.25}rem) / ${totalKeys})` : KEY_SIZE + return ( +
+ +
+
{title}
+
+ {keys.map((key) => ( +
+ ))} +
+
+
+ ) +} diff --git a/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx new file mode 100644 index 00000000..c08bac0b --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx @@ -0,0 +1,74 @@ +import { $t } from '@common/locales' +import { Icon } from '@iconify/react' +import { Handle, Position } from '@xyflow/react' +import React from 'react' +import { useAiSetting } from '../contexts/AiSettingContext' +import { AiSettingListItem, ModelDetailData, ModelStatus } from '../types' + +type ModelCardNodeData = ModelDetailData & { + id: string + position: { x: number; y: number } + alternativeModel?: ModelDetailData +} + +export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) => { + const { name, status, defaultLlm, logo, alternativeModel } = data + const { openConfigModal } = useAiSetting() + + const getStatusIcon = (status: ModelStatus) => { + switch (status) { + case 'enabled': + return { icon: 'mdi:check-circle', color: 'text-green-500' } + case 'disabled': + return { icon: 'mdi:pause-circle', color: 'text-gray-400' } + case 'abnormal': + return { icon: 'mdi:alert-circle', color: 'text-red-500' } + } + } + + const statusConfig = getStatusIcon(status) + + return ( +
+ + +
+
+
+
+ +
+ {name} + +
+ + {/* Action buttons */} +
+ { + openConfigModal({ id: data.id, defaultLlm: defaultLlm } as AiSettingListItem) + }} + /> +
+
+
+ {$t('默认:')} + {defaultLlm} +
+ {status !== 'enabled' && alternativeModel && ( +
+ {$t('关联 API 已转用')} {alternativeModel.name}/{alternativeModel.defaultLlm} +
+ )} +
+
+ ) +} diff --git a/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx b/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx new file mode 100644 index 00000000..e2626cc2 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx @@ -0,0 +1,3 @@ +export { KeyStatusNode } from './KeyStatusNode' +export { ModelCardNode } from './ModelCardNode' +export { ServiceCardNode } from './ServiceCardNode' diff --git a/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx new file mode 100644 index 00000000..176d80da --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx @@ -0,0 +1,18 @@ +import { Icon } from '@iconify/react' +import { Handle, NodeProps, Position } from '@xyflow/react' +import React from 'react' + +export const ServiceCardNode: React.FC = () => { + return ( +
+ +
+ + AI Services +
+
+ ) +} diff --git a/frontend/packages/core/src/pages/aiSetting/constants.ts b/frontend/packages/core/src/pages/aiSetting/constants.ts new file mode 100644 index 00000000..52ec18d8 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/constants.ts @@ -0,0 +1,7 @@ +export const LAYOUT = { + SERVICE_NODE_X: 0, + NODE_START_Y: 20, + NODE_GAP: 120, + MODEL_NODE_X: 700, + KEY_NODE_X: 1200, +} as const; diff --git a/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx new file mode 100644 index 00000000..f8e078bd --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx @@ -0,0 +1,89 @@ +import Icon from '@ant-design/icons' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { checkAccess } from '@common/utils/permission' +import { App } from 'antd' +import { createContext, useContext, useRef } from 'react' +import AiSettingModalContent, { AiSettingModalContentHandle } from '../AiSettingModal' +import { AiSettingListItem, ModelDetailData } from '../types' + +interface AiSettingContextType { + openConfigModal: (entity: AiSettingListItem) => Promise +} + +const AiSettingContext = createContext(undefined) + +export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const { modal, message } = App.useApp() + const { fetchData } = useFetch() + const { aiConfigFlushed, setAiConfigFlushed, accessData } = useGlobalContext() + const modalRef = useRef() + + const openConfigModal = async (entity: AiSettingListItem) => { + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>('ai/provider/config', { + method: 'GET', + eoParams: { provider: entity!.id }, + eoTransformKeys: ['get_apikey_url'] + }) + message.destroy() + if (code !== STATUS_CODE.SUCCESS) { + message.error(msg || $t(RESPONSE_TIPS.error)) + return + } + + modal.confirm({ + title: $t('模型配置'), + content: ( + + ), + onOk: () => { + return modalRef.current?.save().then((res) => { + if (res === true) { + setAiConfigFlushed(!aiConfigFlushed) + } + }) + }, + width: 600, + okText: $t('确认'), + footer: (_, { OkBtn, CancelBtn }) => { + return ( +
+ + {$t('从 (0) 获取 API KEY', [data.provider.name])} + + +
+ + {checkAccess('system.devops.ai_provider.edit', accessData) ? : null} +
+
+ ) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + return {children} +} + +export const useAiSetting = () => { + const context = useContext(AiSettingContext) + if (!context) { + throw new Error('useAiSetting must be used within an AiSettingProvider') + } + return context +} diff --git a/frontend/packages/core/src/pages/aiSetting/styles.css b/frontend/packages/core/src/pages/aiSetting/styles.css new file mode 100644 index 00000000..13d5b62b --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/styles.css @@ -0,0 +1,75 @@ +/* Flow Chart Styles */ +.react-flow { + width: 100%; + height: 100%; + min-height: 500px; +} + +.react-flow__container { + width: 100%; + height: 100%; +} + +.react-flow__renderer { + width: 100%; + height: 100%; +} + +.react-flow__node { + padding: 0; + border-radius: 8px; + min-width: 150px; + width: auto; + max-width: 100%; +} + +/* Custom Node Styles */ +.custom-node { + background: white; + border: 1px solid #ededed; + border-radius: 8px; + padding: 10px; + width: 280px; +} + +.custom-node:hover { + box-shadow: 0 0 0 1px var(--primary-color); +} + +/* API Key Status Grid */ +.api-key-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; +} + +.api-key-status { + aspect-ratio: 1; + border-radius: 4px; + transition: all 0.2s ease; +} + +.api-key-status:hover { + transform: scale(1.05); +} + +/* Responsive Breakpoints */ +@media (max-width: 768px) { + .custom-node { + width: 240px; + } + + .api-key-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 576px) { + .custom-node { + width: 200px; + } + + .api-key-grid { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts new file mode 100644 index 00000000..6ba2ea7a --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/types.ts @@ -0,0 +1,57 @@ +export type ModelStatus = 'enabled' | 'abnormal'|'disabled' +export type KeyStatus ='normal' | 'abnormal'|'disabled' + +export interface KeyData { + id: string + name: string + status: KeyStatus, +} + +export interface ModelListData { + id: string + name: string + logo: string + defaultLlm: string + status: ModelStatus + api_count: number + key_count: number + keys: KeyData[] +} +export interface ModelDetailData extends ModelListData{ + enable:boolean + config: string, + priority?: number + getApikeyUrl: string + status: ModelStatus + configured: boolean +} + + +export type AiSettingListItem = { + name: string + id: string + logo: string + defaultLlm: string + defaultLlmLogo: string + enable: boolean + configured: boolean + priority?: number +} + +export type AiProviderLlmsItems = { + id: string + logo: string + scopes: ('chat' | 'completions')[] + config: string +} + +export type AiProviderDefaultConfig = { + id: string + provider: string + name: string + logo: string + defaultLlm: string + scopes: string[] +} + + diff --git a/frontend/packages/core/src/pages/approval/ApprovalList.tsx b/frontend/packages/core/src/pages/approval/ApprovalList.tsx index 43941a70..c7c3a1cb 100644 --- a/frontend/packages/core/src/pages/approval/ApprovalList.tsx +++ b/frontend/packages/core/src/pages/approval/ApprovalList.tsx @@ -1,185 +1,278 @@ -import {ActionType} from "@ant-design/pro-components"; -import {App, Button} from "antd"; -import {useEffect, useMemo, useRef, useState} from "react"; -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"; +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import { PublishApprovalModalContent } from '@common/components/aoplatform/PublishApprovalModalContent.tsx' import { - PUBLISH_APPROVAL_TABLE_COLUMN, - SUBSCRIBE_APPROVAL_TABLE_COLUMN, - TODO_LIST_COLUMN_NOT_INCLUDE_KEY -} from "@common/const/approval/const.tsx"; + SubscribeApprovalModalContent, + SubscribeApprovalModalHandle +} from '@common/components/aoplatform/SubscribeApprovalModalContent.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' import { - ApprovalTableListItem, - PublishApprovalInfoType, - PublishApprovalModalHandle, - SubscribeApprovalInfoType, -} from "@common/const/approval/type.tsx"; -import {BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; + PUBLISH_APPROVAL_TABLE_COLUMN, + SUBSCRIBE_APPROVAL_TABLE_COLUMN, + TODO_LIST_COLUMN_NOT_INCLUDE_KEY +} from '@common/const/approval/const.tsx' import { - SubscribeApprovalModalContent, - SubscribeApprovalModalHandle -} from "@common/components/aoplatform/SubscribeApprovalModalContent.tsx"; -import { - PublishApprovalModalContent, - -} from "@common/components/aoplatform/PublishApprovalModalContent.tsx"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { SimpleMemberItem } from "@common/const/type.ts"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; + ApprovalTableListItem, + PublishApprovalInfoType, + PublishApprovalModalHandle, + SubscribeApprovalInfoType +} from '@common/const/approval/type.tsx' +import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { SimpleMemberItem } from '@common/const/type.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { App, Button } from 'antd' +import { useEffect, useMemo, useRef, useState } from 'react' -export default function ApprovalList({pageType,pageStatus}:{pageType:'subscribe'|'release',pageStatus:0|1}){ - const { modal,message } = App.useApp() - const [searchWord, setSearchWord] = useState('') - // const [confirmLoading, setConfirmLoading] = useState(false); - const pageListRef = useRef(null); - const [init, setInit] = useState(true) - const {fetchData} = useFetch() - const [tableHttpReload, setTableHttpReload] = useState(true); - const [tableListDataSource, setTableListDataSource] = useState([]); - const subscribeRef = useRef(null) - const publishRef = useRef(null) - const [approvalBtnLoading,setApprovalBtnLoading] = useState(false) - const [memberValueEnum, setMemberValueEnum] = useState<{[k:string]:{text:string}}>({}) - const {state} = useGlobalContext() +export default function ApprovalList({ + pageType, + pageStatus +}: { + pageType: 'subscribe' | 'release' + pageStatus: 0 | 1 +}) { + const { modal, message } = App.useApp() + const [searchWord, setSearchWord] = useState('') + // const [confirmLoading, setConfirmLoading] = useState(false); + const pageListRef = useRef(null) + const [init, setInit] = useState(true) + const { fetchData } = useFetch() + const [tableHttpReload, setTableHttpReload] = useState(true) + const [tableListDataSource, setTableListDataSource] = useState([]) + const subscribeRef = useRef(null) + const publishRef = useRef(null) + const [approvalBtnLoading, setApprovalBtnLoading] = useState(false) + const [memberValueEnum, setMemberValueEnum] = useState<{ [k: string]: { text: string } }>({}) + const { state } = useGlobalContext() - const getApprovalList = ()=>{ - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - return fetchData>( `approval/${pageType}s`,{method:'GET',eoParams:{keyword:searchWord,status:pageStatus},eoTransformKeys:['apply_time','apply_project','approval_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTableListDataSource(data.approvals) - !init && message.success(msg || $t(RESPONSE_TIPS.success)) - setInit((prev)=>prev ? false : prev) - return {data:data.approvals, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) + const getApprovalList = () => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) } - - useEffect(()=>{ - getMemberList() - },[]) - - useEffect(() => { - getApprovalList(); - }, [pageType,pageStatus]); - - const openModal = async(type:'approval'|'view',entity:ApprovalTableListItem)=>{ - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>(`approval/${pageType}`,{method:'GET',eoParams:{id:entity!.id},eoTransformKeys:['apply_project','apply_team','apply_time','approval_time']}) - message.destroy() - if(code === STATUS_CODE.SUCCESS){ - const modalInst = modal.confirm({ - title:type === 'approval' ? $t('审核') : $t('查看'), - content:pageType === 'subscribe' ? - - :, - onOk:()=>{ - if(type === 'approval'){ - return (pageType === 'subscribe'? subscribeRef.current?.save('pass') : publishRef.current?.save('pass'))?.then((res)=> { - res === true && manualReloadTable - }) - } - }, - width:600, - okText:type === 'approval' ? $t('通过') :$t('确认'), - cancelText:$t('取消'), - closable:true, - onCancel:()=>{setApprovalBtnLoading(false)}, - icon:<>, - footer:(_, { OkBtn, CancelBtn }) =>{ - return ( - <> - {type === 'approval' ? <> - - - - : - <> - - - } - - ) - }, - }) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return + return fetchData>(`approval/${pageType}s`, { + method: 'GET', + eoParams: { keyword: searchWord, status: pageStatus }, + eoTransformKeys: ['apply_time', 'apply_project', 'approval_time'] + }) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.approvals) + !init && message.success(msg || $t(RESPONSE_TIPS.success)) + setInit(prev => (prev ? false : prev)) + return { data: data.approvals, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } - } + }) + .catch(() => { + return { data: [], success: false } + }) + } - - const getMemberList = async ()=>{ - setMemberValueEnum({}) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - data.members?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - setMemberValueEnum(tmpValueEnum) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - } + useEffect(() => { + getMemberList() + }, []) - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:1, - fixed:'right', - valueType: 'option', - render: (_: React.ReactNode, entity: ApprovalTableListItem) => [ - pageStatus === 0 ? - {openModal('approval',entity)}} btnTitle="审核"/> - :{openModal('view',entity)}} btnTitle="查看"/>, - ] - } - ] + useEffect(() => { + getApprovalList() + }, [pageType, pageStatus]) - const columns = useMemo(()=>{ - const newCol = [...(pageType === 'subscribe'? SUBSCRIBE_APPROVAL_TABLE_COLUMN:PUBLISH_APPROVAL_TABLE_COLUMN)] - const res = pageStatus === 0 ? newCol.filter((x)=>TODO_LIST_COLUMN_NOT_INCLUDE_KEY.indexOf(x.dataIndex as string) === -1): newCol - return res.map(x=>({...x, title:typeof x.title === 'string' ? $t(x.title as string) : x.title})) - },[pageType,pageStatus, state.language]) - - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - return ( -
- getApprovalList(sorter as { [k: string]: string; } | undefined)} - dataSource={tableListDataSource} - columns = {[...columns,...operation]} - searchPlaceholder="输入申请人、服务、团队查找" - onSearchWordChange={(e) => { - setSearchWord(e.target.value) - }} - onChange={() => { - setTableHttpReload(false) - }} - onRowClick={(row:ApprovalTableListItem)=>openModal(pageStatus === 0 ? 'approval': 'view',row)} + const openModal = async (type: 'approval' | 'view', entity: ApprovalTableListItem) => { + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData< + BasicResponse<{ approval: PublishApprovalInfoType | SubscribeApprovalInfoType }> + >(`approval/${pageType}`, { + method: 'GET', + eoParams: { id: entity!.id }, + eoTransformKeys: ['apply_project', 'apply_team', 'apply_time', 'approval_time'] + }) + message.destroy() + if (code === STATUS_CODE.SUCCESS) { + const modalInst = modal.confirm({ + title: type === 'approval' ? $t('审核') : $t('查看'), + content: + pageType === 'subscribe' ? ( + -
+ ) : ( + + ), + onOk: () => { + if (type === 'approval') { + return ( + pageType === 'subscribe' + ? subscribeRef.current?.save('pass') + : publishRef.current?.save('pass') + )?.then(res => { + res === true && manualReloadTable + }) + } + }, + width: 600, + okText: type === 'approval' ? $t('通过') : $t('确认'), + cancelText: $t('取消'), + closable: true, + onCancel: () => { + setApprovalBtnLoading(false) + }, + icon: <>, + footer: (_, { OkBtn, CancelBtn }) => { + return ( + <> + {type === 'approval' ? ( + <> + + + + + + + + + ) : ( + <> + + + + + + )} + + ) + } + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return + } + } + + const getMemberList = async () => { + setMemberValueEnum({}) + const { code, data, msg } = await fetchData>( + 'simple/member', + { method: 'GET' } ) -} \ No newline at end of file + if (code === STATUS_CODE.SUCCESS) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + data.members?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + setMemberValueEnum(tmpValueEnum) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 1, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: ApprovalTableListItem) => [ + pageStatus === 0 ? ( + { + openModal('approval', entity) + }} + btnTitle="审核" + /> + ) : ( + { + openModal('view', entity) + }} + btnTitle="查看" + /> + ) + ] + } + ] + + const columns = useMemo(() => { + const newCol = [ + ...(pageType === 'subscribe' + ? SUBSCRIBE_APPROVAL_TABLE_COLUMN + : PUBLISH_APPROVAL_TABLE_COLUMN) + ] + const res = + pageStatus === 0 + ? newCol.filter(x => TODO_LIST_COLUMN_NOT_INCLUDE_KEY.indexOf(x.dataIndex as string) === -1) + : newCol + return res.map(x => ({ + ...x, + title: typeof x.title === 'string' ? $t(x.title as string) : x.title + })) + }, [pageType, pageStatus, state.language]) + + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } + + return ( +
+ getApprovalList(sorter as { [k: string]: string } | undefined)} + dataSource={tableListDataSource} + columns={[...columns, ...operation]} + searchPlaceholder="输入申请人、服务、团队查找" + onSearchWordChange={e => { + setSearchWord(e.target.value) + }} + onChange={() => { + setTableHttpReload(false) + }} + onRowClick={(row: ApprovalTableListItem) => + openModal(pageStatus === 0 ? 'approval' : 'view', row) + } + /> +
+ ) +} diff --git a/frontend/packages/core/src/pages/approval/ApprovalPage.tsx b/frontend/packages/core/src/pages/approval/ApprovalPage.tsx index 61f2ebf4..756e45ad 100644 --- a/frontend/packages/core/src/pages/approval/ApprovalPage.tsx +++ b/frontend/packages/core/src/pages/approval/ApprovalPage.tsx @@ -1,74 +1,89 @@ - -import { Menu, MenuProps, Tabs, TabsProps} from "antd"; -import {Link, useLocation, useNavigate} from "react-router-dom"; -import {useEffect, useMemo, useState} from "react"; -import ApprovalList from "./ApprovalList.tsx"; -import { getItem } from "@common/utils/navigation.tsx"; -import { $t } from "@common/locales/index.ts"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; - - +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { $t } from '@common/locales/index.ts' +import { getItem } from '@common/utils/navigation.tsx' +import { Menu, MenuProps, Tabs, TabsProps } from 'antd' +import { useEffect, useMemo, useState } from 'react' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import ApprovalList from './ApprovalList.tsx' const items: TabsProps['items'] = [ - { - key: '0', - label: $t('待审核'), - }, - { - key: '1', - label: $t('已审核'), - } -]; + { + key: '0', + label: $t('待审核') + }, + { + key: '1', + label: $t('已审核') + } +] -export default function ApprovalPage(){ - const navigateTo = useNavigate() - const location = useLocation() - const currentUrl = location.pathname - const query =new URLSearchParams(useLocation().search) - const [pageType,setPageType] = useState<'subscribe'|'release'>((query?.get('type') ||'subscribe') as 'subscribe'|'release') - const [pageStatus,setPageStatus] = useState<0|1>(Number(query.get('status') ||0) as 0|1) - const {state} = useGlobalContext() +export default function ApprovalPage() { + const navigateTo = useNavigate() + const location = useLocation() + const currentUrl = location.pathname + const query = new URLSearchParams(useLocation().search) + const [pageType, setPageType] = useState<'subscribe' | 'release'>( + (query?.get('type') || 'subscribe') as 'subscribe' | 'release' + ) + const [pageStatus, setPageStatus] = useState<0 | 1>(Number(query.get('status') || 0) as 0 | 1) + const { state } = useGlobalContext() - const menuItems = useMemo(()=>[ - getItem($t('管理'), 'mng', null, - [ - getItem({$t('订阅申请')}, 'subscribe'), - getItem({$t('发布申请')}, 'release')], - 'group'), - ],[state.language]) + const menuItems = useMemo( + () => [ + getItem( + $t('管理'), + 'mng', + null, + [ + getItem({$t('订阅申请')}, 'subscribe'), + getItem({$t('发布申请')}, 'release') + ], + 'group' + ) + ], + [state.language] + ) - const onMenuClick: MenuProps['onClick'] = (e) => { - setPageType(e.key as 'subscribe'|'release') - navigateTo(`${currentUrl}?type=${e.key}&status=${pageStatus}`); - }; + const onMenuClick: MenuProps['onClick'] = e => { + setPageType(e.key as 'subscribe' | 'release') + navigateTo(`${currentUrl}?type=${e.key}&status=${pageStatus}`) + } - const onChange = (key:string ) => { - setPageStatus(Number(key) as 0|1) - navigateTo(`${currentUrl}?type=${pageType}&status=${key}`); - }; + const onChange = (key: string) => { + setPageStatus(Number(key) as 0 | 1) + navigateTo(`${currentUrl}?type=${pageType}&status=${key}`) + } + useEffect(() => { + setPageType((query?.get('type') || 'subscribe') as 'subscribe' | 'release') + setPageStatus(Number(query.get('status') || 0) as 0 | 1) + }, [currentUrl]) - useEffect(() => { - setPageType((query?.get('type') ||'subscribe') as 'subscribe'|'release') - setPageStatus(Number(query.get('status') ||0) as 0|1) - }, [currentUrl]); - - return ( - <> -
- -
- - -
-
- - ) -} \ No newline at end of file + return ( + <> +
+ +
+ + +
+
+ + ) +} diff --git a/frontend/packages/core/src/pages/common/ApiRequestSetting.tsx b/frontend/packages/core/src/pages/common/ApiRequestSetting.tsx index 02c688c8..3ecce6c9 100644 --- a/frontend/packages/core/src/pages/common/ApiRequestSetting.tsx +++ b/frontend/packages/core/src/pages/common/ApiRequestSetting.tsx @@ -1,99 +1,108 @@ -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { BasicResponse, STATUS_CODE, RESPONSE_TIPS, PLACEHOLDER } from "@common/const/const"; -import { useFetch } from "@common/hooks/http"; -import { $t } from "@common/locales"; -import { App, Form, Input, Row, Button } from "antd"; -import { useEffect } from "react"; +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Button, Form, Input, Row } from 'antd' +import { useEffect } from 'react' type ApiRequestSettingFieldType = { - siteName:string - siteLogo:string - invokeAddress:string - platformName:string - sitePrefix:string + siteName: string + siteLogo: string + invokeAddress: string + platformName: string + sitePrefix: string } -export default function ApiRequestSetting(){ - const { message } = App.useApp() - const [form] = Form.useForm(); - const {fetchData} = useFetch() - - - const onFinish = () => { - form.validateFields().then((value)=>{ - return fetchData>('system/general',{method:'POST',eoBody:(value),eoTransformKeys:['invokeAddress','siteName','siteLogo','platformName','sitePrefix']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - getSystemSetting() - return Promise.resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=>{ - return Promise.reject(errorInfo) - }) +export default function ApiRequestSetting() { + const { message } = App.useApp() + const [form] = Form.useForm() + const { fetchData } = useFetch() + + const onFinish = () => { + form.validateFields().then(value => { + return fetchData>('system/general', { + method: 'POST', + eoBody: value, + eoTransformKeys: ['invokeAddress', 'siteName', 'siteLogo', 'platformName', 'sitePrefix'] + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + getSystemSetting() + return Promise.resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } }) - }; - - const getSystemSetting = ()=>{ - fetchData>('system/general',{method:'GET',eoTransformKeys:['site_name', 'site_logo','invoke_address','platform_name','site_prefix']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - form.setFieldsValue(data.general) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } + .catch(errorInfo => { + return Promise.reject(errorInfo) }) - } - - useEffect(() => { - getSystemSetting() - return (form.setFieldsValue({})) - }, []); + }) + } - return ( - <> - -
- - label={$t("API 调用地址")} - name="invokeAddress" - rules={[{ required: true,whitespace:true }]} - extra={$t("API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。")} - > - - + const getSystemSetting = () => { + fetchData>('system/general', { + method: 'GET', + eoTransformKeys: ['site_name', 'site_logo', 'invoke_address', 'platform_name', 'site_prefix'] + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + form.setFieldsValue(data.general) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - - label={$t("集成地址")} - name="sitePrefix" - rules={[{ whitespace:true }]} - extra={$t("与外部平台集成时,获取 API 市场中文档信息的域名")} - > - - + useEffect(() => { + getSystemSetting() + return form.setFieldsValue({}) + }, []) - - - - - - -
- - ) -} \ No newline at end of file + return ( + <> + +
+ + label={$t('API 调用地址')} + name="invokeAddress" + rules={[{ required: true, whitespace: true }]} + extra={$t( + 'API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。' + )} + > + + + + + label={$t('集成地址')} + name="sitePrefix" + rules={[{ whitespace: true }]} + extra={$t('与外部平台集成时,获取 API 市场中文档信息的域名')} + > + + + + + + + + + +
+ + ) +} diff --git a/frontend/packages/core/src/pages/common/ServiceCategory.tsx b/frontend/packages/core/src/pages/common/ServiceCategory.tsx index 0cf46083..7ee2abcc 100644 --- a/frontend/packages/core/src/pages/common/ServiceCategory.tsx +++ b/frontend/packages/core/src/pages/common/ServiceCategory.tsx @@ -1,265 +1,336 @@ -import TreeWithMore from "@common/components/aoplatform/TreeWithMore"; -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { PERMISSION_DEFINITION } from "@common/const/permissions"; -import { useFetch } from "@common/hooks/http"; -import { checkAccess } from "@common/utils/permission"; -import { CategorizesType, ServiceHubCategoryConfigHandle } from "@market/const/serviceHub/type"; -import { App, Button, Spin, Tree, TreeDataNode, TreeProps } from "antd"; -import { DataNode } from "antd/es/tree"; -import { Key, useEffect, useMemo, useRef, useState } from "react"; -import { ServiceHubCategoryConfig } from "./ServiceHubCategoryConfig"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import { LoadingOutlined } from "@ant-design/icons"; -import { cloneDeep } from "lodash-es"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import { EntityItem } from "@common/const/type"; -import { $t } from "@common/locales"; +import { LoadingOutlined } from '@ant-design/icons' +import TreeWithMore from '@common/components/aoplatform/TreeWithMore' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { EntityItem } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { checkAccess } from '@common/utils/permission' +import { Icon } from '@iconify/react/dist/iconify.js' +import { CategorizesType, ServiceHubCategoryConfigHandle } from '@market/const/serviceHub/type' +import { App, Button, Spin, Tree, TreeDataNode, TreeProps } from 'antd' +import { DataNode } from 'antd/es/tree' +import { cloneDeep } from 'lodash-es' +import { Key, useEffect, useMemo, useRef, useState } from 'react' +import { ServiceHubCategoryConfig } from './ServiceHubCategoryConfig' -export default function ServiceCategory(){ - const [gData, setGData] = useState([]); - const [cateData, setCateData] = useState([]); - const [expandedKeys, setExpandedKeys] = useState([]); - const {message,modal} = App.useApp() - const {fetchData} = useFetch() - const addRef = useRef(null) - const addChildRef = useRef(null) - const renameRef = useRef(null) - const {accessData} = useGlobalContext() - const { setBreadcrumb } = useBreadcrumb() - const [loading, setLoading] = useState(false) - - const onDrop: TreeProps['onDrop'] = (info) => { - const dropKey = info.node.key; - const dragKey = info.dragNode.key; - const dropPos = info.node.pos.split('-'); - const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]); // the drop position relative to the drop node, inside 0, top -1, bottom 1 +export default function ServiceCategory() { + const [gData, setGData] = useState([]) + const [cateData, setCateData] = useState([]) + const [expandedKeys, setExpandedKeys] = useState([]) + const { message, modal } = App.useApp() + const { fetchData } = useFetch() + const addRef = useRef(null) + const addChildRef = useRef(null) + const renameRef = useRef(null) + const { accessData } = useGlobalContext() + const [loading, setLoading] = useState(false) - const loop = ( - data: TreeDataNode[], - key: React.Key, - callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void, - ) => { - for (let i = 0; i < data.length; i++) { - if (data[i].id === key) { - return callback(data[i], i, data); - } - if (data[i].children) { - loop(data[i].children!, key, callback); - } + const onDrop: TreeProps['onDrop'] = info => { + const dropKey = info.node.key + const dragKey = info.dragNode.key + const dropPos = info.node.pos.split('-') + const dropPosition = info.dropPosition - Number(dropPos[dropPos.length - 1]) // the drop position relative to the drop node, inside 0, top -1, bottom 1 + + const loop = ( + data: TreeDataNode[], + key: React.Key, + callback: (node: TreeDataNode, i: number, data: TreeDataNode[]) => void + ) => { + for (let i = 0; i < data.length; i++) { + if (data[i].id === key) { + return callback(data[i], i, data) } - }; - const data = cloneDeep(gData); - - // Find dragObject - let dragObj: TreeDataNode; - loop(data, dragKey, (item, index, arr) => { - arr.splice(index, 1); - dragObj = item; - }); - - if (!info.dropToGap) { - // Drop on the content - loop(data, dropKey, (item) => { - item.children = item.children || []; - // where to insert. New item was inserted to the start of the array in this example, but can be anywhere - item.children.unshift(dragObj); - }); - } else { - let ar: TreeDataNode[] = []; - let i: number; - loop(data, dropKey, (_item, index, arr) => { - ar = arr; - i = index; - }); - if (dropPosition === -1) { - // Drop on the top of the drop node - ar.splice(i!, 0, dragObj!); - } else { - // Drop on the bottom of the drop node - ar.splice(i! + 1, 0, dragObj!); + if (data[i].children) { + loop(data[i].children!, key, callback) } } + } + const data = cloneDeep(gData) - setGData(data); - sortCategories(data) - }; + // Find dragObject + let dragObj: TreeDataNode + loop(data, dragKey, (item, index, arr) => { + arr.splice(index, 1) + dragObj = item + }) - - const dropdownMenu = (entity:CategorizesType) => [ - { - key: 'addChildCate', - label: ( - + if (!info.dropToGap) { + // Drop on the content + loop(data, dropKey, item => { + item.children = item.children || [] + // where to insert. New item was inserted to the start of the array in this example, but can be anywhere + item.children.unshift(dragObj) + }) + } else { + let ar: TreeDataNode[] = [] + let i: number + loop(data, dropKey, (_item, index, arr) => { + ar = arr + i = index + }) + if (dropPosition === -1) { + // Drop on the top of the drop node + ar.splice(i!, 0, dragObj!) + } else { + // Drop on the bottom of the drop node + ar.splice(i! + 1, 0, dragObj!) + } + } + + setGData(data) + sortCategories(data) + } + + const dropdownMenu = (entity: CategorizesType) => [ + { + key: 'addChildCate', + label: ( + + + + ) + }, + { + key: 'renameCate', + label: ( + + + + ) + }, + { + key: 'delete', + label: ( + + + + ) + } + ] + + const treeData = useMemo(() => { + setExpandedKeys([]) + const loop = (data: CategorizesType[]): DataNode[] => + data?.map(item => { + if (item.children) { + setExpandedKeys(prev => [...prev, item.id]) + return { + title: ( + + {item.name} + ), - }, - { - key: 'renameCate', - label: ( - - ), - }, - { - key: 'delete', - label: ( - - ), - }, - ]; - - const treeData = useMemo(() => { - setExpandedKeys([]) - const loop = (data: CategorizesType[]): DataNode[] => - data?.map((item) => { - if (item.children) { - setExpandedKeys(prev=>[...prev,item.id]) - return { - title: {item.name} , - key: item.id, children: loop(item.children) - }; - } - - return { - title: {item.name}, - key: item.id, - }; - }); - return loop(gData ?? []) - }, [gData]); - - const isActionAllowed = (type:'addCate'|'addChildCate'|'renameCate'|'delete') => { - const actionToPermissionMap = { - 'addCate': 'add', - 'addChildCate': 'add', - 'renameCate': 'edit', - 'delete': 'delete' - }; - - const action = actionToPermissionMap[type]; - const permission :keyof typeof PERMISSION_DEFINITION[0]= `system.api_market.service_classification.${action}`; - - return !checkAccess(permission, accessData); - }; - - const openModal = (type:'addCate'|'addChildCate'|'renameCate'|'delete',entity?:CategorizesType)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'addCate': - title=$t('添加分类') - content= - break; - case 'addChildCate': - title=$t('添加子分类') - content= - break; - case 'renameCate': - title=$t('重命名分类') - content= - break; - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; + key: item.id, + children: loop(item.children) + } } - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ - case 'addCate': - return addRef.current?.save().then((res)=>{if(res === true) getCategoryList()}) - case 'addChildCate': - return addChildRef.current?.save().then((res)=>{if(res === true) getCategoryList()}) - case 'renameCate': - return renameRef.current?.save().then((res)=>{if(res === true) getCategoryList()}) - case 'delete': - return deleteCate(entity!).then((res)=>{if(res === true) getCategoryList()}) - } - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - disabled : isActionAllowed(type) - }, - cancelText:$t('取消'), - closable:true, - icon:<>, + + return { + title: ( + + {item.name} + + ), + key: item.id + } + }) + return loop(gData ?? []) + }, [gData]) + + const isActionAllowed = (type: 'addCate' | 'addChildCate' | 'renameCate' | 'delete') => { + const actionToPermissionMap = { + addCate: 'add', + addChildCate: 'add', + renameCate: 'edit', + delete: 'delete' + } + + const action = actionToPermissionMap[type] + const permission: keyof (typeof PERMISSION_DEFINITION)[0] = `system.api_market.service_classification.${action}` + + return !checkAccess(permission, accessData) + } + + const openModal = ( + type: 'addCate' | 'addChildCate' | 'renameCate' | 'delete', + entity?: CategorizesType + ) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'addCate': + title = $t('添加分类') + content = ( + + ) + break + case 'addChildCate': + title = $t('添加子分类') + content = ( + + ) + break + case 'renameCate': + title = $t('重命名分类') + content = ( + + ) + break + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break + } + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'addCate': + return addRef.current?.save().then(res => { + if (res === true) getCategoryList() + }) + case 'addChildCate': + return addChildRef.current?.save().then(res => { + if (res === true) getCategoryList() + }) + case 'renameCate': + return renameRef.current?.save().then(res => { + if (res === true) getCategoryList() + }) + case 'delete': + return deleteCate(entity!).then(res => { + if (res === true) getCategoryList() + }) + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: isActionAllowed(type) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + const deleteCate = (entity: CategorizesType) => { + return new Promise((resolve, reject) => { + fetchData>('catalogue', { + method: 'DELETE', + eoParams: { catalogue: entity.id } + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) - } + .catch(errorInfo => reject(errorInfo)) + }) + } - const deleteCate = (entity:CategorizesType)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('catalogue',{method:'DELETE',eoParams:{catalogue:entity.id},}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } + const sortCategories = (newData: CategorizesType[]) => { + setLoading(true) + fetchData>('catalogue/sort', { method: 'PUT', eoBody: newData }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + getCategoryList() + } else { + setGData(cateData) + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(() => { + setGData(cateData) + }) + .finally(() => { + setLoading(false) + }) + } - const sortCategories = (newData:CategorizesType[])=>{ - setLoading(true) - fetchData>('catalogue/sort',{method:'PUT',eoBody:newData}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - getCategoryList() - }else{ - setGData(cateData) - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(()=>{setGData(cateData)}).finally(()=>{setLoading(false)}) - } + const getCategoryList = () => { + setLoading(true) + fetchData>('catalogues', { + method: 'GET' + }) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setGData(data.catalogues) + setCateData(data.catalogues) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setLoading(false) + }) + } - const getCategoryList = ()=>{ - setLoading(true) - fetchData>('catalogues',{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setGData(data.catalogues) - setCateData(data.catalogues) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{setLoading(false)}) - } + useEffect(() => { + getCategoryList() + }, []) - useEffect(()=>{ - getCategoryList() - },[]) - - return ( -
- } spinning={loading} className=''> - {setExpandedKeys(expandedKeys as string[])}} - onDrop={onDrop} - treeData={treeData} - /> - - - - -
- ) -} \ No newline at end of file + return ( +
+ } + spinning={loading} + className="" + > + { + setExpandedKeys(expandedKeys as string[]) + }} + onDrop={onDrop} + treeData={treeData} + /> + + + + +
+ ) +} diff --git a/frontend/packages/core/src/pages/common/ServiceHubCategoryConfig.tsx b/frontend/packages/core/src/pages/common/ServiceHubCategoryConfig.tsx index 7add0f3c..e44adaeb 100644 --- a/frontend/packages/core/src/pages/common/ServiceHubCategoryConfig.tsx +++ b/frontend/packages/core/src/pages/common/ServiceHubCategoryConfig.tsx @@ -1,122 +1,141 @@ -import {App, Form, Input} from "antd"; -import {forwardRef, useEffect, useImperativeHandle} from "react"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { ServiceHubCategoryConfigHandle, ServiceHubCategoryConfigFieldType, ServiceHubCategoryConfigProps } from "@market/const/serviceHub/type.ts" -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { $t } from "@common/locales"; +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { + ServiceHubCategoryConfigFieldType, + ServiceHubCategoryConfigHandle, + ServiceHubCategoryConfigProps +} from '@market/const/serviceHub/type.ts' +import { App, Form, Input } from 'antd' +import { forwardRef, useEffect, useImperativeHandle } from 'react' -export const ServiceHubCategoryConfig = forwardRef((props,ref)=>{ - const { message } = App.useApp() - const [form] = Form.useForm(); - const {type,entity} = props - const {fetchData} = useFetch() +export const ServiceHubCategoryConfig = forwardRef< + ServiceHubCategoryConfigHandle, + ServiceHubCategoryConfigProps +>((props, ref) => { + const { message } = App.useApp() + const [form] = Form.useForm() + const { type, entity } = props + const { fetchData } = useFetch() - const save:()=>Promise = ()=>{ - const url:string = 'catalogue' - let method:string - switch (type){ - case 'addCate': - case 'addChildCate': - method = 'POST' - break; - case 'renameCate': - method = 'PUT' - break - } - return new Promise((resolve, reject)=>{ - if(!url || !method){ - reject($t(RESPONSE_TIPS.error)) - return - } - form.validateFields().then((value)=>{ - fetchData>(url,{method,eoBody:(value), eoParams:{ ...(type === 'renameCate' ? {catalogue:value.id} :undefined)}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) + const save: () => Promise = () => { + const url: string = 'catalogue' + let method: string + switch (type) { + case 'addCate': + case 'addChildCate': + method = 'POST' + break + case 'renameCate': + method = 'PUT' + break } - - useImperativeHandle(ref, ()=>({ - save + return new Promise((resolve, reject) => { + if (!url || !method) { + reject($t(RESPONSE_TIPS.error)) + return + } + form + .validateFields() + .then(value => { + fetchData>(url, { + method, + eoBody: value, + eoParams: { ...(type === 'renameCate' ? { catalogue: value.id } : undefined) } + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(errorInfo => reject(errorInfo)) }) - ) + .catch(errorInfo => reject(errorInfo)) + }) + } - useEffect(() => { + useImperativeHandle(ref, () => ({ + save + })) - switch(type){ - case 'addCate': - form.setFieldsValue({}) - break - case 'addChildCate': - form.setFieldsValue({parent:entity!.id}) - break - case 'renameCate': - form.setFieldsValue(entity) - break - } + useEffect(() => { + switch (type) { + case 'addCate': + form.setFieldsValue({}) + break + case 'addChildCate': + form.setFieldsValue({ parent: entity!.id }) + break + case 'renameCate': + form.setFieldsValue(entity) + break + } + }, []) - }, []); + return ( + +
+ {type === 'renameCate' && ( + + label={$t('ID')} + name="id" + hidden + rules={[{ required: true, whitespace: true }]} + > + + + )} + {(type === 'addCate' || type === 'renameCate') && ( + + label={$t('分类名称')} + name="name" + rules={[{ required: true, whitespace: true }]} + > + + + )} + {type === 'addChildCate' && ( + <> + + label={$t('父分类 ID')} + name="parent" + hidden + rules={[{ required: true, whitespace: true }]} + > + + - return ( - - - - {type === 'renameCate' && - - label={$t("ID")} - name="id" - hidden - rules={[{ required: true,whitespace:true }]} - > - - - } - {(type === 'addCate' || type === 'renameCate') && - - label={$t("分类名称")} - name="name" - rules={[{ required: true ,whitespace:true }]} - > - - } - - {type === 'addChildCate' &&<> - - label={$t("父分类 ID")} - name="parent" - hidden - rules={[{ required: true,whitespace:true }]} - > - - - - - label={$t("子分类名称")} - name="name" - rules={[{ required: true ,whitespace:true }]} - > - - - - } - - -) -}) \ No newline at end of file + + label={$t('子分类名称')} + name="name" + rules={[{ required: true, whitespace: true }]} + > + + + + )} + +
+ ) +}) diff --git a/frontend/packages/core/src/pages/keySettings/components/ApiKeyContent.tsx b/frontend/packages/core/src/pages/keySettings/components/ApiKeyContent.tsx new file mode 100644 index 00000000..a303ce36 --- /dev/null +++ b/frontend/packages/core/src/pages/keySettings/components/ApiKeyContent.tsx @@ -0,0 +1,111 @@ +import { Codebox } from '@common/components/postcat/api/Codebox' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { AIProvider } from '@core/components/AIProviderSelect' +import { App, DatePicker, Form, Input, Switch } from 'antd' +import dayjs from 'dayjs' +import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { EditAPIKey } from '../types' + +interface ApiKeyContentProps { + provider?: AIProvider + entity: EditAPIKey +} + +const ApiKeyContent: React.FC = forwardRef(({ provider, entity }, ref) => { + const [form] = Form.useForm() + const [neverExpire, setNeverExpire] = useState(true) + const { fetchData } = useFetch() + const { message } = App.useApp() + + useEffect(() => { + try { + const isNeverExpire = entity.expire_time === 0 + setNeverExpire(isNeverExpire) + form.setFieldsValue({ + name: entity.name, + expire_time: isNeverExpire ? undefined : dayjs(entity.expire_time * 1000), + config: entity.config + }) + } catch (e) { + form.setFieldsValue({ + name: entity.name, + expire_time: undefined, + config: '' + }) + } + }, []) + + const handleOk = async () => { + try { + const values = await form.validateFields() + const { expire_time, ...restValues } = values + const expireTime = neverExpire ? 0 : Math.trunc(expire_time.valueOf() / 1000) + + const response = await fetchData>('ai/resource/key', { + method: entity.id ? 'PUT' : 'POST', + eoParams: { provider: provider?.id, id: entity.id }, + eoBody: { ...restValues, expire_time: expireTime }, + eoTransformKeys: ['config'] + }) + + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + return true + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return false + } + } catch (error) { + console.error('Validation failed:', error) + } + } + useImperativeHandle(ref, () => ({ + handleOk + })) + const handleNeverExpireChange = (checked: boolean) => { + setNeverExpire(checked) + if (!checked) { + form.setFieldsValue({ + expire_time: dayjs().add(7, 'days') + }) + } + } + + return ( +
+ + + + + + + +
+ + {neverExpire ? $t('永不过期') : $t('设置过期时间')} +
+
+ {!neverExpire && ( + + + + )} + + ) +}) + +export default ApiKeyContent diff --git a/frontend/packages/core/src/pages/keySettings/components/StatusFilter.tsx b/frontend/packages/core/src/pages/keySettings/components/StatusFilter.tsx new file mode 100644 index 00000000..3663e12a --- /dev/null +++ b/frontend/packages/core/src/pages/keySettings/components/StatusFilter.tsx @@ -0,0 +1,40 @@ +import { $t } from '@common/locales' +import { Select, Space, theme } from 'antd' +import React from 'react' + +interface StatusFilterProps { + value: string[] + onChange: (value: string[]) => void +} + +const StatusFilter: React.FC = ({ value, onChange }) => { + const { token } = theme.useToken() + + const options = [ + { label: $t('正常'), value: 'normal', color: token.colorSuccess }, + { label: $t('超额'), value: 'exceeded', color: token.colorError }, + { label: $t('过期'), value: 'expired', color: token.colorWarning }, + { label: $t('停用'), value: 'disabled', color: token.colorTextDisabled }, + { label: $t('错误'), value: 'error', color: token.colorError } + ] + + return ( + + {$t('状态')}: + x.id)} - options={roleSelectableList?.map((x:{id:string,name:string})=>({label:(x.name), value:x.id}))} - onChange={(value)=>{ - changeMemberInfo(value,entity ).then((res)=>{ - if(res) manualReloadTable() - }) - }} - /> - - ), - filters : roleSelectableList?.map((x:{id:string,name:string})=>({text:$t(x.name), value:x.id})), - onFilter : (value: unknown, record:MemberTableListItem) =>{ - return record.roles ? record.roles?.map((x)=>x.id).indexOf(value as string) !== -1 : false;} - }:{}), ...(typeof x.title === 'string' ? { title:$t(x.title as string) } : {}), - ...(x.dataIndex === 'enable' ? { - valueEnum:new Map([ - [true,{$t('启用')}], - [false,{$t('禁用')}], - ])} : {})})) - , [ state.language,roleSelectableList]) - - return ( - <> - getMemberList()} - addNewBtnTitle={(!memberGroupId ||['unknown','disable'].indexOf(memberGroupId?.toString()) === -1)?$t("添加账号") : ""} - searchPlaceholder={$t("输入用户名、邮箱查找成员")} - onAddNewBtnClick={() => { - openModal('addMember') - }} - addNewBtnAccess="system.organization.member.add" - rowSelection={{ - // selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT], - selectedRowKeys, - columnWidth: 40, - onChange:handleSelectChange, - getCheckboxProps: (record: MemberTableListItem) => ({ - disabled: record.id === 'admin', // Column configuration not to be checked - name: record.name, - }), - }} - onRowClick={handleRowClick} - tableClickAccess="system.organization.member.edit" - afterNewBtn={[ - selectedRowKeys.length > 0 && memberGroupId &&, - selectedRowKeys.length > 0 && memberGroupId &&, - selectedRowKeys.length > 0 && memberGroupId !== 'disable' &&, - selectedRowKeys.length > 0 && , - selectedRowKeys.length > 0 &&, - ]} - onSearchWordChange={(e) => { - setSearchWord(e.target.value) - }} - onChange={() => { - setTableHttpReload(false) - }} + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 1, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: MemberTableListItem) => [ + { + openModal('editMember', entity) + }} + btnTitle="编辑" /> - ) + ] + } + ] + const getMemberList = () => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) + } + return fetchData>('user/accounts', { + method: 'GET', + eoParams: { + keyword: searchWord, + department: topGroupId === memberGroupId ? null : memberGroupId + }, + eoTransformKeys: ['user_group'] + }) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.members) + setInit(prev => (prev ? false : prev)) + return { data: data.members, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } + } + }) + .catch(() => { + return { data: [], success: false } + }) + } + + const handleSelectChange = (newSelectedRowKeys: React.Key[]) => { + setSelectedRowKeys(newSelectedRowKeys) + } + + const handleRowClick = (entity: MemberTableListItem) => { + if (entity.id === 'admin') return + setSelectedRowKeys(prevData => + prevData?.indexOf(entity.id) === -1 + ? [...prevData, entity.id] + : prevData.filter(x => x !== entity.id) + ) + } + + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } + + const handleMemberAction = (type: 'removeFromDep' | 'blocked' | 'activate' | 'delete') => { + let url: string + let method: string + let params: { [k: string]: unknown } = {} + let body: { [k: string]: unknown } = {} + switch (type) { + case 'removeFromDep': + url = 'user/department/member/remove' + method = 'POST' + params = { department: memberGroupId } + body = { userIds: selectedRowKeys } + break + case 'blocked': + url = 'user/account/disable' + method = 'POST' + body = { userIds: selectedRowKeys } + break + case 'activate': + url = 'user/account/enable' + method = 'POST' + body = { userIds: selectedRowKeys } + break + case 'delete': + url = 'user/account' + method = 'DELETE' + params = { ids: JSON.stringify(selectedRowKeys) } + body = { userIds: selectedRowKeys } + break + } + + return new Promise((resolve, reject) => { + fetchData>(url, { + method, + eoTransformKeys: ['user_ids', 'userIds'], + eoParams: params, + eoBody: body + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(errorInfo => reject(errorInfo)) + }) + } + + const isActionAllowed = ( + type: + | 'addMember' + | 'editMember' + | 'removeFromDep' + | 'addToDep' + | 'blocked' + | 'activate' + | 'delete' + ) => { + const actionToPermissionMap = { + addMember: 'add', + editMember: 'edit', + removeFromDep: 'remove', + addToDep: 'add', + activate: 'block', + blocked: 'block', + delete: 'delete' + } + + const action = actionToPermissionMap[type] + const permission: keyof (typeof PERMISSION_DEFINITION)[0] = `system.organization.member.${action}` + + return !checkAccess(permission, accessData) + } + + const openModal = ( + type: 'addMember' | 'editMember' | 'addToDep' | 'delete', + entity?: MemberTableListItem + ) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'addMember': + title = $t('添加账号') + content = ( + + ) + break + case 'editMember': + title = $t('编辑成员信息') + content = ( + + ) + break + case 'addToDep': + title = $t('加入部门') + content = ( + + ) + break + case 'delete': + title = $t('删除') + content = {$t('确定删除成员?此操作无法恢复,确认操作?')} + break + } + + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'addMember': + return AddMemberRef.current?.save().then(res => { + if (res === true) { + refreshGroup && refreshGroup() + manualReloadTable() + } + }) + case 'editMember': + return EditMemberRef.current?.save().then(res => { + if (res === true) { + refreshGroup && refreshGroup() + manualReloadTable() + } + }) + case 'addToDep': + return AddToDepRef.current?.save().then(res => { + if (res === true) { + refreshGroup && refreshGroup() + manualReloadTable() + } + }) + case 'delete': + return handleMemberAction('delete').then(res => { + if (res === true) { + refreshGroup && refreshGroup() + manualReloadTable() + } + }) + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: isActionAllowed(type) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + useEffect(() => { + !init && manualReloadTable() + setSelectedRowKeys([]) + }, [memberGroupId]) + + useEffect(() => { + getRoleList() + setBreadcrumb([{ title: $t('成员与部门') }]) + getDepartmentList() + }, []) + + const getDepartmentList = async () => { + setDepartmentValueEnum([]) + const { code, data, msg } = await fetchData>( + 'simple/departments', + { method: 'GET' } + ) + if (code === STATUS_CODE.SUCCESS) { + const tmpValueEnum: ColumnFilterItem[] = [ + { + text: data.department.name, + value: data.department.id, + children: handleDepartmentListToFilter(data.department.children) + } + ] + setDepartmentValueEnum(tmpValueEnum) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + const changeMemberInfo = (value: string[], entity: MemberTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>(`account/role`, { + method: 'PUT', + eoBody: { roles: value, users: [entity.id] } + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(errorInfo => reject(errorInfo)) + }) + } + + const getRoleList = () => { + fetchData }>>('simple/roles', { + method: 'GET', + eoParams: { group: 'system' } + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setRoleSelectableList(data.roles) + + return + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const translatedCol = useMemo( + () => + MEMBER_TABLE_COLUMNS.map(x => ({ + ...x, + ...(x.dataIndex === 'roles' + ? { + render: (_, entity) => ( + + + + )} - {type === 'addChild' && - label={$t("父部门 ID")} - name="parent" - hidden - rules={[{ required: true,whitespace:true }]} - > - - } - - - label={[type === 'addChild' ? $t('子部门名称') : $t('部门名称')]} - name="name" - rules={[{ required: true,whitespace:true }]} - > - - - + + label={[type === 'addChild' ? $t('子部门名称') : $t('部门名称')]} + name="name" + rules={[{ required: true, whitespace: true }]} + > + + - ) -}) \ No newline at end of file + + ) + } +) diff --git a/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx b/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx index 53b2882e..6e243094 100644 --- a/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx +++ b/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx @@ -1,16 +1,20 @@ -import { FC, useEffect, useState } from "react"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx"; -import { App, Button, Card, Col, Row, Spin, Tag } from "antd"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx"; -import { useFetch } from "@common/hooks/http.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { LoadingOutlined } from "@ant-design/icons"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; -import { $t } from "@common/locales/index.ts"; -import DashboardSettingEdit, { DashboardPageShowStatus } from "./DashboardSettingEdit.tsx"; -import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDashboardConfigFieldType, PartitionDataLogConfigFieldType } from "@core/const/partitions/types.ts"; -import PageList from "@common/components/aoplatform/PageList.tsx"; -import DataLogSettingEdit from "./DataLogSettingEdit.tsx"; +import { LoadingOutlined } from '@ant-design/icons' +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import PageList from '@common/components/aoplatform/PageList.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { + PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, + PartitionDashboardConfigFieldType, + PartitionDataLogConfigFieldType +} from '@core/const/partitions/types.ts' +import { App, Button, Card, Col, Row, Spin, Tag } from 'antd' +import { FC, useEffect, useState } from 'react' +import DashboardSettingEdit, { DashboardPageShowStatus } from './DashboardSettingEdit.tsx' +import DataLogSettingEdit from './DataLogSettingEdit.tsx' const PartitionInsideDashboardSetting: FC = () => { const { setBreadcrumb } = useBreadcrumb() @@ -25,60 +29,83 @@ const PartitionInsideDashboardSetting: FC = () => { const getDashboardSettingInfo = () => { setLoading(true) - return fetchData>('monitor/config', { method: 'GET', eoTransformKeys: [] }).then(response => { - const { code, data, msg } = response - if (code === STATUS_CODE.SUCCESS) { - data?.info?.driver && setData(data.info) - setShowGraphStatus('view') - } else { - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(() => { - return { data: [], success: false } - }).finally(() => { - setLoading(false) + return fetchData>('monitor/config', { + method: 'GET', + eoTransformKeys: [] }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + data?.info?.driver && setData(data.info) + setShowGraphStatus('view') + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(() => { + return { data: [], success: false } + }) + .finally(() => { + setLoading(false) + }) } const getDataLogSettingInfo = () => { setDataLogLoading(true) - return fetchData>('log/loki', { method: 'GET', eoTransformKeys: [] }).then(response => { - const { code, data, msg } = response - if (code === STATUS_CODE.SUCCESS) { - data?.info && setDataLogData({ - url: data.info?.config?.url || '', - headers: data.info?.config?.headers || [] - }) - setShowDataLogStatus('view') - } else { - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(() => { - return { data: [], success: false } - }).finally(() => { - setDataLogLoading(false) + return fetchData>('log/loki', { + method: 'GET', + eoTransformKeys: [] }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + data?.info && + setDataLogData({ + url: data.info?.config?.url || '', + headers: data.info?.config?.headers || [] + }) + setShowDataLogStatus('view') + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(() => { + return { data: [], success: false } + }) + .finally(() => { + setDataLogLoading(false) + }) } useEffect(() => { - setBreadcrumb([ - { title: $t('数据源') } - ]) + setBreadcrumb([{ title: $t('数据源') }]) getDashboardSettingInfo() getDataLogSettingInfo() - }, []); + }, []) const setDashboardSettingBtn = () => { - return (<> - {showGraphStatus === 'view' && - - } + return ( + <> + {showGraphStatus === 'view' && ( + + + + )} + ) } const setDataLogSettingBtn = () => { - return (<> - {showDataLogStatus === 'view' && - - } + return ( + <> + {showDataLogStatus === 'view' && ( + + + + )} + ) } @@ -86,42 +113,68 @@ const PartitionInsideDashboardSetting: FC = () => { <> -
- } spinning={loading}> -
+
+ } + spinning={loading} + > +
{$t('统计图表')} - {!loading && !data?.driver && {$t('未配置')} - }
} - - extra={setDashboardSettingBtn()}> + className="flex overflow-hidden flex-col justify-between w-full max-h-full" + title={ +
+ {$t('统计图表')} + {!loading && !data?.driver && {$t('未配置')}} +
+ } + extra={setDashboardSettingBtn()} + > {showGraphStatus === 'view' && data && data.driver && DashboardConfigPreview(data)} - {showGraphStatus !== 'view' && } + {showGraphStatus !== 'view' && ( + + )}
- } spinning={dataLogLoading}> -
+ } + spinning={dataLogLoading} + > +
{$t('数据日志')} - {!dataLogLoading && !dataLogData && {$t('未配置')} - }
} - - extra={setDataLogSettingBtn()}> + title={ +
+ {$t('数据日志')} + {!dataLogLoading && !dataLogData && {$t('未配置')}} +
+ } + extra={setDataLogSettingBtn()} + > {showDataLogStatus === 'view' && dataLogData && DataLogConfigPreview(dataLogData)} - {showDataLogStatus !== 'view' && } + {showDataLogStatus !== 'view' && ( + + )}
@@ -132,21 +185,32 @@ const PartitionInsideDashboardSetting: FC = () => { } export function DashboardConfigPreview(x: PartitionDashboardConfigFieldType) { - return
-
{$t('数据源')}:{x?.driver} - {$t('地址(IP:端口)')}:{x?.config?.addr} - {$t('组织(Organization)')}:{x?.config?.org} - + return ( +
+ +
{$t('数据源')}: + {x?.driver} + + + {$t('地址(IP:端口)')}: + {x?.config?.addr} + + + {$t('组织(Organization)')}: + {x?.config?.org} + + + ) } export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) { - const columns = PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS.map(x => { + const columns = PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS.map((x) => { return { ...x, - title: ({$t(x.title as string)}) + title: {$t(x.title as string)} } }) const getTableList = () => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { resolve({ data: x?.headers || [], success: true @@ -154,22 +218,30 @@ export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) { }) } - return
-
{$t('请求前缀')}:{x?.url} - {$t('HTTP 头部')}:-
- getTableList()} - showPagination={false} - noScroll={true} - /> -
- - + return ( +
+ +
{$t('请求前缀')}: + {x?.url} + + + {$t('HTTP 头部')}: + +
+ getTableList()} + showPagination={false} + noScroll={true} + /> +
+ + + + ) } -export default PartitionInsideDashboardSetting \ No newline at end of file +export default PartitionInsideDashboardSetting diff --git a/frontend/packages/core/src/pages/playground/index.tsx b/frontend/packages/core/src/pages/playground/index.tsx new file mode 100644 index 00000000..12f5ec43 --- /dev/null +++ b/frontend/packages/core/src/pages/playground/index.tsx @@ -0,0 +1,7 @@ +'use client' + +import AIFlowChart from '../aiSetting/AIFlowChart' + +export default function Playground() { + return +} diff --git a/frontend/packages/core/src/pages/policy/FilterForm.tsx b/frontend/packages/core/src/pages/policy/FilterForm.tsx index 0e8b16a8..1c3a1bb8 100644 --- a/frontend/packages/core/src/pages/policy/FilterForm.tsx +++ b/frontend/packages/core/src/pages/policy/FilterForm.tsx @@ -1,136 +1,186 @@ -import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'; -import { Form, Input, Select, Checkbox, Table, Spin, TableColumnsType, message } from 'antd'; -import { useFetch } from '@common/hooks/http'; -import { LoadingOutlined } from '@ant-design/icons'; -import { validateApiPath, validateIPorCIDR } from '@common/utils/validate'; -import { $t } from '@common/locales'; -import { FilterFormItemProps, RemoteTitleType, FilterFormHandle, FilterFormProps } from '@common/const/policy/type'; -import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'; +import { LoadingOutlined } from '@ant-design/icons' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { + FilterFormHandle, + FilterFormItemProps, + FilterFormProps, + RemoteTitleType +} from '@common/const/policy/type' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { validateApiPath, validateIPorCIDR } from '@common/utils/validate' +import { Checkbox, Form, Input, Select, Spin, Table, TableColumnsType, message } from 'antd' +import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react' import { v4 as uuidv4 } from 'uuid' -const RemoteFormItem: React.FC = (props) =>{ - const {value, onChange, disabled,option, onShowValueChange,serviceId, teamId} = props +const RemoteFormItem: React.FC = props => { + const { value, onChange, disabled, option, onShowValueChange, serviceId, teamId } = props const [remoteList, setRemoteList] = useState([]) const [remoteTableColumns, setRemoteTableColumns] = useState([]) const [loading, setLoading] = useState(false) const [rowKey, setRowKey] = useState('') - const title = useMemo(()=>option?.title,[option]) + const title = useMemo(() => option?.title, [option]) const [remoteCounts, setRemoteCounts] = useState(0) const [originRemoteList, setOriginRemoteList] = useState([]) - const {fetchData} = useFetch() + const { fetchData } = useFetch() - - const getRemoteDetail = (searchWord?:string)=>{ + const getRemoteDetail = (searchWord?: string) => { setLoading(true) - fetchData[], - titles:Array, - total:number - value:string - }>>(`strategy/${serviceId === undefined ? '' : 'service/'}filter-remote/${option?.name}`,{method:'GET', eoParams:{keyword:searchWord,...(serviceId ? {team:teamId, service:serviceId} : {})}}).then(response=>{ - const {code,data, msg} = response - if(code === STATUS_CODE.SUCCESS){ - setRemoteList(data.list as unknown[]) - setRowKey(data.key as string) - setRemoteTableColumns(data.titles.map((x:RemoteTitleType)=>({ - title: x.title,dataIndex:x.field,key:x.field,ellipsis:true - }))) - setRemoteCounts(data.total) - if(!searchWord){ - setOriginRemoteList(data.list) - if(value?.length === 1 && value[0] === 'ALL'){ - const totalDataArr = data.list?.map((x:Record)=>x[data.key as string]) - onChange?.(totalDataArr) - onShowValueChange?.(totalDataArr.join(',')) - } - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{ - setLoading(false) + fetchData< + BasicResponse<{ + key: string + list: Record[] + titles: Array + total: number + value: string + }> + >(`strategy/${serviceId === undefined ? '' : 'service/'}filter-remote/${option?.name}`, { + method: 'GET', + eoParams: { keyword: searchWord, ...(serviceId ? { team: teamId, service: serviceId } : {}) } }) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setRemoteList(data.list as unknown[]) + setRowKey(data.key as string) + setRemoteTableColumns( + data.titles.map((x: RemoteTitleType) => ({ + title: x.title, + dataIndex: x.field, + key: x.field, + ellipsis: true + })) + ) + setRemoteCounts(data.total) + if (!searchWord) { + setOriginRemoteList(data.list) + if (value?.length === 1 && value[0] === 'ALL') { + const totalDataArr = data.list?.map( + (x: Record) => x[data.key as string] + ) + onChange?.(totalDataArr) + onShowValueChange?.(totalDataArr.join(',')) + } + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setLoading(false) + }) } - useEffect(()=>{getRemoteDetail()},[option]) + useEffect(() => { + getRemoteDetail() + }, [option]) return ( -
- } spinning={loading}> -

- {$t('已选择(0)项(1)数据', [value?.length || 0, title])} -

-
-
- getRemoteDetail(value)} - disabled={disabled} - /> -
-
(disabled ? '' : 'clickable-row')} - rowKey={rowKey} - size='small' - rowSelection={{ - type: 'checkbox', - onChange: (selectedRowKeys: React.Key[]) => { - onChange?.(selectedRowKeys as string[]); - onShowValueChange?.(selectedRowKeys.length === remoteCounts? $t('所有(0)',[title]) : originRemoteList.filter(x=>selectedRowKeys?.indexOf(x[option.key]))?.map(x=>x.title).join(' , ')) - }, - selectedRowKeys: value, - // getCheckboxProps: (record: unknown) => ({ - // disabled: record.name === 'Disabled User', // Column configuration not to be checked - // name: record.name, - // }) +
+ } spinning={loading}> +

+ {$t('已选择(0)项(1)数据', [value?.length || 0, title])} +

+
+
+ getRemoteDetail(value)} + disabled={disabled} + /> +
+
(disabled ? '' : 'clickable-row')} + rowKey={rowKey} + size="small" + rowSelection={{ + type: 'checkbox', + onChange: (selectedRowKeys: React.Key[]) => { + onChange?.(selectedRowKeys as string[]) + onShowValueChange?.( + selectedRowKeys.length === remoteCounts + ? $t('所有(0)', [title]) + : originRemoteList + .filter(x => selectedRowKeys?.indexOf(x[option.key])) + ?.map(x => x.title) + .join(' , ') + ) + }, + selectedRowKeys: value + // getCheckboxProps: (record: unknown) => ({ + // disabled: record.name === 'Disabled User', // Column configuration not to be checked + // name: record.name, + // }) }} - onRow={(record)=>({ - onClick:()=>{ - if(value === undefined){ + onRow={record => ({ + onClick: () => { + if (value === undefined) { onChange?.([record[rowKey]]) - onShowValueChange?.(remoteCounts === 1 ? $t('所有(0)',[option?.title]) : record.name) - }else if(value?.indexOf(record[rowKey])!== -1){ - const newSelectedKeys = value?.filter(x=>x!==record[rowKey]) + onShowValueChange?.( + remoteCounts === 1 ? $t('所有(0)', [option?.title]) : record.name + ) + } else if (value?.indexOf(record[rowKey]) !== -1) { + const newSelectedKeys = value?.filter(x => x !== record[rowKey]) onChange?.(newSelectedKeys!) - onShowValueChange?.(newSelectedKeys.length === remoteCounts? $t('所有(0)',[option?.title]) : originRemoteList.filter(x=>newSelectedKeys.indexOf(x[rowKey]) !== -1)?.map(x=>x.name)?.join(' , ')) - }else{ - const newSelectedKeys = [...value,record[rowKey]] + onShowValueChange?.( + newSelectedKeys.length === remoteCounts + ? $t('所有(0)', [option?.title]) + : originRemoteList + .filter(x => newSelectedKeys.indexOf(x[rowKey]) !== -1) + ?.map(x => x.name) + ?.join(' , ') + ) + } else { + const newSelectedKeys = [...value, record[rowKey]] onChange?.(newSelectedKeys) - onShowValueChange?.(newSelectedKeys.length === remoteCounts? $t('所有(0)',[option?.title]) : originRemoteList.filter(x=>newSelectedKeys.indexOf(x[rowKey]) !== -1)?.map(x=>x.name)?.join(' , ')) - } + onShowValueChange?.( + newSelectedKeys.length === remoteCounts + ? $t('所有(0)', [option?.title]) + : originRemoteList + .filter(x => newSelectedKeys.indexOf(x[rowKey]) !== -1) + ?.map(x => x.name) + ?.join(' , ') + ) + } } })} - /> + /> - ) + + ) } -const StaticFormItem: React.FC = (props) => { - const {value, onChange, disabled,option,onShowValueChange} = props - const showAll = useMemo(()=>option.options.indexOf('ALL') !== -1,[option]) - const allChecked = useMemo(()=>value?.filter(x=>x!== 'ALL').length === option.options.filter(x=>x!== 'ALL').length,[value,option]) - - useEffect(()=>{ - if(value?.length === 1 && value[0] === 'ALL'){ - onChange?.(option.options.filter(x=>x!== 'ALL')) - onShowValueChange?.($t('所有(0)',[option?.title])) - } - },[]) +const StaticFormItem: React.FC = props => { + const { value, onChange, disabled, option, onShowValueChange } = props + const showAll = useMemo(() => option.options.indexOf('ALL') !== -1, [option]) + const allChecked = useMemo( + () => value?.filter(x => x !== 'ALL').length === option.options.filter(x => x !== 'ALL').length, + [value, option] + ) + + useEffect(() => { + if (value?.length === 1 && value[0] === 'ALL') { + onChange?.(option.options.filter(x => x !== 'ALL')) + onShowValueChange?.($t('所有(0)', [option?.title])) + } + }, []) return (
{showAll && ( { + onChange={e => { onChange?.(e.target.checked ? option.options : []) - onShowValueChange?.(e.target.checked ? $t('所有(0)',[option?.title]) : '-') + onShowValueChange?.(e.target.checked ? $t('所有(0)', [option?.title]) : '-') }} disabled={disabled} indeterminate={!allChecked && value?.length > 0} @@ -140,112 +190,148 @@ const StaticFormItem: React.FC = (props) => { )} x!== 'ALL')} - onChange={(checkedValues) => { + options={option?.options.filter(x => x !== 'ALL')} + onChange={checkedValues => { onChange?.(checkedValues) onShowValueChange?.(checkedValues.join(',')) }} disabled={disabled} /> -
) + + ) } -const FilterForm = forwardRef(({ - filterForm, - filterOptions, - selectedOptionNameSet, - disabled, - setFormCanSubmit, - serviceId,teamId},ref)=> { - const [form] = Form.useForm(); - const [filterType, setFilterType] = useState<'remote'|'static'|'pattern'>(); - const [curOption, setCurOption] = useState() - const [label,setLabel] = useState('') - - useImperativeHandle(ref, ()=>({ - clear:()=>{ - form.resetFields() +const FilterForm = forwardRef( + ( + { + filterForm, + filterOptions, + selectedOptionNameSet, + disabled, + setFormCanSubmit, + serviceId, + teamId }, - save:()=>form.validateFields().then((res)=>{ - const selectedOption = filterOptions.filter(x=>x.name === res.name)[0] - return Promise.resolve({ - ...res, - label:filterType === 'pattern' ? res.values : label, - title:selectedOption.label, - _eoKey:uuidv4() - }) - }).catch((errorInfo)=>Promise.reject(errorInfo)) - }) - ) + ref + ) => { + const [form] = Form.useForm() + const [filterType, setFilterType] = useState<'remote' | 'static' | 'pattern'>() + const [curOption, setCurOption] = useState() + const [label, setLabel] = useState('') - const handleValuesChange = (changedValues: any, allValues: any) => { - if(!allValues){ - setFormCanSubmit(false) - return + useImperativeHandle(ref, () => ({ + clear: () => { + form.resetFields() + }, + save: () => + form + .validateFields() + .then(res => { + const selectedOption = filterOptions.filter(x => x.name === res.name)[0] + return Promise.resolve({ + ...res, + label: filterType === 'pattern' ? res.values : label, + title: selectedOption.label, + _eoKey: uuidv4() + }) + }) + .catch(errorInfo => Promise.reject(errorInfo)) + })) + + const handleValuesChange = (changedValues: any, allValues: any) => { + if (!allValues) { + setFormCanSubmit(false) + return + } + if (allValues.values instanceof Array) { + setFormCanSubmit(allValues.values.length > 0) + return + } + setFormCanSubmit(true) } - if(allValues.values instanceof Array){ - setFormCanSubmit(allValues.values.length > 0) - return - } - setFormCanSubmit(true) - }; - - const handleTypeChange = (value:string)=>{ - form.setFieldValue('values',filterForm?.name === value ? filterForm.values : undefined) - const selectedOption = filterOptions?.filter(item=>item.name === value)[0] - setFilterType(selectedOption?.type) - setCurOption(selectedOption) - setFormCanSubmit(filterForm?.name === value ) - } - - const handleIPChange = (e) => { - const inputValue = e.target.value; - const formattedValue = inputValue.replace(/,/g, '\n'); - form.setFieldsValue({ value: formattedValue }); - }; - - useEffect(()=>{ - if(filterForm?.name){ - form.setFieldsValue({ - ...filterForm, - values: filterForm?.type === 'pattern' ? - (filterForm.name === 'ip' ? (filterForm.values as string[])?.join('\n') : (filterForm.values as string[])?.[0] ) :filterForm.values}) - const selectedOption = filterOptions.filter(x=>x.name === filterForm?.name)[0] - setFilterType(selectedOption?.type ) + const handleTypeChange = (value: string) => { + form.setFieldValue('values', filterForm?.name === value ? filterForm.values : undefined) + const selectedOption = filterOptions?.filter(item => item.name === value)[0] + setFilterType(selectedOption?.type) setCurOption(selectedOption) - setFormCanSubmit(filterForm?.values && filterForm?.values?.length >0) - }else{ - const firstOption = filterOptions.filter(x=>!selectedOptionNameSet.has(x.name))[0] - form.setFieldValue('name',firstOption?.name) - setFilterType(firstOption?.type) - setCurOption(firstOption) - } - },[filterForm]) + setFormCanSubmit(filterForm?.name === value) + } - const filterOptionsList = useMemo(() => { - return filterOptions.filter(x=>{ - return !!(filterForm?.name && x.name === filterForm?.name )|| !selectedOptionNameSet.has(x.name)}).map((item) => ({label:item.title, value:item.name})); - }, [filterOptions,filterForm,selectedOptionNameSet]); + const handleIPChange = e => { + const inputValue = e.target.value + const formattedValue = inputValue.replace(/,/g, '\n') + form.setFieldsValue({ value: formattedValue }) + } - return ( -
- - { + return filterOptions + .filter(x => { + return ( + !!(filterForm?.name && x.name === filterForm?.name) || + !selectedOptionNameSet.has(x.name) + ) + }) + .map(item => ({ label: item.title, value: item.name })) + }, [filterOptions, filterForm, selectedOptionNameSet]) + + return ( + + + + )} + + {filterType === 'pattern' && form.getFieldValue('name') === 'ip' && ( (({ /> )} - {filterType === 'static' && } + {filterType === 'static' && ( + + )} - - ); -}) + + ) + } +) -export default FilterForm; \ No newline at end of file +export default FilterForm diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingLogModal.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingLogModal.tsx index 50980da7..5b962eb7 100644 --- a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingLogModal.tsx +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingLogModal.tsx @@ -1,25 +1,25 @@ -import React, { useMemo, useRef, useState } from 'react'; -import { DataMaskLogItem } from "@common/const/policy/type"; -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList"; -import { $t } from "@common/locales"; +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList' +import { DataMaskLogItem } from '@common/const/policy/type' +import { $t } from '@common/locales' import { Button, message } from 'antd' +import React, { useMemo, useRef, useState } from 'react' -import { DATA_MASKING_TABLE_LOG_COLUMNS } from './DataMaskingColumn'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; -import { ActionType } from '@ant-design/pro-components'; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'; -import { useParams } from 'react-router-dom'; -import { RouterParams } from '@common/const/type'; -import { useFetch } from '@common/hooks/http'; -import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'; -import { SearchBody } from '@dashboard/const/type'; -import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission'; +import { ActionType } from '@ant-design/pro-components' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission' +import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { RouterParams } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { SearchBody } from '@dashboard/const/type' +import { useParams } from 'react-router-dom' +import { DATA_MASKING_TABLE_LOG_COLUMNS } from './DataMaskingColumn' const DataMaskingLogModal = (props: any) => { - const { strategy } = props; + const { strategy } = props const { state, accessData } = useGlobalContext() const { serviceId, teamId } = useParams() - const [datePickerValue, setDatePickerValue] = useState(); - const currentSecond = Math.floor(Date.now() / 1000); // 当前秒级时间戳 + const [datePickerValue, setDatePickerValue] = useState() + const currentSecond = Math.floor(Date.now() / 1000) // 当前秒级时间戳 const [queryData, setQueryData] = useState({ start: currentSecond - 24 * 60 * 60, end: currentSecond @@ -29,9 +29,9 @@ const DataMaskingLogModal = (props: any) => { */ const { fetchData } = useFetch() /** - * 列表ref - */ - const pageListRef = useRef(null); + * 列表ref + */ + const pageListRef = useRef(null) /** * 搜索关键字 */ @@ -43,14 +43,16 @@ const DataMaskingLogModal = (props: any) => { /** * 时间按钮 */ - const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>('day'); + const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>( + 'day' + ) /** * 绑定时间范围组件 * @param instance */ const bindRef = (instance: any) => { resetTimeRange = instance.reset - }; + } /** * 操作列 */ @@ -70,40 +72,59 @@ const DataMaskingLogModal = (props: any) => { url += `/${teamId}` } return [ - { window.open(url, '_blank') }} btnTitle="查看" /> + { + window.open(url, '_blank') + }} + btnTitle="查看" + /> ] } } ] /** - * 手动刷新表格数据 - */ + * 手动刷新表格数据 + */ const manualReloadTable = () => { pageListRef.current?.reload() - }; + } const columns = useMemo(() => { const res = DATA_MASKING_TABLE_LOG_COLUMNS.map(x => { if (x.dataIndex === 'url') { - x.render = (text: any, record: any) => <>
{record.method} {text}
+ x.render = (text: any, record: any) => ( + <> +
+ {record.method}  + + {text} + +
+ + ) } return { ...x, - title: ({$t(x.title as string)}) + title: {$t(x.title as string)} } }) return res }, [state.language]) /** - * 获取列表数据 - * @param dataType - * @returns - */ - const getPolicyList = (params: DataMaskLogItem & { - pageSize: number; - current: number; - }) => { - return fetchData>( + * 获取列表数据 + * @param dataType + * @returns + */ + const getPolicyList = ( + params: DataMaskLogItem & { + pageSize: number + current: number + } + ) => { + return fetchData>( `strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/logs`, { method: 'GET', @@ -115,38 +136,43 @@ const DataMaskingLogModal = (props: any) => { page_size: params.pageSize, strategy: strategy, service: serviceId, - team: teamId, + team: teamId }, - eoTransformKeys: ['is_stop', 'is_delete', 'update_time', 'publish_status', 'processed_total'] + eoTransformKeys: [ + 'is_stop', + 'is_delete', + 'update_time', + 'publish_status', + 'processed_total' + ] } - ).then(response => { - const { code, data, msg } = response - if (code === STATUS_CODE.SUCCESS) { - // 保存数据 - return { - data: data.logs || [], - total: data.total, - success: true + ) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + // 保存数据 + return { + data: data.logs || [], + total: data.total, + success: true + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } - } else { - message.error(msg || $t(RESPONSE_TIPS.error)) + }) + .catch(() => { return { data: [], success: false } - } - }).catch(() => { - return { data: [], success: false } - }) - - + }) } const handleTimeRangeChange = (timeRange: TimeRange) => { - setQueryData(pre => ({ ...pre, ...timeRange } as SearchBody)) + setQueryData(pre => ({ ...pre, ...timeRange }) as SearchBody) manualReloadTable() - - }; + } const resetQuery = () => { resetTimeRange() - }; + } return ( <> @@ -156,8 +182,8 @@ const DataMaskingLogModal = (props: any) => { ref={pageListRef} minVirtualHeight={400} columns={[...columns, ...operation]} - afterNewBtn={ - [
+ afterNewBtn={[ +
{ initialTimeButton={timeButton} onTimeButtonChange={setTimeButton} initialDatePickerValue={datePickerValue} - onTimeRangeChange={handleTimeRangeChange} /> -
+ onTimeRangeChange={handleTimeRangeChange} + /> +
-
] - } - request={async (params: DataMaskLogItem & { - pageSize: number; - current: number; - }) => getPolicyList(params)} - searchPlaceholder={$t("输入调用地址、消费者IP和消费者条件查找")} - onSearchWordChange={(e) => { +
+ ]} + request={async ( + params: DataMaskLogItem & { + pageSize: number + current: number + } + ) => getPolicyList(params)} + searchPlaceholder={$t('输入调用地址、消费者IP和消费者条件查找')} + onSearchWordChange={e => { setSearchWord(e.target.value) }} manualReloadTable={manualReloadTable} @@ -185,5 +214,5 @@ const DataMaskingLogModal = (props: any) => {
) -}; -export default DataMaskingLogModal; \ No newline at end of file +} +export default DataMaskingLogModal diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx index baed9c5b..6393481d 100644 --- a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx @@ -1,78 +1,96 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Form, Input, Select, Modal } from 'antd'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; -import { $t } from '@common/locales'; -import { PLACEHOLDER } from '@common/const/const'; -import { v4 as uuidv4 } from 'uuid'; -import { DataMaskRuleFormProps } from '@common/const/policy/type'; -import { MatchRules, DataFormatOptions, DataMaskReplaceStrOptions, DataMaskBaseOptionOptions, DataMaskOrderOptions } from '@common/const/policy/consts'; -const DataMaskRuleForm: React.FC = ({ editData, ruleList, onSave, onClose,modalVisible }) => { - const [form] = Form.useForm(); - const [matchType, setMatchType] = useState(''); - const [matchValue, setMatchValue] = useState(''); - const [maskType, setMaskType] = useState(''); - const [replaceType, setReplaceType] = useState(''); - const {state} = useGlobalContext() +import { PLACEHOLDER } from '@common/const/const' +import { + DataFormatOptions, + DataMaskBaseOptionOptions, + DataMaskOrderOptions, + DataMaskReplaceStrOptions, + MatchRules +} from '@common/const/policy/consts' +import { DataMaskRuleFormProps } from '@common/const/policy/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { Form, Input, Modal, Select } from 'antd' +import React, { useEffect, useMemo, useState } from 'react' +import { v4 as uuidv4 } from 'uuid' +const DataMaskRuleForm: React.FC = ({ + editData, + ruleList, + onSave, + onClose, + modalVisible +}) => { + const [form] = Form.useForm() + const [matchType, setMatchType] = useState('') + const [matchValue, setMatchValue] = useState('') + const [maskType, setMaskType] = useState('') + const [replaceType, setReplaceType] = useState('') + const { state } = useGlobalContext() useEffect(() => { if (editData) { - form.setFieldsValue(editData); + form.setFieldsValue(editData) } - }, [editData, form]); + }, [editData, form]) const handleSave = () => { - form.validateFields().then((values) => { - const submitData = prepareSubmitData(values); - const newRuleList =ruleList ? [...ruleList] : []; + form.validateFields().then(values => { + const submitData = prepareSubmitData(values) + const newRuleList = ruleList ? [...ruleList] : [] if (editData) { - const index = newRuleList.findIndex((rule) => rule.eoKey === editData.eoKey); + const index = newRuleList.findIndex(rule => rule.eoKey === editData.eoKey) if (index !== -1) { - newRuleList.splice(index, 1); + newRuleList.splice(index, 1) } } - newRuleList.unshift({ ...submitData, eoKey: editData?.eoKey || uuidv4() }); - onSave?.(newRuleList); - onClose?.(); + newRuleList.unshift({ ...submitData, eoKey: editData?.eoKey || uuidv4() }) + onSave?.(newRuleList) + onClose?.() clearData() - }); - }; + }) + } - const clearData = ()=>{ - form.resetFields() - setMatchType(''); - setMaskType(''); - setMatchValue(''); - setReplaceType(''); + const clearData = () => { + form.resetFields() + setMatchType('') + setMaskType('') + setMatchValue('') + setReplaceType('') } useEffect(() => { if (editData) { - form.setFieldsValue(editData); - editData?.match?.type && setMatchType(editData.match.type); - editData?.mask?.type && setMaskType(editData.mask.type); - editData?.match?.value && setMatchValue(editData.match.value); - editData?.mask?.replace?.type && setReplaceType(editData.mask.replace.type); + form.setFieldsValue(editData) + editData?.match?.type && setMatchType(editData.match.type) + editData?.mask?.type && setMaskType(editData.mask.type) + editData?.match?.value && setMatchValue(editData.match.value) + editData?.mask?.replace?.type && setReplaceType(editData.mask.replace.type) } - }, [editData, form]); + }, [editData, form]) const handleMatchTypeChange = (value: string) => { - setMatchType(value); - form.resetFields(['match.value','mask.begin', 'mask.length', 'mask.replace.type', 'mask.replace.value']); - }; + setMatchType(value) + form.resetFields([ + 'match.value', + 'mask.begin', + 'mask.length', + 'mask.replace.type', + 'mask.replace.value' + ]) + } const handleMatchValueChange = (value: string) => { - setMatchValue(value); - form.resetFields(['mask.begin', 'mask.length', 'mask.replace.type', 'mask.replace.value']); - }; + setMatchValue(value) + form.resetFields(['mask.begin', 'mask.length', 'mask.replace.type', 'mask.replace.value']) + } const handleMaskTypeChange = (value: string) => { - setMaskType(value); - form.resetFields(['mask.begin', 'mask.length', 'mask.replace.type', 'mask.replace.value']); - }; + setMaskType(value) + form.resetFields(['mask.begin', 'mask.length', 'mask.replace.type', 'mask.replace.value']) + } const handleReplaceTypeChange = (value: string) => { - setReplaceType(value); - form.resetFields(['mask.replace.value']); - }; + setReplaceType(value) + form.resetFields(['mask.replace.value']) + } const prepareSubmitData = (formData: any) => { const submitData: any = { @@ -83,76 +101,121 @@ const DataMaskRuleForm: React.FC = ({ editData, ruleList, mask: { type: formData.mask.type } - }; + } switch (formData.mask.type) { case 'replacement': submitData.mask = { ...submitData.mask, replace: formData.mask.replace - }; - break; + } + break case 'shuffling': - break; + break default: - submitData.mask.begin = Number(formData.mask.begin) || 0; - submitData.mask.length = Number(formData.mask.length) || 0; - break; + submitData.mask.begin = Number(formData.mask.begin) || 0 + submitData.mask.length = Number(formData.mask.length) || 0 + break } - return submitData; - }; + return submitData + } - const matchRuleOptions = useMemo(()=>MatchRules.map(rule => ({ label: $t(rule.label), value: rule.value })),[state.language]) - const dataFormatOptions = useMemo(()=>DataFormatOptions.map(rule => ({ label: $t(rule.label), value: rule.value })),[state.language]) - const dataMaskBaseOptions = useMemo(()=>DataMaskBaseOptionOptions.map(rule => ({ label: $t(rule.label), value: rule.value })),[state.language]) - const dataMaskOrderOptions = useMemo(()=>DataMaskOrderOptions.map(rule => ({ label: $t(rule.label), value: rule.value })),[state.language]) - const dataMaskReplaceStrOptions = useMemo(()=>DataMaskReplaceStrOptions.map(rule => ({ label: $t(rule.label), value: rule.value })),[state.language]) + const matchRuleOptions = useMemo( + () => MatchRules.map(rule => ({ label: $t(rule.label), value: rule.value })), + [state.language] + ) + const dataFormatOptions = useMemo( + () => DataFormatOptions.map(rule => ({ label: $t(rule.label), value: rule.value })), + [state.language] + ) + const dataMaskBaseOptions = useMemo( + () => DataMaskBaseOptionOptions.map(rule => ({ label: $t(rule.label), value: rule.value })), + [state.language] + ) + const dataMaskOrderOptions = useMemo( + () => DataMaskOrderOptions.map(rule => ({ label: $t(rule.label), value: rule.value })), + [state.language] + ) + const dataMaskReplaceStrOptions = useMemo( + () => DataMaskReplaceStrOptions.map(rule => ({ label: $t(rule.label), value: rule.value })), + [state.language] + ) return ( - +
- - + - { matchType && { - matchType === 'inner' ? - } - - } - - - - - - - - - )} - - {maskType === 'replacement' && ( - <> - - + ) : ( + )} + + )} + + + - )} - - )} + + + + + )} - + {maskType === 'replacement' && ( + <> + + + + )} + + )} +
- ); -}; + ) +} -export default DataMaskRuleForm; \ No newline at end of file +export default DataMaskRuleForm diff --git a/frontend/packages/core/src/pages/resourcesettings/ResourceSettings.tsx b/frontend/packages/core/src/pages/resourcesettings/ResourceSettings.tsx index fe24c935..7928f12d 100644 --- a/frontend/packages/core/src/pages/resourcesettings/ResourceSettings.tsx +++ b/frontend/packages/core/src/pages/resourcesettings/ResourceSettings.tsx @@ -1,95 +1,89 @@ +import InsidePage from '@common/components/aoplatform/InsidePage' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { DynamicMenuItem } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { getItem } from '@common/utils/navigation' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { Menu, MenuProps, Skeleton, message } from 'antd' +import { useEffect, useMemo, useState } from 'react' +import { Link, Outlet, useNavigate, useParams } from 'react-router-dom' -import { Menu, MenuProps, Skeleton, message } from "antd"; -import { Link, Outlet, useNavigate, useParams } from "react-router-dom"; -import InsidePage from "@common/components/aoplatform/InsidePage"; -import { useEffect, useMemo, useState } from "react"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { DynamicMenuItem } from "@common/const/type"; -import { useFetch } from "@common/hooks/http"; -import { getItem } from "@common/utils/navigation"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { $t } from "@common/locales"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +const LogSettings = () => { + const { moduleId } = useParams() + const [menuItems, setMenuItems] = useState([]) + const [activeMenu, setActiveMenu] = useState() + const { fetchData } = useFetch() + const [loading, setLoading] = useState(true) + const navigateTo = useNavigate() + const { state } = useGlobalContext() -const LogSettings = ()=>{ - const {moduleId} = useParams(); - const [menuItems, setMenuItems ] = useState([]) - const [activeMenu, setActiveMenu] = useState() - const {fetchData} = useFetch() - const [loading, setLoading] = useState(true) - const navigateTo = useNavigate() - const {state} = useGlobalContext() - - const getDynamicMenuList = ()=>{ - setLoading(true) - fetchData>(`simple/dynamics/resource`,{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - - - setMenuItems(data.dynamics) - if(!activeMenu || activeMenu.length === 0){ - navigateTo(`/resourcesettings/template/${data.dynamics[0].name}`) - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>setLoading(false)) - } + const getDynamicMenuList = () => { + setLoading(true) + fetchData>(`simple/dynamics/resource`, { method: 'GET' }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setMenuItems(data.dynamics) + if (!activeMenu || activeMenu.length === 0) { + navigateTo(`/resourcesettings/template/${data.dynamics[0].name}`) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => setLoading(false)) + } - - const menuData = useMemo(()=>{ - const newMenu = menuItems?.map((x:DynamicMenuItem)=>{ - - return getItem( - {$t(x.title)}, - x.name, - undefined, - undefined, - undefined, - 'system.settings.log_configuration.view') - }) - return newMenu - },[state.language,menuItems]) + const menuData = useMemo(() => { + const newMenu = menuItems?.map((x: DynamicMenuItem) => { + return getItem( + {$t(x.title)}, + x.name, + undefined, + undefined, + undefined, + 'system.settings.log_configuration.view' + ) + }) + return newMenu + }, [state.language, menuItems]) + const onMenuClick: MenuProps['onClick'] = ({ key }) => { + setActiveMenu(key) + } - const onMenuClick: MenuProps['onClick'] = ({key}) => { - setActiveMenu(key) - }; - - useEffect(() => { - setActiveMenu(moduleId) - }, [ moduleId]); - - useEffect(()=>{ - setLoading(true) - getDynamicMenuList() - },[]) - - - return ( - <> - - -
- -
- -
-
-
-
- - ) + useEffect(() => { + setActiveMenu(moduleId) + }, [moduleId]) + + useEffect(() => { + setLoading(true) + getDynamicMenuList() + }, []) + + return ( + <> + + +
+ +
+ +
+
+
+
+ + ) } -export default LogSettings; \ No newline at end of file +export default LogSettings diff --git a/frontend/packages/core/src/pages/role/RoleList.tsx b/frontend/packages/core/src/pages/role/RoleList.tsx index bb491409..cd575bdb 100644 --- a/frontend/packages/core/src/pages/role/RoleList.tsx +++ b/frontend/packages/core/src/pages/role/RoleList.tsx @@ -1,173 +1,157 @@ -import { App} from "antd"; -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"; -import { useEffect, useMemo, useRef,} from "react"; -import {ActionType} from "@ant-design/pro-components"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { ROLE_TABLE_COLUMNS } from "../../const/role/const.tsx"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { PERMISSION_DEFINITION } from "@common/const/permissions.ts"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { checkAccess } from "@common/utils/permission.ts"; -import { useNavigate } from "react-router-dom"; -import { RoleTableListItem } from "@core/const/role/type.ts"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; -import { $t } from "@common/locales/index.ts"; +import { ActionType } from '@ant-design/pro-components' +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { RoleTableListItem } from '@core/const/role/type.ts' +import { App } from 'antd' +import { useEffect, useMemo, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { ROLE_TABLE_COLUMNS } from '../../const/role/const.tsx' +const RoleList = () => { + const { message } = App.useApp() + const { setBreadcrumb } = useBreadcrumb() + const { fetchData } = useFetch() + const pageListRef = useRef(null) + const { state } = useGlobalContext() + const navigateTo = useNavigate() -const RoleList = ()=>{ - const { modal,message } = App.useApp() - const { setBreadcrumb } = useBreadcrumb() - const {fetchData} = useFetch() - const pageListRef = useRef(null); - const {accessData,state} = useGlobalContext() - const navigateTo = useNavigate() + const operation: (type: string) => PageProColumns[] = (type: string) => [ + // TODO 开源版隐藏操作 + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 1, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: RoleTableListItem) => [ + { + navigateTo(`/role/${type}/config/${entity.id}`) + }} + btnTitle="查看" + /> + ] + } + ] - const operation:(type:string)=>PageProColumns[] =(type:string)=>[ - // TODO 开源版隐藏操作 - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:1, - fixed:'right', - valueType: 'option', - render: (_: React.ReactNode, entity: RoleTableListItem) => [ - {navigateTo(`/role/${type}/config/${entity.id}`)}} btnTitle="查看"/>, - // {navigateTo(`/role/${type}/config/${entity.id}`)}} btnTitle="编辑"/>, - // , - // {openModal(type as 'system'|'team','delete',entity)}} btnTitle="删除"/>, - ], + const getRoleList = (group: 'team' | 'system') => { + return fetchData>(`${group}/roles`, { + method: 'GET' + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + return { + data: data.roles?.map((x: RoleTableListItem) => ({ ...x, name: x.name })), + success: true + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } - ] + }) + .catch(() => { + return { data: [], success: false } + }) + } - const getRoleList = (group:'team'|'system')=>{ - return fetchData>(`${group}/roles`,{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - return {data:data.roles?.map((x:RoleTableListItem)=>({...x,name:(x.name)})), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} + const deleteRole = (entity: RoleTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>(`manage/role`, { + method: 'DELETE', + eoParams: { id: entity.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) - } + .catch((errorInfo) => reject(errorInfo)) + }) + } - const deleteRole = (entity:RoleTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>(`manage/role`,{method:'DELETE',eoParams:{id:entity.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } + useEffect(() => { + setBreadcrumb([ + { + title: $t('角色') + } + ]) + }, []) - const manualReloadTable = () => { - pageListRef.current?.reload() - }; + const columns = useMemo( + () => + ROLE_TABLE_COLUMNS.map((x) => ({ + ...x, + title: typeof x.title === 'string' ? $t(x.title as string) : x.title + })), + [state.language] + ) - const isActionAllowed = (accessType:'system'|'team', type:'add'|'edit'|'delete') => { - - const permission = `system.organization.role.${accessType}.${type}` as keyof typeof PERMISSION_DEFINITION[0] ; - - return !checkAccess(permission, accessData); - }; - - const openModal = (accessType:'system'|'team', type:'delete',entity?:RoleTableListItem)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; - } - - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ - case 'delete': - return deleteRole(entity!).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - disabled:isActionAllowed(accessType, type) - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - useEffect(() => { - setBreadcrumb([ - { - title: $t('角色')}]) - }, []); - - const columns = useMemo(()=>ROLE_TABLE_COLUMNS.map((x)=>({...x, title:typeof x.title === 'string' ? $t(x.title as string) : x.title})),[state.language]) - - return (<> - -
-
-

{$t('系统级别角色')}

- [], ...operation('system')]} - request={()=>getRoleList('system')} - addNewBtnTitle={$t("添加角色")} - showPagination={false} - onAddNewBtnClick={() => { - navigateTo(`/role/system/config`) - }} - noScroll={true} - addNewBtnAccess="system.organization.role.system.add" - onRowClick={(row:RoleTableListItem)=> navigateTo(`/role/system/config/${row.id}`)} - tableClickAccess="system.organization.role.system.edit" - /> -
-
-

{$t('团队级别角色')}

- [], ...operation('team')]} - request={()=>getRoleList('team')} - showPagination={false} - addNewBtnTitle={$t("添加角色")} - onAddNewBtnClick={() => { - navigateTo(`/role/team/config`) - }} - noScroll={true} - addNewBtnAccess="system.organization.role.team.add" - onRowClick={(row:RoleTableListItem)=> navigateTo(`/role/team/config/${row.id}`)} - tableClickAccess="system.organization.role.team.edit" - /> -
-
-
- ) + return ( + <> + +
+
+

{$t('系统级别角色')}

+ []), ...operation('system')]} + request={() => getRoleList('system')} + addNewBtnTitle={$t('添加角色')} + showPagination={false} + onAddNewBtnClick={() => { + navigateTo(`/role/system/config`) + }} + noScroll={true} + addNewBtnAccess="system.organization.role.system.add" + onRowClick={(row: RoleTableListItem) => navigateTo(`/role/system/config/${row.id}`)} + tableClickAccess="system.organization.role.system.edit" + /> +
+
+

{$t('团队级别角色')}

+ []), ...operation('team')]} + request={() => getRoleList('team')} + showPagination={false} + addNewBtnTitle={$t('添加角色')} + onAddNewBtnClick={() => { + navigateTo(`/role/team/config`) + }} + noScroll={true} + addNewBtnAccess="system.organization.role.team.add" + onRowClick={(row: RoleTableListItem) => navigateTo(`/role/team/config/${row.id}`)} + tableClickAccess="system.organization.role.team.edit" + /> +
+
+
+ + ) } -export default RoleList; \ No newline at end of file +export default RoleList diff --git a/frontend/packages/core/src/pages/system/SystemConfig.tsx b/frontend/packages/core/src/pages/system/SystemConfig.tsx index 51edbb50..55a833da 100644 --- a/frontend/packages/core/src/pages/system/SystemConfig.tsx +++ b/frontend/packages/core/src/pages/system/SystemConfig.tsx @@ -1,385 +1,473 @@ - -import {forwardRef, useEffect, useImperativeHandle, useMemo, useState} from "react"; -import {App, Button, Form, Input, Radio, Row, Select, TreeSelect, Upload} from "antd"; -import { Link, useLocation, useNavigate, useParams} from "react-router-dom"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import {BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {DefaultOptionType} from "antd/es/cascader"; -import { EntityItem, MemberItem, SimpleTeamItem} from "@common/const/type.ts"; +import { LoadingOutlined } from '@ant-design/icons' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { + BasicResponse, + DELETE_TIPS, + PLACEHOLDER, + RESPONSE_TIPS, + STATUS_CODE +} from '@common/const/const.tsx' +import { EntityItem, MemberItem, SimpleTeamItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { getImgBase64 } from '@common/utils/dataTransfer.ts' +import { normFile } from '@common/utils/uploadPic.ts' +import { validateUrlSlash } from '@common/utils/validate.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts' +import { SERVICE_APPROVAL_OPTIONS } from '@core/const/system/const.tsx' +import { Icon } from '@iconify/react/dist/iconify.js' +import { CategorizesType } from '@market/const/serviceHub/type.ts' +import { App, Button, Form, Input, Radio, Row, Select, TreeSelect, Upload } from 'antd' +import { DefaultOptionType } from 'antd/es/cascader' +import { RcFile, UploadChangeParam, UploadFile, UploadProps } from 'antd/es/upload/interface' +import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react' +import { Link, useLocation, useNavigate, useParams } from 'react-router-dom' import { v4 as uuidv4 } from 'uuid' -import { SystemConfigFieldType, SystemConfigHandle } from "../../const/system/type.ts"; -import { validateUrlSlash } from "@common/utils/validate.ts"; -import { normFile } from "@common/utils/uploadPic.ts"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx"; -import { useSystemContext } from "../../contexts/SystemContext.tsx"; -import { SERVICE_APPROVAL_OPTIONS, SERVICE_KIND_OPTIONS } from "@core/const/system/const.tsx"; -import { RcFile, UploadChangeParam, UploadFile, UploadProps } from "antd/es/upload/interface"; -import { LoadingOutlined } from "@ant-design/icons"; -import { getImgBase64 } from "@common/utils/dataTransfer.ts"; -import { CategorizesType } from "@market/const/serviceHub/type.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { $t } from "@common/locales/index.ts"; -import { AiServiceConfigFieldType } from "@core/const/ai-service/type.ts"; - +import { SystemConfigFieldType, SystemConfigHandle } from '../../const/system/type.ts' +import { useSystemContext } from '../../contexts/SystemContext.tsx' export type SimpleAiProviderItem = EntityItem & { - configured:boolean - logo:string + configured: boolean + logo: string } -const SystemConfig = forwardRef((_,ref) => { - const { message,modal } = App.useApp() - const { teamId, serviceId } = useParams(); - const [onEdit, setOnEdit] = useState(!!teamId) - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const [teamOptionList, setTeamOptionList] = useState() - const navigate = useNavigate(); - const {setBreadcrumb} = useBreadcrumb() - const { setSystemInfo} = useSystemContext() - const [showClassify, setShowClassify] = useState(true) - const [showAI, setShowAI] = useState(false) - const [imageBase64, setImageBase64] = useState(null); - const [tagOptionList, setTagOptionList] = useState([]) - const [serviceClassifyOptionList, setServiceClassifyOptionList] = useState() - const [uploadLoading, setUploadLoading] = useState(false) - const {checkPermission,accessInit, getGlobalAccessData,state, aiConfigFlushed, setAiConfigFlushed} = useGlobalContext() - const [providerOptionList, setProviderOptionList] = useState() - const location = useLocation() - const currentUrl = location.pathname +const SystemConfig = forwardRef((_, ref) => { + const { message, modal } = App.useApp() + const { teamId, serviceId } = useParams() + const [onEdit, setOnEdit] = useState(!!teamId) + const [form] = Form.useForm() + const { fetchData } = useFetch() + const [teamOptionList, setTeamOptionList] = useState() + const navigate = useNavigate() + const { setBreadcrumb } = useBreadcrumb() + const { setSystemInfo } = useSystemContext() + const [showClassify, setShowClassify] = useState(true) + const [showAI, setShowAI] = useState(false) + const [imageBase64, setImageBase64] = useState(null) + const [tagOptionList, setTagOptionList] = useState([]) + const [serviceClassifyOptionList, setServiceClassifyOptionList] = useState() + const [uploadLoading, setUploadLoading] = useState(false) + const { + checkPermission, + accessInit, + getGlobalAccessData, + state, + aiConfigFlushed, + setAiConfigFlushed + } = useGlobalContext() + const [providerOptionList, setProviderOptionList] = useState() + const location = useLocation() + const currentUrl = location.pathname - useImperativeHandle(ref, () => ({ - save:onFinish - })); + useImperativeHandle(ref, () => ({ + save: onFinish + })) - useEffect(()=>{ - if(currentUrl.indexOf('aiInside') !== -1){ - setShowAI(true) - } - },[currentUrl]) - - const getProviderOptionList = ()=>{ - setProviderOptionList([]) - fetchData>('simple/ai/providers',{method:'GET',eoTransformKeys:[]}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const configuredProvider = data.providers?.filter(x=>x.configured)?.map((x:SimpleAiProviderItem)=>{return {...x, - label: x.name, value:x.id - }}) - setProviderOptionList(configuredProvider) - if(!serviceId && configuredProvider.length > 0){ - form.setFieldsValue({provider: configuredProvider[0]?.id}) - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) + useEffect(() => { + if (currentUrl.indexOf('aiInside') !== -1) { + setShowAI(true) } - - const beforeUpload = async (file: RcFile) => { - if (!['image/png', 'image/jpeg', 'image/svg+xml'].includes(file.type)) { - alert($t('只允许上传PNG、JPG或SVG格式的图片')); - return false; + }, [currentUrl]) + + const getProviderOptionList = () => { + setProviderOptionList([]) + fetchData>('simple/ai/providers', { + method: 'GET', + eoTransformKeys: [] + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const configuredProvider = data.providers + ?.filter(x => x.configured) + ?.map((x: SimpleAiProviderItem) => { + return { ...x, label: x.name, value: x.id } + }) + setProviderOptionList(configuredProvider) + if (!serviceId && configuredProvider.length > 0) { + form.setFieldsValue({ provider: configuredProvider[0]?.id }) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const beforeUpload = async (file: RcFile) => { + if (!['image/png', 'image/jpeg', 'image/svg+xml'].includes(file.type)) { + alert($t('只允许上传PNG、JPG或SVG格式的图片')) + return false + } + + // if (file.size > MAX_SIZE) { + // try { + // const compressedBase64 = await compressImage(file, MAX_SIZE); + // setImageBase64(`data:${file.type};base64,${compressedBase64}`); + // form.setFieldValue('logo', `data:${file.type};base64,${compressedBase64}`); + // } catch (error) { + // console.error('压缩图片时出错', error); + // } + // } else { + const reader = new FileReader() + reader.onload = (e: ProgressEvent) => { + setImageBase64(e.target?.result as string) + form.setFieldValue('logo', e.target?.result) + } + reader.readAsDataURL(file) + // } + return false + } + + const handleChange: UploadProps['onChange'] = (info: UploadChangeParam) => { + if (info.file.status === 'uploading') { + setUploadLoading(true) + return + } + if (info.file.status === 'done') { + getImgBase64(info.file.originFileObj as RcFile, () => { + setUploadLoading(false) + }) + } + if (info.fileList.length === 0) { + form.setFieldValue('logo', null) + } + } + + const uploadButton = ( +
+ {uploadLoading ? : } +
+ ) + + const getTagAndServiceClassifyList = () => { + setTagOptionList([]) + setServiceClassifyOptionList([]) + fetchData>('catalogues', { + method: 'GET' + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTagOptionList( + data.tags?.map((x: EntityItem) => { + return { + label: x.name, + value: x.name + } + }) || [] + ) + setServiceClassifyOptionList(data.catalogues) + + if (form.getFieldValue('catalogue') === undefined && data.catalogues.length) { + form.setFieldValue('catalogue', data.catalogues[0].id) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + // 获取表单默认值 + const getSystemInfo = () => { + fetchData>('service/info', { + method: 'GET', + eoParams: { team: teamId, service: serviceId }, + eoTransformKeys: ['team_id', 'service_type', 'approval_type', 'service_kind'] + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTimeout(() => { + form.setFieldsValue({ + ...data.service, + team: data.service.team.id, + catalogue: data.service.catalogue?.id, + tags: data.service.tags?.map((x: EntityItem) => x.name), + provider: data.service.provider?.id, + logoFile: [ + { + uid: '-1', // 文件唯一标识 + name: 'image.png', // 文件名 + status: 'done', // 状态有:uploading, done, error, removed + url: data.service?.logo || '' // 图片 Base64 数据 + } + ] + }) + setImageBase64(data.service.logo) + setShowClassify(data.service.serviceType === 'public') + }, 0) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const onFinish: () => Promise = () => { + return form.validateFields().then(value => { + return fetchData>( + serviceId === undefined ? 'team/service' : 'service/info', + { + method: serviceId === undefined ? 'POST' : 'PUT', + eoParams: { + ...(serviceId === undefined + ? { team: value.team } + : { service: serviceId, team: teamId }) + }, + eoBody: { ...value, prefix: value.prefix?.trim() }, + eoTransformKeys: ['serviceType', 'approvalType', 'serviceKind'] + } + ) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + setSystemInfo(data.service) + return Promise.resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) } - - // if (file.size > MAX_SIZE) { - // try { - // const compressedBase64 = await compressImage(file, MAX_SIZE); - // setImageBase64(`data:${file.type};base64,${compressedBase64}`); - // form.setFieldValue('logo', `data:${file.type};base64,${compressedBase64}`); - // } catch (error) { - // console.error('压缩图片时出错', error); - // } - // } else { - const reader = new FileReader(); - reader.onload = (e: ProgressEvent) => { - setImageBase64(e.target?.result as string); - form.setFieldValue('logo', e.target?.result); - }; - reader.readAsDataURL(file); - // } - return false; - }; - - - - const handleChange: UploadProps['onChange'] = (info: UploadChangeParam) => { - if (info.file.status === 'uploading') { - setUploadLoading(true); - return; - } - if (info.file.status === 'done') { - getImgBase64(info.file.originFileObj as RcFile, () => { - setUploadLoading(false); - }); - } - if (info.fileList.length === 0) { - form.setFieldValue( "logo", null ); - } - }; - - const uploadButton = ( -
- {uploadLoading ? : } -
- ); - - const getTagAndServiceClassifyList = ()=>{ - setTagOptionList([]) - setServiceClassifyOptionList([]) - fetchData>('catalogues',{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTagOptionList(data.tags?.map((x:EntityItem)=>{return { - label:x.name, value:x.name - }})||[]) - setServiceClassifyOptionList(data.catalogues) - - if(form.getFieldValue('catalogue') === undefined&&data.catalogues.length){ - form.setFieldValue('catalogue',data.catalogues[0].id); - } - - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } }) + .catch(errorInfo => { + return Promise.reject(errorInfo) + }) + }) + } + + const getTeamOptionList = () => { + setTeamOptionList([]) + + fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { method: 'GET', eoTransformKeys: [] } + ).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTeamOptionList( + data.teams?.map((x: MemberItem) => { + return { ...x, label: x.name, value: x.id } + }) + ) + if (form.getFieldValue('team') === undefined && data.teams?.length) { + form.setFieldValue('team', data.teams[0].id) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const deleteSystem = () => { + fetchData>('team/service', { + method: 'DELETE', + eoParams: { team: teamId, service: serviceId } + }).then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + navigate(`/service/list`) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + useEffect(() => { + aiConfigFlushed && getProviderOptionList() + }, [aiConfigFlushed]) + + useEffect(() => { + if (accessInit) { + getTeamOptionList() + } else { + getGlobalAccessData()?.then?.(() => { + getTeamOptionList() + }) } - - - // 获取表单默认值 - const getSystemInfo = () => { - fetchData>('service/info',{method:'GET',eoParams:{team:teamId, service:serviceId},eoTransformKeys:['team_id','service_type','approval_type','service_kind']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTimeout(()=>{ - form.setFieldsValue({ - ...data.service, - team:data.service.team.id, - catalogue:data.service.catalogue?.id, - tags:data.service.tags?.map((x:EntityItem)=>x.name), - provider:data.service.provider?.id, - logoFile:[ - { - uid: '-1', // 文件唯一标识 - name: 'image.png', // 文件名 - status: 'done', // 状态有:uploading, done, error, removed - url: data.service?.logo || '', // 图片 Base64 数据 - } - ] - }) - setImageBase64(data.service.logo) - setShowClassify(data.service.serviceType === 'public') - },0) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - }; - - const onFinish:()=>Promise = () => { - return form.validateFields().then((value)=>{ - return fetchData>(serviceId === undefined? 'team/service':'service/info',{method:serviceId === undefined? 'POST' : 'PUT',eoParams: {...(serviceId === undefined ? {team:value.team} :{service:serviceId,team:teamId})},eoBody:({...value,prefix:value.prefix?.trim()}), eoTransformKeys:['serviceType','approvalType','serviceKind']},).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - setSystemInfo(data.service) - return Promise.resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=>{ - return Promise.reject(errorInfo) - }) - }) - }; - - - const getTeamOptionList = ()=>{ - setTeamOptionList([]) - - fetchData>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:[]}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTeamOptionList(data.teams?.map((x:MemberItem)=>{return {...x, - label:x.name, value:x.id - }})) - if(form.getFieldValue('team') === undefined&&data.teams?.length){ - form.setFieldValue('team',data.teams[0].id); - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - const deleteSystem = ()=>{ - fetchData>('team/service',{method:'DELETE',eoParams:{team:teamId,service:serviceId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - navigate(`/service/list`) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - - useEffect(()=>{ - aiConfigFlushed && getProviderOptionList() - },[aiConfigFlushed]) - - useEffect(() => { - if(accessInit){ - getTeamOptionList() - }else{ - getGlobalAccessData()?.then?.(()=>{ - getTeamOptionList() - }) + getProviderOptionList() + getTagAndServiceClassifyList() + if (serviceId !== undefined) { + setOnEdit(true) + getSystemInfo() + setBreadcrumb([ + { + title: {$t('服务')} + }, + { + title: $t('设置') } - getProviderOptionList() - getTagAndServiceClassifyList() - if (serviceId !== undefined) { - setOnEdit(true); - getSystemInfo(); - setBreadcrumb([ - { - title: {$t('服务')} - }, - { - title: $t('设置') - }]) - } else { - setOnEdit(false); - const id = uuidv4() - form.setFieldValue('id',id); - form.setFieldValue('serviceKind','ai') - setShowAI(true) - form.setFieldValue('prefix',`${id.split('-')[0]}/`) - form.setFieldValue('team',teamId); - form.setFieldValue('serviceType','public'); - form.setFieldValue('approvalType','auto'); - } - return (form.setFieldsValue({})) - }, [serviceId]); - - - const deleteSystemModal = async ()=>{ - modal.confirm({ - title:$t('删除'), - content:$t(DELETE_TIPS.default), - onOk:()=> { - return deleteSystem() - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - danger:true - }, - cancelText:$t('取消'), - closable:true, - icon:<> - }) + ]) + } else { + setOnEdit(false) + const id = uuidv4() + form.setFieldValue('id', id) + form.setFieldValue('serviceKind', 'ai') + setShowAI(true) + form.setFieldValue('prefix', `${id.split('-')[0]}/`) + form.setFieldValue('team', teamId) + form.setFieldValue('serviceType', 'public') + form.setFieldValue('approvalType', 'auto') } + return form.setFieldsValue({}) + }, [serviceId]) - // const serviceTypeOptions = useMemo(()=>SERVICE_KIND_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]); - // const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]) - const approvalOptions = useMemo(()=>SERVICE_APPROVAL_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]) + const deleteSystemModal = async () => { + modal.confirm({ + title: $t('删除'), + content: $t(DELETE_TIPS.default), + onOk: () => { + return deleteSystem() + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + danger: true + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } - return ( - <> - -
SERVICE_KIND_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]); + // const visualizationOptions = useMemo(()=>SERVICE_VISUALIZATION_OPTIONS.map((x)=>({...x, label:$t(x.label)})),[state.language]) + const approvalOptions = useMemo( + () => SERVICE_APPROVAL_OPTIONS.map(x => ({ ...x, label: $t(x.label) })), + [state.language] + ) + + return ( + <> + + +
+ + label={$t('服务名称')} + name="name" + rules={[{ required: true, whitespace: true }]} + > + + + + + label={$t('服务 ID')} + name="id" + rules={[{ required: true, whitespace: true }]} + > + + + {!onEdit && ( + + label={$t('服务类型')} + name="serviceKind" + rules={[{ required: true }]} + > + { + setShowAI(e.target.value === 'ai') + }} > -
- - label={$t("服务名称")} - name="name" - rules={[{ required: true ,whitespace:true }]} - > - - + +
+ + {$t('AI 服务')} +
+
+ +
+ + {$t('REST 服务')} +
+
+ + + )} + {showAI && ( + + label={$t('默认 AI 供应商')} + name="provider" + rules={[{ required: true }]} + extra={ + serviceId + ? $t('创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API') + : '' + } + > + {providerOptionList && providerOptionList.length > 0 ? ( + + ) : ( +

+ {$t('未配置任何 AI 模型供应商,')} + setAiConfigFlushed(false)}> + {$t('立即配置')} + +

+ )} + + )} - - label={$t("服务 ID")} - name="id" - rules={[{ required: true ,whitespace:true }]} - > - - - {!onEdit&& - - label={$t("服务类型")} - name="serviceKind" - rules={[{required: true}]} - > - {setShowAI(e.target.value === 'ai')}} > - -
- - {$t('AI 服务')}
-
- -
- - {$t('REST 服务')}
-
-
- -} - {showAI && - label={$t("默认 AI 供应商")} - name="provider" - rules={[{ required: true }]} - extra={serviceId ? $t('创建 API 时会默认选择该供应商,修改默认供应商不会影响现有 API') : ''} - >{ - (providerOptionList && providerOptionList.length >0 ) ? :

{$t("未配置任何 AI 模型供应商,")}setAiConfigFlushed(false)}>{$t('立即配置')}

- } - } - + + label={$t('API 调用前缀')} + name="prefix" + extra={$t( + '作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改' + )} + rules={[ + { required: true, whitespace: true }, + { + validator: validateUrlSlash + } + ]} + > + + + {!onEdit && ( + + label={$t('所属团队')} + name="team" + rules={[{ required: true }]} + > + + + )} - - label={$t("API 调用前缀")} - name="prefix" - extra={$t("作为服务内所有API的前缀,比如host/{service_name}/{api_path},一旦保存无法修改")} - rules={[{ required: true ,whitespace:true }, - { - validator: validateUrlSlash, - }]} - > - - - {!onEdit && - label={$t("所属团队")} - name="team" - rules={[{ required: true }]} - > - - } - - - label={$t("订阅审核")} - name="approvalType" - rules={[{required: true}]} - > - - + + label={$t('订阅审核')} + name="approvalType" + rules={[{ required: true }]} + > + + - {/* + {/* label={$t("服务类型")} name="serviceType" rules={[{required: true}]} @@ -387,101 +475,115 @@ const SystemConfig = forwardRef((_,ref) => { {setShowClassify(e.target.value === 'public')}} /> */} - {showClassify && - - label={$t("所属服务分类")} - name="catalogue" - extra={$t("设置服务展示在服务市场中的哪个分类下")} - rules={[{required: true}]} - > - - - } - - - label={$t("图标")} - name="logoFile" - extra={$t("仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩")} - valuePropName="fileList" getValueFromEvent={normFile} - > - -
- {imageBase64 ? Logo : uploadButton} -
-
+ {showClassify && ( + + label={$t('所属服务分类')} + name="catalogue" + extra={$t('设置服务展示在服务市场中的哪个分类下')} + rules={[{ required: true }]} + > + + + )} - + + label={$t('图标')} + name="logoFile" + extra={$t('仅支持 .png .jpg .jpeg .svg 格式的图片文件, 大于 1KB 的文件将被压缩')} + valuePropName="fileList" + getValueFromEvent={normFile} + > + +
+ {imageBase64 ? ( + Logo + ) : ( + uploadButton + )} +
+
+ - - label={$t("描述")} - name="description" - > - - + label={$t('描述')} name="description"> + + - - label={$t("Logo")} - name="logo" - hidden - > - + label={$t('Logo')} name="logo" hidden> - + label={$t('标签')} name="tags"> + + - - label={$t("标签")} - name="tags" - > - - - - {onEdit && <> - - - - - } -
- {onEdit && <> - -
-

{$t('删除服务')}:{$t('删除操作不可恢复,请谨慎操作!')}

-
- - - -
-
-
- } - - - - ) + {onEdit && ( + <> + + + + + + + )} +
+ {onEdit && ( + <> + +
+

+ {$t('删除服务')}: + {$t('删除操作不可恢复,请谨慎操作!')} +

+
+ + + +
+
+
+ + )} + +
+ + ) }) -export default SystemConfig \ No newline at end of file +export default SystemConfig diff --git a/frontend/packages/core/src/pages/system/SystemInsideDocument.tsx b/frontend/packages/core/src/pages/system/SystemInsideDocument.tsx index b07e2995..8f75b9ad 100644 --- a/frontend/packages/core/src/pages/system/SystemInsideDocument.tsx +++ b/frontend/packages/core/src/pages/system/SystemInsideDocument.tsx @@ -1,145 +1,201 @@ -import { Editor } from '@tinymce/tinymce-react'; -import hljs from 'highlight.js'; -import 'highlight.js/styles/default.css'; -import {useEffect, useState} from "react"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {App, Button} from "antd"; -import { EntityItem } from '@common/const/type.ts'; -import WithPermission from '@common/components/aoplatform/WithPermission.tsx'; -import { RouterParams } from '@core/components/aoplatform/RenderRoutes'; -import { useParams } from 'react-router-dom'; -import { $t } from '@common/locales'; -const ServiceInsideDocument = ()=>{ - const { message } = App.useApp() - const [updater,setUpdater] = useState() - const [updateTime,setUpdateTime]=useState() - const [initDoc, setInitDoc] = useState() - const [doc, setDoc] = useState() - const {fetchData} = useFetch() - const { serviceId, teamId} = useParams(); +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { EntityItem } from '@common/const/type.ts' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { Editor } from '@tinymce/tinymce-react' +import { App, Button } from 'antd' +import hljs from 'highlight.js' +import 'highlight.js/styles/default.css' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +const ServiceInsideDocument = () => { + const { message } = App.useApp() + const [updater, setUpdater] = useState() + const [updateTime, setUpdateTime] = useState() + const [initDoc, setInitDoc] = useState() + const [doc, setDoc] = useState() + const { fetchData } = useFetch() + const { serviceId, teamId } = useParams() - const save = ()=>{ - fetchData>('service/doc',{method:'PUT',eoBody:({doc:doc}) ,eoParams:{service:serviceId,team:teamId},eoTransformKeys:['update_time']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - getServiceDoc() - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - const handleEditorChange = (content:string, editor:unknown) => { - setDoc(content) - }; - const setupEditor = (editor:unknown) => { - editor.on('init', () => { - editor.contentDocument.querySelectorAll('pre code').forEach((block:HTMLElement) => { - hljs.highlightBlock(block); - }); - }); - - editor.on('SetContent', () => { - editor.contentDocument.querySelectorAll('pre code').forEach((block:HTMLElement) => { - hljs.highlightBlock(block); - }); - }); - }; - - const getServiceDoc = ()=>{ - fetchData>('service/doc',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['update_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setUpdater(data.doc.updater.id === '' ? '-' : data.doc.updater.name) - setUpdateTime(data.doc.updater.id === '' ? '-' : data.doc.updateTime) - setInitDoc(data.doc.doc) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - useEffect(() => { + const save = () => { + fetchData< + BasicResponse<{ + service: { id: string; name: string; updater: string; updateTime: string; doc: string } + }> + >('service/doc', { + method: 'PUT', + eoBody: { doc: doc }, + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['update_time'] + }).then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) getServiceDoc() - }, []); + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - return ( -
- - -
-
-

{$t('最近一次更新者')}:{updater || '-'}{$t('最近一次更新时间')}:{updateTime || '-'}

- -
-
-
) + const handleEditorChange = (content: string) => { + setDoc(content) + } + const setupEditor = (editor: unknown) => { + editor.on('init', () => { + editor.contentDocument.querySelectorAll('pre code').forEach((block: HTMLElement) => { + hljs.highlightBlock(block) + }) + }) + + editor.on('SetContent', () => { + editor.contentDocument.querySelectorAll('pre code').forEach((block: HTMLElement) => { + hljs.highlightBlock(block) + }) + }) + } + + const getServiceDoc = () => { + fetchData< + BasicResponse<{ + doc: { + id: string + name: string + updater: EntityItem + updateTime: string + creater: EntityItem + doc: string + } + }> + >('service/doc', { + method: 'GET', + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['update_time'] + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setUpdater(data.doc.updater.id === '' ? '-' : data.doc.updater.name) + setUpdateTime(data.doc.updater.id === '' ? '-' : data.doc.updateTime) + setInitDoc(data.doc.doc) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + useEffect(() => { + getServiceDoc() + }, []) + + return ( +
+ + +
+
+

+ + {$t('最近一次更新者')}:{updater || '-'} + + + {$t('最近一次更新时间')}:{updateTime || '-'} + +

+ + + +
+
+
+ ) } -export default ServiceInsideDocument \ No newline at end of file +export default ServiceInsideDocument diff --git a/frontend/packages/core/src/pages/system/SystemInsidePage.tsx b/frontend/packages/core/src/pages/system/SystemInsidePage.tsx index b7a3082d..ef1fee32 100644 --- a/frontend/packages/core/src/pages/system/SystemInsidePage.tsx +++ b/frontend/packages/core/src/pages/system/SystemInsidePage.tsx @@ -1,25 +1,23 @@ - -import { FC, useEffect, useMemo, useState } from "react"; -import { Link, Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; -import { App, Menu, MenuProps } from "antd"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx"; -import { useFetch } from "@common/hooks/http.ts"; -import { useSystemContext } from "../../contexts/SystemContext.tsx"; -import { SystemConfigFieldType } from "../../const/system/type.ts"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { PERMISSION_DEFINITION } from "@common/const/permissions.ts"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; -import Paragraph from "antd/es/typography/Paragraph"; -import { ItemType, MenuItemGroupType, MenuItemType } from "antd/es/menu/hooks/useItems"; -import { cloneDeep } from "lodash-es"; -import { $t } from "@common/locales/index.ts"; -import { getItem } from "@common/utils/navigation.tsx"; -import { RouterParams } from "@common/const/type.ts"; -const APP_MODE = import.meta.env.VITE_APP_MODE; +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { PERMISSION_DEFINITION } from '@common/const/permissions.ts' +import { RouterParams } from '@common/const/type.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { getItem } from '@common/utils/navigation.tsx' +import { App, Menu, MenuProps } from 'antd' +import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/hooks/useItems' +import Paragraph from 'antd/es/typography/Paragraph' +import { cloneDeep } from 'lodash-es' +import { FC, useEffect, useMemo, useState } from 'react' +import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom' +import { SystemConfigFieldType } from '../../const/system/type.ts' +import { useSystemContext } from '../../contexts/SystemContext.tsx' const SystemInsidePage: FC = () => { const { message } = App.useApp() - const { teamId, serviceId, apiId, routeId,policyId } = useParams(); + const { teamId, serviceId, apiId, routeId, policyId } = useParams() const location = useLocation() const currentUrl = location.pathname const { fetchData } = useFetch() @@ -30,7 +28,10 @@ const SystemInsidePage: FC = () => { const [showMenu, setShowMenu] = useState(false) const getSystemInfo = () => { - fetchData>('service/info', { method: 'GET', eoParams: { team: teamId, service: serviceId } }).then(response => { + fetchData>('service/info', { + method: 'GET', + eoParams: { team: teamId, service: serviceId } + }).then(response => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { setSystemInfo(data.service) @@ -43,7 +44,10 @@ const SystemInsidePage: FC = () => { const getApiDefine = () => { setApiPrefix('') setPrefixForce(false) - fetchData>('service/router/define', { method: 'GET', eoParams: { service: serviceId, team: teamId } }).then(response => { + fetchData>('service/router/define', { + method: 'GET', + eoParams: { service: serviceId, team: teamId } + }).then(response => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { setApiPrefix(data.prefix) @@ -54,72 +58,159 @@ const SystemInsidePage: FC = () => { }) } - - - const SYSTEM_PAGE_MENU_ITEMS = useMemo(() => [ - getItem($t('服务'), 'assets', null, - [ - getItem({$t('API 路由')}, 'route', undefined, undefined, undefined, 'team.service.router.view'), - getItem({$t('API 文档')}, 'api', undefined, undefined, undefined, 'team.service.api_doc.view'), - getItem({$t('上游')}, 'upstream', undefined, undefined, undefined, 'team.service.upstream.view'), - getItem({$t('使用说明')}, 'document', undefined, undefined, undefined, 'team.service.service_intro.view'), - getItem({$t('服务策略')}, 'servicepolicy', undefined, undefined, undefined, 'team.service.policy.view'), - getItem({$t('发布')}, 'publish', undefined, undefined, undefined, 'team.service.release.view'), - ], - 'group'), - getItem($t('订阅管理'), 'provideSer', null, - [ - getItem({$t('订阅审核')}, 'approval', undefined, undefined, undefined, 'team.service.subscription.view'), - getItem({$t('订阅方管理')}, 'subscriber', undefined, undefined, undefined, 'team.service.subscription.view'), - ], - 'group'), - getItem($t('管理'), 'mng', null, + const SYSTEM_PAGE_MENU_ITEMS = useMemo( + () => [ + getItem( + $t('服务'), + 'assets', + null, [ - // APP_MODE === 'pro' ? getItem({$t('调用拓扑图')}, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null, - getItem({$t('设置')}, 'setting',undefined,undefined,undefined,'')], - 'group'), -],[state.language]) - + getItem( + {$t('API 路由')}, + 'route', + undefined, + undefined, + undefined, + 'team.service.router.view' + ), + getItem( + {$t('API 文档')}, + 'api', + undefined, + undefined, + undefined, + 'team.service.api_doc.view' + ), + getItem( + {$t('上游')}, + 'upstream', + undefined, + undefined, + undefined, + 'team.service.upstream.view' + ), + getItem( + {$t('使用说明')}, + 'document', + undefined, + undefined, + undefined, + 'team.service.service_intro.view' + ), + getItem( + {$t('服务策略')}, + 'servicepolicy', + undefined, + undefined, + undefined, + 'team.service.policy.view' + ), + getItem( + {$t('发布')}, + 'publish', + undefined, + undefined, + undefined, + 'team.service.release.view' + ) + ], + 'group' + ), + getItem( + $t('订阅管理'), + 'provideSer', + null, + [ + getItem( + {$t('订阅审核')}, + 'approval', + undefined, + undefined, + undefined, + 'team.service.subscription.view' + ), + getItem( + {$t('订阅方管理')}, + 'subscriber', + undefined, + undefined, + undefined, + 'team.service.subscription.view' + ) + ], + 'group' + ), + getItem( + $t('管理'), + 'mng', + null, + [ + // APP_MODE === 'pro' ? getItem({$t('调用拓扑图')}, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null, + getItem( + {$t('设置')}, + 'setting', + undefined, + undefined, + undefined, + '' + ) + ], + 'group' + ) + ], + [state.language] + ) const menuData = useMemo(() => { const filterMenu = (menu: MenuItemGroupType[]) => { const newMenu = cloneDeep(menu) return newMenu!.filter((m: MenuItemGroupType) => { if (m && m.children && m.children.length > 0) { - m.children = m.children.filter( - (c) => { - if (!c) return false - return (((c as MenuItemType & { access: string }).access ? - checkPermission((c as MenuItemType & { access: string }).access as keyof typeof PERMISSION_DEFINITION[0]) : - true)) - }) + m.children = m.children.filter(c => { + if (!c) return false + return (c as MenuItemType & { access: string }).access + ? checkPermission( + (c as MenuItemType & { access: string }) + .access as keyof (typeof PERMISSION_DEFINITION)[0] + ) + : true + }) } return m.children && m.children.length > 0 }) } const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType[]) - const menu = activeMenu ?? filteredMenu[0]?.children ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key - if (menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`) + const menu = + (activeMenu ?? filteredMenu[0]?.children) + ? filteredMenu[0]?.children?.[0]?.key + : filteredMenu[0]?.key + if (menu && currentUrl.split('/')[-1] !== menu) + navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`) return filteredMenu || [] }, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS]) const onMenuClick: MenuProps['onClick'] = ({ key }) => { setActiveMenu(key) - }; + } - useEffect(() => { + useEffect(() => { // route edit and policy edit page don't need to show menu - setShowMenu(!routeId && !currentUrl.includes('route/create') && !policyId && !currentUrl.includes('servicepolicy/datamasking/create')) + setShowMenu( + !routeId && + !currentUrl.includes('route/create') && + !policyId && + !currentUrl.includes('servicepolicy/datamasking/create') + ) if (apiId !== undefined) { setActiveMenu('api') - } else if(currentUrl.includes('servicepolicy')){ + } else if (currentUrl.includes('servicepolicy')) { setActiveMenu('servicepolicy') } else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) { setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1]) } else { setActiveMenu('route') } - }, [currentUrl]); + }, [currentUrl]) useEffect(() => { if (accessData && checkPermission('team.service.router.view')) { @@ -135,32 +226,44 @@ const SystemInsidePage: FC = () => { useEffect(() => { serviceId && getSystemInfo() - }, [serviceId]); + }, [serviceId]) return ( - <>{showMenu ? - {$t('服务 ID')}:{serviceId || '-'} - }]} - backUrl="/service/list"> -
- []} - /> -
- + <> + {showMenu ? ( + + {$t('服务 ID')}:{serviceId || '-'} + + ) + } + ]} + backUrl="/service/list" + > +
+ []} + /> +
+ +
-
- : } - + + ) : ( + + )} ) } -export default SystemInsidePage \ No newline at end of file +export default SystemInsidePage diff --git a/frontend/packages/core/src/pages/system/SystemInsideSubscriber.tsx b/frontend/packages/core/src/pages/system/SystemInsideSubscriber.tsx index b1ebf43c..43548be1 100644 --- a/frontend/packages/core/src/pages/system/SystemInsideSubscriber.tsx +++ b/frontend/packages/core/src/pages/system/SystemInsideSubscriber.tsx @@ -1,268 +1,327 @@ -import {ActionType} from "@ant-design/pro-components"; -import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react"; -import {Link, useParams} from "react-router-dom"; -import {App, Form,TreeSelect} from "antd"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx"; -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"; -import {DefaultOptionType} from "antd/es/cascader"; -import { SYSTEM_SUBSCRIBER_TABLE_COLUMNS } from "../../const/system/const.tsx"; -import { SystemSubscriberTableListItem, SystemSubscriberConfigFieldType, SystemSubscriberConfigHandle, SystemSubscriberConfigProps, SimpleSystemItem } from "../../const/system/type.ts"; -import { SimpleMemberItem } from "@common/const/type.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { checkAccess } from "@common/utils/permission.ts"; -import { $t } from "@common/locales/index.ts"; +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { + BasicResponse, + COLUMNS_TITLE, + DELETE_TIPS, + PLACEHOLDER, + RESPONSE_TIPS, + STATUS_CODE +} from '@common/const/const.tsx' +import { SimpleMemberItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { checkAccess } from '@common/utils/permission.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { App, Form, TreeSelect } from 'antd' +import { DefaultOptionType } from 'antd/es/cascader' +import { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { SYSTEM_SUBSCRIBER_TABLE_COLUMNS } from '../../const/system/const.tsx' +import { + SimpleSystemItem, + SystemSubscriberConfigFieldType, + SystemSubscriberConfigHandle, + SystemSubscriberConfigProps, + SystemSubscriberTableListItem +} from '../../const/system/type.ts' -const SystemInsideSubscriber:FC = ()=>{ - const { setBreadcrumb } = useBreadcrumb() - const { modal,message } = App.useApp() - const {fetchData} = useFetch() - const {serviceId, teamId} = useParams() - const addRef = useRef(null) - const pageListRef = useRef(null); - const [memberValueEnum, setMemberValueEnum] = useState([]) - const {accessData,state} = useGlobalContext() - const getSystemSubscriber = ()=>{ - return fetchData>('service/subscribers',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['apply_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - return {data:data.subscribers, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) - } - - const getMemberList = async ()=>{ - setMemberValueEnum([]) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - setMemberValueEnum(data.members) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - } - - const manualReloadTable = () => { - pageListRef.current?.reload() - }; - - const deleteSubscriber = (entity:SystemSubscriberTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('service/subscriber',{method:'DELETE',eoParams:{application:entity!.id,service:entity!.service.id,team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const openModal =async (type:'delete'|'add',entity?:SystemSubscriberTableListItem)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'add': - title=$t('新增订阅方') - content= - break; - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; - } - - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ - case 'add': - return addRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) - case 'delete': - return deleteSubscriber(entity!).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - disabled : !checkAccess( `team.service.subscription.${type}`, accessData) - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:1, - fixed:'right', - valueType: 'option', - render: (_: React.ReactNode, entity: SystemSubscriberTableListItem) => [ - {openModal('delete',entity)}} btnTitle="删除"/>, - ], - } - ] - - useEffect(() => { - setBreadcrumb([ - { - title:{$t('服务')} - }, - { - title:$t('订阅方管理') - } - ]) - getMemberList() - manualReloadTable() - }, [serviceId]); - - const columns = useMemo(()=>{ - return [...SYSTEM_SUBSCRIBER_TABLE_COLUMNS].map(x=>{ - if(x.filters &&((x.dataIndex as string[])?.indexOf('applier') !== -1 || (x.dataIndex as string[])?.indexOf('approver') !== -1) ){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - memberValueEnum?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - x.valueEnum = tmpValueEnum - } - if(x.dataIndex === 'from'){ - x.valueEnum = new Map([ - [0,{$t('手动添加')}], - [1,{$t('订阅申请')}], - ]) - } - return { - ...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title} - } - ) - },[memberValueEnum,state.language]) - - return ( - getSystemSubscriber()} - // dataSource={tableListDataSource} - showPagination={false} - addNewBtnTitle={$t("新增订阅方")} - onAddNewBtnClick={()=>{openModal('add')}} - addNewBtnAccess="team.service.subscription.add" - tableClass="pr-PAGE_INSIDE_X" - /> +const SystemInsideSubscriber: FC = () => { + const { setBreadcrumb } = useBreadcrumb() + const { modal, message } = App.useApp() + const { fetchData } = useFetch() + const { serviceId, teamId } = useParams() + const addRef = useRef(null) + const pageListRef = useRef(null) + const [memberValueEnum, setMemberValueEnum] = useState([]) + const { accessData, state } = useGlobalContext() + const getSystemSubscriber = () => { + return fetchData>( + 'service/subscribers', + { + method: 'GET', + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['apply_time'] + } ) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + return { data: data.subscribers, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } + } + }) + .catch(() => { + return { data: [], success: false } + }) + } + + const getMemberList = async () => { + setMemberValueEnum([]) + const { code, data, msg } = await fetchData>( + 'simple/member', + { method: 'GET' } + ) + if (code === STATUS_CODE.SUCCESS) { + setMemberValueEnum(data.members) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + const manualReloadTable = () => { + pageListRef.current?.reload() + } + + const deleteSubscriber = (entity: SystemSubscriberTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>('service/subscriber', { + method: 'DELETE', + eoParams: { application: entity!.id, service: entity!.service.id, team: teamId } + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch(errorInfo => reject(errorInfo)) + }) + } + + const openModal = async (type: 'delete' | 'add', entity?: SystemSubscriberTableListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'add': + title = $t('新增订阅方') + content = + break + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break + } + + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'add': + return addRef.current?.save().then(res => { + if (res === true) manualReloadTable() + }) + case 'delete': + return deleteSubscriber(entity!).then(res => { + if (res === true) manualReloadTable() + }) + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: !checkAccess(`team.service.subscription.${type}`, accessData) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 1, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: SystemSubscriberTableListItem) => [ + { + openModal('delete', entity) + }} + btnTitle="删除" + /> + ] + } + ] + + useEffect(() => { + setBreadcrumb([ + { + title: {$t('服务')} + }, + { + title: $t('订阅方管理') + } + ]) + getMemberList() + manualReloadTable() + }, [serviceId]) + + const columns = useMemo(() => { + return [...SYSTEM_SUBSCRIBER_TABLE_COLUMNS].map(x => { + if ( + x.filters && + ((x.dataIndex as string[])?.indexOf('applier') !== -1 || + (x.dataIndex as string[])?.indexOf('approver') !== -1) + ) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + memberValueEnum?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + x.valueEnum = tmpValueEnum + } + if (x.dataIndex === 'from') { + x.valueEnum = new Map([ + [0, {$t('手动添加')}], + [1, {$t('订阅申请')}] + ]) + } + return { + ...x, + title: typeof x.title === 'string' ? $t(x.title as string) : x.title + } + }) + }, [memberValueEnum, state.language]) + + return ( + getSystemSubscriber()} + // dataSource={tableListDataSource} + showPagination={false} + addNewBtnTitle={$t('新增订阅方')} + onAddNewBtnClick={() => { + openModal('add') + }} + addNewBtnAccess="team.service.subscription.add" + tableClass="pr-PAGE_INSIDE_X" + /> + ) } export default SystemInsideSubscriber -export const SystemSubscriberConfig = forwardRef((props, ref) => { - const { message } = App.useApp() - const { serviceId, teamId} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const [systemOptionList, setSystemOptionList] = useState() - const save:()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>('service/subscriber',{method:'POST',eoBody:({...value}), eoParams:{service:serviceId,team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - useImperativeHandle(ref, ()=>({ - save - }) - ) - - - const getSystemList = ()=>{ - setSystemOptionList([]) - fetchData>('simple/apps/mine',{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const teamMap = new Map(); - data.apps - .filter((x:SimpleSystemItem)=>x.id !== serviceId) - .forEach((item:SimpleSystemItem) => { - if (!teamMap.has(item.team.id)) { - teamMap.set(item.team.id, { - title: item.team.name, - value: item.team.id, - key: item.team.id, - children: [], - selectable: false, // 第一级不可选 - disabled:true - }); - } - - teamMap.get(item.team.id)!.children!.push({ - title: item.name, - value: item.id, - key: item.id, - selectable: true, // 子级可选 - // partition:item.partition?.map((x:EntityItem)=>x.id) || [] - }); - }); - setSystemOptionList(Array.from(teamMap.values())) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) +export const SystemSubscriberConfig = forwardRef< + SystemSubscriberConfigHandle, + SystemSubscriberConfigProps +>((props, ref) => { + const { message } = App.useApp() + const { serviceId, teamId } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() + const [systemOptionList, setSystemOptionList] = useState() + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then(value => { + fetchData>('service/subscriber', { + method: 'POST', + eoBody: { ...value }, + eoParams: { service: serviceId, team: teamId } + }).then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) } + }) }) - } + .catch(errorInfo => reject(errorInfo)) + }) + } + useImperativeHandle(ref, () => ({ + save + })) - useEffect(() => { - getSystemList() - }, [serviceId]); + const getSystemList = () => { + setSystemOptionList([]) + fetchData>('simple/apps/mine', { + method: 'GET' + }).then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const teamMap = new Map() + data.apps + .filter((x: SimpleSystemItem) => x.id !== serviceId) + .forEach((item: SimpleSystemItem) => { + if (!teamMap.has(item.team.id)) { + teamMap.set(item.team.id, { + title: item.team.name, + value: item.team.id, + key: item.team.id, + children: [], + selectable: false, // 第一级不可选 + disabled: true + }) + } - return ( -
x.id) || [] + }) + }) + setSystemOptionList(Array.from(teamMap.values())) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + useEffect(() => { + getSystemList() + }, [serviceId]) + + return ( + + + + label={$t('订阅方')} + name="application" + rules={[{ required: true }]} > - - label={$t("订阅方")} - name="application" - rules={[{ required: true }]} - > - - - - - ) -}) \ No newline at end of file + + + +
+ ) +}) diff --git a/frontend/packages/core/src/pages/system/SystemList.tsx b/frontend/packages/core/src/pages/system/SystemList.tsx index fa906a19..e397ab58 100644 --- a/frontend/packages/core/src/pages/system/SystemList.tsx +++ b/frontend/packages/core/src/pages/system/SystemList.tsx @@ -1,187 +1,213 @@ -import PageList from "@common/components/aoplatform/PageList.tsx" -import Tour from "@common/components/aoplatform/Tour.tsx" -import {ActionType} from "@ant-design/pro-components"; -import {FC, useEffect, useMemo, useRef, useState} from "react"; -import {useNavigate} from "react-router-dom"; -import { App} from "antd"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { SimpleTeamItem ,SimpleMemberItem} from "@common/const/type.ts"; -import { SystemConfigHandle, SystemTableListItem } from "../../const/system/type.ts"; -import { SERVICE_KIND_OPTIONS, SYSTEM_TABLE_COLUMNS } from "../../const/system/const.tsx"; -import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx"; -import SystemConfig from "./SystemConfig.tsx"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { $t } from "@common/locales/index.ts"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; +import { ActionType } from '@ant-design/pro-components' +import { DrawerWithFooter } from '@common/components/aoplatform/DrawerWithFooter.tsx' +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import PageList from '@common/components/aoplatform/PageList.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { SimpleMemberItem, SimpleTeamItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { App } from 'antd' +import { FC, useEffect, useMemo, useRef, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { SERVICE_KIND_OPTIONS, SYSTEM_TABLE_COLUMNS } from '../../const/system/const.tsx' +import { SystemConfigHandle, SystemTableListItem } from '../../const/system/type.ts' +import SystemConfig from './SystemConfig.tsx' -const SystemList:FC = ()=>{ - const navigate = useNavigate(); - const [tableSearchWord, setTableSearchWord] = useState('') - const { setBreadcrumb } = useBreadcrumb() - const [teamList, setTeamList] = useState<{ [k: string]: { text: string; }; }>() - const {fetchData} = useFetch() - const [tableListDataSource, setTableListDataSource] = useState([]); - const [tableHttpReload, setTableHttpReload] = useState(true); - const { message } = App.useApp() - const pageListRef = useRef(null); - const [memberValueEnum, setMemberValueEnum] = useState<{[k:string]:{text:string}}>({}) - const [open, setOpen] = useState(false); - const drawerFormRef = useRef(null) - const {checkPermission,accessInit, getGlobalAccessData,state} = useGlobalContext() +const SystemList: FC = () => { + const navigate = useNavigate() + const [tableSearchWord, setTableSearchWord] = useState('') + const { setBreadcrumb } = useBreadcrumb() + const [teamList, setTeamList] = useState<{ [k: string]: { text: string } }>() + const { fetchData } = useFetch() + const [tableListDataSource, setTableListDataSource] = useState([]) + const [tableHttpReload, setTableHttpReload] = useState(true) + const { message } = App.useApp() + const pageListRef = useRef(null) + const [memberValueEnum, setMemberValueEnum] = useState<{ [k: string]: { text: string } }>({}) + const [open, setOpen] = useState(false) + const drawerFormRef = useRef(null) + const { checkPermission, accessInit, getGlobalAccessData, state } = useGlobalContext() - const getSystemList = ()=>{ - if(!accessInit){ - getGlobalAccessData()?.then?.(()=>{ - getSystemList() - }) - return Promise.resolve({data:[], success:false}) - } - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - return fetchData>(!checkPermission('system.workspace.service.view_all') ? 'my_services':'services',{method:'GET',eoParams:{keyword:tableSearchWord},eoTransformKeys:['api_num','service_num','create_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTableListDataSource(data.services) - setTableHttpReload(false) - return {data:data.services, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) + const getSystemList = () => { + if (!accessInit) { + getGlobalAccessData()?.then?.(() => { + getSystemList() + }) + return Promise.resolve({ data: [], success: false }) } - - const getTeamsList = ()=>{ - if(!accessInit){ - getGlobalAccessData()?.then?.(()=>{ - getTeamsList() - }) - return - } - fetchData>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:[]}).then(response=>{ - const {code,data,msg} = response - setTeamList(data.teams) - if(code === STATUS_CODE.SUCCESS){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - data.teams?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - setTeamList(tmpValueEnum) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }) + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) } - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - const getMemberList = async ()=>{ - setMemberValueEnum({}) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - data.members?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - setMemberValueEnum(tmpValueEnum) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - } - - useEffect(() => { - getTeamsList(); - getMemberList() - setBreadcrumb([ - { - title: $t('服务') - }]) - }, []); - - const onClose = () => { - setOpen(false); - }; - - const columns = useMemo(()=>{ - const res = SYSTEM_TABLE_COLUMNS.map(x=>{ - const dataIndex=(x.dataIndex as string[]); - - if(x.filters &&dataIndex?.indexOf('master') !== -1 ){ - x.valueEnum = memberValueEnum - } - if(x.filters &&dataIndex?.indexOf('team') !== -1 ){ - x.valueEnum = teamList - } - if((x.dataIndex as string)==='service_kind'){ - x.valueEnum={}; - SERVICE_KIND_OPTIONS - .forEach(option => { - (x.valueEnum as any)[option.value] = { text: $t(option.label) }; - }); - } - - return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}}) - return res - },[memberValueEnum,teamList,state.language]) - - const steps = [ - { - target: '.my-first-step', - content: '点击按钮新建服务', - }, - { - target: '.ant-table-tbody', - content: '点击表格查看详情', - placement: 'top' - }, - ]; - - return ( - - - getSystemList()} - addNewBtnTitle={$t("添加服务")} - addNewBtnWrapperClass={'my-first-step'} - searchPlaceholder={$t("输入名称、ID、所属团队、负责人查找服务")} - onAddNewBtnClick={() => { - setOpen(true) - }} - manualReloadTable={manualReloadTable} - onChange={() => { - setTableHttpReload(false) - }} - onSearchWordChange={(e) => { - setTableSearchWord(e.target.value) - }} - onRowClick={(row:SystemTableListItem)=>navigate(`/service/${row.team.id}/${row.service_kind==='ai'?'aiInside':'inside'}/${row.id}`)} - /> - drawerFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res})} > - - - + return fetchData>( + !checkPermission('system.workspace.service.view_all') ? 'my_services' : 'services', + { + method: 'GET', + eoParams: { keyword: tableSearchWord }, + eoTransformKeys: ['api_num', 'service_num', 'create_time'] + } ) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.services) + setTableHttpReload(false) + return { data: data.services, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } + } + }) + .catch(() => { + return { data: [], success: false } + }) + } + const getTeamsList = () => { + if (!accessInit) { + getGlobalAccessData()?.then?.(() => { + getTeamsList() + }) + return + } + fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { method: 'GET', eoTransformKeys: [] } + ).then((response) => { + const { code, data, msg } = response + setTeamList(data.teams) + if (code === STATUS_CODE.SUCCESS) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + data.teams?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + setTeamList(tmpValueEnum) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } + } + }) + } + + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } + + const getMemberList = async () => { + setMemberValueEnum({}) + const { code, data, msg } = await fetchData>('simple/member', { + method: 'GET' + }) + if (code === STATUS_CODE.SUCCESS) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + data.members?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + setMemberValueEnum(tmpValueEnum) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + useEffect(() => { + getTeamsList() + getMemberList() + setBreadcrumb([ + { + title: $t('服务') + } + ]) + }, []) + + const onClose = () => { + setOpen(false) + } + + const columns = useMemo(() => { + const res = SYSTEM_TABLE_COLUMNS.map((x) => { + const dataIndex = x.dataIndex as string[] + + if (x.filters && dataIndex?.indexOf('master') !== -1) { + x.valueEnum = memberValueEnum + } + if (x.filters && dataIndex?.indexOf('team') !== -1) { + x.valueEnum = teamList + } + if ((x.dataIndex as string) === 'service_kind') { + x.valueEnum = {} + SERVICE_KIND_OPTIONS.forEach((option) => { + ;(x.valueEnum as any)[option.value] = { text: $t(option.label) } + }) + } + + return { ...x, title: typeof x.title === 'string' ? $t(x.title as string) : x.title } + }) + return res + }, [memberValueEnum, teamList, state.language]) + + const steps = [ + { + target: '.my-first-step', + content: '点击按钮新建服务' + }, + { + target: '.ant-table-tbody', + content: '点击表格查看详情', + placement: 'top' + } + ] + + return ( + + getSystemList()} + addNewBtnTitle={$t('添加服务')} + addNewBtnWrapperClass={'my-first-step'} + searchPlaceholder={$t('输入名称、ID、所属团队、负责人查找服务')} + onAddNewBtnClick={() => { + setOpen(true) + }} + manualReloadTable={manualReloadTable} + onChange={() => { + setTableHttpReload(false) + }} + onSearchWordChange={(e) => { + setTableSearchWord(e.target.value) + }} + onRowClick={(row: SystemTableListItem) => + navigate(`/service/${row.team.id}/${row.service_kind === 'ai' ? 'aiInside' : 'inside'}/${row.id}`) + } + /> + + drawerFormRef.current?.save()?.then((res) => { + res && manualReloadTable() + return res + }) + } + > + + + + ) } -export default SystemList \ No newline at end of file +export default SystemList diff --git a/frontend/packages/core/src/pages/system/SystemOutlet.tsx b/frontend/packages/core/src/pages/system/SystemOutlet.tsx index 61647c4a..a0071d06 100644 --- a/frontend/packages/core/src/pages/system/SystemOutlet.tsx +++ b/frontend/packages/core/src/pages/system/SystemOutlet.tsx @@ -1,20 +1,18 @@ +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { useEffect } from 'react' +import { Outlet, useParams } from 'react-router-dom' -import { Outlet, useParams } from "react-router-dom" -import { RouterParams } from "@core/components/aoplatform/RenderRoutes" -import { useEffect } from "react" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" +export default function SystemOutlet() { + const { teamId } = useParams() + const { getTeamAccessData, cleanTeamAccessData } = useGlobalContext() -export default function SystemOutlet(){ - const {teamId} = useParams() - const {getTeamAccessData,cleanTeamAccessData} = useGlobalContext() + useEffect(() => { + teamId ? getTeamAccessData(teamId) : cleanTeamAccessData() + return () => { + cleanTeamAccessData() + } + }, [teamId]) - useEffect(()=>{ - teamId ? getTeamAccessData(teamId) : cleanTeamAccessData() - return ()=>{ - cleanTeamAccessData() - } - },[teamId]) - - - return () -} \ No newline at end of file + return +} diff --git a/frontend/packages/core/src/pages/system/SystemTopology.tsx b/frontend/packages/core/src/pages/system/SystemTopology.tsx index d8f34429..42ad6b95 100644 --- a/frontend/packages/core/src/pages/system/SystemTopology.tsx +++ b/frontend/packages/core/src/pages/system/SystemTopology.tsx @@ -1,187 +1,192 @@ - -import G6, { EdgeConfig, Graph, NodeConfig } from "@antv/g6"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { RELATIVE_PICTURE_NODE_FONTSIZE } from "../../const/system-running/const"; -import { EntityItem } from "@common/const/type"; -import { GraphData } from "../../const/system-running/type"; -import { getNodeSpacing } from "@common/utils/systemRunning"; -import { Link, useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { useFetch } from "@common/hooks/http"; -import { App, Button } from "antd"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { useSystemContext } from "../../contexts/SystemContext"; -import { ZoomInOutlined, ZoomOutOutlined } from "@ant-design/icons"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import { debounce } from "lodash-es"; -import { SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP } from "../../const/system/const"; -import { SystemTopologyResponse } from "../../const/system/type"; -import { $t } from "@common/locales"; - +import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons' +import G6, { EdgeConfig, Graph, NodeConfig } from '@antv/g6' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { EntityItem } from '@common/const/type' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { getNodeSpacing } from '@common/utils/systemRunning' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { App, Button } from 'antd' +import { debounce } from 'lodash-es' +import { useCallback, useEffect, useRef, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import { RELATIVE_PICTURE_NODE_FONTSIZE } from '../../const/system-running/const' +import { GraphData } from '../../const/system-running/type' +import { SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP } from '../../const/system/const' +import { SystemTopologyResponse } from '../../const/system/type' +import { useSystemContext } from '../../contexts/SystemContext' export default function SystemTopology() { - const {message} = App.useApp() - const {serviceId, teamId} = useParams() - const graphContainerRef = useRef(null); - const graphRef = useRef(null); - const [graphData, setGraphData] = useState(); - const [graph, setGraph] = useState(null); - const {fetchData} = useFetch() - const { systemInfo } = useSystemContext() - const {setBreadcrumb} = useBreadcrumb() - const [zoomNum, setZoomNum] = useState(1) + const { message } = App.useApp() + const { serviceId, teamId } = useParams() + const graphContainerRef = useRef(null) + const graphRef = useRef(null) + const [graphData, setGraphData] = useState() + const [graph, setGraph] = useState(null) + const { fetchData } = useFetch() + const { systemInfo } = useSystemContext() + const { setBreadcrumb } = useBreadcrumb() + const [zoomNum, setZoomNum] = useState(1) - const getNodeData = ()=>{ - fetchData>('service/topology',{method:'GET',eoParams:{service:serviceId,team:teamId}},).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setGraphData(transformData({...data,currentSystem:{id:serviceId,name:systemInfo?.name || ''}})) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - - const initGraph = ()=>{ - return new G6.Graph({ - container: graphContainerRef.current!, - layout: { - type: 'dagre', - ranksep: 20, - controlPoints: true, - preventOverlap: true, - padding:[20,20,20,20], - nodeStrength: (d: unknown) => { - if (d.isLeaf) { - return -50 - } - return -10 - }, - fitCenter:true - }, - defaultNode: { - size: [20, 20], - style: { - radius: 5, - lineWidth: 1, - fillOpacity: 1 - }, - labelCfg: { - style: { - fontSize: RELATIVE_PICTURE_NODE_FONTSIZE, - fill: '#666' - }, - position: 'bottom', - offset: 12 + const getNodeData = () => { + fetchData>('service/topology', { + method: 'GET', + eoParams: { service: serviceId, team: teamId } + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setGraphData(transformData({ ...data, currentSystem: { id: serviceId, name: systemInfo?.name || '' } })) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const initGraph = () => { + return new G6.Graph({ + container: graphContainerRef.current!, + layout: { + type: 'dagre', + ranksep: 20, + controlPoints: true, + preventOverlap: true, + padding: [20, 20, 20, 20], + nodeStrength: (d: unknown) => { + if (d.isLeaf) { + return -50 } + return -10 }, - defaultEdge: { - type: 'cubic-vertical', + fitCenter: true + }, + defaultNode: { + size: [20, 20], + style: { + radius: 5, + lineWidth: 1, + fillOpacity: 1 + }, + labelCfg: { style: { - radius: 20, - offset: 45, - endArrow: true, - lineWidth: 2, + fontSize: RELATIVE_PICTURE_NODE_FONTSIZE, + fill: '#666' }, - }, - modes: { - default: [ - 'drag-canvas', - 'zoom-canvas', - 'drag-node', - ], + position: 'bottom', + offset: 12 } - }); + }, + defaultEdge: { + type: 'cubic-vertical', + style: { + radius: 20, + offset: 45, + endArrow: true, + lineWidth: 2 + } + }, + modes: { + default: ['drag-canvas', 'zoom-canvas', 'drag-node'] + } + }) + } + + const handleWindowResize = useCallback( + debounce(() => { + if (graphContainerRef.current && graphRef.current && !graphRef.current?.get('destroyed')) { + graphRef.current.changeSize(graphContainerRef.current.offsetWidth, graphContainerRef.current.offsetHeight) + graphRef.current?.fitCenter() + } + }, 400), + [] + ) + + useEffect(() => { + getNodeData() + setBreadcrumb([ + { + title: {$t('服务')} + }, + { + title: $t('调用拓扑图') + } + ]) + }, [serviceId]) + + useEffect(() => { + // 初始化 G6 图 + const graph = initGraph() + graph.setMaxZoom(3) + graph.setMinZoom(0.2) + setGraph(graph) + ;// eslint-disable-next-line @typescript-eslint/ban-ts-comment + (graphRef as any).current = graph + + graph.fitCenter() + handleWindowResize() + // 添加窗口大小变化的监听器 + window.addEventListener('resize', handleWindowResize) + + // 组件卸载时清理资源 + return () => { + window.removeEventListener('resize', handleWindowResize) + if (graphRef.current) { + graphRef.current.destroy() + } + } + }, []) + + useEffect(() => { + if (!graph) return + graph.clear() + graph.data(graphData) + const { nodes } = graphData as GraphData + + if (nodes?.length) { + graph.updateLayout({ + nodeSpacing: getNodeSpacing(nodes.length) + }) } - - const handleWindowResize = useCallback(debounce(() => { - if (graphContainerRef.current && graphRef.current && !graphRef.current?.get('destroyed')) { - graphRef.current.changeSize( - graphContainerRef.current.offsetWidth, - graphContainerRef.current.offsetHeight, - ); - graphRef.current?.fitCenter() - } - }, 400), []); + graph.render() + setTimeout(() => { + graph.fitCenter() + }) + }, [graphData]) - useEffect(()=>{ - getNodeData() - setBreadcrumb([ - { - title: {$t('服务')} - }, - { - title: $t('调用拓扑图') - }]) - },[serviceId]) + /** + * @description 更新缩放比例 + */ + const updateZoomTo = (increase: boolean) => { + if ((increase && zoomNum * 10 >= 20) || (!increase && zoomNum * 10 <= 2)) return + setZoomNum(increase ? (zoomNum * 10 + 2) / 10 : (zoomNum * 10 - 2) / 10) + graph?.zoomTo(increase ? (zoomNum * 10 + 2) / 10 : (zoomNum * 10 - 2) / 10) + } - useEffect(() => { - // 初始化 G6 图 - const graph = initGraph() - graph.setMaxZoom(3) - graph.setMinZoom(0.2) - setGraph(graph) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - graphRef.current = graph; - - graph.fitCenter(); - handleWindowResize() - // 添加窗口大小变化的监听器 - window.addEventListener('resize', handleWindowResize); - - // 组件卸载时清理资源 - return () => { - window.removeEventListener('resize', handleWindowResize); - if (graphRef.current) { - graphRef.current.destroy(); - } - }; - }, []); - - - useEffect(()=>{ - if(!graph) return - graph.clear() - graph.data(graphData) - const { nodes } = graphData as GraphData - - if (nodes?.length) { - graph.updateLayout({ - nodeSpacing: getNodeSpacing(nodes.length) - }) - } - - graph.render() - setTimeout(()=>{ - graph.fitCenter();}) - },[graphData]) - - - /** - * @description 更新缩放比例 - */ - const updateZoomTo = (increase:boolean) =>{ - if((increase && zoomNum*10 >= 20 )||(!increase && zoomNum*10 <= 2)) return - setZoomNum(increase ?( zoomNum*10 + 2)/10 : (zoomNum*10 - 2)/10) - graph?.zoomTo(increase ? ( zoomNum*10 + 2)/10 : (zoomNum*10 - 2)/10) - } - - return ( -
+ return ( +
-
-
-
-
-
-
+
+
+
+
-
+
+
+
{/*
{Object.entries(SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP).map(([_, config]) => (
@@ -194,12 +199,15 @@ export default function SystemTopology() { {config.name}
))}
*/}
- ) + ) } -function transformData(response: SystemTopologyResponse & {currentSystem: EntityItem}): { nodes: NodeConfig[], edges: EdgeConfig[] } { - const nodes: NodeConfig[] = []; - const edges: EdgeConfig[] = []; +function transformData(response: SystemTopologyResponse & { currentSystem: EntityItem }): { + nodes: NodeConfig[] + edges: EdgeConfig[] +} { + const nodes: NodeConfig[] = [] + const edges: EdgeConfig[] = [] // 添加当前服务节点 nodes.push({ @@ -207,10 +215,10 @@ function transformData(response: SystemTopologyResponse & {currentSystem: Entity label: `${response.currentSystem.name}`, type: 'curProject', style: { - // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.curProject.fill, - // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.curProject.stroke, - }, - }); + // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.curProject.fill, + // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.curProject.stroke, + } + }) // response.services.forEach(service =>{ // // nodes.push({ @@ -218,8 +226,8 @@ function transformData(response: SystemTopologyResponse & {currentSystem: Entity // // label: `${service.name}`, // // type: 'subscriberService', // // style: { - // // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberService.fill, - // // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberService.stroke, + // // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberService.fill, + // // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberService.stroke, // // }, // // }); // edges.push({ @@ -236,28 +244,28 @@ function transformData(response: SystemTopologyResponse & {currentSystem: Entity // }) // 添加订阅者节点和边 - response.subscribers.forEach(subscriber => { + response.subscribers.forEach((subscriber) => { nodes.push({ id: subscriber.service.id, label: `${subscriber.service.name}`, type: 'subscriberProject', style: { - fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.fill, - stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.stroke, - }, - }); - subscriber.services.forEach(serviceData => { - const service = response.services.find(s => s.id === serviceData.id); + fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.fill, + stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.stroke + } + }) + subscriber.services.forEach((serviceData) => { + const service = response.services.find((s) => s.id === serviceData.id) if (service) { edges.push({ source: subscriber.service.id, target: response.currentSystem.id, type: 'subscribes', - style:{ - endArrow:true, - stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.stroke, // 订阅服务节点使用蓝色 + style: { + endArrow: true, + stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.subscriberProject.stroke // 订阅服务节点使用蓝色 } - }); + }) // edges.push({ // source: subscriber.service.id, // target: service.id, @@ -275,41 +283,41 @@ function transformData(response: SystemTopologyResponse & {currentSystem: Entity // } // }); } - }); - }); + }) + }) // 添加调用者节点和边 - response.invoke?.forEach(invoker => { + response.invoke?.forEach((invoker) => { nodes.push({ id: invoker.service.id, label: `${invoker.service.name}`, type: 'invokeProject', style: { - fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.fill, - stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.stroke, - }, - }); - - edges.push({ - source: response.currentSystem.id, - target: invoker.service.id, - type: 'subscribes', - style:{ - endArrow:true, - stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.stroke, // 订阅服务节点使用蓝色 - } - }); + fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.fill, + stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.stroke + } + }) + + edges.push({ + source: response.currentSystem.id, + target: invoker.service.id, + type: 'subscribes', + style: { + endArrow: true, + stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeProject.stroke // 订阅服务节点使用蓝色 + } + }) // invoker.services.forEach(serviceData => { // nodes.push({ // id: serviceData.id, // label: `服务: ${serviceData.name}`, // type: 'invokeService', // style: { - // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeService.fill, - // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeService.stroke, + // fill: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeService.fill, + // stroke: SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP.invokeService.stroke, // }, // }); - + // edges.push({ // source: response.currentSystem.id, // target: serviceData.id, @@ -329,8 +337,7 @@ function transformData(response: SystemTopologyResponse & {currentSystem: Entity // } // }); // }); - }); + }) - - return { nodes, edges }; -} \ No newline at end of file + return { nodes, edges } +} diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx index 8991cdcd..814a496e 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx @@ -1,149 +1,224 @@ - -import {forwardRef, useEffect, useState} from "react"; -import { Button, Empty, Spin, Upload, message} from "antd"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { SystemApiDetail, SystemInsideApiDocumentHandle, SystemInsideApiDocumentProps } from "../../../const/system/type.ts"; -import { LoadingOutlined } from "@ant-design/icons"; +import { LoadingOutlined } from '@ant-design/icons' import EmptySVG from '@common/assets/empty.svg' -import { $t } from "@common/locales/index.ts"; import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx' -import { Codebox } from "@common/components/postcat/api/Codebox/index.tsx"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { Codebox } from '@common/components/postcat/api/Codebox/index.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { Button, Empty, Spin, Upload, message } from 'antd' +import { forwardRef, useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { + SystemApiDetail, + SystemInsideApiDocumentHandle, + SystemInsideApiDocumentProps +} from '../../../const/system/type.ts' -const SystemInsideApiDocument = forwardRef((props, ref) => { - const {serviceId, teamId} = useParams() - const {fetchData} = useFetch() - const [apiDetail, setApiDetail] = useState() - const [loading, setLoading] = useState(false) - const [showEditor, setShowEditor] = useState(false) +const SystemInsideApiDocument = forwardRef< + SystemInsideApiDocumentHandle, + SystemInsideApiDocumentProps +>(() => { + const { serviceId, teamId } = useParams() + const { fetchData } = useFetch() + const [apiDetail, setApiDetail] = useState() + const [loading, setLoading] = useState(false) + const [showEditor, setShowEditor] = useState(false) + useEffect(() => { + getApiDetail() + }, []) + + const getApiDetail = () => { + setLoading(true) + fetchData>('service/api_doc', { + method: 'GET', + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['update_time'] + }) + .then(response => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setApiDetail(data.doc?.content) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setLoading(false) + }) + } + + const UploadBtn = ({ type, updated }: { type: 'new' | 'edit'; updated: () => void }) => { + const { fetchData } = useFetch() + + const uploadFile = (file: File) => { + const body = new FormData() + body.append('doc', file) + fetchData>('service/api_doc/upload', { + method: 'POST', + body: body, + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['update_time'] + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + updated?.() + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setLoading(false) + }) + } + return ( + + { + uploadFile(file) + return false + }} + > + + + + ) + } + + const ApiEdit = ({ spec, updated }: { spec?: string | object; updated: () => void }) => { + const [code, setCode] = useState('') + const [saveLoading, setSaveLoading] = useState(false) useEffect(() => { - getApiDetail() - }, []); + try { + setCode(typeof spec === 'string' ? spec : JSON.stringify(spec)) + } catch (e) { + console.warn('文档解析失败', e) + } + }, [spec]) - const getApiDetail = ()=>{ - setLoading(true) - fetchData>('service/api_doc',{method:'GET',eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setApiDetail(data.doc?.content) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{setLoading(false)}) + const saveCode = () => { + setSaveLoading(true) + fetchData>('service/api_doc', { + method: 'PUT', + eoBody: { content: code }, + eoParams: { service: serviceId, team: teamId }, + eoTransformKeys: ['update_time'] + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + updated?.() + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + setSaveLoading(false) + }) } - - - const UploadBtn = ({type, updated}:{type:'new'|'edit', updated:()=>void})=>{ - const {fetchData} = useFetch() - - const uploadFile = (file:File)=>{ - const body = new FormData() - body.append('doc',file) - fetchData>('service/api_doc/upload',{method:'POST',body:body,eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - updated?.() - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{setLoading(false)}) - - } - return ( - - { - uploadFile(file) - return false; - }}> - - - - ) - } - - const ApiEdit = ({spec,updated}:{spec?:string|object,updated:()=>void})=>{ - const [code, setCode] = useState('') - const [saveLoading, setSaveLoading] = useState(false) - useEffect(()=>{ - try{ - setCode(typeof spec === 'string' ? spec:JSON.stringify(spec) ) - }catch(e){ - console.warn('文档解析失败',e) - } - },[spec]) - - const saveCode = ()=>{ - setSaveLoading(true) - fetchData>('service/api_doc',{method:'PUT',eoBody:{content:code},eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - updated?.() - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{setSaveLoading(false)}) - } - - return ( -
-
- - - - -
-
-
- -
-
{ - !code ? : }
-
-
) - } - - const ApiPreview = ({setShowEditor,spec,updated}:{setShowEditor:(show:boolean)=>void,spec?:string | object, updated:()=>void})=>{ - return (
-
- - - - -
-
- -
+ return ( +
+
+ + + +
- ) - } +
+
+ +
+
+ {!code ? : } +
+
+
+ ) + } - const updated = ()=>{ - getApiDetail(); setShowEditor(false) - } + const ApiPreview = ({ + setShowEditor, + spec, + updated + }: { + setShowEditor: (show: boolean) => void + spec?: string | object + updated: () => void + }) => { + return ( +
+
+ + + + +
+
+ +
+
+ ) + } - return (<> - } spinning={loading} wrapperClassName=' h-full overflow-hidden '> -
- {!showEditor && apiDetail && } - - {showEditor && } - {!showEditor && !apiDetail && -
- - - - -
-
} -
-
- ) + const updated = () => { + getApiDetail() + setShowEditor(false) + } + + return ( + <> + } + spinning={loading} + wrapperClassName=" h-full overflow-hidden " + > +
+ {!showEditor && apiDetail && ( + + )} + + {showEditor && } + {!showEditor && !apiDetail && ( + +
+ + + + +
+
+ )} +
+
+ + ) }) -export default SystemInsideApiDocument \ No newline at end of file +export default SystemInsideApiDocument diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx index a316ed40..ae51f4cb 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx @@ -1,251 +1,322 @@ -import {App, Button, Col, Form, Input, Row, Select, Space, Spin, Switch} from "antd"; -import {forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react"; -import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { API_PATH_MATCH_RULES, API_PROTOCOL, HTTP_METHOD, MATCH_CONFIG, MatchPositionEnum, MatchTypeEnum } from "../../../const/system/const.tsx"; -import { SystemInsideRouterCreateHandle, SystemInsideRouterCreateProps, SystemApiProxyFieldType, SystemInsideApiProxyHandle } from "../../../const/system/type.ts"; -import { MatchItem, RouterParams } from "@common/const/type.ts"; -import { validateUrlSlash } from "@common/utils/validate.ts"; -import { $t } from "@common/locales/index.ts"; -import SystemInsideApiProxy from "@core/pages/system/api/SystemInsideApiProxy.tsx"; -import { LoadingOutlined } from "@ant-design/icons"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { useNavigate, useParams } from "react-router-dom"; -import { useSystemContext } from "@core/contexts/SystemContext.tsx"; -import InsidePage from "@common/components/aoplatform/InsidePage.tsx"; +import { LoadingOutlined } from '@ant-design/icons' +import EditableTableWithModal from '@common/components/aoplatform/EditableTableWithModal.tsx' +import InsidePage from '@common/components/aoplatform/InsidePage.tsx' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { MatchItem, RouterParams } from '@common/const/type.ts' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { validateUrlSlash } from '@common/utils/validate.ts' +import { useSystemContext } from '@core/contexts/SystemContext.tsx' +import SystemInsideApiProxy from '@core/pages/system/api/SystemInsideApiProxy.tsx' +import { App, Button, Col, Form, Input, Row, Select, Space, Spin, Switch } from 'antd' +import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { + API_PATH_MATCH_RULES, + API_PROTOCOL, + HTTP_METHOD, + MATCH_CONFIG, + MatchPositionEnum, + MatchTypeEnum +} from '../../../const/system/const.tsx' +import { + SystemApiProxyFieldType, + SystemInsideApiProxyHandle, + SystemInsideRouterCreateHandle, + SystemInsideRouterCreateProps +} from '../../../const/system/type.ts' -const SystemInsideRouterCreate = forwardRef((props, ref) => { +const SystemInsideRouterCreate = forwardRef( + (props, ref) => { const { message } = App.useApp() - const {serviceId, teamId, routeId} = useParams() - const [form] = Form.useForm(); - const {fetchData} = useFetch() + const { serviceId, teamId, routeId } = useParams() + const [form] = Form.useForm() + const { fetchData } = useFetch() const [loading, setLoading] = useState(false) const proxyRef = useRef(null) const { state } = useGlobalContext() - const {apiPrefix, prefixForce} = useSystemContext() + const { apiPrefix, prefixForce } = useSystemContext() const navigator = useNavigate() - const onFinish = ()=>{ - return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([,formValue])=>{ - const body = {...formValue, - path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`, - proxy:{...formValue.proxy,path:formValue.proxy.path ? (formValue.proxy.path.startsWith('/')? formValue.proxy.path: '/'+ formValue.proxy.path) : undefined}} - return fetchData>('service/router',{ - method: routeId ? 'PUT' :'POST' ,eoBody:(body), eoParams: {service:serviceId,team:teamId, router:routeId },eoTransformKeys:['matchType','disable']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - navigator(`/service/${teamId}/inside/${serviceId}/route`) - return Promise.resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(errInfo=>Promise.reject(errInfo)) + const onFinish = () => { + return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([, formValue]) => { + const body = { + ...formValue, + path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`, + proxy: { + ...formValue.proxy, + path: formValue.proxy.path + ? formValue.proxy.path.startsWith('/') + ? formValue.proxy.path + : '/' + formValue.proxy.path + : undefined + } + } + return fetchData>('service/router', { + method: routeId ? 'PUT' : 'POST', + eoBody: body, + eoParams: { service: serviceId, team: teamId, router: routeId }, + eoTransformKeys: ['matchType', 'disable'] }) - } - - const copy: ()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - return form.validateFields().then((value)=>{ - fetchData>('service/api/copy',{method:'POST',eoParams:{service:serviceId,team:teamId, api:routeId},eoBody:({...value,path:value.path.trim()})}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - return resolve(data.api.id) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - useImperativeHandle(ref, ()=>({ - copy, - save:onFinish - }) - ) - - const getRouterConfig = ()=>{ - setLoading(true) - fetchData>('service/router/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, router:routeId}, eoTransformKeys:['create_time','update_time','match_type','upstream_id','opt_type']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const {disable, protocols, path, methods, description, match, proxy} = data.router - let newPath = path - let pathMatch = 'full' - if(prefixForce && path?.startsWith(apiPrefix + '/')){ - newPath = path.slice((apiPrefix?.length || 0) + 1) - } - if(newPath.endsWith('/*')){ - newPath = newPath.slice(0,-2) - pathMatch = 'prefix' - } - form.setFieldsValue({ - disable, - protocols, - path:newPath, - pathMatch, - methods, - description, - match, - proxy - }) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + navigator(`/service/${teamId}/inside/${serviceId}/route`) + return Promise.resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) } - }).catch((errorInfo)=> console.error(errorInfo)) - .finally(()=>setLoading(false)) + }) + .catch((errInfo) => Promise.reject(errInfo)) + }) + } + + const copy: () => Promise = () => { + return new Promise((resolve, reject) => { + return form + .validateFields() + .then((value) => { + fetchData>('service/api/copy', { + method: 'POST', + eoParams: { service: serviceId, team: teamId, api: routeId }, + eoBody: { ...value, path: value.path.trim() } + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + return resolve(data.api.id) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + useImperativeHandle(ref, () => ({ + copy, + save: onFinish + })) + + const getRouterConfig = () => { + setLoading(true) + fetchData>('service/router/detail', { + method: 'GET', + eoParams: { service: serviceId, team: teamId, router: routeId }, + eoTransformKeys: ['create_time', 'update_time', 'match_type', 'upstream_id', 'opt_type'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const { disable, protocols, path, methods, description, match, proxy } = data.router + let newPath = path + let pathMatch = 'full' + if (prefixForce && path?.startsWith(apiPrefix + '/')) { + newPath = path.slice((apiPrefix?.length || 0) + 1) + } + if (newPath.endsWith('/*')) { + newPath = newPath.slice(0, -2) + pathMatch = 'prefix' + } + form.setFieldsValue({ + disable, + protocols, + path: newPath, + pathMatch, + methods, + description, + match, + proxy + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => console.error(errorInfo)) + .finally(() => setLoading(false)) } useEffect(() => { - if(routeId){ - getRouterConfig() - }else{ - form.setFieldValue('prefix',apiPrefix) - form.setFieldValue(['proxy','timeout'],10000) - form.setFieldValue(['proxy','retry'],0) - form.setFieldValue('protocols',['HTTP','HTTPS']) - form.setFieldValue('pathMatch','prefix') + if (routeId) { + getRouterConfig() + } else { + form.setFieldValue('prefix', apiPrefix) + form.setFieldValue(['proxy', 'timeout'], 10000) + form.setFieldValue(['proxy', 'retry'], 0) + form.setFieldValue('protocols', ['HTTP', 'HTTPS']) + form.setFieldValue('pathMatch', 'prefix') + } + return form.setFieldsValue({}) + }, []) + + const translatedMatchConfig = useMemo(() => { + return MATCH_CONFIG.map((item) => { + if (item.key === 'position') { + return { + ...item, + component: ( + { - return { label:$t(value), value:key} - })}/>}) - } - if(item.key === 'matchType'){ - return ({...item, component: { + return { label: $t(value), value: key } + })} + /> + ) + } + } + return { ...item } + }) }, [state.language]) - - - const apiPathMatchRulesOptions = useMemo(()=>API_PATH_MATCH_RULES.map( - x=>({label:$t(x.label), value:x.value})),[state.language]) + + const apiPathMatchRulesOptions = useMemo( + () => API_PATH_MATCH_RULES.map((x) => ({ label: $t(x.label), value: x.value })), + [state.language] + ) return ( - - -
- }> - } spinning={loading} className=''> -
-
-
{$t('API 基础信息')} - - label={$t("拦截该接口的请求")} - name="disable" - extra={$t('开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。')} - > - - - - - label={$t("请求协议")} - name="protocols" - rules={[{ required: true }]} - > - - - - - - { - if((e.target.value as string).endsWith('/*')){ - form.setFieldValue('path',e.target.value.slice(0,-2)) - form.setFieldValue('pathMatch','prefix') - } - }}/> - - - + + + + } + > + } spinning={loading} className=""> + +
+ + {' '} +
+ {$t('API 基础信息')} + + + + label={$t('拦截该接口的请求')} + name="disable" + extra={$t('开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。')} + > + + - - label={$t("请求方式")} - name="methods" - rules={[{ required: true }]} - > - - + label={$t('请求协议')} name="protocols" rules={[{ required: true }]}> + + + + + + { + if ((e.target.value as string).endsWith('/*')) { + form.setFieldValue('path', e.target.value.slice(0, -2)) + form.setFieldValue('pathMatch', 'prefix') + } + }} + /> + + + - - label={$t("描述")} - name="description" - > - - + label={$t('请求方式')} name="methods" rules={[{ required: true }]}> + + - - label={$t("高级匹配")} - name="match" - > - - configFields={translatedMatchConfig} - /> - + label={$t('描述')} name="description"> + + - {$t('转发规则设置')} - - className="mb-0 bg-transparent border-none p-0" - name="proxy" - > - - - - - - + label={$t('高级匹配')} name="match"> + configFields={translatedMatchConfig} /> + + + + + {$t('转发规则设置')} + + + className="p-0 mb-0 bg-transparent border-none" name="proxy"> + + + + + + ) -}) -export default SystemInsideRouterCreate \ No newline at end of file + } +) +export default SystemInsideRouterCreate diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx index b033c321..be903d1a 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx @@ -1,221 +1,224 @@ -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx" -import {ActionType} from "@ant-design/pro-components"; -import {FC, useEffect, useMemo, useRef, useState} from "react"; -import {Link, useNavigate, useParams} from "react-router-dom"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {App, Divider} from "antd"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import { SimpleMemberItem} from '@common/const/type.ts' -import {useFetch} from "@common/hooks/http.ts"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import { SYSTEM_API_TABLE_COLUMNS } from "../../../const/system/const.tsx"; -import {SystemApiTableListItem } from "../../../const/system/type.ts"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; -import { checkAccess } from "@common/utils/permission.ts"; -import { $t } from "@common/locales/index.ts"; +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { SimpleMemberItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { checkAccess } from '@common/utils/permission.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { App, Divider, Typography } from 'antd' +import { FC, useEffect, useMemo, useRef, useState } from 'react' +import { Link, useNavigate, useParams } from 'react-router-dom' +import { SYSTEM_API_TABLE_COLUMNS } from '../../../const/system/const.tsx' +import { SystemApiTableListItem } from '../../../const/system/type.ts' -const SystemInsideRouterList:FC = ()=>{ - const [searchWord, setSearchWord] = useState('') - const { setBreadcrumb } = useBreadcrumb() - const { modal,message } = App.useApp() - const [tableListDataSource, setTableListDataSource] = useState([]); - const [tableHttpReload, setTableHttpReload] = useState(true); - const {fetchData} = useFetch() - const pageListRef = useRef(null); - const [memberValueEnum, setMemberValueEnum] = useState([]) - const {accessData,state} = useGlobalContext() - const {serviceId, teamId} = useParams() - const navigator = useNavigate() +const SystemInsideRouterList: FC = () => { + const [searchWord, setSearchWord] = useState('') + const { setBreadcrumb } = useBreadcrumb() + const { modal, message } = App.useApp() + const [tableListDataSource, setTableListDataSource] = useState([]) + const [tableHttpReload, setTableHttpReload] = useState(true) + const { fetchData } = useFetch() + const pageListRef = useRef(null) + const [memberValueEnum, setMemberValueEnum] = useState([]) + const { accessData, state } = useGlobalContext() + const { serviceId, teamId } = useParams() + const navigator = useNavigate() - const getRoutesList = (): Promise<{ data: SystemApiTableListItem[], success: boolean }>=> { - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - - return fetchData>('service/routers',{method:'GET',eoParams:{service:serviceId,team:teamId, keyword:searchWord},eoTransformKeys:['request_path','create_time','update_time','disable']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTableListDataSource(data.routers) - setTableHttpReload(false) - return {data:data.routers, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) + const getRoutesList = (): Promise<{ data: SystemApiTableListItem[]; success: boolean }> => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) } - const deleteRoute = (entity:SystemApiTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('service/router',{method:'DELETE',eoParams:{service:serviceId,team:teamId, router:entity!.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const openModal = async (type: 'delete',entity:SystemApiTableListItem) =>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; + return fetchData>('service/routers', { + method: 'GET', + eoParams: { service: serviceId, team: teamId, keyword: searchWord }, + eoTransformKeys: ['request_path', 'create_time', 'update_time', 'disable'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.routers) + setTableHttpReload(false) + return { data: data.routers, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } + }) + .catch(() => { + return { data: [], success: false } + }) + } - modal.confirm({ - title, - content, - onOk:()=> { - switch (type){ - case 'delete': - return deleteRoute(entity).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - disabled : !checkAccess( `team.service.router.${type}`, accessData ) - }, - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:2, - fixed:'right', - valueType: 'option', - render: (_: React.ReactNode, entity: SystemApiTableListItem) => [ - {navigator(`/service/${teamId}/inside/${serviceId}/route/${entity.id}`)}} btnTitle="编辑"/>, - , - {openModal('delete',entity)}} btnTitle="删除"/>, - ], - } - ] - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - const getMemberList = async ()=>{ - setMemberValueEnum([]) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - setMemberValueEnum(data.members) - }else{ + const deleteRoute = (entity: SystemApiTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>('service/router', { + method: 'DELETE', + eoParams: { service: serviceId, team: teamId, router: entity!.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { message.error(msg || $t(RESPONSE_TIPS.error)) - } + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const openModal = async (type: 'delete', entity: SystemApiTableListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break } - // const openDrawer = (type:'add'|'edit'|'view',entity?:SystemApiTableListItem)=>{ - // setCurApi(entity) - // setDrawerType(type) - // } + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'delete': + return deleteRoute(entity).then((res) => { + if (res === true) manualReloadTable() + }) + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: !checkAccess(`team.service.router.${type}`, accessData) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } - // useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType]) + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 2, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: SystemApiTableListItem) => [ + { + navigator(`/service/${teamId}/inside/${serviceId}/route/${entity.id}`) + }} + btnTitle="编辑" + />, + , + { + openModal('delete', entity) + }} + btnTitle="删除" + /> + ] + } + ] - useEffect(() => { - setBreadcrumb([ - { - title:{$t('服务')} - }, - { - title:$t('路由') - } - ]) - getMemberList() - manualReloadTable() - }, [serviceId]); + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } - // const onClose = () => { - // setDrawerType(undefined); - // setCurApi(undefined) - // }; - - const columns = useMemo(()=>{ - return [...SYSTEM_API_TABLE_COLUMNS].map(x=>{ - if(x.filters &&((x.dataIndex as string[])?.indexOf('creator') !== -1) ){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - memberValueEnum?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - x.valueEnum = tmpValueEnum - } - - if(x.filters &&((x.dataIndex as string[])?.indexOf('disable') !== -1) ){ - x.valueEnum = { - true:{text:{$t('拦截')}}, - false:{text:{$t('放行')}} - } - } - return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}}) - },[memberValueEnum,state.language]) + const getMemberList = async () => { + setMemberValueEnum([]) + const { code, data, msg } = await fetchData>('simple/member', { + method: 'GET' + }) + if (code === STATUS_CODE.SUCCESS) { + setMemberValueEnum(data.members) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } - // const handlerSubmit:() => Promise|undefined= ()=>{ - // switch(drawerType){ - // case 'add':{ - // return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res}) - // } - // case 'edit':{ - // return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res}) - // } - // default:return undefined - // } - // } + useEffect(() => { + setBreadcrumb([ + { + title: {$t('服务')} + }, + { + title: $t('路由') + } + ]) + getMemberList() + manualReloadTable() + }, [serviceId]) - return ( - <> - getRoutesList()} - dataSource={tableListDataSource} - addNewBtnTitle={$t('添加路由')} - searchPlaceholder={$t('输入 URL 查找路由')} - // onAddNewBtnClick={()=>{openDrawer('add')}} - onAddNewBtnClick={()=>{navigator(`/service/${teamId}/inside/${serviceId}/route/create`)}} - addNewBtnAccess="team.service.router.add" - tableClickAccess="team.service.router.view" - manualReloadTable={manualReloadTable} - onSearchWordChange={(e)=>{setSearchWord(e.target.value)}} - onChange={() => { - setTableHttpReload(false) - }} - onRowClick={(row:SystemApiTableListItem)=>navigator(`/service/${teamId}/inside/${serviceId}/route/${row.id}`)} - tableClass="mr-PAGE_INSIDE_X " - /> - {/* handlerSubmit()} - showOkBtn={drawerType !== 'view'} - > - - */} - - ) + const columns = useMemo(() => { + return [...SYSTEM_API_TABLE_COLUMNS].map((x) => { + if (x.filters && (x.dataIndex as string[])?.indexOf('creator') !== -1) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + memberValueEnum?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + x.valueEnum = tmpValueEnum + } + if (x.filters && (x.dataIndex as string[])?.indexOf('disable') !== -1) { + x.valueEnum = { + true: { text: {$t('拦截')} }, + false: { text: {$t('放行')} } + } + } + return { ...x, title: typeof x.title === 'string' ? $t(x.title as string) : x.title } + }) + }, [memberValueEnum, state.language]) + + return ( + <> + getRoutesList()} + dataSource={tableListDataSource} + addNewBtnTitle={$t('添加路由')} + searchPlaceholder={$t('输入 URL 查找路由')} + onAddNewBtnClick={() => { + navigator(`/service/${teamId}/inside/${serviceId}/route/create`) + }} + addNewBtnAccess="team.service.router.add" + tableClickAccess="team.service.router.view" + manualReloadTable={manualReloadTable} + onSearchWordChange={(e) => { + setSearchWord(e.target.value) + }} + onChange={() => { + setTableHttpReload(false) + }} + onRowClick={(row: SystemApiTableListItem) => + navigator(`/service/${teamId}/inside/${serviceId}/route/${row.id}`) + } + tableClass="mr-PAGE_INSIDE_X " + /> + + ) } -export default SystemInsideRouterList \ No newline at end of file +export default SystemInsideRouterList diff --git a/frontend/packages/core/src/pages/userProfile/ChangePsw.tsx b/frontend/packages/core/src/pages/userProfile/ChangePsw.tsx index 8a89e42e..58fcf859 100644 --- a/frontend/packages/core/src/pages/userProfile/ChangePsw.tsx +++ b/frontend/packages/core/src/pages/userProfile/ChangePsw.tsx @@ -1,108 +1,103 @@ +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { App, Button, Form, Input } from 'antd' -import { App, Button, Form, Input } from "antd"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const"; -import { useFetch } from "@common/hooks/http.ts"; -import { $t } from "@common/locales"; +const ChangePsw = () => { + const { message } = App.useApp() + const { fetchData } = useFetch() + const [form] = Form.useForm() -const ChangePsw= () => { - const { message } = App.useApp() - const {fetchData} = useFetch() - const [form] = Form.useForm(); + const savePsw = () => { + form.validateFields().then(value => { + return fetchData>('account/password/reset', { + method: 'PUT', + eoBody: { ...value } + }) + .then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + form.resetFields() + }) + .catch(errorInfo => { + console.warn(errorInfo) + }) + }) + } + return ( +
+ +
+ + + + + + - const savePsw = ()=>{ - form.validateFields().then((value)=>{ - return fetchData>( - 'account/password/reset', - { - method:'PUT', - eoBody:({...value}), - }).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - form.resetFields() - }).catch((errorInfo)=> {console.warn(errorInfo)}) - }) - } + ({ + validator(_, value) { + if (!value || getFieldValue('new_password') === value) { + return Promise.resolve() + } + return Promise.reject(new Error($t('两次密码不一致'))) + } + }) + ]} + > + + - - return ( -
- - - - - - - - - - - ({ - validator(_, value) { - if (!value || getFieldValue('new_password') === value) { - return Promise.resolve(); - } - return Promise.reject(new Error($t('两次密码不一致'))); - }, - }), - ]} - > - - - - - - - - -
- ) + + + + + + +
+
+ ) } -export default ChangePsw \ No newline at end of file +export default ChangePsw diff --git a/frontend/packages/core/tsconfig.json b/frontend/packages/core/tsconfig.json index b2dc4117..7a1a2b77 100644 --- a/frontend/packages/core/tsconfig.json +++ b/frontend/packages/core/tsconfig.json @@ -1,4 +1,3 @@ - { "compilerOptions": { "target": "ES2020", @@ -6,6 +5,7 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, + "typeRoots": ["./node_modules/@types", "../common/src/types"], /* Bundler mode */ "moduleResolution": "bundler", diff --git a/frontend/packages/dashboard/src/component/MonitorApiPage.tsx b/frontend/packages/dashboard/src/component/MonitorApiPage.tsx index 67d157b8..5bb5d2d4 100644 --- a/frontend/packages/dashboard/src/component/MonitorApiPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorApiPage.tsx @@ -1,228 +1,316 @@ -import { CloseOutlined, ExpandOutlined, SearchOutlined } from "@ant-design/icons"; -import { Select, Input, Button, App, Drawer } from "antd"; -import { debounce } from "lodash-es"; -import { useState, useEffect, useRef } from "react"; -import { MonitorApiData, SearchBody } from "@dashboard/const/type"; -import { getTime } from "../utils/dashboard"; -import ScrollableSection from "@common/components/aoplatform/ScrollableSection"; -import TimeRangeSelector, { RangeValue, TimeRange, TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; -import MonitorTable, { MonitorTableHandler } from "./MonitorTable"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { DefaultOptionType } from "antd/es/select"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { useExcelExport } from "@common/hooks/excel"; -import { API_TABLE_GLOBAL_COLUMNS_CONFIG } from "@dashboard/const/const"; -import { useFetch } from "@common/hooks/http"; -import { EntityItem } from "@common/const/type"; -import { $t } from "@common/locales"; +import { CloseOutlined, ExpandOutlined, SearchOutlined } from '@ant-design/icons' +import ScrollableSection from '@common/components/aoplatform/ScrollableSection' +import TimeRangeSelector, { + RangeValue, + TimeRange, + TimeRangeButton +} from '@common/components/aoplatform/TimeRangeSelector' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { EntityItem } from '@common/const/type' +import { useExcelExport } from '@common/hooks/excel' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { API_TABLE_GLOBAL_COLUMNS_CONFIG } from '@dashboard/const/const' +import { MonitorApiData, SearchBody } from '@dashboard/const/type' +import { App, Button, Drawer, Input, Select } from 'antd' +import { DefaultOptionType } from 'antd/es/select' +import { debounce } from 'lodash-es' +import { useEffect, useRef, useState } from 'react' +import { getTime } from '../utils/dashboard' +import MonitorTable, { MonitorTableHandler } from './MonitorTable' export type MonitorApiPageProps = { - fetchTableData:(body:SearchBody)=>Promise> - detailDrawerContent:React.ReactNode - fullScreen?:boolean - setFullScreen?:(val:boolean) => void - setDetailId:(val:string) =>void - setTimeButton:(val:TimeRangeButton) => void - timeButton:TimeRangeButton - setDetailEntityName:(name:string) => void - detailEntityName:string + fetchTableData: (body: SearchBody) => Promise> + detailDrawerContent: React.ReactNode + fullScreen?: boolean + setFullScreen?: (val: boolean) => void + setDetailId: (val: string) => void + setTimeButton: (val: TimeRangeButton) => void + timeButton: TimeRangeButton + setDetailEntityName: (name: string) => void + detailEntityName: string } -export type MonitorApiQueryData = SearchBody & { path?:string, apis?:string[], services?:string[] } +export type MonitorApiQueryData = SearchBody & { path?: string; apis?: string[]; services?: string[] } -export default function MonitorApiPage(props:MonitorApiPageProps){ - const {fetchTableData,detailDrawerContent,fullScreen,setFullScreen,setDetailId,timeButton,setTimeButton,detailEntityName,setDetailEntityName} = props - const {message} = App.useApp() - const [datePickerValue, setDatePickerValue] = useState(); - const [queryData, setQueryData] = useState(); - const [exportLoading, setExportLoading] = useState(false); - const monitorApiTableRef = useRef(null) - const {exportExcel} = useExcelExport() - const [drawerOpen, setDrawerOpen] = useState(false); - const [apiOptionList, setApiOptionList] = useState([]) - const [projectOptionList, setProjectOptionList] = useState([]) - const [queryBtnLoading, setQueryBtnLoading] = useState(false) - const {fetchData} = useFetch() +export default function MonitorApiPage(props: MonitorApiPageProps) { + const { + fetchTableData, + detailDrawerContent, + fullScreen, + setFullScreen, + setDetailId, + timeButton, + setTimeButton, + detailEntityName, + setDetailEntityName + } = props + const { message } = App.useApp() + const [datePickerValue, setDatePickerValue] = useState() + const [queryData, setQueryData] = useState() + const [exportLoading, setExportLoading] = useState(false) + const monitorApiTableRef = useRef(null) + const { exportExcel } = useExcelExport() + const [drawerOpen, setDrawerOpen] = useState(false) + const [apiOptionList, setApiOptionList] = useState([]) + const [projectOptionList, setProjectOptionList] = useState([]) + const [queryBtnLoading, setQueryBtnLoading] = useState(false) + const { fetchData } = useFetch() - useEffect(() => { - getMonitorData(); - getApiList(); - getProjectList(); - }, []); + useEffect(() => { + getMonitorData() + getApiList() + getProjectList() + }, []) - const getApiList = (projectIds?:string[])=>{ - return fetchData<{apis:EntityItem[]}>('simple/service/apis',{method:'POST',eoBody:({services:projectIds || queryData?.services})}).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - setApiOptionList(data.apis?.map((x:EntityItem)=>({label:x.name, value:x.id}))) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return setApiOptionList([]) - } - }).catch(() => { + const getApiList = (projectIds?: string[]) => { + return fetchData<{ apis: EntityItem[] }>('simple/service/apis', { + method: 'POST', + eoBody: { services: projectIds || queryData?.services } + }) + .then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + setApiOptionList(data.apis?.map((x: EntityItem) => ({ label: x.name, value: x.id }))) + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) return setApiOptionList([]) - }) - } - - const getProjectList = ()=>{ - return fetchData<{services:EntityItem[]}>('simple/services',{method:'GET'}).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - setProjectOptionList(data.services?.map((x:EntityItem)=>({label:x.name, value:x.id}))) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return setProjectOptionList([]) } - }).catch(() => { + }) + .catch(() => { + return setApiOptionList([]) + }) + } + + const getProjectList = () => { + return fetchData<{ services: EntityItem[] }>('simple/services', { method: 'GET' }) + .then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + setProjectOptionList(data.services?.map((x: EntityItem) => ({ label: x.name, value: x.id }))) + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) return setProjectOptionList([]) + } }) - } + .catch(() => { + return setProjectOptionList([]) + }) + } - const getMonitorData = () => { - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - }; - - const getApiTableList = () => { - // ...根据时间和集群获取监控数据... - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - monitorApiTableRef.current?.reload() - }; - - const exportData = () => { - setExportLoading(true); - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! ; - fetchTableData(data).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - exportExcel($t('API调用统计'), [query!.start!, query!.end!], $t('API调用统计'), 'dashboard_api', API_TABLE_GLOBAL_COLUMNS_CONFIG, data.statistics) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) + const getMonitorData = () => { + let query = queryData + if (!queryData || queryData.start === undefined) { + console.log(timeButton, datePickerValue) + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + setQueryData(data) + } + + const getApiTableList = () => { + // ...根据时间和集群获取监控数据... + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + setQueryData(data) + monitorApiTableRef.current?.reload() + } + + const exportData = () => { + setExportLoading(true) + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + fetchTableData(data).then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + exportExcel( + $t('API调用统计'), + [query!.start!, query!.end!], + $t('API调用统计'), + 'dashboard_api', + API_TABLE_GLOBAL_COLUMNS_CONFIG, + data.statistics + ) + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) + } + }) + } + + const clearSearch = () => { + setTimeButton('hour') + setDatePickerValue(null) + setQueryData(undefined) + } + + const handleTimeRangeChange = (timeRange: TimeRange) => { + setQueryData((pre) => ({ ...pre, ...timeRange }) as SearchBody) + } + + const getTablesData = (body: SearchBody) => { + return fetchTableData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + return { + data: data.statistics?.map((x: MonitorApiData) => { + x.proxyRate = Number((x.proxyRate * 100).toFixed(2)) + x.requestRate = Number((x.requestRate * 100).toFixed(2)) + return x + }), + success: true + } + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) + return { data: [], success: false } } }) - }; - - const clearSearch = () => { - setTimeButton('hour'); - setDatePickerValue(null) - setQueryData(undefined); - }; - - const handleTimeRangeChange = (timeRange:TimeRange) => { - setQueryData(pre => ({...pre, ...timeRange} as SearchBody )) - }; - - const getTablesData = (body: SearchBody) => { - return fetchTableData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if(code === STATUS_CODE.SUCCESS){ - return {data:data.statistics?.map((x:MonitorApiData)=>{x.proxyRate = Number((x.proxyRate*100).toFixed(2));x.requestRate = Number((x.requestRate*100).toFixed(2));return x}), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return {data:[], success:false} - } - }).catch(() => { - setQueryBtnLoading(false) - return {data:[], success:false} - }) - }; - - const getDetailData = (entity:MonitorApiData)=>{ - setDetailEntityName(entity.name) - setDetailId(entity.id) - setDrawerOpen(true) - } - - return ( -
- -
- -
- - {setQueryData(prevData=>({...prevData || {}, apis:value}))}} - /> - -
- {/* setQueryData({ ...queryData, path: '' })} /> */} - debounce((e)=>{setQueryData(prevData=>({...prevData || {}, path:e.target.value}))}, 100)(e)} allowClear placeholder={$t('请输入请求路径进行搜索')} prefix={}/> -
- - - -
+ .catch(() => { + setQueryBtnLoading(false) + return { data: [], success: false } + }) + } + + const getDetailData = (entity: MonitorApiData) => { + setDetailEntityName(entity.name) + setDetailId(entity.id) + setDrawerOpen(true) + } + + return ( +
+ +
+ +
+ + { + setQueryData((prevData) => ({ ...(prevData || {}), apis: value })) + }} + /> + +
+ {/* setQueryData({ ...queryData, path: '' })} /> */} + + debounce((e) => { + setQueryData((prevData) => ({ ...(prevData || {}), path: e.target.value })) + }, 100)(e) + } + allowClear + placeholder={$t('请输入请求路径进行搜索')} + prefix={} + /> +
+ + +
-
- {getDetailData(record); }} request={()=>getTablesData(queryData||{})} showPagination={true}/>
- - - - {fullScreen && {setFullScreen?.(false)}}> - {$t('退出全屏')} - } - {$t('(0)调用详情',[detailEntityName])} - {!fullScreen && {setFullScreen?.(true)}}/>} - } - width={fullScreen ? '100%' : '60%'} - onClose={()=>setDrawerOpen(false)} - open={drawerOpen}> - {detailDrawerContent} - -
- ) -} \ No newline at end of file +
+ { + getDetailData(record) + }} + request={() => getTablesData(queryData || {})} + showPagination={true} + /> +
+
+ + + {fullScreen && ( + { + setFullScreen?.(false) + }} + > + + {$t('退出全屏')} + + )} + {$t('(0)调用详情', [detailEntityName])} + {!fullScreen && ( + { + setFullScreen?.(true) + }} + /> + )} + + } + width={fullScreen ? '100%' : '60%'} + onClose={() => setDrawerOpen(false)} + open={drawerOpen} + > + {detailDrawerContent} + +
+ ) +} diff --git a/frontend/packages/dashboard/src/component/MonitorAppPage.tsx b/frontend/packages/dashboard/src/component/MonitorAppPage.tsx index 16de9a16..8bbc866a 100644 --- a/frontend/packages/dashboard/src/component/MonitorAppPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorAppPage.tsx @@ -1,193 +1,262 @@ -import { Select, Button, App, Drawer } from "antd"; -import { useEffect, useRef, useState } from "react"; -import { MonitorSubscriberData, SearchBody } from "@dashboard/const/type"; -import { EntityItem } from "@common/const/type"; -import TimeRangeSelector, { RangeValue, TimeRange, TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; -import MonitorTable, { MonitorTableHandler } from "./MonitorTable"; -import { DefaultOptionType } from "antd/es/select"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { getTime } from "../utils/dashboard"; -import { useExcelExport } from "@common/hooks/excel"; -import { APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG } from "@dashboard/const/const"; -import { CloseOutlined, ExpandOutlined } from "@ant-design/icons"; -import { useFetch } from "@common/hooks/http"; -import { MonitorSubQueryData } from "./MonitorSubPage"; -import { $t } from "@common/locales"; +import { Select, Button, App, Drawer } from 'antd' +import { useEffect, useRef, useState } from 'react' +import { MonitorSubscriberData, SearchBody } from '@dashboard/const/type' +import { EntityItem } from '@common/const/type' +import TimeRangeSelector, { + RangeValue, + TimeRange, + TimeRangeButton +} from '@common/components/aoplatform/TimeRangeSelector' +import MonitorTable, { MonitorTableHandler } from './MonitorTable' +import { DefaultOptionType } from 'antd/es/select' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { getTime } from '../utils/dashboard' +import { useExcelExport } from '@common/hooks/excel' +import { APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG } from '@dashboard/const/const' +import { CloseOutlined, ExpandOutlined } from '@ant-design/icons' +import { useFetch } from '@common/hooks/http' +import { MonitorSubQueryData } from './MonitorSubPage' +import { $t } from '@common/locales' export type MonitorAppPageProps = { - fetchTableData:(body:SearchBody)=>Promise> - fetchAppListData?:(body:SearchBody)=>Promise> - detailDrawerContent:React.ReactNode - fullScreen?:boolean - setFullScreen?:(val:boolean) => void - setDetailId:(val:string) =>void - setTimeButton:(val:TimeRangeButton) => void - timeButton:TimeRangeButton - setDetailEntityName:(name:string) => void - detailEntityName:string + fetchTableData: (body: SearchBody) => Promise> + fetchAppListData?: (body: SearchBody) => Promise> + detailDrawerContent: React.ReactNode + fullScreen?: boolean + setFullScreen?: (val: boolean) => void + setDetailId: (val: string) => void + setTimeButton: (val: TimeRangeButton) => void + timeButton: TimeRangeButton + setDetailEntityName: (name: string) => void + detailEntityName: string } -export default function MonitorAppPage(props:MonitorAppPageProps){ - const {fetchTableData,detailDrawerContent,fullScreen,setFullScreen,setDetailId,timeButton,setTimeButton,detailEntityName,setDetailEntityName} = props - const {message} = App.useApp() - const [queryData, setQueryData] = useState({type:'subscriber'}); - const [exportLoading, setExportLoading] = useState(false); - const [datePickerValue, setDatePickerValue] = useState(); - const monitorAppTableRef = useRef(null) - const {exportExcel} = useExcelExport() - const [drawerOpen, setDrawerOpen] = useState(false); - const [listOfApps, setListOfApps] = useState([]) - const {fetchData} = useFetch() - const [queryBtnLoading, setQueryBtnLoading] = useState(false) +export default function MonitorAppPage(props: MonitorAppPageProps) { + const { + fetchTableData, + detailDrawerContent, + fullScreen, + setFullScreen, + setDetailId, + timeButton, + setTimeButton, + detailEntityName, + setDetailEntityName + } = props + const { message } = App.useApp() + const [queryData, setQueryData] = useState({ type: 'subscriber' }) + const [exportLoading, setExportLoading] = useState(false) + const [datePickerValue, setDatePickerValue] = useState() + const monitorAppTableRef = useRef(null) + const { exportExcel } = useExcelExport() + const [drawerOpen, setDrawerOpen] = useState(false) + const [listOfApps, setListOfApps] = useState([]) + const { fetchData } = useFetch() + const [queryBtnLoading, setQueryBtnLoading] = useState(false) - useEffect(() => { - getMonitorData(); - getAppList() - }, []); + useEffect(() => { + getMonitorData() + getAppList() + }, []) - const getMonitorData = () => { - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - }; + const getMonitorData = () => { + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + setQueryData(data) + } - const getAppList = ()=>{ - return fetchData<{apps:EntityItem[]}>('simple/apps/mine',{method:'GET'}).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - setListOfApps(data.apps?.map((x:EntityItem)=>({label:x.name, value:x.id}))) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return setListOfApps([]) - } - }).catch(() => { + const getAppList = () => { + return fetchData<{ apps: EntityItem[] }>('simple/apps/mine', { method: 'GET' }) + .then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + setListOfApps(data.apps?.map((x: EntityItem) => ({ label: x.name, value: x.id }))) + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) return setListOfApps([]) + } }) - } - - const clearSearch = () => { - setTimeButton('hour'); - setDatePickerValue(null) - setQueryData({type:'subscriber'}); - } - - const getAppTableList = () => { - // ...根据时间和集群获取监控数据... - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - monitorAppTableRef.current?.reload() - }; - - - const exportData = () => { - setExportLoading(true); - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! ; - fetchTableData(data).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - exportExcel($t('消费者调用统计'), [query!.start!, query!.end!], $t('消费者调用统计'), 'dashboard_application', APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG, data.statistics) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }) - }; - - const handleTimeRangeChange = (timeRange:TimeRange) => { - setQueryData(pre => ({...pre, ...timeRange} as SearchBody )) - }; - - - const getTablesData = (body: SearchBody) => { - return fetchTableData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if(code === STATUS_CODE.SUCCESS){ - return {data:data.statistics?.map((x:MonitorSubscriberData)=>{x.proxyRate = Number((x.proxyRate*100).toFixed(2));x.requestRate = Number((x.requestRate*100).toFixed(2));return x}), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return {data:[], success:false} - } - }).catch(() => { - setQueryBtnLoading(false) - return {data:[], success:false} - }) - }; + .catch(() => { + return setListOfApps([]) + }) + } - - const getDetailData = (entity:MonitorSubscriberData)=>{ - setDetailEntityName(entity.name) - setDetailId(entity.id) - setDrawerOpen(true) - } + const clearSearch = () => { + setTimeButton('hour') + setDatePickerValue(null) + setQueryData({ type: 'subscriber' }) + } - return ( -
-
- -
-
- - `and ${selectedList.length} more selected`} + placeholder={$t('请选择消费者')} + value={queryData?.apps} + options={listOfApps} + onChange={(value) => { + setQueryData((prevData) => ({ ...(prevData || {}), apps: value })) + }} + />
-
- {getDetailData(record); }} request={()=>getTablesData(queryData||{})} showPagination={true}/> -
- - - {fullScreen && {setFullScreen?.(false)}}> - 退出全屏 - } - {detailEntityName}调用详情 - {!fullScreen && {setFullScreen?.(true)}}/>} - } - width={fullScreen ? '100%' : '60%'} - onClose={()=>setDrawerOpen(false)} - open={drawerOpen}> - {detailDrawerContent} - -
) -} \ No newline at end of file +
+ + + +
+
+
+
+ { + getDetailData(record) + }} + request={() => getTablesData(queryData || {})} + showPagination={true} + /> +
+ + + {fullScreen && ( + { + setFullScreen?.(false) + }} + > + + 退出全屏 + + )} + {detailEntityName}调用详情 + {!fullScreen && ( + { + setFullScreen?.(true) + }} + /> + )} + + } + width={fullScreen ? '100%' : '60%'} + onClose={() => setDrawerOpen(false)} + open={drawerOpen} + > + {detailDrawerContent} + +
+ ) +} diff --git a/frontend/packages/dashboard/src/component/MonitorDetailPage.tsx b/frontend/packages/dashboard/src/component/MonitorDetailPage.tsx index be394a3a..4b0e2c73 100644 --- a/frontend/packages/dashboard/src/component/MonitorDetailPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorDetailPage.tsx @@ -1,164 +1,236 @@ -import { Button, Modal, Empty, message, Checkbox, Radio } from "antd"; -import { useState, useEffect, useRef } from "react"; -import { InvokeData, MonitorApiData, MonitorSubscriberData, SearchBody } from "@dashboard/const/type"; -import TimeRangeSelector, { RangeValue, TimeRange, TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; -import MonitorLineGraph from "./MonitorLineGraph"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { getTime, getTimeUnit } from "../utils/dashboard"; -import MonitorTable, { MonitorTableHandler } from "./MonitorTable"; -import { DashboardDetailInvokeType } from "@dashboard/pages/DashboardDetail"; -import { MonitorApiQueryData } from "./MonitorApiPage"; -import { MonitorSubQueryData } from "./MonitorSubPage"; -import dayjs from "dayjs"; -import { $t } from "@common/locales"; +import { Button, Modal, Empty, message, Checkbox } from 'antd' +import { useState, useEffect, useRef } from 'react' +import { InvokeData, MonitorApiData, MonitorSubscriberData, SearchBody } from '@dashboard/const/type' +import TimeRangeSelector, { + RangeValue, + TimeRange, + TimeRangeButton +} from '@common/components/aoplatform/TimeRangeSelector' +import MonitorLineGraph from './MonitorLineGraph' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { getTime, getTimeUnit } from '../utils/dashboard' +import MonitorTable, { MonitorTableHandler } from './MonitorTable' +import { DashboardDetailInvokeType } from '@dashboard/pages/DashboardDetail' +import { MonitorApiQueryData } from './MonitorApiPage' +import { MonitorSubQueryData } from './MonitorSubPage' +import dayjs from 'dayjs' +import { $t } from '@common/locales' type MonitorDetailPageProps = { - fetchInvokeData:(body:SearchBody)=>Promise> - fetchTableData:(body:SearchBody)=>Promise> - fetchDetailInvokeData:(params:{[k:string]:string}, body:SearchBody)=>Promise> - dashboardType:'api'|'subscriber'|'provider' - tableType:'api'|'subscribers' - tableId:string - fullScreen:boolean - detailName:string - initialQueryData:(MonitorApiQueryData|MonitorSubQueryData) & {timeButton:TimeRangeButton} + fetchInvokeData: (body: SearchBody) => Promise> + fetchTableData: ( + body: SearchBody + ) => Promise> + fetchDetailInvokeData: ( + params: { [k: string]: string }, + body: SearchBody + ) => Promise> + dashboardType: 'api' | 'subscriber' | 'provider' + tableType: 'api' | 'subscribers' + tableId: string + fullScreen: boolean + detailName: string + initialQueryData: (MonitorApiQueryData | MonitorSubQueryData) & { timeButton: TimeRangeButton } } -export default function MonitorDetailPage(props:MonitorDetailPageProps){ - const {fetchInvokeData,fetchDetailInvokeData,fetchTableData,dashboardType,tableType,tableId,detailName,fullScreen,initialQueryData} = props - const [timeButton, setTimeButton] = useState<''|'hour'|'day'|'threeDays'|'sevenDays'>(initialQueryData.start ?initialQueryData?.timeButton : 'hour'); - const [datePickerValue, setDatePickerValue] = useState(!initialQueryData.timeButton && initialQueryData.start ? [dayjs.unix(Number(initialQueryData.start)), dayjs.unix(Number(initialQueryData.end))]:null); - const [queryData, setQueryData] = useState(initialQueryData.timeButton ? {timeButton:initialQueryData.timeButton,type:initialQueryData?.type} : initialQueryData); - // const [listOfServices, setListOfServices] = useState([]); - // const [listOfApis, setListOfApis] = useState([]); - const [compareTotal, setCompareTotal] = useState(false); - const [modalVisible, setModalVisible] = useState(false); - const [detailInvokeStatic, setDetailInvokeStatic] = useState(); - const [detailInvokeError,setDetailInvokeError] = useState(false) - const [invokeStatic, setInvokeStatic] = useState({ date: [], requestRate: [], requestTotal: [], proxyRate: [], proxyTotal: [], status_4xx: [], status_5xx: [] }); - const [timeUnit, setTimeUnit] = useState() - const [invokeStaticError,setInvokeStaticError] = useState(false) - const monitorTableRef = useRef(null) - const [modalTitle, setModalTitle] = useState($t('调用趋势')) - const [queryBtnLoading, setQueryBtnLoading] = useState(false) +export default function MonitorDetailPage(props: MonitorDetailPageProps) { + const { + fetchInvokeData, + fetchDetailInvokeData, + fetchTableData, + dashboardType, + tableType, + tableId, + detailName, + fullScreen, + initialQueryData + } = props + const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>( + initialQueryData.start ? initialQueryData?.timeButton : 'hour' + ) + const [datePickerValue, setDatePickerValue] = useState( + !initialQueryData.timeButton && initialQueryData.start + ? [dayjs.unix(Number(initialQueryData.start)), dayjs.unix(Number(initialQueryData.end))] + : null + ) + const [queryData, setQueryData] = useState( + initialQueryData.timeButton + ? { timeButton: initialQueryData.timeButton, type: initialQueryData?.type } + : initialQueryData + ) + // const [listOfServices, setListOfServices] = useState([]); + // const [listOfApis, setListOfApis] = useState([]); + const [compareTotal, setCompareTotal] = useState(false) + const [modalVisible, setModalVisible] = useState(false) + const [detailInvokeStatic, setDetailInvokeStatic] = useState() + const [detailInvokeError, setDetailInvokeError] = useState(false) + const [invokeStatic, setInvokeStatic] = useState({ + date: [], + requestRate: [], + requestTotal: [], + proxyRate: [], + proxyTotal: [], + status_4xx: [], + status_5xx: [] + }) + const [timeUnit, setTimeUnit] = useState() + const [invokeStaticError, setInvokeStaticError] = useState(false) + const monitorTableRef = useRef(null) + const [modalTitle, setModalTitle] = useState($t('调用趋势')) + const [queryBtnLoading, setQueryBtnLoading] = useState(false) - useEffect(() => { - // 初始化数据 - getMonitorData(); - }, []); + useEffect(() => { + // 初始化数据 + getMonitorData() + }, []) - const getMonitorData = () => { - // ...根据时间和集群获取监控数据... - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - getInvokeData(data) - monitorTableRef.current?.reload() - }; + const getMonitorData = () => { + // ...根据时间和集群获取监控数据... + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + setQueryData(data) + getInvokeData(data) + monitorTableRef.current?.reload() + } - const getInvokeData = (body: SearchBody) => { - fetchInvokeData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if (code === STATUS_CODE.SUCCESS) { - const { timeInterval, tendency } = data - setInvokeStatic(tendency) - setInvokeStaticError(false) - setTimeUnit((getTimeUnit(timeInterval!))) - // this.invokeLineRef?.changeLineChart() - }else{ - setInvokeStaticError(true) - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }).catch(()=>{setQueryBtnLoading(false)}) - }; - - const getTablesData = (body: SearchBody) => { - return fetchTableData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if(code === STATUS_CODE.SUCCESS){ - return {data:data.statistics?.map((x:(MonitorApiData|MonitorSubscriberData))=>{x.proxyRate = Number((x.proxyRate*100).toFixed(2));x.requestRate = Number((x.requestRate*100).toFixed(2));return x}), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return {data:[], success:false} - } - }).catch(() => { - setQueryBtnLoading(false) - return {data:[], success:false} - }) - }; - - - const clearSearch = () => { - setTimeButton('hour'); - setDatePickerValue(null) - setQueryData(null); - // monitorTableRef.current?.reload() - }; - - const openModal = (entity:MonitorApiData|MonitorSubscriberData) => { - fetchDetailInvokeData({id:entity.id},queryData!).then((resp) => { - const {code,data,msg} = resp + const getInvokeData = (body: SearchBody) => { + fetchInvokeData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) if (code === STATUS_CODE.SUCCESS) { const { timeInterval, tendency } = data - setDetailInvokeStatic(tendency) - setDetailInvokeError(false) - setTimeUnit((getTimeUnit(timeInterval!))) - setModalTitle($t('(0)-(1)调用趋势', [entity.name, detailName])) - setModalVisible(true); - }else{ + setInvokeStatic(tendency) + setInvokeStaticError(false) + setTimeUnit(getTimeUnit(timeInterval!)) + // this.invokeLineRef?.changeLineChart() + } else { setInvokeStaticError(true) message.error(msg || $t(RESPONSE_TIPS.dataError)) } - }) - }; + }) + .catch(() => { + setQueryBtnLoading(false) + }) + } - const handleCloseModal = () => { - setModalVisible(false); - setDetailInvokeError(false) - setDetailInvokeStatic(undefined) - setCompareTotal(false) - }; - - const handleTimeRangeChange = (timeRange:TimeRange) => { - setQueryData(pre => ({...pre, ...timeRange} as SearchBody )) - }; + const getTablesData = (body: SearchBody) => { + return fetchTableData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + return { + data: data.statistics?.map((x: MonitorApiData | MonitorSubscriberData) => { + x.proxyRate = Number((x.proxyRate * 100).toFixed(2)) + x.requestRate = Number((x.requestRate * 100).toFixed(2)) + return x + }), + success: true + } + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) + return { data: [], success: false } + } + }) + .catch(() => { + setQueryBtnLoading(false) + return { data: [], success: false } + }) + } + + const clearSearch = () => { + setTimeButton('hour') + setDatePickerValue(null) + setQueryData(null) + // monitorTableRef.current?.reload() + } + + const openModal = (entity: MonitorApiData | MonitorSubscriberData) => { + fetchDetailInvokeData({ id: entity.id }, queryData!).then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + const { timeInterval, tendency } = data + setDetailInvokeStatic(tendency) + setDetailInvokeError(false) + setTimeUnit(getTimeUnit(timeInterval!)) + setModalTitle($t('(0)-(1)调用趋势', [entity.name, detailName])) + setModalVisible(true) + } else { + setInvokeStaticError(true) + message.error(msg || $t(RESPONSE_TIPS.dataError)) + } + }) + } + + const handleCloseModal = () => { + setModalVisible(false) + setDetailInvokeError(false) + setDetailInvokeStatic(undefined) + setCompareTotal(false) + } + + const handleTimeRangeChange = (timeRange: TimeRange) => { + setQueryData((pre) => ({ ...pre, ...timeRange }) as SearchBody) + } return (
- + - + {$t('重置')} + +
-
+
{/* 这里应该添加图表组件 */} - {invokeStaticError ? : + ) : ( + } + /> + )}
-
- {openModal(record as MonitorApiData | MonitorSubscriberData)}} request={()=>getTablesData({...queryData||{}})} noTop={true} minVirtualHeight={300}/> +
+ { + openModal(record as MonitorApiData | MonitorSubscriberData) + }} + request={() => getTablesData({ ...(queryData || {}) })} + noTop={true} + minVirtualHeight={300} + />
-
-
{setCompareTotal(e.target.checked)}}>{$t('加入总体数据对比')}
- {(detailInvokeError||!modalVisible) ? : +
+ { + setCompareTotal(e.target.checked) + }} + > + {$t('加入总体数据对比')} + +
+ {detailInvokeError || !modalVisible ? ( + + ) : ( + } + /> + )} {/* 这里应该添加图表组件 */} - {compareTotal && <>{ - (invokeStaticError ||!modalVisible ) ? : - }} + {compareTotal && ( + <> + {invokeStaticError || !modalVisible ? ( + + ) : ( + + )} + + )}
- ); -} \ No newline at end of file + ) +} diff --git a/frontend/packages/dashboard/src/component/MonitorLineGraph.tsx b/frontend/packages/dashboard/src/component/MonitorLineGraph.tsx index 914587b3..bb053a0e 100644 --- a/frontend/packages/dashboard/src/component/MonitorLineGraph.tsx +++ b/frontend/packages/dashboard/src/component/MonitorLineGraph.tsx @@ -1,115 +1,142 @@ - -import { FC, useEffect, useMemo, useRef, useState } from 'react'; -import ECharts, { EChartsOption } from 'echarts-for-react'; -import { InvokeData, LineGraphType, MessageData } from '@dashboard/const/type'; -import { MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED, MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED, MONITOR_LINE_CHART_OPTION_CONFIG, MONITOR_NAME_MAP } from '@dashboard/const/const'; -import { yUnitFormatter } from '../utils/dashboard'; -import { $t } from '@common/locales'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; +import { FC, useEffect, useMemo, useRef, useState } from 'react' +import ECharts, { EChartsOption } from 'echarts-for-react' +import { InvokeData, LineGraphType, MessageData } from '@dashboard/const/type' +import { + MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED, + MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED, + MONITOR_LINE_CHART_OPTION_CONFIG, + MONITOR_NAME_MAP +} from '@dashboard/const/const' +import { yUnitFormatter } from '../utils/dashboard' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' type LineGraphProps = { - className?:string - lineData:InvokeData | MessageData - compareData?:InvokeData | MessageData - titles:string[] - yAxisTitle:string - compare?:boolean - type:LineGraphType - modalTitle?:string - dataTitle?:string + className?: string + lineData: InvokeData | MessageData + compareData?: InvokeData | MessageData + titles: string[] + yAxisTitle: string + compare?: boolean + type: LineGraphType + modalTitle?: string + dataTitle?: string } -const MonitorLineGraph: FC = ({ className, lineData, titles, yAxisTitle, compareData, compare, type, modalTitle, dataTitle }) => { - const [legendSelected, setLegendSelected] = useState>(type === 'traffic' ? {...MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED}:{...MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED}) - const chartRef = useRef(null); - const containerRef = useRef(null); - const {state} = useGlobalContext() - +const MonitorLineGraph: FC = ({ + className, + lineData, + titles, + yAxisTitle, + compareData, + compare, + type, + modalTitle, + dataTitle +}) => { + const [legendSelected, setLegendSelected] = useState>( + type === 'traffic' + ? { ...MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED } + : { ...MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED } + ) + const chartRef = useRef(null) + const containerRef = useRef(null) + const { state } = useGlobalContext() + const handleWindowResize = () => { // 直接使用引用中的实例和DOM元素 - const chartInstance = chartRef.current?.getEchartsInstance(); - const containerWidth = containerRef.current?.offsetWidth; - const containerHeight = containerRef.current?.offsetHeight; + const chartInstance = chartRef.current?.getEchartsInstance() + const containerWidth = containerRef.current?.offsetWidth + const containerHeight = containerRef.current?.offsetHeight if (chartInstance && containerWidth && containerHeight) { chartInstance.resize({ width: containerWidth, - height: containerHeight, - }); + height: containerHeight + }) } - }; + } - useEffect(() => { // 创建 ResizeObserver 实例并绑定到父元素 const resizeObserver = new ResizeObserver(() => { handleWindowResize() - }); + }) if (containerRef.current) { - resizeObserver.observe(containerRef.current); + resizeObserver.observe(containerRef.current) } // 清理函数:在组件卸载时移除监听器 return () => { if (containerRef.current) { - resizeObserver.unobserve(containerRef.current); + resizeObserver.unobserve(containerRef.current) } - }; - }, []); + } + }, []) + + const calculateYAxisInterval = ( + data: InvokeData | MessageData, + lineChartOption: EChartsOption, + yAxis: { [k: string]: unknown }, + legendSelected?: Record + ): { [k: string]: unknown } => { + const maxValues = data + ? Object.entries(data).reduce( + (acc, [key, value]) => { + if (key !== 'date' && key !== 'requestRate' && key !== 'proxyRate' && value instanceof Array) { + acc[key] = Math.max(...(value as number[])) + } + return acc + }, + {} as { [key: string]: number } + ) + : {} + + const selectedLegend = legendSelected ?? lineChartOption.legend?.selected ?? {} - const calculateYAxisInterval = (data: InvokeData | MessageData, lineChartOption: EChartsOption, yAxis: {[k:string]:unknown}, legendSelected?: Record): {[k:string]:unknown} => { - const maxValues =data ? Object.entries(data).reduce((acc, [key, value]) => { - if (key !== 'date' && key !== 'requestRate' && key !== 'proxyRate' && value instanceof Array) { - acc[key] = Math.max(...(value as number[])); - } - return acc; - }, {} as { [key: string]: number } ) : {}; - - const selectedLegend = legendSelected ?? (lineChartOption.legend?.selected ?? {}); - const filteredMaxValues = Object.entries(maxValues).reduce((arr, [key, max]) => { if (selectedLegend[MONITOR_NAME_MAP[key]]) { - arr.push(max); + arr.push(max) } - return arr; - }, [] as number[]); - - const maxNum = Math.max(...filteredMaxValues, 0); - + return arr + }, [] as number[]) + + const maxNum = Math.max(...filteredMaxValues, 0) + return { ...yAxis, interval: maxNum > 5 ? maxNum / 5 : 1, - max: maxNum > 5 ? maxNum : 5, - }; - }; + max: maxNum > 5 ? maxNum : 5 + } + } - const handleLegendSelectChange = (val:{selected:Record})=>{ + const handleLegendSelectChange = (val: { selected: Record }) => { setLegendSelected(val.selected) } - - const getTimeFormatter = (time:string)=>{ + + const getTimeFormatter = (time: string) => { switch (yAxisTitle) { case '分钟': { - return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : (new Date(time).getMonth() + 1)}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n ${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()}:${new Date(time).getMinutes() < 10 ? '0' + new Date(time).getMinutes() : new Date(time).getMinutes()}` + return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n ${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()}:${new Date(time).getMinutes() < 10 ? '0' + new Date(time).getMinutes() : new Date(time).getMinutes()}` } case '5分钟': { - return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : (new Date(time).getMonth() + 1)}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n ${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()}:${new Date(time).getMinutes() < 10 ? '0' + new Date(time).getMinutes() : new Date(time).getMinutes()} ` + return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n ${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()}:${new Date(time).getMinutes() < 10 ? '0' + new Date(time).getMinutes() : new Date(time).getMinutes()} ` } case '1小时': { - return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : (new Date(time).getMonth() + 1)}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()} ` + return `${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()} ` } case '1天': { - return `${new Date(time).getFullYear().toString().slice(2)}年-${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : (new Date(time).getMonth() + 1)}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} ` + return `${new Date(time).getFullYear().toString().slice(2)}年-${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} ` } case '1周': { - return `${new Date(time).getFullYear().toString().slice(2)}年-${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : (new Date(time).getMonth() + 1)}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} ` + return `${new Date(time).getFullYear().toString().slice(2)}年-${new Date(time).getMonth() < 9 ? '0' + (new Date(time).getMonth() + 1) : new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} ` } } return `${new Date(time).getMonth() + 1}/${new Date(time).getDate() < 10 ? '0' + new Date(time).getDate() : new Date(time).getDate()} \n${new Date(time).getHours() < 10 ? '0' + new Date(time).getHours() : new Date(time).getHours()}:${new Date(time).getMinutes() < 10 ? '0' + new Date(time).getMinutes() : new Date(time).getMinutes()}` } - const generateInvokeLineChartOption = ()=>({ + const generateInvokeLineChartOption = () => ({ ...MONITOR_LINE_CHART_OPTION_CONFIG, grid: { left: '26', @@ -120,12 +147,13 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx }, xAxis: { type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], axisLabel: { showMaxLabel: true, - formatter: (value:string) => { + formatter: (value: string) => { return value ? getTimeFormatter(value) : '' } }, @@ -134,68 +162,123 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx }, boundaryGap: false }, - yAxis: [{ - type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量',[yAxisTitle]) : '', - nameLocation: 'end', - nameTextStyle: { - align: 'left' + yAxis: [ + { + type: 'value', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', + nameLocation: 'end', + nameTextStyle: { + align: 'left' + }, + min: 0, + max: 'dataMax', + axisLabel: { + formatter: (value: number) => { + return yUnitFormatter(value) + } + } }, - min: 0, - max: 'dataMax', - axisLabel: { - formatter: (value:number) => { - return yUnitFormatter(value) + { + type: 'value', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', + position: 'right', + min: 0, + max: 100, + show: (lineData as InvokeData)?.date.length > 0, + interval: 20, + axisLabel: { + formatter: '{value} %' + }, + axisLine: { + show: false + }, + axisTick: { + show: false } } - }, - { - type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', - position: 'right', - min: 0, - max: 100, - show: (lineData as InvokeData)?.date.length > 0, - interval: 20, - axisLabel: { - formatter: '{value} %' - }, - axisLine: { - show: false - }, - axisTick: { - show: false - } - }], + ], series: [ - { type: 'line', symbol: 'none', name: $t('请求总数'), data: (lineData as InvokeData)?.requestTotal, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('请求成功率'), data: (lineData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], yAxisIndex: 1 }, + { + type: 'line', + symbol: 'none', + name: $t('请求总数'), + data: (lineData as InvokeData)?.requestTotal, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('请求成功率'), + data: (lineData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + yAxisIndex: 1 + }, { type: 'line', symbol: 'none', name: $t('转发总数'), data: (lineData as InvokeData)?.proxyTotal, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], yAxisIndex: 1 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码4xx数'), data: (lineData as InvokeData)?.status_4xx, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码5xx数'), data: (lineData as InvokeData)?.status_5xx, yAxisIndex: 0 } + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + yAxisIndex: 1 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码4xx数'), + data: (lineData as InvokeData)?.status_4xx, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码5xx数'), + data: (lineData as InvokeData)?.status_5xx, + yAxisIndex: 0 + } ] }) - const generateInvokeCompareLineChartOption = ()=>({ + const generateInvokeCompareLineChartOption = () => ({ ...MONITOR_LINE_CHART_OPTION_CONFIG, tooltip: { - formatter: (params:Array>) => { - const startHtml = '
' + (params[0].seriesIndex === 0 ? modalTitle : dataTitle + '调用总体趋势') + '   ' + params[0].axisValue + '
' + formatter: (params: Array>) => { + const startHtml = + '
' + + (params[0].seriesIndex === 0 ? modalTitle : dataTitle + '调用总体趋势') + + '   ' + + params[0].axisValue + + '
' const listArr = [] for (let i = 0; i < params.length; i++) { const item = params[i] // echarts会根据你定义的颜色返回一个生成好的带颜色的标记,直接实用即可 let str = '' if (i === Math.floor(params.length / 2)) { - str = '
' + (params[0].seriesIndex === 0 ? dataTitle + $t('调用总体趋势') :modalTitle) + '   ' + params[0].axisValue + '
' + item.marker + str = + '
' + + (params[0].seriesIndex === 0 ? dataTitle + $t('调用总体趋势') : modalTitle) + + '   ' + + params[0].axisValue + + '
' + + item.marker } else { - str = '
' + item.marker + str = + '
' + + item.marker } if (item.seriesName === $t('请求成功率') || item.seriesName === $t('转发成功率')) { - str += (item.seriesName + '    ' + item.value + '%
') + str += + item.seriesName + + '   
' + + item.value + + '%
' } else { - str += (item.seriesName + '   
' + item.value + '
') + str += + item.seriesName + + '   
' + + item.value + + '
' } listArr.push(str) } @@ -203,15 +286,19 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx }, trigger: 'axis', axisPointer: { - link: [{ - xAxisIndex: 'all' - }] + link: [ + { + xAxisIndex: 'all' + } + ] } }, axisPointer: { - link: [{ - xAxisIndex: 'all' - }] + link: [ + { + xAxisIndex: 'all' + } + ] }, grid: [ { @@ -229,39 +316,42 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx top: '60%' } ], - xAxis: [{ - type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], - axisLabel: { - showMaxLabel: true, - formatter: (value:string) => { - return value ? getTimeFormatter(value) : '' + xAxis: [ + { + type: 'category', + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], + axisLabel: { + showMaxLabel: true, + formatter: (value: string) => { + return value ? getTimeFormatter(value) : '' + } + }, + axisTick: { + show: false } }, - axisTick: { - show: false - } - }, - { - gridIndex: 1, - type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], - axisLabel: { - showMaxLabel: true, - formatter: (value:string) => { - return value ? getTimeFormatter(value) : '' + { + gridIndex: 1, + type: 'category', + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], + axisLabel: { + showMaxLabel: true, + formatter: (value: string) => { + return value ? getTimeFormatter(value) : '' + } } } - } ], yAxis: [ { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', nameLocation: 'end', nameTextStyle: { align: 'left' @@ -269,15 +359,14 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx min: 0, max: 'dataMax', axisLabel: { - - formatter: (value:number) => { + formatter: (value: number) => { return yUnitFormatter(value) } } }, { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', position: 'right', min: 0, max: 100, @@ -297,7 +386,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx { gridIndex: 1, type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', nameLocation: 'end', nameTextStyle: { align: 'left' @@ -305,8 +394,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx min: 0, max: 'dataMax', axisLabel: { - - formatter: (value:number) => { + formatter: (value: number) => { return yUnitFormatter(value) } } @@ -314,7 +402,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx { gridIndex: 1, type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]): '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', position: 'right', min: 0, max: 100, @@ -330,29 +418,118 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx axisTick: { show: false } - }], + } + ], series: [ - { type: 'line', symbol: 'none', name: $t('请求总数'), data: (lineData as InvokeData)?.requestTotal, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('请求成功率'), data: (lineData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], xAxisIndex: 0, yAxisIndex: 1 }, - { type: 'line', symbol: 'none', name: $t('转发总数'), data: (lineData as InvokeData)?.proxyTotal, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], xAxisIndex: 0, yAxisIndex: 1 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码4xx数'), data: (lineData as InvokeData)?.status_4xx, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码5xx数'), data: (lineData as InvokeData)?.status_5xx, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('请求总数'), data: (compareData as InvokeData)?.requestTotal, xAxisIndex: 1, yAxisIndex: 2 }, - { type: 'line', symbol: 'none', name: $t('请求成功率'), data: (compareData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], xAxisIndex: 1, yAxisIndex: 3 }, - { type: 'line', symbol: 'none', name: $t('转发总数'), data: (compareData as InvokeData)?.proxyTotal, xAxisIndex: 1, yAxisIndex: 2 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (compareData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], xAxisIndex: 1, yAxisIndex: 3 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码4xx数'), data: (compareData as InvokeData)?.status_4xx, xAxisIndex: 1, yAxisIndex: 2 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码5xx数'), data: (compareData as InvokeData)?.status_5xx, xAxisIndex: 1, yAxisIndex: 2 } + { + type: 'line', + symbol: 'none', + name: $t('请求总数'), + data: (lineData as InvokeData)?.requestTotal, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('请求成功率'), + data: (lineData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + xAxisIndex: 0, + yAxisIndex: 1 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发总数'), + data: (lineData as InvokeData)?.proxyTotal, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + xAxisIndex: 0, + yAxisIndex: 1 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码4xx数'), + data: (lineData as InvokeData)?.status_4xx, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码5xx数'), + data: (lineData as InvokeData)?.status_5xx, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('请求总数'), + data: (compareData as InvokeData)?.requestTotal, + xAxisIndex: 1, + yAxisIndex: 2 + }, + { + type: 'line', + symbol: 'none', + name: $t('请求成功率'), + data: (compareData as InvokeData)?.requestRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + xAxisIndex: 1, + yAxisIndex: 3 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发总数'), + data: (compareData as InvokeData)?.proxyTotal, + xAxisIndex: 1, + yAxisIndex: 2 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (compareData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + xAxisIndex: 1, + yAxisIndex: 3 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码4xx数'), + data: (compareData as InvokeData)?.status_4xx, + xAxisIndex: 1, + yAxisIndex: 2 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码5xx数'), + data: (compareData as InvokeData)?.status_5xx, + xAxisIndex: 1, + yAxisIndex: 2 + } ] }) - const generateInvokeServiceLineChartOption = ()=>({ + const generateInvokeServiceLineChartOption = () => ({ ...MONITOR_LINE_CHART_OPTION_CONFIG, legend: { orient: 'horizontal', top: '40', - left:'16', + left: '16', selected: { 转发总数: true, 转发成功率: true, @@ -369,12 +546,13 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx }, xAxis: { type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], axisLabel: { showMaxLabel: true, - formatter: (value:string) => { + formatter: (value: string) => { return getTimeFormatter(value) } }, @@ -385,7 +563,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx yAxis: [ { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ?$t('(0)调用量',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', nameLocation: 'end', nameTextStyle: { align: 'left' @@ -394,15 +572,14 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx min: 0, max: 'dataMax', axisLabel: { - - formatter: (value:number) => { + formatter: (value: number) => { return yUnitFormatter(value) } } }, { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', position: 'right', min: 0, max: 100, @@ -417,20 +594,42 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx axisTick: { show: false } - }], + } + ], series: [ { type: 'line', symbol: 'none', name: $t('转发总数'), data: (lineData as InvokeData)?.proxyTotal, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], yAxisIndex: 1 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码4xx数'), data: (lineData as InvokeData)?.status_4xx, yAxisIndex: 0 }, - { type: 'line', lineStyle: { type: 'dashed' }, symbol: 'none', name: $t('状态码5xx数'), data: (lineData as InvokeData)?.status_5xx, yAxisIndex: 0 } - ]}) + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (lineData as InvokeData)?.proxyRate?.map((x) => Number((Number(x) * 100).toFixed(2))) || [], + yAxisIndex: 1 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码4xx数'), + data: (lineData as InvokeData)?.status_4xx, + yAxisIndex: 0 + }, + { + type: 'line', + lineStyle: { type: 'dashed' }, + symbol: 'none', + name: $t('状态码5xx数'), + data: (lineData as InvokeData)?.status_5xx, + yAxisIndex: 0 + } + ] + }) - const generateInvokeServiceCompareLineChartOption = ()=>({ + const generateInvokeServiceCompareLineChartOption = () => ({ ...MONITOR_LINE_CHART_OPTION_CONFIG, legend: { orient: 'horizontal', top: '40', - left:'16', + left: '16', selected: { 转发总数: true, 转发成功率: true, @@ -456,22 +655,43 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx ], tooltip: { trigger: 'axis', - formatter: (params:Array>) => { - const startHtml = '
' + (params[0].seriesIndex === 0 ? modalTitle : dataTitle + $t('调用总体趋势')) + '   ' + params[0].axisValue + '
' + formatter: (params: Array>) => { + const startHtml = + '
' + + (params[0].seriesIndex === 0 ? modalTitle : dataTitle + $t('调用总体趋势')) + + '   ' + + params[0].axisValue + + '
' const listArr = [] for (let i = 0; i < params.length; i++) { const item = params[i] // echarts会根据你定义的颜色返回一个生成好的带颜色的标记,直接实用即可 let str = '' if (i === Math.floor(params.length / 2)) { - str = '
' + (params[0].seriesIndex === 0 ? dataTitle + $t('调用总体趋势') : modalTitle) + '   ' + params[0].axisValue + '
' + item.marker + str = + '
' + + (params[0].seriesIndex === 0 ? dataTitle + $t('调用总体趋势') : modalTitle) + + '   ' + + params[0].axisValue + + '
' + + item.marker } else { - str = '
' + item.marker + str = + '
' + + item.marker } if (item.seriesName === $t('请求成功率') || item.seriesName === $t('转发成功率')) { - str += (item.seriesName + '    ' + item.value + '%
') + str += + item.seriesName + + '   
' + + item.value + + '%
' } else { - str += (item.seriesName + '   
' + item.value + '
') + str += + item.seriesName + + '   
' + + item.value + + '
' } listArr.push(str) } @@ -479,46 +699,52 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx } }, axisPointer: { - link: [{ - xAxisIndex: 'all' - }] + link: [ + { + xAxisIndex: 'all' + } + ] }, - xAxis: [{ - type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], - axisLabel: { - showMaxLabel: true, - formatter: (value:string) => { - return getTimeFormatter(value) + xAxis: [ + { + type: 'category', + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], + axisLabel: { + showMaxLabel: true, + formatter: (value: string) => { + return getTimeFormatter(value) + } + }, + axisTick: { + show: false } }, - axisTick: { - show: false - } - }, - { - gridIndex: 1, - type: 'category', - data: (lineData as InvokeData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], - splitNumber: 5, - axisLabel: { - showMaxLabel: true, - formatter: (value:string) => { - return getTimeFormatter(value) + { + gridIndex: 1, + type: 'category', + data: + (lineData as InvokeData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], + splitNumber: 5, + axisLabel: { + showMaxLabel: true, + formatter: (value: string) => { + return getTimeFormatter(value) + } + }, + axisTick: { + show: false } - }, - axisTick: { - show: false } - }], + ], yAxis: [ { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', nameLocation: 'end', nameTextStyle: { align: 'left' @@ -526,15 +752,14 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx min: 0, max: 'dataMax', axisLabel: { - - formatter: (value:number) => { + formatter: (value: number) => { return yUnitFormatter(value) } } }, { type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', position: 'right', min: 0, max: 100, @@ -554,7 +779,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx { gridIndex: 1, type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量',[yAxisTitle]): '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用量', [yAxisTitle]) : '', nameLocation: 'end', nameTextStyle: { align: 'left' @@ -562,8 +787,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx min: 0, max: 'dataMax', axisLabel: { - - formatter: (value:number) => { + formatter: (value: number) => { return yUnitFormatter(value) } } @@ -571,7 +795,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx { gridIndex: 1, type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', position: 'right', min: 0, max: 100, @@ -587,25 +811,87 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx axisTick: { show: false } - }], + } + ], series: [ - { type: 'line', symbol: 'none', name: $t('转发总数'), data: (lineData as InvokeData)?.proxyTotal, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (lineData as InvokeData)?.proxyRate, xAxisIndex: 0, yAxisIndex: 1 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码4xx数'), data: (lineData as InvokeData)?.status_4xx, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码5xx数'), data: (lineData as InvokeData)?.status_5xx, xAxisIndex: 0, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('转发总数'), data: (compareData as InvokeData)?.proxyTotal, xAxisIndex: 1, yAxisIndex: 2 }, - { type: 'line', symbol: 'none', name: $t('转发成功率'), data: (compareData as InvokeData)?.proxyRate, xAxisIndex: 1, yAxisIndex: 3 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码4xx数'), data: (compareData as InvokeData)?.status_4xx, xAxisIndex: 1, yAxisIndex: 2 }, - { type: 'line', symbol: 'none', lineStyle: { type: 'dashed' }, name: $t('状态码5xx数'), data: (compareData as InvokeData)?.status_5xx, xAxisIndex: 1, yAxisIndex: 2 } - ]}) + { + type: 'line', + symbol: 'none', + name: $t('转发总数'), + data: (lineData as InvokeData)?.proxyTotal, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (lineData as InvokeData)?.proxyRate, + xAxisIndex: 0, + yAxisIndex: 1 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码4xx数'), + data: (lineData as InvokeData)?.status_4xx, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码5xx数'), + data: (lineData as InvokeData)?.status_5xx, + xAxisIndex: 0, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发总数'), + data: (compareData as InvokeData)?.proxyTotal, + xAxisIndex: 1, + yAxisIndex: 2 + }, + { + type: 'line', + symbol: 'none', + name: $t('转发成功率'), + data: (compareData as InvokeData)?.proxyRate, + xAxisIndex: 1, + yAxisIndex: 3 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码4xx数'), + data: (compareData as InvokeData)?.status_4xx, + xAxisIndex: 1, + yAxisIndex: 2 + }, + { + type: 'line', + symbol: 'none', + lineStyle: { type: 'dashed' }, + name: $t('状态码5xx数'), + data: (compareData as InvokeData)?.status_5xx, + xAxisIndex: 1, + yAxisIndex: 2 + } + ] + }) - const generateTrafficLineChartOption = ()=>({ + const generateTrafficLineChartOption = () => ({ ...MONITOR_LINE_CHART_OPTION_CONFIG, legend: { orient: 'horizontal', top: '40', - left:'16', - selected: {...MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED} + left: '16', + selected: { ...MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED } }, grid: { left: '26', @@ -616,12 +902,13 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx }, xAxis: { type: 'category', - data: (lineData as MessageData)?.date?.map((x:string) => { - return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` - }) || [], + data: + (lineData as MessageData)?.date?.map((x: string) => { + return `${new Date(x).getFullYear()}/${new Date(x).getMonth() + 1}/${new Date(x).getDate()} ${new Date(x).getHours()}:${new Date(x).getMinutes()} ` + }) || [], axisLabel: { showMaxLabel: true, - formatter: (value:string) => { + formatter: (value: string) => { return getTimeFormatter(value) } }, @@ -629,54 +916,67 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx show: false } }, - yAxis: [{ - type: 'value', - name: (lineData as MessageData)?.date.length > 0 ? `${$t('(0)报文量',[yAxisTitle])}(KB)` : '', - nameLocation: 'end', - nameTextStyle: { - align: 'left' - }, - axisTick: { - length: 6 - }, + yAxis: [ + { + type: 'value', + name: (lineData as MessageData)?.date.length > 0 ? `${$t('(0)报文量', [yAxisTitle])}(KB)` : '', + nameLocation: 'end', + nameTextStyle: { + align: 'left' + }, + axisTick: { + length: 6 + }, - min: 0, - max: 'dataMax', - axisLabel: { + min: 0, + max: 'dataMax', + axisLabel: { + formatter: (value: number) => { + return yUnitFormatter(value) + } + } + }, + { + type: 'value', + name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率', [yAxisTitle]) : '', + position: 'right', + min: 0, + max: 100, + show: false, - formatter: (value:number) => { - return yUnitFormatter(value) + interval: 20, + axisLabel: { + formatter: '{value} %' + }, + axisLine: { + show: false + }, + axisTick: { + show: false } } - }, - { - type: 'value', - name: (lineData as InvokeData)?.date.length > 0 ? $t('(0)调用成功率',[yAxisTitle]) : '', - position: 'right', - min: 0, - max: 100, - show: false, - - interval: 20, - axisLabel: { - formatter: '{value} %' - }, - axisLine: { - show: false - }, - axisTick: { - show: false - } - }], + ], series: [ - { type: 'line', symbol: 'none', name: $t('请求报文量'), data: (lineData as MessageData).requestMessage, yAxisIndex: 0 }, - { type: 'line', symbol: 'none', name: $t('响应报文量'), data: (lineData as MessageData).responseMessage, yAxisIndex: 0 } - ]}) + { + type: 'line', + symbol: 'none', + name: $t('请求报文量'), + data: (lineData as MessageData).requestMessage, + yAxisIndex: 0 + }, + { + type: 'line', + symbol: 'none', + name: $t('响应报文量'), + data: (lineData as MessageData).responseMessage, + yAxisIndex: 0 + } + ] + }) - - const option:EChartsOption = useMemo(()=>{ - const generateBasicOption = ()=>{ - switch(type){ + const option: EChartsOption = useMemo(() => { + const generateBasicOption = () => { + switch (type) { case 'invoke': return compare ? generateInvokeCompareLineChartOption() : generateInvokeLineChartOption() case 'invokeService': @@ -686,7 +986,7 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx } } - const getOption = ()=>{ + const getOption = () => { const option = generateBasicOption() option.title = { @@ -695,27 +995,35 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx textStyle: { fontSize: 16, color: '#666666', - fontWeight:'normal', + fontWeight: 'normal' } } - // 当勾选请求成功率或转发成功率其中之一时,显示右侧y轴 + // 当勾选请求成功率或转发成功率其中之一时,显示右侧y轴 if (legendSelected && lineData?.date && lineData?.date.length > 0) { - if (!legendSelected[$t('转发成功率')] && !legendSelected[$t('请求成功率')] && (option.yAxis as Array)?.length > 1 && option.yAxis[1].show !== false) { + if ( + !legendSelected[$t('转发成功率')] && + !legendSelected[$t('请求成功率')] && + (option.yAxis as Array)?.length > 1 && + option.yAxis[1].show !== false + ) { option.yAxis[1].show = false - } else if ((legendSelected[$t('转发成功率')] || legendSelected[$t('请求成功率')]) && (option.yAxis as Array)?.length > 1 && option.yAxis[1].show !== true) { + } else if ( + (legendSelected[$t('转发成功率')] || legendSelected[$t('请求成功率')]) && + (option.yAxis as Array)?.length > 1 && + option.yAxis[1].show !== true + ) { option.yAxis[1].show = true } } option.legend = { ...option.legend, selected: legendSelected || undefined } - option.yAxis[0] = calculateYAxisInterval(lineData,option, option.yAxis[0], legendSelected) - if(compare){option.yAxis[2] = calculateYAxisInterval(compareData!, option,option.yAxis[2], legendSelected)} + option.yAxis[0] = calculateYAxisInterval(lineData, option, option.yAxis[0], legendSelected) + if (compare) { + option.yAxis[2] = calculateYAxisInterval(compareData!, option, option.yAxis[2], legendSelected) + } return option } return getOption() - - },[compare, type,lineData,yAxisTitle,titles,legendSelected, state.language]) - - + }, [compare, type, lineData, yAxisTitle, titles, legendSelected, state.language]) // const handleWindowResize = useCallback(debounce(() => { // if (containerRef.current && graphRef.current && !graphRef.current.get('destroyed')) { @@ -725,12 +1033,17 @@ const MonitorLineGraph: FC = ({ className, lineData, titles, yAx // ); // } // }, 400), []); - + return (
- +
- ) -}; + ) +} -export default MonitorLineGraph; \ No newline at end of file +export default MonitorLineGraph diff --git a/frontend/packages/dashboard/src/component/MonitorPieGraph.tsx b/frontend/packages/dashboard/src/component/MonitorPieGraph.tsx index 48888388..f4b4c771 100644 --- a/frontend/packages/dashboard/src/component/MonitorPieGraph.tsx +++ b/frontend/packages/dashboard/src/component/MonitorPieGraph.tsx @@ -1,25 +1,37 @@ -import {FC, useEffect, useMemo} from 'react'; -import ECharts,{EChartsOption} from 'echarts-for-react'; -import { changeNumberUnit } from '../utils/dashboard'; -import { $t } from '@common/locales'; -import { useGlobalContext } from '@common/contexts/GlobalStateContext'; +import { FC, useMemo } from 'react' +import ECharts, { EChartsOption } from 'echarts-for-react' +import { changeNumberUnit } from '../utils/dashboard' +import { $t } from '@common/locales' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' type PieGraphProps = { - className?:string, - title:string, - pieData:{ [key: string]: number }, - labelName:string, - labelValue:string, - subText:string, - subValue:unknown - status4xxCount?: number; - status5xxCount?: number; + className?: string + title: string + pieData: { [key: string]: number } + labelName: string + labelValue: string + subText: string + subValue: unknown + status4xxCount?: number + status5xxCount?: number } -const MonitorPieGraph: FC = ({ className,title, pieData, labelName, labelValue, subText, subValue,status4xxCount,status5xxCount }) => { - const {state} = useGlobalContext() - const transferData: (value:{[key:string]:number})=>Array<{name:string, value:number}> = (value:{[key:string]:number})=> { - const res:Array<{name:string, value:number}> = [] +const MonitorPieGraph: FC = ({ + className, + title, + pieData, + labelName, + labelValue, + subText, + subValue, + status4xxCount, + status5xxCount +}) => { + const { state } = useGlobalContext() + const transferData: (value: { [key: string]: number }) => Array<{ name: string; value: number }> = (value: { + [key: string]: number + }) => { + const res: Array<{ name: string; value: number }> = [] const keys = Object.keys(value) for (const item of keys) { res.push({ name: item, value: value[item] }) @@ -27,78 +39,110 @@ const MonitorPieGraph: FC = ({ className,title, pieData, labelNam return res } - const option: EChartsOption = useMemo(()=>({ - tooltip: { - trigger: 'item', - formatter: (params:Array>) => { - const startHtml = '
' + $t(title) + '
' - return startHtml + ($t(params.name || '-') + '    ' + params.value + '
') - } - }, - title: [ - { - right: '10', - subtext: `{title|${$t(subText)}}{percent|${subValue}}`, - top: '15%', - subtextStyle: { - rich: { - title: { fontSize: 14, color: '#666666', lineHeight: 22, padding: [8, 0, 8, 0] }, - percent: { fontSize: 14, color: '#666666', width: 60, lineHeight: 22, align: 'right', padding: [8, 0, 8, 8] } - }, - fontSize: 14, - color: '#666666', - lineHeight: 22, - padding: [8, 0, 8, 0] + const option: EChartsOption = useMemo( + () => ({ + tooltip: { + trigger: 'item', + formatter: (params: Array>) => { + const startHtml = + '
' + + $t(title) + + '
' + return ( + startHtml + + ($t(params.name || '-') + + '    ' + + params.value + + '
') + ) } }, - ], - legend: [ - { - top: 'center', - right: '10', - orient: 'vertical', - formatter: (name) => { - return `{title|${$t(name)}}{percent|${changeNumberUnit(pieData[name]).value ? (changeNumberUnit(pieData[name]).value + $t(changeNumberUnit(pieData[name]).unit)) : '0'}}`; - }, - textStyle: { - rich: { - title: { fontSize: 14, color: '#666666',lineHeight: 22, width:state.language === 'en' ? 134 :70,padding: [8, 0, 8, 0] }, - percent: { fontSize: 14, color: '#666666', width: 60, lineHeight: 22, align:'right', padding: [8, 0, 8, 8] } + title: [ + { + right: '10', + subtext: `{title|${$t(subText)}}{percent|${subValue}}`, + top: '15%', + subtextStyle: { + rich: { + title: { fontSize: 14, color: '#666666', lineHeight: 22, padding: [8, 0, 8, 0] }, + percent: { + fontSize: 14, + color: '#666666', + width: 60, + lineHeight: 22, + align: 'right', + padding: [8, 0, 8, 8] + } + }, + fontSize: 14, + color: '#666666', + lineHeight: 22, + padding: [8, 0, 8, 0] } } - }, - ], - series: [ - { - center: ['25%', '50%'], - name: title, - type: 'pie', - color: ['#1890FF', '#13c2c2'], - radius: ['50%', '75%'], - avoidLabelOverlap: false, - label: { - show: true, - position: 'center', - formatter: '{text|' + $t(labelName) + '}{value|' + labelValue + '}', - rich: { - text: { fontSize: 14, color: '#666666', lineHeight: 22, padding: [0, 0, 6, 0] }, - value: { fontSize: 20, color: '#333333', lineHeight: 32, padding: [0, 0, 6, 0] }, + ], + legend: [ + { + top: 'center', + right: '10', + orient: 'vertical', + formatter: (name) => { + return `{title|${$t(name)}}{percent|${changeNumberUnit(pieData[name]).value ? changeNumberUnit(pieData[name]).value + $t(changeNumberUnit(pieData[name]).unit) : '0'}}` }, - }, - labelLine: { - show: false, - }, - data: transferData(pieData), - }, - ], - }),[state.language,pieData]) - + textStyle: { + rich: { + title: { + fontSize: 14, + color: '#666666', + lineHeight: 22, + width: state.language === 'en' ? 134 : 70, + padding: [8, 0, 8, 0] + }, + percent: { + fontSize: 14, + color: '#666666', + width: 60, + lineHeight: 22, + align: 'right', + padding: [8, 0, 8, 8] + } + } + } + } + ], + series: [ + { + center: ['25%', '50%'], + name: title, + type: 'pie', + color: ['#1890FF', '#13c2c2'], + radius: ['50%', '75%'], + avoidLabelOverlap: false, + label: { + show: true, + position: 'center', + formatter: '{text|' + $t(labelName) + '}{value|' + labelValue + '}', + rich: { + text: { fontSize: 14, color: '#666666', lineHeight: 22, padding: [0, 0, 6, 0] }, + value: { fontSize: 20, color: '#333333', lineHeight: 32, padding: [0, 0, 6, 0] } + } + }, + labelLine: { + show: false + }, + data: transferData(pieData) + } + ] + }), + [state.language, pieData] + ) + return ( -
-
{title}
+
+
{title}
- +
    @@ -107,7 +151,7 @@ const MonitorPieGraph: FC = ({ className,title, pieData, labelNam {$t('状态码4XX数')} {changeNumberUnit(status4xxCount).value + $t(changeNumberUnit(status4xxCount).unit)} - +
  • {$t('状态码5XX数')} @@ -118,8 +162,9 @@ const MonitorPieGraph: FC = ({ className,title, pieData, labelNam
+
-
); -}; + ) +} -export default MonitorPieGraph; \ No newline at end of file +export default MonitorPieGraph diff --git a/frontend/packages/dashboard/src/component/MonitorSubPage.tsx b/frontend/packages/dashboard/src/component/MonitorSubPage.tsx index f84eb8d3..99e85319 100644 --- a/frontend/packages/dashboard/src/component/MonitorSubPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorSubPage.tsx @@ -1,196 +1,269 @@ -import { Select, Button, App, Radio, Drawer } from "antd"; -import { useEffect, useRef, useState } from "react"; -import { MonitorSubscriberData, SearchBody } from "@dashboard/const/type"; -import { EntityItem } from "@common/const/type"; -import TimeRangeSelector, { RangeValue, TimeRange, TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; -import MonitorTable, { MonitorTableHandler } from "./MonitorTable"; -import { DefaultOptionType } from "antd/es/select"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { getTime } from "../utils/dashboard"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { useExcelExport } from "@common/hooks/excel"; -import { SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG } from "@dashboard/const/const"; -import { CloseOutlined, ExpandOutlined } from "@ant-design/icons"; -import { useFetch } from "@common/hooks/http"; -import { $t } from "@common/locales"; +import { Select, Button, App, Drawer } from 'antd' +import { useEffect, useRef, useState } from 'react' +import { MonitorSubscriberData, SearchBody } from '@dashboard/const/type' +import { EntityItem } from '@common/const/type' +import TimeRangeSelector, { + RangeValue, + TimeRange, + TimeRangeButton +} from '@common/components/aoplatform/TimeRangeSelector' +import MonitorTable, { MonitorTableHandler } from './MonitorTable' +import { DefaultOptionType } from 'antd/es/select' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { getTime } from '../utils/dashboard' +import { useExcelExport } from '@common/hooks/excel' +import { SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG } from '@dashboard/const/const' +import { CloseOutlined, ExpandOutlined } from '@ant-design/icons' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' -export type MonitorSubQueryData = SearchBody & { services?:string[] , apps?:string[],type?:'subscriber'|'provider'} - - -export type MonitorSubPageProps = { - fetchTableData:(body:SearchBody)=>Promise> - fetchAppListData?:(body:SearchBody)=>Promise> - detailDrawerContent:React.ReactNode - fullScreen?:boolean - setFullScreen?:(val:boolean) => void - setDetailId:(val:string) =>void - setTimeButton:(val:TimeRangeButton) => void - timeButton:TimeRangeButton - setDetailEntityName:(name:string) => void - detailEntityName:string +export type MonitorSubQueryData = SearchBody & { + services?: string[] + apps?: string[] + type?: 'subscriber' | 'provider' } -export default function MonitorSubPage(props:MonitorSubPageProps){ - const {fetchTableData,detailDrawerContent,fullScreen,setFullScreen,setDetailId,timeButton,setTimeButton,detailEntityName,setDetailEntityName} = props - const {message} = App.useApp() - const [queryData, setQueryData] = useState({type:'provider'}); - const [exportLoading, setExportLoading] = useState(false); - const [datePickerValue, setDatePickerValue] = useState(); - const monitorAppTableRef = useRef(null) - const {exportExcel} = useExcelExport() - const [drawerOpen, setDrawerOpen] = useState(false); - const [listOfProjects, setListOfProjects] = useState([]) - const {fetchData} = useFetch() - const [queryBtnLoading, setQueryBtnLoading] = useState(false) +export type MonitorSubPageProps = { + fetchTableData: (body: SearchBody) => Promise> + fetchAppListData?: (body: SearchBody) => Promise> + detailDrawerContent: React.ReactNode + fullScreen?: boolean + setFullScreen?: (val: boolean) => void + setDetailId: (val: string) => void + setTimeButton: (val: TimeRangeButton) => void + timeButton: TimeRangeButton + setDetailEntityName: (name: string) => void + detailEntityName: string +} - useEffect(() => { - getMonitorData(); - getProjectList() - }, []); +export default function MonitorSubPage(props: MonitorSubPageProps) { + const { + fetchTableData, + detailDrawerContent, + fullScreen, + setFullScreen, + setDetailId, + timeButton, + setTimeButton, + detailEntityName, + setDetailEntityName + } = props + const { message } = App.useApp() + const [queryData, setQueryData] = useState({ type: 'provider' }) + const [exportLoading, setExportLoading] = useState(false) + const [datePickerValue, setDatePickerValue] = useState() + const monitorAppTableRef = useRef(null) + const { exportExcel } = useExcelExport() + const [drawerOpen, setDrawerOpen] = useState(false) + const [listOfProjects, setListOfProjects] = useState([]) + const { fetchData } = useFetch() + const [queryBtnLoading, setQueryBtnLoading] = useState(false) - const getMonitorData = () => { - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - }; + useEffect(() => { + getMonitorData() + getProjectList() + }, []) - const getProjectList = ()=>{ - return fetchData<{services:EntityItem[]}>('simple/services',{method:'GET'}).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - setListOfProjects(data.services?.map((x:EntityItem)=>({label:x.name, value:x.id}))) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return setListOfProjects([]) - } - }).catch(() => { + const getMonitorData = () => { + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } + } + const data: SearchBody = query! + setQueryData(data) + } + + const getProjectList = () => { + return fetchData<{ services: EntityItem[] }>('simple/services', { method: 'GET' }) + .then((resp) => { + const { code, data, msg } = resp + if (code === STATUS_CODE.SUCCESS) { + setListOfProjects(data.services?.map((x: EntityItem) => ({ label: x.name, value: x.id }))) + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) return setListOfProjects([]) + } }) - } - - const clearSearch = () => { - setTimeButton('hour'); - setDatePickerValue(null) - setQueryData({type:'provider'}); - } - - const getAppTableList = () => { - // ...根据时间和集群获取监控数据... - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - monitorAppTableRef.current?.reload() - }; - - - const exportData = () => { - setExportLoading(true); - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! ; - fetchTableData(data).then((resp) => { - const {code,data,msg} = resp - if(code === STATUS_CODE.SUCCESS){ - exportExcel($t('服务调用统计'), [query!.start!, query!.end!], $t('服务调用统计'), 'dashboard_service', SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG, data.statistics) - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }) - }; - - const handleTimeRangeChange = (timeRange:TimeRange) => { - setQueryData(pre => ({...pre, ...timeRange} as SearchBody )) - }; - - - const getTablesData = (body: SearchBody) => { - return fetchTableData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if(code === STATUS_CODE.SUCCESS){ - return {data:data.statistics?.map((x:MonitorSubscriberData)=>{x.proxyRate = Number((x.proxyRate*100).toFixed(2));x.requestRate = Number((x.requestRate*100).toFixed(2));return x}), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return {data:[], success:false} - } - }).catch(() => { - setQueryBtnLoading(false) - return {data:[], success:false} - }) - }; + .catch(() => { + return setListOfProjects([]) + }) + } - - const getDetailData = (entity:MonitorSubscriberData)=>{ - setDetailEntityName(entity.name) - setDetailId(entity.id) - setDrawerOpen(true) - } + const clearSearch = () => { + setTimeButton('hour') + setDatePickerValue(null) + setQueryData({ type: 'provider' }) + } - return ( -
-
- -
-
- - { + setQueryData((prevData) => ({ ...(prevData || {}), services: value })) + }} + />
-
- {getDetailData(record); }} request={()=>getTablesData(queryData||{})} showPagination={true}/> -
- - - {fullScreen && {setFullScreen?.(false)}}> - {$t('退出全屏')} - } - {detailEntityName}{$t('调用详情')} - {!fullScreen && {setFullScreen?.(true)}}/>} - } - width={fullScreen ? '100%' : '60%'} - onClose={()=>setDrawerOpen(false)} - open={drawerOpen}> - {detailDrawerContent} - -
) -} \ No newline at end of file +
+ + + +
+
+
+
+ { + getDetailData(record) + }} + request={() => getTablesData(queryData || {})} + showPagination={true} + /> +
+ + + {fullScreen && ( + { + setFullScreen?.(false) + }} + > + + {$t('退出全屏')} + + )} + + {detailEntityName} + {$t('调用详情')} + + {!fullScreen && ( + { + setFullScreen?.(true) + }} + /> + )} + + } + width={fullScreen ? '100%' : '60%'} + onClose={() => setDrawerOpen(false)} + open={drawerOpen} + > + {detailDrawerContent} + +
+ ) +} diff --git a/frontend/packages/dashboard/src/component/MonitorTable.tsx b/frontend/packages/dashboard/src/component/MonitorTable.tsx index 6f6fd89a..a924ce36 100644 --- a/frontend/packages/dashboard/src/component/MonitorTable.tsx +++ b/frontend/packages/dashboard/src/component/MonitorTable.tsx @@ -1,112 +1,141 @@ +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission' +import { COLUMNS_TITLE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { + API_TABLE_GLOBAL_COLUMNS_CONFIG, + APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG, + SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG +} from '@dashboard/const/const' +import { Tooltip } from 'antd' +import { StringifyOptions } from 'querystring' +import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react' -import { ActionType } from "@ant-design/pro-components" -import { useImperativeHandle, useMemo, useRef, useState } from "react" -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList" -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission" -import { API_TABLE_GLOBAL_COLUMNS_CONFIG,SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG, APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG } from "@dashboard/const/const" -import {forwardRef} from "react" -import { COLUMNS_TITLE } from "@common/const/const" -import { Tooltip } from "antd" -import { $t } from "@common/locales" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import { StringifyOptions } from "querystring" - -const TableType = { - api :API_TABLE_GLOBAL_COLUMNS_CONFIG, - provider :SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG, - subscribers :APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG +const TableType = { + api: API_TABLE_GLOBAL_COLUMNS_CONFIG, + provider: SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG, + subscribers: APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG } type MonitorTableProps = { - type:'api'|'subscribers'|'provider' - id:string - request:(keyword?:string) => Promise<{ - data: T[]; - success: boolean; - }> - onRowClick:(record:T)=>void - searchPlaceholder?:string - showPagination?:boolean - noTop?:boolean - minVirtualHeight?:number - className?:string - inModal?:boolean + type: 'api' | 'subscribers' | 'provider' + id: string + request: (keyword?: string) => Promise<{ + data: T[] + success: boolean + }> + onRowClick: (record: T) => void + searchPlaceholder?: string + showPagination?: boolean + noTop?: boolean + minVirtualHeight?: number + className?: string + inModal?: boolean } -export interface MonitorTableHandler{ - reload:()=>void +export interface MonitorTableHandler { + reload: () => void } -const MonitorTable = forwardRef>((props,ref) => { - const {type,id,request,onRowClick,searchPlaceholder,showPagination=false,noTop,minVirtualHeight,className,inModal=false} = props - const [searchWord, setSearchWord] = useState('') - const tableRef = useRef(null) - const [tableHttpReload, setTableHttpReload] = useState(true); - const [tableListDataSource, setTableListDataSource] = useState([]); - const {state} = useGlobalContext() +const MonitorTable = forwardRef>((props, ref) => { + const { + type, + id, + request, + onRowClick, + searchPlaceholder, + showPagination = false, + noTop, + minVirtualHeight, + className, + inModal = false + } = props + const [searchWord, setSearchWord] = useState('') + const tableRef = useRef(null) + const [tableHttpReload, setTableHttpReload] = useState(true) + const [tableListDataSource, setTableListDataSource] = useState([]) + const { state } = useGlobalContext() - useImperativeHandle(ref,()=>({ - reload: ()=>{tableRef.current?.reload()} - })) - - const getTableDataSource = ()=>{ - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - return request(searchWord).then(response=>{ - const {data,success} = response - setTableListDataSource(data) - return {data, success} - }).catch(() => { - return {data:[], success:false} - }) + useImperativeHandle(ref, () => ({ + reload: () => { + tableRef.current?.reload() } + })) - const columns = useMemo(()=>[...TableType[type]].map((x)=>({ + const getTableDataSource = () => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) + } + return request(searchWord) + .then((response) => { + const { data, success } = response + setTableListDataSource(data) + return { data, success } + }) + .catch(() => { + return { data: [], success: false } + }) + } + + const columns = useMemo( + () => + [...TableType[type]].map((x) => ({ ...x, - title:{$t(x.title as StringifyOptions)} - })),[type, state]) + title: {$t(x.title as StringifyOptions)} + })), + [type, state] + ) - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:1, - fixed:'right', - hideInSetting:true, - valueType: 'option', - render: (_: React.ReactNode, entity: unknown) => [ - onRowClick(entity)} btnTitle="查看"/> - ], - } - ] - - return ( -
- { - setSearchWord(e.target.value) - }} - onChange={() => { - setTableHttpReload(false) - }} - noTop={noTop} - />
) + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 1, + fixed: 'right', + hideInSetting: true, + valueType: 'option', + render: (_: React.ReactNode, entity: unknown) => [ + onRowClick(entity)} + btnTitle="查看" + /> + ] + } + ] + + return ( +
+ { + setSearchWord(e.target.value) + }} + onChange={() => { + setTableHttpReload(false) + }} + noTop={noTop} + /> +
+ ) }) -export default MonitorTable; \ No newline at end of file +export default MonitorTable diff --git a/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx b/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx index aa87146d..68a7ca83 100644 --- a/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx @@ -1,322 +1,485 @@ +import { App, Button, Tabs, TabsProps, Empty, Drawer, Spin } from 'antd' +import dayjs from 'dayjs' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import { useState, useEffect, useRef, useReducer } from 'react' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { + SummaryPieData, + SearchBody, + PieData, + MonitorApiData, + MonitorSubscriberData, + InvokeData, + MessageData +} from '@dashboard/const/type' +import { getTime, getTimeUnit, changeNumberUnit } from '../utils/dashboard' +import ScrollableSection from '@common/components/aoplatform/ScrollableSection' +import { RangeValue, TimeRange } from '@common/components/aoplatform/TimeRangeSelector' +import TimeRangeSelector from '@common/components/aoplatform/TimeRangeSelector' +import MonitorLineGraph from './MonitorLineGraph' +import MonitorPieGraph from './MonitorPieGraph' +import MonitorTable, { MonitorTableHandler } from './MonitorTable' +import { CloseOutlined, ExpandOutlined, LoadingOutlined } from '@ant-design/icons' +import DashboardDetail from '@dashboard/pages/DashboardDetail' +import { $t } from '@common/locales' -import { App, Button, Tabs, TabsProps, Empty, Drawer, Spin } from "antd"; -import dayjs from "dayjs"; -import customParseFormat from "dayjs/plugin/customParseFormat"; -import { useState, useEffect, useRef, useReducer } from "react"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { SummaryPieData, SearchBody, PieData, MonitorApiData, MonitorSubscriberData, InvokeData, MessageData } from "@dashboard/const/type"; -import { getTime, getTimeUnit, changeNumberUnit } from "../utils/dashboard"; -import ScrollableSection from "@common/components/aoplatform/ScrollableSection"; -import { RangeValue, TimeRange } from "@common/components/aoplatform/TimeRangeSelector"; -import TimeRangeSelector from "@common/components/aoplatform/TimeRangeSelector"; -import MonitorLineGraph from "./MonitorLineGraph"; -import MonitorPieGraph from "./MonitorPieGraph"; -import MonitorTable, { MonitorTableHandler } from "./MonitorTable"; -import { CloseOutlined, ExpandOutlined, LoadingOutlined } from "@ant-design/icons"; -import DashboardDetail from "@dashboard/pages/DashboardDetail"; -import { $t } from "@common/locales"; - -dayjs.extend(customParseFormat); +dayjs.extend(customParseFormat) export type MonitorTotalPageProps = { - fetchPieData:(body:SearchBody)=>Promise> - fetchInvokeData:(body:SearchBody)=>Promise> - fetchMessageData:(body:SearchBody)=>Promise> - fetchTableData:(body:SearchBody,type: 'api' | 'subscriber'|'provider')=>Promise> - goToDetail:(body:SearchBody,val: MonitorApiData|MonitorSubscriberData, type: string) => void + fetchPieData: (body: SearchBody) => Promise> + fetchInvokeData: (body: SearchBody) => Promise> + fetchMessageData: (body: SearchBody) => Promise> + fetchTableData: ( + body: SearchBody, + type: 'api' | 'subscriber' | 'provider' + ) => Promise> + goToDetail: (body: SearchBody, val: MonitorApiData | MonitorSubscriberData, type: string) => void } const ACTIONS = { REQUEST_COMPLETE: 'REQUEST_COMPLETE', - RESET: 'RESET', -}; + RESET: 'RESET' +} const initialState = { getPieData: false, getInvokeData: false, getMessageData: false, - getTablesData: false, -}; + getTablesData: false +} -function reducer(state: typeof initialState, action: { type: string, payload?: string }) { +function reducer(state: typeof initialState, action: { type: string; payload?: string }) { switch (action.type) { case ACTIONS.REQUEST_COMPLETE: - return { ...state, [action.payload!]: true }; + return { ...state, [action.payload!]: true } case ACTIONS.RESET: - return initialState; + return initialState default: - return state; + return state } } -const MonitorTotalPage = (props:MonitorTotalPageProps) => { - const {fetchPieData,fetchInvokeData,fetchMessageData,fetchTableData} = props - const { message } = App.useApp() - const [ queryData, setQueryData] = useState() - const [timeButton, setTimeButton] = useState<''|'hour'|'day'|'threeDays'|'sevenDays'>('hour'); - const [datePickerValue, setDatePickerValue] = useState(); - const [requestStatic, setRequestStatic] = useState(); - const [proxyStatic, setProxyStatic] = useState(); - const [requestPie, setRequestPie] = useState<{ [key: string]: number }>({}); - const [proxyPie, setProxyPie] = useState<{ [key: string]: number }>({}); - const [requestSucRate, setRequestSucRate] = useState('0%'); - const [proxySucRate, setProxySucRate] = useState('0%'); - const [invokeStatic, setInvokeStatic] = useState({ date: [], requestRate: [], requestTotal: [], proxyRate: [], proxyTotal: [], status_4xx: [], status_5xx: [] }); - const [trafficStatic, setTrafficStatic] = useState({ date: [], requestMessage: [], responseMessage: [] }); - const [pieError, setPieError] = useState(false) - const [invokeStaticError,setInvokeStaticError] = useState(false) - const [trafficStaticError,setTrafficStaticError] = useState(false) - const [timeUnit, setTimeUnit] = useState() - const monitorApiTableRef = useRef(null) - const monitorSubTableRef = useRef(null) - const [detailEntityName,setDetailEntityName]= useState('') - const [detailType,setDetailType]= useState<'api'|'provider'|'subscriber'>() - const [drawerOpen, setDrawerOpen] = useState(false); - const [detailId, setDetailId] = useState() - const [fullScreen, setFullScreen] = useState(false) - const [recordQuery, setRecordQuery] = useState() - const [queryBtnLoading, setQueryBtnLoading] = useState(false) - const [totalEmpty, setTotalEmpty] = useState(false) - const [requestStatus, dispatch] = useReducer(reducer, initialState); - - useEffect(() => { - const isLoading = Object.values(requestStatus).every(status => status !== true); - setQueryBtnLoading(isLoading); - }, [requestStatus]); +const MonitorTotalPage = (props: MonitorTotalPageProps) => { + const { fetchPieData, fetchInvokeData, fetchMessageData, fetchTableData } = props + const { message } = App.useApp() + const [queryData, setQueryData] = useState() + const [timeButton, setTimeButton] = useState<'' | 'hour' | 'day' | 'threeDays' | 'sevenDays'>('hour') + const [datePickerValue, setDatePickerValue] = useState() + const [requestStatic, setRequestStatic] = useState() + const [proxyStatic, setProxyStatic] = useState() + const [requestPie, setRequestPie] = useState<{ [key: string]: number }>({}) + const [proxyPie, setProxyPie] = useState<{ [key: string]: number }>({}) + const [requestSucRate, setRequestSucRate] = useState('0%') + const [proxySucRate, setProxySucRate] = useState('0%') + const [invokeStatic, setInvokeStatic] = useState({ + date: [], + requestRate: [], + requestTotal: [], + proxyRate: [], + proxyTotal: [], + status_4xx: [], + status_5xx: [] + }) + const [trafficStatic, setTrafficStatic] = useState({ date: [], requestMessage: [], responseMessage: [] }) + const [pieError, setPieError] = useState(false) + const [invokeStaticError, setInvokeStaticError] = useState(false) + const [trafficStaticError, setTrafficStaticError] = useState(false) + const [timeUnit, setTimeUnit] = useState() + const monitorApiTableRef = useRef(null) + const monitorSubTableRef = useRef(null) + const [detailEntityName, setDetailEntityName] = useState('') + const [detailType, setDetailType] = useState<'api' | 'provider' | 'subscriber'>() + const [drawerOpen, setDrawerOpen] = useState(false) + const [detailId, setDetailId] = useState() + const [fullScreen, setFullScreen] = useState(false) + const [recordQuery, setRecordQuery] = useState< + SearchBody & { timeButton: '' | 'hour' | 'day' | 'threeDays' | 'sevenDays' } + >() + const [queryBtnLoading, setQueryBtnLoading] = useState(false) + const [totalEmpty, setTotalEmpty] = useState(false) + const [requestStatus, dispatch] = useReducer(reducer, initialState) - useEffect(() => { - getMonitorData(); - }, []); - - const getMonitorData = () => { - // setTotalEmpty(true) - dispatch({ type: ACTIONS.RESET }); - // ...根据时间和集群获取监控数据... - let query = queryData - if(!queryData || queryData.start === undefined){ - const { startTime, endTime } = getTime(timeButton, datePickerValue||[],) - query={...query,start: startTime, end: endTime } - } - const data:SearchBody = query! - setQueryData(data) - setRecordQuery({...data,timeButton}) - getPieData(data) - getInvokeData(data) - getMessageData(data) - monitorApiTableRef.current?.reload() - monitorSubTableRef.current?.reload() - }; - - const getPieData = (body: SearchBody) => { - fetchPieData(body) - .then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if (code === STATUS_CODE.SUCCESS) { - setPieError(false) - setRequestStatic(data.requestSummary) - setProxyStatic(data.proxySummary) - setRequestPie({ [('请求成功数')]: data.requestSummary.success, [('请求失败数')]: data.requestSummary.fail }) - setProxyPie({ [('转发成功数')]: data.proxySummary.success, [('转发失败数')]: data.proxySummary.fail }) - setPieError(false) - // this.requestPieRef?.changePieChart() - // this.proxyPieRef?.changePieChart() - setRequestSucRate(data.requestSummary.total === 0 ? '0%' : (data.requestSummary.success * 100 / data.requestSummary.total).toFixed(2) + '%') - setProxySucRate(data.proxySummary.total === 0 ? '0%' : (data.proxySummary.success * 100 / data.proxySummary.total).toFixed(2) + '%') - // setTotalEmpty(data.requestSummary.total === 0 && data.proxySummary.total === 0) - }else{ - setPieError(true) - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }).finally(()=>{ - dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getPieData' }); - }) - }; - - const getInvokeData = (body: SearchBody) => { - fetchInvokeData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if (code === STATUS_CODE.SUCCESS) { - const { timeInterval, ...arr } = data - setInvokeStatic(arr as InvokeData) - setInvokeStaticError(false) - setTimeUnit((getTimeUnit(timeInterval!))) - // this.invokeLineRef?.changeLineChart() - }else{ - setInvokeStaticError(true) - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }).finally(()=>{ - dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getInvokeData' }); - }) - }; - - const getMessageData = (body: SearchBody) => { - fetchMessageData(body).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if (code === STATUS_CODE.SUCCESS) { - setTrafficStaticError(false) - setTrafficStatic(data) - // this.trafficLineRef?.changeLineChart() - }else{ - setTrafficStaticError(true) - message.error(msg || $t(RESPONSE_TIPS.dataError)) - } - }).finally(()=>{ - dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getMessageData' }); - }) - }; - + useEffect(() => { + const isLoading = Object.values(requestStatus).every((status) => status !== true) + setQueryBtnLoading(isLoading) + }, [requestStatus]) - const getTablesData = (body: SearchBody,type: 'api' | 'subscriber'|'provider') => { - return fetchTableData(body,type).then((resp) => { - const {code,data,msg} = resp - setQueryBtnLoading(false) - if(code === STATUS_CODE.SUCCESS){ - return {data:data.top10.map((x:MonitorApiData | MonitorSubscriberData)=>{x.proxyRate = Number((x.proxyRate*100).toFixed(2));x.requestRate = Number((x.requestRate*100).toFixed(2));return x}), success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.dataError)) - return {data:[], success:false} - } - }).catch(() => { - setQueryBtnLoading(false) - return {data:[], success:false} - }).finally(() => { - dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getTablesData'}) - }) - }; - - - const resetQuery = () => { - // ...重置查询条件... - setTimeButton('hour') - setDatePickerValue(null) - setQueryData(undefined) - }; - + useEffect(() => { + getMonitorData() + }, []) - const handleTimeRangeChange = (timeRange:TimeRange) => { - setQueryData(pre => ({...pre, ...timeRange} as SearchBody )) - }; - - - const monitorTopDataTabItems:TabsProps['items'] = [ - { - label:$t('API 请求量 Top10'), - key:'api', - children:{ getDetailData(record as MonitorApiData,'api')}} request={()=>getTablesData(queryData||{},'api')}/> - }, - { - label:$t('消费者调用量 Top10'), - key:'subscribers', - children:{getDetailData(record as MonitorSubscriberData,'subscriber')}} request={()=>getTablesData(queryData||{},'subscriber')} /> - }, - { - label:$t('服务被调用量 Top10'), - key:'providers', - children:{getDetailData(record as MonitorSubscriberData,'provider')}} request={()=>getTablesData(queryData||{},'provider')} /> - } - ] - - const getDetailData = (entity:MonitorApiData|MonitorSubscriberData, type:'api'|'provider'|'subscriber')=>{ - setDetailEntityName(entity.name) - setDetailId(entity.id) - setDetailType(type) - setDrawerOpen(true) + const getMonitorData = () => { + // setTotalEmpty(true) + dispatch({ type: ACTIONS.RESET }) + // ...根据时间和集群获取监控数据... + let query = queryData + if (!queryData || queryData.start === undefined) { + const { startTime, endTime } = getTime(timeButton, datePickerValue || []) + query = { ...query, start: startTime, end: endTime } } + const data: SearchBody = query! + setQueryData(data) + setRecordQuery({ ...data, timeButton }) + getPieData(data) + getInvokeData(data) + getMessageData(data) + monitorApiTableRef.current?.reload() + monitorSubTableRef.current?.reload() + } - return ( -
- -
- -
- - -
+ const getPieData = (body: SearchBody) => { + fetchPieData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + setPieError(false) + setRequestStatic(data.requestSummary) + setProxyStatic(data.proxySummary) + setRequestPie({ ['请求成功数']: data.requestSummary.success, ['请求失败数']: data.requestSummary.fail }) + setProxyPie({ ['转发成功数']: data.proxySummary.success, ['转发失败数']: data.proxySummary.fail }) + setPieError(false) + // this.requestPieRef?.changePieChart() + // this.proxyPieRef?.changePieChart() + setRequestSucRate( + data.requestSummary.total === 0 + ? '0%' + : ((data.requestSummary.success * 100) / data.requestSummary.total).toFixed(2) + '%' + ) + setProxySucRate( + data.proxySummary.total === 0 + ? '0%' + : ((data.proxySummary.success * 100) / data.proxySummary.total).toFixed(2) + '%' + ) + // setTotalEmpty(data.requestSummary.total === 0 && data.proxySummary.total === 0) + } else { + setPieError(true) + message.error(msg || $t(RESPONSE_TIPS.dataError)) + } + }) + .finally(() => { + dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getPieData' }) + }) + } + + const getInvokeData = (body: SearchBody) => { + fetchInvokeData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + const { timeInterval, ...arr } = data + setInvokeStatic(arr as InvokeData) + setInvokeStaticError(false) + setTimeUnit(getTimeUnit(timeInterval!)) + // this.invokeLineRef?.changeLineChart() + } else { + setInvokeStaticError(true) + message.error(msg || $t(RESPONSE_TIPS.dataError)) + } + }) + .finally(() => { + dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getInvokeData' }) + }) + } + + const getMessageData = (body: SearchBody) => { + fetchMessageData(body) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + setTrafficStaticError(false) + setTrafficStatic(data) + // this.trafficLineRef?.changeLineChart() + } else { + setTrafficStaticError(true) + message.error(msg || $t(RESPONSE_TIPS.dataError)) + } + }) + .finally(() => { + dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getMessageData' }) + }) + } + + const getTablesData = (body: SearchBody, type: 'api' | 'subscriber' | 'provider') => { + return fetchTableData(body, type) + .then((resp) => { + const { code, data, msg } = resp + setQueryBtnLoading(false) + if (code === STATUS_CODE.SUCCESS) { + return { + data: data.top10.map((x: MonitorApiData | MonitorSubscriberData) => { + x.proxyRate = Number((x.proxyRate * 100).toFixed(2)) + x.requestRate = Number((x.requestRate * 100).toFixed(2)) + return x + }), + success: true + } + } else { + message.error(msg || $t(RESPONSE_TIPS.dataError)) + return { data: [], success: false } + } + }) + .catch(() => { + setQueryBtnLoading(false) + return { data: [], success: false } + }) + .finally(() => { + dispatch({ type: ACTIONS.REQUEST_COMPLETE, payload: 'getTablesData' }) + }) + } + + const resetQuery = () => { + // ...重置查询条件... + setTimeButton('hour') + setDatePickerValue(null) + setQueryData(undefined) + } + + const handleTimeRangeChange = (timeRange: TimeRange) => { + setQueryData((pre) => ({ ...pre, ...timeRange }) as SearchBody) + } + + const monitorTopDataTabItems: TabsProps['items'] = [ + { + label: $t('API 请求量 Top10'), + key: 'api', + children: ( + { + getDetailData(record as MonitorApiData, 'api') + }} + request={() => getTablesData(queryData || {}, 'api')} + /> + ) + }, + { + label: $t('消费者调用量 Top10'), + key: 'subscribers', + children: ( + { + getDetailData(record as MonitorSubscriberData, 'subscriber') + }} + request={() => getTablesData(queryData || {}, 'subscriber')} + /> + ) + }, + { + label: $t('服务被调用量 Top10'), + key: 'providers', + children: ( + { + getDetailData(record as MonitorSubscriberData, 'provider') + }} + request={() => getTablesData(queryData || {}, 'provider')} + /> + ) + } + ] + + const getDetailData = (entity: MonitorApiData | MonitorSubscriberData, type: 'api' | 'provider' | 'subscriber') => { + setDetailEntityName(entity.name) + setDetailId(entity.id) + setDetailType(type) + setDrawerOpen(true) + } + + return ( +
+ +
+ +
+ +
- } spinning={queryBtnLoading}> - {totalEmpty ?: -
+
+ } + spinning={queryBtnLoading} + > + {totalEmpty ? ( + + ) : ( +
{/* 图表区域 */} -
+
{/* 请求统计饼图 */} - {pieError ? : } + {pieError ? ( + + ) : ( + + )} {/* 转发统计饼图 */} - {pieError ? : } + {pieError ? ( + + ) : ( + + )}
{/* 折线图区域 */} -
- {/* 调用量统计折线图 */} - {invokeStaticError ? : } +
+ {/* 调用量统计折线图 */} + {invokeStaticError ? ( + + ) : ( + + )} {/* 报文量统计折线图 */} - {trafficStaticError ? :} -
+ {trafficStaticError ? ( + + ) : ( + + )} +
{/* 表格区域 */}
- - + - {fullScreen && {setFullScreen(false)}}> - {$t('退出全屏')} - } - {detailEntityName}{$t('调用详情')} - {!fullScreen && {setFullScreen(true)}}/>} - } - width={fullScreen ? '100%' : '60%'} - onClose={()=>setDrawerOpen(false)} - open={drawerOpen}> - + className={fullScreen ? 'h-calc-100vh-minus-navbar mt-navbar-height' : ''} + mask={!fullScreen} + title={ + <> + {fullScreen && ( + { + setFullScreen(false) + }} + > + + {$t('退出全屏')} + + )} + + {detailEntityName} + {$t('调用详情')} + + {!fullScreen && ( + { + setFullScreen(true) + }} + /> + )} + + } + width={fullScreen ? '100%' : '60%'} + onClose={() => setDrawerOpen(false)} + open={drawerOpen} + > +
- } + )} - +
- ) + ) } export default MonitorTotalPage diff --git a/frontend/packages/dashboard/src/const/const.tsx b/frontend/packages/dashboard/src/const/const.tsx index c62a6f62..4533db73 100644 --- a/frontend/packages/dashboard/src/const/const.tsx +++ b/frontend/packages/dashboard/src/const/const.tsx @@ -1,234 +1,228 @@ -import { MonitorApiData, MonitorData } from "./type" -import { EChartsOption } from "echarts-for-react" -import { Tooltip } from "antd" -import { } from "@common/locales" -import { PageProColumns } from "@common/components/aoplatform/PageList" +import { MonitorApiData, MonitorData } from './type' +import { EChartsOption } from 'echarts-for-react' +import {} from '@common/locales' +import { PageProColumns } from '@common/components/aoplatform/PageList' // 监控表格参数 -export const DASHBOARD_BASE_COLUMNS_CONFIG:(PageProColumns&{eoTitle:string})[] = [ - { - title:('请求总数'), - eoTitle:('请求总数'), - dataIndex: 'requestTotal', - sorter: (a,b)=> { - return a.requestTotal - b.requestTotal - }, - ellipsis:true, - width: 96 - }, - { - title: '请求成功数', - eoTitle:('请求成功数'), - dataIndex: 'requestSuccess', - width: 106, - ellipsis:true, - sorter: (a,b)=> { - return a.requestSuccess - b.requestSuccess - }, - }, - { - title: '请求成功率', - eoTitle:('请求成功率'), - dataIndex: 'requestRate', - valueType:'percent', - ellipsis:true, - sorter: (a,b)=> { - return a.requestRate - b.requestRate - }, - width: 106 - }, - { - title:('转发总数'), - eoTitle:('转发总数'), - width: 96, - dataIndex: 'proxyTotal', - ellipsis:true, - sorter: (a,b)=> { - return a.proxyTotal - b.proxyTotal - }, +export const DASHBOARD_BASE_COLUMNS_CONFIG: (PageProColumns & { eoTitle: string })[] = [ + { + title: '请求总数', + eoTitle: '请求总数', + dataIndex: 'requestTotal', + sorter: (a, b) => { + return a.requestTotal - b.requestTotal }, - { - title: '转发成功数', - eoTitle:('转发成功数'), - width: 106, - dataIndex: 'proxySuccess', - ellipsis:true, - sorter: (a,b)=> { - return a.proxySuccess - b.proxySuccess - }, + ellipsis: true, + width: 96 + }, + { + title: '请求成功数', + eoTitle: '请求成功数', + dataIndex: 'requestSuccess', + width: 106, + ellipsis: true, + sorter: (a, b) => { + return a.requestSuccess - b.requestSuccess + } + }, + { + title: '请求成功率', + eoTitle: '请求成功率', + dataIndex: 'requestRate', + valueType: 'percent', + ellipsis: true, + sorter: (a, b) => { + return a.requestRate - b.requestRate }, - { - title: '转发成功率', - eoTitle:('转发成功率'), - width: 106, - dataIndex: 'proxyRate', - valueType:'percent', - ellipsis:true, - sorter: (a,b)=> { - return a.proxyRate - b.proxyRate - }, - }, - { - title: '失败状态码数', - eoTitle:('失败状态码数'), - width: 120, - dataIndex: 'statusFail', - ellipsis:true, - sorter: (a,b)=> { - return a.statusFail - b.statusFail - }, - }, - { - title:'平均响应时间(ms)', - eoTitle:('平均响应时间(ms)'), - width: 148, - dataIndex: 'avgResp', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.avgResp - b.avgResp - }, - }, - { - title:('最大响应时间(ms)'), - eoTitle:('最大响应时间(ms)'), - width: 148, - dataIndex: 'maxResp', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.maxResp - b.maxResp - }, - }, - { - // title:('最小响应时间(ms)', - title: ('最小响应时间(ms)'), - eoTitle:('最小响应时间(ms)'), - width: 148, - dataIndex: 'minResp', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.minResp - b.minResp - }, - }, - { - // title:('平均请求流量(KB)', - title: '平均请求流量(KB)', - eoTitle:('平均请求流量(KB)'), - width: 148, - dataIndex: 'avgTraffic', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.avgTraffic - b.avgTraffic - }, - }, - { - title:('最大请求流量(KB)'), - eoTitle:('最大请求流量(KB)'), - width: 148, - dataIndex: 'maxTraffic', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.maxTraffic - b.maxTraffic - }, - }, - { - title:'最小请求流量(KB)', - eoTitle:('最小请求流量(KB)'), - width: 148, - dataIndex: 'minTraffic', - valueType:'digit', - ellipsis:true, - sorter: (a,b)=> { - return a.minTraffic - b.minTraffic - }, - }] - - - export const API_TABLE_GLOBAL_COLUMNS_CONFIG:(PageProColumns&{eoTitle:string})[] = [ - - { - title:('API 名称'), - eoTitle:('API 名称'), - dataIndex: 'name', - width:120, - ellipsis:true, - fixed: 'left', - disable:true - }, - { - title:('请求路径'), - eoTitle:('请求路径'), - dataIndex: 'path', - ellipsis:true, - width: 80 - }, - { - title:('所属服务'), - eoTitle:('所属服务'), - dataIndex: ['service','name'], - ellipsis:true, - width: 80 - }, - ...DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns&{eoTitle:string})[] - ] + width: 106 + }, + { + title: '转发总数', + eoTitle: '转发总数', + width: 96, + dataIndex: 'proxyTotal', + ellipsis: true, + sorter: (a, b) => { + return a.proxyTotal - b.proxyTotal + } + }, + { + title: '转发成功数', + eoTitle: '转发成功数', + width: 106, + dataIndex: 'proxySuccess', + ellipsis: true, + sorter: (a, b) => { + return a.proxySuccess - b.proxySuccess + } + }, + { + title: '转发成功率', + eoTitle: '转发成功率', + width: 106, + dataIndex: 'proxyRate', + valueType: 'percent', + ellipsis: true, + sorter: (a, b) => { + return a.proxyRate - b.proxyRate + } + }, + { + title: '失败状态码数', + eoTitle: '失败状态码数', + width: 120, + dataIndex: 'statusFail', + ellipsis: true, + sorter: (a, b) => { + return a.statusFail - b.statusFail + } + }, + { + title: '平均响应时间(ms)', + eoTitle: '平均响应时间(ms)', + width: 148, + dataIndex: 'avgResp', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.avgResp - b.avgResp + } + }, + { + title: '最大响应时间(ms)', + eoTitle: '最大响应时间(ms)', + width: 148, + dataIndex: 'maxResp', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.maxResp - b.maxResp + } + }, + { + // title:('最小响应时间(ms)', + title: '最小响应时间(ms)', + eoTitle: '最小响应时间(ms)', + width: 148, + dataIndex: 'minResp', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.minResp - b.minResp + } + }, + { + // title:('平均请求流量(KB)', + title: '平均请求流量(KB)', + eoTitle: '平均请求流量(KB)', + width: 148, + dataIndex: 'avgTraffic', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.avgTraffic - b.avgTraffic + } + }, + { + title: '最大请求流量(KB)', + eoTitle: '最大请求流量(KB)', + width: 148, + dataIndex: 'maxTraffic', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.maxTraffic - b.maxTraffic + } + }, + { + title: '最小请求流量(KB)', + eoTitle: '最小请求流量(KB)', + width: 148, + dataIndex: 'minTraffic', + valueType: 'digit', + ellipsis: true, + sorter: (a, b) => { + return a.minTraffic - b.minTraffic + } + } +] + +export const API_TABLE_GLOBAL_COLUMNS_CONFIG: (PageProColumns & { eoTitle: string })[] = [ + { + title: 'API 名称', + eoTitle: 'API 名称', + dataIndex: 'name', + width: 120, + ellipsis: true, + fixed: 'left', + disable: true + }, + { + title: '请求路径', + eoTitle: '请求路径', + dataIndex: 'path', + ellipsis: true, + width: 80 + }, + { + title: '所属服务', + eoTitle: '所属服务', + dataIndex: ['service', 'name'], + ellipsis: true, + width: 80 + }, + ...(DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns & { eoTitle: string })[]) +] + +export const APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG: (PageProColumns & { eoTitle: string })[] = [ + { + title: '消费者名称', + eoTitle: '消费者名称', + dataIndex: 'name', + width: 160, + ellipsis: true, + fixed: 'left', + disable: true + }, + { + title: '消费者 ID', + eoTitle: '消费者 ID', + dataIndex: 'id', + width: 140, + ellipsis: true, + fixed: 'left' + }, + ...(DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns & { eoTitle: string })[]) +] + +export const SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG: (PageProColumns & { eoTitle: string })[] = [ + { + title: '服务名称', + eoTitle: '服务名称', + dataIndex: 'name', + width: 160, + ellipsis: true, + fixed: 'left', + disable: true + }, + { + title: '服务 ID', + eoTitle: '服务 ID', + dataIndex: 'id', + width: 140, + ellipsis: true, + fixed: 'left' + }, + ...(DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns & { eoTitle: string })[]) +] - - - export const APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG:(PageProColumns&{eoTitle:string})[] = [ - - { - title:('消费者名称'), - eoTitle:('消费者名称'), - dataIndex: 'name', - width:160, - ellipsis:true, - fixed: 'left', - disable:true - }, - { - title:('消费者 ID'), - eoTitle:('消费者 ID'), - dataIndex: 'id', - width: 140, - ellipsis:true, - fixed: 'left' - }, - ...DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns&{eoTitle:string})[] - ] - - export const SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG:(PageProColumns&{eoTitle:string})[] = [ - - { - title:('服务名称'), - eoTitle:('服务名称'), - dataIndex: 'name', - width:160, - ellipsis:true, - fixed: 'left', - disable:true - }, - { - title:('服务 ID'), - eoTitle:('服务 ID'), - dataIndex: 'id', - width: 140, - ellipsis:true, - fixed: 'left' - }, - ...DASHBOARD_BASE_COLUMNS_CONFIG as (PageProColumns&{eoTitle:string})[] - ] - export const MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED = { - 请求总数: true, - 请求成功率: true, - 转发总数: true, - 转发成功率: true, - 状态码4xx数: false, - 状态码5xx数: false + 请求总数: true, + 请求成功率: true, + 转发总数: true, + 转发成功率: true, + 状态码4xx数: false, + 状态码5xx数: false } export const MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED = { @@ -236,36 +230,46 @@ export const MONITOR_LINE_CHART_BASIC_MESSAGE_SELECTED = { 响应报文量: true } -export const MONITOR_LINE_CHART_OPTION_CONFIG : EChartsOption = { +export const MONITOR_LINE_CHART_OPTION_CONFIG: EChartsOption = { legend: { orient: 'horizontal', top: '40', - left:'16', + left: '16', selected: MONITOR_LINE_CHART_BASIC_INVOKE_SELECTED }, tooltip: { trigger: 'axis', // 为了失败率显示成百分比,所以自定义了formatter - formatter: (params:Array>) => { + formatter: (params: Array>) => { const startHtml = params[0].axisValue + '
' const listArr = [] for (let i = 0; i < params.length; i++) { const item = params[i] // echarts会根据你定义的颜色返回一个生成好的带颜色的标记,直接实用即可 - let str = '
' + item.marker + let str = + '
' + + item.marker if (item.seriesName === '请求成功率' || item.seriesName === '转发成功率') { - str += (item.seriesName + '    ' + item.value + '%
') + str += + item.seriesName + + '   
' + + item.value + + '%
' } else { - str += (item.seriesName + '    ' + item.value + '
') + str += + item.seriesName + + '    ' + + item.value + + '
' } listArr.push(str) } return startHtml + listArr.join('') } } -}; +} -export const MONITOR_NAME_MAP : Record= { +export const MONITOR_NAME_MAP: Record = { requestTotal: '请求总数', requestRate: '请求成功率', proxyTotal: '转发总数', @@ -274,4 +278,4 @@ export const MONITOR_NAME_MAP : Record= { status_5xx: '状态码5xx数', requestMessage: '请求报文量', responseMessage: '响应报文量' -} \ No newline at end of file +} diff --git a/frontend/packages/dashboard/src/const/type.ts b/frontend/packages/dashboard/src/const/type.ts index ae53b27e..b2a9d1e6 100644 --- a/frontend/packages/dashboard/src/const/type.ts +++ b/frontend/packages/dashboard/src/const/type.ts @@ -1,208 +1,220 @@ -import { GetProps, DatePicker } from "antd" +import { GetProps, DatePicker } from 'antd' - export type SearchBody = { - clusters?:Array, - start?:number, - end?:number, - } +export type SearchBody = { + clusters?: Array + start?: number + end?: number +} - export const DashboardTypeArr:string[] = [ 'api','project'] +export const DashboardTypeArr: string[] = ['api', 'project'] - // 监控总览中的饼图数据 - export interface SummaryPieData{ - total:number, success:number, fail:number, status_4xx:number, status_5xx:number - } +// 监控总览中的饼图数据 +export interface SummaryPieData { + total: number + success: number + fail: number + status_4xx: number + status_5xx: number +} - export type PieData = { - requestSummary:SummaryPieData, - proxySummary:SummaryPieData - } - - - export interface TotalQueryData{ - uuid:string, - clusters:Array, - start:number, - end:number, - } - - export interface TableQueryData{ - pageNum:number - pageSize:number - total:number - keyword:string - } - - // export interface TableConfigEmitData{ - // thead:THEAD_TYPE[], - // config:{[k:string]:boolean} | undefined - // } - - // 从总调用数据传递到调用详情的query参数 - export interface QueryDetailData{ - time?:string - startTime?:number - endTime?:number - } - - export interface QueryData { - startTime: number - endTime: number - apiId?:string, - clusters?:Array, - appId?:string, - path?:string, - ip?:string, - addr?:string, - proxyPath?:string, - serviceName?:string - serviceTitle?:string - services?: Array - apiIds?: Array - appIds?:Array - } - - export interface StrategyQueryData { - strategyName: string - warnDimension: string | Array - status: string | number - pageNum: number - pageSize: number - total: number - } - - export interface StrategyHistoryQueryData { - startTime: number - endTime: number - pageNum: number - pageSize: number - total: number - strategyName: string - } - - export interface MonitorAlarmStrategyRuleConditionData { - compare: string - unit: string - value: number | null - } - - export interface MonitorAlarmStrategyRuleData { - channels?:Array<{uuid:string, type:number}> - channelUuids: Array - condition: MonitorAlarmStrategyRuleConditionData[] - } - - export interface MonitorAlarmStrategyData { - uuid: string - title: string - desc: string - isEnable: boolean - dimension: string - target: { - rule?: string - values?: Array - } - quota: string - every: number - rule: MonitorAlarmStrategyRuleData[] - continuity: number - hourMax: number - users: Array - } - - export interface MonitorAlarmStrategyListData { - uuid: string - strategyTitle: string - warnDimension: string - warnTarget: string - warnRule: string - warnFrequency: string - isEnable: boolean - operator: string - updateTime: string - createTime: string - } - - export interface MonitorAlarmHistoryData { - strategyTitle: string - warnTarget: string - warnContent: string - createTime: string - } - - export interface MonitorAlarmChannelsData { - uuid: string - title: string - type: 1 | 2 - } - - export interface MonitorAlarmStrategyTargetValueData { - contain: Array - notContain: Array - [key: string]: Array - } - - // 监控基础数据(表格用) - export interface MonitorData{ - id:string - name:string - requestTotal:number, - requestSuccess:number, - requestRate:number - proxyTotal:number, - proxySuccess:number, - proxyRate:number, - statusFail:number, - avgResp:number, - maxResp:number, - minResp:number, - avgTraffic:number, - maxTraffic:number, - minTraffic:number, - } - - // 监控Api数据(表格用) - export interface MonitorApiData extends MonitorData{ - project:string, - path:string, - } +export type PieData = { + requestSummary: SummaryPieData + proxySummary: SummaryPieData +} - export interface MonitorSubscriberData extends MonitorData{ - } +export interface TotalQueryData { + uuid: string + clusters: Array + start: number + end: number +} - export interface MonitorApiProxyData extends MonitorData{ - apiId?:string, - apiName:string - } - - export interface MonitorPathData extends MonitorData{ - path:string - } - - export interface MonitorProxyData extends MonitorData{ - proxyPath:string - } - - export interface MonitorProxyTableConfig { - proxyTotal:boolean, proxySuccess :boolean, proxyRate :boolean, statusFail:boolean, - avgResp :boolean, maxResp :boolean, minResp:boolean, avgTraffic :boolean, maxTraffic:boolean, minTraffic:boolean, - [k:string]:boolean - } - - export interface MonitorRequestTableConfig extends MonitorProxyTableConfig { - requestTotal:boolean, requestSuccess:boolean, requestRate:boolean - } - -export type RangePickerProps = GetProps; +export interface TableQueryData { + pageNum: number + pageSize: number + total: number + keyword: string +} +// export interface TableConfigEmitData{ +// thead:THEAD_TYPE[], +// config:{[k:string]:boolean} | undefined +// } + +// 从总调用数据传递到调用详情的query参数 +export interface QueryDetailData { + time?: string + startTime?: number + endTime?: number +} + +export interface QueryData { + startTime: number + endTime: number + apiId?: string + clusters?: Array + appId?: string + path?: string + ip?: string + addr?: string + proxyPath?: string + serviceName?: string + serviceTitle?: string + services?: Array + apiIds?: Array + appIds?: Array +} + +export interface StrategyQueryData { + strategyName: string + warnDimension: string | Array + status: string | number + pageNum: number + pageSize: number + total: number +} + +export interface StrategyHistoryQueryData { + startTime: number + endTime: number + pageNum: number + pageSize: number + total: number + strategyName: string +} + +export interface MonitorAlarmStrategyRuleConditionData { + compare: string + unit: string + value: number | null +} + +export interface MonitorAlarmStrategyRuleData { + channels?: Array<{ uuid: string; type: number }> + channelUuids: Array + condition: MonitorAlarmStrategyRuleConditionData[] +} + +export interface MonitorAlarmStrategyData { + uuid: string + title: string + desc: string + isEnable: boolean + dimension: string + target: { + rule?: string + values?: Array + } + quota: string + every: number + rule: MonitorAlarmStrategyRuleData[] + continuity: number + hourMax: number + users: Array +} + +export interface MonitorAlarmStrategyListData { + uuid: string + strategyTitle: string + warnDimension: string + warnTarget: string + warnRule: string + warnFrequency: string + isEnable: boolean + operator: string + updateTime: string + createTime: string +} + +export interface MonitorAlarmHistoryData { + strategyTitle: string + warnTarget: string + warnContent: string + createTime: string +} + +export interface MonitorAlarmChannelsData { + uuid: string + title: string + type: 1 | 2 +} + +export interface MonitorAlarmStrategyTargetValueData { + contain: Array + notContain: Array + [key: string]: Array +} + +// 监控基础数据(表格用) +export interface MonitorData { + id: string + name: string + requestTotal: number + requestSuccess: number + requestRate: number + proxyTotal: number + proxySuccess: number + proxyRate: number + statusFail: number + avgResp: number + maxResp: number + minResp: number + avgTraffic: number + maxTraffic: number + minTraffic: number +} + +// 监控Api数据(表格用) +export interface MonitorApiData extends MonitorData { + project: string + path: string +} + +export interface MonitorSubscriberData extends MonitorData {} + +export interface MonitorApiProxyData extends MonitorData { + apiId?: string + apiName: string +} + +export interface MonitorPathData extends MonitorData { + path: string +} + +export interface MonitorProxyData extends MonitorData { + proxyPath: string +} + +export interface MonitorProxyTableConfig { + proxyTotal: boolean + proxySuccess: boolean + proxyRate: boolean + statusFail: boolean + avgResp: boolean + maxResp: boolean + minResp: boolean + avgTraffic: boolean + maxTraffic: boolean + minTraffic: boolean + [k: string]: boolean +} + +export interface MonitorRequestTableConfig extends MonitorProxyTableConfig { + requestTotal: boolean + requestSuccess: boolean + requestRate: boolean +} + +export type RangePickerProps = GetProps export type BaseQueryData = { clusters: Array - date: Array + date: Array } export type TableList = { - api:Array, subscribers:Array + api: Array + subscribers: Array } // 监控告警中用到的折线图封装 @@ -212,20 +224,20 @@ export type TableList = { // 调用趋势:六条线,并且会出现需要加入对比的可能 // 预留数据给标题,标题根据需求 export type InvokeData = { - date:Array - requestTotal:Array - requestRate:Array - proxyTotal:Array - proxyRate:Array - status_4xx:Array - status_5xx:Array - timeInterval?:string + date: Array + requestTotal: Array + requestRate: Array + proxyTotal: Array + proxyRate: Array + status_4xx: Array + status_5xx: Array + timeInterval?: string } export type MessageData = { - date:Array - requestMessage:Array - responseMessage:Array + date: Array + requestMessage: Array + responseMessage: Array } -export type LineGraphType = 'invoke'|'invokeService'|'traffic' \ No newline at end of file +export type LineGraphType = 'invoke' | 'invokeService' | 'traffic' diff --git a/frontend/packages/dashboard/src/pages/Dashboard.tsx b/frontend/packages/dashboard/src/pages/Dashboard.tsx index f5f683e2..8eb762d7 100644 --- a/frontend/packages/dashboard/src/pages/Dashboard.tsx +++ b/frontend/packages/dashboard/src/pages/Dashboard.tsx @@ -1,61 +1,73 @@ +import { useEffect, useState } from 'react' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' +import DashboardPage from './DashboardTabPage' +import { $t } from '@common/locales' +import { useFetch } from '@common/hooks/http' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { App, Spin } from 'antd' +import { reject } from 'lodash-es' +import { EntityItem } from '@common/const/type' +import { LoadingOutlined } from '@ant-design/icons' +import DashboardInstruction from './DashboardInstruction' -import { useEffect, useState } from "react"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import DashboardPage from "./DashboardTabPage"; -import { $t } from "@common/locales"; -import { useFetch } from "@common/hooks/http"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { App, Spin } from "antd"; -import { reject } from "lodash-es"; -import { EntityItem } from "@common/const/type"; -import { LoadingOutlined } from "@ant-design/icons"; -import DashboardInstruction from "./DashboardInstruction"; - -export default function Dashboard(){ - const { setBreadcrumb } = useBreadcrumb() - const {fetchData } = useFetch() - const { message } = App.useApp() - const [clusters, setClusters] = useState>([]) - const [enabledClusters, setEnabledClusters] = useState>([]) - const [loading, setLoading] = useState(false) - const getClusters = ()=>{ - setLoading(true) - fetchData}>>('simple/monitor/clusters',{ - method: 'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const filteredCluster = data?.clusters?.filter(x=>x.enable) - setClusters(data.cluster || []) - setEnabledClusters(filteredCluster) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)).finally(()=>setLoading(false)) - } - - useEffect(() => { - getClusters() - setBreadcrumb([ - { - title:$t('运行视图') - }, - ]) - - - }, []); - - return ( - <> - } spinning={loading}> - { - !loading && <> - { - enabledClusters.length > 0 ? - : - } - - } - - +export default function Dashboard() { + const { setBreadcrumb } = useBreadcrumb() + const { fetchData } = useFetch() + const { message } = App.useApp() + const [clusters, setClusters] = useState>([]) + const [enabledClusters, setEnabledClusters] = useState>([]) + const [loading, setLoading] = useState(false) + const getClusters = () => { + setLoading(true) + fetchData }>>( + 'simple/monitor/clusters', + { + method: 'GET' + } ) -} \ No newline at end of file + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const filteredCluster = data?.clusters?.filter((x) => x.enable) + setClusters(data.cluster || []) + setEnabledClusters(filteredCluster) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + .finally(() => setLoading(false)) + } + + useEffect(() => { + getClusters() + setBreadcrumb([ + { + title: $t('运行视图') + } + ]) + }, []) + + return ( + <> + } + spinning={loading} + > + {!loading && ( + <> + {enabledClusters.length > 0 ? ( + + ) : ( + + )} + + )} + + + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardApiList.tsx b/frontend/packages/dashboard/src/pages/DashboardApiList.tsx index aebc7531..32e96079 100644 --- a/frontend/packages/dashboard/src/pages/DashboardApiList.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardApiList.tsx @@ -1,40 +1,71 @@ +import { useState } from 'react' +import { useParams } from 'react-router-dom' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import MonitorApiPage, { MonitorApiQueryData } from '@dashboard/component/MonitorApiPage' +import { BasicResponse, STATUS_CODE } from '@common/const/const' +import { SearchBody, MonitorApiData } from '@dashboard/const/type' +import { useFetch } from '@common/hooks/http' +import DashboardDetail from './DashboardDetail' +import { TimeRangeButton } from '@common/components/aoplatform/TimeRangeSelector' -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import MonitorApiPage, { MonitorApiQueryData } from "@dashboard/component/MonitorApiPage"; -import { BasicResponse, STATUS_CODE } from "@common/const/const"; -import { SearchBody, MonitorApiData } from "@dashboard/const/type"; -import { useFetch } from "@common/hooks/http"; -import DashboardDetail from "./DashboardDetail"; -import { TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; - -export default function DashboardApiList(){ - const { dashboardType} = useParams() - const {fetchData } = useFetch() +export default function DashboardApiList() { + const { dashboardType } = useParams() + const { fetchData } = useFetch() const [fullScreen, setFullScreen] = useState(false) - const [queryData, setQueryData] = useState(); + const [queryData, setQueryData] = useState() const [detailId, setDetailId] = useState() - const [timeButton, setTimeButton] = useState('hour'); - const [detailEntityName,setDetailEntityName]= useState('') - + const [timeButton, setTimeButton] = useState('hour') + const [detailEntityName, setDetailEntityName] = useState('') - const fetchTableData:(body:SearchBody)=>Promise>= (body:SearchBody) =>fetchData>('monitor/api',{ - method:'POST', - eoBody:({...body, dataType:'api'}), - eoTransformKeys:['dataType','request_total','request_success','request_rate','proxy_total','proxy_success','proxy_rate','status_fail','avg_resp','max_resp','min_resp','avg_traffic','max_traffic','min_traffic','min_traffic']}).then((resp)=>{ - if(resp.code === STATUS_CODE.SUCCESS){ - setQueryData({...body}) - return resp - } - }) + const fetchTableData: (body: SearchBody) => Promise> = ( + body: SearchBody + ) => + fetchData>('monitor/api', { + method: 'POST', + eoBody: { ...body, dataType: 'api' }, + eoTransformKeys: [ + 'dataType', + 'request_total', + 'request_success', + 'request_rate', + 'proxy_total', + 'proxy_success', + 'proxy_rate', + 'status_fail', + 'avg_resp', + 'max_resp', + 'min_resp', + 'avg_traffic', + 'max_traffic', + 'min_traffic', + 'min_traffic' + ] + }).then((resp) => { + if (resp.code === STATUS_CODE.SUCCESS) { + setQueryData({ ...body }) + return resp + } + }) - - return (} fullScreen={fullScreen} setFullScreen={setFullScreen} setDetailId={setDetailId}/>) -} \ No newline at end of file + return ( + + } + fullScreen={fullScreen} + setFullScreen={setFullScreen} + setDetailId={setDetailId} + /> + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardApplicationList.tsx b/frontend/packages/dashboard/src/pages/DashboardApplicationList.tsx index 91f734a2..4beb6cb3 100644 --- a/frontend/packages/dashboard/src/pages/DashboardApplicationList.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardApplicationList.tsx @@ -1,42 +1,73 @@ - -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { BasicResponse, STATUS_CODE } from "@common/const/const"; -import { SearchBody, MonitorSubscriberData } from "@dashboard/const/type"; -import { useFetch } from "@common/hooks/http"; -import { MonitorSubQueryData } from "@dashboard/component/MonitorSubPage"; +import { useState } from 'react' +import { useParams } from 'react-router-dom' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { BasicResponse, STATUS_CODE } from '@common/const/const' +import { SearchBody, MonitorSubscriberData } from '@dashboard/const/type' +import { useFetch } from '@common/hooks/http' +import { MonitorSubQueryData } from '@dashboard/component/MonitorSubPage' import MonitorAppPage from '@dashboard/component/MonitorAppPage' -import DashboardDetail from "./DashboardDetail"; -import { TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; +import DashboardDetail from './DashboardDetail' +import { TimeRangeButton } from '@common/components/aoplatform/TimeRangeSelector' -export default function DashboardApplicationList(){ +export default function DashboardApplicationList() { const { dashboardType } = useParams() - const {fetchData } = useFetch() + const { fetchData } = useFetch() const [fullScreen, setFullScreen] = useState(false) - const [queryData, setQueryData] = useState({type:'subscriber'}); + const [queryData, setQueryData] = useState({ type: 'subscriber' }) const [detailId, setDetailId] = useState() - const [timeButton, setTimeButton] = useState('hour'); - const [detailEntityName,setDetailEntityName]= useState('') + const [timeButton, setTimeButton] = useState('hour') + const [detailEntityName, setDetailEntityName] = useState('') - const fetchTableData:(body:SearchBody)=>Promise>= (body:MonitorSubQueryData) =>{ - return fetchData>(`monitor/subscriber`,{ - method:'POST', - eoBody:({...body, dataType:'subscriber'}), - eoTransformKeys:['dataType','request_total','request_success','request_rate','proxy_total','proxy_success','proxy_rate','status_fail','avg_resp','max_resp','min_resp','avg_traffic','max_traffic','min_traffic','min_traffic']}).then(resp => { - if (resp.code === STATUS_CODE.SUCCESS) { - setQueryData({...body}) - return resp - } - }) - } + const fetchTableData: (body: SearchBody) => Promise> = ( + body: MonitorSubQueryData + ) => { + return fetchData>(`monitor/subscriber`, { + method: 'POST', + eoBody: { ...body, dataType: 'subscriber' }, + eoTransformKeys: [ + 'dataType', + 'request_total', + 'request_success', + 'request_rate', + 'proxy_total', + 'proxy_success', + 'proxy_rate', + 'status_fail', + 'avg_resp', + 'max_resp', + 'min_resp', + 'avg_traffic', + 'max_traffic', + 'min_traffic', + 'min_traffic' + ] + }).then((resp) => { + if (resp.code === STATUS_CODE.SUCCESS) { + setQueryData({ ...body }) + return resp + } + }) + } - - return (} fullScreen={fullScreen} setFullScreen={setFullScreen} setDetailId={setDetailId}/>) -} \ No newline at end of file + return ( + + } + fullScreen={fullScreen} + setFullScreen={setFullScreen} + setDetailId={setDetailId} + /> + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardDetail.tsx b/frontend/packages/dashboard/src/pages/DashboardDetail.tsx index d9b1a4da..d7087172 100644 --- a/frontend/packages/dashboard/src/pages/DashboardDetail.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardDetail.tsx @@ -1,53 +1,108 @@ - -import MonitorDetailPage from "../component/MonitorDetailPage" -import { BasicResponse } from "@common/const/const" -import { SearchBody, MonitorApiData, InvokeData, MonitorSubscriberData } from "@dashboard/const/type" -import { useFetch } from "@common/hooks/http" -import { MonitorApiQueryData } from "@dashboard/component/MonitorApiPage" -import { MonitorSubQueryData } from "@dashboard/component/MonitorSubPage" -import { TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector" +import MonitorDetailPage from '../component/MonitorDetailPage' +import { BasicResponse } from '@common/const/const' +import { SearchBody, MonitorApiData, InvokeData, MonitorSubscriberData } from '@dashboard/const/type' +import { useFetch } from '@common/hooks/http' +import { MonitorApiQueryData } from '@dashboard/component/MonitorApiPage' +import { MonitorSubQueryData } from '@dashboard/component/MonitorSubPage' +import { TimeRangeButton } from '@common/components/aoplatform/TimeRangeSelector' export type DashboardDetailInvokeType = { - tendency:InvokeData, timeInterval:string + tendency: InvokeData + timeInterval: string } -export default function DashboardDetail({fullScreen,name,queryData,dashboardDetailId,dashboardType}:{fullScreen?:boolean,name:string,queryData:(MonitorApiQueryData|MonitorSubQueryData)&{timeButton:TimeRangeButton},dashboardDetailId:string,dashboardType:'api'|'subscriber'|'provider'}){ - const {fetchData } = useFetch() - - const fetchTableData:(body:SearchBody)=>Promise> - = (body:SearchBody) =>fetchData>( - `monitor/${getType(dashboardType as ("api" | "subscriber"),body)}/statistics/${getType(dashboardType as ("api" | "subscriber"),body,true)}`,{ - method:'POST', - eoParams:{id:dashboardDetailId}, - eoBody:({...body}), - eoTransformKeys:['dataType','request_total','request_success','request_rate','proxy_total','proxy_success','proxy_rate','status_fail','avg_resp','max_resp','min_resp','avg_traffic','max_traffic','min_traffic','min_traffic']}) +export default function DashboardDetail({ + fullScreen, + name, + queryData, + dashboardDetailId, + dashboardType +}: { + fullScreen?: boolean + name: string + queryData: (MonitorApiQueryData | MonitorSubQueryData) & { timeButton: TimeRangeButton } + dashboardDetailId: string + dashboardType: 'api' | 'subscriber' | 'provider' +}) { + const { fetchData } = useFetch() - - const fetchInvokeData:(body:SearchBody)=>Promise> - = (body:SearchBody) =>fetchData>( - `monitor/${getType(dashboardType,body)}/trend`,{ - method:'POST', - eoParams:{id:dashboardDetailId}, - eoBody:({...body}), - eoTransformKeys:['dataType','request_total','request_rate','proxy_total','proxy_rate','time_interval']}) + const fetchTableData: ( + body: SearchBody + ) => Promise> = (body: SearchBody) => + fetchData>( + `monitor/${getType(dashboardType as 'api' | 'subscriber', body)}/statistics/${getType(dashboardType as 'api' | 'subscriber', body, true)}`, + { + method: 'POST', + eoParams: { id: dashboardDetailId }, + eoBody: { ...body }, + eoTransformKeys: [ + 'dataType', + 'request_total', + 'request_success', + 'request_rate', + 'proxy_total', + 'proxy_success', + 'proxy_rate', + 'status_fail', + 'avg_resp', + 'max_resp', + 'min_resp', + 'avg_traffic', + 'max_traffic', + 'min_traffic', + 'min_traffic' + ] + } + ) + const fetchInvokeData: (body: SearchBody) => Promise> = (body: SearchBody) => + fetchData>(`monitor/${getType(dashboardType, body)}/trend`, { + method: 'POST', + eoParams: { id: dashboardDetailId }, + eoBody: { ...body }, + eoTransformKeys: ['dataType', 'request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval'] + }) - const fetchDetailInvokeData:(params:{[k:string]:string},body:SearchBody)=>Promise> - = (params:{[k:string]:string},body:SearchBody) =>fetchData>( - `monitor/${getType(dashboardType,body)}/trend/${getType(dashboardType,body,true)}`,{ - method:'POST', - eoParams:{[getType(dashboardType,body)]:dashboardDetailId,[getType(dashboardType,body,true)]:params.id}, - eoBody:({...body}), - eoTransformKeys:['dataType','request_total','request_rate','proxy_total','proxy_rate','time_interval']}) + const fetchDetailInvokeData: ( + params: { [k: string]: string }, + body: SearchBody + ) => Promise> = (params: { [k: string]: string }, body: SearchBody) => + fetchData>( + `monitor/${getType(dashboardType, body)}/trend/${getType(dashboardType, body, true)}`, + { + method: 'POST', + eoParams: { + [getType(dashboardType, body)]: dashboardDetailId, + [getType(dashboardType, body, true)]: params.id + }, + eoBody: { ...body }, + eoTransformKeys: ['dataType', 'request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval'] + } + ) - - const getType = (initType:'api'|'subscriber', body:MonitorApiQueryData|MonitorSubQueryData,reserve?:boolean) => { - const newType = initType === 'api' ? 'api' : (body as MonitorSubQueryData)?.type ?? 'subscriber' - if(reserve){ - return newType === 'api' ? (body as MonitorSubQueryData)?.type ?? 'subscriber' : 'api' - } - return newType + const getType = ( + initType: 'api' | 'subscriber', + body: MonitorApiQueryData | MonitorSubQueryData, + reserve?: boolean + ) => { + const newType = initType === 'api' ? 'api' : ((body as MonitorSubQueryData)?.type ?? 'subscriber') + if (reserve) { + return newType === 'api' ? ((body as MonitorSubQueryData)?.type ?? 'subscriber') : 'api' } + return newType + } - return () - } \ No newline at end of file + return ( + + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx b/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx index f3f577db..93fe7233 100644 --- a/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx @@ -1,32 +1,56 @@ +import WithPermission from '@common/components/aoplatform/WithPermission' +import { $t } from '@common/locales' -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { $t } from "@common/locales"; -import { Link } from "react-router-dom"; - -export default function DashboardInstruction({showClusterIns, showMonitorIns}:{showClusterIns:boolean, showMonitorIns:boolean}) { - return ( -
-
-

{$t('集群配置并开启监控')}

-

{$t('监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;')}

- {/*

更多配置问题,请点击帮助中心 +export default function DashboardInstruction({ + showClusterIns, + showMonitorIns +}: { + showClusterIns: boolean + showMonitorIns: boolean +}) { + return ( +

+
+

{$t('集群配置并开启监控')}

+

+ {$t('监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;')} +

+ {/*

更多配置问题,请点击帮助中心 {/* 查看更多 *

*/} -
- {showClusterIns &&
-

{$t('集群配置')}

-

{$t('配置集群地址,以确保监控系统能够正确识别和连接到集群')}

-

{$t('配置集群信息')}

-
} - {showMonitorIns && -
-

{$t('监控设置')}

-

{$t('设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。')}

-

{$t('配置监控信息')}

-
- } - -
-
- ) -} \ No newline at end of file +
+ {showClusterIns && ( +
+

{$t('集群配置')}

+

+ {$t('配置集群地址,以确保监控系统能够正确识别和连接到集群')} +

+

+ + + {$t('配置集群信息')} + + +

+
+ )} + {showMonitorIns && ( +
+

{$t('监控设置')}

+

+ {$t('设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。')} +

+

+ + + {$t('配置监控信息')} + + +

+
+ )} +
+
+
+ ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardList.tsx b/frontend/packages/dashboard/src/pages/DashboardList.tsx index b3af8bf2..573e0170 100644 --- a/frontend/packages/dashboard/src/pages/DashboardList.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardList.tsx @@ -1,24 +1,17 @@ +import { useParams } from 'react-router-dom' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import DashboardApiList from './DashboardApiList' +import DashboardProjectList from './DashboardProjectList' +import DashboardApplicationList from './DashboardApplicationList' -import { useParams } from "react-router-dom" -import { RouterParams } from "@core/components/aoplatform/RenderRoutes" -import DashboardApiList from "./DashboardApiList" -import DashboardProjectList from "./DashboardProjectList" -import DashboardApplicationList from "./DashboardApplicationList" +export default function DashboardList() { + const { dashboardType } = useParams() -export default function DashboardList(){ - const {dashboardType} = useParams() - - return ( - <> - { - dashboardType === 'api' && - } - { - dashboardType === 'subscriber' && - } - { - dashboardType === 'provider' && - } - - ) -} \ No newline at end of file + return ( + <> + {dashboardType === 'api' && } + {dashboardType === 'subscriber' && } + {dashboardType === 'provider' && } + + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardProjectList.tsx b/frontend/packages/dashboard/src/pages/DashboardProjectList.tsx index 77d7cf1d..9fde3465 100644 --- a/frontend/packages/dashboard/src/pages/DashboardProjectList.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardProjectList.tsx @@ -1,41 +1,72 @@ +import { useState } from 'react' +import { useParams } from 'react-router-dom' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { BasicResponse, STATUS_CODE } from '@common/const/const' +import { SearchBody, MonitorSubscriberData } from '@dashboard/const/type' +import { useFetch } from '@common/hooks/http' +import MonitorSubPage, { MonitorSubQueryData } from '@dashboard/component/MonitorSubPage' +import DashboardDetail from './DashboardDetail' +import { TimeRangeButton } from '@common/components/aoplatform/TimeRangeSelector' -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { BasicResponse, STATUS_CODE } from "@common/const/const"; -import { SearchBody, MonitorSubscriberData } from "@dashboard/const/type"; -import { useFetch } from "@common/hooks/http"; -import MonitorSubPage, { MonitorSubQueryData } from "@dashboard/component/MonitorSubPage"; -import DashboardDetail from "./DashboardDetail"; -import { TimeRangeButton } from "@common/components/aoplatform/TimeRangeSelector"; - -export default function DashboardProjectList(){ +export default function DashboardProjectList() { const { dashboardType } = useParams() - const {fetchData } = useFetch() + const { fetchData } = useFetch() const [fullScreen, setFullScreen] = useState(false) - const [queryData, setQueryData] = useState({type:'provider'}); + const [queryData, setQueryData] = useState({ type: 'provider' }) const [detailId, setDetailId] = useState() - const [timeButton, setTimeButton] = useState('hour'); - const [detailEntityName,setDetailEntityName]= useState('') + const [timeButton, setTimeButton] = useState('hour') + const [detailEntityName, setDetailEntityName] = useState('') - const fetchTableData:(body:SearchBody)=>Promise>= (body:MonitorSubQueryData) =>{ - return fetchData>(`monitor/provider`,{ - method:'POST', - eoBody:({...body, dataType:'subscriber'}), - eoTransformKeys:['dataType','request_total','request_success','request_rate','proxy_total','proxy_success','proxy_rate','status_fail','avg_resp','max_resp','min_resp','avg_traffic','max_traffic','min_traffic','min_traffic']}).then(resp => { - if (resp.code === STATUS_CODE.SUCCESS) { - setQueryData({...body}) - return resp - } - }) - } + const fetchTableData: (body: SearchBody) => Promise> = ( + body: MonitorSubQueryData + ) => { + return fetchData>(`monitor/provider`, { + method: 'POST', + eoBody: { ...body, dataType: 'subscriber' }, + eoTransformKeys: [ + 'dataType', + 'request_total', + 'request_success', + 'request_rate', + 'proxy_total', + 'proxy_success', + 'proxy_rate', + 'status_fail', + 'avg_resp', + 'max_resp', + 'min_resp', + 'avg_traffic', + 'max_traffic', + 'min_traffic', + 'min_traffic' + ] + }).then((resp) => { + if (resp.code === STATUS_CODE.SUCCESS) { + setQueryData({ ...body }) + return resp + } + }) + } - - return (} fullScreen={fullScreen} setFullScreen={setFullScreen} setDetailId={setDetailId}/>) -} \ No newline at end of file + return ( + + } + fullScreen={fullScreen} + setFullScreen={setFullScreen} + setDetailId={setDetailId} + /> + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx b/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx index 5abf107e..68e9d2d3 100644 --- a/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx @@ -1,49 +1,55 @@ +import { Tabs, TabsProps } from 'antd' +import DashboardTotal from './DashboardTotal' +import { Outlet, useNavigate, useParams } from 'react-router-dom' +import { useEffect, useState } from 'react' +import { $t } from '@common/locales' +import { RouterParams } from '@common/const/type' -import { Tabs, TabsProps } from "antd"; -import DashboardTotal from "./DashboardTotal"; -import { Outlet, useNavigate, useParams } from "react-router-dom"; -import { useEffect, useState } from "react"; -import { $t } from "@common/locales"; -import { RouterParams } from "@common/const/type"; +export default function DashboardTabPage() { + const { dashboardType } = useParams() + const [activeKey, setActiveKey] = useState('total') + const navigateTo = useNavigate() + useEffect(() => { + setActiveKey(dashboardType || 'total') + }, [dashboardType]) -export default function DashboardTabPage(){ - const { dashboardType} = useParams() - const [activeKey, setActiveKey] = useState('total') - const navigateTo = useNavigate() + const monitorTabItems: TabsProps['items'] = [ + { + label: $t('监控总览'), + key: 'total', + children: + }, + { + label: $t('服务被调用统计'), + key: 'subscriber', + children: + }, + { + label: $t('消费者调用统计'), + key: 'provider', + children: + }, + { + label: $t('API 调用统计'), + key: 'api', + children: + } + ] - useEffect(()=>{ - setActiveKey(dashboardType || 'total') - },[dashboardType]) - - const monitorTabItems:TabsProps['items'] = [ - { - label:$t('监控总览'), - key:'total', - children: - }, - { - label:$t('服务被调用统计'), - key:'subscriber', - children: - }, - { - label:$t('消费者调用统计'), - key:'provider', - children: - }, - { - label:$t('API 调用统计'), - key:'api', - children: - } - ] - - return (<> - { - setActiveKey(val); - navigateTo(`/analytics/${val === 'total' ? val :`${val}/list`}`) - }} - items={monitorTabItems} className="h-full overflow-hidden mt-[6px] [&>.ant-tabs-content-holder]:overflow-auto [&>.ant-tabs-content-holder]:pr-PAGE_INSIDE_X [&>.ant-tabs-content-holder>.ant-tabs-content]:h-full [&>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane]:h-full" size="small" tabBarStyle={{paddingLeft:'10px',marginTop:'0px',marginBottom:'0px'}} /> - ) -} \ No newline at end of file + return ( + <> + { + setActiveKey(val) + navigateTo(`/analytics/${val === 'total' ? val : `${val}/list`}`) + }} + items={monitorTabItems} + className="h-full overflow-hidden mt-[6px] [&>.ant-tabs-content-holder]:overflow-auto [&>.ant-tabs-content-holder]:pr-PAGE_INSIDE_X [&>.ant-tabs-content-holder>.ant-tabs-content]:h-full [&>.ant-tabs-content-holder>.ant-tabs-content>.ant-tabs-tabpane]:h-full" + size="small" + tabBarStyle={{ paddingLeft: '10px', marginTop: '0px', marginBottom: '0px' }} + /> + + ) +} diff --git a/frontend/packages/dashboard/src/pages/DashboardTotal.tsx b/frontend/packages/dashboard/src/pages/DashboardTotal.tsx index 09725d51..7c322233 100644 --- a/frontend/packages/dashboard/src/pages/DashboardTotal.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardTotal.tsx @@ -1,33 +1,89 @@ - -import { useNavigate, useParams } from "react-router-dom" -import MonitorTotalPage from "@dashboard/component/MonitorTotalPage" -import { BasicResponse } from "@common/const/const" -import { InvokeData, MessageData, MonitorApiData, MonitorSubscriberData, PieData, SearchBody } from "@dashboard/const/type" -import { useFetch } from "@common/hooks/http" -import { objectToSearchParameters } from "@common/utils/router" +import { useNavigate } from 'react-router-dom' +import MonitorTotalPage from '@dashboard/component/MonitorTotalPage' +import { BasicResponse } from '@common/const/const' +import { + InvokeData, + MessageData, + MonitorApiData, + MonitorSubscriberData, + PieData, + SearchBody +} from '@dashboard/const/type' +import { useFetch } from '@common/hooks/http' +import { objectToSearchParameters } from '@common/utils/router' export default function DashboardTotal() { - const {fetchData } = useFetch() - const navigateTo = useNavigate() - const fetchPieData:(body:SearchBody)=>Promise> = (body:SearchBody)=> fetchData>('monitor/overview/summary',{ - method:'POST', eoBody:(body),eoTransformKeys:['request_summary','proxy_summary']}) + const { fetchData } = useFetch() + const navigateTo = useNavigate() + const fetchPieData: (body: SearchBody) => Promise> = (body: SearchBody) => + fetchData>('monitor/overview/summary', { + method: 'POST', + eoBody: body, + eoTransformKeys: ['request_summary', 'proxy_summary'] + }) - const fetchInvokeData:(body:SearchBody)=>Promise> = (body:SearchBody) =>fetchData>('monitor/overview/invoke',{ - method:'POST', eoBody:(body),eoTransformKeys:['request_total','request_rate','proxy_total','proxy_rate','time_interval']}) + const fetchInvokeData: (body: SearchBody) => Promise> = (body: SearchBody) => + fetchData>('monitor/overview/invoke', { + method: 'POST', + eoBody: body, + eoTransformKeys: ['request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval'] + }) - const fetchMessageData:(body:SearchBody)=>Promise>= (body:SearchBody) =>fetchData>('monitor/overview/message',{ - method:'POST', eoBody:(body),eoTransformKeys:['time_interval','request_message','response_message']}) + const fetchMessageData: (body: SearchBody) => Promise> = (body: SearchBody) => + fetchData>('monitor/overview/message', { + method: 'POST', + eoBody: body, + eoTransformKeys: ['time_interval', 'request_message', 'response_message'] + }) - const fetchTableData:(body:SearchBody,type: 'api' | 'subscribers'|'providers')=>Promise>= (body:SearchBody,type: 'api' | 'subscribers'|'providers') =>fetchData>('monitor/overview/top10',{ - method:'POST', - eoBody:({...body, dataType:type}), - eoTransformKeys:['dataType','request_total','request_success','request_rate','proxy_total','proxy_success','proxy_rate','status_fail','avg_resp','max_resp','min_resp','avg_traffic','max_traffic','min_traffic','min_traffic','is_red']}) + const fetchTableData: ( + body: SearchBody, + type: 'api' | 'subscribers' | 'providers' + ) => Promise> = ( + body: SearchBody, + type: 'api' | 'subscribers' | 'providers' + ) => + fetchData>('monitor/overview/top10', { + method: 'POST', + eoBody: { ...body, dataType: type }, + eoTransformKeys: [ + 'dataType', + 'request_total', + 'request_success', + 'request_rate', + 'proxy_total', + 'proxy_success', + 'proxy_rate', + 'status_fail', + 'avg_resp', + 'max_resp', + 'min_resp', + 'avg_traffic', + 'max_traffic', + 'min_traffic', + 'min_traffic', + 'is_red' + ] + }) - const goToDetail:(body:SearchBody,val: MonitorApiData|MonitorSubscriberData, type: string) => void= (body:SearchBody,val: MonitorApiData|MonitorSubscriberData, type: string) => { - // ...跳转到详情页... - const { start:startTime, end:endTime, clusters} = body - navigateTo( - `/analytics/${type}/list?${objectToSearchParameters({id:val.id,clusters:clusters || undefined, start: startTime?.toString(), end: endTime?.toString(), name:val.name}).toString()}`) - }; + const goToDetail: (body: SearchBody, val: MonitorApiData | MonitorSubscriberData, type: string) => void = ( + body: SearchBody, + val: MonitorApiData | MonitorSubscriberData, + type: string + ) => { + // ...跳转到详情页... + const { start: startTime, end: endTime, clusters } = body + navigateTo( + `/analytics/${type}/list?${objectToSearchParameters({ id: val.id, clusters: clusters || undefined, start: startTime?.toString(), end: endTime?.toString(), name: val.name }).toString()}` + ) + } - return -} \ No newline at end of file + return ( + + ) +} diff --git a/frontend/packages/dashboard/src/utils/dashboard.ts b/frontend/packages/dashboard/src/utils/dashboard.ts index 7bdcb452..d81a0f60 100644 --- a/frontend/packages/dashboard/src/utils/dashboard.ts +++ b/frontend/packages/dashboard/src/utils/dashboard.ts @@ -1,98 +1,91 @@ +import { RangeValue } from '@common/components/aoplatform/TimeRangeSelector' +import { $t } from '@common/locales' -import { RangeValue } from "@common/components/aoplatform/TimeRangeSelector"; -import { $t } from "@common/locales"; - -export function getTime ( - timeButton: string, - datePickerValue: RangeValue|[], - init?: boolean - ): { startTime: number; endTime: number } { - const currentSecond = new Date().getTime() // 当前毫秒数时间戳 - let currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳 - let startMin = currentMin - 60 * 60 * 1000 - if (!init && timeButton) { - switch (timeButton) { - case 'hour': { - startMin = currentMin - 60 * 60 * 1000 - break - } - case 'day': { - startMin = currentMin - 24 * 60 * 60 * 1000 - break - } - case 'threeDays': { - startMin = - new Date(new Date().setHours(0, 0, 0, 0)).getTime() - - 2 * 24 * 60 * 60 * 1000 - break - } - case 'sevenDays': { - startMin = - new Date(new Date().setHours(0, 0, 0, 0)).getTime() - - 6 * 24 * 60 * 60 * 1000 - break - } - } - } else if (datePickerValue?.length === 2) { - startMin = datePickerValue[0]!.startOf('day').unix() - currentMin = datePickerValue[1]!.endOf('day').unix() - } - - return { startTime: startMin / 1000, endTime: currentMin / 1000 } - } - - export function getTimeUnit (timeInterval: string): string { - let timeUnit = '' - // 相差秒数 - switch (timeInterval) { - case '1m': { - timeUnit = ('每分钟') +export function getTime( + timeButton: string, + datePickerValue: RangeValue | [], + init?: boolean +): { startTime: number; endTime: number } { + const currentSecond = new Date().getTime() // 当前毫秒数时间戳 + let currentMin = currentSecond - (currentSecond % (60 * 1000)) // 当前分钟数时间戳 + let startMin = currentMin - 60 * 60 * 1000 + if (!init && timeButton) { + switch (timeButton) { + case 'hour': { + startMin = currentMin - 60 * 60 * 1000 break } - case '5m': { - timeUnit = ('每5分钟') + case 'day': { + startMin = currentMin - 24 * 60 * 60 * 1000 break } - case '1h': { - timeUnit = ('每小时') + case 'threeDays': { + startMin = new Date(new Date().setHours(0, 0, 0, 0)).getTime() - 2 * 24 * 60 * 60 * 1000 break } - case '1d': { - timeUnit = ('每天') - break - } - case '1w': { - timeUnit = ('每周') + case 'sevenDays': { + startMin = new Date(new Date().setHours(0, 0, 0, 0)).getTime() - 6 * 24 * 60 * 60 * 1000 break } } - return timeUnit - } - - // 当数据超过10万时,保留两个小数点,单位为万,如123212,显示12.32万; - export function changeNumberUnit (value?:number):{value:string, unit:string} { - if (value && value > 1000000000) { - return {value:(value && value / 100000000).toFixed(2) ,unit: ('亿')} - } else if (value && value > 1000000) { - return {value: (value && value / 10000).toFixed(0) , unit: ('万')} - } else if (value && value > 10000) { - return {value: (value && value / 10000).toFixed(2) , unit: ('万')} - } - return {value: (value ?? '-') + '', unit: (' 次')} - } - - export function yUnitFormatter (value:number):string { - let res:string = '' - if (value > 100000000) { - res = (value / 100000000).toFixed(2) + $t('亿') - } else if (value > 1000000) { - res = (value / 10000).toFixed(0) + $t('万') - } else if (value > 100000) { - res = (value / 10000).toFixed(2) + $t('万') - } else { - res = value.toFixed(0) - } - return res + } else if (datePickerValue?.length === 2) { + startMin = datePickerValue[0]!.startOf('day').unix() + currentMin = datePickerValue[1]!.endOf('day').unix() } - \ No newline at end of file + return { startTime: startMin / 1000, endTime: currentMin / 1000 } +} + +export function getTimeUnit(timeInterval: string): string { + let timeUnit = '' + // 相差秒数 + switch (timeInterval) { + case '1m': { + timeUnit = '每分钟' + break + } + case '5m': { + timeUnit = '每5分钟' + break + } + case '1h': { + timeUnit = '每小时' + break + } + case '1d': { + timeUnit = '每天' + break + } + case '1w': { + timeUnit = '每周' + break + } + } + return timeUnit +} + +// 当数据超过10万时,保留两个小数点,单位为万,如123212,显示12.32万; +export function changeNumberUnit(value?: number): { value: string; unit: string } { + if (value && value > 1000000000) { + return { value: (value && value / 100000000).toFixed(2), unit: '亿' } + } else if (value && value > 1000000) { + return { value: (value && value / 10000).toFixed(0), unit: '万' } + } else if (value && value > 10000) { + return { value: (value && value / 10000).toFixed(2), unit: '万' } + } + return { value: (value ?? '-') + '', unit: ' 次' } +} + +export function yUnitFormatter(value: number): string { + let res: string = '' + if (value > 100000000) { + res = (value / 100000000).toFixed(2) + $t('亿') + } else if (value > 1000000) { + res = (value / 10000).toFixed(0) + $t('万') + } else if (value > 100000) { + res = (value / 10000).toFixed(2) + $t('万') + } else { + res = value.toFixed(0) + } + return res +} diff --git a/frontend/packages/market/src/App.tsx b/frontend/packages/market/src/App.tsx index 7ea7776f..5a1f5eae 100644 --- a/frontend/packages/market/src/App.tsx +++ b/frontend/packages/market/src/App.tsx @@ -1,40 +1,39 @@ - import './App.css' -import { ConfigProvider } from 'antd'; -import RenderRoutes from '@market/components/aoplatform/RenderRoutes'; -import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx"; -import { StyleProvider } from '@ant-design/cssinjs'; -import zhCN from 'antd/locale/zh_CN'; -import useInitializeMonaco from "@common/hooks/useInitializeMonaco"; +import { ConfigProvider } from 'antd' +import RenderRoutes from '@market/components/aoplatform/RenderRoutes' +import { BreadcrumbProvider } from '@common/contexts/BreadcrumbContext.tsx' +import { StyleProvider } from '@ant-design/cssinjs' +import zhCN from 'antd/locale/zh_CN' +import useInitializeMonaco from '@common/hooks/useInitializeMonaco' const antdComponentThemeToken = { token: { // Seed Token,影响范围大 colorPrimary: '#3D46F2', - colorBorder:'#ededed', - colorText:'#333', + colorBorder: '#ededed', + colorText: '#333', borderRadius: 4, // 派生变量,影响范围小 colorBgContainer: '#fff', - colorPrimaryBg:'#EBEEF2', - colorTextQuaternary:'#BBB', - colorTextTertiary:'#999' + colorPrimaryBg: '#EBEEF2', + colorTextQuaternary: '#BBB', + colorTextTertiary: '#999' }, - components:{ + components: { // 派生变量,影响范围小 - Input:{ - activeShadow:'none' + Input: { + activeShadow: 'none' }, - Select:{ - activeShadow:'none' + Select: { + activeShadow: 'none' }, - Checkbox:{ - activeShadow:'none' + Checkbox: { + activeShadow: 'none' }, - Cascader:{ - activeShadow:'none', - optionSelectedBg:'#EBEEF2', - optionHoverBg:'#EBEEF2' + Cascader: { + activeShadow: 'none', + optionSelectedBg: '#EBEEF2', + optionHoverBg: '#EBEEF2' }, Layout: { bodyBg: '#fff', @@ -43,87 +42,85 @@ const antdComponentThemeToken = { headerHeight: 50, headerPadding: '10 20px', lightSiderBg: '#fff', - siderBg: '#fff', + siderBg: '#fff' }, - Breadcrumb:{ - itemColor:'#666', - linkColor:'#666', - lastItemColor:'#333', + Breadcrumb: { + itemColor: '#666', + linkColor: '#666', + lastItemColor: '#333' }, - Table:{ - headerBorderRadius:0, - headerSplitColor:'#ededed', - borderColor:'#ededed', - cellPaddingBlockMD:'10px', - cellPaddingInlineMD:'12px', - cellPaddingBlockSM:'8px', - cellPaddingInlineSM:'12px', - headerFilterHoverBg:'#EBEEF2', - headerSortActiveBg:'#F7F8FA', - headerSortHoverBg:'#F7F8FA', - fixedHeaderSortActiveBg:'#F7F8FA', - headerBg:'#F7F8FA', - rowHoverBg:'#EBEEF2' - + Table: { + headerBorderRadius: 0, + headerSplitColor: '#ededed', + borderColor: '#ededed', + cellPaddingBlockMD: '10px', + cellPaddingInlineMD: '12px', + cellPaddingBlockSM: '8px', + cellPaddingInlineSM: '12px', + headerFilterHoverBg: '#EBEEF2', + headerSortActiveBg: '#F7F8FA', + headerSortHoverBg: '#F7F8FA', + fixedHeaderSortActiveBg: '#F7F8FA', + headerBg: '#F7F8FA', + rowHoverBg: '#EBEEF2' }, - Segmented:{ - itemColor:'#333', - itemSelectedColor:'#333', - trackBg:'#f7f8fa', - trackPadding:0, - // itemHoverColor:'#EBEEF2', - itemActiveBg:'#EBEEF2', - itemHoverBg:'#EBEEF2', - itemSelectedBg:'#EBEEF2', + Segmented: { + itemColor: '#333', + itemSelectedColor: '#333', + trackBg: '#f7f8fa', + trackPadding: 0, + // itemHoverColor:'#EBEEF2', + itemActiveBg: '#EBEEF2', + itemHoverBg: '#EBEEF2', + itemSelectedBg: '#EBEEF2' }, - Tree:{ - // titleHeight:30, - // fontSize:12, - directoryNodeSelectedBg:'#EBEEF2', - directoryNodeSelectedColor:'#333', - nodeSelectedBg:'#EBEEF2', - nodeHoverBg:'#EBEEF2' - }, - Collapse:{ - headerBg:'#f7f8fa', - headerPadding:"12px", - contentPadding:"0 10px 12px 10px" - }, - Button:{ - // paddingInline:8, - dangerShadow:'none', - defaultShadow:'none', - primaryShadow:'none' - }, - Tabs:{ - cardBg:'#EBEEF2', - cardHeight:42, - horizontalItemGutter:8, - horizontalItemPaddingSM:'12px 8px 8px 8px', - horizontalItemPadding:'12px 8px 8px 8px', - }, - Menu:{ - // itemBg:'#F7F8FA', - // subMenuItemBg:'#F7F8FA', - // itemMarginBlock:0, - // activeBarBorderWidth:0, - // itemSelectedColor:'#333', - // itemSelectedBg:'#EBEEF2', - // itemHoverBg:'#EBEEF2' - }, - List:{ - itemPadding:'8px 0' - }, - Form:{ - itemMarginBottom:10, - - }, - Alert:{ - defaultPadding:'12px 16px' - }, - Tag:{ - defaultBg:"#f7f8fa" - }, + Tree: { + // titleHeight:30, + // fontSize:12, + directoryNodeSelectedBg: '#EBEEF2', + directoryNodeSelectedColor: '#333', + nodeSelectedBg: '#EBEEF2', + nodeHoverBg: '#EBEEF2' + }, + Collapse: { + headerBg: '#f7f8fa', + headerPadding: '12px', + contentPadding: '0 10px 12px 10px' + }, + Button: { + // paddingInline:8, + dangerShadow: 'none', + defaultShadow: 'none', + primaryShadow: 'none' + }, + Tabs: { + cardBg: '#EBEEF2', + cardHeight: 42, + horizontalItemGutter: 8, + horizontalItemPaddingSM: '12px 8px 8px 8px', + horizontalItemPadding: '12px 8px 8px 8px' + }, + Menu: { + // itemBg:'#F7F8FA', + // subMenuItemBg:'#F7F8FA', + // itemMarginBlock:0, + // activeBarBorderWidth:0, + // itemSelectedColor:'#333', + // itemSelectedBg:'#EBEEF2', + // itemHoverBg:'#EBEEF2' + }, + List: { + itemPadding: '8px 0' + }, + Form: { + itemMarginBottom: 10 + }, + Alert: { + defaultPadding: '12px 16px' + }, + Tag: { + defaultBg: '#f7f8fa' + } } } @@ -131,17 +128,14 @@ function App() { useInitializeMonaco() return ( - - - - - - - - ); + + + + + + + + ) } export default App diff --git a/frontend/packages/market/src/components/aoplatform/RenderRoutes.tsx b/frontend/packages/market/src/components/aoplatform/RenderRoutes.tsx index 79c3529c..001117b8 100644 --- a/frontend/packages/market/src/components/aoplatform/RenderRoutes.tsx +++ b/frontend/packages/market/src/components/aoplatform/RenderRoutes.tsx @@ -1,185 +1,206 @@ - -import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom'; -import Login from "@core/pages/Login.tsx" -import BasicLayout from '@common/components/aoplatform/BasicLayout'; -import {createElement, ReactElement,ReactNode,Suspense} from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom' +import Login from '@core/pages/Login.tsx' +import BasicLayout from '@common/components/aoplatform/BasicLayout' +import { createElement, ReactElement, ReactNode, Suspense } from 'react' import { v4 as uuidv4 } from 'uuid' -import {App, Skeleton} from "antd"; -import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx"; -import {FC,lazy} from 'react'; -import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx'; +import { App, Skeleton } from 'antd' +import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx' +import { FC, lazy } from 'react' +import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx' type RouteConfig = { - path:string - component?:ReactElement - children?:RouteConfig[] - key:string - provider?:FC<{ children: ReactNode; }> - lazy?:unknown + path: string + component?: ReactElement + children?: RouteConfig[] + key: string + provider?: FC<{ children: ReactNode }> + lazy?: unknown } -export type RouterParams = { - teamId:string - systemId:string - apiId:string - serviceId:string - clusterId:string; - memberGroupId:string - userGroupId:string - pluginName:string - moduleId:string - accessType:'project'|'team'|'system' - categoryId:string - tagId:string - dashboardType:string - dashboardDetailId:string - topologyId:string - appId:string +export type RouterParams = { + teamId: string + systemId: string + apiId: string + serviceId: string + clusterId: string + memberGroupId: string + userGroupId: string + pluginName: string + moduleId: string + accessType: 'project' | 'team' | 'system' + categoryId: string + tagId: string + dashboardType: string + dashboardDetailId: string + topologyId: string + appId: string } -const PUBLIC_ROUTES:RouteConfig[] = [ - { - path:'/', - component:, +const PUBLIC_ROUTES: RouteConfig[] = [ + { + path: '/', + component: , + key: uuidv4() + }, + { + path: '/login', + component: , + key: uuidv4() + }, + { + path: '/', + component: , + key: uuidv4(), + children: [ + { + path: 'serviceHub', + component: , key: uuidv4(), - - }, - { - path:'/login', - component:, - key: uuidv4() - }, - { - path:'/', - component:, - key: uuidv4(), - children:[ - { - path:'serviceHub', - component:, - key:uuidv4(), - children:[ - { - path:'list', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')), - }, - { - path:'detail/:serviceId', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')), - } - ] - }, - { - path:'consumer', - component:, - provider:TenantManagementProvider, - key:uuidv4(), - children:[ - { - path:':teamId/inside/:appId', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')), - children:[ - { - path:'service', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')), - }, - { - path:'authorization', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')), - }, - { - path:'setting', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')), - }, - ] - }, - { - path:'list', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')), - }, - { - path:'list/:teamId', - // component:, - key:uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')), - }, - ] - }, - { - path:'*', - component:, - key:uuidv4(), - } + children: [ + { + path: 'list', + // component:, + key: uuidv4(), + lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')) + }, + { + path: 'detail/:serviceId', + // component:, + key: uuidv4(), + lazy: lazy( + () => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx') + ) + } ] - }, + }, + { + path: 'consumer', + component: , + provider: TenantManagementProvider, + key: uuidv4(), + children: [ + { + path: ':teamId/inside/:appId', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx' + ) + ), + children: [ + { + path: 'service', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx' + ) + ) + }, + { + path: 'authorization', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx' + ) + ) + }, + { + path: 'setting', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx' + ) + ) + } + ] + }, + { + path: 'list', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx' + ) + ) + }, + { + path: 'list/:teamId', + // component:, + key: uuidv4(), + lazy: lazy( + () => + import( + /* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx' + ) + ) + } + ] + }, + { + path: '*', + component: , + key: uuidv4() + } + ] + } ] -const RenderRoutes = ()=> { - return ( - - - - {generateRoutes(PUBLIC_ROUTES)} - - - - ) +const RenderRoutes = () => { + return ( + + + {generateRoutes(PUBLIC_ROUTES)} + + + ) } const generateRoutes = (routerConfig: RouteConfig[]) => { - return routerConfig?.map((route: RouteConfig) => { - let routeElement; - if (route.lazy) { - const LazyComponent = route.lazy as React.ExoticComponent; + return routerConfig?.map((route: RouteConfig) => { + let routeElement + if (route.lazy) { + const LazyComponent = route.lazy as React.ExoticComponent - routeElement = ( -
}> - {route.provider ? ( - createElement(route.provider, {}, ) - ) : ( - - )} - - ); - } else { - routeElement = route.provider ? ( - createElement(route.provider, {}, route.component) - ) : ( - route.component - ); - } + routeElement = ( + + +
+ } + > + {route.provider ? createElement(route.provider, {}, ) : } + + ) + } else { + routeElement = route.provider ? createElement(route.provider, {}, route.component) : route.component + } - return ( - - {route.children && generateRoutes(route.children)} - - ); - } - ) + return ( + + {route.children && generateRoutes(route.children)} + + ) + }) } // 保护的路由组件 function ProtectedRoute() { - const {state} = useGlobalContext() - return state.isAuthenticated? : ; - } + const { state } = useGlobalContext() + return state.isAuthenticated ? : +} -export default RenderRoutes \ No newline at end of file +export default RenderRoutes diff --git a/frontend/packages/market/src/const/serviceHub/const.tsx b/frontend/packages/market/src/const/serviceHub/const.tsx index 52b08e47..57ec9892 100644 --- a/frontend/packages/market/src/const/serviceHub/const.tsx +++ b/frontend/packages/market/src/const/serviceHub/const.tsx @@ -1,57 +1,55 @@ +import { ServiceHubTableListItem } from './type' -import { ServiceHubTableListItem } from "./type"; - -import { PageProColumns } from "@common/components/aoplatform/PageList"; -import { frontendTimeSorter } from "@common/utils/dataTransfer"; +import { PageProColumns } from '@common/components/aoplatform/PageList' +import { frontendTimeSorter } from '@common/utils/dataTransfer' export const SERVICE_HUB_TABLE_COLUMNS: PageProColumns[] = [ - { - title:('消费者'), - dataIndex: 'name', - ellipsis:true, - width:160, - fixed:'left', - sorter: (a:ServiceHubTableListItem,b:ServiceHubTableListItem)=> { - return a.name.localeCompare(b.name) - }, - }, - { - title:('消费者 ID'), - dataIndex: 'id', - width: 140, - ellipsis:true - }, - { - title:('团队'), - dataIndex: ['team','name'], - ellipsis:true, - }, - { - title:('订阅服务数量'), - dataIndex: 'subscribeNum', - ellipsis:true - }, - { - title:('鉴权数量'), - dataIndex: 'authNum', - ellipsis:true - }, - { - title:('描述'), - dataIndex:'description', - ellipsis:true - }, - { - title:('创建时间'), - dataIndex: 'createTime', - width:182, - ellipsis:true, - sorter: (a,b)=>frontendTimeSorter(a,b,'createTime') - }, -]; - + { + title: '消费者', + dataIndex: 'name', + ellipsis: true, + width: 160, + fixed: 'left', + sorter: (a: ServiceHubTableListItem, b: ServiceHubTableListItem) => { + return a.name.localeCompare(b.name) + } + }, + { + title: '消费者 ID', + dataIndex: 'id', + width: 140, + ellipsis: true + }, + { + title: '团队', + dataIndex: ['team', 'name'], + ellipsis: true + }, + { + title: '订阅服务数量', + dataIndex: 'subscribeNum', + ellipsis: true + }, + { + title: '鉴权数量', + dataIndex: 'authNum', + ellipsis: true + }, + { + title: '描述', + dataIndex: 'description', + ellipsis: true + }, + { + title: '创建时间', + dataIndex: 'createTime', + width: 182, + ellipsis: true, + sorter: (a, b) => frontendTimeSorter(a, b, 'createTime') + } +] export const approvalTypeTranslate = { - 'auto':'无需审核', - 'manual':'需要审核' -} \ No newline at end of file + auto: '无需审核', + manual: '需要审核' +} diff --git a/frontend/packages/market/src/const/serviceHub/type.ts b/frontend/packages/market/src/const/serviceHub/type.ts index 34e8f422..e973d88d 100644 --- a/frontend/packages/market/src/const/serviceHub/type.ts +++ b/frontend/packages/market/src/const/serviceHub/type.ts @@ -1,104 +1,98 @@ - -import { DefaultOptionType } from "antd/es/select" -import { EntityItem } from "@common/const/type" -import { SubscribeEnum, SubscribeFromEnum } from "@core/const/system/const" -import WithPermission from "@common/components/aoplatform/WithPermission" +import { DefaultOptionType } from 'antd/es/select' +import { EntityItem } from '@common/const/type' +import { SubscribeEnum, SubscribeFromEnum } from '@core/const/system/const' +import WithPermission from '@common/components/aoplatform/WithPermission' export type ServiceBasicInfoType = { - app:EntityItem - team:EntityItem - master:EntityItem - apiNum:number - appNum:number - catalogue:EntityItem - tags:EntityItem[] - updateTime:string - version:string - logo?:string - invokeAddress:string - approvalType:'auto'|'manual' - serviceKind:'ai'|'rest' - sitePrefix?:string + app: EntityItem + team: EntityItem + master: EntityItem + apiNum: number + appNum: number + catalogue: EntityItem + tags: EntityItem[] + updateTime: string + version: string + logo?: string + invokeAddress: string + approvalType: 'auto' | 'manual' + serviceKind: 'ai' | 'rest' + sitePrefix?: string } export type ServiceDetailType = { - name:string - description:string - basic:ServiceBasicInfoType - apiDoc:string - applied:boolean + name: string + description: string + basic: ServiceBasicInfoType + apiDoc: string + applied: boolean } export type ServiceHubCategoryConfigFieldType = { - id?:string - name:string - parent?:string -}; + id?: string + name: string + parent?: string +} export type ServiceHubCategoryConfigProps = { - type:'addCate'|'addChildCate'|'renameCate' - entity?:{[k:string]:unknown} - WithPermission: typeof WithPermission + type: 'addCate' | 'addChildCate' | 'renameCate' + entity?: { [k: string]: unknown } + WithPermission: typeof WithPermission } export type ServiceHubCategoryConfigHandle = { - save:()=>Promise + save: () => Promise } - export type CategorizesType = { - id:string - name:string - children:CategorizesType[] + id: string + name: string + children: CategorizesType[] } - - export type ServiceHubTableListItem = { - id:string; - name: string; - tags?:EntityItem[]; - catalogue:EntityItem - apiNum:number - subscribeNum:number - description:string - logo:string -}; - + id: string + name: string + tags?: EntityItem[] + catalogue: EntityItem + apiNum: number + subscribeNum: number + description: string + logo: string +} export type ApplyServiceProps = { - entity:ServiceBasicInfoType & EntityItem - mySystemOptionList:DefaultOptionType[] - reApply?:boolean + entity: ServiceBasicInfoType & EntityItem + mySystemOptionList: DefaultOptionType[] + reApply?: boolean } export type ApplyServiceHandle = { - apply:()=>Promise + apply: () => Promise } - export type ServiceHubApplyModalFieldType = { - projects?:string; - reason?:string; -}; + projects?: string + reason?: string +} export type ServiceHubAppListItem = { - id:string, - name:string, - team:EntityItem, - subscribeNum:number, - subscribeVerifyNum:number, - description:string, - master:EntityItem, - createTime:string, + id: string + name: string + team: EntityItem + subscribeNum: number + subscribeVerifyNum: number + description: string + master: EntityItem + createTime: string } export type TenantManagementServiceListItem = { - id:string - service:EntityItem - applyStatus:typeof SubscribeEnum - app:EntityItem - team:EntityItem - from:SubscribeFromEnum - createTime:string -} \ No newline at end of file + id: string + service: EntityItem + applyStatus: typeof SubscribeEnum + app: EntityItem + team: EntityItem + from: SubscribeFromEnum + createTime: string +} diff --git a/frontend/packages/market/src/contexts/TenantManagementContext.tsx b/frontend/packages/market/src/contexts/TenantManagementContext.tsx index 56fb118e..5a41fadd 100644 --- a/frontend/packages/market/src/contexts/TenantManagementContext.tsx +++ b/frontend/packages/market/src/contexts/TenantManagementContext.tsx @@ -1,24 +1,22 @@ - -import { createContext, useContext, useState, ReactNode, FC } from 'react'; +import { createContext, useContext, useState, ReactNode, FC } from 'react' interface TenantManagementContextProps { - appName:string|undefined - setAppName:React.Dispatch>; + appName: string | undefined + setAppName: React.Dispatch> } -const TenantManagementContext = createContext(undefined); +const TenantManagementContext = createContext(undefined) export const useTenantManagementContext = () => { - const context = useContext(TenantManagementContext); - if (!context) { - throw new Error('useArray must be used within a ArrayProvider'); - } - return context; -}; + const context = useContext(TenantManagementContext) + if (!context) { + throw new Error('useArray must be used within a ArrayProvider') + } + return context +} export const TenantManagementProvider: FC<{ children: ReactNode }> = ({ children }) => { - const [appName, setAppName] = useState() + const [appName, setAppName] = useState() - - return {children}; -}; \ No newline at end of file + return {children} +} diff --git a/frontend/packages/market/src/main.tsx b/frontend/packages/market/src/main.tsx index 4b3f3e92..42ac0327 100644 --- a/frontend/packages/market/src/main.tsx +++ b/frontend/packages/market/src/main.tsx @@ -1,28 +1,27 @@ - -import {StrictMode} from 'react' +import { StrictMode } from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' -import {GlobalProvider} from "@common/contexts/GlobalStateContext.tsx"; +import { GlobalProvider } from '@common/contexts/GlobalStateContext.tsx' async function initializeApp() { try { // 初始化行为 // await fetchInitialConfig(); // 示例:获取初始配置 - + // 异步操作完成后,渲染React应用 ReactDOM.createRoot(document.getElementById('root')!).render( - , - ); + + ) } catch (error) { - console.error('Initialization failed:', error); + console.error('Initialization failed:', error) // 处理初始化失败的情况,比如渲染一个错误界面 } } // 执行初始化 -initializeApp(); \ No newline at end of file +initializeApp() diff --git a/frontend/packages/market/src/pages/serviceHub/ApiTestGroup.tsx b/frontend/packages/market/src/pages/serviceHub/ApiTestGroup.tsx index 7e8f673f..3749cc0c 100644 --- a/frontend/packages/market/src/pages/serviceHub/ApiTestGroup.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ApiTestGroup.tsx @@ -1,85 +1,93 @@ - -import {Empty, Input} from "antd"; -import {debounce} from "lodash-es"; -import {SearchOutlined} from "@ant-design/icons"; -import {useEffect, useMemo, useState} from "react"; -import {DataNode} from "antd/es/tree"; -import {ApiDetail} from "@common/const/api-detail"; -import ApiTest from "@common/components/postcat/ApiTest.tsx"; -import DirectoryTree from "antd/es/tree/DirectoryTree"; -import { $t } from "@common/locales"; +import { SearchOutlined } from '@ant-design/icons' +import ApiTest from '@common/components/postcat/ApiTest.tsx' +import { ApiDetail } from '@common/const/api-detail' +import { $t } from '@common/locales' +import { Empty, Input } from 'antd' +import { DataNode } from 'antd/es/tree' +import DirectoryTree from 'antd/es/tree/DirectoryTree' +import { debounce } from 'lodash-es' +import { useEffect, useMemo, useState } from 'react' type ApiTestGroupType = { - apiInfoList:ApiDetail[] - selectedApiId:string + apiInfoList: ApiDetail[] + selectedApiId: string } -export default function ApiTestGroup({apiInfoList,selectedApiId }:ApiTestGroupType){ - const [searchWord, setSearchWord] = useState('') - const [selectedApi,setSelectedApi] = useState([selectedApiId]) - const [selectedApiInfo, setSelectedApiInfo] = useState() - const onSearchWordChange = (e:unknown)=>{ - } +export default function ApiTestGroup({ apiInfoList, selectedApiId }: ApiTestGroupType) { + const [searchWord, setSearchWord] = useState('') + const [selectedApi, setSelectedApi] = useState([selectedApiId]) + const [selectedApiInfo, setSelectedApiInfo] = useState() + const onSearchWordChange = (e: unknown) => {} - useEffect(()=>{ - setSelectedApi([selectedApiId]) - },[selectedApiId] - ) - const treeData = useMemo(() => { - const loop = (data: ApiDetail[]): DataNode[] => - data?.map((item) => { - const strTitle = item.name as string; - const index = strTitle.indexOf(searchWord); - const beforeStr = strTitle.substring(0, index); - const afterStr = strTitle.slice(index + searchWord.length); - const title = - index > -1 ? ( - + useEffect(() => { + setSelectedApi([selectedApiId]) + }, [selectedApiId]) + const treeData = useMemo(() => { + const loop = (data: ApiDetail[]): DataNode[] => + data?.map((item) => { + const strTitle = item.name as string + const index = strTitle.indexOf(searchWord) + const beforeStr = strTitle.substring(0, index) + const afterStr = strTitle.slice(index + searchWord.length) + const title = + index > -1 ? ( + {beforeStr} - {searchWord} - {afterStr} + {searchWord} + {afterStr} - ) : ( - {strTitle} - ); + ) : ( + {strTitle} + ) - return { - title, - key: item.id, - }; - }); - return loop(apiInfoList); - }, [searchWord,apiInfoList]); + return { + title, + key: item.id + } + }) + return loop(apiInfoList) + }, [searchWord, apiInfoList]) - useEffect(()=>{ - apiInfoList && apiInfoList.length > 0 &&setSelectedApi([apiInfoList[0].id]) - },[apiInfoList]) + useEffect(() => { + apiInfoList && apiInfoList.length > 0 && setSelectedApi([apiInfoList[0].id]) + }, [apiInfoList]) - useEffect(() => { - setSelectedApiInfo(selectedApi? apiInfoList.filter(x=>x.id === selectedApi[0])?.[0] || undefined : undefined) - }, [selectedApi]); + useEffect(() => { + setSelectedApiInfo(selectedApi ? apiInfoList.filter((x) => x.id === selectedApi[0])?.[0] || undefined : undefined) + }, [selectedApi]) - return ( -
-
- debounce(onSearchWordChange, 100)(e)} - allowClear placeholder={$t("搜索分类或标签")} - prefix={ { - onSearchWordChange(e) - }}/>}/> - } - className="hidden-switcher" - blockNode={true} - treeData={treeData} - selectedKeys={selectedApi} - onSelect={(selectedKeys) => { - setSelectedApi([selectedKeys[0] as string]) - }} /> -
- {selectedApiInfo ? - : - - } -
- ) -} \ No newline at end of file + return ( +
+
+ debounce(onSearchWordChange, 100)(e)} + allowClear + placeholder={$t('搜索分类或标签')} + prefix={ + { + onSearchWordChange(e) + }} + /> + } + /> + } + className="hidden-switcher" + blockNode={true} + treeData={treeData} + selectedKeys={selectedApi} + onSelect={(selectedKeys) => { + setSelectedApi([selectedKeys[0] as string]) + }} + /> +
+ {selectedApiInfo ? ( + + ) : ( + + )} +
+ ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/ApplyServiceModal.tsx b/frontend/packages/market/src/pages/serviceHub/ApplyServiceModal.tsx index 5a355410..382392dc 100644 --- a/frontend/packages/market/src/pages/serviceHub/ApplyServiceModal.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ApplyServiceModal.tsx @@ -1,76 +1,88 @@ +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Col, Form, Input, Row, Select } from 'antd' +import { forwardRef, useEffect, useImperativeHandle } from 'react' +import { ApplyServiceHandle, ApplyServiceProps } from '../../const/serviceHub/type' -import { App, Form, Row, Col, Select, Input } from "antd"; -import { forwardRef, useEffect, useImperativeHandle, useMemo } from "react"; -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const"; -import { ApplyServiceHandle, ApplyServiceProps } from "../../const/serviceHub/type"; -import { useFetch } from "@common/hooks/http"; -import { $t } from "@common/locales"; +export const ApplyServiceModal = forwardRef((props, ref) => { + const { message } = App.useApp() + const { entity, mySystemOptionList, reApply } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() -export const ApplyServiceModal = forwardRef((props,ref)=>{ - const { message } = App.useApp() - const {entity,mySystemOptionList,reApply} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() + useEffect(() => { + form.setFieldsValue(reApply ? { applications: entity?.app.id } : {}) + }, []) - useEffect(() => { - form.setFieldsValue(reApply ? {applications:entity?.app.id}:{}) - }, []); - - const apply: ()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>('catalogue/service/subscribe',{method:'POST',eoParams:{team:entity?.team?.id}, eoBody:({...value,service:entity.id})}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) + const apply: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + fetchData>('catalogue/service/subscribe', { + method: 'POST', + eoParams: { team: entity?.team?.id }, + eoBody: { ...value, service: entity.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) }) - } + .catch((errorInfo) => reject(errorInfo)) + }) + } - useImperativeHandle(ref, ()=>({ - apply - }) - ) + useImperativeHandle(ref, () => ({ + apply + })) - return ( -
- -
{$t('服务名称')}: - {entity.name} - - - {$t('服务 ID')}: - {entity.id} - - - x.value !== entity.id)} + /> + + {entity.approvalType === 'manual' && ( + + + + )} + + + ) +}) diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx index 56a1d32c..3c6f8cdf 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx @@ -1,59 +1,37 @@ -import { useParams} from "react-router-dom"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import { Button, Drawer, Empty} from "antd"; -import { useEffect, useState} from "react"; -import ApiTestGroup from "./ApiTestGroup.tsx"; -import {ServiceDetailType } from "../../const/serviceHub/type.ts"; -import { $t } from "@common/locales/index.ts"; -import ApiDocument from "@common/components/aoplatform/ApiDocument.tsx"; +import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { Empty } from 'antd' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' +import { ServiceDetailType } from '../../const/serviceHub/type.ts' -const ServiceHubApiDocument = ({service}:{service:ServiceDetailType})=>{ - const {serviceId} = useParams(); - const [apiTestDrawOpen, setApiTestDrawOpen] = useState(false); - const [serviceName, setServiceName] = useState() - const [selectedTestApi,setSelectedTestApi] = useState() - const [apiDocument, setApiDocument] = useState() +const ServiceHubApiDocument = ({ service }: { service: ServiceDetailType }) => { + const { serviceId } = useParams() + const [, setServiceName] = useState() + const [apiDocument, setApiDocument] = useState() - useEffect(()=>{ - if(!service) return - setServiceName(service?.name) - setApiDocument(service?.apiDoc) - },[service]) + useEffect(() => { + if (!service) return + setServiceName(service?.name) + setApiDocument(service?.apiDoc) + }, [service]) - useEffect(() => { - if(!serviceId){ - console.warn('缺少serviceId') - return - } - }, [serviceId]); + useEffect(() => { + if (!serviceId) { + console.warn('缺少serviceId') + return + } + }, [serviceId]) - const onClose = () => { - setApiTestDrawOpen(false); - }; - - - return ( - <> -
-
- {apiDocument ? : -} -
-
- - {/* {$t('退出测试')} - } - closeIcon={false} - > - - */} - - ) + return ( + <> +
+
+ {apiDocument ? : } +
+
+ + ) } -export default ServiceHubApiDocument \ No newline at end of file +export default ServiceHubApiDocument diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx index aaffe600..5d5f8174 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx @@ -1,24 +1,23 @@ -import { Link, useNavigate, useParams } from "react-router-dom"; -import { App, Avatar, Button, Descriptions, Divider, Tabs } from "antd"; -import { useEffect, useRef, useState } from "react"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx"; -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx"; -import { useFetch } from "@common/hooks/http.ts"; -import { DefaultOptionType } from "antd/es/cascader"; -import { ApplyServiceHandle, ServiceBasicInfoType, ServiceDetailType } from "../../const/serviceHub/type.ts"; -import { EntityItem, RouterParams } from "@common/const/type.ts"; -import { ApplyServiceModal } from "./ApplyServiceModal.tsx"; -import ServiceHubApiDocument from "./ServiceHubApiDocument.tsx"; -import Integrate from "./integrate.tsx"; -import { ApiFilled, ArrowLeftOutlined, BgColorsOutlined } from "@ant-design/icons"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import DOMPurify from 'dompurify'; -import { $t } from "@common/locales/index.ts"; -import { approvalTypeTranslate } from "@market/const/serviceHub/const.tsx"; - +import { ApiFilled, ArrowLeftOutlined } from '@ant-design/icons' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { EntityItem, RouterParams } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { Icon } from '@iconify/react/dist/iconify.js' +import { approvalTypeTranslate } from '@market/const/serviceHub/const.tsx' +import { App, Avatar, Button, Descriptions, Divider, Tabs } from 'antd' +import { DefaultOptionType } from 'antd/es/cascader' +import DOMPurify from 'dompurify' +import { useEffect, useRef, useState } from 'react' +import { Link, useNavigate, useParams } from 'react-router-dom' +import { ApplyServiceHandle, ServiceBasicInfoType, ServiceDetailType } from '../../const/serviceHub/type.ts' +import { ApplyServiceModal } from './ApplyServiceModal.tsx' +import ServiceHubApiDocument from './ServiceHubApiDocument.tsx' +import Integrate from './integrate.tsx' const ServiceHubDetail = () => { - const { serviceId } = useParams(); + const { serviceId } = useParams() const { setBreadcrumb } = useBreadcrumb() const [serviceBasicInfo, setServiceBasicInfo] = useState() const [serviceName, setServiceName] = useState() @@ -28,23 +27,21 @@ const ServiceHubDetail = () => { const applyRef = useRef(null) const { modal, message } = App.useApp() const [mySystemOptionList, setMySystemOptionList] = useState() - // const [applied,setApplied] = useState(false) - // const [activeKey, setActiveKey] = useState([]) const [service, setService] = useState() - const navigate = useNavigate(); + const navigate = useNavigate() const modifyApiDoc = (apiDoc: string, apiPrefix: string) => { if (!apiDoc) return '' if (!apiPrefix) return apiDoc try { - const openApiSpec = JSON.parse(apiDoc); + const openApiSpec = JSON.parse(apiDoc) // 遍历并修改 paths,给每个路径添加前缀 - const modifiedPaths: Record = {}; + const modifiedPaths: Record = {} for (const [path, pathItem] of Object.entries(openApiSpec.paths)) { - modifiedPaths[apiPrefix + path] = pathItem; + modifiedPaths[apiPrefix + path] = pathItem } - openApiSpec.paths = modifiedPaths; - return JSON.stringify(openApiSpec); + openApiSpec.paths = modifiedPaths + return JSON.stringify(openApiSpec) } catch (err) { console.warn('拼接api前缀失败', err) } @@ -52,10 +49,26 @@ const ServiceHubDetail = () => { } const getServiceBasicInfo = () => { - fetchData>('catalogue/service', { method: 'GET', eoParams: { service: serviceId }, eoTransformKeys: ['app_num', 'api_num', 'update_time', 'api_doc', 'invoke_address', 'approval_type', 'service_kind','site_prefix'] }).then(response => { + fetchData>('catalogue/service', { + method: 'GET', + eoParams: { service: serviceId }, + eoTransformKeys: [ + 'app_num', + 'api_num', + 'update_time', + 'api_doc', + 'invoke_address', + 'approval_type', + 'service_kind', + 'site_prefix' + ] + }).then((response) => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { - setService({ ...data.service, apiDoc: modifyApiDoc(data.service.apiDoc, data.service.basic?.invokeAddress) }) + setService({ + ...data.service, + apiDoc: modifyApiDoc(data.service.apiDoc, data.service.basic?.invokeAddress) + }) setServiceBasicInfo(data.service.basic) setServiceName(data.service.name) setServiceDesc(data.service.description) @@ -72,41 +85,42 @@ const ServiceHubDetail = () => { return } serviceId && getServiceBasicInfo() - }, [serviceId]); + }, [serviceId]) useEffect(() => { getMySelectList() - setBreadcrumb( - [ - { title: {$t('服务市场')} }, - { title: $t('服务详情') } - ] - ) - - }, []); - + setBreadcrumb([{ title: {$t('服务市场')} }, { title: $t('服务详情') }]) + }, []) const getMySelectList = () => { setMySystemOptionList([]) - fetchData>('apps/can_subscribe', { method: 'GET' }).then(response => { + fetchData>('apps/can_subscribe', { method: 'GET' }).then((response) => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { - setMySystemOptionList(data.app?.map((x: EntityItem) => { - return { - label: x.name, value: x.id - } - })) + setMySystemOptionList( + data.app?.map((x: EntityItem) => { + return { + label: x.name, + value: x.id + } + }) + ) } else { message.error(msg || $t(RESPONSE_TIPS.error)) } }) } - const openModal = (type: 'apply') => { modal.confirm({ title: $t('申请服务'), - content: , + content: ( + + ), onOk: () => { return applyRef.current?.apply().then((res) => { // if(res === true) setApplied(true) @@ -124,75 +138,117 @@ const ServiceHubDetail = () => { { key: 'introduction', label: $t('介绍'), - children: <>
, - icon: , + children: ( + <> +
+ + ), + icon: }, { key: 'api-document', label: $t('API 文档'), - children:
, + children: ( +
+ +
+ ), icon: }, { key: 'api-integrate', label: $t('集成'), - children:
, - icon: , + children: ( +
+ +
+ ), + icon: } ] return ( -
+
-
- +
- +
{/* {service?.name?.substring(0,1)} */} - : undefined} - icon={serviceBasicInfo?.logo ? '' : }> + src={ + serviceBasicInfo?.logo ? ( + Logo + ) : undefined + } + icon={serviceBasicInfo?.logo ? '' : } + > + {' '} +
-

{serviceName} +

+ {serviceName}

{serviceDesc || '-'}

-

{$t('Base URL')}: {serviceBasicInfo?.invokeAddress || '-'}

+

+ + {$t('Base URL')}: {serviceBasicInfo?.invokeAddress || '-'} +

- +
- - +
- - {serviceBasicInfo?.appNum ?? '-'} - {serviceBasicInfo?.team?.name || '-'} - {serviceBasicInfo?.approvalType ? $t((approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-')) : '-'} - {serviceBasicInfo?.catalogue?.name || '-'} - {serviceBasicInfo?.tags?.map(x => x.name)?.join(',') || '-'} + + {serviceBasicInfo?.appNum ?? '-'} + {serviceBasicInfo?.team?.name || '-'} + + {serviceBasicInfo?.approvalType ? $t(approvalTypeTranslate[serviceBasicInfo?.approvalType] || '-') : '-'} + + {serviceBasicInfo?.catalogue?.name || '-'} + + {serviceBasicInfo?.tags?.map((x) => x.name)?.join(',') || '-'} + - - {serviceBasicInfo?.version || '-'} - {serviceBasicInfo?.updateTime || '-'} + + {serviceBasicInfo?.version || '-'} + + + {serviceBasicInfo?.updateTime || '-'} + +
) } -export default ServiceHubDetail \ No newline at end of file +export default ServiceHubDetail diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubGroup.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubGroup.tsx index 285ea49d..dded57cb 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubGroup.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubGroup.tsx @@ -1,119 +1,152 @@ -import {debounce} from "lodash-es"; -import {SearchOutlined} from "@ant-design/icons"; -import {App, Divider, Input, TreeDataNode} from "antd"; -import {useCallback, useEffect, useState} from "react"; -import Tree, {DataNode} from "antd/es/tree"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { CategorizesType } from "../../const/serviceHub/type.ts"; -import { filterServiceList, initialServiceHubListState, SERVICE_HUB_LIST_ACTIONS, ServiceHubListActionType } from "./ServiceHubList.tsx"; -import { EntityItem } from "@common/const/type.ts"; -import { $t } from "@common/locales/index.ts"; +import { debounce } from 'lodash-es' +import { SearchOutlined } from '@ant-design/icons' +import { App, Divider, Input, TreeDataNode } from 'antd' +import { useCallback, useEffect } from 'react' +import Tree, { DataNode } from 'antd/es/tree' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { CategorizesType } from '../../const/serviceHub/type.ts' +import { + filterServiceList, + initialServiceHubListState, + SERVICE_HUB_LIST_ACTIONS, + ServiceHubListActionType +} from './ServiceHubList.tsx' +import { EntityItem } from '@common/const/type.ts' +import { $t } from '@common/locales/index.ts' type ServiceHubGroup = { - children:JSX.Element - filterOption:typeof initialServiceHubListState - dispatch:React.Dispatch + children: JSX.Element + filterOption: typeof initialServiceHubListState + dispatch: React.Dispatch } -export const ServiceHubGroup = ({children,filterOption,dispatch}:ServiceHubGroup)=>{ - const {message} = App.useApp() - const {fetchData} = useFetch() - - useEffect(() => { - getTagAndServiceClassifyList() - }, []); +export const ServiceHubGroup = ({ children, filterOption, dispatch }: ServiceHubGroup) => { + const { message } = App.useApp() + const { fetchData } = useFetch() - const onSearchWordChange = (e:string)=>{ - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD,payload:e}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:true}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_SERVICES,payload: filterServiceList({...filterOption,keyword:e})}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:false}) - } + useEffect(() => { + getTagAndServiceClassifyList() + }, []) - const getTagAndServiceClassifyList = ()=>{ - fetchData>('catalogues',{method:'GET'}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - dispatch({type:SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES,payload:data.catalogues}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.GET_TAGS,payload:[...data.tags,{id:'empty',name:$t('无标签')}]}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE,payload:[...data.catalogues.map((x:CategorizesType)=>x.id)]}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG,payload:[...data.tags.map((x:EntityItem)=>x.id),'empty']}) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } + const onSearchWordChange = (e: string) => { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD, payload: e }) + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: true }) + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.SET_SERVICES, + payload: filterServiceList({ ...filterOption, keyword: e }) + }) + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: false }) + } + + const getTagAndServiceClassifyList = () => { + fetchData>('catalogues', { + method: 'GET' + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES, payload: data.catalogues }) + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.GET_TAGS, + payload: [...data.tags, { id: 'empty', name: $t('无标签') }] }) + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE, + payload: [...data.catalogues.map((x: CategorizesType) => x.id)] + }) + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG, + payload: [...data.tags.map((x: EntityItem) => x.id), 'empty'] + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const transferToTreeData = useCallback((data: CategorizesType[] | EntityItem[]): TreeDataNode[] => { + const loop = (data: CategorizesType[] | EntityItem[]): DataNode[] => + data?.map((item) => { + if ((item as CategorizesType).children) { + return { + title: item.name, + key: item.id, + children: loop((item as CategorizesType).children) + } + } + return { + title: item.name, + key: item.id + } + }) + return loop(data || []) + }, []) + + const onCheckHandler = + (type: 'SET_SELECTED_CATE' | 'SET_SELECTED_TAG' | 'SET_SELECTED_PARTITION') => (checkedKeys: string[]) => { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS[type], payload: checkedKeys }) + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: true }) + + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.SET_SERVICES, + payload: filterServiceList({ + ...filterOption, + [(type === 'SET_SELECTED_CATE' + ? 'selectedCate' + : type === 'SET_SELECTED_TAG' + ? 'selectedTag' + : 'selectedPartition') as keyof typeof filterOption]: checkedKeys + }) + }) + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: false }) } - - const transferToTreeData = useCallback((data:CategorizesType[] | EntityItem[] ):TreeDataNode[]=>{ - const loop = (data: CategorizesType[] | EntityItem[] ): DataNode[] => - data?.map((item) => { - if ((item as CategorizesType).children) { - return { - title:item.name, - key: item.id, children: loop((item as CategorizesType).children) - }; - } - return { - title:item.name, - key: item.id, - }; - }); - return loop(data || []) - },[]) - const onCheckHandler = (type: 'SET_SELECTED_CATE' | 'SET_SELECTED_TAG' | 'SET_SELECTED_PARTITION') => (checkedKeys:string[]) => { - dispatch({ type: SERVICE_HUB_LIST_ACTIONS[type], payload: checkedKeys }); - dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:true}) - - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_SERVICES,payload: filterServiceList({...filterOption,[(type === 'SET_SELECTED_CATE' ? 'selectedCate' : type === 'SET_SELECTED_TAG' ? 'selectedTag' : 'selectedPartition' ) as keyof typeof filterOption]: checkedKeys })}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:false}) - }; - - - return ( -
-
-
- debounce(onSearchWordChange, 500)(e.target.value)} - allowClear placeholder={$t("搜索服务")} - prefix={}/> -
-
-

{$t('分类')}

- x.children && x.children.length > 0).length > 0 ? '' : 'no-first-switch-tree'}`} - checkable - blockNode={true} - checkedKeys={filterOption.selectedCate} - onCheck={onCheckHandler('SET_SELECTED_CATE')} - treeData={transferToTreeData(filterOption.categoriesList)} - showIcon={false} - selectable={false} - /> -
- -
-

{$t('标签')}

- -
-
+ return ( +
+
+
+ debounce(onSearchWordChange, 500)(e.target.value)} + allowClear + placeholder={$t('搜索服务')} + prefix={} + /> +
+
+

{$t('分类')}

+ x.children && x.children.length > 0).length > 0 ? '' : 'no-first-switch-tree'}`} + checkable + blockNode={true} + checkedKeys={filterOption.selectedCate} + onCheck={onCheckHandler('SET_SELECTED_CATE')} + treeData={transferToTreeData(filterOption.categoriesList)} + showIcon={false} + selectable={false} + />
+ +
+

{$t('标签')}

+ +
+
-
- {children} -
-
); +
+
{children}
+
+ ) } export default ServiceHubGroup diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubList.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubList.tsx index fbd98998..a415fd33 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubList.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubList.tsx @@ -1,196 +1,251 @@ -import {ActionType } from "@ant-design/pro-components"; -import {FC, forwardRef, useEffect, useReducer, useRef} from "react"; -import { useNavigate, useParams} from "react-router-dom"; -import {App,Card, Avatar, Tag, Empty, Spin, Tooltip} from "antd"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import { CategorizesType, ServiceHubTableListItem } from "../../const/serviceHub/type.ts"; -import { VirtuosoGrid } from 'react-virtuoso'; -import { ApiOutlined,LoadingOutlined } from "@ant-design/icons"; -import ServiceHubGroup from "./ServiceHubGroup.tsx"; -import { EntityItem } from "@common/const/type.ts"; -import { $t } from "@common/locales/index.ts"; +import { ApiOutlined, LoadingOutlined } from '@ant-design/icons' +import { ActionType } from '@ant-design/pro-components' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { EntityItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { App, Avatar, Card, Empty, Spin, Tag, Tooltip } from 'antd' +import { FC, forwardRef, useEffect, useReducer, useRef } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { VirtuosoGrid } from 'react-virtuoso' +import { CategorizesType, ServiceHubTableListItem } from '../../const/serviceHub/type.ts' +import ServiceHubGroup from './ServiceHubGroup.tsx' export enum SERVICE_HUB_LIST_ACTIONS { - GET_CATEGORIES = 'GET_CATEGORIES', - GET_TAGS ='GET_TAGS', - GET_SERVICES = 'GET_SERVICES', - SET_SERVICES='SET_SERVICES', - SET_SELECTED_CATE = 'SET_SELECTED_CATE', - SET_SELECTED_TAG = 'SET_SELECTED_TAG', - SET_KEYWORD = 'SET_KEYWORD', - LIST_LOADING = 'LIST_LOADING' - } - -export type ServiceHubListActionType = -| { type: SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES, payload: CategorizesType[] } -| { type: SERVICE_HUB_LIST_ACTIONS.GET_TAGS, payload: EntityItem[] } -| { type: SERVICE_HUB_LIST_ACTIONS.GET_SERVICES, payload: ServiceHubTableListItem[] } -| { type: SERVICE_HUB_LIST_ACTIONS.SET_SERVICES, payload: ServiceHubTableListItem[] } -| { type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE, payload: string[] } -| { type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG, payload: string[] } -| { type: SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD, payload: string } -| { type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: boolean } - -export const initialServiceHubListState = { - categoriesList: [] as CategorizesType[], - tagsList: [] as EntityItem[], - servicesList: [] as ServiceHubTableListItem[], - showServicesList: [] as ServiceHubTableListItem[], - selectedCate: [] as string[], - selectedTag: [] as string[], - keyword: '', - getCateAndTagData:false, - listLoading:false, - }; - - function reducer(state: typeof initialServiceHubListState, action: ServiceHubListActionType) { - switch (action.type) { - case SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES: - return { ...state, categoriesList: action.payload , getCateAndTagData:true}; - case SERVICE_HUB_LIST_ACTIONS.GET_TAGS: - return { ...state, tagsList: action.payload , getCateAndTagData:true}; - case SERVICE_HUB_LIST_ACTIONS.GET_SERVICES: - return { ...state, servicesList: action.payload }; - case SERVICE_HUB_LIST_ACTIONS.SET_SERVICES: - return { ...state, showServicesList: action.payload }; - case SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE: - return { ...state, selectedCate: action.payload }; - case SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG: - return { ...state, selectedTag: action.payload }; - case SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD: - return { ...state, keyword: action.payload }; - case SERVICE_HUB_LIST_ACTIONS.LIST_LOADING: - return { ...state, listLoading: action.payload }; - default: - return state; - } - } - - export const filterServiceList = (dataSet: typeof initialServiceHubListState)=>{ - if(!dataSet.getCateAndTagData ){ - return dataSet.servicesList - }else{ - return dataSet.servicesList.filter((x)=>{ - if(!dataSet.selectedCate || dataSet.selectedCate.length === 0 || dataSet.selectedCate.indexOf(x.catalogue.id) === -1) return false - if(!dataSet.selectedTag || dataSet.selectedTag.length === 0) return false - if((!x.tags || !x.tags.length )&& dataSet.selectedTag.indexOf('empty') === -1) return false - if(x.tags && x.tags.length && !x.tags.some(tag => dataSet.selectedTag.includes(tag.id))) return false; - if( dataSet.keyword && !x.name.toLocaleLowerCase().includes(dataSet.keyword.toLocaleLowerCase())) return false - return true - }) - } + GET_CATEGORIES = 'GET_CATEGORIES', + GET_TAGS = 'GET_TAGS', + GET_SERVICES = 'GET_SERVICES', + SET_SERVICES = 'SET_SERVICES', + SET_SELECTED_CATE = 'SET_SELECTED_CATE', + SET_SELECTED_TAG = 'SET_SELECTED_TAG', + SET_KEYWORD = 'SET_KEYWORD', + LIST_LOADING = 'LIST_LOADING' } -const ServiceHubList:FC = ()=>{ - const { setBreadcrumb} = useBreadcrumb() - const { message } = App.useApp() - const {fetchData} = useFetch() - const { categoryId, tagId} = useParams() - const pageListRef = useRef(null); - // const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl'); - const navigate = useNavigate() - const [filterOption, dispatch] = useReducer(reducer, initialServiceHubListState) +export type ServiceHubListActionType = + | { type: SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES; payload: CategorizesType[] } + | { type: SERVICE_HUB_LIST_ACTIONS.GET_TAGS; payload: EntityItem[] } + | { type: SERVICE_HUB_LIST_ACTIONS.GET_SERVICES; payload: ServiceHubTableListItem[] } + | { type: SERVICE_HUB_LIST_ACTIONS.SET_SERVICES; payload: ServiceHubTableListItem[] } + | { type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE; payload: string[] } + | { type: SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG; payload: string[] } + | { type: SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD; payload: string } + | { type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING; payload: boolean } - const getServiceList = ()=>{ - dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:true}) - fetchData>('catalogue/services',{method:'GET',eoTransformKeys:['api_num','subscriber_num']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - dispatch({type:SERVICE_HUB_LIST_ACTIONS.GET_SERVICES,payload:data.services}) - dispatch({type:SERVICE_HUB_LIST_ACTIONS.SET_SERVICES,payload: filterServiceList({...filterOption, servicesList:data.services})}) - - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>{ dispatch({type:SERVICE_HUB_LIST_ACTIONS.LIST_LOADING,payload:false})}) - } +export const initialServiceHubListState = { + categoriesList: [] as CategorizesType[], + tagsList: [] as EntityItem[], + servicesList: [] as ServiceHubTableListItem[], + showServicesList: [] as ServiceHubTableListItem[], + selectedCate: [] as string[], + selectedTag: [] as string[], + keyword: '', + getCateAndTagData: false, + listLoading: false +} - const showDocumentDetail = (entity:ServiceHubTableListItem)=>{ - navigate(`../detail/${entity.id}`) - } +function reducer(state: typeof initialServiceHubListState, action: ServiceHubListActionType) { + switch (action.type) { + case SERVICE_HUB_LIST_ACTIONS.GET_CATEGORIES: + return { ...state, categoriesList: action.payload, getCateAndTagData: true } + case SERVICE_HUB_LIST_ACTIONS.GET_TAGS: + return { ...state, tagsList: action.payload, getCateAndTagData: true } + case SERVICE_HUB_LIST_ACTIONS.GET_SERVICES: + return { ...state, servicesList: action.payload } + case SERVICE_HUB_LIST_ACTIONS.SET_SERVICES: + return { ...state, showServicesList: action.payload } + case SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_CATE: + return { ...state, selectedCate: action.payload } + case SERVICE_HUB_LIST_ACTIONS.SET_SELECTED_TAG: + return { ...state, selectedTag: action.payload } + case SERVICE_HUB_LIST_ACTIONS.SET_KEYWORD: + return { ...state, keyword: action.payload } + case SERVICE_HUB_LIST_ACTIONS.LIST_LOADING: + return { ...state, listLoading: action.payload } + default: + return state + } +} - useEffect(() => { - pageListRef.current?.reload() - }, [categoryId,tagId]); - useEffect(() => { - setBreadcrumb( - [ - {title:$t('服务市场')} - ] - ) - getServiceList() - }, []); +export const filterServiceList = (dataSet: typeof initialServiceHubListState) => { + if (!dataSet.getCateAndTagData) { + return dataSet.servicesList + } else { + return dataSet.servicesList.filter((x) => { + if ( + !dataSet.selectedCate || + dataSet.selectedCate.length === 0 || + dataSet.selectedCate.indexOf(x.catalogue.id) === -1 + ) + return false + if (!dataSet.selectedTag || dataSet.selectedTag.length === 0) return false + if ((!x.tags || !x.tags.length) && dataSet.selectedTag.indexOf('empty') === -1) return false + if (x.tags && x.tags.length && !x.tags.some((tag) => dataSet.selectedTag.includes(tag.id))) return false + if (dataSet.keyword && !x.name.toLocaleLowerCase().includes(dataSet.keyword.toLocaleLowerCase())) return false + return true + }) + } +} - return ( - -
- } spinning={filterOption.listLoading}> - {filterOption.showServicesList && filterOption.showServicesList.length > 0 ? { - const item = filterOption.showServicesList[index]; - return ( -
- showDocumentDetail(item)}> - {item.description || $t('暂无服务描述')} - -
- ); +const ServiceHubList: FC = () => { + const { setBreadcrumb } = useBreadcrumb() + const { message } = App.useApp() + const { fetchData } = useFetch() + const { categoryId, tagId } = useParams() + const pageListRef = useRef(null) + // const callbackUrl = new URLSearchParams(window.location.search).get('callbackUrl'); + const navigate = useNavigate() + const [filterOption, dispatch] = useReducer(reducer, initialServiceHubListState) + + const getServiceList = () => { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: true }) + fetchData>('catalogue/services', { + method: 'GET', + eoTransformKeys: ['api_num', 'subscriber_num'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.GET_SERVICES, payload: data.services }) + dispatch({ + type: SERVICE_HUB_LIST_ACTIONS.SET_SERVICES, + payload: filterServiceList({ ...filterOption, servicesList: data.services }) + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .finally(() => { + dispatch({ type: SERVICE_HUB_LIST_ACTIONS.LIST_LOADING, payload: false }) + }) + } + + const showDocumentDetail = (entity: ServiceHubTableListItem) => { + navigate(`../detail/${entity.id}`) + } + + useEffect(() => { + pageListRef.current?.reload() + }, [categoryId, tagId]) + useEffect(() => { + setBreadcrumb([{ title: $t('服务市场') }]) + getServiceList() + }, []) + + return ( + +
+ } + spinning={filterOption.listLoading} + > + {filterOption.showServicesList && filterOption.showServicesList.length > 0 ? ( + { + const item = filterOption.showServicesList[index] + return ( +
+ showDocumentDetail(item)} + > + + {item.description || $t('暂无服务描述')} + + +
+ ) + }} + components={{ + List: forwardRef(({ style, children, ...props }, ref) => ( +
( -
- {children} -
- )), - Item: ({ children, ...props }) => ( - <> - {children} - ) - }} - />:} - -
- - ) - + > + {children} +
+ )), + Item: ({ children, ...props }) => <>{children} + }} + /> + ) : ( + + )} +
+
+
+ ) } export default ServiceHubList -const CardTitle = (service:ServiceHubTableListItem)=>{ - return( -
- : undefined}> {service.logo ? '' : service.name.substring(0,1)} -
-

{service.name}

-
- {service.catalogue?.name || '-'} - - - {service.apiNum ?? '-'} - - - {service.subscriberNum ?? '-'} - -
-
+const CardTitle = (service: ServiceHubTableListItem) => { + return ( +
+ + ) : undefined + } + > + {' '} + {service.logo ? '' : service.name.substring(0, 1)} + +
+

+ {service.name} +

+
+ + {service.catalogue?.name || '-'} + + + + + + {service.apiNum ?? '-'} + + + + + + + + {service.subscriberNum ?? '-'} + +
- ) -} \ No newline at end of file +
+
+ ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/integrate.tsx b/frontend/packages/market/src/pages/serviceHub/integrate.tsx index 2eb31929..823f3be8 100644 --- a/frontend/packages/market/src/pages/serviceHub/integrate.tsx +++ b/frontend/packages/market/src/pages/serviceHub/integrate.tsx @@ -1,31 +1,31 @@ -import { ServiceDetailType } from "@market/const/serviceHub/type" -import { Input, Button, Space, message } from 'antd' -import { $t } from "@common/locales" import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' -import { useParams } from "react-router-dom" -import { useState, useEffect } from "react" -import { RouterParams } from "@common/const/type" -import useCopyToClipboard from "@common/hooks/copy.ts"; -import { useFetch } from "@common/hooks/http" +import { RouterParams } from '@common/const/type' +import useCopyToClipboard from '@common/hooks/copy.ts' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { ServiceDetailType } from '@market/const/serviceHub/type' +import { Button, Input, Space, message } from 'antd' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router-dom' const Integrate = ({ service }: { service: ServiceDetailType }) => { - const stepClass = "leading-[20px] truncate font-bold items-center gap-[4px] mt-[15px]"; - const [url, setUrl] = useState(''); - const { serviceId} = useParams() - const {fetchData} = useFetch() - const { copyToClipboard } = useCopyToClipboard(); - - useEffect(()=>{ - setUrl(`${service?.basic?.sitePrefix || window.location?.origin}/api/v1/service/swagger/${serviceId}` ) - },[service]) + const stepClass = 'leading-[20px] truncate font-bold items-center gap-[4px] mt-[15px]' + const [url, setUrl] = useState('') + const { serviceId } = useParams() + const { fetchData } = useFetch() + const { copyToClipboard } = useCopyToClipboard() + + useEffect(() => { + setUrl(`${service?.basic?.sitePrefix || window.location?.origin}/api/v1/service/swagger/${serviceId}`) + }, [service]) /** * Agent 平台地址 */ - const agentAddress = '/cluster'; + const agentAddress = '/cluster' /** * 消费者地址 */ - const consumerAddress = '/consumer/list'; + const consumerAddress = '/consumer/list' /** * 复制代码 */ @@ -36,9 +36,12 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => { * 下载文件 */ const onDownload = () => { - fetchData>(`export/openapi/${serviceId}`, { method: 'GET' ,headers:{ - 'Content-Type': 'application/octet-stream' - }}).then(response => { + fetchData>(`export/openapi/${serviceId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/octet-stream' + } + }).then((response) => { const { code, msg } = response if (code === STATUS_CODE.SUCCESS) { message.success(msg || $t(RESPONSE_TIPS.success)) @@ -49,24 +52,43 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => { } return (
-
{$t('支持把当前服务对接主流的 AI Agent平台,实现在 Agent 平台上快速、安全和合规地使用企业开放的 API 能力。')}
-
{$t('可按以下步骤进行对接:')}
+
+ {$t('支持把当前服务对接主流的 AI Agent平台,实现在 Agent 平台上快速、安全和合规地使用企业开放的 API 能力。')} +
+
{$t('可按以下步骤进行对接:')}

{$t('步骤一:Agent 平台上创建自定义插件')}

-
{$t('不同 Agent 平台的操作细节可查看')} {$t('《 Agent 对接手册》')}
+
+ {$t('不同 Agent 平台的操作细节可查看')}{' '} + + {$t('《 Agent 对接手册》')} + + 。 +

{$t('步骤二:导入 API 文档数据')}

-
{$t('可通过以下 URL 或 下载 Json 文件,导入 API 文档数据到 Agent 平台中。')}
+
{$t('可通过以下 URL 或 下载 Json 文件,导入 API 文档数据到 Agent 平台中。')}
- + OR - +

{$t('步骤三:配置 API 密钥')}

-
{$t('在')} {$t('消费者')} {$t('菜单中,选择已通过本 API 服务申请的消费者,')}
-
{$t('把 "访问权限" 菜单下的密钥填入到 Agent 平台对应的插件密钥配置中。')}
+
+ {$t('在')} + + {' '} + {$t('消费者')}{' '} + + {$t('菜单中,选择已通过本 API 服务申请的消费者,')} +
+
{$t('把 "访问权限" 菜单下的密钥填入到 Agent 平台对应的插件密钥配置中。')}
- ); + ) } -export default Integrate; \ No newline at end of file +export default Integrate diff --git a/frontend/packages/market/src/pages/serviceHub/management/ApprovalModalContent.tsx b/frontend/packages/market/src/pages/serviceHub/management/ApprovalModalContent.tsx index 3bfbea77..78be69e9 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ApprovalModalContent.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ApprovalModalContent.tsx @@ -1,91 +1,99 @@ - -import { App, Form, Row, Col, Input } from "antd" -import { forwardRef, useImperativeHandle, useEffect } from "react" -import WithPermission from "@common/components/aoplatform/WithPermission" -import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const" -import { useFetch } from "@common/hooks/http" -import { SYSTEM_SUBSCRIBE_APPROVAL_DETAIL_LIST } from "@core/const/system/const" -import { SubSubscribeApprovalModalHandle, SubSubscribeApprovalModalProps } from "@core/const/system/type" -import { $t } from "@common/locales" +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { SYSTEM_SUBSCRIBE_APPROVAL_DETAIL_LIST } from '@core/const/system/const' +import { SubSubscribeApprovalModalHandle, SubSubscribeApprovalModalProps } from '@core/const/system/type' +import { App, Col, Form, Input, Row } from 'antd' +import { forwardRef, useEffect, useImperativeHandle } from 'react' type FieldType = { - reason: string - opinion?:string + reason: string + opinion?: string } -export const ApprovalModalContent = forwardRef((props, ref) => { +export const ApprovalModalContent = forwardRef( + (props, ref) => { const { message } = App.useApp() - const {data, type, serviceId, teamId} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() + const { data, type, serviceId, teamId } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() - const reApply:()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - if(type === 'view'){ - resolve(true) - return - } - form.validateFields().then((value)=>{ - fetchData>('catalogue/service/subscribe',{method: 'POST',eoParams:{team:teamId}, eoBody:({service:data!.service.id, applications:[serviceId], reason:value.reason})}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) + const reApply: () => Promise = () => { + return new Promise((resolve, reject) => { + if (type === 'view') { + resolve(true) + return + } + form + .validateFields() + .then((value) => { + fetchData>('catalogue/service/subscribe', { + method: 'POST', + eoParams: { team: teamId }, + eoBody: { service: data!.service.id, applications: [serviceId], reason: value.reason } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) } - useImperativeHandle(ref, ()=>({ - reApply - }) - ) - - useEffect(()=>{ - form.setFieldsValue({...data}) - },[]) + useImperativeHandle(ref, () => ({ + reApply + })) + useEffect(() => { + form.setFieldsValue({ ...data }) + }, []) return ( -
+
-
+ {SYSTEM_SUBSCRIBE_APPROVAL_DETAIL_LIST?.map((x) => { + return ( + +
+ {$t(x.title)}: + + {x.nested ? data?.[x.key]?.[x.nested] : (data as { [k: string]: unknown })?.[x.key] || '-'} + + ) + })} + + label={$t('申请原因')} name="reason"> + - - {SYSTEM_SUBSCRIBE_APPROVAL_DETAIL_LIST?.map((x)=>{ - return ( - - {$t(x.title)}: - {x.nested ? data?.[x.key]?.[x.nested] : ( (data as {[k:string]:unknown})?.[x.key] || '-')} - ) - })} - - - label={$t("申请原因")} - name="reason" - > - - - - label={$t("审核意见")} - name="opinion" - > - - - - - + placeholder={$t(PLACEHOLDER.input)} + /> + + label={$t('审核意见')} name="opinion"> + + + + + ) -}) \ No newline at end of file + } +) diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementAppSetting.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementAppSetting.tsx index 657db628..ab42e770 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementAppSetting.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementAppSetting.tsx @@ -1,17 +1,19 @@ -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { useParams } from "react-router-dom"; -import ManagementConfig from "./ManagementConfig"; -import { $t } from "@common/locales"; +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { useParams } from 'react-router-dom' +import ManagementConfig from './ManagementConfig' +import { $t } from '@common/locales' -export default function ManagementAppSetting(){ - const {teamId,appId} = useParams() - - return ( -
-
{$t('消费者管理')}
-
- -
-
- ) -} \ No newline at end of file +export default function ManagementAppSetting() { + const { teamId, appId } = useParams() + + return ( +
+
+ {$t('消费者管理')} +
+
+ +
+
+ ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityConfig.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityConfig.tsx index 48aadf61..140650e7 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityConfig.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityConfig.tsx @@ -1,259 +1,263 @@ -import {forwardRef, useEffect, useImperativeHandle, useState} from "react"; -import {App, Checkbox, DatePicker, Form, Input, Select,Switch} from "antd"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import { v4 as uuidv4} from 'uuid'; -import { ALGORITHM_ITEM } from "@core/const/system/const.tsx"; -import { EditAuthFieldType } from "@core/const/system/type"; -import { RangePickerProps } from "antd/es/date-picker"; -import dayjs from 'dayjs'; -import { $t } from "@common/locales"; +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales' +import { ALGORITHM_ITEM } from '@core/const/system/const.tsx' +import { EditAuthFieldType } from '@core/const/system/type' +import { App, Checkbox, DatePicker, Form, Input, Select, Switch } from 'antd' +import { RangePickerProps } from 'antd/es/date-picker' +import dayjs from 'dayjs' +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { v4 as uuidv4 } from 'uuid' export type ManagementAuthorityConfigProps = { - type:'add'|'edit' - data?:EditAuthFieldType - appId:string - teamId:string + type: 'add' | 'edit' + data?: EditAuthFieldType + appId: string + teamId: string } export type ManagementAuthorityConfigHandle = { - save:()=>Promise + save: () => Promise } -export const ManagementAuthorityConfig = forwardRef((props,ref)=>{ +export const ManagementAuthorityConfig = forwardRef( + (props, ref) => { const { message } = App.useApp() - const {type, data,appId, teamId} = props - const [form] = Form.useForm(); - const [driver, setDriver]=useState('basic') + const { type, data, appId, teamId } = props + const [form] = Form.useForm() + const [driver, setDriver] = useState('basic') const [algorithm, setAlgorithm] = useState('HS256') - const [, forceUpdate] = useState(null); - const {fetchData} = useFetch() + const [, forceUpdate] = useState(null) + const { fetchData } = useFetch() - const save :()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>('app/authorization',{method:type === 'add'? 'POST' : 'PUT',eoBody:({...value,expireTime:value.expireTime ? value.expireTime.unix() : 0}), eoParams:type === 'add' ? {app:appId,team:teamId}:{authorization:data!.id,app:appId,team:teamId},eoTransformKeys:['hideCredential','expireTime','tokenName','userName']}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - })} + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + fetchData>('app/authorization', { + method: type === 'add' ? 'POST' : 'PUT', + eoBody: { ...value, expireTime: value.expireTime ? value.expireTime.unix() : 0 }, + eoParams: + type === 'add' ? { app: appId, team: teamId } : { authorization: data!.id, app: appId, team: teamId }, + eoTransformKeys: ['hideCredential', 'expireTime', 'tokenName', 'userName'] + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } - useImperativeHandle(ref, ()=>({ - save - }) - ) + useImperativeHandle(ref, () => ({ + save + })) const prefixSelector = ( - - - - ); + + + + ) - const onAlgorithmChange = (algorithm:string)=>{ - setAlgorithm(algorithm) + const onAlgorithmChange = (algorithm: string) => { + setAlgorithm(algorithm) } - const onDriverChange = (driver:string)=>{ - setDriver(driver) - if(driver === 'jwt' && !form.getFieldValue(['config','algorithm'])){ - form.setFieldValue(['config','algorithm'],'HS256') - forceUpdate({}) - } + const onDriverChange = (driver: string) => { + setDriver(driver) + if (driver === 'jwt' && !form.getFieldValue(['config', 'algorithm'])) { + form.setFieldValue(['config', 'algorithm'], 'HS256') + forceUpdate({}) + } } - - - const disabledDate: RangePickerProps['disabledDate'] = (current) => { - // Can not select days before today and today - return current && current.isBefore(dayjs().endOf('day'), 'day'); - }; - + const disabledDate: RangePickerProps['disabledDate'] = (current) => { + // Can not select days before today and today + return current && current.isBefore(dayjs().endOf('day'), 'day') + } useEffect(() => { - if(type === 'edit' && data){ - form.setFieldsValue({...data,expireTime:data.expireTime === 0 ? '' : dayjs(data.expireTime * 1000)}) - forceUpdate({}) - }else{ - form.setFieldsValue({driver, position:'Header',tokenName:'Authorization'}) - form.setFieldValue(['config','userName'],uuidv4()) - form.setFieldValue(['config','password'],uuidv4()) - form.setFieldValue(['config','apikey'],uuidv4()) - forceUpdate({}) - } - }, []); + if (type === 'edit' && data) { + form.setFieldsValue({ + ...data, + expireTime: data.expireTime === 0 ? '' : dayjs(data.expireTime * 1000) + }) + forceUpdate({}) + } else { + form.setFieldsValue({ driver, position: 'Header', tokenName: 'Authorization' }) + form.setFieldValue(['config', 'userName'], uuidv4()) + form.setFieldValue(['config', 'password'], uuidv4()) + form.setFieldValue(['config', 'apikey'], uuidv4()) + forceUpdate({}) + } + }, []) return ( - // -
- - label={$t("名称")} - name="name" - rules={[{required: true,whitespace:true }]} - > - - + // + + label={$t('名称')} name="name" rules={[{ required: true, whitespace: true }]}> + + - - label={$t("鉴权类型")} - name="driver" - rules={[{required: true}]} - > - onDriverChange(e)} + placeholder={$t(PLACEHOLDER.input)} + /> + - - - + + + - {(()=>{ - switch(form.getFieldValue('driver')){ - case 'basic': - return <> - - label={$t("用户名")} - name={['config','userName']} - rules={[{required: true,whitespace:true }]} - > - - + {(() => { + switch (form.getFieldValue('driver')) { + case 'basic': + return ( + <> + + label={$t('用户名')} + name={['config', 'userName']} + rules={[{ required: true, whitespace: true }]} + > + + - - label={$t("密码")} - name={['config','password']} - rules={[{required: true,whitespace:true }]} - > - - - - case 'jwt': - return <> - - label={$t("Iss")} - name={['config','iss']} - rules={[{required: true}]} - > - - + + label={$t('密码')} + name={['config', 'password']} + rules={[{ required: true, whitespace: true }]} + > + + + + ) + case 'jwt': + return ( + <> + label={$t('Iss')} name={['config', 'iss']} rules={[{ required: true }]}> + + - - label={$t("签名算法")} - name={['config','algorithm']} - rules={[{required: true}]} - > - { + onAlgorithmChange(value) + }} + /> + - - label={ algorithm.includes('HS') ? $t('Secret'):$t('RSA 公钥')} - name={algorithm.includes('HS') ? ['config','secret']:['config','publicKey']} - rules={[{required: true}]} - > - - + + label={algorithm.includes('HS') ? $t('Secret') : $t('RSA 公钥')} + name={algorithm.includes('HS') ? ['config', 'secret'] : ['config', 'publicKey']} + rules={[{ required: true }]} + > + + - - label={$t("用户名")} - name={['config','user']} - > - - + label={$t('用户名')} name={['config', 'user']}> + + - - label={$t("用户名 JsonPath")} - name={['config','userPath']} - > - - + label={$t('用户名 JsonPath')} name={['config', 'userPath']}> + + - - label={$t("校验字段")} - name={['config','claimsToVerify']} - > - + - {algorithm.includes('HS') && - label={$t("是否 Base64 加密")} - name={['config', 'signatureIsBase64']} - > - - } - - case 'aksk': - return <> - - label={$t("AK")} - name={['config','ak']} - rules={[{required: true}]} - > - - + {algorithm.includes('HS') && ( + label={$t('是否 Base64 加密')} name={['config', 'signatureIsBase64']}> + + + )} + + ) + case 'aksk': + return ( + <> + label={$t('AK')} name={['config', 'ak']} rules={[{ required: true }]}> + + - - label={$t("SK")} - name={['config','sk']} - rules={[{required: true}]} - > - - - - case 'apikey': - return <> - - label={$t("Apikey")} - name={['config','apikey']} - rules={[{required: true,whitespace:true }]} - > - - - - } + label={$t('SK')} name={['config', 'sk']} rules={[{ required: true }]}> + + + + ) + case 'apikey': + return ( + <> + + label={$t('Apikey')} + name={['config', 'apikey']} + rules={[{ required: true, whitespace: true }]} + > + + + + ) + } + })()} - })()} + label={$t('过期时间')} name="expireTime"> + + - - label={$t("过期时间")} - name="expireTime" - > - - - - - label={$t("隐藏鉴权信息")} - name="hideCredential" valuePropName="checked" - > - - - - // - ); -}) \ No newline at end of file + label={$t('隐藏鉴权信息')} name="hideCredential" valuePropName="checked"> + + + + //
+ ) + } +) diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityView.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityView.tsx index 07eb57a1..59bbe3db 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityView.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementAuthorityView.tsx @@ -1,27 +1,31 @@ -import { $t } from "@common/locales"; -import {Col, Row} from "antd"; -import {useEffect, useState} from "react"; +import { $t } from '@common/locales' +import { Col, Row } from 'antd' +import { useEffect, useState } from 'react' export type ManagementAuthorityViewProps = { - entity:Array<{key:string, value:string}> + entity: Array<{ key: string; value: string }> } -export const ManagementAuthorityView = ({entity}:ManagementAuthorityViewProps)=>{ - const [detail,setDetail] = useState>(entity) +export const ManagementAuthorityView = ({ entity }: ManagementAuthorityViewProps) => { + const [detail, setDetail] = useState>(entity) - useEffect(() => { - setDetail(entity) - }, [entity]); + useEffect(() => { + setDetail(entity) + }, [entity]) - return ( -
{ - detail?.length > 0 && detail.map((k,i)=>( - -
{$t(k.key)}: - { ['永久','否','是'].indexOf(k.value)!== -1 ? $t(k.value) : (k.value || '-')} - - )) - } - - ) -} \ No newline at end of file + return ( +
+ {detail?.length > 0 && + detail.map((k, i) => ( + +
+ {$t(k.key)}: + + + {['永久', '否', '是'].indexOf(k.value) !== -1 ? $t(k.value) : k.value || '-'} + + + ))} + + ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementConfig.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementConfig.tsx index c0f7f6da..b34f1072 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementConfig.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementConfig.tsx @@ -1,228 +1,263 @@ - -import {App, Button, Form, Input, Row} from "antd"; -import {forwardRef, useEffect, useImperativeHandle, useState} from "react"; -import {v4 as uuidv4} from 'uuid' -import WithPermission from "@common/components/aoplatform/WithPermission"; -import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const"; -import { useFetch } from "@common/hooks/http"; -import { useNavigate } from "react-router-dom"; -import { useTenantManagementContext } from "@market/contexts/TenantManagementContext"; -import { $t } from "@common/locales"; -import Select, { DefaultOptionType } from "antd/es/select"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { SimpleTeamItem, MemberItem } from "@common/const/type"; +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { MemberItem, SimpleTeamItem } from '@common/const/type' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { useTenantManagementContext } from '@market/contexts/TenantManagementContext' +import { App, Button, Form, Input, Row } from 'antd' +import Select, { DefaultOptionType } from 'antd/es/select' +import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { v4 as uuidv4 } from 'uuid' export type ManagementConfigFieldType = { - name:string - description:string - id?:string - team?:string - asApp?:boolean -}; + name: string + description: string + id?: string + team?: string + asApp?: boolean +} type ManagementConfigProps = { - type:'add'|'edit' - teamId:string - appId?:string - dataShowType?:'block'|'list' + type: 'add' | 'edit' + teamId: string + appId?: string + dataShowType?: 'block' | 'list' } export type ManagementConfigHandle = { - save:()=>Promise + save: () => Promise } - -const ManagementConfig = forwardRef((props, ref) => { - const { message,modal } = App.useApp() - const {type,teamId,appId,dataShowType} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const [delBtnLoading, setDelBtnLoading] = useState(false) - const {setAppName} = type === 'edit' ? useTenantManagementContext():{setAppName:()=>{}} - const navigate = type === 'edit' ? useNavigate() : ()=>{} - const [teamOptionList, setTeamOptionList] = useState() - const {checkPermission,accessInit, getGlobalAccessData} = useGlobalContext() - const save:()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>(type === 'add'? 'team/app' : 'app/info',{method:type === 'add'? 'POST' : 'PUT',eoBody:(value), eoParams:type === 'add' ? {team:dataShowType === 'list' ? value.team : teamId}:{app:appId,team:teamId}}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - form.setFieldsValue(data.apps) - type === 'edit' && setAppName(data.apps.name) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - // 获取表单默认值 - const getApplicationInfo = () => { - fetchData>('app/info',{method:'GET',eoParams:{app:appId,team:teamId},eoTransformKeys:['as_app']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAppName(data.app.name) - setTimeout(()=>{form.setFieldsValue({...data.app})},0) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - }; - - const getTeamOptionList = ()=>{ - setTeamOptionList([]) - - fetchData>(!checkPermission('system.workspace.team.view_all') ?'simple/teams/mine' :'simple/teams',{method:'GET',eoTransformKeys:[]}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTeamOptionList(data.teams?.map((x:MemberItem)=>{return {...x, - label:x.name, value:x.id - }})) - if(form.getFieldValue('team') === undefined&&data.teams?.length){ - form.setFieldValue('team',data.teams[0].id); - } - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - const deleteApplicationModal = async ()=>{ - setDelBtnLoading(true) - modal.confirm({ - title:$t('删除'), - content:$t(DELETE_TIPS.default), - onOk:()=> { - return deleteApplication() - }, - width:600, - okText:$t('确认'), - okButtonProps:{ - danger:true - }, - onCancel:()=>{ - setDelBtnLoading(false) - }, - cancelText:$t('取消'), - closable:true, - icon:<> - }) - } - - - const deleteApplication = ()=>{ - fetchData>('app',{method:'DELETE',eoParams:{app:appId,team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ +const ManagementConfig = forwardRef((props, ref) => { + const { message, modal } = App.useApp() + const { type, teamId, appId, dataShowType } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() + const [delBtnLoading, setDelBtnLoading] = useState(false) + const { setAppName } = type === 'edit' ? useTenantManagementContext() : { setAppName: () => {} } + const navigate = type === 'edit' ? useNavigate() : () => {} + const [teamOptionList, setTeamOptionList] = useState() + const { checkPermission, accessInit, getGlobalAccessData } = useGlobalContext() + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + fetchData>(type === 'add' ? 'team/app' : 'app/info', { + method: type === 'add' ? 'POST' : 'PUT', + eoBody: value, + eoParams: + type === 'add' ? { team: dataShowType === 'list' ? value.team : teamId } : { app: appId, team: teamId } + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { message.success(msg || $t(RESPONSE_TIPS.success)) - navigate(`/consumer/list`) - }else{ + form.setFieldsValue(data.apps) + type === 'edit' && setAppName(data.apps.name) + resolve(true) + } else { message.error(msg || $t(RESPONSE_TIPS.error)) - } + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) }) - } + .catch((errorInfo) => reject(errorInfo)) + }) + } - useImperativeHandle(ref, ()=>({ - save - }) - ) + // 获取表单默认值 + const getApplicationInfo = () => { + fetchData>('app/info', { + method: 'GET', + eoParams: { app: appId, team: teamId }, + eoTransformKeys: ['as_app'] + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setAppName(data.app.name) + setTimeout(() => { + form.setFieldsValue({ ...data.app }) + }, 0) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - useEffect(() => { - if(type === 'edit'){ - appId && getApplicationInfo() - }else{ - form.setFieldsValue({ - 'id':uuidv4()}) - } - if(type !== 'edit' && dataShowType === 'list'){ - if(accessInit){ - getTeamOptionList() - }else{ - getGlobalAccessData()?.then?.(()=>{ - getTeamOptionList() - }) - } - } - }, [appId]); + const getTeamOptionList = () => { + setTeamOptionList([]) - return (<> -
-
- - label={$t("消费者名称")} - name="name" - rules={[{ required: true,whitespace:true }]} - > - - - - - label={$t("消费者 ID")} - name="id" - rules={[{ required: true ,whitespace:true }]} - > - - - - - {dataShowType === 'list' && - label={$t("所属团队")} - name="team" - rules={[{ required: true }]} - > - - } - - - - - {type === 'edit' && <> - - - - - - }
- - { type === 'edit' && <> - - -
-

{$t('删除消费者')}:{$t('删除操作不可恢复,请谨慎操作!')}

-
- - - -
-
-
- } - -
+ fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { method: 'GET', eoTransformKeys: [] } + ).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTeamOptionList( + data.teams?.map((x: MemberItem) => { + return { ...x, label: x.name, value: x.id } + }) ) + if (form.getFieldValue('team') === undefined && data.teams?.length) { + form.setFieldValue('team', data.teams[0].id) + } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const deleteApplicationModal = async () => { + setDelBtnLoading(true) + modal.confirm({ + title: $t('删除'), + content: $t(DELETE_TIPS.default), + onOk: () => { + return deleteApplication() + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + danger: true + }, + onCancel: () => { + setDelBtnLoading(false) + }, + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } + + const deleteApplication = () => { + fetchData>('app', { + method: 'DELETE', + eoParams: { app: appId, team: teamId } + }).then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + navigate(`/consumer/list`) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + useImperativeHandle(ref, () => ({ + save + })) + + useEffect(() => { + if (type === 'edit') { + appId && getApplicationInfo() + } else { + form.setFieldsValue({ + id: uuidv4() + }) + } + if (type !== 'edit' && dataShowType === 'list') { + if (accessInit) { + getTeamOptionList() + } else { + getGlobalAccessData()?.then?.(() => { + getTeamOptionList() + }) + } + } + }, [appId]) + + return ( + <> + +
+
+ + label={$t('消费者名称')} + name="name" + rules={[{ required: true, whitespace: true }]} + > + + + + label={$t('消费者 ID')} + name="id" + rules={[{ required: true, whitespace: true }]} + > + + + {dataShowType === 'list' && ( + label={$t('所属团队')} name="team" rules={[{ required: true }]}> + + + )} + + + + {type === 'edit' && ( + <> + + + + + {' '} + + )}{' '} +
+ + {type === 'edit' && ( + <> + +
+

+ {$t('删除消费者')}: + {$t('删除操作不可恢复,请谨慎操作!')} +

+
+ + + +
+
+
+ + )} + +
+ + ) }) -export default ManagementConfig \ No newline at end of file +export default ManagementConfig diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideAuth.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideAuth.tsx index 017ad08e..c1dafff3 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideAuth.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideAuth.tsx @@ -1,193 +1,294 @@ -import { MoreOutlined } from "@ant-design/icons" -import { message, Card, Button, Tag, Dropdown, App, Empty } from "antd" -import { useState, useEffect, forwardRef, useRef } from "react" -import { VirtuosoGrid } from "react-virtuoso" -import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const" -import { useFetch } from "@common/hooks/http" -import { EditAuthFieldType, SystemAuthorityTableListItem } from "@core/const/system/type" -import { useParams } from "react-router-dom" -import { RouterParams } from "@core/components/aoplatform/RenderRoutes" -import { useTenantManagementContext } from "../../../contexts/TenantManagementContext" -import { ManagementAuthorityConfig, ManagementAuthorityConfigHandle } from "./ManagementAuthorityConfig" -import { ManagementAuthorityView } from "./ManagementAuthorityView" -import { checkAccess } from "@common/utils/permission" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import dayjs from 'dayjs'; -import { $t } from "@common/locales" +import { MoreOutlined } from '@ant-design/icons' +import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { checkAccess } from '@common/utils/permission' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { EditAuthFieldType, SystemAuthorityTableListItem } from '@core/const/system/type' +import { App, Button, Card, Dropdown, Empty, message, Tag } from 'antd' +import dayjs from 'dayjs' +import { forwardRef, useEffect, useRef, useState } from 'react' +import { useParams } from 'react-router-dom' +import { VirtuosoGrid } from 'react-virtuoso' +import { ManagementAuthorityConfig, ManagementAuthorityConfigHandle } from './ManagementAuthorityConfig' +import { ManagementAuthorityView } from './ManagementAuthorityView' -export default function ManagementInsideAuth(){ - const {modal} = App.useApp() - const {fetchData} = useFetch() - const [authList, setAuthList] = useState([]) - const {appId,teamId} = useParams() - const addRef = useRef(null) - const editRef = useRef(null) - const {accessData} = useGlobalContext() +export default function ManagementInsideAuth() { + const { modal } = App.useApp() + const { fetchData } = useFetch() + const [authList, setAuthList] = useState([]) + const { appId, teamId } = useParams() + const addRef = useRef(null) + const editRef = useRef(null) + const { accessData } = useGlobalContext() - - const getSystemAuthority = ()=>{ - return fetchData>('app/authorizations',{method:'GET',eoParams:{app:appId, team:teamId},eoTransformKeys:['hide_credential','create_time','update_time','expire_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setAuthList(data.authorizations) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).catch(() => { - return {data:[], success:false} - }) - } - - useEffect(() => { - getSystemAuthority() - }, []); - - - const deleteAuthority = (entity:SystemAuthorityTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('app/authorization',{method:'DELETE',eoParams:{authorization:entity!.id,app:appId, team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const openModal =async (type:'view'|'delete'|'add'|'edit',entity?:SystemAuthorityTableListItem)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'view':{ - title=$t('鉴权详情') - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>('app/authorization/details',{method:'GET',eoParams:{authorization:entity!.id,app:appId, team:teamId}}) - message.destroy() - if(code === STATUS_CODE.SUCCESS){ - content= - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return - }} - break; - case 'add': - title=$t('添加鉴权') - content= - break; - case 'edit':{ - title=$t('编辑鉴权') - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>('app/authorization',{method:'GET',eoParams:{authorization:entity!.id,app:appId, team:teamId},eoTransformKeys:['hide_credential','token_name','expire_time','user_name','public_key','user_path','claims_to_verify','signature_is_base64']}) - message.destroy() - if(code === STATUS_CODE.SUCCESS){ - content= - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return - }} - break; - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; + const getSystemAuthority = () => { + return fetchData>('app/authorizations', { + method: 'GET', + eoParams: { app: appId, team: teamId }, + eoTransformKeys: ['hide_credential', 'create_time', 'update_time', 'expire_time'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setAuthList(data.authorizations) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) } + }) + .catch(() => { + return { data: [], success: false } + }) + } - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ - case 'add': - return addRef.current?.save().then((res)=>{if(res === true) getSystemAuthority()}) - case 'edit': - return editRef.current?.save().then((res)=>{if(res === true) getSystemAuthority()}) - case 'delete': - return deleteAuthority(entity!).then((res)=>{if(res === true) getSystemAuthority()}) - case 'view': - return true - } - }, - width:600, - okText: $t('确认'), - okButtonProps:{ - disabled : !checkAccess( `team.application.authorization.${type}`, accessData) - }, - cancelText:type === 'view'? $t('关闭'):$t('取消'), - closable:true, - icon:<>, - footer:(_, { OkBtn, CancelBtn }) =>{ - return(<> - - {type !== 'view' && } - ) - } + useEffect(() => { + getSystemAuthority() + }, []) + const deleteAuthority = (entity: SystemAuthorityTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>('app/authorization', { + method: 'DELETE', + eoParams: { authorization: entity!.id, app: appId, team: teamId } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const openModal = async (type: 'view' | 'delete' | 'add' | 'edit', entity?: SystemAuthorityTableListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'view': + { + title = $t('鉴权详情') + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>( + 'app/authorization/details', + { + method: 'GET', + eoParams: { authorization: entity!.id, app: appId, team: teamId } + } + ) + message.destroy() + if (code === STATUS_CODE.SUCCESS) { + content = + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return + } + } + break + case 'add': + title = $t('添加鉴权') + content = + break + case 'edit': + { + title = $t('编辑鉴权') + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>( + 'app/authorization', + { + method: 'GET', + eoParams: { authorization: entity!.id, app: appId, team: teamId }, + eoTransformKeys: [ + 'hide_credential', + 'token_name', + 'expire_time', + 'user_name', + 'public_key', + 'user_path', + 'claims_to_verify', + 'signature_is_base64' + ] + } + ) + message.destroy() + if (code === STATUS_CODE.SUCCESS) { + content = ( + + ) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return + } + } + break + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break } + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'add': + return addRef.current?.save().then((res) => { + if (res === true) getSystemAuthority() + }) + case 'edit': + return editRef.current?.save().then((res) => { + if (res === true) getSystemAuthority() + }) + case 'delete': + return deleteAuthority(entity!).then((res) => { + if (res === true) getSystemAuthority() + }) + case 'view': + return true + } + }, + width: 600, + okText: $t('确认'), + okButtonProps: { + disabled: !checkAccess(`team.application.authorization.${type}`, accessData) + }, + cancelText: type === 'view' ? $t('关闭') : $t('取消'), + closable: true, + icon: <>, + footer: (_, { OkBtn, CancelBtn }) => { + return ( + <> + + {type !== 'view' && } + + ) + } + }) + } - const dropdownMenu = (entity:SystemAuthorityTableListItem) => [ - { - key: 'edit', - label: ( - - ), - }, - { - key: 'delete', - label: ( - - ), - }, - ] + const dropdownMenu = (entity: SystemAuthorityTableListItem) => [ + { + key: 'edit', + label: ( + + ) + }, + { + key: 'delete', + label: ( + + ) + } + ] - return (
-
{$t('访问授权')}
-
- {authList && authList.length > 0 ? { - const item = authList[index]; - return ( -
-
{item.name}
- -
-
- {`${item.driver.substring(0,1).toLocaleUpperCase()}${item.driver.substring(1)}`} - {item.expireTime === 0 ?$t('永不过期') : `${$t('到期时间')}:${dayjs(item.expireTime * 1000).format('YYYY-MM-DD hh:mm:ss')}`}
+ return ( +
+
+ {$t('访问授权')} +
+
+ +
+ {authList && authList.length > 0 ? ( + { + const item = authList[index] + return ( + +
+
+ {item.name} +
+ + + +
- - ); +
+
+ {`${item.driver.substring(0, 1).toLocaleUpperCase()}${item.driver.substring(1)}`} + + {item.expireTime === 0 + ? $t('永不过期') + : `${$t('到期时间')}:${dayjs(item.expireTime * 1000).format('YYYY-MM-DD hh:mm:ss')}`} + +
+
+
+ ) + }} + components={{ + List: forwardRef(({ style, children, ...props }, ref) => ( +
( -
- {children} -
- )), - Item: ({ children, ...props }) => ( - <> - {children} - ) - }} - /> :
} -
) -} \ No newline at end of file + > + {children} +
+ )), + Item: ({ children, ...props }) => <>{children} + }} + /> + ) : ( +
+ +
+ )} +
+ ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx index 79a46416..96d16e22 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx @@ -1,119 +1,133 @@ +import { ArrowLeftOutlined, LoadingOutlined } from '@ant-design/icons' +import { App, Button, Menu, MenuProps, Spin } from 'antd' +import { useState, useEffect, useMemo } from 'react' +import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom' +import { BasicResponse, STATUS_CODE } from '@common/const/const' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' +import { useFetch } from '@common/hooks/http' +import { ItemType } from 'antd/es/breadcrumb/Breadcrumb' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { useTenantManagementContext } from '@market/contexts/TenantManagementContext' +import { ManagementConfigFieldType } from './ManagementConfig' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { $t } from '@common/locales' +import { getItem } from '@common/utils/navigation' +import { MenuItemType } from 'antd/es/menu/interface' +import { PERMISSION_DEFINITION } from '@common/const/permissions' +import { cloneDeep } from 'lodash-es' -import { ArrowLeftOutlined, LoadingOutlined } from "@ant-design/icons"; -import { App, Button, Menu, MenuProps, Spin } from "antd"; -import { useState, useEffect, useMemo } from "react"; -import { Link, Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; -import { BasicResponse, STATUS_CODE } from "@common/const/const"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import { useFetch } from "@common/hooks/http"; -import { ItemType } from "antd/es/breadcrumb/Breadcrumb"; -import { TENANT_MANAGEMENT_APP_MENU } from "../../../const/serviceHub/const"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { useTenantManagementContext } from "@market/contexts/TenantManagementContext"; -import { ManagementConfigFieldType } from "./ManagementConfig"; -import { useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { $t } from "@common/locales"; -import { getItem } from "@common/utils/navigation"; -import { MenuItemGroupType, MenuItemType } from "antd/es/menu/interface"; -import { PERMISSION_DEFINITION } from "@common/const/permissions"; -import { cloneDeep } from "lodash-es"; +export default function ManagementInsidePage() { + const { message } = App.useApp() + const { fetchData } = useFetch() + const { setBreadcrumb } = useBreadcrumb() + const [activeMenu, setActiveMenu] = useState('service') + const { appId, teamId } = useParams() + const navigateTo = useNavigate() + const currentUrl = useLocation().pathname + const [openKeys, setOpenKeys] = useState([]) + const [loading, setLoading] = useState(false) + const { appName, setAppName } = useTenantManagementContext() + const { getTeamAccessData, cleanTeamAccessData, state, accessData, checkPermission, accessInit } = useGlobalContext() -export default function ManagementInsidePage(){ - const { message } = App.useApp() - const {fetchData} = useFetch() - const { setBreadcrumb} = useBreadcrumb() - const [activeMenu, setActiveMenu] = useState('service') - const {appId,teamId} = useParams() - const navigateTo = useNavigate() - const currentUrl = useLocation().pathname - const [openKeys, setOpenKeys] = useState([]) - const [loading, setLoading] = useState(false) - const {appName,setAppName} = useTenantManagementContext() - const {getTeamAccessData,cleanTeamAccessData,state,accessData,checkPermission,accessInit} = useGlobalContext() - - const TENANT_MANAGEMENT_APP_MENU: MenuProps['items'] = useMemo(()=>[ - getItem($t('订阅的服务'), 'service',undefined, undefined, undefined, 'team.application.subscription.view'), - getItem($t('访问授权'), 'authorization',undefined, undefined, undefined, 'team.consumer.authorization.view'), - getItem($t('消费者管理'), 'setting',undefined, undefined, undefined, 'team.application.application.view'), - ],[state.language]) + const TENANT_MANAGEMENT_APP_MENU: MenuProps['items'] = useMemo( + () => [ + getItem($t('订阅的服务'), 'service', undefined, undefined, undefined, 'team.application.subscription.view'), + getItem($t('访问授权'), 'authorization', undefined, undefined, undefined, 'team.consumer.authorization.view'), + getItem($t('消费者管理'), 'setting', undefined, undefined, undefined, 'team.application.application.view') + ], + [state.language] + ) - - const menuData = useMemo(()=>{ - const filterMenu = (menu:(MenuItemType&{access:string})[])=>{ - const newMenu = cloneDeep(menu) - return newMenu!.filter((c:MenuItemType&{access:string} )=>{ - if(!c) return false - return (((c as MenuItemType&{access:string} ).access ? - checkPermission((c as MenuItemType&{access:string} ).access as keyof typeof PERMISSION_DEFINITION[0]): - true)) - })} - const filteredMenu = filterMenu(TENANT_MANAGEMENT_APP_MENU as (MenuItemType&{access:string})[]) - const menu = activeMenu ?? filteredMenu[0]?.children ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key - if(menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/consumer/${teamId}/inside/${appId}/${menu}`) - return filteredMenu || [] - },[accessData,accessInit, TENANT_MANAGEMENT_APP_MENU]) - - useEffect(()=>{ - setActiveMenu(currentUrl.split('/').pop() || 'service') - },[currentUrl]) - - const onMenuClick: MenuProps['onClick'] = (node) => { - setActiveMenu(node.key) - navigateTo(`/consumer/${teamId}/inside/${appId}/${node.key}`) - }; - - useEffect(()=>{ - const fetchDataAsync = async () => { - let _appName = appName - if(appId && !appName && !currentUrl.includes('setting')){ - const {code,data} = await fetchData>('app/info',{method:'GET',eoParams:{app:appId,team:teamId},eoTransformKeys:['as_app']}) - if(code === STATUS_CODE.SUCCESS){ - _appName = data.app.name - setAppName(_appName) - } - } - setBreadcrumb( - [ - {title:{$t('消费者')}}, - ...(_appName ? [{title:_appName}] : []) - ] - ) - }; - fetchDataAsync(); - }, - [appId,appName]) - - -useEffect(()=>{ - if(teamId ){ - getTeamAccessData(teamId) + const menuData = useMemo(() => { + const filterMenu = (menu: (MenuItemType & { access: string })[]) => { + const newMenu = cloneDeep(menu) + return newMenu!.filter((c: MenuItemType & { access: string }) => { + if (!c) return false + return (c as MenuItemType & { access: string }).access + ? checkPermission((c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0]) + : true + }) } - return ()=>{ - cleanTeamAccessData() - } -},[teamId]) + const filteredMenu = filterMenu(TENANT_MANAGEMENT_APP_MENU as (MenuItemType & { access: string })[]) + const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key + if (menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/consumer/${teamId}/inside/${appId}/${menu}`) + return filteredMenu || [] + }, [accessData, accessInit, TENANT_MANAGEMENT_APP_MENU]) - return (<> - } spinning={loading}> + useEffect(() => { + setActiveMenu(currentUrl.split('/').pop() || 'service') + }, [currentUrl]) + + const onMenuClick: MenuProps['onClick'] = (node) => { + setActiveMenu(node.key) + navigateTo(`/consumer/${teamId}/inside/${appId}/${node.key}`) + } + + useEffect(() => { + const fetchDataAsync = async () => { + let _appName = appName + if (appId && !appName && !currentUrl.includes('setting')) { + const { code, data } = await fetchData>('app/info', { + method: 'GET', + eoParams: { app: appId, team: teamId }, + eoTransformKeys: ['as_app'] + }) + if (code === STATUS_CODE.SUCCESS) { + _appName = data.app.name + setAppName(_appName) + } + } + setBreadcrumb([ + { title: {$t('消费者')} }, + ...(_appName ? [{ title: _appName }] : []) + ]) + } + fetchDataAsync() + }, [appId, appName]) + + useEffect(() => { + if (teamId) { + getTeamAccessData(teamId) + } + return () => { + cleanTeamAccessData() + } + }, [teamId]) + + return ( + <> + } + spinning={loading} + >
-
+
- +
{setOpenKeys(e)}} - className="h-[calc(100%-59px)] overflow-auto" - style={{ width: 220}} - selectedKeys={[activeMenu!]} - mode="inline" - items={menuData as unknown as ItemType[] } - /> + onClick={onMenuClick} + openKeys={openKeys} + onOpenChange={(e) => { + setOpenKeys(e) + }} + className="h-[calc(100%-59px)] overflow-auto" + style={{ width: 220 }} + selectedKeys={[activeMenu!]} + mode="inline" + items={menuData as unknown as ItemType[]} + /> +
+
+ {} }}> +
-
- {}}}> -
-
-
) -} \ No newline at end of file +
+ + ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideService.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideService.tsx index 56a272f4..f287d204 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideService.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsideService.tsx @@ -1,214 +1,306 @@ -import { MoreOutlined, SearchOutlined } from "@ant-design/icons" -import { Card, Input,Button ,Dropdown,App, Tag, Empty } from "antd" -import { debounce } from "lodash-es" -import { forwardRef, useEffect, useState } from "react" -import { VirtuosoGrid } from "react-virtuoso" -import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const" -import { useFetch } from "@common/hooks/http" -import { SubscribeApprovalInfoType } from "@common/const/approval/type" -import { useOutletContext, useParams } from "react-router-dom" -import { RouterParams } from "@core/components/aoplatform/RenderRoutes" -import { TenantManagementServiceListItem } from "../../../const/serviceHub/type" -import { ApprovalModalContent } from "./ApprovalModalContent" -import { useGlobalContext } from "@common/contexts/GlobalStateContext" -import { $t } from "@common/locales" -import WithPermission from "@common/components/aoplatform/WithPermission" -import { checkAccess } from "@common/utils/permission" +import { MoreOutlined, SearchOutlined } from '@ant-design/icons' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { SubscribeApprovalInfoType } from '@common/const/approval/type' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { checkAccess } from '@common/utils/permission' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { App, Button, Card, Dropdown, Empty, Input, Tag } from 'antd' +import { debounce } from 'lodash-es' +import { forwardRef, useEffect, useState } from 'react' +import { useOutletContext, useParams } from 'react-router-dom' +import { VirtuosoGrid } from 'react-virtuoso' +import { TenantManagementServiceListItem } from '../../../const/serviceHub/type' +import { ApprovalModalContent } from './ApprovalModalContent' -export default function ManagementInsideService(){ - const {message, modal} = App.useApp() - const [serviceList, setServiceList] = useState([]) - const {fetchData} = useFetch() - const {teamId,appId} = useParams() - const [keyword, setKeyword] = useState('') - const { refreshGroup} = useOutletContext<{refreshGroup:()=>void}>() - const {accessData} = useGlobalContext() +export default function ManagementInsideService() { + const { message, modal } = App.useApp() + const [serviceList, setServiceList] = useState([]) + const { fetchData } = useFetch() + const { teamId, appId } = useParams() + const [keyword, setKeyword] = useState('') + const { refreshGroup } = useOutletContext<{ refreshGroup: () => void }>() + const { accessData } = useGlobalContext() - const onSearchWordChange = (e)=>{ - setKeyword(e.target.value) - } - - const cancelSubscribeApply = (entity:TenantManagementServiceListItem) => { - return new Promise((resolve, reject)=>{ - fetchData>('application/subscription/cancel_apply',{method:'POST',eoParams:{subscription:entity.id!,application:appId!,team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) + const onSearchWordChange = (e) => { + setKeyword(e.target.value) + } + + const cancelSubscribeApply = (entity: TenantManagementServiceListItem) => { + return new Promise((resolve, reject) => { + fetchData>('application/subscription/cancel_apply', { + method: 'POST', + eoParams: { subscription: entity.id!, application: appId!, team: teamId } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) - } + .catch((errorInfo) => reject(errorInfo)) + }) + } - const cancelSubscribe = (entity:TenantManagementServiceListItem) => { - return new Promise((resolve, reject)=>{ - fetchData>('application/subscription/cancel',{method:'POST',eoParams:{subscription:entity.id!,application:appId!,team:teamId}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) + const cancelSubscribe = (entity: TenantManagementServiceListItem) => { + return new Promise((resolve, reject) => { + fetchData>('application/subscription/cancel', { + method: 'POST', + eoParams: { subscription: entity.id!, application: appId!, team: teamId } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } }) - } - - const openModal =async (type:'view'|'cancelSub'|'cancelSubApply',entity?:TenantManagementServiceListItem)=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'view':{ - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>('app/subscription/approval',{method:'GET',eoParams:{subscription:entity!.id, app:appId,team:teamId},eoTransformKeys:['apply_project','apply_team','apply_time','approval_time']}) - message.destroy() - if(code === STATUS_CODE.SUCCESS){ - title=$t('审核详情') - content = ; - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return - } - break; - } - case 'cancelSub': - title=$t('取消订阅') - content=$t('请确认是否取消订阅?') - break; - case 'cancelSubApply': - title=$t('取消订阅申请') - content=$t('请确认是否取消订阅申请?') - break; + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const openModal = async (type: 'view' | 'cancelSub' | 'cancelSubApply', entity?: TenantManagementServiceListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'view': { + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>( + 'app/subscription/approval', + { + method: 'GET', + eoParams: { subscription: entity!.id, app: appId, team: teamId }, + eoTransformKeys: ['apply_project', 'apply_team', 'apply_time', 'approval_time'] + } + ) + message.destroy() + if (code === STATUS_CODE.SUCCESS) { + title = $t('审核详情') + content = + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return } - - modal.confirm({ - title, - content, - onOk:()=>{ - switch (type){ - case 'view': - return true - case 'cancelSubApply': - return cancelSubscribeApply(entity!).then(res=>{if(res){getServiceList(); refreshGroup?.()}} ) - case 'cancelSub': - return cancelSubscribe(entity!).then(res=>{if(res){getServiceList(); refreshGroup?.()}} ) - } - }, - width:600, - okText:$t('确认'), - cancelText:$t('取消'), - okButtonProps:{ - disabled : !checkAccess( `team.application.authorization.${type}`, accessData ) - }, - footer: (_, { OkBtn, CancelBtn }) => ( - <> - - - - - - ), - closable:true, - icon:<>, - }) + break + } + case 'cancelSub': + title = $t('取消订阅') + content = $t('请确认是否取消订阅?') + break + case 'cancelSubApply': + title = $t('取消订阅申请') + content = $t('请确认是否取消订阅申请?') + break } + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'view': + return true + case 'cancelSubApply': + return cancelSubscribeApply(entity!).then((res) => { + if (res) { + getServiceList() + refreshGroup?.() + } + }) + case 'cancelSub': + return cancelSubscribe(entity!).then((res) => { + if (res) { + getServiceList() + refreshGroup?.() + } + }) + } + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + okButtonProps: { + disabled: !checkAccess(`team.application.authorization.${type}`, accessData) + }, + footer: (_, { OkBtn, CancelBtn }) => ( + <> + + + + + + ), + closable: true, + icon: <> + }) + } - const dropdownMenu = (entity:TenantManagementServiceListItem) => [ - // { - // key: 'edit', - // label: ( - // // - // - // // - // ), - // }, - entity.applyStatus === 1 ? { - key: 'cancelSubApply', - label: ( - // - - // - ), - }:{ - key: 'cancelSub', - label: ( - // - - // - ), - }, - ] + const dropdownMenu = (entity: TenantManagementServiceListItem) => [ + // { + // key: 'edit', + // label: ( + // // + // + // // + // ), + // }, + entity.applyStatus === 1 + ? { + key: 'cancelSubApply', + label: ( + // + + // + ) + } + : { + key: 'cancelSub', + label: ( + // + + // + ) + } + ] - const getServiceList = ()=>{ - fetchData>('application/subscriptions',{method:'GET', eoParams:{application:appId,team:teamId},eoTransformKeys:['apply_status']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setServiceList(data.subscriptions && data.subscriptions.length > 0 ? [...data.subscriptions] : []) - // return {data:data.services, success: true,total:data.total} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - // return {data:[], success:false} - } - }) - } + const getServiceList = () => { + fetchData>('application/subscriptions', { + method: 'GET', + eoParams: { application: appId, team: teamId }, + eoTransformKeys: ['apply_status'] + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setServiceList(data.subscriptions && data.subscriptions.length > 0 ? [...data.subscriptions] : []) + // return {data:data.services, success: true,total:data.total} + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + // return {data:[], success:false} + } + }) + } + useEffect(() => { + getServiceList() + }, []) - useEffect(() => { - getServiceList() - }, []); - - return (
-
- {$t('服务')} - debounce(onSearchWordChange, 100)(e) : undefined } onPressEnter={()=>getServiceList()} allowClear placeholder={$t('搜索服务')} prefix={{getServiceList()}}/>}/> -
- { (keyword ? serviceList.filter(x=>x.service.name.includes(keyword)) :serviceList)?.length > 0 ? + return ( +
+
+ {$t('服务')} + debounce(onSearchWordChange, 100)(e) : undefined} + onPressEnter={() => getServiceList()} + allowClear + placeholder={$t('搜索服务')} + prefix={ + { + getServiceList() + }} + /> + } + /> +
+ {(keyword ? serviceList.filter((x) => x.service.name.includes(keyword)) : serviceList)?.length > 0 ? ( x.service.name.includes(keyword)) :serviceList} - totalCount={(keyword ? serviceList.filter(x=>x.service.name.includes(keyword)) :serviceList).length} - itemContent={(index) => { - const item = (keyword ? serviceList.filter(x=>x.service.name.includes(keyword)) :serviceList)[index]; - return ( -
{item.service.name}{ item.applyStatus === 1 && - {$t('审核中')} - } -
- - -
-
- ); - }} - components={{ - List: forwardRef(({ style, children, ...props }, ref) => ( -
x.service.name.includes(keyword)) : serviceList} + totalCount={(keyword ? serviceList.filter((x) => x.service.name.includes(keyword)) : serviceList).length} + itemContent={(index) => { + const item = (keyword ? serviceList.filter((x) => x.service.name.includes(keyword)) : serviceList)[index] + return ( + +
+ + {item.service.name} + {item.applyStatus === 1 && ( + + {$t('审核中')} + + )} + +
+
- )), - Item: ({ children, ...props }) => ( - <> - {children} - ) + {$t('API 文档')} + + + + +
+
+ + ) + }} + components={{ + List: forwardRef(({ style, children, ...props }, ref) => ( +
:
} -
) -} \ No newline at end of file + > + {children} +
+ )), + Item: ({ children, ...props }) => <>{children} + }} + /> + ) : ( +
+ +
+ )} +
+ ) +} diff --git a/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx b/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx index c71f0494..ef5f5ea2 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ServiceHubManagement.tsx @@ -1,375 +1,455 @@ -import { MenuProps, Menu, App, Avatar, Card, Tooltip, Empty, Button, Radio } from "antd"; -import { useState, forwardRef, useEffect, useRef, useMemo, memo, Ref, useImperativeHandle } from "react"; -import { VirtuosoGrid } from "react-virtuoso"; -import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; -import { ServiceHubAppListItem } from "../../../const/serviceHub/type"; -import { useFetch } from "@common/hooks/http"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import ManagementConfig, { ManagementConfigHandle } from "./ManagementConfig"; -import { useNavigate, useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; -import { SimpleTeamItem } from "@common/const/type"; -import { useTenantManagementContext } from "../../../contexts/TenantManagementContext"; -import { Icon } from "@iconify/react/dist/iconify.js"; -import { GlobalProvider, useGlobalContext } from "@common/contexts/GlobalStateContext"; -import { $t } from "@common/locales"; -import WithPermission from "@common/components/aoplatform/WithPermission"; -import InsidePage from "@common/components/aoplatform/InsidePage"; -import PageList from "@common/components/aoplatform/PageList"; -import { SERVICE_HUB_TABLE_COLUMNS } from "@market/const/serviceHub/const"; -import { ActionType } from "@ant-design/pro-components"; +import { ActionType } from '@ant-design/pro-components' +import InsidePage from '@common/components/aoplatform/InsidePage' +import PageList from '@common/components/aoplatform/PageList' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, DATA_SHOW_TYPE_OPTIONS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { SimpleTeamItem } from '@common/const/type' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' +import { GlobalProvider, useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes' +import { Icon } from '@iconify/react/dist/iconify.js' +import { SERVICE_HUB_TABLE_COLUMNS } from '@market/const/serviceHub/const' +import { App, Avatar, Card, Empty, Menu, MenuProps, Radio, Tooltip } from 'antd' +import { forwardRef, memo, Ref, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' +import { useNavigate, useParams } from 'react-router-dom' +import { VirtuosoGrid } from 'react-virtuoso' +import { ServiceHubAppListItem } from '../../../const/serviceHub/type' +import { useTenantManagementContext } from '../../../contexts/TenantManagementContext' +import ManagementConfig, { ManagementConfigHandle } from './ManagementConfig' export default function ServiceHubManagement() { - const { message ,modal} = App.useApp() - const { teamId} = useParams() - const [serviceList, setServiceList] = useState([]) - const {fetchData} = useFetch() - const { setBreadcrumb} = useBreadcrumb() - const addManagementRef = useRef(null) - const [pageLoading, setPageLoading] = useState(false) - const [serviceLoading, setServiceLoading] = useState(false) - const [teamList, setTeamList] = useState([]) - const {setAppName} = useTenantManagementContext() - const navigateTo = useNavigate() - const {getTeamAccessData,cleanTeamAccessData,checkPermission,getGlobalAccessData,accessInit,state} = useGlobalContext() - type MenuItem = Required['items'][number]; - const [dataShowType, setDataShowType] = useState<'block'|'list'>('list') - const [tableHttpReload, setTableHttpReload] = useState(true); - const [tableListDataSource, setTableListDataSource] = useState([]); - const [tableSearchWord, setTableSearchWord] = useState('') - const tableRef = useRef(null) + const { message, modal } = App.useApp() + const { teamId } = useParams() + const [serviceList, setServiceList] = useState([]) + const { fetchData } = useFetch() + const { setBreadcrumb } = useBreadcrumb() + const addManagementRef = useRef(null) + const [pageLoading, setPageLoading] = useState(false) + const [serviceLoading, setServiceLoading] = useState(false) + const [teamList, setTeamList] = useState([]) + const { setAppName } = useTenantManagementContext() + const navigateTo = useNavigate() + const { getTeamAccessData, cleanTeamAccessData, checkPermission, getGlobalAccessData, accessInit, state } = + useGlobalContext() + type MenuItem = Required['items'][number] + const [dataShowType, setDataShowType] = useState<'block' | 'list'>('list') + const [tableHttpReload, setTableHttpReload] = useState(true) + const [tableListDataSource, setTableListDataSource] = useState([]) + const [tableSearchWord, setTableSearchWord] = useState('') + const tableRef = useRef(null) - const getServiceList = (dataType?:'block'|'list')=>{ - dataType = dataType ?? dataShowType - if(!accessInit){ - getGlobalAccessData()?.then?.(()=>{getServiceList(dataType)}) - return Promise.resolve({data:[], success:false}) + const getServiceList = (dataType?: 'block' | 'list') => { + dataType = dataType ?? dataShowType + if (!accessInit) { + getGlobalAccessData()?.then?.(() => { + getServiceList(dataType) + }) + return Promise.resolve({ data: [], success: false }) + } + + if (dataType === 'list' && !tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) + } + + setServiceLoading(true) + return fetchData>( + !checkPermission('system.workspace.application.view_all') ? 'my_apps' : 'apps', + { + method: 'GET', + eoParams: { team: dataType === 'list' ? undefined : teamId, keyword: tableSearchWord }, + eoTransformKeys: ['api_num', 'subscribe_num', 'subscribe_verify_num', 'auth_num', 'create_time', 'can_delete'] + } + ) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setServiceList([...data.apps, { type: 'addNewItem' }]) + setTableListDataSource(data.apps) + setTableHttpReload(false) + return { data: data.apps, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } - - if(dataType === 'list' && !tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - - setServiceLoading(true) - return fetchData>(!checkPermission('system.workspace.application.view_all') ? 'my_apps':'apps',{method:'GET',eoParams:{ team: dataType === 'list' ? undefined : teamId,keyword:tableSearchWord},eoTransformKeys:['api_num','subscribe_num','subscribe_verify_num','auth_num','create_time','can_delete']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setServiceList([...data.apps,{type:'addNewItem'}]) - setTableListDataSource(data.apps) - setTableHttpReload(false) - return {data:data.apps, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }).finally(()=>{ + }) + .catch(() => { + return { data: [], success: false } + }) + .finally(() => { setServiceLoading(false) - }) -} + }) + } const onClick: MenuProps['onClick'] = (e) => { navigateTo(`/consumer/list/${e.key}`) - }; + } - const getTeamsList = () => { - setPageLoading(true); + setPageLoading(true) const fetchTeams = () => { - fetchData>( - !checkPermission('system.workspace.team.view_all') - ? 'simple/teams/mine' - : 'simple/teams', - { - method: 'GET', - eoTransformKeys: ['app_num', 'subscribe_num'], - } - ) - .then(response => { - const { code, data, msg } = response; - if (code === STATUS_CODE.SUCCESS) { - setTeamList( - data.teams.map((x: SimpleTeamItem) => ({ - label: ( -
- - {x.name} - - - {x.appNum || 0} - -
- ), - key: x.id, - })) - ); - if (!teamId && data.teams?.[0]?.id) { - navigateTo(`/consumer/list/${data.teams[0].id}`); - } - } else { - message.error(msg || $t(RESPONSE_TIPS.error)); + fetchData>( + !checkPermission('system.workspace.team.view_all') ? 'simple/teams/mine' : 'simple/teams', + { + method: 'GET', + eoTransformKeys: ['app_num', 'subscribe_num'] + } + ) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTeamList( + data.teams.map((x: SimpleTeamItem) => ({ + label: ( +
+ + {x.name} + + + {x.appNum || 0} + +
+ ), + key: x.id + })) + ) + if (!teamId && data.teams?.[0]?.id) { + navigateTo(`/consumer/list/${data.teams[0].id}`) } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } }) .finally(() => { - setPageLoading(false); - }); - }; + setPageLoading(false) + }) + } if (!accessInit) { - const checkAccessData = () => { - const accessInitd = getGlobalAccessData(); - if (!accessInitd) { - setTimeout(checkAccessData, 100); - return; - } + const checkAccessData = () => { + const accessInitd = getGlobalAccessData() + if (!accessInitd) { + setTimeout(checkAccessData, 100) + return + } - if (typeof accessInitd.then === 'function') { - accessInitd.then(fetchTeams); - } else { - fetchTeams(); - } - }; + if (typeof accessInitd.then === 'function') { + accessInitd.then(fetchTeams) + } else { + fetchTeams() + } + } - checkAccessData(); + checkAccessData() } else { - fetchTeams(); + fetchTeams() } -}; - - - const openModal = async (type:'add'|'edit'|'delete')=>{ - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'add': - title=$t('添加消费者') - content= - break; - // case 'edit':{ - // title='配置 Open Api' - // message.loading('正在加载数据') - // const {code,data,msg} = await fetchData>('external-app',{method:'GET',eoParams:{id:entity!.id}}) - // message.destroy() - // if(code === STATUS_CODE.SUCCESS){ - // content= - // }else{ - // message.error(msg || $t(RESPONSE_TIPS.error)) - // return - // } - // break;} - // case 'delete': - // title='删除' - // content='该数据删除后将无法找回,请确认是否删除?' - // break; + } + + const openModal = async (type: 'add' | 'edit' | 'delete') => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'add': + title = $t('添加消费者') + content = ( + + + + ) + break + // case 'edit':{ + // title='配置 Open Api' + // message.loading('正在加载数据') + // const {code,data,msg} = await fetchData>('external-app',{method:'GET',eoParams:{id:entity!.id}}) + // message.destroy() + // if(code === STATUS_CODE.SUCCESS){ + // content= + // }else{ + // message.error(msg || $t(RESPONSE_TIPS.error)) + // return + // } + // break;} + // case 'delete': + // title='删除' + // content='该数据删除后将无法找回,请确认是否删除?' + // break; } modal.confirm({ - title, - content, - onOk:()=> { - switch (type){ - case 'add': - return addManagementRef.current?.save().then((res)=>{if(res === true) { - getTeamsList(); - if(dataShowType === 'list'){ - setTableHttpReload(true) - tableRef.current?.manualReloadTable() - }else{ - getServiceList() - } - }}) - // case 'edit': - // return editManagementRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) - // case 'delete': - // return deleteManagement(entity!).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - cancelText:$t('取消'), - closable:true, - icon:<>, + title, + content, + onOk: () => { + switch (type) { + case 'add': + return addManagementRef.current?.save().then((res) => { + if (res === true) { + getTeamsList() + if (dataShowType === 'list') { + setTableHttpReload(true) + tableRef.current?.manualReloadTable() + } else { + getServiceList() + } + } + }) + // case 'edit': + // return editManagementRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) + // case 'delete': + // return deleteManagement(entity!).then((res)=>{if(res === true) manualReloadTable()}) + } + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + closable: true, + icon: <> }) -} + } -const dataShowTypeOptions = useMemo(()=>DATA_SHOW_TYPE_OPTIONS.map((x)=>({label:$t(x.label),value:x.value})),[ - state.language -]) + const dataShowTypeOptions = useMemo( + () => DATA_SHOW_TYPE_OPTIONS.map((x) => ({ label: $t(x.label), value: x.value })), + [state.language] + ) -useEffect(()=>{ - if(teamId ){ - getTeamAccessData(teamId) - if(dataShowType === 'block'){getServiceList()} + useEffect(() => { + if (teamId) { + getTeamAccessData(teamId) + if (dataShowType === 'block') { + getServiceList() + } } - return ()=>{ - cleanTeamAccessData() + return () => { + cleanTeamAccessData() } -},[teamId]) + }, [teamId]) -useEffect(() => { - setBreadcrumb( - [ - {title:$t('消费者')} - ] - ) + useEffect(() => { + setBreadcrumb([{ title: $t('消费者') }]) getTeamsList() setAppName('') -}, []); + }, []) - - const BlockArea = ()=>( -
-
-
{$t('团队')}
- -
-
-
{$t('消费者')}
- { - const item = serviceList[index]; - return ( -
{ - item.type === 'addNewItem' ?{openModal('add')}}> -
{$t('添加消费者')}
-
: {setAppName(item.name);navigateTo(`/consumer/${teamId}/inside/${item.id}/service`)}}> - {item.description || $t('暂无消费者描述')} - - }
- ); - }} - components={{ - List: forwardRef(({ style, children, ...props }, ref) => ( -
- {children} -
- )), - Item: ({ children, ...props }) => ( - <> - {children} - ) - }} - /> -
-
+ const BlockArea = () => ( +
+
+
{$t('团队')}
+ +
+
+
{$t('消费者')}
+ { + const item = serviceList[index] + return ( +
+ {item.type === 'addNewItem' ? ( + + { + openModal('add') + }} + > +
+ + {$t('添加消费者')} +
+
+
+ ) : ( + { + setAppName(item.name) + navigateTo(`/consumer/${teamId}/inside/${item.id}/service`) + }} + > + {item.description || $t('暂无消费者描述')} + + )} +
) + }} + components={{ + List: forwardRef(({ style, children, ...props }, ref) => ( +
+ {children} +
+ )), + Item: ({ children, ...props }) => <>{children} + }} + /> +
+
+ ) - - return (<>{ - teamList && teamList.length > 0 ? - {setDataShowType(e.target.value); setTableHttpReload(true); if(e.target.value === 'block'){getServiceList(e.target.value)}}} - value={dataShowType} - optionType="button" - buttonStyle="solid" - />} - >{ - dataShowType === 'block' ? : getServiceList('list')} ref={tableRef} addNewApp={()=>openModal('add')} setTableHttpReload={setTableHttpReload} setTableSearchWord={setTableSearchWord} editApp={(row:ServiceHubAppListItem)=>{setAppName(row.name);navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`)}}/> - } - : + return ( + <> + {teamList && teamList.length > 0 ? ( + { + setDataShowType(e.target.value) + setTableHttpReload(true) + if (e.target.value === 'block') { + getServiceList(e.target.value) + } + }} + value={dataShowType} + optionType="button" + buttonStyle="solid" + /> + } + > + {dataShowType === 'block' ? ( + + ) : ( + getServiceList('list')} + ref={tableRef} + addNewApp={() => openModal('add')} + setTableHttpReload={setTableHttpReload} + setTableSearchWord={setTableSearchWord} + editApp={(row: ServiceHubAppListItem) => { + setAppName(row.name) + navigateTo(`/consumer/${row.team.id}/inside/${row.id}/service`) + }} + /> + )} + + ) : ( - } - ) + )} + + ) } -const CardTitle = (service:ServiceHubAppListItem)=>{ - return( -
- } /> -
-

{service.name}

-
- - {(service.subscribeNum + service.subscribeVerifyNum)?? '-'} - -
-
+const CardTitle = (service: ServiceHubAppListItem) => { + return ( +
+ } + /> +
+

{service.name}

+
+ + + + + + + {service.subscribeNum + service.subscribeVerifyNum || '-'} + + +
- ) +
+
+ ) } - type TableAreaProps = { - language:string - getServiceList:()=>Promise<{data:ServiceHubAppListItem[], success:boolean}> - addNewApp:()=>Promise - setTableHttpReload:(b:boolean)=>void - setTableSearchWord:(s:string)=>void - editApp:(item:ServiceHubAppListItem)=>void + language: string + getServiceList: () => Promise<{ data: ServiceHubAppListItem[]; success: boolean }> + addNewApp: () => Promise + setTableHttpReload: (b: boolean) => void + setTableSearchWord: (s: string) => void + editApp: (item: ServiceHubAppListItem) => void } type TableAreaHandle = { - manualReloadTable:()=>void + manualReloadTable: () => void } -const TableArea = memo(forwardRef((props:TableAreaProps, ref:Ref)=>{ - const {language, getServiceList, addNewApp, setTableHttpReload, setTableSearchWord, editApp} = props - const pageListRef = useRef(null); - const columns = useMemo(()=>{ - const res = SERVICE_HUB_TABLE_COLUMNS.map(x=>{ - return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}}) - return res - },[language]) +const TableArea = memo( + forwardRef((props: TableAreaProps, ref: Ref) => { + const { language, getServiceList, addNewApp, setTableHttpReload, setTableSearchWord, editApp } = props + const pageListRef = useRef(null) + const columns = useMemo(() => { + const res = SERVICE_HUB_TABLE_COLUMNS.map((x) => { + return { ...x, title: typeof x.title === 'string' ? $t(x.title as string) : x.title } + }) + return res + }, [language]) - const manualReloadTable = ()=>{ - pageListRef.current?.reload() + const manualReloadTable = () => { + pageListRef.current?.reload() } - - useImperativeHandle(ref, () =>({ - manualReloadTable})) - + + useImperativeHandle(ref, () => ({ + manualReloadTable + })) return ( - getServiceList()} - addNewBtnTitle={$t("添加消费者")} - searchPlaceholder={$t("输入名称、ID 查找消费者")} + request={() => getServiceList()} + addNewBtnTitle={$t('添加消费者')} + searchPlaceholder={$t('输入名称、ID 查找消费者')} onAddNewBtnClick={addNewApp} onChange={() => { - setTableHttpReload(false) + setTableHttpReload(false) }} onSearchWordChange={(e) => { - setTableSearchWord(e.target.value) + setTableSearchWord(e.target.value) }} - onRowClick={(row:ServiceHubAppListItem)=>editApp(row)} - /> - )})) \ No newline at end of file + onRowClick={(row: ServiceHubAppListItem) => editApp(row)} + /> + ) + }) +) diff --git a/frontend/packages/market/vite.config.ts b/frontend/packages/market/vite.config.ts index a1c1546f..04d29564 100644 --- a/frontend/packages/market/vite.config.ts +++ b/frontend/packages/market/vite.config.ts @@ -1,37 +1,37 @@ - import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' -import dynamicImportVars from '@rollup/plugin-dynamic-import-vars'; +import dynamicImportVars from '@rollup/plugin-dynamic-import-vars' export default defineConfig({ - build:{ - outDir:'../../tenant_dist', + build: { + outDir: '../../tenant_dist', sourcemap: false, chunkSizeWarningLimit: 50000, rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules')) { - return id.toString().split('node_modules/')[1].split('/')[0].toString(); + return id.toString().split('node_modules/')[1].split('/')[0].toString() } } - }, + } } }, - plugins: [react(), - dynamicImportVars({ - include:["src"], - exclude:[], - warnOnError:false - }), - ], + plugins: [ + react(), + dynamicImportVars({ + include: ['src'], + exclude: [], + warnOnError: false + }) + ], resolve: { alias: [ { find: /^~/, replacement: '' }, { find: '@market', replacement: path.resolve(__dirname, './src') }, { find: '@common', replacement: path.resolve(__dirname, '../common/src') }, - { find: '@core', replacement: path.resolve(__dirname, '../core/src') }, + { find: '@core', replacement: path.resolve(__dirname, '../core/src') } ] }, server: { @@ -39,14 +39,14 @@ export default defineConfig({ '/api/v1': { // target: 'http://uat.apikit.com:11204/mockApi/aoplatform/', target: 'http://172.18.166.219:8488/', - changeOrigin: true, + changeOrigin: true }, '/api2/v1': { // target: 'http://uat.apikit.com:11204/mockApi/aoplatform/', target: 'http://172.18.166.219:8488/', - changeOrigin: true, + changeOrigin: true } } }, - logLevel:'info' + logLevel: 'info' }) diff --git a/frontend/packages/openApi/src/consts/const.tsx b/frontend/packages/openApi/src/consts/const.tsx index 648aaf2c..f9a7d023 100644 --- a/frontend/packages/openApi/src/consts/const.tsx +++ b/frontend/packages/openApi/src/consts/const.tsx @@ -1,49 +1,48 @@ -import { PageProColumns } from "@common/components/aoplatform/PageList"; -import { OpenApiTableListItem } from "./type"; -import { $t } from "@common/locales/index.ts"; - +import { PageProColumns } from '@common/components/aoplatform/PageList' +import { OpenApiTableListItem } from './type' +import { $t } from '@common/locales/index.ts' export const OPENAPI_LIST_COLUMNS: PageProColumns[] = [ - { - title:$t('消费者名称'), - dataIndex: 'name', - ellipsis:true, - width:160, - fixed:'left' - }, - { - title:$t('消费者 ID'), - dataIndex: 'id', - ellipsis:true, - width: 140, - }, - { - title:$t('鉴权 Token'), - dataIndex: 'token', - ellipsis:{ - showTitle:true - } - }, - { - title:$t('关联标签'), - dataIndex: 'tag' - }, - { - title:$t('启用'), - dataIndex: 'status' - }, - { - title:$t('更新者'), - dataIndex: ['operator','name'], - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true - }, - { - title:$t('更新时间'), - width:182, - dataIndex: 'updateTime', - sorter: (a,b)=>(new Date(a.updateTime)).getTime() - (new Date(b.updateTime)).getTime() + { + title: $t('消费者名称'), + dataIndex: 'name', + ellipsis: true, + width: 160, + fixed: 'left' + }, + { + title: $t('消费者 ID'), + dataIndex: 'id', + ellipsis: true, + width: 140 + }, + { + title: $t('鉴权 Token'), + dataIndex: 'token', + ellipsis: { + showTitle: true } -]; + }, + { + title: $t('关联标签'), + dataIndex: 'tag' + }, + { + title: $t('启用'), + dataIndex: 'status' + }, + { + title: $t('更新者'), + dataIndex: ['operator', 'name'], + filters: true, + onFilter: true, + valueType: 'select', + filterSearch: true + }, + { + title: $t('更新时间'), + width: 182, + dataIndex: 'updateTime', + sorter: (a, b) => new Date(a.updateTime).getTime() - new Date(b.updateTime).getTime() + } +] diff --git a/frontend/packages/openApi/src/consts/type.ts b/frontend/packages/openApi/src/consts/type.ts index 390e25ee..5b8cc043 100644 --- a/frontend/packages/openApi/src/consts/type.ts +++ b/frontend/packages/openApi/src/consts/type.ts @@ -1,11 +1,11 @@ -import { EntityItem } from "@common/const/type"; +import { EntityItem } from '@common/const/type' export type OpenApiTableListItem = { - id:string; - name: string; - token:string; - tags:string; - status:boolean; - operator:EntityItem; - updateTime:string; -}; \ No newline at end of file + id: string + name: string + token: string + tags: string + status: boolean + operator: EntityItem + updateTime: string +} diff --git a/frontend/packages/openApi/src/pages/OpenApiConfig.tsx b/frontend/packages/openApi/src/pages/OpenApiConfig.tsx index ff7029c7..16a53fcd 100644 --- a/frontend/packages/openApi/src/pages/OpenApiConfig.tsx +++ b/frontend/packages/openApi/src/pages/OpenApiConfig.tsx @@ -1,96 +1,100 @@ - -import {App, Form, Input} from "antd"; -import {forwardRef, useEffect, useImperativeHandle} from "react"; -import {BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx"; -import {useFetch} from "@common/hooks/http.ts"; -import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; -import {v4 as uuidv4} from 'uuid' -import { $t } from "@common/locales"; +import { App, Form, Input } from 'antd' +import { forwardRef, useEffect, useImperativeHandle } from 'react' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useFetch } from '@common/hooks/http.ts' +import WithPermission from '@common/components/aoplatform/WithPermission.tsx' +import { v4 as uuidv4 } from 'uuid' +import { $t } from '@common/locales' export type OpenApiConfigFieldType = { - id?:string - name:string - desc:string -}; + id?: string + name: string + desc: string +} type OpenApiConfigProps = { - type:'add'|'edit' - entity?:OpenApiConfigFieldType + type: 'add' | 'edit' + entity?: OpenApiConfigFieldType } export type OpenApiConfigHandle = { - save:()=>Promise + save: () => Promise } - -export const OpenApiConfig = forwardRef((props, ref) => { - const { message } = App.useApp() - const {type,entity} = props - const [form] = Form.useForm(); - const {fetchData} = useFetch() - const save:()=>Promise = ()=>{ - return new Promise((resolve, reject)=>{ - form.validateFields().then((value)=>{ - fetchData>('external-app',{method:type === 'add'? 'POST' : 'PUT',eoBody:(value), eoParams:type === 'add' ? {}:{id:entity!.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }).catch((errorInfo)=> reject(errorInfo)) +export const OpenApiConfig = forwardRef((props, ref) => { + const { message } = App.useApp() + const { type, entity } = props + const [form] = Form.useForm() + const { fetchData } = useFetch() + const save: () => Promise = () => { + return new Promise((resolve, reject) => { + form + .validateFields() + .then((value) => { + fetchData>('external-app', { + method: type === 'add' ? 'POST' : 'PUT', + eoBody: value, + eoParams: type === 'add' ? {} : { id: entity!.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + useImperativeHandle(ref, () => ({ + save + })) + + useEffect(() => { + if (type === 'edit' && entity) { + form.setFieldsValue(entity) + } else { + form.setFieldValue('id', uuidv4()) } + }, []) - useImperativeHandle(ref, ()=>({ - save - }) - ) - - useEffect(() => { - if(type === 'edit' && entity){ - form.setFieldsValue(entity) - }else{ - form.setFieldValue('id',uuidv4()) - } - }, []); - - return ( -
+ + + label={$t('消费者名称')} + name="name" + rules={[{ required: true, whitespace: true }]} > - - label={$t("消费者名称")} - name="name" - rules={[{ required: true,whitespace:true }]} - > - - + + - - label={$t("消费者 ID")} - name="id" - rules={[{ required: true ,whitespace:true }]} - > - - + + label={$t('消费者 ID')} + name="id" + rules={[{ required: true, whitespace: true }]} + > + + - - - - - -
) -}) \ No newline at end of file + + + + + + ) +}) diff --git a/frontend/packages/openApi/src/pages/OpenApiList.tsx b/frontend/packages/openApi/src/pages/OpenApiList.tsx index e58233ef..d39cd097 100644 --- a/frontend/packages/openApi/src/pages/OpenApiList.tsx +++ b/frontend/packages/openApi/src/pages/OpenApiList.tsx @@ -1,231 +1,300 @@ -import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx"; -import {useEffect, useRef, useState} from "react"; -import {ActionType} from "@ant-design/pro-components"; -import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; -import { App, Divider, Switch} from "antd"; -import copy from "copy-to-clipboard"; -import {useFetch} from "@common/hooks/http.ts"; -import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import {OpenApiConfig, OpenApiConfigFieldType, OpenApiConfigHandle} from "./OpenApiConfig.tsx"; -import { SimpleMemberItem } from "@common/const/type.ts"; -import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; -import { frontendTimeSorter } from "@common/utils/dataTransfer.ts"; -import { OPENAPI_LIST_COLUMNS } from "@openApi/consts/const.tsx"; -import { OpenApiTableListItem } from "@openApi/consts/type.ts"; -import { $t } from "@common/locales/index.ts"; +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx' +import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { SimpleMemberItem } from '@common/const/type.ts' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { frontendTimeSorter } from '@common/utils/dataTransfer.ts' +import { OPENAPI_LIST_COLUMNS } from '@openApi/consts/const.tsx' +import { OpenApiTableListItem } from '@openApi/consts/type.ts' +import { App, Divider, Switch } from 'antd' +import copy from 'copy-to-clipboard' +import { useEffect, useRef, useState } from 'react' +import { OpenApiConfig, OpenApiConfigFieldType, OpenApiConfigHandle } from './OpenApiConfig.tsx' +export default function OpenApiList() { + const { modal, message } = App.useApp() + // const [confirmLoading, setConfirmLoading] = useState(false); + const [init, setInit] = useState(true) + const [tableListDataSource, setTableListDataSource] = useState([]) + const [tableHttpReload, setTableHttpReload] = useState(true) + const [columns, setColumns] = useState[]>([]) + const pageListRef = useRef(null) + const addOpenApiRef = useRef(null) + const editOpenApiRef = useRef(null) + const { fetchData } = useFetch() + const { setBreadcrumb } = useBreadcrumb() + const [memberValueEnum, setMemberValueEnum] = useState<{ [k: string]: { text: string } }>({}) -export default function OpenApiList(){ - const { modal,message } = App.useApp() - // const [confirmLoading, setConfirmLoading] = useState(false); - const [init, setInit] = useState(true) - const [tableListDataSource, setTableListDataSource] = useState([]); - const [tableHttpReload, setTableHttpReload] = useState(true); - const [columns,setColumns] = useState[] >([]) - const pageListRef = useRef(null); - const addOpenApiRef = useRef(null) - const editOpenApiRef = useRef(null) - const {fetchData} = useFetch() - const { setBreadcrumb } = useBreadcrumb() - const [memberValueEnum, setMemberValueEnum] = useState<{[k:string]:{text:string}}>({}) + const operation: PageProColumns[] = [ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums: 4, + valueType: 'option', + fixed: 'right', + render: (_: React.ReactNode, entity: OpenApiTableListItem) => [ + { + refreshToken(entity) + }} + btnTitle="更新token" + />, + , + { + copyToken(entity) + }} + btnTitle="复制token" + />, + , + { + openModal('edit', entity) + }} + btnTitle="编辑" + />, + , + { + openModal('delete', entity) + }} + btnTitle="删除" + /> + ] + } + ] - const operation:PageProColumns[] =[ - { - title: COLUMNS_TITLE.operate, - key: 'option', - btnNums:4, - valueType: 'option', - fixed:'right', - render: (_: React.ReactNode, entity: OpenApiTableListItem) => [ - {refreshToken(entity)}} btnTitle="更新token"/>, - , - {copyToken(entity)}} btnTitle="复制token"/>, - , - {openModal('edit',entity)}} btnTitle="编辑"/>, - , - {openModal('delete',entity)}} btnTitle="删除"/> - ], + const getOpenApiList = (): Promise<{ data: OpenApiTableListItem[]; success: boolean }> => { + if (!tableHttpReload) { + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true + }) + } + return fetchData>('external-apps', { + method: 'GET', + eoTransformKeys: ['update_time'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setTableListDataSource(data.apps) + setInit((prev) => (prev ? false : prev)) + tableHttpReload && + data.apps.sort((a: OpenApiTableListItem, b: OpenApiTableListItem) => frontendTimeSorter(a, b, 'updateTime')) + setTableHttpReload(false) + return { data: data.apps, success: true } + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return { data: [], success: false } } - ] + }) + .catch(() => { + return { data: [], success: false } + }) + } - const getOpenApiList =(): Promise<{ data: OpenApiTableListItem[], success: boolean }>=> { - if(!tableHttpReload){ - setTableHttpReload(true) - return Promise.resolve({ - data: tableListDataSource, - success: true, - }); - } - return fetchData>('external-apps',{method:'GET',eoTransformKeys:['update_time']}).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - setTableListDataSource(data.apps) - setInit((prev)=>prev ? false : prev) - tableHttpReload && data.apps.sort((a:OpenApiTableListItem,b:OpenApiTableListItem)=>frontendTimeSorter(a,b,'updateTime')) - setTableHttpReload(false) - return {data:data.apps, success: true} - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return {data:[], success:false} - } - }).catch(() => { - return {data:[], success:false} - }) + const refreshToken = (entity: OpenApiTableListItem) => { + fetchData>('external-app/token', { + method: 'PUT', + eoParams: { id: entity.id } + }).then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + manualReloadTable() + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + const copyToken = (entity: OpenApiTableListItem) => { + if (copy(entity.token)) { + message.success($t(RESPONSE_TIPS.copySuccess)) + } else { + message.error($t(RESPONSE_TIPS.copyError)) } + } - const refreshToken = (entity: OpenApiTableListItem)=>{ - fetchData>('external-app/token',{method:'PUT',eoParams:{id:entity.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - manualReloadTable() - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } + const manualReloadTable = () => { + setTableHttpReload(true) // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + } - const copyToken = (entity: OpenApiTableListItem)=>{ - if(copy(entity.token)){ - message.success($t(RESPONSE_TIPS.copySuccess)) - }else{ - message.error($t(RESPONSE_TIPS.copyError)) - } - } - - const manualReloadTable = () => { - setTableHttpReload(true); // 表格数据需要从后端接口获取 - pageListRef.current?.reload() - }; - - const deleteOpenApi = (entity:OpenApiTableListItem)=>{ - return new Promise((resolve, reject)=>{ - fetchData>('external-app',{method:'DELETE',eoParams:{id:entity!.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - resolve(true) - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - reject(msg || $t(RESPONSE_TIPS.error)) - } - }).catch((errorInfo)=> reject(errorInfo)) - }) - } - - const openModal = async (type:'add'|'edit'|'delete',entity?:OpenApiTableListItem)=>{ - - let title:string = '' - let content:string|React.ReactNode = '' - switch (type){ - case 'add': - title=$t('添加 Open Api') - content= - break; - case 'edit':{ - title=$t('配置 Open Api') - message.loading($t(RESPONSE_TIPS.loading)) - const {code,data,msg} = await fetchData>('external-app',{method:'GET',eoParams:{id:entity!.id}}) - message.destroy() - if(code === STATUS_CODE.SUCCESS){ - content= - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - return - } - break;} - case 'delete': - title=$t('删除') - content=$t(DELETE_TIPS.default) - break; - } - - modal.confirm({ - title, - content, - onOk:()=> { - switch (type){ - case 'add': - return addOpenApiRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) - case 'edit': - return editOpenApiRef.current?.save().then((res)=>{if(res === true) manualReloadTable()}) - case 'delete': - return deleteOpenApi(entity!).then((res)=>{if(res === true) manualReloadTable()}) - } - }, - width:600, - okText:$t('确认'), - cancelText:$t('取消'), - closable:true, - icon:<>, - }) - } - - const changeOpenApiStatus = (enabled:boolean,entity:OpenApiTableListItem)=>{ - fetchData>(`external-app/${enabled ? 'disable' :'enable'}`,{method:'PUT',eoParams:{id:entity.id}}).then(response=>{ - const {code,msg} = response - if(code === STATUS_CODE.SUCCESS){ - message.success(msg || $t(RESPONSE_TIPS.success)) - manualReloadTable() - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - } - - - const getMemberList = async ()=>{ - setMemberValueEnum({}) - const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) - if(code === STATUS_CODE.SUCCESS){ - const tmpValueEnum:{[k:string]:{text:string}} = {} - data.members?.forEach((x:SimpleMemberItem)=>{ - tmpValueEnum[x.name] = {text:x.name} - }) - setMemberValueEnum(tmpValueEnum) - }else{ + const deleteOpenApi = (entity: OpenApiTableListItem) => { + return new Promise((resolve, reject) => { + fetchData>('external-app', { + method: 'DELETE', + eoParams: { id: entity!.id } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + } else { message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => reject(errorInfo)) + }) + } + + const openModal = async (type: 'add' | 'edit' | 'delete', entity?: OpenApiTableListItem) => { + let title: string = '' + let content: string | React.ReactNode = '' + switch (type) { + case 'add': + title = $t('添加 Open Api') + content = + break + case 'edit': { + title = $t('配置 Open Api') + message.loading($t(RESPONSE_TIPS.loading)) + const { code, data, msg } = await fetchData>('external-app', { + method: 'GET', + eoParams: { id: entity!.id } + }) + message.destroy() + if (code === STATUS_CODE.SUCCESS) { + content = + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return } + break + } + case 'delete': + title = $t('删除') + content = $t(DELETE_TIPS.default) + break } + modal.confirm({ + title, + content, + onOk: () => { + switch (type) { + case 'add': + return addOpenApiRef.current?.save().then((res) => { + if (res === true) manualReloadTable() + }) + case 'edit': + return editOpenApiRef.current?.save().then((res) => { + if (res === true) manualReloadTable() + }) + case 'delete': + return deleteOpenApi(entity!).then((res) => { + if (res === true) manualReloadTable() + }) + } + }, + width: 600, + okText: $t('确认'), + cancelText: $t('取消'), + closable: true, + icon: <> + }) + } - useEffect(() => { - setBreadcrumb([{ title:$t('Open Api')}]) - getMemberList() - setColumns(OPENAPI_LIST_COLUMNS - .map((x)=>{ - if(x.dataIndex === 'status' ){ - x.render = (_,record)=>( -
{e?.stopPropagation()}}>{ changeOpenApiStatus(e,record)}} />
- ) - } - if(x.filters &&((x.dataIndex as string[])?.indexOf('updater') !== -1 )){ - x.valueEnum = memberValueEnum - } - return x - } - ) - ) - }, []); + const changeOpenApiStatus = (enabled: boolean, entity: OpenApiTableListItem) => { + fetchData>(`external-app/${enabled ? 'disable' : 'enable'}`, { + method: 'PUT', + eoParams: { id: entity.id } + }).then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + manualReloadTable() + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } - return ( getOpenApiList()} - dataSource={tableListDataSource} - showPagination={false} - primaryKey="id" - addNewBtnTitle={$t("添加消费者")} - addNewBtnAccess="system.openapi.self.add" - onChange={() => { - setTableHttpReload(false) - }} - onAddNewBtnClick={() => { - openModal('add') - }} - onRowClick={(row:OpenApiTableListItem)=>openModal('edit',row)} - tableClickAccess="system.openapi.self.edit" - />) + const getMemberList = async () => { + setMemberValueEnum({}) + const { code, data, msg } = await fetchData>('simple/member', { + method: 'GET' + }) + if (code === STATUS_CODE.SUCCESS) { + const tmpValueEnum: { [k: string]: { text: string } } = {} + data.members?.forEach((x: SimpleMemberItem) => { + tmpValueEnum[x.name] = { text: x.name } + }) + setMemberValueEnum(tmpValueEnum) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } -} \ No newline at end of file + useEffect(() => { + setBreadcrumb([{ title: $t('Open Api') }]) + getMemberList() + setColumns( + OPENAPI_LIST_COLUMNS.map((x) => { + if (x.dataIndex === 'status') { + x.render = (_, record) => ( +
{ + e?.stopPropagation() + }} + > + { + changeOpenApiStatus(e, record) + }} + /> +
+ ) + } + if (x.filters && (x.dataIndex as string[])?.indexOf('updater') !== -1) { + x.valueEnum = memberValueEnum + } + return x + }) + ) + }, []) + + return ( + getOpenApiList()} + dataSource={tableListDataSource} + showPagination={false} + primaryKey="id" + addNewBtnTitle={$t('添加消费者')} + addNewBtnAccess="system.openapi.self.add" + onChange={() => { + setTableHttpReload(false) + }} + onAddNewBtnClick={() => { + openModal('add') + }} + onRowClick={(row: OpenApiTableListItem) => openModal('edit', row)} + tableClickAccess="system.openapi.self.edit" + /> + ) +} diff --git a/frontend/packages/systemRunning/src/pages/SystemRunning.tsx b/frontend/packages/systemRunning/src/pages/SystemRunning.tsx index be94372a..4266d2a2 100644 --- a/frontend/packages/systemRunning/src/pages/SystemRunning.tsx +++ b/frontend/packages/systemRunning/src/pages/SystemRunning.tsx @@ -1,39 +1,44 @@ - -import { useRef, useState, useEffect, useCallback } from "react"; -import { useFetch } from "@common/hooks/http.ts"; -import { useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; -import { App, Button, Spin, Tooltip } from "antd"; -import { debounce } from "lodash-es"; -import { LoadingOutlined, ZoomInOutlined, ZoomOutOutlined } from "@ant-design/icons"; -import G6, { Graph, registerEdge, Item } from "@antv/g6"; -import { PictureTypeEnum, NodeClickItem, GraphData } from "@core/const/system-running/type.ts"; -import { UnionFind, edgesFormatter, getNodeSpacing, nodesFormatter } from "@common/utils/systemRunning.ts"; -import { EDGE_STYLE, END_ARROW_STYLE, OUT_SPACE_CONTENT_EDGE_COLOR, RELATIVE_PICTURE_NODE_FONTSIZE, SELF_SPACE_CONTENT_EDGE_COLOR, SYSTEM_TUNNING_CONFIG } from "@core/const/system-running/const.ts"; -import ReactDOM from "react-dom"; -import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; -import SystemRunningInstruction from "./SystemRunningInstruction.tsx"; -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx"; -import { $t } from "@common/locales/index.ts"; +import { LoadingOutlined, ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons' +import G6, { Graph, Item, registerEdge } from '@antv/g6' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx' +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx' +import { useFetch } from '@common/hooks/http.ts' +import { $t } from '@common/locales/index.ts' +import { UnionFind, edgesFormatter, getNodeSpacing, nodesFormatter } from '@common/utils/systemRunning.ts' +import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx' +import { + EDGE_STYLE, + END_ARROW_STYLE, + OUT_SPACE_CONTENT_EDGE_COLOR, + RELATIVE_PICTURE_NODE_FONTSIZE, + SELF_SPACE_CONTENT_EDGE_COLOR, + SYSTEM_TUNNING_CONFIG +} from '@core/const/system-running/const.ts' +import { GraphData, NodeClickItem, PictureTypeEnum } from '@core/const/system-running/type.ts' +import { App, Button, Spin, Tooltip } from 'antd' +import { debounce } from 'lodash-es' +import { useCallback, useEffect, useRef, useState } from 'react' +import { useParams } from 'react-router-dom' +import SystemRunningInstruction from './SystemRunningInstruction.tsx' export type TopologyItem = { - projects:TopologyProjectItem[] - services:TopologyServiceItem[] + projects: TopologyProjectItem[] + services: TopologyServiceItem[] } export type TopologyProjectItem = { - id:string, - name:string, - invokeServices:string[], - clusters?:string - isApp?:boolean - isServer?:boolean + id: string + name: string + invokeServices: string[] + clusters?: string + isApp?: boolean + isServer?: boolean } export type TopologyServiceItem = { - id:string, - name:string, - project:string + id: string + name: string + project: string } enum EdgeEvent { @@ -52,552 +57,574 @@ const subjectColors = [ '#9661BC', '#F6903D', '#008685', - '#F08BB4', -]; -const backColor = '#fff'; -const theme = 'default'; -const disableColor = '#777'; -const colorSets = G6.Util.getColorSetsBySubjectColors( - subjectColors, - backColor, - theme, - disableColor, -); - + '#F08BB4' +] +const backColor = '#fff' +const theme = 'default' +const disableColor = '#777' +const colorSets = G6.Util.getColorSetsBySubjectColors(subjectColors, backColor, theme, disableColor) // cache the initial node and combo info -const itemMap :Record= {}; +const itemMap: Record = {} + +export default function SystemRunning() { + const { message } = App.useApp() + const graphRef = useRef(null) + const graphContainerRef = useRef(null) + const { topologyId } = useParams() + const [graph, setGraph] = useState(null) + const [graphData, setGraphData] = useState() + const [currentNode, setCurrentNode] = useState() + const [showEdgeTooltip, setShowEdgeTooltip] = useState(false) + const [edgeTooltipX, setEdgeTooltipX] = useState(0) + const [edgeTooltipY, setEdgeTooltipY] = useState(0) + const [edgeTooltipContent, setEdgeTooltipContent] = useState() + const [pictureType, setPictureType] = useState(PictureTypeEnum.Global) + const { fetchData } = useFetch() + const textColor: string = '#666' + const [showGraph, setShowGraph] = useState(false) + const { setBreadcrumb } = useBreadcrumb() + const [zoomNum, setZoomNum] = useState(1) + const [loading, setLoading] = useState(true) + const [categories, setCategories] = useState(undefined) -export default function SystemRunning(){ - const {message} = App.useApp() - const graphRef = useRef(null); - const graphContainerRef = useRef(null); - const {topologyId} = useParams() - const [graph, setGraph] = useState(null); - const [graphData, setGraphData] = useState(); - const [currentNode, setCurrentNode] = useState() - const [showEdgeTooltip, setShowEdgeTooltip] = useState(false) - const [edgeTooltipX, setEdgeTooltipX] = useState(0) - const [edgeTooltipY, setEdgeTooltipY] = useState(0) - const [edgeTooltipContent, setEdgeTooltipContent] = useState() - const [pictureType, setPictureType] = useState(PictureTypeEnum.Global) - const { fetchData } = useFetch() - const textColor:string = '#666' - const [showGraph, setShowGraph] = useState(false) - const { setBreadcrumb } = useBreadcrumb() - const [zoomNum, setZoomNum] = useState(1) - const [loading, setLoading ] = useState(true) - const [categories, setCategories] = useState(undefined) - /** * @description 关联关系转化器,将接口数据转为 g6 渲染需要的格式 */ - const relativeFormatter = (data: TopologyItem) => { + const relativeFormatter = (data: TopologyItem) => { const { projects, services } = data - const serviceMap:Map = new Map() - services.forEach((s:TopologyServiceItem)=>{ - serviceMap.set(s.id,s) + const serviceMap: Map = new Map() + services.forEach((s: TopologyServiceItem) => { + serviceMap.set(s.id, s) }) // Map> - const tmpProjectConnectMap:Map> = new Map() - projects.forEach((p:TopologyProjectItem) => { - const invokedMap = new Map() - p.invokeServices?.forEach((s:string) => { + const tmpProjectConnectMap: Map> = new Map() + projects.forEach((p: TopologyProjectItem) => { + const invokedMap = new Map() + p.invokeServices?.forEach((s: string) => { const invokedProject = serviceMap.get(s) - if(invokedProject){ - invokedMap.has(invokedProject.project) ? invokedMap.get(invokedProject.project)?.push(invokedProject) : invokedMap.set(invokedProject.project, [invokedProject]) - }else{ - console.warn('存在无所属系统的服务:', s) - } + if (invokedProject) { + invokedMap.has(invokedProject.project) + ? invokedMap.get(invokedProject.project)?.push(invokedProject) + : invokedMap.set(invokedProject.project, [invokedProject]) + } else { + console.warn('存在无所属系统的服务:', s) + } }) tmpProjectConnectMap.set(p.id, invokedMap) }) const newNodes = nodesFormatter(projects) const newEdges = edgesFormatter(tmpProjectConnectMap) - // 从 edges 中提取所有唯一的节点,并将 Set 转换为数组 - const allNodeIds:string[] = Array.from(new Set(newEdges.flatMap(edge => [edge.source, edge.target]))) as string[]; + // 从 edges 中提取所有唯一的节点,并将 Set 转换为数组 + const allNodeIds: string[] = Array.from(new Set(newEdges.flatMap((edge) => [edge.source, edge.target]))) as string[] // 初始化 UnionFind,并处理所有的边 - const unionFind = new UnionFind(allNodeIds); + const unionFind = new UnionFind(allNodeIds) newEdges.forEach(({ source, target }) => { - unionFind.union(source, target); - }); + unionFind.union(source, target) + }) // 预设的颜色数组 const colors: string[] = [ - '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', + '#FF0000', + '#00FF00', + '#0000FF', + '#FFFF00', + '#FF00FF' // ... 根据需要添加更多颜色 - ]; - + ] // 使用 Union-Find 算法处理所有的边 tmpProjectConnectMap.forEach(({ source, target }) => { - unionFind.union(source, target); - }); + unionFind.union(source, target) + }) // 为每个连通分量分配颜色,并更新 nodes 数组 - const clusterToColor: Record = {}; - const categories: Record = {}; + const clusterToColor: Record = {} + const categories: Record = {} const newCom = [] - - newNodes.forEach(node => { - const root = unionFind.find(node.id); - categories[root] = categories[root] || []; - categories[root].push(node.id); + newNodes.forEach((node) => { + const root = unionFind.find(node.id) + categories[root] = categories[root] || [] + categories[root].push(node.id) if (!clusterToColor[root]) { // 分配颜色,确保同一连通分量的节点颜色相同 - clusterToColor[root] = colors[Math.max(0, Object.keys(clusterToColor).length) % colors.length]; + clusterToColor[root] = colors[Math.max(0, Object.keys(clusterToColor).length) % colors.length] } - node.cluster = root || 'none'; - node.comboId = `${root || 'none'}-combo`; + node.cluster = root || 'none' + node.comboId = `${root || 'none'}-combo` // node.color = clusterToColor[root]; - }); + }) - - let i:number = 0 - - for(const c in categories){ - const color = colorSets[i % colorSets.length]; + let i: number = 0 + + for (const c in categories) { + const color = colorSets[i % colorSets.length] const comboStyle = { stroke: color.mainStroke, fill: color.mainFill, - opacity: 0.8} - const comboId = `${c === 'undefined' ? 'none' : (c||'none') }-combo` - newCom.push(comboId === 'none-combo' ?{id:comboId,style:{ - fill:'transparent', - stroke:'transparent', - fillOpacity: 0, - strokeOpacity: 0, - active: { - // 设置激活状态下的透明度 - fill: 'transparent', - stroke: 'transparent', - fillOpacity: 0, - strokeOpacity: 0, - }, - inactive: { - fill: 'transparent', - stroke: 'transparent', - // 设置非激活状态下的透明度 - fillOpacity: 0, - strokeOpacity: 0, - }, - highlight: { - fill: 'transparent', - stroke: 'transparent', - // 设置高亮状态下的透明度 - fillOpacity: 0, - strokeOpacity: 0, - } - }}:{ - id:comboId, - style:comboStyle - }) - itemMap[comboId] = {style : { ...comboStyle }} - i++ + opacity: 0.8 + } + const comboId = `${c === 'undefined' ? 'none' : c || 'none'}-combo` + newCom.push( + comboId === 'none-combo' + ? { + id: comboId, + style: { + fill: 'transparent', + stroke: 'transparent', + fillOpacity: 0, + strokeOpacity: 0, + active: { + // 设置激活状态下的透明度 + fill: 'transparent', + stroke: 'transparent', + fillOpacity: 0, + strokeOpacity: 0 + }, + inactive: { + fill: 'transparent', + stroke: 'transparent', + // 设置非激活状态下的透明度 + fillOpacity: 0, + strokeOpacity: 0 + }, + highlight: { + fill: 'transparent', + stroke: 'transparent', + // 设置高亮状态下的透明度 + fillOpacity: 0, + strokeOpacity: 0 + } + } + } + : { + id: comboId, + style: comboStyle + } + ) + itemMap[comboId] = { style: { ...comboStyle } } + i++ } - - - newNodes.forEach(node => { - const parentCombo = itemMap[node.comboId]; - if(node.isApp){ + newNodes.forEach((node) => { + const parentCombo = itemMap[node.comboId] + if (node.isApp) { node.style = { stroke: '#ffa940', - fill: '#ffa94033', + fill: '#ffa94033' } - }else if (parentCombo) { + } else if (parentCombo) { node.style = { stroke: parentCombo.style.stroke, fill: parentCombo.style.fill } - } + } // node.color = clusterToColor[root]; - }); - + }) return { nodes: newNodes, edges: newEdges, - combos:newCom + combos: newCom } } - - - const getNodeData = ()=>{ - setLoading(true) - fetchData>('topology',{method:'GET',eoTransformKeys:['invoke_services','is_app','is_server']},).then(response=>{ - const {code,data,msg} = response - if(code === STATUS_CODE.SUCCESS){ - const newGraphData = relativeFormatter(data) - setGraphData(newGraphData) - setShowGraph(newGraphData?.nodes?.length > 0) - - }else{ - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }).finally(()=>setLoading(false)) - } - - const handleWindowResize = useCallback(debounce(() => { - if (graphContainerRef.current && graphRef.current && !graphRef.current?.get('destroyed')) { - graphRef.current.changeSize( - graphContainerRef.current.offsetWidth, - graphContainerRef.current.offsetHeight, - ); - graphRef.current?.fitCenter() - // graphRef.current?.fitView() + const getNodeData = () => { + setLoading(true) + fetchData>('topology', { + method: 'GET', + eoTransformKeys: ['invoke_services', 'is_app', 'is_server'] + }) + .then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + const newGraphData = relativeFormatter(data) + setGraphData(newGraphData) + setShowGraph(newGraphData?.nodes?.length > 0) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) } - }, 400), []); - - - /** - * @description 点击节点的回调 - */ - const clickNode = (item: NodeClickItem) => { - // console.log(item) - // router.navigate(['/', 'home', 'api-relative', item.id]) - } - - const updateSelected = ()=> { - if (!currentNode) return - // 设置节点状态 - graph?.setItemState(currentNode, 'selected', true) - } - - /** - * @description 更新缩放比例 - */ - const updateZoomTo = (increase:boolean) =>{ - const zoom:number = graph?.getZoom() || zoomNum - if((increase && zoom*10 >= 20 )||(!increase && zoom*10 <= 2)) return - setZoomNum(increase ?( zoom*10 + 2)/10 : (zoom*10 - 2)/10) - graph?.zoomTo(increase ? ( zoom*10 + 2)/10 : (zoom*10 - 2)/10) - } - - const initGraph = () => { - return new G6.Graph({ - container: ReactDOM.findDOMNode(graphContainerRef.current) as HTMLDivElement, - groupByTypes: false, - // plugins: [tooltip], - fitCenter:true, - // fitView:true, - layout: { - type: 'comboForce', - // 稳定系数,初始动画的加载时长(稳定性)=节点数量/稳定系数 - alphaDecay: 0.08, - // // 因为有分组的存在,整体布局需要往左偏移一点 - // // center: [(graphContainerRef.current?.scrollWidth || 300) / 2 - 150,( graphContainerRef.current?.scrollHeight || 0) / 2], - preventOverlap: true, - preventNodeOverlap:true, - preventComboOverlap:true, - // nodeCollideStrength:1, - // collideStrength:1, - comboCollideStrength:0.9, - nodeSize:24, - padding:[20,20,20,20], - // linkDistance: 30, - nodeStrength: -10, - edgeStrength: 0.1, - // nodeSpacing:40, - // comboSpacing:10, - comboPadding:30, - clustering:true, - clusterNodeStrength: 1000, - clusterEdgeDistance: 50, - clusterNodeSize: 100, - // clusterFociStrength: 1, - // charge: (d: nodeAny) => { - // return 100 - // }, - // linkStrength:()=>{ - // return 100 - // }, - // nodeSpacing: (d: nodeAny,v:nodeAny) => { - // console.log(d, v) - // if (d.comboId=== 'none-combo' || d.cluster==="none") { - // return 40 - // } - // return 50 - // }, - // onTick:()=>{console.log('ticking')}, - // onLayoutEnd:()=>{console.log('layout end')} - }, - modes: { - default: ['drag-combo','drag-canvas', 'drag-node', 'zoom-canvas','activate-relations' - ] - }, - defaultNode: { - size: [24, 24], - style: { - radius: 5, - stroke: '#69c0ff', - lineWidth: 1, - fillOpacity: 1 - }, - labelCfg: { - style: { - fontSize: RELATIVE_PICTURE_NODE_FONTSIZE, - fill: textColor - }, - position: 'bottom', - offset: 12 - } - }, - defaultEdge: { - // type: 'quadratic', - label: $t('调用服务'), - labelCfg: { - style: { - fill: '5B8FF9', - opacity: 0 - } - } - }, - defaultCombo: { - labelCfg: { - style: { - fill: '#666' - }, - }, - }, }) - } + .finally(() => setLoading(false)) + } - const updateEdgeLabel = (type: EdgeEvent, edge: Item) => { - - if (type === EdgeEvent.Mouseenter) { - // hover 边的时候出提示 - edge.update({ - labelCfg: { - style: { - opacity: 1, - fill: '#5B8FF9', - // @ts-expect-error g6 内部没定义好类型 - cursor: 'pointer', - } - } - }) - return + const handleWindowResize = useCallback( + debounce(() => { + if (graphContainerRef.current && graphRef.current && !graphRef.current?.get('destroyed')) { + graphRef.current.changeSize(graphContainerRef.current.offsetWidth, graphContainerRef.current.offsetHeight) + graphRef.current?.fitCenter() + // graphRef.current?.fitView() } - // 移出边时需要隐藏提示 + }, 400), + [] + ) + + /** + * @description 点击节点的回调 + */ + const clickNode = (item: NodeClickItem) => { + // console.log(item) + // router.navigate(['/', 'home', 'api-relative', item.id]) + } + + const updateSelected = () => { + if (!currentNode) return + // 设置节点状态 + graph?.setItemState(currentNode, 'selected', true) + } + + /** + * @description 更新缩放比例 + */ + const updateZoomTo = (increase: boolean) => { + const zoom: number = graph?.getZoom() || zoomNum + if ((increase && zoom * 10 >= 20) || (!increase && zoom * 10 <= 2)) return + setZoomNum(increase ? (zoom * 10 + 2) / 10 : (zoom * 10 - 2) / 10) + graph?.zoomTo(increase ? (zoom * 10 + 2) / 10 : (zoom * 10 - 2) / 10) + } + + const initGraph = () => { + return new G6.Graph({ + container: graphContainerRef.current as HTMLDivElement, + groupByTypes: false, + // plugins: [tooltip], + fitCenter: true, + // fitView:true, + layout: { + type: 'comboForce', + // 稳定系数,初始动画的加载时长(稳定性)=节点数量/稳定系数 + alphaDecay: 0.08, + // // 因为有分组的存在,整体布局需要往左偏移一点 + // // center: [(graphContainerRef.current?.scrollWidth || 300) / 2 - 150,( graphContainerRef.current?.scrollHeight || 0) / 2], + preventOverlap: true, + preventNodeOverlap: true, + preventComboOverlap: true, + // nodeCollideStrength:1, + // collideStrength:1, + comboCollideStrength: 0.9, + nodeSize: 24, + padding: [20, 20, 20, 20], + // linkDistance: 30, + nodeStrength: -10, + edgeStrength: 0.1, + // nodeSpacing:40, + // comboSpacing:10, + comboPadding: 30, + clustering: true, + clusterNodeStrength: 1000, + clusterEdgeDistance: 50, + clusterNodeSize: 100 + // clusterFociStrength: 1, + // charge: (d: nodeAny) => { + // return 100 + // }, + // linkStrength:()=>{ + // return 100 + // }, + // nodeSpacing: (d: nodeAny,v:nodeAny) => { + // console.log(d, v) + // if (d.comboId=== 'none-combo' || d.cluster==="none") { + // return 40 + // } + // return 50 + // }, + // onTick:()=>{console.log('ticking')}, + // onLayoutEnd:()=>{console.log('layout end')} + }, + modes: { + default: ['drag-combo', 'drag-canvas', 'drag-node', 'zoom-canvas', 'activate-relations'] + }, + defaultNode: { + size: [24, 24], + style: { + radius: 5, + stroke: '#69c0ff', + lineWidth: 1, + fillOpacity: 1 + }, + labelCfg: { + style: { + fontSize: RELATIVE_PICTURE_NODE_FONTSIZE, + fill: textColor + }, + position: 'bottom', + offset: 12 + } + }, + defaultEdge: { + // type: 'quadratic', + label: $t('调用服务'), + labelCfg: { + style: { + fill: '5B8FF9', + opacity: 0 + } + } + }, + defaultCombo: { + labelCfg: { + style: { + fill: '#666' + } + } + } + }) + } + + const updateEdgeLabel = (type: EdgeEvent, edge: Item) => { + if (type === EdgeEvent.Mouseenter) { + // hover 边的时候出提示 edge.update({ labelCfg: { style: { - opacity: 0, - // @ts-ignore g6 内部没定义好类型 + opacity: 1, + fill: '#5B8FF9', + // @ts-expect-error g6 内部没定义好类型 cursor: 'pointer' } } }) + return } + // 移出边时需要隐藏提示 + edge.update({ + labelCfg: { + style: { + opacity: 0, + // @ts-ignore g6 内部没定义好类型 + cursor: 'pointer' + } + } + }) + } - const refreshDragNodePosition = (e: unknown)=> { - const model = e.item.get('model') - model.fx = e.x - model.fy = e.y - } + const refreshDragNodePosition = (e: unknown) => { + const model = e.item.get('model') + model.fx = e.x + model.fy = e.y + } - const initGraphEvent = (graph:Graph, opts: { + const initGraphEvent = ( + graph: Graph, + opts: { onClickEdge?: (model: { target: string; source: string }) => void onClickNode?: (item: NodeClickItem) => void - }) => { - - graph.on('node:mouseenter', (e) => { - const node = e.item - if (!node) return - // hover 出文本 - const element = node.getKeyShape() - element.attr('cursor', 'pointer') - }) - - graph.on('node:mouseleave', (e) => { - const node = e.item - if (!node) return - const element = node.getKeyShape() - element.attr('cursor', 'default') - }) - - // 目前只找到这种性能较低的方法 - graph.edge((edge) => { - const sourceNode = graph.findById(edge.source as string) - const theme = sourceNode?._cfg?.model?.isSelfSpace ? SELF_SPACE_CONTENT_EDGE_COLOR : OUT_SPACE_CONTENT_EDGE_COLOR - return { - id: edge.id, - ...EDGE_STYLE, - style: { - stroke: theme, - endArrow: { - ...END_ARROW_STYLE, - fill: theme - } - } - } - }) - - graph.on('edge:mouseenter', (evt) => { - if(evt.item){ - graph.setItemState(evt.item, 'running', true) - } - const edge = evt.item - if (edge) { - updateEdgeLabel(EdgeEvent.Mouseenter, edge) - const model = edge.getModel() - const { endPoint,startPoint } = model - // y=endPoint.y - height / 2,在同一水平线上,x值=endPoint.x - width - 10 - const y = (endPoint.y + startPoint.y) /2 - const x = (endPoint.x + startPoint.x )/2 - const point = graph.getCanvasByPoint(x, y) - setEdgeTooltipX(point.x + 194) // 加上页面左侧导航菜单宽度 - setEdgeTooltipY(point.y + 50) // 加上页面顶部导航与按钮高度 - setShowEdgeTooltip(true) - setEdgeTooltipContent(model?._projectInfo) - } - }) - - graph.on('edge:mouseleave', (evt) => { - const { item } = evt - if (item) { - graph.clearItemStates(item, ['running']) - updateEdgeLabel(EdgeEvent.Mouseleave, item) - } - setEdgeTooltipContent(undefined) - setShowEdgeTooltip(false) - }) } + ) => { + graph.on('node:mouseenter', (e) => { + const node = e.item + if (!node) return + // hover 出文本 + const element = node.getKeyShape() + element.attr('cursor', 'pointer') + }) - const getGraph = ( - opts: { - onClickEdge?: (model: { target: string; source: string }) => void - onClickNode?: (item: NodeClickItem) => void - } - ) => { - const graph = initGraph() - graph.setMaxZoom(3) - graph.setMinZoom(0.2) - initGraphEvent(graph, opts) - return graph - } - - useEffect(()=>{ - if(topologyId !== undefined){ - setPictureType(PictureTypeEnum.Part) - setCurrentNode(topologyId) - return - } - setPictureType(PictureTypeEnum.Global) - },[topologyId]) + graph.on('node:mouseleave', (e) => { + const node = e.item + if (!node) return + const element = node.getKeyShape() + element.attr('cursor', 'default') + }) - useEffect(() => { - if (graphContainerRef.current) { - registerEdge('line-running',SYSTEM_TUNNING_CONFIG,'quadratic') - const graph = getGraph({ - onClickNode: (item: NodeClickItem) => { - clickNode(item) - }}) - - graph.on('beforelayout', async () => { - updateSelected() - }) - - setGraph(graph) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - graphRef.current = graph; - - // 添加窗口大小变化的监听器 - window.addEventListener('resize', handleWindowResize); - - // 组件卸载时清理资源 - return () => { - window.removeEventListener('resize', handleWindowResize); - if (graphRef.current) { - graphRef.current.destroy(); - } - }; - } - }, [handleWindowResize,showGraph,loading]); - - useEffect(()=>{ - if (!graph || !graphData || !showGraph || loading) return; - graph.clear() - graph.data(graphData) - - setTimeout(()=>{ - const { nodes } = graphData as GraphData - - if (nodes?.length) { - graph.updateLayout({ - nodeSpacing: getNodeSpacing(nodes.length,nodes), - comboSpacing: getNodeSpacing(nodes.length) - }) + // 目前只找到这种性能较低的方法 + graph.edge((edge) => { + const sourceNode = graph.findById(edge.source as string) + const theme = sourceNode?._cfg?.model?.isSelfSpace ? SELF_SPACE_CONTENT_EDGE_COLOR : OUT_SPACE_CONTENT_EDGE_COLOR + return { + id: edge.id, + ...EDGE_STYLE, + style: { + stroke: theme, + endArrow: { + ...END_ARROW_STYLE, + fill: theme } - - graph.render() - },200) - },[graph,graphData,showGraph,loading]) + } + } + }) + graph.on('edge:mouseenter', (evt) => { + if (evt.item) { + graph.setItemState(evt.item, 'running', true) + } + const edge = evt.item + if (edge) { + updateEdgeLabel(EdgeEvent.Mouseenter, edge) + const model = edge.getModel() + const { endPoint, startPoint } = model + // y=endPoint.y - height / 2,在同一水平线上,x值=endPoint.x - width - 10 + const y = (endPoint.y + startPoint.y) / 2 + const x = (endPoint.x + startPoint.x) / 2 + const point = graph.getCanvasByPoint(x, y) + setEdgeTooltipX(point.x + 194) // 加上页面左侧导航菜单宽度 + setEdgeTooltipY(point.y + 50) // 加上页面顶部导航与按钮高度 + setShowEdgeTooltip(true) + setEdgeTooltipContent(model?._projectInfo) + } + }) - useEffect(() => { - setBreadcrumb([{title:$t('系统拓扑图')}]) - getNodeData() - }, []); + graph.on('edge:mouseleave', (evt) => { + const { item } = evt + if (item) { + graph.clearItemStates(item, ['running']) + updateEdgeLabel(EdgeEvent.Mouseleave, item) + } + setEdgeTooltipContent(undefined) + setShowEdgeTooltip(false) + }) + } - return (<> - { - showGraph ? -
-
-
-
-
-
-
+ const getGraph = (opts: { + onClickEdge?: (model: { target: string; source: string }) => void + onClickNode?: (item: NodeClickItem) => void + }) => { + const graph = initGraph() + graph.setMaxZoom(3) + graph.setMinZoom(0.2) + initGraphEvent(graph, opts) + return graph + } + + useEffect(() => { + if (topologyId !== undefined) { + setPictureType(PictureTypeEnum.Part) + setCurrentNode(topologyId) + return + } + setPictureType(PictureTypeEnum.Global) + }, [topologyId]) + + useEffect(() => { + if (graphContainerRef.current) { + registerEdge('line-running', SYSTEM_TUNNING_CONFIG, 'quadratic') + const graph = getGraph({ + onClickNode: (item: NodeClickItem) => { + clickNode(item) + } + }) + + graph.on('beforelayout', async () => { + updateSelected() + }) + + setGraph(graph) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + ;(graphRef as any).current = graph + + // 添加窗口大小变化的监听器 + window.addEventListener('resize', handleWindowResize) + + // 组件卸载时清理资源 + return () => { + window.removeEventListener('resize', handleWindowResize) + if (graphRef.current) { + graphRef.current.destroy() + } + } + } + }, [handleWindowResize, showGraph, loading]) + + useEffect(() => { + if (!graph || !graphData || !showGraph || loading) return + graph.clear() + graph.data(graphData) + + setTimeout(() => { + const { nodes } = graphData as GraphData + + if (nodes?.length) { + graph.updateLayout({ + nodeSpacing: getNodeSpacing(nodes.length, nodes), + comboSpacing: getNodeSpacing(nodes.length) + }) + } + + graph.render() + }, 200) + }, [graph, graphData, showGraph, loading]) + + useEffect(() => { + setBreadcrumb([{ title: $t('系统拓扑图') }]) + getNodeData() + }, []) + + return ( + <> + {showGraph ? ( +
+
+
+
+
+
-
- - -
+
+
- :} spinning={loading}>{!loading && } - }) - } - - const EdgeToolTips = ({ x, y, content}) => { - return ( - //
- //
- //
- //
Edge
- //
- //
- { - content.map((x:TopologyServiceItem)=>{x?.name || ''}) - }
} - placement='bottomLeft' - color="#fff" - key="edge-tooltip" + ) : ( + } + spinning={loading} > -
- - ) - } - \ No newline at end of file + {!loading && } +
+ )} + + ) +} + +const EdgeToolTips = ({ x, y, content }) => { + return ( + //
+ //
+ //
+ //
Edge
+ //
+ //
+ + {content.map((x: TopologyServiceItem) => ( + {x?.name || ''} + ))} +
+ } + placement="bottomLeft" + color="#fff" + key="edge-tooltip" + > +
+ + ) +} diff --git a/frontend/packages/systemRunning/src/pages/SystemRunningInstruction.tsx b/frontend/packages/systemRunning/src/pages/SystemRunningInstruction.tsx index 383b716e..2ac07193 100644 --- a/frontend/packages/systemRunning/src/pages/SystemRunningInstruction.tsx +++ b/frontend/packages/systemRunning/src/pages/SystemRunningInstruction.tsx @@ -1,30 +1,36 @@ -import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"; -import { $t } from "@common/locales"; -import { useEffect } from "react"; -import { Link } from "react-router-dom"; +import { useBreadcrumb } from '@common/contexts/BreadcrumbContext' +import { $t } from '@common/locales' +import { useEffect } from 'react' +import { Link } from 'react-router-dom' export default function SystemRunningInstruction() { - const { setBreadcrumb } = useBreadcrumb() + const { setBreadcrumb } = useBreadcrumb() - useEffect(()=>{ - setBreadcrumb([ - {title:$t('系统拓扑图')} - ]) - },[]) - return ( -
-
-

系统配置并开启拓扑关联

-

系统拓扑功能辅助用户可视化了解系统结构,分析系统性能,规划系统部署,诊断系统故障。有助于提高系统可见性、可靠性和可维护性。

- {/*

更多配置及关联问题,请点击帮助中心 + useEffect(() => { + setBreadcrumb([{ title: $t('系统拓扑图') }]) + }, []) + return ( +

+
+

系统配置并开启拓扑关联

+

+ 系统拓扑功能辅助用户可视化了解系统结构,分析系统性能,规划系统部署,诊断系统故障。有助于提高系统可见性、可靠性和可维护性。 +

+ {/*

更多配置及关联问题,请点击帮助中心 {/* 查看更多 *

*/} -
-
-

服务设置

-

支持根据权限,拆分人员对 API 添加 、上游设置、鉴权设置等信息发布及管理;同时支持管理 API 调用服务(包含第三方调用)及订阅;

-

添加服务信息

-
-
-
- ) -} \ No newline at end of file +
+
+

服务设置

+

+ 支持根据权限,拆分人员对 API 添加 、上游设置、鉴权设置等信息发布及管理;同时支持管理 API + 调用服务(包含第三方调用)及订阅; +

+

+ 添加服务信息 +

+
+
+
+
+ ) +} diff --git a/frontend/prompt/Translate.md b/frontend/prompt/Translate.md new file mode 100644 index 00000000..72fb216d --- /dev/null +++ b/frontend/prompt/Translate.md @@ -0,0 +1,24 @@ +# Translation Workflow in Windsurf + +Follow these steps to manage translations in the project: + +1. **Scan for New Translations** + * Navigate to the `frontend` directory + * Run `pnpm run scan` to detect new translatable content + +2. **Locate New Translation Fields** + * Go to `packages/common/src/locales/scan/newJson` + * Find the language-specific JSON files (e.g., en-US.json, ja-JP.json) + * These files contain the new fields that need translation + +3. **Apply Translations** + * After translating the content, go to `packages/common/src/locales/scan` + * Open the corresponding language JSON file include ja-JP.json,en-US.json,zh-CH.json,zh-TW.json + * Paste the translated content into the appropriate file + +4. **Save and Apply** + * Save the file + * Changes will take effect immediately + * No additional build or restart is required + +Note: Available language files are en-US.json, ja-JP.json, zh-CN.json, and zh-TW.json. \ No newline at end of file diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index b6ee0067..93f95296 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,83 +1,78 @@ - /** @type {import('tailwindcss').Config} */ module.exports = { - important:true, - corePlugins:{ - preflight:false + important: true, + corePlugins: { + preflight: false, + }, + content: [`./packages/**/index.html`, `./packages/**/src/**/*.{js,ts,jsx,tsx}`], + theme: { + extend: { + width: { + INPUT_NORMAL: '100%', + // INPUT_NORMAL: '346px', + INPUT_LARGE: '508px', + GROUP: '240px', + SEARCH: '276px', + LOG: '254px', + }, + minHeight: { + TEXTAREA: '68px', + }, + borderRadius: { + DEFAULT: 'var(--border-radius)', + SEARCH_RADIUS: '50px', + }, + boxShadow: { + SCROLL: '0 2px 2px #0000000d', + SCROLL_TOP: ' 0 -2px 2px -2px var(--border-color)', + }, + colors: { + DISABLE_BG: 'var(--disabled-background-color)', + MAIN_TEXT: 'var(--text-color)', + MAIN_HOVER_TEXT: 'var(--text-hover-color)', + SECOND_TEXT: 'var(--disabled-text-color)', + MAIN_BG: 'var(--background-color)', + MENU_BG: 'var(--MENU-BG-COLOR)', + 'bar-theme': 'var(--bar-background-color)', + BORDER: 'var(--border-color)', + NAVBAR_BTN_BG: 'var(--item-active-background-color)', + MAIN_DISABLED_BG: 'var(--disabled-background-color)', + theme: 'var(--primary-color)', + DESC_TEXT: 'var(--TITLE_TEXT)', + HOVER_BG: 'var(--item-hover-background-color)', + guide_cluster: '#ee6760', + guide_upstream: '#f9a429', + guide_api: '#71d24d', + guide_publishApi: '#5884ff', + guide_final: '#915bf9', + table_text: 'var(--table-text-color)', + status_success: '#138913', + status_fail: '#ff3b30', + status_update: '#03a9f4', + status_pending: '#ffa500', + status_offline: '#8f8e93', + A_HOVER: 'var(--button-primary-hover-background-color)', + }, + spacing: { + mbase: 'var(--FORM_SPAN)', + label: '12px', // 选择器和label之间的间距,待删 + btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距 + btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距 + btnrbase: '20px', // 页面最右侧边距20px + formtop: 'var(--FORM_SPAN)', + icon: '5px', + blockbase: '40px', + DEFAULT_BORDER_RADIUS: 'var(--border-radius)', + TREE_TITLE: 'var(--small-padding) var(--LAYOUT_PADDING);', + }, + borderColor: { + 'color-base': 'var(--border-color)', + }, }, - content: [ - `./packages/**/index.html`, - `./packages/**/src/**/*.{js,ts,jsx,tsx}`,], - theme: { - extend: { - width: { - INPUT_NORMAL: '100%', - // INPUT_NORMAL: '346px', - INPUT_LARGE: '508px', - GROUP: '240px', - SEARCH: '276px', - LOG: '254px' - }, - minHeight:{ - TEXTAREA:'68px' - }, - borderRadius: { - DEFAULT: 'var(--border-radius)', - SEARCH_RADIUS: '50px' - }, - boxShadow:{ - SCROLL: '0 2px 2px #0000000d', - SCROLL_TOP:' 0 -2px 2px -2px var(--border-color)' - }, - colors: { - DISABLE_BG: 'var(--disabled-background-color)', - MAIN_TEXT: 'var(--text-color)', - MAIN_HOVER_TEXT: 'var(--text-hover-color)', - SECOND_TEXT:'var(--disabled-text-color)', - MAIN_BG: 'var(--background-color)', - MENU_BG:'var(--MENU-BG-COLOR)', - 'bar-theme': 'var(--bar-background-color)', - BORDER: 'var(--border-color)', - NAVBAR_BTN_BG: 'var(--item-active-background-color)', - MAIN_DISABLED_BG: 'var(--disabled-background-color)', - theme: 'var(--primary-color)', - DESC_TEXT: 'var(--TITLE_TEXT)', - HOVER_BG: 'var(--item-hover-background-color)', - guide_cluster: '#ee6760', - guide_upstream: '#f9a429', - guide_api: '#71d24d', - guide_publishApi: '#5884ff', - guide_final: '#915bf9', - table_text: 'var(--table-text-color)', - status_success:'#138913', - status_fail:"#ff3b30", - status_update:"#03a9f4", - status_pending:"#ffa500", - status_offline:"#8f8e93", - A_HOVER:'var(--button-primary-hover-background-color)' - }, - spacing: { - mbase: 'var(--FORM_SPAN)', - label: '12px', // 选择器和label之间的间距,待删 - btnbase: 'var(--LAYOUT_MARGIN)', // x方向的间距 - btnybase: 'var(--LAYOUT_MARGIN)', // y轴方向的间距 - btnrbase: '20px', // 页面最右侧边距20px - formtop: 'var(--FORM_SPAN)', - icon: '5px', - blockbase: '40px', - DEFAULT_BORDER_RADIUS: 'var(--border-radius)', - TREE_TITLE:'var(--small-padding) var(--LAYOUT_PADDING);' - }, - borderColor: { - 'color-base': 'var(--border-color)' - } - } - }, - plugins: [], - corePlugins: { - preflight: false, - }, - } - - \ No newline at end of file + }, + plugins: [], + corePlugins: { + preflight: false, + }, +};