diff --git a/web/cypress/fixtures/data.json b/web/cypress/fixtures/data.json index 1ea7bcdd53..9c14e98a6d 100644 --- a/web/cypress/fixtures/data.json +++ b/web/cypress/fixtures/data.json @@ -20,6 +20,8 @@ "host1": "11.11.11.11", "host2": "12.12.12.12", "host3": "10.10.10.10", + "port": "80", + "weight": 1, "description": "desc_by_autotest", "description2": "description2", "grafanaAddress": "Grafana Address", diff --git a/web/cypress/fixtures/selector.json b/web/cypress/fixtures/selector.json index b903e0f630..f075bc0ff4 100644 --- a/web/cypress/fixtures/selector.json +++ b/web/cypress/fixtures/selector.json @@ -1,7 +1,6 @@ { "codeMirror": ".CodeMirror", "username": "#username", - "languageSwitcher": ".ant-space-align-center", "dropdown": ".rc-virtual-list", "notification": ".ant-notification-notice-message", @@ -9,12 +8,10 @@ "notificationCloseIcon": ".ant-notification-close-icon", "notificationDesc": ".ant-notification-notice-description", "errorNotification:": ".ant-notification-notice-error", - "pluginCard": ".ant-card", "pluginCardBordered": ".ant-card-bordered", "pageContent": ".ant-pro-page-container", - - "tableBody":".ant-table-tbody", + "tableBody": ".ant-table-tbody", "tableCell": ".ant-table-cell", "empty": ".ant-empty-normal", "refresh": ".anticon-reload", @@ -22,16 +19,15 @@ "disabledSwitcher": "#disable", "checkedSwitcher": ".ant-switch-checked", "deleteButton": ".ant-btn-dangerous", - "name": "#name", "nodes_0_host": "#nodes_0_host", "nodes_0_port": "#nodes_0_port", + "nodes_0_weight": "#nodes_0_weight", "upstream_id": "#upstream_id", "input": ":input", "nameSelector": "[title=Name]", "serviceSelector": "[title=test_service]", "nameSearch": "[title=Name]", - "description": "#desc", "upstreamSelector": "[data-cy=upstream_selector]", "addHost": "[data-cy=addHost]", @@ -43,27 +39,21 @@ "ruleCard": ".ant-modal", "operator": "#operator", "value": "#value", - "fileSelector": "[type=file]", "fileTypeRadio": "[type=radio]", "fileSelectorClose": ".ant-modal-close", - "debugUri": "#debugUri", - "hosts_0": "#hosts_0", "labels_0_labelKey": "#labels_0_labelKey", "labels_0_labelValue": "#labels_0_labelValue", "labelSelector": "[title=Labels]", "labelSelect_0": ".ant-select-selection-overflow", - "pageContainer": ".ant-pro-page-container", "notificationMessage": ".ant-notification-notice-message", "avatar": ".ant-space-align-center", "grafanaURL": "#grafanaURL", "explain": ".ant-form-item-explain", - "upstreamType": ".ant-select-item-option-content", - "errorExplain": ".ant-form-item-explain", "usernameInput": "#control-ref_username", "passwordInput": "#control-ref_password", @@ -76,7 +66,6 @@ "notificationClose": ".anticon-close", "redirectURIInput": "#redirectURI", "redirectCodeSelector": "#ret_code", - "paginationOptions": ".ant-pagination-options", "fiftyPerPage": "[title=\"50 / page\"]", "twentyPerPage": "[title=\"20 / page\"]", @@ -85,7 +74,6 @@ "pageTwoActived": ".ant-pagination-item-2.ant-pagination-item-active", "selectDropdown": ".ant-select-dropdown", "codeMirrorMode": "[data-cy='code-mirror-mode']", - "selectJSON":".ant-select-dropdown [label=JSON]", - + "selectJSON": ".ant-select-dropdown [label=JSON]", "deleteAlert": ".ant-modal-body" } diff --git a/web/cypress/integration/pluginTemplate/create-plugin-template-with-route.spec.js b/web/cypress/integration/pluginTemplate/create-plugin-template-with-route.spec.js index 8be0f49d5c..4a3b70384a 100644 --- a/web/cypress/integration/pluginTemplate/create-plugin-template-with-route.spec.js +++ b/web/cypress/integration/pluginTemplate/create-plugin-template-with-route.spec.js @@ -46,6 +46,8 @@ context('Create PluginTemplate Binding To Route', () => { cy.get(this.domSelector.name).type(this.data.routeName); cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); cy.contains('Custom').should('be.visible'); cy.get(this.domSelector.customSelector).click(); diff --git a/web/cypress/integration/route/can-skip-upstream-when-select-service-id.spec.js b/web/cypress/integration/route/can-skip-upstream-when-select-service-id.spec.js index 0ca5d28d82..d62cd87018 100644 --- a/web/cypress/integration/route/can-skip-upstream-when-select-service-id.spec.js +++ b/web/cypress/integration/route/can-skip-upstream-when-select-service-id.spec.js @@ -31,6 +31,8 @@ context('Can select service_id skip upstream in route', () => { cy.get(this.domSelector.name).type(this.data.upstreamName); cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); + cy.get(this.domSelector.nodes_0_port).clear().type('7000'); + cy.get(this.domSelector.nodes_0_weight).clear().type(1); cy.contains('Next').click(); cy.contains('Submit').click(); cy.get(this.domSelector.notification).should('contain', this.data.createUpstreamSuccess); diff --git a/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js index 6e16a7eef9..59190484d4 100644 --- a/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js +++ b/web/cypress/integration/route/create-edit-duplicate-delete-route.spec.js @@ -65,6 +65,8 @@ context('Create and Delete Route', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type(this.data.host2); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); // redirect plugin should not display in route step3 diff --git a/web/cypress/integration/route/create-route-with-api-breaker-form.spec.js b/web/cypress/integration/route/create-route-with-api-breaker-form.spec.js index 8437d9d32b..93e3e2aaa2 100644 --- a/web/cypress/integration/route/create-route-with-api-breaker-form.spec.js +++ b/web/cypress/integration/route/create-route-with-api-breaker-form.spec.js @@ -40,6 +40,8 @@ context('Create and delete route with api-breaker form', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); // config api-breaker plugin diff --git a/web/cypress/integration/route/create-route-with-cors-form.spec.js b/web/cypress/integration/route/create-route-with-cors-form.spec.js index 915effba32..beabcccff9 100644 --- a/web/cypress/integration/route/create-route-with-cors-form.spec.js +++ b/web/cypress/integration/route/create-route-with-cors-form.spec.js @@ -40,6 +40,8 @@ context('Create and delete route with cors form', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); // config cors plugin diff --git a/web/cypress/integration/route/create-route-with-limit-req-form.spec.js b/web/cypress/integration/route/create-route-with-limit-req-form.spec.js index 8609d0462d..e215163509 100644 --- a/web/cypress/integration/route/create-route-with-limit-req-form.spec.js +++ b/web/cypress/integration/route/create-route-with-limit-req-form.spec.js @@ -42,6 +42,8 @@ context('Create and delete route with limit-req form', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); // config limit-req plugin diff --git a/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js b/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js index 7637568731..87ac24daaf 100644 --- a/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js +++ b/web/cypress/integration/route/create-route-with-proxy-mirror-form.spec.js @@ -40,6 +40,8 @@ context('Create and delete route with proxy-mirror form', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); // config proxy-mirror plugin @@ -100,6 +102,6 @@ context('Create and delete route with proxy-mirror form', () => { cy.contains('OK').click(); }); cy.get(domSelector.notification).should('contain', data.deleteRouteSuccess); - cy.get(domSelector.notificationCloseIcon).click(); + cy.get(domSelector.notificationCloseIcon).click({ multiple: true }); }); }); diff --git a/web/cypress/integration/route/create-route-with-proxy-rewrite-plugin.spec.js b/web/cypress/integration/route/create-route-with-proxy-rewrite-plugin.spec.js index 1d8173d086..5ce18fdcf5 100644 --- a/web/cypress/integration/route/create-route-with-proxy-rewrite-plugin.spec.js +++ b/web/cypress/integration/route/create-route-with-proxy-rewrite-plugin.spec.js @@ -90,6 +90,8 @@ context('create route with proxy-rewrite plugin', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type(this.data.host2); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); // should not see proxy-rewrite plugin in the step3 diff --git a/web/cypress/integration/route/create-route-with-referer-restriction-form.spec.js b/web/cypress/integration/route/create-route-with-referer-restriction-form.spec.js index 39e13a340f..4987d16fe1 100644 --- a/web/cypress/integration/route/create-route-with-referer-restriction-form.spec.js +++ b/web/cypress/integration/route/create-route-with-referer-restriction-form.spec.js @@ -40,6 +40,8 @@ context('Create and delete route with referer-restriction form', () => { cy.contains('Next').click(); cy.get(this.domSelector.nodes_0_host).type('127.0.0.1'); + cy.get(this.domSelector.nodes_0_port).clear().type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).clear().type(this.data.weight); cy.contains('Next').click(); // config referer-restriction plugin diff --git a/web/cypress/integration/route/create-route-with-upstream.spec.js b/web/cypress/integration/route/create-route-with-upstream.spec.js index a6cf351665..2a976b8a73 100644 --- a/web/cypress/integration/route/create-route-with-upstream.spec.js +++ b/web/cypress/integration/route/create-route-with-upstream.spec.js @@ -32,6 +32,8 @@ context('Create Route with Upstream', () => { cy.get(this.domSelector.name).type(this.data.upstreamName); cy.get(this.domSelector.description).type(this.data.description); cy.get(this.domSelector.nodes_0_host).type(this.data.host1); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); cy.contains('Submit').click(); }); @@ -56,6 +58,8 @@ context('Create Route with Upstream', () => { cy.get(this.domSelector.input).should('not.be.disabled'); cy.get(this.domSelector.nodes_0_host).clear().type(this.data.ip1); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); cy.contains('Next').click(); cy.contains('Submit').click(); @@ -87,6 +91,8 @@ context('Create Route with Upstream', () => { cy.get(this.domSelector.input).should('not.be.disabled'); cy.get(this.domSelector.nodes_0_host).clear().type(this.data.ip2); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); cy.contains('Next').click(); cy.contains('Submit').click(); diff --git a/web/cypress/integration/route/import_export_route.spec.js b/web/cypress/integration/route/import_export_route.spec.js index c327c58a8f..ea877f3155 100644 --- a/web/cypress/integration/route/import_export_route.spec.js +++ b/web/cypress/integration/route/import_export_route.spec.js @@ -66,6 +66,8 @@ context('import and export routes', () => { cy.contains(actionBarUS['component.actionbar.button.nextStep']).click(); // input nodes_0_host, click Next cy.get(this.domSelector.nodes_0_host).type(data[`upstream_node0_host_${i}`]); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains(actionBarUS['component.actionbar.button.nextStep']).click(); // do not config plugins, click Next cy.contains(actionBarUS['component.actionbar.button.nextStep']).click(); diff --git a/web/cypress/integration/route/search-route.spec.js b/web/cypress/integration/route/search-route.spec.js index 381b9feb6e..37bae68909 100644 --- a/web/cypress/integration/route/search-route.spec.js +++ b/web/cypress/integration/route/search-route.spec.js @@ -63,6 +63,8 @@ context('Create and Search Route', () => { cy.get(this.domSelector.nodes_0_host).type(this.data.host2, { timeout, }); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); cy.contains('Next').click(); cy.contains('Submit').click(); diff --git a/web/cypress/integration/service/create-edit-delete-service.spec.js b/web/cypress/integration/service/create-edit-delete-service.spec.js index 8c7fa0ac17..5551629496 100644 --- a/web/cypress/integration/service/create-edit-delete-service.spec.js +++ b/web/cypress/integration/service/create-edit-delete-service.spec.js @@ -35,6 +35,8 @@ context('Create and Delete Service ', () => { cy.get(this.domSelector.description).type(this.data.description); cy.get(this.domSelector.nodes_0_host).click(); cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); + cy.get(this.domSelector.nodes_0_port).clear().type('7000'); + cy.get(this.domSelector.nodes_0_weight).clear().type(1); cy.contains('Next').click(); diff --git a/web/cypress/integration/service/edit-service-with-upstream.spec.js b/web/cypress/integration/service/edit-service-with-upstream.spec.js index 1d89ee36c7..87be509c63 100644 --- a/web/cypress/integration/service/edit-service-with-upstream.spec.js +++ b/web/cypress/integration/service/edit-service-with-upstream.spec.js @@ -30,6 +30,8 @@ context('Edit Service with Upstream', () => { cy.contains('Create').click(); cy.get(this.domSelector.name).type(this.data.upstreamName); cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); + cy.get(this.domSelector.nodes_0_port).clear().type('7000'); + cy.get(this.domSelector.nodes_0_weight).clear().type(1); cy.contains('Next').click(); cy.contains('Submit').click(); cy.get(this.domSelector.notification).should('contain', this.data.createUpstreamSuccess); @@ -66,6 +68,8 @@ context('Edit Service with Upstream', () => { cy.get(this.domSelector.upstreamSelector).click(); cy.contains('Custom').click(); cy.get(this.domSelector.nodes_0_host).should('not.be.disabled').clear().type(this.data.ip2); + cy.get(this.domSelector.nodes_0_port).type(this.data.port); + cy.get(this.domSelector.nodes_0_weight).type(this.data.weight); cy.contains('Next').click(); cy.contains('Next').click(); cy.contains('Submit').click(); diff --git a/web/cypress/integration/upstream/create_and_delete_upstream.spec.js b/web/cypress/integration/upstream/create_and_delete_upstream.spec.js index a8063ebd93..4f2c0fad87 100644 --- a/web/cypress/integration/upstream/create_and_delete_upstream.spec.js +++ b/web/cypress/integration/upstream/create_and_delete_upstream.spec.js @@ -34,6 +34,7 @@ context('Create and Delete Upstream', () => { cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); cy.get(this.domSelector.nodes_0_port).clear().type('7000'); + cy.get(this.domSelector.nodes_0_weight).clear().type(1); cy.contains('Next').click(); cy.contains('Submit').click(); cy.get(this.domSelector.notification).should('contain', this.data.createUpstreamSuccess); @@ -78,11 +79,11 @@ context('Create and Delete Upstream', () => { cy.get(this.domSelector.upstreamType).within(() => { cy.contains('CHash').click(); }); - cy.get('#hash_on').click(); + cy.get('#hash_on').click({ force: true }); cy.get(this.domSelector.upstreamType).within(() => { cy.contains('vars').click(); }); - cy.get('#key').click(); + cy.get('#key').click({ force: true }); cy.get(this.domSelector.upstreamType).within(() => { cy.contains('remote_addr').click(); }); @@ -90,6 +91,7 @@ context('Create and Delete Upstream', () => { // add first upstream node cy.get(this.domSelector.nodes_0_host).type(this.data.ip1); cy.get(this.domSelector.nodes_0_port).clear().type('7000'); + cy.get(this.domSelector.nodes_0_weight).clear().type(1); // add second upstream node cy.get('.ant-btn-dashed').click(); diff --git a/web/src/components/Upstream/UpstreamForm.tsx b/web/src/components/Upstream/UpstreamForm.tsx index 7fade146cd..42d71443e5 100644 --- a/web/src/components/Upstream/UpstreamForm.tsx +++ b/web/src/components/Upstream/UpstreamForm.tsx @@ -21,7 +21,6 @@ import type { FormInstance } from 'antd/es/form'; import { PanelSection } from '@api7-dashboard/ui'; import { transformRequest } from '@/pages/Upstream/transform'; -import { DEFAULT_UPSTREAM } from './constant'; import PassiveCheck from './components/passive-check'; import ActiveCheck from './components/active-check' import Nodes from './components/Nodes' @@ -31,6 +30,8 @@ import Type from './components/Type'; import UpstreamSelector from './components/UpstreamSelector'; import Retries from './components/Retries'; import PassHost from './components/PassHost'; +import TLSComponent from './components/TLS'; +import { transformUpstreamDataFromRequest } from './service'; type Upstream = { name?: string; @@ -86,14 +87,18 @@ const UpstreamForm: React.FC = forwardRef( if (required) { requestAnimationFrame(() => { form.resetFields(); - form.setFieldsValue(DEFAULT_UPSTREAM); setHiddenForm(false); }); } } else { if (upstream_id) { requestAnimationFrame(() => { - form.setFieldsValue(list.find((item) => item.id === upstream_id)); + const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData + if (targetData) { + form.setFieldsValue(transformUpstreamDataFromRequest(targetData)); + } else { + // TODO: 提示 upstream_id 找不到想要的数据 + } }); } if (!required && !Object.keys(formData).length) { @@ -106,15 +111,24 @@ const UpstreamForm: React.FC = forwardRef( setReadonly(Boolean(upstream_id) || disabled); }, [list]); - - const ActiveHealthCheck = () => ( + prev.checks.active.type !== next.checks.active.type}> + {() => { + const type = form.getFieldValue(['checks', 'active', 'type']) + if (['https'].includes(type)) { + return + } + return null + }} + + + {formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.status' })} @@ -122,6 +136,7 @@ const UpstreamForm: React.FC = forwardRef( + {formatMessage({ id: 'page.upstream.step.healthyCheck.unhealthyStatus' })} @@ -131,14 +146,11 @@ const UpstreamForm: React.FC = forwardRef( - - Others - - + ); - const InActiveHealthCheck = () => ( + const PassiveHealthCheck = () => ( @@ -161,55 +173,54 @@ const UpstreamForm: React.FC = forwardRef( ); const HealthCheckComponent = () => { - const options = [ - { - label: formatMessage({ id: 'page.upstream.step.healthyCheck.active' }), - name: ['checks', 'active'], - component: ( - <> - - - - ), - }, - { - label: formatMessage({ id: 'page.upstream.step.healthyCheck.passive' }), - name: ['checks', 'passive'], - component: , - }, - ] - return ( - {options.map(({ label, name, component }) => ( -
- - - - - {() => { - if (form.getFieldValue(name)) { - return component; - } - return null; - }} - -
- ))} + + + + + { + () => { + const active = form.getFieldValue(['custom', 'checks', 'active']) + if (active) { + return ( + + ) + } + return null + } + } + + + + + + + { + () => { + const passive = form.getFieldValue(['custom', 'checks', 'passive']) + if (passive) { + /* + * When enable passive check, we should enable active check, too. + * When we use form.setFieldsValue to enable active check, error throws. + * We choose to alert users first, and need users to enable active check manually. + */ + return + } + return null + } + } +
) } - return (
{showSelector && ( = forwardRef( if (prev.upstream_id !== next.upstream_id) { const id = next.upstream_id; if (id) { - form.setFieldsValue(list.find((item) => item.id === id)); + const targetData = list.find((item) => item.id === id) as UpstreamComponent.ResponseData + if (targetData) { + form.setFieldsValue(transformUpstreamDataFromRequest(targetData)); + } form.setFieldsValue({ upstream_id: id, }); @@ -232,10 +246,12 @@ const UpstreamForm: React.FC = forwardRef( onChange={(upstream_id) => { setReadonly(Boolean(upstream_id)); setHiddenForm(Boolean(upstream_id === 'None')); - form.setFieldsValue(list.find((item) => item.id === upstream_id)); + const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData + if (targetData) { + form.setFieldsValue(transformUpstreamDataFromRequest(targetData)); + } if (upstream_id === '') { form.resetFields(); - form.setFieldsValue(DEFAULT_UPSTREAM); } }} /> @@ -248,7 +264,20 @@ const UpstreamForm: React.FC = forwardRef( + + prev.scheme !== next.scheme}> + { + () => { + const scheme = form.getFieldValue("scheme") as string + if (["https", "grpcs"].includes(scheme)) { + return + } + return null + } + } + + {timeoutFields.map((item, index) => ( ))} diff --git a/web/src/components/Upstream/components/DiscoveryType.tsx b/web/src/components/Upstream/components/DiscoveryType.tsx new file mode 100644 index 0000000000..031de335af --- /dev/null +++ b/web/src/components/Upstream/components/DiscoveryType.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react' +import { Form, Input } from 'antd' +import { useIntl } from 'umi' + +type Props = { + readonly?: boolean; +} + +const DiscoveryType: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( + + + + ) +} + +export default DiscoveryType diff --git a/web/src/components/Upstream/components/Nodes.tsx b/web/src/components/Upstream/components/Nodes.tsx index 2a92d48787..eaaa3a4feb 100644 --- a/web/src/components/Upstream/components/Nodes.tsx +++ b/web/src/components/Upstream/components/Nodes.tsx @@ -29,7 +29,7 @@ const Component: React.FC = ({ readonly }) => { const { formatMessage } = useIntl() return ( - + {(fields, { add, remove }) => ( <> diff --git a/web/src/components/Upstream/components/PassHost.tsx b/web/src/components/Upstream/components/PassHost.tsx index 6102076eb8..a2f7e456be 100644 --- a/web/src/components/Upstream/components/PassHost.tsx +++ b/web/src/components/Upstream/components/PassHost.tsx @@ -46,6 +46,7 @@ const Component: React.FC = ({ form, readonly }) => { {options.map(item => { @@ -59,4 +60,4 @@ const Component: React.FC = ({ readonly }) => { ) } -export default Component +export default Scheme diff --git a/web/src/components/Upstream/components/ServiceName.tsx b/web/src/components/Upstream/components/ServiceName.tsx new file mode 100644 index 0000000000..b0ef62cc3f --- /dev/null +++ b/web/src/components/Upstream/components/ServiceName.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react' +import { Form, Input } from 'antd' +import { useIntl } from 'umi' + +type Props = { + readonly?: boolean; +} + +const ServiceName: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( + + + + ) +} + +export default ServiceName diff --git a/web/src/components/Upstream/components/TLS.tsx b/web/src/components/Upstream/components/TLS.tsx new file mode 100644 index 0000000000..2d4feee0a5 --- /dev/null +++ b/web/src/components/Upstream/components/TLS.tsx @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react' +import { Form, Row, Col, Select, FormInstance, Input } from 'antd' +import { useIntl } from 'umi' + +type Props = { + form: FormInstance; + readonly?: boolean; +} + +const TLSComponent: React.FC = ({ form, readonly }) => { + const { formatMessage } = useIntl() + + return ( + + + + + + + + + + + { + return prev.custom.tls !== next.custom.tls + }} + > + { + () => { + if (form.getFieldValue(["custom", "tls"]) === 'enable') { + return ( + + + + + + + + + ) + } + return null + } + } + + + ) +} + +export default TLSComponent diff --git a/web/src/components/Upstream/components/Timeout.tsx b/web/src/components/Upstream/components/Timeout.tsx index 059b63a5f9..8eefdf0118 100644 --- a/web/src/components/Upstream/components/Timeout.tsx +++ b/web/src/components/Upstream/components/Timeout.tsx @@ -20,7 +20,7 @@ import { useIntl } from 'umi' import TimeUnit from './TimeUnit' -const Component: React.FC<{ +const Timeout: React.FC<{ label: string; desc: string; name: string[]; @@ -38,6 +38,7 @@ const Component: React.FC<{ message: formatMessage({ id: `page.upstream.step.input.${name[1]}.timeout` }), }, ]} + initialValue={6} > @@ -46,4 +47,4 @@ const Component: React.FC<{ ) } -export default Component +export default Timeout diff --git a/web/src/components/Upstream/components/Type.tsx b/web/src/components/Upstream/components/Type.tsx index 0a7ce25306..8788ebb783 100644 --- a/web/src/components/Upstream/components/Type.tsx +++ b/web/src/components/Upstream/components/Type.tsx @@ -19,63 +19,38 @@ import { Form, Select } from 'antd' import { useIntl } from 'umi' import type { FormInstance } from 'antd/es/form' -enum Type { - roundrobin = 'roundrobin', - chash = 'chash', - ewma = 'ewma', - // TODO: new type - // least_conn = 'least_conn' -} - -enum HashOn { - vars = 'vars', - header = 'header', - cookie = 'cookie', - consumer = 'consumer', - // TODO: new hash_on key - // vars_combinations = 'vars_combinations' -} - -enum HashKey { - remote_addr = 'remote_addr', - host = 'host', - uri = 'uri', - server_name = 'server_name', - server_addr = 'server_addr', - request_uri = 'request_uri', - query_string = 'query_string', - remote_port = 'remote_port', - hostname = 'hostname', - arg_id = 'arg_id', -} +import { HashOnEnum, CommonHashKeyEnum, AlgorithmEnum } from '../constant' type Props = { readonly?: boolean form: FormInstance } -const CHash: React.FC> = ({ readonly }) => ( - <> - - - - - - - -); +const CHash: React.FC> = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( + + + + + + + + + ) +}; const Component: React.FC = ({ readonly, form }) => { const { formatMessage } = useIntl() @@ -86,9 +61,10 @@ const Component: React.FC = ({ readonly, form }) => { label={formatMessage({ id: 'page.upstream.step.type' })} name="type" rules={[{ required: true }]} + initialValue="roundrobin" > diff --git a/web/src/components/Upstream/components/active-check/HttpPath.tsx b/web/src/components/Upstream/components/active-check/HttpPath.tsx index 4914a49e3d..db5c2b6d9d 100644 --- a/web/src/components/Upstream/components/active-check/HttpPath.tsx +++ b/web/src/components/Upstream/components/active-check/HttpPath.tsx @@ -27,10 +27,10 @@ const Component: React.FC = ({ readonly }) => { return ( = ({ readonly }) => { rules={[ { required: true, - message: formatMessage({ id: 'page.upstream.checks.active.http_path.placeholder' }), + message: formatMessage({ id: 'component.upstream.fields.checks.active.http_path.placeholder' }), }, ]} + initialValue="/" > diff --git a/web/src/components/Upstream/components/active-check/HttpsVerifyCertificate.tsx b/web/src/components/Upstream/components/active-check/HttpsVerifyCertificate.tsx new file mode 100644 index 0000000000..901c5095da --- /dev/null +++ b/web/src/components/Upstream/components/active-check/HttpsVerifyCertificate.tsx @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react' +import { Form, Switch } from 'antd' +import { useIntl } from 'umi' + +type Props = { + readonly?: boolean +} + +const HttpsVerifyCertificateComponent: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( + + + + ) +} + +export default HttpsVerifyCertificateComponent diff --git a/web/src/components/Upstream/components/active-check/Port.tsx b/web/src/components/Upstream/components/active-check/Port.tsx index 3d04e0756b..8ca857b11c 100644 --- a/web/src/components/Upstream/components/active-check/Port.tsx +++ b/web/src/components/Upstream/components/active-check/Port.tsx @@ -25,11 +25,11 @@ type Props = { const Component: React.FC = ({ readonly }) => { const { formatMessage } = useIntl() return ( - - + + = ({ readonly }) => { <> {fields.map((field, index) => ( @@ -51,7 +52,7 @@ const Component: React.FC = ({ readonly }) => { - {!readonly && fields.length > 1 && ( + {!readonly && fields.length > 0 && ( { remove(field.name); diff --git a/web/src/components/Upstream/components/active-check/Timeout.tsx b/web/src/components/Upstream/components/active-check/Timeout.tsx index 4d294b7d05..f2b6bdc813 100644 --- a/web/src/components/Upstream/components/active-check/Timeout.tsx +++ b/web/src/components/Upstream/components/active-check/Timeout.tsx @@ -28,7 +28,7 @@ const ActiveCheckTimeoutComponent: React.FC = ({ readonly }) => { return ( - + diff --git a/web/src/components/Upstream/components/active-check/Type.tsx b/web/src/components/Upstream/components/active-check/Type.tsx index e8b95c63fd..067bf96fa4 100644 --- a/web/src/components/Upstream/components/active-check/Type.tsx +++ b/web/src/components/Upstream/components/active-check/Type.tsx @@ -15,7 +15,8 @@ * limitations under the License. */ import React from 'react' -import { Form, Select } from 'antd' +import { Form, Select, Row, Col } from 'antd' +import { useIntl } from 'umi' type Props = { readonly?: boolean @@ -35,21 +36,32 @@ const options = [ ] const ActiveCheckTypeComponent: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( - + + + + + + + ) } diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx index dbd411827e..953e7851e3 100644 --- a/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx +++ b/web/src/components/Upstream/components/active-check/Unhealthy/HttpFailures.tsx @@ -27,9 +27,9 @@ const Component: React.FC = ({ readonly }) => { return ( = ({ readonly }) => { { required: true, message: formatMessage({ - id: 'page.upstream.step.input.healthyCheck.http_failures', + id: 'component.upstream.fields.checks.active.unhealthy.http_failures.required', }), }, ]} + initialValue={5} > diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx index 0cdad520f6..3e5830ccfb 100644 --- a/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx +++ b/web/src/components/Upstream/components/active-check/Unhealthy/HttpStatuses.tsx @@ -29,7 +29,7 @@ const Component: React.FC = ({ readonly }) => { const { formatMessage } = useIntl() return ( - + {(fields, { add, remove }) => ( <> = ({ readonly }) => { const { formatMessage } = useIntl() return ( = ({ readonly }) => { + const { formatMessage } = useIntl() + + return ( + + + + + + ) +} + +export default TCPFailures diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx b/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx index 2f5af5fb4f..c1faa1e33c 100644 --- a/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx +++ b/web/src/components/Upstream/components/active-check/Unhealthy/Timeouts.tsx @@ -16,23 +16,29 @@ */ import React from 'react' import { Form, InputNumber } from 'antd' +import { useIntl } from 'umi' type Props = { readonly?: boolean } -const Component: React.FC = ({ readonly }) => ( - +const Component: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( - + + + - -) + ) +} export default Component diff --git a/web/src/components/Upstream/components/active-check/Unhealthy/index.ts b/web/src/components/Upstream/components/active-check/Unhealthy/index.ts index bb9b86951a..6dde012e89 100644 --- a/web/src/components/Upstream/components/active-check/Unhealthy/index.ts +++ b/web/src/components/Upstream/components/active-check/Unhealthy/index.ts @@ -18,10 +18,12 @@ import Timeouts from './Timeouts' import Interval from './Interval' import HttpStatuses from './HttpStatuses' import HttpFailures from './HttpFailures' +import TCPFailures from './TCPFailures' export default { Timeouts, Interval, HttpStatuses, - HttpFailures + HttpFailures, + TCPFailures } diff --git a/web/src/components/Upstream/components/active-check/index.ts b/web/src/components/Upstream/components/active-check/index.ts index efc3046091..0a9929030c 100644 --- a/web/src/components/Upstream/components/active-check/index.ts +++ b/web/src/components/Upstream/components/active-check/index.ts @@ -23,6 +23,8 @@ import ReqHeaders from './ReqHeaders' import Host from './Host' import Port from './Port' import HttpPath from './HttpPath' +import Concurrency from './Concurrency' +import HttpsVerifyCertificate from './HttpsVerifyCertificate' export default { Unhealthy, @@ -32,5 +34,7 @@ export default { ReqHeaders, Host, Port, - HttpPath + HttpPath, + Concurrency, + HttpsVerifyCertificate } diff --git a/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx b/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx index f1b06d2105..8ac8ecb033 100644 --- a/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx +++ b/web/src/components/Upstream/components/passive-check/Healthy/HttpStatuses.tsx @@ -29,7 +29,9 @@ const Component: React.FC = ({ readonly }) => { const { formatMessage } = useIntl() return ( - + {(fields, { add, remove }) => ( <> = ({ readonly }) => { const { formatMessage } = useIntl() return ( diff --git a/web/src/components/Upstream/components/passive-check/Type.tsx b/web/src/components/Upstream/components/passive-check/Type.tsx index d3401eeb2c..f43f445e40 100644 --- a/web/src/components/Upstream/components/passive-check/Type.tsx +++ b/web/src/components/Upstream/components/passive-check/Type.tsx @@ -15,43 +15,55 @@ * limitations under the License. */ import React from 'react' -import { Form, Select } from 'antd' +import { Form, Select, Row, Col } from 'antd' +import { useIntl } from 'umi' type Props = { readonly?: boolean } -const PassiveCheckTypeComponent: React.FC = ({ readonly }) => { - const options = [ - { - label: "HTTP", - value: "http" - }, { - label: "HTTPs", - value: "https" - }, { - label: "TCP", - value: "tcp" - } - ] +const options = [ + { + label: "HTTP", + value: "http" + }, { + label: "HTTPs", + value: "https" + }, { + label: "TCP", + value: "tcp" + } +] + +const ActiveCheckTypeComponent: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() return ( - + + + + + + + ) } -export default PassiveCheckTypeComponent +export default ActiveCheckTypeComponent diff --git a/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx index 3ba6154f39..be282b7312 100644 --- a/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx +++ b/web/src/components/Upstream/components/passive-check/Unhealthy/HttpFailures.tsx @@ -27,18 +27,19 @@ const Component: React.FC = ({ readonly }) => { return ( = ({ readonly }) => { const { formatMessage } = useIntl() return ( - + {(fields, { add, remove }) => ( <> = ({ readonly }) => { = ({ readonly }) => ( - +const Component: React.FC = ({ readonly }) => { + const { formatMessage } = useIntl() + return ( - + + + - -) + ) +} export default Component diff --git a/web/src/components/Upstream/constant.ts b/web/src/components/Upstream/constant.ts index d4f243bbe3..66ddc45143 100644 --- a/web/src/components/Upstream/constant.ts +++ b/web/src/components/Upstream/constant.ts @@ -14,77 +14,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export const DEFAULT_UPSTREAM = { - upstream_id: '', - // NOTE: the following fields are the default configurations - // https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L325 - nodes: [ - { - host: '', - port: 80, - weight: 1 - } - ], - retries: 0, - timeout: { - connect: 6, - send: 6, - read: 6, - }, - type: 'roundrobin', - checks: {}, - scheme: "http", - pass_host: "pass", - name: "", - desc: "" -}; - -// NOTE: checks.active -// https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L40 -export const DEFAULT_HEALTH_CHECK_ACTIVE = { - type: "http", - timeout: 1, - concurrency: 10, - host: "", - port: 80, - http_path: "", - https_verify_certificate: true, - healthy: { - interval: 1, - http_statuses: [200, 302], - successes: 2 - }, - unhealthy: { - interval: 1, - http_statuses: [429, 404, 500, 501, 502, 503, 504, 505], - http_failures: 5, - tcp_failures: 2, - timeouts: 3 - }, - req_headers: [] -} - -// NOTE: checks.passive -export const DEFAULT_HEALTH_CHECK_PASSIVE = { - type: "http", - healthy: { - http_statuses: [ - 200, 201, 202, 203, 204, 205, 206, 207, - 208, 226, 300, 301, 302, 303, 304, 305, - 306, 307, 308 - ], - successes: 5 - }, - unhealthy: { - http_statuses: [429, 500, 503], - tcp_failures: 2, - timeouts: 7, - http_failures: 5 - } -} export const removeBtnStyle = { marginLeft: 20, display: 'flex', alignItems: 'center', }; + +export enum AlgorithmEnum { + chash = "chash", + roundrobin = "roundrobin", + ewma = "ewma", + least_conn = "least_conn" +} + +export enum HashOnEnum { + vars = 'vars', + header = 'header', + cookie = 'cookie', + consumer = 'consumer', + vars_combinations = 'vars_combinations' +} + +export enum CommonHashKeyEnum { + remote_addr = 'remote_addr', + host = 'host', + uri = 'uri', + server_name = 'server_name', + server_addr = 'server_addr', + request_uri = 'request_uri', + query_string = 'query_string', + remote_port = 'remote_port', + hostname = 'hostname', + arg_id = 'arg_id', +} + +export enum SchemeEnum { + grpc = "grpc", + grpcs = "grpcs", + http = "http", + https = "https" +} + +export enum PassHostEnum { + pass = "pass", + node = "node", + rewrite = "rewrite" +} diff --git a/web/src/components/Upstream/locales/en-US.ts b/web/src/components/Upstream/locales/en-US.ts new file mode 100644 index 0000000000..585fb6687d --- /dev/null +++ b/web/src/components/Upstream/locales/en-US.ts @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default { + 'component.upstream.fields.tls.client_key': 'Client Key', + 'component.upstream.fields.tls.client_cert': 'Client Cert', + + 'component.upstream.fields.discovery_type': 'Discovery Type', + 'component.upstream.fields.discovery_type.tooltip': 'Discovery Type', + 'component.upstream.fields.discovery_type.placeholder': 'Please enter the discovery type', + + 'component.upstream.fields.service_name': 'Service Name', + 'component.upstream.fields.service_name.tooltip': 'Service Name', + 'component.upstream.fields.service_name.placeholder': 'Please enter the service name', + + 'component.upstream.fields.tls': 'TLS', + 'component.upstream.fields.tls.tooltip': 'TLS Certificate', + + 'component.upstream.fields.hash_on': 'Hash on', + 'component.upstream.fields.hash_on.tooltip': 'What to use as hashing input', + + 'component.upstream.fields.key': 'Key', + 'component.upstream.fields.key.tooltip': 'Key as hashing input', + + 'component.upstream.fields.retries': 'Retries', + 'component.upstream.fields.retries.tooltip': 'The retry mechanism sends the request to the next upstream node. A value of 0 disables the retry mechanism and leaves the table empty to use the number of available backend nodes.', + + 'component.upstream.fields.checks.active.type': 'Type', + 'component.upstream.fields.checks.active.type.tooltip': 'Whether to perform active health checks using HTTP or HTTPS, or just attempt a TCP connection.', + + 'component.upstream.fields.checks.active.concurrency': 'Concurrency', + 'component.upstream.fields.checks.active.concurrency.tooltip': 'Number of targets to check concurrently in active health checks.', + + 'component.upstream.fields.checks.active.host': 'Host', + 'component.upstream.fields.checks.active.host.required': 'Please enter the hostname', + 'component.upstream.fields.checks.active.host.tooltip': 'The hostname of the HTTP request used to perform the active health check', + 'component.upstream.fields.checks.active.host.scope': 'Only letters, numbers and . are supported', + + 'component.upstream.fields.checks.active.port': 'Port', + 'component.upstream.fields.checks.active.port.required': 'Please enter the port', + + 'component.upstream.fields.checks.active.http_path': 'HTTP Path', + 'component.upstream.fields.checks.active.http_path.tooltip': 'The path that should be used when issuing the HTTP GET request to the target. The default value is /.', + + 'component.upstream.fields.checks.active.https_verify_certificate': 'Verify HTTPs Certificate', + 'component.upstream.fields.checks.active.https_verify_certificate.tooltip': 'Whether to check the validity of the SSL certificate of the remote host when performing active health checks using HTTPS.', + + 'component.upstream.fields.checks.active.req_headers': 'Request Headers', + 'component.upstream.fields.checks.active.req_headers.tooltip': 'Additional request headers, example: User-Agent: curl/7.29.0', + + 'component.upstream.fields.checks.active.healthy.interval': 'Interval', + 'component.upstream.fields.checks.active.healthy.interval.tooltip': 'Interval between checks for healthy targets (in seconds)', + + 'component.upstream.fields.checks.active.healthy.successes': 'Successes', + 'component.upstream.fields.checks.active.healthy.successes.tooltip': 'Number of successes to consider a target healthy', + 'component.upstream.fields.checks.active.healthy.successes.required': 'Please enter successes number', + + 'component.upstream.fields.checks.active.healthy.http_statuses': 'HTTP Statuses', + 'component.upstream.fields.checks.active.healthy.http_statuses.tooltip': 'An array of HTTP statuses to consider a success, indicating healthiness, when returned by a probe in active health checks.', + + 'component.upstream.fields.checks.active.unhealthy.timeouts': 'Timeouts', + 'component.upstream.fields.checks.active.unhealthy.timeouts.tooltip': 'Number of timeouts in active probes to consider a target unhealthy.', + + 'component.upstream.fields.checks.active.unhealthy.http_failures': 'HTTP Failures', + 'component.upstream.fields.checks.active.unhealthy.http_failures.tooltip': 'Number of HTTP failures to consider a target unhealthy', + 'component.upstream.fields.checks.active.unhealthy.http_failures.required': 'Please enter the HTTP failures', + + 'component.upstream.fields.checks.active.unhealthy.interval': 'Interval', + 'component.upstream.fields.checks.active.unhealthy.interval.tooltip': 'Interval between active health checks for unhealthy targets (in seconds). A value of zero indicates that active probes for healthy targets should not be performed.', + 'component.upstream.fields.checks.active.unhealthy.required': 'Please enter the unhelthy interval', + + 'component.upstream.fields.checks.passive.healthy.successes': 'Successes', + 'component.upstream.fields.checks.passive.healthy.successes.tooltip': 'Number of successes to consider a target healthy', + 'component.upstream.fields.checks.passive.healthy.successes.required': 'Please enter the successes number', + + 'component.upstream.fields.checks.passive.unhealthy.timeouts': 'Timeouts', + 'component.upstream.fields.checks.passive.unhealthy.timeouts.tooltip': 'Number of timeouts in proxied traffic to consider a target unhealthy, as observed by passive health checks.', +} diff --git a/web/src/components/Upstream/locales/zh-CN.ts b/web/src/components/Upstream/locales/zh-CN.ts new file mode 100644 index 0000000000..62925e6926 --- /dev/null +++ b/web/src/components/Upstream/locales/zh-CN.ts @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default { + 'component.upstream.fields.tls.client_key': '客户端私钥', + 'component.upstream.fields.tls.client_cert': '客户端证书', + + 'component.upstream.fields.discovery_type': '服务发现类型', + 'component.upstream.fields.discovery_type.tooltip': '服务发现类型', + 'component.upstream.fields.discovery_type.placeholder': '请输入服务发现类型', + + 'component.upstream.fields.service_name': '服务名称', + 'component.upstream.fields.service_name.tooltip': '服务名称', + 'component.upstream.fields.service_name.placeholder': '请输入服务名称', + + 'component.upstream.fields.tls': 'TLS', + 'component.upstream.fields.tls.tooltip': 'TLS 证书', + + 'component.upstream.fields.hash_on': '哈希位置', + 'component.upstream.fields.hash_on.tooltip': '哈希的输入的位置(Hash On)', + + 'component.upstream.fields.key': 'Key', + 'component.upstream.fields.key.tooltip': '哈希键(Hash Key)', + + 'component.upstream.fields.retries': '重试次数', + 'component.upstream.fields.retries.tooltip': '重试机制将请求发到下一个上游节点。值为 0 表示禁用重试机制,留空表是使用可用后端节点的数量。', + + 'component.upstream.fields.checks.active.type': '类型', + 'component.upstream.fields.checks.active.type.tooltip': '是使用 HTTP 或 HTTPS 进行主动健康检查,还是只尝试 TCP 连接。', + + 'component.upstream.fields.checks.active.concurrency': '并行数量', + 'component.upstream.fields.checks.active.concurrency.tooltip': '在主动健康检查中同时检查的目标数量。', + + 'component.upstream.fields.checks.active.host': '主机名', + 'component.upstream.fields.checks.active.host.required': '请输入主机名', + 'component.upstream.fields.checks.active.host.tooltip': '进行主动健康检查时使用的 HTTP 请求主机名', + 'component.upstream.fields.checks.active.host.scope': '仅支持字母、数字和 . ', + + 'component.upstream.fields.checks.active.port': '端口', + 'component.upstream.fields.checks.active.port.required': '请输入端口', + + 'component.upstream.fields.checks.active.http_path': '请求路径', + 'component.upstream.fields.checks.active.http_path.tooltip': '向目标节点发出 HTTP GET 请求时应使用的路径。', + 'component.upstream.fields.checks.active.http_path.placeholder': '请输入 HTTP 请求路径', + + 'component.upstream.fields.checks.active.https_verify_certificate': '验证证书', + 'component.upstream.fields.checks.active.https_verify_certificate.tooltip': '在使用 HTTPS 执行主动健康检查时,是否检查远程主机的 SSL 证书的有效性。', + + 'component.upstream.fields.checks.active.req_headers': '请求头', + 'component.upstream.fields.checks.active.req_headers.tooltip': '额外的请求头,示例:User-Agent: curl/7.29.0', + + 'component.upstream.fields.checks.active.healthy.interval': '间隔时间', + 'component.upstream.fields.checks.active.healthy.interval.tooltip': '对健康的上游服务目标节点进行主动健康检查的间隔时间(以秒为单位)。数值为0表示对健康节点不进行主动健康检查。', + + 'component.upstream.fields.checks.active.healthy.successes': '成功次数', + 'component.upstream.fields.checks.active.healthy.successes.tooltip': '主动健康检查的 HTTP 成功次数,若达到此值,表示上游服务目标节点是健康的。', + 'component.upstream.fields.checks.active.healthy.successes.required': '请输入成功次数', + + 'component.upstream.fields.checks.active.healthy.http_statuses': '状态码', + 'component.upstream.fields.checks.active.healthy.http_statuses.tooltip': 'HTTP 状态码列表,当探针在主动健康检查中返回时,视为健康。', + + 'component.upstream.fields.checks.active.unhealthy.timeouts': '超时时间', + 'component.upstream.fields.checks.active.unhealthy.timeouts.tooltip': '活动探针中认为目标不健康的超时次数。', + + 'component.upstream.fields.checks.active.unhealthy.interval': '间隔时间', + 'component.upstream.fields.checks.active.unhealthy.interval.tooltip': '对不健康目标的主动健康检查之间的间隔(以秒为单位)。数值为0表示不应该对健康目标进行主动探查。', + 'component.upstream.fields.checks.active.unhealthy.required': '请输入间隔时间', + + 'component.upstream.fields.checks.active.unhealthy.http_failures': 'HTTP 失败次数', + 'component.upstream.fields.checks.active.unhealthy.http_failures.tooltip': '主动健康检查的 HTTP 失败次数,默认值为0。若达到此值,表示上游服务目标节点是不健康的。', + 'component.upstream.fields.checks.active.unhealthy.http_failures.required': '请输入 HTTP 失败次数', + + 'component.upstream.fields.checks.active.unhealthy.tcp_failures': 'TCP 失败次数', + 'component.upstream.fields.checks.active.unhealthy.tcp_failures.tooltip': '主动探测中 TCP 失败次数超过该值时,认为目标不健康。', + 'component.upstream.fields.checks.active.unhealthy.tcp_failures.required': '请输入 TCP 失败次数', + + 'component.upstream.fields.checks.passive.healthy.successes': '成功次数', + 'component.upstream.fields.checks.passive.healthy.successes.tooltip': '通过被动健康检查观察到的正常代理流量的成功次数。如果达到该值,上游服务目标节点将被视为健康。', + 'component.upstream.fields.checks.passive.healthy.successes.required': '请输入成功次数', + + 'component.upstream.fields.checks.passive.unhealthy.timeouts': '超时时间', + 'component.upstream.fields.checks.passive.unhealthy.timeouts.tooltip': '根据被动健康检查的观察,在代理中认为目标不健康的超时次数。', +} diff --git a/web/src/components/Upstream/service.ts b/web/src/components/Upstream/service.ts new file mode 100644 index 0000000000..2642100dc2 --- /dev/null +++ b/web/src/components/Upstream/service.ts @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import cloneDeep from 'lodash/cloneDeep' + +/** + * Because we have some `custom` field in Upstream Form, like custom.tls/custom.checks.active etc, + * we need to transform data that doesn't have `custom` field to data contains `custom` field +*/ +export const transformUpstreamDataFromRequest = (originData: UpstreamComponent.ResponseData) => { + const data = cloneDeep(originData) + data.custom = {} + + if (data.checks) { + data.custom.checks = {} + + if (data.checks.active) { + data.custom.checks.active = true + } + + if (data.checks.passive) { + data.custom.checks.passive = true + } + } + + if (data.tls) { + data.custom.tls = "enable" + } + + return data +} diff --git a/web/src/components/Upstream/typings.d.ts b/web/src/components/Upstream/typings.d.ts new file mode 100644 index 0000000000..bbe22d3e6e --- /dev/null +++ b/web/src/components/Upstream/typings.d.ts @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Schema: https://github.com/apache/apisix/blob/master/apisix/schema_def.lua#L335 +*/ +declare namespace UpstreamComponent { + type ActiveCheck = {} + + type PassiveCheck = {} + + type TLS = { + client_cert: string; + client_key: string + } + + type Node = { + host: string; + port: number; + weight: number; + priority?: number; + } + + type Timeout = { + connect: number; + send: number; + read: number; + } + + type ResponseData = { + nodes: Node[]; + retries?: number; + timeout?: Timeout; + tls?: TLS; + type?: string; + checks?: { + active?: ActiveCheck; + passive?: PassiveCheck; + } + hash_on?: string; + key?: string; + scheme?: string; + discovery_type?: string; + pass_host?: string; + upstream_host?: string; + name?: string; + desc?: string; + service_name?: string; + id?: string; + // NOTE: custom field + custom?: Record; + } +} diff --git a/web/src/locales/en-US.ts b/web/src/locales/en-US.ts index 19c348a53d..e4773a6f7e 100644 --- a/web/src/locales/en-US.ts +++ b/web/src/locales/en-US.ts @@ -26,6 +26,7 @@ import other from './en-US/other' import PluginOrchestration from '../components/PluginOrchestration/locales/en-US'; import Plugin from '../components/Plugin/locales/en-US'; import RawDataEditor from '../components/RawDataEditor/locales/en-US'; +import UpstreamComponent from '../components/Upstream/locales/en-US' export default { 'navBar.lang': 'Languages', @@ -43,5 +44,6 @@ export default { ...ActionBarEnUS, ...PluginOrchestration, ...Plugin, - ...RawDataEditor + ...RawDataEditor, + ...UpstreamComponent }; diff --git a/web/src/locales/en-US/component.ts b/web/src/locales/en-US/component.ts index fe9e2ea329..a5d6356aab 100644 --- a/web/src/locales/en-US/component.ts +++ b/web/src/locales/en-US/component.ts @@ -24,6 +24,7 @@ export default { 'component.global.format': 'Format', 'component.global.document': 'Document', 'component.global.enable': 'Enable', + 'component.global.disable': 'Disable', 'component.global.scope': 'Scope', 'component.global.data.editor': 'Raw Data Editor', 'component.global.delete': 'Delete', diff --git a/web/src/locales/zh-CN.ts b/web/src/locales/zh-CN.ts index 0c8037533d..4d89580db0 100644 --- a/web/src/locales/zh-CN.ts +++ b/web/src/locales/zh-CN.ts @@ -26,6 +26,7 @@ import settings from './zh-CN/setting'; import PluginOrchestration from '../components/PluginOrchestration/locales/zh-CN'; import Plugin from '../components/Plugin/locales/zh-CN'; import RawDataEditor from '../components/RawDataEditor/locales/zh-CN'; +import UpstreamComponent from '../components/Upstream/locales/zh-CN' export default { 'navBar.lang': '语言', @@ -43,5 +44,6 @@ export default { ...ActionBarZhCN, ...PluginOrchestration, ...Plugin, - ...RawDataEditor + ...RawDataEditor, + ...UpstreamComponent }; diff --git a/web/src/locales/zh-CN/component.ts b/web/src/locales/zh-CN/component.ts index bf5ee199ef..df60e7f519 100644 --- a/web/src/locales/zh-CN/component.ts +++ b/web/src/locales/zh-CN/component.ts @@ -24,6 +24,7 @@ export default { 'component.global.format': '格式化', 'component.global.document': '文档', 'component.global.enable': '启用', + 'component.global.disable': '禁用', 'component.global.scope': '作用域', 'component.global.data.editor': '数据编辑器', 'component.global.delete': '删除', diff --git a/web/src/pages/Route/Create.tsx b/web/src/pages/Route/Create.tsx index 648546f350..8c478d1f4d 100644 --- a/web/src/pages/Route/Create.tsx +++ b/web/src/pages/Route/Create.tsx @@ -21,7 +21,6 @@ import { history, useIntl } from 'umi'; import { isEmpty } from 'lodash'; import ActionBar from '@/components/ActionBar'; -import { DEFAULT_UPSTREAM } from '@/components/Upstream'; import { transformer as chartTransformer } from '@/components/PluginOrchestration'; import { create, fetchItem, update, checkUniqueName, checkHostWithSSL } from './service'; @@ -83,7 +82,6 @@ const Page: React.FC = (props) => { setAdvancedMatchingRules([]); setStep3Data(DEFAULT_STEP_3_DATA); form1.setFieldsValue(DEFAULT_STEP_1_DATA); - form2.setFieldsValue(DEFAULT_UPSTREAM); setStep(1); }; @@ -95,7 +93,7 @@ const Page: React.FC = (props) => { } }, []); - const getProxyRewriteEnable =() => { + const getProxyRewriteEnable = () => { return !isEmpty(transformProxyRewrite2Plugin(form1.getFieldValue('proxyRewrite'))); } @@ -269,9 +267,8 @@ const Page: React.FC = (props) => { return ( <> diff --git a/web/src/pages/Service/Create.tsx b/web/src/pages/Service/Create.tsx index cb3d6672a0..4d1dbc29a0 100644 --- a/web/src/pages/Service/Create.tsx +++ b/web/src/pages/Service/Create.tsx @@ -22,7 +22,6 @@ import { omit } from 'lodash'; import ActionBar from '@/components/ActionBar'; import PluginPage from '@/components/Plugin'; -import { DEFAULT_UPSTREAM } from '@/components/Upstream'; import Preview from './components/Preview'; import Step1 from './components/Step1'; import { create, update, fetchItem } from './service'; @@ -46,9 +45,6 @@ const Page: React.FC = (props) => { const [step, setStep] = useState(1); useEffect(() => { - // init upstream default value - upstreamForm.setFieldsValue(DEFAULT_UPSTREAM); - const { serviceId } = (props as any).match.params; if (serviceId) { fetchItem(serviceId).then(({ data }) => { @@ -82,8 +78,8 @@ const Page: React.FC = (props) => { .then(() => { notification.success({ message: `${serviceId - ? formatMessage({ id: 'component.global.edit' }) - : formatMessage({ id: 'component.global.create' }) + ? formatMessage({ id: 'component.global.edit' }) + : formatMessage({ id: 'component.global.create' }) } ${formatMessage({ id: 'menu.service' })} ${formatMessage({ id: 'component.status.success', })}`, diff --git a/web/src/pages/Upstream/Create.tsx b/web/src/pages/Upstream/Create.tsx index 0e068c7bdb..993e20a136 100644 --- a/web/src/pages/Upstream/Create.tsx +++ b/web/src/pages/Upstream/Create.tsx @@ -21,6 +21,7 @@ import { history, useIntl } from 'umi'; import { QuestionCircleOutlined } from '@ant-design/icons'; import ActionBar from '@/components/ActionBar'; +import { transformUpstreamDataFromRequest } from '@/components/Upstream/service'; import Step1 from './components/Step1'; import { fetchOne, create, update } from './service'; @@ -35,8 +36,8 @@ const Page: React.FC = (props) => { const { id } = (props as any).match.params; if (id) { - fetchOne(id).then((data) => { - form1.setFieldsValue(data.data); + fetchOne(id).then(({ data }) => { + form1.setFieldsValue(transformUpstreamDataFromRequest(data)); }); } }, []); diff --git a/web/src/pages/Upstream/components/Step1.tsx b/web/src/pages/Upstream/components/Step1.tsx index 182ffbc458..40f3c31e69 100644 --- a/web/src/pages/Upstream/components/Step1.tsx +++ b/web/src/pages/Upstream/components/Step1.tsx @@ -19,7 +19,7 @@ import { Form, Input } from 'antd'; import type { FormInstance } from 'antd/lib/form'; import { useIntl } from 'umi'; -import UpstreamForm, { DEFAULT_UPSTREAM } from '@/components/Upstream'; +import UpstreamForm from '@/components/Upstream'; import { fetchList } from '../service'; type Props = { @@ -38,7 +38,7 @@ const Step1: React.FC = ({ form, disabled, upstreamRef }) => { return ( <> - + { let data = pickBy(formData, identity) as UpstreamModule.RequestBody; + + data = omit(data, 'custom') + const { type, hash_on, @@ -30,6 +33,7 @@ export const transformRequest = ( upstream_host, upstream_id, } = data; + data.checks = pickBy(data.checks || {}, identity); if (data.checks.active) { data.checks.active = pickBy( diff --git a/web/src/pages/Upstream/typing.d.ts b/web/src/pages/Upstream/typing.d.ts index 39ae372cbc..a262ba139f 100644 --- a/web/src/pages/Upstream/typing.d.ts +++ b/web/src/pages/Upstream/typing.d.ts @@ -79,6 +79,9 @@ declare namespace UpstreamModule { desc?: string; pass_host?: 'pass' | 'node' | 'rewrite'; upstream_host: UpstreamHost[]; + + // Custom Fields that need to be omitted + custom?: {}; }; // TODO: typing