You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ju...@apache.org on 2020/09/01 01:43:11 UTC
[apisix-dashboard] branch master updated: feat: added chash (#429)
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 <31...@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 <ma...@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 <ma...@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 <ma...@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 <gx...@163.com>
Co-authored-by: 琚致远 <ju...@juzhiyuandeMini.lan>
* feat: cherry-pick 4fd0ce79bb34dbe8c31b7a27884930e3b0e5437c
* feat(compose): remove images
* feat: added line
Co-authored-by: kv <gx...@163.com>
Co-authored-by: 琚致远 <ju...@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 <gx...@163.com>
Co-authored-by: nic-chen <33...@users.noreply.github.com>
Co-authored-by: 琚致远 <ju...@juzhiyuandeMBP.lan>
Co-authored-by: juzhiyuan <ju...@apache.org>
Co-authored-by: Joey <ma...@gmail.com>
Co-authored-by: bzp2010 <bz...@gmail.com>
Co-authored-by: TikWind <65...@users.noreply.github.com>
Co-authored-by: Lien <li...@apache.org>
Co-authored-by: Rapiz <ra...@foxmail.com>
Co-authored-by: liuxiran <be...@126.com>
Co-authored-by: jie <ji...@163.com>
Co-authored-by: Rapiz <co...@rapiz.me>
Co-authored-by: 琚致远 <ju...@juzhiyuandeMini.lan>
Co-authored-by: Tusdasa翼 <tu...@tusdasa.net>
Co-authored-by: Shuyang Wu <wo...@gmail.com>
Co-authored-by: Baoyuan <ba...@gmail.com>
* feat: added chash
* feat: update transform
Co-authored-by: kv <gx...@163.com>
Co-authored-by: nic-chen <33...@users.noreply.github.com>
Co-authored-by: 琚致远 <ju...@juzhiyuandeMBP.lan>
Co-authored-by: juzhiyuan <ju...@apache.org>
Co-authored-by: Joey <ma...@gmail.com>
Co-authored-by: bzp2010 <bz...@gmail.com>
Co-authored-by: TikWind <65...@users.noreply.github.com>
Co-authored-by: Lien <li...@apache.org>
Co-authored-by: Rapiz <ra...@foxmail.com>
Co-authored-by: liuxiran <be...@126.com>
Co-authored-by: jie <ji...@163.com>
Co-authored-by: Rapiz <co...@rapiz.me>
Co-authored-by: 琚致远 <ju...@juzhiyuandeMini.lan>
Co-authored-by: Tusdasa翼 <tu...@tusdasa.net>
Co-authored-by: Shuyang Wu <wo...@gmail.com>
Co-authored-by: Baoyuan <ba...@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'];