import type { FormikErrors , FormikProps } from 'formik' ; import { forOwn , uniqBy } from 'lodash' ; import React from 'react' ; import type { Option } from 'react-select' ; import { combineLatest as observableCombineLatest , from as observableFrom , Subject } from 'rxjs' ; import { takeUntil } from 'rxjs/operators' ; import type { Application , IAccount , IRegion , ISecurityGroupsByAccountSourceData , ISubnet , IVpc , IWizardPageComponent , } from '@spinnaker/core' ; import { FormikFormField , HelpField , ReactInjector , ReactSelectInput , SubnetReader , TetheredSelect , } from '@spinnaker/core' ; import type { IAmazonFunctionUpsertCommand } from '../../index' ; import { VpcReader } from '../../vpc' ; export interface ISubnetOption { subnetId : string ; vpcId : string ; } export interface INetworkProps { formik : FormikProps < IAmazonFunctionUpsertCommand > ; isNew ? : boolean ; app : Application ; } export interface INetworkState { vpcOptions : Array < { } > ; accounts : IAccount [ ] ; regions : IRegion [ ] ; subnets : ISubnetOption [ ] ; availableSubnets : ISubnetOption [ ] ; securityGroups : ISecurityGroupsByAccountSourceData ; } export class Network extends React . Component < INetworkProps , INetworkState > implements IWizardPageComponent < IAmazonFunctionUpsertCommand > { constructor ( props ) { super ( props ) ; this . getAllVpcs ( ) ; } public state : INetworkState = { vpcOptions : [ ] , accounts : null , regions : [ ] , subnets : [ ] , availableSubnets : [ ] , securityGroups : null , } ; private props$ = new Subject < INetworkProps > ( ) ; private destroy$ = new Subject < void > ( ) ; private getAllVpcs ( ) { observableFrom ( VpcReader . listVpcs ( ) ) . pipe ( takeUntil ( this . destroy$ ) ) . subscribe ( ( Vpcs ) => { this . setState ( { vpcOptions : Vpcs } ) ; } ) ; } public validate ( ) { return { } ; } private getAvailableSubnets ( ) { return SubnetReader . listSubnetsByProvider ( 'aws' ) ; } private getAvailableSecurityGroups ( ) { return ReactInjector . securityGroupReader . getAllSecurityGroups ( ) ; } private makeSubnetOptions ( availableSubnets ) { const subOptions = availableSubnets . map ( ( s ) => ( { subnetId : s . id , vpcId : s . vpcId } ) ) ; const uniqueSubOptions = uniqBy ( subOptions , 'subnetId' ) ; return uniqueSubOptions ; } public componentDidUpdate ( ) { this . props$ . next ( this . props ) ; } public componentWillUnmount ( ) { this . destroy$ . next ( ) ; } public componentDidMount ( ) { const allSubnets = Promise . resolve ( this . getAvailableSubnets ( ) ) . then ( ( subnets ) => { subnets . forEach ( ( subnet ) => { subnet . label = subnet . id ; subnet . deprecated = ! ! subnet . deprecated ; if ( subnet . deprecated ) { subnet . label += ' (deprecated)' ; } } ) ; return subnets . filter ( ( s ) => s . label ) ; } ) . then ( ( subnets ) => { return this . makeSubnetOptions ( subnets ) ; } ) ; const secGroups$ = Promise . resolve ( this . getAvailableSecurityGroups ( ) ) ; observableCombineLatest ( [ allSubnets , secGroups$ ] ) . pipe ( takeUntil ( this . destroy$ ) ) . subscribe ( ( [ availableSubnets , securityGroups ] ) => { return this . setState ( { availableSubnets , securityGroups } ) ; } ) ; } private handleSubnetUpdate = ( options ) => { const subnetsSelected = options . map ( ( o ) => o . value ) ; this . props . formik . setFieldValue ( 'subnetIds' , subnetsSelected ) ; } ; private handleSecurityGroupsUpdate = ( options ) => { const sgSelected = options . map ( ( o ) => o . value ) ; this . props . formik . setFieldValue ( 'securityGroupIds' , sgSelected ) ; } ; private setVpc = ( vpcId ) : void => { this . props . formik . setFieldValue ( 'vpcId' , vpcId ) ; this . props . formik . setFieldValue ( 'subnetIds' , [ ] ) ; const { availableSubnets } = this . state ; const subs = availableSubnets . filter ( function ( s ) { return s . vpcId . includes ( vpcId ) ; } ) ; this . setState ( { subnets : subs } ) ; } ; private toSubnetOption = ( value ) : Option < string > => { return { value : value . subnetId , label : value . subnetId } ; } ; private getSecurityGroupsByVpc = ( sgs ) : Array < Option < string > > => { const { values } = this . props . formik ; const sgOptions = [ ] ; /** Get security groups that belong to current selected account */ forOwn ( sgs , function ( sgByAccount , acc ) { if ( acc === values . credentials ) { /** Get security groups that fall under the provider 'aws' */ forOwn ( sgByAccount , function ( sgByRegion , provider ) { if ( provider === 'aws' ) { /** Get security groups that are under the current selected region */ forOwn ( sgByRegion , function ( groups , region ) { if ( region === values . region ) { /** Get security groups that fall under the current selected VPC */ groups . forEach ( function ( group ) { if ( group . vpcId === values . vpcId ) { sgOptions . push ( { value : group . id , label : group . name } ) ; } } ) ; } } ) ; } } ) ; } } ) ; return sgOptions ; } ; private getSubnetOptions = ( ) : Array < Option < string > > => { const { subnets , availableSubnets } = this . state ; const { values } = this . props . formik ; if ( ! this . props . isNew && values . vpcId ) { return availableSubnets . filter ( function ( s ) { return s . vpcId . includes ( values . vpcId ) ; } ) . map ( this . toSubnetOption ) ; } else { return subnets . map ( this . toSubnetOption ) ; } } ; public render ( ) { const { vpcOptions , securityGroups } = this . state ; const { values } = this . props . formik ; const subnetOptions = this . getSubnetOptions ( ) ; const sgOptions = securityGroups ? this . getSecurityGroupsByVpc ( securityGroups ) : [ ] ; return ( < div className = "form-group" > < div className = "col-md-11" > < div className = "sp-margin-m-bottom" > { values . credentials && ( < FormikFormField name = "vpcId" label = "VPC Id" help = { < HelpField id = "aws.function.vpc.id" / > } input = { ( props ) => ( < ReactSelectInput { ... props } stringOptions = { vpcOptions . filter ( ( v ) => v . account === values . credentials ) . map ( ( v ) => v . id ) } clearable = { true } / > ) } onChange = { this . setVpc } required = { false } / > ) } < /div> < div className = "form-group" > < div className = "col-md-4 sm-label-right" > < b > Subnets < /b> < HelpField id = "aws.function.subnet" / > < /div> < div className = "col-md-7" > { subnetOptions . length === 0 && ( < div className = "form-control-static" > No subnets found in the selected account / region / VPC < /div>)} { values . vpcId ? ( < TetheredSelect multi = { true } options = { subnetOptions } value = { values . subnetIds } onChange = { this . handleSubnetUpdate } / > ) : null } < /div> < /div> < div className = "form-group" > < div className = "col-md-4 sm-label-right" > < b > Security Groups < /b> < HelpField id = "aws.function.subnet" / > < /div> < div className = "col-md-7" > { sgOptions . length === 0 && ( < div className = "form-control-static" > No security groups found in the selected account / region / VPC < /div>)} { values . credentials && values . vpcId ? ( < TetheredSelect multi = { true } options = { sgOptions } value = { values . securityGroupIds } onChange = { this . handleSecurityGroupsUpdate } / > ) : null } < /div> < /div> < /div> < /div>); } }