You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2020/10/10 10:07:52 UTC

[GitHub] [apisix-dashboard] juzhiyuan opened a new pull request #550: feat(Upstream): debug API

juzhiyuan opened a new pull request #550:
URL: https://github.com/apache/apisix-dashboard/pull/550


   Please answer these questions before submitting a pull request
   
   - Why submit this pull request?
   - [ ] Bug fix
   - [ ] New feature provided
   - [ ] Improve performance
   
   - Related issues
   
   ___
   ### Bugfix
   - Description
   
   - How to fix?
   
   ___
   ### New feature or improvement
   - Describe the details and related test reports.
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix-dashboard] LiteSun merged pull request #550: feat(Upstream): debug API

Posted by GitBox <gi...@apache.org>.
LiteSun merged pull request #550:
URL: https://github.com/apache/apisix-dashboard/pull/550


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix-dashboard] LiteSun commented on a change in pull request #550: feat(Upstream): debug API

Posted by GitBox <gi...@apache.org>.
LiteSun commented on a change in pull request #550:
URL: https://github.com/apache/apisix-dashboard/pull/550#discussion_r502800765



##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+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 = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>

Review comment:
       should use the same code style.`marginBottom: 10`

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+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 = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}

Review comment:
       same

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+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 = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}
+                        onClick={() => {
+                          remove(field.name);
+                        }}
+                      />
+                    )}
+                  </Col>
+                </Row>
+              </Form.Item>
+            ))}
+            {!readonly && (
+              <Form.Item wrapperCol={{ offset: 3 }}>
+                <Button type="dashed" onClick={() => add()}>
+                  <PlusOutlined />
+                  创建请求头
+                </Button>
+              </Form.Item>
+            )}
+          </>
+        )}
+      </Form.List>
+    </>
+  );
+  const InActiveHealthCheck = () => (
+    <>
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                required
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.passive.http_statuses' })
+                }
+                labelCol={{ span: index === 0 ? 3 : 0 }}
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }}>

Review comment:
       ,

