From dce9a7addbc87cef0edeaf880149d122426e2ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E6=A2=A6=E6=B4=81?= Date: Thu, 21 Nov 2024 16:08:08 +0800 Subject: [PATCH] feat: Complete static pages for Phase 1 of V1.3 --- .../src/components/aoplatform/BasicLayout.tsx | 9 +- .../aoplatform/PolicyPublishModalContent.tsx | 96 ++++ .../aoplatform/UnUsedWordForTranslate.tsx | 16 + frontend/packages/common/src/const/const.tsx | 41 +- .../common/src/const/policy/consts.tsx | 57 +++ .../packages/common/src/const/policy/type.ts | 123 +++++ frontend/packages/common/src/const/type.ts | 28 +- .../src/contexts/GlobalStateContext.tsx | 4 +- .../common/src/contexts/LocaleContext.tsx | 17 +- .../common/src/locales/keyHashMap.json | 89 ++-- .../common/src/locales/scan/en-US.json | 43 +- .../common/src/locales/scan/ja-JP.json | 43 +- .../src/locales/scan/newJson/en-US.json | 4 +- .../src/locales/scan/newJson/ja-JP.json | 4 +- .../src/locales/scan/newJson/zh-CN.json | 45 +- .../src/locales/scan/newJson/zh-TW.json | 4 +- .../src/locales/scan/oldJson/en-US.json | 11 + .../src/locales/scan/oldJson/ja-JP.json | 11 + .../src/locales/scan/oldJson/zh-CN.json | 11 + .../src/locales/scan/oldJson/zh-TW.json | 11 + .../common/src/locales/scan/zh-TW.json | 43 +- .../packages/common/src/utils/validate.ts | 33 ++ frontend/packages/core/src/App.css | 7 + frontend/packages/core/src/const/const.tsx | 47 +- .../packages/core/src/const/system/const.tsx | 7 +- frontend/packages/core/src/index.css | 7 +- frontend/packages/core/src/main.tsx | 3 + .../core/src/pages/policy/FilterForm.tsx | 256 ++++++++++ .../core/src/pages/policy/FilterTable.tsx | 144 ++++++ .../src/pages/policy/GlobalPolicyLayout.tsx | 14 + .../core/src/pages/policy/dataMasking.tsx | 463 ------------------ .../pages/policy/dataMasking/DataMasking.tsx | 331 +++++++++++++ .../DataMaskingColumn.tsx} | 37 +- .../policy/dataMasking/DataMaskingConfig.tsx | 171 +++++++ .../dataMasking/DataMaskingRuleForm.tsx | 158 ++++++ .../dataMasking/DataMaskingRuleTable.tsx | 152 ++++++ .../core/src/pages/policy/globalPolicy.tsx | 11 +- .../core/src/pages/policy/servicePolicy.tsx | 4 +- .../system/api/SystemInsideRouterCreate.tsx | 6 +- .../publish/SystemInsidePublishList.tsx | 3 +- .../dashboard/src/component/MonitorTable.tsx | 7 +- .../src/component/MonitorTotalPage.tsx | 11 +- .../dashboard/src/pages/DashboardTabPage.tsx | 9 +- .../market/src/const/serviceHub/type.ts | 1 + frontend/packages/market/src/index.css | 1 + .../src/pages/serviceHub/ServiceHubDetail.tsx | 8 +- .../market/src/pages/serviceHub/integrate.tsx | 43 +- 47 files changed, 2035 insertions(+), 609 deletions(-) create mode 100644 frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx create mode 100644 frontend/packages/common/src/const/policy/consts.tsx create mode 100644 frontend/packages/common/src/const/policy/type.ts create mode 100644 frontend/packages/core/src/pages/policy/FilterForm.tsx create mode 100644 frontend/packages/core/src/pages/policy/FilterTable.tsx create mode 100644 frontend/packages/core/src/pages/policy/GlobalPolicyLayout.tsx delete mode 100644 frontend/packages/core/src/pages/policy/dataMasking.tsx create mode 100644 frontend/packages/core/src/pages/policy/dataMasking/DataMasking.tsx rename frontend/packages/core/src/pages/policy/{dataMaskingColumn.tsx => dataMasking/DataMaskingColumn.tsx} (54%) create mode 100644 frontend/packages/core/src/pages/policy/dataMasking/DataMaskingConfig.tsx create mode 100644 frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx create mode 100644 frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleTable.tsx diff --git a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx index 997f1068..b46732a7 100644 --- a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx +++ b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx @@ -75,21 +75,20 @@ const themeToken = { 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; + return {...item,name:$t(item.name) }; }) .filter(x => x); // 过滤掉处理后为 null 的项 }; // 初始过滤操作 - const res = [...(menuItems || [])]!.filter(x => x).map((x: any) => (x.routes ? { ...x, routes: filterMenu(x.routes) } : x)); + 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]); diff --git a/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx b/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx new file mode 100644 index 00000000..c8df5598 --- /dev/null +++ b/frontend/packages/common/src/components/aoplatform/PolicyPublishModalContent.tsx @@ -0,0 +1,96 @@ +import {App, Form, Input, Row, Table} from "antd"; +import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react"; +import {useFetch} from "@common/hooks/http.ts"; +import {BasicResponse, PLACEHOLDER, PolicyPublishColumns, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; +import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; +import { $t } from "@common/locales"; +import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +import { PolicyPublishModalHandle, PolicyPublishModalProps } from "@common/const/type"; + + +export const PolicyPublishModalContent = forwardRef((props, ref) => { + const { message } = App.useApp() + const { data} = props + const [form] = Form.useForm(); + const {fetchData} = useFetch() + const {state} = useGlobalContext() + + const publish:()=>Promise> = ()=>{ + return new Promise((resolve, reject)=>{ + form.validateFields().then((value)=>{ + const body = {...value, source:data.source} + fetchData>('strategy/global/data-masking/publish',{method: 'POST',eoBody:body,eoTransformKeys:['versionName']}).then(response=>{ + const {code,msg} = response + if(code === STATUS_CODE.SUCCESS){ + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(response) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }).catch((errorInfo)=> reject(errorInfo)) + }).catch((errorInfo)=> reject(errorInfo)) + }) + } + + useImperativeHandle(ref, ()=>({ + publish, + }) + ) + + useEffect(()=>{ + form.setFieldsValue(data) + },[data]) + + const translatedPolicyColumns = useMemo(()=>PolicyPublishColumns.map((x)=>({ + ...x, + title: typeof x.title === 'string' ? $t(x.title) : x.title, +})),[state.language]) + +console.log(translatedPolicyColumns,data.strategies) + + return ( + <> +
+ + + + + + + + + {$t('策略列表')}: + + + {!data?.isPublish&& data?.unpublishMsg&&

{data.unpublishMsg}

} + + + + + ) +}) \ No newline at end of file diff --git a/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx b/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx index 5cac8172..4cba7f4a 100644 --- a/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx +++ b/frontend/packages/common/src/components/aoplatform/UnUsedWordForTranslate.tsx @@ -163,6 +163,22 @@ export const TranslateWord = ()=>{ {$t('优先级')} {$t('筛选条件')} {$t('处理数')} + {$t('数据格式')} + {$t('关键字')} + {$t('正则表达式')} + {$t('手机号')} + {$t('身份证号')} + {$t('银行卡号')} + {$t('金额')} + {$t('日期')} + {$t('局部显示')} + {$t('局部遮蔽')} + {$t('截取')} + {$t('替换')} + {$t('乱序')} + {$t('随机字符串')} + {$t('自定义字符串')} + {$t('请输入IP地址或CIDR范围,每条以换行分割')} ) } \ No newline at end of file diff --git a/frontend/packages/common/src/const/const.tsx b/frontend/packages/common/src/const/const.tsx index 84b7481c..092671f6 100644 --- a/frontend/packages/common/src/const/const.tsx +++ b/frontend/packages/common/src/const/const.tsx @@ -1,10 +1,5 @@ -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 { TenantManagementProvider } from '@market/contexts/TenantManagementContext' -import { lazy } from 'react' -import { Outlet, Navigate } from 'react-router-dom' +import { $t } from '@common/locales' +import { StrategyStatusColorClass, StrategyStatusEnum } from './policy/consts' export type BasicResponse = { code:number @@ -24,7 +19,7 @@ export const STATUS_COLOR = { } -// avoid changing route within ths same category +// TODO should be generated dynamically export const routerKeyMap = new Map([ ['workspace',['consumer','service','team','guide']], ['my',['consumer','service','team']], @@ -52,6 +47,7 @@ export const routerKeyMap = new Map([ startWithAlphabet:('英文数字下划线任意一种,首字母必须为英文'), specialStartWithAlphabet:('支持字母开头、英文数字中横线下划线组合'), onlyAlphabet:('字符非法,仅支持英文'), + ipAndCidr:'请输入IP地址或CIDR范围,每条以换行分割' } export const FORM_ERROR_TIPS = { @@ -81,3 +77,32 @@ export const routerKeyMap = new Map([ {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)=> {$t(StrategyStatusEnum[text as keyof typeof StrategyStatusEnum])}, + }, + { + title: ('更新时间'), + dataIndex: 'optTime', + width: 182, + ellipsis: true, + }, +] \ No newline at end of file diff --git a/frontend/packages/common/src/const/policy/consts.tsx b/frontend/packages/common/src/const/policy/consts.tsx new file mode 100644 index 00000000..d9897596 --- /dev/null +++ b/frontend/packages/common/src/const/policy/consts.tsx @@ -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', + } + \ No newline at end of file diff --git a/frontend/packages/common/src/const/policy/type.ts b/frontend/packages/common/src/const/policy/type.ts new file mode 100644 index 00000000..f91403aa --- /dev/null +++ b/frontend/packages/common/src/const/policy/type.ts @@ -0,0 +1,123 @@ +import { DefaultOptionType } from "antd/es/select"; +import { StrategyStatusEnum } from "./consts"; + +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 + isDeleted:boolean + publishStatus:keyof typeof StrategyStatusEnum + filters:string + conf:string + operator:string + updateTime:string + } + + +export type FilterFormField= { + name: string; + value: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 + value:unknown + } + + export type FilterFormProps = { + filterForm: FilterFormType; + filterOptions:DefaultOptionType[]; + selectedOptionNameSet: Set; + disabled: boolean; + onFilterFormChange: (form: FilterFormType) => void; + setFormCanSubmit:(canSubmit:boolean)=>void + } + + export type FilterFormHandle = { + clear:()=>void + save:()=>Promise + } + + export type FilterFormItemProps = { + value?: string[]; + onChange?: (value: string[]) => void; + disabled:boolean + option:unknown + onShowValueChange?:(value:string)=>void + } + + export type RemoteTitleType = { + title:string + field:string + } \ No newline at end of file diff --git a/frontend/packages/common/src/const/type.ts b/frontend/packages/common/src/const/type.ts index 7a890724..e0cfe1bf 100644 --- a/frontend/packages/common/src/const/type.ts +++ b/frontend/packages/common/src/const/type.ts @@ -3,6 +3,7 @@ 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 @@ -141,6 +142,7 @@ export type RouterParams = { roleType:string roleId:string routeId:string + policyId:string } @@ -185,4 +187,28 @@ export type RouterMapConfig = { data?:Record pathMatch?:string } - \ No newline at end of file + + +export type PolichPublishItemType = { + name:string + priority:number + status:keyof typeof StrategyStatusEnum + optTime:string +} + +// 发布详情(版本) +export type PolicyPublishInfoType = { + source:string + strategies:Array + isPublish:boolean + versionName:string + unpublishMsg:string +}; + +export type PolicyPublishModalProps = { + data:PolicyPublishInfoType +} + +export type PolicyPublishModalHandle = { + publish:()=>Promise> +} \ No newline at end of file diff --git a/frontend/packages/common/src/contexts/GlobalStateContext.tsx b/frontend/packages/common/src/contexts/GlobalStateContext.tsx index b9b83b4d..8fa8215e 100644 --- a/frontend/packages/common/src/contexts/GlobalStateContext.tsx +++ b/frontend/packages/common/src/contexts/GlobalStateContext.tsx @@ -91,14 +91,14 @@ export type GlobalAction = }, { "name": "仪表盘", - "key": "mainPage", + "key": "analytics", "path": "/analytics", "icon": "ic:baseline-bar-chart", "children": [ { "name": "运行视图", "key": "analytics", - "path": "/analytics/total", + "path": "/analytics", "icon": "ic:baseline-bar-chart", "access": "system.analysis.run_view.view" } diff --git a/frontend/packages/common/src/contexts/LocaleContext.tsx b/frontend/packages/common/src/contexts/LocaleContext.tsx index 4f5aa7b8..497e1a79 100644 --- a/frontend/packages/common/src/contexts/LocaleContext.tsx +++ b/frontend/packages/common/src/contexts/LocaleContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, useState, useEffect, useMemo } from 'react'; +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'; @@ -21,12 +21,9 @@ const languageMap: Record = { const LocaleContext = createContext<{ locale: Locale; setLocale: (locale: string) => void; -}>({ - locale: zhCN, - setLocale: () => {}, -}); +}|undefined>(undefined); -export const LocaleProvider: React.FC = ({ children }) => { +export const LocaleProvider: FC<{children:ReactNode}> = ({ children }) => { const [locale, setLocaleState] = useState(zhCN); const setLocale = (language: string) => { @@ -41,4 +38,10 @@ export const LocaleProvider: React.FC = ({ children }) => { ); }; -export const useLocaleContext = () => useContext(LocaleContext); \ No newline at end of file +export const useLocaleContext = () => { + const context = useContext(LocaleContext); + if (!context) { + throw new Error('useLocaleContext must be used within a LocaleContext'); + } + return context; +}; \ No newline at end of file diff --git a/frontend/packages/common/src/locales/keyHashMap.json b/frontend/packages/common/src/locales/keyHashMap.json index becc4647..6fd4a111 100644 --- a/frontend/packages/common/src/locales/keyHashMap.json +++ b/frontend/packages/common/src/locales/keyHashMap.json @@ -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", @@ -235,6 +215,26 @@ "地址": "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", "暂无操作权限,请联系管理员分配。": "K23fda291", "微信小程序": "K4618cb0a", "获取文件,需填路径": "Ka854f511", @@ -322,11 +322,14 @@ "至": "K7e1ab4b0", "详情": "Kf1b166e7", "暂不支持带有双斜杠//的url": "K28555332", + "输入的IP或CIDR不符合格式": "K83237c89", + "请正确输入路径,如/usr/*或*/usr/*": "K5ae2c87a", "必填项": "K71661ee8", "不是有效邮箱地址": "Kcbee3f8", "最近一次更新者": "K617f34f1", "最近一次更新时间": "K6ebca204", "保存": "Kabfe9512", + "服务": "Kb58e0c3f", "API 路由": "K51d1eb5d", "API 文档": "Ka2b6d281", "使用说明": "Kdefa9caa", @@ -402,11 +405,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 +419,7 @@ "如果需要调用某个服务的 API,需要先订阅该服务,并且等待提供服务的团队审核后才可发起 API 请求。": "Kf2410413", "审核订阅申请": "K6c2e44b8", "提供服务的团队可以审核来自其他团队的订阅申请,审核通过后的消费者才可发起 API 请求。": "Kaa717866", + "集成": "K4057391a", "APIPark 提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K3453272", "Hello!欢迎使用 APIPark": "Kd518ba3e", "你能通过 APIPark 快速在企业内部构建 API 开放门户/市场,享受极致的转发性能、API 可观测、服务治理、多租户管理、订阅审核流程等诸多好处。": "K7e04ea16", @@ -425,6 +431,7 @@ "了解更多功能": "K48f7e21f", "隐藏该教程": "K698296e2", "请输入账号": "Kf076f63c", + "账号": "K80a560a1", "请输入密码": "K25c895d5", "密码": "K551b0348", "登录": "Kd2c1a316", @@ -467,6 +474,7 @@ "密钥": "K8ef69ee2", "上传密钥": "Kba3507d6", "密钥文件的后缀名一般为 .key 的文件内容": "K93ac0f23", + "证书": "K481e8a05", "上传证书": "K7cdd1331", "证书文件的后缀名一般为 .crt 或 .pem 的文件内容": "K6d91905d", "添加证书": "Kd0f6ded7", @@ -485,19 +493,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", @@ -578,6 +606,7 @@ "退出全屏": "Kaf70c3b", "(0)调用详情": "Kd22841a4", "消费者调用统计": "K61cca533", + "消费者": "K7acfcfad", "请选择消费者": "Kdfff59d4", "调用趋势": "K8c7f2d2e", "(0)-(1)调用趋势": "K657c3452", @@ -610,6 +639,7 @@ "暂无调用量统计数据": "Kcd125e4d", "暂无报文量统计数据": "Kaa114e8b", "报文量统计": "K3ad84406", + "运行视图": "K714c192d", "集群配置并开启监控": "Kfa088d49", "监控功能用于辅助管理集群内信息,请配置集群、设置监控信息后查看当前集群监控情况;": "K3da3b9a0", "集群配置": "Kaddacfb", @@ -699,6 +729,7 @@ "配置 Open Api": "K7829bb78", "Open Api": "Kcdf76005", "调用服务": "Ke2601944", + "系统拓扑图": "Kd57dfe97", "放大": "K8504bca8", "缩小": "K693c1b41" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/en-US.json b/frontend/packages/common/src/locales/scan/en-US.json index 7b4013bf..a693bcda 100644 --- a/frontend/packages/common/src/locales/scan/en-US.json +++ b/frontend/packages/common/src/locales/scan/en-US.json @@ -703,5 +703,46 @@ "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)" } diff --git a/frontend/packages/common/src/locales/scan/ja-JP.json b/frontend/packages/common/src/locales/scan/ja-JP.json index 7b03a501..8559a6bb 100644 --- a/frontend/packages/common/src/locales/scan/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/ja-JP.json @@ -725,5 +725,46 @@ "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)" } diff --git a/frontend/packages/common/src/locales/scan/newJson/en-US.json b/frontend/packages/common/src/locales/scan/newJson/en-US.json index ddcaee31..9e26dfee 100644 --- a/frontend/packages/common/src/locales/scan/newJson/en-US.json +++ b/frontend/packages/common/src/locales/scan/newJson/en-US.json @@ -1,3 +1 @@ -{ - "Kf5fd27ed": "输入名称查找用户" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/newJson/ja-JP.json b/frontend/packages/common/src/locales/scan/newJson/ja-JP.json index ddcaee31..9e26dfee 100644 --- a/frontend/packages/common/src/locales/scan/newJson/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/newJson/ja-JP.json @@ -1,3 +1 @@ -{ - "Kf5fd27ed": "输入名称查找用户" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/newJson/zh-CN.json b/frontend/packages/common/src/locales/scan/newJson/zh-CN.json index 44f232eb..878c5a8e 100644 --- a/frontend/packages/common/src/locales/scan/newJson/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/newJson/zh-CN.json @@ -1,4 +1,7 @@ { + "K630c9e6d": "APIPark", + "Ka3e9f580": "发布名称", + "Kb2480682": "策略列表", "K76036e25": "HTTP 请求头", "K44607e3f": "全等匹配", "Kc287500a": "前缀匹配", @@ -21,7 +24,43 @@ "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范围,每条以换行分割", + "K83237c89": "输入的IP或CIDR不符合格式", + "K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*", + "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": "输入名称查找用户" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/newJson/zh-TW.json b/frontend/packages/common/src/locales/scan/newJson/zh-TW.json index ddcaee31..9e26dfee 100644 --- a/frontend/packages/common/src/locales/scan/newJson/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/newJson/zh-TW.json @@ -1,3 +1 @@ -{ - "Kf5fd27ed": "输入名称查找用户" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/en-US.json b/frontend/packages/common/src/locales/scan/oldJson/en-US.json index 8b7a4c95..f4755dd7 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/en-US.json +++ b/frontend/packages/common/src/locales/scan/oldJson/en-US.json @@ -1,4 +1,15 @@ { + "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", "Kb9052305": "Search Username, Email", "K40a89bd8": "Enter Name, ID to Search Member" } \ No newline at end of file diff --git a/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json b/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json index 552991b2..56a74a5c 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json +++ b/frontend/packages/common/src/locales/scan/oldJson/ja-JP.json @@ -1,5 +1,16 @@ { + "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 を安全に呼び出すためには、アプリケーションとトークンを作成する必要があります。", diff --git a/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json b/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json index d2364570..502e8f18 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/oldJson/zh-CN.json @@ -1,5 +1,16 @@ { + "Kc0e5ef9f": "工作空间", + "K4de11e23": "首页", "Kfe93ef35": "消费者", + "K61c89f5f": "API 门户", + "K3fe97dcc": "系统设置", + "Kecbb0e45": "系统", + "Ka358e23d": "常规", + "K449058e9": "API 网关", + "K99935e6f": "AI 模型", + "K1deaa2dd": "用户", + "K631d646f": "Open API", + "K1196b104": "APIPark", "Kffd7e274": "无审核:允许所有消费者订阅该服务", "K8a8b13e4": "人工审核:仅允许审核通过的消费者订阅该服务", "K9bdd8403": "为了安全地调用 API,你需要创建一个消费者以及Token。", diff --git a/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json b/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json index 7ac8fd66..46e9364a 100644 --- a/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/oldJson/zh-TW.json @@ -1,5 +1,16 @@ { + "Kc0e5ef9f": "工作區", + "K4de11e23": "主頁", "Kfe93ef35": "應用程式", + "K61c89f5f": "API 門戶", + "K3fe97dcc": "系統設置", + "Kecbb0e45": "系統", + "Ka358e23d": "常規", + "K449058e9": "API 網關", + "K99935e6f": "AI 模型", + "K1deaa2dd": "用戶", + "K631d646f": "Open API", + "K1196b104": "APIPark", "Kffd7e274": "無審核:允許所有應用程式訂閱該服務", "K8a8b13e4": "人工審核:僅允許審核通過的應用程式訂閱該服務", "K9bdd8403": "為了安全地調用 API,你需要創建一個應用以及Token。", diff --git a/frontend/packages/common/src/locales/scan/zh-TW.json b/frontend/packages/common/src/locales/scan/zh-TW.json index 16b2e6e3..6c5787a9 100644 --- a/frontend/packages/common/src/locales/scan/zh-TW.json +++ b/frontend/packages/common/src/locales/scan/zh-TW.json @@ -725,5 +725,46 @@ "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)" } diff --git a/frontend/packages/common/src/utils/validate.ts b/frontend/packages/common/src/utils/validate.ts index e0a63f5e..ca780fc4 100644 --- a/frontend/packages/common/src/utils/validate.ts +++ b/frontend/packages/common/src/utils/validate.ts @@ -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(); }; \ No newline at end of file diff --git a/frontend/packages/core/src/App.css b/frontend/packages/core/src/App.css index dbc2b579..e5f6c0b2 100644 --- a/frontend/packages/core/src/App.css +++ b/frontend/packages/core/src/App.css @@ -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; + } } \ No newline at end of file diff --git a/frontend/packages/core/src/const/const.tsx b/frontend/packages/core/src/const/const.tsx index 6e3c8e6b..5a937d28 100644 --- a/frontend/packages/core/src/const/const.tsx +++ b/frontend/packages/core/src/const/const.tsx @@ -172,7 +172,7 @@ import { Outlet, Navigate } from 'react-router-dom'; { path:'servicepolicy', key: 'servicePolicy', - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/servicePolicy')), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/ServicePolicy')), children:[ ] @@ -266,7 +266,7 @@ import { Outlet, Navigate } from 'react-router-dom'; { path:'servicepolicy', key: 'servicePolicy', - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/servicePolicy')), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/ServicePolicy')), children:[ ] @@ -413,6 +413,23 @@ import { Outlet, Navigate } from 'react-router-dom'; key:'analytics2', lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardTotal.tsx')), }, + { + path:':dashboardType', + key:'analytics3', + component:, + 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', @@ -449,6 +466,28 @@ import { Outlet, Navigate } from 'react-router-dom'; key:'changePsw' }]}], ['globalPolicy', { type: 'module', - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/globalPolicy.tsx')), - key:'globalPolicy'}] + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/policy/GlobalPolicyLayout')), + key:'globalPolicy', + children:[{ + path:'datamasking', + component:, + 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' + }] + }] + }], ]) \ No newline at end of file diff --git a/frontend/packages/core/src/const/system/const.tsx b/frontend/packages/core/src/const/system/const.tsx index 19d329a2..36711421 100644 --- a/frontend/packages/core/src/const/system/const.tsx +++ b/frontend/packages/core/src/const/system/const.tsx @@ -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[] = [ }, { title:('参数名'), key: 'key', - component: , + component: , renderText: (value: unknown) => value, required: true }, { @@ -236,7 +237,7 @@ export const MATCH_CONFIG:ConfigField[] = [ title:('参数值'), key: 'pattern', unRender:(formValue)=>{return formValue?.matchType === 'NULL' || formValue?.matchType==='EXIST' || formValue?.matchType === 'UNEXIST'}, - component: , + component: , renderText: (value: string) => { return value }, diff --git a/frontend/packages/core/src/index.css b/frontend/packages/core/src/index.css index 7b0ade1e..dcfb7383 100644 --- a/frontend/packages/core/src/index.css +++ b/frontend/packages/core/src/index.css @@ -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; diff --git a/frontend/packages/core/src/main.tsx b/frontend/packages/core/src/main.tsx index c8bd43ec..6aa026a9 100644 --- a/frontend/packages/core/src/main.tsx +++ b/frontend/packages/core/src/main.tsx @@ -3,6 +3,7 @@ import {StrictMode} from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' +import { LocaleProvider } from '@common/contexts/LocaleContext.tsx'; async function initializeApp() { try { // 初始化行为 @@ -11,7 +12,9 @@ async function initializeApp() { // 异步操作完成后,渲染React消费者 ReactDOM.createRoot(document.getElementById('root')!).render( + + , ); } catch (error) { diff --git a/frontend/packages/core/src/pages/policy/FilterForm.tsx b/frontend/packages/core/src/pages/policy/FilterForm.tsx new file mode 100644 index 00000000..5a082df3 --- /dev/null +++ b/frontend/packages/core/src/pages/policy/FilterForm.tsx @@ -0,0 +1,256 @@ +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'; + +const RemoteFormItem: React.FC = (props) =>{ + const {value, onChange, disabled,option, onShowValueChange} = props + const [remoteList, setRemoteList] = useState([]) + const [remoteTableColumns, setRemoteTableColumns] = useState([]) + const [loading, setLoading] = useState(false) + const [rowKey, setRowKey] = useState('') + const title = useMemo(()=>option?.title,[option]) + const [remoteCounts, setRemoteCounts] = useState(0) + const [originRemoteList, setOriginRemoteList] = useState([]) + const {fetchData} = useFetch() + + + const getRemoteDetail = (searchWord?:string)=>{ + setLoading(true) + fetchData[], + target:string, + titles:Array, + total:number + value:string + }>>(`strategy/filter-REMOTE/${option?.name}`,{method:'GET', eoParams:{keyword:searchWord}}).then(response=>{ + const {code,data, msg} = response + if(code === STATUS_CODE.SUCCESS){ + setRemoteList(data[data.target as string] 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[data.target as string]) + if(value?.length === 1 && value[0] === 'ALL'){ + const totalDataArr = data[data.target as string]?.map((x:Record)=>x[data.key as string]) + onChange?.(totalDataArr) + onShowValueChange?.(totalDataArr.join(',')) + } + } + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }).finally(()=>{ + setLoading(false) + }) + } + + useEffect(()=>{getRemoteDetail()},[option]) + return ( +
+ } spinning={loading}> +

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

