diff --git a/.asf.yaml b/.asf.yaml index fe73d207c7..b2617d954d 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -16,22 +16,22 @@ # github: - description: Dashboard for Apache APISIX - homepage: https://apisix.apache.org/ - labels: - - dashboard - - api - - api-management - - apisix - - devops - - docker + description: Dashboard for Apache APISIX + homepage: https://apisix.apache.org/ + labels: + - dashboard + - api + - api-management + - apisix + - devops + - docker - enabled_merge_buttons: - squash: true - merge: false - rebase: false + enabled_merge_buttons: + squash: true + merge: false + rebase: false - notifications: - commits: notifications@apisix.apache.org - issues: notifications@apisix.apache.org - pullrequests: notifications@apisix.apache.org + notifications: + commits: notifications@apisix.apache.org + issues: notifications@apisix.apache.org + pullrequests: notifications@apisix.apache.org diff --git a/.eslintrc.js b/.eslintrc.js index f8549393c8..e3b873ba21 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,4 +21,7 @@ module.exports = { page: true, REACT_APP_ENV: true, }, + rules: { + '@typescript-eslint/naming-convention': 'off', + }, }; diff --git a/.github/apisix-config.yaml b/.github/apisix-config.yaml index 2ff8ceba94..fcb7843fbb 100644 --- a/.github/apisix-config.yaml +++ b/.github/apisix-config.yaml @@ -20,8 +20,8 @@ etcd: host: - - "http://etcd:2379" + - 'http://etcd:2379' apisix: - allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow - - 0.0.0.0/0 # If we don't set any IP list, then any IP access is allowed by default. + allow_admin: # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow + - 0.0.0.0/0 # If we don't set any IP list, then any IP access is allowed by default. diff --git a/.github/workflows/api_ci.yml b/.github/workflows/api_ci.yml index 1fbfbb7f0c..787525c210 100644 --- a/.github/workflows/api_ci.yml +++ b/.github/workflows/api_ci.yml @@ -30,14 +30,12 @@ jobs: - name: run Makefile run: | make license-check - - name: get lua lib run: | wget https://github.com/api7/dag-to-lua/archive/v1.1.tar.gz sudo mkdir -p /go/manager-api/dag-to-lua/ tar -zxvf v1.1.tar.gz sudo mv ./dag-to-lua-1.1/lib/* /go/manager-api/dag-to-lua/ - - name: install runtime run: | sudo apt-get update @@ -46,7 +44,6 @@ jobs: sudo apt update export GO111MOUDULE=on sudo apt install golang-1.14-go - - name: generate json schema working-directory: ./api run: | @@ -56,9 +53,8 @@ jobs: sudo mv ./apisix-master/apisix/* ./build-tools/apisix/ rm -rf ./apisix-master cd ./build-tools/ && lua schema-sync.lua > ../conf/schema.json - - name: run test working-directory: ./api run: | export APIX_ETCD_ENDPOINTS=127.0.0.1:2379 - go test ./... + go test ./... \ No newline at end of file diff --git a/.github/workflows/api_cicd.yml b/.github/workflows/api_cicd.yml index 481c8ccf8f..b8cab6b3b6 100644 --- a/.github/workflows/api_cicd.yml +++ b/.github/workflows/api_cicd.yml @@ -39,22 +39,18 @@ jobs: apache/apisix:dev sleep 5 docker logs apisix - - name: setting up database run: | mysql -h 127.0.0.1 --port 3306 -u root -p123456 < ./api/script/db/schema.sql - - name: ping apisix run: | curl 127.0.0.1:9080 - - name: get lua lib run: | wget https://github.com/api7/dag-to-lua/archive/v1.1.tar.gz sudo mkdir -p /go/manager-api/dag-to-lua/ tar -zxvf v1.1.tar.gz sudo mv ./dag-to-lua-1.1/lib/* /go/manager-api/dag-to-lua/ - - name: install runtime run: | sudo apt-get update @@ -63,7 +59,6 @@ jobs: sudo apt update export GO111MOUDULE=on sudo apt install golang-1.14-go - - name: generate json schema working-directory: ./api run: | @@ -73,7 +68,6 @@ jobs: sudo mv ./apisix-master/apisix/* ./build-tools/apisix/ rm -rf ./apisix-master cd ./build-tools/ && lua schema-sync.lua > ../conf/schema.json - - uses: Azure/docker-login@v1 with: login-server: apisixacr.azurecr.cn @@ -84,4 +78,4 @@ jobs: run: | cd ./api docker build . -t apisixacr.azurecr.cn/managerapi:${{ github.sha }} - docker push apisixacr.azurecr.cn/managerapi:${{ github.sha }} + docker push apisixacr.azurecr.cn/managerapi:${{ github.sha }} \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 86cc48c74f..cb6c978912 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,22 +2,21 @@ name: Deploy to Azure on: push: - branches: [ master ] + branches: [master] jobs: build: runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - - uses: actions/checkout@v2 + - uses: Azure/docker-login@v1 + with: + login-server: apisixacr.azurecr.cn + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} - - uses: Azure/docker-login@v1 - with: - login-server: apisixacr.azurecr.cn - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} - - - run: | - docker build . -t apisixacr.azurecr.cn/dashboard:${{ github.sha }} - docker push apisixacr.azurecr.cn/dashboard:${{ github.sha }} \ No newline at end of file + - run: | + docker build . -t apisixacr.azurecr.cn/dashboard:${{ github.sha }} + docker push apisixacr.azurecr.cn/dashboard:${{ github.sha }} diff --git a/.github/workflows/license_ci.yml b/.github/workflows/license_ci.yml index 28a222ebd2..412be05b88 100644 --- a/.github/workflows/license_ci.yml +++ b/.github/workflows/license_ci.yml @@ -9,15 +9,12 @@ on: - master jobs: - check-license: - runs-on: ubuntu-latest steps: + - uses: actions/checkout@v2 - - uses: actions/checkout@v2 - - - name: run license check - run: | - make license-check + - name: run license check + run: | + make license-check diff --git a/.github/workflows/multiple-node-build.yml b/.github/workflows/multiple-node-build.yml index ad6c2bc1ce..e713e1d38a 100644 --- a/.github/workflows/multiple-node-build.yml +++ b/.github/workflows/multiple-node-build.yml @@ -6,9 +6,9 @@ name: multiple-node-build # events but only for the master branch on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a322373715..bfd8dac155 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,32 +19,21 @@ # Contributing to Apache APISIX Dashboard -Firstly, thanks for your interest in contributing! I hope that this will be a -pleasant first experience for you, and that you will return to continue -contributing. +Firstly, thanks for your interest in contributing! I hope that this will be a pleasant first experience for you, and that you will return to continue contributing. ## Code of Conduct -This project and everyone participating in it is governed by the Apache -software Foundation's -[Code of Conduct](http://www.apache.org/foundation/policies/conduct.html). By -participating, you are expected to adhere to this code. If you are aware of -unacceptable behavior, please visit the -[Reporting Guidelines page](http://www.apache.org/foundation/policies/conduct.html#reporting-guidelines) -and follow the instructions there. +This project and everyone participating in it is governed by the Apache software Foundation's [Code of Conduct](http://www.apache.org/foundation/policies/conduct.html). By participating, you are expected to adhere to this code. If you are aware of unacceptable behavior, please visit the [Reporting Guidelines page](http://www.apache.org/foundation/policies/conduct.html#reporting-guidelines) and follow the instructions there. ## How to contribute? -Most of the contributions that we receive are code contributions, but you can -also contribute to the documentation or simply report solid bugs -for us to fix. +Most of the contributions that we receive are code contributions, but you can also contribute to the documentation or simply report solid bugs for us to fix. ## How to report a bug? -* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/apache/apisix-dashboard/issues). - -* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/apache/apisix-dashboard/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. +- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/apache/apisix-dashboard/issues). +- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/apache/apisix-dashboard/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. ## How to add a new feature or change an existing one @@ -52,11 +41,11 @@ _Before making any significant changes, please [open an issue](https://github.co Once we've discussed your changes and you've got your code ready, make sure that tests are passing and open your pull request. Your PR is most likely to be accepted if it: -* Update the README.md with details of changes to the interface. -* Includes tests for new functionality. -* References the original issue in description, e.g. "resolve #123". -* Has a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +- Update the README.md with details of changes to the interface. +- Includes tests for new functionality. +- References the original issue in description, e.g. "resolve #123". +- Has a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). ## Do you have questions about the source code? -* Subscribe to our mail list and send the question mail to [dev@apisix.apache.org](mailto:dev@apisix.apache.org) +- Subscribe to our mail list and send the question mail to [dev@apisix.apache.org](mailto:dev@apisix.apache.org) diff --git a/api/docker-compose.yml b/api/docker-compose.yml index 4f838116d0..5e91b30631 100644 --- a/api/docker-compose.yml +++ b/api/docker-compose.yml @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version: "3" +version: '3' services: manager: diff --git a/compose/apisix_conf/config.yaml b/compose/apisix_conf/config.yaml index 7437eb0d00..4fd7ff255d 100644 --- a/compose/apisix_conf/config.yaml +++ b/compose/apisix_conf/config.yaml @@ -22,18 +22,17 @@ apisix: allow_admin: - 0.0.0.0/0 admin_key: - - - name: "admin" + - name: 'admin' key: edd1c9f034335f136f87ad84b625c8f1 - role: admin # admin: manage all configuration data - # viewer: only can view configuration data - - - name: "viewer" + role: + admin # admin: manage all configuration data + # viewer: only can view configuration data + - name: 'viewer' key: 4054f7cf07e344346cd3f287985e76a1 role: viewer etcd: - host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster. - - "http://192.17.5.10:2379" # multiple etcd address - prefix: "/apisix" # apisix configurations prefix - timeout: 30 # 30 seconds + host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster. + - 'http://192.17.5.10:2379' # multiple etcd address + prefix: '/apisix' # apisix configurations prefix + timeout: 30 # 30 seconds diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 5d432bfff7..c4819c775c 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -version: "3" +version: '3' services: apisix: @@ -27,8 +27,8 @@ services: - etcd ##network_mode: host ports: - - "9080:9080/tcp" - - "9443:9443/tcp" + - '9080:9080/tcp' + - '9443:9443/tcp' networks: apisix-dashboard: ipv4_address: 192.17.5.11 @@ -41,12 +41,12 @@ services: - ./etcd_data:/etcd_data environment: ETCD_DATA_DIR: /etcd_data - ETCD_ENABLE_V2: "true" - ALLOW_NONE_AUTHENTICATION: "yes" - ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379" - ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" + ETCD_ENABLE_V2: 'true' + ALLOW_NONE_AUTHENTICATION: 'yes' + ETCD_ADVERTISE_CLIENT_URLS: 'http://0.0.0.0:2379' + ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379' ports: - - "2379:2379/tcp" + - '2379:2379/tcp' networks: apisix-dashboard: ipv4_address: 192.17.5.10 @@ -56,7 +56,7 @@ services: command: sh -c "mkdir -p /tmp/www && echo 'web1' > /tmp/www/web1.txt && ruby -run -ehttpd /tmp/www -p8000" restart: always ports: - - "9081:8000/tcp" + - '9081:8000/tcp' networks: apisix-dashboard: ipv4_address: 192.17.5.12 @@ -66,7 +66,7 @@ services: command: sh -c "mkdir -p /tmp/www && echo 'web2' > /tmp/www/web2.txt && ruby -run -ehttpd /tmp/www -p8000" restart: always ports: - - "9082:8000/tcp" + - '9082:8000/tcp' networks: apisix-dashboard: ipv4_address: 192.17.5.13 @@ -75,7 +75,7 @@ services: image: mysql:latest restart: always ports: - - "3309:3306/tcp" + - '3309:3306/tcp' environment: - MYSQL_ROOT_PASSWORD=123456 volumes: @@ -92,7 +92,7 @@ services: dockerfile: Dockerfile restart: always ports: - - "8080:8080/tcp" + - '8080:8080/tcp' environment: - ENV=prod volumes: @@ -108,7 +108,7 @@ services: volumes: - ./prometheus_conf/prometheus.yml:/etc/prometheus/prometheus.yml ports: - - "9090:9090" + - '9090:9090' networks: apisix-dashboard: ipv4_address: 192.17.5.16 @@ -119,11 +119,11 @@ services: hostname: grafana restart: always ports: - - "3000:3000" + - '3000:3000' volumes: - - "./grafana_conf/provisioning:/etc/grafana/provisioning" - - "./grafana_conf/dashboards:/var/lib/grafana/dashboards" - - "./grafana_conf/config/grafana.ini:/etc/grafana/grafana.ini" + - './grafana_conf/provisioning:/etc/grafana/provisioning' + - './grafana_conf/dashboards:/var/lib/grafana/dashboards' + - './grafana_conf/config/grafana.ini:/etc/grafana/grafana.ini' networks: apisix-dashboard: ipv4_address: 192.17.5.17 @@ -134,9 +134,9 @@ services: dockerfile: Dockerfile restart: always ports: - - "80:80/tcp" + - '80:80/tcp' volumes: - - "./dashboard_conf/nginx.conf:/etc/nginx/conf.d/default.conf" + - './dashboard_conf/nginx.conf:/etc/nginx/conf.d/default.conf' networks: apisix-dashboard: ipv4_address: 192.17.5.18 @@ -146,4 +146,4 @@ networks: driver: bridge ipam: config: - - subnet: 192.17.0.0/16 + - subnet: 192.17.0.0/16 diff --git a/compose/grafana_conf/dashboards/apisix_http_prometheus.json b/compose/grafana_conf/dashboards/apisix_http_prometheus.json index 8dd9f8ba34..ad1e23fdd8 100644 --- a/compose/grafana_conf/dashboards/apisix_http_prometheus.json +++ b/compose/grafana_conf/dashboards/apisix_http_prometheus.json @@ -40,11 +40,7 @@ "cacheTimeout": null, "colorBackground": false, "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], + "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], "datasource": "apisix", "description": "", "fieldConfig": { @@ -131,11 +127,7 @@ "cacheTimeout": null, "colorBackground": false, "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], + "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], "datasource": "apisix", "description": "", "fieldConfig": { @@ -222,11 +214,7 @@ "cacheTimeout": null, "colorBackground": false, "colorValue": false, - "colors": [ - "#299c46", - "rgba(237, 129, 40, 0.89)", - "#d44a3a" - ], + "colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"], "datasource": "apisix", "description": "", "fieldConfig": { @@ -936,21 +924,10 @@ "to": "now" }, "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ] + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] }, "timezone": "", "title": "Apache APISIX", "uid": "bLlNuRLWz", "version": 1 -} \ No newline at end of file +} diff --git a/compose/grafana_conf/provisioning/dashboards/all.yaml b/compose/grafana_conf/provisioning/dashboards/all.yaml index 6ca6695601..d239437152 100644 --- a/compose/grafana_conf/provisioning/dashboards/all.yaml +++ b/compose/grafana_conf/provisioning/dashboards/all.yaml @@ -17,11 +17,11 @@ apiVersion: 1 providers: -- name: 'default' - orgId: 1 - folder: '' - type: file - disableDeletion: false - editable: false - options: - path: /var/lib/grafana/dashboards + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: false + options: + path: /var/lib/grafana/dashboards diff --git a/compose/grafana_conf/provisioning/datasources/all.yaml b/compose/grafana_conf/provisioning/datasources/all.yaml index 681bd7097a..0f72aed334 100644 --- a/compose/grafana_conf/provisioning/datasources/all.yaml +++ b/compose/grafana_conf/provisioning/datasources/all.yaml @@ -15,11 +15,11 @@ # limitations under the License. # datasources: - - access: 'proxy' - editable: true - is_default: true - name: 'apisix' - org_id: 1 - type: 'prometheus' - url: 'http://prometheus:9090' - version: 1 + - access: 'proxy' + editable: true + is_default: true + name: 'apisix' + org_id: 1 + type: 'prometheus' + url: 'http://prometheus:9090' + version: 1 diff --git a/compose/prometheus_conf/prometheus.yml b/compose/prometheus_conf/prometheus.yml index 73f08e9fe1..50b5aa99cd 100644 --- a/compose/prometheus_conf/prometheus.yml +++ b/compose/prometheus_conf/prometheus.yml @@ -20,20 +20,20 @@ global: # Attach these labels to any time series or alerts when communicating with # external systems (federation, remote storage, Alertmanager). external_labels: - monitor: "codelab-monitor" + monitor: 'codelab-monitor' # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=` to any timeseries scraped from this config. - - job_name: "prometheus" + - job_name: 'prometheus' # Override the global default and scrape targets from this job every 5 seconds. scrape_interval: 5s static_configs: - - targets: ["localhost:9090"] - - job_name: "apisix" - metrics_path: "/apisix/prometheus/metrics" + - targets: ['localhost:9090'] + - job_name: 'apisix' + metrics_path: '/apisix/prometheus/metrics' static_configs: - - targets: ["192.17.5.11:9080"] + - targets: ['192.17.5.11:9080'] diff --git a/config/proxy.ts b/config/proxy.ts index 3932be628d..316c65e7d9 100644 --- a/config/proxy.ts +++ b/config/proxy.ts @@ -18,7 +18,7 @@ export default { dev: { '/apisix/admin': { // NOTE: This is the manager-api pre-deployed in Azure just for preview, please refer to https://www.yuque.com/umijs/umi/proxy for more info. - target: 'http://139.217.185.221', + target: 'http://40.73.92.163:8080', changeOrigin: true, }, }, diff --git a/config/routes.ts b/config/routes.ts index 4056bd30cf..6648413784 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -35,22 +35,6 @@ const routes = [ path: '/routes/:rid/edit', component: './Route/Create', }, - { - path: '/routes/:rid/debug', - component: './Route/Debug', - }, - { - path: '/routegroup/list', - component: './RouteGroup/List', - }, - { - path: '/routegroup/create', - component: './RouteGroup/Create', - }, - { - path: '/routegroup/:gid/edit', - component: './RouteGroup/Create', - }, { path: '/ssl/:id/edit', component: './SSL/Create', diff --git a/manager-api.md b/manager-api.md index 994685bcc5..e46d6e4528 100644 --- a/manager-api.md +++ b/manager-api.md @@ -34,4 +34,3 @@ The manager-api has its own storage. The currently used mysql, mysql stores some When deploying dashboard, we are divided into 3 modules, namely dashboard page, manager-api, and the mysql database that manager-api depends on. The purple part in the figure is the two new modules added this time. You can also use docker-compose for one-click deployment, refer to [here](./compose/README.md) - diff --git a/package.json b/package.json index d2a960d419..8cd5576329 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,8 @@ "@ant-design/icons": "^4.0.0", "@ant-design/pro-layout": "^6.0.0", "@ant-design/pro-table": "2.6.3", - "@api7-dashboard/plugin": "^1.0.6", - "@api7-dashboard/pluginchart": "^1.0.13", + "@api7-dashboard/plugin": "^1.0.7", + "@api7-dashboard/pluginchart": "^1.0.14", "@api7-dashboard/ui": "^1.0.3", "@rjsf/antd": "2.2.0", "@rjsf/core": "2.2.0", diff --git a/src/components/Upstream/UpstreamForm.tsx b/src/components/Upstream/UpstreamForm.tsx new file mode 100644 index 0000000000..2d41bce96c --- /dev/null +++ b/src/components/Upstream/UpstreamForm.tsx @@ -0,0 +1,640 @@ +/* + * 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 { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; +import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd'; +import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'react'; +import { useIntl } from 'umi'; + +import { PanelSection } from '@api7-dashboard/ui'; +import { transformRequest } from '@/pages/Upstream/transform'; +import type { FormInstance } from 'antd/es/form'; + +enum Type { + roundrobin = 'roundrobin', + chash = 'chash', +} + +enum HashOn { + vars = 'vars', + header = 'header', + cookie = 'cookie', + consumer = 'consumer', +} + +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', +} + +type Upstream = { + name?: string; + id?: string; +}; + +type Props = { + form: FormInstance; + disabled?: boolean; + list?: Upstream[]; + showSelector?: boolean; + // FIXME: use proper typing + ref?: any; +}; + +const timeoutFields = [ + { + label: '连接超时', + name: ['timeout', 'connect'], + }, + { + label: '发送超时', + name: ['timeout', 'send'], + }, + { + label: '接收超时', + name: ['timeout', 'read'], + }, +]; + +const removeBtnStyle = { + marginLeft: -10, + display: 'flex', + alignItems: 'center', +}; + +const UpstreamForm: React.FC = forwardRef( + ({ form, disabled, list = [], showSelector }, ref) => { + const { formatMessage } = useIntl(); + const [readonly, setReadonly] = useState( + Boolean(form.getFieldValue('upstream_id')) || disabled, + ); + + useImperativeHandle(ref, () => ({ + getData: () => transformRequest(form.getFieldsValue()), + })); + + useEffect(() => { + const id = form.getFieldValue('upstream_id'); + if (id) { + form.setFieldsValue(list.find((item) => item.id === id)); + } + }, [list]); + + const CHash = () => ( + <> + + + + + + + + ); + + const TimeUnit = () => ms; + const NodeList = () => ( + + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => ( + + + + + + + + + + + + + + + + + + + {!readonly && fields.length > 1 && ( + remove(field.name)} /> + )} + + + + ))} + {!readonly && ( + + + + )} + + )} + + ); + + const ActiveHealthCheck = () => ( + <> + + + + + s + + + + + + + + + + + + + + 健康状态 + + + + + + + + + + + + + + 不健康状态 + + + + + + + + + + + + + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => ( + + + + + + + + + {!readonly && fields.length > 1 && ( + { + remove(field.name); + }} + /> + )} + + + + ))} + {!readonly && ( + + + + )} + + )} + + + ); + const InActiveHealthCheck = () => ( + <> + + 健康状态 + + + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => ( + + + + + + + + + {!readonly && fields.length > 1 && ( + { + remove(field.name); + }} + /> + )} + + + + ))} + {!readonly && ( + + + + )} + + )} + + + + + + + + + 不健康状态 + + + {(fields, { add, remove }) => ( + <> + {fields.map((field, index) => ( + + + + + + + + + {!readonly && fields.length > 1 && ( + { + remove(field.name); + }} + /> + )} + + + + ))} + {!readonly && ( + + + + )} + + )} + + + + + + + + + + + + + ); + + return ( +
+ {showSelector && ( + + + + )} + + + + + + {() => { + if (form.getFieldValue('type') === 'chash') { + return ; + } + return null; + }} + + + + + {timeoutFields.map(({ label, name }) => ( + + + + + + + ))} + + + {[ + { + label: '探活健康检查', + name: ['checks', 'active'], + component: , + }, + { + label: '被动健康检查', + name: ['checks', 'passive'], + component: , + }, + ].map(({ label, name, component }) => ( + <> + + + + + {() => { + if (form.getFieldValue(name)) { + return component; + } + return null; + }} + + + ))} + + + ); + }, +); + +export default UpstreamForm; diff --git a/src/pages/RouteGroup/locales/zh-CN.ts b/src/components/Upstream/constant.ts similarity index 82% rename from src/pages/RouteGroup/locales/zh-CN.ts rename to src/components/Upstream/constant.ts index cf4b167522..8f7c0bf408 100644 --- a/src/pages/RouteGroup/locales/zh-CN.ts +++ b/src/components/Upstream/constant.ts @@ -14,6 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export default { - 'page.routegroup.input.placeholder.name': '请输入分组名称', +export const DEFAULT_UPSTREAM = { + nodes: [{ host: '', port: 80, weight: 1 }], + type: 'roundrobin', + timeout: { + connect: 6000, + send: 6000, + read: 6000, + }, }; diff --git a/src/pages/RouteGroup/index.ts b/src/components/Upstream/index.ts similarity index 87% rename from src/pages/RouteGroup/index.ts rename to src/components/Upstream/index.ts index 7120a79f08..f653d02785 100644 --- a/src/pages/RouteGroup/index.ts +++ b/src/components/Upstream/index.ts @@ -14,5 +14,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { default as RouteGroupCN } from './locales/zh-CN'; -export { default as RouteGroupUS } from './locales/en-US'; +export { default } from './UpstreamForm'; +export * from './constant'; diff --git a/src/helpers.tsx b/src/helpers.tsx index 4b9151d9cf..39e97da160 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -34,11 +34,6 @@ export const getMenuData = (): MenuDataItem[] => { path: '/routes/list', icon: , }, - { - name: 'routegroup', - path: '/routegroup/list', - icon: , - }, { name: 'ssl', path: '/ssl/list', diff --git a/src/locales/en-US/component.ts b/src/locales/en-US/component.ts index 6e8b5d9f7d..12a9fc8073 100644 --- a/src/locales/en-US/component.ts +++ b/src/locales/en-US/component.ts @@ -53,7 +53,7 @@ export default { 'component.global.sendTimeout': 'Send Timeout', 'component.global.receiveTimeout': 'Receive Timeout', 'component.global.name': 'Name', - 'component.global.editTime': 'Edit Time', + 'component.global.updateTime': 'UpdateAt', 'component.global.form.itemExtraMessage.nameGloballyUnique': 'Name should be globally unique', 'component.global.input.placeholder.description': 'Can not more than 200 characters', // User component diff --git a/src/locales/en-US/menu.ts b/src/locales/en-US/menu.ts index 8afd19b0df..0528b97847 100644 --- a/src/locales/en-US/menu.ts +++ b/src/locales/en-US/menu.ts @@ -66,7 +66,6 @@ export default { 'menu.editor.koni': 'Koni Editor', 'menu.metrics': 'Metrics', 'menu.routes': 'Route', - 'menu.routegroup': 'RouteGroup', 'menu.ssl': 'SSL', 'menu.upstream': 'Upstream', 'menu.consumer': 'Consumer', diff --git a/src/locales/zh-CN/component.ts b/src/locales/zh-CN/component.ts index 8a9dec373e..e807c15436 100644 --- a/src/locales/zh-CN/component.ts +++ b/src/locales/zh-CN/component.ts @@ -42,7 +42,7 @@ export default { 'component.global.sendTimeout': '发送超时时间', 'component.global.receiveTimeout': '接收超时时间', 'component.global.name': '名称', - 'component.global.editTime': '编辑时间', + 'component.global.updateTime': '更新时间', 'component.global.popconfirm.title.delete': '确定删除该条记录吗?', 'component.global.steps.stepTitle.basicInformation': '基础信息', diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts index 7597a94247..df0fbcaacb 100644 --- a/src/locales/zh-CN/menu.ts +++ b/src/locales/zh-CN/menu.ts @@ -66,7 +66,6 @@ export default { 'menu.editor.koni': '拓扑编辑器', 'menu.metrics': '监控', 'menu.routes': '路由', - 'menu.routegroup': '路由分组', 'menu.ssl': '证书', 'menu.upstream': '上游', 'menu.consumer': '用户', diff --git a/src/pages/Consumer/List.tsx b/src/pages/Consumer/List.tsx index b382bc5c78..b6abbcacad 100644 --- a/src/pages/Consumer/List.tsx +++ b/src/pages/Consumer/List.tsx @@ -14,19 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; -import { PlusOutlined } from '@ant-design/icons'; -import { Popconfirm, Button, notification, Input } from 'antd'; +import { Popconfirm, Button, notification } from 'antd'; import moment from 'moment'; - import { history, useIntl } from 'umi'; +import { PlusOutlined } from '@ant-design/icons'; + import { fetchList, remove } from './service'; const Page: React.FC = () => { const ref = useRef(); - const [search, setSearch] = useState(''); const { formatMessage } = useIntl(); const columns: ProColumns[] = [ @@ -37,15 +36,18 @@ const Page: React.FC = () => { { title: formatMessage({ id: 'component.global.description' }), dataIndex: 'desc', + hideInSearch: true, }, { title: formatMessage({ id: 'page.consumer.updateTime' }), dataIndex: 'update_time', + hideInSearch: true, render: (text) => `${moment.unix(Number(text)).format('YYYY-MM-DD HH:mm:ss')}`, }, { title: formatMessage({ id: 'component.global.operation' }), valueType: 'option', + hideInSearch: true, render: (_, record) => ( <> , diff --git a/src/pages/Consumer/service.ts b/src/pages/Consumer/service.ts index 801d162c9e..0d16c466ba 100644 --- a/src/pages/Consumer/service.ts +++ b/src/pages/Consumer/service.ts @@ -16,16 +16,16 @@ */ import { request } from 'umi'; -export const fetchList = ({ current = 1, pageSize = 10 }, search: string) => +export const fetchList = ({ current = 1, pageSize = 10, ...res }) => request('/consumers', { params: { + username: res.username, page: current, - size: pageSize, - search, + page_size: pageSize, }, - }).then(({ list, count }) => ({ - data: list, - total: count, + }).then(({ data }) => ({ + data: data.rows, + total: data.total_size, })); export const fetchItem = (id: string) => diff --git a/src/pages/Metrics/index.ts b/src/pages/Metrics/index.ts index f721fee134..5259cf22d7 100644 --- a/src/pages/Metrics/index.ts +++ b/src/pages/Metrics/index.ts @@ -15,4 +15,3 @@ * limitations under the License. */ export { default } from './Metrics'; - diff --git a/src/pages/Route/Create.tsx b/src/pages/Route/Create.tsx index f9ff9cbdb5..e6d7ffd9c6 100644 --- a/src/pages/Route/Create.tsx +++ b/src/pages/Route/Create.tsx @@ -14,26 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Card, Steps, Form } from 'antd'; import { PageHeaderWrapper } from '@ant-design/pro-layout'; import { history, useIntl } from 'umi'; import { transformer as chartTransformer } from '@api7-dashboard/pluginchart'; import ActionBar from '@/components/ActionBar'; +import { DEFAULT_UPSTREAM } from '@/components/Upstream'; import { create, fetchItem, update, checkUniqueName, checkHostWithSSL } from './service'; import Step1 from './components/Step1'; import Step2 from './components/Step2'; import Step3 from './components/Step3'; - import CreateStep4 from './components/CreateStep4'; -import { - DEFAULT_STEP_1_DATA, - DEFAULT_STEP_2_DATA, - DEFAULT_STEP_3_DATA, - INIT_CHART, -} from './constants'; +import { DEFAULT_STEP_1_DATA, DEFAULT_STEP_3_DATA, INIT_CHART } from './constants'; import ResultView from './components/ResultView'; import styles from './Create.less'; @@ -63,12 +58,12 @@ const Page: React.FC = (props) => { const [advancedMatchingRules, setAdvancedMatchingRules] = useState( [], ); - const [upstreamHeaderList, setUpstreamHeaderList] = useState([]); const [step3Data, setStep3Data] = useState(DEFAULT_STEP_3_DATA); const [redirect, setRedirect] = useState(false); const [form1] = Form.useForm(); const [form2] = Form.useForm(); + const upstreamRef = useRef(); const [step, setStep] = useState(1); const [stepHeader, setStepHeader] = useState(STEP_HEADER_4); @@ -79,16 +74,14 @@ const Page: React.FC = (props) => { form1.setFieldsValue(data.form1Data); setAdvancedMatchingRules(data.advancedMatchingRules); form2.setFieldsValue(data.form2Data); - setUpstreamHeaderList(data.upstreamHeaderList); setStep3Data(data.step3Data); }); const onReset = () => { setAdvancedMatchingRules([]); - setUpstreamHeaderList([]); setStep3Data(DEFAULT_STEP_3_DATA); form1.setFieldsValue(DEFAULT_STEP_1_DATA); - form2.setFieldsValue(DEFAULT_STEP_2_DATA); + form2.setFieldsValue(DEFAULT_UPSTREAM); setStep(1); }; @@ -100,7 +93,7 @@ const Page: React.FC = (props) => { } }, []); - const renderStep = () => { + const StepList = () => { if (step === 1) { return ( = (props) => { return ( ); } - return ( - { - if (action === 'upstreamHeaderListChange') { - setUpstreamHeaderList(data); - } - }} - /> - ); + return ; } if (step === 3) { @@ -166,9 +149,9 @@ const Page: React.FC = (props) => { return ( ); @@ -190,14 +173,13 @@ const Page: React.FC = (props) => { }; const onStepChange = (nextStep: number) => { - const routeData = { - form1Data: form1.getFieldsValue(), - form2Data: form2.getFieldsValue(), - step3Data, - upstreamHeaderList, - advancedMatchingRules, - } as RouteModule.RequestData; const onUpdateOrCreate = () => { + const routeData = { + form1Data: form1.getFieldsValue(), + form2Data: upstreamRef.current?.getData(), + step3Data, + advancedMatchingRules, + } as RouteModule.RequestData; if (props.route.path.indexOf('edit') !== -1) { update((props as any).match.params.rid, routeData).then(() => { setStep(5); @@ -279,7 +261,7 @@ const Page: React.FC = (props) => { ))} - {renderStep()} + diff --git a/src/pages/Route/Debug.tsx b/src/pages/Route/Debug.tsx deleted file mode 100644 index 22eafb793a..0000000000 --- a/src/pages/Route/Debug.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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, { useEffect, useState } from 'react'; -import { PageHeaderWrapper } from '@ant-design/pro-layout'; -import SwaggerUI from 'swagger-ui-react'; - -import 'swagger-ui-react/swagger-ui.css'; - -import { fetchItemDebugInfo } from './service'; - -interface DebugProps { - match: any; -} -const swaggerDataBase = { - openapi: '3.0.1', - info: { - description: 'test apisix-dashboard', - title: 'APISIX route online debug', - license: { - name: 'Apache 2.0', - url: 'http://www.apache.org/licenses/LICENSE-2.0.html', - }, - }, -}; - -const Page: React.FC = (props) => { - const [swaggerData, setSwaggerData] = useState(swaggerDataBase); - - useEffect(() => { - fetchItemDebugInfo(props.match.params.rid).then((data) => { - setSwaggerData({ ...swaggerDataBase, ...data }); - }); - }, []); - return ( - - - - ); -}; - -export default Page; diff --git a/src/pages/Route/List.tsx b/src/pages/Route/List.tsx index c4028e35dd..aaf98259e6 100644 --- a/src/pages/Route/List.tsx +++ b/src/pages/Route/List.tsx @@ -14,22 +14,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import { PageHeaderWrapper } from '@ant-design/pro-layout'; import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; -import { Button, Popconfirm, notification, Tag, Input, Space } from 'antd'; -import { PlusOutlined } from '@ant-design/icons'; +import { Button, Popconfirm, notification, Tag, Space } from 'antd'; import moment from 'moment'; import { history, useIntl } from 'umi'; +import { PlusOutlined } from '@ant-design/icons'; -import { fetchList, offline, publish, remove } from './service'; +import { fetchList, remove } from './service'; const Page: React.FC = () => { const ref = useRef(); - const [search, setSearch] = useState(''); const { formatMessage } = useIntl(); - const columns: ProColumns[] = [ + const columns: ProColumns[] = [ { title: formatMessage({ id: 'component.global.name' }), dataIndex: 'name', @@ -37,6 +36,7 @@ const Page: React.FC = () => { { title: formatMessage({ id: 'page.route.domainName' }), dataIndex: 'hosts', + hideInSearch: true, render: (_, record) => (record.hosts || []).map((host) => ( @@ -48,66 +48,30 @@ const Page: React.FC = () => { title: formatMessage({ id: 'page.route.path' }), dataIndex: 'uri', render: (_, record) => - record.uris.map((uri) => ( + record.uris?.map((uri) => ( {uri} )), }, - // { - // title: '优先级', - // dataIndex: 'priority', - // }, { title: formatMessage({ id: 'component.global.description' }), - dataIndex: 'description', - }, - { - title: formatMessage({ id: 'page.route.routeGroup' }), - dataIndex: 'route_group_name', + dataIndex: 'desc', + hideInSearch: true, }, { - title: formatMessage({ id: 'page.route.status' }), - dataIndex: 'status', - render: (_, record) => ( - <> - {record.status ? ( - {formatMessage({ id: 'page.route.published' })} - ) : ( - {formatMessage({ id: 'page.route.unpublished' })} - )} - - ), - }, - { - title: formatMessage({ id: 'component.global.editTime' }), + title: formatMessage({ id: 'component.global.updateTime' }), dataIndex: 'update_time', + hideInSearch: true, render: (text) => `${moment.unix(Number(text)).format('YYYY-MM-DD HH:mm:ss')}`, }, { title: formatMessage({ id: 'component.global.operation' }), valueType: 'option', + hideInSearch: true, render: (_, record) => ( <> - - - { - offline(record.id!).then(() => { - notification.success({ - message: `${formatMessage({ id: 'menu.routes' })} - ${formatMessage({ id: 'page.route.offline' })} - ${formatMessage({ id: 'component.status.success' })}`, - }); - /* eslint-disable no-unused-expressions */ - ref.current?.reload(); - }); - }} - okText={formatMessage({ id: 'component.global.confirm' })} - cancelText={formatMessage({ id: 'component.global.cancel' })} - > - - { @@ -174,22 +111,13 @@ const Page: React.FC = () => { id: 'component.global.list', })}`} > - + actionRef={ref} rowKey="name" columns={columns} - search={false} - request={(params) => fetchList(params, search)} - toolBarRender={(action) => [ - { - setSearch(value); - action.setPageInfo({ page: 1 }); - action.reload(); - }} - />, - , diff --git a/src/pages/Route/components/CreateStep4/CreateStep4.tsx b/src/pages/Route/components/CreateStep4/CreateStep4.tsx index 617cb0acc4..5e153e38ae 100644 --- a/src/pages/Route/components/CreateStep4/CreateStep4.tsx +++ b/src/pages/Route/components/CreateStep4/CreateStep4.tsx @@ -29,14 +29,14 @@ type Props = { redirect?: boolean; step3Data: RouteModule.Step3Data; advancedMatchingRules: RouteModule.MatchingRule[]; - upstreamHeaderList: RouteModule.UpstreamHeader[]; + upstreamRef: any; }; const style = { marginTop: '40px', }; -const CreateStep4: React.FC = ({ form1, form2, redirect, ...rest }) => { +const CreateStep4: React.FC = ({ form1, form2, redirect, upstreamRef, ...rest }) => { const { formatMessage } = useIntl(); const { plugins = {}, script = {} } = rest.step3Data; @@ -46,14 +46,13 @@ const CreateStep4: React.FC = ({ form1, form2, redirect, ...rest }) => { {!redirect && ( <> -

{formatMessage({ id: 'page.route.steps.stepTitle.defineApiBackendServe' })}

- {}} - /> -

{formatMessage({ id: 'component.global.steps.stepTitle.pluginConfig' })}

+

+ {formatMessage({ id: 'page.route.steps.stepTitle.defineApiBackendServe' })} +

+ +

+ {formatMessage({ id: 'component.global.steps.stepTitle.pluginConfig' })} +

{Boolean(Object.keys(plugins).length !== 0) && ( )} diff --git a/src/pages/Route/components/Step1/MetaView.tsx b/src/pages/Route/components/Step1/MetaView.tsx index 6c85339e85..7c3053ee3c 100644 --- a/src/pages/Route/components/Step1/MetaView.tsx +++ b/src/pages/Route/components/Step1/MetaView.tsx @@ -14,35 +14,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import Form from 'antd/es/form'; -import { Input, Select, Switch } from 'antd'; +import { Input } from 'antd'; import { useIntl } from 'umi'; import { PanelSection } from '@api7-dashboard/ui'; -import { fetchRouteGroupList, fetchRouteGroupItem } from '@/pages/Route/service'; - -const MetaView: React.FC = ({ form, disabled, isEdit }) => { +const MetaView: React.FC = ({ disabled }) => { const { formatMessage } = useIntl(); - const [routeGroups, setRouteGroups] = useState<{ id: string; name: string }[]>(); - let routeGroupDisabled = disabled || Boolean(form.getFieldValue('route_group_id')); - - useEffect(() => { - // eslint-disable-next-line no-shadow - fetchRouteGroupList().then(({ data }) => { - setRouteGroups([ - { - name: `${formatMessage({ id: 'component.global.create' })} ${formatMessage({ - id: 'page.route.routeGroup', - })}`, - id: null, - }, - ...data, - ]); - }); - }, []); - return ( = ({ form, disabled, isEdit disabled={disabled} /> - - - - - - - {!isEdit && ( - - - - )} = ({ onChange = () => {}, }) => { const { formatMessage } = useIntl(); - const renderHosts = () => ( + const HostList = () => ( {(fields, { add, remove }) => { return ( @@ -99,8 +99,8 @@ const RequestConfigView: React.FC = ({ ); - const renderPaths = () => ( - + const UriList = () => ( + {(fields, { add, remove }) => { return (
@@ -181,33 +181,8 @@ const RequestConfigView: React.FC = ({ - - - - - - - {/* - - */} - {renderHosts()} - {renderPaths()} + + = ({ - + - - - - - {showModalValue && ( - - - - )} - - - ); - }; - - return ( - - {!disabled && ( - - )} - - {visible ? renderModal() : null} - - ); -}; - -export default HttpHeaderRewriteView; diff --git a/src/pages/Route/components/Step2/RequestRewriteView.tsx b/src/pages/Route/components/Step2/RequestRewriteView.tsx index 007c9b887e..a3bf8dcb0f 100644 --- a/src/pages/Route/components/Step2/RequestRewriteView.tsx +++ b/src/pages/Route/components/Step2/RequestRewriteView.tsx @@ -15,374 +15,28 @@ * limitations under the License. */ import React, { useEffect, useState } from 'react'; -import Form from 'antd/es/form'; -import Radio from 'antd/lib/radio'; -import { Input, Row, Col, InputNumber, Button, Select } from 'antd'; -import { PlusOutlined, MinusCircleOutlined } from '@ant-design/icons'; -import { useIntl } from 'umi'; -import { PanelSection } from '@api7-dashboard/ui'; +import UpstreamForm from '@/components/Upstream'; -import { - FORM_ITEM_LAYOUT, - FORM_ITEM_WITHOUT_LABEL, - HASH_KEY_LIST, - HASH_ON_LIST, -} from '@/pages/Route/constants'; -import styles from '../../Create.less'; -import { fetchUpstreamList, fetchUpstreamItem } from '../../service'; - -const RequestRewriteView: React.FC = ({ form, disabled }) => { - const [upstearms, setUpstreams] = useState<{ id: string; name: string }[]>(); - const [upstreamId, setUpstreamId] = useState(form.getFieldValue('upstream_id')); - // TODO: need to check - let upstreamDisabled = disabled || Boolean(form.getFieldValue('upstream_id')); - - if (upstreamId) { - fetchUpstreamItem(upstreamId).then((data) => { - form.setFieldsValue({ - ...form.getFieldsValue(), - ...data, - }); - upstreamDisabled = true; - }); - } - const { formatMessage } = useIntl(); +import { fetchUpstreamList } from '../../service'; +const RequestRewriteView: React.FC = ({ + form, + upstreamRef, + disabled, +}) => { + const [list, setList] = useState([]); useEffect(() => { - // eslint-disable-next-line no-shadow - fetchUpstreamList().then(({ data }) => { - setUpstreams([ - { name: formatMessage({ id: 'page.route.select.option.inputManually' }), id: null }, - ...data, - ]); - }); + fetchUpstreamList().then(({ data }) => setList(data)); }, []); - const renderUpstreamMeta = () => ( - <> - - - - prev.type !== next.type}> - {() => { - if (form.getFieldValue('type') === 'chash') { - return ( - <> - - - - - - - - ); - } - return null; - }} - - - {(fields, { add, remove }) => ( - <> - {fields.map((field, index) => ( - - - - - - - - - - - - - - - - - - - {!upstreamDisabled && - (fields.length > 1 ? ( - { - remove(field.name); - }} - /> - ) : null)} - - - - ))} - {!upstreamDisabled && ( - - - - )} - - )} - - - ); - - const renderTimeUnit = () => ms; return ( - -
- - - {formatMessage({ id: 'page.route.radio.staySame' })} - HTTP - HTTPS - - - - - {formatMessage({ id: 'page.route.radio.staySame' })} - {formatMessage({ id: 'page.route.radio.static' })} - {formatMessage({ id: 'page.route.radio.regx' })} - - - prev.rewriteType !== next.rewriteType}> - {() => { - if (form.getFieldValue('rewriteType') === 'regx') { - return ( - - - - ); - } - return null; - }} - - prev.rewriteType !== next.rewriteType}> - {() => { - if ( - form.getFieldValue('rewriteType') === 'static' || - form.getFieldValue('rewriteType') === 'regx' - ) { - return ( - - - - ); - } - return null; - }} - - - - - - {renderUpstreamMeta()} - - - - - {renderTimeUnit()} - - - - - - {renderTimeUnit()} - - - - - - {renderTimeUnit()} - - -
+ ); }; diff --git a/src/pages/Route/components/Step2/index.tsx b/src/pages/Route/components/Step2/index.tsx index ba60b12eba..4c6c2713f4 100644 --- a/src/pages/Route/components/Step2/index.tsx +++ b/src/pages/Route/components/Step2/index.tsx @@ -17,15 +17,7 @@ import React from 'react'; import RequestRewriteView from './RequestRewriteView'; -import HttpHeaderRewriteView from './HttpHeaderRewriteView'; -const Step2: React.FC = (props) => { - return ( - <> - - - - ); -}; +const Step2: React.FC = (props) => ; export default Step2; diff --git a/src/pages/Route/constants.ts b/src/pages/Route/constants.ts index d421b57637..56f7162f55 100644 --- a/src/pages/Route/constants.ts +++ b/src/pages/Route/constants.ts @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export const HTTP_METHOD_OPTION_LIST: RouteModule.HttpMethod[] = [ +export const HTTP_METHOD_OPTION_LIST: HttpMethod[] = [ 'GET', 'HEAD', 'POST', @@ -41,38 +41,19 @@ export const FORM_ITEM_WITHOUT_LABEL = { }; export const DEFAULT_STEP_1_DATA: RouteModule.Form1Data = { - route_group_id: null, - route_group_name: '', name: '', desc: '', status: false, priority: 0, - protocols: ['http', 'https'], websocket: false, hosts: [''], - paths: ['/*'], + uris: ['/*'], redirectOption: 'disabled', redirectURI: '', - redirectCode: 302, + ret_code: 302, methods: HTTP_METHOD_OPTION_LIST, }; -export const DEFAULT_STEP_2_DATA: RouteModule.Form2Data = { - upstream_protocol: 'keep', - upstreamHostList: [{} as RouteModule.UpstreamHost], - upstream_id: null, - pass_host: 'pass', - upstreamPath: undefined, - type: 'roundrobin', - mappingStrategy: undefined, - rewriteType: 'keep', - timeout: { - connect: 6000, - send: 6000, - read: 6000, - }, -}; - export const DEFAULT_STEP_3_DATA: RouteModule.Step3Data = { plugins: {}, script: {}, diff --git a/src/pages/Route/locales/en-US.ts b/src/pages/Route/locales/en-US.ts index 058a10b900..9dcabe59de 100644 --- a/src/pages/Route/locales/en-US.ts +++ b/src/pages/Route/locales/en-US.ts @@ -16,7 +16,6 @@ */ export default { 'page.route.button.returnList': 'Return Route List', - 'page.route.button.onlineDebug': 'Online Debug', 'page.route.parameterPosition': 'Parameter Position', 'page.route.httpRequestHeader': 'HTTP Request Header', @@ -41,7 +40,6 @@ export default { 'page.route.panelSection.title.nameDescription': 'Name And Description', 'page.route.form.itemLabel.apiName': 'API Name', - 'page.route.form.itemLable.routeGroup': 'Route Group Name', 'page.route.form.itemRulesPatternMessage.apiNameRule': 'Maximum length 100, only letters, Numbers, _, and - are supported, and can only begin with letters', @@ -69,12 +67,8 @@ export default { 'page.route.httpAction': 'Action', 'page.route.httpOverrideOrCreate': 'Override/Create', 'page.route.panelSection.title.httpOverrideRequestHeader': 'Override HTTP request header', - 'page.route.routeGroup': 'RouteGroup', 'page.route.status': 'Status', - 'page.route.published': 'Published', - 'page.route.unpublished': 'Unpublished', 'page.route.groupName': 'GroupName', - 'page.route.publish': 'Publish', 'page.route.offline': 'Offline', 'page.route.select.option.inputManually': 'Input Manually', diff --git a/src/pages/Route/locales/zh-CN.ts b/src/pages/Route/locales/zh-CN.ts index f032c4b171..9b27ebd1fb 100644 --- a/src/pages/Route/locales/zh-CN.ts +++ b/src/pages/Route/locales/zh-CN.ts @@ -32,17 +32,12 @@ export default { 'page.route.value': '参数值', 'page.route.protocol': '协议', 'page.route.httpHeaderName': 'HTTP 请求头名称', - 'page.route.routeGroup': '路由分组', 'page.route.status': '状态', - 'page.route.published': '已发布', - 'page.route.unpublished': '未发布', 'page.route.groupName': '分组名称', - 'page.route.publish': '发布', 'page.route.offline': '下线', // button 'page.route.button.returnList': '返回路由列表', - 'page.route.button.onlineDebug': '在线调试', // input 'page.route.input.placeholder.parameterNameHttpHeader': '请求头键名,例如:HOST', @@ -51,7 +46,6 @@ export default { // form 'page.route.form.itemRulesRequiredMessage.parameterName': '仅支持字母和数字,且只能以字母开头', 'page.route.form.itemLabel.apiName': 'API 名称', - 'page.route.form.itemLable.routeGroup': '路由分组名称', 'page.route.form.itemRulesPatternMessage.apiNameRule': '最大长度100,仅支持字母、数字、- 和 _,且只能以字母开头', 'page.route.form.itemLabel.httpMethod': 'HTTP 方法', diff --git a/src/pages/Route/service.ts b/src/pages/Route/service.ts index 62046dae52..2bb835eb9f 100644 --- a/src/pages/Route/service.ts +++ b/src/pages/Route/service.ts @@ -17,12 +17,7 @@ import { request } from 'umi'; import { pickBy, identity } from 'lodash'; -import { - transformStepData, - transformRouteData, - transformUpstreamNodes, - transformRouteDebugData, -} from './transform'; +import { transformStepData, transformRouteData, transformUpstreamNodes } from './transform'; export const create = (data: RouteModule.RequestData) => request(`/routes`, { @@ -37,29 +32,25 @@ export const update = (rid: number, data: RouteModule.RequestData) => }); export const fetchItem = (rid: number) => - request(`/routes/${rid}`).then((data) => transformRouteData(data)); + request(`/routes/${rid}`).then((data) => transformRouteData(data.data)); -export const fetchItemDebugInfo = (rid: number) => - request(`/routes/${rid}/debuginfo`).then((data) => transformRouteDebugData(data)); - -export const fetchList = ({ current = 1, pageSize = 10 }, search: string) => { - return request('/routes', { +export const fetchList = ({ current = 1, pageSize = 10, ...res }) => { + return request>>('/routes', { params: { + name: res.name, + uri: res.uri, page: current, - size: pageSize, - search, + page_size: pageSize, }, - }).then(({ data, count }) => { + }).then(({ data }) => { return { - data, - total: count, + data: data.rows, + total: data.total_size, }; }); }; -export const remove = (rid: number) => request(`/routes/${rid}`, { method: 'DELETE' }); -export const offline = (rid: number) => request(`/routes/${rid}/offline`, { method: 'PUT' }); -export const publish = (rid: number) => request(`/routes/${rid}/publish`, { method: 'PUT' }); +export const remove = (rid: string) => request(`/routes/${rid}`, { method: 'DELETE' }); export const checkUniqueName = (name = '', exclude = '') => request('/notexist/routes', { @@ -72,19 +63,14 @@ export const checkUniqueName = (name = '', exclude = '') => ), }); -export const fetchRouteGroupList = () => request(`/names/routegroups`); - -export const fetchRouteGroupItem = (gid: string) => { - return request(`/routegroups/${gid}`).then((data) => { - return { - route_group_name: data.name, - route_group_id: data.id, - }; - }); +export const fetchUpstreamList = () => { + // TODO: Use Cache and search on local + return request>>('/upstreams').then(({ data }) => ({ + data: data.rows, + total: data.total_size, + })); }; -export const fetchUpstreamList = () => request(`/names/upstreams`); - export const fetchUpstreamItem = (sid: string) => { return request(`/upstreams/${sid}`).then(({ nodes, timeout, id }) => { return { diff --git a/src/pages/Route/transform.ts b/src/pages/Route/transform.ts index b8a912dfd2..88e0cf3649 100644 --- a/src/pages/Route/transform.ts +++ b/src/pages/Route/transform.ts @@ -21,25 +21,8 @@ export const transformStepData = ({ form1Data, form2Data, advancedMatchingRules, - upstreamHeaderList, step3Data, }: RouteModule.RequestData) => { - const nodes = {}; - (form2Data.upstreamHostList || []).forEach((node) => { - nodes[`${node.host}:${node.port}`] = node.weight; - }); - - const upstream_header = {}; - (upstreamHeaderList || []).forEach((header) => { - upstream_header[header.header_name] = header.header_value || ''; - }); - - const chashData: any = {}; - if (form2Data.type === 'chash') { - chashData.key = form2Data.key; - chashData.hash_on = form2Data.hash_on; - } - let redirect: RouteModule.Redirect = {}; if (form1Data.redirectOption === 'disabled') { redirect = {}; @@ -47,23 +30,14 @@ export const transformStepData = ({ redirect = { http_to_https: true }; } else if (form1Data.redirectURI !== '') { redirect = { - code: form1Data.redirectCode, + ret_code: form1Data.ret_code, uri: form1Data.redirectURI, }; } - let { protocols } = form1Data; - if (form1Data.websocket) { - protocols = protocols.concat('websocket'); - } - const data: Partial = { ...form1Data, - ...form2Data, ...step3Data, - protocols, - uris: form1Data.paths, - redirect, vars: advancedMatchingRules.map((rule) => { const { operator, position, name, value } = rule; let key = ''; @@ -79,64 +53,44 @@ export const transformStepData = ({ } return [key, operator, value]; }), - upstream: { - type: form2Data.type, - ...chashData, - nodes, - timeout: form2Data.timeout, - }, - upstream_header, }; - if (form2Data.upstreamPath) { - data.upstream_path = { - to: form2Data.upstreamPath, - }; - if (form2Data.mappingStrategy) { - data.upstream_path = { - ...data.upstream_path, - from: form2Data.mappingStrategy, - type: 'regx', - }; - } - } - // 未启用 redirect if (!redirect.uri) { - if (step3Data.plugins.prometheus) { - // eslint-disable-next-line no-param-reassign - step3Data.plugins.prometheus = {}; + if (form2Data.upstream_id) { + data.upstream_id = form2Data.upstream_id; + } else { + data.upstream = form2Data; } + // 移除前端部分自定义变量 return omit(data, [ 'advancedMatchingRules', 'upstreamHostList', 'upstreamPath', - 'rewriteType', - 'mappingStrategy', - 'upstreamHeaderList', - 'websocket', 'timeout', 'redirectURI', - 'redirectCode', - 'forceHttps', + 'ret_code', 'redirectOption', + !Object.keys(step3Data.plugins || {}).length ? 'plugins' : '', + !Object.keys(step3Data.script || {}).length ? 'script' : '', form1Data.hosts.filter(Boolean).length === 0 ? 'hosts' : '', form1Data.redirectOption === 'disabled' ? 'redirect' : '', - form2Data.upstream_id ? 'upstream' : 'upstream_id', ]); } + if (Object.keys(redirect).length) { + data.plugins = { redirect }; + } + return pick(data, [ 'name', 'desc', - 'protocols', 'uris', 'methods', 'redirect', 'vars', - 'route_group_id', - 'route_group_name', + 'plugins', form1Data.hosts.filter(Boolean).length !== 0 ? 'hosts' : '', ]); }; @@ -170,86 +124,32 @@ export const transformUpstreamNodes = ( }; export const transformRouteData = (data: RouteModule.Body) => { - const { - name, - route_group_id, - route_group_name, - desc, - methods, - uris, - protocols, - hosts, - vars, - redirect, - status, - } = data; - + const { name, desc, methods, uris, hosts, vars, status, upstream, upstream_id } = data; const form1Data: Partial = { name, - route_group_id, - route_group_name, desc, status, - protocols: protocols.filter((item) => item !== 'websocket'), - websocket: protocols.includes('websocket'), hosts: (hosts || []).filter(Boolean).length === 0 ? [''] : hosts, - paths: uris, + uris, methods, }; - const advancedMatchingRules: RouteModule.MatchingRule[] = transformVarsToRules(vars); - if (redirect?.http_to_https) { - form1Data.redirectOption = 'forceHttps'; - } else if (redirect?.uri) { - form1Data.redirectOption = 'customRedirect'; - form1Data.redirectCode = redirect?.code; - form1Data.redirectURI = redirect?.uri; - } else { - form1Data.redirectOption = 'disabled'; - } - - const { - upstream, - upstream_path, - upstream_header, - upstream_protocol = 'keep', - upstream_id, - } = data; - let rewriteType = 'keep'; - if (upstream_path && upstream_path.to) { - if (upstream_path.from) { - rewriteType = 'regx'; + if (data.plugins) { + const { redirect } = data.plugins; + if (redirect?.http_to_https) { + form1Data.redirectOption = 'forceHttps'; + } else if (redirect?.uri) { + form1Data.redirectOption = 'customRedirect'; + form1Data.ret_code = redirect?.ret_code; + form1Data.redirectURI = redirect?.uri; } else { - rewriteType = 'static'; + form1Data.redirectOption = 'disabled'; } } - const upstreamHeaderList = Object.entries(upstream_header || {}).map(([k, v]) => { - return { - header_name: k, - header_value: v, - key: Math.random().toString(36).slice(2), - header_action: v ? 'override' : 'remove', - }; - }); + const advancedMatchingRules: RouteModule.MatchingRule[] = transformVarsToRules(vars); - const form2Data: RouteModule.Form2Data = { - upstream_protocol, - upstreamHeaderList, - type: upstream ? upstream.type : 'roundrobin', - hash_on: upstream ? upstream.hash_on : undefined, - key: upstream ? upstream.key : undefined, - upstreamHostList: transformUpstreamNodes(upstream?.nodes), - upstream_id, - upstreamPath: upstream_path?.to, - mappingStrategy: upstream_path?.from, - rewriteType, - timeout: upstream?.timeout || { - connect: 6000, - send: 6000, - read: 6000, - }, - }; + const form2Data: RouteModule.Form2Data = upstream || { upstream_id }; const { plugins, script } = data; @@ -266,131 +166,6 @@ export const transformRouteData = (data: RouteModule.Body) => { form1Data, form2Data, step3Data, - upstreamHeaderList, advancedMatchingRules, }; }; - -export const transformRouteDebugData = (data: RouteModule.Body) => { - const { - name, - desc, - methods, - uris, - protocols, - // hosts, - vars, - // redirect, - url, - } = data; - - const paths = {}; - const tags: RouteModule.TagSchema[] = [ - { - name: `Route-${name}`, - description: desc, - }, - ]; - let servers: RouteModule.Server[] = []; - const responses: RouteModule.ResponseSchema = { - // default response code - '200': { - description: 'OK', - content: {}, - }, - '400': { - description: 'Invalid parameter', - content: {}, - }, - '500': { - description: 'Internal Server Error', - content: {}, - }, - }; - const params = transformVarsToRules(vars); - const formatParams = params.map((param) => { - const { position, operator } = param; - let paramPostion; - - switch (position) { - case 'cookie': - paramPostion = 'cookie'; - break; - case 'http': - paramPostion = 'header'; - break; - case 'arg': - paramPostion = 'query'; - break; - default: - break; - } - return { - name: param.name, - in: paramPostion, - description: `default value should ${operator} ${param.value}`, - required: true, - type: 'string', - }; - }); - const pathParams = { - name: 'pathParam', - in: 'path', - description: `enter your path param`, - required: true, - type: 'string', - }; - const requestBodyMethod = ['POST', 'PUT', 'PATCH']; - - protocols.forEach((protocol) => { - if (protocol !== 'websocket') { - servers = [ - ...servers, - { - url: `${protocol}://${url}`, - }, - ]; - } - }); - - uris.forEach((uri) => { - if (uri.indexOf('*') > -1) { - paths[`${uri.split('*')[0]}{pathParam}`] = {}; - return; - } - paths[uri] = {}; - }); - - methods.forEach((method) => { - Object.keys(paths).forEach((path) => { - paths[path] = { - ...paths[path], - [method.toLocaleLowerCase()]: { - tags: [tags[0].name], - operationId: `${method.toLocaleLowerCase()}${path.split('/')[1]}`, - parameters: [...formatParams], - responses, - }, - }; - // route contains * - if (path.match(/{pathParam}/)) { - paths[path][method.toLocaleLowerCase()].parameters.push(pathParams); - } - // post, put, patch add requestBody - if (requestBodyMethod.indexOf(method) > -1) { - paths[path][method.toLocaleLowerCase()] = { - ...paths[path][method.toLocaleLowerCase()], - requestBody: { - description: 'body parameters', - content: {}, - }, - }; - } - }); - }); - return { - tags, - servers, - paths, - }; -}; diff --git a/src/pages/Route/typing.d.ts b/src/pages/Route/typing.d.ts index 6675054350..03d6df1027 100644 --- a/src/pages/Route/typing.d.ts +++ b/src/pages/Route/typing.d.ts @@ -27,7 +27,6 @@ declare namespace RouteModule { key: string; } - type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'PATCH'; type RequestProtocol = 'https' | 'http' | 'websocket'; type BaseData = { @@ -63,7 +62,7 @@ declare namespace RouteModule { type ModalType = 'CREATE' | 'EDIT'; type Redirect = { - code?: number; + ret_code?: number; uri?: string; http_to_https?: boolean; }; @@ -71,8 +70,6 @@ declare namespace RouteModule { // Request Body or Response Data for API type Body = { id?: number; - route_group_id: string; - route_group_name: string; status: boolean; name: string; desc: string; @@ -80,8 +77,6 @@ declare namespace RouteModule { methods: HttpMethod[]; uris: string[]; hosts: string[]; - protocols: RequestProtocol[]; - redirect?: Redirect; vars: [string, Operator, string][]; upstream: { type: 'roundrobin' | 'chash'; @@ -102,10 +97,6 @@ declare namespace RouteModule { to: string; }; upstream_id?: string; - upstream_protocol: 'keep' | 'http' | 'https'; - upstream_header: { - [key: string]: string; - }; plugins: { [name: string]: any; }; @@ -113,11 +104,6 @@ declare namespace RouteModule { url?: string; }; - // for route debug - type Server = { - url: string; - }; - type RouteParamSchema = { type: string | integer | boolean | object | array; }; @@ -160,23 +146,6 @@ declare namespace RouteModule { externalDocs?: object; }; - type DebugData = { - servers: Server[]; - tag: TagSchema[]; - paths: { - [url: string]: { - [httpType: string]: { - tags: string; - summary: string; - operationId: string; - requestBody?: {}; - parameters?: RouteParam[]; - responses: ResponseSchema; - }; - }; - }; - }; - // step1 interface MatchingRule { position: VarPosition; @@ -200,17 +169,14 @@ declare namespace RouteModule { type Form1Data = { name: string; desc: string; - route_group_id: string | null; - route_group_name: string; priority: number; - protocols: RequestProtocol[]; websocket: boolean; hosts: string[]; - paths: string[]; + uris: string[]; methods: HttpMethod[]; redirectOption: 'forceHttps' | 'customRedirect' | 'disabled'; redirectURI?: string; - redirectCode?: number; + ret_code?: number; status: boolean; }; @@ -227,35 +193,76 @@ declare namespace RouteModule { type Step2PassProps = { form: FormInstance; - upstreamHeaderList: UpstreamHeader[] | undefined; disabled?: boolean; - onChange(data: { action: 'upstreamHeaderListChange'; data: T }): void; + upstreamRef: any; }; type Form2Data = { - upstream_protocol: 'http' | 'https' | 'keep'; type: 'roundrobin' | 'chash'; hash_on?: string; key?: string; - mappingStrategy?: string; - rewriteType?: string; upstreamPath?: string; - upstream_id: string | null; + upstream_id?: string | null; timeout: { connect: number; send: number; read: number; }; - pass_host: 'pass' | 'node' | 'rewrite'; - upstream_host?: string; - upstreamHostList: UpstreamHost[]; + nodes: { + [key: string]: number; + }; }; type RequestData = { form1Data: Form1Data; form2Data: Form2Data; step3Data: Step3Data; - upstreamHeaderList: UpstreamHeader[]; advancedMatchingRules: MatchingRule[]; }; + + type RequestBody = { + name?: string; + desc?: string; + uri: string; + host?: string; + hosts?: string[]; + remote_addr?: string; + remote_addrs?: string[]; + methods?: HttpMethod[]; + priority?: number; + vars?: [string, Operator, string][]; + filter_func?: string; + plugins?: Record; + script?: Record; + // TODO: + upstream?: any; + upstream_id?: string; + service_id?: string; + service_protocol?: 'grpc' | 'http'; + }; + + type ResponseBody = { + hosts: string[]; + id: string; + methods: HttpMethod[]; + name: string; + remote_addrs: string[]; + script: any; + desc?: string; + upstream: { + checks: UpstreamModule.HealthCheck; + create_time: number; + k8s_deployment_info: UpstreamModule.K8SDeploymentInfo; + id: string; + nodes: { + port: number; + }[]; + timeout: UpstreamModule.Timeout; + type: UpstreamModule.Type; + }; + uri: string; + uris?: string[]; + create_time: number; + update_time: number; + }; } diff --git a/src/pages/RouteGroup/Create.tsx b/src/pages/RouteGroup/Create.tsx deleted file mode 100644 index 8008e491f6..0000000000 --- a/src/pages/RouteGroup/Create.tsx +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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, { useEffect, useState } from 'react'; -import { PageContainer } from '@ant-design/pro-layout'; -import { Card, Form, notification, Steps } from 'antd'; - -import ActionBar from '@/components/ActionBar'; -import { history, useIntl } from 'umi'; - -import Step1 from './components/Step1'; -import Preview from './components/Preview'; -import { create, fetchOne, update } from './service'; - -const Page: React.FC = (props) => { - const [step, setStep] = useState(1); - const [form1] = Form.useForm(); - const { formatMessage } = useIntl(); - - useEffect(() => { - const { gid } = (props as any).match.params; - - if (gid) { - fetchOne(gid).then((data) => { - form1.setFieldsValue(data); - }); - } - }, []); - - const onSubmit = () => { - const data = { ...form1.getFieldsValue() } as RouteGroupModule.RouteGroupEntity; - const { gid } = (props as any).match.params; - (gid ? update(gid, data) : create(data)).then(() => { - notification.success({ - message: `${ - gid - ? formatMessage({ id: 'component.global.edit' }) - : formatMessage({ id: 'component.global.create' }) - }${formatMessage({ id: 'menu.routegroup' })}${formatMessage({ - id: 'component.status.success', - })}`, - }); - history.replace('/routegroup/list'); - }); - }; - - const onStepChange = (nextStep: number) => { - if (step === 1) { - form1.validateFields().then(() => { - setStep(nextStep); - }); - } else if (nextStep === 3) { - onSubmit(); - } else { - setStep(nextStep); - } - }; - - return ( - <> - - - - - - - - {step === 1 && } - {step === 2 && } - - - - - ); -}; - -export default Page; diff --git a/src/pages/RouteGroup/List.tsx b/src/pages/RouteGroup/List.tsx deleted file mode 100644 index 8d8d0715d6..0000000000 --- a/src/pages/RouteGroup/List.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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, { useRef, useState } from 'react'; -import { PageContainer } from '@ant-design/pro-layout'; -import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; -import { PlusOutlined } from '@ant-design/icons'; -import { Popconfirm, Button, notification, Input } from 'antd'; -import { history, useIntl } from 'umi'; -import moment from 'moment'; - -import { fetchList, remove } from './service'; - -const Page: React.FC = () => { - const ref = useRef(); - - const [search, setSearch] = useState(''); - const { formatMessage } = useIntl(); - - const columns: ProColumns[] = [ - { - title: formatMessage({ id: 'component.global.name' }), - dataIndex: 'name', - }, - { - title: formatMessage({ id: 'component.global.description' }), - dataIndex: 'description', - }, - { - title: formatMessage({ id: 'component.global.editTime' }), - dataIndex: 'update_time', - render: (text) => `${moment.unix(Number(text)).format('YYYY-MM-DD HH:mm:ss')}`, - }, - { - title: formatMessage({ id: 'component.global.operation' }), - valueType: 'option', - render: (_, record) => ( - <> - - { - remove(record.id!).then(() => { - notification.success({ - message: `${formatMessage({ id: 'component.global.delete' })} ${formatMessage({ - id: 'menu.routegroup', - })} ${formatMessage({ id: 'component.status.success' })}`, - }); - /* eslint-disable no-unused-expressions */ - ref.current?.reload(); - }); - }} - > - - - - ), - }, - ]; - - return ( - - - actionRef={ref} - columns={columns} - rowKey="id" - search={false} - request={(params) => fetchList(params, search)} - toolBarRender={(action) => [ - { - setSearch(value); - action.setPageInfo({ page: 1 }); - action.reload(); - }} - />, - , - ]} - /> - - ); -}; - -export default Page; diff --git a/src/pages/RouteGroup/components/Preview.tsx b/src/pages/RouteGroup/components/Preview.tsx deleted file mode 100644 index 92d436f595..0000000000 --- a/src/pages/RouteGroup/components/Preview.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { FormInstance } from 'antd/lib/form'; - -import Step1 from './Step1'; - -type Props = { - form1: FormInstance; -}; - -const Page: React.FC = ({ form1 }) => ; - -export default Page; diff --git a/src/pages/RouteGroup/components/Step1.tsx b/src/pages/RouteGroup/components/Step1.tsx deleted file mode 100644 index 89e61b3976..0000000000 --- a/src/pages/RouteGroup/components/Step1.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { FormInstance } from 'antd/lib/form'; -import { useIntl } from 'umi'; - -import { FORM_ITEM_LAYOUT } from '@/pages/Upstream/constants'; - -type Props = { - form: FormInstance; - disabled?: boolean; -}; - -const initialValues = { - name: '', - description: '', -}; - -const Step1: React.FC = ({ form, disabled }) => { - const { formatMessage } = useIntl(); - return ( -
- - - - - - - - ); -}; - -export default Step1; diff --git a/src/pages/RouteGroup/constants.ts b/src/pages/RouteGroup/constants.ts deleted file mode 100644 index 25bb12f92c..0000000000 --- a/src/pages/RouteGroup/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 const FORM_ITEM_LAYOUT = { - labelCol: { - span: 6, - }, - wrapperCol: { - span: 18, - }, -}; - -export const FORM_ITEM_WITHOUT_LABEL = { - wrapperCol: { - xs: { span: 24, offset: 0 }, - sm: { span: 20, offset: 6 }, - }, -}; diff --git a/src/pages/RouteGroup/locales/en-US.ts b/src/pages/RouteGroup/locales/en-US.ts deleted file mode 100644 index 0338a7a3bc..0000000000 --- a/src/pages/RouteGroup/locales/en-US.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { - 'page.routegroup.input.placeholder.name': 'Please input routegroup name', -}; diff --git a/src/pages/RouteGroup/service.ts b/src/pages/RouteGroup/service.ts deleted file mode 100644 index 8504e4b57f..0000000000 --- a/src/pages/RouteGroup/service.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 { request } from 'umi'; - -export const fetchList = ({ current = 1, pageSize = 10 }, search: string) => - request('/routegroups', { - params: { - page: current, - size: pageSize, - search, - }, - }).then(({ data, count }) => ({ - data, - total: count, - })); - -export const fetchOne = (id: string) => - request(`/routegroups/${id}`); - -export const create = (data: RouteGroupModule.RouteGroupEntity) => - request('/routegroups', { - method: 'POST', - data, - }); - -export const update = (id: string, data: RouteGroupModule.RouteGroupEntity) => - request(`/routegroups/${id}`, { - method: 'PUT', - data, - }); - -export const remove = (id: string) => request(`/routegroups/${id}`, { method: 'DELETE' }); diff --git a/src/pages/RouteGroup/typing.d.ts b/src/pages/RouteGroup/typing.d.ts deleted file mode 100644 index 313272af40..0000000000 --- a/src/pages/RouteGroup/typing.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ -declare namespace RouteGroupModule { - type RouteGroupEntity = { - id: string; - name: string; - description: string; - }; -} diff --git a/src/pages/SSL/List.tsx b/src/pages/SSL/List.tsx index 8d9ebf28eb..7517c1c384 100644 --- a/src/pages/SSL/List.tsx +++ b/src/pages/SSL/List.tsx @@ -77,7 +77,7 @@ const Page: React.FC = () => { ), }, { - title: formatMessage({ id: 'component.global.editTime' }), + title: formatMessage({ id: 'component.global.updateTime' }), dataIndex: 'update_time', hideInSearch: true, render: (text) => `${moment.unix(Number(text)).format('YYYY-MM-DD HH:mm:ss')}`, diff --git a/src/pages/Upstream/Create.tsx b/src/pages/Upstream/Create.tsx index 5f4e81743f..09e14de62a 100644 --- a/src/pages/Upstream/Create.tsx +++ b/src/pages/Upstream/Create.tsx @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; import { Card, Steps, notification, Form } from 'antd'; import { history, useIntl } from 'umi'; @@ -22,51 +22,44 @@ import { history, useIntl } from 'umi'; import ActionBar from '@/components/ActionBar'; import Step1 from './components/Step1'; -import Preview from './components/Preview'; import { fetchOne, create, update } from './service'; -import { transformCreate, transformFetch } from './transform'; const Page: React.FC = (props) => { const [step, setStep] = useState(1); - const [active, setActive] = useState(false); - const [passive, setPassive] = useState(false); const [form1] = Form.useForm(); const { formatMessage } = useIntl(); - - const onChange = (checkActive: boolean, checkPassive: boolean) => { - setActive(checkActive); - setPassive(checkPassive); - }; + const upstreamRef = useRef(); useEffect(() => { const { id } = (props as any).match.params; if (id) { fetchOne(id).then((data) => { - const transformData = transformFetch(data); - form1.setFieldsValue(transformData); - if (transformData.active) { - setActive(true); - } - if (transformData.passive) { - setPassive(true); - } + form1.setFieldsValue(data.data); }); } }, []); const onSubmit = () => { - const data = transformCreate({ ...form1.getFieldsValue() } as UpstreamModule.Body); - const { id } = (props as any).match.params; - (id ? update(id, data) : create(data)).then(() => { - notification.success({ - message: `${ - id - ? formatMessage({ id: 'upstream.create.edit' }) - : formatMessage({ id: 'upstream.create.create' }) - } ${formatMessage({ id: 'upstream.create.upstream.successfully' })}`, + form1.validateFields().then(() => { + const data = upstreamRef.current?.getData(); + if (!data) { + // TODO: i18n + notification.error({ message: '请检查配置' }); + return; + } + + const { id } = (props as any).match.params; + (id ? update(id, data) : create(data)).then(() => { + notification.success({ + message: `${ + id + ? formatMessage({ id: 'upstream.create.edit' }) + : formatMessage({ id: 'upstream.create.create' }) + } ${formatMessage({ id: 'upstream.create.upstream.successfully' })}`, + }); + history.replace('/upstream/list'); }); - history.replace('/upstream/list'); }); }; @@ -91,12 +84,8 @@ const Page: React.FC = (props) => { - {step === 1 && ( - - )} - {step === 2 && ( - - )} + {step === 1 && } + {step === 2 && } diff --git a/src/pages/Upstream/List.tsx b/src/pages/Upstream/List.tsx index 5a073a81fb..75ba4a7a27 100644 --- a/src/pages/Upstream/List.tsx +++ b/src/pages/Upstream/List.tsx @@ -14,23 +14,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useRef, useState } from 'react'; +import React, { useRef } from 'react'; import { PageContainer } from '@ant-design/pro-layout'; import ProTable, { ProColumns, ActionType } from '@ant-design/pro-table'; -import { PlusOutlined } from '@ant-design/icons'; -import { Popconfirm, Button, notification, Input } from 'antd'; +import { Popconfirm, Button, notification } from 'antd'; import { history, useIntl } from 'umi'; import moment from 'moment'; +import { PlusOutlined } from '@ant-design/icons'; import { fetchList, remove } from './service'; const Page: React.FC = () => { const ref = useRef(); - const [search, setSearch] = useState(''); const { formatMessage } = useIntl(); - const columns: ProColumns[] = [ + const columns: ProColumns[] = [ { title: formatMessage({ id: 'upstream.list.name' }), dataIndex: 'name', @@ -38,19 +37,23 @@ const Page: React.FC = () => { { title: formatMessage({ id: 'upstream.list.type' }), dataIndex: 'type', + hideInSearch: true, }, { title: formatMessage({ id: 'upstream.list.description' }), dataIndex: 'description', + hideInSearch: true, }, { title: formatMessage({ id: 'upstream.list.edit.time' }), dataIndex: 'update_time', + hideInSearch: true, render: (text) => `${moment.unix(Number(text)).format('YYYY-MM-DD HH:mm:ss')}`, }, { title: formatMessage({ id: 'upstream.list.operation' }), valueType: 'option', + hideInSearch: true, render: (_, record) => ( <> , ]} /> diff --git a/src/pages/Upstream/components/Preview.tsx b/src/pages/Upstream/components/Preview.tsx deleted file mode 100644 index f73cf165e0..0000000000 --- a/src/pages/Upstream/components/Preview.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 { FormInstance } from 'antd/lib/form'; - -import Step1 from './Step1'; - -type Props = { - form1: FormInstance; - isActive: boolean; - isPassive: boolean; - onChange(a: boolean, p: boolean): void; -}; - -const Page: React.FC = ({ form1, isActive, onChange, isPassive }) => ( - -); - -export default Page; diff --git a/src/pages/Upstream/components/Step1.tsx b/src/pages/Upstream/components/Step1.tsx index 0e5e3a592c..1e6b4f6ec1 100644 --- a/src/pages/Upstream/components/Step1.tsx +++ b/src/pages/Upstream/components/Step1.tsx @@ -14,701 +14,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { Form, Input, Row, Col, InputNumber, Select, Switch, notification } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { Form, Input } from 'antd'; import { FormInstance } from 'antd/lib/form'; import { useIntl } from 'umi'; -import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; -import Button from 'antd/es/button'; -import { - FORM_ITEM_WITHOUT_LABEL, - FORM_ITEM_LAYOUT, - HASH_KEY_LIST, - HASH_ON_LIST, -} from '@/pages/Upstream/constants'; -import { PanelSection } from '@api7-dashboard/ui'; +import UpstreamForm, { DEFAULT_UPSTREAM } from '@/components/Upstream'; +import { fetchList } from '../service'; type Props = { form: FormInstance; disabled?: boolean; - isActive: boolean; - isPassive: boolean; - onChange(checkActive: boolean, checkPassive: boolean): void; + upstreamRef?: React.MutableRefObject; }; -const initialValues = { - name: '', - description: '', - type: 'roundrobin', - upstreamHostList: [{} as UpstreamModule.UpstreamHost], - timeout: { - connect: 6000, - send: 6000, - read: 6000, - }, - active: false, - passive: false, - checks: { - active: { - timeout: 5, - http_path: '', - host: '', - healthy: { - interval: 2, - successes: 1, - }, - unhealthy: { - interval: 1, - http_failures: 2, - }, - req_headers: [''], - }, - passive: { - healthy: { - http_statuses: [undefined], - successes: 3, - }, - unhealthy: { - http_statuses: [undefined], - http_failures: 3, - tcp_failures: 3, - }, - }, - }, -}; - -const Step1: React.FC = ({ form, disabled, isActive, onChange, isPassive }) => { +const Step1: React.FC = ({ form, disabled, upstreamRef }) => { const { formatMessage } = useIntl(); + const [list, setList] = useState([]); - const renderUpstreamMeta = () => ( - - {(fields, { add, remove }) => ( - <> - {fields.map((field, index) => ( - - -
- - - - - - - - - - - - - - - - {!disabled && - (fields.length > 1 ? ( - { - remove(field.name); - }} - /> - ) : null)} - - - - ))} - {!disabled && ( - - - - )} - - )} - - ); - - const renderTimeUnit = () => ms; - - const handleActiveChange = () => { - if (isActive) { - onChange(!isActive, false); - form.setFieldsValue({ ...form.getFieldsValue(), passive: false }); - return; - } - onChange(!isActive, isPassive); - form.setFieldsValue({ ...form.getFieldsValue(), active: !isActive }); - }; - const handlePassiveChange = () => { - if (!isActive) { - notification.warning({ - message: formatMessage({ id: 'upstream.notificationMessage.enableHealthCheckFirst' }), - }); - form.setFieldsValue({ ...form.getFieldsValue(), passive: isPassive }); - return; - } - onChange(isActive, !isPassive); - form.setFieldsValue({ ...form.getFieldsValue(), passive: !isPassive }); - }; - - const renderPassiveHealthyCheck = () => ( - <> - - - {(fields, { add, remove }) => ( - <> - {fields.map((field, index) => ( - - - - - - - - - {!disabled && - (fields.length > 1 ? ( - { - remove(field.name); - }} - /> - ) : null)} - - - - ))} - {!disabled && ( - - - - )} - - )} - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map((field, index) => ( - - - - - - - - - {!disabled && - (fields.length > 1 ? ( - { - remove(field.name); - }} - /> - ) : null)} - - - - ))} - {!disabled && ( - - - - )} - - )} - - - - - - - - - - - - - ); + useEffect(() => { + fetchList({}).then(({ data }) => setList(data)); + }, []); - const renderActiveHealthyCheck = () => ( + return ( <> - - - - - s - - +
-
- - - + - - - - - - - - - - - - - - - - - - - - - - - - - {(fields, { add, remove }) => ( - <> - {fields.map((field, index) => ( - - -
- - - - - - {!disabled && - (fields.length > 1 ? ( - { - remove(field.name); - }} - /> - ) : null)} - - - - ))} - {!disabled && ( - - - - )} - - )} - + + ); - return ( -
- - - - - - - - - - - {() => { - if (form.getFieldValue('type') === 'chash') { - return ( - <> - - - - - - - - ); - } - return null; - }} - - {renderUpstreamMeta()} - - - - - {renderTimeUnit()} - - - - - - {renderTimeUnit()} - - - - - - {renderTimeUnit()} - - - - - - {isActive && renderActiveHealthyCheck()} - - - - {isPassive && renderPassiveHealthyCheck()} - - - ); }; export default Step1; diff --git a/src/pages/Upstream/service.ts b/src/pages/Upstream/service.ts index 21b37cd322..78caa4a5d6 100644 --- a/src/pages/Upstream/service.ts +++ b/src/pages/Upstream/service.ts @@ -16,27 +16,29 @@ */ import { request } from 'umi'; -export const fetchList = ({ current = 1, pageSize = 10 }, search: string) => - request('/upstreams', { +export const fetchList = ({ current = 1, pageSize = 10, ...res }) => { + // TODO: Use Cache and search on local + return request>>('/upstreams', { params: { + name: res.name, page: current, - size: pageSize, - search, + page_size: pageSize, }, - }).then(({ data, count }) => ({ - data, - total: count, + }).then(({ data }) => ({ + data: data.rows, + total: data.total_size, })); +}; -export const fetchOne = (id: string) => request(`/upstreams/${id}`); +export const fetchOne = (id: string) => request>(`/upstreams/${id}`); -export const create = (data: UpstreamModule.Entity) => +export const create = (data: UpstreamModule.RequestBody) => request('/upstreams', { method: 'POST', data, }); -export const update = (id: string, data: UpstreamModule.Entity) => +export const update = (id: string, data: UpstreamModule.RequestBody) => request(`/upstreams/${id}`, { method: 'PUT', data, diff --git a/src/pages/Upstream/transform.ts b/src/pages/Upstream/transform.ts index 5d7d077def..cdecdf8b7e 100644 --- a/src/pages/Upstream/transform.ts +++ b/src/pages/Upstream/transform.ts @@ -1,3 +1,5 @@ +import { pickBy, identity, omit } from 'lodash'; + /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -14,47 +16,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { omit } from 'lodash'; - -const transformUpstreamNodes = ( - nodes: { [key: string]: number } = {}, -): RouteModule.UpstreamHost[] => { - const data: RouteModule.UpstreamHost[] = []; - Object.entries(nodes).forEach(([k, v]) => { - const [host, port] = k.split(':'); - data.push({ host, port: Number(port), weight: Number(v) }); - }); - if (data.length === 0) { - data.push({} as RouteModule.UpstreamHost); +export const transformRequest = ( + formData: UpstreamModule.RequestBody, +): UpstreamModule.RequestBody | undefined | { upstream_id: string } => { + let data = pickBy(formData, identity) as UpstreamModule.RequestBody; + const { + type, + hash_on, + key, + k8s_deployment_info, + nodes, + pass_host, + upstream_host, + upstream_id, + } = data; + data.checks = pickBy(data.checks, identity); + + if (upstream_id) { + return { upstream_id }; } - return data; -}; -export const transformCreate = (props: UpstreamModule.Body): UpstreamModule.Entity => { - const nodes = {}; - props.upstreamHostList.forEach((node) => { - nodes[`${node.host}:${node.port}`] = node.weight; - }); - return { - ...omit(props, 'upstreamHostList', 'active', 'passive'), - nodes, - }; -}; + if (Object.keys(data.checks).length === 0) { + data = omit(data, 'checks'); + } + if (nodes && k8s_deployment_info) { + return undefined; + } -export const transformFetch = (props: UpstreamModule.Entity) => { - const upstreamHostList = transformUpstreamNodes(props.nodes); - let active = false; - let passive = false; - if (props.checks) { - active = true; - if (props.checks.passive) { - passive = true; + if (!nodes && !k8s_deployment_info) { + return undefined; + } + + if (type === 'chash') { + if (!hash_on) { + return undefined; + } + + if (hash_on !== 'consumer' && !key) { + return undefined; } } - return { - ...omit(props, 'nodes'), - upstreamHostList, - active, - passive, - }; + + if (pass_host === 'rewrite' && !upstream_host) { + return undefined; + } + + if (nodes) { + return data; + } + + return undefined; }; diff --git a/src/pages/Upstream/typing.d.ts b/src/pages/Upstream/typing.d.ts index 25603d3d09..e9d1062e48 100644 --- a/src/pages/Upstream/typing.d.ts +++ b/src/pages/Upstream/typing.d.ts @@ -15,62 +15,71 @@ * limitations under the License. */ declare namespace UpstreamModule { - type UpstreamHost = { - host: string; - port: number; - weight: number; - }; + type Node = Record; + type Type = 'roundrobin' | 'chash' | 'ewma'; + + type Timeout = Record<'connect' | 'send' | 'read', number>; - type Base = { - name: string; - timeout: { - connect: number; - read: number; - send: number; + type HealthCheck = { + active?: { + timeout?: number; + http_path: string; + host: string; + healthy: { + interval: number; + successes: number; + }; + unhealthy: { + interval: number; + http_failures: number; + }; + req_headers?: string[]; }; - type: 'roundrobin' | 'chash'; - description: string; - checks: { - active: { - timeout?: number; - http_path: string; - host: string; - healthy: { - interval: number; - successes: number; - }; - unhealthy: { - interval: number; - http_failures: number; - }; - req_headers?: string[]; + passive?: { + healthy: { + http_statuses: number[]; + successes: number; }; - passive: { - healthy: { - http_statuses: number[]; - successes: number; - }; - unhealthy: { - http_statuses: number[]; - http_failures: number; - tcp_failures: number; - }; + unhealthy: { + http_statuses: number[]; + http_failures: number; + tcp_failures: number; }; }; }; - type Entity = Base & { - nodes: { - [ipWithPort: string]: number; - }; + type UpstreamHost = { + host: string; + port: number; + weight: number; }; - type Body = Base & { - upstreamHostList: UpstreamHost[]; + type K8SDeploymentInfo = { + namespace: string; + deploy_name: string; + service_name: string; + backend_type: string; + port: number; }; - type ResEntity = Entity & { + type RequestBody = { id: string; - update_time: string; + upstream_id: string; + type: Type; + nodes?: Node; + k8s_deployment_info?: K8SDeploymentInfo; + hash_on?: 'vars' | 'header' | 'cookie' | 'consumer'; + key?: string; + checks?: HealthCheck; + retries?: number; + enable_websocket?: boolean; + timeout?: Timeout; + name?: string; + desc?: string; + pass_host?: 'pass' | 'node' | 'rewrite'; + upstream_host: UpstreamHost[]; }; + + // TODO: typing + type ResponseBody = {} & RequestBody; } diff --git a/src/pages/User/components/LoginMethodPassword.tsx b/src/pages/User/components/LoginMethodPassword.tsx index 504f739806..86eb3a88b1 100644 --- a/src/pages/User/components/LoginMethodPassword.tsx +++ b/src/pages/User/components/LoginMethodPassword.tsx @@ -92,16 +92,16 @@ const LoginMethodPassword: UserModule.LoginMethod = { } return false; }, - submit: async (data) => { - if (data.username !== '' && data.password !== '') { + submit: async ({ username, password }) => { + if (username !== '' && password !== '') { try { const result = await request('/apisix/admin/user/login', { method: 'POST', - requestType: 'form', + requestType: 'json', prefix: '', data: { - username: data.username, - password: data.password, + username, + password, }, }); diff --git a/src/pages/document.ejs b/src/pages/document.ejs index 9a82df99e1..9ccb53b40c 100644 --- a/src/pages/document.ejs +++ b/src/pages/document.ejs @@ -190,7 +190,14 @@ SOFTWARE. }
@@ -200,12 +207,8 @@ SOFTWARE. >
-
- +
+ Apache APISIX Dashboard
diff --git a/src/service-worker.js b/src/service-worker.js index c5f7d7f98e..d99e1cfbca 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -73,7 +73,7 @@ workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst()) /** * Response to client after skipping waiting with MessageChannel */ -addEventListener('message', event => { +addEventListener('message', (event) => { const replyPort = event.ports[0]; const message = event.data; if (replyPort && message && message.type === 'skip-waiting') { @@ -83,7 +83,7 @@ addEventListener('message', event => { replyPort.postMessage({ error: null, }), - error => + (error) => replyPort.postMessage({ error, }), diff --git a/src/typings.d.ts b/src/typings.d.ts index a8875a6238..691ae14204 100644 --- a/src/typings.d.ts +++ b/src/typings.d.ts @@ -54,3 +54,17 @@ declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefine declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false; type PageMode = 'CREATE' | 'EDIT' | 'VIEW'; + +type Res = { + code: number; + message: string; + request_id: string; + data: T; +}; + +type ResListData = { + rows: T[]; + total_size: number; +}; + +type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'PUT' | 'OPTIONS' | 'HEAD' | 'PATCH'; diff --git a/yarn.lock b/yarn.lock index cd31f5399a..5d87a2ea65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,10 +199,22 @@ json-schema "^0.2.5" set-value "^3.0.2" -"@api7-dashboard/pluginchart@^1.0.13": - version "1.0.13" - resolved "https://registry.yarnpkg.com/@api7-dashboard/pluginchart/-/pluginchart-1.0.13.tgz#93c24773f838ec9de45a7142a5e6932c1bfc5538" - integrity sha512-qcqzx0lNFX4lh63RW2GUrsdVQHI7sJgZwRnkPDFaZEh2O2o9HOf5Z7krnL61dI+uWZEh8RRqjAbSlaBKqxWUbg== +"@api7-dashboard/plugin@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@api7-dashboard/plugin/-/plugin-1.0.7.tgz#ab4bd47c0ce3f3aac3c1ed5de7f78725f77d31de" + integrity sha512-VVQW6xfrKou2n1UBTBZVwZbvc1ZSskvvGlDb7x5kBbVoAzAVAT8bZqGnvrn6aZtei4f8YKkmSWFCD7+zBAKx8w== + dependencies: + "@rjsf/antd" "^2.3.0" + "@rjsf/core" "^2.3.0" + "@uiw/react-codemirror" "^3.0.1" + ajv "^6.12.5" + json-schema "^0.2.5" + set-value "^3.0.2" + +"@api7-dashboard/pluginchart@^1.0.14": + version "1.0.14" + resolved "https://registry.yarnpkg.com/@api7-dashboard/pluginchart/-/pluginchart-1.0.14.tgz#3147b7e89cdcb541ed1559970b77cc443a3ef15d" + integrity sha512-LD5uhQgDwmsWkK7PfMl1XZulIU7klpFvBDHTrXIvJ23xwSPnT6tYlNS256O1QyVMuQaKRRH/lqbtPPop+Sgg1w== dependencies: "@ant-design/icons" "^4.2.2" "@api7-dashboard/plugin" "^1.0.6"