| /* |
| * 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. |
| */ |
| import { CopyOutlined } from '@ant-design/icons'; |
| import type { Monaco } from '@monaco-editor/react'; |
| import Editor from '@monaco-editor/react'; |
| import { Button, Card, Drawer, Form, Input, notification, Radio, Select, Spin, Tabs } from 'antd'; |
| import Base64 from 'base-64'; |
| import type * as monacoEditor from 'monaco-editor'; |
| import queryString from 'query-string'; |
| import React, { useEffect, useState } from 'react'; |
| import CopyToClipboard from 'react-copy-to-clipboard'; |
| import { useIntl } from 'umi'; |
| |
| import PanelSection from '@/components/PanelSection'; |
| |
| import { |
| DEBUG_BODY_MODE_SUPPORTED, |
| DEBUG_BODY_TYPE_SUPPORTED, |
| DEBUG_RESPONSE_BODY_MODE_SUPPORTED, |
| DebugBodyFormDataValueType, |
| DEFAULT_DEBUG_AUTH_FORM_DATA, |
| DEFAULT_DEBUG_PARAM_FORM_DATA, |
| HTTP_METHOD_OPTION_LIST, |
| PROTOCOL_SUPPORTED, |
| } from '../../constants'; |
| import { debugRoute } from '../../service'; |
| import { AuthenticationView, DebugFormDataView, DebugParamsView } from '.'; |
| import styles from './index.less'; |
| |
| const { Option } = Select; |
| const { Search } = Input; |
| const { TabPane } = Tabs; |
| |
| const DebugDrawView: React.FC<RouteModule.DebugDrawProps> = (props) => { |
| const { formatMessage } = useIntl(); |
| const [httpMethod, setHttpMethod] = useState(HTTP_METHOD_OPTION_LIST[0]); |
| const [requestProtocol, setRequestProtocol] = useState(PROTOCOL_SUPPORTED[0]); |
| const [showBodyTab, setShowBodyTab] = useState(false); |
| const [queryForm] = Form.useForm(); |
| const [urlencodedForm] = Form.useForm(); |
| const [formDataForm] = Form.useForm(); |
| const [authForm] = Form.useForm(); |
| const [headerForm] = Form.useForm(); |
| const [response, setResponse] = useState<RouteModule.debugResponse | null>(); |
| const [loading, setLoading] = useState(false); |
| const [body, setBody] = useState(''); |
| const [height, setHeight] = useState(50); |
| const [bodyType, setBodyType] = useState('none'); |
| const methodWithoutBody = ['GET', 'HEAD']; |
| const [responseBodyMode, setResponseBodyMode] = useState( |
| DEBUG_RESPONSE_BODY_MODE_SUPPORTED[0].mode, |
| ); |
| const [bodyMode, setBodyCodeMode] = useState(DEBUG_BODY_MODE_SUPPORTED[0].mode); |
| |
| enum DebugBodyType { |
| None = 0, |
| FormUrlencoded, |
| FormData, |
| RawInput, |
| } |
| |
| const resetForms = () => { |
| queryForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); |
| urlencodedForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); |
| formDataForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); |
| headerForm.setFieldsValue(DEFAULT_DEBUG_PARAM_FORM_DATA); |
| authForm.setFieldsValue(DEFAULT_DEBUG_AUTH_FORM_DATA); |
| setResponse(null); |
| setBodyType(DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.None]); |
| }; |
| |
| useEffect(() => { |
| resetForms(); |
| }, []); |
| |
| const transformBodyParamsFormData = () => { |
| if (methodWithoutBody.includes(httpMethod)) { |
| return { |
| bodyFormData: undefined, |
| }; |
| } |
| |
| switch (bodyType) { |
| case DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded]: { |
| let transformFormUrlencoded: string[] = []; |
| const FormUrlencodedData: RouteModule.debugRequestParamsFormData[] = urlencodedForm.getFieldsValue() |
| .params; |
| transformFormUrlencoded = (FormUrlencodedData || []) |
| .filter((data) => data && data.check) |
| .map((data) => { |
| return `${data.key}=${data.value}`; |
| }); |
| |
| return { |
| bodyFormData: transformFormUrlencoded.join('&'), |
| header: { |
| 'Content-type': ['application/x-www-form-urlencoded;charset=UTF-8'], |
| }, |
| }; |
| } |
| case DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput]: { |
| let contentType = ['']; |
| switch (bodyMode) { |
| case DEBUG_BODY_MODE_SUPPORTED[0].mode: |
| contentType = ['application/json;charset=UTF-8']; |
| break; |
| case DEBUG_BODY_MODE_SUPPORTED[1].mode: |
| contentType = ['text/plain;charset=UTF-8']; |
| break; |
| case DEBUG_BODY_MODE_SUPPORTED[2].mode: |
| contentType = ['application/xml;charset=UTF-8']; |
| break; |
| default: |
| break; |
| } |
| |
| return { |
| bodyFormData: body, |
| header: { |
| 'Content-type': contentType, |
| }, |
| }; |
| } |
| case DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormData]: { |
| const transformFormData = new FormData(); |
| const formDataData: RouteModule.debugRequestParamsFormData[] = formDataForm.getFieldsValue() |
| .params; |
| |
| (formDataData || []) |
| .filter((data) => data && data.check) |
| .forEach((data) => { |
| if (data.type === DebugBodyFormDataValueType.File) { |
| transformFormData.append(data.key, data.value.originFileObj); |
| } else { |
| transformFormData.append(data.key, data.value); |
| } |
| }); |
| return { |
| bodyFormData: transformFormData, |
| }; |
| } |
| case DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.None]: |
| default: |
| return { |
| bodyFormData: undefined, |
| }; |
| } |
| }; |
| |
| const transformHeaderAndQueryParamsFormData = ( |
| formData: RouteModule.debugRequestParamsFormData[], |
| ) => { |
| let transformData = {}; |
| (formData || []) |
| .filter((data) => data && data.check) |
| .forEach((data) => { |
| transformData = { |
| ...transformData, |
| [data.key]: [...(transformData[data.key] || []), data.value], |
| }; |
| }); |
| |
| return transformData; |
| }; |
| |
| const transformAuthFormData = ( |
| formData: RouteModule.authData, |
| userHeaderData: any, |
| formHeaderData: any, |
| ) => { |
| const { authType } = formData; |
| |
| switch (authType) { |
| case 'basic-auth': |
| return { |
| ...formHeaderData, |
| ...userHeaderData, |
| Authorization: [`Basic ${Base64.encode(`${formData.username}:${formData.password}`)}`], |
| }; |
| case 'jwt-auth': |
| return { |
| ...formHeaderData, |
| ...userHeaderData, |
| Authorization: [formData.Authorization], |
| }; |
| case 'key-auth': |
| return { |
| ...formHeaderData, |
| ...userHeaderData, |
| apikey: [formData.apikey], |
| }; |
| default: |
| break; |
| } |
| |
| return { |
| ...formHeaderData, |
| ...userHeaderData, |
| }; |
| }; |
| |
| const handleDebug = (url: string) => { |
| const queryFormData = transformHeaderAndQueryParamsFormData(queryForm.getFieldsValue().params); |
| const bodyFormRelateData = transformBodyParamsFormData(); |
| const { bodyFormData, header: bodyFormHeader } = bodyFormRelateData; |
| const pureHeaderFormData = transformHeaderAndQueryParamsFormData( |
| headerForm.getFieldsValue().params, |
| ); |
| const headerFormData = transformAuthFormData( |
| authForm.getFieldsValue(), |
| pureHeaderFormData, |
| bodyFormHeader, |
| ); |
| const urlQueryString = |
| url.indexOf('?') === -1 |
| ? `?${queryString.stringify(queryFormData)}` |
| : `&${queryString.stringify(queryFormData)}`; |
| |
| setLoading(true); |
| // TODO: grpc and websocket |
| debugRoute( |
| { |
| online_debug_header_params: JSON.stringify(headerFormData), |
| online_debug_url: `${requestProtocol}://${url}${urlQueryString}`, |
| online_debug_request_protocol: requestProtocol, |
| online_debug_method: httpMethod, |
| }, |
| bodyFormData, |
| ) |
| .then((req) => { |
| setLoading(false); |
| const resp: RouteModule.debugResponse = req.data; |
| if (typeof resp.data !== 'string') { |
| resp.data = JSON.stringify(resp.data, null, 2); |
| } |
| setResponse(resp); |
| const contentType = resp.header['Content-Type']; |
| if (contentType == null || contentType.length !== 1) { |
| setResponseBodyMode('TEXT'); |
| } else if (contentType[0].toLowerCase().indexOf('json') !== -1) { |
| setResponseBodyMode('JSON'); |
| } else if (contentType[0].toLowerCase().indexOf('xml') !== -1) { |
| setResponseBodyMode('XML'); |
| } else if (contentType[0].toLowerCase().indexOf('html') !== -1) { |
| setResponseBodyMode('HTML'); |
| } else { |
| setResponseBodyMode('TEXT'); |
| } |
| }) |
| .catch(() => { |
| setLoading(false); |
| }); |
| }; |
| |
| const handleEditorMount = (editor: monacoEditor.editor.IStandaloneCodeEditor, monaco: Monaco) => { |
| editor.onDidChangeModelDecorations(() => { |
| if (!editor.getDomNode()) { |
| return; |
| } |
| const padding = 40; |
| const lineHeight = editor.getOption(monaco.editor.EditorOption.lineHeight); |
| const lineCount = editor.getModel()?.getLineCount() || 1; |
| setHeight(editor.getTopForLineNumber(lineCount + 1) + lineHeight + padding); |
| }); |
| }; |
| |
| return ( |
| <Drawer |
| title={formatMessage({ id: 'page.route.onlineDebug' })} |
| mask={false} |
| maskClosable={false} |
| visible={props.visible} |
| width={650} |
| onClose={() => { |
| props.onClose(); |
| }} |
| className={styles.routeDebugDraw} |
| data-cy="debug-draw" |
| > |
| <Card bordered={false}> |
| <Input.Group compact> |
| <Select |
| defaultValue={httpMethod} |
| style={{ width: '20%' }} |
| onChange={(value) => { |
| setHttpMethod(value); |
| setShowBodyTab(!(methodWithoutBody.indexOf(value) > -1)); |
| }} |
| size="large" |
| data-cy="debug-method" |
| > |
| {HTTP_METHOD_OPTION_LIST.map((method) => { |
| return ( |
| <Option key={method} value={method}> |
| {method} |
| </Option> |
| ); |
| })} |
| </Select> |
| <Select |
| defaultValue={requestProtocol} |
| style={{ width: '18%' }} |
| onChange={(value) => { |
| setRequestProtocol(value); |
| }} |
| size="large" |
| data-cy="debug-protocol" |
| > |
| {PROTOCOL_SUPPORTED.map((protocol) => { |
| return ( |
| <Option key={protocol} value={protocol}> |
| {`${protocol}://`} |
| </Option> |
| ); |
| })} |
| </Select> |
| <Search |
| id="debugUri" |
| placeholder={formatMessage({ id: 'page.route.input.placeholder.requestUrl' })} |
| allowClear |
| enterButton={formatMessage({ id: 'page.route.button.send' })} |
| size="large" |
| style={{ width: '62%' }} |
| onSearch={handleDebug} |
| onPressEnter={(e) => { |
| handleDebug(e.currentTarget.value); |
| }} |
| onChange={(e) => { |
| if (e.currentTarget.value === '') { |
| resetForms(); |
| } |
| }} |
| /> |
| </Input.Group> |
| <PanelSection |
| title={formatMessage({ id: 'page.route.PanelSection.title.defineRequestParams' })} |
| > |
| <Tabs> |
| <TabPane |
| data-cy="query" |
| tab={formatMessage({ id: 'page.route.TabPane.queryParams' })} |
| key="query" |
| > |
| <DebugParamsView form={queryForm} name="queryForm" /> |
| </TabPane> |
| <TabPane |
| data-cy="auth" |
| tab={formatMessage({ id: 'page.route.TabPane.authentication' })} |
| key="auth" |
| > |
| <AuthenticationView form={authForm} /> |
| </TabPane> |
| <TabPane |
| data-cy="header" |
| tab={formatMessage({ id: 'page.route.TabPane.headerParams' })} |
| key="header" |
| > |
| <DebugParamsView form={headerForm} name="headerForm" inputType="header" /> |
| </TabPane> |
| {showBodyTab && ( |
| <TabPane |
| data-cy="body" |
| 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) => { |
| setBodyCodeMode(value); |
| }} |
| style={{ width: 100 }} |
| defaultValue={bodyMode} |
| > |
| {DEBUG_BODY_MODE_SUPPORTED.map((modeObj) => ( |
| <Option key={modeObj.name} value={modeObj.mode}> |
| {modeObj.name} |
| </Option> |
| ))} |
| </Select> |
| )} |
| <div style={{ marginTop: 16 }}> |
| {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormUrlencoded] && ( |
| <DebugParamsView form={urlencodedForm} name="urlencodedForm" /> |
| )} |
| |
| {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.FormData] && ( |
| <DebugFormDataView form={formDataForm} /> |
| )} |
| |
| {bodyType === DEBUG_BODY_TYPE_SUPPORTED[DebugBodyType.RawInput] && ( |
| <Form> |
| <Form.Item> |
| <Editor |
| value={body} |
| language={bodyMode.toLowerCase()} |
| onChange={(text) => { |
| if (text) { |
| setBody(text); |
| } else { |
| setBody(''); |
| } |
| }} |
| height={250} |
| beforeMount={(monaco) => { |
| monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({ |
| validate: false, |
| }); |
| }} |
| options={{ |
| scrollbar: { |
| vertical: 'hidden', |
| horizontal: 'hidden', |
| }, |
| wordWrap: 'on', |
| minimap: { enabled: false }, |
| }} |
| /> |
| </Form.Item> |
| </Form> |
| )} |
| </div> |
| </TabPane> |
| )} |
| </Tabs> |
| </PanelSection> |
| <PanelSection title={formatMessage({ id: 'page.route.PanelSection.title.responseResult' })}> |
| <Spin tip="Loading..." spinning={loading}> |
| <Tabs |
| tabBarExtraContent={ |
| response |
| ? response.message |
| : formatMessage({ id: 'page.route.debug.showResultAfterSendRequest' }) |
| } |
| > |
| <TabPane tab={formatMessage({ id: 'page.route.TabPane.response' })} key="response"> |
| <Select |
| disabled={response == null} |
| value={responseBodyMode} |
| onSelect={(mode) => setResponseBodyMode(mode as string)} |
| > |
| {DEBUG_RESPONSE_BODY_MODE_SUPPORTED.map((mode) => { |
| return ( |
| <Option value={mode.mode} key={mode.mode}> |
| {mode.name} |
| </Option> |
| ); |
| })} |
| </Select> |
| <CopyToClipboard |
| text={response ? response.data : ''} |
| onCopy={(_: string, result: boolean) => { |
| if (!result) { |
| notification.error({ |
| message: formatMessage({ id: 'component.global.copyFail' }), |
| }); |
| return; |
| } |
| notification.success({ |
| message: formatMessage({ id: 'component.global.copySuccess' }), |
| }); |
| }} |
| > |
| <Button type="text" disabled={!response}> |
| <CopyOutlined /> |
| </Button> |
| </CopyToClipboard> |
| <div id="monaco-response" style={{ marginTop: 16 }}> |
| <Editor |
| value={response ? response.data : ''} |
| height={height} |
| language={responseBodyMode.toLowerCase()} |
| onMount={handleEditorMount} |
| beforeMount={(monaco) => { |
| monaco?.languages.json.jsonDefaults.setDiagnosticsOptions({ |
| validate: false, |
| }); |
| }} |
| options={{ |
| automaticLayout: true, |
| scrollbar: { |
| vertical: 'hidden', |
| horizontal: 'hidden', |
| }, |
| wordWrap: 'on', |
| minimap: { enabled: false }, |
| readOnly: true, |
| }} |
| /> |
| </div> |
| </TabPane> |
| <TabPane tab={formatMessage({ id: 'page.route.TabPane.header' })} key="header"> |
| {response && |
| Object.keys(response.header).map((header) => { |
| return response.header[header].map((value) => { |
| return ( |
| <div> |
| <b>{header}</b>: {value} |
| </div> |
| ); |
| }); |
| })} |
| </TabPane> |
| </Tabs> |
| </Spin> |
| </PanelSection> |
| </Card> |
| </Drawer> |
| ); |
| }; |
| |
| export default DebugDrawView; |