This is an automated email from the ASF dual-hosted git repository. juzhiyuan pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/master by this push: new a0d7e0c feat: added chash (#429) a0d7e0c is described below commit a0d7e0ce2269a366e56e5f0a9ca6ecacddd07070 Author: litesun <31329157+lite...@users.noreply.github.com> AuthorDate: Tue Sep 1 09:43:01 2020 +0800 feat: added chash (#429) * merge master (#1) * add: Determine duplicate names api for route & upstream (#305) * fix: transaction in routes and upstreams (#306) * add transaction for ssl and consumer (#308) * update ci/cd for api (#307) * update github actions for api ci cd * fix: working-directory * fix error * fix: step name * fix: mysql config for github action * test * use default config * test: add e2e test for ssl and consumer (#309) * test: add e2e test for ssl and consumer * fix: change assert to avoid the mutual influence of route and service test * remove useless code * Feat: added Route Consumer and Upstream (#304) * feat: added routes * feat: added Consumer * feat: added upstream * feat: update SSL * fix: routes * feat: added commit command * feat(route): set empty array for upstreamHeaderList * fix: e2e test use the same function to set up router (#310) * fix: return all objects when search route & upstream (#311) * fix: route search * fix: upstream search * fix(deploy): added missing yarn.lock * fix: proxy-rewrite plugin in upstream (#312) * fix(SSL): search api * docs: added tips when deployment * feat(Deploy): use node alpine image * fix(Route): set required field for custom redirect * fix(Route): check if redirect is empty object * fix(Deploy): add Python installation in dockerfile (#316) Signed-off-by: imjoey <majunj...@gmail.com> * fix(Route): update desc for status code * fix: proxy-path default type is static (#318) * add proxyRewrite test (#319) * feat: bump dependencies version (#320) * feat(Deploy): update Dockerfile * feat(Deploy): update Deploy Dockerfile * feat(Pages): update pages (#324) * feat(Pages): update pages * chore: update routes * fix(Route): omit upstream_id when not exist * i18n consumer (#325) * i18n ssl (#335) * nationalization PluginPage component (#323) * i18n upstream (#334) * feat(i18n): set module (#336) * i18n set * change set to setting * feat(i18n): metrics module (#326) * i18n metrics * combine import * feat(i18n): route module (#327) * i18n route * combine import * doc: sync config.yaml from the latest version of APISIX (#344) * i18n route (#342) * i18n actionbar (#343) * fix: transform vars error (#347) * feat(i18n): pluginpage component (#345) * i18n pluginpage * change pluginpage to PluginPage * feature: support run in mac system (#349) * combine import (#348) * i18n menu (#351) * i18n PluginPage (#350) * feat: prepare to release (#352) * feat(ManagerAPI): added ASF header * feat(FE): aded ASF Header * feat(FE): added ASF header * fix(FE): update PluginDrawer * feat: remove some images * feat: added LICENSE * feat: update Version * feat: added NOTICE & CODE_OF_CONDUCT * feat: added initial CHANGELOG * feat: rename CODE_OF_CONDUCT * feat: revert version * feat: update LICENSE * feat: update License * feat(conf): update default preview API (#353) * doc: add install doc for manager-api (#355) * doc: add install doc for manager-api * doc: modify folder from build to run * doc: add ASF header * fix(ci): resolve lint failures (#354) * fix(deploy): failed to start manager_api (#363) Signed-off-by: imjoey <majunj...@gmail.com> * feat(i18n): modify some i18n according to the proposal#331 (#366) * Create CONTRIBUTING.md (#368) * Create CONTRIBUTING.md * Create ISSUE_TEMPLATE * Create PULL_REQUEST_TEMPLATE * doc: remove all ‘incubator’ (#367) * feat(deploy): set gen-config-yaml.sh executable (#362) This also would simplify the docs. Signed-off-by: imjoey <majunj...@gmail.com> * feat(i18n): Use auto load i18n (#332) (#371) * Create ci.yml (#372) * feat: release 1.5 (#364) * Feat release 1.5 (#358) * feat(doc): update README * feat: update CHANGELOG * doc: add usage of dashbaord * Revert "doc: add usage of dashbaord" This reverts commit 5a08c7f43539a44cd0cf0f6175574e59efbd0ab6. * feat(Doc): update deployment * feat(Doc): update the deployment * feat(Doc): update the deployment * feat: remove incubator text * doc: modify doc for manager-api runing in local * feat(Doc): update README * doc: check env variables and give run.sh power to execute * feat(Doc): update Deployment * feat(Doc): update deployment * doc: modify manager-api build * feat: update ignore file Co-authored-by: kv <gxt...@163.com> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandemini.lan> * feat: cherry-pick 4fd0ce79bb34dbe8c31b7a27884930e3b0e5437c * feat(compose): remove images * feat: added line Co-authored-by: kv <gxt...@163.com> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandemini.lan> * feat: Unified access entrance, only the dashboard port is exposed to … (#370) * feat: Unified access entrance, only the dashboard port is exposed to the outside * add EOL * docs: create I18N_USER_GUIDE.md (#373) * docs: create I18N_USER_GUIDE.md * docs: modify I18N_USER_GUIDE.md * feat(Doc): added deploy doc for docker (#376) * feat(Doc): added deploy doc for docker * feat: added CD * feat(Netlify): added proxy * feat: update API * feat: remove console * feat(Netlify): update redirect rule * feat: update README * feat: update README * update go module proxy (#378) * Update README.md (#379) * Update README.md * Update README.md * Create Preview.md * feat(Doc): added snapshots for Preview * feat(Doc): update images * feat(Doc): update images * Update README.md * Update netlify.toml * feat(route): route add params mapping feature (#375) (#377) * feat(doc): update deploy manually doc * fix: mv config.yml to config-default.yml in the latest version of apisix (#383) * fix: wget config-default.yaml the output file need to be named config.yaml (#384) * fix #386 wget special output file use -O (#387) * feat(authentication): create authentication module (#330) * feat(authentication): create module typing definition * feat(authentication): create Login page * feat(authentication): update typing definition * feat(authentication): add centent to Login page * feat(authentication): update typing definition * feat(authentication): update Login page to add Password and Test method * feat(authentication): update typing definition to add check and submit function * feat(authentication): move Test login method to Example * feat(authentication): add check and submit function * feat(authentication): add submit function in Login page * feat(authentication): add test to Password login method * feat(authentication): change example LoginMethod text * feat(authentication): add i18n content * feat(authentication): redirect to index when login success * feat(i18n): update i18n file import remove import i18n file of user module manually and try auto import by umi.js * feat(authentication): create authentication configure items * fix(authentication): fix logging filter write back request body for read by PostForm function * feat(authentication): create authentication controller * feat(authentication): update dependencies * fix(authentication): fix logging filter * feat(authentication): change to session for authentication * feat(authentication): create authentication filter use authentication filter to check every request * feat(authentication): create unit test case * fix(authentication): change HTTP code when authentication fail request * feat(authentication): add jwt dependency * feat(authentication): create session configures * feat(authentication): change cookie-based session to jwt * feat(authentication): change cors Access-Control-Allow-Headers header * feat(authentication): change login page path and error handler * feat(authentication): create request interceptor to add Authorization header * feat(authentication): connect to backend login API and i18n * feat(authentication): create logout page * feat(authentication): add redirect query to back previous page * feat(authentication): update LoginMethod definition for logout * feat(authentication): add logout button * feat(authentication): improve login page * fix: clean codes * fix(authentication): fix unit test crash * feat(authentication): remove API url setting * feat(authentication): improve session check * feat(authentication): redirect to login page when not exist token * fix: clean codes and add ASF header * feat(User): update prefix * fix(ci): fix preview environment (#388) * fix README typo (#389) * fix(ci): fix read configuration file path in docker (#390) * doc: Introducing manager-api (#391) * Update nginx.conf * Update Dockerfile * Revert "Update Dockerfile" This reverts commit ea827bfd2789c2d939a2517b279170cccdadf35b. * fix: preview mysql pwd was wrong (#393) * README in Chinese (#398) * feat(doc): added Chinese version of README * fix(README.zh-CN.md): fix wrong link * fix(README.zh-CN.md): add link to README.md * fix(README.zh-CN.md): sync with README.md * fix(README.zh-CN.md): Fix some translation errors * fix: dashboard /user/login get error code 405 (#397) * fix: fix dashboard /user/login get error code 405 * fix: modify nginx according to giphoo proposal * fix(authentication): change Apache APISIX copyright (#401) * fix: configure only necessary items, such as etcd host (#405) * fix: configure only necessary items, such as etcd host * fix: configure only necessary items, such as etcd host * fix end of line * fix: using default admin key (#408) * fix: we need conf.json when deploying manager-api in local (#409) * fix: we need conf.json when deploying manager-api in loal * fix: log error when starting manager failed * fix: click create ssl prestep not response (#407) * fix: submit setting grafanaURl without validation (#413) * feat: support generate `script` for APISIX (#411) * feat: support generate `script` for APISIX * not run in `/root` dir * add `config.yaml` for APISIX * fix path * fix(authentication): change login api url (#414) * fix(authentication): change manager API login path * fix(authentication): change authentication unit test * fix(authentication): clean nginx.conf codes * fix(authentication): change login URL of front end * fix(authentication): change authentication filter rule Co-authored-by: kv <gxt...@163.com> Co-authored-by: nic-chen <33000667+nic-c...@users.noreply.github.com> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandembp.lan> Co-authored-by: juzhiyuan <juzhiy...@apache.org> Co-authored-by: Joey <majunj...@gmail.com> Co-authored-by: bzp2010 <bzp20000...@gmail.com> Co-authored-by: TikWind <65604564+tikw...@users.noreply.github.com> Co-authored-by: Lien <lil...@apache.org> Co-authored-by: Rapiz <ra...@foxmail.com> Co-authored-by: liuxiran <belovedx...@126.com> Co-authored-by: jie <jie123...@163.com> Co-authored-by: Rapiz <cont...@rapiz.me> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandemini.lan> Co-authored-by: Tusdasa翼 <tusd...@tusdasa.net> Co-authored-by: Shuyang Wu <wosoyo...@gmail.com> Co-authored-by: Baoyuan <baoyuan....@gmail.com> * feat: added chash * feat: update transform Co-authored-by: kv <gxt...@163.com> Co-authored-by: nic-chen <33000667+nic-c...@users.noreply.github.com> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandembp.lan> Co-authored-by: juzhiyuan <juzhiy...@apache.org> Co-authored-by: Joey <majunj...@gmail.com> Co-authored-by: bzp2010 <bzp20000...@gmail.com> Co-authored-by: TikWind <65604564+tikw...@users.noreply.github.com> Co-authored-by: Lien <lil...@apache.org> Co-authored-by: Rapiz <ra...@foxmail.com> Co-authored-by: liuxiran <belovedx...@126.com> Co-authored-by: jie <jie123...@163.com> Co-authored-by: Rapiz <cont...@rapiz.me> Co-authored-by: 琚致远 <juzhiy...@juzhiyuandemini.lan> Co-authored-by: Tusdasa翼 <tusd...@tusdasa.net> Co-authored-by: Shuyang Wu <wosoyo...@gmail.com> Co-authored-by: Baoyuan <baoyuan....@gmail.com> --- src/pages/Route/Create.tsx | 2 + .../Route/components/Step2/RequestRewriteView.tsx | 263 ++++++++++++--------- src/pages/Route/constants.ts | 16 ++ src/pages/Route/transform.ts | 12 +- src/pages/Route/typing.d.ts | 5 + src/pages/Upstream/components/Step1.tsx | 127 ++++++++-- src/pages/Upstream/constants.ts | 18 +- 7 files changed, 312 insertions(+), 131 deletions(-) diff --git a/src/pages/Route/Create.tsx b/src/pages/Route/Create.tsx index 326f667..51ee1c0 100644 --- a/src/pages/Route/Create.tsx +++ b/src/pages/Route/Create.tsx @@ -156,7 +156,9 @@ const Page: React.FC<Props> = (props) => { ...form2.getFieldsValue(), ...data, }); + setStep2Data({ ...form2.getFieldsValue(), ...params } as RouteModule.Step2Data); }); + return; } setStep2Data({ ...form2.getFieldsValue(), ...params } as RouteModule.Step2Data); }} diff --git a/src/pages/Route/components/Step2/RequestRewriteView.tsx b/src/pages/Route/components/Step2/RequestRewriteView.tsx index 8bb4616..da56ef1 100644 --- a/src/pages/Route/components/Step2/RequestRewriteView.tsx +++ b/src/pages/Route/components/Step2/RequestRewriteView.tsx @@ -21,7 +21,12 @@ import { Input, Row, Col, InputNumber, Button, Select } from 'antd'; import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons'; import { useIntl } from 'umi'; -import { FORM_ITEM_LAYOUT, FORM_ITEM_WITHOUT_LABEL } from '@/pages/Route/constants'; +import { + FORM_ITEM_LAYOUT, + FORM_ITEM_WITHOUT_LABEL, + HASH_KEY_LIST, + HASH_ON_LIST, +} from '@/pages/Route/constants'; import PanelSection from '@/components/PanelSection'; import styles from '../../Create.less'; import { fetchUpstreamList } from '../../service'; @@ -48,117 +53,157 @@ const RequestRewriteView: React.FC<Props> = ({ data, form, disabled, onChange }) }); }, []); const renderUpstreamMeta = () => ( - <Form.List name="upstreamHostList"> - {(fields, { add, remove }) => ( + <> + <Form.Item label="类型" name="type" rules={[{ required: true }]}> + <Select disabled={upstreamDisabled} onChange={(params) => onChange({ type: params })}> + <Select.Option value="roundrobin">roundrobin</Select.Option> + <Select.Option value="chash">chash</Select.Option> + </Select> + </Form.Item> + {step2Data.type === 'chash' && ( <> - {fields.map((field, index) => ( - <Form.Item - required - key={field.key} - {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)} - label={ - index === 0 ? formatMessage({ id: 'route.request.override.domain.name.or.ip' }) : '' - } - extra={ - index === 0 - ? formatMessage({ id: 'route.request.override.use.domain.name.default.analysis' }) - : '' - } - > - <Row style={{ marginBottom: '10px' }} gutter={16}> - <Col span={9}> - <Form.Item - style={{ marginBottom: 0 }} - name={[field.name, 'host']} - rules={[ - { - required: true, - message: formatMessage({ id: 'route.request.override.input.domain.or.ip' }), - }, - { - pattern: new RegExp( - /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/, - 'g', - ), - message: formatMessage({ id: 'route.request.override.domain.or.ip.rules' }), - }, - ]} - > - <Input - placeholder={formatMessage({ - id: 'route.request.override.domain.name.or.ip', - })} - disabled={upstreamDisabled} - /> - </Form.Item> - </Col> - <Col span={4}> - <Form.Item - style={{ marginBottom: 0 }} - name={[field.name, 'port']} - rules={[ - { - required: true, - message: formatMessage({ id: 'route.request.override.input.port.number' }), - }, - ]} - > - <InputNumber - placeholder={formatMessage({ id: 'route.request.override.port.number' })} - disabled={upstreamDisabled} - min={1} - max={65535} - /> - </Form.Item> - </Col> - <Col span={4} offset={1}> - <Form.Item - style={{ marginBottom: 0 }} - name={[field.name, 'weight']} - rules={[ - { - required: true, - message: formatMessage({ id: 'route.request.override.input.weight' }), - }, - ]} - > - <InputNumber - placeholder={formatMessage({ id: 'route.request.override.weight' })} - disabled={upstreamDisabled} - min={0} - max={1000} - /> - </Form.Item> - </Col> - <Col> - {!upstreamDisabled && - (fields.length > 1 ? ( - <MinusCircleOutlined - style={{ margin: '0 8px' }} - onClick={() => { - remove(field.name); - }} - /> - ) : null)} - </Col> - </Row> - </Form.Item> - ))} - {!upstreamDisabled && ( - <Form.Item {...FORM_ITEM_WITHOUT_LABEL}> - <Button - type="dashed" - onClick={() => { - add(); - }} - > - <PlusOutlined /> {formatMessage({ id: 'route.request.override.create' })} - </Button> - </Form.Item> - )} + <Form.Item label="Hash On" name="hash_on"> + <Select disabled={upstreamDisabled}> + {HASH_ON_LIST.map((item) => ( + <Select.Option value={item} key={item}> + {item} + </Select.Option> + ))} + </Select> + </Form.Item> + <Form.Item label="Key" name="key"> + <Select disabled={upstreamDisabled}> + {HASH_KEY_LIST.map((item) => ( + <Select.Option value={item} key={item}> + {item} + </Select.Option> + ))} + </Select> + </Form.Item> </> )} - </Form.List> + <Form.List name="upstreamHostList"> + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => ( + <Form.Item + required + key={field.key} + {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)} + label={ + index === 0 + ? formatMessage({ id: 'route.request.override.domain.name.or.ip' }) + : '' + } + extra={ + index === 0 + ? formatMessage({ + id: 'route.request.override.use.domain.name.default.analysis', + }) + : '' + } + > + <Row style={{ marginBottom: '10px' }} gutter={16}> + <Col span={9}> + <Form.Item + style={{ marginBottom: 0 }} + name={[field.name, 'host']} + rules={[ + { + required: true, + message: formatMessage({ + id: 'route.request.override.input.domain.or.ip', + }), + }, + { + pattern: new RegExp( + /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/, + 'g', + ), + message: formatMessage({ + id: 'route.request.override.domain.or.ip.rules', + }), + }, + ]} + > + <Input + placeholder={formatMessage({ + id: 'route.request.override.domain.name.or.ip', + })} + disabled={upstreamDisabled} + /> + </Form.Item> + </Col> + <Col span={4}> + <Form.Item + style={{ marginBottom: 0 }} + name={[field.name, 'port']} + rules={[ + { + required: true, + message: formatMessage({ + id: 'route.request.override.input.port.number', + }), + }, + ]} + > + <InputNumber + placeholder={formatMessage({ id: 'route.request.override.port.number' })} + disabled={upstreamDisabled} + min={1} + max={65535} + /> + </Form.Item> + </Col> + <Col span={4} offset={1}> + <Form.Item + style={{ marginBottom: 0 }} + name={[field.name, 'weight']} + rules={[ + { + required: true, + message: formatMessage({ id: 'route.request.override.input.weight' }), + }, + ]} + > + <InputNumber + placeholder={formatMessage({ id: 'route.request.override.weight' })} + disabled={upstreamDisabled} + min={0} + max={1000} + /> + </Form.Item> + </Col> + <Col> + {!upstreamDisabled && + (fields.length > 1 ? ( + <MinusCircleOutlined + style={{ margin: '0 8px' }} + onClick={() => { + remove(field.name); + }} + /> + ) : null)} + </Col> + </Row> + </Form.Item> + ))} + {!upstreamDisabled && ( + <Form.Item {...FORM_ITEM_WITHOUT_LABEL}> + <Button + type="dashed" + onClick={() => { + add(); + }} + > + <PlusOutlined /> {formatMessage({ id: 'route.request.override.create' })} + </Button> + </Form.Item> + )} + </> + )} + </Form.List> + </> ); const renderTimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>; diff --git a/src/pages/Route/constants.ts b/src/pages/Route/constants.ts index 435dbe9..0620bbd 100644 --- a/src/pages/Route/constants.ts +++ b/src/pages/Route/constants.ts @@ -60,6 +60,7 @@ export const DEFAULT_STEP_2_DATA: RouteModule.Step2Data = { upstreamHostList: [{} as RouteModule.UpstreamHost], upstreamHeaderList: [], upstreamPath: undefined, + type: 'roundrobin', mappingStrategy: undefined, rewriteType: 'keep', timeout: { @@ -82,3 +83,18 @@ export const INIT_CHART = { selected: {}, hovered: {}, }; + +export const HASH_KEY_LIST = [ + 'remote_addr', + 'host', + 'uri', + 'server_name', + 'server_addr', + 'request_uri', + 'query_string', + 'remote_port', + 'hostname', + 'arg_id', +]; + +export const HASH_ON_LIST = ['vars', 'header', 'cookie', 'consumer']; diff --git a/src/pages/Route/transform.ts b/src/pages/Route/transform.ts index 02ce6de..dae7d91 100644 --- a/src/pages/Route/transform.ts +++ b/src/pages/Route/transform.ts @@ -30,6 +30,12 @@ export const transformStepData = ({ upstream_header[header.header_name] = header.header_value || ''; }); + const chashData: any = {}; + if (step2Data.type === 'chash') { + chashData.key = step2Data.key; + chashData.hash_on = step2Data.hash_on; + } + let redirect: RouteModule.Redirect = {}; if (step1Data.redirectOption === 'disabled') { redirect = {}; @@ -70,7 +76,8 @@ export const transformStepData = ({ return [key, operator, value]; }), upstream: { - type: 'roundrobin', + type: step2Data.type, + ...chashData, nodes, timeout: step2Data.timeout, }, @@ -199,6 +206,9 @@ export const transformRouteData = (data: RouteModule.Body) => { const step2Data: RouteModule.Step2Data = { upstream_protocol, upstreamHeaderList, + type: upstream ? upstream.type : 'roundrobin', + hash_on: upstream ? upstream.hash_on : undefined, + key: upstream ? upstream.key : undefined, upstreamHostList: transformUpstreamNodes(upstream?.nodes), upstream_id, upstreamPath: upstream_path?.to, diff --git a/src/pages/Route/typing.d.ts b/src/pages/Route/typing.d.ts index 56d4e7a..9fc25dc 100644 --- a/src/pages/Route/typing.d.ts +++ b/src/pages/Route/typing.d.ts @@ -86,6 +86,9 @@ declare namespace RouteModule { type Step2Data = { upstream_protocol: 'http' | 'https' | 'keep'; + type: 'roundrobin' | 'chash'; + hash_on?: string; + key?: string; upstreamHostList: UpstreamHost[]; mappingStrategy: string | undefined; rewriteType: string | undefined; @@ -121,6 +124,8 @@ declare namespace RouteModule { vars: [string, Operator, string][]; upstream: { type: 'roundrobin' | 'chash'; + hash_on?: string; + key?: string; nodes: { [key: string]: number; }; diff --git a/src/pages/Upstream/components/Step1.tsx b/src/pages/Upstream/components/Step1.tsx index 9ac7f73..cfbf2b4 100644 --- a/src/pages/Upstream/components/Step1.tsx +++ b/src/pages/Upstream/components/Step1.tsx @@ -21,7 +21,12 @@ import { useIntl } from 'umi'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import Button from 'antd/es/button'; -import { FORM_ITEM_WITHOUT_LABEL, FORM_ITEM_LAYOUT } from '@/pages/Upstream/constants'; +import { + FORM_ITEM_WITHOUT_LABEL, + FORM_ITEM_LAYOUT, + HASH_KEY_LIST, + HASH_ON_LIST, +} from '@/pages/Upstream/constants'; type Props = { form: FormInstance; @@ -41,7 +46,6 @@ const initialValues = { }; const Step1: React.FC<Props> = ({ form, disabled }) => { - const { formatMessage } = useIntl(); const renderUpstreamMeta = () => ( @@ -53,7 +57,11 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { required key={field.key} {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)} - label={index === 0 ? formatMessage({ id: 'upstream.step.backend.server.domain.or.ip' }) : ''} + label={ + index === 0 + ? formatMessage({ id: 'upstream.step.backend.server.domain.or.ip' }) + : '' + } extra={ index === 0 ? formatMessage({ id: 'upstream.step.domain.name.default.analysis' }) @@ -66,7 +74,10 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { style={{ marginBottom: 0 }} name={[field.name, 'host']} rules={[ - { required: true, message: formatMessage({ id: 'upstream.step.input.domain.name.or.ip' }) }, + { + required: true, + message: formatMessage({ id: 'upstream.step.input.domain.name.or.ip' }), + }, { pattern: new RegExp( /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/, @@ -76,25 +87,48 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { }, ]} > - <Input placeholder={formatMessage({ id: 'upstream.step.domain.name.or.ip' })} disabled={disabled} /> + <Input + placeholder={formatMessage({ id: 'upstream.step.domain.name.or.ip' })} + disabled={disabled} + /> </Form.Item> </Col> <Col span={3}> <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'port']} - rules={[{ required: true, message: formatMessage({ id: 'upstream.step.input.port' }) }]} + rules={[ + { + required: true, + message: formatMessage({ id: 'upstream.step.input.port' }), + }, + ]} > - <InputNumber placeholder={formatMessage({ id: 'upstream.step.port' })} disabled={disabled} min={1} max={65535} /> + <InputNumber + placeholder={formatMessage({ id: 'upstream.step.port' })} + disabled={disabled} + min={1} + max={65535} + /> </Form.Item> </Col> <Col span={3}> <Form.Item style={{ marginBottom: 0 }} name={[field.name, 'weight']} - rules={[{ required: true, message: formatMessage({ id: 'upstream.step.input.weight' }) }]} + rules={[ + { + required: true, + message: formatMessage({ id: 'upstream.step.input.weight' }), + }, + ]} > - <InputNumber placeholder={formatMessage({ id: 'upstream.step.weight' })} disabled={disabled} min={0} max={1000} /> + <InputNumber + placeholder={formatMessage({ id: 'upstream.step.weight' })} + disabled={disabled} + min={0} + max={1000} + /> </Form.Item> </Col> <Col @@ -125,7 +159,8 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { add(); }} > - <PlusOutlined />{formatMessage({ id: 'upstream.step.create' })} + <PlusOutlined /> + {formatMessage({ id: 'upstream.step.create' })} </Button> </Form.Item> )} @@ -138,24 +173,73 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { return ( <Form {...FORM_ITEM_LAYOUT} form={form} initialValues={initialValues}> - <Form.Item label={formatMessage({ id: 'upstream.step.name' })} name="name" rules={[{ required: true }]} extra={formatMessage({ id: 'upstream.step.name.should.unique' })}> - <Input placeholder={formatMessage({ id: 'upstream.step.input.upstream.name' })} disabled={disabled} /> + <Form.Item + label={formatMessage({ id: 'upstream.step.name' })} + name="name" + rules={[{ required: true }]} + extra={formatMessage({ id: 'upstream.step.name.should.unique' })} + > + <Input + placeholder={formatMessage({ id: 'upstream.step.input.upstream.name' })} + disabled={disabled} + /> </Form.Item> <Form.Item label={formatMessage({ id: 'upstream.step.description' })} name="description"> - <Input.TextArea placeholder={formatMessage({ id: 'upstream.step.input.description' })} disabled={disabled} /> + <Input.TextArea + placeholder={formatMessage({ id: 'upstream.step.input.description' })} + disabled={disabled} + /> </Form.Item> - <Form.Item label={formatMessage({ id: 'upstream.step.type' })} name="type" rules={[{ required: true }]}> + <Form.Item + label={formatMessage({ id: 'upstream.step.type' })} + name="type" + rules={[{ required: true }]} + > <Select disabled={disabled}> <Select.Option value="roundrobin">roundrobin</Select.Option> - {/* TODO: chash */} + <Select.Option value="chash">chash</Select.Option> </Select> </Form.Item> + <Form.Item shouldUpdate> + {() => { + if (form.getFieldValue('type') === 'chash') { + return ( + <> + <Form.Item label="Hash On" name="hash_on" labelCol={{ span: 6 }}> + <Select disabled={disabled}> + {HASH_ON_LIST.map((item) => ( + <Select.Option value={item} key={item}> + {item} + </Select.Option> + ))} + </Select> + </Form.Item> + <Form.Item label="Key" name="key" labelCol={{ span: 6 }}> + <Select disabled={disabled}> + {HASH_KEY_LIST.map((item) => ( + <Select.Option value={item} key={item}> + {item} + </Select.Option> + ))} + </Select> + </Form.Item> + </> + ); + } + return null; + }} + </Form.Item> {renderUpstreamMeta()} <Form.Item label={formatMessage({ id: 'upstream.step.connect.timeout' })} required> <Form.Item name={['timeout', 'connect']} noStyle - rules={[{ required: true, message: formatMessage({ id: 'upstream.step.input.connect.timeout' }) }]} + rules={[ + { + required: true, + message: formatMessage({ id: 'upstream.step.input.connect.timeout' }), + }, + ]} > <InputNumber disabled={disabled} /> </Form.Item> @@ -165,7 +249,9 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { <Form.Item name={['timeout', 'send']} noStyle - rules={[{ required: true, message: formatMessage({ id: 'upstream.step.input.send.timeout' }) }]} + rules={[ + { required: true, message: formatMessage({ id: 'upstream.step.input.send.timeout' }) }, + ]} > <InputNumber disabled={disabled} /> </Form.Item> @@ -175,7 +261,12 @@ const Step1: React.FC<Props> = ({ form, disabled }) => { <Form.Item name={['timeout', 'read']} noStyle - rules={[{ required: true, message: formatMessage({ id: 'upstream.step.input.receive.timeout' }) }]} + rules={[ + { + required: true, + message: formatMessage({ id: 'upstream.step.input.receive.timeout' }), + }, + ]} > <InputNumber disabled={disabled} /> </Form.Item> diff --git a/src/pages/Upstream/constants.ts b/src/pages/Upstream/constants.ts index 25bb12f..c9e2306 100644 --- a/src/pages/Upstream/constants.ts +++ b/src/pages/Upstream/constants.ts @@ -18,9 +18,6 @@ export const FORM_ITEM_LAYOUT = { labelCol: { span: 6, }, - wrapperCol: { - span: 18, - }, }; export const FORM_ITEM_WITHOUT_LABEL = { @@ -29,3 +26,18 @@ export const FORM_ITEM_WITHOUT_LABEL = { sm: { span: 20, offset: 6 }, }, }; + +export const HASH_KEY_LIST = [ + 'remote_addr', + 'host', + 'uri', + 'server_name', + 'server_addr', + 'request_uri', + 'query_string', + 'remote_port', + 'hostname', + 'arg_id', +]; + +export const HASH_ON_LIST = ['vars', 'header', 'cookie', 'consumer'];