Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(route): add operators #2022

Merged
merged 8 commits into from
Aug 7, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 71 additions & 41 deletions web/src/pages/Route/components/Step1/MatchingRulesView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ import PanelSection from '@/components/PanelSection';
const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
advancedMatchingRules,
disabled,
onChange = () => { },
onChange = () => {},
}) => {
const [visible, setVisible] = useState(false);
const [mode, setMode] = useState<RouteModule.ModalType>('CREATE');
const [namePlaceholder, setNamePlaceholder] = useState('');
const [modalForm] = Form.useForm();

const [operatorValueSample, setOperatorValueSample] = useState("")
const [operatorValueSample, setOperatorValueSample] = useState('');

const { Option } = Select;

const { formatMessage } = useIntl();

const onOk = () => {
modalForm.validateFields().then((value: RouteModule.MatchingRule) => {
if (value.operator === "IN") {
if (value.operator === 'IN') {
try {
JSON.parse(value.value as string)
JSON.parse(value.value as string);
} catch (error) {
notification.warning({
message: formatMessage({ id: 'page.route.fields.vars.invalid' }),
description: formatMessage({ id: 'page.route.fields.vars.in.invalid' })
})
return
description: formatMessage({ id: 'page.route.fields.vars.in.invalid' }),
});
return;
}
}
if (mode === 'EDIT') {
Expand Down Expand Up @@ -136,9 +136,18 @@ const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
case '~~':
renderText = formatMessage({ id: 'page.route.regexMatch' });
break;
case '~*':
renderText = formatMessage({ id: 'page.royte.caseInsensitiveRegexMatch' });
break;
case 'IN':
renderText = formatMessage({ id: 'page.route.in' });
break;
case 'HAS':
liuxiran marked this conversation as resolved.
Show resolved Hide resolved
renderText = formatMessage({ id: 'page.route.has' });
break;
case '!':
renderText = formatMessage({ id: 'page.route.reverse' });
break;
default:
renderText = '';
}
Expand All @@ -153,27 +162,29 @@ 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 = () => {
return (
<Modal
title={mode === 'EDIT'
? formatMessage({ id: 'page.route.rule.edit' })
: formatMessage({ id: 'page.route.rule.create' })}
title={
mode === 'EDIT'
? formatMessage({ id: 'page.route.rule.edit' })
: formatMessage({ id: 'page.route.rule.create' })
}
centered
visible
onOk={onOk}
Expand Down Expand Up @@ -234,7 +245,9 @@ const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
message: formatMessage({ id: 'component.global.input.ruleMessage.name' }),
},
]}
tooltip={formatMessage({ id: 'page.route.form.itemRulesRequiredMessage.parameterName' })}
tooltip={formatMessage({
id: 'page.route.form.itemRulesRequiredMessage.parameterName',
})}
extra={namePlaceholder}
>
<Input />
Expand All @@ -245,30 +258,44 @@ const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
rules={[
{
required: true,
message: `${formatMessage({ id: 'component.global.pleaseChoose' })} ${formatMessage({
id: 'page.route.operationalCharacter',
})}`,
message: `${formatMessage({ id: 'component.global.pleaseChoose' })} ${formatMessage(
{
id: 'page.route.operationalCharacter',
},
)}`,
},
]}
>
<Select onChange={(e: string) => {
switch (e) {
case "IN":
setOperatorValueSample(formatMessage({ id: 'page.route.advanced-match.operator.sample.IN' }))
break
case "~~":
setOperatorValueSample(formatMessage({ id: 'page.route.advanced-match.operator.sample.~~' }))
break
default:
setOperatorValueSample("")
}
}}>
<Select
onChange={(e: string) => {
switch (e) {
case 'IN':
setOperatorValueSample(
formatMessage({ id: 'page.route.advanced-match.operator.sample.IN' }),
);
break;
case '~~':
case '~*':
setOperatorValueSample(
formatMessage({ id: 'page.route.advanced-match.operator.sample.~~' }),
);
break;
default:
setOperatorValueSample('');
}
}}
>
<Option value="==">{formatMessage({ id: 'page.route.equal' })}</Option>
<Option value="~=">{formatMessage({ id: 'page.route.unequal' })}</Option>
<Option value=">">{formatMessage({ id: 'page.route.greaterThan' })}</Option>
<Option value="<">{formatMessage({ id: 'page.route.lessThan' })}</Option>
<Option value="~~">{formatMessage({ id: 'page.route.regexMatch' })}</Option>
<Option value="~*">
{formatMessage({ id: 'page.route.caseInsensitiveRegexMatch' })}
</Option>
<Option value="IN">{formatMessage({ id: 'page.route.in' })}</Option>
<Option value="HAS">{formatMessage({ id: 'page.route.has' })}</Option>
<Option value="!">{formatMessage({ id: 'page.route.reverse' })}</Option>
</Select>
</Form.Item>
<Form.Item
Expand All @@ -288,11 +315,14 @@ const MatchingRulesView: React.FC<RouteModule.Step1PassProps> = ({
</Form.Item>
</Form>
</Modal>
)
);
};

return (
<PanelSection title={formatMessage({ id: 'page.route.panelSection.title.advancedMatchRule' })} tooltip={formatMessage({id: 'page.route.advanced-match.tooltip'})}>
<PanelSection
title={formatMessage({ id: 'page.route.panelSection.title.advancedMatchRule' })}
tooltip={formatMessage({ id: 'page.route.advanced-match.tooltip' })}
>
{!disabled && (
<Button
onClick={() => {
Expand Down
30 changes: 21 additions & 9 deletions web/src/pages/Route/locales/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ export default {
'page.route.greaterThan': 'Greater Than(>)',
'page.route.lessThan': 'Less Than(<)',
'page.route.regexMatch': 'Regex Match(~~)',
'page.route.caseInsensitiveRegexMatch': 'Case insensitive regular match(~*)',
'page.route.in': 'IN',
'page.route.has': 'HAS',
'page.route.reverse': 'Reverse the result(!)',
'page.route.rule': 'Rule',
'page.route.httpHeaderName': 'HTTP Request Header Name',
'page.route.service': 'Service',
Expand Down Expand Up @@ -140,23 +143,29 @@ export default {
'page.route.panelSection.title.requestOverride': 'Request Override',
'page.route.form.itemLabel.headerRewrite': 'Header Override',
'page.route.tooltip.pluginOrchOnlySupportChrome': 'Plugin orchestration only supports Chrome.',
'page.route.tooltip.pluginOrchWithoutProxyRewrite': 'Plugin orchestration mode cannot be used when request override is configured in Step 1.',
'page.route.tooltip.pluginOrchWithoutRedirect': 'Plugin orchestration mode cannot be used when Redirect in Step 1 is selected to enable HTTPS.',
'page.route.tooltip.pluginOrchWithoutProxyRewrite':
'Plugin orchestration mode cannot be used when request override is configured in Step 1.',
'page.route.tooltip.pluginOrchWithoutRedirect':
'Plugin orchestration mode cannot be used when Redirect in Step 1 is selected to enable HTTPS.',

'page.route.tabs.normalMode': 'Normal',
'page.route.tabs.orchestration': 'Orchestration',

'page.route.list.description': 'Route is the entry point of a request, which defines the matching rules between a client request and a service. A route can be associated with a service (Service), an upstream (Upstream), a service can correspond to a set of routes, and a route can correspond to an upstream object (a set of backend service nodes), so each request matching to a route will be proxied by the gateway to the route-bound upstream service.',
'page.route.list.description':
'Route is the entry point of a request, which defines the matching rules between a client request and a service. A route can be associated with a service (Service), an upstream (Upstream), a service can correspond to a set of routes, and a route can correspond to an upstream object (a set of backend service nodes), so each request matching to a route will be proxied by the gateway to the route-bound upstream service.',

'page.route.configuration.name.rules.required.description': 'Please enter the route name',
'page.route.configuration.name.placeholder': 'Please enter the route name',
'page.route.configuration.desc.tooltip': 'Please enter the description of the route',
'page.route.configuration.publish.tooltip': 'Used to control whether a route is published to the gateway immediately after it is created',
'page.route.configuration.publish.tooltip':
'Used to control whether a route is published to the gateway immediately after it is created',
'page.route.configuration.version.placeholder': 'Please enter the routing version number',
'page.route.configuration.version.tooltip': 'Version number of the route, e.g. V1',
'page.route.configuration.normal-labels.tooltip': 'Add custom labels to routes that can be used for route grouping.',
'page.route.configuration.normal-labels.tooltip':
'Add custom labels to routes that can be used for route grouping.',

'page.route.configuration.path.rules.required.description': 'Please enter a valid HTTP request path',
'page.route.configuration.path.rules.required.description':
'Please enter a valid HTTP request path',
'page.route.configuration.path.placeholder': 'Please enter the HTTP request path',
'page.route.configuration.remote_addrs.placeholder': 'Please enter the client address',
'page.route.configuration.host.placeholder': 'Please enter the HTTP request hostname',
Expand All @@ -169,12 +178,15 @@ export default {
'page.route.advanced-match.operator.sample.IN': 'Please enter an array, e.g ["1", "2"]',
'page.route.advanced-match.operator.sample.~~': 'Please enter a regular expression, e.g [a-z]+',
'page.route.fields.service_id.invalid': 'Please check the configuration of binding service',
'page.route.fields.service_id.without-upstream': 'If you do not bind the service, you must set the Upstream (Step 2)',
'page.route.advanced-match.tooltip': 'It supports route matching through request headers, request parameters and cookies, and can be applied to scenarios such as grayscale publishing and blue-green testing.',
'page.route.fields.service_id.without-upstream':
'If you do not bind the service, you must set the Upstream (Step 2)',
'page.route.advanced-match.tooltip':
'It supports route matching through request headers, request parameters and cookies, and can be applied to scenarios such as grayscale publishing and blue-green testing.',

'page.route.fields.custom.redirectOption.tooltip': 'This is related to redirect plugin',
'page.route.fields.service_id.tooltip': 'Bind Service object to reuse their configuration.',

'page.route.fields.vars.invalid': 'Please check the advanced match condition configuration',
'page.route.fields.vars.in.invalid': 'When using the IN operator, enter the parameter values in array format.',
'page.route.fields.vars.in.invalid':
'When using the IN operator, enter the parameter values in array format.',
};
15 changes: 11 additions & 4 deletions web/src/pages/Route/locales/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export default {
'page.route.greaterThan': '大于(>)',
'page.route.lessThan': '小于(<)',
'page.route.regexMatch': '正则匹配(~~)',
'page.route.caseInsensitiveRegexMatch': '不区分大小写的正则匹配(~*)',
juzhiyuan marked this conversation as resolved.
Show resolved Hide resolved
'page.route.in': 'IN',
'page.route.has': 'HAS',
'page.route.reverse': '非(!)',
'page.route.rule': '规则',
'page.route.host': '域名',
'page.route.path': '路径',
Expand Down Expand Up @@ -140,13 +143,16 @@ export default {
'page.route.button.selectFile': '请选择上传文件',
'page.route.list': '路由列表',
'page.route.tooltip.pluginOrchOnlySupportChrome': '插件编排仅支持 Chrome 浏览器。',
'page.route.tooltip.pluginOrchWithoutProxyRewrite': '当步骤一中 配置了 请求改写时,不可使用插件编排模式。',
'page.route.tooltip.pluginOrchWithoutRedirect': '当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。',
'page.route.tooltip.pluginOrchWithoutProxyRewrite':
'当步骤一中 配置了 请求改写时,不可使用插件编排模式。',
'page.route.tooltip.pluginOrchWithoutRedirect':
'当步骤一中 重定向 选择为 启用 HTTPS 时,不可使用插件编排模式。',

'page.route.tabs.normalMode': '普通模式',
'page.route.tabs.orchestration': '编排模式',

'page.route.list.description': '路由(Route)是请求的入口点,它定义了客户端请求与服务之间的匹配规则。路由可以与服务(Service)、上游(Upstream)关联,一个服务可对应一组路由,一个路由可以对应一个上游对象(一组后端服务节点),因此,每个匹配到路由的请求将被网关代理到路由绑定的上游服务中。',
'page.route.list.description':
'路由(Route)是请求的入口点,它定义了客户端请求与服务之间的匹配规则。路由可以与服务(Service)、上游(Upstream)关联,一个服务可对应一组路由,一个路由可以对应一个上游对象(一组后端服务节点),因此,每个匹配到路由的请求将被网关代理到路由绑定的上游服务中。',

'page.route.configuration.name.rules.required.description': '请输入路由名称',
'page.route.configuration.name.placeholder': '请输入路由名称',
Expand All @@ -170,7 +176,8 @@ export default {
'page.route.advanced-match.operator.sample.~~': '请输入正则表达式,示例:[a-z]+',
'page.route.fields.service_id.invalid': '请检查路由绑定的服务',
'page.route.fields.service_id.without-upstream': '如果不绑定服务,则必须设置上游服务(步骤 2)',
'page.route.advanced-match.tooltip': '支持通过请求头,请求参数、Cookie 进行路由匹配,可应用于灰度发布,蓝绿测试等场景。',
'page.route.advanced-match.tooltip':
'支持通过请求头,请求参数、Cookie 进行路由匹配,可应用于灰度发布,蓝绿测试等场景。',

'page.route.fields.custom.redirectOption.tooltip': '在此配置 redirect 插件',
'page.route.fields.service_id.tooltip': '绑定服务(Service)对象,以便复用其中的配置。',
Expand Down
20 changes: 10 additions & 10 deletions web/src/pages/Route/typing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/
declare namespace RouteModule {
type Operator = '==' | '~=' | '>' | '<' | '~~' | 'IN';
type Operator = '==' | '~=' | '>' | '<' | '~~' | '~*' | 'IN' | 'HAS' | '!';

type VarPosition = 'arg' | 'http' | 'cookie';

Expand All @@ -36,7 +36,7 @@ declare namespace RouteModule {
plugins: PluginPage.PluginData;
// TEMP
script: any;
plugin_config_id?: string
plugin_config_id?: string;
};

type UpstreamHost = {
Expand Down Expand Up @@ -130,15 +130,15 @@ declare namespace RouteModule {
type Kvobject = {
key: string;
value: string;
}
};
type ProxyRewrite = {
scheme?: 'keep' | 'http' | 'https';
uri?: string;
regex_uri?: string[];
host?: string;
kvHeaders?: Kvobject[];
headers?: Record<string, string>;
}
};

type AdvancedMatchingRules = {
advancedMatchingRules: MatchingRule[];
Expand Down Expand Up @@ -217,12 +217,12 @@ declare namespace RouteModule {
header_params?: any;
};

type debugResponse ={
code: number,
message: string,
data: any,
header: Record<string, string[]>
}
type debugResponse = {
code: number;
message: string;
data: any;
header: Record<string, string[]>;
};

type authData = {
authType: string;
Expand Down