diff --git a/app/kubernetes/converters/configuration.js b/app/kubernetes/converters/configuration.js index 04bc54d44..c31c8d006 100644 --- a/app/kubernetes/converters/configuration.js +++ b/app/kubernetes/converters/configuration.js @@ -14,6 +14,7 @@ class KubernetesConfigurationConverter { _.forEach(secret.Data, (entry) => { res.Data[entry.Key] = entry.Value; }); + res.data = res.Data; res.ConfigurationOwner = secret.ConfigurationOwner; res.IsRegistrySecret = secret.IsRegistrySecret; res.SecretType = secret.SecretType; @@ -34,6 +35,7 @@ class KubernetesConfigurationConverter { _.forEach(configMap.Data, (entry) => { res.Data[entry.Key] = entry.Value; }); + res.data = res.Data; res.ConfigurationOwner = configMap.ConfigurationOwner; return res; } diff --git a/app/kubernetes/converters/secret.js b/app/kubernetes/converters/secret.js index 45922d4d6..1ab9702b1 100644 --- a/app/kubernetes/converters/secret.js +++ b/app/kubernetes/converters/secret.js @@ -90,6 +90,7 @@ class KubernetesSecretConverter { } return entry; }); + res.data = res.Data; return res; } diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js index 21ff38277..9440b910a 100644 --- a/app/kubernetes/helpers/application/index.js +++ b/app/kubernetes/helpers/application/index.js @@ -6,7 +6,6 @@ import { KubernetesApplicationAutoScalerFormValue, KubernetesApplicationConfigurationFormValue, KubernetesApplicationConfigurationFormValueOverridenKey, - KubernetesApplicationConfigurationFormValueOverridenKeyTypes, KubernetesApplicationEnvironmentVariableFormValue, KubernetesApplicationPersistedFolderFormValue, KubernetesApplicationPlacementFormValue, @@ -169,21 +168,25 @@ class KubernetesApplicationHelper { const overrideThreshold = max - _.max(_.map(keys, 'VolCount')); const res = _.map(new Array(max), () => new KubernetesApplicationConfigurationFormValue()); _.forEach(res, (item, index) => { - item.SelectedConfiguration = cfg; + item.selectedConfiguration = cfg; + // workaround to load configurations in the app in the select inputs + // this should be removed when the edit parent view is migrated to react + item.selectedConfiguration.metadata = {}; + item.selectedConfiguration.metadata.name = cfg.Name; const overriden = index >= overrideThreshold; if (overriden) { - item.Overriden = true; - item.OverridenKeys = _.map(keys, (k) => { + item.overriden = true; + item.overridenKeys = _.map(keys, (k) => { const fvKey = new KubernetesApplicationConfigurationFormValueOverridenKey(); - fvKey.Key = k.Key; + fvKey.key = k.Key; if (!k.Count) { - // !k.Count indicates k.Key is new added to the configuration and has not been loaded to the application yet - fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.NONE; + // !k.Count indicates k.key is new added to the configuration and has not been loaded to the application yet + fvKey.type = 'NONE'; } else if (index < k.EnvCount) { - fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT; + fvKey.type = 'ENVIRONMENT'; } else { - fvKey.Type = KubernetesApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM; - fvKey.Path = k.Sum[index].rootMountPath; + fvKey.type = 'FILESYSTEM'; + fvKey.path = k.Sum[index].rootMountPath; } return fvKey; }); @@ -201,46 +204,46 @@ class KubernetesApplicationHelper { let finalMounts = []; _.forEach(configurations, (config) => { - const isBasic = config.SelectedConfiguration.Kind === KubernetesConfigurationKinds.CONFIGMAP; + const isBasic = config.selectedConfiguration.kind === 'ConfigMap'; - if (!config.Overriden) { - const envKeys = _.keys(config.SelectedConfiguration.Data); + if (!config.overriden) { + const envKeys = _.keys(config.selectedConfiguration.data); _.forEach(envKeys, (item) => { const res = isBasic ? new KubernetesApplicationEnvConfigMapPayload() : new KubernetesApplicationEnvSecretPayload(); res.name = item; if (isBasic) { - res.valueFrom.configMapKeyRef.name = config.SelectedConfiguration.Name; + res.valueFrom.configMapKeyRef.name = config.selectedConfiguration.metadata.name; res.valueFrom.configMapKeyRef.key = item; } else { - res.valueFrom.secretKeyRef.name = config.SelectedConfiguration.Name; + res.valueFrom.secretKeyRef.name = config.selectedConfiguration.metadata.name; res.valueFrom.secretKeyRef.key = item; } finalEnv.push(res); }); } else { - const envKeys = _.filter(config.OverridenKeys, (item) => item.Type === KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT); + const envKeys = _.filter(config.overridenKeys, (item) => item.type === 'ENVIRONMENT'); _.forEach(envKeys, (item) => { const res = isBasic ? new KubernetesApplicationEnvConfigMapPayload() : new KubernetesApplicationEnvSecretPayload(); - res.name = item.Key; + res.name = item.key; if (isBasic) { - res.valueFrom.configMapKeyRef.name = config.SelectedConfiguration.Name; - res.valueFrom.configMapKeyRef.key = item.Key; + res.valueFrom.configMapKeyRef.name = config.selectedConfiguration.metadata.name; + res.valueFrom.configMapKeyRef.key = item.key; } else { - res.valueFrom.secretKeyRef.name = config.SelectedConfiguration.Name; - res.valueFrom.secretKeyRef.key = item.Key; + res.valueFrom.secretKeyRef.name = config.selectedConfiguration.metadata.name; + res.valueFrom.secretKeyRef.key = item.key; } finalEnv.push(res); }); - const volKeys = _.filter(config.OverridenKeys, (item) => item.Type === KubernetesApplicationConfigurationFormValueOverridenKeyTypes.FILESYSTEM); - const groupedVolKeys = _.groupBy(volKeys, 'Path'); + const volKeys = _.filter(config.overridenKeys, (item) => item.type === 'FILESYSTEM'); + const groupedVolKeys = _.groupBy(volKeys, 'path'); _.forEach(groupedVolKeys, (items, path) => { const volumeName = KubernetesVolumeHelper.generatedApplicationConfigVolumeName(app.Name); - const configurationName = config.SelectedConfiguration.Name; + const configurationName = config.selectedConfiguration.metadata.name; const itemsMap = _.map(items, (item) => { const entry = new KubernetesApplicationVolumeEntryPayload(); - entry.key = item.Key; - entry.path = item.Key; + entry.key = item.key; + entry.path = item.key; return entry; }); diff --git a/app/kubernetes/models/application/formValues.js b/app/kubernetes/models/application/formValues.js index f4add318b..243765758 100644 --- a/app/kubernetes/models/application/formValues.js +++ b/app/kubernetes/models/application/formValues.js @@ -30,19 +30,13 @@ export function KubernetesApplicationFormValues() { this.OriginalIngresses = undefined; } -export const KubernetesApplicationConfigurationFormValueOverridenKeyTypes = Object.freeze({ - NONE: 0, - ENVIRONMENT: 1, - FILESYSTEM: 2, -}); - /** * KubernetesApplicationConfigurationFormValueOverridenKey Model */ const _KubernetesApplicationConfigurationFormValueOverridenKey = Object.freeze({ - Key: '', - Path: '', - Type: KubernetesApplicationConfigurationFormValueOverridenKeyTypes.ENVIRONMENT, + key: '', + path: '', + type: 'ENVIRONMENT', }); export class KubernetesApplicationConfigurationFormValueOverridenKey { @@ -55,9 +49,9 @@ export class KubernetesApplicationConfigurationFormValueOverridenKey { * KubernetesApplicationConfigurationFormValue Model */ const _KubernetesApplicationConfigurationFormValue = Object.freeze({ - SelectedConfiguration: undefined, - Overriden: false, - OverridenKeys: [], // KubernetesApplicationConfigurationFormValueOverridenKey list + selectedConfiguration: undefined, + overriden: false, + overridenKeys: [], }); export class KubernetesApplicationConfigurationFormValue { diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts index 16e531a75..a6bc99a7f 100644 --- a/app/kubernetes/react/components/index.ts +++ b/app/kubernetes/react/components/index.ts @@ -25,6 +25,9 @@ import { ApplicationsStacksDatatable } from '@/react/kubernetes/applications/Lis import { NodesDatatable } from '@/react/kubernetes/cluster/HomeView/NodesDatatable'; import { StackName } from '@/react/kubernetes/DeployView/StackName/StackName'; import { kubeEnvVarValidationSchema } from '@/react/kubernetes/applications/ApplicationForm/kubeEnvVarValidationSchema'; +import { SecretsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/SecretsFormSection'; +import { configurationsValidationSchema } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/configurationValidationSchema'; +import { ConfigMapsFormSection } from '@/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection'; import { EnvironmentVariablesFieldset } from '@@/form-components/EnvironmentVariablesFieldset'; @@ -186,3 +189,19 @@ withFormValidation( // use kubeEnvVarValidationSchema instead of envVarValidation to add a regex matches rule kubeEnvVarValidationSchema ); + +withFormValidation( + ngModule, + withUIRouter(withCurrentUser(withReactQuery(ConfigMapsFormSection))), + 'configMapsFormSection', + ['values', 'onChange', 'namespace'], + configurationsValidationSchema +); + +withFormValidation( + ngModule, + withUIRouter(withCurrentUser(withReactQuery(SecretsFormSection))), + 'secretsFormSection', + ['values', 'onChange', 'namespace'], + configurationsValidationSchema +); diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index 56c9bc261..1a597a51d 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -381,7 +381,7 @@
- +
-
-
- -
-
- - Portainer will automatically expose all the keys of a ConfigMap as environment variables. This behavior can be overridden to filesystem mounts for each key - via the override option. -
-
- - -
-
-
- name - -
- -
- - -
- - -
- -
-
- The following keys will be loaded from the {{ config.SelectedConfiguration.Name }} - ConfigMap as environment variables: - - {{ key }}{{ $last ? '' : ', ' }} - -
-
- - - -
-
- key - -
- -
- - -
- -
-
- path on disk - -
-
-
-
- -

Path is required.

-
-

- This path is already used. -

-
-
-
-
-
- -
- -
- -
-
-

There are no ConfigMaps available in this namespace.

-
+ -
-
- -
-
- - Portainer will automatically expose all the keys of a Secret as environment variables. This behavior can be overridden to filesystem mounts for each key via - the override option. -
-
- - -
-
-
- name - -
- -
- - -
- - -
- -
-
- The following keys will be loaded from the {{ config.SelectedConfiguration.Name }} Secret as environment variables: - - {{ key }}{{ $last ? '' : ', ' }} - -
-
- - - -
-
- key - -
- -
- - -
- -
-
- path on disk - -
-
-
-
- -

Path is required.

-
-

This path is already used.

-
-
-
-
-
- -
- -
-

- - There are no secrets available in this namespace. -

-
- -
- -
+
- +
diff --git a/app/kubernetes/views/applications/create/createApplicationController.js b/app/kubernetes/views/applications/create/createApplicationController.js index 7fd07e967..10c33e1ea 100644 --- a/app/kubernetes/views/applications/create/createApplicationController.js +++ b/app/kubernetes/views/applications/create/createApplicationController.js @@ -17,9 +17,6 @@ import { KubernetesDeploymentTypes, } from 'Kubernetes/models/application/models'; import { - KubernetesApplicationConfigurationFormValue, - KubernetesApplicationConfigurationFormValueOverridenKey, - KubernetesApplicationConfigurationFormValueOverridenKeyTypes, KubernetesApplicationEnvironmentVariableFormValue, KubernetesApplicationFormValues, KubernetesApplicationPersistedFolderFormValue, @@ -85,7 +82,6 @@ class KubernetesCreateApplicationController { this.ApplicationPublishingTypes = KubernetesApplicationPublishingTypes; this.ApplicationPlacementTypes = KubernetesApplicationPlacementTypes; this.ApplicationTypes = KubernetesApplicationTypes; - this.ApplicationConfigurationFormValueOverridenKeyTypes = KubernetesApplicationConfigurationFormValueOverridenKeyTypes; this.ServiceTypes = KubernetesServiceTypes; this.KubernetesDeploymentTypes = KubernetesDeploymentTypes; @@ -155,6 +151,8 @@ class KubernetesCreateApplicationController { this.onChangePlacementType = this.onChangePlacementType.bind(this); this.onServicesChange = this.onServicesChange.bind(this); this.onEnvironmentVariableChange = this.onEnvironmentVariableChange.bind(this); + this.onConfigMapsChange = this.onConfigMapsChange.bind(this); + this.onSecretsChange = this.onSecretsChange.bind(this); } /* #endregion */ @@ -240,34 +238,12 @@ class KubernetesCreateApplicationController { /* #endregion */ /* #region CONFIGMAP UI MANAGEMENT */ - addConfigMap() { - let config = new KubernetesApplicationConfigurationFormValue(); - config.SelectedConfiguration = this.configMaps[0]; - this.formValues.ConfigMaps.push(config); - } - - removeConfigMap(index) { - this.formValues.ConfigMaps.splice(index, 1); - this.onChangeConfigMapPath(); - } - - overrideConfigMap(index) { - const config = this.formValues.ConfigMaps[index]; - config.Overriden = true; - config.OverridenKeys = _.map(_.keys(config.SelectedConfiguration.Data), (key) => { - const res = new KubernetesApplicationConfigurationFormValueOverridenKey(); - res.Key = key; - return res; + onConfigMapsChange(configMaps) { + return this.$async(async () => { + this.formValues.ConfigMaps = configMaps; }); } - resetConfigMap(index) { - const config = this.formValues.ConfigMaps[index]; - config.Overriden = false; - config.OverridenKeys = []; - this.onChangeConfigMapPath(); - } - clearConfigMaps() { this.formValues.ConfigMaps = []; } @@ -278,7 +254,7 @@ class KubernetesCreateApplicationController { const paths = _.reduce( this.formValues.ConfigMaps, (result, config) => { - const uniqOverridenKeysPath = _.uniq(_.map(config.OverridenKeys, 'Path')); + const uniqOverridenKeysPath = _.uniq(_.map(config.overridenKeys, 'path')); return _.concat(result, uniqOverridenKeysPath); }, [] @@ -287,8 +263,8 @@ class KubernetesCreateApplicationController { const duplicatePaths = KubernetesFormValidationHelper.getDuplicates(paths); _.forEach(this.formValues.ConfigMaps, (config, index) => { - _.forEach(config.OverridenKeys, (overridenKey, keyIndex) => { - const findPath = _.find(duplicatePaths, (path) => path === overridenKey.Path); + _.forEach(config.overridenKeys, (overridenKey, keyIndex) => { + const findPath = _.find(duplicatePaths, (path) => path === overridenKey.path); if (findPath) { this.state.duplicates.configMapPaths.refs[index + '_' + keyIndex] = findPath; } @@ -300,63 +276,15 @@ class KubernetesCreateApplicationController { /* #endregion */ /* #region SECRET UI MANAGEMENT */ - addSecret() { - let secret = new KubernetesApplicationConfigurationFormValue(); - secret.SelectedConfiguration = this.secrets[0]; - this.formValues.Secrets.push(secret); - } - - removeSecret(index) { - this.formValues.Secrets.splice(index, 1); - this.onChangeSecretPath(); - } - - overrideSecret(index) { - const secret = this.formValues.Secrets[index]; - secret.Overriden = true; - secret.OverridenKeys = _.map(_.keys(secret.SelectedConfiguration.Data), (key) => { - const res = new KubernetesApplicationConfigurationFormValueOverridenKey(); - res.Key = key; - return res; + onSecretsChange(secrets) { + return this.$async(async () => { + this.formValues.Secrets = secrets; }); } - resetSecret(index) { - const secret = this.formValues.Secrets[index]; - secret.Overriden = false; - secret.OverridenKeys = []; - this.onChangeSecretPath(); - } - clearSecrets() { this.formValues.Secrets = []; } - - onChangeSecretPath() { - this.state.duplicates.secretPaths.refs = []; - - const paths = _.reduce( - this.formValues.Secrets, - (result, secret) => { - const uniqOverridenKeysPath = _.uniq(_.map(secret.OverridenKeys, 'Path')); - return _.concat(result, uniqOverridenKeysPath); - }, - [] - ); - - const duplicatePaths = KubernetesFormValidationHelper.getDuplicates(paths); - - _.forEach(this.formValues.Secrets, (secret, index) => { - _.forEach(secret.OverridenKeys, (overridenKey, keyIndex) => { - const findPath = _.find(duplicatePaths, (path) => path === overridenKey.Path); - if (findPath) { - this.state.duplicates.secretPaths.refs[index + '_' + keyIndex] = findPath; - } - }); - }); - - this.state.duplicates.secretPaths.hasRefs = Object.keys(this.state.duplicates.secretPaths.refs).length > 0; - } /* #endregion */ /* #region ENVIRONMENT UI MANAGEMENT */ @@ -920,7 +848,7 @@ class KubernetesCreateApplicationController { try { this.formValues.ApplicationOwner = this.Authentication.getUserDetails().username; // combine the secrets and configmap form values when submitting the form - _.remove(this.formValues.Configurations, (item) => item.SelectedConfiguration === undefined); + _.remove(this.formValues.Configurations, (item) => item.selectedConfiguration === undefined); await this.KubernetesApplicationService.create(this.formValues, this.originalServicePorts, this.deploymentOptions.hideStacksFunctionality); this.Notifications.success('Request to deploy application successfully submitted', this.formValues.Name); this.$state.go('kubernetes.applications'); diff --git a/app/react/components/form-components/InputList/InputList.tsx b/app/react/components/form-components/InputList/InputList.tsx index 61f588838..dce0818d0 100644 --- a/app/react/components/form-components/InputList/InputList.tsx +++ b/app/react/components/form-components/InputList/InputList.tsx @@ -71,7 +71,11 @@ interface Props { errors?: ArrayError; textTip?: string; isAddButtonHidden?: boolean; + addButtonDataCy?: string; + isDeleteButtonHidden?: boolean; + deleteButtonDataCy?: string; disabled?: boolean; + addButtonError?: string; readOnly?: boolean; 'aria-label'?: string; } @@ -91,12 +95,17 @@ export function InputList({ errors, textTip, isAddButtonHidden = false, + addButtonDataCy, + isDeleteButtonHidden = false, + deleteButtonDataCy, disabled, + addButtonError, readOnly, 'aria-label': ariaLabel, }: Props) { const initialItemsCount = useRef(value.length); const isAddButtonVisible = !(isAddButtonHidden || readOnly); + const isDeleteButtonVisible = !(isDeleteButtonHidden || readOnly); return (
{label && ( @@ -160,16 +169,17 @@ export function InputList({ /> )} - {!readOnly && !canUndoDelete && ( + {isDeleteButtonVisible && !canUndoDelete && ( -
+ <> +
+ +
+ {addButtonError && ( +
+ {addButtonError} +
+ )} + )}
); diff --git a/app/react/components/form-components/ReactSelect.tsx b/app/react/components/form-components/ReactSelect.tsx index 912dbc43b..bb460e141 100644 --- a/app/react/components/form-components/ReactSelect.tsx +++ b/app/react/components/form-components/ReactSelect.tsx @@ -10,6 +10,7 @@ import { RefAttributes } from 'react'; import ReactSelectType from 'react-select/dist/declarations/src/Select'; import './ReactSelect.css'; +import { AutomationTestingProps } from '@/types'; interface DefaultOption { value: string; @@ -25,7 +26,8 @@ type RegularProps< IsMulti, Group > & - RefAttributes>; + RefAttributes> & + AutomationTestingProps; type CreatableProps< Option = DefaultOption, @@ -35,7 +37,8 @@ type CreatableProps< Option, IsMulti, Group ->; +> & + AutomationTestingProps; type Props< Option = DefaultOption, diff --git a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationVolumeConfigsTable.tsx b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationVolumeConfigsTable.tsx index 1609c6e1e..45e43b9c7 100644 --- a/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationVolumeConfigsTable.tsx +++ b/app/react/kubernetes/applications/DetailsView/ApplicationDetailsWidget/ApplicationVolumeConfigsTable.tsx @@ -1,6 +1,9 @@ -import { KeyToPath, Pod } from 'kubernetes-types/core/v1'; +import { KeyToPath, Pod, Secret } from 'kubernetes-types/core/v1'; import { Asterisk, Plus } from 'lucide-react'; +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; +import { useSecrets } from '@/react/kubernetes/configs/secret.service'; + import { Icon } from '@@/Icon'; import { Link } from '@@/Link'; @@ -15,6 +18,8 @@ type Props = { export function ApplicationVolumeConfigsTable({ namespace, app }: Props) { const containerVolumeConfigs = getApplicationVolumeConfigs(app); + const { data: secrets } = useSecrets(useEnvironmentId(), namespace); + if (containerVolumeConfigs.length === 0) { return null; } @@ -71,10 +76,19 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) { {!item.key && '-'} - {volumeConfigName && ( + {isVolumeConfigNameFromSecret(secrets, volumeConfigName) ? ( + + {volumeConfigName} + + ) : ( + @@ -91,6 +105,13 @@ export function ApplicationVolumeConfigsTable({ namespace, app }: Props) { ); } +function isVolumeConfigNameFromSecret( + secrets?: Secret[], + volumeConfigName?: string +) { + return secrets?.some((secret) => secret.metadata?.name === volumeConfigName); +} + // getApplicationVolumeConfigs returns a list of volume configs / secrets for each container and each item within the matching volume function getApplicationVolumeConfigs(app?: Application) { if (!app) { diff --git a/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection.tsx b/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection.tsx new file mode 100644 index 000000000..33e68171e --- /dev/null +++ b/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigMapsFormSection.tsx @@ -0,0 +1,82 @@ +import { FormikErrors } from 'formik'; + +import { useEnvironmentId } from '@/react/hooks/useEnvironmentId'; +import { useConfigMaps } from '@/react/kubernetes/configs/configmap.service'; + +import { FormSection } from '@@/form-components/FormSection/FormSection'; +import { TextTip } from '@@/Tip/TextTip'; +import { InputList } from '@@/form-components/InputList'; +import { InlineLoader } from '@@/InlineLoader'; + +import { ConfigurationItem } from './ConfigurationItem'; +import { ConfigurationFormValues } from './types'; + +type Props = { + values: ConfigurationFormValues[]; + onChange: (values: ConfigurationFormValues[]) => void; + errors: FormikErrors; + namespace: string; +}; + +export function ConfigMapsFormSection({ + values, + onChange, + errors, + namespace, +}: Props) { + const configMapsQuery = useConfigMaps(useEnvironmentId(), namespace); + const configMaps = configMapsQuery.data || []; + + if (configMapsQuery.isLoading) { + return Loading ConfigMaps...; + } + + return ( + + {!!values.length && ( + + Portainer will automatically expose all the keys of a ConfigMap as + environment variables. This behavior can be overridden to filesystem + mounts for each key via the override option. + + )} + + + value={values} + onChange={onChange} + errors={errors} + isDeleteButtonHidden + deleteButtonDataCy="k8sAppCreate-configRemoveButton" + addButtonDataCy="k8sAppCreate-configAddButton" + disabled={configMaps.length === 0} + addButtonError={ + configMaps.length === 0 + ? 'There are no ConfigMaps available in this namespace.' + : undefined + } + renderItem={(item, onChange, index, error) => ( + onRemoveItem(index)} + index={index} + dataCyType="config" + /> + )} + itemBuilder={() => ({ + selectedConfigMap: configMaps[0]?.metadata?.name || '', + overriden: false, + overridenKeys: [], + selectedConfiguration: configMaps[0], + })} + addLabel="Add ConfigMap" + /> + + ); + + function onRemoveItem(index: number) { + onChange(values.filter((_, i) => i !== index)); + } +} diff --git a/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigurationItem.tsx b/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigurationItem.tsx new file mode 100644 index 000000000..9d8303838 --- /dev/null +++ b/app/react/kubernetes/applications/components/ConfigurationsFormSection/ConfigurationItem.tsx @@ -0,0 +1,153 @@ +import clsx from 'clsx'; +import { List, RotateCw, Trash2 } from 'lucide-react'; +import { ConfigMap, Secret } from 'kubernetes-types/core/v1'; +import { SingleValue } from 'react-select'; + +import { InputGroup } from '@@/form-components/InputGroup'; +import { Select } from '@@/form-components/ReactSelect'; +import { FormError } from '@@/form-components/FormError'; +import { ItemError } from '@@/form-components/InputList/InputList'; +import { isErrorType } from '@@/form-components/formikUtils'; +import { Button } from '@@/buttons'; +import { TextTip } from '@@/Tip/TextTip'; + +import { ConfigurationFormValues, ConfigurationOverrideKey } from './types'; +import { ConfigurationData } from './ConfigurationKey'; + +type Props = { + item: ConfigurationFormValues; + onChange: (values: ConfigurationFormValues) => void; + onRemoveItem: () => void; + configurations: Array; + index: number; + error?: ItemError; + dataCyType: 'config' | 'secret'; +}; + +export function ConfigurationItem({ + item, + onChange, + error, + configurations, + index, + onRemoveItem, + dataCyType, +}: Props) { + // rule out the error being of type string + const formikError = isErrorType(error) ? error : undefined; + const configurationData = item.selectedConfiguration.data || {}; + + return ( +
+
+
+ + Name +