2022-09-21 04:49:42 +00:00
import { ChangeEvent , ReactNode } from 'react' ;
2022-11-28 02:00:28 +00:00
import { Info , Plus , RefreshCw , Trash2 } from 'lucide-react' ;
import Route from '@/assets/ico/route.svg?c' ;
2022-09-21 04:49:42 +00:00
import { Link } from '@@/Link' ;
import { Icon } from '@@/Icon' ;
import { Select , 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' ;
2022-11-07 06:03:11 +00:00
import { Annotations } from './Annotations' ;
2022-09-21 04:49:42 +00:00
import { Rule , ServicePorts } from './types' ;
import '../style.css' ;
const PathTypes : Record < string , string [ ] > = {
nginx : [ 'ImplementationSpecific' , 'Prefix' , 'Exact' ] ,
traefik : [ 'Prefix' , 'Exact' ] ,
other : [ 'Prefix' , 'Exact' ] ,
} ;
const PlaceholderAnnotations : Record < string , string [ ] > = {
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 : Record < string , ReactNode > ;
isLoading : boolean ;
isEdit : boolean ;
namespace : string ;
servicePorts : ServicePorts ;
ingressClassOptions : Option < string > [ ] ;
serviceOptions : Option < string > [ ] ;
tlsOptions : Option < string > [ ] ;
namespacesOptions : Option < string > [ ] ;
removeIngressRoute : ( hostIndex : number , pathIndex : number ) = > void ;
removeIngressHost : ( hostIndex : number ) = > void ;
removeAnnotation : ( index : number ) = > void ;
addNewIngressHost : ( noHost? : boolean ) = > void ;
addNewIngressRoute : ( hostIndex : number ) = > void ;
2022-10-24 20:41:30 +00:00
addNewAnnotation : ( type ? : 'rewrite' | 'regex' | 'ingressClass' ) = > void ;
2022-09-21 04:49:42 +00:00
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 ,
isLoading ,
isEdit ,
servicePorts ,
tlsOptions ,
handleTLSChange ,
addNewIngressHost ,
serviceOptions ,
handleHostChange ,
handleIngressChange ,
handlePathChange ,
addNewIngressRoute ,
removeIngressRoute ,
removeIngressHost ,
addNewAnnotation ,
removeAnnotation ,
reloadTLSCerts ,
handleAnnotationChange ,
ingressClassOptions ,
errors ,
namespacesOptions ,
handleNamespaceChange ,
namespace ,
} : Props ) {
if ( isLoading ) {
return < div > Loading . . . < / div > ;
}
const hasNoHostRule = rule . Hosts ? . some ( ( host ) = > host . NoHost ) ;
const placeholderAnnotation =
2022-10-05 02:17:53 +00:00
PlaceholderAnnotations [ rule . IngressType || 'other' ] ||
PlaceholderAnnotations . other ;
const pathTypes = PathTypes [ rule . IngressType || 'other' ] || PathTypes . other ;
2022-09-21 04:49:42 +00:00
return (
< Widget >
2022-11-28 02:00:28 +00:00
< WidgetTitle icon = { Route } title = "Ingress" / >
2022-09-21 04:49:42 +00:00
< WidgetBody key = { rule . Key + rule . Namespace } >
< div className = "row" >
< div className = "form-horizontal" >
< div className = "form-group" >
< label
className = "control-label text-muted col-sm-3 col-lg-2 required"
htmlFor = "namespace"
>
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 >
< / div >
< / div >
< / div >
{ namespace && (
< div className = "row" >
< div className = "form-horizontal" >
< div className = "form-group" >
< label
className = "control-label text-muted col-sm-3 col-lg-2 required"
htmlFor = "ingress_name"
>
Ingress name
< / label >
< div className = "col-sm-4" >
{ isEdit ? (
rule . IngressName
) : (
< input
name = "ingress_name"
type = "text"
className = "form-control"
placeholder = "Ingress name"
defaultValue = { rule . IngressName }
onChange = { ( e : ChangeEvent < HTMLInputElement > ) = >
handleIngressChange ( 'IngressName' , e . target . value )
}
disabled = { isEdit }
/ >
) }
{ errors . ingressName && ! isEdit && (
2023-02-12 21:04:24 +00:00
< FormError className = "error-inline mt-1" >
2022-09-21 04:49:42 +00:00
{ errors . ingressName }
< / FormError >
) }
< / div >
< / div >
2022-10-05 02:17:53 +00:00
< div className = "form-group" key = { ingressClassOptions . toString ( ) } >
2022-09-21 04:49:42 +00:00
< label
className = "control-label text-muted col-sm-3 col-lg-2 required"
htmlFor = "ingress_class"
>
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 }
/ >
{ errors . className && (
2023-02-12 21:04:24 +00:00
< FormError className = "error-inline mt-1" >
2022-09-21 04:49:42 +00:00
{ errors . className }
< / FormError >
) }
< / div >
< / div >
< / div >
2023-02-12 21:04:24 +00:00
< div className = "col-sm-12 text-muted !mb-0 px-0" >
2022-09-21 04:49:42 +00:00
< div className = "mb-2" > Annotations < / div >
< p className = "vertical-center text-muted small" >
2022-11-28 02:00:28 +00:00
< Icon icon = { Info } mode = "primary" / >
2022-09-21 04:49:42 +00:00
< span >
You can specify { ' ' }
< a
href = "https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/"
target = "_black"
>
annotations
< / a > { ' ' }
for the object . See further Kubernetes documentation on { ' ' }
< a
href = "https://kubernetes.io/docs/reference/labels-annotations-taints/"
target = "_black"
>
well - known annotations
< / a >
.
< / span >
< / p >
< / div >
{ rule ? . Annotations && (
< Annotations
placeholder = { placeholderAnnotation }
annotations = { rule . Annotations }
handleAnnotationChange = { handleAnnotationChange }
removeAnnotation = { removeAnnotation }
errors = { errors }
/ >
) }
2023-02-12 21:04:24 +00:00
< div className = "col-sm-12 anntation-actions p-0" >
2022-09-21 04:49:42 +00:00
< Button
className = "btn btn-sm btn-light mb-2 !ml-0"
onClick = { ( ) = > addNewAnnotation ( ) }
icon = { Plus }
title = "Use annotations to configure options for an ingress. Review Nginx or Traefik documentation to find the annotations supported by your choice of ingress type."
>
{ ' ' }
add annotation
< / Button >
{ rule . IngressType === 'nginx' && (
< >
< Button
className = "btn btn-sm btn-light mb-2 ml-2"
onClick = { ( ) = > addNewAnnotation ( 'rewrite' ) }
icon = { Plus }
title = "When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
2022-10-24 20:41:30 +00:00
data - cy = "add-rewrite-annotation"
2022-09-21 04:49:42 +00:00
>
{ ' ' }
2022-10-24 20:41:30 +00:00
Add rewrite annotation
2022-09-21 04:49:42 +00:00
< / Button >
< Button
className = "btn btn-sm btn-light mb-2 ml-2"
onClick = { ( ) = > addNewAnnotation ( 'regex' ) }
icon = { Plus }
title = "When the exposed URLs for your applications differ from the specified paths in the ingress, use the rewrite target annotation to denote the path to redirect to."
2022-10-24 20:41:30 +00:00
data - cy = "add-regex-annotation"
2022-09-21 04:49:42 +00:00
>
2022-10-24 20:41:30 +00:00
Add regular expression annotation
2022-09-21 04:49:42 +00:00
< / Button >
< / >
) }
2022-10-24 20:41:30 +00:00
{ rule . IngressType === 'custom' && (
< Button
className = "btn btn-sm btn-light mb-2 ml-2"
onClick = { ( ) = > addNewAnnotation ( 'ingressClass' ) }
icon = { Plus }
data - cy = "add-ingress-class-annotation"
>
Add kubernetes . io / ingress . class annotation
< / Button >
) }
2022-09-21 04:49:42 +00:00
< / div >
2023-02-12 21:04:24 +00:00
< div className = "col-sm-12 text-muted px-0" > Rules < / div >
2022-09-21 04:49:42 +00:00
< / div >
) }
{ namespace &&
rule ? . Hosts ? . map ( ( host , hostIndex ) = > (
2023-02-12 21:04:24 +00:00
< div className = "row rule bordered mb-5" key = { host . Key } >
2022-09-21 04:49:42 +00:00
< div className = "col-sm-12" >
2023-02-12 21:04:24 +00:00
< div className = "row rule-actions mt-5" >
2022-09-21 04:49:42 +00:00
< div className = "col-sm-3 p-0" >
{ ! host . NoHost ? 'Rule' : 'Fallback rule' }
< / div >
< div className = "col-sm-9 p-0 text-right" >
{ ! host . NoHost && (
< Button
className = "btn btn-light btn-sm"
onClick = { ( ) = > reloadTLSCerts ( ) }
icon = { RefreshCw }
>
Reload TLS secrets
< / Button >
) }
< Button
2022-10-07 03:55:11 +00:00
className = "btn btn-sm ml-2"
color = "dangerlight"
2022-09-21 04:49:42 +00:00
type = "button"
data - cy = { ` k8sAppCreate-rmHostButton_ ${ hostIndex } ` }
onClick = { ( ) = > removeIngressHost ( hostIndex ) }
disabled = { rule . Hosts . length === 1 }
icon = { Trash2 }
>
Remove rule
< / Button >
< / div >
< / div >
{ ! host . NoHost && (
< div className = "row" >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-6 col-lg-4 !pl-0 !pr-2" >
2022-09-21 04:49:42 +00:00
< div className = "input-group input-group-sm" >
< span className = "input-group-addon required" >
Hostname
< / span >
< input
name = { ` ingress_host_ ${ hostIndex } ` }
type = "text"
className = "form-control form-control-sm"
placeholder = "e.g. example.com"
defaultValue = { host . Host }
onChange = { ( e : ChangeEvent < HTMLInputElement > ) = >
handleHostChange ( hostIndex , e . target . value )
}
/ >
< / div >
{ errors [ ` hosts[ ${ hostIndex } ].host ` ] && (
< FormError className = "mt-1 !mb-0" >
{ errors [ ` hosts[ ${ hostIndex } ].host ` ] }
< / FormError >
) }
< / div >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-6 col-lg-4 !pr-0 !pl-2" >
2022-09-21 04:49:42 +00:00
< div className = "input-group input-group-sm" >
< span className = "input-group-addon" > TLS secret < / span >
< Select
key = { tlsOptions . toString ( ) + host . Secret }
name = { ` ingress_tls_ ${ hostIndex } ` }
options = { tlsOptions }
onChange = { ( e : ChangeEvent < HTMLSelectElement > ) = >
handleTLSChange ( hostIndex , e . target . value )
}
defaultValue = { host . Secret }
/ >
< / div >
< / div >
2023-02-12 21:04:24 +00:00
< p className = "vertical-center text-muted small col-sm-12 whitespace-nowrap !p-0" >
2022-11-28 02:00:28 +00:00
< Icon icon = { Info } mode = "primary" size = "md" / >
2022-09-21 04:49:42 +00:00
< span >
Add a secret via { ' ' }
< Link
to = "kubernetes.configurations"
params = { { id : environmentID } }
className = "text-primary"
target = "_blank"
>
ConfigMaps & amp ; Secrets
< / Link >
{ ', ' }
then select & apos ; Reload TLS secrets & apos ; above to
populate the dropdown with your changes .
< / span >
< / p >
< / div >
) }
{ host . NoHost && (
2023-02-12 21:04:24 +00:00
< p className = "vertical-center text-muted small col-sm-12 whitespace-nowrap !p-0" >
2022-11-28 02:00:28 +00:00
< Icon icon = { Info } mode = "primary" size = "md" / > 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 .
2022-09-21 04:49:42 +00:00
< / p >
) }
< div className = "row" >
2023-02-12 21:04:24 +00:00
< div className = "col-sm-12 text-muted !mb-0 mt-2 px-0" >
2022-09-21 04:49:42 +00:00
Paths
< / div >
< / div >
{ host . Paths . map ( ( path , pathIndex ) = > (
< div
2023-02-12 21:04:24 +00:00
className = "row path mt-5 !mb-5"
2022-09-21 04:49:42 +00:00
key = { ` path_ ${ path . Key } } ` }
>
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-3 col-xl-2 !m-0 !pl-0" >
2022-09-21 04:49:42 +00:00
< div className = "input-group input-group-sm" >
< span className = "input-group-addon required" >
Service
< / span >
< Select
key = { serviceOptions . toString ( ) + path . ServiceName }
name = { ` ingress_service_ ${ hostIndex } _ ${ pathIndex } ` }
options = { serviceOptions }
onChange = { ( e : ChangeEvent < HTMLSelectElement > ) = >
handlePathChange (
hostIndex ,
pathIndex ,
'ServiceName' ,
e . target . value
)
}
defaultValue = { path . ServiceName }
/ >
< / div >
{ errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].servicename `
] && (
2023-02-12 21:04:24 +00:00
< FormError className = "error-inline mt-1 !mb-0" >
2022-09-21 04:49:42 +00:00
{
errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].servicename `
]
}
< / FormError >
) }
< / div >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-2 col-xl-2 !m-0 !pl-0" >
2022-09-21 04:49:42 +00:00
{ servicePorts && (
< >
< div className = "input-group input-group-sm" >
< span className = "input-group-addon required" >
Service port
< / span >
< Select
key = { servicePorts . toString ( ) + path . ServicePort }
name = { ` ingress_servicePort_ ${ hostIndex } _ ${ pathIndex } ` }
options = {
path . ServiceName &&
servicePorts [ path . ServiceName ]
? servicePorts [ path . ServiceName ]
: [
{
label : 'Select port' ,
value : '' ,
} ,
]
}
onChange = { ( e : ChangeEvent < HTMLSelectElement > ) = >
handlePathChange (
hostIndex ,
pathIndex ,
'ServicePort' ,
e . target . value
)
}
defaultValue = { path . ServicePort }
/ >
< / div >
{ errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].serviceport `
] && (
< FormError className = "mt-1 !mb-0" >
{
errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].serviceport `
]
}
< / FormError >
) }
< / >
) }
< / div >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-3 col-xl-2 !m-0 !pl-0" >
2022-09-21 04:49:42 +00:00
< div className = "input-group input-group-sm" >
< span className = "input-group-addon required" >
Path type
< / span >
< Select
key = { servicePorts . toString ( ) + path . PathType }
name = { ` ingress_pathType_ ${ hostIndex } _ ${ pathIndex } ` }
options = {
pathTypes
? pathTypes . map ( ( type ) = > ( {
label : type ,
value : type ,
} ) )
: [ ]
}
onChange = { ( e : ChangeEvent < HTMLSelectElement > ) = >
handlePathChange (
hostIndex ,
pathIndex ,
'PathType' ,
e . target . value
)
}
defaultValue = { path . PathType }
/ >
< / div >
{ errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].pathType `
] && (
< FormError className = "mt-1 !mb-0" >
{
errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].pathType `
]
}
< / FormError >
) }
< / div >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-3 col-xl-3 !m-0 !pl-0" >
2022-09-21 04:49:42 +00:00
< div className = "input-group input-group-sm" >
< span className = "input-group-addon required" > Path < / span >
< input
className = "form-control"
name = { ` ingress_route_ ${ hostIndex } - ${ pathIndex } ` }
placeholder = "/example"
data - pattern = "/^(\/?[a-zA-Z0-9]+([a-zA-Z0-9-/_]*[a-zA-Z0-9])?|[a-zA-Z0-9]+)|(\/){1}$/"
data - cy = { ` k8sAppCreate-route_ ${ hostIndex } - ${ pathIndex } ` }
defaultValue = { path . Route }
onChange = { ( e : ChangeEvent < HTMLInputElement > ) = >
handlePathChange (
hostIndex ,
pathIndex ,
'Route' ,
e . target . value
)
}
/ >
< / div >
{ errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].path `
] && (
< FormError className = "mt-1 !mb-0" >
{
errors [
` hosts[ ${ hostIndex } ].paths[ ${ pathIndex } ].path `
]
}
< / FormError >
) }
< / div >
2023-02-12 21:04:24 +00:00
< div className = "form-group col-sm-1 !m-0 !pl-0" >
2022-09-21 04:49:42 +00:00
< Button
2023-02-12 21:04:24 +00:00
className = "btn btn-sm btn-only-icon vertical-center !ml-0"
2022-10-07 03:55:11 +00:00
color = "dangerlight"
2022-09-21 04:49:42 +00:00
type = "button"
data - cy = { ` k8sAppCreate-rmPortButton_ ${ hostIndex } - ${ pathIndex } ` }
onClick = { ( ) = > removeIngressRoute ( hostIndex , pathIndex ) }
disabled = { host . Paths . length === 1 }
icon = { Trash2 }
/ >
< / div >
< / div >
) ) }
< div className = "row mt-5" >
< Button
className = "btn btn-sm btn-light !ml-0"
type = "button"
onClick = { ( ) = > addNewIngressRoute ( hostIndex ) }
icon = { Plus }
>
Add path
< / Button >
< / div >
< / div >
< / div >
) ) }
{ namespace && (
2023-02-12 21:04:24 +00:00
< div className = "row rules-action p-0" >
< div className = "col-sm-12 vertical-center p-0" >
2022-09-21 04:49:42 +00:00
< Button
className = "btn btn-sm btn-light !ml-0"
type = "button"
onClick = { ( ) = > addNewIngressHost ( ) }
icon = { Plus }
>
Add new host
< / Button >
< Button
className = "btn btn-sm btn-light ml-2"
type = "button"
onClick = { ( ) = > addNewIngressHost ( true ) }
disabled = { hasNoHostRule }
icon = { Plus }
>
Add fallback rule
< / Button >
< Tooltip message = "A fallback rule will be applied to all requests that do not match any of the defined hosts." / >
< / div >
< / div >
) }
< / WidgetBody >
< / Widget >
) ;
}