@flightctl/ui-components 1.1.0-rc2 → 1.1.0-rc3
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/types/imagebuilder/index.d.ts +1 -0
- package/dist/types/imagebuilder/index.d.ts.map +1 -1
- package/dist/types/imagebuilder/index.js +3 -1
- package/dist/types/imagebuilder/index.js.map +1 -1
- package/dist/types/imagebuilder/models/ApiVersion.d.ts +8 -0
- package/dist/types/imagebuilder/models/ApiVersion.d.ts.map +1 -0
- package/dist/types/imagebuilder/models/ApiVersion.js +16 -0
- package/dist/types/imagebuilder/models/ApiVersion.js.map +1 -0
- package/dist/types/imagebuilder/models/ImageBuild.d.ts +2 -4
- package/dist/types/imagebuilder/models/ImageBuild.d.ts.map +1 -1
- package/dist/types/imagebuilder/models/ImageBuildList.d.ts +2 -4
- package/dist/types/imagebuilder/models/ImageBuildList.d.ts.map +1 -1
- package/dist/types/imagebuilder/models/ImageExport.d.ts +2 -4
- package/dist/types/imagebuilder/models/ImageExport.d.ts.map +1 -1
- package/dist/types/imagebuilder/models/ImageExportList.d.ts +2 -4
- package/dist/types/imagebuilder/models/ImageExportList.d.ts.map +1 -1
- package/dist/types/imagebuilder/models/Status.d.ts +2 -4
- package/dist/types/imagebuilder/models/Status.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +3 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/models/ApiVersion.d.ts +9 -0
- package/dist/types/models/ApiVersion.d.ts.map +1 -0
- package/dist/types/models/ApiVersion.js +17 -0
- package/dist/types/models/ApiVersion.js.map +1 -0
- package/dist/types/models/AuthConfig.d.ts +2 -4
- package/dist/types/models/AuthConfig.d.ts.map +1 -1
- package/dist/types/models/AuthProvider.d.ts +2 -4
- package/dist/types/models/AuthProvider.d.ts.map +1 -1
- package/dist/types/models/AuthProviderList.d.ts +2 -4
- package/dist/types/models/AuthProviderList.d.ts.map +1 -1
- package/dist/types/models/CertificateSigningRequest.d.ts +2 -4
- package/dist/types/models/CertificateSigningRequest.d.ts.map +1 -1
- package/dist/types/models/CertificateSigningRequestList.d.ts +2 -4
- package/dist/types/models/CertificateSigningRequestList.d.ts.map +1 -1
- package/dist/types/models/Device.d.ts +2 -4
- package/dist/types/models/Device.d.ts.map +1 -1
- package/dist/types/models/DeviceList.d.ts +2 -4
- package/dist/types/models/DeviceList.d.ts.map +1 -1
- package/dist/types/models/EnrollmentRequest.d.ts +2 -4
- package/dist/types/models/EnrollmentRequest.d.ts.map +1 -1
- package/dist/types/models/EnrollmentRequestList.d.ts +2 -4
- package/dist/types/models/EnrollmentRequestList.d.ts.map +1 -1
- package/dist/types/models/Event.d.ts +2 -4
- package/dist/types/models/Event.d.ts.map +1 -1
- package/dist/types/models/Event.js.map +1 -1
- package/dist/types/models/EventList.d.ts +2 -4
- package/dist/types/models/EventList.d.ts.map +1 -1
- package/dist/types/models/Fleet.d.ts +2 -4
- package/dist/types/models/Fleet.d.ts.map +1 -1
- package/dist/types/models/FleetList.d.ts +2 -4
- package/dist/types/models/FleetList.d.ts.map +1 -1
- package/dist/types/models/Organization.d.ts +2 -4
- package/dist/types/models/Organization.d.ts.map +1 -1
- package/dist/types/models/OrganizationList.d.ts +2 -4
- package/dist/types/models/OrganizationList.d.ts.map +1 -1
- package/dist/types/models/Repository.d.ts +2 -4
- package/dist/types/models/Repository.d.ts.map +1 -1
- package/dist/types/models/RepositoryList.d.ts +2 -4
- package/dist/types/models/RepositoryList.d.ts.map +1 -1
- package/dist/types/models/ResourceSync.d.ts +2 -4
- package/dist/types/models/ResourceSync.d.ts.map +1 -1
- package/dist/types/models/ResourceSyncList.d.ts +2 -4
- package/dist/types/models/ResourceSyncList.d.ts.map +1 -1
- package/dist/types/models/Status.d.ts +2 -4
- package/dist/types/models/Status.d.ts.map +1 -1
- package/dist/types/models/TemplateVersion.d.ts +2 -4
- package/dist/types/models/TemplateVersion.d.ts.map +1 -1
- package/dist/types/models/TemplateVersionList.d.ts +2 -4
- package/dist/types/models/TemplateVersionList.d.ts.map +1 -1
- package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.d.ts.map +1 -1
- package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js +51 -51
- package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js.map +1 -1
- package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.js +5 -1
- package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js +3 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js.map +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js +1 -1
- package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js.map +1 -1
- package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.d.ts +4 -2
- package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.d.ts.map +1 -1
- package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.js +2 -2
- package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.js.map +1 -1
- package/dist/ui-components/src/components/Fleet/CreateFleet/utils.d.ts +1 -1
- package/dist/ui-components/src/components/Fleet/CreateFleet/utils.d.ts.map +1 -1
- 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/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts +3 -3
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js +20 -13
- package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.js +4 -3
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.js +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.js +13 -10
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.js +4 -2
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.js +7 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/types.d.ts +3 -5
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/types.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.d.ts +3 -2
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js +139 -34
- package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js +1 -1
- 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 +25 -16
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.d.ts +1 -0
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.js +17 -18
- package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.js.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts.map +1 -1
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js +22 -10
- package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.d.ts +2 -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 +9 -3
- package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/utils.d.ts.map +1 -1
- package/dist/ui-components/src/components/Repository/CreateRepository/utils.js +3 -4
- package/dist/ui-components/src/components/Repository/CreateRepository/utils.js.map +1 -1
- package/dist/ui-components/src/components/form/RepositorySelect.d.ts.map +1 -1
- package/dist/ui-components/src/components/form/RepositorySelect.js +1 -1
- package/dist/ui-components/src/components/form/RepositorySelect.js.map +1 -1
- package/dist/ui-components/src/components/form/UploadField.d.ts.map +1 -1
- package/dist/ui-components/src/components/form/UploadField.js +25 -16
- package/dist/ui-components/src/components/form/UploadField.js.map +1 -1
- package/dist/ui-components/src/components/form/validations.d.ts +5 -0
- package/dist/ui-components/src/components/form/validations.d.ts.map +1 -1
- package/dist/ui-components/src/components/form/validations.js +40 -23
- package/dist/ui-components/src/components/form/validations.js.map +1 -1
- package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.d.ts +2 -1
- package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.d.ts.map +1 -1
- package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.js +2 -2
- package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.js.map +1 -1
- package/dist/ui-components/src/constants.d.ts +0 -2
- package/dist/ui-components/src/constants.d.ts.map +1 -1
- package/dist/ui-components/src/constants.js +5 -5
- package/dist/ui-components/src/constants.js.map +1 -1
- package/dist/ui-components/src/hooks/useWebSocket.d.ts.map +1 -1
- package/dist/ui-components/src/hooks/useWebSocket.js +25 -4
- package/dist/ui-components/src/hooks/useWebSocket.js.map +1 -1
- package/dist/ui-components/src/utils/imageBuilds.d.ts.map +1 -1
- package/dist/ui-components/src/utils/imageBuilds.js +13 -28
- package/dist/ui-components/src/utils/imageBuilds.js.map +1 -1
- package/dist/ui-components/src/utils/search.d.ts +2 -1
- package/dist/ui-components/src/utils/search.d.ts.map +1 -1
- package/dist/ui-components/src/utils/search.js +2 -2
- package/dist/ui-components/src/utils/search.js.map +1 -1
- package/package.json +2 -2
- package/src/components/AuthProvider/CreateAuthProvider/utils.ts +2 -2
- package/src/components/Device/DeviceDetails/TerminalTab.tsx +9 -1
- package/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts +3 -1
- package/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx +1 -1
- package/src/components/ErrorAlert/ErrorAlert.tsx +13 -3
- package/src/components/Fleet/CreateFleet/utils.ts +2 -3
- package/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.tsx +28 -15
- package/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx +8 -3
- package/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx +1 -1
- package/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.tsx +18 -10
- package/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.tsx +5 -1
- package/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.tsx +13 -1
- package/src/components/ImageBuilds/CreateImageBuildWizard/types.ts +3 -6
- package/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts +161 -37
- package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +1 -1
- package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.tsx +27 -18
- package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx +22 -26
- package/src/components/ImageBuilds/ImageExportCards.tsx +31 -12
- package/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx +13 -4
- package/src/components/Repository/CreateRepository/utils.ts +4 -4
- package/src/components/form/RepositorySelect.tsx +1 -0
- package/src/components/form/UploadField.tsx +29 -30
- package/src/components/form/validations.ts +44 -24
- package/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.tsx +3 -1
- package/src/constants.ts +5 -5
- package/src/hooks/useWebSocket.ts +25 -3
- package/src/utils/imageBuilds.ts +14 -32
- package/src/utils/search.ts +2 -2
|
@@ -2,8 +2,9 @@ import * as React from 'react';
|
|
|
2
2
|
import { Gallery } from '@patternfly/react-core';
|
|
3
3
|
import { saveAs } from 'file-saver';
|
|
4
4
|
|
|
5
|
-
import { ExportFormatType, ImageExport } from '@flightctl/types/imagebuilder';
|
|
5
|
+
import { ExportFormatType, ImageBuildConditionReason, ImageExport } from '@flightctl/types/imagebuilder';
|
|
6
6
|
import { ImageBuildWithExports } from '../../../types/extraTypes';
|
|
7
|
+
import { getImageBuildStatusReason } from '../../../utils/imageBuilds';
|
|
7
8
|
import { RESOURCE, VERB } from '../../../types/rbac';
|
|
8
9
|
import { useFetch } from '../../../hooks/useFetch';
|
|
9
10
|
import { usePermissionsContext } from '../../common/PermissionsContext';
|
|
@@ -13,8 +14,8 @@ import { ImageExportAction, ViewImageBuildExportCard } from '../ImageExportCards
|
|
|
13
14
|
import { useOciRegistriesContext } from '../OciRegistriesContext';
|
|
14
15
|
import { showSpinnerBriefly } from '../../../utils/time';
|
|
15
16
|
import { getAllExportFormats, getExportDownloadResult, getImageReference } from '../../../utils/imageBuilds';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
17
|
+
import { ROUTE, useNavigate } from '../../../hooks/useNavigate';
|
|
18
|
+
import { IMAGE_EXPORT_ID_PARAM } from './ImageBuildLogsTab';
|
|
18
19
|
|
|
19
20
|
type ImageBuildExportsGalleryProps = {
|
|
20
21
|
imageBuild: ImageBuildWithExports;
|
|
@@ -50,8 +51,21 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
|
|
|
50
51
|
const [canCreateExport, canDelete, canViewLogs, canDownload, canCancel] =
|
|
51
52
|
checkPermissions(imageBuildExportsPermissions);
|
|
52
53
|
|
|
54
|
+
const buildReason = getImageBuildStatusReason(imageBuild);
|
|
55
|
+
|
|
53
56
|
const actionPermissions = React.useMemo(() => {
|
|
54
57
|
const actions: ImageExportAction[] = [];
|
|
58
|
+
if (
|
|
59
|
+
buildReason === ImageBuildConditionReason.ImageBuildConditionReasonFailed ||
|
|
60
|
+
buildReason === ImageBuildConditionReason.ImageBuildConditionReasonCanceled ||
|
|
61
|
+
buildReason === ImageBuildConditionReason.ImageBuildConditionReasonCanceling
|
|
62
|
+
) {
|
|
63
|
+
if (canDelete) {
|
|
64
|
+
actions.push('delete');
|
|
65
|
+
}
|
|
66
|
+
return actions;
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
if (canCreateExport) {
|
|
56
70
|
actions.push('createExport');
|
|
57
71
|
actions.push('rebuild');
|
|
@@ -70,12 +84,9 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
|
|
|
70
84
|
actions.push('cancel');
|
|
71
85
|
}
|
|
72
86
|
return actions;
|
|
73
|
-
}, [canCreateExport, canDelete, canViewLogs, canDownload, canCancel]);
|
|
87
|
+
}, [buildReason, canCreateExport, canDelete, canViewLogs, canDownload, canCancel]);
|
|
74
88
|
|
|
75
|
-
const
|
|
76
|
-
router: { useNavigate: useRouterNavigate, appRoutes },
|
|
77
|
-
} = useAppContext();
|
|
78
|
-
const routerNavigate = useRouterNavigate();
|
|
89
|
+
const navigate = useNavigate();
|
|
79
90
|
const [error, setError] = React.useState<{
|
|
80
91
|
format: ExportFormatType;
|
|
81
92
|
action: ImageExportAction;
|
|
@@ -88,11 +99,6 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
|
|
|
88
99
|
>();
|
|
89
100
|
const imageBuildId = imageBuild.metadata.name as string;
|
|
90
101
|
|
|
91
|
-
const handleViewLogs = () => {
|
|
92
|
-
const baseRoute = appRoutes[ROUTE.IMAGE_BUILD_DETAILS];
|
|
93
|
-
routerNavigate(`${baseRoute}/${imageBuildId}/logs`);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
102
|
const handleCreateNewExport = async (format: ExportFormatType) => {
|
|
97
103
|
try {
|
|
98
104
|
const imageExport = getImageExportResource(imageBuildId, format);
|
|
@@ -110,14 +116,13 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
|
|
|
110
116
|
const response = await proxyFetch(`imagebuilder/api/v1/imageexports/${ieName}/download`, {
|
|
111
117
|
method: 'GET',
|
|
112
118
|
credentials: 'include',
|
|
113
|
-
redirect: 'manual', // Prevent automatic redirect following to avoid CORS issues
|
|
114
119
|
});
|
|
115
|
-
|
|
116
120
|
const downloadResult = await getExportDownloadResult(response);
|
|
117
121
|
if (downloadResult === null) {
|
|
118
122
|
await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
|
|
119
123
|
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
120
|
-
}
|
|
124
|
+
}
|
|
125
|
+
if (downloadResult.type === 'redirect') {
|
|
121
126
|
createDownloadLink(downloadResult.url);
|
|
122
127
|
await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
|
|
123
128
|
} else {
|
|
@@ -164,9 +169,13 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
|
|
|
164
169
|
case 'delete':
|
|
165
170
|
await handleDelete(ieName);
|
|
166
171
|
break;
|
|
167
|
-
case 'viewLogs':
|
|
168
|
-
|
|
172
|
+
case 'viewLogs': {
|
|
173
|
+
const searchParams = new URLSearchParams({
|
|
174
|
+
[IMAGE_EXPORT_ID_PARAM]: ieName,
|
|
175
|
+
});
|
|
176
|
+
navigate({ route: ROUTE.IMAGE_BUILD_DETAILS, postfix: `${imageBuildId}/logs?${searchParams.toString()}` });
|
|
169
177
|
break;
|
|
178
|
+
}
|
|
170
179
|
}
|
|
171
180
|
} catch (error) {
|
|
172
181
|
setError({ format, message: getErrorMessage(error), action });
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { TFunction } from 'react-i18next';
|
|
3
2
|
import {
|
|
4
3
|
Alert,
|
|
5
4
|
Bullseye,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
import { ExportFormatType, ImageBuildConditionReason, ImageExportConditionReason } from '@flightctl/types/imagebuilder';
|
|
26
25
|
import { useTranslation } from '../../../hooks/useTranslation';
|
|
27
26
|
import {
|
|
27
|
+
getExportFormatLabel,
|
|
28
28
|
getImageBuildStatusReason,
|
|
29
29
|
getImageExportStatusReason,
|
|
30
30
|
isImageBuildActiveReason,
|
|
@@ -34,6 +34,9 @@ import { ImageBuildWithExports } from '../../../types/extraTypes';
|
|
|
34
34
|
import { LogResourceType, useImageBuildLogs } from '../useImageBuildLogs';
|
|
35
35
|
import { StatusDisplayContent } from '../../Status/StatusDisplay';
|
|
36
36
|
import { StatusLevel } from '../../../utils/status/common';
|
|
37
|
+
import { useAppContext } from '../../../hooks/useAppContext';
|
|
38
|
+
|
|
39
|
+
export const IMAGE_EXPORT_ID_PARAM = 'exportId';
|
|
37
40
|
|
|
38
41
|
type LogEntity = {
|
|
39
42
|
type: LogResourceType;
|
|
@@ -43,19 +46,6 @@ type LogEntity = {
|
|
|
43
46
|
status: ImageBuildConditionReason | ImageExportConditionReason;
|
|
44
47
|
};
|
|
45
48
|
|
|
46
|
-
const getExportFormatText = (t: TFunction, format: ExportFormatType) => {
|
|
47
|
-
switch (format) {
|
|
48
|
-
case ExportFormatType.ExportFormatTypeVMDK:
|
|
49
|
-
return t('Virtualization (VMDK)');
|
|
50
|
-
case ExportFormatType.ExportFormatTypeQCOW2:
|
|
51
|
-
return t('OpenStack/KVM (QCOW2)');
|
|
52
|
-
case ExportFormatType.ExportFormatTypeQCOW2DiskContainer:
|
|
53
|
-
return t('OpenShift Virtualization (QCOW2)');
|
|
54
|
-
case ExportFormatType.ExportFormatTypeISO:
|
|
55
|
-
return t('Metal installer (ISO)');
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
49
|
const ImageBuildAndExportLogStatus = ({
|
|
60
50
|
status,
|
|
61
51
|
}: {
|
|
@@ -85,18 +75,23 @@ const ImageBuildAndExportLogStatus = ({
|
|
|
85
75
|
|
|
86
76
|
const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports }) => {
|
|
87
77
|
const { t } = useTranslation();
|
|
78
|
+
const {
|
|
79
|
+
router: { useSearchParams },
|
|
80
|
+
} = useAppContext();
|
|
81
|
+
const [searchParams] = useSearchParams();
|
|
88
82
|
const [isLogSelectOpen, setIsLogSelectOpen] = React.useState(false);
|
|
89
83
|
const logsRef = React.useRef<HTMLTextAreaElement>(null);
|
|
90
84
|
|
|
85
|
+
const buildName = imageBuild.metadata.name as string;
|
|
86
|
+
|
|
91
87
|
const { selectableEntities, hasExports } = React.useMemo(() => {
|
|
92
88
|
const entities: LogEntity[] = [];
|
|
93
|
-
const buildName = imageBuild.metadata.name as string;
|
|
94
89
|
|
|
95
90
|
const buildReason = getImageBuildStatusReason(imageBuild);
|
|
96
91
|
entities.push({
|
|
97
92
|
type: LogResourceType.BUILD,
|
|
98
93
|
id: buildName,
|
|
99
|
-
label:
|
|
94
|
+
label: t('Image build'),
|
|
100
95
|
isActive: isImageBuildActiveReason(buildReason),
|
|
101
96
|
status: buildReason,
|
|
102
97
|
});
|
|
@@ -115,7 +110,7 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports }
|
|
|
115
110
|
entities.push({
|
|
116
111
|
type: LogResourceType.EXPORT,
|
|
117
112
|
id: ie.metadata.name as string,
|
|
118
|
-
label:
|
|
113
|
+
label: getExportFormatLabel(t, format),
|
|
119
114
|
isActive: isImageExportActiveReason(exportReason),
|
|
120
115
|
status: exportReason,
|
|
121
116
|
});
|
|
@@ -125,9 +120,15 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports }
|
|
|
125
120
|
selectableEntities: entities,
|
|
126
121
|
hasExports,
|
|
127
122
|
};
|
|
128
|
-
}, [imageBuild, t]);
|
|
123
|
+
}, [imageBuild, buildName, t]);
|
|
129
124
|
|
|
130
|
-
const [selectedEntityId, setSelectedEntityId] = React.useState<string>(
|
|
125
|
+
const [selectedEntityId, setSelectedEntityId] = React.useState<string>(() => {
|
|
126
|
+
const selectedId = searchParams.get(IMAGE_EXPORT_ID_PARAM);
|
|
127
|
+
if (selectedId && selectableEntities.some((e) => e.id === selectedId)) {
|
|
128
|
+
return selectedId;
|
|
129
|
+
}
|
|
130
|
+
return buildName;
|
|
131
|
+
});
|
|
131
132
|
const selectedEntity = selectableEntities.find((entity) => entity.id === selectedEntityId) || selectableEntities[0];
|
|
132
133
|
const { logs, isLoading, error, isStreaming } = useImageBuildLogs(
|
|
133
134
|
selectedEntity.id,
|
|
@@ -209,13 +210,8 @@ const ImageBuildLogsTab = ({ imageBuild }: { imageBuild: ImageBuildWithExports }
|
|
|
209
210
|
{selectableEntities
|
|
210
211
|
.filter((entity) => entity.type === LogResourceType.BUILD)
|
|
211
212
|
.map((entity) => (
|
|
212
|
-
<MenuItem
|
|
213
|
-
|
|
214
|
-
itemId={entity.id}
|
|
215
|
-
isSelected={selectedEntityId === entity.id}
|
|
216
|
-
description={entity.id}
|
|
217
|
-
>
|
|
218
|
-
{entity.label}
|
|
213
|
+
<MenuItem key={entity.id} itemId={entity.id} isSelected={selectedEntityId === entity.id}>
|
|
214
|
+
{entity.id}
|
|
219
215
|
</MenuItem>
|
|
220
216
|
))}
|
|
221
217
|
</MenuList>
|
|
@@ -26,12 +26,14 @@ import { VirtualMachineIcon } from '@patternfly/react-icons/dist/js/icons/virtua
|
|
|
26
26
|
import { CloudSecurityIcon } from '@patternfly/react-icons/dist/js/icons/cloud-security-icon';
|
|
27
27
|
import { ServerGroupIcon } from '@patternfly/react-icons/dist/js/icons/server-group-icon';
|
|
28
28
|
import { BuilderImageIcon } from '@patternfly/react-icons/dist/js/icons/builder-image-icon';
|
|
29
|
+
import { InfoCircleIcon } from '@patternfly/react-icons/dist/js/icons/info-circle-icon';
|
|
29
30
|
|
|
30
31
|
import { ExportFormatType, ImageExport, ImageExportConditionReason } from '@flightctl/types/imagebuilder';
|
|
31
32
|
import { getExportFormatDescription, getExportFormatLabel, getImageExportStatusReason } from '../../utils/imageBuilds';
|
|
32
33
|
import { getDateDisplay } from '../../utils/dates';
|
|
33
34
|
import { useTranslation } from '../../hooks/useTranslation';
|
|
34
|
-
import
|
|
35
|
+
import WithTooltip from '../common/WithTooltip';
|
|
36
|
+
import ConfirmImageExportActionModal, {
|
|
35
37
|
ConfirmImageExportAction,
|
|
36
38
|
} from './ConfirmImageExportModal/ConfirmImageExportModal';
|
|
37
39
|
import { ImageExportStatusDisplay } from './ImageBuildAndExportStatus';
|
|
@@ -41,6 +43,7 @@ import './ImageExportCards.css';
|
|
|
41
43
|
export type ImageExportAction = 'cancel' | 'delete' | 'viewLogs' | 'download' | 'retry' | 'rebuild' | 'createExport';
|
|
42
44
|
|
|
43
45
|
const getActionsForStatus = (
|
|
46
|
+
format: ExportFormatType,
|
|
44
47
|
exportReason: ImageExportConditionReason | undefined,
|
|
45
48
|
actionPermissions: ImageExportAction[],
|
|
46
49
|
): ImageExportAction[] => {
|
|
@@ -54,7 +57,10 @@ const getActionsForStatus = (
|
|
|
54
57
|
actions.push('cancel', 'viewLogs');
|
|
55
58
|
break;
|
|
56
59
|
case ImageExportConditionReason.ImageExportConditionReasonCompleted:
|
|
57
|
-
|
|
60
|
+
if (format !== ExportFormatType.ExportFormatTypeQCOW2DiskContainer) {
|
|
61
|
+
actions.push('download');
|
|
62
|
+
}
|
|
63
|
+
actions.push('viewLogs', 'delete', 'rebuild');
|
|
58
64
|
break;
|
|
59
65
|
case ImageExportConditionReason.ImageExportConditionReasonFailed:
|
|
60
66
|
case ImageExportConditionReason.ImageExportConditionReasonCanceled:
|
|
@@ -102,7 +108,7 @@ const getActionTitle = (t: TFunction, action: ImageExportAction, inProgress: boo
|
|
|
102
108
|
case 'createExport':
|
|
103
109
|
return inProgress ? t('Exporting...') : t('Export image');
|
|
104
110
|
default:
|
|
105
|
-
return t('
|
|
111
|
+
return t('Actions');
|
|
106
112
|
}
|
|
107
113
|
};
|
|
108
114
|
const iconMap: Record<ExportFormatType, React.ReactElement> = {
|
|
@@ -173,9 +179,14 @@ export const ViewImageBuildExportCard = ({
|
|
|
173
179
|
const [actionsDropdownOpen, setActionsDropdownOpen] = React.useState(false);
|
|
174
180
|
const [pendingConfirmAction, setPendingConfirmAction] = React.useState<ConfirmImageExportAction>();
|
|
175
181
|
const exists = !!imageExport;
|
|
182
|
+
const exportReason = exists ? getImageExportStatusReason(imageExport) : undefined;
|
|
176
183
|
|
|
177
184
|
const handleCardAction = (action: ImageExportAction) => {
|
|
178
|
-
if (
|
|
185
|
+
if (
|
|
186
|
+
action === 'cancel' ||
|
|
187
|
+
action === 'delete' ||
|
|
188
|
+
(action === 'rebuild' && exportReason === ImageExportConditionReason.ImageExportConditionReasonCompleted)
|
|
189
|
+
) {
|
|
179
190
|
setPendingConfirmAction(action);
|
|
180
191
|
} else {
|
|
181
192
|
onCardAction({ format, action });
|
|
@@ -189,13 +200,12 @@ export const ViewImageBuildExportCard = ({
|
|
|
189
200
|
setPendingConfirmAction(undefined);
|
|
190
201
|
};
|
|
191
202
|
|
|
192
|
-
const exportReason = exists ? getImageExportStatusReason(imageExport) : undefined;
|
|
193
203
|
const { primaryAction, remainingActions } = React.useMemo(() => {
|
|
194
|
-
const allActions = getActionsForStatus(exportReason, actionPermissions);
|
|
204
|
+
const allActions = getActionsForStatus(format, exportReason, actionPermissions);
|
|
195
205
|
const primaryAction = allActions.length > 0 ? allActions[0] : undefined;
|
|
196
206
|
const remainingActions = allActions.length > 1 ? allActions.slice(1) : [];
|
|
197
207
|
return { primaryAction, remainingActions };
|
|
198
|
-
}, [exportReason, actionPermissions]);
|
|
208
|
+
}, [format, exportReason, actionPermissions]);
|
|
199
209
|
|
|
200
210
|
const renderActionButton = (exportAction: ImageExportAction, variant: 'primary' | 'secondary' = 'secondary') => {
|
|
201
211
|
const isDisabled = activeAction !== undefined;
|
|
@@ -259,9 +269,18 @@ export const ViewImageBuildExportCard = ({
|
|
|
259
269
|
</Flex>
|
|
260
270
|
</FlexItem>
|
|
261
271
|
<FlexItem>
|
|
262
|
-
<
|
|
263
|
-
<
|
|
264
|
-
|
|
272
|
+
<Flex gap={{ default: 'gapSm' }}>
|
|
273
|
+
<FlexItem>
|
|
274
|
+
<Content component={ContentVariants.h2}>{getExportFormatLabel(t, format)}</Content>
|
|
275
|
+
</FlexItem>
|
|
276
|
+
{imageExport?.metadata?.name && (
|
|
277
|
+
<FlexItem>
|
|
278
|
+
<WithTooltip showTooltip content={imageExport.metadata.name}>
|
|
279
|
+
<InfoCircleIcon />
|
|
280
|
+
</WithTooltip>
|
|
281
|
+
</FlexItem>
|
|
282
|
+
)}
|
|
283
|
+
</Flex>
|
|
265
284
|
</FlexItem>
|
|
266
285
|
</Flex>
|
|
267
286
|
</CardHeader>
|
|
@@ -287,7 +306,7 @@ export const ViewImageBuildExportCard = ({
|
|
|
287
306
|
variant="secondary"
|
|
288
307
|
isDisabled={activeAction !== undefined}
|
|
289
308
|
>
|
|
290
|
-
{t('
|
|
309
|
+
{t('Actions')}
|
|
291
310
|
</MenuToggle>
|
|
292
311
|
)}
|
|
293
312
|
>
|
|
@@ -318,7 +337,7 @@ export const ViewImageBuildExportCard = ({
|
|
|
318
337
|
</Stack>
|
|
319
338
|
</CardFooter>
|
|
320
339
|
{pendingConfirmAction && (
|
|
321
|
-
<
|
|
340
|
+
<ConfirmImageExportActionModal action={pendingConfirmAction} onClose={handleConfirmAction} />
|
|
322
341
|
)}
|
|
323
342
|
</Card>
|
|
324
343
|
);
|
|
@@ -439,6 +439,8 @@ export type CreateRepositoryFormProps = {
|
|
|
439
439
|
onSuccess: (repository: Repository) => void;
|
|
440
440
|
repository?: Repository;
|
|
441
441
|
resourceSyncs?: ResourceSync[];
|
|
442
|
+
// If provided, the repository submission will be blocked if the validation fails
|
|
443
|
+
validateBefore?: (repo: Repository) => string | undefined;
|
|
442
444
|
options?: {
|
|
443
445
|
isReadOnly?: boolean;
|
|
444
446
|
canUseResourceSyncs?: boolean;
|
|
@@ -447,13 +449,14 @@ export type CreateRepositoryFormProps = {
|
|
|
447
449
|
};
|
|
448
450
|
};
|
|
449
451
|
|
|
450
|
-
const CreateRepositoryForm
|
|
452
|
+
const CreateRepositoryForm = ({
|
|
451
453
|
repository,
|
|
452
454
|
resourceSyncs,
|
|
455
|
+
validateBefore,
|
|
453
456
|
options,
|
|
454
457
|
onClose,
|
|
455
458
|
onSuccess,
|
|
456
|
-
}) => {
|
|
459
|
+
}: CreateRepositoryFormProps) => {
|
|
457
460
|
const [errors, setErrors] = React.useState<string[]>();
|
|
458
461
|
const { patch, remove, post } = useFetch();
|
|
459
462
|
const { t } = useTranslation();
|
|
@@ -513,8 +516,14 @@ const CreateRepositoryForm: React.FC<CreateRepositoryFormProps> = ({
|
|
|
513
516
|
setErrors([getErrorMessage(e)]);
|
|
514
517
|
}
|
|
515
518
|
} else {
|
|
519
|
+
const repoToCreate = getRepository(values);
|
|
520
|
+
const validationError = validateBefore?.(repoToCreate);
|
|
521
|
+
if (validationError) {
|
|
522
|
+
setErrors([validationError]);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
516
525
|
try {
|
|
517
|
-
const repo = await post<Repository>('repositories',
|
|
526
|
+
const repo = await post<Repository>('repositories', repoToCreate);
|
|
518
527
|
if (values.useResourceSyncs) {
|
|
519
528
|
const resourceSyncPromises = values.resourceSyncs.map((rs) =>
|
|
520
529
|
post<ResourceSync>('resourcesyncs', getResourceSync(values.name, rs)),
|
|
@@ -534,7 +543,7 @@ const CreateRepositoryForm: React.FC<CreateRepositoryFormProps> = ({
|
|
|
534
543
|
>
|
|
535
544
|
<CreateRepositoryFormContent isEdit={!!repository} onClose={onClose} isReadOnly={!!options?.isReadOnly}>
|
|
536
545
|
{errors?.length && (
|
|
537
|
-
<Alert isInline variant="danger" title={t('
|
|
546
|
+
<Alert isInline variant="danger" title={t('Repository could not be saved')}>
|
|
538
547
|
{errors.map((e, index) => (
|
|
539
548
|
<div key={index}>{e}</div>
|
|
540
549
|
))}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as Yup from 'yup';
|
|
2
2
|
import { TFunction } from 'i18next';
|
|
3
3
|
import {
|
|
4
|
+
ApiVersion,
|
|
4
5
|
DockerAuth,
|
|
5
6
|
GitRepoSpec,
|
|
6
7
|
HttpConfig,
|
|
@@ -16,7 +17,6 @@ import {
|
|
|
16
17
|
} from '@flightctl/types';
|
|
17
18
|
|
|
18
19
|
import { RepositoryFormValues, ResourceSyncFormValue } from './types';
|
|
19
|
-
import { CORE_API_VERSION } from '../../../constants';
|
|
20
20
|
import { getErrorMessage } from '../../../utils/error';
|
|
21
21
|
import { appendJSONPatch } from '../../../utils/patch';
|
|
22
22
|
import { MAX_TARGET_REVISION_LENGTH, maxLengthString, validKubernetesDnsSubdomain } from '../../form/validations';
|
|
@@ -779,7 +779,7 @@ export const getRepository = (values: Omit<RepositoryFormValues, 'useResourceSyn
|
|
|
779
779
|
}
|
|
780
780
|
|
|
781
781
|
return {
|
|
782
|
-
apiVersion:
|
|
782
|
+
apiVersion: ApiVersion.ApiVersionV1beta1,
|
|
783
783
|
kind: 'Repository',
|
|
784
784
|
metadata: {
|
|
785
785
|
name: values.name,
|
|
@@ -877,7 +877,7 @@ export const getRepository = (values: Omit<RepositoryFormValues, 'useResourceSyn
|
|
|
877
877
|
}
|
|
878
878
|
|
|
879
879
|
return {
|
|
880
|
-
apiVersion:
|
|
880
|
+
apiVersion: ApiVersion.ApiVersionV1beta1,
|
|
881
881
|
kind: 'Repository',
|
|
882
882
|
metadata: {
|
|
883
883
|
name: values.name,
|
|
@@ -888,7 +888,7 @@ export const getRepository = (values: Omit<RepositoryFormValues, 'useResourceSyn
|
|
|
888
888
|
|
|
889
889
|
export const getResourceSync = (repositoryId: string, values: ResourceSyncFormValue): ResourceSync => {
|
|
890
890
|
return {
|
|
891
|
-
apiVersion:
|
|
891
|
+
apiVersion: ApiVersion.ApiVersionV1beta1,
|
|
892
892
|
kind: 'ResourceSync',
|
|
893
893
|
metadata: {
|
|
894
894
|
name: values.name,
|
|
@@ -15,44 +15,38 @@ type UploadFieldProps = {
|
|
|
15
15
|
maxFileBytes?: number;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const ONE_MB = 1024 * 1024;
|
|
19
|
+
|
|
20
|
+
const UploadMaxFileSizeHelperText = ({ maxFileBytes }: { maxFileBytes: number }) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
|
|
23
|
+
const showKB = maxFileBytes < ONE_MB;
|
|
24
|
+
const maxFileSize = maxFileBytes / (showKB ? 1024 : ONE_MB);
|
|
25
|
+
|
|
26
|
+
const helperText = showKB
|
|
27
|
+
? t('Max file size {{ maxFileSize }} KB', { maxFileSize })
|
|
28
|
+
: t('Max file size {{ maxFileSize }} MB', { maxFileSize });
|
|
29
|
+
|
|
30
|
+
return <DefaultHelperText helperText={helperText} />;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const UploadErrorHelperText = ({
|
|
19
34
|
meta,
|
|
20
35
|
isUploading,
|
|
21
36
|
isRejected,
|
|
22
|
-
maxFileBytes,
|
|
23
37
|
}: {
|
|
24
38
|
meta?: FieldMetaProps<unknown>;
|
|
25
39
|
isUploading: boolean;
|
|
26
40
|
isRejected: boolean;
|
|
27
|
-
maxFileBytes?: number;
|
|
28
41
|
}) => {
|
|
29
42
|
const { t } = useTranslation();
|
|
30
43
|
|
|
31
|
-
const maxFileMB = (maxFileBytes || 0) / (1024 * 1024);
|
|
32
|
-
|
|
33
|
-
const defaultContent = maxFileBytes ? (
|
|
34
|
-
<DefaultHelperText helperText={t('Max file size {{ maxFileSize }} MB', { maxFileSize: maxFileMB })} />
|
|
35
|
-
) : null;
|
|
36
44
|
if (isRejected) {
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
{defaultContent}
|
|
40
|
-
<ErrorHelperText
|
|
41
|
-
error={t('Content exceeds max file size of {{ maxFileSize }} MB', {
|
|
42
|
-
maxFileSize: maxFileMB,
|
|
43
|
-
})}
|
|
44
|
-
/>
|
|
45
|
-
</>
|
|
46
|
-
);
|
|
45
|
+
return <ErrorHelperText error={t('File exceeds maximum allowed size.')} />;
|
|
47
46
|
} else if (!isUploading && !!meta?.error) {
|
|
48
|
-
return
|
|
49
|
-
<>
|
|
50
|
-
{defaultContent}
|
|
51
|
-
<ErrorHelperText meta={meta} />
|
|
52
|
-
</>
|
|
53
|
-
);
|
|
47
|
+
return <ErrorHelperText meta={meta} />;
|
|
54
48
|
}
|
|
55
|
-
return
|
|
49
|
+
return null;
|
|
56
50
|
};
|
|
57
51
|
|
|
58
52
|
const UploadField = ({ ariaLabel, maxFileBytes, isRequired, name }: UploadFieldProps) => {
|
|
@@ -83,8 +77,9 @@ const UploadField = ({ ariaLabel, maxFileBytes, isRequired, name }: UploadFieldP
|
|
|
83
77
|
}
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
// To prevent timing issues with validations, we must touch the field before we set the new value
|
|
87
81
|
void setTouched(true);
|
|
82
|
+
void setValue(file, true);
|
|
88
83
|
setIsRejected(false);
|
|
89
84
|
};
|
|
90
85
|
|
|
@@ -105,7 +100,9 @@ const UploadField = ({ ariaLabel, maxFileBytes, isRequired, name }: UploadFieldP
|
|
|
105
100
|
onDataChange={handleFileChange}
|
|
106
101
|
onTextChange={handleFileChange}
|
|
107
102
|
onFileInputChange={(_event, file) => {
|
|
108
|
-
if (
|
|
103
|
+
if (maxFileBytes && file.size > maxFileBytes) {
|
|
104
|
+
handleFileRejected(true);
|
|
105
|
+
} else {
|
|
109
106
|
setFilename(file.name);
|
|
110
107
|
setIsRejected(false);
|
|
111
108
|
}
|
|
@@ -122,12 +119,14 @@ const UploadField = ({ ariaLabel, maxFileBytes, isRequired, name }: UploadFieldP
|
|
|
122
119
|
}}
|
|
123
120
|
onClearClick={() => {
|
|
124
121
|
setFilename('');
|
|
122
|
+
if (!isRequired) {
|
|
123
|
+
void setTouched(false);
|
|
124
|
+
}
|
|
125
125
|
void setValue('');
|
|
126
|
-
void setTouched(false);
|
|
127
126
|
}}
|
|
128
127
|
/>
|
|
129
|
-
|
|
130
|
-
<
|
|
128
|
+
{maxFileBytes && <UploadMaxFileSizeHelperText maxFileBytes={maxFileBytes} />}
|
|
129
|
+
<UploadErrorHelperText meta={meta} isUploading={isFileUploading} isRejected={isRejected} />
|
|
131
130
|
</FormGroup>
|
|
132
131
|
);
|
|
133
132
|
};
|
|
@@ -47,9 +47,9 @@ const K8S_DNS_SUBDOMAIN_START_END = /^[a-z0-9](.*[a-z0-9])?$/;
|
|
|
47
47
|
const K8S_DNS_SUBDOMAIN_ALLOWED_CHARACTERS = /^[a-z0-9.-]*$/;
|
|
48
48
|
const K8S_DNS_SUBDOMAIN_VALUE_MAX_LENGTH = 253;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
50
|
+
const OCI_IMAGE_FULL_REGEXP = /^(?![./_])[a-zA-Z0-9.\-\/:@_+]*$/;
|
|
51
|
+
// Accepts all characters from the above regex, but it rejects leading dot, slash, or underscore
|
|
52
|
+
const OCI_IMAGE_ALLOWED_CHARS_REGEXP = /^[a-zA-Z0-9.\-\/:@_+]*$/;
|
|
53
53
|
const APPLICATION_NAME_REGEXP = /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/;
|
|
54
54
|
const APPLICATION_VAR_NAME_REGEXP = /^[a-zA-Z_]+[a-zA-Z0-9_]*$/;
|
|
55
55
|
|
|
@@ -230,6 +230,23 @@ export const validApplicationAndVolumeName = (t: TFunction) =>
|
|
|
230
230
|
t('Use lowercase alphanumeric characters, or dash (-). Must start and end with an alphanumeric character.'),
|
|
231
231
|
);
|
|
232
232
|
|
|
233
|
+
export const getBuildNameValidations = (t: TFunction) => [
|
|
234
|
+
{
|
|
235
|
+
key: 'imageBuildName',
|
|
236
|
+
message: t(
|
|
237
|
+
'Use lowercase alphanumeric characters, or dash (-). Must start and end with an alphanumeric character.',
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
export const validImageBuildName = (t: TFunction) =>
|
|
243
|
+
Yup.string()
|
|
244
|
+
.required(t('Build name is required'))
|
|
245
|
+
.test('imageBuildName', function (value: string | undefined) {
|
|
246
|
+
if (!value) return true;
|
|
247
|
+
return APPLICATION_NAME_REGEXP.test(value) || this.createError({ message: { imageBuildName: 'failed' } });
|
|
248
|
+
});
|
|
249
|
+
|
|
233
250
|
export const maxLengthString = (t: TFunction, props: { maxLength: number; fieldName: string }) =>
|
|
234
251
|
Yup.string().max(props.maxLength, t('{{ fieldName }} must not exceed {{ maxLength }} characters', props));
|
|
235
252
|
|
|
@@ -258,7 +275,7 @@ export const validOsImage = (t: TFunction, { isFleet }: { isFleet: boolean }) =>
|
|
|
258
275
|
}
|
|
259
276
|
}
|
|
260
277
|
|
|
261
|
-
return
|
|
278
|
+
return OCI_IMAGE_FULL_REGEXP.test(validateOsImage);
|
|
262
279
|
},
|
|
263
280
|
);
|
|
264
281
|
|
|
@@ -654,17 +671,27 @@ export const validateMemoryLimit = (memory: string | undefined): boolean => {
|
|
|
654
671
|
return true;
|
|
655
672
|
};
|
|
656
673
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
674
|
+
const ociImageSchema = (t: TFunction) =>
|
|
675
|
+
Yup.string().test('oci-image-format', (value, testContext) => {
|
|
676
|
+
if (!value) return true;
|
|
677
|
+
if (!OCI_IMAGE_ALLOWED_CHARS_REGEXP.test(value)) {
|
|
678
|
+
return testContext.createError({
|
|
679
|
+
message: t('Image includes invalid characters.'),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
if (!OCI_IMAGE_FULL_REGEXP.test(value)) {
|
|
683
|
+
return testContext.createError({
|
|
684
|
+
message: t('Image must not start with a dot (.), slash (/), or underscore (_).'),
|
|
685
|
+
});
|
|
664
686
|
}
|
|
665
|
-
return
|
|
687
|
+
return true;
|
|
666
688
|
});
|
|
667
689
|
|
|
690
|
+
const requiredOciImageSchema = (t: TFunction, requiredMessage?: string) =>
|
|
691
|
+
ociImageSchema(t).required(requiredMessage || t('Image is required.'));
|
|
692
|
+
|
|
693
|
+
const volumeNameSchema = (t: TFunction) => validApplicationAndVolumeName(t).required(t('Volume name is required'));
|
|
694
|
+
|
|
668
695
|
const imagePullPolicySchema = (t: TFunction) =>
|
|
669
696
|
Yup.string()
|
|
670
697
|
.oneOf(
|
|
@@ -678,12 +705,11 @@ const mountPathSchema = (t: TFunction, isRequired: boolean = true) => {
|
|
|
678
705
|
return isRequired ? schema.required(t('Mount path is required for this volume type')) : schema;
|
|
679
706
|
};
|
|
680
707
|
|
|
681
|
-
// Volume validation schemas
|
|
682
708
|
const singleContainerVolumesSchema = (t: TFunction) =>
|
|
683
709
|
Yup.array().of(
|
|
684
710
|
Yup.object().shape({
|
|
685
711
|
name: volumeNameSchema(t),
|
|
686
|
-
imageRef:
|
|
712
|
+
imageRef: ociImageSchema(t),
|
|
687
713
|
imagePullPolicy: Yup.string(),
|
|
688
714
|
mountPath: mountPathSchema(t, true),
|
|
689
715
|
}),
|
|
@@ -693,7 +719,7 @@ const composeQuadletVolumesSchema = (t: TFunction) =>
|
|
|
693
719
|
Yup.array().of(
|
|
694
720
|
Yup.object().shape({
|
|
695
721
|
name: volumeNameSchema(t),
|
|
696
|
-
imageRef:
|
|
722
|
+
imageRef: requiredOciImageSchema(t, t('Image reference is required for this volume type')),
|
|
697
723
|
imagePullPolicy: imagePullPolicySchema(t),
|
|
698
724
|
}),
|
|
699
725
|
);
|
|
@@ -710,9 +736,7 @@ export const validApplicationsSchema = (t: TFunction) => {
|
|
|
710
736
|
.required(t('Definition source must be image for this type of applications')),
|
|
711
737
|
appType: Yup.string().oneOf([AppType.AppTypeContainer]).required(t('Application type is required')),
|
|
712
738
|
name: validApplicationAndVolumeName(t),
|
|
713
|
-
image:
|
|
714
|
-
.required(t('Image is required.'))
|
|
715
|
-
.matches(APPLICATION_IMAGE_REGEXP, t('Application image includes invalid characters.')),
|
|
739
|
+
image: requiredOciImageSchema(t),
|
|
716
740
|
ports: Yup.array().of(
|
|
717
741
|
Yup.object()
|
|
718
742
|
.shape({
|
|
@@ -749,9 +773,7 @@ export const validApplicationsSchema = (t: TFunction) => {
|
|
|
749
773
|
.required(t('Definition source must be image for this type of applications')),
|
|
750
774
|
appType: Yup.string().oneOf([AppType.AppTypeHelm]).required(t('Application type is required')),
|
|
751
775
|
name: validApplicationAndVolumeName(t),
|
|
752
|
-
image:
|
|
753
|
-
.required(t('Image is required.'))
|
|
754
|
-
.matches(APPLICATION_IMAGE_REGEXP, t('Application image includes invalid characters.')),
|
|
776
|
+
image: requiredOciImageSchema(t),
|
|
755
777
|
namespace: validHelmNamespace(t),
|
|
756
778
|
valuesYaml: Yup.string().test('valid-yaml', t('YAML content is invalid.'), (value) => {
|
|
757
779
|
if (!value || value.trim() === '') {
|
|
@@ -778,9 +800,7 @@ export const validApplicationsSchema = (t: TFunction) => {
|
|
|
778
800
|
.oneOf([AppType.AppTypeCompose, AppType.AppTypeQuadlet])
|
|
779
801
|
.required(t('Application type is required')),
|
|
780
802
|
name: validApplicationAndVolumeName(t),
|
|
781
|
-
image:
|
|
782
|
-
.required(t('Image is required.'))
|
|
783
|
-
.matches(APPLICATION_IMAGE_REGEXP, t('Application image includes invalid characters.')),
|
|
803
|
+
image: requiredOciImageSchema(t),
|
|
784
804
|
volumes: composeQuadletVolumesSchema(t),
|
|
785
805
|
variables: appVariablesSchema(t),
|
|
786
806
|
});
|