This is an automated email from the ASF dual-hosted git repository.
juzhiyuan pushed a commit to branch chore-route
in repository https://gitbox.apache.org/repos/asf/apisix-dashboard.git
The following commit(s) were added to refs/heads/chore-route by this push:
new 6cf9694 added descriptions for Route fields & move them into small
part
6cf9694 is described below
commit 6cf9694000575d6b1b7be16f8498999c008bb381
Author: juzhiyuan <[email protected]>
AuthorDate: Wed Apr 7 23:38:54 2021 +0800
added descriptions for Route fields & move them into small part
---
docs/en/latest/I18N_USER_GUIDE.md | 7 +-
web/src/components/LabelsfDrawer/LabelsDrawer.tsx | 4 +-
web/src/locales/en-US/component.ts | 5 +-
web/src/locales/zh-CN/component.ts | 5 +-
web/src/pages/PluginTemplate/components/Step1.tsx | 3 +-
web/src/pages/Route/List.tsx | 2 +-
.../Route/components/Step1/MatchingRulesView.tsx | 35 +-
web/src/pages/Route/components/Step1/MetaView.tsx | 139 +++--
.../Route/components/Step1/RequestConfigView.tsx | 627 +++++++++++----------
web/src/pages/Route/locales/en-US.ts | 6 +-
web/src/pages/Route/locales/zh-CN.ts | 22 +-
11 files changed, 470 insertions(+), 385 deletions(-)
diff --git a/docs/en/latest/I18N_USER_GUIDE.md
b/docs/en/latest/I18N_USER_GUIDE.md
index 00f5db3..1bdb051 100644
--- a/docs/en/latest/I18N_USER_GUIDE.md
+++ b/docs/en/latest/I18N_USER_GUIDE.md
@@ -23,13 +23,13 @@ title: i18n User Guide
The Apache APISIX Dashboard uses
[@umijs/plugin-locale](https://umijs.org/plugins/plugin-locale) to solve the
i18n issues, in order to make the i18n more clear and reasonable, we would
recommend to obey the following rules
-## Location of locale configuration:
+## Location of locale configuration
- Please put **the global locales** under `src/locales`.
- Please put **each page's locale file** under `src/pages/$PAGE/locales`
folder.
- Please put **the Component's locale file** under
`src/components/$COMPONENT/locales` folder, and we **MUST** import them manually
-## How to name the key for each locale filed:
+## How to name the key for each locale filed
the key can be like this : [basicModule].[moduleName].[elementName].[...desc]
@@ -64,7 +64,6 @@ we have already defined many global keys, before you do i18n,
you can refer to [
```js
'page.route.form.itemRulesExtraMessage.parameterName': '仅支持字母和数字,且只能以字母开头',
-'page.route.form.itemLabel.apiName': 'API 名称',
'page.route.form.itemRulesPatternMessage.apiNameRule': '最大长度100,仅支持字母、数字、- 和
_,且只能以字母开头',
```
@@ -101,7 +100,7 @@ we have already defined many global keys, before you do
i18n, you can refer to [
**Example:**
```js
-'page.route.steps.stepTitle.defineApiRequest': '定义 API 请求',
+'page.route.steps.stepTitle.defineApiRequest': '设置路由信息',
```
- **Select**
diff --git a/web/src/components/LabelsfDrawer/LabelsDrawer.tsx
b/web/src/components/LabelsfDrawer/LabelsDrawer.tsx
index 33f5880..9f6ff5b 100644
--- a/web/src/components/LabelsfDrawer/LabelsDrawer.tsx
+++ b/web/src/components/LabelsfDrawer/LabelsDrawer.tsx
@@ -113,7 +113,7 @@ const LabelList = (disabled: boolean, labelList: LabelList,
filterList: string[]
};
const LabelsDrawer: React.FC<Props> = ({
- title = 'Label Manager',
+ title = "",
actionName = '',
disabled = false,
dataSource = [],
@@ -135,7 +135,7 @@ const LabelsDrawer: React.FC<Props> = ({
return (
<Drawer
- title={title}
+ title={title || formatMessage({ id: "component.label-manager" })}
placement="right"
width={512}
visible
diff --git a/web/src/locales/en-US/component.ts
b/web/src/locales/en-US/component.ts
index 55b6a8a..a2969c2 100644
--- a/web/src/locales/en-US/component.ts
+++ b/web/src/locales/en-US/component.ts
@@ -67,10 +67,11 @@ export default {
'component.global.name': 'Name',
'component.global.updateTime': 'UpdateAt',
'component.global.form.itemExtraMessage.nameGloballyUnique': 'Name should be
globally unique',
- 'component.global.input.placeholder.description': 'Can not more than 256
characters',
+ 'component.global.input.placeholder.description': 'Please enter the
description for this route, max 256 characters',
// User component
'component.user.loginByPassword': 'Username & Password',
'component.user.login': 'Login',
- 'component.document': 'Document'
+ 'component.document': 'Document',
+ 'component.label-manager': 'Label Manager'
};
diff --git a/web/src/locales/zh-CN/component.ts
b/web/src/locales/zh-CN/component.ts
index f4171ec..22b634c 100644
--- a/web/src/locales/zh-CN/component.ts
+++ b/web/src/locales/zh-CN/component.ts
@@ -62,11 +62,12 @@ export default {
'component.global.steps.stepTitle.pluginConfig': '插件配置',
'component.global.input.ruleMessage.name': '仅支持字母、数字、- 和 _,且只能以字母开头',
'component.global.form.itemExtraMessage.nameGloballyUnique': '名称需全局唯一',
- 'component.global.input.placeholder.description': '不超过 256 个字符',
+ 'component.global.input.placeholder.description': '请输入路由描述(内容不超过 256 个字符)',
// User component
'component.user.loginByPassword': '账号密码登录',
'component.user.login': '登录',
- 'component.document': '操作手册'
+ 'component.document': '操作手册',
+ 'component.label-manager': '标签管理器'
};
diff --git a/web/src/pages/PluginTemplate/components/Step1.tsx
b/web/src/pages/PluginTemplate/components/Step1.tsx
index dbeea66..6e46b77 100644
--- a/web/src/pages/PluginTemplate/components/Step1.tsx
+++ b/web/src/pages/PluginTemplate/components/Step1.tsx
@@ -42,7 +42,6 @@ const Step1: React.FC<Props> = ({ form, disabled }) => {
const NormalLabelComponent = () => {
const field = 'custom_normal_labels';
- const title = 'Label Manager';
return (
<React.Fragment>
<Form.Item label={formatMessage({ id: 'component.global.labels' })}
name={field}>
@@ -74,7 +73,7 @@ const Step1: React.FC<Props> = ({ form, disabled }) => {
const labels = form.getFieldValue(field) || [];
return (
<LabelsDrawer
- title={title}
+ title={formatMessage({ id: "component.label-manager" })}
actionName={field}
dataSource={labels}
disabled={disabled || false}
diff --git a/web/src/pages/Route/List.tsx b/web/src/pages/Route/List.tsx
index ccbfc91..75bddc5 100644
--- a/web/src/pages/Route/List.tsx
+++ b/web/src/pages/Route/List.tsx
@@ -333,7 +333,7 @@ const Page: React.FC = () => {
dataIndex: 'name',
},
{
- title: formatMessage({ id: 'page.route.domainName' }),
+ title: formatMessage({ id: 'page.route.host' }),
hideInSearch: true,
render: (_, record) => {
const list = record.hosts || (record.host && [record.host]) || [];
diff --git a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
index 7216bee..1b01077 100644
--- a/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
+++ b/web/src/pages/Route/components/Step1/MatchingRulesView.tsx
@@ -22,7 +22,7 @@ import { PanelSection } from '@api7-dashboard/ui';
const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
advancedMatchingRules,
disabled,
- onChange = () => {},
+ onChange = () => { },
}) => {
const [visible, setVisible] = useState(false);
const [mode, setMode] = useState<RouteModule.ModalType>('CREATE');
@@ -139,28 +139,27 @@ const MatchingRulesView:
React.FC<RouteModule.Step1PassProps> = ({
disabled
? {}
: {
- title: formatMessage({ id: 'component.global.operation' }),
- key: 'action',
- render: (_: any, record: RouteModule.MatchingRule) => (
- <Space size="middle">
- <a onClick={() => handleEdit(record)}>
- {formatMessage({ id: 'component.global.edit' })}
- </a>
- <a onClick={() => handleRemove(record.key)}>
- {formatMessage({ id: 'component.global.delete' })}
- </a>
- </Space>
- ),
- },
+ title: formatMessage({ id: 'component.global.operation' }),
+ key: 'action',
+ render: (_: any, record: RouteModule.MatchingRule) => (
+ <Space size="middle">
+ <a onClick={() => handleEdit(record)}>
+ {formatMessage({ id: 'component.global.edit' })}
+ </a>
+ <a onClick={() => handleRemove(record.key)}>
+ {formatMessage({ id: 'component.global.delete' })}
+ </a>
+ </Space>
+ ),
+ },
].filter((item) => Object.keys(item).length);
const renderModal = () => (
<Modal
- title={`${
- mode === 'EDIT'
+ title={`${mode === 'EDIT'
? formatMessage({ id: 'component.global.edit' })
: formatMessage({ id: 'component.global.create' })
- } ${formatMessage({ id: 'page.route.rule' })}`}
+ } ${formatMessage({ id: 'page.route.rule' })}`}
centered
visible
onOk={onOk}
@@ -277,7 +276,7 @@ const MatchingRulesView:
React.FC<RouteModule.Step1PassProps> = ({
marginBottom: 16,
}}
>
- {formatMessage({ id: 'component.global.create' })}
+ {formatMessage({ id: 'component.global.add' })}
</Button>
)}
<Table key="table" bordered dataSource={advancedMatchingRules}
columns={columns} />
diff --git a/web/src/pages/Route/components/Step1/MetaView.tsx
b/web/src/pages/Route/components/Step1/MetaView.tsx
index 8bfb2e1..b409180 100644
--- a/web/src/pages/Route/components/Step1/MetaView.tsx
+++ b/web/src/pages/Route/components/Step1/MetaView.tsx
@@ -16,7 +16,7 @@
*/
import React, { useEffect, useState } from 'react';
import Form from 'antd/es/form';
-import { Input, Switch, Select, Button, Tag, AutoComplete } from 'antd';
+import { Input, Switch, Select, Button, Tag, AutoComplete, Row, Col } from
'antd';
import { useIntl } from 'umi';
import { PanelSection } from '@api7-dashboard/ui';
@@ -30,17 +30,15 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
disabled, form, isEdit
const [labelList, setLabelList] = useState<LabelList>({});
useEffect(() => {
- // TODO: use a better state name
fetchLabelList().then(setLabelList);
}, []);
const NormalLabelComponent = () => {
const field = 'custom_normal_labels';
- const title = 'Label Manager';
return (
<React.Fragment>
- <Form.Item label={formatMessage({ id: 'component.global.labels' })}
name={field}>
+ <Form.Item label={formatMessage({ id: 'component.global.labels' })}
name={field} tooltip="为路由增加自定义标签,可用于路由分组。">
<Select
mode="tags"
style={{ width: '100%' }}
@@ -69,7 +67,7 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
disabled, form, isEdit
const labels = form.getFieldValue(field) || [];
return (
<LabelsDrawer
- title={title}
+ title={formatMessage({ id: "component.label-manager" })}
actionName={field}
dataSource={labels}
disabled={disabled || false}
@@ -88,66 +86,97 @@ const MetaView: React.FC<RouteModule.Step1PassProps> = ({
disabled, form, isEdit
const VersionLabelComponent = () => {
return (
- <React.Fragment>
- <Form.Item
- label={formatMessage({ id: 'component.global.version' })}
- name="custom_version_label"
- >
- <AutoComplete
- options={(labelList.API_VERSION || []).map((item) => ({ value:
item }))}
- disabled={disabled}
- />
- </Form.Item>
- </React.Fragment>
+ <Form.Item
+ label={formatMessage({ id: 'component.global.version' })}
tooltip="路由的版本号,如 V1">
+ <Row>
+ <Col span={10}>
+ <Form.Item
+ noStyle
+ name="custom_version_label"
+ >
+ <AutoComplete
+ options={(labelList.API_VERSION || []).map((item) => ({ value:
item }))}
+ disabled={disabled}
+ placeholder="请输入路由版本号"
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
);
};
+ const Name: React.FC = () => (
+ <Form.Item label={formatMessage({ id: 'component.global.name' })}
tooltip={formatMessage({ id:
'page.route.form.itemRulesPatternMessage.apiNameRule' })}>
+ <Row>
+ <Col span={10}>
+ <Form.Item
+ noStyle
+ name="name"
+ rules={[
+ {
+ required: true,
+ message: "请输入路由名称",
+ },
+ {
+ pattern: new RegExp(/^[a-zA-Z][a-zA-Z0-9_-]{0,100}$/, 'g'),
+ message: formatMessage({ id:
'page.route.form.itemRulesPatternMessage.apiNameRule' }),
+ },
+ ]}
+ >
+ <Input
+ placeholder="请输入路由名称"
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
+ const Description: React.FC = () => (
+ <Form.Item label={formatMessage({ id: 'component.global.description' })}
tooltip="路由的描述信息">
+ <Row>
+ <Col span={10}>
+ <Form.Item noStyle name="desc">
+ <Input.TextArea
+ placeholder={formatMessage({ id:
'component.global.input.placeholder.description' })}
+ disabled={disabled}
+ showCount
+ maxLength={256}
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
+ const Publish: React.FC = () => (
+ <Form.Item label={formatMessage({ id: 'page.route.publish' })}
tooltip="用于控制路由创建后,是否立即发布到网关">
+ <Row>
+ <Col>
+ <Form.Item
+ noStyle
+ name="status"
+ valuePropName="checked"
+ >
+ <Switch disabled={isEdit} />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
return (
<PanelSection title={formatMessage({ id:
'page.route.panelSection.title.nameDescription' })}>
- <Form.Item
- label={formatMessage({ id: 'component.global.name' })}
- name="name"
- rules={[
- {
- required: true,
- message: `${formatMessage({ id: 'component.global.pleaseEnter' })}
${formatMessage({
- id: 'page.route.form.itemLabel.apiName',
- })}`,
- },
- {
- pattern: new RegExp(/^[a-zA-Z][a-zA-Z0-9_-]{0,100}$/, 'g'),
- message: formatMessage({ id:
'page.route.form.itemRulesPatternMessage.apiNameRule' }),
- },
- ]}
- extra={formatMessage({ id:
'page.route.form.itemRulesPatternMessage.apiNameRule' })}
- >
- <Input
- placeholder={`${formatMessage({ id: 'component.global.pleaseEnter'
})} ${formatMessage({
- id: 'page.route.form.itemLabel.apiName',
- })}`}
- disabled={disabled}
- />
- </Form.Item>
+ <Name />
<NormalLabelComponent />
<VersionLabelComponent />
- <Form.Item label={formatMessage({ id: 'component.global.description' })}
name="desc">
- <Input.TextArea
- placeholder={formatMessage({ id:
'component.global.input.placeholder.description' })}
- disabled={disabled}
- showCount
- maxLength={256}
- />
- </Form.Item>
+ <Description />
+ <Publish />
- <Form.Item
- label={formatMessage({ id: 'page.route.publish' })}
- name="status"
- valuePropName="checked"
- >
- <Switch disabled={isEdit} />
- </Form.Item>
</PanelSection>
);
};
diff --git a/web/src/pages/Route/components/Step1/RequestConfigView.tsx
b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
index 7ad1631..08c55f8 100644
--- a/web/src/pages/Route/components/Step1/RequestConfigView.tsx
+++ b/web/src/pages/Route/components/Step1/RequestConfigView.tsx
@@ -28,10 +28,16 @@ import {
} from '@/pages/Route/constants';
import { fetchServiceList } from '../../service';
+const removeBtnStyle = {
+ marginLeft: 20,
+ display: 'flex',
+ alignItems: 'center',
+};
+
const RequestConfigView: React.FC<RouteModule.Step1PassProps> = ({
form,
disabled,
- onChange = () => {},
+ onChange = () => { },
}) => {
const { formatMessage } = useIntl();
const [serviceList, setServiceList] =
useState<ServiceModule.ResponseBody[]>([]);
@@ -45,47 +51,47 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
{(fields, { add, remove }) => {
return (
<div>
- {fields.map((field, index) => (
- <Form.Item
- {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
- label={index === 0 && formatMessage({ id:
'page.route.domainName' })}
- key={field.key}
- extra={
- index === 0 && formatMessage({ id:
'page.route.form.itemExtraMessage.domain' })
- }
- >
- <Form.Item
- {...field}
- validateTrigger={['onChange', 'onBlur']}
- rules={[
- {
- pattern: new RegExp(/(^\*?[a-zA-Z0-9._-]+$|^\*$)/, 'g'),
- message: formatMessage({
- id: 'page.route.form.itemRulesPatternMessage.domain',
- }),
- },
- ]}
- noStyle
- >
- <Input
- placeholder={`${formatMessage({
- id: 'component.global.pleaseEnter',
- })} ${formatMessage({ id: 'page.route.domainName' })}`}
- style={{ width: '60%' }}
- disabled={disabled}
- />
- </Form.Item>
- {!disabled && fields.length > 1 ? (
- <MinusCircleOutlined
- className="dynamic-delete-button"
- style={{ margin: '0 8px' }}
- onClick={() => {
- remove(field.name);
- }}
- />
- ) : null}
- </Form.Item>
- ))}
+ <Form.Item
+ label={formatMessage({ id: 'page.route.host' })}
+ tooltip={formatMessage({ id:
'page.route.form.itemExtraMessage.domain' })}
+ style={{ marginBottom: 0 }}
+ >
+ {fields.map((field, index) => (
+ <Row style={{ marginBottom: 10 }} gutter={16} key={index}>
+ <Col span={10}>
+ <Form.Item
+ {...field}
+ validateTrigger={['onChange', 'onBlur']}
+ rules={[
+ {
+ // NOTE:
https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L40
+ pattern: new RegExp(/^\\*?[0-9a-zA-Z-._]+$/, 'g'),
+ message: formatMessage({
+ id:
'page.route.form.itemRulesPatternMessage.domain',
+ }),
+ },
+ ]}
+ noStyle
+ >
+ <Input
+ placeholder="请输入 HTTP 请求域名"
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ <Col style={{ ...removeBtnStyle, marginLeft: -10 }}>
+ {!disabled && fields.length > 1 ? (
+ <MinusCircleOutlined
+ className="dynamic-delete-button"
+ onClick={() => {
+ remove(field.name);
+ }}
+ />
+ ) : null}
+ </Col>
+ </Row>
+ ))}
+ </Form.Item>
{!disabled && (
<Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
<Button
@@ -95,7 +101,7 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
add();
}}
>
- <PlusOutlined /> {formatMessage({ id:
'component.global.create' })}
+ <PlusOutlined /> {formatMessage({ id: 'component.global.add'
})}
</Button>
</Form.Item>
)}
@@ -110,61 +116,53 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
{(fields, { add, remove }) => {
return (
<div>
- {fields.map((field, index) => (
- <Form.Item
- {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
- label={index === 0 && formatMessage({ id: 'page.route.path' })}
- required
- key={field.key}
- extra={
- index === 0 && (
- <div>
- {formatMessage({ id:
'page.route.form.itemExtraMessage1.path' })}
- <br />
- {formatMessage({ id:
'page.route.form.itemExtraMessage2.path' })}
- </div>
- )
- }
- >
- <Form.Item
- {...field}
- validateTrigger={['onChange', 'onBlur']}
- rules={[
- {
- required: true,
- whitespace: true,
- message: `${formatMessage({
- id: 'component.global.pleaseEnter',
- })} ${formatMessage({ id: 'page.route.path' })}`,
- },
- {
- pattern: new
RegExp(/^\/[a-zA-Z0-9\-._~%!$&'()+,;=:@/]*\*?$/, 'g'),
- message: formatMessage({
- id: 'page.route.form.itemRulesPatternMessage.path',
- }),
- },
- ]}
- noStyle
- >
- <Input
- placeholder={`${formatMessage({
- id: 'component.global.pleaseEnter',
- })} ${formatMessage({ id: 'page.route.path' })}`}
- style={{ width: '60%' }}
- disabled={disabled}
- />
- </Form.Item>
- {!disabled && fields.length > 1 && (
- <MinusCircleOutlined
- className="dynamic-delete-button"
- style={{ margin: '0 8px' }}
- onClick={() => {
- remove(field.name);
- }}
- />
- )}
- </Form.Item>
- ))}
+ <Form.Item
+ label={formatMessage({ id: 'page.route.path' })}
+ required
+ tooltip={
+ formatMessage({ id: 'page.route.form.itemExtraMessage1.path' })
+ }
+ style={{ marginBottom: 0 }}
+ >
+ {fields.map((field, index) => (
+ <Row style={{ marginBottom: 10 }} gutter={16} key={index}>
+ <Col span={10}>
+ <Form.Item
+ {...field}
+ validateTrigger={['onChange', 'onBlur']}
+ rules={[
+ {
+ required: true,
+ whitespace: true,
+ message: "请输入有效的 HTTP 请求路径",
+ },
+ {
+ pattern: new
RegExp(/^\/[a-zA-Z0-9\-._~%!$&'()+,;=:@/]*\*?$/, 'g'),
+ message: formatMessage({
+ id: 'page.route.form.itemRulesPatternMessage.path',
+ }),
+ },
+ ]}
+ noStyle
+ >
+ <Input
+ placeholder="请输入 HTTP 请求路径"
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ <Col style={{ ...removeBtnStyle, marginLeft: -10 }}>
+ {!disabled && fields.length > 1 && (
+ <MinusCircleOutlined
+ className="dynamic-delete-button"
+ onClick={() => {
+ remove(field.name);
+ }}
+ />
+ )}</Col>
+ </Row>
+ ))}
+ </Form.Item>
{!disabled && (
<Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
<Button
@@ -174,7 +172,7 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
add();
}}
>
- <PlusOutlined /> {formatMessage({ id:
'component.global.create' })}
+ <PlusOutlined /> {formatMessage({ id: 'component.global.add'
})}
</Button>
</Form.Item>
)}
@@ -189,54 +187,49 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
{(fields, { add, remove }) => {
return (
<div>
- {fields.map((field, index) => (
- <Form.Item
- {...(index === 0 ? FORM_ITEM_LAYOUT : FORM_ITEM_WITHOUT_LABEL)}
- label={index === 0 && formatMessage({ id:
'page.route.remoteAddrs' })}
- key={field.key}
- extra={
- index === 0 && (
- <div>
- {formatMessage({ id:
'page.route.form.itemExtraMessage1.remoteAddrs' })}
- </div>
- )
- }
- >
- <Form.Item
- {...field}
- validateTrigger={['onChange', 'onBlur']}
- rules={[
- {
- pattern: new RegExp(
-
/^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$|^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}\/[0-9]{1,2}$|^([a-fA-F0-9]{0,4}:){0,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?$|^([a-fA-F0-9]{0,4}:){0,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?\/[0-9]{1,3}$/,
- 'g',
- ),
- message: formatMessage({
- id:
'page.route.form.itemRulesPatternMessage.remoteAddrs',
- }),
- },
- ]}
- noStyle
- >
- <Input
- placeholder={`${formatMessage({
- id: 'component.global.pleaseEnter',
- })} ${formatMessage({ id: 'page.route.remoteAddrs' })}`}
- style={{ width: '60%' }}
- disabled={disabled}
- />
- </Form.Item>
- {!disabled && fields.length > 1 && (
- <MinusCircleOutlined
- className="dynamic-delete-button"
- style={{ margin: '0 8px' }}
- onClick={() => {
- remove(field.name);
- }}
- />
- )}
- </Form.Item>
- ))}
+ <Form.Item
+ label={formatMessage({ id: 'page.route.remoteAddrs' })}
+ tooltip={formatMessage({ id:
'page.route.form.itemExtraMessage1.remoteAddrs' })}
+ style={{ marginBottom: 0 }}
+ >
+ {fields.map((field, index) => (
+ <Row style={{ marginBottom: 10 }} gutter={16} key={index}>
+ <Col span={10}>
+ <Form.Item
+ {...field}
+ validateTrigger={['onChange', 'onBlur']}
+ rules={[
+ {
+ pattern: new RegExp(
+
/^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$|^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}\/[0-9]{1,2}$|^([a-fA-F0-9]{0,4}:){0,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?$|^([a-fA-F0-9]{0,4}:){0,8}(:[a-fA-F0-9]{0,4}){0,8}([a-fA-F0-9]{0,4})?\/[0-9]{1,3}$/,
+ 'g',
+ ),
+ message: formatMessage({
+ id:
'page.route.form.itemRulesPatternMessage.remoteAddrs',
+ }),
+ },
+ ]}
+ noStyle
+ >
+ <Input
+ placeholder="请输入客户端地址"
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ <Col style={{ ...removeBtnStyle, marginLeft: -10 }}>
+ {!disabled && fields.length > 1 && (
+ <MinusCircleOutlined
+ className="dynamic-delete-button"
+ onClick={() => {
+ remove(field.name);
+ }}
+ />
+ )}
+ </Col>
+ </Row>
+ ))}
+ </Form.Item>
{!disabled && (
<Form.Item {...FORM_ITEM_WITHOUT_LABEL}>
<Button
@@ -246,7 +239,7 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
add();
}}
>
- <PlusOutlined /> {formatMessage({ id:
'component.global.create' })}
+ <PlusOutlined /> {formatMessage({ id: 'component.global.add'
})}
</Button>
</Form.Item>
)}
@@ -256,6 +249,201 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
</Form.List>
);
+ const HTTPMethods: React.FC = () => (
+ <Form.Item
+ label={formatMessage({ id: 'page.route.form.itemLabel.httpMethod' })}
+ >
+ <Row>
+ <Col span={10}>
+ <Form.Item
+ name="methods"
+ noStyle
+ >
+ <Select
+ mode="multiple"
+ style={{ width: '100%' }}
+ optionLabelProp="label"
+ disabled={disabled}
+ onChange={(value) => {
+ if ((value as string[]).includes('ALL')) {
+ form.setFieldsValue({
+ methods: ['ALL'],
+ });
+ }
+ }}
+ >
+ {['ALL'].concat(HTTP_METHOD_OPTION_LIST).map((item) => {
+ return (
+ <Select.Option key={item} value={item}>
+ {item}
+ </Select.Option>
+ );
+ })}
+ </Select>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
+ const RoutePriority: React.FC = () => (
+ <Form.Item label={formatMessage({ id: 'page.route.form.itemLabel.priority'
})}>
+ <Row>
+ <Col span={5}>
+ <Form.Item
+ noStyle
+ name="priority"
+ >
+ <InputNumber
+ placeholder={`Please input ${formatMessage({
+ id: 'page.route.form.itemLabel.priority',
+ })}`}
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
+ const WebSocket: React.FC = () => (
+ <Form.Item label="WebSocket">
+ <Row>
+ <Col>
+ <Form.Item noStyle valuePropName="checked" name="enable_websocket">
+ <Switch disabled={disabled} />
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
+ const Redirect: React.FC = () => {
+ const list = [
+ {
+ value: "forceHttps",
+ label: formatMessage({ id: 'page.route.select.option.enableHttps' })
+ }, {
+ value: "customRedirect",
+ label: formatMessage({ id: 'page.route.select.option.configCustom' })
+ }, {
+ value: "disabled",
+ label: formatMessage({ id: 'page.route.select.option.forbidden' })
+ }
+ ]
+
+ return (
+ <Form.Item label={formatMessage({ id:
'page.route.form.itemLabel.redirect' })}>
+ <Row>
+ <Col span={5}>
+ <Form.Item
+ name="redirectOption"
+ noStyle
+ >
+ <Select
+ disabled={disabled}
+ onChange={(parmas) => {
+ onChange({ action: 'redirectOptionChange', data: parmas });
+ }}
+ >
+ {list.map(item => (
+ <Select.Option value={item.value} key={item.value}>
+ {item.label}
+ </Select.Option>
+ ))}
+ </Select>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+ }
+
+ const CustomRedirect: React.FC = () => (
+ <Form.Item
+ noStyle
+ shouldUpdate={(prev, next) => {
+ if (prev.redirectOption !== next.redirectOption) {
+ onChange({ action: 'redirectOptionChange', data: next.redirectOption
});
+ }
+ return prev.redirectOption !== next.redirectOption;
+ }}
+ >
+ {() => {
+ if (form.getFieldValue('redirectOption') === 'customRedirect') {
+ return (
+ <Form.Item
+ label={formatMessage({ id:
'page.route.form.itemLabel.redirectCustom' })}
+ required
+ >
+ <Row gutter={10}>
+ <Col>
+ <Form.Item
+ name="redirectURI"
+ rules={[
+ {
+ required: true,
+ message: `${formatMessage({
+ id: 'component.global.pleaseEnter',
+ })}${formatMessage({
+ id: 'page.route.form.itemLabel.redirectURI',
+ })}`,
+ },
+ ]}
+ >
+ <Input
+ placeholder={formatMessage({
+ id: 'page.route.input.placeholder.redirectCustom',
+ })}
+ disabled={disabled}
+ />
+ </Form.Item>
+ </Col>
+ <Col span={10}>
+ <Form.Item name="ret_code" rules={[{ required: true }]}>
+ <Select disabled={disabled}>
+ <Select.Option value={301}>
+ {formatMessage({ id:
'page.route.select.option.redirect301' })}
+ </Select.Option>
+ <Select.Option value={302}>
+ {formatMessage({ id:
'page.route.select.option.redirect302' })}
+ </Select.Option>
+ </Select>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ );
+ }
+ return null;
+ }}
+ </Form.Item>
+ )
+
+ const ServiceSelector: React.FC = () => (
+ <Form.Item label={formatMessage({ id: 'page.route.service' })}>
+ <Row>
+ <Col span={5}>
+ <Form.Item noStyle name="service_id">
+ <Select disabled={disabled}>
+ {/* TODO: value === '' means no service_id select, need to find
a better way */}
+ <Select.Option value=""
key={Math.random().toString(36).substring(7)}>
+ None
+ </Select.Option>
+ {serviceList.map((item) => {
+ return (
+ <Select.Option value={item.id} key={item.id}>
+ {item.name}
+ </Select.Option>
+ );
+ })}
+ </Select>
+ </Form.Item>
+ </Col>
+ </Row>
+ </Form.Item>
+ )
+
return (
<PanelSection
title={formatMessage({ id:
'page.route.panelSection.title.requestConfigBasicDefine' })}
@@ -263,141 +451,14 @@ const RequestConfigView:
React.FC<RouteModule.Step1PassProps> = ({
<HostList />
<UriList />
<RemoteAddrList />
- <Form.Item
- label={formatMessage({ id: 'page.route.form.itemLabel.httpMethod' })}
- name="methods"
- >
- <Select
- mode="multiple"
- style={{ width: '100%' }}
- optionLabelProp="label"
- disabled={disabled}
- onChange={(value) => {
- if ((value as string[]).includes('ALL')) {
- form.setFieldsValue({
- methods: ['ALL'],
- });
- }
- }}
- >
- {['ALL'].concat(HTTP_METHOD_OPTION_LIST).map((item) => {
- return (
- <Select.Option key={item} value={item}>
- {item}
- </Select.Option>
- );
- })}
- </Select>
- </Form.Item>
- <Form.Item
- label={formatMessage({ id: 'page.route.form.itemLabel.priority' })}
- name="priority"
- >
- <InputNumber
- placeholder={`Please input ${formatMessage({
- id: 'page.route.form.itemLabel.priority',
- })}`}
- style={{ width: '60%' }}
- disabled={disabled}
- />
- </Form.Item>
- <Form.Item label="Websocket" valuePropName="checked"
name="enable_websocket">
- <Switch disabled={disabled} />
- </Form.Item>
- <Form.Item
- label={formatMessage({ id: 'page.route.form.itemLabel.redirect' })}
- name="redirectOption"
- >
- <Select
- disabled={disabled}
- onChange={(parmas) => {
- onChange({ action: 'redirectOptionChange', data: parmas });
- }}
- >
- <Select.Option value="forceHttps">
- {formatMessage({ id: 'page.route.select.option.enableHttps' })}
- </Select.Option>
- <Select.Option value="customRedirect">
- {formatMessage({ id: 'page.route.select.option.configCustom' })}
- </Select.Option>
- <Select.Option value="disabled">
- {formatMessage({ id: 'page.route.select.option.forbidden' })}
- </Select.Option>
- </Select>
- </Form.Item>
- <Form.Item
- noStyle
- shouldUpdate={(prev, next) => {
- if (prev.redirectOption !== next.redirectOption) {
- onChange({ action: 'redirectOptionChange', data:
next.redirectOption });
- }
- return prev.redirectOption !== next.redirectOption;
- }}
- >
- {() => {
- if (form.getFieldValue('redirectOption') === 'customRedirect') {
- return (
- <Form.Item
- label={formatMessage({ id:
'page.route.form.itemLabel.redirectCustom' })}
- required
- >
- <Row gutter={10}>
- <Col>
- <Form.Item
- name="redirectURI"
- rules={[
- {
- required: true,
- message: `${formatMessage({
- id: 'component.global.pleaseEnter',
- })}${formatMessage({
- id: 'page.route.form.itemLabel.redirectURI',
- })}`,
- },
- ]}
- >
- <Input
- placeholder={formatMessage({
- id: 'page.route.input.placeholder.redirectCustom',
- })}
- disabled={disabled}
- />
- </Form.Item>
- </Col>
- <Col span={10}>
- <Form.Item name="ret_code" rules={[{ required: true }]}>
- <Select disabled={disabled}>
- <Select.Option value={301}>
- {formatMessage({ id:
'page.route.select.option.redirect301' })}
- </Select.Option>
- <Select.Option value={302}>
- {formatMessage({ id:
'page.route.select.option.redirect302' })}
- </Select.Option>
- </Select>
- </Form.Item>
- </Col>
- </Row>
- </Form.Item>
- );
- }
- return null;
- }}
- </Form.Item>
- <Form.Item label={formatMessage({ id: 'page.route.service' })}
name="service_id">
- <Select disabled={disabled}>
- {/* TODO: value === '' means no service_id select, need to find a
better way */}
- <Select.Option value=""
key={Math.random().toString(36).substring(7)}>
- None
- </Select.Option>
- {serviceList.map((item) => {
- return (
- <Select.Option value={item.id} key={item.id}>
- {item.name}
- </Select.Option>
- );
- })}
- </Select>
- </Form.Item>
+ <HTTPMethods />
+ <RoutePriority />
+ <WebSocket />
+
+ <Redirect />
+ <CustomRedirect />
+
+ <ServiceSelector />
</PanelSection>
);
};
diff --git a/web/src/pages/Route/locales/en-US.ts
b/web/src/pages/Route/locales/en-US.ts
index 5d4be8a..c1d8773 100644
--- a/web/src/pages/Route/locales/en-US.ts
+++ b/web/src/pages/Route/locales/en-US.ts
@@ -52,7 +52,6 @@ export default {
'page.route.panelSection.title.advancedMatchRule': 'Advanced Routing
Matching Conditions',
'page.route.panelSection.title.nameDescription': 'Name And Description',
- 'page.route.form.itemLabel.apiName': 'API Name',
'page.route.form.itemRulesPatternMessage.apiNameRule':
'Maximum length 100, only letters, Numbers, _, and - are supported, and
can only begin with letters',
@@ -78,8 +77,7 @@ export default {
'page.route.form.itemRulesPatternMessage.domain':
'Only letters, numbers and * are supported. * can only be at the
beginning, and only single * is supported',
'page.route.form.itemExtraMessage1.path':
- '1. Request path, for example: /foo/index.html, supports request path
prefix /foo/* ;',
- 'page.route.form.itemExtraMessage2.path': '2. /* represents all paths',
+ 'HTTP Request path, for example: /foo/index.html, supports request path
prefix /foo/* ; /* represents all paths',
'page.route.form.itemRulesPatternMessage.path': 'Begin with / , and * can
only at the end',
'page.route.form.itemRulesPatternMessage.remoteAddrs':
'Please enter a valid IP address, for example: 192.168.1.101,
192.168.1.0/24, ::1, fe80::1, fe80::1/64',
@@ -124,7 +122,7 @@ export default {
'page.route.form.itemHelp.status':
'Whether a route can be used after it is created, the default value is
false.',
- 'page.route.domainName': 'Domain Name',
+ 'page.route.host': 'Host',
'page.route.path': 'Path',
'page.route.remoteAddrs': 'Remote Addrs',
'page.route.PanelSection.title.defineRequestParams': 'Define Request
Parameters',
diff --git a/web/src/pages/Route/locales/zh-CN.ts
b/web/src/pages/Route/locales/zh-CN.ts
index 54410b2..ec48bde 100644
--- a/web/src/pages/Route/locales/zh-CN.ts
+++ b/web/src/pages/Route/locales/zh-CN.ts
@@ -28,7 +28,7 @@ export default {
'page.route.regexMatch': '正则匹配',
'page.route.in': 'IN',
'page.route.rule': '规则',
- 'page.route.domainName': '域名',
+ 'page.route.host': '域名',
'page.route.path': '路径',
'page.route.remoteAddrs': '客户端地址',
'page.route.value': '参数值',
@@ -61,9 +61,8 @@ export default {
'page.route.input.placeholder.paramValue': '参数值',
// form
'page.route.form.itemRulesRequiredMessage.parameterName':
'仅支持字母和数字,且只能以字母开头',
- 'page.route.form.itemLabel.apiName': 'API 名称',
'page.route.form.itemRulesPatternMessage.apiNameRule':
- '最大长度100,仅支持字母、数字、- 和 _,且只能以字母开头',
+ '路由的名称,最大长度100,仅支持字母、数字、- 和 _,且只能以字母开头',
'page.route.form.itemLabel.httpMethod': 'HTTP 方法',
'page.route.form.itemLabel.scheme': '协议',
'page.route.form.itemLabel.priority': '优先级',
@@ -73,15 +72,14 @@ export default {
'page.route.form.itemLabel.hostRewriteType': '域名改写',
'page.route.form.itemLabel.headerRewrite': '请求头改写',
'page.route.form.itemLabel.redirectURI': '重定向路径',
- 'page.route.form.itemExtraMessage.domain': '域名或IP,支持泛域名,如:*.test.com',
+ 'page.route.form.itemExtraMessage.domain': '路由匹配的域名列表。支持泛域名,如:*.test.com',
'page.route.form.itemRulesPatternMessage.domain':
'仅支持字母、数字和 * ,且 * 只能是在开头,支持单个 * ',
'page.route.form.itemExtraMessage1.path':
- '1. 请求路径,如 /foo/index.html,支持请求路径前缀 /foo/* ;',
- 'page.route.form.itemExtraMessage2.path': '2. /* 代表所有路径',
+ 'HTTP 请求路径,如 /foo/index.html,支持请求路径前缀 /foo/*。/* 代表所有路径',
'page.route.form.itemRulesPatternMessage.path': '以 / 开头,且 * 只能在最后',
'page.route.form.itemExtraMessage1.remoteAddrs':
- '客户端 IP,例如:192.168.1.101,192.168.1.0/24,::1,fe80::1,fe80::1/64',
+ '客户端与服务器握手时 IP,即客户端
IP,例如:192.168.1.101,192.168.1.0/24,::1,fe80::1,fe80::1/64',
'page.route.form.itemRulesPatternMessage.remoteAddrs':
'请输入合法的 IP 地址,例如:192.168.1.101,192.168.1.0/24,::1,fe80::1,fe80::1/64',
'page.route.form.itemLabel.username': '用户名',
@@ -98,15 +96,15 @@ export default {
'page.route.select.option.inputManually': '手动填写',
// steps
- 'page.route.steps.stepTitle.defineApiRequest': '定义 API 请求',
- 'page.route.steps.stepTitle.defineApiBackendServe': '定义 API 后端服务',
+ 'page.route.steps.stepTitle.defineApiRequest': '设置路由信息',
+ 'page.route.steps.stepTitle.defineApiBackendServe': '设置上游服务',
// panelSection
- 'page.route.panelSection.title.nameDescription': '名称及其描述',
+ 'page.route.panelSection.title.nameDescription': '基本信息',
'page.route.panelSection.title.httpOverrideRequestHeader': 'HTTP 请求头改写',
'page.route.panelSection.title.requestOverride': '请求改写',
- 'page.route.panelSection.title.requestConfigBasicDefine': '请求基础定义',
- 'page.route.panelSection.title.advancedMatchRule': '高级路由匹配条件',
+ 'page.route.panelSection.title.requestConfigBasicDefine': '匹配条件',
+ 'page.route.panelSection.title.advancedMatchRule': '高级匹配条件',
'page.route.PanelSection.title.defineRequestParams': '请求参数定义',
'page.route.PanelSection.title.responseResult': '请求响应结果',