This is an automated email from the ASF dual-hosted git repository. juzhiyuan pushed a commit to branch feat-api in repository https://gitbox.apache.org/repos/asf/incubator-apisix-dashboard.git
commit b3c10ec393fb2451862c98226021ad589ddcf9f7 Author: juzhiyuan <jjzhiy...@gmail.com> AuthorDate: Wed Jun 3 17:08:53 2020 +0800 feat: added API --- src/pages/Routes/Create.tsx | 46 ++++++++-------- .../Routes/components/Step1/MatchingRulesView.tsx | 28 +++++----- src/pages/Routes/components/Step1/MetaView.tsx | 1 - .../Routes/components/Step1/RequestConfigView.tsx | 4 +- .../components/Step2/HttpHeaderRewriteView.tsx | 11 +--- .../Routes/components/Step2/RequestRewriteView.tsx | 6 +-- src/pages/Routes/constants.ts | 4 +- src/pages/Routes/service.ts | 9 ++++ src/pages/Routes/transform.ts | 63 ++++++++++++++++++++++ src/pages/Routes/typing.d.ts | 61 ++++++++++++++++++--- 10 files changed, 170 insertions(+), 63 deletions(-) diff --git a/src/pages/Routes/Create.tsx b/src/pages/Routes/Create.tsx index b9e8cca..5a7dbad 100644 --- a/src/pages/Routes/Create.tsx +++ b/src/pages/Routes/Create.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Card, Steps, Form } from 'antd'; +import { Card, Steps, Form, notification } from 'antd'; import { PageHeaderWrapper } from '@ant-design/pro-layout'; import Step1 from './components/Step1'; @@ -9,6 +9,7 @@ import CreateStep3 from './components/CreateStep3'; import ActionBar from './components/ActionBar'; import CreateStep4 from './components/CreateStep4'; import { DEFAULT_STEP_1_DATA, DEFAULT_STEP_2_DATA, DEFAULT_STEP_3_DATA } from './constants'; +import { createRoute } from './service'; const { Step } = Steps; @@ -64,28 +65,31 @@ const Create: React.FC = () => { }; const onStepChange = (nextStep: number) => { - const nextStepAction = () => { + if (nextStep === 0) { setStep(nextStep); - window.scrollTo({ top: 0 }); - }; - if (nextStep > step && nextStep < 3) { - // Form Validation - if (step === 0) { - form1.validateFields().then((value) => { - setStep1Data({ ...step1Data, ...value }); - nextStepAction(); - }); - return; - } - if (step === 1) { - form2.validateFields().then((value) => { - setStep1Data({ ...step1Data, ...value }); - nextStepAction(); - }); - return; - } } - nextStepAction(); + if (nextStep === 1) { + form1.validateFields().then((value) => { + setStep1Data({ ...step1Data, ...value }); + setStep(nextStep); + }); + return; + } + if (nextStep === 2) { + form2.validateFields().then((value) => { + setStep2Data({ ...step2Data, ...value }); + setStep(nextStep); + }); + return; + } + if (nextStep === 3) { + setStep(nextStep); + } + if (nextStep === 4) { + createRoute({ data }).then(() => { + notification.success({ message: '创建路由成功' }); + }); + } }; return ( <> diff --git a/src/pages/Routes/components/Step1/MatchingRulesView.tsx b/src/pages/Routes/components/Step1/MatchingRulesView.tsx index 40eb4b0..ee13c2a 100644 --- a/src/pages/Routes/components/Step1/MatchingRulesView.tsx +++ b/src/pages/Routes/components/Step1/MatchingRulesView.tsx @@ -56,23 +56,23 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => { const columns = [ { title: '参数位置', - dataIndex: 'paramsLocation', - key: 'paramsLocation', + dataIndex: 'position', + key: 'position', }, { title: '参数名称', - dataIndex: 'paramsName', - key: 'paramsName', + dataIndex: 'name', + key: 'name', }, { title: '运算符', - dataIndex: 'paramsExpresstion', - key: 'paramsExpresstion', + dataIndex: 'operator', + key: 'operator', }, { title: '参数值', - dataIndex: 'paramsValue', - key: 'paramsValue', + dataIndex: 'value', + key: 'value', }, disabled ? {} @@ -104,7 +104,7 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => { <Form form={modalForm} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}> <Form.Item label="参数位置" - name="paramsLocation" + name="position" rules={[{ required: true, message: '请选择参数位置' }]} > <Select> @@ -115,14 +115,14 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => { </Form.Item> <Form.Item label="参数名称" - name="paramsName" + name="name" rules={[{ required: true, message: '请输入参数名称' }]} > <Input /> </Form.Item> <Form.Item label="运算符" - name="paramsExpresstion" + name="operator" rules={[{ required: true, message: '请选择运算符' }]} > <Select> @@ -133,11 +133,7 @@ const MatchingRulesView: React.FC<Props> = ({ data, disabled, onChange }) => { <Option value="~~">正则匹配</Option> </Select> </Form.Item> - <Form.Item - label="值" - name="paramsValue" - rules={[{ required: true, message: '请输入参数值' }]} - > + <Form.Item label="值" name="value" rules={[{ required: true, message: '请输入参数值' }]}> <Input /> </Form.Item> </Form> diff --git a/src/pages/Routes/components/Step1/MetaView.tsx b/src/pages/Routes/components/Step1/MetaView.tsx index 3c1679d..69e64c7 100644 --- a/src/pages/Routes/components/Step1/MetaView.tsx +++ b/src/pages/Routes/components/Step1/MetaView.tsx @@ -2,7 +2,6 @@ import React from 'react'; import Form from 'antd/es/form'; import { Input } from 'antd'; -// import { FORM_ITEM_LAYOUT } from '@/pages/Routes/constants'; import PanelSection from '../PanelSection'; interface Props extends RouteModule.Data {} diff --git a/src/pages/Routes/components/Step1/RequestConfigView.tsx b/src/pages/Routes/components/Step1/RequestConfigView.tsx index dc24649..a52e647 100644 --- a/src/pages/Routes/components/Step1/RequestConfigView.tsx +++ b/src/pages/Routes/components/Step1/RequestConfigView.tsx @@ -126,14 +126,14 @@ const RequestConfigView: React.FC<Props> = ({ data, disabled, onChange }) => { onChange={onProtocolChange} /> </Form.Item> - <Form.Item label="WebSocket" name="WebSocket" valuePropName="checked"> + <Form.Item label="WebSocket" name="websocket" valuePropName="checked"> <Switch disabled={disabled} /> </Form.Item> {renderHosts()} <Form.Item label="路径">{renderPaths()}</Form.Item> <Form.Item label="HTTP 方法" - name="httpMethods" + name="methods" rules={[{ required: true, message: '请勾选 HTTP 方法' }]} > <Checkbox.Group options={HTTP_METHOD_OPTION_LIST} disabled={disabled} /> diff --git a/src/pages/Routes/components/Step2/HttpHeaderRewriteView.tsx b/src/pages/Routes/components/Step2/HttpHeaderRewriteView.tsx index e06cb31..3baff95 100644 --- a/src/pages/Routes/components/Step2/HttpHeaderRewriteView.tsx +++ b/src/pages/Routes/components/Step2/HttpHeaderRewriteView.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import Form from 'antd/es/form'; import { Button, Table, Space, Modal, Input } from 'antd'; -import TextArea from 'antd/lib/input/TextArea'; import PanelSection from '../PanelSection'; @@ -33,11 +32,6 @@ const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) => dataIndex: 'header_value', key: 'header_value', }, - { - title: '描述', - dataIndex: 'header_desc', - key: 'header_desc', - }, disabled ? {} : { @@ -93,7 +87,7 @@ const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) => return ( <Modal - title={mode === 'EDIT' ? '编辑' : '增加'} + title={mode === 'EDIT' ? '编辑请求头' : '增加请求头'} centered visible={visible} onOk={handleOk} @@ -118,9 +112,6 @@ const HttpHeaderRewriteView: React.FC<Props> = ({ data, disabled, onChange }) => > <Input /> </Form.Item> - <Form.Item label="描述" name="header_desc"> - <TextArea /> - </Form.Item> </Form> </Modal> ); diff --git a/src/pages/Routes/components/Step2/RequestRewriteView.tsx b/src/pages/Routes/components/Step2/RequestRewriteView.tsx index 373c534..4e338e5 100644 --- a/src/pages/Routes/components/Step2/RequestRewriteView.tsx +++ b/src/pages/Routes/components/Step2/RequestRewriteView.tsx @@ -118,7 +118,7 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) > <Input disabled={disabled} /> </Form.Item> - <Form.Item label="连接超时"> + <Form.Item label="连接超时" required> <Form.Item name={['timeout', 'connect']} noStyle @@ -128,7 +128,7 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) </Form.Item> {renderTimeUnit()} </Form.Item> - <Form.Item label="发送超时"> + <Form.Item label="发送超时" required> <Form.Item name={['timeout', 'send']} noStyle @@ -138,7 +138,7 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) </Form.Item> {renderTimeUnit()} </Form.Item> - <Form.Item label="接收超时"> + <Form.Item label="接收超时" required> <Form.Item name={['timeout', 'read']} noStyle diff --git a/src/pages/Routes/constants.ts b/src/pages/Routes/constants.ts index b7d9574..6ccce6a 100644 --- a/src/pages/Routes/constants.ts +++ b/src/pages/Routes/constants.ts @@ -27,10 +27,10 @@ export const FORM_ITEM_WITHOUT_LABEL = { export const DEFAULT_STEP_1_DATA: RouteModule.Step1Data = { name: '', protocols: ['HTTP', 'HTTPS'], - WebSocket: false, + websocket: false, hosts: [''], paths: [], - httpMethods: HTTP_METHOD_OPTION_LIST, + methods: HTTP_METHOD_OPTION_LIST, advancedMatchingRules: [], }; diff --git a/src/pages/Routes/service.ts b/src/pages/Routes/service.ts index e69de29..be34816 100644 --- a/src/pages/Routes/service.ts +++ b/src/pages/Routes/service.ts @@ -0,0 +1,9 @@ +import { request } from 'umi'; + +import { transformStepData } from './transform'; + +export const createRoute = (data: Pick<RouteModule.Data, 'data'>) => { + return request('/workspaces/default/routes', { data: transformStepData(data) }); +}; + +export const updateRoute = () => {}; diff --git a/src/pages/Routes/transform.ts b/src/pages/Routes/transform.ts new file mode 100644 index 0000000..dbaa55f --- /dev/null +++ b/src/pages/Routes/transform.ts @@ -0,0 +1,63 @@ +import { omit } from 'lodash'; + +export const transformStepData = ({ + data: { step1Data, step2Data, step3Data }, +}: Pick<RouteModule.Data, 'data'>) => { + const nodes = {}; + step2Data.upstreamHostList.forEach((node) => { + nodes[`${node.host}:${node.port}`] = node.weight; + }); + + const upstream_header = {}; + step2Data.upstreamHeaderList.forEach((header) => { + upstream_header[header.header_name] = header.header_value; + }); + + let data: RouteModule.Body = { + ...step1Data, + ...step2Data, + ...step3Data, + priority: 0, + protocols: step1Data.protocols.concat(step1Data.websocket ? 'websocket' : []), + uris: step1Data.paths, + redirect: { + redirect_to_https: true, + }, + vars: step1Data.advancedMatchingRules.map((rule) => { + const { operator, position, name, value } = rule; + let key = ''; + switch (position) { + case 'cookie': + key = `cookie_${name}`; + break; + case 'header': + key = `http_${name}`; + break; + default: + key = `args_${name}`; + } + return [key, operator, value]; + }), + upstream: { + type: 'roundrobin', + nodes, + timeout: step2Data.timeout, + }, + upstream_header, + upstream_path: { + to: step2Data.upstreamPath, + }, + }; + + data = omit(data, [ + 'advancedMatchingRules', + 'upstreamProtocol', + 'upstreamHostList', + 'upstreamPath', + 'upstreamHeaderList', + 'websocket', + 'timeout', + ]) as RouteModule.Body; + + return data; +}; diff --git a/src/pages/Routes/typing.d.ts b/src/pages/Routes/typing.d.ts index a50f0fe..f8229b3 100644 --- a/src/pages/Routes/typing.d.ts +++ b/src/pages/Routes/typing.d.ts @@ -1,22 +1,24 @@ declare namespace RouteModule { + type Operator = '==' | '~=' | '>' | '<' | '~~'; + interface MatchingRule { - paramsLocation: 'query' | 'params' | 'header' | 'cookie'; - paramsName: string; - paramsExpresstion: '==' | '~=' | '>' | '<' | '~~'; - paramsValue: string; + position: 'query' | 'params' | 'header' | 'cookie'; + name: string; + operator: Operator; + value: string; key: string; } type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'PATCH'; - type RequestProtocol = 'HTTPS' | 'HTTP' | 'WebSocket'; + type RequestProtocol = 'HTTPS' | 'HTTP' | 'websocket'; type Step1Data = { name: string; protocols: RequestProtocol[]; - WebSocket: boolean; + websocket: boolean; hosts: string[]; paths: string[]; - httpMethods: HttpMethod[]; + methods: HttpMethod[]; advancedMatchingRules: MatchingRule[]; }; @@ -45,7 +47,9 @@ declare namespace RouteModule { interface UpstreamHeader { header_name: string; header_value: string; - header_desc: string; + } + + interface UpstreamHeader { key: string; } @@ -62,4 +66,45 @@ declare namespace RouteModule { }; type ModalType = 'CREATE' | 'EDIT'; + + // Request Body or Response Data for API + type Body = { + name: string; + desc?: string; + priority?: number; + methods: HttpMethod[]; + uris: string[]; + hosts: string[]; + protocols: RequestProtocol[]; + redirect: + | { + code: 301 | 302; + uri: string; + } + | { + redirect_to_https?: boolean; + }; + vars: [string, Operator, string][]; + upstream: { + type: 'roundrobin' | 'chash'; + nodes: { + [key: string]: number; + }; + timeout: { + connect: number; + send: number; + read: number; + }; + }; + upstream_path: { + from?: string; + to: string; + }; + upstream_header: { + [key: string]: string; + }; + plugins: { + [name: string]: any; + }; + }; }