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 053721c  fix(Plugin): use codemirror instead of form in plugin module 
(#898)
053721c is described below

commit 053721cc483c6ffc21981b1d6a70cfce07aa808b
Author: 琚致远 <[email protected]>
AuthorDate: Thu Dec 10 13:55:27 2020 +0800

    fix(Plugin): use codemirror instead of form in plugin module (#898)
    
    * feat(plugin): added code mirror
    
    * chore: format codes
    
    * feat(Plugin): remove a7 plugin
    
    * feat(Plugin): remove @api7-dashboard/plugin
    
    * feat: added codes format
    
    * feat: use local icon files
    
    * feat: update ASF Release cfg
    
    * feat: update ASF Release cfg
---
 .actions/ASF-Release.cfg                           |   1 +
 web/config/config.ts                               |   2 +-
 web/package.json                                   |   4 +-
 web/src/app.tsx                                    |   2 +
 .../IconFont/IconFont.tsx}                         |  19 ++-
 .../{iconfont.ts => components/IconFont/index.ts}  |   9 +-
 web/src/components/Plugin/CodeMirrorDrawer.tsx     | 142 ++++++++++++++++
 web/src/components/Plugin/PluginPage.tsx           | 182 +++++++++++++++++++++
 web/src/components/Plugin/data.tsx                 | 166 +++++++++++++++++++
 .../{iconfont.ts => components/Plugin/index.ts}    |  10 +-
 web/src/components/Plugin/service.ts               |  98 +++++++++++
 .../Preview.tsx => components/Plugin/typing.d.ts}  |  37 +++--
 web/src/e2e/Login.e2e.js                           |   7 +-
 web/src/e2e/Logout.e2e.js                          |   9 +-
 web/src/e2e/service.js                             |   2 +-
 web/src/global.less                                |  12 ++
 web/src/helpers.tsx                                |  20 +--
 web/src/libs/iconfont.js                           |  85 ++++++++++
 web/src/pages/Consumer/Create.tsx                  |  21 +--
 web/src/pages/Consumer/components/Preview.tsx      |   4 +-
 web/src/pages/Route/Create.tsx                     |   5 +-
 .../Route/components/CreateStep4/CreateStep4.tsx   |   2 +-
 .../Route/components/Step1/MatchingRulesView.tsx   |   2 +-
 .../Route/components/Step1/RequestConfigView.tsx   |   2 +-
 web/src/pages/Route/components/Step3/index.tsx     |  68 ++++----
 web/yarn.lock                                      |  90 ++++++++--
 26 files changed, 872 insertions(+), 129 deletions(-)

diff --git a/.actions/ASF-Release.cfg b/.actions/ASF-Release.cfg
index bed65bf..e2828f7 100644
--- a/.actions/ASF-Release.cfg
+++ b/.actions/ASF-Release.cfg
@@ -78,6 +78,7 @@ web/src/e2e/__mocks__/antd-pro-merge-less.js
 web/src/e2e/baseLayout.e2e.js
 web/src/pages/404.tsx
 web/src/service-worker.js
+web/src/libs/iconfont.js
 api/build-tools/json.lua
 
 # Skip files containing Apache 2.0 License
diff --git a/web/config/config.ts b/web/config/config.ts
index f71d512..024b223 100644
--- a/web/config/config.ts
+++ b/web/config/config.ts
@@ -61,5 +61,5 @@ export default defineConfig({
   manifest: {
     basePath: '/',
   },
-  outputPath: '../output/html'
+  outputPath: '../output/html',
 });
diff --git a/web/package.json b/web/package.json
index d2b53bf..3f088fd 100644
--- a/web/package.json
+++ b/web/package.json
@@ -55,14 +55,15 @@
     "@ant-design/icons": "^4.0.0",
     "@ant-design/pro-layout": "^6.0.0",
     "@ant-design/pro-table": "2.6.3",
-    "@api7-dashboard/plugin": "^1.0.15",
     "@api7-dashboard/pluginchart": "^1.0.14",
     "@api7-dashboard/ui": "^1.0.3",
     "@rjsf/antd": "2.2.0",
     "@rjsf/core": "2.2.0",
+    "@uiw/react-codemirror": "^3.0.1",
     "antd": "^4.4.0",
     "classnames": "^2.2.6",
     "dayjs": "1.8.28",
+    "js-beautify": "^1.13.0",
     "json-schema": "0.2.5",
     "lodash": "^4.17.11",
     "moment": "^2.25.3",
@@ -87,6 +88,7 @@
     "@types/express": "^4.17.0",
     "@types/history": "^4.7.2",
     "@types/jest": "^26.0.0",
+    "@types/js-beautify": "^1.13.1",
     "@types/lodash": "^4.14.144",
     "@types/node-forge": "0.9.3",
     "@types/qs": "^6.5.3",
diff --git a/web/src/app.tsx b/web/src/app.tsx
index 41921f1..8e67c87 100644
--- a/web/src/app.tsx
+++ b/web/src/app.tsx
@@ -26,6 +26,8 @@ import RightContent from '@/components/RightContent';
 import Footer from '@/components/Footer';
 import { queryCurrent } from '@/services/user';
 import { getMenuData, errorHandler } from '@/helpers';
