blob: 8f07e6e3ecf5fd72a0f6f6c9db5353661451dd25 [file] [log] [blame]
/*
* 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 { Divider, Form, notification, Switch } from 'antd';
import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
import { useIntl } from 'umi';
import type { FormInstance } from 'antd/es/form';
import PanelSection from '@/components/PanelSection';
import PassiveCheck from './components/passive-check';
import ActiveCheck from './components/active-check'
import Nodes from './components/Nodes'
import Scheme from './components/Scheme';
import Timeout from './components/Timeout';
import Type from './components/Type';
import UpstreamSelector from './components/UpstreamSelector';
import Retries from './components/Retries';
import PassHost from './components/PassHost';
import TLSComponent from './components/TLS';
import { convertToRequestData } from './service';
type Upstream = {
name?: string;
id?: string;
};
type Props = {
form: FormInstance;
disabled?: boolean;
list?: Upstream[];
showSelector?: boolean;
// FIXME: use proper typing
ref?: any;
required?: boolean;
neverReadonly?: boolean
};
/**
* UpstreamForm is used to reuse Upstream Form UI,
* before using this component, we need to execute the following command:
* form.setFieldsValue(convertToFormData(VALUE_FROM_API))
*/
const UpstreamForm: React.FC<Props> = forwardRef(
({ form, disabled = false, list = [], showSelector = false, required = true, neverReadonly = false }, ref) => {
const { formatMessage } = useIntl();
const [readonly, setReadonly] = useState(false);
const [hiddenForm, setHiddenForm] = useState(false);
const timeoutFields = [
{
label: formatMessage({ id: 'page.upstream.step.connect.timeout' }),
name: ['timeout', 'connect'],
desc: formatMessage({ id: 'page.upstream.step.connect.timeout.desc' })
},
{
label: formatMessage({ id: 'page.upstream.step.send.timeout' }),
name: ['timeout', 'send'],
desc: formatMessage({ id: 'page.upstream.step.send.timeout.desc' })
},
{
label: formatMessage({ id: 'page.upstream.step.read.timeout' }),
name: ['timeout', 'read'],
desc: formatMessage({ id: 'page.upstream.step.read.timeout.desc' })
},
];
useImperativeHandle(ref, () => ({
getData: () => convertToRequestData(form.getFieldsValue()),
}));
const resetForm = (upstream_id: string) => {
if (upstream_id === undefined) {
setReadonly(disabled);
return
}
if (!neverReadonly) {
setReadonly(!["Custom", "None"].includes(upstream_id) || disabled);
}
/**
* upstream_id === None <==> required === false
* No need to bind Upstream object.
* When creating Route and binds with a Service, no need to configure Upstream in Route.
*/
if (upstream_id === 'None') {
setHiddenForm(true);
form.resetFields()
form.setFieldsValue({ upstream_id: 'None' })
return
}
setHiddenForm(false)
// NOTE: Use Ant Design's form object to set data automatically
if (upstream_id === "Custom") {
return
}
// NOTE: Set data from Upstream List (Upstream Selector)
if (list.length === 0) {
return
}
form.resetFields()
const targetData = list.find((item) => item.id === upstream_id) as UpstreamComponent.ResponseData
if (targetData) {
form.setFieldsValue(targetData);
}
}
/**
* upstream_id
* - None: No need to bind Upstream to a resource (e.g Service).
* - Custom: Users could input values on UpstreamForm
* - Upstream ID from API
*/
useEffect(() => {
const upstream_id = form.getFieldValue('upstream_id');
resetForm(upstream_id)
}, [form.getFieldValue('upstream_id'), list]);
const ActiveHealthCheck = () => (
<React.Fragment>
<ActiveCheck.Type readonly={readonly} />
<Form.Item noStyle shouldUpdate={(prev, next) => prev.checks.active.type !== next.checks.active.type}>
{() => {
const type = form.getFieldValue(['checks', 'active', 'type'])
if (['https'].includes(type)) {
return <ActiveCheck.HttpsVerifyCertificate readonly={readonly} />
}
return null
}}
</Form.Item>
<ActiveCheck.Timeout readonly={readonly} />
<ActiveCheck.Concurrency readonly={readonly} />
<ActiveCheck.Host readonly={readonly} />
<ActiveCheck.Port readonly={readonly} />
<ActiveCheck.HttpPath readonly={readonly} />
<ActiveCheck.ReqHeaders readonly={readonly} />
<Divider orientation="left" plain>
{formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.status' })}
</Divider>
<ActiveCheck.Healthy.Interval readonly={readonly} />
<ActiveCheck.Healthy.Successes readonly={readonly} />
<ActiveCheck.Healthy.HttpStatuses readonly={readonly} />
<Divider orientation="left" plain>
{formatMessage({ id: 'page.upstream.step.healthyCheck.unhealthyStatus' })}
</Divider>
<ActiveCheck.Unhealthy.Timeouts readonly={readonly} />
<ActiveCheck.Unhealthy.Interval readonly={readonly} />
<ActiveCheck.Unhealthy.HttpStatuses readonly={readonly} />
<ActiveCheck.Unhealthy.HttpFailures readonly={readonly} />
<ActiveCheck.Unhealthy.TCPFailures readonly={readonly} />
</React.Fragment>
);
const PassiveHealthCheck = () => (
<React.Fragment>
<PassiveCheck.Type readonly={readonly} />
<Divider orientation="left" plain>
{formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.status' })}
</Divider>
<PassiveCheck.Healthy.HttpStatuses readonly={readonly} />
<PassiveCheck.Healthy.Successes readonly={readonly} />
<Divider orientation="left" plain>
{formatMessage({ id: 'page.upstream.step.healthyCheck.unhealthyStatus' })}
</Divider>
<PassiveCheck.Unhealthy.Timeouts readonly={readonly} />
<PassiveCheck.Unhealthy.TcpFailures readonly={readonly} />
<PassiveCheck.Unhealthy.HttpFailures readonly={readonly} />
<PassiveCheck.Unhealthy.HttpStatuses readonly={readonly} />
</React.Fragment>
);
const HealthCheckComponent = () => {
return (
<PanelSection
title={formatMessage({ id: 'page.upstream.step.healthyCheck.healthy.check' })}
>
<Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.active' })} name={['custom', 'checks', 'active']} valuePropName="checked">
<Switch disabled={readonly} />
</Form.Item>
<Form.Item shouldUpdate noStyle>
{
() => {
const active = form.getFieldValue(['custom', 'checks', 'active'])
if (active) {
return (
<ActiveHealthCheck />
)
}
return null
}
}
</Form.Item>
<Divider orientation="left" plain />
<Form.Item label={formatMessage({ id: 'page.upstream.step.healthyCheck.passive' })} name={['custom', 'checks', 'passive']} valuePropName="checked" tooltip={formatMessage({ id: 'component.upstream.other.health-check.passive-only' })}>
<Switch disabled={readonly} />
</Form.Item>
<Form.Item shouldUpdate={(prev, next) => prev.custom?.checks?.passive !== next.custom?.checks?.passive} noStyle>
{
() => {
const passive = form.getFieldValue(['custom', 'checks', 'passive'])
const active = form.getFieldValue(['custom', 'checks', 'active'])
if (passive) {
/*
* When enable passive check, we should enable active check, too.
* When we use form.setFieldsValue to enable active check, error throws.
* We choose to alert users first, and need users to enable active check manually.
*/
if (!active) {
notification.warn({
message: formatMessage({ id: 'component.upstream.other.health-check.invalid' }),
description: formatMessage({ id: 'component.upstream.other.health-check.passive-only' })
})
}
return <PassiveHealthCheck />
}
return null
}
}
</Form.Item>
</PanelSection>
)
}
return (
<Form
form={form}
labelCol={{ span: 3 }}
>
{showSelector && (
<UpstreamSelector
list={list}
disabled={disabled}
required={required}
onChange={(nextUpstreamId) => {
resetForm(nextUpstreamId);
}}
/>
)}
{!hiddenForm && (
<React.Fragment>
<Type form={form} readonly={readonly} />
<Nodes readonly={readonly} />
<PassHost form={form} readonly={readonly} />
<Retries readonly={readonly} />
<Scheme readonly={readonly} />
<Form.Item noStyle shouldUpdate={(prev, next) => prev.scheme !== next.scheme}>
{
() => {
const scheme = form.getFieldValue("scheme") as string
if (["https", "grpcs"].includes(scheme)) {
return <TLSComponent form={form} readonly={readonly} />
}
return null
}
}
</Form.Item>
{timeoutFields.map((item, index) => (
<Timeout key={index} {...item} readonly={readonly} />
))}
<HealthCheckComponent />
</React.Fragment>
)}
</Form>
);
},
);
export default UpstreamForm;