##########
File path: src/components/Upstream/UpstreamForm.tsx
##########
@@ -0,0 +1,602 @@
+import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
+import { Button, Col, Divider, Form, Input, InputNumber, Row, Select, Switch } from 'antd';
+import React, { useEffect, useState } from 'react';
+import { useIntl } from 'umi';
+import type { FormInstance } from 'antd/lib/form';
+
+import { PanelSection } from '@api7-dashboard/ui';
+
+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 = {};
+
+type Props = {
+  form: FormInstance;
+  upstream?: Upstream;
+  id?: string;
+};
+
+const timeoutFields = [
+  {
+    label: '连接超时',
+    name: ['timeout', 'connect'],
+  },
+  {
+    label: '发送超时',
+    name: ['timeout', 'send'],
+  },
+  {
+    label: '接收超时',
+    name: ['timeout', 'read'],
+  },
+];
+
+const UpstreamForm: React.FC<Props> = ({ form, id }) => {
+  const [readonly] = useState(false);
+  const { formatMessage } = useIntl();
+
+  useEffect(() => {
+    // TODO: 获取 upstream 列表
+  }, []);
+
+  useEffect(() => {
+    if (id) {
+      // TODO: 获取 upstream、设置 readonly、填充数据
+    }
+  }, [id]);
+
+  const CHash = () => (
+    <>
+      <Form.Item label="Hash On" name="hash_on" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashOn).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+      <Form.Item label="Key" name="key" rules={[{ required: true }]}>
+        <Select disabled={readonly}>
+          {Object.entries(HashKey).map(([label, value]) => (
+            <Select.Option value={value} key={value}>
+              {label}
+            </Select.Option>
+          ))}
+        </Select>
+      </Form.Item>
+    </>
+  );
+
+  const TimeUnit = () => <span style={{ margin: '0 8px' }}>ms</span>;
+  const NodeList = () => (
+    <Form.List name="nodes">
+      {(fields, { add, remove }) => (
+        <>
+          {fields.map((field, index) => (
+            <Form.Item
+              required
+              key={field.key}
+              label={index === 0 && '节点域名/IP'}
+              extra={
+                index === 0 && '使用域名时,默认解析本地 /etc/resolv.conf;权重为0则熔断该节点'
+              }
+              labelCol={{ span: index === 0 ? 3 : 0 }}
+              wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+            >
+              <Row style={{ marginBottom: '10px' }} gutter={16}>
+                <Col span={5}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'host']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                      {
+                        pattern: new RegExp(
+                          /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                          'g',
+                        ),
+                      },
+                    ]}
+                  >
+                    <Input placeholder="域名/IP" disabled={readonly} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'port']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="端口号" disabled={readonly} min={1} max={65535} />
+                  </Form.Item>
+                </Col>
+                <Col span={2}>
+                  <Form.Item
+                    style={{ marginBottom: 0 }}
+                    name={[field.name, 'weight']}
+                    rules={[
+                      {
+                        required: true,
+                      },
+                    ]}
+                  >
+                    <InputNumber placeholder="权重" disabled={readonly} min={0} max={1000} />
+                  </Form.Item>
+                </Col>
+                <Col
+                  style={{
+                    marginLeft: -10,
+                    display: 'flex',
+                    alignItems: 'center',
+                  }}
+                >
+                  {!readonly && fields.length > 1 && (
+                    <MinusCircleOutlined onClick={() => remove(field.name)} />
+                  )}
+                </Col>
+              </Row>
+            </Form.Item>
+          ))}
+          {!readonly && (
+            <Form.Item wrapperCol={{ offset: 3 }}>
+              <Button type="dashed" onClick={add}>
+                <PlusOutlined />
+                创建节点
+              </Button>
+            </Form.Item>
+          )}
+        </>
+      )}
+    </Form.List>
+  );
+
+  const ActiveHealthCheck = () => (
+    <>
+      <Form.Item label="超时时间">
+        <Form.Item name={['checks', 'active', 'timeout']} noStyle>
+          <InputNumber disabled={readonly} />
+        </Form.Item>
+        <span style={{ margin: '0 8px' }}>s</span>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.http_path' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'http_path']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.http_path' }),
+            },
+          ]}
+        >
+          <Input
+            disabled={readonly}
+            placeholder={formatMessage({
+              id: 'upstream.step.input.healthy.checks.active.http_path',
+            })}
+          />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.active.host' })} required>
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'host']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' }),
+            },
+            {
+              pattern: new RegExp(
+                /(^([1-9]?\d|1\d{2}|2[0-4]\d|25[0-5])(\.(25[0-5]|1\d{2}|2[0-4]\d|[1-9]?\d)){3}$|^(?![0-9.]+$)([a-zA-Z0-9_-]+)(\.[a-zA-Z0-9_-]+){0,}$)/,
+                'g',
+              ),
+              message: formatMessage({ id: 'upstream.step.domain.name.or.ip.rule' }),
+            },
+          ]}
+        >
+          <Input
+            placeholder={formatMessage({ id: 'upstream.step.input.healthy.checks.active.host' })}
+            disabled={readonly}
+          />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          style={{ marginBottom: 0 }}
+          name={['checks', 'active', 'healthy', 'interval']}
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item label={formatMessage({ id: 'upstream.step.healthy.checks.successes' })} required>
+        <Form.Item
+          name={['checks', 'active', 'healthy', 'successes']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.successes' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+
+      <Divider orientation="left" plain>
+        不健康状态
+      </Divider>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.active.interval' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'interval']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({
+                id: 'upstream.step.input.healthy.checks.active.interval',
+              }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} />
+        </Form.Item>
+      </Form.Item>
+      <Form.Item
+        label={formatMessage({ id: 'upstream.step.healthy.checks.http_failures' })}
+        required
+      >
+        <Form.Item
+          name={['checks', 'active', 'unhealthy', 'http_failures']}
+          noStyle
+          rules={[
+            {
+              required: true,
+              message: formatMessage({ id: 'upstream.step.input.healthy.checks.http_failures' }),
+            },
+          ]}
+        >
+          <InputNumber disabled={readonly} min={1} max={254} />
+        </Form.Item>
+      </Form.Item>
+      <Form.List name={['checks', 'active', 'req_headers']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.active.req_headers' })
+                }
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }} gutter={16}>
+                  <Col span={10}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <Input
+                        placeholder={formatMessage({
+                          id: 'upstream.step.input.healthy.checks.active.req_headers',
+                        })}
+                        disabled={readonly}
+                      />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,
+                      display: 'flex',
+                      alignItems: 'center',
+                    }}
+                  >
+                    {!readonly && fields.length > 1 && (
+                      <MinusCircleOutlined
+                        style={{ margin: '0 8px' }}
+                        onClick={() => {
+                          remove(field.name);
+                        }}
+                      />
+                    )}
+                  </Col>
+                </Row>
+              </Form.Item>
+            ))}
+            {!readonly && (
+              <Form.Item wrapperCol={{ offset: 3 }}>
+                <Button type="dashed" onClick={() => add()}>
+                  <PlusOutlined />
+                  创建请求头
+                </Button>
+              </Form.Item>
+            )}
+          </>
+        )}
+      </Form.List>
+    </>
+  );
+  const InActiveHealthCheck = () => (
+    <>
+      <Divider orientation="left" plain>
+        健康状态
+      </Divider>
+      <Form.List name={['checks', 'passive', 'healthy', 'http_statuses']}>
+        {(fields, { add, remove }) => (
+          <>
+            {fields.map((field, index) => (
+              <Form.Item
+                required
+                key={field.key}
+                label={
+                  index === 0 &&
+                  formatMessage({ id: 'upstream.step.healthy.checks.passive.http_statuses' })
+                }
+                labelCol={{ span: index === 0 ? 3 : 0 }}
+                wrapperCol={{ offset: index === 0 ? 0 : 3 }}
+              >
+                <Row style={{ marginBottom: '10px' }}>
+                  <Col span={2}>
+                    <Form.Item style={{ marginBottom: 0 }} name={[field.name]}>
+                      <InputNumber disabled={readonly} />
+                    </Form.Item>
+                  </Col>
+                  <Col
+                    style={{
+                      marginLeft: -10,

Review comment:
       Extracts the same style as a variable.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [apisix-dashboard] juzhiyuan commented on pull request #550: feat(Upstream): debug API

Posted by GitBox <gi...@apache.org>.
juzhiyuan commented on pull request #550:
URL: https://github.com/apache/apisix-dashboard/pull/550#issuecomment-706565152


   @LiteSun Just ignore those checks above for this branch's API test cases are outdated, we just make sure the final PR which will be merged into master should pass all checks.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org