@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
@@ -10,31 +10,25 @@ import {
10
10
  AppSpecType,
11
11
  BatchForm,
12
12
  BatchLimitType,
13
- ComposeImageAppForm,
14
- ComposeInlineAppForm,
13
+ ComposeAppForm,
15
14
  DisruptionBudgetForm,
16
15
  GitConfigTemplate,
17
- HelmImageAppForm,
16
+ HelmAppForm,
18
17
  HttpConfigTemplate,
19
18
  InlineConfigTemplate,
19
+ InlineFileForm,
20
20
  KubeSecretTemplate,
21
21
  PortMapping,
22
- QuadletImageAppForm,
23
- QuadletInlineAppForm,
22
+ QuadletAppForm,
24
23
  RolloutPolicyForm,
25
24
  SpecConfigTemplate,
26
25
  SystemdUnitFormValue,
27
26
  UpdatePolicyForm,
28
27
  getAppIdentifier,
29
- isComposeImageAppForm,
30
28
  isGitConfigTemplate,
31
- isHelmImageAppForm,
32
29
  isHttpConfigTemplate,
33
30
  isInlineConfigTemplate,
34
31
  isKubeSecretTemplate,
35
- isQuadletImageAppForm,
36
- isQuadletInlineAppForm,
37
- isSingleContainerAppForm,
38
32
  } from '../../types/deviceSpec';
39
33
  import { labelToString } from '../../utils/labels';
40
34
  import { UpdateScheduleMode } from '../../utils/time';
@@ -74,6 +68,9 @@ const relativePathRegex = /^(?!\.\.\/|\.\.\$|\.\/)(\.\/)*[\w.-]+(?:\/[\w.-]+)*\/
74
68
  export const MAX_TARGET_REVISION_LENGTH = 244;
75
69
  const MAX_FILE_PATH_LENGTH = 253;
76
70
 
71
+ const HELM_NAMESPACE_MAX_LENGTH = 63;
72
+ const HELM_VALUES_FILE_EXT_REGEXP = /\.(yaml|yml)$/i;
73
+
77
74
  const isInteger = (val: number | undefined) => val === undefined || Number.isInteger(val);
78
75
 
79
76
  const validComposeFileNames = [
@@ -265,6 +262,48 @@ export const validOsImage = (t: TFunction, { isFleet }: { isFleet: boolean }) =>
265
262
  },
266
263
  );
267
264
 
265
+ export const validHelmNamespace = (t: TFunction) =>
266
+ Yup.string()
267
+ .max(
268
+ HELM_NAMESPACE_MAX_LENGTH,
269
+ t('Namespace must not exceed {{ max }} characters.', { max: HELM_NAMESPACE_MAX_LENGTH }),
270
+ )
271
+ .test(
272
+ 'helm-namespace-format',
273
+ t(
274
+ 'Namespace must only include lowercase letters, numbers, and hyphens. It must start and end with a letter or number.',
275
+ ),
276
+ (value) => {
277
+ return !value || APPLICATION_NAME_REGEXP.test(value);
278
+ },
279
+ );
280
+
281
+ export const validHelmValuesFile = (t: TFunction) =>
282
+ Yup.string().test('helm-values-file', function (filename) {
283
+ if (!filename) {
284
+ return true;
285
+ }
286
+ if (filename.length > MAX_FILE_PATH_LENGTH) {
287
+ return this.createError({
288
+ message: t('Values file path must not exceed {{ max }} characters.', {
289
+ max: MAX_FILE_PATH_LENGTH,
290
+ }),
291
+ });
292
+ }
293
+ if (filename.startsWith('/') || filename.includes('..')) {
294
+ return this.createError({
295
+ message: t('Values file path must be relative and cannot contain parent directory references (..).'),
296
+ });
297
+ }
298
+
299
+ if (!HELM_VALUES_FILE_EXT_REGEXP.test(filename)) {
300
+ return this.createError({
301
+ message: t('Values file must have .yaml or .yml extension.'),
302
+ });
303
+ }
304
+ return true;
305
+ });
306
+
268
307
  export const validLabelsSchema = (t: TFunction, forbiddenLabels?: string[]) =>
269
308
  Yup.array()
