import { ChangeEvent, useEffect } from 'react'; import { Plus, RefreshCw, Trash2 } from 'lucide-react'; import Route from '@/assets/ico/route.svg?c'; import { Link } from '@@/Link'; 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 { InlineLoader } from '@@/InlineLoader'; import { Select } from '@@/form-components/ReactSelect'; import { Card } from '@@/Card'; import { InputGroup } from '@@/form-components/InputGroup'; import { AnnotationsForm } from '../../annotations/AnnotationsForm'; import { GroupedServiceOptions, IngressErrors, Rule, ServicePorts, } from './types'; import '../style.css'; const PathTypes: Record = { nginx: ['ImplementationSpecific', 'Prefix', 'Exact'], traefik: ['Prefix', 'Exact'], other: ['Prefix', 'Exact'], }; const PlaceholderAnnotations: Record = { nginx: ['e.g. nginx.ingress.kubernetes.io/rewrite-target', '/$1'], traefik: ['e.g. traefik.ingress.kubernetes.io/router.tls', 'true'], other: ['e.g. app.kubernetes.io/name', 'examplename'], }; interface Props { environmentID: number; rule: Rule; errors: IngressErrors; isEdit: boolean; namespace: string; servicePorts: ServicePorts; ingressClassOptions: Option[]; isIngressClassOptionsLoading: boolean; serviceOptions: GroupedServiceOptions; tlsOptions: Option[]; namespacesOptions: Option[]; isNamespaceOptionsLoading: boolean; isIngressNamesLoading: boolean; removeIngressRoute: (hostIndex: number, pathIndex: number) => void; removeIngressHost: (hostIndex: number) => void; removeAnnotation: (index: number) => void; addNewIngressHost: (noHost?: boolean) => void; addNewIngressRoute: (hostIndex: number) => void; addNewAnnotation: (type?: 'rewrite' | 'regex' | 'ingressClass') => void; handleNamespaceChange: (val: string) => void; handleHostChange: (hostIndex: number, val: string) => void; handleTLSChange: (hostIndex: number, tls: string) => void; handleIngressChange: ( key: 'IngressName' | 'IngressClassName', value: string ) => void; handleAnnotationChange: ( index: number, key: 'Key' | 'Value', val: string ) => void; handlePathChange: ( hostIndex: number, pathIndex: number, key: 'Route' | 'PathType' | 'ServiceName' | 'ServicePort', val: string ) => void; reloadTLSCerts: () => void; } export function IngressForm({ environmentID, rule, isEdit, servicePorts, tlsOptions, handleTLSChange, addNewIngressHost, serviceOptions, handleHostChange, handleIngressChange, handlePathChange, addNewIngressRoute, removeIngressRoute, removeIngressHost, addNewAnnotation, removeAnnotation, reloadTLSCerts, handleAnnotationChange, ingressClassOptions, isIngressClassOptionsLoading, errors, namespacesOptions, isNamespaceOptionsLoading, isIngressNamesLoading, handleNamespaceChange, namespace, }: Props) { 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 ( !isEdit && !namespaces.includes(namespace) && namespaces.length > 0 && !isIngressNamesLoading ) { handleNamespaceChange(namespaces[0]); } }, [ namespacesOptions, namespace, handleNamespaceChange, isNamespaceOptionsLoading, isEdit, isIngressNamesLoading, ]); return (
{isNamespaceOptionsLoading && (
Loading namespaces...
)} {!isNamespaceOptionsLoading && (
{isEdit ? ( namespace ) : ( ) => handleIngressChange('IngressName', e.target.value) } disabled={isEdit} /> )} {errors.ingressName && !isEdit && ( {errors.ingressName} )}
{isIngressClassOptionsLoading && ( Loading ingress classes... )} {!isIngressClassOptionsLoading && ( <> handleTLSChange(hostIndex, TLSOption?.value || '') } placeholder={ tlsOptions.length ? 'Select a TLS secret' : 'No TLS secrets available' } noOptionsMessage={() => 'No TLS secrets available'} size="sm" /> {!host.NoHost && (
)}
You may also use the{' '} Create secret {' '} function, and reload the dropdown.
)} {host.NoHost && ( A fallback rule has no host specified. This rule only applies when an inbound request has a hostname that does not match with any of your other rules. )}
Paths
{!host.Paths.length && ( You may save the ingress without a path and it will then be an ingress default that a user may select via the hostname dropdown in Create/Edit application. )} {host.Paths.map((path, pathIndex) => (
Service ({ ...portOption, value: portOption.value.toString(), }) ) || [] } onChange={(option) => handlePathChange( hostIndex, pathIndex, 'ServicePort', option?.value || '' ) } value={ path.ServicePort ? { label: path.ServicePort.toString(), value: path.ServicePort.toString(), } : null } placeholder={ servicePorts[path.ServiceName]?.length ? 'Select a port' : 'No ports available' } noOptionsMessage={() => 'No ports available'} size="sm" /> {errors[ `hosts[${hostIndex}].paths[${pathIndex}].serviceport` ] && ( { errors[ `hosts[${hostIndex}].paths[${pathIndex}].serviceport` ] } )} )}
Path type