Compare commits

..

35 Commits

Author SHA1 Message Date
ningyv c33b070509 Merge branch 'feature/v1.3/mj' of github.com:APIParkLab/APIPark into feature/v1.3/mj 2024-11-29 18:39:20 +08:00
ningyv de12d5686c feature: dataMask logs dialog 2024-11-29 18:39:13 +08:00
杨梦洁 28bef97faa fix: Dashboard Table Height 2024-11-29 10:35:32 +08:00
杨梦洁 a37fe1d794 fix: get service remote option 2024-11-28 18:38:12 +08:00
杨梦洁 6646bb1e56 fix: service data-masking layout 2024-11-28 18:14:02 +08:00
杨梦洁 4bf8db4898 fix: Modify Context Arct 2024-11-28 16:59:51 +08:00
杨梦洁 14e17ccf2c fix: add translation 2024-11-28 13:47:31 +08:00
杨梦洁 8c166dae9b fix: role list scroll bug and add translation 2024-11-28 10:40:43 +08:00
杨梦洁 96bd1cf9f6 fix: data-masking and integration bugs 2024-11-27 19:20:47 +08:00
杨梦洁 febb64b8bb fix: data-masking bugs 2024-11-27 18:21:43 +08:00
杨梦洁 ad45ab2e82 fix: Change Dashboard Fields 2024-11-21 18:37:39 +08:00
杨梦洁 3a57c609f7 fix: Change Vite Config 2024-11-21 16:42:32 +08:00
杨梦洁 3fa02ec65c fix: Change File Name 2024-11-21 16:32:07 +08:00
杨梦洁 a70ecea02b fix: Change File Name 2024-11-21 16:25:18 +08:00
杨梦洁 8e68eb35f3 fix: Change file name 2024-11-21 16:20:52 +08:00
杨梦洁 2893331ff5 Merge branch 'main' into feature/v1.3/mj 2024-11-21 16:12:16 +08:00
杨梦洁 dce9a7addb feat: Complete static pages for Phase 1 of V1.3 2024-11-21 16:08:08 +08:00
Dot.L b5ad739b93 Merge pull request #134 from PeterDaveHelloKitchen/zh-TW
Improve zh-TW Traditional Chinese locale
2024-11-15 14:19:39 +08:00
杨梦洁 0b7f0405d5 feat: Merge MF with data-masking list 2024-11-15 13:42:30 +08:00
杨梦洁 522489c9e9 feat: plugin system 2024-11-14 19:09:46 +08:00
Peter Dave Hello a9eb2a790f Improve zh-TW Traditional Chinese locale 2024-11-14 05:49:34 +08:00
maggieyyy a092ed1108 fix: Move plugin code to common 2024-11-06 14:26:38 +08:00
maggieyyy 503515281d Merge branch 'main' into feature/mf 2024-11-06 11:33:21 +08:00
maggieyyy 2326d4dfb5 feat: plugin system 2024-11-05 16:20:40 +08:00
maggieyyy 2b874fe59f fix: Modify execute list 2024-11-01 18:17:16 +08:00
maggieyyy 1aa3f2fb05 fix: plugin config 2024-11-01 13:39:52 +08:00
maggieyyy ad6b64ca74 fix: Modify config 2024-10-31 13:59:06 +08:00
maggieyyy d27a2b8cf3 Merge branch 'main' into feature/mf 2024-10-31 09:27:08 +08:00
maggieyyy bcb68d552f fix: Modify mock plugin config 2024-10-30 17:52:41 +08:00
maggieyyy 3de87723ae fix: Modify mock plugin config 2024-10-30 17:51:10 +08:00
maggieyyy 535d70ac5a merge 2024-10-30 14:24:16 +08:00
maggieyyy bf2aefe2da fix: route config 2024-10-22 14:05:12 +08:00
maggieyyy 540a31f237 fix: aiservice router 2024-10-22 10:20:34 +08:00
maggieyyy 0505045c81 fix: router config 2024-10-21 17:41:30 +08:00
maggieyyy 1a18d79d94 fix: Add MF 2024-10-21 14:41:49 +08:00
120 changed files with 5017 additions and 2821 deletions
+9
View File
@@ -0,0 +1,9 @@
{
"cSpell.words": [
"Antd",
"apinto",
"Apipark",
"logsettings",
"resourcesettings"
]
}
-17
View File
@@ -1,17 +0,0 @@
# 部署
## 代码同步
packages目录下,部分子项目为企业版独有,不要同步到开源版:
packages/businessEntry, packages/openApi, packages/systemRunning, README.pro.md
## 安装依赖
建议使用pnpm
`npm install -g pnpm`
使用pnpm安装依赖
`pnpm install`
## 编译
### 开源版本
`pnpm run build`
### 企业版本
`pnpm run build:pro`
-2
View File
@@ -9,11 +9,9 @@
"scripts": {
"test": "jest",
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
"build:pro": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=business-entry --stream --verbose ",
"serve": "lerna run preview --parallel",
"serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream",
"dev:pro": "lerna run dev --scope=business-entry --stream",
"stop": "kill-port --port 5000",
"scan": "i18next-scanner --config i18next-scanner.config.js"
},
-5
View File
@@ -1,5 +0,0 @@
// .env.pro
VITE_APP_MODE=pro
VITE_APP_TITLE=My Production App
VITE_API_BASE_URL=https://api.production.example.com
@@ -1,18 +0,0 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs','public','code-snippet','ace-editor'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
@@ -1,25 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
public/tinymce
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
-11
View File
@@ -1,11 +0,0 @@
# `businessEntry`
> TODO: description
## Usage
```
const businessEntry = require('businessEntry');
// TODO: DEMONSTRATE API
```
@@ -1,7 +0,0 @@
'use strict';
const businessEntry = require('..');
const assert = require('assert').strict;
assert.strictEqual(businessEntry(), 'Hello from businessEntry');
console.info('businessEntry tests passed');
@@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/frontend/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2APIPark</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script src="/frontend/iconpark_eolink.js"></script>
<script src="/frontend/iconpark_apinto.js"></script>
</body>
</html>
@@ -1,17 +0,0 @@
{
"name": "business-entry",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": " vite --port 5000 --strictPort",
"build": "vite build ",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview --port 5000 --strictPort",
"serve": "vite preview --port 5000 --strictPort"
},
"dependencies": {
},
"devDependencies": {
}
}
@@ -1,9 +0,0 @@
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
},
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-158
View File
@@ -1,158 +0,0 @@
import '@core/App.css'
import { ConfigProvider } from 'antd';
import RenderRoutes from '@businessEntry/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 ThemeSwitcher from '@common/components/aoplatform/ThemeSwitcher'
const antdComponentThemeToken = {
token: {
// Seed Token,影响范围大
colorPrimary: '#3D46F2',
colorLink:'#3D46F2',
colorBorder:'#ededed',
colorText:'#333',
borderRadius: 4,
// 派生变量,影响范围小
colorBgContainer: '#fff',
colorPrimaryBg:'#EBEEF2',
colorTextQuaternary:'#BBB',
colorTextTertiary:'#999'
},
components:{
// 派生变量,影响范围小
Input:{
activeShadow:'none'
},
Select:{
activeShadow:'none'
},
Checkbox:{
activeShadow:'none'
},
Cascader:{
activeShadow:'none',
optionSelectedBg:'#EBEEF2',
optionHoverBg:'#EBEEF2'
},
Layout: {
bodyBg: '#17163E',
headerBg: 'transparent',
headerColor: '#333',
headerPadding: '10 20px',
lightSiderBg: 'transparent',
siderBg: 'transparent',
},
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'
},
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'
// itemHeight:'72px',
// darkItemBg:'transparent',
// itemBg:'transparent',
// itemSelectedBg:'transparent',
// darkItemSelectedBg:'transparent',
// subMenuItemBg:'transparent',
// itemActiveBg:'transparent',
// darkSubMenuItemBg:'transparent',
// activeBarHeight:'2px',
// activeBarBorderWidth:2
},
List:{
itemPadding:'8px 0'
},
Form:{
itemMarginBottom:10,
},
Alert:{
defaultPadding:'12px 16px'
},
Tag:{
defaultBg:"#f7f8fa"
},
}
}
function App() {
useInitializeMonaco()
return (
<StyleProvider hashPriority={"high"}>
<ConfigProvider
locale={zhCN}
wave={{disabled:true}}
theme={antdComponentThemeToken}>
<ThemeSwitcher />
<BreadcrumbProvider>
<RenderRoutes />
</BreadcrumbProvider>
</ConfigProvider>
</StyleProvider>
);
}
export default App
@@ -1,484 +0,0 @@
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 ApprovalPage from "@core/pages/approval/ApprovalPage.tsx";
import {SystemProvider} from "@core/contexts/SystemContext.tsx";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import {FC,lazy} from 'react';
import { TeamProvider } from '@core/contexts/TeamContext.tsx';
import SystemOutlet from '@core/pages/system/SystemOutlet.tsx';
import { DashboardProvider } from '@core/contexts/DashboardContext.tsx';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
type RouteConfig = {
path:string
component?:ReactElement
children?:(RouteConfig|false)[]
key:string
provider?:FC<{ children: ReactNode; }>
lazy?:unknown
}
const APP_MODE = import.meta.env.VITE_APP_MODE;
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
}
const PUBLIC_ROUTES:RouteConfig[] = [
{
path:'/',
component:<Login/>,
key: uuidv4(),
},
{
path:'/login',
component:<Login/>,
key: uuidv4()
},
{
path:'/',
component:<ProtectedRoute/>,
key: uuidv4(),
children:[
{
path:'approval/*',
component:<ApprovalPage />,
key:uuidv4()
},
{
path:'team',
component:<Outlet/>,
key: uuidv4(),
provider: TeamProvider,
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
},
{
path:'inside/:teamId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
key: uuidv4(),
children:[
{
path:'member',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
},
]
}
]
},
{
path:'service',
component:<SystemOutlet />,
key: uuidv4(),
provider: SystemProvider,
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:'list/:teamId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:':teamId',
component:<Outlet/>,
key: uuidv4(),
children:[
{
path:'inside/:serviceId',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children:[
{
path:'api',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path:'router',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')),
},
{
path:'upstream',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
},
{
path:'document',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
},
{
path:'subscriber',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
}
]
},
{
path:'topology',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemTopology.tsx')),
key: uuidv4(),
children:[
]
},
{
path:'publish',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children:[
{
path:'',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
},
{
path:'*',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
}
]
},
{
path:'setting',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
]
}
]
}
]
},{
path:'datasourcing',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')),
},
{
path:'cluster',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
},
{
path:'cert',
key: uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
},
{
path:'serviceHub',
component:<Outlet />,
key:uuidv4(),
children:[
{
path:'',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
},
{
path:'detail/:serviceId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
}]
},
{
path:'commonsetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
key:uuidv4(),
},
{
path:'consumer',
component:<Outlet />,
provider:TenantManagementProvider,
key:uuidv4(),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:':teamId/inside/:appId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
children:[
{
path:'service',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
},
{
path:'authorization',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
},
{
path:'setting',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
},
]
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
{
path:'list/:teamId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
]
},
{
path:'member',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
children:[
{
path:'',
key:uuidv4(),
component:<Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
},
{
path:'list/:memberGroupId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
}
]
},
{
path:'role',
key:uuidv4(),
component:<Outlet></Outlet>,
children:[
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path:'list',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
},
{
path:':roleType/config',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
},
{
path:':roleType/config/:roleId',
key:uuidv4(),
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}
]
},
APP_MODE === 'pro' &&{
path:'openapi',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@openApi/pages/OpenApiList.tsx')),
key:uuidv4(),
},
{
path:'assets',
component:<p></p>,
key:uuidv4()
},
APP_MODE === 'pro' &&{
path:'analytics',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key:uuidv4(),
children:[
{
path:':dashboardType',
component:<Outlet/>,
key:uuidv4(),
provider:DashboardProvider,
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardList.tsx')),
key:uuidv4()
},
{
path:'detail/:dashboardDetailId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardDetail.tsx')),
key:uuidv4()
},
]
},
]
},
{
path:'systemrunning',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@systemRunning/pages/SystemRunning.tsx')),
key:uuidv4()
},
{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '../../../../common/src/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
},
{
path:'logsettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
APP_MODE ==='pro' && {
path:'resourcesettings/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
key: uuidv4(),
children:[{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:uuidv4()
}]
},
{
path:'userProfile/*',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/UserProfile.tsx')),
key:uuidv4(),
children:[{
path:'changepsw',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/ChangePsw.tsx')),
key:uuidv4()
}]
}
]
},
]
const RenderRoutes = ()=> {
return (
<App className="h-full" message={{ maxCount: 1 }}>
<Router>
<Routes>
{generateRoutes(PUBLIC_ROUTES)}
</Routes>
</Router>
</App>
)
}
const generateRoutes = (routerConfig: RouteConfig[]) => {
return routerConfig?.map((route: RouteConfig) => {
let routeElement;
if (route.lazy) {
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
routeElement = (
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <LazyComponent />)
) : (
<LazyComponent />
)}
</Suspense>
);
} else {
routeElement = route.provider ? (
createElement(route.provider, {}, route.component)
) : (
route.component
);
}
return (
<Route
key={route.key}
path={route.path}
element={routeElement}
>
{route.children && generateRoutes(route.children as RouteConfig[])}
</Route>
);
}
)
}
// 保护的路由组件
function ProtectedRoute() {
const {state} = useGlobalContext()
return state.isAuthenticated? <BasicLayout project="core" /> : <Navigate to="/login" />;
}
export default RenderRoutes
@@ -1,27 +0,0 @@
import {StrictMode} from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import '@core/index.css'
import {GlobalProvider} from "@common/contexts/GlobalStateContext.tsx";
async function initializeApp() {
try {
// 初始化行为
// await fetchInitialConfig(); // 示例:获取初始配置
// 异步操作完成后,渲染React应用
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalProvider>
<App />
</GlobalProvider>
</StrictMode>,
);
} catch (error) {
console.error('Initialization failed:', error);
// 处理初始化失败的情况,比如渲染一个错误界面
}
}
// 执行初始化
initializeApp();
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
@@ -1,17 +0,0 @@
// start-vite.js// start-vite.js
import { exec } from 'child_process';
const viteProcess = exec('pnpm run build');
viteProcess.stdout.on('data', (data) => {
console.log(data.toString());
});
viteProcess.stderr.on('data', (data) => {
console.error(data.toString());
});
viteProcess.on('close', (code) => {
console.log(`Vite process exited with code ${code}`);
});
@@ -1,33 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
"paths": {
"@core/*": ["../core/src/*"],
"@common/*": ["../common/src/*"],
"@market/*": ["../market/src/*"],
"@dashboard/*": ["../dashboard/src/*"],
"@openApi/*": ["../openApi/src/*"],
"@systemRunning/*": ["../systemRunning/src/*"],
"@businessEntry/*": ["./src/*"],
},
},
"include": ["src", "public/iconpark_eolink.js", "public/iconpark_apinto.js", "../common/src/component/aoplatform/EditableTableWithModal.tsx", "../common/src/components/aoplatform/TreeWithMore.tsx", "../common/src/components/aoplatform/DatePicker.tsx", "../common/src/components/aoplatform/TimeRangeSelector.tsx", "../common/src/components/aoplatform/TimePicker.tsx", "../common/src/components/aoplatform/MemberTransfer.tsx", "../common/src/components/aoplatform/Navigation.tsx", "../common/src/components/aoplatform/PageList.tsx", "../common/src/components/aoplatform/ErrorBoundary.tsx", "../common/src/components/aoplatform/ScrollableSection.tsx", "../common/src/utils/postcat.tsx", "../common/src/utils/curl.ts", "../common/src/components/aoplatform/ResetPsw.tsx", "../common/src/components/aoplatform/SubscribeApprovalModalContent.tsx", "src/components/aoplatform/RenderRoutes.tsx", "../common/src/components/aoplatform/PublishApprovalModalContent.tsx", "../common/src/components/aoplatform/InsidePage.tsx", "../common/src/const/type.ts", "../common/src/components/aoplatform/intelligent-plugin", "../common/src/const/domain"],
"references": [{ "path": "./tsconfig.node.json" }]
}
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
@@ -1,80 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
export default defineConfig({
cacheDir: './node_modules/.vite',
build:{
outDir:'../../dist',
sourcemap: false,
chunkSizeWarningLimit: 50000,
cacheDir: './node_modules/.vite',
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
// 针对 pnpm 和 Monorepo 特殊处理
if (id.includes('.pnpm')) {
const segments = id.split(path.sep);
const packageName = segments[segments.indexOf('.pnpm') + 1].split('@')[0];
return packageName;
}
}
},
},
css: {
postcss: {
plugins: [
tailwindcss(path.resolve(__dirname, '../common/tailwind.config.js')),
autoprefixer
],
},
preprocessorOptions: {
less: {
javascriptEnabled: true,
},
},
modules:{
localsConvention:"camelCase",
generateScopedName:"[local]_[hash:base64:2]"
}
},
plugins: [react(),
dynamicImportVars({
include:["src"],
exclude:[],
warnOnError:false
}),
],
resolve: {
alias: [
{ find: /^~/, replacement: '' },
{ find: '@common', replacement: path.resolve(__dirname, '../common/src') },
{ find: '@market', replacement: path.resolve(__dirname, '../market/src') },
{ find: '@core', replacement: path.resolve(__dirname, '../core/src') },
{ find: '@dashboard', replacement: path.resolve(__dirname, '../dashboard/src') },
{ find: '@openApi', replacement: path.resolve(__dirname, '../openApi/src') },
{ find: '@systemRunning', replacement: path.resolve(__dirname, '../systemRunning/src') },
{ find: '@businessEntry', replacement: path.resolve(__dirname, './src') },
]
},
server: {
proxy: {
'/api/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
},
'/api2/v1': {
// target: 'http://uat.apikit.com:11204/mockApi/aoplatform/',
target: 'http://172.18.166.219:8288/',
changeOrigin: true,
}
}
},
logLevel:'info'
})
File diff suppressed because one or more lines are too long
@@ -8,18 +8,19 @@ import {
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import Logo from '@common/assets/layout-logo.png';
import AvatarPic from '@common/assets/default-avatar.png'
import { useEffect, useMemo, useState } from "react";
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 } from '@common/utils/navigation';
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';
const APP_MODE = import.meta.env.VITE_APP_MODE;
export type MenuItem = Required<MenuProps>['items'][number];
@@ -35,48 +36,21 @@ const themeToken = {
}
}
function BasicLayout({ project = 'core' }: { project: string }) {
const navigator = useNavigate()
const location = useLocation()
const currentUrl = location.pathname
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData } = useGlobalContext()
const [pathname, setPathname] = useState(currentUrl); const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
const TOTAL_MENU_ITEMS: MenuProps['items'] = useMemo(() => [
getNavItem($t('工作空间'), 'workspace', '/guide/page', <Icon icon="ic:baseline-space-dashboard" width="18" height="18" />, [
getNavItem(<a>{$t('首页')}</a>, 'guide', '/guide/page', <Icon icon="ic:baseline-home" width="18" height="18" />, undefined, undefined, 'all'),
getNavItem(<a>{$t('服务')}</a>, 'service', '/service', <Icon icon="ic:baseline-blinds-closed" width="18" height="18" />, undefined, undefined, 'all'),
getNavItem(<a>{$t('消费者')}</a>, 'consumer', '/consumer', <Icon icon="ic:baseline-apps" width="18" height="18" />, undefined, undefined, 'all'),
getNavItem(<a>{$t('团队')}</a>, 'team', '/team', <Icon icon="ic:baseline-people-alt" width="18" height="18" />, undefined, undefined, 'all'),
]),
getNavItem($t('API 市场'), 'serviceHub', '/serviceHub', <Icon icon="ic:baseline-hub" width="18" height="18" />, undefined, undefined, 'system.api_portal.api_portal.view'),
getNavItem($t('仪表盘'), 'mainPage', APP_MODE === 'pro' ? '/analytics' : '/analytics/total', <Icon icon="ic:baseline-bar-chart" width="18" height="18" />, [
getNavItem(<a >{$t('运行视图')}</a>, 'analytics', APP_MODE === 'pro' ? '/analytics' : '/analytics/total', <ProjectFilled />, undefined, undefined, 'system.analysis.run_view.view'),
APP_MODE === 'pro' ? getNavItem(<a >{$t('系统拓扑图')}</a>, 'systemrunning', '/systemrunning', <ProjectFilled />, undefined, undefined, 'system.dashboard.systemrunning.view') : null,
], undefined, 'system.analysis.run_view.view'),
getNavItem($t('系统设置'), 'operationCenter', '/commonsetting', <Icon icon="ic:baseline-settings" width="18" height="18" />, [
getNavItem($t('系统'), 'serviceHubSetting', '/commonsetting', null, [
getNavItem(<a>{$t('常规')}</a>, 'commonsetting', '/commonsetting', <Icon icon="ic:baseline-hub" width="18" height="18" />, undefined, undefined, 'system.api_market.service_classification.view'),
getNavItem(<a>{$t('API 网关')}</a>, 'cluster', '/cluster', <Icon icon="ic:baseline-device-hub" width="18" height="18" />, undefined, undefined, 'system.settings.api_gateway.view'),
getNavItem(<a>{$t('AI 模型')}</a>, 'aisetting', '/aisetting', <Icon icon="hugeicons:ai-network" width="18" height="18" />, undefined, undefined, 'system.settings.api_gateway.view'),
], undefined, 'system.api_market.service_classification.view'),
getNavItem($t('用户'), 'organization', '/member', null, [
getNavItem(<a>{$t('账号')}</a>, 'member', '/member', <Icon icon="ic:baseline-people-alt" width="18" height="18" />, undefined, undefined, 'system.settings.account.view'),
getNavItem(<a>{$t('角色')}</a>, 'role', '/role', <Icon icon="ic:baseline-verified-user" width="18" height="18" />, undefined, undefined, 'system.organization.role.view'),
], undefined, ''),
getNavItem($t('集成'), 'maintenanceCenter', '/datasourcing', null, [
getNavItem(<a>{$t('数据源')}</a>, 'datasourcing', '/datasourcing', <Icon icon="ic:baseline-monitor-heart" width="18" height="18" />, undefined, undefined, 'system.settings.data_source.view'),
getNavItem(<a>{$t('全局策略')}</a>, 'globalPolicy', '/globalPolicy', <Icon icon="uil:comment-shield" width="18" height="18" />, undefined, undefined, 'system.settings.data_source.view'),
getNavItem(<a>{$t('证书')}</a>, 'cert', '/cert', <Icon icon="ic:baseline-security" width="18" height="18" />, undefined, undefined, 'system.settings.ssl_certificate.view'),
getNavItem(<a>{$t('日志')}</a>, 'logsettings', '/logsettings', <Icon icon="ic:baseline-sticky-note-2" width="18" height="18" />, undefined, undefined, 'system.settings.log_configuration.view'),
APP_MODE === 'pro' ? getNavItem(<a>{$t('资源')}</a>, 'resourcesettings', '/resourcesettings', null, undefined, undefined, 'system.partition.self.view') : null,
APP_MODE === 'pro' ? getNavItem(<a>{$t('Open API')}</a>, 'openapi', '/openapi', null, undefined, undefined, 'system.openapi.self.view') : null,
]),
]),
], [state.language, accessInit])
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<MenuProps['items']>();
const pluginSlotHub = usePluginSlotHub()
useEffect(()=>{
const newMenu = transformMenuData(menuList)
setMenuItems(newMenu);
},[menuList, state.language,accessInit])
useEffect(() => {
if (currentUrl === '/') {
@@ -101,32 +75,28 @@ function BasicLayout({ project = 'core' }: { project: string }) {
if (filteredRoutes.length === 0) {
return false
}
return { ...item, routes: filteredRoutes };
return { ...item,routes: filteredRoutes,name:$t(item.name) };
}
// 处理没有 routes 的菜单项
if (item.access) {
return (item.access === 'all' || hasAccess(item.access)) ? item : null;
return (item.access === 'all' || hasAccess(item.access)) ? {...item,name:$t(item.name)} : null;
}
// 如果没有 access 和 routes,则保留
return item;
})
.filter(x => x); // 过滤掉处理后为 null 的项
};
// 初始过滤操作
const res = [...TOTAL_MENU_ITEMS]!.filter(x => x).map((x: any) => (x.routes ? { ...x, routes: filterMenu(x.routes) } : x));
// 返回处理后的数据
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]);
const { message } = App.useApp()
const [userInfo, setUserInfo] = useState<UserInfoType>()
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<UserInfoType>()
const {fetchData} = useFetch()
const navigate = useNavigate();
const getUserInfo = () => {
fetchData<BasicResponse<{ profile: UserInfoType }>>('account/profile', { method: 'GET' })
@@ -178,110 +148,115 @@ function BasicLayout({ project = 'core' }: { project: string }) {
].filter(Boolean), [userInfo]);
const actionRender =useMemo( ()=>{
return [
<LanguageSetting />,
<Button className=" text-[#ffffffb3] hover:text-[#fff] border-none" type="default" ghost onClick={()=>{window.open('https://docs.apipark.com','_blank')}}>
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-help" width="14" height="14"/>{$t('文档')}</span>
</Button> ,
...((pluginSlotHub.getSlot('basicLayoutAfterBtns') as unknown[] )||[] )
]
},[pluginSlotHub.getSlot('basicLayoutAfterBtns') ])
return (
<div
id="test-pro-layout"
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
return(
<div
id="test-pro-layout"
style={{
height: '100vh',
overflow: 'auto',
}}
>
<ProLayout
prefixCls="apipark-layout"
location={{
pathname,
}}
siderWidth={220}
breakpoint={'lg'}
route={headerMenuData}
token={themeToken}
siderMenuType="group"
menu={{
type: 'group',
collapsedShowGroupTitle: true,
}}
disableMobile={true}
avatarProps={{
src: AvatarPic || userInfo?.avatar,
size: 'small',
title: userInfo?.username || 'unknown',
render: (props, dom) => {
return (
<Dropdown
menu={{
items
<ProConfigProvider hashed={false}>
<ConfigProvider
getTargetContainer={() => {
return document.getElementById('test-pro-layout') || document.body;
}}
>
<div className='avatar-dom'>{dom}
>
<ProLayout
prefixCls="apipark-layout"
location={{
pathname,
}}
siderWidth={220}
breakpoint={'lg'}
route={headerMenuData}
token={themeToken}
siderMenuType="group"
menu={{
type: 'group',
collapsedShowGroupTitle: true,
}}
disableMobile={true}
avatarProps={{
src: AvatarPic || userInfo?.avatar,
size: 'small',
title: userInfo?.username||'unknown',
render: (props, dom) => {
return (
<Dropdown
menu={{
items
}}
>
<div className='avatar-dom'>{dom}
</div>
</Dropdown>
);
},
}}
actionsRender={(props) => {
if (props.isMobile) return [];
if (typeof window === 'undefined') return [];
return actionRender;
}}
headerTitleRender={() => (
<div className="w-[192px] flex items-center">
<img
className="h-[20px] cursor-pointer "
src={Logo}
onClick={()=> navigator(mainPage)}
/>
</div>
)}
logo={Logo}
pageTitleRender={()=>$t('APIPark')}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
// 同级目录点击无效
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}
</div>
)}
fixSiderbar={true}
layout='mix'
splitMenus={true}
collapsed={false}
collapsedButtonRender={false}
>
<div className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden' }`}>
<Outlet />
</div>
</Dropdown>
);
},
}}
actionsRender={(props) => {
if (props.isMobile) return [];
if (typeof window === 'undefined') return [];
return [
<LanguageSetting />,
<Button className=" text-[#ffffffb3] hover:text-[#fff] border-none" type="default" ghost onClick={() => { window.open('https://docs.apipark.com', '_blank') }}>
<span className='flex items-center gap-[8px]'> <Icon icon="ic:baseline-help" width="14" height="14" />{$t('文档')}</span>
</Button>
];
}}
headerTitleRender={() => (
<div className="w-[192px] flex items-center">
<img
className="h-[20px] cursor-pointer "
src={Logo}
onClick={() => navigator(mainPage)}
/>
</div>
)}
logo={Logo}
pageTitleRender={() => $t('APIPark')}
menuFooterRender={(props) => {
if (props?.collapsed) return undefined;
}}
menuItemRender={(item, dom) => (
<div
onClick={() => {
// 同级目录点击无效
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}
</div>
)}
fixSiderbar={true}
layout='mix'
splitMenus={true}
collapsed={false}
collapsedButtonRender={false}
>
<div className={`w-full h-calc-100vh-minus-navbar pl-PAGE_INSIDE_X pt-PAGE_INSIDE_T ${currentUrl.startsWith('/role/list') ? 'overflow-auto' : 'overflow-hidden'}`}>
<Outlet />
</div>
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
)
</ProLayout>
</ConfigProvider>
</ProConfigProvider>
</div>
)
}
export default BasicLayout
@@ -37,13 +37,13 @@ const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showB
// <div className="h-full flex flex-col flex-1 overflow-hidden bg-[#f7f8fa]">
<div className={`h-full flex flex-col flex-1 overflow-hidden ${className}`}>
{ showBanner && <div className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-b-[1px] border-solid border-BORDER' : ''} ${headerClassName}`}>
<div className="mb-[30px]">
{!pageTitle && !description && !backUrl &&!customBtn ? <></>: <div className="mb-[30px]">
{backUrl &&<div className="text-[18px] leading-[25px] mb-[12px]">
<Button type="text" onClick={goBack}><ArrowLeftOutlined className="max-h-[14px]" />{$t('返回')}</Button>
</div>}
<div className="flex justify-between mb-[20px] items-center ">
<div className="flex items-center gap-TAG_LEFT ">
<p className="text-theme text-[26px] ">{pageTitle}</p>
<div className="text-theme text-[26px] ">{pageTitle}</div>
{tagList && tagList?.length > 0 && tagList?.map((tag)=>{
return ( <Tag key={tag.label as string} bordered={false} >{tag.label}</Tag>)
})}
@@ -53,10 +53,10 @@ const InsidePage:FC<InsidePageProps> = ({showBanner=true,pageTitle,tagList,showB
}}>{btnTitle}</Button></WithPermission>}
{customBtn}
</div>
<p >
<div >
{description}
</p>
</div>
</div>
</div>}
</div>}
<div className={`h-full ${scrollPage ? 'overflow-hidden' : 'overflow-auto'} ${contentClassName || ''}`}>{children}</div>
</div>
@@ -0,0 +1,22 @@
import React, { useEffect, useState } from 'react';
import { Result, Skeleton } from 'antd';
const NotFound: React.FC = () => {
const [showPage, setShowPage] = useState<boolean>(false)
useEffect(()=>{
setTimeout(()=>setShowPage(true), 1000)
},[])
return (
<div className={`h-full w-full flex flex-1 align-middle ${showPage ? 'items-center' : ''}`}>
{ showPage ? <Result
className='w-full'
status="404"
title="404"
subTitle="Sorry, the page you visited does not exist."
/> : <Skeleton active /> }
</div>
)}
export default NotFound;
@@ -0,0 +1,95 @@
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<PolicyPublishModalHandle,PolicyPublishModalProps>((props, ref) => {
const { message } = App.useApp()
const { data} = props
const [form] = Form.useForm();
const {fetchData} = useFetch()
const {state} = useGlobalContext()
const publish:()=>Promise<boolean | string | Record<string, unknown>> = ()=>{
return new Promise((resolve, reject)=>{
form.validateFields().then((value)=>{
const body = {...value, source:data.source}
fetchData<BasicResponse<null>>('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,
})
)
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 (
<>
<WithPermission access=""><Form
className=" mx-auto"
form={form}
labelAlign='left'
layout='vertical'
scrollToFirstError
name="publishApprovalModalContent"
// labelCol={{span: 3}}
// wrapperCol={{span: 21}}
autoComplete="off"
>
<Form.Item
label={$t("发布名称")}
name='versionName'
rules={[{required: true,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item
label={$t("描述")}
name="desc"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}</span></Row>
<Row className="mb-mbase ">
<Table
columns={translatedPolicyColumns}
bordered={true}
rowKey="name"
size="small"
dataSource={data.strategies || []}
pagination={false}
/>
{!data?.isPublish&& data?.unpublishMsg&& <p className="text-status_fail mt-[4px]">{data.unpublishMsg}</p>}
</Row>
</Form>
</WithPermission>
</>)
})
@@ -6,7 +6,7 @@ import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE,
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 { ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const";
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";
@@ -140,6 +140,19 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
}
}),[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)=>(
entity.status === 0 ? $t('正常') : $t('无效'))
}:{})
}
}),[state.language])
return (
<>
{!insidePage && <>
@@ -219,6 +232,16 @@ export const PublishApprovalModalContent = forwardRef<PublishApprovalModalHandle
/></Row>
</>
}
<Row className="mt-mbase pb-[8px] h-[32px] font-bold" ><span >{$t('策略列表')}</span></Row>
<Row className="mb-mbase ">
<Table
bordered={true}
columns={translatedPolicyColumns}
size="small"
rowKey="id"
dataSource={data.diffs?.strategies || []}
pagination={false}
/></Row>
{/* <Form.Item
label={$t("备注")}
name="remark"
@@ -163,6 +163,24 @@ export const TranslateWord = ()=>{
{$t('优先级')}
{$t('筛选条件')}
{$t('处理数')}
{$t('数据格式')}
{$t('关键字')}
{$t('正则表达式')}
{$t('手机号')}
{$t('身份证号')}
{$t('银行卡号')}
{$t('金额')}
{$t('日期')}
{$t('局部显示')}
{$t('局部遮蔽')}
{$t('截取')}
{$t('替换')}
{$t('乱序')}
{$t('随机字符串')}
{$t('自定义字符串')}
{$t('请输入IP地址或CIDR范围,每条以换行分割')}
{$t('待更新')}
{$t('待删除')}
</>
)
}
@@ -0,0 +1,79 @@
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';
const withRouteGuard = (WrappedComponent: ExoticComponent<any> | JSXElementConstructor<any>, {
canActivate,
canLoad ,
canDeactivate,
deactivated,
pathPrefix
}: { pathPrefix?:string, canActivate?: () => Promise<boolean>; canLoad?: () => Promise<boolean>; canDeactivate?: () => Promise<boolean>; deactivated?: () => Promise<void>; } = {}) => {
return function RouteGuard(props: JSX.IntrinsicAttributes) {
const [isActivated, setIsActivated] = useState<boolean>(false);
const location = useLocation();
// check canActivate
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 = '';
}
};
// 激活组件时的检查
useEffect(() => {
startLifecycle();
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
deactivated?.();
};
}, []);
const blocker = useBlocker((tx) => {
const currentPath = location.pathname;
const targetPath = tx.nextLocation.pathname;
if (pathPrefix && currentPath.startsWith(pathPrefix) && !targetPath.startsWith(pathPrefix) && canDeactivate) {
canDeactivate().then((res) => {
if(res){
return false;
}else{
return true;
}
})
} else {
return false
}
});
const checkCanLoad = async()=>{
const loadRes = await canLoad!();
!loadRes && setIsActivated(false);
}
useEffect(() => {
if (isActivated && canLoad) {
checkCanLoad()
}
}, [isActivated]);
return isActivated ? <WrappedComponent {...props}/> : null;
};
}
export default withRouteGuard;
@@ -58,7 +58,7 @@ import {
UPDATE_DATASETS_EVENT_EMITTER,
UPDATE_HISTORY_EVENT_EMITTER,
} from './constants'
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
export type PromptEditorProps = {
instanceId?: string
@@ -30,7 +30,7 @@ import { $splitNodeContainingQuery } from '../../utils'
import { useOptions } from './hooks'
import type { PickerBlockMenuOption } from './menu'
// import VarReferenceVars from '@/app/components/workflow/nodes/_base/components/variable/var-reference-vars'
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
type ComponentPickerProps = {
triggerString: string
@@ -14,7 +14,7 @@ import { DELETE_CONTEXT_BLOCK_COMMAND } from './index'
// PortalToFollowElemContent,
// PortalToFollowElemTrigger,
// } from '@/app/components/base/portal-to-follow-elem'
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
import { $t } from '@common/locales'
type ContextBlockComponentProps = {
@@ -14,7 +14,7 @@ import { DELETE_HISTORY_BLOCK_COMMAND } from './index'
// PortalToFollowElemContent,
// PortalToFollowElemTrigger,
// } from '@/app/components/base/portal-to-follow-elem'
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
import { $t } from '@common/locales'
type HistoryBlockComponentProps = {
@@ -3,7 +3,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
import { textToEditorState } from '../utils'
import { CustomTextNode } from './custom-text/node'
import { CLEAR_HIDE_MENU_TIMEOUT } from './workflow-variable-block'
import { useEventEmitterContextContext } from '@common/contexts/event-emitter'
import { useEventEmitterContextContext } from '@common/contexts/EventEmitterContext'
export const PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER = 'PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER'
export const PROMPT_EDITOR_INSERT_QUICKLY = 'PROMPT_EDITOR_INSERT_QUICKLY'
@@ -241,6 +241,26 @@ export const ApprovalRouteColumns = [
}
]
export const ApprovalPolicyColumns = [
{
title:('名称'),
dataIndex:'name',
ellipsis:true,
},
{
title:('优先级'),
dataIndex:'priority',
ellipsis:true
},
{
title:('状态'),
dataIndex:'status',
}
]
export const ApprovalUpstreamColumns = [
{
title:('上游类型'),
@@ -67,6 +67,7 @@ export type PublishApprovalInfoType = {
diffs:{
apis:PublishApprovalApiItem[]
upstreams:PublishApprovalUpstreamItem[]
strategies:Array<{name:string, priority:number,statues:0|1}>
}
clusterPublishStatus?:SystemInsidePublishOnlineItems[],
error:string
+35 -3
View File
@@ -1,3 +1,5 @@
import { $t } from '@common/locales'
import { StrategyStatusColorClass, StrategyStatusEnum } from './policy/consts'
export type BasicResponse<T> = {
code:number
@@ -5,7 +7,6 @@ export type BasicResponse<T> = {
msg:string
}
export const STATUS_CODE = {
SUCCESS:0,
UNANTHORIZED:401,
@@ -18,7 +19,7 @@ export const STATUS_COLOR = {
}
// avoid changing route within ths same category
// TODO should be generated dynamically
export const routerKeyMap = new Map<string, string[]|string>([
['workspace',['consumer','service','team','guide']],
['my',['consumer','service','team']],
@@ -29,6 +30,7 @@ export const routerKeyMap = new Map<string, string[]|string>([
['maintenanceCenter',['aisetting','datasourcing','cluster','cert','logsettings','resourcesettings','openapi']
]])
export const COLUMNS_TITLE = {
operate : ''
@@ -45,6 +47,7 @@ export const routerKeyMap = new Map<string, string[]|string>([
startWithAlphabet:('英文数字下划线任意一种,首字母必须为英文'),
specialStartWithAlphabet:('支持字母开头、英文数字中横线下划线组合'),
onlyAlphabet:('字符非法,仅支持英文'),
ipAndCidr:'请输入IP地址或CIDR范围,每条以换行分割'
}
export const FORM_ERROR_TIPS = {
@@ -73,4 +76,33 @@ export const routerKeyMap = new Map<string, string[]|string>([
export const DATA_SHOW_TYPE_OPTIONS = [
{label:'列表', value:'list'},
{label:'块', value:'block'},
]
]
export const PolicyPublishColumns = [
{
title: ('策略名称'),
dataIndex: 'name',
ellipsis: true,
width: 160
},
{
title: ('优先级'),
dataIndex: 'priority',
width: 140,
ellipsis: true
},
{
title: ('状态'),
dataIndex: 'status',
width: 140,
render:(text:string)=> <span className={StrategyStatusColorClass[text as keyof typeof StrategyStatusColorClass]}>{$t(StrategyStatusEnum[text as keyof typeof StrategyStatusEnum])}</span>,
},
{
title: ('更新时间'),
dataIndex: 'optTime',
width: 182,
ellipsis: true,
},
]
@@ -239,6 +239,31 @@ export const PERMISSION_DEFINITION = [
"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"] }]
@@ -429,6 +454,31 @@ export const PERMISSION_DEFINITION = [
"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"] }]
@@ -1,204 +0,0 @@
system:
- name: organization
cname: '组织管理'
value: 'organization'
children:
- name: member
cname: '成员'
value: 'member'
access:
- system.settings.account.view
- system.organization.member.add
- system.organization.member.edit
- system.organization.member.delete
- system.organization.member.block
- system.organization.member.department.add
- system.organization.member.department.edit
- system.organization.member.department.delete
- name: team_manager
cname: '团队管理'
desc: '团队管理'
- system.workspace.team.view_all
- system.organization.team.add
- system.organization.team.edit
- system.organization.team.delete
- system.organization.team.running
- name: role_manager
cname: '角色管理'
desc: '角色管理'
- system.organization.role.view
- system.organization.role.system.view
- system.organization.role.system.add
- system.organization.role.system.edit
- system.organization.role.system.delete
- system.organization.role.team.view
- system.organization.role.team.add
- system.organization.role.team.edit
- system.organization.role.team.delete
- name: API Market
cname: 'API市场'
value: 'api_market'
children:
- name: service classification
cname: '服务分类'
value: 'service_classification'
children:
- system.api_market.service_classification.view
- system.api_market.service_classification.add
- system.api_market.service_classification.edit
- system.api_market.service_classification.delete
- name: devops
cname: 运维
value: 'devops'
children:
- name: cluster
cname: 集群
value: 'cluster'
children:
- system.settings.api_gateway.view
- system.devops.cluster.add
- system.devops.cluster.edit
- system.devops.cluster.delete
- name: ssl certificate
cname: 证书
value: 'ssl_certificate'
children:
- system.settings.ssl_certificate.view
- system.devops.ssl_certificate.add
- system.devops.ssl_certificate.edit
- system.devops.ssl_certificate.delete
- name: log configuration
cname: 日志
value: 'log_configuration'
children:
- system.settings.log_configuration.view
- system.devops.log_configuration.add
- system.devops.log_configuration.edit
- system.devops.log_configuration.publish
- system.devops.log_configuration.delete
- name: workspace
cname: 工作空间
value: 'workspace'
children:
- name: application
cname: 应用
value: 'application'
children:
- system.workspace.application.view_all
- name: service
cname: 服务
value: 'service'
children:
- system.workspace.service.view_all
- name: team
cname: 团队
value: 'team'
children:
- system.workspace.team.view_all
- name: api market
cname: API市场
value: 'api_market'
children:
- system.api_portal.api_portal.view
team:
- name: service
cname: 服务
value: 'service'
children:
- name: api
cname: API
value: 'api'
children:
- team.service.api_doc.view
- team.service.api_doc.add
- team.service.api_doc.edit
- name: route
cname: route
value: 'route'
children:
- team.service.router.view
- team.service.router.add
- team.service.router.edit
- team.service.router.delete
- name: upstream
cname: 上游
value: 'upstream'
children:
- team.service.upstream.view
- team.service.upstream.add
- team.service.upstream.edit
- team.service.upstream.delete
- name: release
cname: 发布
value: 'release'
children:
- team.service.release.view
- team.service.release.add
- team.service.release.rollback
- team.service.release.delete
- team.service.release.approval
- team.service.release.online
- team.service.release.cancel
- team.service.release.stop
- name: subscription management
cname: 订阅方管理
value: 'subscription'
children:
- team.service.subscription.view
- team.service.subscription.approval
- team.service.subscription.add
- team.service.subscription.delete
- name: service
cname: 服务管理
value: 'service'
children:
- team.service.service.view
- team.service.service.add
- team.service.service.edit
- team.service.service.delete
- name: application
cname: 应用
value: 'application'
children:
- name: subscription Service
cname: 订阅服务
value: 'subscription'
children:
- team.application.subscription.view
- team.application.subscription.add
- team.application.subscription.edit
- team.application.subscription.delete
- name: authorization
cname: 访问授权
value: 'authorization'
children:
- team.consumer.authorization.view
- team.consumer.authorization.manager
- team.application.authorization.add
- team.application.authorization.edit
- team.application.authorization.delete
- name: application
cname: 应用
value: 'application'
children:
- team.application.application.view
- team.application.application.add
- team.application.application.edit
- team.application.application.delete
- name: team
cname: 团队
value: 'team'
children:
- name: member
cname: 成员
value: 'member'
children:
- team.team.member.view
- team.team.member.add
- team.team.member.edit
- name: team
cname: 团队管理
value: 'team'
children:
- team.team.team.view
- team.team.team.edit
@@ -0,0 +1,57 @@
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'},
]
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',
}
@@ -0,0 +1,145 @@
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
}
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
}
}
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
}
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
}
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<string>;
disabled: boolean;
onFilterFormChange: (form: FilterFormType) => void;
setFormCanSubmit:(canSubmit:boolean)=>void
serviceId?:string
teamId?:string
}
export type FilterFormHandle = {
clear:()=>void
save:()=>Promise<FilterFormField>
}
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
}
+112 -1
View File
@@ -1,5 +1,9 @@
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"
export type UserInfoType = {
username: string
@@ -8,7 +12,6 @@ export type UserInfoType = {
phone: string
avatar: string
type:string
}
export type UserProfileProps = {
@@ -100,4 +103,112 @@ export type SimpleMemberItem = {
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<string, string>
lifecycle?:{
canActivate?:()=>Promise<boolean>
canLoad?:()=>Promise<boolean>
canDeactivate?:()=>Promise<boolean>
deactivated?:()=>Promise<boolean>
}
}
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
}
export type CoreObj = {
routerConfig: RouteConfig[];
setExecuteList: (param:unknown[])=>void;
pluginLoader: {
loadModule: (path: string, name: string, expose: string, pluginPath: string) => Promise<any>;
};
pluginProvider: ReturnType<typeof useGlobalContext>
// pluginLifecycleGuard: PluginLifecycleGuard;
builtInPluginLoader: (name: string) => any;
}
export type PluginConfigType = {
name: string;
router: Array<PluginRouterConfig>;
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<string, string>
pathMatch?:string
}
export type PolichPublishItemType = {
name:string
priority:number
status:keyof typeof StrategyStatusEnum
optTime:string
}
// 发布详情(版本)
export type PolicyPublishInfoType = {
source:string
strategies:Array<PolichPublishItemType>
isPublish:boolean
versionName:string
unpublishMsg:string
};
export type PolicyPublishModalProps = {
data:PolicyPublishInfoType
}
export type PolicyPublishModalHandle = {
publish:()=>Promise<boolean|string|Record<string, unknown>>
}
@@ -1,11 +1,19 @@
import {createContext, Dispatch, FC, ReactNode, useContext, useReducer, useState} from "react";
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";
interface GlobalState {
isAuthenticated: boolean;
userData: UserData | null;
@@ -14,6 +22,7 @@ interface GlobalState {
powered:string;
mainPage:string;
language:string;
pluginsLoaded:boolean
}
interface UserData {
@@ -29,8 +38,171 @@ export type GlobalAction =
| { 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/page",
"icon": "ic:baseline-space-dashboard",
"children": [
{
"name": "首页",
"key": "guide",
"path": "/guide/page",
"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": "uil:comment-shield",
"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"
},
]
}
]
}
]
/*
*/
@@ -39,9 +211,11 @@ export const GlobalContext = createContext<{
dispatch: Dispatch<GlobalAction>;
accessData:Map<string,string[]>;
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<keyof typeof PERMISSION_DEFINITION[0]>)=>boolean
@@ -49,6 +223,11 @@ export const GlobalContext = createContext<{
accessInit:boolean
aiConfigFlushed:boolean
setAiConfigFlushed:(flush:boolean)=>void
routeConfig: RouteConfig[];
setRouterConfig: (isRoot: boolean, config: RouteConfig) => void;
addRouteConfig: (parentRoute: RouteConfig, config: RouteConfig) => void;
fetchData: ReturnType<typeof useFetch>['fetchData'];
$t: typeof $t;
} | undefined>(undefined);
const globalReducer = (state: GlobalState, action: GlobalAction): GlobalState => {
@@ -94,23 +273,38 @@ const globalReducer = (state: GlobalState, action: GlobalAction): GlobalState =>
...state,
language: action.language,
};
case 'SET_PLUGINS_LOADED':
return {
...state,
pluginsLoaded: action.pluginsLoaded,
};
default:
return state;
}
};
export const DefaultRouteConfig = [
{ path: '/', pathMatch: 'full', component: <Root /> ,key:'root',},
{ path: '/login', component: <Login /> ,key:'login'},
{ path: '/dataMaskCompare/:logId/:serviceId?/:teamId?', component: <DataMaskingCompare /> ,key:'dataMaskCompare'},
{ path: '/', pathMatch:'prefix',component:<ProtectedRoute /> ,key:'basciLayout',children:[
{ path: '*', component: <ErrorBoundary><NotFound/></ErrorBoundary>, key: 'errorBoundary' }
]}
]
// Create a context provider component
export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
const {fetchData} = useFetch()
const { message } = App.useApp()
const { setLocale } = useLocaleContext();
const [state, dispatch] = useReducer(globalReducer, {
isAuthenticated: true, //mock用
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'
language:'en-US',
pluginsLoaded:false
});
const [accessData,setAccessData] = useState<Map<string,string[]>>(new Map())
const [pluginAccessDictionary, setPluginAccessDictionary] = useState<{[k:string]:string}>({})
@@ -118,6 +312,43 @@ export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
const [accessInit, setAccessInit] = useState<boolean>(false)
const [aiConfigFlushed, setAiConfigFlushed] = useState<boolean>(false)
let getGlobalAccessPromise: Promise<BasicResponse<{ access:string[] }>> | null = null
const [menuList, setMenuList] = useState<MenuItem[]>(mockData);
const [routeConfig, setRouteConfigState] = useState<RouteConfig[]>(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];
}
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){
@@ -151,6 +382,18 @@ export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
})
}
const getMenuList = ()=>{
// TODO 等待对接后端接口
// fetchData<BasicResponse<{ access:string[]}>>('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',[]))
@@ -176,15 +419,26 @@ export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
return revs
}
return (
<GlobalContext.Provider value={
{ state, dispatch,accessData,pluginAccessDictionary,
{ state, dispatch,
accessData,
pluginAccessDictionary,
getGlobalAccessData,
getPluginAccessDictionary,
getTeamAccessData,teamDataFlushed,
getTeamAccessData,teamDataFlushed,getMenuList,menuList,
cleanTeamAccessData,
resetAccess ,checkPermission,accessInit,
aiConfigFlushed, setAiConfigFlushed}}>
resetAccess ,checkPermission,
accessInit,
aiConfigFlushed,
setAiConfigFlushed,
routeConfig,
setRouterConfig,
addRouteConfig,
fetchData,
$t:$t,}}>
{children}
</GlobalContext.Provider>
);
@@ -194,7 +448,40 @@ export const GlobalProvider: FC<{children:ReactNode}> = ({ children }) => {
export const useGlobalContext = () => {
const context = useContext(GlobalContext);
if (!context) {
throw new Error('useGlobalContext must be used within a GlobalProvider');
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;
};
@@ -0,0 +1,47 @@
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';
type Locale = ConfigProviderProps['locale'];
const languageMap: Record<string, Locale> = {
'zh-CN': zhCN,
'en-US': enUS,
'zh-TW': zhTW,
'ja-JP': jaJP,
};
const LocaleContext = createContext<{
locale: Locale;
setLocale: (locale: string) => void;
}|undefined>(undefined);
export const LocaleProvider: FC<{children:ReactNode}> = ({ children }) => {
const [locale, setLocaleState] = useState<Locale>(zhCN);
const setLocale = (language: string) => {
dayjs.locale(language);
setLocaleState(languageMap[language]);
};
return (
<LocaleContext.Provider value={{ locale, setLocale }}>
{children}
</LocaleContext.Provider>
);
};
export const useLocaleContext = () => {
const context = useContext(LocaleContext);
if (!context) {
throw new Error('useLocaleContext must be used within a LocaleContext');
}
return context;
};
@@ -0,0 +1,76 @@
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)
}
}
export const PluginEventHubContext = createContext<EventEmitter | undefined>(undefined);
export const PluginEventHubProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [pluginEventHub] = useState<EventEmitter>(new EventEmitter());
return (
<PluginEventHubContext.Provider value={pluginEventHub}>
{children}
</PluginEventHubContext.Provider>
);
};
export const usePluginEventHub = () => {
const context = useContext(PluginEventHubContext);
if (!context) {
throw new Error('usePluginEventHub must be used within a PluginEventHubProvider');
}
return context;
};
@@ -0,0 +1,36 @@
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 PluginSlotHubProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [pluginSlotHub] = useState<Map<string, unknown>>(new Map());
const pluginSlotHubService = {
addSlot: (name: string, content: any) => {
pluginSlotHub.set(name, pluginSlotHub.get(name) ? [...(pluginSlotHub.get(name) as Array<unknown>), content] : [content] ); },
addSlotArr: (name: string, content: any[]) => { pluginSlotHub.get(name) ? pluginSlotHub.set(name, (pluginSlotHub.get(name) as Array<unknown>).push(content)) : pluginSlotHub.set(name, content); },
removeSlot: (name: string) => { pluginSlotHub.delete(name); },
getSlot: (name: string) => {
return pluginSlotHub.get(name) ; }
};
return (
<PluginSlotHubContext.Provider value={pluginSlotHubService}>
{children}
</PluginSlotHubContext.Provider>
);
};
export const usePluginSlotHub = () => {
const context = useContext(PluginSlotHubContext);
if (!context) {
throw new Error('usePluginSlotHub must be used within a PluginSlotHubProvider');
}
return context;
};
+10 -3
View File
@@ -1,4 +1,6 @@
import { STATUS_CODE } from "@common/const/const";
import { BasicResponse, STATUS_CODE } from "@common/const/const";
import { usePluginEventHub } from "@common/contexts/PluginEventHubContext";
import EventEmitter from "events";
const urlWhiteList = [/api.example.com\/users/, /api.example2.com\/products/]; // 正则白名单
@@ -137,7 +139,10 @@ type EoRequest = RequestInit & {eoParams?:{[k:string]:unknown},eoTransformKeys?:
type EoHeaders = Headers | {[k:string]:string}
export function useFetch(){
export function useFetch() {
// plugin cannot use usePluginEventHub directly, so we need to pass it as a parameter
const pluginEventHub = usePluginEventHub()
function fetchData<T>(url:string, options: EoRequest ) {
// 合并传入的headers与默认headers
const headers = { ...(options.body ? {}:DEFAULT_HEADERS), ...options.headers };
@@ -184,9 +189,11 @@ export function useFetch(){
// 如果响应体为JSON且指定了转换键,则转换响应数据
if ( isJsonHttp(response.headers)) {
const data = await response.json();
return shouldTransformKeys ? keysToCamel(data,options.eoTransformKeys as string[]) as T:data
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 => {
@@ -0,0 +1,449 @@
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";
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:[
{
driver:'apipark.builtIn.component',
name:'guide',
router:[
{
path:'guide/*',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'team',
router:[
{
path:'team',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'service',
router:[
{
path:'service',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'datasourcing',
router:[
{
path:'datasourcing',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'cluster',
router:[
{
path:'cluster',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'aisetting',
router:[
{
path:'aisetting',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'cert',
router:[
{
path:'cert',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'serviceHub',
router:[
{
path:'serviceHub',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'commonsetting',
router:[
{
path:'commonsetting',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'consumer',
router:[
{
path:'consumer',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'member',
router:[
{
path:'member',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'role',
router:[
{
path:'role',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'analytics',
router:[
{
path:'analytics',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'template',
router:[
{
path:'template/:moduleId',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'logsettings',
router:[
{
path:'logsettings/*',
type:'normal'
}
]
},
{
driver:'apipark.builtIn.component',
name:'resourcesettings',
router:[
{
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",
// "router": [
// {
// "path": "remote",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.local.preload",
// "name": "auth",
// "router": [
// {
// "expose": "AppModule",
// "path": "auth",
// "type": "root"
// },
// {
// "expose": "AuthInfoModule",
// "path": "auth-info",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.builtIn.component",
// "name": "email",
// "router": [
// {
// "path": "system/email",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.builtIn.module",
// "name": "open-api",
// "router": [
// {
// "path": "system/ext-app",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.local.preload",
// "name": "remote",
// "router": [
// {
// "expose": "App",
// "path": "router1/*",
// "type": "normal"
// }
// ]
// },
// {
// "driver": "apipark.remote.normal",
// "name": "apispace",
// "router": [
// {
// "path": "remote/apispace",
// "type": "normal"
// }
// ]
// }
],
powered:'Powered by https://eolink.com',
product:'apipark',
version:'6438d5aa'
}
export type ExecutePluginType = PluginConfigType & {
expose:string,
bootstrap:string
}
const usePluginLoader = (apipark:ApiparkPluginDriverType,routerMap:Map<string, RouterMapConfig>) => {
const [modules, setModules] = useState(new Map());
const [executeList, setExecuteList] = useState<ExecutePluginType[]>([]);
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<boolean>(false)
const messageService = message;
const modalService = modal;
let startInstallPlugin = false
useEffect(()=>{
if (startLoadExecutePlugin && pendingTasks === 0 && executeList.length > 0) {
loadExecutedPlugin();
}
},[pendingTasks, executeList])
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;
}
});
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) => {
try {
const { module } = routerMap.get(pluginName)!;
return module;
} catch (err) {
console.warn(`安装内置插件[${pluginName}]出错:`, err);
}
};
return {
loadPlugins,
loadModule,
loadExecutedPlugin,
setBaseHref,
getModule
};
};
export default usePluginLoader;
@@ -1,32 +1,8 @@
{
"工作空间": "Kc0e5ef9f",
"首页": "K4de11e23",
"服务": "Kb58e0c3f",
"消费者": "K7acfcfad",
"团队": "Kc9e489f5",
"API 市场": "K61c89f5f",
"仪表盘": "K16d71239",
"运行视图": "K714c192d",
"系统拓扑图": "Kd57dfe97",
"系统设置": "K3fe97dcc",
"系统": "Kecbb0e45",
"常规": "Ka358e23d",
"API 网关": "K449058e9",
"AI 模型": "K99935e6f",
"用户": "K1deaa2dd",
"账号": "K80a560a1",
"角色": "Kf644225f",
"集成": "K4057391a",
"数据源": "K8fa58214",
"全局策略": "Ke8cbb878",
"证书": "K481e8a05",
"日志": "Kca53edd0",
"资源": "Kb283e720",
"Open API": "K631d646f",
"账号设置": "K6535ff9c",
"退出登录": "Kf15499b4",
"文档": "Kabbd6e6",
"APIPark - 企业API数据开放平台": "K1196b104",
"APIPark": "K630c9e6d",
"HTTP 状态码": "K1f42de3",
"系统状态码": "K4770dff4",
"描述": "Kf89e58f1",
@@ -44,6 +20,8 @@
"ID": "K11d3633a",
"名称": "Kbff43de3",
"Driver": "K16ca79ef",
"资源": "Kb283e720",
"日志": "Kca53edd0",
"已发布": "K7a369eef",
"下线": "Kcfa1a4d2",
"上线": "K771dc3b7",
@@ -51,6 +29,8 @@
"删除": "Kecbd7449",
"确认": "K1cbe2507",
"搜索(0)名称": "K48325b6",
"发布名称": "Ka3e9f580",
"策略列表": "Kb2480682",
"上下文": "Kc6340091",
"查询内容": "K74ecb1fa",
"会话历史": "K79f2e2f9",
@@ -65,6 +45,8 @@
"成功": "K43fcaf94",
"上线失败": "Kc71c6a9",
"失败": "K56c686f8",
"正常": "Ke039b9b5",
"无效": "K1da86266",
"申请系统": "K1ff96ff",
"所属团队": "K9bf855d6",
"申请人": "K11b994ed",
@@ -235,6 +217,28 @@
"地址": "K78b1ca25",
"新增": "K1644b775",
"申请方消费者": "Kec91f0db",
"策略名称": "K931615d7",
"优先级": "K31faa2a1",
"筛选条件": "Kbdec9fa",
"处理数": "Kbcbb7391",
"数据格式": "K118d8d74",
"关键字": "Kfe7c7d2d",
"正则表达式": "K2f57a694",
"手机号": "K8953e0a6",
"身份证号": "K6f86a038",
"银行卡号": "K7954e7c8",
"金额": "K320fdb17",
"日期": "K7867acda",
"局部显示": "K7d327ae8",
"局部遮蔽": "Kfbf38e3c",
"截取": "Kd8c1fbb0",
"替换": "K89829921",
"乱序": "K480a7165",
"随机字符串": "Kea0d69df",
"自定义字符串": "Ke7c84d1d",
"请输入IP地址或CIDR范围,每条以换行分割": "K49731763",
"待更新": "K3a34d49b",
"待删除": "Kd2850420",
"暂无操作权限,请联系管理员分配。": "K23fda291",
"微信小程序": "K4618cb0a",
"获取文件,需填路径": "Ka854f511",
@@ -322,14 +326,18 @@
"至": "K7e1ab4b0",
"详情": "Kf1b166e7",
"暂不支持带有双斜杠//的url": "K28555332",
"输入的IP或CIDR不符合格式": "K83237c89",
"请正确输入路径,如/usr/*或*/usr/*": "K5ae2c87a",
"必填项": "K71661ee8",
"不是有效邮箱地址": "Kcbee3f8",
"最近一次更新者": "K617f34f1",
"最近一次更新时间": "K6ebca204",
"保存": "Kabfe9512",
"服务": "Kb58e0c3f",
"API 路由": "K51d1eb5d",
"API 文档": "Ka2b6d281",
"使用说明": "Kdefa9caa",
"服务策略": "K52f72551",
"发布": "K36856e71",
"订阅管理": "K6382bbfd",
"订阅审核": "K31af5b99",
@@ -381,6 +389,8 @@
"发布申请": "K56b4254f",
"API 调用地址": "Kea2f9279",
"API base URL 一般设置为API 网关的外部网络访问地址,或者是API网关绑定的域名。": "K7fc496a1",
"集成地址": "K508d8bf4",
"与外部平台集成时,获取 API 市场中文档信息的域名": "K67f4e9bb",
"常规设置": "K8ab0fc95",
"API 请求设置": "Kb66fec9d",
"服务分类": "K4de0af74",
@@ -402,11 +412,13 @@
"现在你可以通过 Token 来调用这些 API。": "Kd6d7ca1f",
"快速接入 REST API": "K86cf95f",
"创建 REST 服务和 API": "K7a3a8417",
"仪表盘": "K16d71239",
"统计 API 调用情况": "K4a84214e",
"仪表盘中提供了多种统计图表,帮助我们了解 API 的运行情况。": "K297d8563",
"核心功能": "K2cdbb773",
"账号与角色": "K3378c50d",
"邀请你的团队成员加入 APIPark,共同管理和调用 API。": "Kda5bb930",
"团队": "Kc9e489f5",
"团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。": "Keee27105",
"服务内包含一组 API,并且可以发布到 API 市场被其他团队使用。": "Kd5be0cd7",
"权限管理": "K62e89ee7",
@@ -414,6 +426,7 @@
"如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。": "Kf2410413",
"审核订阅申请": "K6c2e44b8",
"提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。": "Kaa717866",
"集成": "K4057391a",
"APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K3453272",
"Hello!欢迎使用 APIPark": "Kd518ba3e",
"你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。": "K7e04ea16",
@@ -425,6 +438,7 @@
"了解更多功能": "K48f7e21f",
"隐藏该教程": "K698296e2",
"请输入账号": "Kf076f63c",
"账号": "K80a560a1",
"请输入密码": "K25c895d5",
"密码": "K551b0348",
"登录": "Kd2c1a316",
@@ -456,7 +470,6 @@
"添加部门": "K26c698bb",
"添加子部门": "Kb9cf2a7d",
"重命名": "Kc83551f5",
"该数据删除后将无法找回,请确认是否删除?": "K5cfdd950",
"成员": "K74aef1ad",
"设置成员和对应的角色,成员只能够看到权限范围内的功能和数据。": "K3f1077c9",
"搜索部门": "Kdce62a6",
@@ -467,6 +480,7 @@
"密钥": "K8ef69ee2",
"上传密钥": "Kba3507d6",
"密钥文件的后缀名一般为 .key 的文件内容": "K93ac0f23",
"证书": "K481e8a05",
"上传证书": "K7cdd1331",
"证书文件的后缀名一般为 .crt 或 .pem 的文件内容": "K6d91905d",
"添加证书": "Kd0f6ded7",
@@ -476,7 +490,6 @@
"集群": "Ke93d36ed",
"修改配置": "K877985b7",
"设置访问 API 的集群,让 API 在分布式环境中稳定运行,并且能够根据业务需求进行灵活扩展和优化。": "Kdf66a675",
"正常": "Ke039b9b5",
"异常": "K23a3bd72",
"私有网络": "Ke1b1865",
"公共网络": "K4786c57c",
@@ -485,19 +498,39 @@
"同步地址": "K2a49373f",
"集群地址": "K5878440c",
"下一步": "K5e9022f8",
"数据源": "K8fa58214",
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
"统计图表": "K1358acf",
"地址(IP:端口)": "K62dabdf6",
"组织(Organization": "K2db12335",
"添加策略": "K34d0d409",
"输入名称、筛选条件查找": "Kbb4298ac",
"策略名称": "K931615d7",
"优先级": "K31faa2a1",
"筛选条件": "Kbdec9fa",
"处理数": "Kbcbb7391",
"编辑策略": "Kc82b8374",
"策略类型": "K4b34a5e5",
"匹配条件": "K57f0fee8",
"数据脱敏规则": "K10650c58",
"配置脱敏规则": "K1b34a9ab",
"匹配值": "K26d22405",
"脱敏类型": "K1546e1fe",
"起始位置": "K9b9b0629",
"长度": "K52c84fe1",
"替换类型": "Kde84409c",
"替换值": "K338653b4",
"JSON Path": "Kbaeed3b7",
"脱敏规则": "K4cd91d61",
"自定义字符串; 值:": "K8dcad979",
"起始位置:(0)位;长度:(1)位": "K82e3f7b7",
"编辑": "Kad207008",
"已选择(0)项(1)数据": "K49dfc123",
"所有(0)": "K8457ea34",
"属性名称": "K7ca9a795",
"属性值": "Kc4391744",
"配置(0)": "K678e13fc",
"数据脱敏": "Kabac9caf",
"全局策略": "Ke8cbb878",
"支持对系统全局进行统一的策略配置,从而简化管理并确保一致性。全局策略的优先级比服务策略略低。": "Kc975cd5a",
"资源配置": "K8e7a0f80",
"角色": "Kf644225f",
"设置角色的权限范围。": "K95c3fd8b",
"系统级别角色": "K138facd3",
"添加角色": "K6eac768d",
@@ -532,7 +565,6 @@
"删除服务": "Kde6bae17",
"删除操作不可恢复,请谨慎操作!": "K885ea699",
"上游": "Kda8d5ea1",
"服务策略": "K52f72551",
"服务提供了高性能 API 网关,并且可以无缝接入多种大型 AI 模型,并将这些 AI 能力打包成 API 进行调用,从而大幅简化了 AI 模型的使用门槛。同时,我们的平台提供了完善的 API 管理功能,支持 API 的创建、监控、访问控制等,保障开发者可以高效、安全地开发和管理 API 服务。": "K12f58863",
"添加服务": "K2d6658ed",
"输入名称、ID、所属团队、负责人查找服务": "K7b8f623f",
@@ -578,6 +610,7 @@
"退出全屏": "Kaf70c3b",
"(0)调用详情": "Kd22841a4",
"消费者调用统计": "K61cca533",
"消费者": "K7acfcfad",
"请选择消费者": "Kdfff59d4",
"调用趋势": "K8c7f2d2e",
"(0)-(1)调用趋势": "K657c3452",
@@ -610,6 +643,7 @@
"暂无调用量统计数据": "Kcd125e4d",
"暂无报文量统计数据": "Kaa114e8b",
"报文量统计": "K3ad84406",
"运行视图": "K714c192d",
"集群配置并开启监控": "Kfa088d49",
"监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;": "K3da3b9a0",
"集群配置": "Kaddacfb",
@@ -699,6 +733,7 @@
"配置 Open Api": "K7829bb78",
"Open Api": "Kcdf76005",
"调用服务": "Ke2601944",
"系统拓扑图": "Kd57dfe97",
"放大": "K8504bca8",
"缩小": "K693c1b41"
}
@@ -684,7 +684,7 @@
"K35f23b64": "Follow these steps for integration:",
"Kf5cd608b": "Step 1: Create a custom plugin on the Agent platform",
"K4c81c7b6": "For details on the operations of different Agent platforms, refer to",
"K275f7ffa": "the 'Agent Integration Manual'",
"K275f7ffa": "the Agent Integration Manual",
"K49b81d06": "Step 2: Import API documentation data",
"K4a3b62be": "You can import API documentation data to the Agent platform via the following URL or by downloading the JSON file.",
"K42697e11": "Copy URL",
@@ -703,5 +703,51 @@
"K31faa2a1": "Priority",
"Kbdec9fa": "Filter Criteria",
"Kbcbb7391": "Processed Count",
"Kad207008": "Edit"
"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"
}
@@ -725,5 +725,52 @@
"K31faa2a1": "優先度",
"Kbdec9fa": "フィルタ条件",
"Kbcbb7391": "処理数",
"Kad207008": "編集"
"Kad207008": "編集",
"K630c9e6d": "APIPark",
"Ka3e9f580": "リリース名",
"Kb2480682": "ポリシーリスト",
"K118d8d74": "データフォーマット",
"Kfe7c7d2d": "キーワード",
"K2f57a694": "正規表現",
"K8953e0a6": "携帯番号",
"K6f86a038": "IDカード番号",
"K7954e7c8": "銀行カード番号",
"K320fdb17": "金額",
"K7867acda": "日付",
"K7d327ae8": "部分表示",
"Kfbf38e3c": "部分マスキング",
"Kd8c1fbb0": "切り取り",
"K89829921": "置換",
"K480a7165": "シャッフル",
"Kea0d69df": "ランダム文字列",
"Ke7c84d1d": "カスタム文字列",
"K49731763": "IPアドレスまたはCIDR範囲を入力してください。各行で改行してください。",
"K83237c89": "入力されたIPまたはCIDRがフォーマットに一致しません。",
"K5ae2c87a": "正しいパスを入力してください。例: /usr/* または */usr/*。",
"Kc82b8374": "ポリシーを編集",
"K4b34a5e5": "ポリシータイプ",
"K57f0fee8": "一致条件",
"K10650c58": "データマスキングルール",
"K1b34a9ab": "マスキングルールを設定",
"K26d22405": "一致値",
"K1546e1fe": "マスキングタイプ",
"K9b9b0629": "開始位置",
"K52c84fe1": "長さ",
"Kde84409c": "置換タイプ",
"K338653b4": "置換値",
"Kbaeed3b7": "JSON パス",
"K4cd91d61": "マスキングルール",
"K8dcad979": "カスタム文字列; 値:",
"K82e3f7b7": "開始位置: (0); 長さ: (1)",
"K49dfc123": "選択された (0) アイテム (1) データ",
"K8457ea34": "すべて (0)",
"K7ca9a795": "属性名",
"Kc4391744": "属性値",
"K678e13fc": "設定 (0)",
"K508d8bf4": "統合アドレス",
"K67f4e9bb": "外部プラットフォームと統合する際に、API市場のドキュメント情報を取得するためのドメイン名",
"K1da86266": "無効",
"K3a34d49b": "更新待ち",
"Kd2850420": "削除待ち"
}
@@ -1,3 +1 @@
{
"Kf5fd27ed": "输入名称查找用户"
}
{}
@@ -1,3 +1 @@
{
"Kf5fd27ed": "输入名称查找用户"
}
{}
@@ -1,4 +1,8 @@
{
"K630c9e6d": "APIPark",
"Ka3e9f580": "发布名称",
"Kb2480682": "策略列表",
"K1da86266": "无效",
"K76036e25": "HTTP 请求头",
"K44607e3f": "全等匹配",
"Kc287500a": "前缀匹配",
@@ -21,7 +25,47 @@
"K78b1ca25": "地址",
"K1644b775": "新增",
"Kec91f0db": "申请方消费者",
"Kf5fd27ed": "输入名称查找用户",
"Kc3b7bfa8": "暂无消费者描述",
"K3a6f905d": "输入名称、ID 查找消费者"
"K118d8d74": "数据格式",
"Kfe7c7d2d": "关键字",
"K2f57a694": "正则表达式",
"K8953e0a6": "手机号",
"K6f86a038": "身份证号",
"K7954e7c8": "银行卡号",
"K320fdb17": "金额",
"K7867acda": "日期",
"K7d327ae8": "局部显示",
"Kfbf38e3c": "局部遮蔽",
"Kd8c1fbb0": "截取",
"K89829921": "替换",
"K480a7165": "乱序",
"Kea0d69df": "随机字符串",
"Ke7c84d1d": "自定义字符串",
"K49731763": "请输入IP地址或CIDR范围,每条以换行分割",
"K3a34d49b": "待更新",
"Kd2850420": "待删除",
"K83237c89": "输入的IP或CIDR不符合格式",
"K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*",
"K508d8bf4": "集成地址",
"K67f4e9bb": "与外部平台集成时,获取 API 市场中文档信息的域名",
"Kc82b8374": "编辑策略",
"K4b34a5e5": "策略类型",
"K57f0fee8": "匹配条件",
"K10650c58": "数据脱敏规则",
"K1b34a9ab": "配置脱敏规则",
"K26d22405": "匹配值",
"K1546e1fe": "脱敏类型",
"K9b9b0629": "起始位置",
"K52c84fe1": "长度",
"Kde84409c": "替换类型",
"K338653b4": "替换值",
"Kbaeed3b7": "JSON Path",
"K4cd91d61": "脱敏规则",
"K8dcad979": "自定义字符串; 值:",
"K82e3f7b7": "起始位置:(0)位;长度:(1)位",
"K49dfc123": "已选择(0)项(1)数据",
"K8457ea34": "所有(0)",
"K7ca9a795": "属性名称",
"Kc4391744": "属性值",
"K678e13fc": "配置(0)",
"Kf5fd27ed": "输入名称查找用户"
}
@@ -1,3 +1 @@
{
"Kf5fd27ed": "输入名称查找用户"
}
{}
@@ -1,4 +1,16 @@
{
"Kc0e5ef9f": "Workspace",
"K4de11e23": "Home",
"K61c89f5f": "API Portal",
"K3fe97dcc": "System Settings",
"Kecbb0e45": "System",
"Ka358e23d": "General",
"K449058e9": "API Gateway",
"K99935e6f": "AI Model",
"K1deaa2dd": "User",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"K5cfdd950": "This data cannot be recovered after deletion. Are you sure you want to delete?",
"Kb9052305": "Search Username, Email",
"K40a89bd8": "Enter Name, ID to Search Member"
}
@@ -1,10 +1,22 @@
{
"Kc0e5ef9f": "Workspace",
"K4de11e23": "Home",
"Kfe93ef35": "Application",
"K61c89f5f": "API Portal",
"K3fe97dcc": "設定",
"Kecbb0e45": "システム",
"Ka358e23d": "一般",
"K449058e9": "API ゲートウェイ",
"K99935e6f": "AI モデル",
"K1deaa2dd": "ユーザー",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "無審査:すべてのアプリケーションがこのサービスにサブスクライブできます",
"K8a8b13e4": "手動審査:承認されたアプリケーションのみがこのサービスにサブスクライブできます",
"K9bdd8403": "API を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。",
"Kc8239422": "チームにはユーザー、アプリケーション、サービスが含まれ、異なるチームのアプリケーションとサービスのデータは分離されています。企業内の部門/プロジェクトグループ/チームの管理に使用できます。",
"Ka0a8840a": "他のアプリケーションのサブスクリプション申請をレビューし、承認後に API リクエストが発行できます。",
"K5cfdd950": "このデータを削除すると、復元できません。削除しますか?",
"Kb9052305": "ユーザー名またはメールを検索",
"K5ece3bac": "チームとメンバーを設定してから、チーム内でサービスとアプリケーションを作成し、API をサブスクライブできます。メンバーは所属チーム内のサービスとアプリケーションのみを表示できます。",
"K1512e983": "アプリケーション呼び出し統計",
@@ -1,10 +1,22 @@
{
"Kc0e5ef9f": "工作空间",
"K4de11e23": "首页",
"Kfe93ef35": "消费者",
"K61c89f5f": "API 门户",
"K3fe97dcc": "系统设置",
"Kecbb0e45": "系统",
"Ka358e23d": "常规",
"K449058e9": "API 网关",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用户",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "无审核:允许所有消费者订阅该服务",
"K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务",
"K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。",
"Kc8239422": "团队中包含了人员、消费者和服务,不同团队之间的消费者和服务数据是隔离的,可用于管理企业内部不同的部门/项目组/团队。",
"Ka0a8840a": "审核其他消费者的订阅申请,审核通过后的才可发起 API 请求。",
"K5cfdd950": "该数据删除后将无法找回,是否删除?",
"Kb9052305": "搜索用户名、邮箱",
"K5ece3bac": "设置团队和成员,然后你可以在团队内创建服务和消费者、订阅API,成员只能看到所属团队内的服务和消费者。",
"K1512e983": "消费者调用统计",
@@ -1,10 +1,22 @@
{
"Kc0e5ef9f": "工作區",
"K4de11e23": "主頁",
"Kfe93ef35": "應用程式",
"K61c89f5f": "API 門戶",
"K3fe97dcc": "系統設置",
"Kecbb0e45": "系統",
"Ka358e23d": "常規",
"K449058e9": "API 網關",
"K99935e6f": "AI 模型",
"K1deaa2dd": "用戶",
"K631d646f": "Open API",
"K1196b104": "APIPark",
"Kffd7e274": "無審核:允許所有應用程式訂閱該服務",
"K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務",
"K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。",
"Kc8239422": "團隊中包含了人員、應用程式和服務,不同團隊之間的應用程式和服務數據是隔離的,可用於管理企業內部不同的部門/項目組/團隊。",
"Ka0a8840a": "審核其他應用程式的訂閱申請,審核通過後的才可發起 API 請求。",
"K5cfdd950": "該數據刪除後將無法找回,是否刪除?",
"Kb9052305": "搜索用戶名、電郵",
"K5ece3bac": "設置團隊和成員,然後你可以在團隊內創建服務和應用程式、訂閱API,成員只能看到所屬團隊內的服務和應用程式。",
"K1512e983": "應用程式調用統計",
@@ -725,5 +725,51 @@
"K31faa2a1": "優先級",
"Kbdec9fa": "篩選條件",
"Kbcbb7391": "處理數",
"Kad207008": "編輯"
"Kad207008": "編輯",
"K630c9e6d": "APIPark",
"Ka3e9f580": "發布名稱",
"Kb2480682": "策略列表",
"K118d8d74": "數據格式",
"Kfe7c7d2d": "關鍵字",
"K2f57a694": "正則表達式",
"K8953e0a6": "手機號碼",
"K6f86a038": "身份證號碼",
"K7954e7c8": "銀行卡號碼",
"K320fdb17": "金額",
"K7867acda": "日期",
"K7d327ae8": "局部顯示",
"Kfbf38e3c": "局部遮蔽",
"Kd8c1fbb0": "截取",
"K89829921": "替換",
"K480a7165": "亂序",
"Kea0d69df": "隨機字串",
"Ke7c84d1d": "自訂字串",
"K49731763": "請輸入IP地址或CIDR範圍,每行以換行符分隔",
"K83237c89": "輸入的IP或CIDR格式不正確",
"K5ae2c87a": "請正確輸入路徑,例如/usr/*或*/usr/*",
"Kc82b8374": "編輯策略",
"K4b34a5e5": "策略類型",
"K57f0fee8": "匹配條件",
"K10650c58": "數據脫敏規則",
"K1b34a9ab": "配置脫敏規則",
"K26d22405": "匹配值",
"K1546e1fe": "脫敏類型",
"K9b9b0629": "起始位置",
"K52c84fe1": "長度",
"Kde84409c": "替換類型",
"K338653b4": "替換值",
"Kbaeed3b7": "JSON路徑",
"K4cd91d61": "脫敏規則",
"K8dcad979": "自訂字串; 值:",
"K82e3f7b7": "起始位置:(0)位;長度:(1)位",
"K49dfc123": "已選擇(0)項(1)數據",
"K8457ea34": "所有(0)",
"K7ca9a795": "屬性名稱",
"Kc4391744": "屬性值",
"K678e13fc": "配置(0)",
"K508d8bf4": "集成地址",
"K67f4e9bb": "與外部平台集成時,用於獲取 API 市場文檔信息的域名",
"K1da86266": "無效",
"K3a34d49b": "待更新",
"Kd2850420": "待刪除"
}
@@ -1,25 +1,34 @@
import { Icon } from "@iconify/react/dist/iconify.js";
import { MenuProps } from "antd";
export type MenuItem = Required<MenuProps>['items'][number];
export function getNavItem(
label: React.ReactNode,
key: React.Key,
path:string,
icon?: React.ReactNode,
children?: MenuItem[],
type?: 'group',
access?:string[] | string
): MenuItem {
export function getNavItem({
label,
key,
path,
icon,
children,
type,
access
}: {
label: React.ReactNode;
key: React.Key;
path?: string;
icon?: React.ReactNode;
children?: MenuItem[];
type?: 'group';
access?: string[] | string;
}): MenuItem {
return {
key,
icon :icon ,
path,
icon,
routes:children,
name:label,
type,
access
access,
path
} as MenuItem;
}
@@ -53,4 +62,19 @@ export function getItem(
label,
access
}
}
}
export function transformMenuData(data: any[]): MenuItem[] {
return data.map(item => {
const { name, key, path, icon, children, access } = item;
return getNavItem({
label:name,
key,
path,
icon: icon ? <Icon icon={icon} width="18" height="18" /> : undefined,
children:children ? transformMenuData(children) : undefined,
access}
);
});
}
@@ -0,0 +1,154 @@
import { 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';
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 function generateRemoteModuleTemplate(
pluginName: string,
exposedModule: string,
pluginPath: string
):RemoteModuleConfig {
return {
type: 'module',
remoteEntry: pluginPath,
exposedModule,
remoteName: pluginName
}
}
/** check the lifecycle method of plugin */
export function validateExportLifecycle(exports: unknown) {
const { bootstrap, mount, unmount } = exports ?? {}
return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount)
}
export const DEFAULT_LOCAL_PLUGIN_PATH = '/plugin-frontend/'
export const ApiparkPluginDriver = (routerMap: Map<string, RouterMapConfig>): 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[]
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}
]
})
}
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
}
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)
}
return coreObj
}
// extender: (coreObj?:CoreObj, 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 loadedModulePage = loadedModule[pluginRouter.expose!]
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) => <LazyComponent {...props} /> }),
pathPrefix: pluginRouter.path.endsWith('/*') ? pluginRouter.path.slice(0, -2) : pluginRouter.path,
lifecycle:{
canActivate: loadedModule?.beforeMount,
canLoad: loadedModule?.mount,
canDeactivate: loadedModule?.beforeUnmount,
deactivated: loadedModule?.unmount
}
};
coreObj.pluginProvider.setRouterConfig(pluginRouter.type === 'root' , newRouter)
}
}
}
@@ -4,5 +4,38 @@ export const validateUrlSlash = (_, value) => {
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 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();
};
+7
View File
@@ -299,4 +299,11 @@ a{
a[disabled]:hover {
color: #BBB;
cursor: not-allowed;
}
.ant-input-group-addon{
height:32px !important;
.ant-btn.ant-btn-default{
height:32px !important;
}
}
+28 -40
View File
@@ -1,30 +1,16 @@
import './App.css'
import { ConfigProvider, ConfigProviderProps, Radio, RadioChangeEvent } from 'antd';
import { ConfigProvider, App as AppAntd } from 'antd';
import RenderRoutes from '@core/components/aoplatform/RenderRoutes';
import {BreadcrumbProvider} from "@common/contexts/BreadcrumbContext.tsx";
import { StyleProvider } from '@ant-design/cssinjs';
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 useInitializeMonaco from "@common/hooks/useInitializeMonaco";
import { useEffect, useMemo, useState } from 'react';
import 'dayjs/locale/zh-cn';
import 'dayjs/locale/zh-tw';
import 'dayjs/locale/ja';
import dayjs from 'dayjs';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { useMemo } from 'react';
import { GlobalProvider } from '@common/contexts/GlobalStateContext';
import { $t } from '@common/locales';
type Locale = ConfigProviderProps['locale'];
const languageMap: Record<string, Locale> = {
'zh-CN':zhCN,
'en-US':enUS,
'zh-TW':zhTW,
'ja-JP':jaJP
}
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 = {
@@ -150,33 +136,35 @@ const antdComponentThemeToken = {
function App() {
const [locale, setLocal] = useState<Locale>();
const { locale } = useLocaleContext();
useInitializeMonaco()
const { state} = useGlobalContext()
useEffect(() => {
dayjs.locale(state.language);
setLocal(languageMap[state.language]);
},[state.language])
const validateMessages = useMemo(()=>({
required: $t('必填项'),
email:$t('不是有效邮箱地址')}
),[state.language])
),[locale])
return (
<StyleProvider hashPriority={"high"}>
<ConfigProvider
locale={locale}
wave={{disabled:true}}
theme={antdComponentThemeToken}
form={{validateMessages }}>
<BreadcrumbProvider>
<RenderRoutes />
</BreadcrumbProvider>
</ConfigProvider>
</StyleProvider>
<StyleProvider hashPriority={"high"}>
<ConfigProvider
locale={locale}
wave={{disabled:true}}
theme={antdComponentThemeToken}
form={{validateMessages }}>
<PluginEventHubProvider>
<AppAntd className="h-full" message={{ maxCount: 1 }}>
<PluginSlotHubProvider>
<GlobalProvider>
<BreadcrumbProvider>
<RenderRoutes />
</BreadcrumbProvider>
</GlobalProvider>
</PluginSlotHubProvider>
</AppAntd>
</PluginEventHubProvider>
</ConfigProvider>
</StyleProvider>
);
}
@@ -1,582 +1,95 @@
import { BrowserRouter as Router, Routes, Route, Navigate, Outlet } from 'react-router-dom';
import Login from "@core/pages/Login.tsx"
import { Navigate, RouterProvider, createBrowserRouter, RouteObject } from 'react-router-dom';
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 { TeamProvider } from '@core/contexts/TeamContext.tsx';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext.tsx';
import Guide from '@core/pages/guide/Guide';
import { AiServiceProvider } from '@core/contexts/AiServiceContext';
import AiServiceOutlet from '@core/pages/aiService/AiServiceOutlet';
import SystemOutlet from '@core/pages/system/SystemOutlet';
import { SystemProvider } from '@core/contexts/SystemContext';
import {createElement,Suspense, useEffect, useState} from 'react';
import {Skeleton, Spin} from "antd";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import usePluginLoader from '@common/hooks/pluginLoader.ts';
import { RouteConfig } from '@common/const/type.ts';
import { ApiparkPluginDriver } from '@common/utils/plugin.tsx';
import { routerMap } from '@core/const/const';
import withRouteGuard from "@common/components/aoplatform/WithRouteGuard.tsx";
import ErrorBoundary from "@common/components/aoplatform/ErrorBoundary";
import React from 'react';
import { LoadingOutlined } from '@ant-design/icons';
type RouteConfig = {
path: string
component?: ReactElement
children?: (RouteConfig | false)[]
key: string
provider?: FC<{ children: ReactNode; }>
lazy?: unknown
}
const APP_MODE = import.meta.env.VITE_APP_MODE;
const RenderRoutes = ()=> {
const { loadPlugins,loadExecutedPlugin } = usePluginLoader(ApiparkPluginDriver(routerMap), routerMap)
const { routeConfig,dispatch,state } = useGlobalContext();
const [router, setRouter] = useState<unknown>(null);
useEffect(()=>{
loadPlugins().then(()=>{
loadExecutedPlugin()
})
},[])
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
}
useEffect(() => {
if (routeConfig && routeConfig.length > 0) {
const routerInstance = createBrowserRouter(generateRoutes(routeConfig));
setRouter(routerInstance);
dispatch({ type: 'SET_PLUGINS_LOADED', pluginsLoaded: true });
}
}, [routeConfig]);
const PUBLIC_ROUTES: RouteConfig[] = [
{
path: '/',
component: <Login />,
key: uuidv4(),
},
{
path: '/login',
component: <Login />,
key: uuidv4()
},
{
path: '/',
component: <ProtectedRoute />,
key: uuidv4(),
children: [
// {
// path:'approval/*',
// component:<ApprovalPage />,
// key:uuidv4()
// },
{
path: 'guide/*',
component: <Guide />,
key: uuidv4()
},
{
path: 'team',
component: <Outlet />,
key: uuidv4(),
provider: TeamProvider,
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
},
{
path: 'inside/:teamId',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
key: uuidv4(),
children: [
{
path: 'member',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
},
{
path: 'setting',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
},
]
}
]
},
{
path: 'service',
key: uuidv4(),
component: <SystemOutlet />,
provider: SystemProvider,
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path: 'list/:teamId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path: ':teamId',
component: <Outlet />,
key: uuidv4(),
children: [
{
path: 'inside/:serviceId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children: [
{
path: 'api',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path: 'route/create',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path: 'route/:routeId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path: 'route',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')),
},
{
path: 'upstream',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
},
{
path: 'document',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
},
{
path: 'subscriber',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
children: [
]
},
{
path: 'approval',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
children: [
{
path: '',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
},
{
path: '*',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
}
]
},
{
path: 'topology',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemTopology.tsx')),
key: uuidv4(),
children: [
]
},
{
path: 'publish',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children: [
{
path: '',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
},
{
path: '*',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
}
]
},
{
path: 'setting',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children: [
]
},
{
path: 'servicePolicy',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/servicePolicy'))
}
]
},
{
path: 'aiInside/:serviceId',
component: <AiServiceOutlet />,
provider: AiServiceProvider,
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')),
children: [
{
path: 'api',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideApiDocument')),
},
{
path: 'route/create',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path: 'route/:routeId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path: 'route',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterList')),
},
{
path: 'document',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideDocument.tsx')),
},
{
path: 'subscriber',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideSubscriber.tsx')),
children: [
]
},
{
path: 'approval',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApproval')),
children: [
{
path: '',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
},
{
path: '*',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
}
]
},
{
path: 'publish',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublish')),
children: [
{
path: '',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
},
{
path: '*',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
}
]
},
{
path: 'setting',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children: [
]
},
]
}
]
}
]
},
{
path: 'datasourcing',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')),
},
{
path: 'cluster',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
},
{
path: 'aisetting',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiSetting/AiSettingList.tsx')),
},
{
path: 'cert',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
},
{
path: 'globalPolicy',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/globalPolicy')),
},
{
path: 'serviceHub',
component: <Outlet />,
key: uuidv4(),
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
},
{
path: 'detail/:serviceId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
}]
},
{
path: 'commonsetting',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
key: uuidv4(),
},
{
path: 'consumer',
component: <Outlet />,
provider: TenantManagementProvider,
key: uuidv4(),
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: ':teamId/inside/:appId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
children: [
{
path: 'service',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
},
{
path: 'authorization',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
},
{
path: 'setting',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
},
]
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
{
path: 'list/:teamId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
]
},
{
path: 'member',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
},
{
path: 'list/:memberGroupId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
}
]
},
{
path: 'role',
key: uuidv4(),
component: <Outlet />,
children: [
{
path: '',
key: uuidv4(),
component: <Navigate to="list" />
},
{
path: 'list',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
}, {
path: ':roleType/config/:roleId',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}, {
path: ':roleType/config',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}
]
},
{
path: 'assets',
component: <p></p>,
key: uuidv4()
},
{
path: 'analytics',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key: uuidv4(),
children: [
{
path: 'total',
key: uuidv4(),
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardTotal.tsx')),
},
]
},
{
path: 'template/:moduleId',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key: uuidv4()
},
{
path: 'logsettings/*',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
key: uuidv4(),
children: [{
path: 'template/:moduleId',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key: uuidv4()
}]
},
APP_MODE === 'pro' && {
path: 'resourcesettings/*',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
key: uuidv4(),
children: [{
path: 'template/:moduleId',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key: uuidv4()
}]
},
{
path: 'userProfile/*',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/UserProfile.tsx')),
key: uuidv4(),
children: [{
path: 'changepsw',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/ChangePsw.tsx')),
key: uuidv4()
}]
}
]
},
]
const RenderRoutes = () => {
return (
<App className="h-full" message={{ maxCount: 1 }}>
<Router>
<Routes>
{generateRoutes(PUBLIC_ROUTES)}
</Routes>
</Router>
</App>
)
}
const generateRoutes = (routerConfig: RouteConfig[]) => {
return routerConfig?.map((route: RouteConfig) => {
let routeElement;
if (route.lazy) {
const LazyComponent = route.lazy as React.ExoticComponent<unknown>;
routeElement = (
<Suspense fallback={<div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <LazyComponent />)
) : (
<LazyComponent />
)}
</Suspense>
);
} else {
routeElement = route.provider ? (
createElement(route.provider, {}, route.component)
) : (
route.component
);
if (!router || !state?.pluginsLoaded) {
return <Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={true} className='w-full h-full flex items-center justify-center'></Spin>;
}
return (
<Route
key={route.key}
path={route.path}
element={routeElement}
>
{route.children && generateRoutes(route.children as RouteConfig[])}
</Route>
<RouterProvider router={router} />
);
}
)
}
const generateRoutes = (routerConfig: RouteConfig[]):RouteObject[] => {
return routerConfig?.map((route: RouteConfig) => {
let routeElement;
if (route.lazy) {
let LazyComponent;
if (typeof route.lazy === 'function') {
const result = route.lazy();
if (result instanceof Promise) {
LazyComponent = React.lazy(() => result.then(module => ({ default: module.default || module })));
} else {
LazyComponent = result;
}
} else {
LazyComponent = route.lazy;
}
const GuardedComponent = route.lifecycle ? withRouteGuard(LazyComponent, {pathPrefix:`/${route.pathPrefix ?? route.path}`, ...route.lifecycle}): LazyComponent
routeElement = (
<Suspense fallback={ <div className=''><Skeleton className='m-btnbase w-calc-100vw-minus-padding-r' active /></div>}>
{route.provider ? (
createElement(route.provider, {}, <GuardedComponent />)
) : (
<GuardedComponent />
)}
</Suspense>
);
} else {
routeElement = route.provider ? (
createElement(route.provider, {}, route.component)
) : (
route.component
);
}
return (
{
path: route.path,
element: <ErrorBoundary>{routeElement}</ErrorBoundary> ,
children: route.children ? generateRoutes(route.children as RouteConfig[]) : undefined,
exact:route?.pathMatch === 'full'
}
);
}
)
}
// 保护的路由组件
function ProtectedRoute() {
const { state } = useGlobalContext()
return state.isAuthenticated ? <BasicLayout project="core" /> : <Navigate to="/login" />;
}
export function ProtectedRoute() {
// return state.isAuthenticated? <BasicLayout project="core" /> : <Navigate to="/login" />;
return <BasicLayout project="core" />
}
export default RenderRoutes
export default RenderRoutes
+533
View File
@@ -0,0 +1,533 @@
import component from '@common/components/aoplatform/prompt-editor/plugins/workflow-variable-block/component';
import { RouterMapConfig } from '@common/const/type';
import { ProtectedRoute } from '@core/components/aoplatform/RenderRoutes';
import { AiServiceProvider } from '@core/contexts/AiServiceContext';
import { SystemProvider } from '@core/contexts/SystemContext';
import { TeamProvider } from '@core/contexts/TeamContext';
import AiServiceOutlet from '@core/pages/aiService/AiServiceOutlet';
import Guide from '@core/pages/guide/Guide';
import Login from '@core/pages/Login';
import ServicePolicyLayout from '@core/pages/policy/ServicePolicyLayout';
import SystemOutlet from '@core/pages/system/SystemOutlet';
import { TenantManagementProvider } from '@market/contexts/TenantManagementContext';
import { lazy } from 'react';
import { Outlet, Navigate } from 'react-router-dom';
// 内置插件与对应组件/模块
export const routerMap:Map<string, RouterMapConfig> = new Map([
['basicLayout', { type: 'component', component: <ProtectedRoute />}],
['navHidden', { type: 'component', component: <ProtectedRoute /> }],
['login', { type: 'component', component: <Login /> }],
['guide',{
type:'component',
component:<Guide />
}],
['team', {type: 'module',
component:<Outlet/>,
key: 'team',
provider: TeamProvider,
children:[
{
path:'',
key: 'teamList',
component: <Navigate to="list" />
},
{
path:'list',
key: 'teamList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamList.tsx'))
},
{
path:'inside/:teamId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsidePage.tsx')),
key: 'teamInside',
children:[
{
path:'member',
key: 'teamMember',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamInsideMember.tsx')),
},
{
path:'setting',
key: 'teamSetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/team/TeamConfig.tsx')),
},
]
}
]
}],
['service', {
type: 'module',
path:'service',
component:<SystemOutlet />,
key: 'service',
provider: SystemProvider,
children:[
{
path:'',
key:'serviceList',
component:<Navigate to="list" />
},
{
path:'list',
key: 'serviceList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:'list/:teamId',
key: 'serviceList3',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemList.tsx')),
},
{
path:':teamId',
component:<Outlet/>,
key: 'serviceInside',
children:[
{
path:'inside/:serviceId',
key: 'restServiceInside',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children:[
{
path:'api',
key: 'restServiceInsideApi',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')),
},
{
path:'route/create',
key: 'restServiceInsideRouteCreate',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path:'route/:routeId',
key: 'restServiceInsideRouteEdit',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterCreate')),
},
{
path:'route',
key: 'restServiceInsideRoute',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')),
},
{
path:'upstream',
key: 'restServiceInsideUpstream',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/upstream/SystemInsideUpstreamContent.tsx')),
},
{
path:'document',
key: 'restServiceInsideDocument',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideDocument.tsx')),
},
{
path:'subscriber',
key: 'restServiceInsideSubscriber',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: 'restServiceInsideApproval',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApproval.tsx')),
children:[
{
path:'',
key: 'restServiceInsideApprovalList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
},
{
path:'*',
key: 'restServiceInsideApprovalList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/approval/SystemInsideApprovalList.tsx')),
}
]
},
{
path:'publish',
key: 'systemPublish',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublish.tsx')),
children:[
{
path:'',
key: 'systemPublishList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
},
{
path:'*',
key: 'systemPublishList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/publish/SystemInsidePublishList.tsx')),
}
]
},
{
path:'setting',
key: 'systemConfig',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
{
path:'servicepolicy',
key: 'servicepolicy',
component:<ServicePolicyLayout />,
children:[{
path:'datamasking',
component:<Outlet />,
key:'dataMasking',
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/servicePolicy')),
key:'dataMaskingList'
},
{
path:'create',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
},
{
path:':policyId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
}]
}
]
},
]
},
{
path:'aiInside/:serviceId',
component:<AiServiceOutlet />,
provider: AiServiceProvider,
key: 'aiServiceInside',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')),
children:[
{
path:'api',
key: 'aiServiceInsideApi',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideApiDocument')),
},
{
path:'route/create',
key: 'aiServiceInsideRouteCreate',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route/:routeId',
key: 'aiServiceInsideRouteEdit',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterCreate')),
},
{
path:'route',
key: 'aiServiceInsideRouteList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/api/AiServiceInsideRouterList')),
},
{
path:'document',
key: 'aiServiceInsideDocument',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideDocument.tsx')),
},
{
path:'subscriber',
key: 'aiServiceInsideSubscriber',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsideSubscriber.tsx')),
children:[
]
},
{
path:'approval',
key: 'aiServiceInsideApproval',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApproval')),
children:[
{
path:'',
key: 'aiServiceInsideApprovalList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
},
{
path:'*',
key: 'aiServiceInsideApprovalList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/approval/AiServiceInsideApprovalList')),
}
]
},
{
path:'publish',
key: 'aiServiceInsidePublish',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublish')),
children:[
{
path:'',
key: 'aiServiceInsidePublishList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
},
{
path:'*',
key: 'aiServiceInsidePublishList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/publish/AiServiceInsidePublishList')),
}
]
},
{
path:'setting',
key: 'aiServiceInsideSetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemConfig.tsx')),
children:[
]
},
{
path:'servicepolicy',
key: 'servicepolicy',
component:<ServicePolicyLayout />,
children:[{
path:'datamasking',
component:<Outlet />,
key:'dataMasking',
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/servicePolicy')),
key:'dataMaskingList'
},
{
path:'create',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
},
{
path:':policyId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
}]
}
]
},
]
}
]
}
]
}],
['datasourcing', { type: 'component',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx'))
}],
['cluster', { type: 'component',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCluster.tsx')),
}],
['aisetting', { type: 'component',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiSetting/AiSettingList.tsx')),
}],
['cert', { type: 'component',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideCert.tsx')),
}],
['serviceHub', {
type: 'module',
component:<Outlet />,
key:'serviceHub',
children:[
{
path:'',
key: 'serviceHubList',
component: <Navigate to="list" />
},
{
path:'list',
key:'serviceHubList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubList.tsx')),
},
{
path:'detail/:serviceId',
key:'serviceHubDetail',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/ServiceHubDetail.tsx')),
}]
}],
['commonsetting', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/common/CommonPage.tsx')),
}],
['consumer', { type: 'module',
component:<Outlet />,
provider:TenantManagementProvider,
key:'consumer',
children:[
{
path:'',
key:'consumerList',
component:<Navigate to="list" />
},
{
path:':teamId/inside/:appId',
key:'consumerInside',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsidePage.tsx')),
children:[
{
path:'service',
key:'consumerInsideService',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideService.tsx')),
},
{
path:'authorization',
key:'consumerInsideAuthorization',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementInsideAuth.tsx')),
},
{
path:'setting',
key:'consumerSetting',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ManagementAppSetting.tsx')),
},
]
},
{
path:'list',
key:'serviceHubManagementList',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
{
path:'list/:teamId',
key:'serviceHubManagementList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@market/pages/serviceHub/management/ServiceHubManagement.tsx')),
},
]}],
['member', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberPage.tsx')),
children:[
{
path:'',
key:'memberList',
component:<Navigate to="list" />
},
{
path:'list',
key:'memberList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
},
{
path:'list/:memberGroupId',
key:'memberList3',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/member/MemberList.tsx')),
}
],
}],
['role', { type: 'module',
component:<Outlet></Outlet>,
children:[
{
path: '',
key: 'roleList',
component: <Navigate to="list" />
},
{
path:'list',
key:'roleList2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleList.tsx')),
},
{
path:':roleType/config/:roleId',
key:'roleConfig',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
},
{
path:':roleType/config',
key:'roleConfig2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/role/RoleConfig.tsx')),
}
]
}],
['analytics', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/Dashboard.tsx')),
key:'analytics',
children:[
{
path:'total',
key:'analytics2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardTotal.tsx')),
},
{
path:':dashboardType',
key:'analytics3',
component:<Outlet />,
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardList.tsx')),
key:'analyticsList'
},
{
path:'detail/:dashboardDetailId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardDetail.tsx')),
key:'analyticsDetail'
},
]
}
]
}],
['template', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
}],
['logsettings', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/logsettings/LogSettings.tsx')),
key:'logsettings',
children:[
{
path:'template/:moduleId',
key:'logSettings2',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
},
]
}],
['resourcesettings', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/resourcesettings/ResourceSettings.tsx')),
key:'resourcesettings',
children:[
{
path:'template/:moduleId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@common/components/aoplatform/intelligent-plugin/IntelligentPluginList.tsx')),
key:'resourceSettings2'
},
]
}],
['userProfile', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/UserProfile.tsx')),
key:'userProfile',
children:[{
path:'changepsw',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/userProfile/ChangePsw.tsx')),
key:'changePsw'
}]}],
['globalPolicy', { type: 'module',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/GlobalPolicyLayout')),
key:'globalPolicy',
children:[{
path:'datamasking',
component:<Outlet />,
key:'dataMasking',
children:[
{
path:'list',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/globalPolicy')),
key:'dataMaskingList'
},
{
path:'create',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
},
{
path:':policyId',
lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/dataMasking/DataMaskingConfig')),
key:'dataMaskingAdd'
}]
}]
}],
])
@@ -3,9 +3,10 @@ import { Input, TabsProps } from "antd";
import { MatchItem } from "@common/const/type";
import { ConfigField } from "@common/components/aoplatform/EditableTableWithModal";
import { frontendTimeSorter } from "@common/utils/dataTransfer";
import { COLUMNS_TITLE } from "@common/const/const";
import { COLUMNS_TITLE, PLACEHOLDER } from "@common/const/const";
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { $t } from "@common/locales";
export enum SubscribeEnum{
Rejected = 0,
@@ -222,7 +223,7 @@ export const MATCH_CONFIG:ConfigField<MatchItem>[] = [
}, {
title:('参数名'),
key: 'key',
component: <Input className="w-INPUT_NORMAL" />,
component: <Input className="w-INPUT_NORMAL"/>,
renderText: (value: unknown) => value,
required: true
}, {
@@ -236,7 +237,7 @@ export const MATCH_CONFIG:ConfigField<MatchItem>[] = [
title:('参数值'),
key: 'pattern',
unRender:(formValue)=>{return formValue?.matchType === 'NULL' || formValue?.matchType==='EXIST' || formValue?.matchType === 'UNEXIST'},
component: <Input className="w-INPUT_NORMAL"/>,
component: <Input className="w-INPUT_NORMAL" />,
renderText: (value: string) => {
return value
},
@@ -1,23 +0,0 @@
import {FC, createContext, useContext, useState, ReactNode } from 'react';
import { PartitionConfigFieldType } from '../const/partitions/types.ts';
interface PartitionContextProps {
partitionInfo:PartitionConfigFieldType|undefined
setPartitionInfo:React.Dispatch<React.SetStateAction<PartitionConfigFieldType|undefined>>;
}
const PartitionContext = createContext<PartitionContextProps | undefined>(undefined);
export const usePartitionContext = () => {
const context = useContext(PartitionContext);
if (!context) {
throw new Error('useArray must be used within a ArrayProvider');
}
return context;
};
export const PartitionProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [partitionInfo, setPartitionInfo] = useState<PartitionConfigFieldType>()
return <PartitionContext.Provider value={{ partitionInfo, setPartitionInfo }}>{children}</PartitionContext.Provider>;
};
@@ -1,23 +0,0 @@
import { createContext, useContext, useState, ReactNode, FC } from 'react';
import { MyServiceFieldType } from '../const/system/type.ts';
interface SystemMyServiceContextProps {
serviceInfo:MyServiceFieldType|undefined
setServiceInfo:React.Dispatch<React.SetStateAction<MyServiceFieldType|undefined>>;
}
const SystemMyServiceContext = createContext<SystemMyServiceContextProps | undefined>(undefined);
export const useSystemMyServiceContext = () => {
const context = useContext(SystemMyServiceContext);
if (!context) {
throw new Error('useArray must be used within a ArrayProvider');
}
return context;
};
export const SystemMyServiceProvider: FC<{ children: ReactNode }> = ({ children }) => {
const [serviceInfo, setServiceInfo] = useState<MyServiceFieldType>()
return <SystemMyServiceContext.Provider value={{ serviceInfo, setServiceInfo }}>{children}</SystemMyServiceContext.Provider>;
};
+8 -3
View File
@@ -321,6 +321,11 @@ p{
}
}
.ant-table-body {
scrollbar-width: auto;
scrollbar-color: auto;
}
::-webkit-scrollbar {
width: 10px;
@@ -664,7 +669,7 @@ p{
.ant-pro-table-list-toolbar-setting-items{
position:absolute;
top:18px;
right:16px;
right:22px;
z-index:9;
.ant-pro-table-list-toolbar-setting-item{
font-size:14px;
@@ -970,7 +975,7 @@ p{
.ant-drawer-content-wrapper{
min-width: 820px !important;
.ant-table:not(.ant-table-bordered){
border:1px solid var(--border-color) !important;
/* border:1px solid var(--border-color) !important; */
border-top:0px !important;
}
.ant-table:not(.ant-table-borderer){
@@ -996,7 +1001,7 @@ p{
.ant-modal-wrap:not(.height-fixed-modal){
.ant-modal-body{
.ant-table:not(.ant-table-bordered){
border:1px solid var(--border-color) !important;
/* border:1px solid var(--border-color) !important; */
border-top:0px !important;
}
.ant-table:not(.ant-table-borderer){
+3 -4
View File
@@ -3,8 +3,7 @@ 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 { LocaleProvider } from '@common/contexts/LocaleContext.tsx';
async function initializeApp() {
try {
// 初始化行为
@@ -13,9 +12,9 @@ async function initializeApp() {
// 异步操作完成后,渲染React消费者
ReactDOM.createRoot(document.getElementById('root')!).render(
<StrictMode>
<GlobalProvider>
<LocaleProvider>
<App />
</GlobalProvider>
</LocaleProvider>
</StrictMode>,
);
} catch (error) {
+9 -5
View File
@@ -1,5 +1,5 @@
import {FC, useCallback, useEffect, useRef, useState} from "react";
import {App, Button, Divider, Form, FormInstance, Input, Tooltip} from "antd";
import {App, Button, Divider, Form, FormInstance, Input, Spin, Tooltip} from "antd";
import {useGlobalContext} from "@common/contexts/GlobalStateContext.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {BasicResponse, STATUS_CODE} from "@common/const/const.tsx";
@@ -9,6 +9,7 @@ import Logo from '@common/assets/layout-logo.png'
import { $t } from "@common/locales";
import { Icon } from "@iconify/react/dist/iconify.js";
import LanguageSetting from "@common/components/aoplatform/LanguageSetting";
import { LoadingOutlined } from "@ant-design/icons";
const Login:FC = ()=> {
const {state, dispatch} = useGlobalContext()
@@ -17,20 +18,21 @@ const Login:FC = ()=> {
const navigate = useNavigate();
const formRef = useRef<FormInstance>(null);
const [loading,setLoading] = useState<boolean>()
// const { encryptByEnAES } = useCrypto();
const [allowGuest, setAllowGuest] = useState<boolean>(false)
const [spinning,setSpinning] = useState<boolean>(false)
const check = useCallback(()=>{
state.isAuthenticated &&setSpinning(true)
fetchData<BasicResponse<{channel:Array<{name:string}>, status:string}>>('account/login',{method:'GET'}).then(response=>{
const {code,data} = response
if(code === STATUS_CODE.SUCCESS && data.status !== 'anonymous'){
dispatch({type:'LOGIN'})
navigate(state.mainPage)
navigate(state.mainPage,{replace:true})
}else{
dispatch({type:'LOGOUT'})
setAllowGuest(data.channel.filter(x=>x.name === 'guest_access').length > 0)
setSpinning(false)
}
})
},[])
@@ -100,6 +102,8 @@ const Login:FC = ()=> {
}, []);
return (
spinning?
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={spinning} className='w-full h-full flex items-center justify-center'></Spin> :
<div className="h-full w-full flex flex-col items-center overflow-auto min-h-[490px] bg-[#0d1117]">
<div id="glow-background" className="background-container">
<svg className="background-pattern" aria-hidden="true">
@@ -206,7 +210,7 @@ const Login:FC = ()=> {
<section className="flex flex-col items-center mt-[46px] text-SECOND_TEXT">
<p className="leading-[28px]">
{$t('Version (0)-(1)',[state.version,state.updateDate])}, {$t(state.powered)}
{$t('Version (0)-(1)',[state?.version,state?.updateDate])}, {$t(state?.powered || '-')}
</p>
<LanguageSetting mode="light"/>
</section>
+13
View File
@@ -0,0 +1,13 @@
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
import { Navigate, useLocation } from "react-router-dom"
export default function Root() {
const { state } = useGlobalContext()
const location = useLocation()
return (<>
{
state?.isAuthenticated && !location?.pathname
?<Navigate to={location?.pathname ?? state?.mainPage} />:<Navigate to="/login" />
}</>
)
}
@@ -19,7 +19,7 @@ const APP_MODE = import.meta.env.VITE_APP_MODE;
const AiServiceInsidePage:FC = ()=> {
const { message } = App.useApp()
const { teamId,serviceId,apiId, routeId } = useParams<RouterParams>();
const { teamId,serviceId,apiId, routeId,policyId } = useParams<RouterParams>();
const location = useLocation()
const currentUrl = location.pathname
const {fetchData} = useFetch()
@@ -42,7 +42,6 @@ const AiServiceInsidePage:FC = ()=> {
const getApiDefine = ()=>{
console.log('@@@@@@@')
setApiPrefix('')
setPrefixForce(false)
fetchData<BasicResponse<{ prefix:string, force:boolean }>>('service/router/define',{method:'GET',eoParams:{service:serviceId,team:teamId}}).then(response=>{
@@ -64,6 +63,7 @@ const AiServiceInsidePage:FC = ()=> {
getItem(<Link to="./route">{$t('API 路由')}</Link>, 'route',undefined,undefined,undefined,'team.service.router.view'),
getItem(<Link to="./api">{$t('API 文档')}</Link>, 'api',undefined,undefined,undefined,'team.service.api_doc.view'),
getItem(<Link to="./document">{$t('使用说明')}</Link>, 'document',undefined,undefined,undefined,'team.service.service_intro.view'),
getItem(<Link to="./servicepolicy">{$t('服务策略')}</Link>, 'servicepolicy', undefined, undefined, undefined, 'team.service.policy.view'),
getItem(<Link to="./publish">{$t('发布')}</Link>, 'publish',undefined,undefined,undefined,'team.service.release.view'),
],
'group'),
@@ -109,10 +109,14 @@ const AiServiceInsidePage:FC = ()=> {
};
useEffect(() => {
setShowMenu(!routeId && !currentUrl.includes('route/create'))
// 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(serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]){
} 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')
@@ -10,6 +10,7 @@ type ApiRequestSettingFieldType = {
siteLogo:string
invokeAddress:string
platformName:string
sitePrefix:string
}
export default function ApiRequestSetting(){
@@ -20,7 +21,7 @@ export default function ApiRequestSetting(){
const onFinish = () => {
form.validateFields().then((value)=>{
return fetchData<BasicResponse<null>>('system/general',{method:'POST',eoBody:(value),eoTransformKeys:['invokeAddress','siteName','siteLogo','platformName']}).then(response=>{
return fetchData<BasicResponse<null>>('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))
@@ -37,7 +38,7 @@ export default function ApiRequestSetting(){
};
const getSystemSetting = ()=>{
fetchData<BasicResponse<{ general: ApiRequestSettingFieldType }>>('system/general',{method:'GET',eoTransformKeys:['site_name', 'site_logo','invoke_address','platform_name']}).then(response=>{
fetchData<BasicResponse<{ general: ApiRequestSettingFieldType }>>('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)
@@ -74,6 +75,14 @@ export default function ApiRequestSetting(){
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<ApiRequestSettingFieldType>
label={$t("集成地址")}
name="sitePrefix"
rules={[{ whitespace:true }]}
extra={$t("与外部平台集成时,获取 API 市场中文档信息的域名")}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Row className="mb-[10px]"
>
@@ -7,7 +7,7 @@ import {debounce} from "lodash-es";
import {DownOutlined, SearchOutlined} from "@ant-design/icons";
import TreeWithMore from "@common/components/aoplatform/TreeWithMore.tsx";
import {useFetch} from "@common/hooks/http.ts";
import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import {BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx";
import { DepartmentListItem, MemberDropdownModalHandle } from "../../const/member/type.ts";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
@@ -77,7 +77,7 @@ const MemberPage = ()=>{
break;
case 'delete':
title=$t('删除')
content=$t('该数据删除后将无法找回,请确认是否删除?')
content=DELETE_TIPS.default
break;
}
modal.confirm({
@@ -0,0 +1,262 @@
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 { v4 as uuidv4 } from 'uuid'
const RemoteFormItem: React.FC<FilterFormItemProps> = (props) =>{
const {value, onChange, disabled,option, onShowValueChange,serviceId, teamId} = props
const [remoteList, setRemoteList] = useState<unknown[]>([])
const [remoteTableColumns, setRemoteTableColumns] = useState<TableColumnsType>([])
const [loading, setLoading] = useState<boolean>(false)
const [rowKey, setRowKey] = useState<string>('')
const title = useMemo(()=>option?.title,[option])
const [remoteCounts, setRemoteCounts] = useState<number>(0)
const [originRemoteList, setOriginRemoteList] = useState<unknown[]>([])
const {fetchData} = useFetch()
const getRemoteDetail = (searchWord?:string)=>{
setLoading(true)
fetchData<BasicResponse<{
key:string,
list:Record<string,unknown>[],
titles:Array<RemoteTitleType>,
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<string,unknown>)=>x[data.key as string])
onChange?.(totalDataArr)
onShowValueChange?.(totalDataArr.join(','))
}
}
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>{
setLoading(false)
})
}
useEffect(()=>{getRemoteDetail()},[option])
return (
<div className="w-full transfer-section rounded-DEFAULT" style={{ border: '1px solid var(--border-color)', borderBottom: 'none' }}>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin/>} spinning={loading}>
<p className="flex items-center mt-[12px] text-[16px] font-bold px-btnbase">
{$t('已选择(0)项(1)数据', [value?.length || 0, title])}
</p>
<div className="flex items-center justify-between py-btnybase px-btnbase">
<div></div>
<Input.Search
className="w-[224px] h-[32px]"
placeholder={$t(PLACEHOLDER.input)}
onSearch={(value)=>getRemoteDetail(value)}
disabled={disabled}
/>
</div>
<Table
columns={remoteTableColumns}
dataSource={remoteList}
pagination={false}
scroll={{ y: 316 }}
rowClassName={() => (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){
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])
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]]
onChange?.(newSelectedKeys)
onShowValueChange?.(newSelectedKeys.length === remoteCounts? $t('所有(0)',[option?.title]) : originRemoteList.filter(x=>newSelectedKeys.indexOf(x[rowKey]) !== -1)?.map(x=>x.name)?.join(' , '))
}
}
})}
/>
</Spin>
</div>)
}
const StaticFormItem: React.FC<FilterFormItemProps> = (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 (
<div className="w-auto">
{showAll && (
<Checkbox
className='mr-[8px]'
checked={allChecked}
onChange={(e) =>{
onChange?.(e.target.checked ? option.options : [])
onShowValueChange?.(e.target.checked ? $t('所有(0)',[option?.title]) : '-')
}}
disabled={disabled}
indeterminate={!allChecked && value?.length > 0}
>
ALL
</Checkbox>
)}
<Checkbox.Group
value={value}
options={option?.options.filter(x=>x!== 'ALL')}
onChange={(checkedValues) => {
onChange?.(checkedValues)
onShowValueChange?.(checkedValues.join(','))
}}
disabled={disabled}
/>
</div>)
}
const FilterForm = forwardRef<FilterFormHandle,FilterFormProps>(({
filterForm,
filterOptions,
selectedOptionNameSet,
disabled,
setFormCanSubmit,
serviceId,teamId},ref)=> {
const [form] = Form.useForm();
const [filterType, setFilterType] = useState<'remote'|'static'|'pattern'>();
const [curOption, setCurOption] = useState<unknown>()
const [label,setLabel] = useState<string>('')
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)
};
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 )
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])
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]);
return (
<Form form={form} layout="vertical" onValuesChange={handleValuesChange}>
<Form.Item name="name" label={$t('属性名称')} rules={[{ required: true }]}>
<Select disabled={disabled} onChange={handleTypeChange} options={filterOptionsList} />
</Form.Item>
<Form.Item name="values" label={$t('属性值')} rules={
(filterType === 'pattern' ? ( [{validator:form.getFieldValue('name') === 'ip' ? validateIPorCIDR : validateApiPath}]):[])
}>
{filterType === 'remote' && <RemoteFormItem serviceId={serviceId} teamId={teamId} option={curOption} disabled={disabled} onShowValueChange={setLabel}/>}
{filterType === 'pattern' && form.getFieldValue('name') !== 'ip' && (
<Input
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.input)}
/>
)}
{filterType === 'pattern' && form.getFieldValue('name') === 'ip' && (
<Input.TextArea
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.ipAndCidr)}
onChange={handleIPChange}
/>
)}
{filterType === 'static' && <StaticFormItem option={curOption} disabled={disabled} onShowValueChange={setLabel}/>}
</Form.Item>
</Form>
);
})
export default FilterForm;
@@ -0,0 +1,149 @@
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
import { Button, Table, Modal, App, Divider } from 'antd';
import { ColumnsType } from 'antd/es/table';
import { $t } from '@common/locales';
import { useFetch } from '@common/hooks/http';
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
import { FilterFormField, FilterTableProps, FilterOptionType, FilterFormHandle } from '@common/const/policy/type.ts';
import FilterForm from './FilterForm';
import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const';
import { useParams } from 'react-router-dom';
import { RouterParams } from '@common/const/type';
const FilterTable: React.FC<FilterTableProps> = ({
disabled = false,
drawerTitle = '筛选条件',
value,
onChange
}) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [filterForm, setFilterForm] = useState<FilterFormField>();
const [filterOptions, setFilterOptions] = useState<FilterOptionType[]>([]);
const {message} = App.useApp()
const {state} = useGlobalContext()
const {fetchData} = useFetch()
const formRef = useRef<FilterFormHandle>(null);
const [formCanSubmit,setFormCanSubmit] = useState(false)
const [selectedOptionNameSet, setSelectedOptionNameSet] = useState<Set<string>>(new Set());
const {serviceId,teamId} = useParams<RouterParams>()
const openDrawer = (type: string, data?: FilterFormField) => {
switch (type) {
case 'addFilter':
setFilterForm(undefined)
break;
case 'editFilter':
setFilterForm(data)
}
setIsModalVisible(true);
};
const closeDrawer = () => {
setIsModalVisible(false);
cleanFilterForm();
};
const cleanFilterForm = () => {
setFilterForm(undefined);
};
const handleSaveFilter = () => {
const formPromise = formRef.current?.save();
formPromise?.then?.((res)=>{
const newFilterForm = {
name:res.name,
values:res.values instanceof Array ? res.values : [res.values],
label:res.label,
title:res.title
}
onChange?.([newFilterForm, ...(value?.filter(x=>!filterForm?.name|| x.name!==filterForm.name) || [])])
closeDrawer()
})
};
const handleDeleteFilter = useCallback((item: FilterFormField) => {
const newFilterShowList = value?.filter((filter) => filter._eoKey !== item._eoKey) || [];
onChange?.(newFilterShowList);
}, [value, onChange]);
const getFilterOptions = ()=>{
fetchData<BasicResponse<{options:FilterOptionType[]}>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/filter-options`,{method:'GET', eoParams:{...(serviceId ? {team:teamId, service:serviceId} : {})}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setFilterOptions(data.options)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch((errorInfo)=> console.error(errorInfo))
}
const columns: ColumnsType<FilterFormField> =useMemo(()=>[
{
title: $t('属性名称'),
dataIndex: 'name',
key: 'name',
},
{
title: $t('属性值'),
dataIndex: 'label',
key: 'label',
},
{
title: COLUMNS_TITLE.operate,
key: 'action',
width:100,
render: (_, record) => (
<div className='flex items-center gap-[8px]'>
<TableBtnWithPermission key="edit" btnType="edit" onClick={()=>{openDrawer('editFilter', record)}} btnTitle={$t("编辑")}/>
<Divider type="vertical" className="mx-0" key="div2"/>
<TableBtnWithPermission key="delete" btnType="delete" onClick={()=>{handleDeleteFilter(record)}} btnTitle={$t("删除")}/></div>)
}
],[state.language,handleDeleteFilter])
useEffect(()=>{
getFilterOptions()
},[state.language])
useEffect(()=>{
setSelectedOptionNameSet(new Set(value?.map(x=>x.name) || []))
},[value])
return (
<div>
{
!disabled &&<Button onClick={() => openDrawer('addFilter')}>
{$t('添加配置')}
</Button>
}
{value && value.length >0 && <Table
className={`mt-btnybase border-solid border-[1px] border-BORDER border-b-0 rounded ${disabled ? '' : 'mt-btnbase'}`}
pagination={false}
size='small'
columns={columns} dataSource={value} rowKey='_eoKey' /> }
<div role="alert" className="ant-form-item-explain-error">
</div>
<Modal
title={filterForm?.name ? $t('编辑(0)',[$t(drawerTitle)]) :$t('配置(0)',[$t(drawerTitle)])}
visible={isModalVisible}
onCancel={closeDrawer}
width={900}
okButtonProps={{ disabled:!formCanSubmit }}
onOk={()=>handleSaveFilter()}
destroyOnClose={true}
>
<FilterForm
ref={formRef}
filterForm={filterForm}
filterOptions={filterOptions}
selectedOptionNameSet={selectedOptionNameSet}
disabled={disabled}
setFormCanSubmit={setFormCanSubmit}
serviceId={serviceId}
teamId={teamId}
/>
</Modal>
</div>
);
};
export default FilterTable;
@@ -0,0 +1,14 @@
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 === '/globalpolicy'){
navigator('/globalpolicy/datamasking/list')
}
},[pathName])
return (<Outlet></Outlet>)
}
@@ -0,0 +1,15 @@
import { useEffect } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
export default function ServicePolicyLayout(){
const location = useLocation()
const pathName = location.pathname
const navigator = useNavigate()
useEffect(()=>{
const tmpPath = pathName.split('/')
if(tmpPath[tmpPath.length -1 ] === 'servicepolicy'){
navigator('datamasking/list')
}
},[pathName])
return (<Outlet></Outlet>)
}
@@ -1,464 +0,0 @@
import { ActionType } from "@ant-design/pro-components";
import { useMemo, useRef, useState } from "react";
import { Button, message, Switch } from 'antd'
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { DATA_MASSKING_TABLE_COLUMNS } from './dataMaskingColumn'
import { $t } from "@common/locales";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
import { useFetch } from "@common/hooks/http";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission";
const DataMasking = (props: any) => {
const {
// 是否显示发布按钮
publishBtn = false,
// 行操作
rowOperation = []
} = props;
const { checkPermission, getGlobalAccessData, accessInit, state } = useGlobalContext()
/**
* ref
*/
const pageListRef = useRef<ActionType>(null);
/**
*
*/
const [tableHttpReload, setTableHttpReload] = useState(true);
/**
*
*/
const [tableListDataSource, setTableListDataSource] = useState<ServiceHubAppListItem[]>([]);
/**
*
*/
const { fetchData } = useFetch()
/**
*
*/
const [searchWord, setSearchWord] = useState<string>('')
/**
*
*/
const columns = useMemo(() => {
const res = DATA_MASSKING_TABLE_COLUMNS.map(x => {
// 启动列渲染
if (x.dataIndex === 'enabled') {
x.render = (text: any, record: any) => <Switch checked={record.enabled} onChange={(e) => { changeOpenApiStatus(e, record) }} />
}
// 处理数列渲染
if (x.dataIndex === 'treatmentNumber') {
x.render = (text: any, record: any) => <span className="w-full block cursor-pointer [&>.ant-typography]:text-theme" onClick={(e) => { openLogsModal(record) }} >{ text }</span>
}
// 名称筛选,这里是全量返回时候的,分页的话应该要接口返回对应的筛选数据
if (x.dataIndex === 'name') {
const nameList = tableListDataSource.map(item => item.name)
const valueEnum: any = {}
nameList.forEach(item => {
valueEnum[item] = { text: item }
})
x.valueEnum = valueEnum
}
return {
...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
}
})
return res
}, [tableListDataSource, state.language])
/**
*
*/
const operation: PageProColumns<any>[] = rowOperation.length ? [
{
title: '',
key: 'option',
btnNums: rowOperation.length,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
...(rowOperation.length && rowOperation.find((item: string) => item === 'edit') ? [<TableBtnWithPermission access="system.organization.member.edit" key="edit" btnType="edit" onClick={() => { openEditModal(entity) }} btnTitle="编辑" />] : []),
...(rowOperation.length && rowOperation.find((item: string) => item === 'logs') ? [<TableBtnWithPermission access="system.organization.member.edit" key="logs" btnType="logs" onClick={() => { openLogsModal(entity) }} btnTitle="详情" />] : []),
...(rowOperation.length && rowOperation.find((item: string) => item === 'delete') ? [<TableBtnWithPermission access="system.organization.member.edit" key="delete" btnType="delete" onClick={() => { deletePolicy(entity) }} btnTitle="删除" />] : []),
],
}
] : []
/**
*
*/
const manualReloadTable = () => {
setTableHttpReload(true)
pageListRef.current?.reload()
};
/**
*
* @param enabled
* @param entity
*/
const changeOpenApiStatus = (enabled: boolean, entity: any) => {
console.log('更改启动状态', enabled, entity);
manualReloadTable()
// 待补充,请求接口更改状态,然后刷新表格
// fetchData<BasicResponse<null>>(
// `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))
// }
// })
}
/**
*
* @param dataType
* @returns
*/
const getServiceList = () => {
if (!accessInit) {
getGlobalAccessData()?.then?.(() => { getServiceList() })
return Promise.resolve({ data: [], success: false })
}
if (!tableHttpReload) {
setTableHttpReload(true)
return Promise.resolve({
data: tableListDataSource,
success: true,
});
}
return fetchData<BasicResponse<any>>(
!checkPermission('system.workspace.team.view_all') ? 'teams' : 'manager/teams',
{
method: 'GET',
eoParams: { keyword: searchWord },
eoTransformKeys: ['create_time', 'service_num', 'can_delete']
}
).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const data = [
{
name: 'test',
priority: 1,
status: true,
enabled: true,
condition: 'test',
treatmentNumber: 1,
updater: 'test',
createTime: '2021-10-01'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
},
{
name: 'test2',
priority: 2,
status: false,
enabled: false,
condition: 'test2',
treatmentNumber: 2,
updater: 'test2',
createTime: '2021-10-02'
}
]
// 保存数据
setTableListDataSource(data)
setTableHttpReload(false)
return {
data,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
}).catch(() => {
return { data: [], success: false }
})
}
/**
*
* @param type
*/
const addPolicy = () => {
console.log('添加策略');
}
/**
*
*/
const publish = () => {
console.log('发布策略');
}
/**
*
*/
const openEditModal = (entity: any) => {
console.log('编辑', entity);
}
/**
*
* @param entity
*/
const openLogsModal = (entity: any) => {
console.log('日志', entity);
}
/**
*
* @param entity
*/
const deletePolicy = (entity: any) => {
console.log('删除', entity);
manualReloadTable()
}
return (
<>
<PageList
id="data_masking_list"
ref={pageListRef}
columns={[...columns, ...operation]}
request={() => getServiceList()}
addNewBtnTitle={$t("添加策略")}
onAddNewBtnClick={() => { addPolicy() }}
searchPlaceholder={$t("输入名称、筛选条件查找")}
afterNewBtn={
publishBtn && [<WithPermission key="removeFromDepPermission" access="system.organization.member.edit"><Button className="mr-btnbase" key="removeFromDep" onClick={() => publish()}>{$t('发布')}</Button></WithPermission>]
}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
manualReloadTable={manualReloadTable}
onChange={() => {
setTableHttpReload(false)
}}
/>
</>
)
}
export default DataMasking;
@@ -0,0 +1,396 @@
import { ActionType } from "@ant-design/pro-components";
import { useEffect, useMemo, useRef, useState } from "react";
import { App, Button, message, Switch, Modal } from 'antd'
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { $t } from "@common/locales";
import { useGlobalContext } from "@common/contexts/GlobalStateContext";
import { BasicResponse, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx";
import { useFetch } from "@common/hooks/http";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission";
import { DATA_MASKING_TABLE_COLUMNS } from "./DataMaskingColumn";
import { useNavigate, useParams } from "react-router-dom";
import { PolicyPublishInfoType, PolicyPublishModalHandle, RouterParams } from "@common/const/type";
import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter";
import { DataMaskStrategyItem } from "@common/const/policy/type";
import { PolicyPublishModalContent } from '@common/components/aoplatform/PolicyPublishModalContent'
import { checkAccess } from "@common/utils/permission";
import DataMaskingLogModal from "./DataMaskingLogModal.tsx";
const DataMasking = (props: any) => {
const {
// 是否显示发布按钮
publishBtn = false,
// 行操作
rowOperation = []
} = props;
const { serviceId, teamId } = useParams<RouterParams>()
const { state, accessData } = useGlobalContext()
const navigator = useNavigate()
const [drawerVisible, setDrawerVisible] = useState<boolean>(false)
const [modalVisible, setModalVisible] = useState<boolean>(false);
const [drawerData, setDrawerData] = useState<PolicyPublishInfoType>()
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
const drawerRef = useRef<PolicyPublishModalHandle>(null)
const { modal } = App.useApp()
/**
* ref
*/
const pageListRef = useRef<ActionType>(null);
/**
*
*/
const { fetchData } = useFetch()
/**
*
*/
const [searchWord, setSearchWord] = useState<string>('')
const [strategy, setStrategy] = useState<string>('')
/**
*
*/
const columns = useMemo(() => {
const res = DATA_MASKING_TABLE_COLUMNS.map(x => {
// 启动列渲染
if (x.dataIndex === 'isStop') {
x.render = (text: any, record: any) => <WithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.edit`} ><Switch checked={!record.isStop} onChange={(e) => { changeOpenApiStatus(e, record) }} /></WithPermission>
}
// 处理数列渲染
if (x.dataIndex === 'treatmentNumber') {
x.render = (text: any, record: any) => <span className="w-full block cursor-pointer [&>.ant-typography]:text-theme" onClick={(e) => { openLogsModal(record) }} >{text}</span>
}
return {
...x,
title: <span title={$t(x.title as string)}>{$t(x.title as string)}</span>
}
})
return res
}, [state.language])
/**
*
*/
const operation: PageProColumns<any>[] = rowOperation.length ? [
{
title: '',
key: 'option',
btnNums: rowOperation.length -1,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => [
...(rowOperation.length && rowOperation.find((item: string) => item === 'edit') ? [<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.edit`} key="edit" btnType="edit" onClick={() => { openEditModal(entity) }} btnTitle="编辑" />] : []),
...(rowOperation.length && rowOperation.find((item: string) => item === 'logs') ? [<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="logs" btnType="logs" onClick={() => { openLogsModal(entity) }} btnTitle="日志" />] : []),
...(rowOperation.length && rowOperation.find((item: string) => item === 'delete') ? [
entity.isDelete ? <TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.edit`} key="refresh" btnType="refresh" onClick={() => { restorePolicy(entity) }} btnTitle="恢复" /> :
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.delete`} key="delete" btnType="delete" onClick={() => { openModal('delete', entity) }} btnTitle="删除" />
] : []),
],
}
] : []
const handleCloseModal = () => {
setModalVisible(false);
// setDetailInvokeError(false)
// setDetailInvokeStatic(undefined)
// setCompareTotal(false)
};
/**
*
*/
const manualReloadTable = () => {
pageListRef.current?.reload()
};
/**
*
* @param enabled
* @param entity
*/
const changeOpenApiStatus = (enabled: boolean, entity: any) => {
fetchData<BasicResponse<null>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/${enabled ? 'enable' : 'disable'}`,
{
method: 'PATCH',
eoParams: {
service: serviceId,
team: teamId,
strategy: 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))
}
})
}
/**
*
* @param dataType
* @returns
*/
const getPolicyList = (params: DataMaskStrategyItem & {
pageSize: number;
current: number;
},
sort: Record<string, string>,
filter: Record<string, string>) => {
let filters
if (filter) {
filters = []
if (filter.isStop) {
if (filter.isStop.indexOf('true') !== -1) {
filters.push('enable')
}
if (filter.isStop.indexOf('false') !== -1) {
filters.push('disable')
}
if (filter.publishStatus?.length > 0) {
filters = [...filters, ...filter.publishStatus]
}
}
}
return fetchData<BasicResponse<{ list: DataMaskStrategyItem[], total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
{
method: 'GET',
eoParams: {
order: Object.keys(sort)?.[0],
sort: Object.keys(sort)?.length > 0 ? Object.values(sort)?.[0] === 'descend' ? 'desc' : 'asc' : undefined,
filters: JSON.stringify(filters),
keyword: searchWord,
service: serviceId,
team: teamId,
},
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.list,
total: data.total,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
}).catch(() => {
return { data: [], success: false }
})
}
/**
*
* @param type
*/
const addPolicy = () => {
navigator('../create')
}
/**
*
*/
const publish = async () => {
message.loading($t(RESPONSE_TIPS.loading));
const { code, data, msg } = await fetchData<BasicResponse<PolicyPublishInfoType>>(
'strategy/global/data-masking/to-publishs',
{ method: 'GET', eoTransformKeys: ['opt_time', 'is_publish', 'version_name', 'unpublish_msg'] }
);
message.destroy();
if (code === STATUS_CODE.SUCCESS) {
setDrawerVisible(true)
setDrawerData(data)
setIsOkToPublish(data.isPublish ?? true)
} else {
message.error(msg || $t(RESPONSE_TIPS.error));
return
}
}
/**
*
*/
const openEditModal = (entity: any) => {
navigator(`../${entity.id}`)
}
/**
*
* @param entity
*/
const openLogsModal = (entity: any) => {
console.log('日志', entity);
setStrategy(entity.id)
setModalVisible(true)
}
const openModal = async (type: 'delete', entity?: DataMaskStrategyItem) => {
if (entity?.publishStatus === 'online') {
return deletePolicy(entity!).then((res) => { if (res === true) manualReloadTable() })
}
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 deletePolicy(entity!).then((res) => { if (res === true) manualReloadTable() })
}
},
width: 600,
okText: $t('确认'),
okButtonProps: {
disabled: !checkAccess(`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.edit`, accessData)
},
cancelText: $t('取消'),
closable: true,
icon: <></>,
})
}
/**
*
* @param entity
*/
const deletePolicy = (entity: DataMaskStrategyItem) => {
return fetchData<BasicResponse<null>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking`,
{
method: 'DELETE',
eoParams: {
service: serviceId,
team: teamId,
strategy: 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))
}
/**
*
* @param entity
*/
const restorePolicy = (entity: any) => {
fetchData<BasicResponse<null>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/restore`,
{
method: 'PATCH',
eoParams: {
service: serviceId,
team: teamId,
strategy: 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 onSubmit = () => {
return drawerRef.current?.publish()?.then((res) => {
manualReloadTable();
return res;
});
}
return (
<>
<PageList<DataMaskStrategyItem>
id="data_masking_list"
ref={pageListRef}
columns={[...columns, ...operation]}
request={async (params: DataMaskStrategyItem & {
pageSize: number;
current: number;
},
sort: Record<string, string>,
filter: Record<string, string>) => getPolicyList(params, sort, filter)}
addNewBtnTitle={$t("添加策略")}
addNewBtnAccess={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.edit`}
onAddNewBtnClick={() => { addPolicy() }}
searchPlaceholder={$t("输入名称、筛选条件查找")}
afterNewBtn={
publishBtn && [<WithPermission key="removeFromDepPermission" access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.publish`} >
<Button className="mr-btnbase" key="removeFromDep" onClick={() => publish()}>{$t('发布')}</Button>
</WithPermission>]
}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
manualReloadTable={manualReloadTable}
/>
<DrawerWithFooter
destroyOnClose={true}
title={$t('申请发布')}
width={'60%'}
onClose={() => { setDrawerVisible(false) }}
okBtnTitle={$t('发布')}
open={drawerVisible}
submitDisabled={!isOkToPublish}
submitAccess={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.publish`}
onSubmit={onSubmit}
>
<PolicyPublishModalContent
ref={drawerRef}
data={drawerData!}
/>
</DrawerWithFooter>
<Modal
title={$t('处理日志')}
visible={modalVisible}
onCancel={handleCloseModal}
footer={null}
wrapClassName="modal-without-footer"
width={1000}
maskClosable={true}
>
<div className="pb-btnybase flex flex-nowrap flex-col h-full w-full items-center justify-between">
<DataMaskingLogModal strategy={strategy}></DataMaskingLogModal>
</div>
</Modal>
</>
)
}
export default DataMasking;
@@ -0,0 +1,107 @@
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { frontendTimeSorter } from "@common/utils/dataTransfer";
import { $t } from "@common/locales";
import { StrategyStatusEnum, StrategyStatusColorClass } from "@common/const/policy/consts";
export const DATA_MASKING_TABLE_COLUMNS: PageProColumns<any>[] = [
{
title: ('策略名称'),
dataIndex: 'name',
ellipsis: true,
width: 160
},
{
title: ('优先级'),
dataIndex: 'priority',
width: 140,
ellipsis: true,
sorter: (a: any, b: any) => {
return (a.priority as number) - (b.priority as number)
}
},
{
title: ('发布状态'),
dataIndex: 'publishStatus',
filters: true,
onFilter: false ,
width: 140,
valueEnum: new Map(
Object.keys(StrategyStatusEnum).map(key=>
[key,
<span className={StrategyStatusColorClass[key as keyof typeof StrategyStatusColorClass]}>{$t(StrategyStatusEnum[key as keyof typeof StrategyStatusEnum])}</span>
]))
},
{
title: ('启用'),
dataIndex: 'isStop',
filters: true,
onFilter: false ,
valueEnum: {
false: { text: <span className="text-status_success">{$t('启用')}</span> },
true: { text: <span className="text-status_fail">{$t('禁用')}</span> }
}
},
{
title: ('筛选条件'),
dataIndex: 'filters',
ellipsis: true
},
{
title: ('处理数'),
dataIndex: 'processedTotal',
ellipsis: true
},
{
title: ('更新者'),
dataIndex: ['updater','name'],
width: 140,
ellipsis: true
},
{
title: ('更新时间'),
dataIndex: 'updateTime',
width: 182,
ellipsis: true,
sorter: (a, b) => frontendTimeSorter(a, b, 'updateTime')
},
];
export const DATA_MASKING_TABLE_LOG_COLUMNS: PageProColumns<any>[] = [
{
title: ('服务'),
dataIndex: ['service', 'name'],
ellipsis: true,
width: 80
},
{
title: ('调用地址'),
dataIndex: 'url',
ellipsis: true,
width: 200
},
{
title: ('消费者IP'),
dataIndex: 'remote_ip',
ellipsis: true,
width: 150
},
{
title: ('消费者'),
dataIndex: ['consumer', 'name'],
ellipsis: true,
width: 80
},
{
title: ('鉴权名称'),
dataIndex: 'authorization',
ellipsis: true,
width: 100
},
{
title: ('时间'),
dataIndex: 'record_time',
width: 150,
ellipsis: true
},
]
@@ -0,0 +1,97 @@
import { Codebox } from "@common/components/postcat/api/Codebox";
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { RouterParams } from "@common/const/type";
import { $t } from "@common/locales";
import { App, Button, message, Switch, Modal, Spin } from 'antd'
import { useFetch } from "@common/hooks/http";
import { useEffect, useState } from "react";
import { LoadingOutlined } from "@ant-design/icons";
import { useParams } from "react-router-dom";
type LogItems = {
id: string;
origin: string;
target: string;
}
const DataMaskingCompare = () => {
const { logId, serviceId, teamId } = useParams();
const { fetchData } = useFetch()
const [loading, setLoading] = useState(false)
const [originValue, setOriginValue] = useState('')
const [targetValue, settTargetValue] = useState('')
const getLogData = () => {
setLoading(true)
return fetchData<BasicResponse<{ log: LogItems }>>(`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/log`,
{
method: 'GET',
eoParams: {
log: logId,
service: serviceId,
team: teamId,
}
}).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const { log } = data
setOriginValue(log.origin || '')
settTargetValue(log.target || '')
setLoading(false)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).catch(() => {
return { data: [], success: false }
}).finally(() => {
const aa = `{
"code": {
"gg": "gg",
"gg1": "gg",
"gg2": "gg",
"gg3": "gg",
"gg4": "gg"
}
}`
setOriginValue(JSON.stringify(JSON.parse(aa), null, 2))
settTargetValue(JSON.stringify(JSON.parse(aa), null, 2))
setLoading(false)
})
}
useEffect(() => {
getLogData()
}, []);
return (
<Spin wrapperClassName=" h-full flex-1" indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading}>
<div className="flex h-full overflow-hidden">
<div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-gray-200 mb-2 flex items-center justify-center">
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
height="100%"
width="100%"
value={originValue}
/>
</div>
</div>
<div className="w-1/2 p-2 h-full">
<div className="h-[30px] bg-green-100 mb-2 flex items-center justify-center">
</div>
<div style={{ height: 'calc(100vh - 50px)' }}>
<Codebox
language='json'
width="100%"
height="100%"
value={targetValue}
sx={{ whiteSpace: 'nowrap' }}
readOnly
/>
</div>
</div>
</div>
</Spin>
);
}
export default DataMaskingCompare;
@@ -0,0 +1,178 @@
import { LoadingOutlined } from "@ant-design/icons"
import InsidePage from "@common/components/aoplatform/InsidePage"
import WithPermission from "@common/components/aoplatform/WithPermission"
import { BasicResponse, STATUS_CODE, RESPONSE_TIPS, PLACEHOLDER } from "@common/const/const"
import { RouterParams } from "@common/const/type"
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
import { useFetch } from "@common/hooks/http"
import { $t } from "@common/locales"
import { App, Button, Form, Input, InputNumber, Row, Select, Spin } from "antd"
import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react"
import { useParams, useNavigate } from "react-router-dom"
import DataMaskRuleTable from "./DataMaskingRuleTable"
import FilterTable from "../FilterTable"
import { DataMaskingConfigHandle ,DataMaskingConfigFieldType, PolicyMatchType} from "@common/const/policy/type"
import {PolicyOptions} from '@common/const/policy/consts'
import {v4 as uuidv4} from 'uuid'
const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
const { message,modal } = App.useApp()
const { teamId, serviceId, policyId } = useParams<RouterParams>();
const [onEdit, setOnEdit] = useState<boolean>(!!teamId)
const [form] = Form.useForm();
const {fetchData} = useFetch()
const { state } = useGlobalContext()
const [ loading, setLoading ] = useState<boolean>(false)
const navigator = useNavigate()
useImperativeHandle(ref, () => ({
save:onFinish
}));
// 获取表单默认值
const getPolicyInfo = () => {
setLoading(true)
fetchData<BasicResponse<{ strategy: DataMaskingConfigFieldType }>>( `strategy/${serviceId === undefined? 'global':'service'}/data-masking`,{method:'GET',eoParams:{team:teamId, service:serviceId, strategy:policyId}}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
setTimeout(()=>{
form.setFieldsValue({
...data.strategy,
type:'data-masking',
filters:data.strategy.filters?.map((x)=>{x._eoKey = uuidv4(); return x})
})
},0)
}else{
message.error(msg || $t(RESPONSE_TIPS.error))
}
}).finally(()=>setLoading(false))
};
const onFinish:()=>Promise<boolean|string> = () => {
return form.validateFields().then((value)=>{
if(value.filters){
value.filters = value.filters.map((x:PolicyMatchType)=>({
...x,
values:x.name === 'ip' ? x.values?.[0].split('\n'): (x.values.indexOf('ALL')!== -1 ? ['ALL']:x.values)}))
}
return fetchData<BasicResponse<{service:{id:string}}>>(
`strategy/${serviceId === undefined? 'global':'service'}/data-masking`,
{
method:policyId === undefined? 'POST' : 'PUT',
eoParams: {service:serviceId,team:teamId, strategy:policyId},
eoBody:({...value})
}).then(response=>{
const {code,data,msg} = response
if(code === STATUS_CODE.SUCCESS){
message.success(msg || $t(RESPONSE_TIPS.success))
navigator('../list')
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)
})
})
};
useEffect(() => {
if (policyId !== undefined) {
setOnEdit(true);
getPolicyInfo();
} else {
setOnEdit(false);
form.setFieldValue('type','data-masking')
}
return (form.setFieldsValue({}))
}, [policyId]);
const policyOptions = useMemo(()=>PolicyOptions.map((x)=>({...x, label:$t(x.label)})),[state.language])
return (
<InsidePage pageTitle={serviceId ? undefined: $t('编辑策略')}
showBorder={false}
scrollPage={false}
className="overflow-y-auto"
backUrl={serviceId ? '../list' : undefined}
>
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} spinning={loading} wrapperClassName=' pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X'>
<WithPermission access={onEdit ? [`${ serviceId === undefined ? 'system.devops':'team.service'}.policy.edit`] :''}>
<Form
layout='vertical'
labelAlign='left'
scrollToFirstError
form={form}
className="w-full "
name="systemConfig"
onFinish={onFinish}
autoComplete="off"
>
<div>
<Form.Item<DataMaskingConfigFieldType>
label={$t("策略名称")}
name="name"
rules={[{ required: true ,whitespace:true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<DataMaskingConfigFieldType>
label={$t("策略类型")}
name="type"
rules={[{ required: true }]}
>
<Select className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} options={policyOptions} >
</Select>
</Form.Item>
<Form.Item<DataMaskingConfigFieldType>
label={$t("优先级")}
name={'priority'}
rules={[{required: true}]}
>
<InputNumber className="w-INPUT_NORMAL" min={1} placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<DataMaskingConfigFieldType>
label={$t("描述")}
name="description"
>
<Input.TextArea className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)}/>
</Form.Item>
<Form.Item<DataMaskingConfigFieldType>
label={$t("匹配条件")}
name="filters"
>
<FilterTable />
</Form.Item>
<Form.Item<DataMaskingConfigFieldType>
label={$t("数据脱敏规则")}
name={["config","rules"]}
rules={[{required: true}]}
>
<DataMaskRuleTable />
</Form.Item>
<Row className="mb-[10px]">
<WithPermission access={onEdit ? [`${ serviceId === undefined ? 'system.devops':'team.service'}.policy.edit`] :''}>
<Button type="primary" htmlType="submit">
{$t('保存')}
</Button>
</WithPermission>
<Button className="ml-btnrbase" type="default" onClick={() => navigator('../list')}>
{$t('取消')}
</Button>
</Row>
</div>
</Form>
</WithPermission>
</Spin>
</InsidePage>
)
})
export default DataMaskingConfig
@@ -0,0 +1,373 @@
import React, { useEffect, 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 { App, Button, message, DatePicker, Modal } from 'antd'
import { DATA_MASKING_TABLE_LOG_COLUMNS, DATA_MASKING_TABLE_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 WithPermission from '@common/components/aoplatform/WithPermission';
import TimeRangeSelector, { TimeRange } from '@common/components/aoplatform/TimeRangeSelector';
import { SearchBody } from '@dashboard/const/type';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
const { RangePicker } = DatePicker;
const DataMaskingLogModal = (props: any) => {
const { strategy } = props;
const { state, accessData } = useGlobalContext()
const { serviceId, teamId } = useParams<RouterParams>()
const [datePickerValue, setDatePickerValue] = useState<any>();
const [queryData, setQueryData] = useState<SearchBody>({})
/**
*
*/
const { fetchData } = useFetch()
/**
* ref
*/
const pageListRef = useRef<ActionType>(null);
/**
*
*/
const [searchWord, setSearchWord] = useState<string>('')
/**
*
*/
const operation: PageProColumns<any>[] = [
{
title: '操作',
key: 'option',
btnNums: 1,
fixed: 'right',
valueType: 'option',
render: (_: React.ReactNode, entity: any) => {
let url = `/dataMaskCompare/${entity.id}`
if (serviceId) {
url += `/${serviceId}`
}
if (teamId) {
url += `/${teamId}`
}
return [
<TableBtnWithPermission access={`${serviceId === undefined ? 'system.devops' : 'team.service'}.policy.view`} key="view" btnType="view" onClick={() => { 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) => <><span className='text-green-500'>{record.method}</span>&nbsp;<span>{text}</span></>
}
return {
...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
}
})
return res
}, [state.language])
/**
*
* @param dataType
* @returns
*/
const getPolicyList = (params: DataMaskLogItem & {
pageSize: number;
current: number;
}) => {
return fetchData<BasicResponse<{ logs: DataMaskLogItem[], total: number }>>(
`strategy/${serviceId === undefined ? 'global' : 'service'}/data-masking/list`,
{
method: 'GET',
eoParams: {
keyword: searchWord,
begin: queryData?.start,
end: queryData?.end,
page: params.current,
page_size: params.pageSize,
strategy: strategy,
service: serviceId,
team: teamId,
},
eoTransformKeys: ['is_stop', 'is_delete', 'update_time', 'publish_status', 'processed_total']
}
).then(response => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const mockData: any = [
{
id: '12334',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff1',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff2',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff3',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff4',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff5',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff6',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff7',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff8',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff9',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff11',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:12',
},
{
id: 'fff22',
service: {
id: 'xxx',
name: 'xxx'
},
url: 'url',
remote_ip: '9234923',
consumer: {
id: 'yyy',
name: 'yyy'
},
method: 'GET',
authorization: 'authorization',
record_time: '2021-09-09 12:12:11',
}
]
// 保存数据
return {
data: mockData,
total: data.total,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
}).catch(() => {
return { data: [], success: false }
})
}
const handleTimeRangeChange = (timeRange: TimeRange) => {
setQueryData(pre => ({ ...pre, ...timeRange } as SearchBody))
manualReloadTable()
};
const handleDatePickerChange = (dates: any) => {
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
handleTimeRangeChange({ start, end });
} else {
handleTimeRangeChange({ start: null, end: null})
}
}
const resetQuery = () => {
setDatePickerValue(null)
handleTimeRangeChange({ start: null, end: null})
};
return (
<>
<div className="w-full h-full p-[20px]">
<PageList<DataMaskLogItem>
id="data_masking_log_list"
ref={pageListRef}
minVirtualHeight={400}
columns={[...columns, ...operation]}
afterNewBtn={
[<div className="flex items-center flex-wrap p-[10px] px-btnbase content-before bg-MAIN_BG ">
<RangePicker
onChange={handleDatePickerChange}
value={datePickerValue} />
<div className="flex [&>.reset-btn]:!h-auto flex-nowrap items-center ml-[10px]">
<Button className="reset-btn" onClick={resetQuery}>{$t('重置')}</Button>
</div>
</div>]
}
request={async (params: DataMaskLogItem & {
pageSize: number;
current: number;
}) => getPolicyList(params)}
searchPlaceholder={$t("输入调用地址、消费者IP和消费者条件查找")}
onSearchWordChange={(e) => {
setSearchWord(e.target.value)
}}
manualReloadTable={manualReloadTable}
></PageList>
</div>
</>
)
};
export default DataMaskingLogModal;
@@ -0,0 +1,158 @@
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<DataMaskRuleFormProps> = ({ editData, ruleList, onSave, onClose,modalVisible }) => {
const [form] = Form.useForm();
const [matchType, setMatchType] = useState<string>('');
const [matchValue, setMatchValue] = useState<string>('');
const [maskType, setMaskType] = useState<string>('');
const [replaceType, setReplaceType] = useState<string>('');
const {state} = useGlobalContext()
useEffect(() => {
if (editData) {
form.setFieldsValue(editData);
}
}, [editData, form]);
const handleSave = () => {
form.validateFields().then((values) => {
const submitData = prepareSubmitData(values);
const newRuleList =ruleList ? [...ruleList] : [];
if (editData) {
const index = newRuleList.findIndex((rule) => rule.eoKey === editData.eoKey);
if (index !== -1) {
newRuleList.splice(index, 1);
}
}
newRuleList.unshift({ ...submitData, eoKey: editData?.eoKey || uuidv4() });
onSave?.(newRuleList);
onClose?.();
clearData()
});
};
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);
}
}, [editData, form]);
const handleMatchTypeChange = (value: string) => {
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']);
};
const handleMaskTypeChange = (value: string) => {
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']);
};
const prepareSubmitData = (formData: any) => {
const submitData: any = {
match: {
type: formData.match.type,
value: formData.match.value
},
mask: {
type: formData.mask.type
}
};
switch (formData.mask.type) {
case 'replacement':
submitData.mask = {
...submitData.mask,
replace: formData.mask.replace
};
break;
case 'shuffling':
break;
default:
submitData.mask.begin = Number(formData.mask.begin) || 0;
submitData.mask.length = Number(formData.mask.length) || 0;
break;
}
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])
return (
<Modal open={modalVisible} onCancel={onClose} onOk={handleSave} title={$t("配置脱敏规则")}>
<Form form={form} layout="vertical" className="p-4">
<Form.Item name={['match', 'type']} label={$t("匹配类型")} rules={[{ required: true }]}>
<Select placeholder={$t(PLACEHOLDER.select)} onChange={handleMatchTypeChange} options={matchRuleOptions}/>
</Form.Item>
{ matchType && <Form.Item name={['match', 'value']} label={$t("匹配值")} rules={[{ required: true }]}>{
matchType === 'inner' ?
<Select placeholder={$t(PLACEHOLDER.select)} onChange={handleMatchValueChange} options={dataFormatOptions}/>
:<Input placeholder={$t(PLACEHOLDER.input)} />}
</Form.Item>
}
<Form.Item name={['mask', 'type']} label={$t("脱敏类型")} rules={[{ required: true }]}>
<Select placeholder={$t(PLACEHOLDER.select)} onChange={handleMaskTypeChange} options={ matchType && ['name', 'phone', 'id-card', 'bank-card'].indexOf(matchValue) !== -1 ? dataMaskOrderOptions:dataMaskBaseOptions} />
</Form.Item>
{['partial-display', 'partial-masking', 'truncation'].includes(maskType) && (
<>
<Form.Item name={['mask', 'begin']} label={$t("起始位置")} rules={[{ required: true }]}>
<Input type="number" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item name={['mask', 'length']} label={$t("长度")} rules={[{ required: true }]}>
<Input type="number" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
</>
)}
{maskType === 'replacement' && (
<>
<Form.Item name={['mask', 'replace', 'type']} label={$t("替换类型")} rules={[{ required: true }]}>
<Select placeholder={$t(PLACEHOLDER.select)} onChange={handleReplaceTypeChange} options={dataMaskReplaceStrOptions}/>
</Form.Item>
{replaceType === 'custom' && (
<Form.Item name={['mask', 'replace', 'value']} label={$t("替换值")} rules={[{ required: true }]}>
<Input placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
)}
</>
)}
</Form>
</Modal>
);
};
export default DataMaskRuleForm;
@@ -0,0 +1,152 @@
import { useMemo, useState } from 'react';
import { Button, Table, Tooltip } from 'antd';
import DataMaskRuleForm from './DataMaskingRuleForm';
import { $t } from '@common/locales';
import {DataMaskRuleTableProps, MaskRuleData} from "@common/const/policy/type";
import { useGlobalContext } from '@common/contexts/GlobalStateContext';
import { COLUMNS_TITLE } from '@common/const/const';
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission';
const DataMaskRuleTable: React.FC<DataMaskRuleTableProps> = ({
disabled = false,
value,
onChange
}) => {
const [editData, setEditData] = useState<MaskRuleData | undefined>(undefined);
const [isModalVisible, setIsModalVisible] = useState(false);
const {state} = useGlobalContext()
const openDrawer = (type:'add'|'edit',data?: MaskRuleData) => {
setEditData(data);
setIsModalVisible(true);
};
const closeDrawer = () => {
setIsModalVisible(false);
setEditData(undefined);
};
const handleSave = (newRuleList: MaskRuleData[]) => {
onChange?.(newRuleList);
};
const columns = useMemo(()=> [
{
title: $t('匹配类型'),
dataIndex: ['match', 'type'],
key: 'matchType',
render: (text: string) => {
switch (text) {
case 'inner':
return $t('数据格式');
case 'keyword':
return $t('关键字');
case 'regex':
return $t('正则表达式');
case 'json_path':
return $t('JSON Path');
default:
return text;
}
},
},
{
title: $t('匹配值'),
dataIndex: ['match', 'value'],
key: 'matchValue',
render: (text: string) => {
switch (text) {
case 'name':
return $t('姓名');
case 'phone':
return $t('手机号');
case 'id-card':
return $t('身份证号');
case 'bank-card':
return $t('银行卡号');
case 'date':
return $t('日期');
case 'amount':
return $t('金额');
default:
return text;
}
},
},
{
title: $t('脱敏类型'),
dataIndex: ['mask', 'type'],
key: 'maskType',
render: (text: string) => {
switch (text) {
case 'partial-display':
return $t('局部显示');
case 'partial-masking':
return $t('局部遮蔽');
case 'truncation':
return $t('截取');
case 'replacement':
return $t('替换');
case 'shuffling':
return $t('乱序');
default:
return text;
}
},
},
{
title: $t('脱敏规则'),
dataIndex: 'mask',
key: 'maskRule',
render: (mask: any) => {
switch (mask.type) {
case 'replacement':
return (
<Tooltip title={`${$t('类型')}${mask.replace.type === 'random' ? $t('随机字符串') : $t('自定义字符串; 值:')}${mask.replace.value}`}>
{$t('类型')}{mask.replace.type === 'random' ? $t('随机字符串') : $t('自定义字符串; 值:')}{mask.replace.value}
</Tooltip>
);
case 'shuffling':
return '-';
default:
return (
<Tooltip title={$t('起始位置:(0)位;长度:(1)位',[mask.begin,mask.length])}>
{$t('起始位置:(0)位;长度:(1)位',[mask.begin,mask.length])}
</Tooltip>
);
}
},
},
{
title: COLUMNS_TITLE.operate,
key: 'action',
render: (_: any, record: MaskRuleData) => (
<TableBtnWithPermission key="edit" btnType="edit" onClick={()=>{openDrawer('edit', record)}} btnTitle={$t("编辑")}/>
),
},
],[state.language])
return (
<div>
{
!disabled &&<Button onClick={() => openDrawer('add')}>
{$t('添加配置')}
</Button>
}
{value && value.length >0 && <Table
className={disabled ? '' : 'mt-btnbase'}
size='small'
pagination={false}
columns={columns} dataSource={value} rowKey="eoKey" /> }
<DataMaskRuleForm
editData={editData}
ruleList={value}
onSave={handleSave}
onClose={closeDrawer}
modalVisible = {isModalVisible}
/>
</div>
);
};
export default DataMaskRuleTable;
@@ -1,70 +0,0 @@
import { PageProColumns } from "@common/components/aoplatform/PageList";
import { frontendTimeSorter } from "@common/utils/dataTransfer";
import { $t } from "@common/locales";
export const DATA_MASSKING_TABLE_COLUMNS: PageProColumns<any>[] = [
{
title: ('策略名称'),
dataIndex: 'name',
ellipsis: true,
filters: true,
onFilter: true,
valueType: 'select',
filterSearch: true,
width: 160
},
{
title: ('优先级'),
dataIndex: 'priority',
width: 140,
ellipsis: true,
sorter: (a: any, b: any) => {
return (a.priority as number) - (b.priority as number)
}
},
{
title: ('发布状态'),
dataIndex: 'status',
filters: true,
onFilter: true,
width: 140,
valueEnum: new Map([
[true, <span className="text-status_success">{$t('已发布')}</span>],
[false, <span className="text-status_fail">{$t('未发布')}</span>]
])
},
{
title: ('启用'),
dataIndex: 'enabled',
filters: true,
onFilter: true,
valueEnum: {
true: { text: <span className="text-status_success">{$t('启用')}</span> },
false: { text: <span className="text-status_fail">{$t('禁用')}</span> }
}
},
{
title: ('筛选条件'),
dataIndex: 'condition',
ellipsis: true
},
{
title: ('处理数'),
dataIndex: 'treatmentNumber',
ellipsis: true
},
{
title: ('更新者'),
dataIndex: 'updater',
width: 140,
ellipsis: true
},
{
title: ('更新时间'),
dataIndex: 'createTime',
width: 182,
ellipsis: true,
sorter: (a, b) => frontendTimeSorter(a, b, 'createTime')
},
];
@@ -1,23 +1,27 @@
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
import { $t } from "@common/locales/index.ts";
import PolicyTabContainer from "./policyTabContainer.tsx";
import DataMasking from "./dataMasking.tsx";
import DataMasking from "./dataMasking/DataMasking.tsx";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
import { useMemo } from "react";
import { useParams } from "react-router-dom";
import { RouterParams } from "@common/const/type.ts";
const PartitionInsideGlobalPolicy = () => {
const {state} = useGlobalContext()
const {serviceId} = useParams<RouterParams>()
/**
* tab列表
*/
const tabItems = [
const tabItems =useMemo(()=> [
{
key: 'dataMasking',
label: $t('数据脱敏'),
children: <div className="pr-[40px] preview-document h-full pb-[40px]"><DataMasking publishBtn rowOperation={['edit', 'logs', 'delete']} /></div>
}
]
],[state.language])
return (
<>
<InsidePage
pageTitle={$t('全局策略')}
description={$t("支持对系统全局进行统一的策略配置,从而简化管理并确保一致性。全局策略的优先级比服务策略略低。")}
@@ -26,7 +30,6 @@ const PartitionInsideGlobalPolicy = () => {
>
<PolicyTabContainer tabs={tabItems} />
</InsidePage>
</>
)
}
@@ -1,6 +1,6 @@
import { $t } from "@common/locales/index.ts";
import DataMasking from "@core/pages/policy/dataMasking";
import PolicyTabContainer from "@core/pages/policy/policyTabContainer";
import DataMasking from "./dataMasking/DataMasking";
import PolicyTabContainer from "./policyTabContainer.tsx";
const servicePolicy = () => {
/**
@@ -10,9 +10,10 @@ const servicePolicy = () => {
{
key: 'dataMasking',
label: $t('数据脱敏'),
children: <div className="pr-[40px] h-full preview-document mb-PAGE_INSIDE_B"><DataMasking rowOperation={['edit', 'logs', 'delete']} /></div>
children: <div className="pr-[40px] h-full preview-document mb-PAGE_INSIDE_B"><DataMasking publishBtn={false} rowOperation={['edit', 'logs', 'delete']} /></div>
}
]
return (
<>
<PolicyTabContainer tabs={tabItems} />
@@ -125,9 +125,10 @@ const RoleList = ()=>{
pageTitle={$t('角色')}
description={$t("设置角色的权限范围。")}
showBorder={false}
scrollPage={false}
scrollPage={true}
>
<div className="pr-PAGE_INSIDE_X">
<div className="pr-PAGE_INSIDE_X overflow-y-scroll h-full">
<div>
<h3 className="mt-0">{$t('系统级别角色')}</h3>
<PageList
id="global_role"
@@ -145,6 +146,8 @@ const RoleList = ()=>{
onRowClick={(row:RoleTableListItem)=> navigateTo(`/role/system/config/${row.id}`)}
tableClickAccess="system.organization.role.system.edit"
/>
</div>
<div>
<h3 className=" pt-btnbase ">{$t('团队级别角色')}</h3>
<PageList
id="global_role"
@@ -163,6 +166,7 @@ const RoleList = ()=>{
tableClickAccess="system.organization.role.team.edit"
/>
</div>
</div>
</InsidePage>
</>)
}
@@ -1,7 +1,6 @@
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";
@@ -15,11 +14,12 @@ import { ItemType, MenuItemGroupType, MenuItemType } from "antd/es/menu/hooks/us
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;
const SystemInsidePage: FC = () => {
const { message } = App.useApp()
const { teamId, serviceId, apiId, routeId } = useParams<RouterParams>();
const { teamId, serviceId, apiId, routeId,policyId } = useParams<RouterParams>();
const location = useLocation()
const currentUrl = location.pathname
const { fetchData } = useFetch()
@@ -63,7 +63,7 @@ const SystemInsidePage: FC = () => {
getItem(<Link to="./api">{$t('API 文档')}</Link>, 'api', undefined, undefined, undefined, 'team.service.api_doc.view'),
getItem(<Link to="./upstream">{$t('上游')}</Link>, 'upstream', undefined, undefined, undefined, 'team.service.upstream.view'),
getItem(<Link to="./document">{$t('使用说明')}</Link>, 'document', undefined, undefined, undefined, 'team.service.service_intro.view'),
getItem(<Link to="./servicePolicy">{$t('服务策略')}</Link>, 'servicePolicy', undefined, undefined, undefined, 'team.service.service_intro.view'),
getItem(<Link to="./servicepolicy">{$t('服务策略')}</Link>, 'servicepolicy', undefined, undefined, undefined, 'team.service.policy.view'),
getItem(<Link to="./publish">{$t('发布')}</Link>, 'publish', undefined, undefined, undefined, 'team.service.release.view'),
],
'group'),
@@ -74,11 +74,11 @@ const SystemInsidePage: FC = () => {
],
'group'),
getItem($t('管理'), 'mng', null,
[
APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology', undefined, undefined, undefined, 'project.mySystem.topology.view') : null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, '')],
'group'),
], [state.language])
[
// APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting',undefined,undefined,undefined,'')],
'group'),
],[state.language])
const menuData = useMemo(() => {
@@ -107,10 +107,13 @@ const SystemInsidePage: FC = () => {
setActiveMenu(key)
};
useEffect(() => {
setShowMenu(!routeId && !currentUrl.includes('route/create'))
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 {
@@ -1,18 +1,16 @@
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 styles from "./SystemInsideApi.module.css"
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 } from "@common/const/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 { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
import { useNavigate, useParams } from "react-router-dom";
import { useSystemContext } from "@core/contexts/SystemContext.tsx";
import InsidePage from "@common/components/aoplatform/InsidePage.tsx";
@@ -236,8 +234,6 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle,Syste
configFields={translatedMatchConfig}
/>
</Form.Item>
{/* } */}
<Row className="mb-btnybase mt-[40px]"><Col ><span className="font-bold mr-[13px]">{$t('转发规则设置')} </span></Col></Row>
<Form.Item<SystemApiProxyFieldType>
@@ -4,10 +4,9 @@ import { useState, useRef, useEffect, useMemo, FC } from "react";
import { useParams, Link, useLocation } from "react-router-dom";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { PublishApprovalModalContent } from "@common/components/aoplatform/PublishApprovalModalContent";
import { RouterParams } from "@core/components/aoplatform/RenderRoutes";
import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN, PublishApplyStatusEnum, PublishStatusEnum, PublishTableStatusColorClass } from "@common/const/approval/const";
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { SimpleMemberItem } from "@common/const/type.ts";
import { RouterParams, SimpleMemberItem } from "@common/const/type.ts";
import { MemberTableListItem } from "../../../const/member/type";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
import { useFetch } from "@common/hooks/http";

Some files were not shown because too many files have changed in this diff Show More