+
+import './libs/iconfont';
 import defaultSettings from '../config/defaultSettings';
 
 export async function getInitialState(): Promise<{
diff --git a/web/src/iconfont.ts b/web/src/components/IconFont/IconFont.tsx
similarity index 74%
copy from web/src/iconfont.ts
copy to web/src/components/IconFont/IconFont.tsx
index df1b507..540ddfc 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/IconFont/IconFont.tsx
@@ -14,11 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { createFromIconfontCN } from '@ant-design/icons';
+import React from 'react';
 
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
+type Props = {
+  name: string;
+};
+
+/**
+ * Icon Font
+ * https://www.iconfont.cn/help/detail?helptype=code
+ */
+const IconFont: React.FC<Props> = ({ name }) => (
+  <svg className="icon" aria-hidden="true">
+    <use xlinkHref={`#${name}`} />
+  </svg>
+);
 
 export default IconFont;
diff --git a/web/src/iconfont.ts b/web/src/components/IconFont/index.ts
similarity index 73%
copy from web/src/iconfont.ts
copy to web/src/components/IconFont/index.ts
index df1b507..a020929 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/IconFont/index.ts
@@ -14,11 +14,4 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { createFromIconfontCN } from '@ant-design/icons';
-
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
-
-export default IconFont;
+export { default } from './IconFont';
diff --git a/web/src/components/Plugin/CodeMirrorDrawer.tsx 
b/web/src/components/Plugin/CodeMirrorDrawer.tsx
new file mode 100644
index 0000000..95ef951
--- /dev/null
+++ b/web/src/components/Plugin/CodeMirrorDrawer.tsx
@@ -0,0 +1,142 @@
+/*
+ * 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, { useRef } from 'react';
+import { Drawer, Button, notification, PageHeader } from 'antd';
+import CodeMirror from '@uiw/react-codemirror';
+import { js_beautify } from 'js-beautify';
+import { LinkOutlined } from '@ant-design/icons';
+
+type Props = {
+  name: string;
+  visible?: boolean;
+  data?: object;
+  readonly?: boolean;
+  onClose?: () => void;
+  onSubmit?: (data: object) => void;
+};
+
+const CodeMirrorDrawer: React.FC<Props> = ({
+  name,
+  visible = false,
+  readonly = false,
+  data = {},
+  onClose,
+  onSubmit,
+}) => {
+  const ref = useRef<any>(null);
+
+  const formatCodes = () => {
+    try {
+      if (ref.current) {
+        ref.current.editor.setValue(
+          js_beautify(ref.current.editor.getValue(), {
+            indent_size: 2,
+          }),
+        );
+      }
+    } catch (error) {
+      notification.error({
+        message: 'Format failed',
+      });
+    }
+  };
+
+  return (
+    <>
+      <style>
+        {`
+        .ant-drawer-body {
+          padding: 0;
+        }
+        .ant-page-header.ant-page-header-compact {
+          height: 100%;
+        }
+        .ant-page-header-content {
+          height: 95%;
+        }
+      `}
+      </style>
+      <Drawer
+        title="Plugin Data Editor"
+        visible={visible}
+        width={500}
+        maskClosable={false}
+        destroyOnClose
+        onClose={onClose}
+        footer={
+          !readonly && (
+            <div style={{ display: 'flex', justifyContent: 'space-between' }}>
+              <Button onClick={onClose}>Cancel</Button>
+              <Button
+                type="primary"
+                style={{ marginRight: 8, marginLeft: 8 }}
+                onClick={() => {
+                  try {
+                    if (onSubmit) {
+                      onSubmit(JSON.parse(ref.current?.editor.getValue()));
+                    }
+                  } catch (error) {
+                    notification.error({
+                      message: 'Invalid JSON data',
+                    });
+                  }
+                }}
+              >
+                Submit
+              </Button>
+            </div>
+          )
+        }
+      >
+        <PageHeader
+          title=""
+          subTitle={`Current Plugin: ${name}`}
+          ghost={false}
+          extra={[
+            <Button
+              type="default"
+              icon={<LinkOutlined />}
+              onClick={() => {
+                
window.open(`https://github.com/apache/apisix/blob/master/doc/plugins/${name}.md`);
+              }}
+            >
+              Document
+            </Button>,
+            <Button type="primary" onClick={formatCodes}>
+              Format
+            </Button>,
+          ]}
+        >
+          <CodeMirror
+            ref={ref}
+            value={JSON.stringify(data, null, 2)}
+            options={{
+              mode: 'json-ld',
+              readOnly: readonly ? 'nocursor' : '',
+              lineWrapping: true,
+              lineNumbers: true,
+              showCursorWhenSelecting: true,
+              autofocus: true,
+            }}
+          />
+        </PageHeader>
+      </Drawer>
+    </>
+  );
+};
+
+export default CodeMirrorDrawer;
diff --git a/web/src/components/Plugin/PluginPage.tsx 
b/web/src/components/Plugin/PluginPage.tsx
new file mode 100644
index 0000000..8c5ab79
--- /dev/null
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -0,0 +1,182 @@
+/*
+ * 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, { useEffect, useState } from 'react';
+import { Anchor, Layout, Switch, Card, Tooltip, Button, notification, Avatar } 
from 'antd';
+import { SettingFilled } from '@ant-design/icons';
+import { PanelSection } from '@api7-dashboard/ui';
+import { validate } from 'json-schema';
+
+import { fetchSchema, getList } from './service';
+import CodeMirrorDrawer from './CodeMirrorDrawer';
+
+type Props = {
+  readonly?: boolean;
+  initialData?: PluginComponent.Data;
+  schemaType?: PluginComponent.Schema;
+  onChange?: (data: PluginComponent.Data) => void;
+};
+
+const PanelSectionStyle = {
+  display: 'grid',
+  gridTemplateColumns: 'repeat(3, 33.333333%)',
+  gridRowGap: 15,
+  gridColumnGap: 10,
+  width: 'calc(100% - 20px)',
+};
+
+const { Sider, Content } = Layout;
+
+// NOTE: use this flag as plugin's name to hide drawer
+const NEVER_EXIST_PLUGIN_FLAG = 'NEVER_EXIST_PLUGIN_FLAG';
+
+const PluginPage: React.FC<Props> = ({
+  readonly = false,
+  initialData = {},
+  schemaType = '',
+  onChange = () => {},
+}) => {
+  const [pluginList, setPlugin] = useState<PluginComponent.Meta[][]>([]);
+  const [name, setName] = useState<string>(NEVER_EXIST_PLUGIN_FLAG);
+
+  useEffect(() => {
+    getList().then(setPlugin);
+  }, []);
+
+  const validateData = (pluginName: string, value: PluginComponent.Data) => {
+    fetchSchema(pluginName, schemaType).then((schema) => {
+      const { valid, errors } = validate(value, schema);
+      if (valid) {
+        setName(NEVER_EXIST_PLUGIN_FLAG);
+        onChange({ ...initialData, [pluginName]: { ...value, disable: false } 
});
+        return;
+      }
+      errors?.forEach((item) => {
+        notification.error({
+          message: 'Invalid plugin data',
+          description: item.message,
+        });
+      });
+      setName(pluginName);
+    });
+  };
+
+  return (
+    <>
+      <style>{`
+        .ant-avatar > img {
+          object-fit: contain;
+        }
+        .ant-avatar {
+          background-color: transparent;
+        }
+        .ant-avatar.ant-avatar-icon {
+          font-size: 32px;
+        }
+      `}</style>
+      <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}
+                />
+              );
+            })}
+          </Anchor>
+        </Sider>
+        <Content style={{ padding: '0 10px', backgroundColor: '#fff', 
minHeight: 1400 }}>
+          {pluginList.map((plugins) => {
+            const { category } = plugins[0];
+            return (
+              <PanelSection
+                title={category}
+                key={category}
+                style={PanelSectionStyle}
+                id={`plugin-category-${category}`}
+              >
+                {plugins.map((item) => (
+                  <Card
+                    key={item.name}
+                    title={[
+                      item.avatar && (
+                        <Avatar
+                          icon={item.avatar}
+                          className="plugin-avatar"
+                          style={{
+                            marginRight: 5,
+                          }}
+                        />
+                      ),
+                      <span>{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]);
+                          } else {
+                            onChange({
+                              ...initialData,
+                              [item.name]: { ...initialData[item.name], 
disable: true },
+                            });
+                          }
+                        }}
+                        key={Math.random().toString(36).substring(7)}
+                      />,
+                    ]}
+                  />
+                ))}
+              </PanelSection>
+            );
+          })}
+        </Content>
+      </Layout>
+      <CodeMirrorDrawer
+        name={name}
+        visible={name !== NEVER_EXIST_PLUGIN_FLAG}
+        data={initialData[name]}
+        readonly={readonly}
+        onClose={() => {
+          setName(NEVER_EXIST_PLUGIN_FLAG);
+        }}
+        onSubmit={(value) => {
+          validateData(name, value);
+        }}
+      />
+    </>
+  );
+};
+
+export default PluginPage;
diff --git a/web/src/components/Plugin/data.tsx 
b/web/src/components/Plugin/data.tsx
new file mode 100644
index 0000000..40b1d60
--- /dev/null
+++ b/web/src/components/Plugin/data.tsx
@@ -0,0 +1,166 @@
+/*
+ * 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 IconFont from '../IconFont';
+
+export const PLUGIN_MAPPER_SOURCE: Record<string, Omit<PluginComponent.Meta, 
'name'>> = {
+  'limit-req': {
+    category: 'Limit traffic',
+    priority: 1,
+  },
+  'limit-count': {
+    category: 'Limit traffic',
+    priority: 2,
+  },
+  'limit-conn': {
+    category: 'Limit traffic',
+    priority: 3,
+  },
+  prometheus: {
+    category: 'Observability',
+    priority: 1,
+    avatar: <IconFont name="iconPrometheus_software_logo" />,
+  },
+  skywalking: {
+    category: 'Observability',
+    priority: 2,
+    avatar: <IconFont name="iconskywalking" />,
+  },
+  zipkin: {
+    category: 'Observability',
+    priority: 3,
+  },
+  'request-id': {
+    category: 'Observability',
+    priority: 4,
+  },
+  'key-auth': {
+    category: 'Authentication',
+    priority: 1,
+  },
+  'basic-auth': {
+    category: 'Authentication',
+    priority: 3,
+  },
+  'node-status': {
+    category: 'Other',
+  },
+  'jwt-auth': {
+    category: 'Authentication',
+    priority: 2,
+    avatar: <IconFont name="iconjwt-3" />,
+  },
+  'authz-keycloak': {
+    category: 'Authentication',
+    priority: 5,
+    avatar: <IconFont name="iconkeycloak_icon_32px" />,
+  },
+  'ip-restriction': {
+    category: 'Security',
+    priority: 1,
+  },
+  'grpc-transcode': {
+    category: 'Other',
+  },
+  'serverless-pre-function': {
+    category: 'Other',
+  },
+  'serverless-post-function': {
+    category: 'Other',
+  },
+  'openid-connect': {
+    category: 'Authentication',
+    priority: 4,
+    avatar: <IconFont name="iconicons8-openid" />,
+  },
+  'proxy-rewrite': {
+    category: 'Other',
+  },
+  redirect: {
+    category: 'Other',
+    hidden: true,
+  },
+  'response-rewrite': {
+    category: 'Other',
+  },
+  'fault-injection': {
+    category: 'Security',
+    priority: 4,
+  },
+  'udp-logger': {
+    category: 'Log',
+    priority: 4,
+  },
+  'wolf-rbac': {
+    category: 'Other',
+  },
+  'proxy-cache': {
+    category: 'Other',
+    priority: 1,
+  },
+  'tcp-logger': {
+    category: 'Log',
+    priority: 3,
+  },
+  'proxy-mirror': {
+    category: 'Other',
+    priority: 2,
+  },
+  'kafka-logger': {
+    category: 'Log',
+    priority: 1,
+    avatar: <IconFont name="iconApache_kafka" />,
+  },
+  cors: {
+    category: 'Security',
+    priority: 2,
+  },
+  'uri-blocker': {
+    category: 'Security',
+    priority: 3,
+  },
+  'request-validator': {
+    category: 'Security',
+    priority: 5,
+  },
+  heartbeat: {
+    category: 'Other',
+    hidden: true,
+  },
+  'batch-requests': {
+    category: 'Other',
+  },
+  'http-logger': {
+    category: 'Log',
+    priority: 2,
+  },
+  'mqtt-proxy': {
+    category: 'Other',
+  },
+  oauth: {
+    category: 'Security',
+  },
+  syslog: {
+    category: 'Log',
+    priority: 5,
+  },
+  echo: {
+    category: 'Other',
+    priority: 3,
+  },
+};
diff --git a/web/src/iconfont.ts b/web/src/components/Plugin/index.ts
similarity index 73%
rename from web/src/iconfont.ts
rename to web/src/components/Plugin/index.ts
index df1b507..940c9f0 100644
--- a/web/src/iconfont.ts
+++ b/web/src/components/Plugin/index.ts
@@ -14,11 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { createFromIconfontCN } from '@ant-design/icons';
-
-// NOTE: 增加新图标时,请访问 https://www.iconfont.cn/manage/index 进行图标管理
-const IconFont = createFromIconfontCN({
-  scriptUrl: '//at.alicdn.com/t/font_1918158_alfpv3n06l6.js',
-});
-
-export default IconFont;
+export { default } from './PluginPage';
+export { PLUGIN_MAPPER_SOURCE } from './data';
diff --git a/web/src/components/Plugin/service.ts 
b/web/src/components/Plugin/service.ts
new file mode 100644
index 0000000..01f171b
--- /dev/null
+++ b/web/src/components/Plugin/service.ts
@@ -0,0 +1,98 @@
+/*
+ * 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 { JSONSchema7 } from 'json-schema';
+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);
+      });
+    });
+};
+
+/**
+ * cache pulgin schema by schemaType
+ * default schema is route for plugins in route
+ * support schema: consumer for plugins in consumer
+ */
+const cachedPluginSchema: Record<string, object> = {
+  route: {},
+  consumer: {},
+};
+export const fetchSchema = async (
+  name: string,
+  schemaType: PluginComponent.Schema,
+): Promise<JSONSchema7> => {
+  if (!cachedPluginSchema[schemaType][name]) {
+    const queryString = schemaType !== 'route' ? `?schema_type=${schemaType}` 
: '';
+    cachedPluginSchema[schemaType][name] = (
+      await request(`/schema/plugins/${name}${queryString}`)
+    ).data;
+    // for plugins schema returned with properties: [], which will cause parse 
error
+    if (JSON.stringify(cachedPluginSchema[schemaType][name].properties) === 
'[]') {
+      cachedPluginSchema[schemaType][name] = omit(
+        cachedPluginSchema[schemaType][name],
+        'properties',
+      );
+    }
+  }
+  return cachedPluginSchema[schemaType][name];
+};
diff --git a/web/src/pages/Consumer/components/Preview.tsx 
b/web/src/components/Plugin/typing.d.ts
similarity index 60%
copy from web/src/pages/Consumer/components/Preview.tsx
copy to web/src/components/Plugin/typing.d.ts
index 92e4b01..3a899aa 100644
--- a/web/src/pages/Consumer/components/Preview.tsx
+++ b/web/src/components/Plugin/typing.d.ts
@@ -14,24 +14,25 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React from 'react';
-import { FormInstance } from 'antd/lib/form';
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
+declare namespace PluginComponent {
+  type Data = object;
 
-import Step1 from './Step1';
+  type Schema = '' | 'route' | 'consumer';
 
-type Props = {
-  form1: FormInstance;
-  plugins: PluginPageType.FinalData;
-};
+  type Category =
+    | 'Security'
+    | 'Limit traffic'
+    | 'Log'
+    | 'Observability'
+    | 'Other'
+    | 'Authentication';
 
-const Page: React.FC<Props> = ({ form1, plugins }) => {
-  return (
-    <>
-      <Step1 form={form1} disabled />
-      <PluginPage initialData={plugins} readonly />
-    </>
-  );
-};
-
-export default Page;
+  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;
+  };
+}
diff --git a/web/src/e2e/Login.e2e.js b/web/src/e2e/Login.e2e.js
index 1829daa..5ea3ac9 100644
--- a/web/src/e2e/Login.e2e.js
+++ b/web/src/e2e/Login.e2e.js
@@ -18,10 +18,7 @@
 /* eslint-disable import/no-extraneous-dependencies */
 const puppeteer = require('puppeteer');
 
