You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/06/11 09:24:31 UTC

[incubator-apisix-dashboard] branch next updated: Feat plugins (#254)

This is an automated email from the ASF dual-hosted git repository.

juzhiyuan pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git


The following commit(s) were added to refs/heads/next by this push:
     new 8b3dab5  Feat plugins (#254)
8b3dab5 is described below

commit 8b3dab5bc03450829b70e50a248dd6d8fc373885
Author: 琚致远 <ju...@apache.org>
AuthorDate: Thu Jun 11 17:24:21 2020 +0800

    Feat plugins (#254)
    
    * feat: refactor plugin list
    
    * feat: added locale
    
    * feat: refactor plugin initial
    
    * feat: update category
    
    * codes clean
    
    * feat: added translation
    
    * feat: added locale for limit-count
    
    * feat: added locale for limit-req
    
    * codes clean
---
 src/components/PluginForm/PluginForm.tsx           |  10 +-
 src/components/PluginForm/data.ts                  | 128 ++++++++++++---------
 src/components/PluginForm/index.ts                 |   2 -
 src/components/PluginForm/locales/en-US.ts         |  56 +++++++++
 src/components/PluginForm/locales/zh-CN.ts         |  54 +++++++++
 src/components/PluginForm/service.ts               |   2 -
 src/components/PluginForm/typing.d.ts              |   9 +-
 src/pages/Routes/Create.tsx                        |  80 +++++++++----
 .../Routes/components/CreateStep3/CreateStep3.tsx  |  55 +++------
 src/pages/Routes/constants.ts                      |   2 +
 src/pages/Routes/service.ts                        |   2 +
 src/pages/Routes/typing.d.ts                       |   2 +
 12 files changed, 281 insertions(+), 121 deletions(-)

diff --git a/src/components/PluginForm/PluginForm.tsx b/src/components/PluginForm/PluginForm.tsx
index ee3018f..5b69bec 100644
--- a/src/components/PluginForm/PluginForm.tsx
+++ b/src/components/PluginForm/PluginForm.tsx
@@ -108,6 +108,7 @@ const PluginForm: React.FC<PluginForm.Props> = ({
   initialData = {},
   onFinish,
 }) => {
+  const { formatMessage } = useIntl();
   const [schema, setSchema] = useState<PluginForm.PluginSchema>();
 
   useEffect(() => {
@@ -167,7 +168,14 @@ const PluginForm: React.FC<PluginForm.Props> = ({
 
         return (
           <Form.Item
-            label={propertyName}
+            label={formatMessage({
+              id: `PluginForm.plugin.${name}.property.${propertyName}`,
+              defaultMessage: propertyName,
+            })}
+            extra={formatMessage({
+              id: `PluginForm.plugin.${name}.property.${propertyName}.extra`,
+              defaultMessage: '',
+            })}
             name={propertyName}
             key={propertyName}
             rules={transformPropertyToRules(schema!, propertyName, propertyValue)}
diff --git a/src/components/PluginForm/data.ts b/src/components/PluginForm/data.ts
index f4d6afa..6cd12bd 100644
--- a/src/components/PluginForm/data.ts
+++ b/src/components/PluginForm/data.ts
@@ -1,83 +1,99 @@
-export const list: PluginForm.PluginProps[] = [
-  {
-    name: 'basic-auth',
+export const PLUGIN_MAPPER_SOURCE: { [name: string]: PluginForm.PluginMapperItem } = {
+  'limit-req': {
+    category: 'Limit',
   },
-  {
-    name: 'batch-requests',
+  'limit-count': {
+    category: 'Limit',
   },
-  {
-    name: 'cors',
+  'limit-conn': {
+    category: 'Limit',
   },
-  {
-    name: 'fault-injection',
+  'key-auth': {
+    category: 'Security',
+    hidden: true,
   },
-  {
-    name: 'grpc-transcoding',
+  'basic-auth': {
+    category: 'Security',
+    hidden: true,
   },
-  {
-    name: 'http-logger',
+  prometheus: {
+    category: 'Metric',
   },
-  {
-    name: 'ip-restriction',
+  'node-status': {
+    category: 'Other',
   },
-  {
-    name: 'jwt-auth',
+  'jwt-auth': {
+    category: 'Security',
+    hidden: true,
   },
-  {
-    name: 'kafka-logger',
+  zipkin: {
+    category: 'Metric',
   },
-  {
-    name: 'key-auth',
+  'ip-restriction': {
+    category: 'Security',
   },
-  {
-    name: 'limit-conn',
+  'grpc-transcode': {
+    category: 'Other',
+    hidden: true,
   },
-  {
-    name: 'limit-count',
+  'serverless-pre-function': {
+    category: 'Other',
   },
-  {
-    name: 'limit-req',
+  'serverless-post-function': {
+    category: 'Other',
   },
-  {
-    name: 'mqtt-proxy',
+  'openid-connect': {
+    category: 'Security',
   },
-  {
-    name: 'oauth',
+  'proxy-rewrite': {
+    category: 'Other',
+    hidden: true,
   },
-  {
-    name: 'prometheus',
+  redirect: {
+    category: 'Other',
+    hidden: true,
   },
-  {
-    name: 'proxy-cache',
+  'response-rewrite': {
+    category: 'Other',
   },
-  {
-    name: 'proxy-mirror',
+  'fault-injection': {
+    category: 'Security',
   },
-  {
-    name: 'proxy-rewrite',
+  'udp-logger': {
+    category: 'Log',
   },
-  {
-    name: 'redirect',
+  'wolf-rbac': {
+    category: 'Other',
+    hidden: true,
   },
-  {
-    name: 'response-rewrite',
+  'proxy-cache': {
+    category: 'Other',
   },
-  {
-    name: 'serverless',
+  'tcp-logger': {
+    category: 'Log',
   },
-  {
-    name: 'syslog',
+  'proxy-mirror': {
+    category: 'Other',
   },
-  {
-    name: 'tcp-logger',
+  'kafka-logger': {
+    category: 'Log',
   },
-  {
-    name: 'udp-logger',
+  cors: {
+    category: 'Security',
   },
-  {
-    name: 'wolf-rbac',
+  heartbeat: {
+    category: 'Other',
   },
-  {
-    name: 'zipkin',
+  'batch-requests': {
+    category: 'Other',
   },
-];
+  'http-logger': {
+    category: 'Log',
+  },
+  'mqtt-proxy': {
+    category: 'Other',
+  },
+  oauth: {
+    category: 'Security',
+  },
+};
diff --git a/src/components/PluginForm/index.ts b/src/components/PluginForm/index.ts
index 4872268..8770cd6 100644
--- a/src/components/PluginForm/index.ts
+++ b/src/components/PluginForm/index.ts
@@ -1,5 +1,3 @@
 export { default } from './PluginForm';
-export { fetchList as fetchPluginList } from './service';
-export { list as pluginList } from './data';
 export { default as PluginFormZhCN } from './locales/zh-CN';
 export { default as PluginFormEnUS } from './locales/en-US';
diff --git a/src/components/PluginForm/locales/en-US.ts b/src/components/PluginForm/locales/en-US.ts
index 9f57841..68ac691 100644
--- a/src/components/PluginForm/locales/en-US.ts
+++ b/src/components/PluginForm/locales/en-US.ts
@@ -17,11 +17,60 @@ export default {
     'kafka-logger is a plugin which works as a Kafka client driver for the ngx_lua nginx module.',
   'PluginForm.plugin.key-auth.desc':
     'key-auth is an authentication plugin, it should work with consumer together.',
+
+  // TODO:
   'PluginForm.plugin.limit-conn.desc':
     'Limiting request concurrency (or concurrent connections) plugin for Apisix.',
+  'PluginForm.plugin.limit-conn.property.burst': '',
+  'PluginForm.plugin.limit-conn.property.burst.extra': '',
+  'PluginForm.plugin.limit-conn.property.conn': '',
+  'PluginForm.plugin.limit-conn.property.conn.extra': '',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay': '',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay.extra': '',
+  'PluginForm.plugin.limit-conn.property.key': '',
+  'PluginForm.plugin.limit-conn.property.key.extra': '',
+  'PluginForm.plugin.limit-conn.property.rejected_code': '',
+  'PluginForm.plugin.limit-conn.property.rejected_code.extra': '',
+
+  // FIXME
   'PluginForm.plugin.limit-count.desc':
     'Limit request rate by a fixed number of requests in a given time window.',
+  'PluginForm.plugin.limit-count.property.count': '单位窗口内请求数量',
+  'PluginForm.plugin.limit-count.property.count.extra': '指定时间窗口内的请求数量阈值',
+  'PluginForm.plugin.limit-count.property.time_window': '时间窗口大小',
+  'PluginForm.plugin.limit-count.property.time_window.extra':
+    '时间窗口的大小(以秒为单位),超过这个时间就会重置',
+  'PluginForm.plugin.limit-count.property.key': 'Key',
+  'PluginForm.plugin.limit-count.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-count.property.rejected_code': '错误 HTTP 状态码',
+  'PluginForm.plugin.limit-count.property.rejected_code.extra':
+    '当请求超过阈值时返回的 HTTP 状态码, 默认值是503。',
+  'PluginForm.plugin.limit-count.property.policy': '策略',
+  'PluginForm.plugin.limit-count.property.policy.extra': '用于检索和增加限制的速率限制策略',
+  'PluginForm.plugin.limit-count.property.redis_host': ' Redis 地址',
+  'PluginForm.plugin.limit-count.property.redis_host.extra': ' Redis 服务节点的地址',
+  'PluginForm.plugin.limit-count.property.redis_port': 'Redis 端口',
+  'PluginForm.plugin.limit-count.property.redis_port.extra': 'Redis 服务节点的端口',
+  'PluginForm.plugin.limit-count.property.redis_password': 'Redis 密码',
+  'PluginForm.plugin.limit-count.property.redis_password.extra': 'Redis 服务节点的密码',
+  'PluginForm.plugin.limit-count.property.redis_timeout': 'Redis 超时时间',
+  'PluginForm.plugin.limit-count.property.redis_timeout.extra':
+    'Redis 服务节点以毫秒为单位的超时时间',
+
+  // FIXME
   'PluginForm.plugin.limit-req.desc': 'limit request rate using the "leaky bucket" method.',
+  'PluginForm.plugin.limit-req.property.rate': 'Rate',
+  'PluginForm.plugin.limit-req.property.rate.extra':
+    '指定的请求速率(以秒为单位),请求速率超过 rate 但没有超过 (rate + brust)的请求会被加上延时。',
+  'PluginForm.plugin.limit-req.property.burst': 'Burst',
+  'PluginForm.plugin.limit-req.property.burst.extra':
+    '请求速率超过 (rate + brust)的请求会被直接拒绝。',
+  'PluginForm.plugin.limit-req.property.key': 'Key',
+  'PluginForm.plugin.limit-req.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-req.property.rejected_code': '错误 HTTP 状态码',
+  'PluginForm.plugin.limit-req.property.rejected_code.extra':
+    '当请求超过阈值时返回的 HTTP 状态码, 默认值是503。',
+
   'PluginForm.plugin.mqtt-proxy.desc':
     'The plugin mqtt-proxy only works in stream model, it help you to dynamic load balance by client_id of MQTT.',
   'PluginForm.plugin.oauth.desc':
@@ -46,4 +95,11 @@ export default {
   'PluginForm.plugin.wolf-rbac.desc':
     'wolf-rbac is an authentication and authorization (rbac) plugin',
   'PluginForm.plugin.zipkin.desc': 'Zipkin is a OpenTracing plugin.',
+  'PluginForm.plugin.node-status.desc': 'No description currently.',
+  'PluginForm.plugin.serverless-pre-function.desc':
+    'It belongs to serverless, and will execute first',
+  'PluginForm.plugin.serverless-post-function.desc':
+    'It belongs to serverless and will execute in the end',
+  'PluginForm.plugin.openid-connect.desc': 'No description currently.',
+  'PluginForm.plugin.heartbeat.desc': 'No description currently.',
 };
diff --git a/src/components/PluginForm/locales/zh-CN.ts b/src/components/PluginForm/locales/zh-CN.ts
index 548ecfa..5ea1019 100644
--- a/src/components/PluginForm/locales/zh-CN.ts
+++ b/src/components/PluginForm/locales/zh-CN.ts
@@ -17,10 +17,59 @@ export default {
     'kafka-logger 是一个插件,可用作ngx_lua nginx 模块的 Kafka 客户端驱动程序。',
   'PluginForm.plugin.key-auth.desc':
     'key-auth 是一个认证插件,它需要与 consumer 一起配合才能工作。',
+
   'PluginForm.plugin.limit-conn.desc': 'APISIX 的限制并发请求(或并发连接)插件。',
+  'PluginForm.plugin.limit-conn.property.burst': '最大并发请求数',
+  'PluginForm.plugin.limit-conn.property.burst.extra': '允许的最大并发请求数',
+  'PluginForm.plugin.limit-conn.property.conn': '可延迟并发请求数',
+  'PluginForm.plugin.limit-conn.property.conn.extra': '允许延迟的过多并发请求(或连接)的数量。',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay': '默认请求延迟时间',
+  'PluginForm.plugin.limit-conn.property.default_conn_delay.extra':
+    '默认的典型连接(或请求)的处理延迟时间。',
+  'PluginForm.plugin.limit-conn.property.key': '关键字',
+  'PluginForm.plugin.limit-conn.property.key.extra':
+    '用户指定的限制并发级别的关键字,可以是客户端IP或服务端IP。',
+  'PluginForm.plugin.limit-conn.property.rejected_code': '错误 HTTP 状态码',
+  'PluginForm.plugin.limit-conn.property.rejected_code.extra':
+    '当请求超过阈值时返回的 HTTP 状态码, 默认值是503。',
+
   'PluginForm.plugin.limit-count.desc':
     '和 GitHub API 的限速类似, 在指定的时间范围内,限制总的请求个数。并且在 HTTP 响应头中返回剩余可以请求的个数。',
+  'PluginForm.plugin.limit-count.property.count': '单位窗口内请求数量',
+  'PluginForm.plugin.limit-count.property.count.extra': '指定时间窗口内的请求数量阈值',
+  'PluginForm.plugin.limit-count.property.time_window': '时间窗口大小',
+  'PluginForm.plugin.limit-count.property.time_window.extra':
+    '时间窗口的大小(以秒为单位),超过这个时间就会重置',
+  'PluginForm.plugin.limit-count.property.key': 'Key',
+  'PluginForm.plugin.limit-count.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-count.property.rejected_code': '错误 HTTP 状态码',
+  'PluginForm.plugin.limit-count.property.rejected_code.extra':
+    '当请求超过阈值时返回的 HTTP 状态码, 默认值是503。',
+  'PluginForm.plugin.limit-count.property.policy': '策略',
+  'PluginForm.plugin.limit-count.property.policy.extra': '用于检索和增加限制的速率限制策略',
+  'PluginForm.plugin.limit-count.property.redis_host': ' Redis 地址',
+  'PluginForm.plugin.limit-count.property.redis_host.extra': ' Redis 服务节点的地址',
+  'PluginForm.plugin.limit-count.property.redis_port': 'Redis 端口',
+  'PluginForm.plugin.limit-count.property.redis_port.extra': 'Redis 服务节点的端口',
+  'PluginForm.plugin.limit-count.property.redis_password': 'Redis 密码',
+  'PluginForm.plugin.limit-count.property.redis_password.extra': 'Redis 服务节点的密码',
+  'PluginForm.plugin.limit-count.property.redis_timeout': 'Redis 超时时间',
+  'PluginForm.plugin.limit-count.property.redis_timeout.extra':
+    'Redis 服务节点以毫秒为单位的超时时间',
+
   'PluginForm.plugin.limit-req.desc': '限制请求速度的插件,使用的是漏桶算法。',
+  'PluginForm.plugin.limit-req.property.rate': 'Rate',
+  'PluginForm.plugin.limit-req.property.rate.extra':
+    '指定的请求速率(以秒为单位),请求速率超过 rate 但没有超过 (rate + brust)的请求会被加上延时。',
+  'PluginForm.plugin.limit-req.property.burst': 'Burst',
+  'PluginForm.plugin.limit-req.property.burst.extra':
+    '请求速率超过 (rate + brust)的请求会被直接拒绝。',
+  'PluginForm.plugin.limit-req.property.key': 'Key',
+  'PluginForm.plugin.limit-req.property.key.extra': '用来做请求计数的依据',
+  'PluginForm.plugin.limit-req.property.rejected_code': '错误 HTTP 状态码',
+  'PluginForm.plugin.limit-req.property.rejected_code.extra':
+    '当请求超过阈值时返回的 HTTP 状态码, 默认值是503。',
+
   'PluginForm.plugin.mqtt-proxy.desc':
     'mqtt-proxy 只工作在流模式,它可以帮助你根据 MQTT 的 client_id 实现动态负载均衡。',
   'PluginForm.plugin.oauth.desc':
@@ -40,4 +89,9 @@ export default {
   'PluginForm.plugin.wolf-rbac.desc':
     'wolf-rbac 是一个认证及授权(rbac)插件,它需要与 consumer 一起配合才能工作。',
   'PluginForm.plugin.zipkin.desc': 'zipkin 是一个开源的服务跟踪插件。',
+  'PluginForm.plugin.node-status.desc': '暂无描述',
+  'PluginForm.plugin.serverless-pre-function.desc': '属于 serverless,会在指定阶段最开始运行。',
+  'PluginForm.plugin.serverless-post-function.desc': '属于 serverless,会在指定阶段最后运行。',
+  'PluginForm.plugin.openid-connect.desc': '暂无描述。',
+  'PluginForm.plugin.heartbeat.desc': '暂无描述',
 };
diff --git a/src/components/PluginForm/service.ts b/src/components/PluginForm/service.ts
index 447bd9a..a688e9c 100644
--- a/src/components/PluginForm/service.ts
+++ b/src/components/PluginForm/service.ts
@@ -1,7 +1,5 @@
 import { request } from 'umi';
 import { transformSchemaFromAPI } from './transformer';
 
-export const fetchList = () => request('/plugins/list');
-
 export const fetchPluginSchema = (name: string): Promise<PluginForm.PluginSchema> =>
   request(`/schema/plugins/${name}`).then((data) => transformSchemaFromAPI(data, name));
diff --git a/src/components/PluginForm/typing.d.ts b/src/components/PluginForm/typing.d.ts
index 0d1fb08..dbc106b 100644
--- a/src/components/PluginForm/typing.d.ts
+++ b/src/components/PluginForm/typing.d.ts
@@ -51,7 +51,14 @@ declare namespace PluginForm {
     };
   }
 
-  interface PluginProps {
+  type PluginCategory = 'Security' | 'Limit' | 'Log' | 'Metric' | 'Other';
+
+  type PluginMapperItem = {
+    category: PluginCategory;
+    hidden?: boolean;
+  };
+
+  interface PluginProps extends PluginMapperItem {
     name: string;
   }
 }
diff --git a/src/pages/Routes/Create.tsx b/src/pages/Routes/Create.tsx
index 02187d3..4dd8d78 100644
--- a/src/pages/Routes/Create.tsx
+++ b/src/pages/Routes/Create.tsx
@@ -2,7 +2,8 @@ import React, { useState, useEffect } from 'react';
 import { Card, Steps, Form } from 'antd';
 import { PageHeaderWrapper } from '@ant-design/pro-layout';
 
-import { createRoute, fetchRoute, updateRoute } from './service';
+import { PLUGIN_MAPPER_SOURCE } from '@/components/PluginForm/data';
+import { createRoute, fetchRoute, updateRoute, fetchPluginList } from './service';
 import Step1 from './components/Step1';
 import Step2 from './components/Step2';
 import CreateStep3 from './components/CreateStep3';
@@ -20,7 +21,13 @@ import styles from './Create.less';
 
 const { Step } = Steps;
 
-const Create: React.FC = (props) => {
+type Props = {
+  // FIXME
+  route: any;
+  match: any;
+};
+
+const Create: React.FC<Props> = (props) => {
   const [step1Data, setStep1Data] = useState(DEFAULT_STEP_1_DATA);
   const [step2Data, setStep2Data] = useState(DEFAULT_STEP_2_DATA);
   const [step3Data, setStep3Data] = useState(DEFAULT_STEP_3_DATA);
@@ -39,7 +46,33 @@ const Create: React.FC = (props) => {
     step3Data,
   };
 
-  const initRoute = (rid: number) => {
+  const setupPlugin = () => {
+    const PLUGIN_BLOCK_LIST = Object.entries(PLUGIN_MAPPER_SOURCE)
+      .filter(([, value]) => value.hidden)
+      .flat()
+      .filter((item) => typeof item === 'string');
+
+    fetchPluginList().then((data: string[]) => {
+      const names = data.filter((name) => !PLUGIN_BLOCK_LIST.includes(name));
+
+      const enabledNames = Object.keys(step3Data.plugins);
+      const disabledNames = names.filter((name) => !enabledNames.includes(name));
+
+      setStep3Data({
+        plugins: step3Data.plugins,
+        _disabledPluginList: disabledNames.map((name) => ({
+          name,
+          ...PLUGIN_MAPPER_SOURCE[name],
+        })),
+        _enabledPluginList: enabledNames.map((name) => ({
+          name,
+          ...PLUGIN_MAPPER_SOURCE[name],
+        })),
+      });
+    });
+  };
+
+  const setupRoute = (rid: number) =>
     fetchRoute(rid).then((data) => {
       form1.setFieldsValue(data.step1Data);
       setStep1Data(data.step1Data as RouteModule.Step1Data);
@@ -49,30 +82,36 @@ const Create: React.FC = (props) => {
 
       setStep3Data(data.step3Data);
     });
-  };
 
   useEffect(() => {
-    if ((props as any).route.name === 'edit') {
-      initRoute((props as any).match.params.rid);
+    console.log(props);
+    if (props.route.name === 'edit') {
+      setupRoute(props.match.params.rid).then(() => setupPlugin());
+    } else {
+      setupPlugin();
     }
   }, []);
 
   useEffect(() => {
-    if (step1Data.redirectURI !== '') {
-      if (step1Data.forceHttps) {
-        setStep1Data({ ...step1Data, redirectURI: '' });
-        setRedirect(false);
-        setStepHeader(STEP_HEADER_4);
-        return;
-      }
-      setRedirect(true);
-      setStepHeader(STEP_HEADER_2);
-    } else {
+    const { redirectURI, forceHttps } = step1Data;
+    if (redirectURI === '') {
       setRedirect(false);
       setStepHeader(STEP_HEADER_4);
+      return;
+    }
+
+    if (!forceHttps) {
+      setRedirect(true);
+      setStepHeader(STEP_HEADER_2);
+      return;
     }
+
+    setStep1Data({ ...step1Data, redirectURI: '' });
+    setRedirect(false);
+    setStepHeader(STEP_HEADER_4);
   }, [step1Data]);
 
+  // FIXME
   const onReset = () => {
     setStep1Data(DEFAULT_STEP_1_DATA);
     setStep2Data(DEFAULT_STEP_2_DATA);
@@ -101,6 +140,7 @@ const Create: React.FC = (props) => {
           <CreateStep4 data={routeData} form1={form1} form2={form2} onChange={() => {}} redirect />
         );
       }
+
       return (
         <Step2
           data={routeData}
@@ -183,13 +223,7 @@ const Create: React.FC = (props) => {
           {renderStep()}
         </Card>
       </PageHeaderWrapper>
-      <ActionBar
-        step={step}
-        redirect={redirect}
-        onChange={(nextStep) => {
-          onStepChange(nextStep);
-        }}
-      />
+      <ActionBar step={step} redirect={redirect} onChange={onStepChange} />
     </>
   );
 };
diff --git a/src/pages/Routes/components/CreateStep3/CreateStep3.tsx b/src/pages/Routes/components/CreateStep3/CreateStep3.tsx
index 8d20af4..59e08c2 100644
--- a/src/pages/Routes/components/CreateStep3/CreateStep3.tsx
+++ b/src/pages/Routes/components/CreateStep3/CreateStep3.tsx
@@ -1,8 +1,8 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
 import { SettingOutlined, LinkOutlined } from '@ant-design/icons';
 import { omit, merge } from 'lodash';
 
-import { pluginList } from '@/components/PluginForm';
+import { PLUGIN_MAPPER_SOURCE } from '@/components/PluginForm/data';
 import PanelSection from '../PanelSection';
 import PluginDrawer from './PluginDrawer';
 import PluginCard from './PluginCard';
@@ -17,38 +17,13 @@ const sectionStyle = {
 };
 
 const CreateStep3: React.FC<Props> = ({ data, disabled, onChange }) => {
-  // NOTE: Plugin in blacklist WILL NOT be shown on Step3.
-  const pluginBlackList = [
-    'basic-auth',
-    'batch-requests',
-    'grpc-transcoding',
-    'http-logger',
-    'jwt-auth',
-    'key-auth',
-    'mqtt-proxy',
-    'oauth',
-    'redirect',
-    'wolf-rbac',
-    // BUG:
-    'proxy-rewrite',
-  ];
-
-  const list = pluginList.filter(({ name }) => !pluginBlackList.includes(name));
-  const [activeList, setActiveList] = useState<PluginForm.PluginProps[]>([]);
-  const [inactiveList, setInactiveList] = useState<PluginForm.PluginProps[]>([]);
-
-  useEffect(() => {
-    const pluginKeys = Object.keys(data.step3Data.plugins || []);
-    setActiveList(list.filter((item) => pluginKeys.includes(item.name)));
-    setInactiveList(list.filter((item) => !pluginKeys.includes(item.name)));
-  }, [data.step3Data.plugins]);
-
   const [currentPlugin, setCurrentPlugin] = useState<string | undefined>();
+  const { _disabledPluginList = [], _enabledPluginList = [] } = data.step3Data;
 
   return (
     <>
       <PanelSection title="已启用" style={sectionStyle}>
-        {activeList.map(({ name }) => (
+        {_enabledPluginList.map(({ name }) => (
           <PluginCard
             name={name}
             actions={[
@@ -67,7 +42,7 @@ const CreateStep3: React.FC<Props> = ({ data, disabled, onChange }) => {
       </PanelSection>
       {!disabled && (
         <PanelSection title="未启用" style={sectionStyle}>
-          {inactiveList.map(({ name }) => (
+          {_disabledPluginList.map(({ name }) => (
             <PluginCard
               name={name}
               actions={[
@@ -89,15 +64,23 @@ const CreateStep3: React.FC<Props> = ({ data, disabled, onChange }) => {
         name={currentPlugin}
         disabled={disabled}
         initialData={currentPlugin ? data.step3Data.plugins[currentPlugin] : {}}
-        active={Boolean(activeList.find((item) => item.name === currentPlugin))}
+        active={Boolean(_enabledPluginList.find((item) => item.name === currentPlugin))}
         onActive={(name: string) => {
-          setInactiveList(inactiveList.filter((item) => item.name !== name));
-          setActiveList(activeList.concat({ name }));
+          onChange({
+            ...data.step3Data,
+            _disabledPluginList: _disabledPluginList.filter((item) => item.name !== name),
+            _enabledPluginList: _enabledPluginList.concat({ name, ...PLUGIN_MAPPER_SOURCE[name] }),
+          });
         }}
         onInactive={(name: string) => {
-          setActiveList(activeList.filter((item) => item.name !== name));
-          setInactiveList(inactiveList.concat({ name }));
-          onChange(omit({ ...data.step3Data }, `plugins.${currentPlugin}`));
+          onChange({
+            ...omit({ ...data.step3Data }, `plugins.${currentPlugin}`),
+            _disabledPluginList: _disabledPluginList.concat({
+              name,
+              ...PLUGIN_MAPPER_SOURCE[name],
+            }),
+            _enabledPluginList: _enabledPluginList.filter((item) => item.name !== name),
+          });
           setCurrentPlugin(undefined);
         }}
         onClose={() => setCurrentPlugin(undefined)}
diff --git a/src/pages/Routes/constants.ts b/src/pages/Routes/constants.ts
index c0ef624..5bcb489 100644
--- a/src/pages/Routes/constants.ts
+++ b/src/pages/Routes/constants.ts
@@ -52,6 +52,8 @@ export const DEFAULT_STEP_2_DATA: RouteModule.Step2Data = {
 
 export const DEFAULT_STEP_3_DATA: RouteModule.Step3Data = {
   plugins: {},
+  _enabledPluginList: [],
+  _disabledPluginList: [],
 };
 
 export const STEP_HEADER_2 = ['定义 API 请求', '预览'];
diff --git a/src/pages/Routes/service.ts b/src/pages/Routes/service.ts
index 9300030..9049d62 100644
--- a/src/pages/Routes/service.ts
+++ b/src/pages/Routes/service.ts
@@ -21,3 +21,5 @@ export const fetchRouteList = (wid = 0) => request(`/workspaces/${wid}/routes?pa
 
 export const removeRoute = (rid: number, wid = 0) =>
   request(`/workspaces/${wid}/routes/${rid}`, { method: 'DELETE' });
+
+export const fetchPluginList = () => request('/plugins');
diff --git a/src/pages/Routes/typing.d.ts b/src/pages/Routes/typing.d.ts
index 6b42531..aad0372 100644
--- a/src/pages/Routes/typing.d.ts
+++ b/src/pages/Routes/typing.d.ts
@@ -38,6 +38,8 @@ declare namespace RouteModule {
     plugins: {
       [name: string]: any;
     };
+    _enabledPluginList: PluginForm.PluginProps[];
+    _disabledPluginList: PluginForm.PluginProps[];
   };
 
   interface Data {