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; 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 implements IWizardPageComponent { constructor(props: INetworkProps) { super(props); this.getAllVpcs(); } public state: INetworkState = { vpcOptions: [], accounts: null, regions: [], subnets: [], availableSubnets: [], securityGroups: null, }; private props$ = new Subject(); private destroy$ = new Subject(); private getAllVpcs(): void { observableFrom(VpcReader.listVpcs()) .pipe(takeUntil(this.destroy$)) .subscribe((Vpcs) => { this.setState({ vpcOptions: Vpcs }); }); } public validate(): FormikErrors { return {}; } private getAvailableSubnets(): PromiseLike { return SubnetReader.listSubnetsByProvider('aws'); } private getAvailableSecurityGroups(): PromiseLike { return ReactInjector.securityGroupReader.getAllSecurityGroups(); } private makeSubnetOptions(availableSubnets: ISubnet[]): ISubnetOption[] { const subOptions: ISubnetOption[] = availableSubnets.map((s) => ({ subnetId: s.id, vpcId: s.vpcId })); // we have to filter out any duplicate options const uniqueSubOptions = uniqBy(subOptions, 'subnetId'); return uniqueSubOptions; } public componentDidUpdate() { this.props$.next(this.props); } public componentWillUnmount(): void { this.destroy$.next(); } public componentDidMount(): void { const allSubnets = Promise.resolve(this.getAvailableSubnets()) .then((subnets: ISubnet[]) => { subnets.forEach((subnet: ISubnet) => { subnet.label = subnet.id; subnet.deprecated = !!subnet.deprecated; if (subnet.deprecated) { subnet.label += ' (deprecated)'; } }); return subnets.filter((s) => s.label); }) .then((subnets: ISubnet[]) => { 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: Array>) => { const subnetsSelected = options.map((o) => o.value); this.props.formik.setFieldValue('subnetIds', subnetsSelected); }; private handleSecurityGroupsUpdate = (options: Array>) => { const sgSelected = options.map((o) => o.value); this.props.formik.setFieldValue('securityGroupIds', sgSelected); }; private setVpc = (vpcId: string): void => { this.props.formik.setFieldValue('vpcId', vpcId); this.props.formik.setFieldValue('subnetIds', []); const { availableSubnets } = this.state; const subs = availableSubnets.filter(function (s: ISubnetOption) { return s.vpcId.includes(vpcId); }); this.setState({ subnets: subs }); }; private toSubnetOption = (value: ISubnetOption): Option => { return { value: value.subnetId, label: value.subnetId }; }; private getSecurityGroupsByVpc = (sgs: ISecurityGroupsByAccountSourceData): Array> => { const { values } = this.props.formik; const sgOptions: Array> = []; /** 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> => { const { subnets, availableSubnets } = this.state; const { values } = this.props.formik; if (!this.props.isNew && values.vpcId) { return availableSubnets .filter(function (s: ISubnetOption) { 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 (
{values.credentials && ( } input={(props) => ( v.account === values.credentials) .map((v: IVpc) => v.id)} clearable={true} /> )} onChange={this.setVpc} required={false} /> )}
Subnets
{subnetOptions.length === 0 && (
No subnets found in the selected account/region/VPC
)} {values.vpcId ? ( ) : null}
Security Groups
{sgOptions.length === 0 && (
No security groups found in the selected account/region/VPC
)} {values.credentials && values.vpcId ? ( ) : null}
); } }