-const {
-  setupLogin,
-  BASE_URL
-} = require('./service')
+const { setupLogin, BASE_URL } = require('./service');
 
 let browser;
 const domSelectors = {
@@ -38,7 +35,7 @@ const loginFailedData = {
 beforeAll(async () => {
   browser = await puppeteer.launch({
     headless: true,
-    slowMo: 100
+    slowMo: 100,
   });
 });
 
diff --git a/web/src/e2e/Logout.e2e.js b/web/src/e2e/Logout.e2e.js
index cef92cf..c10083c 100644
--- a/web/src/e2e/Logout.e2e.js
+++ b/web/src/e2e/Logout.e2e.js
@@ -18,24 +18,21 @@
 /* eslint-disable import/no-extraneous-dependencies */
 const puppeteer = require('puppeteer');
 
-const {
-  setupLogin
-} = require('./service')
+const { setupLogin } = require('./service');
 
 let browser;
 const domSelectors = {
   userProfile: '.ant-space-horizontal div:nth-child(2)',
   dropdownMenuItem: '.ant-dropdown-menu-item',
-  buttonLogin: ".ant-btn-lg",
+  buttonLogin: '.ant-btn-lg',
   logoutButton: '.ant-dropdown > ul > li:nth-child(3)',
 };
 
 describe('Logout', () => {
-
   beforeAll(async () => {
     browser = await puppeteer.launch({
       headless: true,
-      slowMo: 100
+      slowMo: 100,
     });
   });
 
diff --git a/web/src/e2e/service.js b/web/src/e2e/service.js
index 84bfcb0..49279cd 100644
--- a/web/src/e2e/service.js
+++ b/web/src/e2e/service.js
@@ -36,4 +36,4 @@ export const setupLogin = async (page) => {
   await page.click(domSelectors.buttonLogin);
   await page.waitForSelector(domSelectors.loginSuccessIcon);
   await page.waitForNavigation();
-}
+};
diff --git a/web/src/global.less b/web/src/global.less
index b86beab..aa17671 100644
--- a/web/src/global.less
+++ b/web/src/global.less
@@ -49,11 +49,13 @@ ol {
   .ant-table {
     width: 100%;
     overflow-x: auto;
+
     &-thead > tr,
     &-tbody > tr {
       > th,
       > td {
         white-space: pre;
+
         > span {
           display: block;
         }
@@ -88,3 +90,13 @@ ol {
 .ant-layout.ant-layout-has-sider > .ant-layout-content {
   overflow-x: unset;
 }
+
+// NOTE: compatible with IconFont
+.icon {
+  width: 1.2em;
+  height: 1.2em;
+  margin-right: 1em;
+  overflow: hidden;
+  vertical-align: -0.15em;
+  fill: currentColor;
+}
diff --git a/web/src/helpers.tsx b/web/src/helpers.tsx
index ac8eb84..a0ffe74 100644
--- a/web/src/helpers.tsx
+++ b/web/src/helpers.tsx
@@ -21,39 +21,39 @@ import { history } from 'umi';
 import moment from 'moment';
 
 import { codeMessage } from './constants';
-import IconFont from './iconfont';
+import IconFont from './components/IconFont';
 
 export const getMenuData = (): MenuDataItem[] => {
   return [
     {
       name: 'metrics',
       path: '/metrics',
-      icon: <IconFont type="icondashboard" />,
+      icon: <IconFont name="icondashboard" />,
     },
     {
       name: 'routes',
       path: '/routes/list',
-      icon: <IconFont type="iconroute" />,
+      icon: <IconFont name="iconroute" />,
     },
     {
       name: 'ssl',
       path: '/ssl/list',
-      icon: <IconFont type="iconSSLshuzizhengshu" />,
+      icon: <IconFont name="iconssl" />,
     },
     {
       name: 'upstream',
       path: '/upstream/list',
-      icon: <IconFont type="iconupstream" />,
+      icon: <IconFont name="iconserver" />,
     },
     {
       name: 'consumer',
       path: '/consumer/list',
-      icon: <IconFont type="iconfuwuliebiao" />,
+      icon: <IconFont name="iconconsumer" />,
     },
     {
       name: 'setting',
       path: '/settings',
-      icon: <IconFont type="iconsetting" />,
+      icon: <IconFont name="iconsetting" />,
     },
   ];
 };
@@ -101,8 +101,8 @@ export const getUrlQuery: (key: string) => string | false = 
(key: string) => {
 export const timestampToLocaleString = (timestamp: number) => {
   if (!timestamp) {
     // TODO: i18n
-    return "None"
+    return 'None';
   }
 
-  return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss')
-}
+  return moment.unix(timestamp).format('YYYY-MM-DD HH:mm:ss');
+};
diff --git a/web/src/libs/iconfont.js b/web/src/libs/iconfont.js
new file mode 100644
index 0000000..0c8d3d6
--- /dev/null
+++ b/web/src/libs/iconfont.js
@@ -0,0 +1,85 @@
+/*
+* MIT License
+
+* Copyright (c) 2019 Alipay.inc
+
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+
+* The above copyright notice and this permission notice shall be included in 
all
+* copies or substantial portions of the Software.
+
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+* SOFTWARE.
+*/
+/* eslint-disable */
+!(function (l) {
+  var c,
+    h,
+    t,
+    a,
+    i,
+    e,
+    z =
+      '<svg><symbol id="icondashboard" viewBox="0 0 1024 1024"><path 
d="M662.87 343.467l60.33 60.33-128.768 128.768a85.333 85.333 0 1 
1-60.33-60.33l128.767-128.768zM85.332 512C85.333 276.352 276.352 85.333 512 
85.333S938.667 276.352 938.667 512a425.813 425.813 0 0 1-156.758 
330.453l-70.826-53.162a341.333 341.333 0 1 0-398.123 0l-70.87 53.162A425.813 
425.813 0 0 1 85.334 512zM384 725.333h256v85.334H384v-85.334z"  
></path></symbol><symbol id="iconssl" viewBox="0 0 1024 1024"><path d="M167. 
[...]
+    p = (p = document.getElementsByTagName('script'))[p.length - 
1].getAttribute('data-injectcss');
+  if (p && !l.__iconfont__svg__cssinject__) {
+    l.__iconfont__svg__cssinject__ = !0;
+    try {
+      document.write(
+        '<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: 
currentColor;vertical-align: -0.1em;font-size:16px;}</style>',
+      );
+    } catch (l) {
+      console && console.log(l);
+    }
+  }
+  function o() {
+    i || ((i = !0), t());
+  }
+  (c = function () {
+    var l, c, h, t;
+    ((t = document.createElement('div')).innerHTML = z),
+      (z = null),
+      (h = t.getElementsByTagName('svg')[0]) &&
+        (h.setAttribute('aria-hidden', 'true'),
+        (h.style.position = 'absolute'),
+        (h.style.width = 0),
+        (h.style.height = 0),
+        (h.style.overflow = 'hidden'),
+        (l = h),
+        (c = document.body).firstChild
+          ? ((t = l), (h = c.firstChild).parentNode.insertBefore(t, h))
+          : c.appendChild(l));
+  }),
+    document.addEventListener
+      ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState)
+        ? setTimeout(c, 0)
+        : ((h = function () {
+            document.removeEventListener('DOMContentLoaded', h, !1), c();
+          }),
+          document.addEventListener('DOMContentLoaded', h, !1))
+      : document.attachEvent &&
+        ((t = c),
+        (a = l.document),
+        (i = !1),
+        (e = function () {
+          try {
+            a.documentElement.doScroll('left');
+          } catch (l) {
+            return void setTimeout(e, 50);
+          }
+          o();
+        })(),
+        (a.onreadystatechange = function () {
+          'complete' == a.readyState && ((a.onreadystatechange = null), o());
+        }));
+})(window);
diff --git a/web/src/pages/Consumer/Create.tsx 
b/web/src/pages/Consumer/Create.tsx
index d866076..cab68b9 100644
--- a/web/src/pages/Consumer/Create.tsx
+++ b/web/src/pages/Consumer/Create.tsx
@@ -18,9 +18,9 @@ import React, { useState, useEffect } from 'react';
 import { PageContainer } from '@ant-design/pro-layout';
 import { Card, Steps, notification, Form } from 'antd';
 import { history, useIntl } from 'umi';
-import { PluginPage, PluginPageType, PLUGIN_MAPPER_SOURCE } from 
'@api7-dashboard/plugin';
 
 import ActionBar from '@/components/ActionBar';
+import PluginPage, { PLUGIN_MAPPER_SOURCE } from '@/components/Plugin';
 
 import Step1 from './components/Step1';
 import Preview from './components/Preview';
@@ -28,7 +28,7 @@ import { fetchItem, create, update } from './service';
 
 const Page: React.FC = (props) => {
   const [step, setStep] = useState(1);
-  const [plugins, setPlugins] = useState<PluginPageType.FinalData>({});
+  const [plugins, setPlugins] = useState<PluginComponent.Data>({});
   const [form1] = Form.useForm();
   const { formatMessage } = useIntl();
 
@@ -70,14 +70,15 @@ const Page: React.FC = (props) => {
         setStep(nextStep);
       });
     } else if (nextStep === 3) {
-      const authPluginNames = Object.keys(PLUGIN_MAPPER_SOURCE).filter(
-        (pluginName) => PLUGIN_MAPPER_SOURCE[pluginName].category === 
'Authentication',
-      );
-      const currentAuthPlugin = Object.keys(plugins).filter((plugin) =>
-        authPluginNames.includes(plugin),
-      );
-      const currentAuthPluginLen = currentAuthPlugin.length;
-      if (currentAuthPluginLen > 1 || currentAuthPluginLen === 0) {
+      // TRICK: waiting for 
https://github.com/apache/apisix-dashboard/issues/532
+      if (
+        !Object.keys(plugins).filter(
+          (name) =>
+            (name.indexOf('auth') !== -1 ||
+              PLUGIN_MAPPER_SOURCE[name]?.category === 'Authentication') &&
+            !plugins[name].disable,
+        ).length
+      ) {
         notification.warning({
           message: formatMessage({
             id: 
'page.consumer.notification.warning.enableAuthenticationPlugin',
diff --git a/web/src/pages/Consumer/components/Preview.tsx 
b/web/src/pages/Consumer/components/Preview.tsx
index 92e4b01..a1d1daf 100644
--- a/web/src/pages/Consumer/components/Preview.tsx
+++ b/web/src/pages/Consumer/components/Preview.tsx
@@ -16,13 +16,13 @@
  */
 import React from 'react';
 import { FormInstance } from 'antd/lib/form';
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
 
+import PluginPage from '@/components/Plugin';
 import Step1 from './Step1';
 
 type Props = {
   form1: FormInstance;
-  plugins: PluginPageType.FinalData;
+  plugins: PluginComponent.Data;
 };
 
 const Page: React.FC<Props> = ({ form1, plugins }) => {
diff --git a/web/src/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx
index 396008c..73e3e9e 100644
--- a/web/src/pages/Route/Create.tsx
+++ b/web/src/pages/Route/Create.tsx
@@ -252,10 +252,11 @@ 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 5e153e3..f0c668a 100644
--- a/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
+++ b/web/src/pages/Route/components/CreateStep4/CreateStep4.tsx
@@ -17,9 +17,9 @@
 import React from 'react';
 import { FormInstance } from 'antd/lib/form';
 import { useIntl } from 'umi';
-import { PluginPage } from '@api7-dashboard/plugin';
 import PluginOrchestration from '@api7-dashboard/pluginchart';
 
+import PluginPage from '@/components/Plugin';
 import Step1 from '../Step1';
 import Step2 from '../Step2';
 
diff --git a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx 
b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
index 9ba0b80..48260e7 100644
--- a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
+++ b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
@@ -149,7 +149,7 @@ const MatchingRulesView: 
React.FC<RouteModule.Step1PassProps> = ({
             </Space>
           ),
         },
-  ].filter(item => Object.keys(item).length)
+  ].filter((item) => Object.keys(item).length);
 
   const renderModal = () => (
     <Modal
diff --git a/web/src/pages/Route/components/Step1/RequestConfigView.tsx 
b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
index 0d41815..20d8457 100644
--- a/web/src/pages/Route/components/Step1/RequestConfigView.tsx
+++ b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
@@ -30,7 +30,7 @@ import {
 const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
   form,
   disabled,
-  onChange = () => { },
+  onChange = () => {},
 }) => {
   const { formatMessage } = useIntl();
   const HostList = () => (
diff --git a/web/src/pages/Route/components/Step3/index.tsx 
b/web/src/pages/Route/components/Step3/index.tsx
index fcc6b01..3eb30b8 100644
--- a/web/src/pages/Route/components/Step3/index.tsx
+++ b/web/src/pages/Route/components/Step3/index.tsx
@@ -19,17 +19,17 @@ import { Radio, Tooltip } from 'antd';
 import { QuestionCircleOutlined } from '@ant-design/icons';
 import { isChrome } from 'react-device-detect';
 
-import { PluginPage, PluginPageType } from '@api7-dashboard/plugin';
 import PluginOrchestration from '@api7-dashboard/pluginchart';
+import PluginPage from '@/components/Plugin';
 
 type Props = {
   data: {
-    plugins: PluginPageType.FinalData;
+    plugins: PluginComponent.Data;
     script: Record<string, any>;
   };
-  onChange(data: { plugins: PluginPageType.FinalData; script: any }): void;
+  onChange(data: { plugins: PluginComponent.Data; script: any }): void;
   readonly?: boolean;
-  isForceHttps: boolean
+  isForceHttps: boolean;
 };
 
 type Mode = 'NORMAL' | 'DRAW';
@@ -61,42 +61,40 @@ const Page: React.FC<Props> = ({ data, onChange, readonly = 
false, isForceHttps
         </Radio.Group>
         {Boolean(disableDraw) && (
           <div style={{ marginLeft: '10px' }}>
-            <Tooltip placement="right" title={() => {
-              // NOTE: forceHttps do not support DRAW mode
-              // TODO: i18n
-              const titleArr: string[] = [];
-              if (!isChrome) {
-                titleArr.push('插件编排仅支持 Chrome 浏览器。');
-              }
-              if (isForceHttps) {
-                titleArr.push('当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。');
-              }
-              return (
-                titleArr.map((item, index) => `${index + 1}.${item}`).join("")
-              )
-            }}>
+            <Tooltip
+              placement="right"
+              title={() => {
+                // NOTE: forceHttps do not support DRAW mode
+                // TODO: i18n
+                const titleArr: string[] = [];
+                if (!isChrome) {
+                  titleArr.push('插件编排仅支持 Chrome 浏览器。');
+                }
+                if (isForceHttps) {
+                  titleArr.push('当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。');
+                }
+                return titleArr.map((item, index) => `${index + 
1}.${item}`).join('');
+              }}
+            >
               <QuestionCircleOutlined />
             </Tooltip>
           </div>
         )}
       </div>
-      {
-        Boolean(mode === 'NORMAL') && (
-          <PluginPage
-            initialData={plugins}
-            onChange={(pluginsData) => onChange({ plugins: pluginsData, 
script: {} })}
-          />
-        )
-      }
-      {
-        Boolean(mode === 'DRAW') && (
-          <PluginOrchestration
-            data={script?.chart}
-            onChange={(scriptData) => onChange({ plugins: {}, script: 
scriptData })}
-            readonly={readonly}
-          />
-        )
-      }
+      {Boolean(mode === 'NORMAL') && (
+        <PluginPage
+          initialData={plugins}
+          schemaType="route"
+          onChange={(pluginsData) => onChange({ plugins: pluginsData, script: 
{} })}
+        />
+      )}
+      {Boolean(mode === 'DRAW') && (
+        <PluginOrchestration
+          data={script?.chart}
+          onChange={(scriptData) => onChange({ plugins: {}, script: scriptData 
})}
+          readonly={readonly}
+        />
+      )}
     </>
   );
 };
diff --git a/web/yarn.lock b/web/yarn.lock
index ec89ce3..754e9a9 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -187,18 +187,6 @@
     lodash "^4.17.15"
     resize-observer-polyfill "^1.5.0"
 
-"@api7-dashboard/plugin@^1.0.15":
-  version "1.0.15"
-  resolved 
"https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.15.tgz#690ab5666125ef7e45c6841fa348e9f3870b890b";
-  integrity 
sha512-B7B0zmRxWQwNjdqm6GI91E5QLaJ3Dzs6VE3OP6zVz5Mlgx3KuyfYGwB8E0jr+ApeB59RvgHAGgRExN6YDMTzFA==
-  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/plugin@^1.0.6":
   version "1.0.6"
   resolved 
"https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.6.tgz#d17bd53da9151c0fee2d5591965b642bc7e1005d";
@@ -2395,6 +2383,11 @@
     jest-diff "^25.2.1"
     pretty-format "^25.2.1"
 
+"@types/js-beautify@^1.13.1":
+  version "1.13.1"
+  resolved 
"https://registry.yarnpkg.com/@types/js-beautify/-/js-beautify-1.13.1.tgz#d4739266c5dcad561226cd1ec5407fa0542d863d";
+  integrity 
sha512-F3YCoZS//n74Wu+hxoVrxX1H8qaWo+WAgQ+ObmFH4ZFwI0fIwiJTW7pvkCRShw8ST7+ej7sB68K+ZHAZgK4S4Q==
+
 "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", 
"@types/json-schema@^7.0.5":
   version "7.0.5"
   resolved 
"https://registry.npm.taobao.org/@types/json-schema/download/@types/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd";
@@ -3756,6 +3749,11 @@ abab@^2.0.0:
   resolved 
"https://registry.npm.taobao.org/abab/download/abab-2.0.4.tgz?cache=0&sync_timestamp=1596258082074&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fabab%2Fdownload%2Fabab-2.0.4.tgz#6dfa57b417ca06d21b2478f0e638302f99c2405c";
   integrity sha1-bfpXtBfKBtIbJHjw5jgwL5nCQFw=
 
+abbrev@1:
+  version "1.1.1"
+  resolved 
"https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8";
+  integrity 
sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
 accepts@~1.3.5, accepts@~1.3.7:
   version "1.3.7"
   resolved 
"https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd";
@@ -5541,6 +5539,14 @@ concat-stream@^1.5.0, concat-stream@^1.5.2, 
concat-stream@^1.6.2:
     readable-stream "^2.2.2"
     typedarray "^0.0.6"
 
+config-chain@^1.1.12:
+  version "1.1.12"
+  resolved 
"https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa";
+  integrity 
sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
+  dependencies:
+    ini "^1.3.4"
+    proto-list "~1.2.1"
+
 confusing-browser-globals@^1.0.5, confusing-browser-globals@^1.0.9:
   version "1.0.9"
   resolved 
"https://registry.npm.taobao.org/confusing-browser-globals/download/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd";
@@ -6596,6 +6602,16 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
+editorconfig@^0.15.3:
+  version "0.15.3"
+  resolved 
"https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5";
+  integrity 
sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==
+  dependencies:
+    commander "^2.19.0"
+    lru-cache "^4.1.5"
+    semver "^5.6.0"
+    sigmund "^1.0.1"
+
 [email protected]:
   version "1.1.1"
   resolved 
"https://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d";
@@ -9059,7 +9075,7 @@ [email protected]:
   resolved 
"https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de";
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
-ini@^1.3.5:
+ini@^1.3.4, ini@^1.3.5:
   version "1.3.5"
   resolved 
"https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927";
   integrity sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=
@@ -10214,6 +10230,17 @@ joi@^17.1.1:
     "@sideway/formula" "^3.0.0"
     "@sideway/pinpoint" "^2.0.0"
 
+js-beautify@^1.13.0:
+  version "1.13.0"
+  resolved 
"https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.13.0.tgz#a056d5d3acfd4918549aae3ab039f9f3c51eebb2";
+  integrity 
sha512-/Tbp1OVzZjbwzwJQFIlYLm9eWQ+3aYbBXLSaqb1mEJzhcQAfrqMMQYtjb6io+U6KpD0ID4F+Id3/xcjH3l/sqA==
+  dependencies:
+    config-chain "^1.1.12"
+    editorconfig "^0.15.3"
+    glob "^7.1.3"
+    mkdirp "^1.0.4"
+    nopt "^5.0.0"
+
 js-file-download@^0.4.1:
   version "0.4.12"
   resolved 
"https://registry.npm.taobao.org/js-file-download/download/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821";
@@ -10976,6 +11003,14 @@ lowlight@^1.14.0:
     fault "^1.0.0"
     highlight.js "~10.1.0"
 
+lru-cache@^4.1.5:
+  version "4.1.5"
+  resolved 
"https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd";
+  integrity 
sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+  dependencies:
+    pseudomap "^1.0.2"
+    yallist "^2.1.2"
+
 lru-cache@^5.1.1:
   version "5.1.1"
   resolved 
"https://registry.npm.taobao.org/lru-cache/download/lru-cache-5.1.1.tgz?cache=0&sync_timestamp=1594427569171&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Flru-cache%2Fdownload%2Flru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920";
@@ -11499,7 +11534,7 @@ [email protected], mkdirp@^0.5.1, mkdirp@^0.5.3, 
mkdirp@^0.5.4, mkdirp@^0.5.5, mkdirp
   dependencies:
     minimist "^1.2.5"
 
[email protected], mkdirp@^1.0.0:
[email protected], mkdirp@^1.0.0, mkdirp@^1.0.4:
   version "1.0.4"
   resolved 
"https://registry.npm.taobao.org/mkdirp/download/mkdirp-1.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmkdirp%2Fdownload%2Fmkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e";
   integrity sha1-PrXtYmInVteaXw4qIh3+utdcL34=
@@ -11789,6 +11824,13 @@ node-releases@^1.1.13, node-releases@^1.1.60:
   resolved 
"https://registry.npm.taobao.org/node-releases/download/node-releases-1.1.60.tgz?cache=0&sync_timestamp=1595485348533&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-releases%2Fdownload%2Fnode-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084";
   integrity sha1-aUi9/OgobwtdDlqI6DhOlU3+cIQ=
 
+nopt@^5.0.0:
+  version "5.0.0"
+  resolved 
"https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88";
+  integrity 
sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
+  dependencies:
+    abbrev "1"
+
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, 
normalize-package-data@^2.5.0:
   version "2.5.0"
   resolved 
"https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8";
@@ -13494,6 +13536,11 @@ property-information@^5.0.0:
   dependencies:
     xtend "^4.0.0"
 
+proto-list@~1.2.1:
+  version "1.2.4"
+  resolved 
"https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849";
+  integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+
 protocols@^1.1.0, protocols@^1.4.0:
   version "1.4.8"
   resolved 
"https://registry.npm.taobao.org/protocols/download/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8";
@@ -13524,6 +13571,11 @@ [email protected]:
   dependencies:
     event-stream "=3.3.4"
 
+pseudomap@^1.0.2:
+  version "1.0.2"
+  resolved 
"https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3";
+  integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+
 psl@^1.1.28:
   version "1.8.0"
   resolved 
"https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24";
@@ -15701,6 +15753,11 @@ side-channel@^1.0.2:
     es-abstract "^1.18.0-next.0"
     object-inspect "^1.8.0"
 
+sigmund@^1.0.1:
+  version "1.0.1"
+  resolved 
"https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590";
+  integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
+
 signal-exit@^3.0.0, signal-exit@^3.0.2:
   version "3.0.3"
   resolved 
"https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c";
@@ -18279,6 +18336,11 @@ y18n@^4.0.0:
   resolved 
"https://registry.npm.taobao.org/y18n/download/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b";
   integrity sha1-le+U+F7MgdAHwmThkKEg8KPIVms=
 
+yallist@^2.1.2:
+  version "2.1.2"
+  resolved 
"https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52";
+  integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+
 yallist@^3.0.2:
   version "3.1.1"
   resolved 
"https://registry.npm.taobao.org/yallist/download/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd";

Reply via email to