@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.
- package/dist/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
- package/dist/types/imagebuilder/index.d.ts +1 -0
- package/dist/types/imagebuilder/index.d.ts.map +1 -1
- package/dist/types/imagebuilder/models/Status.d.ts +30 -0
- package/dist/types/imagebuilder/models/Status.d.ts.map +1 -0
- package/dist/types/imagebuilder/models/Status.js +3 -0
- package/dist/types/imagebuilder/models/Status.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/models/ApplicationProviderBase.d.ts +12 -0
- package/dist/types/models/ApplicationProviderBase.d.ts.map +1 -0
- package/dist/types/models/ApplicationProviderBase.js +3 -0
- package/dist/types/models/ApplicationProviderBase.js.map +1 -0
- package/dist/types/models/ApplicationProviderSpec.d.ts +5 -15
- package/dist/types/models/ApplicationProviderSpec.d.ts.map +1 -1
- package/dist/types/models/ApplicationUser.d.ts +7 -0
- package/dist/types/models/ApplicationUser.d.ts.map +1 -0
- package/dist/types/models/ApplicationUser.js +3 -0
- package/dist/types/models/ApplicationUser.js.map +1 -0
- package/dist/types/models/ComposeApplication.d.ts +7 -0
- package/dist/types/models/ComposeApplication.d.ts.map +1 -0
- package/dist/types/models/ComposeApplication.js +3 -0
- package/dist/types/models/ComposeApplication.js.map +1 -0
- package/dist/types/models/ContainerApplication.d.ts +18 -0
- package/dist/types/models/ContainerApplication.d.ts.map +1 -0
- package/dist/types/models/ContainerApplication.js +3 -0
- package/dist/types/models/ContainerApplication.js.map +1 -0
- package/dist/types/models/ContainerApplicationProperties.d.ts +13 -0
- package/dist/types/models/ContainerApplicationProperties.d.ts.map +1 -0
- package/dist/types/models/ContainerApplicationProperties.js +3 -0
- package/dist/types/models/ContainerApplicationProperties.js.map +1 -0
- package/dist/types/models/HelmApplication.d.ts +20 -0
- package/dist/types/models/HelmApplication.d.ts.map +1 -0
- package/dist/types/models/HelmApplication.js +3 -0
- package/dist/types/models/HelmApplication.js.map +1 -0
- package/dist/types/models/ImageApplicationProviderSpec.d.ts +2 -22
- package/dist/types/models/ImageApplicationProviderSpec.d.ts.map +1 -1
- package/dist/types/models/InlineApplicationProviderSpec.d.ts +2 -3
- package/dist/types/models/InlineApplicationProviderSpec.d.ts.map +1 -1
- package/dist/types/models/QuadletApplication.d.ts +8 -0
- package/dist/types/models/QuadletApplication.d.ts.map +1 -0
- package/dist/types/models/QuadletApplication.js +3 -0
- package/dist/types/models/QuadletApplication.js.map +1 -0
- package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js +1 -1
- package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js.map +1 -1
- package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js +1 -1
- package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js.map +1 -1
- package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js +5 -4
- package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts +3 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js +308 -363
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts +1 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js +18 -19
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts +1 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js +4 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts +1 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js +2 -2
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts +3 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js +20 -23
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js +3 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js +25 -45
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts +8 -0
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts.map +1 -0
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js +37 -0
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js.map +1 -0
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts +1 -3
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js +5 -8
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/utils.d.ts +18 -18
- package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js +2 -2
- package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js.map +1 -1
- package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.d.ts.map +1 -1
- package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js +3 -1
- package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts +7 -0
- package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts.map +1 -0
- package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js +40 -0
- package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js.map +1 -0
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts +8 -0
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts.map +1 -0
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js +30 -0
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js.map +1 -0
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js +2 -2
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js +22 -11
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js +103 -36
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts +5 -2
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js +22 -12
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js +17 -8
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts +10 -9
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js +109 -25
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.d.ts.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/utils.js +3 -3
- package/dist/ui-components/src/components/Repository/CreateRepository/utils.js.map +1 -1
- package/dist/ui-components/src/components/form/validations.d.ts +20 -18
- package/dist/ui-components/src/components/form/validations.d.ts.map +1 -1
- package/dist/ui-components/src/components/form/validations.js +40 -11
- package/dist/ui-components/src/components/form/validations.js.map +1 -1
- package/dist/ui-components/src/constants.d.ts +7 -6
- package/dist/ui-components/src/constants.d.ts.map +1 -1
- package/dist/ui-components/src/constants.js +19 -11
- package/dist/ui-components/src/constants.js.map +1 -1
- package/dist/ui-components/src/types/deviceSpec.d.ts +44 -76
- package/dist/ui-components/src/types/deviceSpec.d.ts.map +1 -1
- package/dist/ui-components/src/types/deviceSpec.js +13 -26
- package/dist/ui-components/src/types/deviceSpec.js.map +1 -1
- package/dist/ui-components/src/types/extraTypes.d.ts +1 -7
- package/dist/ui-components/src/types/extraTypes.d.ts.map +1 -1
- package/dist/ui-components/src/types/extraTypes.js.map +1 -1
- package/dist/ui-components/src/types/rbac.d.ts +7 -1
- package/dist/ui-components/src/types/rbac.d.ts.map +1 -1
- package/dist/ui-components/src/types/rbac.js +6 -0
- package/dist/ui-components/src/types/rbac.js.map +1 -1
- package/dist/ui-components/src/utils/imageBuilds.d.ts +1 -0
- package/dist/ui-components/src/utils/imageBuilds.d.ts.map +1 -1
- package/dist/ui-components/src/utils/imageBuilds.js +7 -1
- package/dist/ui-components/src/utils/imageBuilds.js.map +1 -1
- package/dist/ui-components/src/utils/search.js +1 -1
- package/dist/ui-components/src/utils/search.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AuthProvider/CreateAuthProvider/utils.ts +2 -2
- package/src/components/DetailsPage/Tables/ApplicationsTable.tsx +2 -2
- package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +10 -4
- package/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts +359 -425
- package/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx +19 -29
- package/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx +4 -12
- package/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx +2 -16
- package/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.tsx +8 -7
- package/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.tsx +5 -5
- package/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx +29 -101
- package/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx +87 -0
- package/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx +5 -10
- package/src/components/Fleet/CreateFleet/utils.ts +4 -4
- package/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.tsx +11 -8
- package/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.tsx +81 -0
- package/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.tsx +48 -0
- package/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts +3 -3
- package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +35 -16
- package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.tsx +120 -42
- package/src/components/ImageBuilds/ImageBuildRow.tsx +41 -20
- package/src/components/ImageBuilds/ImageBuildsPage.tsx +34 -15
- package/src/components/ImageBuilds/ImageExportCards.tsx +176 -77
- package/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
- package/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx +1 -0
- package/src/components/Repository/CreateRepository/utils.ts +4 -4
- package/src/components/form/validations.ts +112 -82
- package/src/constants.ts +19 -6
- package/src/types/deviceSpec.ts +68 -108
- package/src/types/extraTypes.ts +2 -12
- package/src/types/rbac.ts +6 -0
- package/src/utils/imageBuilds.ts +8 -0
- 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:
|
|
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
|
|
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
|
|
55
|
-
|
|
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([...
|
|
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 = [...
|
|
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 ?
|
|
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 = [...
|
|
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 &&
|
|
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}.
|
|
275
|
-
value={app.
|
|
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}.
|
|
290
|
-
value={app.
|
|
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,
|
|
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:
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
58
|
+
files,
|
|
59
59
|
index,
|
|
60
60
|
isReadOnly,
|
|
61
61
|
}: {
|
|
62
|
-
|
|
62
|
+
files: InlineFileForm[];
|
|
63
63
|
index: number;
|
|
64
64
|
isReadOnly?: boolean;
|
|
65
65
|
}) => {
|
|
66
66
|
const { t } = useTranslation();
|
|
67
67
|
|
|
68
|
-
|
|
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
|
-
{
|
|
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 &&
|
|
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 {
|
|
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 ?
|
|
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
|
|
49
|
+
value={runAs}
|
|
50
50
|
isDisabled
|
|
51
51
|
readOnly
|
|
52
52
|
helperText={t(
|
|
53
|
-
"
|
|
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:
|
|
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 },
|
|
41
|
+
const [{ value: app }, , { setValue }] = useField<AppForm>(appFieldName);
|
|
52
42
|
const { appType, specType, name: appName } = app;
|
|
53
43
|
|
|
54
|
-
const isContainer =
|
|
55
|
-
const isHelm =
|
|
56
|
-
const isQuadlet =
|
|
57
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
73
|
-
setValue(
|
|
61
|
+
const initialApp = createInitialAppForm(appType, appName || '');
|
|
62
|
+
setValue(initialApp, false);
|
|
74
63
|
}
|
|
75
|
-
}, [shouldResetApp,
|
|
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
|
|
82
|
+
<ApplicationContainerForm index={index} isReadOnly={isReadOnly} />
|
|
94
83
|
) : isHelm ? (
|
|
95
|
-
<ApplicationHelmForm
|
|
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
|
-
|
|
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={
|
|
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
|
-
{
|
|
156
|
-
|
|
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
|
-
<
|
|
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
|
|
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
|
-
|
|
127
|
+
push({
|
|
130
128
|
name: '',
|
|
131
129
|
imageRef: '',
|
|
132
130
|
imagePullPolicy: ImagePullPolicy.PullIfNotPresent,
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
emptyVolume.mountPath = '';
|
|
136
|
-
}
|
|
137
|
-
push(emptyVolume);
|
|
131
|
+
mountPath: '',
|
|
132
|
+
});
|
|
138
133
|
}}
|
|
139
134
|
>
|
|
140
135
|
{t('Add volume')}
|