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: 琚致远 <juzhiy...@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: 琚致远 <juzhiy...@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 <majunj...@gmail.com> * feat: git hash support generating .githash for apache release Signed-off-by: imjoey <majunj...@gmail.com> * feat: Add testcase for the new githash info Signed-off-by: imjoey <majunj...@gmail.com> * feat: add test case for .githash content validation Signed-off-by: imjoey <majunj...@gmail.com> * feat: Remove git command dependency for getting git hash Signed-off-by: imjoey <majunj...@gmail.com> * feat: set VERSION to 2.3 in branch v2.3 Signed-off-by: imjoey <majunj...@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 <majunj...@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: 琚致远 <juzhiy...@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 <majunj...@gmail.com> * do not record the false in log Signed-off-by: imjoey <majunj...@gmail.com> Co-authored-by: Joey <majunj...@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 <majunj...@gmail.com> * do not record the false in log Signed-off-by: imjoey <majunj...@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 <majunj...@gmail.com> Co-authored-by: 琚致远 <juzhiy...@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 <belovedx...@126.com> Co-authored-by: Joey <majunj...@gmail.com> Co-authored-by: nic-chen <j...@163.com> Co-authored-by: nic-chen <33000667+nic-c...@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"