@flightctl/ui-components 1.1.0-rc1 → 1.1.0-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
  2. package/dist/types/imagebuilder/index.d.ts +1 -0
  3. package/dist/types/imagebuilder/index.d.ts.map +1 -1
  4. package/dist/types/imagebuilder/models/Status.d.ts +30 -0
  5. package/dist/types/imagebuilder/models/Status.d.ts.map +1 -0
  6. package/dist/types/imagebuilder/models/Status.js +3 -0
  7. package/dist/types/imagebuilder/models/Status.js.map +1 -0
  8. package/dist/types/index.d.ts +7 -0
  9. package/dist/types/index.d.ts.map +1 -1
  10. package/dist/types/index.js.map +1 -1
  11. package/dist/types/models/ApplicationProviderBase.d.ts +12 -0
  12. package/dist/types/models/ApplicationProviderBase.d.ts.map +1 -0
  13. package/dist/types/models/ApplicationProviderBase.js +3 -0
  14. package/dist/types/models/ApplicationProviderBase.js.map +1 -0
  15. package/dist/types/models/ApplicationProviderSpec.d.ts +5 -15
  16. package/dist/types/models/ApplicationProviderSpec.d.ts.map +1 -1
  17. package/dist/types/models/ApplicationUser.d.ts +7 -0
  18. package/dist/types/models/ApplicationUser.d.ts.map +1 -0
  19. package/dist/types/models/ApplicationUser.js +3 -0
  20. package/dist/types/models/ApplicationUser.js.map +1 -0
  21. package/dist/types/models/ComposeApplication.d.ts +7 -0
  22. package/dist/types/models/ComposeApplication.d.ts.map +1 -0
  23. package/dist/types/models/ComposeApplication.js +3 -0
  24. package/dist/types/models/ComposeApplication.js.map +1 -0
  25. package/dist/types/models/ContainerApplication.d.ts +18 -0
  26. package/dist/types/models/ContainerApplication.d.ts.map +1 -0
  27. package/dist/types/models/ContainerApplication.js +3 -0
  28. package/dist/types/models/ContainerApplication.js.map +1 -0
  29. package/dist/types/models/ContainerApplicationProperties.d.ts +13 -0
  30. package/dist/types/models/ContainerApplicationProperties.d.ts.map +1 -0
  31. package/dist/types/models/ContainerApplicationProperties.js +3 -0
  32. package/dist/types/models/ContainerApplicationProperties.js.map +1 -0
  33. package/dist/types/models/HelmApplication.d.ts +20 -0
  34. package/dist/types/models/HelmApplication.d.ts.map +1 -0
  35. package/dist/types/models/HelmApplication.js +3 -0
  36. package/dist/types/models/HelmApplication.js.map +1 -0
  37. package/dist/types/models/ImageApplicationProviderSpec.d.ts +2 -22
  38. package/dist/types/models/ImageApplicationProviderSpec.d.ts.map +1 -1
  39. package/dist/types/models/InlineApplicationProviderSpec.d.ts +2 -3
  40. package/dist/types/models/InlineApplicationProviderSpec.d.ts.map +1 -1
  41. package/dist/types/models/QuadletApplication.d.ts +8 -0
  42. package/dist/types/models/QuadletApplication.d.ts.map +1 -0
  43. package/dist/types/models/QuadletApplication.js +3 -0
  44. package/dist/types/models/QuadletApplication.js.map +1 -0
  45. package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js +1 -1
  46. package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js.map +1 -1
  47. package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js +1 -1
  48. package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js.map +1 -1
  49. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
  50. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js +5 -4
  51. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
  52. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts +3 -3
  53. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts.map +1 -1
  54. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js +308 -363
  55. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js.map +1 -1
  56. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts +1 -3
  57. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts.map +1 -1
  58. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js +18 -19
  59. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js.map +1 -1
  60. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts +1 -3
  61. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts.map +1 -1
  62. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js +4 -3
  63. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js.map +1 -1
  64. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts +1 -3
  65. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts.map +1 -1
  66. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js +2 -2
  67. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js.map +1 -1
  68. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts +3 -3
  69. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts.map +1 -1
  70. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js +20 -23
  71. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js.map +1 -1
  72. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js +3 -3
  73. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js.map +1 -1
  74. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.d.ts.map +1 -1
  75. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js +25 -45
  76. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js.map +1 -1
  77. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts +8 -0
  78. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts.map +1 -0
  79. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js +37 -0
  80. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js.map +1 -0
  81. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts +1 -3
  82. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts.map +1 -1
  83. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js +5 -8
  84. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js.map +1 -1
  85. package/dist/ui-components/src/components/Device/EditDeviceWizard/utils.d.ts +18 -18
  86. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js +2 -2
  87. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js.map +1 -1
  88. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.d.ts.map +1 -1
  89. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js +3 -1
  90. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js.map +1 -1
  91. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts +7 -0
  92. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts.map +1 -0
  93. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js +40 -0
  94. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js.map +1 -0
  95. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts +8 -0
  96. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts.map +1 -0
  97. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js +30 -0
  98. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js.map +1 -0
  99. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js +2 -2
  100. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js.map +1 -1
  101. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.d.ts.map +1 -1
  102. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js +22 -11
  103. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js.map +1 -1
  104. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.d.ts.map +1 -1
  105. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js +103 -36
  106. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js.map +1 -1
  107. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts +5 -2
  108. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts.map +1 -1
  109. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js +22 -12
  110. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js.map +1 -1
  111. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.d.ts.map +1 -1
  112. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js +17 -8
  113. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js.map +1 -1
  114. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts +10 -9
  115. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts.map +1 -1
  116. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js +109 -25
  117. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js.map +1 -1
  118. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.d.ts.map +1 -1
  119. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js +1 -1
  120. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js.map +1 -1
  121. package/dist/ui-components/src/components/Repository/CreateRepository/utils.js +3 -3
  122. package/dist/ui-components/src/components/Repository/CreateRepository/utils.js.map +1 -1
  123. package/dist/ui-components/src/components/form/validations.d.ts +20 -18
  124. package/dist/ui-components/src/components/form/validations.d.ts.map +1 -1
  125. package/dist/ui-components/src/components/form/validations.js +40 -11
  126. package/dist/ui-components/src/components/form/validations.js.map +1 -1
  127. package/dist/ui-components/src/constants.d.ts +7 -6
  128. package/dist/ui-components/src/constants.d.ts.map +1 -1
  129. package/dist/ui-components/src/constants.js +19 -11
  130. package/dist/ui-components/src/constants.js.map +1 -1
  131. package/dist/ui-components/src/types/deviceSpec.d.ts +44 -76
  132. package/dist/ui-components/src/types/deviceSpec.d.ts.map +1 -1
  133. package/dist/ui-components/src/types/deviceSpec.js +13 -26
  134. package/dist/ui-components/src/types/deviceSpec.js.map +1 -1
  135. package/dist/ui-components/src/types/extraTypes.d.ts +1 -7
  136. package/dist/ui-components/src/types/extraTypes.d.ts.map +1 -1
  137. package/dist/ui-components/src/types/extraTypes.js.map +1 -1
  138. package/dist/ui-components/src/types/rbac.d.ts +7 -1
  139. package/dist/ui-components/src/types/rbac.d.ts.map +1 -1
  140. package/dist/ui-components/src/types/rbac.js +6 -0
  141. package/dist/ui-components/src/types/rbac.js.map +1 -1
  142. package/dist/ui-components/src/utils/imageBuilds.d.ts +1 -0
  143. package/dist/ui-components/src/utils/imageBuilds.d.ts.map +1 -1
  144. package/dist/ui-components/src/utils/imageBuilds.js +7 -1
  145. package/dist/ui-components/src/utils/imageBuilds.js.map +1 -1
  146. package/dist/ui-components/src/utils/search.js +1 -1
  147. package/dist/ui-components/src/utils/search.js.map +1 -1
  148. package/package.json +1 -1
  149. package/src/components/AuthProvider/CreateAuthProvider/utils.ts +2 -2
  150. package/src/components/DetailsPage/Tables/ApplicationsTable.tsx +2 -2
  151. package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +10 -4
  152. package/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts +359 -425
  153. package/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx +19 -29
  154. package/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx +4 -12
  155. package/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx +2 -16
  156. package/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.tsx +8 -7
  157. package/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.tsx +5 -5
  158. package/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx +29 -101
  159. package/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx +87 -0
  160. package/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx +5 -10
  161. package/src/components/Fleet/CreateFleet/utils.ts +4 -4
  162. package/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.tsx +11 -8
  163. package/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.tsx +81 -0
  164. package/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.tsx +48 -0
  165. package/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts +3 -3
  166. package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +35 -16
  167. package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.tsx +120 -42
  168. package/src/components/ImageBuilds/ImageBuildRow.tsx +41 -20
  169. package/src/components/ImageBuilds/ImageBuildsPage.tsx +34 -15
  170. package/src/components/ImageBuilds/ImageExportCards.tsx +176 -77
  171. package/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
  172. package/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx +1 -0
  173. package/src/components/Repository/CreateRepository/utils.ts +4 -4
  174. package/src/components/form/validations.ts +112 -82
  175. package/src/constants.ts +19 -6
  176. package/src/types/deviceSpec.ts +68 -108
  177. package/src/types/extraTypes.ts +2 -12
  178. package/src/types/rbac.ts +6 -0
  179. package/src/utils/imageBuilds.ts +8 -0
  180. package/src/utils/search.ts +2 -2
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { useField } from 'formik';
2
+ import { useField, useFormikContext } from 'formik';
3
3
  import { Button, FormGroup, Grid, Label, LabelGroup, Split, SplitItem, TextInput } from '@patternfly/react-core';