+
+
+ getRemoteDetail(value)} + disabled={disabled} + /> +
+
(disabled ? '' : 'clickable-row')} + rowKey={rowKey} + size='small' + rowSelection={{ + type: 'checkbox', + onChange: (selectedRowKeys: React.Key[]) => { + onChange?.(selectedRowKeys as string[]); + onShowValueChange?.(selectedRowKeys.length === remoteCounts? $t('所有(0)',[title]) : originRemoteList.filter(x=>selectedRowKeys?.indexOf(x[option.target]))?.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?.(record.title) + }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.title)?.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.title)?.join(' , ')) + } + } + })} + /> + + ) +} + +const StaticFormItem: React.FC = (props) => { + const {value, onChange, disabled,option,onShowValueChange} = props + const showAll = useMemo(()=>option.options.indexOf('ALL') !== -1,[option]) + const allChecked = useMemo(()=>value?.filter(x=>x!== 'ALL').length === option.options.filter(x=>x!== 'ALL').length,[value,option]) + + useEffect(()=>{ + if(value?.length === 1 && value[0] === 'ALL'){ + onChange?.(option.options.filter(x=>x!== 'ALL')) + onShowValueChange?.($t('所有(0)',[option?.title])) + } + },[]) + + return ( +
+ {showAll && ( + { + onChange?.(e.target.checked ? option.options : []) + onShowValueChange?.(e.target.checked ? $t('所有(0)',[option?.title]) : '-') + }} + disabled={disabled} + indeterminate={!allChecked && value?.length > 0} + > + ALL + + )} + x!== 'ALL')} + onChange={(checkedValues) => { + onChange?.(checkedValues) + onShowValueChange?.(checkedValues.join(',')) + }} + disabled={disabled} + /> +
) +} + +const FilterForm = forwardRef(({ + filterForm, + filterOptions, + selectedOptionNameSet, + disabled, + setFormCanSubmit},ref)=> { + const [form] = Form.useForm(); + const [filterType, setFilterType] = useState<'remote'|'static'|'pattern'>(); + const [curOption, setCurOption] = useState() + const [label,setLabel] = useState('') + + useImperativeHandle(ref, ()=>({ + clear:()=>{ + form.resetFields() + }, + save:()=>form.validateFields().then((res)=>{ + const selectedOption = filterOptions.filter(x=>x.name === res.name)[0] + return Promise.resolve({ + ...res, + label:filterType === 'pattern' ? res.value : label, + title:selectedOption.label + }) + }).catch((errorInfo)=>Promise.reject(errorInfo)) + }) + ) + + const handleValuesChange = (changedValues: any, allValues: any) => { + if(!allValues){ + setFormCanSubmit(false) + return + } + if(allValues.value instanceof Array){ + setFormCanSubmit(allValues.value.length > 0) + return + } + setFormCanSubmit(true) + }; + + + const handleTypeChange = (value:string)=>{ + form.setFieldValue('value',filterForm?.name === value ? filterForm.value : 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) + const selectedOption = filterOptions.filter(x=>x.name === filterForm?.name)[0] + setFilterType(selectedOption?.type ) + setCurOption(selectedOption) + }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 ( +
+ + + )} + + {filterType === 'pattern' && form.getFieldValue('name') === 'ip' && ( + + )} + + {filterType === 'static' && } + + + ); +}) + +export default FilterForm; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/FilterTable.tsx b/frontend/packages/core/src/pages/policy/FilterTable.tsx new file mode 100644 index 00000000..ccbcabfb --- /dev/null +++ b/frontend/packages/core/src/pages/policy/FilterTable.tsx @@ -0,0 +1,144 @@ +import React, { useState, useEffect, useRef, useMemo } 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'; + + +const FilterTable: React.FC = ({ + disabled = false, + drawerTitle = '筛选条件', + value, + onChange +}) => { + const [isModalVisible, setIsModalVisible] = useState(false); + const [filterForm, setFilterForm] = useState(); + const [filterOptions, setFilterOptions] = useState([]); + const {message} = App.useApp() + const {state} = useGlobalContext() + const {fetchData} = useFetch() + const formRef = useRef(null); + const [formCanSubmit,setFormCanSubmit] = useState(false) + const [selectedOptionNameSet, setSelectedOptionNameSet] = useState>(new Set()); + const openDrawer = (type: string, data?: FilterFormField) => { + switch (type) { + case 'addFilter': + setFilterForm(undefined) + break; + case 'editFilter': + console.log(data) + 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, + value:res.value instanceof Array ? res.value : [res.value], + label:res.label, + title:res.title + } + onChange?.([newFilterForm, ...(value?.filter(x=>!filterForm?.name|| x.name!==filterForm.name) || [])]) + setSelectedOptionNameSet(prev=>{filterForm?.name &&prev.delete(filterForm?.name); prev.add(res.name); return prev}) + closeDrawer() + }) + }; + + const handleDeleteFilter = (item: FilterFormField) => { + setSelectedOptionNameSet(prev=>{prev.delete(item?.name); return prev}) + const newFilterShowList = value.filter((filter) => filter !== item); + onChange?.(newFilterShowList); + }; + + const getFilterOptions = ()=>{ + fetchData>('strategy/filter-options',{method:'GET'}).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 =useMemo(()=>[ + { + title: $t('属性名称'), + dataIndex: 'name', + key: 'name', + }, + { + title: $t('属性值'), + dataIndex: 'label', + key: 'label', + }, + { + title: COLUMNS_TITLE.operate, + key: 'action', + width:100, + render: (_, record) => ( +
+ {openDrawer('editFilter', record)}} btnTitle={$t("编辑")}/> + + {handleDeleteFilter(record)}} btnTitle={$t("删除")}/>
) + } + ],[state.language]) + + useEffect(()=>{ + getFilterOptions() + },[state.language]) + + return ( +
+ { + !disabled && + } + {value && value.length >0 &&
} +
+
+ handleSaveFilter()} + destroyOnClose={true} + > + + + + ); +}; + +export default FilterTable; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/GlobalPolicyLayout.tsx b/frontend/packages/core/src/pages/policy/GlobalPolicyLayout.tsx new file mode 100644 index 00000000..d072238b --- /dev/null +++ b/frontend/packages/core/src/pages/policy/GlobalPolicyLayout.tsx @@ -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 () +} \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/dataMasking.tsx b/frontend/packages/core/src/pages/policy/dataMasking.tsx deleted file mode 100644 index 0245eab3..00000000 --- a/frontend/packages/core/src/pages/policy/dataMasking.tsx +++ /dev/null @@ -1,463 +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(null); - - /** - * 表格数据重新加载 - */ - const [tableHttpReload, setTableHttpReload] = useState(true); - - /** - * 表格数据 - */ - const [tableListDataSource, setTableListDataSource] = useState([]); - - /** - * 请求数据 - */ - const { fetchData } = useFetch() - - /** - * 搜索关键字 - */ - const [searchWord, setSearchWord] = useState('') - - /** - * 获取列数据,国际化变化时重新获取 - */ - const columns = useMemo(() => { - const res = DATA_MASSKING_TABLE_COLUMNS.map(x => { - // 启动列渲染 - if (x.dataIndex === 'enabled') { - x.render = (text: any, record: any) => { changeOpenApiStatus(e, record) }} /> - } - // 处理数列渲染 - if (x.dataIndex === 'treatmentNumber') { - x.render = (text: any, record: any) => { openLogsModal(record) }} >{ text } - } - // 名称筛选,这里是全量返回时候的,分页的话应该要接口返回对应的筛选数据 - 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[] = 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') ? [ { openEditModal(entity) }} btnTitle="编辑" />] : []), - ...(rowOperation.length && rowOperation.find((item: string) => item === 'logs') ? [ { openLogsModal(entity) }} btnTitle="详情" />] : []), - ...(rowOperation.length && rowOperation.find((item: string) => item === 'delete') ? [ { 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>( - // `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>( - !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 ( - <> - getServiceList()} - addNewBtnTitle={$t("添加策略")} - onAddNewBtnClick={() => { addPolicy() }} - searchPlaceholder={$t("输入名称、筛选条件查找")} - afterNewBtn={ - publishBtn && [] - } - onSearchWordChange={(e) => { - setSearchWord(e.target.value) - }} - manualReloadTable={manualReloadTable} - onChange={() => { - setTableHttpReload(false) - }} - /> - - ) -} - -export default DataMasking; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMasking.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMasking.tsx new file mode 100644 index 00000000..23dc41b0 --- /dev/null +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMasking.tsx @@ -0,0 +1,331 @@ +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 { $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"; +import { DATA_MASSKING_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' + +const DataMasking = (props: any) => { + + const { + // 是否显示发布按钮 + publishBtn = false, + // 行操作 + rowOperation = [] + } = props; + const { serviceId, teamId } = useParams() + const { state } = useGlobalContext() + const navigator = useNavigate() + const [drawerVisible, setDrawerVisible] = useState(false) + const [drawerData, setDrawerData] = useState() + const [isOkToPublish, setIsOkToPublish] = useState(false) + const drawerRef = useRef(null) + /** + * 列表ref + */ + const pageListRef = useRef(null); + + /** + * 请求数据 + */ + const { fetchData } = useFetch() + + /** + * 搜索关键字 + */ + const [searchWord, setSearchWord] = useState('') + + /** + * 获取列数据,国际化变化时重新获取 + */ + const columns = useMemo(() => { + const res = DATA_MASSKING_TABLE_COLUMNS.map(x => { + // 启动列渲染 + if (x.dataIndex === 'isStop') { + x.render = (text: any, record: any) => { changeOpenApiStatus(e, record) }} /> + } + // 处理数列渲染 + if (x.dataIndex === 'treatmentNumber') { + x.render = (text: any, record: any) => { openLogsModal(record) }} >{ text } + } + return { + ...x, + title: typeof x.title === 'string' ? $t(x.title as string) : x.title + } + }) + return res + }, [ state.language]) + + /** + * 操作列 + */ + const operation: PageProColumns[] = 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') ? [ { openEditModal(entity) }} btnTitle="编辑" />] : []), + // ...(rowOperation.length && rowOperation.find((item: string) => item === 'logs') ? [ { openLogsModal(entity) }} btnTitle="详情" />] : []), + ...(rowOperation.length && rowOperation.find((item: string) => item === 'delete') ? [ + entity.isDeleted ? { restorePolicy(entity) }} btnTitle="恢复" /> : + { deletePolicy(entity) }} btnTitle="删除" /> + ] : []), + ], + } + ] : [] + + /** + * 手动刷新表格数据 + */ + const manualReloadTable = () => { + pageListRef.current?.reload() + }; + + /** + * 更改启动状态 + * @param enabled 状态 + * @param entity 行数据 + */ + const changeOpenApiStatus = (enabled: boolean, entity: any) => { + fetchData>( + `strategy/${serviceId === undefined? 'global':'service'}/data-masking/${enabled ? 'disable' : 'enable'}`, + { + method: 'PUT', + 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, + filter:Record) => { + 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>( + `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_deleted', 'update_time','publish_status','processed_total'] + } + ).then(response => { + const { code,data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + // 保存数据 + return { + data:data.strategies, + 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('/globalpolicy/datamasking/create') + } + + /** + * 发布策略 + */ + const publish = async () => { + message.loading($t(RESPONSE_TIPS.loading)); + const { code, data, msg } = await fetchData>( + '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(`/globalpolicy/datamasking/${entity.id}`) + } + + /** + * 日志 + * @param entity + */ + const openLogsModal = (entity: any) => { + console.log('日志', entity); + } + + /** + * 删除 + * @param entity + */ + const deletePolicy = (entity: DataMaskStrategyItem) => { + fetchData>( + `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)) + manualReloadTable() + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + /** + * 恢复 + * @param entity + */ + const restorePolicy = (entity: any) => { + fetchData>( + `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 ( + <> + + id="data_masking_list" + ref={pageListRef} + columns={[...columns, ...operation]} + request={async (params: DataMaskStrategyItem & { + pageSize: number; + current: number; + }, + sort:Record, + filter:Record) => getPolicyList(params,sort, filter)} + addNewBtnTitle={$t("添加策略")} + addNewBtnAccess="system.organization.member.edit" + onAddNewBtnClick={() => { addPolicy() }} + searchPlaceholder={$t("输入名称、筛选条件查找")} + afterNewBtn={ + publishBtn && [ + + ] + } + onSearchWordChange={(e) => { + setSearchWord(e.target.value) + }} + manualReloadTable={manualReloadTable} + /> + {setDrawerVisible(false)}} + okBtnTitle={$t('发布')} + open={drawerVisible} + submitDisabled={!isOkToPublish} + submitAccess={`team.service.release.add`} + onSubmit={onSubmit} + > + + + + ) +} + +export default DataMasking; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/dataMaskingColumn.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingColumn.tsx similarity index 54% rename from frontend/packages/core/src/pages/policy/dataMaskingColumn.tsx rename to frontend/packages/core/src/pages/policy/dataMasking/DataMaskingColumn.tsx index 8d3dad92..51cc8974 100644 --- a/frontend/packages/core/src/pages/policy/dataMaskingColumn.tsx +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingColumn.tsx @@ -2,16 +2,14 @@ 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_MASSKING_TABLE_COLUMNS: PageProColumns[] = [ { title: ('策略名称'), dataIndex: 'name', ellipsis: true, - filters: true, - onFilter: true, - valueType: 'select', - filterSearch: true, width: 160 }, { @@ -25,46 +23,47 @@ export const DATA_MASSKING_TABLE_COLUMNS: PageProColumns[] = [ }, { title: ('发布状态'), - dataIndex: 'status', + dataIndex: 'publishStatus', filters: true, - onFilter: true, + onFilter: false , width: 140, - valueEnum: new Map([ - [true, {$t('已发布')}], - [false, {$t('未发布')}] - ]) + valueEnum: new Map( + Object.keys(StrategyStatusEnum).map(key=> + [key, + {$t(StrategyStatusEnum[key as keyof typeof StrategyStatusEnum])} + ])) }, { title: ('启用'), - dataIndex: 'enabled', + dataIndex: 'isStop', filters: true, - onFilter: true, + onFilter: false , valueEnum: { - true: { text: {$t('启用')} }, - false: { text: {$t('禁用')} } + false: { text: {$t('启用')} }, + true: { text: {$t('禁用')} } } }, { title: ('筛选条件'), - dataIndex: 'condition', + dataIndex: 'filters', ellipsis: true }, { title: ('处理数'), - dataIndex: 'treatmentNumber', + dataIndex: 'processedTotal', ellipsis: true }, { title: ('更新者'), - dataIndex: 'updater', + dataIndex: 'operator', width: 140, ellipsis: true }, { title: ('更新时间'), - dataIndex: 'createTime', + dataIndex: 'updateTime', width: 182, ellipsis: true, - sorter: (a, b) => frontendTimeSorter(a, b, 'createTime') + sorter: (a, b) => frontendTimeSorter(a, b, 'updateTime') }, ]; diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingConfig.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingConfig.tsx new file mode 100644 index 00000000..fdeb9416 --- /dev/null +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingConfig.tsx @@ -0,0 +1,171 @@ +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} from "@common/const/policy/type" +import {PolicyOptions} from '@common/const/policy/consts' + +const DataMaskingConfig = forwardRef((_,ref) => { + const { message,modal } = App.useApp() + const { teamId, serviceId, policyId } = useParams(); + const [onEdit, setOnEdit] = useState(!!teamId) + const [form] = Form.useForm(); + const {fetchData} = useFetch() + const { state } = useGlobalContext() + const [ loading, setLoading ] = useState(false) + const navigator = useNavigate() + + useImperativeHandle(ref, () => ({ + save:onFinish + })); + + // 获取表单默认值 + const getPolicyInfo = () => { + setLoading(true) + fetchData>( `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' + }) + },0) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }).finally(()=>setLoading(false)) + }; + + const onFinish:()=>Promise = () => { + return form.validateFields().then((value)=>{ + return fetchData>( + `strategy/${serviceId === undefined? 'global':'service'}/data-masking`, + { + method:policyId === undefined? 'POST' : 'PUT', + eoParams: {service:serviceId,team:teamId, policyId:policyId}, + eoBody:({...value}) + }).then(response=>{ + const {code,data,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)=>{ + 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 ( + + + } spinning={loading} wrapperClassName=' pb-PAGE_INSIDE_B pr-PAGE_INSIDE_X'> + + +
+
+ + label={$t("策略名称")} + name="name" + rules={[{ required: true ,whitespace:true }]} + > + + + + + label={$t("策略类型")} + name="type" + rules={[{ required: true }]} + > + + + + + + label={$t("优先级")} + name={'priority'} + rules={[{required: true}]} + > + + + + + label={$t("描述")} + name="description" + > + + + + + label={$t("匹配条件")} + name="filters" + > + + + + + label={$t("数据脱敏规则")} + name="rules" + rules={[{required: true}]} + > + + + + + + + + + +
+ +
+
+
+ ) +}) +export default DataMaskingConfig \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx new file mode 100644 index 00000000..4ad2707a --- /dev/null +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleForm.tsx @@ -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 = ({ editData, ruleList, onSave, onClose,modalVisible }) => { + const [form] = Form.useForm(); + const [matchType, setMatchType] = useState(''); + const [matchValue, setMatchValue] = useState(''); + const [maskType, setMaskType] = useState(''); + const [replaceType, setReplaceType] = useState(''); + const {state} = useGlobalContext() + useEffect(() => { + if (editData) { + form.setFieldsValue(editData); + } + }, [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 = formData.mask.begin; + submitData.mask.length = formData.mask.length; + 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 ( + +
+ + + :} + + } + + + + + + + + + )} + + {maskType === 'replacement' && ( + <> + + + + )} + + )} + + +
+ ); +}; + +export default DataMaskRuleForm; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleTable.tsx b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleTable.tsx new file mode 100644 index 00000000..9043918f --- /dev/null +++ b/frontend/packages/core/src/pages/policy/dataMasking/DataMaskingRuleTable.tsx @@ -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 = ({ + disabled = false, + value, + onChange +}) => { + const [editData, setEditData] = useState(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 ( + + {$t('类型')}:{mask.replace.type === 'random' ? $t('随机字符串') : $t('自定义字符串; 值:')}{mask.replace.value} + + ); + case 'shuffling': + return '-'; + default: + return ( + + {$t('起始位置:(0)位;长度:(1)位',[mask.begin,mask.length])} + + ); + } + }, + }, + { + title: COLUMNS_TITLE.operate, + key: 'action', + render: (_: any, record: MaskRuleData) => ( + {openDrawer('edit', record)}} btnTitle={$t("编辑")}/> + ), + }, + ],[state.language]) + + return ( +
+ { + !disabled && + } + {value && value.length >0 &&
} + + + ); +}; + +export default DataMaskRuleTable; \ No newline at end of file diff --git a/frontend/packages/core/src/pages/policy/globalPolicy.tsx b/frontend/packages/core/src/pages/policy/globalPolicy.tsx index 00572e57..3e5f7b5c 100644 --- a/frontend/packages/core/src/pages/policy/globalPolicy.tsx +++ b/frontend/packages/core/src/pages/policy/globalPolicy.tsx @@ -1,20 +1,23 @@ 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 PolicyTabContainer from "./PolicyTabContainer.tsx"; +import DataMasking from "./dataMasking/DataMasking.tsx"; +import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; +import { useMemo } from "react"; const PartitionInsideGlobalPolicy = () => { + const {state} = useGlobalContext() /** * tab列表 */ - const tabItems = [ + const tabItems =useMemo(()=> [ { key: 'dataMasking', label: $t('数据脱敏'), children:
} - ] + ],[state.language]) return ( <> diff --git a/frontend/packages/core/src/pages/policy/servicePolicy.tsx b/frontend/packages/core/src/pages/policy/servicePolicy.tsx index 850d24f9..32022c02 100644 --- a/frontend/packages/core/src/pages/policy/servicePolicy.tsx +++ b/frontend/packages/core/src/pages/policy/servicePolicy.tsx @@ -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"; const servicePolicy = () => { /** diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx index 7721d564..a316ed40 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx @@ -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 - {/* } */} -
{$t('转发规则设置')} diff --git a/frontend/packages/core/src/pages/system/publish/SystemInsidePublishList.tsx b/frontend/packages/core/src/pages/system/publish/SystemInsidePublishList.tsx index d02fec26..894a2682 100644 --- a/frontend/packages/core/src/pages/system/publish/SystemInsidePublishList.tsx +++ b/frontend/packages/core/src/pages/system/publish/SystemInsidePublishList.tsx @@ -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"; diff --git a/frontend/packages/dashboard/src/component/MonitorTable.tsx b/frontend/packages/dashboard/src/component/MonitorTable.tsx index 66ffec60..fd46c0e1 100644 --- a/frontend/packages/dashboard/src/component/MonitorTable.tsx +++ b/frontend/packages/dashboard/src/component/MonitorTable.tsx @@ -16,8 +16,6 @@ const TableType = { provider :SERVICE_TABLE_GLOBAL_COLUMNS_CONFIG, subscribers :APPLICATION_TABLE_GLOBAL_COLUMNS_CONFIG } -const APP_MODE = import.meta.env.VITE_APP_MODE; - type MonitorTableProps = { type:'api'|'subscribers'|'provider' @@ -77,13 +75,12 @@ const MonitorTable = forwardRef> { title: COLUMNS_TITLE.operate, key: 'option', - btnNums:2, + btnNums:1, fixed:'right', hideInSetting:true, valueType: 'option', render: (_: React.ReactNode, entity: unknown) => [ - // onRowClick(entity)} btnTitle="查看"/>, - APP_MODE === 'pro' ? onRowClick(entity)} btnTitle="查看"/> : null + onRowClick(entity)} btnTitle="查看"/> ], } ] diff --git a/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx b/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx index 3f7c18a7..aa87146d 100644 --- a/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx +++ b/frontend/packages/dashboard/src/component/MonitorTotalPage.tsx @@ -1,13 +1,11 @@ -import { App, Select, Button, Tabs, TabsProps, Empty, Drawer, Spin } from "antd"; +import { App, Button, Tabs, TabsProps, Empty, Drawer, Spin } from "antd"; import dayjs from "dayjs"; import customParseFormat from "dayjs/plugin/customParseFormat"; import { useState, useEffect, useRef, useReducer } from "react"; -import { useParams } from "react-router-dom"; import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const"; import { SummaryPieData, SearchBody, PieData, MonitorApiData, MonitorSubscriberData, InvokeData, MessageData } from "@dashboard/const/type"; import { getTime, getTimeUnit, changeNumberUnit } from "../utils/dashboard"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; import ScrollableSection from "@common/components/aoplatform/ScrollableSection"; import { RangeValue, TimeRange } from "@common/components/aoplatform/TimeRangeSelector"; import TimeRangeSelector from "@common/components/aoplatform/TimeRangeSelector"; @@ -19,7 +17,6 @@ import DashboardDetail from "@dashboard/pages/DashboardDetail"; import { $t } from "@common/locales"; dayjs.extend(customParseFormat); -const APP_MODE = import.meta.env.VITE_APP_MODE; export type MonitorTotalPageProps = { fetchPieData:(body:SearchBody)=>Promise> @@ -209,17 +206,17 @@ const MonitorTotalPage = (props:MonitorTotalPageProps) => { { label:$t('API 请求量 Top10'), key:'api', - children:{APP_MODE !== 'pro' ? null : getDetailData(record as MonitorApiData,'api')}} request={()=>getTablesData(queryData||{},'api')}/> + children:{ getDetailData(record as MonitorApiData,'api')}} request={()=>getTablesData(queryData||{},'api')}/> }, { label:$t('消费者调用量 Top10'), key:'subscribers', - children:{APP_MODE !== 'pro' ? null : getDetailData(record as MonitorSubscriberData,'subscriber')}} request={()=>getTablesData(queryData||{},'subscriber')} /> + children:{getDetailData(record as MonitorSubscriberData,'subscriber')}} request={()=>getTablesData(queryData||{},'subscriber')} /> }, { label:$t('服务被调用量 Top10'), key:'providers', - children:{APP_MODE !== 'pro' ? null : getDetailData(record as MonitorSubscriberData,'provider')}} request={()=>getTablesData(queryData||{},'provider')} /> + children:{getDetailData(record as MonitorSubscriberData,'provider')}} request={()=>getTablesData(queryData||{},'provider')} /> } ] diff --git a/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx b/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx index a9cb2c07..ff005815 100644 --- a/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardTabPage.tsx @@ -2,11 +2,10 @@ import { Tabs, TabsProps } from "antd"; import DashboardTotal from "./DashboardTotal"; import { Outlet, useNavigate, useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes"; import { useEffect, useState } from "react"; import { $t } from "@common/locales"; +import { RouterParams } from "@common/const/type"; -const APP_MODE = import.meta.env.VITE_APP_MODE; export default function DashboardTabPage(){ const { dashboardType} = useParams() @@ -41,10 +40,10 @@ export default function DashboardTabPage(){ ] return (<> - {APP_MODE === 'pro' ? { + { setActiveKey(val); navigateTo(`/analytics/${val === 'total' ? val :`${val}/list`}`) }} - items={monitorTabItems} className="h-auto mt-[6px]" size="small" tabBarStyle={{paddingLeft:'10px',marginTop:'0px',marginBottom:'0px'}} /> - : } ) + items={monitorTabItems} className="h-full overflow-hidden mt-[6px] [&>.ant-tabs-content-holder]:overflow-auto" size="small" tabBarStyle={{paddingLeft:'10px',marginTop:'0px',marginBottom:'0px'}} /> + ) } \ No newline at end of file diff --git a/frontend/packages/market/src/const/serviceHub/type.ts b/frontend/packages/market/src/const/serviceHub/type.ts index a6b080f5..34e8f422 100644 --- a/frontend/packages/market/src/const/serviceHub/type.ts +++ b/frontend/packages/market/src/const/serviceHub/type.ts @@ -18,6 +18,7 @@ export type ServiceBasicInfoType = { invokeAddress:string approvalType:'auto'|'manual' serviceKind:'ai'|'rest' + sitePrefix?:string } export type ServiceDetailType = { diff --git a/frontend/packages/market/src/index.css b/frontend/packages/market/src/index.css index 9154cbf7..69e22726 100644 --- a/frontend/packages/market/src/index.css +++ b/frontend/packages/market/src/index.css @@ -335,6 +335,7 @@ p{ background-color:transparent } + /* .hidden-switcher .ant-tree-switcher { @apply hidden; } */ diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx index a858e448..273b0b6d 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx @@ -1,18 +1,16 @@ import { Link, useNavigate, useParams } from "react-router-dom"; -import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; import { App, Avatar, Button, Descriptions, Divider, Tabs } from "antd"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx"; import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const.tsx"; import { useFetch } from "@common/hooks/http.ts"; import { DefaultOptionType } from "antd/es/cascader"; import { ApplyServiceHandle, ServiceBasicInfoType, ServiceDetailType } from "../../const/serviceHub/type.ts"; -import { EntityItem } from "@common/const/type.ts"; +import { EntityItem, RouterParams } from "@common/const/type.ts"; import { ApplyServiceModal } from "./ApplyServiceModal.tsx"; import ServiceHubApiDocument from "./ServiceHubApiDocument.tsx"; import Integrate from "./integrate.tsx"; import { ApiFilled, ArrowLeftOutlined, BgColorsOutlined } from "@ant-design/icons"; -import { SimpleSystemItem } from "@core/const/system/type.ts"; import { Icon } from "@iconify/react/dist/iconify.js"; import DOMPurify from 'dompurify'; import { $t } from "@common/locales/index.ts"; @@ -54,7 +52,7 @@ const ServiceHubDetail = () => { } const getServiceBasicInfo = () => { - fetchData>('catalogue/service', { method: 'GET', eoParams: { service: serviceId }, eoTransformKeys: ['app_num', 'api_num', 'update_time', 'api_doc', 'invoke_address', 'approval_type', 'service_kind'] }).then(response => { + fetchData>('catalogue/service', { method: 'GET', eoParams: { service: serviceId }, eoTransformKeys: ['app_num', 'api_num', 'update_time', 'api_doc', 'invoke_address', 'approval_type', 'service_kind','site_prefix'] }).then(response => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { setService({ ...data.service, apiDoc: modifyApiDoc(data.service.apiDoc, data.service.basic?.invokeAddress) }) diff --git a/frontend/packages/market/src/pages/serviceHub/integrate.tsx b/frontend/packages/market/src/pages/serviceHub/integrate.tsx index 15d904a1..4eb553bb 100644 --- a/frontend/packages/market/src/pages/serviceHub/integrate.tsx +++ b/frontend/packages/market/src/pages/serviceHub/integrate.tsx @@ -1,15 +1,21 @@ import { ServiceDetailType } from "@market/const/serviceHub/type" import { Input, Button, Space, message } from 'antd' import { $t } from "@common/locales" -import { RESPONSE_TIPS } from '@common/const/const' -import { downloadFile } from "@common/utils/download.ts" - +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useParams } from "react-router-dom" +import { useState, useEffect } from "react" +import { RouterParams } from "@common/const/type" +import { useFetch } from "@common/hooks/http" const Integrate = ({ service }: { service: ServiceDetailType }) => { - console.log('service', service); const stepClass = "leading-[20px] truncate font-bold items-center gap-[4px] mt-[15px]"; - const url = 'https://www.baidu.com'; - + const [url, setUrl] = useState(''); + const { serviceId} = useParams() + const {fetchData} = useFetch() + + useEffect(()=>{ + setUrl(`${service?.basic?.sitePrefix || window.location?.origin}/${serviceId}/swagger` ) + },[service]) /** * Agent 平台地址 */ @@ -29,13 +35,15 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => { * 下载文件 */ const onDownload = () => { - console.log('downloadFile'); - downloadFile({ - body: '', - contentType: 'raw', - filename: 'test_response', - responseType: 'text', - uri: '' + fetchData>(`export/openapi/${serviceId}`, { method: 'GET' ,headers:{ + 'Content-Type': 'application/octet-stream' + }}).then(response => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } }) } return ( @@ -46,12 +54,13 @@ const Integrate = ({ service }: { service: ServiceDetailType }) => {
{$t('不同 Agent 平台的操作细节可查看')} {$t('《 Agent 对接手册》')}

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

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

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

{$t('在')} {$t('消费者')} {$t('菜单中,选择已通过本 API 服务申请的消费者,')}