From d39951b361ebabd574adcfa6c3ba54f8bdbbeee4 Mon Sep 17 00:00:00 2001 From: Joey Date: Thu, 7 Jan 2021 09:37:36 +0800 Subject: [PATCH 1/7] fix(be): search by status on the route list page is invalid (#1207) (#1224) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix search by status on the route list page is invalid * Fix test cases and references * Update route.go * Update route.go * Format error repair * use strconv.Itoa Method conversion uint8 * chore: use the correct API version (#1215) Co-authored-by: litesun 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: 琚致远 feat: add tips when plugin type is auth and schemaType is not consumer (#1219) Co-authored-by: 琚致远 * Fix indent format problem Co-authored-by: 琚致远 Co-authored-by: JinChen <36916582+Jaycean@users.noreply.github.com> Co-authored-by: 琚致远 --- api/internal/handler/route/route.go | 12 ++++-- api/internal/handler/route/route_test.go | 49 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/api/internal/handler/route/route.go b/api/internal/handler/route/route.go index 59dc6cea5a..89083ed82a 100644 --- a/api/internal/handler/route/route.go +++ b/api/internal/handler/route/route.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "strings" "github.com/gin-gonic/gin" @@ -178,9 +179,10 @@ func (h *Handler) Get(c droplet.Context) (interface{}, error) { } type ListInput struct { - Name string `auto_read:"name,query"` - URI string `auto_read:"uri,query"` - Label string `auto_read:"label,query"` + Name string `auto_read:"name,query"` + URI string `auto_read:"uri,query"` + Label string `auto_read:"label,query"` + Status string `auto_read:"status,query"` store.Pagination } @@ -219,6 +221,10 @@ func (h *Handler) List(c droplet.Context) (interface{}, error) { return false } + if input.Status != "" && strconv.Itoa(int(obj.(*entity.Route).Status)) != input.Status { + return false + } + return true }, Format: func(obj interface{}) interface{} { diff --git a/api/internal/handler/route/route_test.go b/api/internal/handler/route/route_test.go index f84fc901f9..4618755824 100644 --- a/api/internal/handler/route/route_test.go +++ b/api/internal/handler/route/route_test.go @@ -59,6 +59,7 @@ func TestRoute(t *testing.T) { "vars": [], "remote_addrs": ["127.0.0.0/8"], "methods": ["PUT", "GET"], + "status": 1, "upstream": { "type": "roundrobin", "nodes": [{ @@ -415,6 +416,7 @@ func TestRoute(t *testing.T) { "hosts": ["foo.com", "*.bar.com"], "remote_addrs": ["127.0.0.0/8"], "methods": ["PUT", "GET"], + "status": 1, "labels": { "l1": "v1", "l2": "v2" @@ -925,6 +927,42 @@ func TestRoute(t *testing.T) { dataPage = retPage.(*store.ListOutput) assert.Equal(t, len(dataPage.Rows), 0) + // list search and status match + listInput = &ListInput{} + reqBody = `{"page_size": 1, "page": 1, "status": "1"}` + err = json.Unmarshal([]byte(reqBody), listInput) + assert.Nil(t, err) + ctx.SetInput(listInput) + retPage, err = handler.List(ctx) + assert.Nil(t, err) + dataPage = retPage.(*store.ListOutput) + assert.Equal(t, len(dataPage.Rows), 1) + + //sleep + time.Sleep(time.Duration(100) * time.Millisecond) + + // list search and status not match + listInput = &ListInput{} + reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "0"}` + err = json.Unmarshal([]byte(reqBody), listInput) + assert.Nil(t, err) + ctx.SetInput(listInput) + retPage, err = handler.List(ctx) + assert.Nil(t, err) + dataPage = retPage.(*store.ListOutput) + assert.Equal(t, len(dataPage.Rows), 0) + + //list search with name and status + listInput = &ListInput{} + reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "1"}` + err = json.Unmarshal([]byte(reqBody), listInput) + assert.Nil(t, err) + ctx.SetInput(listInput) + retPage, err = handler.List(ctx) + assert.Nil(t, err) + dataPage = retPage.(*store.ListOutput) + assert.Equal(t, len(dataPage.Rows), 1) + //list search with name and label listInput = &ListInput{} reqBody = `{"page_size": 1, "page": 1, "name": "a", "label":"l1:v1"}` @@ -958,6 +996,17 @@ func TestRoute(t *testing.T) { dataPage = retPage.(*store.ListOutput) assert.Equal(t, len(dataPage.Rows), 1) + //list search with uri,name, status and label + listInput = &ListInput{} + reqBody = `{"page_size": 1, "page": 1, "name": "a", "status": "1", "uri": "index", "label":"l1:v1"}` + err = json.Unmarshal([]byte(reqBody), listInput) + assert.Nil(t, err) + ctx.SetInput(listInput) + retPage, err = handler.List(ctx) + assert.Nil(t, err) + dataPage = retPage.(*store.ListOutput) + assert.Equal(t, len(dataPage.Rows), 1) + //create route using uris route3 := &entity.Route{} reqBody = `{ From cc9e7de8f6a0bf8e96fcf573578ca5333521dde4 Mon Sep 17 00:00:00 2001 From: Joey Date: Thu, 7 Jan 2021 11:44:27 +0800 Subject: [PATCH 2/7] feat: Improve consumer for i18n (#1212) (#1225) Signed-off-by: imjoey --- web/src/locales/zh-CN/menu.ts | 2 +- web/src/pages/Consumer/locales/en-US.ts | 4 ++-- web/src/pages/Consumer/locales/zh-CN.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/locales/zh-CN/menu.ts b/web/src/locales/zh-CN/menu.ts index 6a53236041..8533c4d00a 100644 --- a/web/src/locales/zh-CN/menu.ts +++ b/web/src/locales/zh-CN/menu.ts @@ -67,7 +67,7 @@ export default { 'menu.routes': '路由', 'menu.ssl': '证书', 'menu.upstream': '上游', - 'menu.consumer': 'Consumer', + 'menu.consumer': '应用', 'menu.plugin': '插件', 'menu.service': '服务', 'menu.setting': '设置', diff --git a/web/src/pages/Consumer/locales/en-US.ts b/web/src/pages/Consumer/locales/en-US.ts index 5bddf33236..64f6f32820 100644 --- a/web/src/pages/Consumer/locales/en-US.ts +++ b/web/src/pages/Consumer/locales/en-US.ts @@ -17,9 +17,9 @@ export default { 'page.consumer.form.itemRuleMessage.username': 'Maximum length is 100, only letters, numbers and _ are supported, and can only begin with letters', - 'page.consumer.form.itemExtraMessage.username': 'Username should be unique', + 'page.consumer.form.itemExtraMessage.username': 'Name should be unique', 'page.consumer.notification.warning.enableAuthenticationPlugin': 'Please enable one authentication plugin', - 'page.consumer.username': 'Username', + 'page.consumer.username': 'Name', 'page.consumer.updateTime': 'Update Time', }; diff --git a/web/src/pages/Consumer/locales/zh-CN.ts b/web/src/pages/Consumer/locales/zh-CN.ts index c54bcc2f77..ea89de99f4 100644 --- a/web/src/pages/Consumer/locales/zh-CN.ts +++ b/web/src/pages/Consumer/locales/zh-CN.ts @@ -17,8 +17,8 @@ export default { 'page.consumer.form.itemRuleMessage.username': '最大长度100,仅支持字母、数字和 _ ,且只能以字母开头', - 'page.consumer.form.itemExtraMessage.username': '用户名需唯一', + 'page.consumer.form.itemExtraMessage.username': '名称需唯一', 'page.consumer.notification.warning.enableAuthenticationPlugin': '请启用一种身份认证类插件', - 'page.consumer.username': '用户名', + 'page.consumer.username': '名称', 'page.consumer.updateTime': '更新时间', }; From 073a252466d37ae3ebc4069d1162391f13d2b7b1 Mon Sep 17 00:00:00 2001 From: Joey Date: Thu, 7 Jan 2021 11:47:31 +0800 Subject: [PATCH 3/7] feat(release): Enable CI trigger for v2.3 branch (#1226) * feat: Trigger CI in v2.3 branch Signed-off-by: imjoey * Re-trigger CI process Signed-off-by: imjoey --- .github/workflows/backend-cli-test.yml | 4 ++-- .github/workflows/backend-e2e-test.yml | 4 ++-- .github/workflows/backend-unit-test.yml | 4 ++-- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/deploy-with-docker.yml | 2 +- .github/workflows/frontend-e2e-test.yml | 4 ++-- .github/workflows/gitleaks.yml | 4 ++-- .github/workflows/go-lint.yml | 5 +++-- .github/workflows/license-checker.yml | 4 ++-- .github/workflows/release-test.yml | 4 ++-- .github/workflows/spellchecker.yml | 2 +- .github/workflows/test-frontend-multiple-node-build.yml | 6 +++--- 12 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/backend-cli-test.yml b/.github/workflows/backend-cli-test.yml index e0398c3c8f..5181ed7579 100644 --- a/.github/workflows/backend-cli-test.yml +++ b/.github/workflows/backend-cli-test.yml @@ -3,10 +3,10 @@ name: Backend CLI Test on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: run-test: diff --git a/.github/workflows/backend-e2e-test.yml b/.github/workflows/backend-e2e-test.yml index 7ba1cbddfc..4bffa9c153 100644 --- a/.github/workflows/backend-e2e-test.yml +++ b/.github/workflows/backend-e2e-test.yml @@ -3,10 +3,10 @@ name: Backend E2E Test on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: run-test: diff --git a/.github/workflows/backend-unit-test.yml b/.github/workflows/backend-unit-test.yml index 40d7dbf0f6..c02e9ed5fd 100644 --- a/.github/workflows/backend-unit-test.yml +++ b/.github/workflows/backend-unit-test.yml @@ -3,10 +3,10 @@ name: Backend Unit Test on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: run-test: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 04e2699f01..c76fa02b43 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [master] + branches: [v2.3] pull_request: # The branches below must be a subset of the branches above - branches: [master] + branches: [v2.3] schedule: - cron: '18 23 * * 0' diff --git a/.github/workflows/deploy-with-docker.yml b/.github/workflows/deploy-with-docker.yml index 5fa7e9d741..5dc741d014 100644 --- a/.github/workflows/deploy-with-docker.yml +++ b/.github/workflows/deploy-with-docker.yml @@ -3,7 +3,7 @@ name: Test and Deploy with Docker on: push: branches: - - master + - v2.3 jobs: build: diff --git a/.github/workflows/frontend-e2e-test.yml b/.github/workflows/frontend-e2e-test.yml index a785058585..85f87cf94c 100644 --- a/.github/workflows/frontend-e2e-test.yml +++ b/.github/workflows/frontend-e2e-test.yml @@ -3,10 +3,10 @@ name: Frontend e2e test on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 defaults: run: working-directory: web diff --git a/.github/workflows/gitleaks.yml b/.github/workflows/gitleaks.yml index ad9fd414c2..776ff8624e 100644 --- a/.github/workflows/gitleaks.yml +++ b/.github/workflows/gitleaks.yml @@ -5,10 +5,10 @@ name: gitLeaks on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: gitleaks: diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml index c579dd25d8..8f2fc1c837 100644 --- a/.github/workflows/go-lint.yml +++ b/.github/workflows/go-lint.yml @@ -1,11 +1,12 @@ name: go-lint + on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: golangci: diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml index 09b2cbaac1..af29983979 100644 --- a/.github/workflows/license-checker.yml +++ b/.github/workflows/license-checker.yml @@ -3,10 +3,10 @@ name: License checker on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: check-license: diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index b821fa26bb..701118631f 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -3,10 +3,10 @@ name: Release Test on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 jobs: run-test: diff --git a/.github/workflows/spellchecker.yml b/.github/workflows/spellchecker.yml index 6b246517f8..0be20a835e 100644 --- a/.github/workflows/spellchecker.yml +++ b/.github/workflows/spellchecker.yml @@ -2,7 +2,7 @@ name: spellchecker on: pull_request: branches: - - master + - v2.3 jobs: misspell: name: runner/misspell diff --git a/.github/workflows/test-frontend-multiple-node-build.yml b/.github/workflows/test-frontend-multiple-node-build.yml index 7a29774a39..528cbf6390 100644 --- a/.github/workflows/test-frontend-multiple-node-build.yml +++ b/.github/workflows/test-frontend-multiple-node-build.yml @@ -3,14 +3,14 @@ name: Test building web in multiple node version # Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch +# events but only for the v2.3 branch on: push: branches: - - master + - v2.3 pull_request: branches: - - master + - v2.3 # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: From 72f2195b70aecfcdf0a4ff0f06af8cd1bc2cf2b7 Mon Sep 17 00:00:00 2001 From: Joey Date: Thu, 7 Jan 2021 14:00:08 +0800 Subject: [PATCH 4/7] fix: sync the preload packages in schema-sync.lua (#1216) (#1221) (#1228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 琚致远 Co-authored-by: Peter Zhu Co-authored-by: 琚致远 --- api/build-tools/schema-sync.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/build-tools/schema-sync.lua b/api/build-tools/schema-sync.lua index 7e96e964e4..74271cff11 100644 --- a/api/build-tools/schema-sync.lua +++ b/api/build-tools/schema-sync.lua @@ -26,6 +26,7 @@ local fake_module_list = { 'ngx.errlog', 'ngx.process', 'ngx.re', + 'ngx.ssl', 'net.url', 'opentracing.tracer', 'pb', @@ -35,6 +36,7 @@ local fake_module_list = { 'resty.cookie', 'resty.core.regex', + 'resty.core.base', 'resty.hmac', 'resty.http', 'resty.ipmatcher', @@ -52,6 +54,9 @@ local fake_module_list = { 'resty.rediscluster', 'resty.signal', 'resty.string', + 'resty.aes', + 'resty.radixtree', + 'resty.expr.v1', 'apisix.consumer', 'apisix.core.json', @@ -84,6 +89,8 @@ ngx.socket = {} ngx.thread = {} ngx.worker = {} ngx.re.gmatch = empty_function +ngx.req = {} +ngx.config = {} ngx.shared = { ["plugin-api-breaker"] = {} } @@ -112,7 +119,10 @@ package.loaded["apisix.core"] = { table = { insert = empty_function }, - string = {} + string = {}, + version = { + VERSION = "" + } } From ce47759b04cb34825839fa8c25ffab0c0bce43fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Thu, 7 Jan 2021 14:26:54 +0800 Subject: [PATCH 5/7] sync v2.3 (#1232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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: 琚致远 * 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: 琚致远 * feat: add tips when plugin type is auth and schemaType is not consumer (#1219) Co-authored-by: 琚致远 * 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: 琚致远 * 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 * feat: git hash support generating .githash for apache release Signed-off-by: imjoey * feat: Add testcase for the new githash info Signed-off-by: imjoey * feat: add test case for .githash content validation Signed-off-by: imjoey * feat: Remove git command dependency for getting git hash Signed-off-by: imjoey * feat: set VERSION to 2.3 in branch v2.3 Signed-off-by: imjoey * 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 * 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: 琚致远 * fix: well handle with malformed auth token in request header (#1206) (#1210) * fix: not panic if auth token is invalid Signed-off-by: imjoey * do not record the false in log Signed-off-by: imjoey Co-authored-by: Joey * 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 * do not record the false in log Signed-off-by: imjoey * 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 Co-authored-by: 琚致远 * 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 Co-authored-by: liuxiran Co-authored-by: Joey Co-authored-by: nic-chen Co-authored-by: nic-chen <33000667+nic-chen@users.noreply.github.com> * 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: 琚致远 Co-authored-by: liuxiran Co-authored-by: litesun Co-authored-by: Joey Co-authored-by: nic-chen Co-authored-by: nic-chen <33000667+nic-chen@users.noreply.github.com> --- .../route_online_debug/route_online_debug.go | 6 +- api/test/e2e/route_online_debug_test.go | 5 +- web/cypress/fixtures/plugin-dataset.json | 1347 +++++++++++++++++ .../plugin/schema-smocktest.spec.js | 85 ++ .../integration/route/search-route.spec.js | 5 +- .../service/create-and-delete-service.spec.js | 10 +- .../create_and_delete_upstream.spec.js | 2 +- web/package.json | 3 +- web/public/static/default-plugin.png | Bin 0 -> 2244 bytes web/src/components/Plugin/PluginDetail.tsx | 49 +- web/src/components/Plugin/PluginPage.tsx | 45 +- 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/PluginMarket.tsx | 2 +- web/src/pages/Plugin/service.ts | 18 +- web/src/pages/Route/List.tsx | 8 +- .../components/DebugViews/DebugDrawView.tsx | 117 +- .../Route/components/DebugViews/index.less | 20 +- web/src/pages/Route/constants.ts | 15 + web/src/pages/Route/service.ts | 2 +- web/src/pages/Route/typing.d.ts | 8 +- web/src/pages/Service/Create.tsx | 9 +- web/src/pages/Service/List.tsx | 2 +- web/src/typings.d.ts | 1 + web/yarn.lock | 15 +- 26 files changed, 1708 insertions(+), 228 deletions(-) create mode 100644 web/cypress/fixtures/plugin-dataset.json create mode 100644 web/cypress/integration/plugin/schema-smocktest.spec.js create mode 100644 web/public/static/default-plugin.png 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 18241b7d85..ac1c50230a 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 4796d20fb6..fba4f03826 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, diff --git a/web/cypress/fixtures/plugin-dataset.json b/web/cypress/fixtures/plugin-dataset.json new file mode 100644 index 0000000000..7f1ada3e30 --- /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 0000000000..1878fbf82f --- /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 287b248570..89a635deb6 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 1536951c17..9ee46a6626 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 f22067726d..b445f0c8e4 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 9016a80575..02e655219f 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/public/static/default-plugin.png b/web/public/static/default-plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..422d7dd0438778085adff67cab79cc27a32e8e5f GIT binary patch literal 2244 zcmV;#2s`(QP)(zuf_J0JsF03v3U309*~cuH)L)O9LQ2zSjcRjd8PI4G=T{ z;sB6u`VCWD6c0fIpgRCp01G^-7Y#uJz(c_?CYJ%X1XL{+f(Af`3ApQ@rU#Xkii&>k+?IA zCX9UHi?$oW9YBU*Z;Fo8x(=#*=Fjd%-vPMFXaYtSiD<7AFksZ}vd;j>?3=~ZtBf3K zt`KsoWN6V>0A$`>Qns{9Rf9Y}o$o6E(sykLWL5A|pP<%c(`NuQA&`+njV0m5v5^~q zErA_@&wy`@q7!>3D0x;dIxRgX05bnBW9Mti%F!FUMbbo92uhyMS4m6H1;A^-zGJ@e z9|6Z0WzK{`P}J((S+P<}OV0to-;P^-2@sULj7v2wJv9KUf$cn=^*JGs0EeW0dvr(5 zBy>K*nT(nefQNyT0z#1k0vS2<+4Lcx8)~Lqr?IC(kO}}1UH}L!DIt*Os>Pf^XrIT~ z$1Y%*2tg_U-UW8=7>N`R$Q$*vy?#12U8S07T6ziq<|!6QS5j=gtbr7sXzJo6=<&a`w( z0Nw`n?4oL5o!TMDnwD+_fCw)Dq$;&SkUcHk3V;;1|JiWW27%@v&&*f^Enu+%Al2=^ zRtSnoOSb@EYv8L?_x>^0Dg91RR9d34qng)>uKi)K3jTwzPCj z0Ia4wy9lUJClv%))6z8ouo?x^5VNZfYY4KZr8fZFXjnp!JuSTf;08b}tRV2ax}jAA zz(2UXgrEW74?r*P1Py>N0D1?3CIGJ8E5k!wZjIhN4m1I$)AoWISu4~Khtzpu>dsj-CD`un4FntaVom|>JEz-0Gjyj@XNT!+)~At z=6404iSHKw89>eikkQd%OBVpFcE2n(NP!Le3s?-uMi0f8Pyl4RvZclMDo+D{9vzS^ zhh)pGzlst9z^Ns^#TF1{*&F!*;2~gj0qqHDf-eBa0gnfH?!WOLz_QV&+J!Ja02~QC z+kZX1dMLN~H-I-s>mT3us#UO&3xL{p{)!E~?0dLIaQR!!v)#Aj(*Teo-{t7{p%i(0 z|75h+p?t$daLtp?i;UwW0OkQJ61I2&u<3-)MvCo_-{fmP-vF|=@u#Ct@5LS$$4LOB zT-YHaWhJtvg(r|49HG2)u0E z*b{+Ajjz$%J0dXcB~Vtc$mtgr<&@FDiG}axGav%4pMmmnCR> void; @@ -36,6 +48,7 @@ type Props = { }; const ajv = new Ajv(); +addFormats(ajv); const FORM_ITEM_LAYOUT = { labelCol: { @@ -65,6 +78,7 @@ const PluginDetail: React.FC = ({ type = 'scoped', schemaType = 'route', visible, + pluginList = [], readonly = false, initialData = {}, onClose = () => {}, @@ -73,10 +87,14 @@ const PluginDetail: React.FC = ({ const { formatMessage } = useIntl(); const [form] = Form.useForm(); const ref = useRef(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 }); + form.setFieldsValue({ + disable: initialData[name] && !initialData[name].disable, + scope: 'global', + }); }, []); const validateData = (pluginName: string, value: PluginComponent.Data) => { @@ -89,7 +107,6 @@ const PluginDetail: React.FC = ({ } else { injectDisableProperty(schema); } - const validate = ajv.compile(schema); if (validate(value)) { resolve(value); @@ -149,7 +166,7 @@ const PluginDetail: React.FC = ({ placement="right" closable={false} onClose={onClose} - width={600} + width={700} footer={
{' '} @@ -195,8 +212,8 @@ const PluginDetail: React.FC = ({ {type === 'global' && ( - + Global )} @@ -204,7 +221,13 @@ const PluginDetail: React.FC = ({ Data Editor + ) : ( + <>Current plugin: {name} + ) + } ghost={false} extra={[ , ]} + title={[ +
+ + {item.name} + +
, + ]} bodyStyle={{ - height: 151, + minHeight: 151, display: 'flex', justifyContent: 'center', - textAlign: 'center', + alignItems: 'center' }} - title={[ -
- {item.name} -
, - ]} - style={{ height: 258, width: 200 }} - /> + style={{ width: 200 }} + > + {Boolean(PLUGIN_ICON_LIST[item.name]) && PLUGIN_ICON_LIST[item.name]} + {Boolean(!PLUGIN_ICON_LIST[item.name]) && pluginImg} + ))} ); @@ -140,6 +162,7 @@ const PluginPage: React.FC = ({ visible={name !== NEVER_EXIST_PLUGIN_FLAG} schemaType={schemaType} initialData={initialData} + pluginList={pluginList} onClose={() => { setName(NEVER_EXIST_PLUGIN_FLAG); }} diff --git a/web/src/components/Plugin/data.tsx b/web/src/components/Plugin/data.tsx index 40b1d608d7..75baecbf4f 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> = { - '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: , - }, - skywalking: { - category: 'Observability', - priority: 2, - avatar: , - }, - 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: , - }, - 'authz-keycloak': { - category: 'Authentication', - priority: 5, - avatar: , - }, - '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: , - }, - '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: , - }, - 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 = { + prometheus: , + skywalking: , + 'jwt-auth': , + 'authz-keycloak': , + 'openid-connect': , + 'kafka-logger': , }; diff --git a/web/src/components/Plugin/index.ts b/web/src/components/Plugin/index.ts index 940c9f0e88..cee617dfc5 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 2e9c18f788..3a73cc1423 100644 --- a/web/src/pages/Plugin/List.tsx +++ b/web/src/pages/Plugin/List.tsx @@ -24,15 +24,20 @@ 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(); const { formatMessage } = useIntl(); const [visible, setVisible] = useState(false); const [initialData, setInitialData] = useState({}); + const [pluginList, setPluginList] = useState([]); const [name, setName] = useState(''); + useEffect(() => { + fetchPluginList().then(setPluginList); + }, []); + useEffect(() => { if (!name) { fetchList().then(({ data }) => { @@ -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/PluginMarket.tsx b/web/src/pages/Plugin/PluginMarket.tsx index ac3a4e19ce..dadbdcf2ea 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 a11e2ad751..8a030ca84f 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; }; }>(`/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, @@ -44,3 +46,9 @@ export const createOrUpdate = (data: Partial method: 'PUT', data: { id: DEFAULT_GLOBAL_RULE_ID, ...data }, }); + + export const fetchPluginList = () => { + return request>('/plugins?all=true').then((data) => { + return data.data; + }); + }; diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx index 466fdd6c7c..43983aa4cd 100644 --- a/web/src/pages/Route/List.tsx +++ b/web/src/pages/Route/List.tsx @@ -199,8 +199,12 @@ const Page: React.FC = () => { return ( ); }, diff --git a/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx b/web/src/pages/Route/components/DebugViews/DebugDrawView.tsx index 5991fbf0b4..18b3d0e66d 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,19 @@ const DebugDrawView: React.FC = (props) => { const [responseCode, setResponseCode] = useState(); const [loading, setLoading] = useState(false); const [codeMirrorHeight, setCodeMirrorHeight] = useState(50); + const bodyCodeMirrorRef = useRef(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 +70,44 @@ const DebugDrawView: React.FC = (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 +162,7 @@ const DebugDrawView: React.FC = (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 +267,60 @@ const DebugDrawView: React.FC = (props) => { {showBodyTab && ( - + { + setBodyType(e.target.value); + }} + value={bodyType} + > + {DEBUG_BODY_TYPE_SUPPORTED.map((type) => ( + + {type} + + ))} + + {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && ( + + )} +
+ {(bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] || + bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.Json]) && ( + + )} + + {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && ( +
+ + + +
+ )} +
)} diff --git a/web/src/pages/Route/components/DebugViews/index.less b/web/src/pages/Route/components/DebugViews/index.less index f9cb5dcf08..65ee10bef6 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 1a8dc46a3d..10a083c83f 100644 --- a/web/src/pages/Route/constants.ts +++ b/web/src/pages/Route/constants.ts @@ -98,8 +98,23 @@ 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/service.ts b/web/src/pages/Route/service.ts index 7ae96d06c9..95b7392018 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 60afe5f87b..2d19fae018 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; diff --git a/web/src/pages/Service/Create.tsx b/web/src/pages/Service/Create.tsx index 15dc252ab8..c85849d079 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 && ( )} - {step === 3 && } + {step === 3 && ( + + )} diff --git a/web/src/pages/Service/List.tsx b/web/src/pages/Service/List.tsx index 6fd28fc591..ed17347a2f 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 691ae14204..ae6769a1fb 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; } declare let ga: Function; diff --git a/web/yarn.lock b/web/yarn.lock index 93ed598148..dc98253c69 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" From d518b477b7d5e506ae124283aeb7cedb05027dc1 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Thu, 7 Jan 2021 15:11:55 +0800 Subject: [PATCH 6/7] doc: add changelog for v2.3 (#1235) --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3927102e..ce3398a022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ # Table of Contents +- [2.3.0](#230) - [2.2.0](#220) - [2.1.1](#211) - [2.1.0](#210) @@ -26,6 +27,30 @@ - [1.5.0](#150) - [1.0.0](#100) +# 2.3.0 + +### Core + +* Support to use absolute path in conf.WebDir. [#1055](https://github.com/apache/apisix-dashboard/pull/1055) +* Support to search route by label. [#1061](https://github.com/apache/apisix-dashboard/pull/1061) +* Support server info preview. [#958](https://github.com/apache/apisix-dashboard/pull/958) [#949](https://github.com/apache/apisix-dashboard/pull/949) +* Support search route by labels. [#1061](https://github.com/apache/apisix-dashboard/pull/1061) +* Support custom port for Upstream module. [#1078](https://github.com/apache/apisix-dashboard/pull/1078) +* Support to show plugin type and other properties [#1111](https://github.com/apache/apisix-dashboard/pull/1111) +* Support websocket for Route module. [#1079](https://github.com/apache/apisix-dashboard/pull/1079) +* Support Service module on the frontend. [#1089](https://github.com/apache/apisix-dashboard/pull/1089) +* Support group for Route module. [#999](https://github.com/apache/apisix-dashboard/pull/999) +* Support Global Plugin. [#1057](https://github.com/apache/apisix-dashboard/pull/1057) [#1106](https://github.com/apache/apisix-dashboard/pull/1106) +* Support Version Manager. [#1157](https://github.com/apache/apisix-dashboard/pull/1157) +* Use Cobra as the cli scafford. [#773](https://github.com/apache/apisix-dashboard/pull/773) +* Remove Lua dependency. [#1083](https://github.com/apache/apisix-dashboard/pull/1083) +* Improve E2E testcases for the backend. [#1012](https://github.com/apache/apisix-dashboard/pull/1012), [#1123](https://github.com/apache/apisix-dashboard/pull/1123) +* Improve E2E testcases for the frontend. [#1074](https://github.com/apache/apisix-dashboard/pull/1074) +* Improve online debug. [#979](https://github.com/apache/apisix-dashboard/pull/979) +* Improve Route publish/offline. [#1081](https://github.com/apache/apisix-dashboard/pull/1081) [#991](https://github.com/apache/apisix-dashboard/pull/991) +* Improve plugin module for the frontend. [#1047](https://github.com/apache/apisix-dashboard/pull/1047) [#978](https://github.com/apache/apisix-dashboard/pull/978) +* Fix error occurred when enable or disable existing SSL [#1064](https://github.com/apache/apisix-dashboard/pull/1064) +* Fix the problem that route created by Admin API (without ID) cannot be shown in Manager API. [#1063](https://github.com/apache/apisix-dashboard/pull/1063) # 2.2.0 From 3a55ad3f15ea272ef6aa433b3a431b3d30f0cd81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=90=9A=E8=87=B4=E8=BF=9C?= Date: Sat, 9 Jan 2021 21:30:16 +0800 Subject: [PATCH 7/7] Update deploy.md --- docs/deploy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deploy.md b/docs/deploy.md index 2fc2eaebd8..f4e50487dd 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -43,7 +43,7 @@ $ go env -w GOPROXY=https://goproxy.cn,direct ## Clone the project ```sh -$ git clone -b v2.2 https://github.com/apache/apisix-dashboard.git +$ git clone -b v2.3 https://github.com/apache/apisix-dashboard.git ``` ## Build