270
309
  .of(
@@ -370,11 +409,7 @@ const inlineAppFileSchema = (t: TFunction) =>
370
409
 
371
410
  // Common test for unique file paths in inline applications
372
411
  const uniqueFilePathsTest =
373
- (t: TFunction) =>
374
- (
375
- files: (QuadletInlineAppForm | ComposeInlineAppForm)['files'] | undefined,
376
- testContext: Yup.TestContext<Yup.AnyObject>,
377
- ) => {
412
+ (t: TFunction) => (files: InlineFileForm[] | undefined, testContext: Yup.TestContext<Yup.AnyObject>) => {
378
413
  if (!files || files.length === 0) {
379
414
  return true;
380
415
  }
@@ -412,34 +447,33 @@ const uniqueFilePathsTest =
412
447
  });
413
448
  };
414
449
 
415
- const composeFileName =
416
- (t: TFunction) => (files: ComposeInlineAppForm['files'], testContext: Yup.TestContext<Yup.AnyObject>) => {
417
- const invalidFiles = files
418
- .map((file, index) => {
419
- if (!file.path) {
420
- return null;
421
- }
422
- // Extract filename from relative path (get last part after slash, or use whole path if no slash)
423
- const fileName = file.path.includes('/') ? file.path.split('/').pop() || file.path : file.path;
424
- if (!validComposeFileNames.includes(fileName)) {
425
- return index;
426
- }
450
+ const composeFileName = (t: TFunction) => (files: InlineFileForm[], testContext: Yup.TestContext<Yup.AnyObject>) => {
451
+ const invalidFiles = files
452
+ .map((file, index) => {
453
+ if (!file.path) {
427
454
  return null;
428
- })
429
- .filter((index): index is number => index !== null);
455
+ }
456
+ // Extract filename from relative path (get last part after slash, or use whole path if no slash)
457
+ const fileName = file.path.includes('/') ? file.path.split('/').pop() || file.path : file.path;
458
+ if (!validComposeFileNames.includes(fileName)) {
459
+ return index;
460
+ }
461
+ return null;
462
+ })
463
+ .filter((index): index is number => index !== null);
430
464
 
431
- if (invalidFiles.length > 0) {
432
- const firstInvalidIndex = invalidFiles[0];
433
- return testContext.createError({
434
- path: `${testContext.path}[${firstInvalidIndex}].path`,
435
- message: () =>
436
- t('File name must be one of: {{ allowedFileNames }}', {
437
- allowedFileNames: validComposeFileNameDisplay,
438
- }),
439
- });
440
- }
441
- return true;
442
- };
465
+ if (invalidFiles.length > 0) {
466
+ const firstInvalidIndex = invalidFiles[0];
467
+ return testContext.createError({
468
+ path: `${testContext.path}[${firstInvalidIndex}].path`,
469
+ message: () =>
470
+ t('File name must be one of: {{ allowedFileNames }}', {
471
+ allowedFileNames: validComposeFileNameDisplay,
472
+ }),
473
+ });
474
+ }
475
+ return true;
476
+ };
443
477
 
444
478
  // Helper to extract file extension from a path
