// 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'
// UI Elements
import {
Box,
Checkbox,
ColumnLayout,
Container,
ExpandableSection,
FormField,
Input,
Select,
SpaceBetween,
} from '@cloudscape-design/components'
// State
import {
setState,
getState,
useState,
clearState,
updateState,
ssmPolicy,
} from '../../store'
// Components
import {
HeadNodeActionsEditor,
InstanceSelect,
RootVolume,
SecurityGroups,
SubnetSelect,
IamPoliciesEditor,
ActionsEditor,
} from './Components'
import {useFeatureFlag} from '../../feature-flags/useFeatureFlag'
import {
SlurmSettings,
validateSlurmSettings,
} from './SlurmSettings/SlurmSettings'
import InfoLink from '../../components/InfoLink'
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']
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'])
}
valid = validateSlurmSettings()
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)
}),
)
}
}
}
function KeypairSelect() {
const {t} = useTranslation()
const keypairs = useState(['aws', 'keypairs']) || []
const keypairPath = [...headNodePath, 'Ssh', 'KeyName']
const keypair = 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', ...keypairs]
const setKeyPair = (kpValue: any) => {
if (kpValue) setState(keypairPath, kpValue)
else {
clearState([...headNodePath, 'Ssh'])
enableSsm(true)
}
}
return (