// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance // with the License. A copy of the License is located at // // http://aws.amazon.com/apache2.0/ // // or in the "LICENSE.txt" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and // limitations under the License. // Frameworks import * as React from 'react' import i18next from 'i18next' import {Trans, useTranslation} from 'react-i18next' import {useSelector} from 'react-redux' import {findFirst, getIn} from '../../util' import safeGet from 'lodash/get' // UI Elements import { Box, ColumnLayout, Container, ExpandableSection, FormField, Header, Input, Select, SpaceBetween, TextContent, } from '@cloudscape-design/components' // State import { setState, getState, useState, clearState, updateState, ssmPolicy, } from '../../store' // Components import { HeadNodeActionsEditor, InstanceSelect, RootVolume, SecurityGroups, SubnetSelect, IamPoliciesEditor, ActionsEditor, CheckboxWithHelpPanel, } from './Components' import {useFeatureFlag} from '../../feature-flags/useFeatureFlag' import TitleDescriptionHelpPanel from '../../components/help-panel/TitleDescriptionHelpPanel' import {useHelpPanel} from '../../components/help-panel/HelpPanel' // Constants const headNodePath = ['app', 'wizard', 'config', 'HeadNode'] const errorsPath = ['app', 'wizard', 'errors', 'headNode'] const keypairPath = [...headNodePath, 'Ssh', 'KeyName'] const imdsSecuredPath = [...headNodePath, 'Imds', 'Secured'] function headNodeValidate() { const subnetPath = [...headNodePath, 'Networking', 'SubnetId'] const subnetValue = getState(subnetPath) const rootVolumeSizePath = [ ...headNodePath, 'LocalStorage', 'RootVolume', 'Size', ] const rootVolumeValue = getState(rootVolumeSizePath) const instanceTypePath = [...headNodePath, 'InstanceType'] const instanceTypeValue = getState(instanceTypePath) const actionsPath = [...headNodePath, 'CustomActions'] const onStartPath = [...actionsPath, 'OnNodeStart'] const onStart = getState(onStartPath) const onConfiguredPath = [...actionsPath, 'OnNodeConfigured'] const onConfigured = getState(onConfiguredPath) const onUpdatedPath = [...actionsPath, 'OnNodeUpdated'] const onUpdated = getState(onUpdatedPath) let valid = true if (!subnetValue) { setState( [...errorsPath, 'subnet'], i18next.t('wizard.headNode.validation.selectSubnet'), ) valid = false } else { clearState([...errorsPath, 'subnet']) } if (!instanceTypeValue) { setState( [...errorsPath, 'instanceType'], i18next.t('wizard.headNode.validation.selectInstanceType'), ) valid = false } else { clearState([...errorsPath, 'instanceType']) } if (rootVolumeValue === '') { setState( [...errorsPath, 'rootVolume'], i18next.t('wizard.headNode.validation.setRootVolumeSize'), ) valid = false } else if ( rootVolumeValue && (!Number.isInteger(rootVolumeValue) || rootVolumeValue < 35) ) { setState( [...errorsPath, 'rootVolume'], i18next.t('wizard.headNode.validation.rootVolumeMinimum'), ) valid = false } else { clearState([...errorsPath, 'rootVolume']) } if ( onStart && getState([...onStartPath, 'Args']) && !getState([...onStartPath, 'Script']) ) { setState( [...errorsPath, 'onStart'], i18next.t('wizard.headNode.validation.scriptWithArgs'), ) valid = false } else { clearState([...errorsPath, 'onStart']) } if ( onConfigured && getState([...onConfiguredPath, 'Args']) && !getState([...onConfiguredPath, 'Script']) ) { setState( [...errorsPath, 'onConfigured'], i18next.t('wizard.headNode.validation.scriptWithArgs'), ) valid = false } else { clearState([...errorsPath, 'onConfigured']) } if ( onUpdated && getState([...onUpdatedPath, 'Args']) && !getState([...onUpdatedPath, 'Script']) ) { setState( [...errorsPath, 'onUpdated'], i18next.t('wizard.headNode.validation.scriptWithArgs'), ) valid = false } else { clearState([...errorsPath, 'onUpdated']) } setState([...errorsPath, 'validated'], true) return valid } function enableSsm(enable: any) { const iamPolicies = getState([ ...headNodePath, 'Iam', 'AdditionalIamPolicies', ]) const defaultRegion = getState(['aws', 'region']) const region = getState(['app', 'selectedRegion']) || defaultRegion if (enable) { if (iamPolicies && findFirst(iamPolicies, isSsmPolicy)) return updateState( [...headNodePath, 'Iam', 'AdditionalIamPolicies'], (existing: any) => { return [...(existing || []), {Policy: ssmPolicy(region)}] }, ) } else { if (!iamPolicies || (iamPolicies && !findFirst(iamPolicies, isSsmPolicy))) return if (iamPolicies.length === 1) clearState([...headNodePath, 'Iam']) else { updateState( [...headNodePath, 'Iam', 'AdditionalIamPolicies'], (existing: any) => existing.filter((p: any) => { return !isSsmPolicy(p) }), ) } } } const setKeyPair = (kpValue?: string) => { if (kpValue) setState(keypairPath, kpValue) else { clearState([...headNodePath, 'Ssh']) enableSsm(true) } } function KeypairSelect() { const {t} = useTranslation() const keypairsInAWSConfig = useState(['aws', 'keypairs']) const selectedKeypairName = useState(keypairPath) const editing = useState(['app', 'wizard', 'editing']) const keypairToOption = (kp: any) => { if (kp === 'None' || kp === null || kp === undefined) return {label: 'None', value: null} else return {label: kp.KeyName, value: kp.KeyName} } const keypairsWithNone = ['None', ...keypairsInAWSConfig] React.useEffect(() => { const firstAvailableKeypair = safeGet(keypairsInAWSConfig, ['0', 'KeyName']) if (!selectedKeypairName && firstAvailableKeypair) { setKeyPair(firstAvailableKeypair) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [keypairsInAWSConfig]) return ( )} {dcvEnabled && ( )} ) } function IMDSSecuredSettings() { const {t} = useTranslation() let imdsSecured = useState(imdsSecuredPath) const editing = useState(['app', 'wizard', 'editing']) React.useEffect(() => { if (null == imdsSecured && !editing) { setState(imdsSecuredPath, true) } }, [imdsSecured]) const toggleImdsSecured = React.useCallback(() => { setState(imdsSecuredPath, !imdsSecured) }, [imdsSecured]) const footerLinks = React.useMemo( () => [ { title: t('wizard.headNode.imdsSecured.imdsv2Link.title'), href: t('wizard.headNode.imdsSecured.imdsv2Link.href'), }, ], [t], ) return ( } > ) } const headNodeSubnetPath = [ 'app', 'wizard', 'config', 'HeadNode', 'Networking', 'SubnetId', ] function HeadNode() { const {t} = useTranslation() const subnetPath = [...headNodePath, 'Networking', 'SubnetId'] const instanceTypeErrors = useState([...errorsPath, 'instanceType']) const subnetErrors = useState([...errorsPath, 'subnet']) const subnetValue = useState(subnetPath) || '' const editing = useState(['app', 'wizard', 'editing']) const isOnNodeUpdatedActive = useFeatureFlag('on_node_updated') const subnets = useState(['aws', 'subnets']) const vpcId = useState(['app', 'wizard', 'vpc']) const currentHeadNodeSubnet = useState(headNodeSubnetPath) React.useEffect(() => { if (currentHeadNodeSubnet) { return } const filteredSubnets = (subnets || []).filter( (s: any) => s.VpcId === vpcId, ) if (filteredSubnets.length > 0) { var subnet = filteredSubnets[0] setState(headNodeSubnetPath, subnet.SubnetId) } }, [subnets, vpcId, currentHeadNodeSubnet]) useHelpPanel() return ( {t('wizard.headNode.instance.title')} } >

{t('wizard.headNode.advancedOptions.scripts.title')}

{isOnNodeUpdatedActive ? ( ) : ( )}
{t('wizard.headNode.networking.header')} } > setState(subnetPath, subnetId)} /> {t('wizard.headNode.security.header')} } >
) } const HeadNodePropertiesHelpPanel = () => { const {t} = useTranslation() const footerLinks = React.useMemo( () => [ { title: t('wizard.headNode.help.instanceSelectionLink.title'), href: t('wizard.headNode.help.instanceSelectionLink.href'), }, { title: t('wizard.headNode.help.headNodePropertiesLink.title'), href: t('wizard.headNode.help.headNodePropertiesLink.href'), }, { title: t('wizard.headNode.help.ssmLink.title'), href: t('wizard.headNode.help.ssmLink.href'), }, { title: t('wizard.headNode.help.dcvLink.title'), href: t('wizard.headNode.help.dcvLink.href'), }, { title: t('wizard.headNode.help.customActionsLink.title'), href: t('wizard.headNode.help.customActionsLink.href'), }, ], [t], ) return ( } footerLinks={footerLinks} /> ) } export { HeadNode, headNodeValidate, HeadNodePropertiesHelpPanel, IMDSSecuredSettings, }