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 2021/01/07 06:10:50 UTC

[apisix-dashboard] branch v2.3-sync created (now 6990e5b)

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

juzhiyuan pushed a change to branch v2.3-sync
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git.


      at 6990e5b  feat: add plugin icon (#1220)

This branch includes the following new commits:

     new 4e4332d  fix(be): change bodyParams to string to accept any content-type (#1202)
     new 7862edc  fix: online debug body params support content-type x-www-form-urlencoded (#1201)
     new fb01721  feat: add tips when plugin type is auth and schemaType is not consumer (#1219)
     new 6bc8c8e  feat: added E2E test for plugins (#1214)
     new 6990e5b  feat: add plugin icon (#1220)

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[apisix-dashboard] 01/05: fix(be): change bodyParams to string to accept any content-type (#1202)

Posted by ju...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4e4332dd57a743fb8d151fbb0cdc343dea7f642a
Author: liuxiran <be...@126.com>
AuthorDate: Wed Jan 6 10:38:56 2021 +0800

    fix(be): change bodyParams to string to accept any content-type (#1202)
    
    * fix: change bodyParams to string to accept any content-type
    
    * fix: ci error
    
    * fix: change bodyparam to type string in e2e test case
    
    * fix: ci
    
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 api/internal/handler/route_online_debug/route_online_debug.go | 6 +++---
 api/test/e2e/route_online_debug_test.go                       | 5 +----
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/api/internal/handler/route_online_debug/route_online_debug.go b/api/internal/handler/route_online_debug/route_online_debug.go
index 18241b7..ac1c502 100644
--- a/api/internal/handler/route_online_debug/route_online_debug.go
+++ b/api/internal/handler/route_online_debug/route_online_debug.go
@@ -52,7 +52,7 @@ func (h *Handler) ApplyRoute(r *gin.Engine) {
 type ParamsInput struct {
 	URL             string              `json:"url,omitempty"`
 	RequestProtocol string              `json:"request_protocol,omitempty"`
-	BodyParams      map[string]string   `json:"body_params,omitempty"`
+	BodyParams      string              `json:"body_params,omitempty"`
 	Method          string              `json:"method,omitempty"`
 	HeaderParams    map[string][]string `json:"header_params,omitempty"`
 }
@@ -88,11 +88,11 @@ type HTTPProtocolSupport struct {
 
 func (h *HTTPProtocolSupport) RequestForwarding(c droplet.Context) (interface{}, error) {
 	paramsInput := c.Input().(*ParamsInput)
-	bodyParams, _ := json.Marshal(paramsInput.BodyParams)
+	bodyParams := paramsInput.BodyParams
 	client := &http.Client{}
 
 	client.Timeout = 5 * time.Second
-	req, err := http.NewRequest(strings.ToUpper(paramsInput.Method), paramsInput.URL, strings.NewReader(string(bodyParams)))
+	req, err := http.NewRequest(strings.ToUpper(paramsInput.Method), paramsInput.URL, strings.NewReader(bodyParams))
 	if err != nil {
 		return &data.SpecCodeResponse{StatusCode: http.StatusInternalServerError}, err
 	}
diff --git a/api/test/e2e/route_online_debug_test.go b/api/test/e2e/route_online_debug_test.go
index 4796d20..fba4f03 100644
--- a/api/test/e2e/route_online_debug_test.go
+++ b/api/test/e2e/route_online_debug_test.go
@@ -242,10 +242,7 @@ func TestRoute_Online_Debug_Route_With_Body_Params(t *testing.T) {
 				"url": "` + APISIXInternalUrl + `/hello",
 				"request_protocol": "http",
 				"method": "POST",
-				"body_params": {
-					"name": "test",
-					"desc": "online debug route with body params"
-				}
+				"body_params": "{\"name\":\"test\",\"desc\":\"online debug route with body params\"}"
 			}`,
 			Headers:      map[string]string{"Authorization": token},
 			ExpectStatus: http.StatusOK,


[apisix-dashboard] 04/05: feat: added E2E test for plugins (#1214)

Posted by ju...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6bc8c8ecaa742d796152a9d6a4bae9115d8580ba
Author: 琚致远 <ju...@apache.org>
AuthorDate: Thu Jan 7 09:42:15 2021 +0800

    feat: added E2E test for plugins (#1214)
    
    * fix(FE): delete global plugin failed (#1170)
    
    * fix: delete global plugin failed
    
    * fix: filter disable plugins
    
    * fix: update online debug api protocol validation and error msg (#1166)
    
    * fix: update validation and msg
    
    * Update api/internal/handler/route_online_debug/route_online_debug.go
    
    * fix: update refer to code review
    
    Co-authored-by: 琚致远 <ju...@apache.org>
    
    * feat(cli): Show GitHash for manager-api in branch v2.3 (backport #1162) (#1181)
    
    * fix: correct Version and GitHash output for manager-api command (#1162)
    
    * bug: fix Version and add GitHash for manager-api command
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: git hash support generating .githash for apache release
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: Add testcase for the new githash info
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: add test case for .githash content validation
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: Remove git command dependency for getting git hash
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: set VERSION to 2.3 in branch v2.3
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * fix(fe): route search with status (#1205)
    
    * fix(fe): route search with status
    
    * fix: version and status select box allowclear
    
    * fix: remove console
    
    * fix: set create_time/update_time as omitempty (#1203)
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * fix(FE): service issues (#1209)
    
    * fix: omit checks when empty
    
    * fix: desc search
    
    * fix: omit checks when empty
    
    * feat: remove desc search
    
    * feat: add create service e2e test
    
    * feat: update code
    
    * feat: update code
    
    * chore: sync json schema from Apache APISIX 2.2 (#1177)
    
    * chore: sync json schema from Apache APISIX 2.2
    
    * fix: remove schema of plugins that not enable by default
    
    * fix test cases for plugin skywalking which is not enable by default
    
    * chore: expose port for control API
    
    * fix: control API config
    
    * fix yaml format
    
    * fix CI failed
    
    * fix: log path
    
    * fix: log path
    
    Co-authored-by: 琚致远 <ju...@apache.org>
    
    * fix: well handle with malformed auth token in request header (#1206) (#1210)
    
    * fix: not panic if auth token is invalid
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * do not record the false in log
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    Co-authored-by: Joey <ma...@gmail.com>
    
    * fix: route list search query string (#1197)
    
    * fix: route list search qurey string
    
    * fix: well handle with malformed auth token in request header (#1206)
    
    * fix: not panic if auth token is invalid
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * do not record the false in log
    
    Signed-off-by: imjoey <ma...@gmail.com>
    
    * feat: add search lables e2e
    
    * feat: add search route labels testcase
    
    * feat: update code
    
    * Update selector.json
    
    * Update search-route.spec.js
    
    Co-authored-by: Joey <ma...@gmail.com>
    Co-authored-by: 琚致远 <ju...@apache.org>
    
    * feat: init cypress with plugin
    
    * style: codes format
    
    * feat: added come testcases
    
    * feat: use the correct api version
    
    * feat: added tip
    
    * feat: added tip
    
    * feat: added test cases
    
    * feat: added disable
    
    * feat: added disable
    
    * feat: added disable
    
    * style: codes format
    
    * feat: added ajv formats
    
    * feat: remove useless codes
    
    Co-authored-by: litesun <su...@apache.org>
    Co-authored-by: liuxiran <be...@126.com>
    Co-authored-by: Joey <ma...@gmail.com>
    Co-authored-by: nic-chen <jo...@163.com>
    Co-authored-by: nic-chen <33...@users.noreply.github.com>
---
 web/cypress/fixtures/plugin-dataset.json           | 1347 ++++++++++++++++++++
 .../integration/plugin/schema-smocktest.spec.js    |   85 ++
 web/cypress/integration/route/search-route.spec.js |    5 +-
 .../service/create-and-delete-service.spec.js      |   10 +-
 .../upstream/create_and_delete_upstream.spec.js    |    2 +-
 web/package.json                                   |    3 +-
 web/src/components/Plugin/PluginDetail.tsx         |   50 +-
 web/src/components/Plugin/PluginPage.tsx           |    6 +-
 web/src/pages/Plugin/PluginMarket.tsx              |    2 +-
 web/src/pages/Plugin/service.ts                    |   12 +-
 web/src/pages/Route/List.tsx                       |    8 +-
 .../Route/components/DebugViews/DebugDrawView.tsx  |   67 +-
 web/src/pages/Route/constants.ts                   |   13 +-
 web/src/pages/Route/service.ts                     |    2 +-
 web/src/pages/Route/typing.d.ts                    |    2 +-
 web/src/pages/Service/Create.tsx                   |    9 +-
 web/src/pages/Service/List.tsx                     |    2 +-
 web/src/typings.d.ts                               |    1 +
 web/yarn.lock                                      |   15 +-
 19 files changed, 1566 insertions(+), 75 deletions(-)

diff --git a/web/cypress/fixtures/plugin-dataset.json b/web/cypress/fixtures/plugin-dataset.json
new file mode 100644
index 0000000..7f1ada3
--- /dev/null
+++ b/web/cypress/fixtures/plugin-dataset.json
@@ -0,0 +1,1347 @@
+{
+  "basic-auth": [
+    {
+      "type": "consumer",
+      "shouldValid": true,
+      "data": {
+        "username": "foo",
+        "password": "bar"
+      }
+    },
+    {
+      "type": "consumer",
+      "shouldValid": false,
+      "data": {
+        "username": 123,
+        "password": "bar"
+      }
+    },
+    {
+      "type": "consumer",
+      "shouldValid": false,
+      "data": {
+        "username": "foo"
+      }
+    },
+    {
+      "type": "consumer",
+      "shouldValid": false,
+      "data": {}
+    },
+    {
+      "type": "consumer",
+      "shouldValid": false,
+      "data": "blah"
+    },
+    {
+      "shouldValid": true,
+      "data": {}
+    }
+  ],
+  "hmac-auth": [
+    {
+      "shouldValid": true,
+      "data": {}
+    }
+  ],
+  "jwt-auth": [
+    {
+      "shouldValid": true,
+      "data": {}
+    }
+  ],
+  "key-auth": [
+    {
+      "shouldValid": true,
+      "data": {}
+    }
+  ],
+  "wolf-rbac": [
+    {
+      "shouldValid": true,
+      "data": {}
+    }
+  ],
+  "api-breaker": [
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502,
+        "unhealthy": {
+          "http_statuses": [500],
+          "failures": 1
+        },
+        "healthy": {
+          "http_statuses": [200],
+          "successes": 1
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502,
+        "healthy": {}
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502,
+        "unhealthy": {}
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "break_response_code": 199,
+        "unhealthy": {
+          "http_statuses": [500, 503],
+          "failures": 3
+        },
+        "healthy": {
+          "http_statuses": [200, 206],
+          "successes": 3
+        }
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "break_response_code": 200,
+        "max_breaker_sec": -1
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "break_response_code": 200,
+        "max_breaker_sec": 40,
+        "unhealthy": {
+          "http_statuses": [500, 603],
+          "failures": 3
+        },
+        "healthy": {
+          "http_statuses": [200, 206],
+          "successes": 3
+        }
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "break_response_code": 500,
+        "unhealthy": {
+          "http_statuses": [500, 503],
+          "failures": 3
+        },
+        "healthy": {
+          "http_statuses": [206, 206],
+          "successes": 3
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 599,
+        "unhealthy": {
+          "http_statuses": [500, 503],
+          "failures": 3
+        },
+        "healthy": {
+          "http_statuses": [200, 206],
+          "successes": 3
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502,
+        "unhealthy": {
+          "failures": 3
+        },
+        "healthy": {
+          "successes": 3
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "break_response_code": 502,
+        "max_breaker_sec": 10,
+        "unhealthy": {
+          "http_statuses": [500, 503],
+          "failures": 1
+        },
+        "healthy": {
+          "successes": 3
+        }
+      }
+    }
+  ],
+  "authz-keycloak": [
+    {
+      "shouldValid": true,
+      "data": {
+        "token_endpoint": "https://efactory-security-portal.salzburgresearch.at/",
+        "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "token_endpoint": "https://efactory-security-portal.salzburgresearch.at/",
+        "permissions": ["res:customer#scopes:view"],
+        "timeout": 1000,
+        "audience": "University",
+        "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "permissions": ["res:customer#scopes:view"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "token_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token",
+        "permissions": ["course_resource#view"],
+        "audience": "course_management",
+        "grant_type": "urn:ietf:params:oauth:grant-type:uma-ticket",
+        "timeout": 3000
+      }
+    }
+  ],
+  "batch-requests": [],
+  "consumer-restriction": [
+    {
+      "shouldValid": true,
+      "data": {
+        "title": "whitelist",
+        "whitelist": ["jack1", "jack2"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "whitelist": ["jack1"],
+        "blacklist": ["jack2"]
+      }
+    }
+  ],
+  "cors": [
+    {
+      "shouldValid": true,
+      "data": {
+        "allow_origins": "",
+        "allow_methods": "",
+        "allow_headers": "",
+        "expose_headers": "",
+        "max_age": 600,
+        "allow_credential": true
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "allow_origins": "",
+        "allow_methods": "",
+        "allow_headers": "",
+        "expose_headers": "",
+        "max_age": "600",
+        "allow_credential": true
+      }
+    }
+  ],
+  "echo": [],
+  "fault-injection": [
+    {
+      "shouldValid": false,
+      "data": {
+        "abort": {
+          "http_status": 100,
+          "body": "Fault Injection!\n"
+        }
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "abort": {}
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {}
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "delay": {}
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "delay": {
+          "duration": "test"
+        }
+      }
+    }
+  ],
+  "grpc-transcode": [],
+  "http-logger": [
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "http://127.0.0.1"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "http://127.0.0.1",
+        "auth_header": "Basic 123",
+        "timeout": 3,
+        "name": "http-logger",
+        "max_retry_count": 2,
+        "retry_delay": 2,
+        "buffer_duration": 2,
+        "inactive_timeout": 2,
+        "batch_max_size": 500
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "auth_header": "Basic 123",
+        "timeout": 3,
+        "name": "http-logger",
+        "max_retry_count": 2,
+        "retry_delay": 2,
+        "buffer_duration": 2,
+        "inactive_timeout": 2,
+        "batch_max_size": 500
+      }
+    }
+  ],
+  "ip-restriction": [
+    {
+      "shouldValid": true,
+      "data": {
+        "whitelist": ["10.255.254.0/24", "192.168.0.0/16"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "whitelist": ["10.255.256.0/24", "192.168.0.0/16"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "whitelist": ["10.255.254.0/38", "192.168.0.0/16"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {}
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "blacklist": []
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "whitelist": ["172.17.40.0/24"],
+        "blacklist": ["10.255.0.0/16"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "blacklist": ["::1", "fe80::/32"]
+      }
+    }
+  ],
+  "kafka-logger": [
+    {
+      "shouldValid": true,
+      "data": {
+        "kafka_topic": "test",
+        "key": "key1",
+        "broker_list": {
+          "127.0.0.1": 3
+        }
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "kafka_topic": "test",
+        "key": "key1"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "kafka_topic": "test",
+        "key": "key1",
+        "broker_list": {
+          "127.0.0.1": 3000
+        },
+        "timeout": "10"
+      }
+    }
+  ],
+  "limit-conn": [
+    {
+      "shouldValid": true,
+      "data": {
+        "conn": 1,
+        "burst": 0,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "conn": 1,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "burst": 0,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "conn": -1,
+        "burst": 0,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "conn": 100,
+        "burst": 50,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "server_addr"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "conn": 5,
+        "burst": 1,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "http_x_real_ip"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "conn": 5,
+        "burst": 1,
+        "default_conn_delay": 0.1,
+        "rejected_code": 503,
+        "key": "http_x_forwarded_for"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "conn": 2,
+        "burst": 1,
+        "default_conn_delay": 0.1,
+        "key": "remote_addr"
+      }
+    }
+  ],
+  "limit-count": [
+    {
+      "shouldValid": true,
+      "data": { "count": 2, "time_window": 60, "rejected_code": 503, "key": "remote_addr" }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "count": 2,
+        "time_window": 60,
+        "rejected_code": 503,
+        "key": "host"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "time_window": 60,
+        "rejected_code": 503
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "count": -100,
+        "time_window": 60,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "count": 2,
+        "time_window": 60,
+        "rejected_code": 503,
+        "key": "server_addr"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "count": 2,
+        "time_window": 60,
+        "key": "remote_addr"
+      }
+    }
+  ],
+  "limit-req": [
+    {
+      "shouldValid": true,
+      "data": {
+        "rate": 1,
+        "burst": 0,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "burst": 0,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "rate": -1,
+        "burst": 0.1,
+        "rejected_code": 503,
+        "key": "remote_addr"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "rate": 1,
+        "burst": 0,
+        "key": "remote_addr"
+      }
+    }
+  ],
+  "openid-connect": [
+    {
+      "shouldValid": true,
+      "data": {
+        "client_id": "a",
+        "client_secret": "b",
+        "discovery": "c"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "client_secret": "b",
+        "discovery": "c"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "client_id": 123,
+        "client_secret": "b",
+        "discovery": "c"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+        "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+        "discovery": "http://127.0.0.1:1980/.well-known/openid-configuration",
+        "redirect_uri": "https://iresty.com",
+        "ssl_verify": false,
+        "timeout": 10,
+        "scope": "apisix"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "discovery": "http://127.0.0.1:8090/auth/realms/University/.well-known/openid-configuration",
+        "realm": "University",
+        "client_id": "course_management",
+        "client_secret": "d1ec69e9-55d2-4109-a3ea-befa071579d5",
+        "redirect_uri": "http://127.0.0.1:]] .. ngx.var.server_port .. [[/authenticated",
+        "ssl_verify": false,
+        "timeout": 10,
+        "introspection_endpoint_auth_method": "client_secret_post",
+        "introspection_endpoint": "http://127.0.0.1:8090/auth/realms/University/protocol/openid-connect/token/introspect",
+        "set_access_token_header": true,
+        "access_token_in_authorization_header": false,
+        "set_id_token_header": true,
+        "set_userinfo_header": true
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+        "client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+        "discovery": "https://samples.auth0.com/.well-known/openid-configuration",
+        "redirect_uri": "https://iresty.com",
+        "ssl_verify": false,
+        "timeout": 10,
+        "bearer_only": true,
+        "scope": "apisix"
+      }
+    }
+  ],
+  "prometheus": [
+    {
+      "shouldValid": true,
+      "data": {}
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "invalid": "invalid"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "invalid_property": 1
+      }
+    }
+  ],
+  "proxy-cache": [
+    {
+      "shouldValid": true,
+      "data": {
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": "GET",
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_key": "${uri}-cache-key",
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_bypass": "$arg_bypass",
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": "$arg_no_cache"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_key": ["$uri-", "-cache-id"],
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": true,
+        "no_cache": ["$arg_no_cache"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "cache_zone": "disk_cache_one",
+        "cache_bypass": ["$arg_bypass"],
+        "cache_method": ["GET"],
+        "cache_http_status": [200],
+        "hide_cache_headers": false,
+        "no_cache": ["$arg_no_cache"]
+      }
+    }
+  ],
+  "proxy-mirror": [
+    {
+      "shouldValid": false,
+      "data": {
+        "host": "127.0.0.1:1999"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "http://127.0.0.1"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "host": "http://127.0.0.1:1999/invalid_uri"
+      }
+    }
+  ],
+  "proxy-rewrite": [
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/apisix/home",
+        "host": "apisix.apache.org",
+        "scheme": "http"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "uri": "/apisix/home",
+        "host": "apisix.apache.org",
+        "scheme": "tcp"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/uri/plugin_proxy_rewrite",
+        "headers": {
+          "X-Api-Version": "v2"
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "regex_uri": ["^/test/(.*)/(.*)/(.*)", "/$1_$2_$3"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/hello",
+        "regex_uri": ["^/test/(.*)", "/${1}1"]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "uri": "home"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "uri": "/apisix/home",
+        "host": "apisix.apache.org",
+        "scheme": "http",
+        "invalid_att": "invalid"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/uri",
+        "headers": {
+          "x-api": "$remote_addr",
+          "name": "$arg_name",
+          "x-key": "$http_key"
+        }
+      }
+    }
+  ],
+  "redirect": [
+    {
+      "shouldValid": true,
+      "data": {
+        "ret_code": 302,
+        "uri": "/foo"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/foo"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "$uri/test/a${arg_name}c",
+        "ret_code": 302
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "/foo$$uri",
+        "ret_code": 302
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "\\$uri/foo$uri\\$uri/bar",
+        "ret_code": 301
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "uri": "https://$host$request_uri",
+        "ret_code": 301
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "http_to_https": true
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "http_to_https": true,
+        "ret_code": 302
+      }
+    }
+  ],
+  "referer-restriction": [
+    {
+      "shouldValid": true,
+      "data": {
+        "whitelist": ["*.xx.com", "yy.com"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "bypass_missing": true,
+        "whitelist": ["*.xx.com", "yy.com"]
+      }
+    }
+  ],
+  "request-id": [
+    {
+      "shouldValid": true,
+      "data": {}
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "include_in_response": "bad_type"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "header_name": "Custom-Header-Name"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "include_in_response": true
+      }
+    }
+  ],
+  "response-rewrite": [
+    {
+      "shouldValid": true,
+      "data": {
+        "body": "Hello world",
+        "headers": {
+          "X-Server-id": 3
+        }
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "status_code": 599
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "body": 2,
+        "headers": {
+          "X-Server-id": "3"
+        }
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "headers": {
+          "X-Server-id": 3,
+          "X-Server-status": "on",
+          "Content-Type": ""
+        },
+        "body": "new body\n"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "body": "new body2\n"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "headers": {
+          "Location": "https://www.apache.org"
+        },
+        "status_code": 302
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "body": "SGVsbG8K",
+        "body_base64": true
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "body": "1",
+        "body_base64": true
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "headers": {
+          "X-Server-id": 3,
+          "X-Server-status": "on",
+          "Content-Type": ""
+        },
+        "body": "new body\n"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "body": "Hello world",
+        "headers": {
+          "X-Server-id": 3
+        },
+        "invalid_att": "invalid"
+      }
+    }
+  ],
+  "serverless-post-function": [],
+  "serverless-pre-function": [],
+  "sls-logger": [
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "cn-zhangjiakou-intranet.log.aliyuncs.com",
+        "port": 10009,
+        "project": "your-project",
+        "logstore": "your-logstore",
+        "access_key_id": "your_access_key",
+        "access_key_secret": "your_access_secret"
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "host": "cn-zhangjiakou-intranet.log.aliyuncs.com",
+        "port": 10009,
+        "project": "your-project",
+        "logstore": "your-logstore",
+        "access_key_id": "your_access_key",
+        "access_key_secret": "your_access_secret",
+        "timeout": "10"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "100.100.99.135",
+        "port": 10009,
+        "project": "your_project",
+        "logstore": "your_logstore",
+        "access_key_id": "your_access_key_id",
+        "access_key_secret": "your_access_key_secret",
+        "timeout": 30000
+      }
+    }
+  ],
+  "syslog": [
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 3000
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": { "host": "127.0.0.1" }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "host": "127.0.0.1",
+        "port": "3000"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 5044
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 5044,
+        "flush_limit": 1,
+        "timeout": 1
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 5044,
+        "batch_max_size": 1
+      }
+    }
+  ],
+  "tcp-logger": [
+    {
+      "shouldValid": true,
+      "data": { "host": "127.0.0.1", "port": 3000 }
+    },
+    {
+      "shouldValid": false,
+      "data": { "port": 3000 }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 2000,
+        "timeout": "10",
+        "tls": false,
+        "tls_options": "tls options"
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 5044,
+        "tls": false
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "312.0.0.1",
+        "port": 2000,
+        "batch_max_size": 1,
+        "max_retry_count": 2,
+        "retry_delay": 0
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "host": "127.0.0.1",
+        "port": 5044,
+        "tls": false,
+        "batch_max_size": 1
+      }
+    }
+  ],
+  "traffic-split": [
+    {
+      "shouldValid": true,
+      "data": {
+        "rules": [
+          {
+            "match": [
+              {
+                "vars": [
+                  ["arg_name", "==", "jack"],
+                  ["arg_age", "!", "<", "16"]
+                ]
+              },
+              {
+                "vars": [
+                  ["arg_name", "==", "rose"],
+                  ["arg_age", "!", ">", "32"]
+                ]
+              }
+            ],
+            "weighted_upstreams": [
+              {
+                "upstream": {
+                  "name": "upstream_A",
+                  "type": "roundrobin",
+                  "nodes": { "127.0.0.1:1981": 2 },
+                  "timeout": { "connect": 15, "send": 15, "read": 15 }
+                },
+                "weight": 2
+              },
+              {
+                "upstream": {
+                  "name": "upstream_B",
+                  "type": "roundrobin",
+                  "nodes": { "127.0.0.1:1982": 2 },
+                  "timeout": { "connect": 15, "send": 15, "read": 15 }
+                },
+                "weight": 2
+              },
+              {
+                "weight": 1
+              }
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "rules": [
+          {
+            "weighted_upstreams": [
+              {
+                "upstream": {
+                  "name": "upstream_A",
+                  "type": "roundrobin",
+                  "nodes": { "127.0.0.1:1981": 2 },
+                  "timeout": { "connect": 15, "send": 15, "read": 15 }
+                },
+                "weight": 2
+              },
+              {
+                "weight": 1
+              }
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "rules": [
+          {
+            "match": [
+              {
+                "vars": ["arg_name", 123, "jack"]
+              }
+            ],
+            "weighted_upstreams": [
+              {
+                "upstream": {
+                  "name": "upstream_A",
+                  "type": "roundrobin",
+                  "nodes": {
+                    "127.0.0.1:1981": 2
+                  },
+                  "timeout": { "connect": 15, "send": 15, "read": 15 }
+                },
+                "weight": 2
+              },
+              {
+                "weight": 1
+              }
+            ]
+          }
+        ]
+      }
+    }
+  ],
+  "udp-logger": [
+    {
+      "shouldValid": true,
+      "data": { "host": "127.0.0.1", "port": 3000 }
+    },
+    {
+      "shouldValid": false,
+      "data": { "port": 3000 }
+    },
+    {
+      "shouldValid": false,
+      "data": { "host": "127.0.0.1", "port": 3000, "timeout": "10" }
+    }
+  ],
+  "uri-blocker": [
+    {
+      "shouldValid": true,
+      "data": {
+        "block_rules": [".+("]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "block_rules": ["^a", "^b"]
+      }
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "block_rules": ["aa"]
+      }
+    }
+  ],
+  "zipkin": [
+    {
+      "shouldValid": true,
+      "data": {
+        "endpoint": "http://127.0.0.1",
+        "sample_ratio": 0.001
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "endpoint": "http://127.0.0.1",
+        "sample_ratio": -0.1
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {
+        "endpoint": "http://127.0.0.1",
+        "sample_ratio": 2
+      }
+    }
+  ],
+  "request-validation": [
+    {
+      "shouldValid": true,
+      "data": {
+        "body_schema": {}
+      }
+    },
+    {
+      "shouldValid": false,
+      "data": {}
+    },
+    {
+      "shouldValid": true,
+      "data": {
+        "body_schema": {
+          "type": "object",
+          "required": ["required_payload"],
+          "properties": {
+            "required_payload": { "type": "string" },
+            "boolean_payload": { "type": "boolean" },
+            "timeouts": {
+              "type": "integer",
+              "minimum": 1,
+              "maximum": 254,
+              "default": 3
+            },
+            "req_headers": {
+              "type": "array",
+              "minItems": 1,
+              "items": {
+                "type": "string"
+              }
+            }
+          }
+        }
+      }
+    }
+  ],
+  "mqtt-proxy": []
+}
diff --git a/web/cypress/integration/plugin/schema-smocktest.spec.js b/web/cypress/integration/plugin/schema-smocktest.spec.js
new file mode 100644
index 0000000..1878fbf
--- /dev/null
+++ b/web/cypress/integration/plugin/schema-smocktest.spec.js
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+/* eslint-disable no-undef */
+
+context('smoke test for plugin schema', () => {
+  beforeEach(() => {
+    cy.login();
+
+    cy.fixture('selector.json').as('selector');
+    cy.fixture('plugin-dataset.json').as('cases');
+  });
+
+  it('should visit plugin market', function () {
+    cy.visit('/');
+    cy.contains('Plugin').click();
+    cy.contains('Create').click();
+
+    const nameSelector = '[data-cy-plugin-name]';
+    cy.get(nameSelector).then(function (cards) {
+      [...cards].forEach((card) => {
+        const name = card.innerText;
+        const cases = this.cases[name] || [];
+        cases.forEach(({ shouldValid, data, type = '' }) => {
+          /**
+           * NOTE: This test is mainly for GlobalPlugin, which is using non-consumer-type schema.
+           */
+          if (type === 'consumer') {
+            return true;
+          }
+
+          cy.contains(name)
+            .parents('.ant-card-bordered')
+            .within(() => {
+              cy.contains('Enable').click({
+                force: true,
+              });
+            });
+
+          // NOTE: wait for the Drawer to appear on the DOM
+          cy.wait(800);
+          const switchSelector = '#disable';
+          cy.get(switchSelector).click();
+
+          cy.window().then(({ codemirror }) => {
+            if (codemirror) {
+              codemirror.setValue(JSON.stringify(data));
+            }
+          });
+
+          cy.contains('Submit').click();
+
+          // NOTE: wait for the HTTP call
+          cy.wait(500);
+          if (shouldValid) {
+            const drawerSelector = '.ant-drawer-content';
+            cy.get(drawerSelector).should('not.exist');
+          } else {
+            cy.get(this.selector.notification).should('contain', 'Invalid plugin data');
+
+            cy.get('.anticon-close').click({
+              multiple: true,
+            });
+            cy.contains('Cancel').click({
+              force: true,
+            });
+          }
+        });
+      });
+    });
+  });
+});
diff --git a/web/cypress/integration/route/search-route.spec.js b/web/cypress/integration/route/search-route.spec.js
index 287b248..89a635d 100644
--- a/web/cypress/integration/route/search-route.spec.js
+++ b/web/cypress/integration/route/search-route.spec.js
@@ -104,10 +104,7 @@ context('Create and Search Route', () => {
   it('should delete the route', function () {
     cy.visit('/routes/list');
     for (let i = 0; i < 3; i += 1) {
-      cy.contains(`test${i}`)
-        .siblings()
-        .contains('Delete')
-        .click();
+      cy.contains(`test${i}`).siblings().contains('Delete').click();
       cy.contains('button', 'Confirm').click();
       cy.get(this.domSelector.notification).should('contain', 'Delete Route Successfully');
       cy.wait(300);
diff --git a/web/cypress/integration/service/create-and-delete-service.spec.js b/web/cypress/integration/service/create-and-delete-service.spec.js
index 1536951..9ee46a6 100644
--- a/web/cypress/integration/service/create-and-delete-service.spec.js
+++ b/web/cypress/integration/service/create-and-delete-service.spec.js
@@ -17,14 +17,12 @@
 /* eslint-disable no-undef */
 
 context('create and delete service ', () => {
-
   beforeEach(() => {
     // init login
     cy.login();
   });
 
   it('should create service', () => {
-
     // go to create service page
     cy.visit('/');
     cy.contains('Service').click();
@@ -38,7 +36,7 @@ context('create and delete service ', () => {
     cy.contains('Next').click();
     cy.contains('Next').click();
     cy.contains('Submit').click();
-  })
+  });
 
   it('should delete the service', () => {
     cy.visit('/');
@@ -46,12 +44,10 @@ context('create and delete service ', () => {
 
     cy.get('[title=Name]').type('service');
     cy.contains('Search').click();
-    
+
     cy.contains('service').siblings().contains('Delete').click();
     cy.contains('button', 'Confirm').click();
-    cy.fixture('selector.json').then(({
-      notification
-    }) => {
+    cy.fixture('selector.json').then(({ notification }) => {
       cy.get(notification).should('contain', 'Delete Service Successfully');
     });
   });
diff --git a/web/cypress/integration/upstream/create_and_delete_upstream.spec.js b/web/cypress/integration/upstream/create_and_delete_upstream.spec.js
index f220677..b445f0c 100644
--- a/web/cypress/integration/upstream/create_and_delete_upstream.spec.js
+++ b/web/cypress/integration/upstream/create_and_delete_upstream.spec.js
@@ -21,7 +21,7 @@ context('Create and Delete Upstream', () => {
   const sleepTime = 100; // the unit is milliseconds
   const domSelectors = {
     notification: '.ant-notification-notice-message',
-    selectItem: '.ant-select-item-option-content'
+    selectItem: '.ant-select-item-option-content',
   };
 
   beforeEach(() => {
diff --git a/web/package.json b/web/package.json
index 9016a80..02e6552 100644
--- a/web/package.json
+++ b/web/package.json
@@ -55,7 +55,8 @@
     "@rjsf/antd": "2.2.0",
     "@rjsf/core": "2.2.0",
     "@uiw/react-codemirror": "^3.0.1",
-    "ajv": "^7.0.0-rc.2",
+    "ajv": "^7.0.3",
+    "ajv-formats": "^1.5.1",
     "antd": "^4.4.0",
     "base-64": "^1.0.0",
     "classnames": "^2.2.6",
diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx
index b154985..ad76e03 100644
--- a/web/src/components/Plugin/PluginDetail.tsx
+++ b/web/src/components/Plugin/PluginDetail.tsx
@@ -15,13 +15,24 @@
  * limitations under the License.
  */
 import React, { useEffect, useRef } from 'react';
-import { Button, notification, PageHeader, Switch, Form, Select, Divider, Drawer, Alert } from 'antd';
+import {
+  Button,
+  notification,
+  PageHeader,
+  Switch,
+  Form,
+  Select,
+  Divider,
+  Drawer,
+  Alert,
+} from 'antd';
 import { useIntl } from 'umi';
 import CodeMirror from '@uiw/react-codemirror';
 import { js_beautify } from 'js-beautify';
 import { LinkOutlined } from '@ant-design/icons';
-
 import Ajv, { DefinedError } from 'ajv';
+import addFormats from 'ajv-formats';
+
 import { fetchSchema } from './service';
 
 type Props = {
@@ -29,7 +40,7 @@ type Props = {
   type?: 'global' | 'scoped';
   schemaType: PluginComponent.Schema;
   initialData: object;
-  pluginList: PluginComponent.Meta[],
+  pluginList: PluginComponent.Meta[];
   readonly?: boolean;
   visible: boolean;
   onClose?: () => void;
@@ -37,6 +48,7 @@ type Props = {
 };
 
 const ajv = new Ajv();
+addFormats(ajv);
 
 const FORM_ITEM_LAYOUT = {
   labelCol: {
@@ -69,17 +81,20 @@ const PluginDetail: React.FC<Props> = ({
   pluginList = [],
   readonly = false,
   initialData = {},
-  onClose = () => { },
-  onChange = () => { },
+  onClose = () => {},
+  onChange = () => {},
 }) => {
   const { formatMessage } = useIntl();
   const [form] = Form.useForm();
   const ref = useRef<any>(null);
   const data = initialData[name] || {};
-  const pluginType = pluginList.find(item => item.name === name)?.type
+  const pluginType = pluginList.find((item) => item.name === name)?.type;
 
   useEffect(() => {
-    form.setFieldsValue({ disable: initialData[name] && !initialData[name].disable });
+    form.setFieldsValue({
+      disable: initialData[name] && !initialData[name].disable,
+      scope: 'global',
+    });
   }, []);
 
   const validateData = (pluginName: string, value: PluginComponent.Data) => {
@@ -92,7 +107,6 @@ const PluginDetail: React.FC<Props> = ({
         } else {
           injectDisableProperty(schema);
         }
-
         const validate = ajv.compile(schema);
         if (validate(value)) {
           resolve(value);
@@ -198,8 +212,8 @@ const PluginDetail: React.FC<Props> = ({
           </Form.Item>
           {type === 'global' && (
             <Form.Item label="Scope" name="scope">
-              <Select disabled defaultValue="Global">
-                <Select.Option value="Global">Global</Select.Option>
+              <Select disabled>
+                <Select.Option value="global">Global</Select.Option>
               </Select>
             </Form.Item>
           )}
@@ -208,8 +222,12 @@ const PluginDetail: React.FC<Props> = ({
         <PageHeader
           title=""
           subTitle={
-            (pluginType === 'auth' && schemaType !== 'consumer') ? <Alert message={`${name} does not require configuration`} type="warning" />
-              : <>Current plugin: {name}</>}
+            pluginType === 'auth' && schemaType !== 'consumer' ? (
+              <Alert message={`${name} does not require configuration`} type="warning" />
+            ) : (
+              <>Current plugin: {name}</>
+            )
+          }
           ghost={false}
           extra={[
             <Button
@@ -228,7 +246,13 @@ const PluginDetail: React.FC<Props> = ({
           ]}
         />
         <CodeMirror
-          ref={ref}
+          ref={(codemirror) => {
+            ref.current = codemirror;
+            if (codemirror) {
+              // NOTE: for debug & test
+              window.codemirror = codemirror.editor;
+            }
+          }}
           value={JSON.stringify(data, null, 2)}
           options={{
             mode: 'json-ld',
diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx
index f6d45a4..ec11987 100644
--- a/web/src/components/Plugin/PluginPage.tsx
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -117,8 +117,10 @@ const PluginPage: React.FC<Props> = ({
                     textAlign: 'center',
                   }}
                   title={[
-                    <div style={{ width: '100%', textAlign: 'center' }}>
-                      <span key={2}>{item.name}</span>
+                    <div style={{ width: '100%', textAlign: 'center' }} key={1}>
+                      <span key={2} data-cy-plugin-name={item.name}>
+                        {item.name}
+                      </span>
                     </div>,
                   ]}
                   style={{ height: 258, width: 200 }}
diff --git a/web/src/pages/Plugin/PluginMarket.tsx b/web/src/pages/Plugin/PluginMarket.tsx
index ac3a4e1..dadbdcf 100644
--- a/web/src/pages/Plugin/PluginMarket.tsx
+++ b/web/src/pages/Plugin/PluginMarket.tsx
@@ -32,7 +32,7 @@ const PluginMarket: React.FC = () => {
       });
       setInitialData(plugins);
     });
-  }
+  };
 
   useEffect(() => {
     initPageData();
diff --git a/web/src/pages/Plugin/service.ts b/web/src/pages/Plugin/service.ts
index a11e2ad..7c51d03 100644
--- a/web/src/pages/Plugin/service.ts
+++ b/web/src/pages/Plugin/service.ts
@@ -27,11 +27,13 @@ export const fetchList = (): Promise<{
       plugins: Record<string, any>;
     };
   }>(`/global_rules/${DEFAULT_GLOBAL_RULE_ID}`).then(({ data }) => {
-    const plugins = Object.entries(data.plugins || {}).filter(([, value]) => !value.disable).map(([name, value]) => ({
-      id: name,
-      name,
-      value,
-    }));
+    const plugins = Object.entries(data.plugins || {})
+      .filter(([, value]) => !value.disable)
+      .map(([name, value]) => ({
+        id: name,
+        name,
+        value,
+      }));
 
     return {
       data: plugins,
diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx
index 466fdd6..43983aa 100644
--- a/web/src/pages/Route/List.tsx
+++ b/web/src/pages/Route/List.tsx
@@ -199,8 +199,12 @@ const Page: React.FC = () => {
 
         return (
           <Select style={{ width: '100%' }} allowClear>
-            <option key={RouteStatus.Offline} value={RouteStatus.Offline}>{formatMessage({ id: 'page.route.unpublished' })}</option>
-            <option key={RouteStatus.Publish} value={RouteStatus.Publish}>{formatMessage({ id: 'page.route.published' })}</option>
+            <option key={RouteStatus.Offline} value={RouteStatus.Offline}>
+              {formatMessage({ id: 'page.route.unpublished' })}
+            </option>
+            <option key={RouteStatus.Publish} value={RouteStatus.Publish}>
+              {formatMessage({ id: 'page.route.published' })}
+            </option>
           </Select>
         );
       },
diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
index 9180fe6..18b3d0e 100644
--- a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
+++ b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
@@ -53,7 +53,9 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
   const bodyCodeMirrorRef = useRef<any>(null);
   const [bodyType, setBodyType] = useState('none');
   const methodWithoutBody = ['GET', 'HEAD'];
-  const [bodyCodeMirrorMode, setBodyCodeMirrorMode] = useState(DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED[0].mode)
+  const [bodyCodeMirrorMode, setBodyCodeMirrorMode] = useState(
+    DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED[0].mode,
+  );
 
   enum DebugBodyType {
     None = 0,
@@ -84,7 +86,7 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
         transformDataForm = (formData || [])
           .filter((data) => data.check)
           .map((data) => {
-            return `${data.key}=${data.value}`
+            return `${data.key}=${data.value}`;
           });
 
         return transformDataForm.join('&');
@@ -265,37 +267,44 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
             </TabPane>
             {showBodyTab && (
               <TabPane tab={formatMessage({ id: 'page.route.TabPane.bodyParams' })} key="body">
-                <Radio.Group onChange={(e) => {setBodyType(e.target.value)}} value={bodyType}>
-                  {
-                    DEBUG_BODY_TYPE_SUPPORTED.map((type) => (
-                      <Radio value={type} key={type}>{type}</Radio>
-                    ))
-                  }
-                </Radio.Group>
-                {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && <Select
-                  size="small"
-                  onChange={(value) => {
-                    setBodyCodeMirrorMode(value)
+                <Radio.Group
+                  onChange={(e) => {
+                    setBodyType(e.target.value);
                   }}
-                  style={{ width: 100 }}
-                  defaultValue={bodyCodeMirrorMode}>
-                  {
-                    DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED.map((modeObj) =>(
-                    <Select.Option key={modeObj.name} value={modeObj.mode}>{modeObj.name}</Select.Option>
-                    ))
-                  }
-                </Select>}
-                <div style={{marginTop: 16}}>
-                  {
-                    (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] || bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.Json]) &&
+                  value={bodyType}
+                >
+                  {DEBUG_BODY_TYPE_SUPPORTED.map((type) => (
+                    <Radio value={type} key={type}>
+                      {type}
+                    </Radio>
+                  ))}
+                </Radio.Group>
+                {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && (
+                  <Select
+                    size="small"
+                    onChange={(value) => {
+                      setBodyCodeMirrorMode(value);
+                    }}
+                    style={{ width: 100 }}
+                    defaultValue={bodyCodeMirrorMode}
+                  >
+                    {DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED.map((modeObj) => (
+                      <Select.Option key={modeObj.name} value={modeObj.mode}>
+                        {modeObj.name}
+                      </Select.Option>
+                    ))}
+                  </Select>
+                )}
+                <div style={{ marginTop: 16 }}>
+                  {(bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] ||
+                    bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.Json]) && (
                     <DebugParamsView form={bodyForm} />
-                  }
+                  )}
 
-                  {
-                    (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput]) &&
+                  {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && (
                     <Form>
                       <Form.Item>
-                          <CodeMirror
+                        <CodeMirror
                           ref={bodyCodeMirrorRef}
                           height={250}
                           options={{
@@ -310,7 +319,7 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
                         />
                       </Form.Item>
                     </Form>
-                  }
+                  )}
                 </div>
               </TabPane>
             )}
diff --git a/web/src/pages/Route/constants.ts b/web/src/pages/Route/constants.ts
index 22b2e61..10a083c 100644
--- a/web/src/pages/Route/constants.ts
+++ b/web/src/pages/Route/constants.ts
@@ -105,7 +105,16 @@ export const DEFAULT_DEBUG_AUTH_FORM_DATA = {
   authType: 'none',
 };
 
-export const DEBUG_BODY_TYPE_SUPPORTED: RouteModule.DebugBodyType[]= ['none', 'x-www-form-urlencoded','json','raw input'];
+export const DEBUG_BODY_TYPE_SUPPORTED: RouteModule.DebugBodyType[] = [
+  'none',
+  'x-www-form-urlencoded',
+  'json',
+  'raw input',
+];
 
 // Note: codemirror mode: apl for text; javascript for json(need to format); xml for xml;
-export const DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED = [{name: 'Json', mode: 'javascript'}, {name: 'Text', mode: 'apl'}, {name: 'XML', mode:'xml'}]
+export const DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED = [
+  { name: 'Json', mode: 'javascript' },
+  { name: 'Text', mode: 'apl' },
+  { name: 'XML', mode: 'xml' },
+];
diff --git a/web/src/pages/Route/service.ts b/web/src/pages/Route/service.ts
index 7ae96d0..95b7392 100644
--- a/web/src/pages/Route/service.ts
+++ b/web/src/pages/Route/service.ts
@@ -49,7 +49,7 @@ export const fetchList = ({ current = 1, pageSize = 10, ...res }) => {
       label: labels.concat(API_VERSION).join(','),
       page: current,
       page_size: pageSize,
-      status
+      status,
     },
   }).then(({ data }) => {
     return {
diff --git a/web/src/pages/Route/typing.d.ts b/web/src/pages/Route/typing.d.ts
index 8ae302f..2d19fae 100644
--- a/web/src/pages/Route/typing.d.ts
+++ b/web/src/pages/Route/typing.d.ts
@@ -274,7 +274,7 @@ declare namespace RouteModule {
   type DebugBodyType = 'none' | 'x-www-form-urlencoded' | 'json' | 'raw input';
   type DebugDodyViewProps = {
     form: FormInstance;
-    changeBodyParamsType:(type: DebugBodyType) => void;
+    changeBodyParamsType: (type: DebugBodyType) => void;
     codeMirrorRef: any;
   };
   type DebugDrawProps = {
diff --git a/web/src/pages/Service/Create.tsx b/web/src/pages/Service/Create.tsx
index 15dc252..c85849d 100644
--- a/web/src/pages/Service/Create.tsx
+++ b/web/src/pages/Service/Create.tsx
@@ -133,7 +133,14 @@ const Page: React.FC = (props) => {
           {step === 2 && (
             <PluginPage initialData={plugins} onChange={setPlugins} schemaType="route" />
           )}
-          {step === 3 && <Preview upstreamForm={upstreamForm} upstreamRef={upstreamRef} form={form} plugins={plugins} />}
+          {step === 3 && (
+            <Preview
+              upstreamForm={upstreamForm}
+              upstreamRef={upstreamRef}
+              form={form}
+              plugins={plugins}
+            />
+          )}
         </Card>
       </PageHeaderWrapper>
       <ActionBar step={step} lastStep={3} onChange={onStepChange} withResultView />
diff --git a/web/src/pages/Service/List.tsx b/web/src/pages/Service/List.tsx
index 6fd28fc..ed17347 100644
--- a/web/src/pages/Service/List.tsx
+++ b/web/src/pages/Service/List.tsx
@@ -40,7 +40,7 @@ const Page: React.FC = () => {
     {
       title: formatMessage({ id: 'component.global.description' }),
       dataIndex: 'desc',
-      hideInSearch: true
+      hideInSearch: true,
     },
     {
       title: formatMessage({ id: 'component.global.operation' }),
diff --git a/web/src/typings.d.ts b/web/src/typings.d.ts
index 691ae14..ae6769a 100644
--- a/web/src/typings.d.ts
+++ b/web/src/typings.d.ts
@@ -43,6 +43,7 @@ interface Window {
     fieldsObject: GAFieldsObject | string,
   ) => void;
   reloadAuthorized: () => void;
+  codemirror: Record<string, any>;
 }
 
 declare let ga: Function;
diff --git a/web/yarn.lock b/web/yarn.lock
index 93ed598..dc98253 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -3833,6 +3833,13 @@ ajv-errors@^1.0.0:
   resolved "https://registry.npm.taobao.org/ajv-errors/download/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
   integrity sha1-81mGrOuRr63sQQL72FAUlQzvpk0=
 
+ajv-formats@^1.5.1:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-1.5.1.tgz#0f301b1b3846182f224cc563fc0a032daafb7dab"
+  integrity sha512-s1RBVF4HZd2UjGkb6t6uWoXjf6o7j7dXPQIL7vprcIT/67bTD6+5ocsU0UKShS2qWxueGDWuGfKHfOxHWrlTQg==
+  dependencies:
+    ajv "^7.0.0"
+
 ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
   version "3.5.2"
   resolved "https://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-3.5.2.tgz?cache=0&sync_timestamp=1595906977498&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fajv-keywords%2Fdownload%2Fajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
@@ -3848,10 +3855,10 @@ 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@^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"
-  integrity sha512-D2iqHvbT3lszv5KSsTvJL9PSPf/2/s45i68vLXJmT124cxK/JOoOFyo/QnrgMKa2FHlVaMIsp1ZN1P4EH3bCKw==
+ajv@^7.0.0, ajv@^7.0.3:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2"
+  integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==
   dependencies:
     fast-deep-equal "^3.1.1"
     json-schema-traverse "^1.0.0"


[apisix-dashboard] 02/05: fix: online debug body params support content-type x-www-form-urlencoded (#1201)

Posted by ju...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7862edc75058f405fd6cdb13282a1011748683cd
Author: liuxiran <be...@126.com>
AuthorDate: Wed Jan 6 12:32:38 2021 +0800

    fix: online debug body params support content-type x-www-form-urlencoded (#1201)
    
    * fix: online debug body params support content-type x-www-form-urlencoded
    
    * fix: body code mirror support different mode
    
    * fix: use enum instead of real string
    
    * fix: lint error
    
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 .../Route/components/DebugViews/DebugDrawView.tsx  | 108 ++++++++++++++++++---
 .../pages/Route/components/DebugViews/index.less   |  20 ++--
 web/src/pages/Route/constants.ts                   |   6 ++
 web/src/pages/Route/typing.d.ts                    |   8 +-
 4 files changed, 117 insertions(+), 25 deletions(-)

diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
index 5991fbf..9180fe6 100644
--- a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
+++ b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx
@@ -14,8 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import React, { useEffect, useState } from 'react';
-import { Input, Select, Card, Tabs, Form, Drawer, Spin, notification } from 'antd';
+import React, { useEffect, useState, useRef } from 'react';
+import { Input, Select, Card, Tabs, Form, Drawer, Spin, notification, Radio } from 'antd';
 import { useIntl } from 'umi';
 import CodeMirror from '@uiw/react-codemirror';
 import { PanelSection } from '@api7-dashboard/ui';
@@ -27,6 +27,8 @@ import {
   DEFAULT_DEBUG_PARAM_FORM_DATA,
   DEFAULT_DEBUG_AUTH_FORM_DATA,
   PROTOCOL_SUPPORTED,
+  DEBUG_BODY_TYPE_SUPPORTED,
+  DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED,
 } from '../../constants';
 import { DebugParamsView, AuthenticationView } from '.';
 import { debugRoute } from '../../service';
@@ -48,7 +50,17 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
   const [responseCode, setResponseCode] = useState<string>();
   const [loading, setLoading] = useState(false);
   const [codeMirrorHeight, setCodeMirrorHeight] = useState<number | string>(50);
+  const bodyCodeMirrorRef = useRef<any>(null);
+  const [bodyType, setBodyType] = useState('none');
   const methodWithoutBody = ['GET', 'HEAD'];
+  const [bodyCodeMirrorMode, setBodyCodeMirrorMode] = useState(DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED[0].mode)
+
+  enum DebugBodyType {
+    None = 0,
+    FormUrlencoded,
+    Json,
+    RawInput,
+  }
 
   const resetForms = () => {
     queryForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA);
@@ -56,24 +68,44 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
     headerForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA);
     authForm.setFieldsValue(DEFAULT_DEBUG_AUTH_FORM_DATA);
     setResponseCode(`${formatMessage({ id: 'page.route.debug.showResultAfterSendRequest' })}`);
+    setBodyType(DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.None]);
   };
 
   useEffect(() => {
     resetForms();
   }, []);
 
-  const transformBodyParamsFormData = (formData: RouteModule.debugRequestParamsFormData[]) => {
-    let transformData = {};
-    (formData || [])
-      .filter((data) => data.check)
-      .forEach((data) => {
-        transformData = {
-          ...transformData,
-          [data.key]: data.value,
-        };
-      });
+  const transformBodyParamsFormData = () => {
+    let transformDataForm: string[];
+    let transformDataJson: object;
+    const formData: RouteModule.debugRequestParamsFormData[] = bodyForm.getFieldsValue().params;
+    switch (bodyType) {
+      case 'x-www-form-urlencoded':
+        transformDataForm = (formData || [])
+          .filter((data) => data.check)
+          .map((data) => {
+            return `${data.key}=${data.value}`
+          });
 
-    return transformData;
+        return transformDataForm.join('&');
+      case 'json':
+        transformDataJson = {};
+        (formData || [])
+          .filter((data) => data.check)
+          .forEach((data) => {
+            transformDataJson = {
+              ...transformDataJson,
+              [data.key]: data.value,
+            };
+          });
+
+        return JSON.stringify(transformDataJson);
+      case 'raw input':
+        return bodyCodeMirrorRef.current.editor.getValue();
+      case 'none':
+      default:
+        return undefined;
+    }
   };
 
   const transformHeaderAndQueryParamsFormData = (
@@ -128,7 +160,7 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
       return;
     }
     const queryFormData = transformHeaderAndQueryParamsFormData(queryForm.getFieldsValue().params);
-    const bodyFormData = transformBodyParamsFormData(bodyForm.getFieldsValue().params);
+    const bodyFormData = transformBodyParamsFormData();
     const pureHeaderFormData = transformHeaderAndQueryParamsFormData(
       headerForm.getFieldsValue().params,
     );
@@ -233,7 +265,53 @@ const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => {
             </TabPane>
             {showBodyTab && (
               <TabPane tab={formatMessage({ id: 'page.route.TabPane.bodyParams' })} key="body">
-                <DebugParamsView form={bodyForm} />
+                <Radio.Group onChange={(e) => {setBodyType(e.target.value)}} value={bodyType}>
+                  {
+                    DEBUG_BODY_TYPE_SUPPORTED.map((type) => (
+                      <Radio value={type} key={type}>{type}</Radio>
+                    ))
+                  }
+                </Radio.Group>
+                {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && <Select
+                  size="small"
+                  onChange={(value) => {
+                    setBodyCodeMirrorMode(value)
+                  }}
+                  style={{ width: 100 }}
+                  defaultValue={bodyCodeMirrorMode}>
+                  {
+                    DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED.map((modeObj) =>(
+                    <Select.Option key={modeObj.name} value={modeObj.mode}>{modeObj.name}</Select.Option>
+                    ))
+                  }
+                </Select>}
+                <div style={{marginTop: 16}}>
+                  {
+                    (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] || bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.Json]) &&
+                    <DebugParamsView form={bodyForm} />
+                  }
+
+                  {
+                    (bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput]) &&
+                    <Form>
+                      <Form.Item>
+                          <CodeMirror
+                          ref={bodyCodeMirrorRef}
+                          height={250}
+                          options={{
+                            mode: bodyCodeMirrorMode,
+                            readOnly: '',
+                            lineWrapping: true,
+                            lineNumbers: true,
+                            showCursorWhenSelecting: true,
+                            autofocus: true,
+                            scrollbarStyle: null,
+                          }}
+                        />
+                      </Form.Item>
+                    </Form>
+                  }
+                </div>
               </TabPane>
             )}
           </Tabs>
diff --git a/web/src/pages/Route/components/DebugViews/index.less b/web/src/pages/Route/components/DebugViews/index.less
index f9cb5dc..65ee10b 100644
--- a/web/src/pages/Route/components/DebugViews/index.less
+++ b/web/src/pages/Route/components/DebugViews/index.less
@@ -24,21 +24,23 @@
     .ant-drawer-body {
       padding: 16px;
     }
-    .ant-radio-wrapper {
-      display: block;
-      height: 30px;
-      line-height: 30px;
-    }
     .ant-form-item {
       margin-bottom: 8px;
     }
-    .ant-radio-group {
-      margin-right: 16px;
-      border-right: 1px solid #e0e0e0;
-    }
   }
   .authForm {
     display: flex;
     margin: 16px;
+    :global {
+      .ant-radio-wrapper {
+        display: block;
+        height: 30px;
+        line-height: 30px;
+      }
+      .ant-radio-group {
+        margin-right: 16px;
+        border-right: 1px solid #e0e0e0;
+      }
+    }
   }
 }
diff --git a/web/src/pages/Route/constants.ts b/web/src/pages/Route/constants.ts
index 1a8dc46..22b2e61 100644
--- a/web/src/pages/Route/constants.ts
+++ b/web/src/pages/Route/constants.ts
@@ -98,8 +98,14 @@ export const DEFAULT_DEBUG_PARAM_FORM_DATA = {
       value: '',
     },
   ],
+  type: 'json',
 };
 
 export const DEFAULT_DEBUG_AUTH_FORM_DATA = {
   authType: 'none',
 };
+
+export const DEBUG_BODY_TYPE_SUPPORTED: RouteModule.DebugBodyType[]= ['none', 'x-www-form-urlencoded','json','raw input'];
+
+// Note: codemirror mode: apl for text; javascript for json(need to format); xml for xml;
+export const DEBUG_BODY_CODEMIRROR_MODE_SUPPORTED = [{name: 'Json', mode: 'javascript'}, {name: 'Text', mode: 'apl'}, {name: 'XML', mode:'xml'}]
diff --git a/web/src/pages/Route/typing.d.ts b/web/src/pages/Route/typing.d.ts
index 60afe5f..8ae302f 100644
--- a/web/src/pages/Route/typing.d.ts
+++ b/web/src/pages/Route/typing.d.ts
@@ -251,7 +251,7 @@ declare namespace RouteModule {
   // TODO: grpc and websocket
   type debugRequest = {
     url: string;
-    request_protocol: 'http' | 'https' | 'grpc' | 'websocket';
+    request_protocol: RequestProtocol | 'grpc';
     method: string;
     body_params?: any;
     header_params?: any;
@@ -271,6 +271,12 @@ declare namespace RouteModule {
   type DebugViewProps = {
     form: FormInstance;
   };
+  type DebugBodyType = 'none' | 'x-www-form-urlencoded' | 'json' | 'raw input';
+  type DebugDodyViewProps = {
+    form: FormInstance;
+    changeBodyParamsType:(type: DebugBodyType) => void;
+    codeMirrorRef: any;
+  };
   type DebugDrawProps = {
     visible: boolean;
     onClose(): void;


[apisix-dashboard] 03/05: feat: add tips when plugin type is auth and schemaType is not consumer (#1219)

Posted by ju...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit fb017217ee1b2def60b116cdcab84a07a460b151
Author: litesun <su...@apache.org>
AuthorDate: Wed Jan 6 14:17:55 2021 +0800

    feat: add tips when plugin type is auth and schemaType is not consumer (#1219)
    
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 web/src/components/Plugin/PluginDetail.tsx | 17 +++++++++++------
 web/src/components/Plugin/PluginPage.tsx   |  1 +
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx
index 2ff336c..b154985 100644
--- a/web/src/components/Plugin/PluginDetail.tsx
+++ b/web/src/components/Plugin/PluginDetail.tsx
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 import React, { useEffect, useRef } from 'react';
-import { Button, notification, PageHeader, Switch, Form, Select, Divider, Drawer } from 'antd';
+import { Button, notification, PageHeader, Switch, Form, Select, Divider, Drawer, Alert } from 'antd';
 import { useIntl } from 'umi';
 import CodeMirror from '@uiw/react-codemirror';
 import { js_beautify } from 'js-beautify';
@@ -29,6 +29,7 @@ type Props = {
   type?: 'global' | 'scoped';
   schemaType: PluginComponent.Schema;
   initialData: object;
+  pluginList: PluginComponent.Meta[],
   readonly?: boolean;
   visible: boolean;
   onClose?: () => void;
@@ -65,15 +66,17 @@ const PluginDetail: React.FC<Props> = ({
   type = 'scoped',
   schemaType = 'route',
   visible,
+  pluginList = [],
   readonly = false,
   initialData = {},
-  onClose = () => {},
-  onChange = () => {},
+  onClose = () => { },
+  onChange = () => { },
 }) => {
   const { formatMessage } = useIntl();
   const [form] = Form.useForm();
   const ref = useRef<any>(null);
-  const data = initialData[name];
+  const data = initialData[name] || {};
+  const pluginType = pluginList.find(item => item.name === name)?.type
 
   useEffect(() => {
     form.setFieldsValue({ disable: initialData[name] && !initialData[name].disable });
@@ -149,7 +152,7 @@ const PluginDetail: React.FC<Props> = ({
         placement="right"
         closable={false}
         onClose={onClose}
-        width={600}
+        width={700}
         footer={
           <div style={{ display: 'flex', justifyContent: 'space-between' }}>
             {' '}
@@ -204,7 +207,9 @@ const PluginDetail: React.FC<Props> = ({
         <Divider orientation="left">Data Editor</Divider>
         <PageHeader
           title=""
-          subTitle={`Current Plugin: ${name}`}
+          subTitle={
+            (pluginType === 'auth' && schemaType !== 'consumer') ? <Alert message={`${name} does not require configuration`} type="warning" />
+              : <>Current plugin: {name}</>}
           ghost={false}
           extra={[
             <Button
diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx
index cde2897..f6d45a4 100644
--- a/web/src/components/Plugin/PluginPage.tsx
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -140,6 +140,7 @@ const PluginPage: React.FC<Props> = ({
         visible={name !== NEVER_EXIST_PLUGIN_FLAG}
         schemaType={schemaType}
         initialData={initialData}
+        pluginList={pluginList}
         onClose={() => {
           setName(NEVER_EXIST_PLUGIN_FLAG);
         }}


[apisix-dashboard] 05/05: feat: add plugin icon (#1220)

Posted by ju...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6990e5b0033172afc4c8f29cf9cd978c8e6bb831
Author: litesun <su...@apache.org>
AuthorDate: Thu Jan 7 11:27:08 2021 +0800

    feat: add plugin icon (#1220)
    
    * feat: add default plugin img
    
    * feat: add plugin edit drawer tips
    
    * feat: change icon opacity to 0.2
    
    * feat: add plugin icon
    
    * feat: update style
    
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 web/public/static/default-plugin.png     | Bin 0 -> 2244 bytes
 web/src/components/Plugin/PluginPage.tsx |  40 ++++++--
 web/src/components/Plugin/data.tsx       | 152 ++-----------------------------
 web/src/components/Plugin/index.ts       |   2 +-
 web/src/pages/Plugin/List.tsx            |   8 +-
 web/src/pages/Plugin/service.ts          |   6 ++
 6 files changed, 51 insertions(+), 157 deletions(-)

diff --git a/web/public/static/default-plugin.png b/web/public/static/default-plugin.png
new file mode 100644
index 0000000..422d7dd
Binary files /dev/null and b/web/public/static/default-plugin.png differ
diff --git a/web/src/components/Plugin/PluginPage.tsx b/web/src/components/Plugin/PluginPage.tsx
index ec11987..a0e214e 100644
--- a/web/src/components/Plugin/PluginPage.tsx
+++ b/web/src/components/Plugin/PluginPage.tsx
@@ -21,6 +21,8 @@ import { orderBy } from 'lodash';
 
 import PluginDetail from './PluginDetail';
 import { fetchList } from './service';
+import { PLUGIN_ICON_LIST } from './data'
+import defaultPluginImg from '../../../public/static/default-plugin.png';
 
 type Props = {
   readonly?: boolean;
@@ -48,7 +50,7 @@ const PluginPage: React.FC<Props> = ({
   initialData = {},
   schemaType = 'route',
   type = 'scoped',
-  onChange = () => {},
+  onChange = () => { },
 }) => {
   const [pluginList, setPluginList] = useState<PluginComponent.Meta[]>([]);
   const [name, setName] = useState<string>(NEVER_EXIST_PLUGIN_FLAG);
@@ -70,7 +72,18 @@ const PluginPage: React.FC<Props> = ({
   }, []);
 
   const PluginList = () => (
-    <>
+    <><style>
+      {`
+      .ant-card-body .icon {
+          width: 5em;
+          height: 5em;
+          margin-right: 0;
+          overflow: hidden;
+          vertical-align: -0.15em;
+          fill: currentColor;
+        }`
+      }
+    </style>
       <Sider theme="light">
         <Anchor offsetTop={150}>
           {/* eslint-disable-next-line no-shadow */}
@@ -110,12 +123,6 @@ const PluginPage: React.FC<Props> = ({
                       Enable
                     </Button>,
                   ]}
-                  bodyStyle={{
-                    height: 151,
-                    display: 'flex',
-                    justifyContent: 'center',
-                    textAlign: 'center',
-                  }}
                   title={[
                     <div style={{ width: '100%', textAlign: 'center' }} key={1}>
                       <span key={2} data-cy-plugin-name={item.name}>
@@ -123,8 +130,21 @@ const PluginPage: React.FC<Props> = ({
                       </span>
                     </div>,
                   ]}
-                  style={{ height: 258, width: 200 }}
-                />
+                  bodyStyle={{
+                    minHeight: 151,
+                    display: 'flex',
+                    justifyContent: 'center',
+                    alignItems: 'center'
+                  }}
+                  style={{ width: 200 }}
+                >
+                  {Boolean(PLUGIN_ICON_LIST[item.name]) && PLUGIN_ICON_LIST[item.name]}
+                  {Boolean(!PLUGIN_ICON_LIST[item.name]) && <img
+                    alt="pluginImg"
+                    src={defaultPluginImg}
+                    style={{ width: 50, height: 50, opacity: 0.2 }}
+                  />}
+                </Card>
               ))}
             </PanelSection>
           );
diff --git a/web/src/components/Plugin/data.tsx b/web/src/components/Plugin/data.tsx
index 40b1d60..75baecb 100644
--- a/web/src/components/Plugin/data.tsx
+++ b/web/src/components/Plugin/data.tsx
@@ -18,149 +18,11 @@ 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,
-  },
+export const PLUGIN_ICON_LIST: Record<string, any> = {
+  prometheus: <IconFont name="iconPrometheus_software_logo" />,
+  skywalking: <IconFont name="iconskywalking" />,
+  'jwt-auth': <IconFont name="iconjwt-3" />,
+  'authz-keycloak': <IconFont name="iconkeycloak_icon_32px" />,
+  'openid-connect': <IconFont name="iconicons8-openid" />,
+  'kafka-logger': <IconFont name="iconApache_kafka" />,
 };
diff --git a/web/src/components/Plugin/index.ts b/web/src/components/Plugin/index.ts
index 940c9f0..cee617d 100644
--- a/web/src/components/Plugin/index.ts
+++ b/web/src/components/Plugin/index.ts
@@ -15,4 +15,4 @@
  * limitations under the License.
  */
 export { default } from './PluginPage';
-export { PLUGIN_MAPPER_SOURCE } from './data';
+export { PLUGIN_ICON_LIST } from './data';
diff --git a/web/src/pages/Plugin/List.tsx b/web/src/pages/Plugin/List.tsx
index 2e9c18f..3a73cc1 100644
--- a/web/src/pages/Plugin/List.tsx
+++ b/web/src/pages/Plugin/List.tsx
@@ -24,16 +24,21 @@ import { omit } from 'lodash';
 
 import PluginDetail from '@/components/Plugin/PluginDetail';
 
-import { fetchList, createOrUpdate } from './service';
+import { fetchList, fetchPluginList, createOrUpdate } from './service';
 
 const Page: React.FC = () => {
   const ref = useRef<ActionType>();
   const { formatMessage } = useIntl();
   const [visible, setVisible] = useState(false);
   const [initialData, setInitialData] = useState({});
+  const [pluginList, setPluginList] = useState<PluginComponent.Meta[]>([]);
   const [name, setName] = useState('');
 
   useEffect(() => {
+    fetchPluginList().then(setPluginList);
+  }, []);
+
+  useEffect(() => {
     if (!name) {
       fetchList().then(({ data }) => {
         const plugins: any = {};
@@ -98,6 +103,7 @@ const Page: React.FC = () => {
       type="global"
       schemaType="route"
       initialData={initialData}
+      pluginList={pluginList}
       onClose={() => {
         setVisible(false);
       }}
diff --git a/web/src/pages/Plugin/service.ts b/web/src/pages/Plugin/service.ts
index 7c51d03..8a030ca 100644
--- a/web/src/pages/Plugin/service.ts
+++ b/web/src/pages/Plugin/service.ts
@@ -46,3 +46,9 @@ export const createOrUpdate = (data: Partial<Omit<PluginModule.GlobalRule, 'id'>
     method: 'PUT',
     data: { id: DEFAULT_GLOBAL_RULE_ID, ...data },
   });
+
+  export const fetchPluginList = () => {
+    return request<Res<PluginComponent.Meta[]>>('/plugins?all=true').then((data) => {
+      return data.data;
+    });
+  };