4
4
  import { ArrowRightIcon } from '@patternfly/react-icons/dist/js/icons/arrow-right-icon';
5
5
 
@@ -9,22 +9,15 @@ import ErrorHelperText from '../../../form/FieldHelperText';
9
9
  import { isDuplicatePortMapping, isValidPortMapping, validatePortNumber } from '../../../form/validations';
10
10
  import { useTranslation } from '../../../../hooks/useTranslation';
11
11
  import { PortMapping, SingleContainerAppForm } from '../../../../types/deviceSpec';
12
- import ApplicationIntegritySettings from './ApplicationIntegritySettings';
13
12
 
14
13
  import './ApplicationContainerForm.css';
15
14
 
16
- const ApplicationContainerForm = ({
17
- app,
18
- index,
19
- isReadOnly,
20
- }: {
21
- app: SingleContainerAppForm;
22
- index: number;
23
- isReadOnly?: boolean;
24
- }) => {
15
+ const ApplicationContainerForm = ({ index, isReadOnly }: { index: number; isReadOnly?: boolean }) => {
25
16
  const { t } = useTranslation();
26
17
  const appFieldName = `applications[${index}]`;
27
- const [{ value: ports }, , { setValue: setPorts, setTouched }] = useField<PortMapping[]>(`${appFieldName}.ports`);
18
+ const [{ value: app }] = useField<SingleContainerAppForm>(`${appFieldName}`);
19
+ const { setFieldValue, setFieldTouched } = useFormikContext();
20
+ const ports = React.useMemo(() => app.ports || [], [app.ports]);
28
21
 
29
22
  const [hostPort, setHostPort] = React.useState('');
30
23
  const [containerPort, setContainerPort] = React.useState('');
@@ -33,7 +26,7 @@ const ApplicationContainerForm = ({
33
26
  const [editingPortIndex, setEditingPortIndex] = React.useState<number | null>(null);
34
27
  const [editingPortError, setEditingPortError] = React.useState<string | undefined>(undefined);
35
28
 
36
- const canAddPorts = !editingPortError && isValidPortMapping(hostPort, containerPort, ports || []) && !isReadOnly;
29
+ const canAddPorts = !editingPortError && isValidPortMapping(hostPort, containerPort, ports) && !isReadOnly;
37
30
 
38
31
  const validatePort = (port: string): string | undefined => {
39
32
  const error = validatePortNumber(port, t);
@@ -41,7 +34,7 @@ const ApplicationContainerForm = ({
41
34
  return error;
42
35
  }
43
36
  // Check for duplicate if both ports match the number pattern
44
- if (isDuplicatePortMapping(port, containerPort, ports || [])) {
37
+ if (isDuplicatePortMapping(port, containerPort, ports)) {
45
38
  return t('This port mapping already exists');
46
39
  }
47
40
  return undefined;
@@ -51,13 +44,13 @@ const ApplicationContainerForm = ({
51
44
  const containerPortError = containerPortTouched ? validatePort(containerPort) : undefined;
52
45
 
53
46
  const updatePorts = async (newPorts: PortMapping[]) => {
54
- await setPorts(newPorts, true);
55
- setTouched(true);
47
+ await setFieldValue(`${appFieldName}.ports`, newPorts, true);
48
+ setFieldTouched(`${appFieldName}.ports`, true);
56
49
  };
57
50
 
58
51
  const onAddPort = () => {
59
- if (isValidPortMapping(hostPort, containerPort, ports || [])) {
60
- updatePorts([...(ports || []), { hostPort, containerPort }]);
52
+ if (isValidPortMapping(hostPort, containerPort, ports)) {
53
+ updatePorts([...ports, { hostPort, containerPort }]);
61
54
  setHostPort('');
62
55
  setContainerPort('');
63
56
  setHostPortTouched(false);
@@ -68,7 +61,7 @@ const ApplicationContainerForm = ({
68
61
  };
69
62
 
70
63
  const onDeletePort = async (index: number) => {
71
- const newPorts = [...(ports || [])];
64
+ const newPorts = [...ports];
72
65
  newPorts.splice(index, 1);
73
66
  await updatePorts(newPorts);
74
67
  // Clear error state if the deleted port was being edited
@@ -107,7 +100,7 @@ const ApplicationContainerForm = ({
107
100
  }
108
101
 
109
102
  // Check for duplicates, excluding the current port being edited
110
- const otherPorts = excludeIndex !== undefined ? (ports || []).filter((_, i) => i !== excludeIndex) : ports || [];
103
+ const otherPorts = excludeIndex !== undefined ? ports.filter((_, i) => i !== excludeIndex) : ports;
111
104
  if (isDuplicatePortMapping(hostPortValue, containerPortValue, otherPorts)) {
112
105
  return t('This port mapping already exists');
113
106
  }
@@ -127,7 +120,7 @@ const ApplicationContainerForm = ({
127
120
  return;
128
121
  }
129
122
 
130
- const newPorts = [...(ports || [])];
123
+ const newPorts = [...ports];
131
124
  newPorts[index] = { hostPort: newHostPort || '', containerPort: newContainerPort || '' };
132
125
  await updatePorts(newPorts);
133
126
 
@@ -146,7 +139,7 @@ const ApplicationContainerForm = ({
146
139
 
147
140
  // Clear error state if the editing index becomes invalid (e.g., port was deleted externally)
148
141
  React.useEffect(() => {
149
- if (editingPortIndex !== null && (!ports || editingPortIndex >= ports.length)) {
142
+ if (editingPortIndex !== null && editingPortIndex >= ports.length) {
150
143
  setEditingPortIndex(null);
151
144
  setEditingPortError(undefined);
152
145
  }
@@ -164,7 +157,6 @@ const ApplicationContainerForm = ({
164
157
  <FormGroupWithHelperText
165
158
  label={t('Application name')}
166
159
  content={t('If not specified, the image name will be used. Application name must be unique.')}
167
- isRequired
168
160
  >
169
161
  <TextField aria-label={t('Application name')} name={`${appFieldName}.name`} isDisabled={isReadOnly} />
170
162
  </FormGroupWithHelperText>
@@ -172,7 +164,6 @@ const ApplicationContainerForm = ({
172
164
  <TextField
173
165
  aria-label={t('Image')}
174
166
  name={`${appFieldName}.image`}
175
- value={app.image || ''}
176
167
  isDisabled={isReadOnly}
177
168
  helperText={t('Provide a valid image reference')}
178
169
  />
@@ -271,8 +262,8 @@ const ApplicationContainerForm = ({
271
262
  >
272
263
  <TextField
273
264
  aria-label={t('CPU limit')}
274
- name={`${appFieldName}.limits.cpu`}
275
- value={app.limits?.cpu || ''}
265
+ name={`${appFieldName}.cpuLimit`}
266
+ value={app.cpuLimit || ''}
276
267
  placeholder={t('Enter numeric value')}
277
268
  isDisabled={isReadOnly}
278
269
  helperText={t('Provide a valid CPU value (e.g., "0.4" or "2").')}
@@ -286,8 +277,8 @@ const ApplicationContainerForm = ({
286
277
  >
287
278
  <TextField
288
279
  aria-label={t('Memory limit')}
289
- name={`${appFieldName}.limits.memory`}
290
- value={app.limits?.memory || ''}
280
+ name={`${appFieldName}.memoryLimit`}
281
+ value={app.memoryLimit || ''}
291
282
  placeholder={t('Enter numeric value with optional unit')}
292
283
  isDisabled={isReadOnly}
293
284
  helperText={t('Provide a valid memory value (e.g., "512", "512m", "2g", "1024k").')}
@@ -295,7 +286,6 @@ const ApplicationContainerForm = ({
295
286
  </FormGroupWithHelperText>
296
287
  </Grid>
297
288
  </FormGroup>
298
- <ApplicationIntegritySettings index={index} isReadOnly={isReadOnly} />
299
289
  </Grid>
300
290
  );
301
291
  };
@@ -8,20 +8,13 @@ import { FormGroupWithHelperText } from '../../../common/WithHelperText';
8
8
  import TextField from '../../../form/TextField';
9
9
  import UploadField from '../../../form/UploadField';
10
10
  import { useTranslation } from '../../../../hooks/useTranslation';
11
- import { AppSpecType, HelmImageAppForm } from '../../../../types/deviceSpec';
11
+ import { AppSpecType, HelmAppForm } from '../../../../types/deviceSpec';
12
12
 
13
- const ApplicationHelmForm = ({
14
- app,
15
- index,
16
- isReadOnly,
17
- }: {
18
- app: HelmImageAppForm;
19
- index: number;
20
- isReadOnly?: boolean;
21
- }) => {
13
+ const ApplicationHelmForm = ({ index, isReadOnly }: { index: number; isReadOnly?: boolean }) => {
22
14
  const { t } = useTranslation();
23
15
  const appFieldName = `applications[${index}]`;
24
- const [{ value: valuesFiles }] = useField<Array<string>>(`${appFieldName}.valuesFiles`);
16
+ const [{ value: app }] = useField<HelmAppForm>(`${appFieldName}`);
17
+ const valuesFiles = app.valuesFiles || [];
25
18
  const canAddValuesFile = valuesFiles && valuesFiles.every((file) => file && file.trim() !== '');
26
19
 
27
20
  return (
@@ -48,7 +41,6 @@ const ApplicationHelmForm = ({
48
41
  <TextField
49
42
  aria-label={t('Image')}
50
43
  name={`${appFieldName}.image`}
51
- value={app.image || ''}
52
44
  isDisabled={isReadOnly}
53
45
  helperText={t('Provide a valid image reference')}
54
46
  />
@@ -6,17 +6,8 @@ import TextField from '../../../form/TextField';
6
6
  import LearnMoreLink from '../../../common/LearnMoreLink';
7
7
  import { useTranslation } from '../../../../hooks/useTranslation';
8
8
  import { useAppLinks } from '../../../../hooks/useAppLinks';
9
- import { ComposeImageAppForm, QuadletImageAppForm } from '../../../../types/deviceSpec';
10
9
 
11
- const ApplicationImageForm = ({
12
- app,
13
- index,
14
- isReadOnly,
15
- }: {
16
- app: QuadletImageAppForm | ComposeImageAppForm;
17
- index: number;
18
- isReadOnly?: boolean;
19
- }) => {
10
+ const ApplicationImageForm = ({ index, isReadOnly }: { index: number; isReadOnly?: boolean }) => {
20
11
  const { t } = useTranslation();
21
12
  const createAppLink = useAppLinks('createApp');
22
13
 
@@ -32,12 +23,7 @@ const ApplicationImageForm = ({
32
23
  }
33
24
  isRequired
34
25
  >
35
- <TextField
36
- aria-label={t('Image')}
37
- name={`applications.${index}.image`}
38
- value={app.image || ''}
39
- isDisabled={isReadOnly}
40
- />
26
+ <TextField aria-label={t('Image')} name={`applications.${index}.image`} isDisabled={isReadOnly} />
41
27
  </FormGroupWithHelperText>
42
28
  </Grid>
43
29
  );
@@ -9,12 +9,12 @@ import CheckboxField from '../../../form/CheckboxField';
9
9
  import UploadField from '../../../form/UploadField';
10
10
  import TextField from '../../../form/TextField';
11
11
  import ExpandableFormSection from '../../../form/ExpandableFormSection';
12
- import { ComposeInlineAppForm, QuadletInlineAppForm } from '../../../../types/deviceSpec';
12
+ import { InlineFileForm } from '../../../../types/deviceSpec';
13
13
 
14
14
  const MAX_INLINE_FILE_SIZE_BYTES = 1024 * 1024;
15
15
 
16
16
  type InlineApplicationFileFormProps = {
17
- file: (QuadletInlineAppForm | ComposeInlineAppForm)['files'][0];
17
+ file: InlineFileForm;
18
18
  fileFieldName: string;
19
19
  fileIndex: number;
20
20
  isReadOnly?: boolean;
@@ -55,17 +55,18 @@ const InlineApplicationFileForm = ({ file, fileIndex, fileFieldName, isReadOnly
55
55
  };
56
56
 
57
57
  const ApplicationInlineForm = ({
58
- app,
58
+ files,
59
59
  index,
60
60
  isReadOnly,
61
61
  }: {
62
- app: QuadletInlineAppForm | ComposeInlineAppForm;
62
+ files: InlineFileForm[];
63
63
  index: number;
64
64
  isReadOnly?: boolean;
65
65
  }) => {
66
66
  const { t } = useTranslation();
67
67
 
68
- if (isReadOnly && !app.files?.length) {
68
+ const fileList = files || [];
69
+ if (isReadOnly && fileList.length === 0) {
69
70
  return null;
70
71
  }
71
72
 
@@ -74,7 +75,7 @@ const ApplicationInlineForm = ({
74
75
  <FieldArray name={`applications.${index}.files`}>
75
76
  {({ push, remove }) => (
76
77
  <>
77
- {app.files?.map((file, fileIndex) => {
78
+ {fileList.map((file, fileIndex) => {
78
79
  const fieldName = `applications[${index}].files[${fileIndex}]`;
79
80
  return (
80
81
  <Split key={fileIndex} hasGutter>
@@ -86,7 +87,7 @@ const ApplicationInlineForm = ({
86
87
  isReadOnly={isReadOnly}
87
88
  />
88
89
  </SplitItem>
89
- {!isReadOnly && app.files.length > 1 && (
90
+ {!isReadOnly && fileList.length > 1 && (
90
91
  <SplitItem>
91
92
  <Button
92
93
  aria-label={t('Delete file')}
@@ -5,7 +5,7 @@ import { Content, FormGroup, FormSection, Switch } from '@patternfly/react-core'
5
5
  import { useTranslation } from '../../../../hooks/useTranslation';
6
6
  import TextField from '../../../form/TextField';
7
7
  import { DefaultHelperText } from '../../../form/FieldHelperText';
8
- import { RUN_AS_DEFAULT_USER, RUN_AS_ROOT_USER } from '../../../../types/deviceSpec';
8
+ import { RUN_AS_FLIGHTCTL_USER, RUN_AS_ROOT_USER } from '../../../../types/deviceSpec';
9
9
 
10
10
  type ApplicationIntegritySettingsProps = {
11
11
  index: number;
@@ -27,7 +27,7 @@ const ApplicationIntegritySettings = ({ index, isReadOnly }: ApplicationIntegrit
27
27
  label={isRootless ? t('System integrity protection enabled') : t('System integrity protection disabled')}
28
28
  isChecked={isRootless}
29
29
  onChange={async (_, checked) => {
30
- await setRunAs(checked ? RUN_AS_DEFAULT_USER : RUN_AS_ROOT_USER);
30
+ await setRunAs(checked ? RUN_AS_FLIGHTCTL_USER : RUN_AS_ROOT_USER);
31
31
  }}
32
32
  isDisabled={isReadOnly}
33
33
  />
@@ -46,13 +46,13 @@ const ApplicationIntegritySettings = ({ index, isReadOnly }: ApplicationIntegrit
46
46
  <TextField
47
47
  aria-label={t('Rootless user identity')}
48
48
  name={`${appFieldName}.runAs`}
49
- value={runAs || RUN_AS_DEFAULT_USER}
49
+ value={runAs}
50
50
  isDisabled
51
51
  readOnly
52
52
  helperText={t(
53
- "By default, workloads run as the '{{ runAsUser }}' user. To specify a custom user identity, edit the application configuration via YAML or CLI.",
53
+ "The recommended user identity is '{{ runAsUser }}'. To specify a custom user identity, edit the application configuration via YAML or CLI.",
54
54
  {
55
- runAsUser: RUN_AS_DEFAULT_USER,
55
+ runAsUser: RUN_AS_FLIGHTCTL_USER,
56
56
  },
57
57
  )}
58
58
  />
@@ -16,23 +16,12 @@ import { MinusCircleIcon } from '@patternfly/react-icons/dist/js/icons/minus-cir
16
16
  import { PlusCircleIcon } from '@patternfly/react-icons/dist/js/icons/plus-circle-icon';
17
17
 
18
18
  import { AppType } from '@flightctl/types';
19
- import {
20
- AppForm,
21
- AppSpecType,
22
- DeviceSpecConfigFormValues,
23
- isComposeImageAppForm,
24
- isComposeInlineAppForm,
25
- isHelmImageAppForm,
26
- isQuadletImageAppForm,
27
- isQuadletInlineAppForm,
28
- isSingleContainerAppForm,
29
- } from '../../../../types/deviceSpec';
19
+ import { AppForm, AppSpecType, DeviceSpecConfigFormValues } from '../../../../types/deviceSpec';
30
20
  import { createInitialAppForm } from '../deviceSpecUtils';
31
21
  import { useTranslation } from '../../../../hooks/useTranslation';
32
22
  import TextField from '../../../form/TextField';
33
23
  import FormSelect from '../../../form/FormSelect';
34
24
  import RadioField from '../../../form/RadioField';
35
- import ErrorHelperText from '../../../form/FieldHelperText';
36
25
  import ExpandableFormSection from '../../../form/ExpandableFormSection';
37
26
  import { FormGroupWithHelperText } from '../../../common/WithHelperText';
38
27
  import { appTypeOptions } from '../../../../utils/apps';
@@ -41,6 +30,7 @@ import ApplicationInlineForm from './ApplicationInlineForm';
41
30
  import ApplicationContainerForm from './ApplicationContainerForm';
42
31
  import ApplicationHelmForm from './ApplicationHelmForm';
43
32
  import ApplicationVolumeForm from './ApplicationVolumeForm';
33
+ import ApplicationVariablesForm from './ApplicationVariablesForm';
44
34
  import ApplicationIntegritySettings from './ApplicationIntegritySettings';
45
35
 
46
36
  import './ApplicationsForm.css';
@@ -48,31 +38,30 @@ import './ApplicationsForm.css';
48
38
  const ApplicationSection = ({ index, isReadOnly }: { index: number; isReadOnly?: boolean }) => {
49
39
  const { t } = useTranslation();
50
40
  const appFieldName = `applications[${index}]`;
51
- const [{ value: app }, { error }, { setValue }] = useField<AppForm>(appFieldName);
41
+ const [{ value: app }, , { setValue }] = useField<AppForm>(appFieldName);
52
42
  const { appType, specType, name: appName } = app;
53
43
 
54
- const isContainer = isSingleContainerAppForm(app);
55
- const isHelm = isHelmImageAppForm(app);
56
- const isQuadlet = isQuadletImageAppForm(app) || isQuadletInlineAppForm(app);
57
- const isImageIncomplete = !isContainer && specType === AppSpecType.OCI_IMAGE && !('image' in app);
58
- const isInlineIncomplete = !isContainer && specType === AppSpecType.INLINE && !('files' in app);
59
- const isContainerIncomplete = isContainer && (!('ports' in app) || !('volumes' in app));
60
- const isHelmIncomplete = isHelm && !('valuesFiles' in app);
44
+ const isContainer = app.appType === AppType.AppTypeContainer;
45
+ const isHelm = app.appType === AppType.AppTypeHelm;
46
+ const isQuadlet = app.appType === AppType.AppTypeQuadlet;
47
+ const isCompose = app.appType === AppType.AppTypeCompose;
61
48
 
62
- const shouldResetApp = isInlineIncomplete || isImageIncomplete || isContainerIncomplete || isHelmIncomplete;
49
+ // Each AppForm type has all data structures it needs initialized with safe defaults (eg. empty arrays, etc).
50
+ // However, when the user switches to a type that doesn't have those fields, we must reset the app to define the missing fields.
51
+ const isContainerIncomplete = isContainer && !('ports' in app);
52
+ const isHelmIncomplete = isHelm && !('valuesFiles' in app);
53
+ const isQuadletComposeIncomplete = (isQuadlet || isCompose) && !('volumes' in app);
63
54
 
64
- // @ts-expect-error Formik error object includes "variables"
65
- const appVarsError = typeof error?.variables === 'string' ? (error.variables as string) : undefined; // eslint-disable @typescript-eslint/no-unsafe-assignment
55
+ const shouldResetApp = isContainerIncomplete || isHelmIncomplete || isQuadletComposeIncomplete;
66
56
 
67
57
  const appTypesOptions = appTypeOptions(t);
68
58
 
69
59
  React.useEffect(() => {
70
- // When switching types we must ensure all mandatory fields are initialized for the new type
71
60
  if (shouldResetApp) {
72
- const app = createInitialAppForm(appType, specType, appName || '');
73
- setValue(app, false);
61
+ const initialApp = createInitialAppForm(appType, appName || '');
62
+ setValue(initialApp, false);
74
63
  }
75
- }, [shouldResetApp, specType, appType, appName, setValue]);
64
+ }, [shouldResetApp, appType, appName, setValue]);
76
65
 
77
66
  return (
78
67
  <ExpandableFormSection
@@ -90,9 +79,9 @@ const ApplicationSection = ({ index, isReadOnly }: { index: number; isReadOnly?:
90
79
  </FormGroup>
91
80
 
92
81
  {isContainer ? (
93
- <ApplicationContainerForm app={app} index={index} isReadOnly={isReadOnly} />
82
+ <ApplicationContainerForm index={index} isReadOnly={isReadOnly} />
94
83
  ) : isHelm ? (
95
- <ApplicationHelmForm app={app} index={index} isReadOnly={isReadOnly} />
84
+ <ApplicationHelmForm index={index} isReadOnly={isReadOnly} />
96
85
  ) : (
97
86
  <>
98
87
  <FormGroupWithHelperText
@@ -124,8 +113,8 @@ const ApplicationSection = ({ index, isReadOnly }: { index: number; isReadOnly?:
124
113
  id={`${appFieldName}-spec-type-image`}
125
114
  name={`${appFieldName}.specType`}
126
115
  label={t('OCI reference URL')}
127
- checkedValue={AppSpecType.OCI_IMAGE}
128
116
  isDisabled={isReadOnly}
117
+ checkedValue={AppSpecType.OCI_IMAGE}
129
118
  />
130
119
  </SplitItem>
131
120
  <SplitItem>
@@ -133,8 +122,8 @@ const ApplicationSection = ({ index, isReadOnly }: { index: number; isReadOnly?:
133
122
  id={`${appFieldName}-spec-type-inline`}
134
123
  name={`${appFieldName}.specType`}
135
124
  label={t('Inline')}
136
- checkedValue={AppSpecType.INLINE}
137
125
  isDisabled={isReadOnly}
126
+ checkedValue={AppSpecType.INLINE}
138
127
  />
139
128
  </SplitItem>{' '}
140
129
  </Split>
@@ -143,93 +132,32 @@ const ApplicationSection = ({ index, isReadOnly }: { index: number; isReadOnly?:
143
132
  <FormGroupWithHelperText
144
133
  label={t('Application name')}
145
134
  content={
146
- app.specType === AppSpecType.INLINE
135
+ specType === AppSpecType.INLINE
147
136
  ? t('The unique identifier for this application.')
148
137
  : t('If not specified, the image name will be used. Application name must be unique.')
149
138
  }
150
- isRequired={app.specType === AppSpecType.INLINE}
139
+ isRequired={specType === AppSpecType.INLINE}
151
140
  >
152
141
  <TextField aria-label={t('Application name')} name={`${appFieldName}.name`} isDisabled={isReadOnly} />
153
142
  </FormGroupWithHelperText>
154
143
 
155
- {(isQuadletImageAppForm(app) || isComposeImageAppForm(app)) && (
156
- <ApplicationImageForm app={app} index={index} isReadOnly={isReadOnly} />
144
+ {specType === AppSpecType.OCI_IMAGE && <ApplicationImageForm index={index} isReadOnly={isReadOnly} />}
145
+ {specType === AppSpecType.INLINE && (
146
+ <ApplicationInlineForm files={app.files || []} index={index} isReadOnly={isReadOnly} />
157
147
  )}
158
- {(isQuadletInlineAppForm(app) || isComposeInlineAppForm(app)) && (
159
- <ApplicationInlineForm app={app} index={index} isReadOnly={isReadOnly} />
160
- )}
161
- {isQuadlet && <ApplicationIntegritySettings index={index} isReadOnly={isReadOnly} />}
162
148
  </>
163
149
  )}
164
150
 
151
+ {(isQuadlet || isContainer) && <ApplicationIntegritySettings index={index} isReadOnly={isReadOnly} />}
152
+
165
153
  {!isHelm && (
166
154
  <>
167
155
  <ApplicationVolumeForm
168
156
  appFieldName={appFieldName}
169
- volumes={app.volumes || []}
170
157
  isReadOnly={isReadOnly}
171
158
  isSingleContainerApp={isContainer}
172
159
  />
173
- <FieldArray name={`${appFieldName}.variables`}>
174
- {({ push, remove }) => (
175
- <>
176
- {app.variables?.map((variable, varIndex) => (
177
- <Split hasGutter key={varIndex}>
178
- <SplitItem className="fctl-application-template__variable-name">
179
- <FormGroup label={t('Variable {{ number }}', { number: varIndex + 1 })} />
180
- </SplitItem>
181
- <SplitItem>
182
- <FormGroup label={t('Name')} isRequired>
183
- <TextField
184
- aria-label={t('Name')}
185
- name={`${appFieldName}.variables.${varIndex}.name`}
186
- value={variable.name}
187
- isDisabled={isReadOnly}
188
- />
189
- </FormGroup>
190
- </SplitItem>
191
- <SplitItem isFilled>
192
- <FormGroup label={t('Value')} isRequired>
193
- <TextField
194
- aria-label={t('Value')}
195
- name={`${appFieldName}.variables.${varIndex}.value`}
196
- value={variable.value}
197
- isDisabled={isReadOnly}
198
- />
199
- </FormGroup>
200
- </SplitItem>
201
- {!isReadOnly && (
202
- <SplitItem>
203
- <Button
204
- aria-label={t('Delete variable')}
205
- variant="link"
206
- icon={<MinusCircleIcon />}
207
- iconPosition="end"
208
- onClick={() => remove(varIndex)}
209
- />
210
- </SplitItem>
211
- )}
212
- </Split>
213
- ))}
214
- <ErrorHelperText error={appVarsError} />
215
- {!isReadOnly && (
216
- <FormGroup>
217
- <Button
218
- variant="link"
219
- style={{ paddingInline: 0 }}
220
- icon={<PlusCircleIcon />}
221
- iconPosition="start"
222
- onClick={() => {
223
- push({ name: '', value: '' });
224
- }}
225
- >
226
- {t('Add an application variable')}
227
- </Button>
228
- </FormGroup>
229
- )}
230
- </>
231
- )}
232
- </FieldArray>
160
+ <ApplicationVariablesForm appFieldName={appFieldName} isReadOnly={isReadOnly} />
233
161
  </>
234
162
  )}
235
163
  </Grid>
@@ -287,7 +215,7 @@ const ApplicationTemplates = ({ isReadOnly }: { isReadOnly?: boolean }) => {
287
215
  icon={<PlusCircleIcon />}
288
216
  iconPosition="start"
289
217
  onClick={() => {
290
- push(createInitialAppForm(AppType.AppTypeContainer, AppSpecType.OCI_IMAGE));
218
+ push(createInitialAppForm(AppType.AppTypeContainer));
291
219
  }}
292
220
  >
293
221
  {t('Add application')}
@@ -0,0 +1,87 @@
1
+ import * as React from 'react';
2
+ import { FieldArray, useField } from 'formik';
3
+ import { Button, FormGroup, FormSection, Grid, Split, SplitItem } from '@patternfly/react-core';
4
+ import { MinusCircleIcon } from '@patternfly/react-icons/dist/js/icons/minus-circle-icon';
5
+ import { PlusCircleIcon } from '@patternfly/react-icons/dist/js/icons/plus-circle-icon';
6
+
7
+ import { VariablesForm } from '../../../../types/deviceSpec';
8
+ import { useTranslation } from '../../../../hooks/useTranslation';
9
+ import TextField from '../../../form/TextField';
10
+ import ErrorHelperText from '../../../form/FieldHelperText';
11
+
12
+ type ApplicationVariablesFormProps = {
13
+ appFieldName: string;
14
+ isReadOnly?: boolean;
15
+ };
16
+
17
+ const ApplicationVariablesForm = ({ appFieldName, isReadOnly }: ApplicationVariablesFormProps) => {
18
+ const { t } = useTranslation();
19
+ const [{ value: variables = [] }, { error }] = useField<VariablesForm>(`${appFieldName}.variables`);
20
+
21
+ // @ts-expect-error Formik error object includes "variables"
22
+ const appVarsError = typeof error?.variables === 'string' ? (error.variables as string) : undefined;
23
+
24
+ return (
25
+ <FormGroup label={t('Environment variables')} className="fctl-application-variables-form">
26
+ <FieldArray name={`${appFieldName}.variables`}>
27
+ {({ push, remove }) => (
28
+ <>
29
+ {variables.map((_variable, variableIndex) => {
30
+ const variableFieldName = `${appFieldName}.variables[${variableIndex}]`;
31
+ return (
32
+ <FormSection key={variableIndex}>
33
+ <Split hasGutter>
34
+ <SplitItem isFilled>
35
+ <Grid hasGutter>
36
+ <FormGroup label={t('Name')} isRequired>
37
+ <TextField
38
+ name={`${variableFieldName}.name`}
39
+ aria-label={t('Variable name')}
40
+ isDisabled={isReadOnly}
41
+ />
42
+ </FormGroup>
43
+ <FormGroup label={t('Value')} isRequired>
44
+ <TextField
45
+ name={`${variableFieldName}.value`}
46
+ aria-label={t('Variable value')}
47
+ isDisabled={isReadOnly}
48
+ />
49
+ </FormGroup>
50
+ </Grid>
51
+ </SplitItem>
52
+ {!isReadOnly && (
53
+ <SplitItem>
54
+ <Button
55
+ aria-label={t('Delete variable')}
56
+ variant="link"
57
+ icon={<MinusCircleIcon />}
58
+ iconPosition="start"
59
+ onClick={() => remove(variableIndex)}
60
+ />
61
+ </SplitItem>
62
+ )}
63
+ </Split>
64
+ </FormSection>
65
+ );
66
+ })}
67
+ <ErrorHelperText error={appVarsError} />
68
+ {!isReadOnly && (
69
+ <FormGroup>
70
+ <Button
71
+ variant="link"
72
+ icon={<PlusCircleIcon />}
73
+ iconPosition="start"
74
+ onClick={() => push({ name: '', value: '' })}
75
+ >
76
+ {t('Add variable')}
77
+ </Button>
78
+ </FormGroup>
79
+ )}
80
+ </>
81
+ )}
82
+ </FieldArray>
83
+ </FormGroup>
84
+ );
85
+ };
86
+
87
+ export default ApplicationVariablesForm;
@@ -18,7 +18,6 @@ import './ApplicationVolumeForm.css';
18
18
 
19
19
  type ApplicationVolumeFormProps = {
20
20
  appFieldName: string;
21
- volumes: VolumeFormType[];
22
21
  isReadOnly?: boolean;
23
22
  isSingleContainerApp?: boolean;
24
23
  };
@@ -31,16 +30,15 @@ const getPullPolicyOptions = (t: TFunction) => ({
31
30
 
32
31
  const ApplicationVolumeForm = ({
33
32
  appFieldName,
34
- volumes,
35
33
  isReadOnly,
36
34
  isSingleContainerApp = false,
37
35
  }: ApplicationVolumeFormProps) => {
38
36
  const { t } = useTranslation();
39
- const [, { error }] = useField<VolumeFormType[]>(`${appFieldName}.volumes`);
40
-
41
37
  const pullPolicyOptions = React.useMemo(() => getPullPolicyOptions(t), [t]);
42
38
 
39
+ const [{ value: volumes = [] }, { error }] = useField<VolumeFormType[]>(`${appFieldName}.volumes`);
43
40
  const volumesError = typeof error === 'string' ? error : undefined;
41
+
44
42
  return (
45
43
  <FormGroup label={t('Volumes')} className="fctl-application-volume-form">
46
44
  <FieldArray name={`${appFieldName}.volumes`}>
@@ -126,15 +124,12 @@ const ApplicationVolumeForm = ({
126
124
  icon={<PlusCircleIcon />}
127
125
  iconPosition="start"
128
126
  onClick={() => {
129
- const emptyVolume: VolumeFormType = {
127
+ push({
130
128
  name: '',
131
129
  imageRef: '',
132
130
  imagePullPolicy: ImagePullPolicy.PullIfNotPresent,
133
- };
134
- if (isSingleContainerApp) {
135
- emptyVolume.mountPath = '';
136
- }
137
- push(emptyVolume);
131
+ mountPath: '',
132
+ });
138
133
  }}
139
134
  >
140
135
  {t('Add volume')}