1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

fix(ingress): loading and ui fixes [EE-5132] (#9959)

This commit is contained in:
Ali 2023-08-01 19:31:35 +12:00 committed by GitHub
parent e400c4dfc6
commit d0ecf6c16b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 309 additions and 174 deletions

View file

@ -1,19 +1,23 @@
import { ChangeEvent, ReactNode } from 'react';
import { ChangeEvent, ReactNode, useEffect } from 'react';
import { Plus, RefreshCw, Trash2 } from 'lucide-react';
import Route from '@/assets/ico/route.svg?c';
import { Link } from '@@/Link';
import { Select, Option } from '@@/form-components/Input/Select';
import { Option } from '@@/form-components/Input/Select';
import { FormError } from '@@/form-components/FormError';
import { Widget, WidgetBody, WidgetTitle } from '@@/Widget';
import { Tooltip } from '@@/Tip/Tooltip';
import { Button } from '@@/buttons';
import { TooltipWithChildren } from '@@/Tip/TooltipWithChildren';
import { TextTip } from '@@/Tip/TextTip';
import { Card } from '@@/Card';
import { InputGroup } from '@@/form-components/InputGroup';
import { InlineLoader } from '@@/InlineLoader';
import { Select } from '@@/form-components/ReactSelect';
import { Annotations } from './Annotations';
import { Rule, ServicePorts } from './types';
import { GroupedServiceOptions, Rule, ServicePorts } from './types';
import '../style.css';
@ -33,15 +37,16 @@ interface Props {
rule: Rule;
errors: Record<string, ReactNode>;
isLoading: boolean;
isEdit: boolean;
namespace: string;
servicePorts: ServicePorts;
ingressClassOptions: Option<string>[];
serviceOptions: Option<string>[];
isIngressClassOptionsLoading: boolean;
serviceOptions: GroupedServiceOptions;
tlsOptions: Option<string>[];
namespacesOptions: Option<string>[];
isNamespaceOptionsLoading: boolean;
removeIngressRoute: (hostIndex: number, pathIndex: number) => void;
removeIngressHost: (hostIndex: number) => void;
@ -76,7 +81,6 @@ interface Props {
export function IngressForm({
environmentID,
rule,
isLoading,
isEdit,
servicePorts,
tlsOptions,
@ -94,20 +98,38 @@ export function IngressForm({
reloadTLSCerts,
handleAnnotationChange,
ingressClassOptions,
isIngressClassOptionsLoading,
errors,
namespacesOptions,
isNamespaceOptionsLoading,
handleNamespaceChange,
namespace,
}: Props) {
if (isLoading) {
return <div>Loading...</div>;
}
const hasNoHostRule = rule.Hosts?.some((host) => host.NoHost);
const placeholderAnnotation =
PlaceholderAnnotations[rule.IngressType || 'other'] ||
PlaceholderAnnotations.other;
const pathTypes = PathTypes[rule.IngressType || 'other'] || PathTypes.other;
// when the namespace options update the value to an available one
useEffect(() => {
const namespaces = namespacesOptions.map((option) => option.value);
if (!namespaces.includes(namespace) && namespaces.length > 0) {
handleNamespaceChange(namespaces[0]);
}
}, [namespacesOptions, namespace, handleNamespaceChange]);
// when the ingress class options update update the value to an available one
useEffect(() => {
const ingressClasses = ingressClassOptions.map((option) => option.value);
if (
!ingressClasses.includes(rule.IngressClassName) &&
ingressClasses.length > 0
) {
handleIngressChange('IngressClassName', ingressClasses[0]);
}
}, [ingressClassOptions, rule.IngressClassName, handleIngressChange]);
return (
<Widget>
<WidgetTitle icon={Route} title="Ingress" />
@ -121,19 +143,30 @@ export function IngressForm({
>
Namespace
</label>
<div className="col-sm-4">
{isEdit ? (
namespace
) : (
<Select
name="namespaces"
options={namespacesOptions || []}
onChange={(e) => handleNamespaceChange(e.target.value)}
defaultValue={namespace}
disabled={isEdit}
/>
)}
</div>
{isNamespaceOptionsLoading && (
<div className="col-sm-4">
<InlineLoader className="pt-2">
Loading namespaces...
</InlineLoader>
</div>
)}
{!isNamespaceOptionsLoading && (
<div className={`col-sm-4 ${isEdit && 'control-label'}`}>
{isEdit ? (
namespace
) : (
<Select
name="namespaces"
options={namespacesOptions || []}
value={{ value: namespace, label: namespace }}
isDisabled={isEdit}
onChange={(val) =>
handleNamespaceChange(val?.value || '')
}
/>
)}
</div>
)}
</div>
</div>
</div>
@ -180,16 +213,35 @@ export function IngressForm({
Ingress class
</label>
<div className="col-sm-4">
<Select
name="ingress_class"
className="form-control"
placeholder="Ingress name"
defaultValue={rule.IngressClassName}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
handleIngressChange('IngressClassName', e.target.value)
}
options={ingressClassOptions}
/>
{isIngressClassOptionsLoading && (
<InlineLoader className="pt-2">
Loading ingress classes...
</InlineLoader>
)}
{!isIngressClassOptionsLoading && (
<>
<Select
name="ingress_class"
placeholder="Ingress name"
options={ingressClassOptions}
value={{
label: rule.IngressClassName,
value: rule.IngressClassName,
}}
onChange={(ingressClassOption) =>
handleIngressChange(
'IngressClassName',
ingressClassOption?.value || ''
)
}
/>
{errors.className && (
<FormError className="error-inline mt-1">
{errors.className}
</FormError>
)}
</>
)}
{errors.className && (
<FormError className="error-inline mt-1">
{errors.className}
@ -300,9 +352,9 @@ export function IngressForm({
{namespace &&
rule?.Hosts?.map((host, hostIndex) => (
<div className="row rule bordered mb-5" key={host.Key}>
<div className="col-sm-12">
<div className="row rule-actions mt-5">
<Card key={host.Key} className="mb-5">
<div className="flex flex-col">
<div className="row rule-actions">
<div className="col-sm-3 p-0">
{!host.NoHost ? 'Rule' : 'Fallback rule'}
</div>
@ -323,11 +375,9 @@ export function IngressForm({
{!host.NoHost && (
<div className="row">
<div className="form-group col-sm-6 col-lg-4 !pl-0 !pr-2">
<div className="input-group input-group-sm">
<span className="input-group-addon required">
Hostname
</span>
<input
<InputGroup size="small">
<InputGroup.Addon required>Hostname</InputGroup.Addon>
<InputGroup.Input
name={`ingress_host_${hostIndex}`}
type="text"
className="form-control form-control-sm"
@ -337,7 +387,7 @@ export function IngressForm({
handleHostChange(hostIndex, e.target.value)
}
/>
</div>
</InputGroup>
{errors[`hosts[${hostIndex}].host`] && (
<FormError className="mt-1 !mb-0">
{errors[`hosts[${hostIndex}].host`]}
@ -346,17 +396,19 @@ export function IngressForm({
</div>
<div className="form-group col-sm-6 col-lg-4 !pr-0 !pl-2">
<div className="input-group input-group-sm">
<span className="input-group-addon">TLS secret</span>
<InputGroup size="small">
<InputGroup.Addon>TLS secret</InputGroup.Addon>
<Select
key={tlsOptions.toString() + host.Secret}
name={`ingress_tls_${hostIndex}`}
options={tlsOptions}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
handleTLSChange(hostIndex, e.target.value)
value={{
value: rule.Hosts[hostIndex].Secret,
label: rule.Hosts[hostIndex].Secret || 'No TLS',
}}
onChange={(TLSOption) =>
handleTLSChange(hostIndex, TLSOption?.value || '')
}
defaultValue={host.Secret}
className="!rounded-r-none"
size="sm"
/>
{!host.NoHost && (
<div className="input-group-btn">
@ -367,7 +419,7 @@ export function IngressForm({
/>
</div>
)}
</div>
</InputGroup>
</div>
<div className="col-sm-12 col-lg-4 flex h-[30px] items-center pl-2">
@ -414,25 +466,27 @@ export function IngressForm({
key={`path_${path.Key}}`}
>
<div className="form-group col-sm-3 col-xl-2 !m-0 !pl-0">
<div className="input-group input-group-sm">
<span className="input-group-addon required">
Service
</span>
<InputGroup size="small">
<InputGroup.Addon required>Service</InputGroup.Addon>
<Select
key={serviceOptions.toString() + path.ServiceName}
name={`ingress_service_${hostIndex}_${pathIndex}`}
options={serviceOptions}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
value={{
value: path.ServiceName,
label: path.ServiceName || 'Select a service',
}}
onChange={(serviceOption) =>
handlePathChange(
hostIndex,
pathIndex,
'ServiceName',
e.target.value
serviceOption?.value || ''
)
}
defaultValue={path.ServiceName}
size="sm"
/>
</div>
</InputGroup>
{errors[
`hosts[${hostIndex}].paths[${pathIndex}].servicename`
] && (
@ -449,35 +503,41 @@ export function IngressForm({
<div className="form-group col-sm-2 col-xl-2 !m-0 !pl-0">
{servicePorts && (
<>
<div className="input-group input-group-sm">
<span className="input-group-addon required">
<InputGroup size="small">
<InputGroup.Addon required>
Service port
</span>
</InputGroup.Addon>
<Select
key={servicePorts.toString() + path.ServicePort}
name={`ingress_servicePort_${hostIndex}_${pathIndex}`}
options={
path.ServiceName &&
servicePorts[path.ServiceName]
? servicePorts[path.ServiceName]
: [
{
label: 'Select port',
value: '',
},
]
servicePorts[path.ServiceName]?.map(
(portOption) => ({
...portOption,
value: portOption.value.toString(),
})
) || []
}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
onChange={(option) =>
handlePathChange(
hostIndex,
pathIndex,
'ServicePort',
e.target.value
option?.value || ''
)
}
defaultValue={path.ServicePort}
value={{
label: (
path.ServicePort || 'Select a port'
).toString(),
value:
rule.Hosts[hostIndex].Paths[
pathIndex
].ServicePort.toString(),
}}
size="sm"
/>
</div>
</InputGroup>
{errors[
`hosts[${hostIndex}].paths[${pathIndex}].serviceport`
] && (
@ -494,30 +554,32 @@ export function IngressForm({
</div>
<div className="form-group col-sm-3 col-xl-2 !m-0 !pl-0">
<div className="input-group input-group-sm">
<span className="input-group-addon">Path type</span>
<Select
<InputGroup size="small">
<InputGroup.Addon>Path type</InputGroup.Addon>
<Select<Option<string>>
key={servicePorts.toString() + path.PathType}
name={`ingress_pathType_${hostIndex}_${pathIndex}`}
options={
pathTypes
? pathTypes.map((type) => ({
label: type,
value: type,
}))
: []
pathTypes?.map((type) => ({
label: type,
value: type,
})) || []
}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
onChange={(option) =>
handlePathChange(
hostIndex,
pathIndex,
'PathType',
e.target.value
option?.value || ''
)
}
defaultValue={path.PathType}
value={{
label: path.PathType || 'Select a path type',
value: path.PathType || '',
}}
size="sm"
/>
</div>
</InputGroup>
{errors[
`hosts[${hostIndex}].paths[${pathIndex}].pathType`
] && (
@ -532,9 +594,9 @@ export function IngressForm({
</div>
<div className="form-group col-sm-3 col-xl-3 !m-0 !pl-0">
<div className="input-group input-group-sm">
<span className="input-group-addon required">Path</span>
<input
<InputGroup size="small">
<InputGroup.Addon required>Path</InputGroup.Addon>
<InputGroup.Input
className="form-control"
name={`ingress_route_${hostIndex}-${pathIndex}`}
placeholder="/example"
@ -550,7 +612,7 @@ export function IngressForm({
)
}
/>
</div>
</InputGroup>
{errors[
`hosts[${hostIndex}].paths[${pathIndex}].path`
] && (
@ -592,7 +654,7 @@ export function IngressForm({
</Button>
</div>
</div>
</div>
</Card>
))}
{namespace && (