From de74542e78370563b84bc354160a3ff6e09d0cfa Mon Sep 17 00:00:00 2001 From: maggieyyy <61950669+maggieyyy@users.noreply.github.com> Date: Fri, 30 Aug 2024 19:22:38 +0800 Subject: [PATCH] feat:openapi ui --- frontend/package.json | 1 + .../components/aoplatform/RenderRoutes.tsx | 9 +- frontend/packages/common/src/assets/empty.svg | 1 + .../src/components/aoplatform/ApiDocument.tsx | 39 +++ .../src/components/aoplatform/BasicLayout.tsx | 6 +- .../aoplatform/EditableTableWithModal.tsx | 2 +- .../PublishApprovalModalContent.tsx | 33 ++- .../aoplatform/UnUsedWordForTranslate.tsx | 44 +--- .../components/aoplatform/WithPermission.tsx | 3 +- .../components/postcat/api/Codebox/index.tsx | 11 +- .../common/src/const/approval/const.tsx | 15 +- frontend/packages/common/src/const/const.tsx | 2 +- .../packages/common/src/const/permissions.ts | 48 ++-- .../common/src/const/permissions.yaml | 17 +- frontend/packages/common/src/hooks/http.ts | 2 +- .../common/src/locales/scan/en-GB.json | 49 +++- .../src/locales/scan/newJson/zh-CN.json | 3 + .../packages/common/src/utils/permission.ts | 2 - .../core/__tests__/hooks/copy.test.ts | 1 - frontend/packages/core/package.json | 6 +- frontend/packages/core/src/App.css | 13 + .../components/aoplatform/RenderRoutes.tsx | 9 +- .../packages/core/src/const/system/const.tsx | 59 ++--- .../packages/core/src/const/system/type.ts | 60 +---- .../PartitionInsideDashboardSetting.tsx | 6 +- .../core/src/pages/role/RoleConfig.tsx | 26 +- .../packages/core/src/pages/role/RoleList.tsx | 2 +- .../core/src/pages/system/SystemConfig.tsx | 2 +- .../src/pages/system/SystemInsidePage.tsx | 7 +- .../system/api/SystemInsideApiDocument.tsx | 135 +++++++++-- .../pages/system/api/SystemInsideApiProxy.tsx | 2 +- .../system/api/SystemInsideRouterCreate.tsx | 209 ++++++++++++++++ .../system/api/SystemInsideRouterList.tsx | 229 ++++++++++++++++++ .../src/pages/DashboardInstruction.tsx | 21 +- .../market/src/const/serviceHub/type.ts | 3 +- .../serviceHub/ServiceHubApiDocument.tsx | 90 +------ .../src/pages/serviceHub/ServiceHubDetail.tsx | 12 +- 37 files changed, 844 insertions(+), 335 deletions(-) create mode 100644 frontend/packages/common/src/assets/empty.svg create mode 100644 frontend/packages/common/src/components/aoplatform/ApiDocument.tsx create mode 100644 frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx create mode 100644 frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx diff --git a/frontend/package.json b/frontend/package.json index 0818e906..4ed98404 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,6 +44,7 @@ "react-i18next": "^15.0.1", "react-joyride": "^2.8.2", "react-router-dom": "^6.20.0", + "swagger-ui-react": "^5.17.14", "tailwindcss": "^3.3.5", "uuid": "^9.0.1", "vite-tsconfig-paths": "^4.3.2" diff --git a/frontend/packages/businessEntry/src/components/aoplatform/RenderRoutes.tsx b/frontend/packages/businessEntry/src/components/aoplatform/RenderRoutes.tsx index da7bd552..d86daa4d 100644 --- a/frontend/packages/businessEntry/src/components/aoplatform/RenderRoutes.tsx +++ b/frontend/packages/businessEntry/src/components/aoplatform/RenderRoutes.tsx @@ -132,7 +132,12 @@ const PUBLIC_ROUTES:RouteConfig[] = [ { path:'api', key: uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiList.tsx')), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')), + }, + { + path:'router', + key: uuidv4(), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')), }, { path:'upstream', @@ -207,7 +212,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [ } ] },{ - path:'dashboardsetting', + path:'datasourcing', key: uuidv4(), lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')), }, diff --git a/frontend/packages/common/src/assets/empty.svg b/frontend/packages/common/src/assets/empty.svg new file mode 100644 index 00000000..bac1d28e --- /dev/null +++ b/frontend/packages/common/src/assets/empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx b/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx new file mode 100644 index 00000000..ab431f20 --- /dev/null +++ b/frontend/packages/common/src/components/aoplatform/ApiDocument.tsx @@ -0,0 +1,39 @@ +import React from "react" +import SwaggerUI from 'swagger-ui-react'; +import 'swagger-ui-react/swagger-ui.css'; + +export default function ApiDocument({spec}:{spec?:string|object}) { + + class OperationsLayout extends React.Component { + render() { + const { + getComponent + } = this.props + const Operations = getComponent("operations", true) + + return ( +
+ +
+ ) + } + } + + // Create the plugin that provides our layout component + const OperationsLayoutPlugin = () => { + return { + components: { + OperationsLayout: OperationsLayout + } + } + } + + return( + null}} + layout="OperationsLayout" + plugins={[OperationsLayoutPlugin ]} /> + ) +} \ No newline at end of file diff --git a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx index 51f44706..e93d221c 100644 --- a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx +++ b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx @@ -57,9 +57,9 @@ const themeToken = { getNavItem({$t('API 市场')}, 'serviceHub','/serviceHub',,undefined,undefined,'system.workspace.api_market.view'), getNavItem($t('仪表盘'), 'mainPage', APP_MODE === 'pro' ? '/dashboard' : '/dashboard/total',,[ - getNavItem({$t('运行视图')}, 'dashboard',APP_MODE === 'pro' ? '/dashboard' : '/dashboard/total' ,,undefined,undefined,'system.dashboard.dashboard.view'), + getNavItem({$t('运行视图')}, 'dashboard',APP_MODE === 'pro' ? '/dashboard' : '/dashboard/total' ,,undefined,undefined,'system.dashboard.run_view.view'), APP_MODE === 'pro' ? getNavItem({$t('系统拓扑图')}, 'systemrunning','/systemrunning',,undefined,undefined,'system.dashboard.systemrunning.view') : null, - ]), + ],undefined,'system.dashboard.run_view.view'), getNavItem($t('系统设置'), 'operationCenter','/member',, [ getNavItem($t('组织'), 'organization','/member',null,[ @@ -72,7 +72,7 @@ const themeToken = { getNavItem($t('运维与集成'), 'maintenanceCenter','/cluster', null, [ getNavItem({$t('集群')}, 'cluster','/cluster',,undefined,undefined,'system.devops.cluster.view'), - getNavItem({$t('监控报表')}, 'dashboardsetting','/dashboardsetting',,undefined,undefined,'system.devops.dashboardsetting.view'), + getNavItem({$t('数据源')}, 'datasourcing','/datasourcing',,undefined,undefined,'system.devops.data_source.view'), getNavItem({$t('证书')}, 'cert','/cert',,undefined,undefined,'system.devops.ssl_certificate.view'), getNavItem({$t('日志')}, 'logsettings','/logsettings',,undefined,undefined,'system.devops.log_configuration.view'), APP_MODE === 'pro' ? getNavItem({$t('资源')}, 'resourcesettings','/resourcesettings',null,undefined,undefined,'system.partition.self.view'):null, diff --git a/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx b/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx index 76d4af89..e388c6f9 100644 --- a/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx +++ b/frontend/packages/common/src/components/aoplatform/EditableTableWithModal.tsx @@ -91,7 +91,7 @@ const EditableTableWithModal = ({ title:$t(title), dataIndex: key as string, key: key as string, - render: renderText ? (value, record) => $t(renderText(value, record)) : undefined, + render: renderText ? (value, record) => $t(renderText(value, record) || '') : undefined, ellipsis:true })), ...(disabled ? []:[{ diff --git a/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx b/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx index 5e815fa6..022df0de 100644 --- a/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx +++ b/frontend/packages/common/src/components/aoplatform/PublishApprovalModalContent.tsx @@ -2,12 +2,14 @@ import {App, Col, Form, Input, Row, Table, Tooltip} from "antd"; import {forwardRef, useEffect, useImperativeHandle, useMemo} from "react"; import {PublishApprovalInfoType, PublishApprovalModalHandle, PublishApprovalModalProps, PublishVersionTableListItem} from "@common/const/approval/type.tsx"; import {useFetch} from "@common/hooks/http.ts"; -import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx"; +import {BasicResponse, FORM_ERROR_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, STATUS_COLOR, VALIDATE_MESSAGE} from "@common/const/const.tsx"; import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; import { SYSTEM_PUBLISH_ONLINE_COLUMNS } from "@core/const/system/const.tsx"; import { $t } from "@common/locales"; -import { ApprovalApiColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const"; +import { ApprovalRouteColumns, ApprovalStatusColorClass, ApprovalUpstreamColumns, ChangeTypeEnum } from "@common/const/approval/const"; import { useGlobalContext } from "@common/contexts/GlobalStateContext"; +import { LoadingOutlined } from "@ant-design/icons"; +import { SystemInsidePublishOnlineItems } from "@core/pages/system/publish/SystemInsidePublishOnline"; export const PublishApprovalModalContent = forwardRef((props, ref) => { @@ -107,7 +109,7 @@ export const PublishApprovalModalContent = forwardRefApprovalApiColumns.map((x)=>({ + const translatedRouteColumns = useMemo(()=>ApprovalRouteColumns.map((x)=>({ ...x, ...(x.dataIndex === 'change' ? { render:(_,entity)=>( @@ -122,6 +124,22 @@ export const PublishApprovalModalContent = forwardRefSYSTEM_PUBLISH_ONLINE_COLUMNS.map((x)=>{ + if(x.dataIndex === 'status'){ + return {...x,title:$t(x.title), + render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{ + switch(entity.status){ + case 'done': + return {$t('成功')} + case 'error': + return {$t('失败')} {entity.error} + default: + return + } + }} + } + }),[state.language]) + return ( <> {!insideSystem && <> @@ -180,11 +198,11 @@ export const PublishApprovalModalContent = forwardRef{$t('API 列表')}: {$t('上游列表')}: @@ -217,11 +235,12 @@ export const PublishApprovalModalContent = forwardRef } */} - {['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <> 上线情况: + {['error','done'].indexOf(data.status) !== -1 && data.clusterPublishStatus &&data.clusterPublishStatus.length > 0 && <> + {$t('上线情况')}:
{ return ( <> - {$t('文件日志')} - {$t('HTTP日志')} - {$t('Kafka日志')} - {$t('NSQ日志')} - {$t('Syslog日志')} - {$t('未分配')} - {$t('超级管理员')} - {$t('团队管理员')} - {$t('运维管理员')} - {$t('普通成员')} - {$t('只读成员')} - {$t('服务管理员')} - {$t('服务开发者')} - {$t('应用开发者')} - {$t('应用管理员')} - {$t('驱动名称')} - {$t('请求失败数')} - {$t('转发失败数')} - {$t('作用范围')} - {$t('添加条目')} - {$t('添加地址')} - {$t('文件名称')} - {$t('存放目录')} - {$t('日志分割周期')} - {$t('过期时间')} - {$t('单位:天')} - {$t('输出格式')} - {$t('格式化配置')} - {$t('服务器地址')} - {$t('Access日志')} - {$t('NSQD地址列表')} - {$t('鉴权Secret')} - {$t('网络协议')} - {$t('日志等级')} - {$t('单行')} - {$t('小时')} - {$t('天')} - {$t('未发布')} - {$t('待发布')} - {$t('单位:s,最小值:1')} + {$t('上传文件')} + {$t('替换文件')} + {$t('是否放行')} + {$t('监控')} ) } \ No newline at end of file diff --git a/frontend/packages/common/src/components/aoplatform/WithPermission.tsx b/frontend/packages/common/src/components/aoplatform/WithPermission.tsx index 48b582d5..ecc14aac 100644 --- a/frontend/packages/common/src/components/aoplatform/WithPermission.tsx +++ b/frontend/packages/common/src/components/aoplatform/WithPermission.tsx @@ -4,6 +4,7 @@ import { ReactElement, cloneElement, useEffect, useMemo, useState } from "reac import { useGlobalContext } from "../../contexts/GlobalStateContext"; import { PERMISSION_DEFINITION } from "@common/const/permissions"; import { $t } from "@common/locales"; +import { last } from "lodash-es"; type WithPermissionProps = { access?:string | string[] @@ -36,7 +37,7 @@ const WithPermission = ({access, tooltip, children,disabled, showDisabled = true {editAccess && disabled && { cloneElement(children, {disabled:true})} } - {!editAccess && (children?.type?.displayName !== 'Button' && showDisabled ) && + {!editAccess && (children?.type?.displayName !== 'Button' && children?.type?.displayName !== 'Upload' && showDisabled ) && { cloneElement(children, {disabled:true})} } diff --git a/frontend/packages/common/src/components/postcat/api/Codebox/index.tsx b/frontend/packages/common/src/components/postcat/api/Codebox/index.tsx index 0a60fb6c..cf0d4b61 100644 --- a/frontend/packages/common/src/components/postcat/api/Codebox/index.tsx +++ b/frontend/packages/common/src/components/postcat/api/Codebox/index.tsx @@ -22,8 +22,10 @@ interface CodeboxProps { height?: string | null readOnly?: boolean apiRef?: RefObject - language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext' + language?: 'html' | 'json' | 'xml' | 'javascript' | 'css' | 'plaintext'|'yaml' extraContent?:React.ReactNode + sx?:Record + editorTheme?:'vs' | 'vs-dark' | 'hc-black' } export const Codebox = memo((props: CodeboxProps) => { @@ -37,7 +39,8 @@ export const Codebox = memo((props: CodeboxProps) => { apiRef, readOnly = false, language = 'plaintext', - extraContent + extraContent, + editorTheme = 'vs' } = props const [code, setCode] = useState(``) @@ -153,7 +156,8 @@ export const Codebox = memo((props: CodeboxProps) => { sx={{ // border: `1px solid ${theme.palette.divider}`, height: '100%', - width: '100%' + width: '100%', + ...props.sx }} > {enableToolbar ? (<> @@ -189,6 +193,7 @@ export const Codebox = memo((props: CodeboxProps) => { value={isControlled ? controlledValue : code} options={{ ...defaultOptions, ...options }} onChange={handleEditorChange} + theme={editorTheme} /> ) diff --git a/frontend/packages/common/src/const/approval/const.tsx b/frontend/packages/common/src/const/approval/const.tsx index 83f76bbc..16a920d5 100644 --- a/frontend/packages/common/src/const/approval/const.tsx +++ b/frontend/packages/common/src/const/approval/const.tsx @@ -212,22 +212,23 @@ export const ApprovalStatusColorClass = { -export const ApprovalApiColumns = [ - { - title:('API 名称'), - dataIndex:'name', - ellipsis:true - }, +export const ApprovalRouteColumns = [ { title:('请求方式'), dataIndex:'method', - ellipsis:true + ellipsis:true, + renderText:(value)=>value.join(',') }, { title:('路径'), dataIndex:'path', ellipsis:true }, + { + title:('描述'), + dataIndex:'description', + + }, { title:('类型'), dataIndex:'change', diff --git a/frontend/packages/common/src/const/const.tsx b/frontend/packages/common/src/const/const.tsx index dc761814..2f85209c 100644 --- a/frontend/packages/common/src/const/const.tsx +++ b/frontend/packages/common/src/const/const.tsx @@ -26,7 +26,7 @@ export const routerKeyMap = new Map([ ['operationCenter',['member','user','role','servicecategories']], ['organization',['member','user','role']], ['serviceHubSetting',['servicecategories']], - ['maintenanceCenter',['dashboardsetting','cluster','cert','logsettings','resourcesettings','openapi'] + ['maintenanceCenter',['datasourcing','cluster','cert','logsettings','resourcesettings','openapi'] ]]) diff --git a/frontend/packages/common/src/const/permissions.ts b/frontend/packages/common/src/const/permissions.ts index 520dd31a..f5ea9d72 100644 --- a/frontend/packages/common/src/const/permissions.ts +++ b/frontend/packages/common/src/const/permissions.ts @@ -139,9 +139,19 @@ export const PERMISSION_DEFINITION = [ "anyOf": [{ "backend": ["system.api_market.service_classification.manager"] }] } }, - "system.devops.dashboardsetting.view":{ + "system.dashboard.run_view.view":{ + "granted": { + "anyOf": [{ "backend": ['system.dashboard.run_view.view'] }] + } + }, + "system.devops.data_source.view":{ "grented":{ - "anyOf":[{"backend":[]}] + "anyOf":[{"backend":['system.devops.data_source.view']}] + } + }, + "system.devops.data_source.edit":{ + "grented":{ + "anyOf":[{"backend":['system.devops.data_source.manager']}] } }, "system.devops.cluster.view": { @@ -239,34 +249,44 @@ export const PERMISSION_DEFINITION = [ "anyOf": [{ "backend": [] }] } }, - "team.service.api.view": { + "team.service.api_doc.view": { "granted": { - "anyOf": [{ "backend": ["team.service.api.view"] }] + "anyOf": [{ "backend": ["team.service.api_doc.view"] }] } }, - "team.service.api.add": { + "team.service.api_doc.add": { "granted": { - "anyOf": [{ "backend": ["team.service.api.manager"] }] + "anyOf": [{ "backend": ["team.service.api_doc.manager"] }] } }, - "team.service.api.edit": { + "team.service.api_doc.edit": { "granted": { - "anyOf": [{ "backend": ["team.service.api.manager"] }] + "anyOf": [{ "backend": ["team.service.api_doc.manager"] }] } }, - "team.service.api.copy": { + "team.service.api_doc.import": { "granted": { - "anyOf": [{ "backend": ["team.service.api.manager"] }] + "anyOf": [{ "backend": ["team.service.api_doc.manager"] }] } }, - "team.service.api.delete": { + "team.service.router.view": { "granted": { - "anyOf": [{ "backend": ["team.service.api.manager"] }] + "anyOf": [{ "backend": ["team.service.router.view"] }] } }, - "team.service.api.import": { + "team.service.router.add": { "granted": { - "anyOf": [{ "backend": ["team.service.api.manager"] }] + "anyOf": [{ "backend": ["team.service.router.manager"] }] + } + }, + "team.service.router.edit": { + "granted": { + "anyOf": [{ "backend": ["team.service.router.manager"] }] + } + }, + "team.service.router.delete": { + "granted": { + "anyOf": [{ "backend": ["team.service.router.manager"] }] } }, "team.service.upstream.view": { diff --git a/frontend/packages/common/src/const/permissions.yaml b/frontend/packages/common/src/const/permissions.yaml index af742507..cf54cb5b 100644 --- a/frontend/packages/common/src/const/permissions.yaml +++ b/frontend/packages/common/src/const/permissions.yaml @@ -109,12 +109,17 @@ team: cname: API value: 'api' children: - - team.service.api.view - - team.service.api.add - - team.service.api.edit - - team.service.api.copy - - team.service.api.delete - - team.service.api.import + - team.service.api_doc.view + - team.service.api_doc.add + - team.service.api_doc.edit + - name: route + cname: route + value: 'route' + children: + - team.service.router.view + - team.service.router.add + - team.service.router.edit + - team.service.router.delete - name: upstream cname: 上游 value: 'upstream' diff --git a/frontend/packages/common/src/hooks/http.ts b/frontend/packages/common/src/hooks/http.ts index 9b433238..13886e0e 100644 --- a/frontend/packages/common/src/hooks/http.ts +++ b/frontend/packages/common/src/hooks/http.ts @@ -140,7 +140,7 @@ type EoHeaders = Headers | {[k:string]:string} export function useFetch(){ function fetchData(url:string, options: EoRequest ) { // 合并传入的headers与默认headers - const headers = { ...DEFAULT_HEADERS, ...options.headers }; + const headers = { ...(options.body ? {}:DEFAULT_HEADERS), ...options.headers }; // 检查是否需要转换键 const shouldTransformKeys = !shouldNotTransform(url) && options?.eoTransformKeys && options?.eoTransformKeys?.length > 0; diff --git a/frontend/packages/common/src/locales/scan/en-GB.json b/frontend/packages/common/src/locales/scan/en-GB.json index 8015bf2f..f55d959d 100644 --- a/frontend/packages/common/src/locales/scan/en-GB.json +++ b/frontend/packages/common/src/locales/scan/en-GB.json @@ -5,7 +5,7 @@ "Kfe93ef35": "Applications", "Kb58e0c3f": "Services", "Kc9e489f5": "Team", - "K61c89f5f": "API Marketplace", + "K61c89f5f": "API Portal", "K16d71239": "Dashboard", "K714c192d": "Runtime", "Kd57dfe97": "Topology", @@ -24,7 +24,7 @@ "K6535ff9c": "Account Settings", "Kf15499b4": "Log Out", "Kabbd6e6": "Documentation", - "K1196b104": "APIPark - API Open Platform", + "K1196b104": "APIPark - API Developer Portal", "K1f42de3": "HTTP Status Codes", "K4770dff4": "System Status Codes", "Kf89e58f1": "Description", @@ -336,15 +336,15 @@ "K3818f03d": "Approval", "K56b4254f": "Publishing Application", "Kd518ba3e": "Hello! Welcome to APIPark", - "Ke66e4182": "APIPark allows you to quickly build an API open portal/marketplace within your enterprise, offering extreme forwarding performance, API observability, service governance, multi-tenant management, subscription approval processes, and many other benefits.", + "Ke66e4182": "APIPark allows you to quickly build an API open portal within your enterprise, offering extreme forwarding performance, API observability, service governance, multi-tenant management, subscription approval processes, and many other benefits.", "Kedd41c18": "If you like our product, please consider giving us a Star or providing feedback.", "Kef02fd87": "Quick Start", "K43a3b38d": "We've provided some tasks to help you quickly get acquainted with APIPark.", "Kc8239422": "Teams include personnel, applications, and services. Data between different teams is isolated, and can be used to manage different departments/project teams/teams within the enterprise.", - "Kd5be0cd7": "Services include a set of APIs and can be published to the API Marketplace for use by other teams.", - "K4ea67613": "Applications serve as identities for applying for services and calling APIs. They can apply for service calls in the API Marketplace, and each application has its own independent API access authentication.", + "Kd5be0cd7": "Services include a set of APIs and can be published to the API Portal for use by other teams.", + "K4ea67613": "Applications serve as identities for applying for services and calling APIs. They can apply for service calls in the API Portal, and each application has its own independent API access authentication.", "Ka4748416": "Search for Services and APIs", - "K383e17e5": "You can view all public services in the API Marketplace.", + "K383e17e5": "You can view all public services in the API Portal.", "K8f7808e6": "Subscribe to Services", "Kb0755523": "To call an API of a particular service, you need to subscribe to the service first and wait for approval from the team providing the service before initiating API requests.", "Kd28a1aa5": "Review Subscription Applications", @@ -442,7 +442,7 @@ "K427a5bd5": "Only .png .jpg .jpeg .svg Format Images Are Supported, Files Larger Than 1KB Will Be Compressed", "K44bc352d": "Logo", "Kf52a584d": "Service Category", - "K72b21be5": "Set the Category Where the Service Is Displayed in the Service Marketplace", + "K72b21be5": "Set the Category Where the Service Is Displayed in the Service Portal", "Kde6bae17": "Delete Service", "K885ea699": "This Action Is Irreversible, Please Proceed with Caution!", "K617f34f1": "Updated By", @@ -582,7 +582,7 @@ "K3c7b175f": "Number of Subscribed Services: (0) Approved, (1) Pending", "K850b4b2d": "Status Code", "Kbe3e9335": "Exit Test", - "K370a3eb2": "Service Marketplace", + "K370a3eb2": "Service Portal", "Kf7ec36d": "Service Details", "K59cdbec3": "Introduction", "K4aa9ed2c": "Apply", @@ -641,5 +641,36 @@ "K3509a9f8": "Day", "Kb3960e83": "Unpublished", "K8bd1e18": "Pending", - "K225a6c43": "Unit: Seconds, Minimum Value: 1" + "K225a6c43": "Unit: Seconds, Minimum Value: 1", + "Ka450909c": "Organization", + "K62933442": "View System Roles", + "Kd677d04a": "View Team Roles", + "Kd352fa1d": "API Portal", + "K39280ee": "Operations", + "K4bf109e8": "View All Applications", + "Keceae2": "View All Services", + "K7c866f28": "View All Teams", + "Kf9dcef3a": "Routing", + "K6134bbe8": "Add Route", + "Kad6d2797": "Search Routes by Name and URL", + "K28435c5c": "Route Details", + "Kfa088d49": "Configure Cluster and Enable Monitoring", + "K3da3b9a0": "Monitoring function is used to assist in managing cluster information. Please configure the cluster and set up monitoring information before viewing the current cluster monitoring status.", + "Kaddacfb": "Cluster Configuration", + "K4ac33975": "Configure cluster address to ensure monitoring system can correctly identify and connect to the cluster", + "Ke5ed9810": "Configure Cluster Information", + "K1a132228": "Monitoring Settings", + "K6af08c3c": "Configure Monitoring Information", + "K7e52ffa3": "Deployment Status", + "Kad1c674c": "Protocol", + "Kad01bc3e": "Method", + "Kca1dc104": "Open Editor", + "Kba92c499": "Intercept Requests to This Interface", + "Kb7df6ac1": "Block", + "K5c1722fe": "Allow", + "Ke00c858c": "Upload File", + "K6d9dd1f5": "Replace File", + "K71753476": "Allow Access", + "K597435c5": "Runtime", + "Kde9d6e8e": "Once interception is enabled, the gateway will block all traffic to this endpoint, similar to a firewall restricting access to a specific resource." } \ 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 2c63c085..f3239c9a 100644 --- a/frontend/packages/common/src/locales/scan/newJson/zh-CN.json +++ b/frontend/packages/common/src/locales/scan/newJson/zh-CN.json @@ -1,2 +1,5 @@ { + "K71753476": "是否放行", + "K597435c5": "监控", + "Kde9d6e8e": "开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。" } diff --git a/frontend/packages/common/src/utils/permission.ts b/frontend/packages/common/src/utils/permission.ts index b2362e6b..f03fa824 100644 --- a/frontend/packages/common/src/utils/permission.ts +++ b/frontend/packages/common/src/utils/permission.ts @@ -17,8 +17,6 @@ export const checkAccess:(access:AccessDataType, accessData:Map } const hasIntersection = (arr1:string[], arr2:string[])=> { - // 当没有对应后端权限字段时,默认有权限 - if(arr1.length === 0) return true const set = new Set(arr1.length > arr2.length ? arr2:arr1) const arr = arr1.length > arr2.length ? arr1:arr2 for (const item of arr) { diff --git a/frontend/packages/core/__tests__/hooks/copy.test.ts b/frontend/packages/core/__tests__/hooks/copy.test.ts index 92b3d19f..5c78ef39 100644 --- a/frontend/packages/core/__tests__/hooks/copy.test.ts +++ b/frontend/packages/core/__tests__/hooks/copy.test.ts @@ -16,7 +16,6 @@ describe('useCopyToClipboard', () => { globalThis.navigator = {}; } // 确保 clipboard 对象存在 - console.log(globalThis.navigator, navigator,navigator.clipboard) if (!navigator.clipboard) { // @ts-expect-error clipboard object may not exist in some environments navigator.clipboard = {}; diff --git a/frontend/packages/core/package.json b/frontend/packages/core/package.json index 25f04f21..7b9c483d 100644 --- a/frontend/packages/core/package.json +++ b/frontend/packages/core/package.json @@ -15,10 +15,8 @@ }, "dependencies": { "@tinymce/tinymce-react": "^4.3.2", - "tinymce": "^6.8.1", + "fs-extra": "^11.2.0", "highlight.js": "^11.9.0", - "fs-extra": "^11.2.0" - }, - "devDependencies": { + "tinymce": "^6.8.1" } } diff --git a/frontend/packages/core/src/App.css b/frontend/packages/core/src/App.css index ef705ac1..e0925d02 100644 --- a/frontend/packages/core/src/App.css +++ b/frontend/packages/core/src/App.css @@ -208,4 +208,17 @@ a{ overflow: hidden; border-radius: 10px; border:1px solid var(--table-border-color) !important; +} + +.swagger-ui{ + width: 100%; + .model-box-control:focus,.models-control:focus, .opblock-summary-control:focus{ + outline:unset !important; + } + .information-container{ + .info{ + display: none; + } + + } } \ No newline at end of file diff --git a/frontend/packages/core/src/components/aoplatform/RenderRoutes.tsx b/frontend/packages/core/src/components/aoplatform/RenderRoutes.tsx index 9968330d..b507f828 100644 --- a/frontend/packages/core/src/components/aoplatform/RenderRoutes.tsx +++ b/frontend/packages/core/src/components/aoplatform/RenderRoutes.tsx @@ -139,7 +139,12 @@ const PUBLIC_ROUTES:RouteConfig[] = [ { path:'api', key: uuidv4(), - lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiList.tsx')), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideApiDocument.tsx')), + }, + { + path:'route', + key: uuidv4(), + lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/api/SystemInsideRouterList')), }, { path:'upstream', @@ -215,7 +220,7 @@ const PUBLIC_ROUTES:RouteConfig[] = [ ] }, { - path:'dashboardsetting', + path:'datasourcing', key: uuidv4(), lazy:lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/partitions/PartitionInsideDashboardSetting.tsx')), }, diff --git a/frontend/packages/core/src/const/system/const.tsx b/frontend/packages/core/src/const/system/const.tsx index 7356b7c0..fbdd80c6 100644 --- a/frontend/packages/core/src/const/system/const.tsx +++ b/frontend/packages/core/src/const/system/const.tsx @@ -71,6 +71,10 @@ export const SYSTEM_I18NEXT_FOR_ENUM = { } export const HTTP_METHOD = ['GET','POST','PUT','DELETE','PATCH','HEAD'] +export const API_PROTOCOL = [ + {label:'HTTP',value:'http'}, + {label:'HTTPS',value:'https'} +] export const ALGORITHM_ITEM = [ @@ -234,9 +238,6 @@ export const MATCH_CONFIG:ConfigField[] = [ { title:('参数位置'), key: 'position', - component: { - return { label:value, value:key} - })}/>, renderText: (value:keyof typeof MatchTypeEnum) => { return MatchTypeEnum[value] }, @@ -272,34 +270,33 @@ export const MATCH_CONFIG:ConfigField[] = [ export const SYSTEM_API_TABLE_COLUMNS: PageProColumns[] = [ { - title:('名称'), - dataIndex: 'name', - ellipsis:true, - width:160, - fixed:'left', - valueType: 'text', - sorter: (a,b)=> { - return a.name.localeCompare(b.name) - }, + title:('URL'), + dataIndex: 'requestPath', + ellipsis:true }, { - title:('协议/方法'), + title:('协议'), + dataIndex: 'protocols', + ellipsis:true, + renderText:(value)=>value?.join(',') + }, + { + title:('方法'), dataIndex: 'method', ellipsis:true, + renderText:(value)=>value?.join(',') + }, + { + title:'是否放行', + dataIndex:'isDisabled', + ellipsis:true, filters: true, onFilter: true, - valueType: 'select', - valueEnum: { - POST: { text: 'POST' }, - PUT: { text: 'PUT' }, - GET: { text: 'GET' }, - DELETE: { text: 'DELETE' }, - PATCH: { text: 'PATCH' }, - }, + valueType: 'select' }, { - title:('URL'), - dataIndex: 'requestPath', + title:('描述'), + dataIndex: 'description', ellipsis:true }, { @@ -636,16 +633,6 @@ export const SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP = { ellipsis:{ showTitle:true }, - render:(_:unknown,entity:SystemInsidePublishOnlineItems)=>{ - switch(entity.status){ - case 'done': - return {('成功')} - case 'error': - return {('失败')} {entity.error} - default: - return - } - } }, ] diff --git a/frontend/packages/core/src/const/system/type.ts b/frontend/packages/core/src/const/system/type.ts index 4694c26a..ac28cb6d 100644 --- a/frontend/packages/core/src/const/system/type.ts +++ b/frontend/packages/core/src/const/system/type.ts @@ -77,49 +77,9 @@ export type SystemMemberTableListItem = { }; export type SystemApiDetail = { - id:string - name:string - description:string - protocol:Protocol - method:HTTPMethod - path:string - creator:EntityItem - createTime:string - updater:EntityItem + content:string updateTime:string - match?:MatchItem[] - proxy?:SystemApiProxyType - doc?:{ - encoding: string, - tag: string, - requestParams: { - headerParams: HeaderParamsType[], - bodyParams: BodyParamsType[], - queryParams: QueryParamsType[], - restParams: RestParamsType[] - }, - resultList: ResultListType[], - responseList: [{ - id: number, - responseUuid: string, - apiUuid: string, - oldId: number, - name: string, - httpCode: string, - contentType: ApiBodyType, - isDefault: number, - updateUserId: number, - createUserId: number, - createTime: number, - updateTime: number, - responseParams: { - headerParams: HeaderParamsType[], - bodyParams: BodyParamsType[] - queryParams: QueryParamsType[], - restParams: RestParamsType[] - } - }] - } + updater:string } @@ -131,11 +91,11 @@ export type SystemApiProxyType = { } export type SystemApiProxyFieldType = { - name: string; + protocols: string[]; id:string; description?:string; path:string; - method:string; + method:string[]; match:MatchItem[] isDisable?: boolean; service?:string; @@ -155,16 +115,16 @@ export type SystemApiSimpleFieldType = { update_time: string } -export type SystemInsideApiCreateProps = { - type?:'copy' - entity?:SystemApiProxyFieldType &{systemId:string} +export type SystemInsideRouterCreateProps = { + type?:'add'|'edit'|'copy' + entity?:SystemApiTableListItem modalApiPrefix?:string modalPrefixForce?:boolean serviceId:string teamId:string } -export type SystemInsideApiCreateHandle = { +export type SystemInsideRouterCreateHandle = { copy:()=>Promise; save:()=>Promise; } @@ -172,14 +132,14 @@ export type SystemInsideApiCreateHandle = { export type SystemApiTableListItem = { id:string; - name: string; method:string; + protocols:string; requestPath:string; + description:string creator:EntityItem; createTime:string; updater:EntityItem updateTime:string - canDelete:boolean }; diff --git a/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx b/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx index c0d79b49..98013d27 100644 --- a/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx +++ b/frontend/packages/core/src/pages/partitions/PartitionInsideDashboardSetting.tsx @@ -38,14 +38,14 @@ const PartitionInsideDashboardSetting:FC = ()=> { useEffect(() => { setBreadcrumb([ - {title: $t('监控报表')} + {title: $t('数据源')} ]) getDashboardSettingInfo() }, []); const setDashboardSettingBtn = ()=>{ return (<> - {showStatus === 'view' && + {showStatus === 'view' && } ) @@ -54,7 +54,7 @@ const PartitionInsideDashboardSetting:FC = ()=> { return ( <> void;})=>{ + const onSingleCheckboxChange: GetProp = (e) => { if(e.target.checked){ onChange?.(Array.from(new Set([...value, e.target.id, ...(dependenciesMap?.get(e.target.id!)?.dependents || [])] as string[]))) @@ -55,9 +57,9 @@ const PermissionContent = ({permits,onChange,value=[],id,dependenciesMap}:{permi permits.map((item:PermissionClassify)=>( <>
- {item.cname !== '' &&

{item.cname}

} + {item.cname !== '' &&

{$t(item.cname)}

}
- {item.children.map(x=> 0 && value.indexOf(x.value)>-1} onChange={onSingleCheckboxChange}>{x.cname})} + {item.children.map(x=> 0 && value.indexOf(x.value)>-1} onChange={onSingleCheckboxChange}>{$t(x.cname)})}
@@ -69,15 +71,16 @@ const PermissionContent = ({permits,onChange,value=[],id,dependenciesMap}:{permi const PermissionCollapse:React.FC = (props)=>{ const { id, value = [], onChange,permissionTemplate ,dependenciesMap} = props; const [openCollapses, setOpenCollapses] = useState([]) + const {state} = useGlobalContext() const items = useMemo(()=>{ const generatePermissionItem = (permissionItem:RolePermissionItem[])=> permissionItem.map((item:RolePermissionItem)=>({ key:item.name, - label:item.cname, + label:$t(item.cname), children:onChange?.(e)} id={id!} dependenciesMap={dependenciesMap!}/> })) return permissionTemplate && permissionTemplate.length > 0 ? generatePermissionItem(permissionTemplate) : [] - },[permissionTemplate,value]) + },[permissionTemplate,value,state.language]) useEffect(()=>{ permissionTemplate && setOpenCollapses(permissionTemplate?.map(x=>x.name)) @@ -100,7 +103,8 @@ const RoleConfig = ()=>{ const [permissionTemplate, setPermissionTemplate] = useState() const [dependenciesMap, setDependenciesMap] = useState() const APP_MODE = import.meta.env.VITE_APP_MODE; - + const [permissionInfo, setPermissionInfo] = useState() + const { state } = useGlobalContext() const generateDependenciesMap = (data:RolePermissionItem[])=>{ const map = new Map() @@ -161,15 +165,17 @@ const RoleConfig = ()=>{ fetchData>(`${roleType}/role`,{method:'GET',eoParams:{role:roleId}}).then(response=>{ const {code,data,msg} = response if(code === STATUS_CODE.SUCCESS){ - form.setFieldsValue({name:data.role.name,permits:data.role.permit}) - return Promise.resolve(true) + setPermissionInfo(data.role) }else{ message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg || $t(RESPONSE_TIPS.error)) } - }).catch((errInfo)=>Promise.reject(errInfo)) + }).catch((errInfo)=>console.error(errInfo)) } + useEffect(()=>{ + form.setFieldsValue({name:$t(permissionInfo?.name || ''),permits:permissionInfo?.permit}) + },[permissionInfo, state.language]) + useEffect(() => { getPermissionTemplate() form.setFieldsValue({name:'',permits:[]}) @@ -195,7 +201,7 @@ const RoleConfig = ()=>{ return (
- +
{ id="global_role" ref={pageListRef} tableClass="role_table " - columns={[...ROLE_TABLE_COLUMNS as PageProColumns[], ...operation('team')]} + columns={[...columns as PageProColumns[], ...operation('team')]} request={()=>getRoleList('team')} showPagination={false} addNewBtnTitle={$t("添加角色")} diff --git a/frontend/packages/core/src/pages/system/SystemConfig.tsx b/frontend/packages/core/src/pages/system/SystemConfig.tsx index 92d15d07..fc900edb 100644 --- a/frontend/packages/core/src/pages/system/SystemConfig.tsx +++ b/frontend/packages/core/src/pages/system/SystemConfig.tsx @@ -188,7 +188,7 @@ const SystemConfig = forwardRef((_,ref) => { useEffect(() => { if(accessInit){ - getTeamOptionList + getTeamOptionList() }else{ getGlobalAccessData()?.then(()=>{ getTeamOptionList() diff --git a/frontend/packages/core/src/pages/system/SystemInsidePage.tsx b/frontend/packages/core/src/pages/system/SystemInsidePage.tsx index 1aa1a4e6..7bbf499b 100644 --- a/frontend/packages/core/src/pages/system/SystemInsidePage.tsx +++ b/frontend/packages/core/src/pages/system/SystemInsidePage.tsx @@ -42,7 +42,7 @@ const SystemInsidePage:FC = ()=> { const getApiDefine = ()=>{ setApiPrefix('') setPrefixForce(false) - fetchData>('service/api/define',{method:'GET',eoParams:{service:serviceId,team:teamId}}).then(response=>{ + fetchData>('service/router/define',{method:'GET',eoParams:{service:serviceId,team:teamId}}).then(response=>{ const {code,data,msg} = response if(code === STATUS_CODE.SUCCESS){ setApiPrefix(data.prefix) @@ -58,7 +58,8 @@ const SystemInsidePage:FC = ()=> { const SYSTEM_PAGE_MENU_ITEMS = useMemo(()=>[ getItem($t('服务'), 'assets', null, [ - getItem({$t('API')}, 'api',undefined,undefined,undefined,'team.service.api.view'), + getItem({$t('API')}, 'api',undefined,undefined,undefined,'team.service.api_doc.view'), + getItem({$t('路由')}, 'route',undefined,undefined,undefined,'team.service.router.view'), getItem({$t('上游')}, 'upstream',undefined,undefined,undefined,'team.service.upstream.view'), getItem({$t('使用说明')}, 'document',undefined,undefined,undefined,''), getItem({$t('发布')}, 'publish',undefined,undefined,undefined,'team.service.release.view'), @@ -113,7 +114,7 @@ const SystemInsidePage:FC = ()=> { }, [currentUrl]); useEffect(()=>{ - if(accessData && accessData.get('team') && accessData.get('team')?.indexOf('team.service.api.view') !== -1){ + if(accessData && accessData.get('team') && accessData.get('team')?.indexOf('team.service.router.view') !== -1){ getApiDefine() } },[accessData]) diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx index bab72375..f6a20350 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideApiDocument.tsx @@ -1,63 +1,146 @@ -import {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react"; -import ApiEdit, {ApiEditApi} from "@common/components/postcat/ApiEdit.tsx"; -import { Spin, message} from "antd"; +import {forwardRef, useEffect, useState} from "react"; +import { Button, Empty, Spin, Upload, message} from "antd"; import {BasicResponse, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; import {useFetch} from "@common/hooks/http.ts"; import { SystemApiDetail, SystemInsideApiDocumentHandle, SystemInsideApiDocumentProps } from "../../../const/system/type.ts"; import { LoadingOutlined } from "@ant-design/icons"; - +import EmptySVG from '@common/assets/empty.svg' +import { $t } from "@common/locales/index.ts"; +import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx' +import { Codebox } from "@common/components/postcat/api/Codebox/index.tsx"; +import { useParams } from "react-router-dom"; +import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx"; +import WithPermission from "@common/components/aoplatform/WithPermission.tsx"; const SystemInsideApiDocument = forwardRef((props, ref) => { - const {serviceId, teamId, apiId} = props + const {serviceId, teamId} = useParams() const {fetchData} = useFetch() const [apiDetail, setApiDetail] = useState() - const apiEditRef = useRef(null) - const [loaded,setLoaded] = useState(false) const [loading, setLoading] = useState(false) - - useImperativeHandle(ref, ()=>({ - save - }) -) + const [showEditor, setShowEditor] = useState(false) useEffect(() => { getApiDetail() }, []); const getApiDetail = ()=>{ setLoading(true) - fetchData>('service/api/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, api:apiId},eoTransformKeys:['create_time','update_time','match_type','upstream_id','opt_type']}).then(response=>{ + fetchData>('service/api_doc',{method:'GET',eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ const {code,data,msg} = response - //console.log(data,code, STATUS_CODE.SUCCESS,code === STATUS_CODE.SUCCESS) if(code === STATUS_CODE.SUCCESS){ - setApiDetail(data.api) - setLoaded(true) + setApiDetail(data.doc?.content) }else{ message.error(msg || $t(RESPONSE_TIPS.error)) } }).finally(()=>{setLoading(false)}) } - const save = ()=>{ - return apiEditRef.current?.getData()?.then((res)=>{ - return fetchData>('service/api',{method:'PUT',eoParams:{service:serviceId,team:teamId,api:apiId},eoBody:(res.apiInfo)}).then(response=>{ + + + const UploadBtn = ({type, updated}:{type:'new'|'edit', updated:()=>void})=>{ + const {fetchData} = useFetch() + + const uploadFile = (file:File)=>{ + const body = new FormData() + body.append('doc',file) + fetchData>('service/api_doc/upload',{method:'POST',body:body,eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ const {code,msg} = response if(code === STATUS_CODE.SUCCESS){ message.success(msg || $t(RESPONSE_TIPS.success)) - return Promise.resolve(true) + updated?.() }else{ message.error(msg || $t(RESPONSE_TIPS.error)) - return Promise.reject(msg|| $t(RESPONSE_TIPS.error)) } - }).catch(errInfo => Promise.reject(errInfo)) - }) + }).finally(()=>{setLoading(false)}) + } + return ( + + { + uploadFile(file) + return false; + }}> + + + + ) + } + + const ApiEdit = ({spec,updated}:{spec?:string|object,updated:()=>void})=>{ + const [code, setCode] = useState('') + const [saveLoading, setSaveLoading] = useState(false) + useEffect(()=>{ + try{ + setCode(typeof spec === 'string' ? spec:JSON.stringify(spec) ) + }catch(e){ + console.warn('文档解析失败',e) + } + },[spec]) + + const saveCode = ()=>{ + setSaveLoading(true) + fetchData>('service/api_doc',{method:'PUT',eoBody:{content:code},eoParams:{service:serviceId,team:teamId },eoTransformKeys:['update_time']}).then(response=>{ + const {code,msg} = response + if(code === STATUS_CODE.SUCCESS){ + message.success(msg || $t(RESPONSE_TIPS.success)) + updated?.() + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }).finally(()=>{setSaveLoading(false)}) + } + + return ( +
+
+ + + + +
+
+
+ +
+
{ + !code ? : }
+
+
) + } + + const ApiPreview = ({setShowEditor,spec,updated}:{setShowEditor:(show:boolean)=>void,spec?:string | object, updated:()=>void})=>{ + return (
+
+ + + + +
+
+ +
+
+ ) + } + + const updated = ()=>{ + getApiDetail(); setShowEditor(false) } return (<> - } spinning={loading} className=' h-full overflow-auto '> -
- + } spinning={loading} wrapperClassName=' h-full overflow-hidden '> +
+ {!showEditor && apiDetail && } + + {showEditor && } + {!showEditor && !apiDetail && +
+ + + + +
+
}
) diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideApiProxy.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideApiProxy.tsx index 81dd5fe3..3f51eb4c 100644 --- a/frontend/packages/core/src/pages/system/api/SystemInsideApiProxy.tsx +++ b/frontend/packages/core/src/pages/system/api/SystemInsideApiProxy.tsx @@ -43,7 +43,7 @@ const SystemInsideApiProxy = forwardRef{onChange?.(allValues)}} autoComplete="off"> diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx new file mode 100644 index 00000000..22946b84 --- /dev/null +++ b/frontend/packages/core/src/pages/system/api/SystemInsideRouterCreate.tsx @@ -0,0 +1,209 @@ +import {App, Col, Form, Input, Row, Select, 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_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 { 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"; + +const SystemInsideRouterCreate = forwardRef((props, ref) => { + const { message } = App.useApp() + const {type, entity, serviceId,teamId, modalApiPrefix:apiPrefix, modalPrefixForce:prefixForce} = props + const [form] = Form.useForm(); + const {fetchData} = useFetch() + const [loading, setLoading] = useState(false) + const proxyRef = useRef(null) + const { state } = useGlobalContext() + + const onFinish = ()=>{ + return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([,formValue])=>{ + const body = {...formValue,path:formValue.path.trim(),proxy:{...formValue.proxy,path:formValue.proxy.path ? (formValue.proxy.path.startsWith('/')? formValue.proxy.path: '/'+ formValue.proxy.path) : undefined}} + return fetchData>('service/router',{method: type === 'add' ? 'POST' : 'PUT',eoBody:(body), eoParams: {service:serviceId,team:teamId, ...(type === 'edit' ? {router:entity?.id}: {})},eoTransformKeys:['matchType','isDisable']}).then(response=>{ + const {code,msg} = response + if(code === STATUS_CODE.SUCCESS){ + message.success(msg || $t(RESPONSE_TIPS.success)) + return Promise.resolve(true) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } + }).catch(errInfo=>Promise.reject(errInfo)) + }) + } + + const copy: ()=>Promise = ()=>{ + return new Promise((resolve, reject)=>{ + return form.validateFields().then((value)=>{ + fetchData>('service/api/copy',{method:'POST',eoParams:{service:serviceId,team:teamId, api:entity!.id},eoBody:({...value,path:value.path.trim()})}).then(response=>{ + const {code,data,msg} = response + if(code === STATUS_CODE.SUCCESS){ + message.success(msg || $t(RESPONSE_TIPS.success)) + return resolve(data.api.id) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + return reject(msg || $t(RESPONSE_TIPS.error)) + } + }).catch((errorInfo)=> reject(errorInfo)) + }).catch((errorInfo)=> reject(errorInfo)) + }) + } + + useImperativeHandle(ref, ()=>({ + copy, + save:onFinish + }) + ) + + const getRouterConfig = ()=>{ + setLoading(true) + fetchData>('service/router/detail',{method:'GET',eoParams:{service:serviceId,team:teamId, router:entity!.id}}).then(response=>{ + const {code,data,msg} = response + if(code === STATUS_CODE.SUCCESS){ + const {isDisable, protocols, path, method, description, match, proxy} = data.router + form.setFieldsValue({isDisable, protocols, path, method, description, match,proxy + }) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }).catch((errorInfo)=> console.error(errorInfo)) + .finally(()=>setLoading(false)) + } + + useEffect(() => { + switch(type){ + case 'edit': + getRouterConfig() + break; + case 'add': + form.setFieldValue('prefix',apiPrefix) + form.setFieldValue(['proxy','timeout'],10000) + form.setFieldValue(['proxy','retry'],0) + form.setFieldValue('protocols',['http','https']) + break; + case 'copy': + // form.setFieldsValue({ + // ...entity, + // name:`${$t('副本')}-${entity!.name}`, + // ...(prefixForce? + // {prefix:apiPrefix,path: entity!.path.substring(apiPrefix?.length|| 0)}: + // {}), + // proxy:{timeout:10000, retry:0, ...entity?.proxy} + // }); + break; + } + return (form.setFieldsValue({})) + }, []); + + + const translatedMatchConfig = useMemo(()=>{ + return MATCH_CONFIG.map((item)=>{ + if(item.key === 'position'){ + return ({...item,component:{ + return { label:$t(value), value:key} + })}/>}) + } + return {...item} + }) + }, [state.language]) + + return (
+ } spinning={loading} className=''> + +
+
{$t('API 基础信息')} + + label={$t("拦截该接口的请求")} + name="isDisable" + extra={$t('开启拦截后,网关会拦截所有该路径的请求,相当于防火墙禁用了特定路径的访问。')} + > + + + + + label={$t("请求协议")} + name="protocols" + rules={[{ required: true }]} + > + + + + + label={$t("请求路径")} + name="path" + rules={[{ required: true,whitespace:true }, + { + validator: validateUrlSlash, + }]} + className={styles['form-input-group']} + > + + + + + label={$t("请求方式")} + name="method" + rules={[{ required: true }]} + > + + + + + label={$t("描述")} + name="description" + > + + + + + label={$t("高级匹配")} + name="match" + > + + configFields={translatedMatchConfig} + /> + + {/* } */} + + { type !== 'copy' &&<> + + {$t('转发规则设置')} + + className="mb-0 bg-transparent border-none p-0" + name="proxy" + > + + + } + + + + + ) +}) +export default SystemInsideRouterCreate \ No newline at end of file diff --git a/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx b/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx new file mode 100644 index 00000000..1c389189 --- /dev/null +++ b/frontend/packages/core/src/pages/system/api/SystemInsideRouterList.tsx @@ -0,0 +1,229 @@ +import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx" +import {ActionType} from "@ant-design/pro-components"; +import {FC, useEffect, useMemo, useRef, useState} from "react"; +import {Link, useParams} from "react-router-dom"; +import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx"; +import {App, Divider} from "antd"; +import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE} from "@common/const/const.tsx"; +import { SimpleMemberItem} from '@common/const/type.ts' +import {useFetch} from "@common/hooks/http.ts"; +import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; +import SystemInsideRouterCreate from "./SystemInsideRouterCreate.tsx"; +import {useSystemContext} from "../../../contexts/SystemContext.tsx"; +import { SYSTEM_API_TABLE_COLUMNS } from "../../../const/system/const.tsx"; +import {SystemApiTableListItem, SystemInsideRouterCreateHandle, SystemInsideApiDocumentHandle } from "../../../const/system/type.ts"; +import TableBtnWithPermission from "@common/components/aoplatform/TableBtnWithPermission.tsx"; +import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx"; +import { checkAccess } from "@common/utils/permission.ts"; +import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter.tsx"; +import { $t } from "@common/locales/index.ts"; + +const SystemInsideRouterList:FC = ()=>{ + const [searchWord, setSearchWord] = useState('') + const { setBreadcrumb } = useBreadcrumb() + const { modal,message } = App.useApp() + const [tableListDataSource, setTableListDataSource] = useState([]); + const [tableHttpReload, setTableHttpReload] = useState(true); + const {fetchData} = useFetch() + const pageListRef = useRef(null); + const {apiPrefix, prefixForce} = useSystemContext() + const [memberValueEnum, setMemberValueEnum] = useState([]) + const {accessData,state} = useGlobalContext() + const [drawerType,setDrawerType]= useState<'add'|'edit'|'view'|'upstream'|undefined>() + const [open, setOpen] = useState(false); + const drawerAddFormRef = useRef(null) + const {serviceId, teamId} = useParams() + + const [curApi, setCurApi] = useState() + + const getRoutesList = (): Promise<{ data: SystemApiTableListItem[], success: boolean }>=> { + //console.log(sorter, filter) + if(!tableHttpReload){ + setTableHttpReload(true) + return Promise.resolve({ + data: tableListDataSource, + success: true, + }); + } + + return fetchData>('service/routers',{method:'GET',eoParams:{service:serviceId,team:teamId, keyword:searchWord},eoTransformKeys:['request_path','create_time','update_time','is_disable']}).then(response=>{ + const {code,data,msg} = response + if(code === STATUS_CODE.SUCCESS){ + setTableListDataSource(data.routers) + setTableHttpReload(false) + return {data:data.routers, success: true} + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + return {data:[], success:false} + } + }).catch(() => { + return {data:[], success:false} + }) + } + + const deleteRoute = (entity:SystemApiTableListItem)=>{ + return new Promise((resolve, reject)=>{ + fetchData>('service/router',{method:'DELETE',eoParams:{service:serviceId,team:teamId, router:entity!.id}}).then(response=>{ + const {code,msg} = response + if(code === STATUS_CODE.SUCCESS){ + message.success(msg || $t(RESPONSE_TIPS.success)) + resolve(true) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + reject(msg || $t(RESPONSE_TIPS.error)) + } + }).catch((errorInfo)=> reject(errorInfo)) + }) + } + + const openModal = async (type: 'delete',entity:SystemApiTableListItem) =>{ + let title:string = '' + let content:string|React.ReactNode = '' + switch (type){ + case 'delete': + title=$t('删除') + content=$t(DELETE_TIPS.default) + break; + } + + modal.confirm({ + title, + content, + onOk:()=> { + switch (type){ + case 'delete': + return deleteRoute(entity).then((res)=>{if(res === true) manualReloadTable()}) + } + }, + width:600, + okText:$t('确认'), + okButtonProps:{ + disabled : !checkAccess( `team.service.router.${type}`, accessData ) + }, + cancelText:$t('取消'), + closable:true, + icon:<>, + }) + } + + const operation:PageProColumns[] =[ + { + title: COLUMNS_TITLE.operate, + key: 'option', + btnNums:2, + fixed:'right', + valueType: 'option', + render: (_: React.ReactNode, entity: SystemApiTableListItem) => [ + {openDrawer('edit',entity)}} btnTitle="编辑"/>, + , + {openModal('delete',entity)}} btnTitle="删除"/>, + ], + } + ] + + const manualReloadTable = () => { + setTableHttpReload(true); // 表格数据需要从后端接口获取 + pageListRef.current?.reload() + }; + + const getMemberList = async ()=>{ + setMemberValueEnum([]) + const {code,data,msg} = await fetchData>('simple/member',{method:'GET'}) + if(code === STATUS_CODE.SUCCESS){ + setMemberValueEnum(data.members) + }else{ + message.error(msg || $t(RESPONSE_TIPS.error)) + } + } + + const openDrawer = (type:'add'|'edit'|'view',entity?:SystemApiTableListItem)=>{ + setCurApi(entity) + setDrawerType(type) + } + + useEffect(()=>{drawerType !== undefined ? setOpen(true):setOpen(false)},[drawerType]) + + useEffect(() => { + setBreadcrumb([ + { + title:{$t('服务')} + }, + { + title:$t('路由') + } + ]) + getMemberList() + manualReloadTable() + }, [serviceId]); + + const onClose = () => { + setDrawerType(undefined); + setCurApi(undefined) + }; + + const columns = useMemo(()=>{ + return [...SYSTEM_API_TABLE_COLUMNS].map(x=>{ + if(x.filters &&((x.dataIndex as string[])?.indexOf('creator') !== -1) ){ + const tmpValueEnum:{[k:string]:{text:string}} = {} + memberValueEnum?.forEach((x:SimpleMemberItem)=>{ + tmpValueEnum[x.name] = {text:x.name} + }) + x.valueEnum = tmpValueEnum + } + + if(x.filters &&((x.dataIndex as string[])?.indexOf('isDisabled') !== -1) ){ + x.valueEnum = { + true:{text:{$t('拦截')}}, + false:{text:{$t('放行')}} + } + } + return {...x,title:typeof x.title === 'string' ? $t(x.title as string) : x.title}}) + },[memberValueEnum,state.language]) + + const handlerSubmit:() => Promise|undefined= ()=>{ + switch(drawerType){ + case 'add':{ + return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res}) + } + case 'edit':{ + return drawerAddFormRef.current?.save()?.then((res)=>{res && manualReloadTable();return res}) + } + default:return undefined + } + } + + return ( + <> + getRoutesList()} + dataSource={tableListDataSource} + addNewBtnTitle={$t('添加路由')} + searchPlaceholder={$t('输入名称、URL 查找路由')} + onAddNewBtnClick={()=>{openDrawer('add')}} + addNewBtnAccess="team.service.router.add" + tableClickAccess="team.service.router.view" + manualReloadTable={manualReloadTable} + onSearchWordChange={(e)=>{setSearchWord(e.target.value)}} + onChange={() => { + setTableHttpReload(false) + }} + onRowClick={(row:SystemApiTableListItem)=>openDrawer('view',row)} + tableClass="mr-PAGE_INSIDE_X " + /> + handlerSubmit()} + showOkBtn={drawerType !== 'view'} + > + + + + ) + +} +export default SystemInsideRouterList \ No newline at end of file diff --git a/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx b/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx index 4faf2ad7..f2a7c727 100644 --- a/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx +++ b/frontend/packages/dashboard/src/pages/DashboardInstruction.tsx @@ -1,26 +1,27 @@ +import { $t } from "@common/locales"; import { Link } from "react-router-dom"; export default function DashboardInstruction({showClusterIns, showMonitorIns}:{showClusterIns:boolean, showMonitorIns:boolean}) { return (
-

集群配置并开启监控

-

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

+

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

+

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

{/*

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

*/}
- {showClusterIns &&
-

集群配置

-

配置集群地址,以确保监控系统能够正确识别和连接到集群

-

配置集群信息

+ {showClusterIns &&
+

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

+

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

+

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

} {showMonitorIns && -
-

监控设置

-

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

-

配置监控信息

+
+

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

+

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

+

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

} diff --git a/frontend/packages/market/src/const/serviceHub/type.ts b/frontend/packages/market/src/const/serviceHub/type.ts index e3041f3b..6747e2ac 100644 --- a/frontend/packages/market/src/const/serviceHub/type.ts +++ b/frontend/packages/market/src/const/serviceHub/type.ts @@ -1,6 +1,5 @@ import { DefaultOptionType } from "antd/es/select" -import { ApiDetail } from "@common/const/api-detail" import { EntityItem } from "@common/const/type" import { SubscribeEnum, SubscribeFromEnum } from "@core/const/system/const" import WithPermission from "@common/components/aoplatform/WithPermission" @@ -22,7 +21,7 @@ export type ServiceDetailType = { name:string description:string basic:ServiceBasicInfoType - apis:ApiDetail[] + apiDoc:string applied:boolean } diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx index 7ac2fef8..187f1788 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubApiDocument.tsx @@ -1,50 +1,25 @@ import { useParams} from "react-router-dom"; import {RouterParams} from "@core/components/aoplatform/RenderRoutes.tsx"; -import {Anchor, Button, Collapse, Drawer, FloatButton, Input, Space} from "antd"; -import { useEffect, useMemo, useState} from "react"; -import ApiPreview from "@common/components/postcat/ApiPreview.tsx"; +import { Button, Drawer, Empty} from "antd"; +import { useEffect, useState} from "react"; import ApiTestGroup from "./ApiTestGroup.tsx"; -import {ApiDetail} from "@common/const/api-detail"; import {ServiceDetailType } from "../../const/serviceHub/type.ts"; -import ApiMatch from "@common/components/postcat/api/ApiPreview/components/ApiMatch/index.tsx"; -import ApiProxy from "@common/components/postcat/api/ApiPreview/components/ApiProxy/index.tsx"; import { $t } from "@common/locales/index.ts"; +import ApiDocument from "@common/components/aoplatform/ApiDocument.tsx"; const ServiceHubApiDocument = ({service}:{service:ServiceDetailType})=>{ const {serviceId} = useParams(); const [apiTestDrawOpen, setApiTestDrawOpen] = useState(false); const [serviceName, setServiceName] = useState() - const [apiDocs,setApiDocs ] = useState() const [selectedTestApi,setSelectedTestApi] = useState() - const [activeKey, setActiveKey] = useState([]) + const [apiDocument, setApiDocument] = useState() useEffect(()=>{ if(!service) return setServiceName(service?.name) - setApiDocs(service?.apis) - setActiveKey(service?.apis.map((x)=>x.id)) + setApiDocument(service.apiDoc) },[service]) - const category = useMemo(() => [ - { - key: 'apiDocument-list', - href: '#apiDocument-list', - title:$t('API 列表'), - children:apiDocs?.map((x)=>({ - key:x.id, - href:`#apiDocument-${x.id}`, - title:x.name - })) || [] - }, - // { - // key: 'apiDocument-statusCode', - // href: '#apiDocument-statusCode', - // title:$t('状态码', - // }, - ], [apiDocs]); - - const floatButtonStyle = { top:'10px',position:'sticky', width:'180px',height:'200px'} - useEffect(() => { if(!serviceId){ console.warn('缺少serviceId') @@ -52,10 +27,6 @@ const ServiceHubApiDocument = ({service}:{service:ServiceDetailType})=>{ } }, [serviceId]); - const testClick = (id:string)=>{//console.log('test'); - setApiTestDrawOpen(true) - setSelectedTestApi(id)} - const onClose = () => { setApiTestDrawOpen(false); }; @@ -64,54 +35,9 @@ const ServiceHubApiDocument = ({service}:{service:ServiceDetailType})=>{ return ( <>
-
-
-

{$t('API 列表')}

-
- {apiDocs?.map((apiDetail)=>( -
- (isActive? : )} - items={[{ - key: apiDetail.id, - label: {apiDetail.method}{apiDetail.name}, - children:
- - - - { - apiDetail?.match && apiDetail.match?.length > 0 && - - } - - { - apiDetail?.proxy && Object.keys(apiDetail?.proxy).length > 0 && - - } - - {apiDetail && } -
- }]} - activeKey={activeKey} - onChange={(val)=>{setActiveKey(val as string[])}} - /> -
- ))} - -
-
- - - document.getElementById('layout-ref')!} - items={category} - /> - +
+ {apiDocument ? : +}
diff --git a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx index d3ba6379..06d78f14 100644 --- a/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx +++ b/frontend/packages/market/src/pages/serviceHub/ServiceHubDetail.tsx @@ -28,22 +28,22 @@ const ServiceHubDetail = ()=>{ const applyRef = useRef(null) const { modal,message } = App.useApp() const [mySystemOptionList, setMySystemOptionList] = useState() - const [applied,setApplied] = useState(false) - const [activeKey, setActiveKey] = useState([]) + // const [applied,setApplied] = useState(false) + // const [activeKey, setActiveKey] = useState([]) const [service, setService] = useState() const navigate = useNavigate(); const getServiceBasicInfo = ()=>{ - fetchData>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time']}).then(response=>{ + fetchData>('catalogue/service',{method:'GET',eoParams:{service:serviceId}, eoTransformKeys:['app_num','api_num','update_time','api_doc']}).then(response=>{ const {code,data,msg} = response if(code === STATUS_CODE.SUCCESS){ setService(data.service) setServiceBasicInfo(data.service.basic) setServiceName(data.service.name) setServiceDesc(data.service.description) - setApplied(data.service.applied) + // setApplied(data.service.applied) setServiceDoc(DOMPurify.sanitize(data.service.document)) - setActiveKey(data.service.apis.map((x)=>x.id)) + // setActiveKey(data.service.apis.map((x)=>x.id)) }else{ message.error(msg || $t(RESPONSE_TIPS.error)) } @@ -91,7 +91,7 @@ const ServiceHubDetail = ()=>{ content:, onOk:()=>{ return applyRef.current?.apply().then((res)=>{ - if(res === true) setApplied(true) + // if(res === true) setApplied(true) }) }, okText:$t('确认'),