2023-03-01 00:11:12 +00:00
import { useState , useEffect , useMemo , ReactNode , useCallback } from 'react' ;
2022-09-21 04:49:42 +00:00
import { useCurrentStateAndParams , useRouter } from '@uirouter/react' ;
import { v4 as uuidv4 } from 'uuid' ;
2023-03-01 00:11:12 +00:00
import { debounce } from 'lodash' ;
2022-09-21 04:49:42 +00:00
2022-11-13 08:10:18 +00:00
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId' ;
2022-09-21 04:49:42 +00:00
import { useConfigurations } from '@/react/kubernetes/configs/queries' ;
import { useNamespaces } from '@/react/kubernetes/namespaces/queries' ;
2022-11-07 06:03:11 +00:00
import { useServices } from '@/react/kubernetes/networks/services/queries' ;
2022-12-04 21:47:56 +00:00
import { notifySuccess , notifyError } from '@/portainer/services/notifications' ;
import { useAuthorizations } from '@/react/hooks/useUser' ;
2022-09-21 04:49:42 +00:00
import { Link } from '@@/Link' ;
import { PageHeader } from '@@/PageHeader' ;
import { Option } from '@@/form-components/Input/Select' ;
import { Button } from '@@/buttons' ;
2022-10-05 02:17:53 +00:00
import { Ingress , IngressController } from '../types' ;
2022-09-21 04:49:42 +00:00
import {
useCreateIngress ,
useIngresses ,
useUpdateIngress ,
useIngressControllers ,
} from '../queries' ;
2022-11-07 06:03:11 +00:00
import { Annotation } from './Annotations/types' ;
2022-09-21 04:49:42 +00:00
import { Rule , Path , Host } from './types' ;
import { IngressForm } from './IngressForm' ;
import {
prepareTLS ,
preparePaths ,
prepareAnnotations ,
prepareRuleFromIngress ,
checkIfPathExistsWithHost ,
} from './utils' ;
export function CreateIngressView() {
const environmentId = useEnvironmentId ( ) ;
const { params } = useCurrentStateAndParams ( ) ;
2022-12-04 21:47:56 +00:00
const isAuthorisedToAddEdit = useAuthorizations ( [ 'K8sIngressesW' ] ) ;
2022-09-21 04:49:42 +00:00
const router = useRouter ( ) ;
const isEdit = ! ! params . namespace ;
2022-12-04 21:47:56 +00:00
useEffect ( ( ) = > {
if ( ! isAuthorisedToAddEdit ) {
const message = ` Not authorized to ${ isEdit ? 'edit' : 'add' } ingresses ` ;
notifyError ( 'Error' , new Error ( message ) ) ;
router . stateService . go ( 'kubernetes.ingresses' ) ;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ isAuthorisedToAddEdit , isEdit ] ) ;
2022-09-21 04:49:42 +00:00
const [ namespace , setNamespace ] = useState < string > ( params . namespace || '' ) ;
const [ ingressRule , setIngressRule ] = useState < Rule > ( { } as Rule ) ;
const [ errors , setErrors ] = useState < Record < string , ReactNode > > (
{ } as Record < string , string >
) ;
const namespacesResults = useNamespaces ( environmentId ) ;
const servicesResults = useServices ( environmentId , namespace ) ;
const configResults = useConfigurations ( environmentId , namespace ) ;
const ingressesResults = useIngresses (
environmentId ,
namespacesResults . data ? Object . keys ( namespacesResults ? . data || { } ) : [ ]
) ;
const ingressControllersResults = useIngressControllers (
environmentId ,
namespace
) ;
const createIngressMutation = useCreateIngress ( ) ;
const updateIngressMutation = useUpdateIngress ( ) ;
const isLoading =
( servicesResults . isLoading &&
configResults . isLoading &&
namespacesResults . isLoading &&
2022-10-05 02:17:53 +00:00
ingressesResults . isLoading &&
ingressControllersResults . isLoading ) ||
2022-09-21 04:49:42 +00:00
( isEdit && ! ingressRule . IngressName ) ;
const [ ingressNames , ingresses , ruleCounterByNamespace , hostWithTLS ] =
useMemo ( ( ) : [
string [ ] ,
Ingress [ ] ,
Record < string , number > ,
Record < string , string >
] = > {
const ruleCounterByNamespace : Record < string , number > = { } ;
const hostWithTLS : Record < string , string > = { } ;
ingressesResults . data ? . forEach ( ( ingress ) = > {
ingress . TLS ? . forEach ( ( tls ) = > {
tls . Hosts . forEach ( ( host ) = > {
hostWithTLS [ host ] = tls . SecretName ;
} ) ;
} ) ;
} ) ;
const ingressNames : string [ ] = [ ] ;
ingressesResults . data ? . forEach ( ( ing ) = > {
ruleCounterByNamespace [ ing . Namespace ] =
ruleCounterByNamespace [ ing . Namespace ] || 0 ;
const n = ing . Name . match ( /^(.*)-(\d+)$/ ) ;
if ( n ? . length === 3 ) {
ruleCounterByNamespace [ ing . Namespace ] = Math . max (
ruleCounterByNamespace [ ing . Namespace ] ,
Number ( n [ 2 ] )
) ;
}
if ( ing . Namespace === namespace ) {
ingressNames . push ( ing . Name ) ;
}
} ) ;
return [
ingressNames || [ ] ,
ingressesResults . data || [ ] ,
ruleCounterByNamespace ,
hostWithTLS ,
] ;
} , [ ingressesResults . data , namespace ] ) ;
const namespacesOptions : Option < string > [ ] = [
{ label : 'Select a namespace' , value : '' } ,
] ;
Object . entries ( namespacesResults ? . data || { } ) . forEach ( ( [ ns , val ] ) = > {
if ( ! val . IsSystem ) {
namespacesOptions . push ( {
label : ns ,
value : ns ,
} ) ;
}
} ) ;
const clusterIpServices = useMemo (
( ) = > servicesResults . data ? . filter ( ( s ) = > s . Type === 'ClusterIP' ) ,
[ servicesResults . data ]
) ;
const servicesOptions = useMemo (
( ) = >
clusterIpServices ? . map ( ( service ) = > ( {
label : service.Name ,
value : service.Name ,
} ) ) ,
[ clusterIpServices ]
) ;
const serviceOptions = [
{ label : 'Select a service' , value : '' } ,
. . . ( servicesOptions || [ ] ) ,
] ;
2022-10-07 03:55:11 +00:00
const servicePorts = useMemo (
( ) = >
clusterIpServices
? Object . fromEntries (
clusterIpServices ? . map ( ( service ) = > [
service . Name ,
service . Ports . map ( ( port ) = > ( {
label : String ( port . Port ) ,
value : String ( port . Port ) ,
} ) ) ,
] )
)
: { } ,
[ clusterIpServices ]
) ;
2022-09-21 04:49:42 +00:00
const existingIngressClass = useMemo (
( ) = >
ingressControllersResults . data ? . find (
2022-10-24 20:41:30 +00:00
( i ) = >
i . ClassName === ingressRule . IngressClassName ||
( i . Type === 'custom' && ingressRule . IngressClassName === '' )
2022-09-21 04:49:42 +00:00
) ,
[ ingressControllersResults . data , ingressRule . IngressClassName ]
) ;
const ingressClassOptions : Option < string > [ ] = [
{ label : 'Select an ingress class' , value : '' } ,
2022-09-26 19:43:24 +00:00
. . . ( ingressControllersResults . data
? . filter ( ( cls ) = > cls . Availability )
. map ( ( cls ) = > ( {
label : cls.ClassName ,
value : cls.ClassName ,
} ) ) || [ ] ) ,
2022-09-21 04:49:42 +00:00
] ;
2022-10-05 02:17:53 +00:00
if (
( ! existingIngressClass ||
( existingIngressClass && ! existingIngressClass . Availability ) ) &&
ingressRule . IngressClassName &&
2022-10-16 21:44:17 +00:00
! ingressControllersResults . isLoading
2022-10-05 02:17:53 +00:00
) {
2022-10-24 20:41:30 +00:00
const optionLabel = ! ingressRule . IngressType
? ` ${ ingressRule . IngressClassName } - NOT FOUND `
: ` ${ ingressRule . IngressClassName } - DISALLOWED ` ;
2022-09-21 04:49:42 +00:00
ingressClassOptions . push ( {
2022-10-24 20:41:30 +00:00
label : optionLabel ,
2022-09-21 04:49:42 +00:00
value : ingressRule.IngressClassName ,
} ) ;
}
const matchedConfigs = configResults ? . data ? . filter (
( config ) = >
config . SecretType === 'kubernetes.io/tls' &&
config . Namespace === namespace
) ;
2022-10-09 20:32:30 +00:00
const tlsOptions : Option < string > [ ] = useMemo (
( ) = > [
{ label : 'No TLS' , value : '' } ,
. . . ( matchedConfigs ? . map ( ( config ) = > ( {
label : config.Name ,
value : config.Name ,
} ) ) || [ ] ) ,
] ,
[ matchedConfigs ]
) ;
2022-09-21 04:49:42 +00:00
useEffect ( ( ) = > {
2022-10-05 02:17:53 +00:00
if (
! ! params . name &&
ingressesResults . data &&
! ingressRule . IngressName &&
2022-10-24 20:41:30 +00:00
! ingressControllersResults . isLoading &&
2022-10-16 21:44:17 +00:00
! ingressControllersResults . isLoading
2022-10-05 02:17:53 +00:00
) {
2022-09-21 04:49:42 +00:00
// if it is an edit screen, prepare the rule from the ingress
const ing = ingressesResults . data ? . find (
( ing ) = > ing . Name === params . name && ing . Namespace === params . namespace
) ;
if ( ing ) {
const type = ingressControllersResults . data ? . find (
2022-10-24 20:41:30 +00:00
( c ) = >
c . ClassName === ing . ClassName ||
( c . Type === 'custom' && ! ing . ClassName )
2022-09-21 04:49:42 +00:00
) ? . Type ;
2022-10-24 20:41:30 +00:00
const r = prepareRuleFromIngress ( ing , type ) ;
2022-10-05 02:17:53 +00:00
r . IngressType = type || r . IngressType ;
2022-09-21 04:49:42 +00:00
setIngressRule ( r ) ;
}
}
2022-10-16 21:44:17 +00:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2022-09-21 04:49:42 +00:00
} , [
params . name ,
ingressesResults . data ,
ingressControllersResults . data ,
ingressRule . IngressName ,
params . namespace ,
] ) ;
2022-10-09 20:32:30 +00:00
useEffect ( ( ) = > {
// for each host, if the tls selection doesn't exist as an option, change it to the first option
if ( ingressRule ? . Hosts ? . length ) {
ingressRule . Hosts . forEach ( ( host , hIndex ) = > {
const secret = host . Secret || '' ;
const tlsOptionVals = tlsOptions . map ( ( o ) = > o . value ) ;
if ( tlsOptions ? . length && ! tlsOptionVals ? . includes ( secret ) ) {
handleTLSChange ( hIndex , tlsOptionVals [ 0 ] ) ;
}
} ) ;
}
} , [ tlsOptions , ingressRule . Hosts ] ) ;
2022-10-07 03:55:11 +00:00
useEffect ( ( ) = > {
// for each path in each host, if the service port doesn't exist as an option, change it to the first option
if ( ingressRule ? . Hosts ? . length ) {
ingressRule . Hosts . forEach ( ( host , hIndex ) = > {
host ? . Paths ? . forEach ( ( path , pIndex ) = > {
const serviceName = path . ServiceName ;
const currentServicePorts = servicePorts [ serviceName ] ? . map (
( p ) = > p . value
) ;
if (
currentServicePorts ? . length &&
! currentServicePorts ? . includes ( String ( path . ServicePort ) )
) {
handlePathChange (
hIndex ,
pIndex ,
'ServicePort' ,
currentServicePorts [ 0 ]
) ;
}
} ) ;
} ) ;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
} , [ ingressRule , servicePorts ] ) ;
2023-03-01 00:11:12 +00:00
const validate = useCallback (
(
ingressRule : Rule ,
ingressNames : string [ ] ,
serviceOptions : Option < string > [ ] ,
existingIngressClass? : IngressController
) = > {
const errors : Record < string , ReactNode > = { } ;
const rule = { . . . ingressRule } ;
// User cannot edit the namespace and the ingress name
if ( ! isEdit ) {
if ( ! rule . Namespace ) {
errors . namespace = 'Namespace is required' ;
}
const nameRegex = /^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/ ;
if ( ! rule . IngressName ) {
errors . ingressName = 'Ingress name is required' ;
} else if ( ! nameRegex . test ( rule . IngressName ) ) {
errors . ingressName =
"This field must consist of lower case alphanumeric characters or '-', contain at most 63 characters, start with an alphabetic character, and end with an alphanumeric character (e.g. 'my-name', or 'abc-123')." ;
} else if ( ingressNames . includes ( rule . IngressName ) ) {
errors . ingressName = 'Ingress name already exists' ;
}
if ( ! rule . IngressClassName ) {
errors . className = 'Ingress class is required' ;
}
}
if ( isEdit && ! ingressRule . IngressClassName ) {
errors . className =
'No ingress class is currently set for this ingress - use of the Portainer UI requires one to be set.' ;
}
if (
isEdit &&
( ! existingIngressClass ||
( existingIngressClass && ! existingIngressClass . Availability ) ) &&
ingressRule . IngressClassName
) {
if ( ! rule . IngressType ) {
errors . className =
'Currently set to an ingress class that cannot be found in the cluster - you must select a valid class.' ;
} else {
errors . className =
'Currently set to an ingress class that you do not have access to - you must select a valid class.' ;
}
}
const duplicatedAnnotations : string [ ] = [ ] ;
2023-04-26 04:48:55 +00:00
const re = /^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$/ ;
2023-03-01 00:11:12 +00:00
rule . Annotations ? . forEach ( ( a , i ) = > {
if ( ! a . Key ) {
2023-04-26 04:48:55 +00:00
errors [ ` annotations.key[ ${ i } ] ` ] = 'Key is required.' ;
2023-03-01 00:11:12 +00:00
} else if ( duplicatedAnnotations . includes ( a . Key ) ) {
2023-04-26 04:48:55 +00:00
errors [ ` annotations.key[ ${ i } ] ` ] =
'Key is a duplicate of an existing one.' ;
} else {
const key = a . Key . split ( '/' ) ;
if ( key . length > 2 ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
'Two segments are allowed, separated by a slash (/): a prefix (optional) and a name.' ;
} else if ( key . length === 2 ) {
if ( key [ 0 ] . length > 253 ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
"Prefix (before the slash) can't exceed 253 characters." ;
} else if ( key [ 1 ] . length > 63 ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
"Name (after the slash) can't exceed 63 characters." ;
} else if ( ! re . test ( key [ 1 ] ) ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
'Start and end with alphanumeric characters only, limiting characters in between to dashes, underscores, and alphanumerics.' ;
}
} else if ( key . length === 1 ) {
if ( key [ 0 ] . length > 63 ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
"Name (the segment after a slash (/), or only segment if no slash) can't exceed 63 characters." ;
} else if ( ! re . test ( key [ 0 ] ) ) {
errors [ ` annotations.key[ ${ i } ] ` ] =
'Start and end with alphanumeric characters only, limiting characters in between to dashes, underscores, and alphanumerics.' ;
}
}
2023-03-01 00:11:12 +00:00
}
if ( ! a . Value ) {
2023-04-26 04:48:55 +00:00
errors [ ` annotations.value[ ${ i } ] ` ] = 'Value is required.' ;
2023-03-01 00:11:12 +00:00
}
duplicatedAnnotations . push ( a . Key ) ;
} ) ;
const duplicatedHosts : string [ ] = [ ] ;
// Check if the paths are duplicates
rule . Hosts ? . forEach ( ( host , hi ) = > {
if ( ! host . NoHost ) {
if ( ! host . Host ) {
errors [ ` hosts[ ${ hi } ].host ` ] = 'Host is required' ;
} else if ( duplicatedHosts . includes ( host . Host ) ) {
errors [ ` hosts[ ${ hi } ].host ` ] = 'Host cannot be duplicated' ;
}
duplicatedHosts . push ( host . Host ) ;
}
// Validate service
host . Paths ? . forEach ( ( path , pi ) = > {
if ( ! path . ServiceName ) {
errors [ ` hosts[ ${ hi } ].paths[ ${ pi } ].servicename ` ] =
'Service name is required' ;
}
if (
isEdit &&
path . ServiceName &&
! serviceOptions . find ( ( s ) = > s . value === path . ServiceName )
) {
errors [ ` hosts[ ${ hi } ].paths[ ${ pi } ].servicename ` ] = (
< span >
Currently set to { path . ServiceName } , which does not exist . You
can create a service with this name for a particular deployment
via { ' ' }
< Link
to = "kubernetes.applications"
params = { { id : environmentId } }
className = "text-primary"
target = "_blank"
>
Applications
< / Link >
, and on returning here it will be picked up .
< / span >
) ;
}
if ( ! path . ServicePort ) {
errors [ ` hosts[ ${ hi } ].paths[ ${ pi } ].serviceport ` ] =
'Service port is required' ;
}
} ) ;
// Validate paths
const paths = host . Paths . map ( ( path ) = > path . Route ) ;
paths . forEach ( ( item , idx ) = > {
if ( ! item ) {
errors [ ` hosts[ ${ hi } ].paths[ ${ idx } ].path ` ] = 'Path cannot be empty' ;
} else if ( paths . indexOf ( item ) !== idx ) {
errors [ ` hosts[ ${ hi } ].paths[ ${ idx } ].path ` ] =
'Paths cannot be duplicated' ;
} else {
// Validate host and path combination globally
const isExists = checkIfPathExistsWithHost (
ingresses ,
host . Host ,
item ,
params . name
) ;
if ( isExists ) {
errors [ ` hosts[ ${ hi } ].paths[ ${ idx } ].path ` ] =
'Path is already in use with the same host' ;
}
}
} ) ;
} ) ;
setErrors ( errors ) ;
if ( Object . keys ( errors ) . length > 0 ) {
return false ;
}
return true ;
} ,
[ ingresses , environmentId , isEdit , params . name ]
) ;
const debouncedValidate = useMemo ( ( ) = > debounce ( validate , 300 ) , [ validate ] ) ;
2022-09-21 04:49:42 +00:00
useEffect ( ( ) = > {
if ( namespace . length > 0 ) {
2023-03-01 00:11:12 +00:00
debouncedValidate (
2022-09-21 04:49:42 +00:00
ingressRule ,
ingressNames || [ ] ,
servicesOptions || [ ] ,
2022-10-05 02:17:53 +00:00
existingIngressClass
2022-09-21 04:49:42 +00:00
) ;
}
2022-10-05 02:17:53 +00:00
// eslint-disable-next-line react-hooks/exhaustive-deps
2022-09-21 04:49:42 +00:00
} , [
ingressRule ,
namespace ,
ingressNames ,
servicesOptions ,
existingIngressClass ,
2023-03-01 00:11:12 +00:00
debouncedValidate ,
2022-09-21 04:49:42 +00:00
] ) ;
return (
< >
< PageHeader
title = { isEdit ? 'Edit ingress' : 'Add ingress' }
breadcrumbs = { [
{
link : 'kubernetes.ingresses' ,
label : 'Ingresses' ,
} ,
{
label : isEdit ? 'Edit ingress' : 'Add ingress' ,
} ,
] }
/ >
< div className = "row ingress-rules" >
< div className = "col-sm-12" >
< IngressForm
environmentID = { environmentId }
isLoading = { isLoading }
isEdit = { isEdit }
rule = { ingressRule }
ingressClassOptions = { ingressClassOptions }
errors = { errors }
servicePorts = { servicePorts }
tlsOptions = { tlsOptions }
serviceOptions = { serviceOptions }
addNewIngressHost = { addNewIngressHost }
handleTLSChange = { handleTLSChange }
handleHostChange = { handleHostChange }
handleIngressChange = { handleIngressChange }
handlePathChange = { handlePathChange }
addNewIngressRoute = { addNewIngressRoute }
removeIngressHost = { removeIngressHost }
removeIngressRoute = { removeIngressRoute }
addNewAnnotation = { addNewAnnotation }
removeAnnotation = { removeAnnotation }
reloadTLSCerts = { reloadTLSCerts }
handleAnnotationChange = { handleAnnotationChange }
namespace = { namespace }
handleNamespaceChange = { handleNamespaceChange }
namespacesOptions = { namespacesOptions }
/ >
< / div >
{ namespace && ! isLoading && (
< div className = "col-sm-12" >
< Button
onClick = { ( ) = > handleCreateIngressRules ( ) }
disabled = { Object . keys ( errors ) . length > 0 }
>
{ isEdit ? 'Update' : 'Create' }
< / Button >
< / div >
) }
< / div >
< / >
) ;
function handleNamespaceChange ( ns : string ) {
setNamespace ( ns ) ;
if ( ! isEdit ) {
addNewIngress ( ns ) ;
}
}
function handleIngressChange ( key : string , val : string ) {
setIngressRule ( ( prevRules ) = > {
const rule = { . . . prevRules , [ key ] : val } ;
if ( key === 'IngressClassName' ) {
rule . IngressType = ingressControllersResults . data ? . find (
( c ) = > c . ClassName === val
) ? . Type ;
}
return rule ;
} ) ;
}
function handleTLSChange ( hostIndex : number , tls : string ) {
setIngressRule ( ( prevRules ) = > {
const rule = { . . . prevRules } ;
rule . Hosts [ hostIndex ] = { . . . rule . Hosts [ hostIndex ] , Secret : tls } ;
return rule ;
} ) ;
}
function handleHostChange ( hostIndex : number , val : string ) {
setIngressRule ( ( prevRules ) = > {
const rule = { . . . prevRules } ;
rule . Hosts [ hostIndex ] = { . . . rule . Hosts [ hostIndex ] , Host : val } ;
rule . Hosts [ hostIndex ] . Secret =
hostWithTLS [ val ] || rule . Hosts [ hostIndex ] . Secret ;
return rule ;
} ) ;
}
function handlePathChange (
hostIndex : number ,
pathIndex : number ,
key : 'Route' | 'PathType' | 'ServiceName' | 'ServicePort' ,
val : string
) {
setIngressRule ( ( prevRules ) = > {
const rule = { . . . prevRules } ;
const h = { . . . rule . Hosts [ hostIndex ] } ;
h . Paths [ pathIndex ] = {
. . . h . Paths [ pathIndex ] ,
[ key ] : key === 'ServicePort' ? Number ( val ) : val ,
} ;
// set the first port of the service as the default port
if (
key === 'ServiceName' &&
servicePorts [ val ] &&
servicePorts [ val ] . length > 0
) {
h . Paths [ pathIndex ] . ServicePort = Number ( servicePorts [ val ] [ 0 ] . value ) ;
}
rule . Hosts [ hostIndex ] = h ;
return rule ;
} ) ;
}
function handleAnnotationChange (
index : number ,
key : 'Key' | 'Value' ,
val : string
) {
setIngressRule ( ( prevRules ) = > {
const rules = { . . . prevRules } ;
rules . Annotations = rules . Annotations || [ ] ;
rules . Annotations [ index ] = rules . Annotations [ index ] || {
Key : '' ,
Value : '' ,
} ;
rules . Annotations [ index ] [ key ] = val ;
return rules ;
} ) ;
}
function addNewIngress ( namespace : string ) {
const newKey = ` ${ namespace } -ingress- ${
( ruleCounterByNamespace [ namespace ] || 0 ) + 1
} ` ;
const path : Path = {
Key : uuidv4 ( ) ,
ServiceName : '' ,
ServicePort : 0 ,
Route : '' ,
PathType : 'Prefix' ,
} ;
const host : Host = {
Host : '' ,
Secret : '' ,
Paths : [ path ] ,
Key : uuidv4 ( ) ,
} ;
const rule : Rule = {
Key : uuidv4 ( ) ,
Namespace : namespace ,
IngressName : newKey ,
IngressClassName : '' ,
Hosts : [ host ] ,
} ;
setIngressRule ( rule ) ;
}
function addNewIngressHost ( noHost = false ) {
const rule = { . . . ingressRule } ;
const path : Path = {
ServiceName : '' ,
ServicePort : 0 ,
Route : '' ,
PathType : 'Prefix' ,
Key : uuidv4 ( ) ,
} ;
const host : Host = {
Host : '' ,
Secret : '' ,
Paths : [ path ] ,
NoHost : noHost ,
Key : uuidv4 ( ) ,
} ;
rule . Hosts . push ( host ) ;
setIngressRule ( rule ) ;
}
function addNewIngressRoute ( hostIndex : number ) {
const rule = { . . . ingressRule } ;
const path : Path = {
ServiceName : '' ,
ServicePort : 0 ,
Route : '' ,
PathType : 'Prefix' ,
Key : uuidv4 ( ) ,
} ;
rule . Hosts [ hostIndex ] . Paths . push ( path ) ;
setIngressRule ( rule ) ;
}
2022-10-24 20:41:30 +00:00
function addNewAnnotation ( type ? : 'rewrite' | 'regex' | 'ingressClass' ) {
2022-09-21 04:49:42 +00:00
const rule = { . . . ingressRule } ;
const annotation : Annotation = {
Key : '' ,
Value : '' ,
ID : uuidv4 ( ) ,
} ;
2022-10-24 20:41:30 +00:00
switch ( type ) {
case 'rewrite' :
annotation . Key = 'nginx.ingress.kubernetes.io/rewrite-target' ;
annotation . Value = '/$1' ;
break ;
case 'regex' :
annotation . Key = 'nginx.ingress.kubernetes.io/use-regex' ;
annotation . Value = 'true' ;
break ;
case 'ingressClass' :
annotation . Key = 'kubernetes.io/ingress.class' ;
annotation . Value = '' ;
break ;
default :
break ;
2022-09-21 04:49:42 +00:00
}
rule . Annotations = rule . Annotations || [ ] ;
rule . Annotations ? . push ( annotation ) ;
setIngressRule ( rule ) ;
}
function removeAnnotation ( index : number ) {
const rule = { . . . ingressRule } ;
if ( index > - 1 ) {
rule . Annotations ? . splice ( index , 1 ) ;
}
setIngressRule ( rule ) ;
}
function removeIngressRoute ( hostIndex : number , pathIndex : number ) {
const rule = { . . . ingressRule , Hosts : [ . . . ingressRule . Hosts ] } ;
if ( hostIndex > - 1 && pathIndex > - 1 ) {
rule . Hosts [ hostIndex ] . Paths . splice ( pathIndex , 1 ) ;
}
setIngressRule ( rule ) ;
}
function removeIngressHost ( hostIndex : number ) {
const rule = { . . . ingressRule , Hosts : [ . . . ingressRule . Hosts ] } ;
if ( hostIndex > - 1 ) {
rule . Hosts . splice ( hostIndex , 1 ) ;
}
setIngressRule ( rule ) ;
}
function reloadTLSCerts() {
configResults . refetch ( ) ;
}
function handleCreateIngressRules() {
const rule = { . . . ingressRule } ;
2022-10-24 20:41:30 +00:00
const classNameToSend =
rule . IngressClassName === 'none' ? '' : rule . IngressClassName ;
2022-09-21 04:49:42 +00:00
const ingress : Ingress = {
Namespace : namespace ,
Name : rule.IngressName ,
2022-10-24 20:41:30 +00:00
ClassName : classNameToSend ,
2022-09-21 04:49:42 +00:00
Hosts : rule.Hosts.map ( ( host ) = > host . Host ) ,
Paths : preparePaths ( rule . IngressName , rule . Hosts ) ,
TLS : prepareTLS ( rule . Hosts ) ,
Annotations : prepareAnnotations ( rule . Annotations || [ ] ) ,
} ;
if ( isEdit ) {
updateIngressMutation . mutate (
{ environmentId , ingress } ,
{
onSuccess : ( ) = > {
notifySuccess ( 'Success' , 'Ingress updated successfully' ) ;
router . stateService . go ( 'kubernetes.ingresses' ) ;
} ,
}
) ;
} else {
createIngressMutation . mutate (
{ environmentId , ingress } ,
{
onSuccess : ( ) = > {
notifySuccess ( 'Success' , 'Ingress created successfully' ) ;
router . stateService . go ( 'kubernetes.ingresses' ) ;
} ,
}
) ;
}
}
}