This is an automated email from the ASF dual-hosted git repository.
juzhiyuan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push:
new 8f94c84 feat(FE): fetch category from manager-api (#1122)
8f94c84 is described below
commit 8f94c844218913fdf9e36cd7c62bbe94188ffed3
Author: litesun <[email protected]>
AuthorDate: Tue Dec 29 15:01:54 2020 +0800
feat(FE): fetch category from manager-api (#1122)
* feat: fetch category from manager-api
* feat: add category sort
* feat: use consumer_schema in consumer module
* feat: use local PluginOrchestration components
* feat: add license
* feat: pluginOrchestration fetch type from manager-api
* feat: remove json-schema
* feat: add pluginOrchestration i18n
* feat: add showList sort
* feat: clean code
* feat: update name
Co-authored-by: juzhiyuan <[email protected]>
---
web/package.json | 6 +-
web/src/components/Plugin/PluginPage.tsx | 211 ++++++++--------
web/src/components/Plugin/service.ts | 53 +---
web/src/components/Plugin/typing.d.ts | 18 +-
.../PluginOrchestration/DrawPluginStyle.ts | 82 ++++++
.../components/Page.tsx} | 24 +-
.../components/SidebarItem.tsx} | 42 ++--
.../components/index.ts} | 24 +-
.../PluginOrchestration/constants.ts} | 63 +++--
.../PluginOrchestration/customConfig.tsx | 71 ++++++
web/src/components/PluginOrchestration/index.tsx | 277 +++++++++++++++++++++
.../locales/en-US.ts} | 32 +--
.../locales/zh-CN.ts} | 32 +--
.../typing.d.ts => PluginOrchestration/service.ts} | 27 +-
.../components/PluginOrchestration/transform.ts | 122 +++++++++
.../{Plugin => PluginOrchestration}/typing.d.ts | 24 +-
web/src/locales/en-US.ts | 2 +
web/src/locales/zh-CN.ts | 2 +
web/src/pages/Route/Create.tsx | 7 +-
.../Route/components/CreateStep4/CreateStep4.tsx | 4 +-
web/src/pages/Route/components/Step3/index.tsx | 2 +-
web/yarn.lock | 88 ++-----
22 files changed, 805 insertions(+), 408 deletions(-)
diff --git a/web/package.json b/web/package.json
index 02a5bb8..9016a80 100644
--- a/web/package.json
+++ b/web/package.json
@@ -50,8 +50,8 @@
"@ant-design/icons": "^4.0.0",
"@ant-design/pro-layout": "^6.0.0",
"@ant-design/pro-table": "2.6.3",
- "@api7-dashboard/pluginchart": "^1.0.14",
"@api7-dashboard/ui": "^1.0.3",
+ "@mrblenny/react-flow-chart": "^0.0.14",
"@rjsf/antd": "2.2.0",
"@rjsf/core": "2.2.0",
"@uiw/react-codemirror": "^3.0.1",
@@ -73,6 +73,7 @@
"react-dom": "^16.8.6",
"react-helmet-async": "^1.0.4",
"start-server-and-test": "^1.11.5",
+ "styled-components": "^5.2.1",
"umi": "^3.1.2",
"umi-request": "^1.0.8",
"use-merge-value": "^1.0.1",
@@ -80,6 +81,7 @@
},
"devDependencies": {
"@ant-design/pro-cli": "^2.0.2",
+ "@types/base-64": "^0.1.3",
"@types/classnames": "^2.2.7",
"@types/express": "^4.17.0",
"@types/history": "^4.7.2",
@@ -91,8 +93,8 @@
"@types/react": "^16.9.17",
"@types/react-dom": "^16.8.4",
"@types/react-helmet": "^5.0.13",
+ "@types/styled-components": "^5.1.7",
"@types/uuid": "7.0.4",
- "@types/base-64": "^0.1.3",
"@umijs/fabric": "^2.2.0",
"@umijs/plugin-blocks": "^2.0.5",
"@umijs/plugin-esbuild": "^1.0.0-beta.2",
diff --git a/web/src/components/Plugin/PluginPage.tsx
b/web/src/components/Plugin/PluginPage.tsx
index e88f417..3a3f76f 100644
--- a/web/src/components/Plugin/PluginPage.tsx
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -15,12 +15,12 @@
* limitations under the License.
*/
import React, { useEffect, useState } from 'react';
-import { Anchor, Layout, Switch, Card, Tooltip, Button, notification, Avatar }
from 'antd';
+import { Anchor, Layout, Switch, Card, Tooltip, Button, notification } from
'antd';
import { SettingFilled } from '@ant-design/icons';
import { PanelSection } from '@api7-dashboard/ui';
import Ajv, { DefinedError } from 'ajv';
-import { fetchSchema, getList } from './service';
+import { fetchList } from './service';
import CodeMirrorDrawer from './CodeMirrorDrawer';
type Props = {
@@ -51,11 +51,23 @@ const PluginPage: React.FC<Props> = ({
schemaType = '',
onChange = () => {},
}) => {
- const [pluginList, setPlugin] = useState<PluginComponent.Meta[][]>([]);
+ const [pluginList, setPluginList] = useState<PluginComponent.Meta[]>([]);
const [name, setName] = useState<string>(NEVER_EXIST_PLUGIN_FLAG);
+ const [typeList, setTypeList] = useState<string[]>([]);
+ const firstUpperCase = ([first, ...rest]: string) => first.toUpperCase() +
rest.join('');
useEffect(() => {
- getList().then(setPlugin);
+ fetchList().then((data) => {
+ setPluginList(data);
+
+ const categoryList: string[] = [];
+ data.forEach((item) => {
+ if (!categoryList.includes(firstUpperCase(item.type))) {
+ categoryList.push(firstUpperCase(item.type));
+ }
+ });
+ setTypeList(categoryList.sort());
+ });
}, []);
// NOTE: This function has side effect because it mutates the original
schema data
@@ -73,48 +85,55 @@ const PluginPage: React.FC<Props> = ({
};
const validateData = (pluginName: string, value: PluginComponent.Data) => {
- fetchSchema(pluginName, schemaType).then((schema) => {
- if (schema.oneOf) {
- (schema.oneOf || []).forEach((item: any) => {
- injectDisableProperty(item);
- });
- } else {
- injectDisableProperty(schema);
- }
+ const plugin = pluginList.find((item) => item.name === pluginName);
+ let schema: any = {};
- const validate = ajv.compile(schema);
+ if (schemaType === 'consumer' && plugin?.consumer_schema) {
+ schema = plugin.consumer_schema;
+ } else if (plugin?.schema) {
+ schema = plugin.schema;
+ }
- if (validate(value)) {
- setName(NEVER_EXIST_PLUGIN_FLAG);
- onChange({ ...initialData, [pluginName]: value });
- return;
- }
+ if (schema.oneOf) {
+ (schema.oneOf || []).forEach((item: any) => {
+ injectDisableProperty(item);
+ });
+ } else {
+ injectDisableProperty(schema);
+ }
- // eslint-disable-next-line
- for (const err of validate.errors as DefinedError[]) {
- let description = '';
- switch (err.keyword) {
- case 'enum':
- description = `${err.dataPath} ${err.message}:
${err.params.allowedValues.join(', ')}`;
- break;
- case 'minItems':
- case 'type':
- description = `${err.dataPath} ${err.message}`;
- break;
- case 'oneOf':
- case 'required':
- description = err.message || '';
- break;
- default:
- description = `${err.schemaPath} ${err.message}`;
- }
- notification.error({
- message: 'Invalid plugin data',
- description,
- });
+ const validate = ajv.compile(schema);
+
+ if (validate(value)) {
+ setName(NEVER_EXIST_PLUGIN_FLAG);
+ onChange({ ...initialData, [pluginName]: value });
+ return;
+ }
+
+ // eslint-disable-next-line
+ for (const err of validate.errors as DefinedError[]) {
+ let description = '';
+ switch (err.keyword) {
+ case 'enum':
+ description = `${err.dataPath} ${err.message}:
${err.params.allowedValues.join(', ')}`;
+ break;
+ case 'minItems':
+ case 'type':
+ description = `${err.dataPath} ${err.message}`;
+ break;
+ case 'oneOf':
+ case 'required':
+ description = err.message || '';
+ break;
+ default:
+ description = `${err.schemaPath} ${err.message}`;
}
- setName(pluginName);
- });
+ notification.error({
+ message: 'Invalid plugin data',
+ description,
+ });
+ }
+ setName(pluginName);
};
return (
@@ -133,78 +152,60 @@ const PluginPage: React.FC<Props> = ({
<Layout>
<Sider theme="light">
<Anchor offsetTop={150}>
- {pluginList.map((plugins) => {
- const { category } = plugins[0];
- return (
- <Anchor.Link
- href={`#plugin-category-${category}`}
- title={category}
- key={category}
- />
- );
+ {typeList.map((type) => {
+ return <Anchor.Link href={`#plugin-category-${type}`}
title={type} key={type} />;
})}
</Anchor>
</Sider>
<Content style={{ padding: '0 10px', backgroundColor: '#fff',
minHeight: 1400 }}>
- {pluginList.map((plugins) => {
- const { category } = plugins[0];
+ {typeList.map((type) => {
return (
<PanelSection
- title={category}
- key={category}
+ title={type}
+ key={type}
style={PanelSectionStyle}
- id={`plugin-category-${category}`}
+ id={`plugin-category-${type}`}
>
- {plugins.map((item) => (
- <Card
- key={item.name}
- title={[
- item.avatar && (
- <Avatar
- key={1}
- icon={item.avatar}
- className="plugin-avatar"
- style={{
- marginRight: 5,
- }}
- />
- ),
- <span key={2}>{item.name}</span>,
- ]}
- style={{ height: 66 }}
- extra={[
- <Tooltip title="Setting"
key={`plugin-card-${item.name}-extra-tooltip-2`}>
- <Button
- shape="circle"
- icon={<SettingFilled />}
- style={{ marginRight: 10, marginLeft: 10 }}
- size="middle"
- onClick={() => {
- setName(item.name);
+ {pluginList
+ .filter((item) => item.type === type.toLowerCase())
+ .map((item) => (
+ <Card
+ key={item.name}
+ title={[<span key={2}>{item.name}</span>]}
+ style={{ height: 66 }}
+ extra={[
+ <Tooltip title="Setting"
key={`plugin-card-${item.name}-extra-tooltip-2`}>
+ <Button
+ shape="circle"
+ icon={<SettingFilled />}
+ style={{ marginRight: 10, marginLeft: 10 }}
+ size="middle"
+ onClick={() => {
+ setName(item.name);
+ }}
+ />
+ </Tooltip>,
+ <Switch
+ defaultChecked={initialData[item.name] &&
!initialData[item.name].disable}
+ disabled={readonly}
+ onChange={(isChecked) => {
+ if (isChecked) {
+ validateData(item.name, {
+ ...initialData[item.name],
+ disable: false,
+ });
+ } else {
+ onChange({
+ ...initialData,
+ [item.name]: { ...initialData[item.name],
disable: true },
+ });
+ }
}}
- />
- </Tooltip>,
- <Switch
- defaultChecked={initialData[item.name] &&
!initialData[item.name].disable}
- disabled={readonly}
- onChange={(isChecked) => {
- if (isChecked) {
- validateData(item.name, {
- ...initialData[item.name],
- disable: false,
- });
- } else {
- onChange({
- ...initialData,
- [item.name]: { ...initialData[item.name],
disable: true },
- });
- }
- }}
- key={Math.random().toString(36).substring(7)}
- />,
- ]}
- />
- ))}
+ key={Math.random().toString(36).substring(7)}
+ />,
+ ]}
+ />
+ ))}
</PanelSection>
);
})}
diff --git a/web/src/components/Plugin/service.ts
b/web/src/components/Plugin/service.ts
index 1f94ab6..7a15639 100644
--- a/web/src/components/Plugin/service.ts
+++ b/web/src/components/Plugin/service.ts
@@ -17,55 +17,10 @@
import { omit } from 'lodash';
import { request } from 'umi';
-import { PLUGIN_MAPPER_SOURCE } from './data';
-
-enum Category {
- 'Limit traffic',
- 'Observability',
- 'Security',
- 'Authentication',
- 'Log',
- 'Other',
-}
-
-export const fetchList = () => request<Res<string[]>>('/plugins');
-
-let cachedPluginNameList: string[] = [];
-export const getList = async () => {
- if (!cachedPluginNameList.length) {
- cachedPluginNameList = (await fetchList()).data;
- }
- const names = cachedPluginNameList;
- const data: Record<string, PluginComponent.Meta[]> = {};
-
- names.forEach((name) => {
- const plugin = PLUGIN_MAPPER_SOURCE[name] || {};
- const { category = 'Other', hidden = false } = plugin;
-
- // NOTE: assign it to Authentication plugin
- if (name.includes('auth')) {
- plugin.category = 'Authentication';
- }
-
- if (!data[category]) {
- data[category] = [];
- }
-
- if (!hidden) {
- data[category] = data[category].concat({
- ...plugin,
- name,
- });
- }
- });
-
- return Object.keys(data)
- .sort((a, b) => Category[a] - Category[b])
- .map((category) => {
- return data[category].sort((a, b) => {
- return (a.priority || 9999) - (b.priority || 9999);
- });
- });
+export const fetchList = () => {
+ return request<Res<PluginComponent.Meta[]>>('/plugins?all=true').then(data
=> {
+ return data.data;
+ })
};
/**
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/Plugin/typing.d.ts
index 1d8b7c8..5745bfd 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/Plugin/typing.d.ts
@@ -19,20 +19,12 @@ declare namespace PluginComponent {
type Schema = '' | 'route' | 'consumer' | 'service';
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
type Meta = {
name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
+ priority: number;
+ schema: object;
+ type: string;
+ version: number;
+ consumer_schema?: object;
};
}
diff --git a/web/src/components/PluginOrchestration/DrawPluginStyle.ts
b/web/src/components/PluginOrchestration/DrawPluginStyle.ts
new file mode 100644
index 0000000..a1d40b2
--- /dev/null
+++ b/web/src/components/PluginOrchestration/DrawPluginStyle.ts
@@ -0,0 +1,82 @@
+/*
+ * 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 styled from 'styled-components';
+
+export const SOuter = styled.div`
+ padding: 30px;
+`;
+
+export const SInput = styled.input`
+ padding: 10px;
+ border: 1px solid cornflowerblue;
+ width: 100%;
+`;
+
+export const SMessage = styled.div`
+ margin: 10px;
+ padding: 10px;
+ line-height: 1.4em;
+`;
+
+export const SButton = styled.div`
+ padding: 10px 15px;
+ background: cornflowerblue;
+ color: white;
+ border-radius: 3px;
+ text-align: center;
+ transition: 0.3s ease all;
+ cursor: pointer;
+ &:hover {
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
+ }
+ &:active {
+ background: #5682d2;
+ }
+`;
+
+export const SPortDefaultOuter = styled.div`
+ width: 24px;
+ height: 24px;
+ background: cornflowerblue;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+export const SContent = styled.div`
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow: hidden;
+`;
+
+export const SSidebar = styled.div`
+ width: 300px;
+ background: white;
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+`;
+
+export const SPageContent = styled.div`
+ display: flex;
+ flex-direction: row;
+ flex: 1;
+ max-width: 100vw;
+ max-height: 100vh;
+`;
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/components/Page.tsx
similarity index 59%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/components/Page.tsx
index 1d8b7c8..2bdc3ff 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/components/Page.tsx
@@ -14,25 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
+import * as React from 'react';
+import { SPageContent } from '../DrawPluginStyle';
- type Schema = '' | 'route' | 'consumer' | 'service';
-
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
- type Meta = {
- name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
- };
-}
+export const Page: React.FC = (props) =>
<SPageContent>{props.children}</SPageContent>;
diff --git a/web/src/locales/en-US.ts
b/web/src/components/PluginOrchestration/components/SidebarItem.tsx
similarity index 55%
copy from web/src/locales/en-US.ts
copy to web/src/components/PluginOrchestration/components/SidebarItem.tsx
index 6ea6069..90be87d 100644
--- a/web/src/locales/en-US.ts
+++ b/web/src/components/PluginOrchestration/components/SidebarItem.tsx
@@ -14,26 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ActionBarEnUS } from '@/components/ActionBar';
+import * as React from 'react';
+import { INode, REACT_FLOW_CHART } from '@mrblenny/react-flow-chart';
+import { Button } from 'antd';
-import component from './en-US/component';
-import globalHeader from './en-US/globalHeader';
-import menu from './en-US/menu';
-import pwa from './en-US/pwa';
-import settingDrawer from './en-US/settingDrawer';
-import settings from './en-US/setting';
+import { SOuter } from '../DrawPluginStyle';
-export default {
- 'navBar.lang': 'Languages',
- 'layout.user.link.help': 'Help',
- 'layout.user.link.privacy': 'Privacy',
- 'layout.user.link.terms': 'Terms',
- 'app.preview.down.block': 'Download this page to your local project',
- ...globalHeader,
- ...menu,
- ...settingDrawer,
- ...settings,
- ...pwa,
- ...component,
- ...ActionBarEnUS,
+export interface ISidebarItemProps {
+ type: string;
+ ports: INode['ports'];
+ properties?: any;
+}
+
+export const SidebarItem: React.FC<ISidebarItemProps> = ({ type, ports,
properties }) => {
+ return (
+ <SOuter
+ draggable
+ onDragStart={(event: any) => {
+ event.dataTransfer.setData(REACT_FLOW_CHART, JSON.stringify({ type,
ports, properties }));
+ }}
+ style={{ padding: '5px' }}
+ >
+ <Button type="dashed">{type}</Button>
+ </SOuter>
+ );
};
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/components/index.ts
similarity index 59%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/components/index.ts
index 1d8b7c8..cb920f2 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/components/index.ts
@@ -14,25 +14,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
-
- type Schema = '' | 'route' | 'consumer' | 'service';
-
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
- type Meta = {
- name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
- };
-}
+export * from './Page';
+export * from './SidebarItem';
diff --git a/web/src/locales/en-US.ts
b/web/src/components/PluginOrchestration/constants.ts
similarity index 55%
copy from web/src/locales/en-US.ts
copy to web/src/components/PluginOrchestration/constants.ts
index 6ea6069..2a8c97c 100644
--- a/web/src/locales/en-US.ts
+++ b/web/src/components/PluginOrchestration/constants.ts
@@ -14,26 +14,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ActionBarEnUS } from '@/components/ActionBar';
+export const INIT_CHART = {
+ offset: { x: 0, y: 0 },
+ scale: 0.577,
+ nodes: {},
+ links: {},
+ selected: {},
+ hovered: {},
+};
-import component from './en-US/component';
-import globalHeader from './en-US/globalHeader';
-import menu from './en-US/menu';
-import pwa from './en-US/pwa';
-import settingDrawer from './en-US/settingDrawer';
-import settings from './en-US/setting';
+export const PLUGINS_PORTS = {
+ port1: {
+ id: 'port1',
+ type: 'input',
+ properties: {
+ custom: 'property',
+ },
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ properties: {
+ custom: 'property',
+ },
+ },
+};
-export default {
- 'navBar.lang': 'Languages',
- 'layout.user.link.help': 'Help',
- 'layout.user.link.privacy': 'Privacy',
- 'layout.user.link.terms': 'Terms',
- 'app.preview.down.block': 'Download this page to your local project',
- ...globalHeader,
- ...menu,
- ...settingDrawer,
- ...settings,
- ...pwa,
- ...component,
- ...ActionBarEnUS,
+export const CONDITION_PORTS = {
+ port1: {
+ id: 'port1',
+ type: 'input',
+ },
+ port2: {
+ id: 'port2',
+ type: 'output',
+ properties: {
+ value: 'no',
+ },
+ },
+ port3: {
+ id: 'port3',
+ type: 'output',
+ properties: {
+ value: 'yes',
+ },
+ },
};
diff --git a/web/src/components/PluginOrchestration/customConfig.tsx
b/web/src/components/PluginOrchestration/customConfig.tsx
new file mode 100644
index 0000000..1d09f0b
--- /dev/null
+++ b/web/src/components/PluginOrchestration/customConfig.tsx
@@ -0,0 +1,71 @@
+/*
+ * 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 React from 'react';
+import { useIntl } from 'umi';
+import { INodeInnerDefaultProps, IPortDefaultProps } from
'@mrblenny/react-flow-chart';
+
+import { SOuter, SPortDefaultOuter } from './DrawPluginStyle';
+import { PanelType } from './index';
+
+export const NodeInnerCustom = ({ node }: INodeInnerDefaultProps) => {
+ const { formatMessage } = useIntl();
+ const { customData } = node.properties;
+ if (customData.type === PanelType.Condition) {
+ return (
+ <SOuter>
+ <p>{formatMessage({ id: "page.panel.condition.name"
})}:{customData.name || `(${formatMessage({ id: 'page.panel.condition.tips'
})})`}</p>
+ </SOuter>
+ );
+ }
+
+ if (customData.type === PanelType.Plugin) {
+ return (
+ <SOuter>
+ <p>{formatMessage({ id: "page.panel.plugin.name" })}: {customData.name
|| `(${formatMessage({ id: 'page.panel.plugin.tips' })})`}</p>
+ </SOuter>
+ );
+ }
+
+ return (
+ <SOuter>
+ <br />
+ </SOuter>
+ );
+};
+
+export const PortCustom = (props: IPortDefaultProps) => (
+ <SPortDefaultOuter>
+ {props.port.properties && props.port.properties.value === 'yes' && (
+ <svg style={{ width: '24px', height: '24px' }} viewBox="0 0 24 24">
+ <path fill="white"
d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z" />
+ </svg>
+ )}
+ {props.port.properties && props.port.properties.value === 'no' && (
+ <svg style={{ width: '24px', height: '24px' }} viewBox="0 0 24 24">
+ <path
+ fill="white"
+
d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
+ />
+ </svg>
+ )}
+ {!props.port.properties && (
+ <svg style={{ width: '24px', height: '24px' }} viewBox="0 0 24 24">
+ <path fill="white"
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" />
+ </svg>
+ )}
+ </SPortDefaultOuter>
+);
diff --git a/web/src/components/PluginOrchestration/index.tsx
b/web/src/components/PluginOrchestration/index.tsx
new file mode 100644
index 0000000..37607e6
--- /dev/null
+++ b/web/src/components/PluginOrchestration/index.tsx
@@ -0,0 +1,277 @@
+/*
+ * 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 React, { Fragment, useState, useEffect } from 'react';
+import { cloneDeep } from 'lodash';
+import { FlowChart, IFlowChartCallbacks } from '@mrblenny/react-flow-chart';
+import * as actions from '@mrblenny/react-flow-chart/src/container/actions';
+import { Form, Input, Button, Divider, Card, Select } from 'antd';
+import { withTheme } from '@rjsf/core';
+import { useIntl } from 'umi'
+
+// @ts-ignore
+import { Theme as AntDTheme } from '@rjsf/antd';
+
+import { Page, SidebarItem } from './components';
+import { INIT_CHART, PLUGINS_PORTS, CONDITION_PORTS } from './constants';
+import { SMessage, SContent, SSidebar } from './DrawPluginStyle';
+import { PortCustom, NodeInnerCustom } from './customConfig';
+import { fetchList } from './service';
+import { PluginOrchestrationModule } from './typing';
+
+export * from './transform';
+
+export enum PanelType {
+ Plugin,
+ Condition,
+ Default,
+}
+
+type Props = {
+ data: any;
+ onChange(data: object): void;
+ readonly: boolean;
+};
+
+const PluginForm = withTheme(AntDTheme);
+
+const LAYOUT = {
+ labelCol: { span: 8 },
+ wrapperCol: { span: 16 },
+};
+const TAIL_LAYOUT = {
+ wrapperCol: { offset: 8, span: 16 },
+};
+
+const SelectedSidebar: React.FC<Props> = ({ data = {}, onChange, readonly =
false }) => {
+ const [form] = Form.useForm();
+ const [chart, setChart] = useState(cloneDeep(Object.keys(data).length ? data
: INIT_CHART));
+ const [schema, setSchema] = useState<any>();
+ const [selectedType, setSelectedType] =
useState<PanelType>(PanelType.Default);
+ const [pluginList, setPluginList] =
useState<PluginOrchestrationModule.Meta[]>([]);
+ const [pluginCategory, setPluginCategory] = useState('All');
+ const [showList, setShowList] = useState<string[]>();
+ const [typeList, setTypeList] = useState<string[]>([]);
+
+ const { formatMessage } = useIntl();
+
+ const getCustomDataById = (id = chart.selected.id) => {
+ if (!id || !chart.nodes[id].properties) {
+ return {};
+ }
+ return chart.nodes[id].properties.customData;
+ };
+
+ const stateActionCallbacks = Object.keys(actions).reduce((obj, key) => {
+ const clonedObj = cloneDeep(obj);
+ clonedObj[key] = (...args: any) => {
+ const action = actions[key];
+ const newChartTransformer = action(...args);
+ const newChart = newChartTransformer(chart);
+ if (
+ ['onLinkMouseEnter', 'onLinkMouseLeave', 'onNodeMouseEnter',
'onNodeMouseLeave'].includes(
+ key,
+ )
+ ) {
+ return newChart;
+ }
+
+ if (key === 'onDragCanvasStop') {
+ setSelectedType(PanelType.Default);
+ return newChart;
+ }
+
+ setChart({ ...chart, ...newChart });
+ if (['onCanvasDrop', 'onNodeClick'].includes(key)) {
+ const { type, name } = getCustomDataById(args.nodeId);
+ setSelectedType(type);
+ if (type === PanelType.Plugin && name) {
+ const plugin = pluginList.find(item => item.name === name);
+ if (plugin) {
+ setSchema(plugin.schema);
+ }
+ }
+ }
+ onChange(newChart);
+ return newChart;
+ };
+ return clonedObj;
+ }, {}) as IFlowChartCallbacks;
+
+ const firstUpperCase = ([first, ...rest]: string) => first.toUpperCase() +
rest.join("");
+ useEffect(() => {
+ // eslint-disable-next-line no-shadow
+ fetchList().then((data) => {
+ const categoryList: string[] = [];
+ data.forEach(item => {
+ if (!categoryList.includes(firstUpperCase(item.type))) {
+ categoryList.push(firstUpperCase(item.type));
+ }
+ });
+ setTypeList(['All', ...categoryList.sort()]);
+ setPluginList(data);
+ setShowList(data.map(item => item.name).sort());
+ });
+ }, []);
+
+ const renderSidebar = () => {
+ if (selectedType === PanelType.Condition) {
+ form.setFieldsValue({ condition: getCustomDataById().name });
+ return (
+ <SMessage>
+ <Form
+ {...LAYOUT}
+ name="basic"
+ form={form}
+ onFinish={(values) => {
+ const clonedChart = cloneDeep(chart);
+ clonedChart.nodes[chart.selected.id!].properties.customData.name
= values.condition;
+ setChart(clonedChart);
+ onChange(clonedChart);
+ setSelectedType(PanelType.Default);
+ }}
+ >
+ <Form.Item
+ label={formatMessage({ id:
'page.siderBar.form.label.panelType.condition' })}
+ name="condition"
+ rules={[{ required: true, message: formatMessage({ id:
'page.siderBar.form.rule.panelType.condition' }) }]}
+ >
+ <Input />
+ </Form.Item>
+ <Form.Item {...TAIL_LAYOUT}>
+ <Button type="primary" htmlType="submit">
+ {formatMessage({ id: "page.siderBar.button.submit" })}
+ </Button>
+ </Form.Item>
+ </Form>
+ </SMessage>
+ );
+ }
+ if (selectedType === PanelType.Plugin && schema) {
+ return (
+ <SMessage style={{ overflow: 'scroll' }}>
+ <PluginForm
+ schema={schema}
+ liveValidate
+ formData={getCustomDataById().data || {}}
+ showErrorList={false}
+ onSubmit={({ formData }) => {
+ const clonedChart = cloneDeep(chart);
+ clonedChart.nodes[chart.selected.id!].properties.customData.data
= formData;
+ setChart(clonedChart);
+ onChange(clonedChart);
+ setSelectedType(PanelType.Default);
+ }}
+ >
+ {/* NOTE: Leave blank to hide the Submit button */}
+ <Fragment />
+
+ <Button type="primary" htmlType="submit">
+ {formatMessage({ id: "page.siderBar.button.submit" })}
+ </Button>
+ </PluginForm>
+ </SMessage>
+ );
+ }
+
+ return (
+ <SSidebar>
+ <SMessage style={{ fontSize: '16px', fontWeight: 'bold'
}}>{formatMessage({ id: 'page.siderBar.tips' })}</SMessage>
+ <Divider style={{ margin: '0px' }} />
+ <SidebarItem
+ type={formatMessage({ id:
'page.siderBar.form.label.panelType.condition' })}
+ ports={CONDITION_PORTS}
+ properties={{
+ customData: {
+ type: PanelType.Condition,
+ },
+ }}
+ />
+ <Divider orientation="left">{formatMessage({ id:
'page.siderBar.plugin' })}</Divider>
+ <Select
+ showSearch
+ placeholder={formatMessage({ id:
"page.siderBar.form.label.panelType.plugin" })}
+ optionFilterProp="children"
+ defaultValue={pluginCategory}
+ onChange={(value) => {
+ setPluginCategory(value);
+ if (value === 'All') {
+ setShowList(pluginList.map(item => item.name).sort());
+ return;
+ }
+ setShowList(pluginList.filter(item => item.type ===
value.toLowerCase()).map(item => item.name).sort());
+ }}
+ filterOption={(input, option) =>
+ option?.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+ }
+ >
+ {typeList.map((item) => (
+ <Select.Option value={item}>{item}</Select.Option>
+ ))}
+ </Select>
+ <Card size="small" title={pluginCategory} style={{ height: 'unset' }}>
+ <div
+ style={{
+ overflowY: 'scroll',
+ height: '500px',
+ display: 'flex',
+ flexWrap: 'wrap',
+ alignContent: 'flex-start',
+ }}
+ >
+ {showList &&
+ showList.map((name) => {
+ return (
+ <SidebarItem
+ key={name}
+ type={name}
+ ports={PLUGINS_PORTS}
+ properties={{
+ customData: {
+ type: PanelType.Plugin,
+ name,
+ },
+ }}
+ />
+ );
+ })}
+ </div>
+ </Card>
+ </SSidebar>
+ );
+ };
+ return (
+ <Page>
+ <SContent>
+ <FlowChart
+ chart={chart}
+ callbacks={stateActionCallbacks}
+ config={{
+ zoom: { wheel: { disabled: true } },
+ readonly,
+ }}
+ Components={{
+ Port: PortCustom,
+ NodeInner: NodeInnerCustom,
+ }}
+ />
+ </SContent>
+ {Boolean(!readonly) && <SSidebar>{renderSidebar()}</SSidebar>}
+ </Page>
+ );
+};
+
+export default SelectedSidebar;
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/locales/en-US.ts
similarity index 57%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/locales/en-US.ts
index 1d8b7c8..08ba06a 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/locales/en-US.ts
@@ -14,25 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
+export default {
+ 'page.siderBar.form.label.panelType.condition': 'Condition',
+ 'page.siderBar.form.rule.panelType.condition': 'Please enter the condition
of judgment',
+ 'page.siderBar.form.label.panelType.plugin': 'Plugin Category',
- type Schema = '' | 'route' | 'consumer' | 'service';
+ 'page.siderBar.button.submit': 'Save',
+ 'page.siderBar.plugin': 'Plugin',
+ 'page.siderBar.tips': 'Drag the required components to the panel',
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
- type Meta = {
- name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
- };
-}
+ 'page.panel.condition.tips': 'Click here to configure',
+ 'page.panel.condition.name': 'Condition',
+ 'page.panel.plugin.tips': 'Click to configure the plugin',
+ 'page.panel.plugin.name': 'Plugin Name',
+};
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/locales/zh-CN.ts
similarity index 59%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/locales/zh-CN.ts
index 1d8b7c8..1f1cbe3 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/locales/zh-CN.ts
@@ -14,25 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
+export default {
+ 'page.siderBar.form.label.panelType.condition': '判断条件',
+ 'page.siderBar.form.rule.panelType.condition': '请输入判断条件',
+ 'page.siderBar.form.label.panelType.plugin': '插件分类',
- type Schema = '' | 'route' | 'consumer' | 'service';
+ 'page.siderBar.button.submit': '保存',
+ 'page.siderBar.plugin': '插件',
+ 'page.siderBar.tips': '拖动所需组件至面板',
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
- type Meta = {
- name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
- };
-}
+ 'page.panel.condition.tips': '点击配置判断条件',
+ 'page.panel.condition.name': '判断条件',
+ 'page.panel.plugin.tips': '点击配置插件',
+ 'page.panel.plugin.name': '插件名称',
+};
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/service.ts
similarity index 59%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/service.ts
index 1d8b7c8..e7c62ad 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/service.ts
@@ -14,25 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
+import { request } from 'umi';
- type Schema = '' | 'route' | 'consumer' | 'service';
+import { PluginOrchestrationModule } from './typing';
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
- type Meta = {
- name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
- };
-}
+export const fetchList = () => {
+ return
request<Res<PluginOrchestrationModule.Meta[]>>('/plugins?all=true').then(data
=> {
+ return data.data;
+ })
+};
diff --git a/web/src/components/PluginOrchestration/transform.ts
b/web/src/components/PluginOrchestration/transform.ts
new file mode 100644
index 0000000..658d009
--- /dev/null
+++ b/web/src/components/PluginOrchestration/transform.ts
@@ -0,0 +1,122 @@
+/*
+ * 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 { PanelType } from '.';
+
+export const transformer = (chart: any) => {
+ const rule: any = {};
+ const conf: any = {};
+
+ const { links } = chart;
+
+ const findStartNode = () => {
+ const nodeIdFormArr: string[] = [];
+ const nodeIdToArr: string[] = [];
+ Object.keys(links).forEach((key) => {
+ const item = links[key];
+ nodeIdFormArr.push(item.from.nodeId);
+ nodeIdToArr.push(item.to.nodeId);
+ });
+ return nodeIdFormArr.filter((item) => !nodeIdToArr.includes(item))[0];
+ };
+
+ const findLinkId = (type: string, nodeId: string, port?: string) => {
+ let returnId;
+
+ Object.keys(links).forEach((key) => {
+ const item = links[key];
+ // condition
+ if (port) {
+ if (port === item[type].portId && item[type].nodeId === nodeId) {
+ returnId = key;
+ }
+ return;
+ }
+ // plugin
+ if (nodeId === item[type].nodeId) {
+ returnId = key;
+ }
+ });
+
+ return returnId;
+ };
+
+ const processRule = (id: string) => {
+ if (!chart.nodes[id]) return;
+
+ const link = findLinkId('from', id);
+ if (!link) return;
+
+ const nextNodeId = links[link].to.nodeId;
+
+ const nextNodeType = chart.nodes[nextNodeId].properties.customData.type;
+
+ if (nextNodeType === PanelType.Plugin) {
+ rule[id] = [['', nextNodeId]];
+ processRule(nextNodeId);
+ }
+
+ if (nextNodeType === PanelType.Condition) {
+ let truePortId;
+ let falsePortId;
+ const { ports } = chart.nodes[nextNodeId];
+ Object.keys(ports).forEach((key) => {
+ const item = ports[key];
+ if (item.properties) {
+ if (item.properties.value === 'yes') {
+ truePortId = item.id;
+ }
+ if (item.properties.value === 'no') {
+ falsePortId = item.id;
+ }
+ }
+ });
+ const trueLinkId = findLinkId('from', nextNodeId, truePortId);
+ const falseLinkId = findLinkId('from', nextNodeId, falsePortId);
+ const nextTrueNode = trueLinkId ? links[trueLinkId].to.nodeId :
undefined;
+ const nextFalseNode = falseLinkId ? links[falseLinkId].to.nodeId :
undefined;
+
+ rule[id] = [];
+ if (nextTrueNode) {
+ rule[id][0] = [chart.nodes[nextNodeId].properties.customData.name,
nextTrueNode];
+ processRule(nextTrueNode);
+ }
+
+ if (nextFalseNode) {
+ rule[id][1] = ['', nextFalseNode];
+ processRule(nextFalseNode);
+ }
+ }
+ };
+
+ const startId = findStartNode();
+ rule.root = startId;
+
+ processRule(startId);
+
+ // handle conf
+ Object.keys(chart.nodes).forEach((key) => {
+ const item = chart.nodes[key];
+ if (item.properties.customData && item.properties.customData.type === 0) {
+ conf[key] = {
+ name: item.properties.customData.name,
+ conf: item.properties.customData.data,
+ };
+ }
+ });
+
+ return { rule, conf };
+};
diff --git a/web/src/components/Plugin/typing.d.ts
b/web/src/components/PluginOrchestration/typing.d.ts
similarity index 62%
copy from web/src/components/Plugin/typing.d.ts
copy to web/src/components/PluginOrchestration/typing.d.ts
index 1d8b7c8..c9e909a 100644
--- a/web/src/components/Plugin/typing.d.ts
+++ b/web/src/components/PluginOrchestration/typing.d.ts
@@ -14,25 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-declare namespace PluginComponent {
- type Data = object;
-
- type Schema = '' | 'route' | 'consumer' | 'service';
-
- type Category =
- | 'Security'
- | 'Limit traffic'
- | 'Log'
- | 'Observability'
- | 'Other'
- | 'Authentication';
-
+export declare namespace PluginOrchestrationModule {
type Meta = {
name: string;
- category: Category;
- hidden?: boolean;
- // Note: Plugins are sorted by priority under the same category in the
frontend, the smaller the number, the higher the priority. The default value is
9999.
- priority?: number;
- avatar?: React.ReactNode;
+ priority: number;
+ schema: object;
+ type: string;
+ version: number;
+ consumer_schema?: object;
};
}
diff --git a/web/src/locales/en-US.ts b/web/src/locales/en-US.ts
index 6ea6069..04c515f 100644
--- a/web/src/locales/en-US.ts
+++ b/web/src/locales/en-US.ts
@@ -22,6 +22,7 @@ import menu from './en-US/menu';
import pwa from './en-US/pwa';
import settingDrawer from './en-US/settingDrawer';
import settings from './en-US/setting';
+import PluginOrchestration from
'../components/PluginOrchestration/locales/en-US';
export default {
'navBar.lang': 'Languages',
@@ -36,4 +37,5 @@ export default {
...pwa,
...component,
...ActionBarEnUS,
+ ...PluginOrchestration
};
diff --git a/web/src/locales/zh-CN.ts b/web/src/locales/zh-CN.ts
index 3eb93eb..e04343a 100644
--- a/web/src/locales/zh-CN.ts
+++ b/web/src/locales/zh-CN.ts
@@ -22,6 +22,7 @@ import menu from './zh-CN/menu';
import pwa from './zh-CN/pwa';
import settingDrawer from './zh-CN/settingDrawer';
import settings from './zh-CN/setting';
+import PluginOrchestration from
'../components/PluginOrchestration/locales/zh-CN';
export default {
'navBar.lang': '语言',
@@ -36,4 +37,5 @@ export default {
...pwa,
...component,
...ActionBarZhCN,
+ ...PluginOrchestration
};
diff --git a/web/src/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx
index 41cadae..8bd2d69 100644
--- a/web/src/pages/Route/Create.tsx
+++ b/web/src/pages/Route/Create.tsx
@@ -18,11 +18,11 @@ import React, { useState, useEffect, useRef } from 'react';
import { Card, Steps, Form } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { history, useIntl } from 'umi';
-import { transformer as chartTransformer } from '@api7-dashboard/pluginchart';
import ActionBar from '@/components/ActionBar';
import { DEFAULT_UPSTREAM } from '@/components/Upstream';
+import { transformer as chartTransformer } from
'@/components/PluginOrchestration';
import { create, fetchItem, update, checkUniqueName, checkHostWithSSL } from
'./service';
import Step1 from './components/Step1';
import Step2 from './components/Step2';
@@ -256,11 +256,10 @@ const Page: React.FC<Props> = (props) => {
return (
<>
<PageHeaderWrapper
- title={`${
- (props as any).match.params.rid
+ title={`${(props as any).match.params.rid
? formatMessage({ id: 'component.global.edit' })
: formatMessage({ id: 'component.global.create' })
- } ${formatMessage({ id: 'menu.routes' })}`}
+ } ${formatMessage({ id: 'menu.routes' })}`}
>
<Card bordered={false}>
<Steps current={step - 1} className={styles.steps}>
diff --git a/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
b/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
index 16ba096..c2b28c9 100644
--- a/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
+++ b/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
@@ -17,8 +17,8 @@
import React from 'react';
import { FormInstance } from 'antd/lib/form';
import { useIntl } from 'umi';
-import PluginOrchestration from '@api7-dashboard/pluginchart';
+import PluginOrchestration from '@/components/PluginOrchestration';
import PluginPage from '@/components/Plugin';
import Step1 from '../Step1';
import Step2 from '../Step2';
@@ -58,7 +58,7 @@ const CreateStep4: React.FC<Props> = ({ form1, form2,
redirect, upstreamRef, ...
<PluginPage initialData={rest.step3Data.plugins} readonly />
)}
{Boolean(Object.keys(script).length !== 0) && (
- <PluginOrchestration data={rest.step3Data.script.chart} readonly
onChange={() => {}} />
+ <PluginOrchestration data={rest.step3Data.script.chart} readonly
onChange={() => { }} />
)}
</>
)}
diff --git a/web/src/pages/Route/components/Step3/index.tsx
b/web/src/pages/Route/components/Step3/index.tsx
index 3eb30b8..df9a3e3 100644
--- a/web/src/pages/Route/components/Step3/index.tsx
+++ b/web/src/pages/Route/components/Step3/index.tsx
@@ -19,7 +19,7 @@ import { Radio, Tooltip } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import { isChrome } from 'react-device-detect';
-import PluginOrchestration from '@api7-dashboard/pluginchart';
+import PluginOrchestration from '@/components/PluginOrchestration';
import PluginPage from '@/components/Plugin';
type Props = {
diff --git a/web/yarn.lock b/web/yarn.lock
index f5727a2..93ed598 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -187,32 +187,6 @@
lodash "^4.17.15"
resize-observer-polyfill "^1.5.0"
-"@api7-dashboard/plugin@^1.0.6":
- version "1.0.6"
- resolved
"https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.6.tgz#d17bd53da9151c0fee2d5591965b642bc7e1005d"
- integrity
sha512-xqrhPc5/HVIf9jBr5SNfwyfTFvXWfUn9rt2mHepDUCFRlE3WrMhorv2u+Zsk33LcgxG2uNvJrFA1T9Zhfl2hNA==
- dependencies:
- "@rjsf/antd" "^2.3.0"
- "@rjsf/core" "^2.3.0"
- "@uiw/react-codemirror" "^3.0.1"
- ajv "^6.12.5"
- json-schema "^0.2.5"
- set-value "^3.0.2"
-
-"@api7-dashboard/pluginchart@^1.0.14":
- version "1.0.14"
- resolved
"https://registry.yarnpkg.com/@api7-dashboard/pluginchart/-/pluginchart-1.0.14.tgz#3147b7e89cdcb541ed1559970b77cc443a3ef15d"
- integrity
sha512-LD5uhQgDwmsWkK7PfMl1XZulIU7klpFvBDHTrXIvJ23xwSPnT6tYlNS256O1QyVMuQaKRRH/lqbtPPop+Sgg1w==
- dependencies:
- "@ant-design/icons" "^4.2.2"
- "@api7-dashboard/plugin" "^1.0.6"
- "@mrblenny/react-flow-chart" "^0.0.14"
- "@rjsf/antd" "^2.3.0"
- "@rjsf/core" "^2.3.0"
- json-schema "^0.2.5"
- lodash "^4.17.20"
- styled-components "^5.1.1"
-
"@api7-dashboard/ui@^1.0.3":
version "1.0.3"
resolved
"https://registry.yarnpkg.com/@api7-dashboard/ui/-/ui-1.0.3.tgz#77011750bebee7bb6f6966ea0596c5576951e3ff"
@@ -1920,8 +1894,8 @@
"@mrblenny/react-flow-chart@^0.0.14":
version "0.0.14"
- resolved
"https://registry.npm.taobao.org/@mrblenny/react-flow-chart/download/@mrblenny/react-flow-chart-0.0.14.tgz#be11d06345c7222b41f488b38011b109e48a04b3"
- integrity sha1-vhHQY0XHIitB9IizgBGxCeSKBLM=
+ resolved
"https://registry.yarnpkg.com/@mrblenny/react-flow-chart/-/react-flow-chart-0.0.14.tgz#be11d06345c7222b41f488b38011b109e48a04b3"
+ integrity
sha512-3bFjlmlYuqHpCRCPoA59jok2Vhe59ZKT5g9lb6U5IM+Zk2fIsKmXp8LEcliW0TrHtNMtZw5Gm3/rScrg/DwAFQ==
dependencies:
pathfinding "^0.4.18"
react-draggable "^4.4.3"
@@ -1988,11 +1962,6 @@
resolved
"https://registry.npm.taobao.org/@rjsf/antd/download/@rjsf/antd-2.2.0.tgz#0a92d67c877bf080008c187ea714de640e58425d"
integrity sha1-CpLWfId78IAAjBh+pxTeZA5YQl0=
-"@rjsf/antd@^2.3.0":
- version "2.3.0"
- resolved
"https://registry.npm.taobao.org/@rjsf/antd/download/@rjsf/antd-2.3.0.tgz#c6df9ce0a5fd2cffe14d5ebcedafdaade4674272"
- integrity sha1-xt+c4KX9LP/hTV687a/areRnQnI=
-
"@rjsf/[email protected]":
version "2.2.0"
resolved
"https://registry.npm.taobao.org/@rjsf/core/download/@rjsf/core-2.2.0.tgz#bedb20c51984769c0afe5c1932414a7f65f7cf31"
@@ -2010,23 +1979,6 @@
react-is "^16.9.0"
shortid "^2.2.14"
-"@rjsf/core@^2.3.0":
- version "2.3.0"
- resolved
"https://registry.npm.taobao.org/@rjsf/core/download/@rjsf/core-2.3.0.tgz#334c73d2262ef1a8cda477e238067af7336c5599"
- integrity sha1-M0xz0iYu8ajNpHfiOAZ69zNsVZk=
- dependencies:
- "@babel/runtime-corejs2" "^7.8.7"
- "@types/json-schema" "^7.0.4"
- ajv "^6.7.0"
- core-js "^2.5.7"
- json-schema-merge-allof "^0.6.0"
- jsonpointer "^4.0.1"
- lodash "^4.17.15"
- prop-types "^15.7.2"
- react-app-polyfill "^1.0.4"
- react-is "^16.9.0"
- shortid "^2.2.14"
-
"@samverschueren/stream-to-observable@^0.3.0":
version "0.3.1"
resolved
"https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301"
@@ -2364,7 +2316,7 @@
resolved
"https://registry.npm.taobao.org/@types/history/download/@types/history-4.7.7.tgz#613957d900fab9ff84c8dfb24fa3eef0c2a40896"
integrity sha1-YTlX2QD6uf+EyN+yT6Pu8MKkCJY=
-"@types/hoist-non-react-statics@^3.3.0",
"@types/hoist-non-react-statics@^3.3.1":
+"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0",
"@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved
"https://registry.npm.taobao.org/@types/hoist-non-react-statics/download/@types/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha1-ESSq/lEYy1kZd66xzqrtEHDrA58=
@@ -2728,6 +2680,15 @@
resolved
"https://registry.npm.taobao.org/@types/stack-utils/download/@types/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha1-CoUdO9lkmPolwzq3J47TvWXwbD4=
+"@types/styled-components@^5.1.7":
+ version "5.1.7"
+ resolved
"https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.7.tgz#3cd10b088c1cb1acde2e4b166b3e8275a3083710"
+ integrity
sha512-BJzPhFygYspyefAGFZTZ/8lCEY4Tk+Iqktvnko3xmJf9LrLqs3+grxPeU3O0zLl6yjbYBopD0/VikbHgXDbJtA==
+ dependencies:
+ "@types/hoist-non-react-statics" "*"
+ "@types/react" "*"
+ csstype "^3.0.2"
+
"@types/tapable@*", "@types/[email protected]":
version "1.0.6"
resolved
"https://registry.npm.taobao.org/@types/tapable/download/@types/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
@@ -3887,16 +3848,6 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2,
ajv@^6.12.3, ajv@^6.7.0, ajv@
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-ajv@^6.12.5:
- version "6.12.5"
- resolved
"https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
- integrity
sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
- dependencies:
- fast-deep-equal "^3.1.1"
- fast-json-stable-stringify "^2.0.0"
- json-schema-traverse "^0.4.1"
- uri-js "^4.2.2"
-
ajv@^7.0.0-rc.2:
version "7.0.0-rc.2"
resolved
"https://registry.yarnpkg.com/ajv/-/ajv-7.0.0-rc.2.tgz#9c237b95072c1ee8c38e2df76422f37bacc9ae5e"
@@ -10504,11 +10455,6 @@ [email protected]:
resolved
"https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
-json-schema@^0.2.5:
- version "0.2.5"
- resolved
"https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.5.tgz#97997f50972dd0500214e208c407efa4b5d7063b"
- integrity sha1-l5l/UJct0FACFOIIxAfvpLXXBjs=
-
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved
"https://registry.npm.taobao.org/json-stable-stringify-without-jsonify/download/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@@ -15729,7 +15675,7 @@ set-blocking@^2.0.0:
resolved
"https://registry.npm.taobao.org/set-blocking/download/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
[email protected], set-value@^3.0.2:
[email protected]:
version "3.0.2"
resolved
"https://registry.npm.taobao.org/set-value/download/set-value-3.0.2.tgz#74e8ecd023c33d0f77199d415409a40f21e61b90"
integrity sha1-dOjs0CPDPQ93GZ1BVAmkDyHmG5A=
@@ -16576,10 +16522,10 @@ styled-components@^4.4.0:
stylis-rule-sheet "^0.0.10"
supports-color "^5.5.0"
-styled-components@^5.1.1:
- version "5.1.1"
- resolved
"https://registry.npm.taobao.org/styled-components/download/styled-components-5.1.1.tgz#96dfb02a8025794960863b9e8e365e3b6be5518d"
- integrity sha1-lt+wKoAleUlghjuejjZeO2vlUY0=
+styled-components@^5.2.1:
+ version "5.2.1"
+ resolved
"https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a"
+ integrity
sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/traverse" "^7.4.5"