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 0000000000..422d7dd043 Binary files /dev/null and b/web/public/static/default-plugin.png differ diff --git a/web/src/components/Plugin/PluginDetail.tsx b/web/src/components/Plugin/PluginDetail.tsx index 2ff336c730..ad76e036d2 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 } 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,6 +40,7 @@ type Props = { type?: 'global' | 'scoped'; schemaType: PluginComponent.Schema; initialData: object; + pluginList: PluginComponent.Meta[]; readonly?: boolean; visible: boolean; onClose?: () => 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"