445
479
  const getFileExtension = (path: string): string => {
@@ -454,7 +488,7 @@ const isAtRoot = (path: string): boolean => {
454
488
 
455
489
  // Validation for quadlet applications: checks for unsupported types first, then requires at least one supported type
456
490
  const quadletFileTypesValidation =
457
- (t: TFunction) => (files: QuadletInlineAppForm['files'], testContext: Yup.TestContext<Yup.AnyObject>) => {
491
+ (t: TFunction) => (files: InlineFileForm[], testContext: Yup.TestContext<Yup.AnyObject>) => {
458
492
  if (!files || files.length === 0) {
459
493
  return true; // This is handled by the min(1) requirement
460
494
  }
@@ -512,36 +546,35 @@ const quadletFileTypesValidation =
512
546
  };
513
547
 
514
548
  // Validation for quadlet applications: quadlet files must be at root level
515
- const quadletFilesAtRoot =
516
- (t: TFunction) => (files: QuadletInlineAppForm['files'], testContext: Yup.TestContext<Yup.AnyObject>) => {
517
- if (!files || files.length === 0) {
518
- return true;
519
- }
549
+ const quadletFilesAtRoot = (t: TFunction) => (files: InlineFileForm[], testContext: Yup.TestContext<Yup.AnyObject>) => {
550
+ if (!files || files.length === 0) {
551
+ return true;
552
+ }
520
553
 
521
- const invalidFiles = files
522
- .map((file, index) => {
523
- if (!file.path) {
524
- return null;
525
- }
526
- const ext = getFileExtension(file.path);
527
- // Only check files with supported quadlet extensions
528
- if (supportedQuadletExtensions.includes(ext) && !isAtRoot(file.path)) {
529
- return index;
530
- }
554
+ const invalidFiles = files
555
+ .map((file, index) => {
556
+ if (!file.path) {
531
557
  return null;
532
- })
533
- .filter((index): index is number => index !== null);
558
+ }
559
+ const ext = getFileExtension(file.path);
560
+ // Only check files with supported quadlet extensions
561
+ if (supportedQuadletExtensions.includes(ext) && !isAtRoot(file.path)) {
562
+ return index;
563
+ }
564
+ return null;
565
+ })
566
+ .filter((index): index is number => index !== null);
534
567
 
535
- if (invalidFiles.length > 0) {
536
- const firstInvalidIndex = invalidFiles[0];
537
- return testContext.createError({
538
- path: `${testContext.path}[${firstInvalidIndex}].path`,
539
- message: () => t('Quadlet files must be at root level (no subdirectories)'),
540
- });
541
- }
568
+ if (invalidFiles.length > 0) {
569
+ const firstInvalidIndex = invalidFiles[0];
570
+ return testContext.createError({
571
+ path: `${testContext.path}[${firstInvalidIndex}].path`,
572
+ message: () => t('Quadlet files must be at root level (no subdirectories)'),
573
+ });
574
+ }
542
575
 
543
- return true;
544
- };
576
+ return true;
577
+ };
545
578
 
546
579
  const PORT_NUMBER_REGEXP = /^\d+$/;
547
580
  const MAX_PORT = 65535;
@@ -670,13 +703,13 @@ export const validApplicationsSchema = (t: TFunction) => {
670
703
  .of(
671
704
  Yup.lazy((value: AppForm) => {
672
705
  // Container applications (image-based with ports and resources)
673
- if (isSingleContainerAppForm(value)) {
706
+ if (value.appType === AppType.AppTypeContainer) {
674
707
  return Yup.object().shape({
675
708
  specType: Yup.string()
676
709
  .oneOf([AppSpecType.OCI_IMAGE])
677
710
  .required(t('Definition source must be image for this type of applications')),
678
711
  appType: Yup.string().oneOf([AppType.AppTypeContainer]).required(t('Application type is required')),
679
- name: validApplicationAndVolumeName(t).required(t('Name is required for single container applications.')),
712
+ name: validApplicationAndVolumeName(t),
680
713
  image: Yup.string()
681
714
  .required(t('Image is required.'))
682
715
  .matches(APPLICATION_IMAGE_REGEXP, t('Application image includes invalid characters.')),
@@ -709,8 +742,8 @@ export const validApplicationsSchema = (t: TFunction) => {
709
742
  }
710
743
 
711
744
  // Helm applications
712
- if (isHelmImageAppForm(value)) {
713
- return Yup.object<HelmImageAppForm>().shape({
745
+ if (value.appType === AppType.AppTypeHelm) {
746
+ return Yup.object<HelmAppForm>().shape({
714
747
  specType: Yup.string()
715
748
  .oneOf([AppSpecType.OCI_IMAGE])
716
749
  .required(t('Definition source must be image for this type of applications')),
@@ -719,7 +752,7 @@ export const validApplicationsSchema = (t: TFunction) => {
719
752
  image: Yup.string()
720
753
  .required(t('Image is required.'))
721
754
  .matches(APPLICATION_IMAGE_REGEXP, t('Application image includes invalid characters.')),
722
- namespace: Yup.string(),
755
+ namespace: validHelmNamespace(t),
723
756
  valuesYaml: Yup.string().test('valid-yaml', t('YAML content is invalid.'), (value) => {
724
757
  if (!value || value.trim() === '') {
725
758
  return true;
@@ -731,16 +764,13 @@ export const validApplicationsSchema = (t: TFunction) => {
731
764
  }
732
765
  return true;
733
766
  }),
734
- // Values files don't need to be validated.
735
- // Since the UX design has the first field added by default, we will ignore all empty values files.
736
- // The user can't add more values files if any is empty (though they can clear existing ones).
737
- valuesFiles: Yup.array().of(Yup.string()),
767
+ valuesFiles: Yup.array().of(validHelmValuesFile(t)),
738
768
  });
739
769
  }
740
770
 
741
771
  // Image applications (Quadlet or Compose)
742
- if (isQuadletImageAppForm(value) || isComposeImageAppForm(value)) {
743
- return Yup.object<QuadletImageAppForm | ComposeImageAppForm>().shape({
772
+ if (value.specType === AppSpecType.OCI_IMAGE) {
773
+ return Yup.object<QuadletAppForm | ComposeAppForm>().shape({
744
774
  specType: Yup.string()
745
775
  .oneOf([AppSpecType.OCI_IMAGE])
746
776
  .required(t('Definition source must be image for this type of applications')),
@@ -757,8 +787,8 @@ export const validApplicationsSchema = (t: TFunction) => {
757
787
  }
758
788
 
759
789
  // Inline quadlet applications
760
- if (isQuadletInlineAppForm(value)) {
761
- return Yup.object<QuadletInlineAppForm>().shape({
790
+ if (value.appType === AppType.AppTypeQuadlet && value.specType === AppSpecType.INLINE) {
791
+ return Yup.object<QuadletAppForm>().shape({
762
792
  specType: appSpecTypeSchema(t),
763
793
  appType: Yup.string().oneOf([AppType.AppTypeQuadlet]).required(t('Application type is required')),
764
794
  name: validApplicationAndVolumeName(t).required(t('Name is required for quadlet applications.')),
@@ -772,7 +802,7 @@ export const validApplicationsSchema = (t: TFunction) => {
772
802
  }
773
803
 
774
804
  // Inline compose applications
775
- return Yup.object<ComposeInlineAppForm>().shape({
805
+ return Yup.object<ComposeAppForm>().shape({
776
806
  specType: appSpecTypeSchema(t),
777
807
  appType: Yup.string().oneOf([AppType.AppTypeCompose]).required(t('Application type is required')),
778
808
  name: validApplicationAndVolumeName(t).required(t('Name is required for compose applications.')),
package/src/constants.ts CHANGED
@@ -1,7 +1,20 @@
1
- const APP_TITLE = 'Edge Manager';
2
- const API_VERSION = 'v1beta1';
3
- const PAGE_SIZE = 15;
4
- const EVENT_PAGE_SIZE = 200; // It's 500 in OCP console
5
- const CERTIFICATE_VALIDITY_IN_YEARS = 1;
1
+ export const APP_TITLE = 'Edge Manager';
2
+ export const CORE_API_VERSION = 'v1beta1';
3
+ export const IMAGEBUILDER_API_VERSION = 'v1alpha1';
6
4
 
7
- export { APP_TITLE, API_VERSION, PAGE_SIZE, EVENT_PAGE_SIZE, CERTIFICATE_VALIDITY_IN_YEARS };
5
+ export const PAGE_SIZE = 15;
6
+ export const EVENT_PAGE_SIZE = 200; // It's 500 in OCP console
7
+
8
+ export const CERTIFICATE_VALIDITY_IN_YEARS = 1;
9
+
10
+ export const getApiVersion = (api: 'flightctl' | 'imagebuilder' | 'alerts'): string | undefined => {
11
+ switch (api) {
12
+ case 'flightctl':
13
+ return CORE_API_VERSION;
14
+ case 'imagebuilder':
15
+ return IMAGEBUILDER_API_VERSION;
16
+ case 'alerts':
17
+ default:
18
+ return undefined;
19
+ }
20
+ };
@@ -1,20 +1,25 @@
1
1
  import {
2
- AppType,
3
- ApplicationResourceLimits,
2
+ ApplicationProviderSpec,
3
+ ComposeApplication,
4
4
  ConfigProviderSpec,
5
+ ContainerApplication,
5
6
  DisruptionBudget,
6
7
  GitConfigProviderSpec,
8
+ HelmApplication,
7
9
  HttpConfigProviderSpec,
8
10
  ImageApplicationProviderSpec,
9
11
  ImagePullPolicy,
12
+ InlineApplicationProviderSpec,
10
13
  InlineConfigProviderSpec,
11
14
  KubernetesSecretProviderSpec,
15
+ QuadletApplication,
12
16
  } from '@flightctl/types';
13
- import { ApplicationProviderSpecFixed, FlightCtlLabel } from './extraTypes';
17
+ import { FlightCtlLabel } from './extraTypes';
14
18
  import { UpdateScheduleMode } from '../utils/time';
15
19
 
16
- export const RUN_AS_DEFAULT_USER = 'flightctl';
20
+ // At the moment the "root" user is the default user when no user is specified.
17
21
  export const RUN_AS_ROOT_USER = 'root';
22
+ export const RUN_AS_FLIGHTCTL_USER = 'flightctl';
18
23
 
19
24
  export enum ConfigType {
20
25
  GIT = 'git',
@@ -35,77 +40,12 @@ export type GitConfigTemplate = ConfigTemplate & {
35
40
  path: string;
36
41
  };
37
42
 
43
+ /** Used when adding a Compose/Quadlet app to choose image vs inline source. */
38
44
  export enum AppSpecType {
39
45
  OCI_IMAGE = 'image',
40
46
  INLINE = 'inline',
41
47
  }
42
48
 
43
- type InlineContent = {
44
- content?: string;
45
- path: string;
46
- base64?: boolean;
47
- };
48
-
49
- type AppBase = {
50
- appType: AppType;
51
- specType: AppSpecType;
52
- name?: string;
53
- variables: { name: string; value: string }[];
54
- volumes?: ApplicationVolumeForm[];
55
- };
56
-
57
- export type PortMapping = {
58
- hostPort: string;
59
- containerPort: string;
60
- };
61
-
62
- export type SingleContainerAppForm = AppBase & {
63
- appType: AppType.AppTypeContainer;
64
- specType: AppSpecType.OCI_IMAGE;
65
- name: string;
66
- image: string;
67
- ports?: PortMapping[];
68
- limits?: ApplicationResourceLimits;
69
- runAs?: string;
70
- };
71
-
72
- export type QuadletImageAppForm = AppBase & {
73
- appType: AppType.AppTypeQuadlet;
74
- specType: AppSpecType.OCI_IMAGE;
75
- image: string;
76
- runAs?: string;
77
- };
78
-
79
- export type QuadletInlineAppForm = AppBase & {
80
- appType: AppType.AppTypeQuadlet;
81
- specType: AppSpecType.INLINE;
82
- name: string; // transforms the field in required
83
- files: InlineContent[];
84
- runAs?: string;
85
- };
86
-
87
- export type ComposeImageAppForm = AppBase & {
88
- appType: AppType.AppTypeCompose;
89
- specType: AppSpecType.OCI_IMAGE;
90
- image: string;
91
- };
92
-
93
- export type ComposeInlineAppForm = AppBase & {
94
- appType: AppType.AppTypeCompose;
95
- specType: AppSpecType.INLINE;
96
- name: string;
97
- files: InlineContent[];
98
- };
99
-
100
- export type HelmImageAppForm = Omit<AppBase, 'variables' | 'volumes'> & {
101
- appType: AppType.AppTypeHelm;
102
- specType: AppSpecType.OCI_IMAGE;
103
- image: string;
104
- namespace?: string;
105
- valuesFiles: Array<string>;
106
- valuesYaml?: string;
107
- };
108
-
109
49
  export const isGitConfigTemplate = (configTemplate: ConfigTemplate): configTemplate is GitConfigTemplate =>
110
50
  configTemplate.type === ConfigType.GIT;
111
51
 
@@ -123,50 +63,70 @@ export type RepoConfig = GitConfigProviderSpec | HttpConfigProviderSpec;
123
63
  export const isRepoConfig = (config: ConfigSourceProvider): config is RepoConfig =>
124
64
  isGitProviderSpec(config) || isHttpProviderSpec(config);
125
65
 
126
- export type AppForm =
127
- | QuadletImageAppForm
128
- | QuadletInlineAppForm
129
- | ComposeImageAppForm
130
- | ComposeInlineAppForm
131
- | SingleContainerAppForm
132
- | HelmImageAppForm;
133
-
134
- export const isImageAppProvider = (
135
- app: ApplicationProviderSpecFixed,
136
- ): app is ApplicationProviderSpecFixed & ImageApplicationProviderSpec => 'image' in app;
137
-
138
- // Type guards for the 5 explicit types
139
- export const isQuadletImageAppForm = (app: AppForm): app is QuadletImageAppForm =>
140
- app.appType === AppType.AppTypeQuadlet && app.specType === AppSpecType.OCI_IMAGE;
141
- export const isQuadletInlineAppForm = (app: AppForm): app is QuadletInlineAppForm =>
142
- app.appType === AppType.AppTypeQuadlet && app.specType === AppSpecType.INLINE;
143
- export const isComposeImageAppForm = (app: AppForm): app is ComposeImageAppForm =>
144
- app.appType === AppType.AppTypeCompose && app.specType === AppSpecType.OCI_IMAGE;
145
- export const isComposeInlineAppForm = (app: AppForm): app is ComposeInlineAppForm =>
146
- app.appType === AppType.AppTypeCompose && app.specType === AppSpecType.INLINE;
147
- export const isSingleContainerAppForm = (app: AppForm): app is SingleContainerAppForm =>
148
- app.appType === AppType.AppTypeContainer;
149
- export const isHelmImageAppForm = (app: AppForm): app is HelmImageAppForm =>
150
- app.appType === AppType.AppTypeHelm && app.specType === AppSpecType.OCI_IMAGE;
66
+ export const isImageVariantApp = (
67
+ app: ApplicationProviderSpec,
68
+ ): app is ApplicationProviderSpec & ImageApplicationProviderSpec => 'image' in app;
69
+ export const isInlineVariantApp = (
70
+ app: ApplicationProviderSpec,
71
+ ): app is ApplicationProviderSpec & InlineApplicationProviderSpec => 'inline' in app;
151
72
 
152
73
  export type ApplicationVolumeForm = {
153
74
  name: string;
154
- imageRef?: string;
155
- imagePullPolicy?: ImagePullPolicy;
156
- mountPath?: string;
75
+ imageRef: string;
76
+ imagePullPolicy: ImagePullPolicy;
77
+ mountPath: string;
157
78
  };
158
79
 
80
+ export type PortMapping = {
81
+ hostPort: string;
82
+ containerPort: string;
83
+ };
84
+
85
+ export type VariablesForm = { name: string; value: string }[];
86
+
87
+ export type InlineFileForm = { path: string; content?: string; base64?: boolean };
88
+
89
+ type InlineOrImageVariantForm = {
90
+ specType: AppSpecType;
91
+ image: string;
92
+ files: InlineFileForm[];
93
+ };
94
+
95
+ export type SingleContainerAppForm = Omit<ContainerApplication, 'ports' | 'resources' | 'envVars' | 'volumes'> & {
96
+ specType: AppSpecType.OCI_IMAGE;
97
+ ports: PortMapping[];
98
+ cpuLimit: string;
99
+ memoryLimit: string;
100
+ variables: VariablesForm;
101
+ volumes: ApplicationVolumeForm[];
102
+ };
103
+
104
+ export type HelmAppForm = Omit<HelmApplication, 'values'> & {
105
+ specType: AppSpecType.OCI_IMAGE;
106
+ valuesYaml?: string;
107
+ valuesFiles: string[];
108
+ };
109
+
110
+ export type QuadletAppForm = Omit<QuadletApplication, 'envVars' | 'volumes' | 'image' | 'inline'> &
111
+ InlineOrImageVariantForm & {
112
+ variables: VariablesForm;
113
+ volumes: ApplicationVolumeForm[];
114
+ };
115
+
116
+ export type ComposeAppForm = Omit<ComposeApplication, 'envVars' | 'volumes' | 'image' | 'inline'> &
117
+ InlineOrImageVariantForm & {
118
+ variables: VariablesForm;
119
+ volumes: ApplicationVolumeForm[];
120
+ };
121
+
122
+ export type AppForm = SingleContainerAppForm | HelmAppForm | QuadletAppForm | ComposeAppForm;
123
+
159
124
  const hasTemplateVariables = (str: string) => /{{.+?}}/.test(str);
160
125
 
161
- export const getAppIdentifier = (app: AppForm) => {
162
- if (isSingleContainerAppForm(app)) {
163
- return app.name || app.image;
164
- }
165
- if (isQuadletImageAppForm(app) || isComposeImageAppForm(app)) {
166
- return app.name || app.image;
167
- }
168
- // Name is mandatory for inline applications
169
- return app.name;
126
+ export const getAppIdentifier = (app: AppForm | ApplicationProviderSpec): string => {
127
+ if (app.name) return app.name;
128
+ if ('image' in app && app.image) return app.image;
129
+ return '';
170
130
  };
171
131
 
172
132
  const removeSlashes = (url: string | undefined) => (url || '').replace(/^\/+|\/+$/g, '');
@@ -214,7 +174,7 @@ export const isKubeProviderSpec = (providerSpec: ConfigProviderSpec): providerSp
214
174
 
215
175
  export type InlineConfigTemplate = ConfigTemplate & {
216
176
  type: ConfigType.INLINE;
217
- files: Array<InlineContent & { permissions?: string; user?: string; group?: string }>;
177
+ files: Array<InlineFileForm & { permissions?: string; user?: string; group?: string }>;
218
178
  };
219
179
 
220
180
  export const isInlineConfigTemplate = (configTemplate: ConfigTemplate): configTemplate is InlineConfigTemplate =>
@@ -1,7 +1,4 @@
1
1
  import {
2
- AppType,
3
- ApplicationEnvVars,
4
- ApplicationVolumeProviderSpec,
5
2
  AuthProvider,
6
3
  Condition,
7
4
  ConditionType,
@@ -9,7 +6,6 @@ import {
9
6
  EnrollmentRequest,
10
7
  FileContent,
11
8
  Fleet,
12
- ImageApplicationProviderSpec,
13
9
  OAuth2ProviderSpec,
14
10
  OIDCProviderSpec,
15
11
  RelativePath,
@@ -55,16 +51,10 @@ export type AnnotationType = DeviceAnnotation; // Add more types when they are a
55
51
 
56
52
  export const isFleet = (resource: ResourceSync | Fleet): resource is Fleet => resource.kind === 'Fleet';
57
53
 
58
- // ApplicationProviderSpec's definition for inline files adds a Record<string, any>. We use the fixed types to get full Typescript checks for the field
54
+ // ApplicationProviderSpec's definition for inline files adds a Record<string, any>.
55
+ // We use the fixed type to get proper Typescript checks for the field
59
56
  export type InlineApplicationFileFixed = FileContent & RelativePath;
60
57
 
61
- // "FixedApplicationProviderSpec" will need to be manually adjusted whenever the API definition changes
62
- export type ApplicationProviderSpecFixed = ApplicationEnvVars &
63
- ApplicationVolumeProviderSpec & {
64
- name?: string;
65
- appType: AppType;
66
- } & (ImageApplicationProviderSpec | { inline: InlineApplicationFileFixed[] });
67
-
68
58
  type CliArtifact = {
69
59
  os: string;
70
60
  arch: string;
package/src/types/rbac.ts CHANGED
@@ -21,4 +21,10 @@ export enum RESOURCE {
21
21
  ALERTS = 'alerts',
22
22
  AUTH_PROVIDER = 'authproviders',
23
23
  IMAGE_BUILD = 'imagebuilds',
24
+ IMAGE_BUILD_LOG = 'imagebuilds/log',
25
+ IMAGE_BUILD_CANCEL = 'imagebuilds/cancel',
26
+ IMAGE_EXPORT = 'imageexports',
27
+ IMAGE_EXPORT_LOG = 'imageexports/log',
28
+ IMAGE_EXPORT_CANCEL = 'imageexports/cancel',
29
+ IMAGE_EXPORT_DOWNLOAD = 'imageexports/download',
24
30
  }
@@ -99,6 +99,14 @@ export const isImageBuildActiveReason = (reason: ImageBuildConditionReason): boo
99
99
  );
100
100
  };
101
101
 
102
+ export const isImageBuildCancelable = (reason: ImageBuildConditionReason): boolean => {
103
+ return (
104
+ reason === ImageBuildConditionReason.ImageBuildConditionReasonPending ||
105
+ reason === ImageBuildConditionReason.ImageBuildConditionReasonBuilding ||
106
+ reason === ImageBuildConditionReason.ImageBuildConditionReasonPushing
107
+ );
108
+ };
109
+
102
110
  export const isImageExportActiveReason = (reason: ImageExportConditionReason): boolean => {
103
111
  return (
104
112
  reason === ImageExportConditionReason.ImageExportConditionReasonPending ||
@@ -1,5 +1,5 @@
1
1
  import fuzzy from 'fuzzysearch';
2
- import { API_VERSION } from '../constants';
2
+ import { CORE_API_VERSION } from '../constants';
3
3
 
4
4
  // Must be an even number for "getSearchResultsCount" to work
5
5
  export const MAX_TOTAL_SEARCH_RESULTS = 10;
@@ -26,7 +26,7 @@ export const getSearchResultsCount = (labelCount: number, fleetCount: number) =>
26
26
  };
27
27
 
28
28
  export const getEmptyFleetSearch = () => ({
29
- apiVersion: API_VERSION,
29
+ apiVersion: CORE_API_VERSION,
30
30
  kind: 'Fleet',
31
31
  metadata: {},
32
32
  items: [],