You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by li...@apache.org on 2022/06/23 14:48:47 UTC
[incubator-shenyu-dashboard] branch master updated: api doc (#209)
This is an automated email from the ASF dual-hosted git repository.
likeguo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-shenyu-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 233a85fb api doc (#209)
233a85fb is described below
commit 233a85fb19d05cee77068c245e66568aea4a3159
Author: dayu <we...@qq.com>
AuthorDate: Thu Jun 23 22:48:41 2022 +0800
api doc (#209)
---
package.json | 5 +-
src/common/menu.js | 106 +++++----
src/common/router.js | 5 +
src/layouts/UserLayout.js | 6 +-
src/locales/en-US.json | 28 ++-
src/locales/zh-CN.json | 24 +-
src/routes/Document/ApiDoc.js | 93 ++++++++
src/routes/Document/components/ApiContext.js | 23 ++
src/routes/Document/components/ApiDebug.js | 326 +++++++++++++++++++++++++++
src/routes/Document/components/ApiInfo.js | 198 ++++++++++++++++
src/routes/Document/components/SearchApi.js | 134 +++++++++++
src/services/api.js | 62 +++--
12 files changed, 940 insertions(+), 70 deletions(-)
diff --git a/package.json b/package.json
index 2c088618..b4e80a09 100755
--- a/package.json
+++ b/package.json
@@ -33,12 +33,13 @@
"path-to-regexp": "^2.1.0",
"prop-types": "^15.5.10",
"qs": "^6.5.0",
- "react": "^16.4.1",
+ "react": "^16.14.0",
"react-container-query": "^0.11.0",
"react-document-title": "^2.0.3",
- "react-dom": "^16.4.1",
+ "react-dom": "^16.13.1",
"react-fittext": "^1.0.0",
"react-intl-universal": "^2.4.2",
+ "react-json-view": "^1.21.3",
"react-resizable": "^1.11.0",
"redux-logger": "^3.0.6",
"setprototypeof": "^1.1.0",
diff --git a/src/common/menu.js b/src/common/menu.js
index 47adc592..b2a2526a 100644
--- a/src/common/menu.js
+++ b/src/common/menu.js
@@ -15,76 +15,88 @@
* limitations under the License.
*/
-import { isUrl } from '../utils/utils';
-import { getIntlContent } from '../utils/IntlUtils'
+import { isUrl } from "../utils/utils";
+import { getIntlContent } from "../utils/IntlUtils";
export const menuData = [
{
- name: getIntlContent('SHENYU.MENU.PLUGIN.LIST'),
- icon: 'dashboard',
- path: 'plug',
- locale: 'SHENYU.MENU.PLUGIN.LIST',
- children: [
- ],
+ name: getIntlContent("SHENYU.MENU.PLUGIN.LIST"),
+ icon: "dashboard",
+ path: "plug",
+ locale: "SHENYU.MENU.PLUGIN.LIST",
+ children: []
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT'),
- icon: 'setting',
- path: 'system',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT',
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT"),
+ icon: "setting",
+ path: "system",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT",
children: [
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.ROLE'),
- path: 'role',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.ROLE'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.ROLE"),
+ path: "role",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.ROLE"
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.USER'),
- path: 'manage',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.USER'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.USER"),
+ path: "manage",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.USER"
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.RESOURCE'),
- path: 'resource',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.RESOURCE'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.RESOURCE"),
+ path: "resource",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.RESOURCE"
}
- ],
+ ]
},
{
- name: getIntlContent('SHENYU.MENU.CONFIG.MANAGMENT'),
- icon: 'setting',
- path: 'config',
- locale: 'SHENYU.MENU.CONFIG.MANAGMENT',
+ name: getIntlContent("SHENYU.MENU.CONFIG.MANAGMENT"),
+ icon: "setting",
+ path: "config",
+ locale: "SHENYU.MENU.CONFIG.MANAGMENT",
children: [
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.PLUGIN'),
- path: 'plugin',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.PLUGIN'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.PLUGIN"),
+ path: "plugin",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.PLUGIN"
},
{
- name: getIntlContent('SHENYU.PLUGIN.PLUGINHANDLE'),
- path: 'pluginhandle',
- locale: 'SHENYU.PLUGIN.PLUGINHANDLE'
+ name: getIntlContent("SHENYU.PLUGIN.PLUGINHANDLE"),
+ path: "pluginhandle",
+ locale: "SHENYU.PLUGIN.PLUGINHANDLE"
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN'),
- path: 'auth',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN"),
+ path: "auth",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.AUTHEN"
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.METADATA'),
- path: 'metadata',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.METADATA'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.METADATA"),
+ path: "metadata",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.METADATA"
},
{
- name: getIntlContent('SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY'),
- path: 'dict',
- locale: 'SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY'
+ name: getIntlContent("SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY"),
+ path: "dict",
+ locale: "SHENYU.MENU.SYSTEM.MANAGMENT.DICTIONARY"
}
- ],
+ ]
},
+ {
+ name: getIntlContent("SHENYU.MENU.DOCUMENT"),
+ icon: "file-text",
+ path: "document",
+ locale: "SHENYU.MENU.DOCUMENT",
+ children: [
+ {
+ name: getIntlContent("SHENYU.MENU.DOCUMENT.APIDOC"),
+ path: "apidoc",
+ locale: "SHENYU.MENU.DOCUMENT.APIDOC"
+ }
+ ]
+ }
];
-function formatter(data, parentPath = '/', parentAuthority) {
+function formatter(data, parentPath = "/", parentAuthority) {
return data.map(item => {
let { path } = item;
if (!isUrl(path)) {
@@ -93,10 +105,14 @@ function formatter(data, parentPath = '/', parentAuthority) {
const result = {
...item,
path,
- authority: item.authority || parentAuthority,
+ authority: item.authority || parentAuthority
};
if (item.children) {
- result.children = formatter(item.children, `${parentPath}${item.path}/`, item.authority);
+ result.children = formatter(
+ item.children,
+ `${parentPath}${item.path}/`,
+ item.authority
+ );
}
return result;
diff --git a/src/common/router.js b/src/common/router.js
index c28947bf..2a4f1814 100644
--- a/src/common/router.js
+++ b/src/common/router.js
@@ -176,6 +176,11 @@ export const getRouterData = app => {
component: dynamicWrapper(app, ["login"], () =>
import("../routes/User/Login")
)
+ },
+ "/document/apidoc": {
+ component: dynamicWrapper(app, [], () =>
+ import("../routes/Document/ApiDoc")
+ )
}
};
// Get name from ./menu.js or just set it in the router data.
diff --git a/src/layouts/UserLayout.js b/src/layouts/UserLayout.js
index fe76cab6..8c692825 100644
--- a/src/layouts/UserLayout.js
+++ b/src/layouts/UserLayout.js
@@ -34,7 +34,7 @@ const links = [];
const copyright = (
<Fragment>
- Copyright <Icon type="copyright" /> 2018 出品
+ Copyright <Icon type="copyright" /> {new Date().getFullYear()} 出品
</Fragment>
);
@@ -69,7 +69,9 @@ class UserLayout extends React.PureComponent {
<img alt="logo" className={styles.logo} src={TitleLogo} />
</Link>
</div>
- <div className={styles.desc}>Apache ShenYu Gateway Management System</div>
+ <div className={styles.desc}>
+ Apache ShenYu Gateway Management System
+ </div>
</div>
<Switch>
{getRoutes(match.path, routerData).map(item => (
diff --git a/src/locales/en-US.json b/src/locales/en-US.json
index c3c02eb1..31fbce90 100644
--- a/src/locales/en-US.json
+++ b/src/locales/en-US.json
@@ -285,5 +285,31 @@
"SHENYU.PLUGIN.REQUEST.COOKIE.NEW.KEY": "New Cookie Key",
"SHENYU.PLUGIN.PARAM.KEY": "Parameter Key",
"SHENYU.PLUGIN.PARAM.PATH": "Parameter Path",
- "SHENYU.PLUGIN.PARAM.VALUE": "Parameter Value"
+ "SHENYU.PLUGIN.PARAM.VALUE": "Parameter Value",
+ "SHENYU.MENU.DOCUMENT": "Document",
+ "SHENYU.MENU.DOCUMENT.APIDOC": "API Doc",
+ "SHENYU.DOCUMENT.APIDOC.SEARCH.PLACEHOLDER":
+ "Support interface name and document title",
+ "SHENYU.DOCUMENT.APIDOC.INFO.NAME": "Interface Name",
+ "SHENYU.DOCUMENT.APIDOC.INFO.DESCRIPTION": "Interface Description",
+ "SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS": "Request Address",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.PARAMETERS": "Request Parameters",
+ "SHENYU.DOCUMENT.APIDOC.INFO.SERVICE.REQUEST.PARAMETERS":
+ "Service Request Parameters",
+ "SHENYU.DOCUMENT.APIDOC.INFO.RESPONSE.PARAMETERS": "Response Parameters",
+ "SHENYU.DOCUMENT.APIDOC.INFO.COMMON.RESPONSE.PARAMETERS":
+ "Common Response Parameters",
+ "SHENYU.DOCUMENT.APIDOC.INFO.BUSINESS.RESPONSE.PARAMETERS":
+ "Business Response Parameters",
+ "SHENYU.DOCUMENT.APIDOC.INFO.INTERFACE.DEBUG": "Interface Debug",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.INFORMATION": "Request Information",
+ "SHENYU.DOCUMENT.APIDOC.INFO.SEND.REQUEST": "Send Request",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.RESULTS": "Request Results",
+ "SHENYU.DOCUMENT.APIDOC.CONTENTS.TO.BE.SIGNED": "Contents To Be Signed",
+ "SHENYU.DOCUMENT.APIDOC.SIGNATURE": "Signature",
+ "SHENYU.COMMON.REQUIRED": "Required",
+ "SHENYU.COMMON.MAX.LENGTH": "Max Length",
+ "SHENYU.COMMON.MAX.EXAMPLE": "Example",
+ "SHENYU.COMMON.MAX.ENVIRONMENT": "Environment",
+ "SHENYU.COMMON.HTTP.METHOD": "HttpMethod"
}
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index b60ff7c5..4ca3f1c3 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -277,5 +277,27 @@
"SHENYU.PLUGIN.REQUEST.COOKIE.NEW.KEY": "New Cookie Key",
"SHENYU.PLUGIN.PARAM.KEY": "Parameter Key",
"SHENYU.PLUGIN.PARAM.PATH": "Parameter Path",
- "SHENYU.PLUGIN.PARAM.VALUE": "Parameter Value"
+ "SHENYU.PLUGIN.PARAM.VALUE": "Parameter Value",
+ "SHENYU.MENU.DOCUMENT": "文档说明",
+ "SHENYU.MENU.DOCUMENT.APIDOC": "API文档",
+ "SHENYU.DOCUMENT.APIDOC.SEARCH.PLACEHOLDER": "支持接口名称、文档标题",
+ "SHENYU.DOCUMENT.APIDOC.INFO.NAME": "接口名称",
+ "SHENYU.DOCUMENT.APIDOC.INFO.DESCRIPTION": "接口描述",
+ "SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS": "请求地址",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.PARAMETERS": "请求参数",
+ "SHENYU.DOCUMENT.APIDOC.INFO.SERVICE.REQUEST.PARAMETERS": "业务请求参数",
+ "SHENYU.DOCUMENT.APIDOC.INFO.RESPONSE.PARAMETERS": "响应参数",
+ "SHENYU.DOCUMENT.APIDOC.INFO.COMMON.RESPONSE.PARAMETERS": "公共响应参数",
+ "SHENYU.DOCUMENT.APIDOC.INFO.BUSINESS.RESPONSE.PARAMETERS": "业务响应参数",
+ "SHENYU.DOCUMENT.APIDOC.INFO.INTERFACE.DEBUG": "接口调试",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.INFORMATION": "请求信息",
+ "SHENYU.DOCUMENT.APIDOC.INFO.SEND.REQUEST": "发送请求",
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.RESULTS": "请求结果",
+ "SHENYU.DOCUMENT.APIDOC.CONTENTS.TO.BE.SIGNED": "待签名内容",
+ "SHENYU.DOCUMENT.APIDOC.SIGNATURE": "签名(sign)",
+ "SHENYU.COMMON.REQUIRED": "必填",
+ "SHENYU.COMMON.MAX.LENGTH": "最大长度",
+ "SHENYU.COMMON.MAX.EXAMPLE": "示例值",
+ "SHENYU.COMMON.MAX.ENVIRONMENT": "环境",
+ "SHENYU.COMMON.HTTP.METHOD": "请求方法"
}
diff --git a/src/routes/Document/ApiDoc.js b/src/routes/Document/ApiDoc.js
new file mode 100644
index 00000000..b7fd63d7
--- /dev/null
+++ b/src/routes/Document/ApiDoc.js
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Col, Row, Card, BackTop, Empty } from "antd";
+import React, { useEffect, useState } from "react";
+import SearchApi from "./components/SearchApi";
+import ApiInfo from "./components/ApiInfo";
+import { getDocItem, getDocMenus } from "../../services/api";
+import ApiContext from "./components/ApiContext";
+
+function ApiDoc() {
+ const [apiDetail, setApiDetail] = useState({});
+ const [apiData, setApiData] = useState({});
+
+ const initData = async () => {
+ const { code, data = {} } = await getDocMenus();
+ if (code === 200) {
+ const { menuProjects = [] } = data;
+ const createKey = (treeData, keys) => {
+ treeData.forEach((item, index) => {
+ const { children, id, name } = item;
+ const key = [...keys, index].join("-");
+ item.key = key;
+ item.id = id;
+ item.name = name;
+ if (children?.length) {
+ createKey(children, [...keys, index]);
+ }
+ });
+ };
+ createKey(menuProjects, []);
+ data.menuProjects = menuProjects;
+ setApiData(data);
+ }
+ };
+
+ const handleSelectNode = async (_, e) => {
+ const {
+ node: {
+ props: { id }
+ }
+ } = e;
+ const { code, data } = await getDocItem({ id });
+ if (code === 200) {
+ setApiDetail(data);
+ }
+ };
+
+ useEffect(() => {
+ initData();
+ }, []);
+
+ return (
+ <ApiContext.Provider
+ value={{
+ apiDetail,
+ apiData
+ }}
+ >
+ <Card style={{ margin: 24 }}>
+ <Row gutter={24}>
+ <Col span={6}>
+ <SearchApi onSelect={handleSelectNode} />
+ </Col>
+ <Col span={18}>
+ {apiDetail.id ? (
+ <ApiInfo />
+ ) : (
+ <Empty description={false} style={{ padding: "160px 0" }} />
+ )}
+ </Col>
+ </Row>
+ <BackTop />
+ </Card>
+ </ApiContext.Provider>
+ );
+}
+
+export default ApiDoc;
diff --git a/src/routes/Document/components/ApiContext.js b/src/routes/Document/components/ApiContext.js
new file mode 100644
index 00000000..cebaf595
--- /dev/null
+++ b/src/routes/Document/components/ApiContext.js
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { createContext } from "react";
+
+export default createContext({
+ apiDetail: {},
+ apiData: {}
+});
diff --git a/src/routes/Document/components/ApiDebug.js b/src/routes/Document/components/ApiDebug.js
new file mode 100644
index 00000000..7fc13bf5
--- /dev/null
+++ b/src/routes/Document/components/ApiDebug.js
@@ -0,0 +1,326 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ Typography,
+ Form,
+ Input,
+ Button,
+ Radio,
+ Row,
+ Col,
+ Tree,
+ Empty,
+ Tabs
+} from "antd";
+import React, {
+ useEffect,
+ useState,
+ forwardRef,
+ useImperativeHandle,
+ createRef,
+ useContext
+} from "react";
+import ReactJson from "react-json-view";
+import fetch from "dva/fetch";
+import { sandboxProxyGateway } from "../../../services/api";
+import ApiContext from "./ApiContext";
+import { getIntlContent } from "../../../utils/IntlUtils";
+import AuthButton from "../../../utils/AuthButton";
+
+const { Title, Text, Paragraph } = Typography;
+const { TreeNode } = Tree;
+const { TabPane } = Tabs;
+const FormItem = Form.Item;
+
+const FCForm = forwardRef(({ form, onSubmit }, ref) => {
+ useImperativeHandle(ref, () => ({
+ form
+ }));
+
+ const {
+ apiDetail: { name: apiUrl, httpMethodList, requestParameters },
+ apiData: { appKey, gatewayUrl, cookie }
+ } = useContext(ApiContext);
+ const [questJson, setRequestJson] = useState({});
+
+ const handleSubmit = e => {
+ e.preventDefault();
+ ref.current.form.validateFieldsAndScroll((errors, values) => {
+ if (!errors) {
+ onSubmit({
+ ...values,
+ bizParam: questJson
+ });
+ }
+ });
+ };
+
+ const createRequestJson = (params = []) => {
+ const exampleJSON = {};
+ const key = [];
+ const loopExample = (data, obj) => {
+ data.forEach(item => {
+ const { name, refs, example, type } = item;
+ key.push(name);
+ switch (type) {
+ case "array":
+ if (Array.isArray(refs)) {
+ obj[name] = [{}];
+ key.push(0);
+ loopExample(refs, obj[name][0]);
+ key.pop();
+ } else {
+ obj[name] = [];
+ }
+ break;
+ case "object":
+ obj[name] = {};
+ if (Array.isArray(refs)) {
+ loopExample(refs, obj[name]);
+ }
+ break;
+ default:
+ obj[name] = example;
+ break;
+ }
+ key.pop();
+ });
+ };
+
+ loopExample(params, exampleJSON);
+ setRequestJson(exampleJSON);
+ };
+
+ const renderTreeNode = (data, indexArr = []) => {
+ return data.map((item, index) => {
+ const { name, type, required, description } = item;
+ const TreeTitle = (
+ <>
+ <Text strong>{name}</Text>
+ <Text code>{type}</Text>
+
+ {required ? (
+ <Text type="danger">required</Text>
+ ) : (
+ <Text type="warning">optional</Text>
+ )}
+ <Text type="secondary">{description}</Text>
+ </>
+ );
+ return (
+ <TreeNode key={[...indexArr, index].join("-")} title={TreeTitle}>
+ {item.refs && renderTreeNode(item.refs, [...indexArr, index])}
+ </TreeNode>
+ );
+ });
+ };
+
+ const updateJson = obj => {
+ setRequestJson(obj.updated_src);
+ };
+
+ useEffect(
+ () => {
+ createRequestJson(requestParameters);
+ },
+ [requestParameters]
+ );
+
+ return (
+ <Form onSubmit={handleSubmit}>
+ <Title level={4}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.INFORMATION")}
+ </Title>
+ <FormItem label={getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS")}>
+ {form.getFieldDecorator("requestUrl", {
+ initialValue: gatewayUrl + apiUrl,
+ rules: [{ type: "string", required: true }]
+ })(<Input allowClear />)}
+ </FormItem>
+ <FormItem label="AppKey">
+ {form.getFieldDecorator("appKey", {
+ initialValue: appKey,
+ rules: [{ type: "string" }]
+ })(
+ <Input
+ allowClear
+ placeholder=" If the current API requires signature authentication, this parameter is required"
+ />
+ )}
+ </FormItem>
+ <FormItem label="Cookie">
+ {form.getFieldDecorator("cookie", {
+ initialValue: cookie,
+ rules: [{ type: "string" }]
+ })(
+ <Input
+ allowClear
+ placeholder="Fill in the real cookie value.(signature authentication and login free API ignore this item)"
+ />
+ )}
+ </FormItem>
+ <FormItem label={getIntlContent("SHENYU.COMMON.HTTP.METHOD")}>
+ {form.getFieldDecorator("httpMethod", {
+ initialValue: httpMethodList?.[0]?.toLocaleUpperCase(),
+ rules: [{ type: "string", required: true }]
+ })(
+ <Radio.Group
+ options={httpMethodList?.map(v => v.toLocaleUpperCase())}
+ />
+ )}
+ </FormItem>
+ <FormItem
+ label={getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.PARAMETERS")}
+ required
+ />
+ <Row gutter={16}>
+ <Col span={14}>
+ <ReactJson
+ src={questJson}
+ theme="monokai"
+ displayDataTypes={false}
+ name={false}
+ onAdd={updateJson}
+ onEdit={updateJson}
+ onDelete={updateJson}
+ style={{ borderRadius: 4, padding: 16 }}
+ />
+ </Col>
+ <Col span={10}>
+ <div
+ style={{
+ borderRadius: 4,
+ border: "1px solid #e8e8e8",
+ overflow: "auto",
+ padding: 8
+ }}
+ >
+ {requestParameters && (
+ <Tree showLine defaultExpandAll>
+ {renderTreeNode(requestParameters)}
+ </Tree>
+ )}
+ </div>
+ </Col>
+ </Row>
+ <AuthButton perms="document:apirun:send">
+ <FormItem label=" " colon={false}>
+ <Button htmlType="submit" type="primary">
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.SEND.REQUEST")}
+ </Button>
+ </FormItem>
+ </AuthButton>
+ </Form>
+ );
+});
+
+const EnhancedFCForm = Form.create()(FCForm);
+
+function ApiDebug() {
+ const {
+ apiDetail: { id }
+ } = useContext(ApiContext);
+ const [responseInfo, setResponseInfo] = useState({});
+ const [activeKey, setActiveKey] = useState("2");
+
+ const formRef = createRef();
+
+ const handleSubmit = async values => {
+ fetch(sandboxProxyGateway(), {
+ method: "POST",
+ headers: {
+ "X-Access-Token": sessionStorage.token,
+ "Content-Type": "application/json"
+ },
+ body: JSON.stringify(values)
+ }).then(async response => {
+ const data = await response.json();
+ setResponseInfo({
+ "sandbox-params": response.headers.get("sandbox-params"),
+ "sandbox-beforesign": response.headers.get("sandbox-beforesign"),
+ "sandbox-sign": response.headers.get("sandbox-sign"),
+ body: data
+ });
+ });
+ };
+
+ useEffect(
+ () => {
+ setResponseInfo({});
+ // eslint-disable-next-line no-unused-expressions
+ formRef.current?.form.resetFields(["method"]);
+ setActiveKey("2");
+ },
+ [id]
+ );
+
+ return (
+ <>
+ <EnhancedFCForm wrappedComponentRef={formRef} onSubmit={handleSubmit} />
+ <Tabs
+ type="card"
+ activeKey={activeKey}
+ onChange={key => setActiveKey(key)}
+ >
+ <TabPane
+ tab={getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.INFORMATION"
+ )}
+ key="1"
+ >
+ {Object.keys(responseInfo).length ? (
+ <>
+ <Paragraph>
+ <Text strong>
+ {getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.CONTENTS.TO.BE.SIGNED"
+ )}
+ </Text>
+ <br />
+ <Text code>
+ {responseInfo["sandbox-beforesign"] || "undefined"}
+ </Text>
+ </Paragraph>
+ <Paragraph>
+ <Text strong>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.SIGNATURE")}
+ </Text>
+ <br />
+ <Text code>{responseInfo["sandbox-sign"] || "undefined"}</Text>
+ </Paragraph>
+ </>
+ ) : (
+ <Empty description={false} />
+ )}
+ </TabPane>
+ <TabPane
+ tab={getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.RESULTS")}
+ key="2"
+ >
+ {Object.keys(responseInfo).length ? (
+ <ReactJson src={responseInfo.body} name={false} />
+ ) : (
+ <Empty description={false} />
+ )}
+ </TabPane>
+ </Tabs>
+ </>
+ );
+}
+
+export default ApiDebug;
diff --git a/src/routes/Document/components/ApiInfo.js b/src/routes/Document/components/ApiInfo.js
new file mode 100644
index 00000000..fa8a8f7f
--- /dev/null
+++ b/src/routes/Document/components/ApiInfo.js
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Typography, Table } from "antd";
+import React, { useContext } from "react";
+import ApiDebug from "./ApiDebug";
+import ApiContext from "./ApiContext";
+import { getIntlContent } from "../../../utils/IntlUtils";
+
+const { Title, Text, Paragraph } = Typography;
+
+function ApiInfo() {
+ const {
+ apiData: { envProps = [] },
+ apiDetail: {
+ summary,
+ name: apiName,
+ description,
+ requestParameters,
+ responseParameters
+ }
+ } = useContext(ApiContext);
+
+ const columns = [
+ {
+ title: getIntlContent("SHENYU.PLUGIN.SELECTOR.LIST.COLUMN.NAME"),
+ dataIndex: "name"
+ },
+ {
+ title: getIntlContent("SHENYU.COMMON.TYPE"),
+ dataIndex: "type"
+ },
+ {
+ title: getIntlContent("SHENYU.COMMON.REQUIRED"),
+ dataIndex: "required",
+ render: v =>
+ v ? (
+ <Text type="danger">{getIntlContent("SHENYU.COMMON.YES")}</Text>
+ ) : (
+ getIntlContent("SHENYU.COMMON.NO")
+ )
+ },
+ {
+ title: getIntlContent("SHENYU.COMMON.MAX.LENGTH"),
+ dataIndex: "maxLength"
+ },
+ {
+ title: getIntlContent("SHENYU.PLUGIN.DESCRIBE"),
+ dataIndex: "description"
+ },
+ {
+ title: getIntlContent("SHENYU.COMMON.MAX.EXAMPLE"),
+ dataIndex: "example"
+ }
+ ];
+
+ const defaultCommonData = [
+ {
+ id: 1,
+ name: "code",
+ type: "integer",
+ description: "返回码",
+ example: "200"
+ },
+ {
+ id: 2,
+ name: "message",
+ type: "string",
+ description: "错误描述信息",
+ example: "非法的参数"
+ },
+ {
+ id: 3,
+ name: "data",
+ type: "object",
+ description: "响应的业务结果",
+ example: '{"id":"1988771289091030"}'
+ }
+ ];
+
+ const envPropsColumns = [
+ {
+ title: getIntlContent("SHENYU.COMMON.MAX.ENVIRONMENT"),
+ dataIndex: "envLabel"
+ },
+ {
+ title: getIntlContent("SHENYU.COMMON.TYPE"),
+ dataIndex: "addressLabel"
+ },
+ {
+ title: getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS"),
+ dataIndex: "addressUrl"
+ }
+ ];
+
+ return (
+ <>
+ <Title level={2}>{summary}</Title>
+ <Title level={4}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.NAME")}
+ </Title>
+ <Text code>{apiName}</Text>
+ <Title level={4}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.DESCRIPTION")}
+ </Title>
+ <Text type="secondary">{description}</Text>
+ <Title level={4}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.ADDRESS")}
+ </Title>
+ <Paragraph>
+ <Table
+ size="small"
+ rowKey="envLabel"
+ bordered
+ dataSource={envProps}
+ pagination={false}
+ columns={envPropsColumns}
+ />
+ </Paragraph>
+
+ <Title level={2}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.REQUEST.PARAMETERS")}
+ </Title>
+ <Title level={4}>
+ {getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.INFO.SERVICE.REQUEST.PARAMETERS"
+ )}
+ </Title>
+ <Paragraph>
+ <Table
+ size="small"
+ rowKey="id"
+ bordered
+ dataSource={requestParameters}
+ pagination={false}
+ childrenColumnName="refs"
+ columns={columns}
+ />
+ </Paragraph>
+
+ <Title level={2}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.RESPONSE.PARAMETERS")}
+ </Title>
+ <Title level={4}>
+ {getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.INFO.COMMON.RESPONSE.PARAMETERS"
+ )}
+ </Title>
+ <Paragraph>
+ <Table
+ size="small"
+ rowKey="id"
+ bordered
+ dataSource={defaultCommonData}
+ pagination={false}
+ columns={columns.filter((_, i) => ![2, 3].includes(i))}
+ />
+ </Paragraph>
+ <Title level={4}>
+ {getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.INFO.BUSINESS.RESPONSE.PARAMETERS"
+ )}
+ </Title>
+ <Paragraph>
+ <Table
+ size="small"
+ rowKey="id"
+ bordered
+ dataSource={responseParameters}
+ pagination={false}
+ childrenColumnName="refs"
+ columns={columns}
+ />
+ </Paragraph>
+
+ <Title level={2}>
+ {getIntlContent("SHENYU.DOCUMENT.APIDOC.INFO.INTERFACE.DEBUG")}
+ </Title>
+ <ApiDebug />
+ </>
+ );
+}
+
+export default ApiInfo;
diff --git a/src/routes/Document/components/SearchApi.js b/src/routes/Document/components/SearchApi.js
new file mode 100644
index 00000000..ca9b6a47
--- /dev/null
+++ b/src/routes/Document/components/SearchApi.js
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Tree, Input, Empty } from "antd";
+import React, { useContext, useEffect, useState } from "react";
+import ApiContext from "./ApiContext";
+import { getIntlContent } from "../../../utils/IntlUtils";
+
+const { TreeNode } = Tree;
+const { Search } = Input;
+
+function SearchApi(props) {
+ const { onSelect } = props;
+ const [expandedKeys, setExpandedKeys] = useState([]);
+ const [searchValue, setSearchValue] = useState("");
+ const [autoExpandParent, setAutoExpandParent] = useState(true);
+ const {
+ apiData: { menuProjects }
+ } = useContext(ApiContext);
+
+ const renderTreeNode = data => {
+ return data.map(item => {
+ const { children, id, label, key, name } = item;
+ const index = label.indexOf(searchValue);
+ const sameName = name?.indexOf(searchValue);
+ const beforeStr = label.substr(0, index);
+ const afterStr = label.substr(index + searchValue.length);
+ let titleObj = <span>{label}</span>;
+ if (index > -1) {
+ titleObj = (
+ <span>
+ {beforeStr}
+ <span style={{ color: "#f50" }}>{searchValue}</span>
+ {afterStr}
+ </span>
+ );
+ }
+ if (searchValue && sameName > -1) {
+ titleObj = <span style={{ color: "#f50" }}>{label}</span>;
+ }
+ return (
+ <TreeNode
+ key={key}
+ title={titleObj}
+ selectable={id !== undefined}
+ id={id}
+ >
+ {children?.length && renderTreeNode(children)}
+ </TreeNode>
+ );
+ });
+ };
+
+ const handleSearchChange = e => {
+ const { value } = e.target;
+ const keys = [];
+ const findSearchKeys = data =>
+ data.forEach(item => {
+ if (item.label.indexOf(value) > -1 || item.name?.indexOf(value) > -1) {
+ keys.push(item.key);
+ }
+ if (Array.isArray(item.children)) {
+ findSearchKeys(item.children);
+ }
+ });
+ findSearchKeys(menuProjects);
+ setExpandedKeys(keys);
+ setSearchValue(value);
+ setAutoExpandParent(true);
+ };
+
+ const handleExpandChange = keys => {
+ setExpandedKeys(keys);
+ setAutoExpandParent(false);
+ };
+
+ useEffect(
+ () => {
+ if (Array.isArray(menuProjects)) {
+ const allKeys = [];
+ const getAllParentsKey = data =>
+ data.forEach(item => {
+ if (item.children) {
+ allKeys.push(item.key);
+ getAllParentsKey(item.children);
+ }
+ });
+ getAllParentsKey(menuProjects);
+ setExpandedKeys(allKeys);
+ }
+ },
+ [menuProjects]
+ );
+
+ return (
+ <div style={{ overflow: "auto" }}>
+ <Search
+ allowClear
+ onChange={handleSearchChange}
+ placeholder={getIntlContent(
+ "SHENYU.DOCUMENT.APIDOC.SEARCH.PLACEHOLDER"
+ )}
+ />
+ {menuProjects?.length ? (
+ <Tree
+ autoExpandParent={autoExpandParent}
+ expandedKeys={expandedKeys}
+ onExpand={handleExpandChange}
+ onSelect={onSelect}
+ >
+ {renderTreeNode(menuProjects)}
+ </Tree>
+ ) : (
+ <Empty style={{ padding: "80px 0" }} description={false} />
+ )}
+ </div>
+ );
+}
+
+export default SearchApi;
diff --git a/src/services/api.js b/src/services/api.js
index 46f81425..6c02c8f7 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import {stringify} from "qs";
+import { stringify } from "qs";
import request from "../utils/request";
const baseUrl = document.getElementById("httpPath").innerHTML;
@@ -66,10 +66,15 @@ export async function updatePassword(params) {
/* get all metadata */
export async function getAllMetadata(params) {
- const {appName, currentPage, pageSize} = params;
- return request(`${baseUrl}/meta-data/queryList?${stringify(appName ? params : {currentPage, pageSize})}`, {
- method: `GET`
- });
+ const { appName, currentPage, pageSize } = params;
+ return request(
+ `${baseUrl}/meta-data/queryList?${stringify(
+ appName ? params : { currentPage, pageSize }
+ )}`,
+ {
+ method: `GET`
+ }
+ );
}
export async function findMetadata(params) {
@@ -143,8 +148,8 @@ export async function updateEnabled(params) {
/* getAllUsers */
export async function getAllUsers(params) {
- const {userName, currentPage, pageSize} = params;
- const myParams = userName ? params : {currentPage, pageSize};
+ const { userName, currentPage, pageSize } = params;
+ const myParams = userName ? params : { currentPage, pageSize };
return request(`${baseUrl}/dashboardUser?${stringify(myParams)}`, {
method: `GET`
});
@@ -198,7 +203,7 @@ export async function getAllPlugins(params) {
}
/* get Plugins snapshot */
-export function activePluginSnapshot() {
+export function activePluginSnapshot() {
return request(`${baseUrl}/plugin/snapshot/active`, {
method: `GET`
});
@@ -254,8 +259,8 @@ export async function updateAuth(params) {
/* getAllAuth */
export async function getAllAuth(params) {
- const {appKey, currentPage, pageSize} = params;
- let myParams = appKey ? params : {currentPage, pageSize};
+ const { appKey, currentPage, pageSize } = params;
+ let myParams = appKey ? params : { currentPage, pageSize };
return request(`${baseUrl}/appAuth?${stringify(myParams)}`, {
method: `GET`
});
@@ -271,8 +276,8 @@ export async function syncAuthsData() {
/* getAllAuths */
export async function getAllAuths(params) {
- const {appKey, phone, currentPage, pageSize} = params;
- const myParams = appKey || phone ? params : {currentPage, pageSize};
+ const { appKey, phone, currentPage, pageSize } = params;
+ const myParams = appKey || phone ? params : { currentPage, pageSize };
return request(`${baseUrl}/appAuth/findPageByQuery?${stringify(myParams)}`, {
method: `GET`
});
@@ -585,10 +590,10 @@ export async function getAllRoles() {
/* get roles by page */
export async function getRoleList(params) {
- const {roleName, currentPage, pageSize} = params;
- let myParams = {...params};
+ const { roleName, currentPage, pageSize } = params;
+ let myParams = { ...params };
if (!roleName) {
- myParams = {currentPage, pageSize};
+ myParams = { currentPage, pageSize };
}
return request(`${baseUrl}/role?${stringify(myParams)}`, {
method: `GET`
@@ -634,10 +639,10 @@ export async function updateRole(params) {
/* get resources by page */
export async function getAllResources(params) {
- const {title, currentPage, pageSize} = params;
- let myParams = {...params};
+ const { title, currentPage, pageSize } = params;
+ let myParams = { ...params };
if (!title) {
- myParams = {currentPage, pageSize};
+ myParams = { currentPage, pageSize };
}
return request(`${baseUrl}/resource?${stringify(myParams)}`, {
method: `GET`
@@ -758,8 +763,27 @@ export async function deleteDataPermisionRule(params) {
}
/* get new event recode logs */
-export function getNewEventRecodLogList() {
+export function getNewEventRecodLogList() {
return request(`${baseUrl}/operation-record/log/list`, {
method: `GET`
});
}
+
+/* get all api */
+export function getDocMenus() {
+ return request(`${baseUrl}/apidoc/getDocMenus`, {
+ method: `GET`
+ });
+}
+
+/* get api item */
+export function getDocItem(params) {
+ return request(`${baseUrl}/apidoc/getDocItem?${stringify(params)}`, {
+ method: `GET`
+ });
+}
+
+/* sandbox proxyGateway */
+export function sandboxProxyGateway() {
+ return `${baseUrl}/sandbox/proxyGateway`;
+}