@flightctl/ui-components 1.1.0-rc1 → 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.
Files changed (306) hide show
  1. package/dist/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
  2. package/dist/types/imagebuilder/index.d.ts +2 -0
  3. package/dist/types/imagebuilder/index.d.ts.map +1 -1
  4. package/dist/types/imagebuilder/index.js +3 -1
  5. package/dist/types/imagebuilder/index.js.map +1 -1
  6. package/dist/types/imagebuilder/models/ApiVersion.d.ts +8 -0
  7. package/dist/types/imagebuilder/models/ApiVersion.d.ts.map +1 -0
  8. package/dist/types/imagebuilder/models/ApiVersion.js +16 -0
  9. package/dist/types/imagebuilder/models/ApiVersion.js.map +1 -0
  10. package/dist/types/imagebuilder/models/ImageBuild.d.ts +2 -4
  11. package/dist/types/imagebuilder/models/ImageBuild.d.ts.map +1 -1
  12. package/dist/types/imagebuilder/models/ImageBuildList.d.ts +2 -4
  13. package/dist/types/imagebuilder/models/ImageBuildList.d.ts.map +1 -1
  14. package/dist/types/imagebuilder/models/ImageExport.d.ts +2 -4
  15. package/dist/types/imagebuilder/models/ImageExport.d.ts.map +1 -1
  16. package/dist/types/imagebuilder/models/ImageExportList.d.ts +2 -4
  17. package/dist/types/imagebuilder/models/ImageExportList.d.ts.map +1 -1
  18. package/dist/types/imagebuilder/models/Status.d.ts +28 -0
  19. package/dist/types/imagebuilder/models/Status.d.ts.map +1 -0
  20. package/dist/types/imagebuilder/models/Status.js +3 -0
  21. package/dist/types/imagebuilder/models/Status.js.map +1 -0
  22. package/dist/types/index.d.ts +8 -0
  23. package/dist/types/index.d.ts.map +1 -1
  24. package/dist/types/index.js +3 -1
  25. package/dist/types/index.js.map +1 -1
  26. package/dist/types/models/ApiVersion.d.ts +9 -0
  27. package/dist/types/models/ApiVersion.d.ts.map +1 -0
  28. package/dist/types/models/ApiVersion.js +17 -0
  29. package/dist/types/models/ApiVersion.js.map +1 -0
  30. package/dist/types/models/ApplicationProviderBase.d.ts +12 -0
  31. package/dist/types/models/ApplicationProviderBase.d.ts.map +1 -0
  32. package/dist/types/models/ApplicationProviderBase.js +3 -0
  33. package/dist/types/models/ApplicationProviderBase.js.map +1 -0
  34. package/dist/types/models/ApplicationProviderSpec.d.ts +5 -15
  35. package/dist/types/models/ApplicationProviderSpec.d.ts.map +1 -1
  36. package/dist/types/models/ApplicationUser.d.ts +7 -0
  37. package/dist/types/models/ApplicationUser.d.ts.map +1 -0
  38. package/dist/types/models/ApplicationUser.js +3 -0
  39. package/dist/types/models/ApplicationUser.js.map +1 -0
  40. package/dist/types/models/AuthConfig.d.ts +2 -4
  41. package/dist/types/models/AuthConfig.d.ts.map +1 -1
  42. package/dist/types/models/AuthProvider.d.ts +2 -4
  43. package/dist/types/models/AuthProvider.d.ts.map +1 -1
  44. package/dist/types/models/AuthProviderList.d.ts +2 -4
  45. package/dist/types/models/AuthProviderList.d.ts.map +1 -1
  46. package/dist/types/models/CertificateSigningRequest.d.ts +2 -4
  47. package/dist/types/models/CertificateSigningRequest.d.ts.map +1 -1
  48. package/dist/types/models/CertificateSigningRequestList.d.ts +2 -4
  49. package/dist/types/models/CertificateSigningRequestList.d.ts.map +1 -1
  50. package/dist/types/models/ComposeApplication.d.ts +7 -0
  51. package/dist/types/models/ComposeApplication.d.ts.map +1 -0
  52. package/dist/types/models/ComposeApplication.js +3 -0
  53. package/dist/types/models/ComposeApplication.js.map +1 -0
  54. package/dist/types/models/ContainerApplication.d.ts +18 -0
  55. package/dist/types/models/ContainerApplication.d.ts.map +1 -0
  56. package/dist/types/models/ContainerApplication.js +3 -0
  57. package/dist/types/models/ContainerApplication.js.map +1 -0
  58. package/dist/types/models/ContainerApplicationProperties.d.ts +13 -0
  59. package/dist/types/models/ContainerApplicationProperties.d.ts.map +1 -0
  60. package/dist/types/models/ContainerApplicationProperties.js +3 -0
  61. package/dist/types/models/ContainerApplicationProperties.js.map +1 -0
  62. package/dist/types/models/Device.d.ts +2 -4
  63. package/dist/types/models/Device.d.ts.map +1 -1
  64. package/dist/types/models/DeviceList.d.ts +2 -4
  65. package/dist/types/models/DeviceList.d.ts.map +1 -1
  66. package/dist/types/models/EnrollmentRequest.d.ts +2 -4
  67. package/dist/types/models/EnrollmentRequest.d.ts.map +1 -1
  68. package/dist/types/models/EnrollmentRequestList.d.ts +2 -4
  69. package/dist/types/models/EnrollmentRequestList.d.ts.map +1 -1
  70. package/dist/types/models/Event.d.ts +2 -4
  71. package/dist/types/models/Event.d.ts.map +1 -1
  72. package/dist/types/models/Event.js.map +1 -1
  73. package/dist/types/models/EventList.d.ts +2 -4
  74. package/dist/types/models/EventList.d.ts.map +1 -1
  75. package/dist/types/models/Fleet.d.ts +2 -4
  76. package/dist/types/models/Fleet.d.ts.map +1 -1
  77. package/dist/types/models/FleetList.d.ts +2 -4
  78. package/dist/types/models/FleetList.d.ts.map +1 -1
  79. package/dist/types/models/HelmApplication.d.ts +20 -0
  80. package/dist/types/models/HelmApplication.d.ts.map +1 -0
  81. package/dist/types/models/HelmApplication.js +3 -0
  82. package/dist/types/models/HelmApplication.js.map +1 -0
  83. package/dist/types/models/ImageApplicationProviderSpec.d.ts +2 -22
  84. package/dist/types/models/ImageApplicationProviderSpec.d.ts.map +1 -1
  85. package/dist/types/models/InlineApplicationProviderSpec.d.ts +2 -3
  86. package/dist/types/models/InlineApplicationProviderSpec.d.ts.map +1 -1
  87. package/dist/types/models/Organization.d.ts +2 -4
  88. package/dist/types/models/Organization.d.ts.map +1 -1
  89. package/dist/types/models/OrganizationList.d.ts +2 -4
  90. package/dist/types/models/OrganizationList.d.ts.map +1 -1
  91. package/dist/types/models/QuadletApplication.d.ts +8 -0
  92. package/dist/types/models/QuadletApplication.d.ts.map +1 -0
  93. package/dist/types/models/QuadletApplication.js +3 -0
  94. package/dist/types/models/QuadletApplication.js.map +1 -0
  95. package/dist/types/models/Repository.d.ts +2 -4
  96. package/dist/types/models/Repository.d.ts.map +1 -1
  97. package/dist/types/models/RepositoryList.d.ts +2 -4
  98. package/dist/types/models/RepositoryList.d.ts.map +1 -1
  99. package/dist/types/models/ResourceSync.d.ts +2 -4
  100. package/dist/types/models/ResourceSync.d.ts.map +1 -1
  101. package/dist/types/models/ResourceSyncList.d.ts +2 -4
  102. package/dist/types/models/ResourceSyncList.d.ts.map +1 -1
  103. package/dist/types/models/Status.d.ts +2 -4
  104. package/dist/types/models/Status.d.ts.map +1 -1
  105. package/dist/types/models/TemplateVersion.d.ts +2 -4
  106. package/dist/types/models/TemplateVersion.d.ts.map +1 -1
  107. package/dist/types/models/TemplateVersionList.d.ts +2 -4
  108. package/dist/types/models/TemplateVersionList.d.ts.map +1 -1
  109. package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.d.ts.map +1 -1
  110. package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js +51 -51
  111. package/dist/ui-components/src/components/AuthProvider/CreateAuthProvider/utils.js.map +1 -1
  112. package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js +1 -1
  113. package/dist/ui-components/src/components/DetailsPage/Tables/ApplicationsTable.js.map +1 -1
  114. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.d.ts.map +1 -1
  115. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js +5 -4
  116. package/dist/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.js.map +1 -1
  117. package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.d.ts.map +1 -1
  118. package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.js +5 -1
  119. package/dist/ui-components/src/components/Device/DeviceDetails/TerminalTab.js.map +1 -1
  120. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts +3 -3
  121. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.d.ts.map +1 -1
  122. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js +310 -363
  123. package/dist/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.js.map +1 -1
  124. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts +1 -3
  125. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.d.ts.map +1 -1
  126. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js +18 -19
  127. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.js.map +1 -1
  128. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts +1 -3
  129. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.d.ts.map +1 -1
  130. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js +5 -4
  131. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.js.map +1 -1
  132. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts +1 -3
  133. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.d.ts.map +1 -1
  134. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js +2 -2
  135. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.js.map +1 -1
  136. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts +3 -3
  137. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.d.ts.map +1 -1
  138. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js +20 -23
  139. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.js.map +1 -1
  140. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js +3 -3
  141. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.js.map +1 -1
  142. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.d.ts.map +1 -1
  143. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js +25 -45
  144. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.js.map +1 -1
  145. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts +8 -0
  146. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.d.ts.map +1 -0
  147. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js +37 -0
  148. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.js.map +1 -0
  149. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts +1 -3
  150. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.d.ts.map +1 -1
  151. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js +5 -8
  152. package/dist/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.js.map +1 -1
  153. package/dist/ui-components/src/components/Device/EditDeviceWizard/utils.d.ts +18 -18
  154. package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.d.ts +4 -2
  155. package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.d.ts.map +1 -1
  156. package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.js +2 -2
  157. package/dist/ui-components/src/components/ErrorAlert/ErrorAlert.js.map +1 -1
  158. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.d.ts +1 -1
  159. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.d.ts.map +1 -1
  160. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js +3 -3
  161. package/dist/ui-components/src/components/Fleet/CreateFleet/utils.js.map +1 -1
  162. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.d.ts.map +1 -1
  163. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js +3 -1
  164. package/dist/ui-components/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.js.map +1 -1
  165. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts +7 -0
  166. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.d.ts.map +1 -0
  167. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js +40 -0
  168. package/dist/ui-components/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.js.map +1 -0
  169. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts +8 -0
  170. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.d.ts.map +1 -0
  171. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js +37 -0
  172. package/dist/ui-components/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.js.map +1 -0
  173. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.d.ts.map +1 -1
  174. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.js +4 -3
  175. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.js.map +1 -1
  176. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.js +1 -1
  177. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.js.map +1 -1
  178. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.d.ts.map +1 -1
  179. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.js +13 -10
  180. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.js.map +1 -1
  181. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.d.ts.map +1 -1
  182. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.js +4 -2
  183. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.js.map +1 -1
  184. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.d.ts.map +1 -1
  185. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.js +7 -1
  186. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.js.map +1 -1
  187. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/types.d.ts +3 -5
  188. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/types.d.ts.map +1 -1
  189. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.d.ts +3 -2
  190. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.d.ts.map +1 -1
  191. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js +139 -34
  192. package/dist/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/utils.js.map +1 -1
  193. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.d.ts.map +1 -1
  194. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js +23 -12
  195. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.js.map +1 -1
  196. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.d.ts.map +1 -1
  197. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js +115 -39
  198. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.js.map +1 -1
  199. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.d.ts +1 -0
  200. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.d.ts.map +1 -1
  201. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.js +17 -18
  202. package/dist/ui-components/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.js.map +1 -1
  203. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts +5 -2
  204. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.d.ts.map +1 -1
  205. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js +22 -12
  206. package/dist/ui-components/src/components/ImageBuilds/ImageBuildRow.js.map +1 -1
  207. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.d.ts.map +1 -1
  208. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js +17 -8
  209. package/dist/ui-components/src/components/ImageBuilds/ImageBuildsPage.js.map +1 -1
  210. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts +10 -9
  211. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.d.ts.map +1 -1
  212. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js +122 -26
  213. package/dist/ui-components/src/components/ImageBuilds/ImageExportCards.js.map +1 -1
  214. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.d.ts +2 -1
  215. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.d.ts.map +1 -1
  216. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js +10 -4
  217. package/dist/ui-components/src/components/Repository/CreateRepository/CreateRepositoryForm.js.map +1 -1
  218. package/dist/ui-components/src/components/Repository/CreateRepository/utils.d.ts.map +1 -1
  219. package/dist/ui-components/src/components/Repository/CreateRepository/utils.js +3 -4
  220. package/dist/ui-components/src/components/Repository/CreateRepository/utils.js.map +1 -1
  221. package/dist/ui-components/src/components/form/RepositorySelect.d.ts.map +1 -1
  222. package/dist/ui-components/src/components/form/RepositorySelect.js +1 -1
  223. package/dist/ui-components/src/components/form/RepositorySelect.js.map +1 -1
  224. package/dist/ui-components/src/components/form/UploadField.d.ts.map +1 -1
  225. package/dist/ui-components/src/components/form/UploadField.js +25 -16
  226. package/dist/ui-components/src/components/form/UploadField.js.map +1 -1
  227. package/dist/ui-components/src/components/form/validations.d.ts +25 -18
  228. package/dist/ui-components/src/components/form/validations.d.ts.map +1 -1
  229. package/dist/ui-components/src/components/form/validations.js +79 -33
  230. package/dist/ui-components/src/components/form/validations.js.map +1 -1
  231. package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.d.ts +2 -1
  232. package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.d.ts.map +1 -1
  233. package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.js +2 -2
  234. package/dist/ui-components/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.js.map +1 -1
  235. package/dist/ui-components/src/constants.d.ts +5 -6
  236. package/dist/ui-components/src/constants.d.ts.map +1 -1
  237. package/dist/ui-components/src/constants.js +19 -11
  238. package/dist/ui-components/src/constants.js.map +1 -1
  239. package/dist/ui-components/src/hooks/useWebSocket.d.ts.map +1 -1
  240. package/dist/ui-components/src/hooks/useWebSocket.js +25 -4
  241. package/dist/ui-components/src/hooks/useWebSocket.js.map +1 -1
  242. package/dist/ui-components/src/types/deviceSpec.d.ts +44 -76
  243. package/dist/ui-components/src/types/deviceSpec.d.ts.map +1 -1
  244. package/dist/ui-components/src/types/deviceSpec.js +13 -26
  245. package/dist/ui-components/src/types/deviceSpec.js.map +1 -1
  246. package/dist/ui-components/src/types/extraTypes.d.ts +1 -7
  247. package/dist/ui-components/src/types/extraTypes.d.ts.map +1 -1
  248. package/dist/ui-components/src/types/extraTypes.js.map +1 -1
  249. package/dist/ui-components/src/types/rbac.d.ts +7 -1
  250. package/dist/ui-components/src/types/rbac.d.ts.map +1 -1
  251. package/dist/ui-components/src/types/rbac.js +6 -0
  252. package/dist/ui-components/src/types/rbac.js.map +1 -1
  253. package/dist/ui-components/src/utils/imageBuilds.d.ts +1 -0
  254. package/dist/ui-components/src/utils/imageBuilds.d.ts.map +1 -1
  255. package/dist/ui-components/src/utils/imageBuilds.js +20 -29
  256. package/dist/ui-components/src/utils/imageBuilds.js.map +1 -1
  257. package/dist/ui-components/src/utils/search.d.ts +2 -1
  258. package/dist/ui-components/src/utils/search.d.ts.map +1 -1
  259. package/dist/ui-components/src/utils/search.js +2 -2
  260. package/dist/ui-components/src/utils/search.js.map +1 -1
  261. package/package.json +2 -2
  262. package/src/components/AuthProvider/CreateAuthProvider/utils.ts +2 -2
  263. package/src/components/DetailsPage/Tables/ApplicationsTable.tsx +2 -2
  264. package/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx +10 -4
  265. package/src/components/Device/DeviceDetails/TerminalTab.tsx +9 -1
  266. package/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts +361 -425
  267. package/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx +19 -29
  268. package/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx +5 -13
  269. package/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx +2 -16
  270. package/src/components/Device/EditDeviceWizard/steps/ApplicationInlineForm.tsx +8 -7
  271. package/src/components/Device/EditDeviceWizard/steps/ApplicationIntegritySettings.tsx +5 -5
  272. package/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx +29 -101
  273. package/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx +87 -0
  274. package/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx +5 -10
  275. package/src/components/ErrorAlert/ErrorAlert.tsx +13 -3
  276. package/src/components/Fleet/CreateFleet/utils.ts +4 -5
  277. package/src/components/Fleet/ImportFleetWizard/steps/RepositoryStep.tsx +11 -8
  278. package/src/components/ImageBuilds/CancelImageBuildModal/CancelImageBuildModal.tsx +81 -0
  279. package/src/components/ImageBuilds/ConfirmImageExportModal/ConfirmImageExportModal.tsx +61 -0
  280. package/src/components/ImageBuilds/CreateImageBuildWizard/CreateImageBuildWizard.tsx +8 -3
  281. package/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx +1 -1
  282. package/src/components/ImageBuilds/CreateImageBuildWizard/steps/RegistrationStep.tsx +18 -10
  283. package/src/components/ImageBuilds/CreateImageBuildWizard/steps/ReviewStep.tsx +5 -1
  284. package/src/components/ImageBuilds/CreateImageBuildWizard/steps/SourceImageStep.tsx +13 -1
  285. package/src/components/ImageBuilds/CreateImageBuildWizard/types.ts +3 -6
  286. package/src/components/ImageBuilds/CreateImageBuildWizard/utils.ts +161 -37
  287. package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildDetailsPage.tsx +36 -17
  288. package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildExportsGallery.tsx +131 -44
  289. package/src/components/ImageBuilds/ImageBuildDetails/ImageBuildLogsTab.tsx +22 -26
  290. package/src/components/ImageBuilds/ImageBuildRow.tsx +41 -20
  291. package/src/components/ImageBuilds/ImageBuildsPage.tsx +34 -15
  292. package/src/components/ImageBuilds/ImageExportCards.tsx +198 -80
  293. package/src/components/Repository/CreateRepository/CreateRepositoryForm.css +5 -1
  294. package/src/components/Repository/CreateRepository/CreateRepositoryForm.tsx +14 -4
  295. package/src/components/Repository/CreateRepository/utils.ts +4 -4
  296. package/src/components/form/RepositorySelect.tsx +1 -0
  297. package/src/components/form/UploadField.tsx +29 -30
  298. package/src/components/form/validations.ts +156 -106
  299. package/src/components/modals/CreateRepositoryModal/CreateRepositoryModal.tsx +3 -1
  300. package/src/constants.ts +19 -6
  301. package/src/hooks/useWebSocket.ts +25 -3
  302. package/src/types/deviceSpec.ts +68 -108
  303. package/src/types/extraTypes.ts +2 -12
  304. package/src/types/rbac.ts +6 -0
  305. package/src/utils/imageBuilds.ts +22 -32
  306. package/src/utils/search.ts +2 -2
@@ -2,32 +2,148 @@ import { TFunction } from 'i18next';
2
2
  import * as Yup from 'yup';
3
3
 
4
4
  import {
5
+ ApiVersion,
5
6
  BindingType,
6
7
  ExportFormatType,
7
8
  ImageBuild,
8
9
  ImageBuildDestination,
9
10
  ImageBuildSource,
11
+ ImageBuildUserConfiguration,
10
12
  ImageExport,
11
13
  ResourceKind,
12
14
  } from '@flightctl/types/imagebuilder';
13
- import { API_VERSION } from '../../../constants';
15
+ import { validImageBuildName } from '../../form/validations';
14
16
  import { ImageBuildFormValues } from './types';
15
17
  import { ImageBuildWithExports } from '../../../types/extraTypes';
16
18
 
17
- export const getValidationSchema = (t: TFunction) => {
18
- return Yup.object<ImageBuildFormValues>({
19
- source: Yup.object<ImageBuildSource>({
20
- repository: Yup.string().required(t('Source repository is required')),
21
- imageName: Yup.string().required(t('Image name is required')),
22
- imageTag: Yup.string().required(t('Image tag is required')),
23
- }).required(t('Source image is required')),
24
- destination: Yup.object<ImageBuildDestination>({
25
- repository: Yup.string().required(t('Target repository is required')),
26
- imageName: Yup.string().required(t('Image name is required')),
27
- imageTag: Yup.string().required(t('Image tag is required')),
28
- }).required(t('Target image is required')),
29
- bindingType: Yup.string<BindingType>().required(t('Binding type is required')),
19
+ export const PUBLIC_KEY_MAX_LENGTH = 8 * 1024; // (8 KB)
20
+ const VALID_SSH_PUBLIC_KEY_TYPES = [
21
+ 'ssh-rsa',
22
+ 'ssh-ed25519',
23
+ 'ecdsa-sha2-nistp256',
24
+ 'ecdsa-sha2-nistp384',
25
+ 'ecdsa-sha2-nistp521',
26
+ 'ssh-dss',
27
+ ];
28
+
29
+ const SSH_PUBLIC_KEY_BASE64_DATA_REGEX = /^(?=.{50,}$)[A-Za-z0-9+/]+=*$/;
30
+ // Characters that could be used for injection attacks
31
+ const MALICIOUS_PUBLIC_KEY_CHARACTERS = /[;|&`()[\]{}<>"'\\\t$]/;
32
+
33
+ const OCI_IMAGE_NAME_MAX_LENGTH = 255;
34
+ const OCI_IMAGE_NAME_REGEX = /^[a-z0-9]+(?:[._-]+[a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-]+[a-z0-9]+)*)*$/;
35
+ const OCI_IMAGE_NAME_VALID_CHARS = /^[a-z0-9._/-]+$/;
36
+
37
+ const OCI_IMAGE_TAG_MAX_LENGTH = 128;
38
+ const OCI_IMAGE_TAG_REGEX = /^[\w][\w.-]{0,127}$/;
39
+ const OCI_IMAGE_TAG_VALID_CHARS = /^[\w.-]+$/;
40
+
41
+ /** Returns an error message for image name: invalid chars, or invalid format. */
42
+ const getImageNameValidationError = (value: string, t: TFunction): string | undefined => {
43
+ if (!value) return undefined;
44
+ if (value.length > OCI_IMAGE_NAME_MAX_LENGTH) {
45
+ return t('Image name must not exceed {{max}} characters.', { max: OCI_IMAGE_NAME_MAX_LENGTH });
46
+ }
47
+ if (!OCI_IMAGE_NAME_VALID_CHARS.test(value)) {
48
+ return t('Image name may only contain alphanumeric characters, dots, underscores, hyphens, and slashes.');
49
+ }
50
+ if (!OCI_IMAGE_NAME_REGEX.test(value)) {
51
+ return t('Only alphanumeric characters are allowed at the start and end of each path component.');
52
+ }
53
+ return undefined;
54
+ };
55
+
56
+ const getImageTagValidationError = (value: string, t: TFunction): string | undefined => {
57
+ if (!value) return undefined;
58
+ if (value.length > OCI_IMAGE_TAG_MAX_LENGTH) {
59
+ return t('Image tag must not exceed {{max}} characters.', { max: OCI_IMAGE_TAG_MAX_LENGTH });
60
+ }
61
+ if (!OCI_IMAGE_TAG_VALID_CHARS.test(value)) {
62
+ return t('Image tag may only contain letters, numbers, underscores, dots, and hyphens.');
63
+ }
64
+ if (!OCI_IMAGE_TAG_REGEX.test(value)) {
65
+ return t('Image tag must start with a letter, number, or underscore.');
66
+ }
67
+ return undefined;
68
+ };
69
+
70
+ const getPublicKeyValidationError = (publicKey: string, t: TFunction): string | undefined => {
71
+ if (publicKey.length > PUBLIC_KEY_MAX_LENGTH) {
72
+ return t('SSH public key is too long');
73
+ }
74
+
75
+ // Allow newlines only at the end
76
+ const trimmedKey = publicKey.replace(/[\r\n]+$/g, '');
77
+ if (/[\r\n]/.test(trimmedKey)) {
78
+ return t('A single public key can be provided only');
79
+ }
80
+
81
+ if (MALICIOUS_PUBLIC_KEY_CHARACTERS.test(trimmedKey)) {
82
+ return t('Invalid SSH public key');
83
+ }
84
+
85
+ const parts = trimmedKey.trim().split(/\s+/);
86
+ if (parts.length < 2) {
87
+ return t('Invalid SSH public key format. Expected: "[TYPE] key [comment]"');
88
+ }
89
+
90
+ const keyType = parts[0];
91
+ if (!VALID_SSH_PUBLIC_KEY_TYPES.includes(keyType)) {
92
+ return t('Unsupported SSH public key type. Supported types: {{supportedTypes}}', {
93
+ supportedTypes: VALID_SSH_PUBLIC_KEY_TYPES.join(', '),
94
+ });
95
+ }
96
+
97
+ const base64Data = parts[1];
98
+ if (!SSH_PUBLIC_KEY_BASE64_DATA_REGEX.test(base64Data)) {
99
+ return t('Invalid SSH public key data');
100
+ }
101
+
102
+ return undefined;
103
+ };
104
+
105
+ const validImageBuildImageFields = (t: TFunction) =>
106
+ Yup.object<ImageBuildSource | ImageBuildDestination>({
107
+ repository: Yup.string().required(t('Repository is required')),
108
+ imageName: Yup.string()
109
+ .required(t('Image name is required'))
110
+ .test('oci-image-name', function (value) {
111
+ if (!value) return true;
112
+ const error = getImageNameValidationError(value, t);
113
+ return error ? this.createError({ message: error }) : true;
114
+ }),
115
+ imageTag: Yup.string()
116
+ .required(t('Image tag is required'))
117
+ .test('oci-image-tag', function (value) {
118
+ if (!value) return true;
119
+ const error = getImageTagValidationError(value, t);
120
+ return error ? this.createError({ message: error }) : true;
121
+ }),
30
122
  });
123
+
124
+ export const getValidationSchema = (t: TFunction) => {
125
+ return Yup.lazy((values: ImageBuildFormValues) =>
126
+ Yup.object<ImageBuildFormValues>({
127
+ buildName: validImageBuildName(t),
128
+ source: validImageBuildImageFields(t).required(t('Source image is required')),
129
+ destination: validImageBuildImageFields(t).required(t('Target image is required')),
130
+ bindingType: Yup.string<BindingType>().required(t('Binding type is required')),
131
+ userConfiguration: Yup.object<ImageBuildUserConfiguration>({
132
+ username: values.remoteAccessEnabled ? Yup.string().required(t('Username is required')) : Yup.string(),
133
+ publickey: values.remoteAccessEnabled
134
+ ? Yup.string()
135
+ .required(t('SSH public key is required'))
136
+ .test('flightctl-ssh-public-key', function (publicKey) {
137
+ if (!publicKey) {
138
+ return true;
139
+ }
140
+ const error = getPublicKeyValidationError(publicKey, t);
141
+ return error ? this.createError({ message: error }) : true;
142
+ })
143
+ : Yup.string(),
144
+ }),
145
+ }),
146
+ );
31
147
  };
32
148
 
33
149
  // Returns an array with one item per format (VMDK, QCOW2, ISO), where each item is either
@@ -84,32 +200,40 @@ export const toImageBuildWithExports = (imageBuild: ImageBuild): ImageBuildWithE
84
200
  };
85
201
  };
86
202
 
87
- export const getInitialValues = (imageBuild?: ImageBuildWithExports): ImageBuildFormValues => {
203
+ const getExistingImageData = (image: ImageBuildSource | ImageBuildDestination, repoIds: Set<string>) => {
204
+ if (repoIds.has(image.repository)) {
205
+ return image;
206
+ }
207
+ // When copying the image build, drop the reference to the repository if it doesn't exist anymore
208
+ return {
209
+ ...image,
210
+ repository: '',
211
+ };
212
+ };
213
+
214
+ export const getInitialValues = (
215
+ imageBuild: ImageBuildWithExports | undefined,
216
+ repoIds: Set<string>,
217
+ ): ImageBuildFormValues => {
88
218
  if (imageBuild) {
89
219
  const exportFormats = imageBuild.imageExports
90
220
  .filter((ie): ie is ImageExport => ie !== undefined)
91
221
  .map((imageExport) => imageExport.spec.format);
92
222
  const userConfig = imageBuild.spec.userConfiguration;
93
- const userConfiguration = userConfig
94
- ? {
95
- ...userConfig,
96
- enabled: !!(userConfig.username || userConfig.publickey),
97
- }
98
- : {
99
- username: '',
100
- publickey: '',
101
- enabled: false,
102
- };
103
223
  return {
104
- source: imageBuild.spec.source,
105
- destination: imageBuild.spec.destination,
224
+ // Since we're always creating new imageBuilds, we don't copy the current name
225
+ buildName: '',
226
+ source: getExistingImageData(imageBuild.spec.source, repoIds),
227
+ destination: getExistingImageData(imageBuild.spec.destination, repoIds),
106
228
  bindingType: imageBuild.spec.binding.type as BindingType,
107
229
  exportFormats: exportFormats || [],
108
- userConfiguration,
230
+ remoteAccessEnabled: !!(userConfig?.username || userConfig?.publickey),
231
+ userConfiguration: userConfig || { username: '', publickey: '' },
109
232
  };
110
233
  }
111
234
 
112
235
  return {
236
+ buildName: '',
113
237
  source: {
114
238
  repository: '',
115
239
  imageName: '',
@@ -122,10 +246,10 @@ export const getInitialValues = (imageBuild?: ImageBuildWithExports): ImageBuild
122
246
  },
123
247
  bindingType: BindingType.BindingTypeEarly,
124
248
  exportFormats: [],
249
+ remoteAccessEnabled: false,
125
250
  userConfiguration: {
126
251
  username: '',
127
252
  publickey: '',
128
- enabled: false,
129
253
  },
130
254
  };
131
255
  };
@@ -136,13 +260,13 @@ const getHash = () =>
136
260
  .toString(16)
137
261
  .padStart(6, '0');
138
262
 
139
- const generateBuildName = () => `imagebuild-${getHash()}`;
140
263
  const generateExportName = (imageBuildName: string, format: ExportFormatType) => {
141
- return `${imageBuildName}-${format}-${getHash()}`;
264
+ const formatKey = format === ExportFormatType.ExportFormatTypeQCOW2DiskContainer ? 'qcow2-disk' : format;
265
+ return `${imageBuildName}-${formatKey}-${getHash()}`;
142
266
  };
143
267
 
144
268
  export const getImageBuildResource = (values: ImageBuildFormValues): ImageBuild => {
145
- const name = generateBuildName();
269
+ const name = values.buildName;
146
270
  const spec: ImageBuild['spec'] = {
147
271
  source: values.source,
148
272
  destination: values.destination,
@@ -152,9 +276,9 @@ export const getImageBuildResource = (values: ImageBuildFormValues): ImageBuild
152
276
  };
153
277
 
154
278
  // Allow the user to uncheck the toggle without having cleared the fields
155
- const username = values.userConfiguration?.username || '';
156
- const publickey = values.userConfiguration?.publickey || '';
157
- if (values.userConfiguration?.enabled && username && publickey) {
279
+ const username = values.userConfiguration.username || '';
280
+ const publickey = values.userConfiguration.publickey || '';
281
+ if (values.remoteAccessEnabled && username && publickey) {
158
282
  spec.userConfiguration = {
159
283
  username,
160
284
  publickey,
@@ -162,7 +286,7 @@ export const getImageBuildResource = (values: ImageBuildFormValues): ImageBuild
162
286
  }
163
287
 
164
288
  return {
165
- apiVersion: API_VERSION,
289
+ apiVersion: ApiVersion.ApiVersionV1alpha1,
166
290
  kind: ResourceKind.IMAGE_BUILD,
167
291
  metadata: {
168
292
  name,
@@ -175,7 +299,7 @@ export const getImageExportResource = (imageBuildName: string, format: ExportFor
175
299
  const exportName = generateExportName(imageBuildName, format);
176
300
 
177
301
  return {
178
- apiVersion: API_VERSION,
302
+ apiVersion: ApiVersion.ApiVersionV1alpha1,
179
303
  kind: ResourceKind.IMAGE_EXPORT,
180
304
  metadata: {
181
305
  name: exportName,
@@ -8,7 +8,7 @@ import { useTranslation } from '../../../hooks/useTranslation';
8
8
  import { ROUTE, useNavigate } from '../../../hooks/useNavigate';
9
9
  import { usePermissionsContext } from '../../common/PermissionsContext';
10
10
  import { useAppContext } from '../../../hooks/useAppContext';
11
- import { getImageBuildStatusReason } from '../../../utils/imageBuilds';
11
+ import { getImageBuildStatusReason, isImageBuildCancelable } from '../../../utils/imageBuilds';
12
12
  import DetailsPage from '../../DetailsPage/DetailsPage';
13
13
  import DetailsPageActions from '../../DetailsPage/DetailsPageActions';
14
14
  import DeleteImageBuildModal from '../DeleteImageBuildModal/DeleteImageBuildModal';
@@ -19,10 +19,14 @@ import ImageBuildYaml from './ImageBuildYaml';
19
19
  import ImageBuildDetailsTab from './ImageBuildDetailsTab';
20
20
  import ImageBuildExportsGallery from './ImageBuildExportsGallery';
21
21
  import ImageBuildLogsTab from './ImageBuildLogsTab';
22
+ import CancelImageBuildModal from '../CancelImageBuildModal/CancelImageBuildModal';
22
23
 
23
24
  const imageBuildDetailsPermissions = [
24
25
  { kind: RESOURCE.IMAGE_BUILD, verb: VERB.CREATE },
26
+ { kind: RESOURCE.IMAGE_BUILD_CANCEL, verb: VERB.CREATE },
25
27
  { kind: RESOURCE.IMAGE_BUILD, verb: VERB.DELETE },
28
+ // Users that can view logs for imagebuilds also can view logs for imageexports
29
+ { kind: RESOURCE.IMAGE_BUILD_LOG, verb: VERB.GET },
26
30
  ];
27
31
 
28
32
  const ImageBuildDetailsPageContent = () => {
@@ -34,10 +38,17 @@ const ImageBuildDetailsPageContent = () => {
34
38
 
35
39
  const { imageBuildId } = useParams() as { imageBuildId: string };
36
40
  const [imageBuild, isLoading, error, refetch] = useImageBuild(imageBuildId);
37
- const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState<boolean>();
38
41
  const { checkPermissions } = usePermissionsContext();
39
- const [canCreate, canDelete] = checkPermissions(imageBuildDetailsPermissions);
40
42
  const buildReason = imageBuild ? getImageBuildStatusReason(imageBuild) : undefined;
43
+ const [canCreate, hasCancelPermission, canDelete, canViewLogs] = checkPermissions(imageBuildDetailsPermissions);
44
+ const canCancel = hasCancelPermission && buildReason && isImageBuildCancelable(buildReason);
45
+
46
+ const tabKeys = React.useMemo(
47
+ () => (canViewLogs ? ['details', 'exports', 'yaml', 'logs'] : ['details', 'exports', 'yaml']),
48
+ [canViewLogs],
49
+ );
50
+ const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState<boolean>();
51
+ const [isCancelModalOpen, setIsCancelModalOpen] = React.useState<boolean>();
41
52
 
42
53
  return (
43
54
  <DetailsPage
@@ -48,11 +59,11 @@ const ImageBuildDetailsPageContent = () => {
48
59
  resourceType="Image builds"
49
60
  resourceTypeLabel={t('Image builds')}
50
61
  nav={
51
- <TabsNav aria-label="Image build details tabs" tabKeys={['details', 'exports', 'yaml', 'logs']}>
52
- <Tab eventKey="details" title={t('Image details')} />
62
+ <TabsNav aria-label="Image build details tabs" tabKeys={tabKeys}>
63
+ <Tab eventKey="details" title={t('Base image')} />
53
64
  <Tab eventKey="exports" title={t('Export images')} />
54
65
  <Tab eventKey="yaml" title={t('YAML')} />
55
- <Tab eventKey="logs" title={t('Logs')} />
66
+ {canViewLogs && <Tab eventKey="logs" title={t('Logs')} />}
56
67
  </TabsNav>
57
68
  }
58
69
  actions={
@@ -66,7 +77,10 @@ const ImageBuildDetailsPageContent = () => {
66
77
  : t('Duplicate')}
67
78
  </DropdownItem>
68
79
  )}
69
- {canDelete && (
80
+ {canCancel && (
81
+ <DropdownItem onClick={() => setIsCancelModalOpen(true)}>{t('Cancel image build')}</DropdownItem>
82
+ )}
83
+ {canDelete && !canCancel && (
70
84
  <DropdownItem onClick={() => setIsDeleteModalOpen(true)}>{t('Delete image build')}</DropdownItem>
71
85
  )}
72
86
  </DropdownList>
@@ -81,7 +95,7 @@ const ImageBuildDetailsPageContent = () => {
81
95
  <Route path="details" element={<ImageBuildDetailsTab imageBuild={imageBuild} />} />
82
96
  <Route path="exports" element={<ImageBuildExportsGallery imageBuild={imageBuild} refetch={refetch} />} />
83
97
  <Route path="yaml" element={<ImageBuildYaml imageBuild={imageBuild} refetch={refetch} />} />
84
- <Route path="logs" element={<ImageBuildLogsTab imageBuild={imageBuild} />} />
98
+ {canViewLogs && <Route path="logs" element={<ImageBuildLogsTab imageBuild={imageBuild} />} />}
85
99
  </Routes>
86
100
  {isDeleteModalOpen && (
87
101
  <DeleteImageBuildModal
@@ -94,26 +108,31 @@ const ImageBuildDetailsPageContent = () => {
94
108
  }}
95
109
  />
96
110
  )}
111
+ {isCancelModalOpen && (
112
+ <CancelImageBuildModal
113
+ imageBuildId={imageBuildId}
114
+ onClose={(confirmed) => {
115
+ setIsCancelModalOpen(false);
116
+ if (confirmed) {
117
+ refetch();
118
+ }
119
+ }}
120
+ />
121
+ )}
97
122
  </>
98
123
  )}
99
124
  </DetailsPage>
100
125
  );
101
126
  };
102
127
 
103
- const ImageBuildDetailsPage = () => {
104
- return (
105
- <OciRegistriesContextProvider>
106
- <ImageBuildDetailsPageContent />
107
- </OciRegistriesContextProvider>
108
- );
109
- };
110
-
111
128
  const ImageBuildDetailsWithPermissions = () => {
112
129
  const { checkPermissions, loading } = usePermissionsContext();
113
130
  const [allowed] = checkPermissions([{ kind: RESOURCE.IMAGE_BUILD, verb: VERB.GET }]);
114
131
  return (
115
132
  <PageWithPermissions allowed={allowed} loading={loading}>
116
- <ImageBuildDetailsPage />
133
+ <OciRegistriesContextProvider>
134
+ <ImageBuildDetailsPageContent />
135
+ </OciRegistriesContextProvider>
117
136
  </PageWithPermissions>
118
137
  );
119
138
  };
@@ -2,15 +2,20 @@ 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';
8
+ import { RESOURCE, VERB } from '../../../types/rbac';
7
9
  import { useFetch } from '../../../hooks/useFetch';
10
+ import { usePermissionsContext } from '../../common/PermissionsContext';
8
11
  import { getErrorMessage } from '../../../utils/error';
9
12
  import { getImageExportResource } from '../CreateImageBuildWizard/utils';
10
- import { ViewImageBuildExportCard } from '../ImageExportCards';
13
+ import { ImageExportAction, ViewImageBuildExportCard } from '../ImageExportCards';
11
14
  import { useOciRegistriesContext } from '../OciRegistriesContext';
12
15
  import { showSpinnerBriefly } from '../../../utils/time';
13
16
  import { getAllExportFormats, getExportDownloadResult, getImageReference } from '../../../utils/imageBuilds';
17
+ import { ROUTE, useNavigate } from '../../../hooks/useNavigate';
18
+ import { IMAGE_EXPORT_ID_PARAM } from './ImageBuildLogsTab';
14
19
 
15
20
  type ImageBuildExportsGalleryProps = {
16
21
  imageBuild: ImageBuildWithExports;
@@ -21,6 +26,8 @@ const REFRESH_IMAGE_BUILD_DELAY = 450;
21
26
  // Delay to keep loading state while browser processes redirect
22
27
  const DOWNLOAD_REDIRECT_DELAY = 1000;
23
28
 
29
+ const createExportAliases = ['retry', 'rebuild', 'createExport'];
30
+
24
31
  const createDownloadLink = (url: string) => {
25
32
  const link = document.createElement('a');
26
33
  link.href = url;
@@ -30,21 +37,69 @@ const createDownloadLink = (url: string) => {
30
37
  document.body.removeChild(link);
31
38
  };
32
39
 
40
+ const imageBuildExportsPermissions = [
41
+ { kind: RESOURCE.IMAGE_EXPORT, verb: VERB.CREATE },
42
+ { kind: RESOURCE.IMAGE_EXPORT, verb: VERB.DELETE },
43
+ { kind: RESOURCE.IMAGE_EXPORT_LOG, verb: VERB.GET },
44
+ { kind: RESOURCE.IMAGE_EXPORT_DOWNLOAD, verb: VERB.GET },
45
+ { kind: RESOURCE.IMAGE_EXPORT_CANCEL, verb: VERB.CREATE },
46
+ ];
47
+
33
48
  const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGalleryProps) => {
34
- const { post, proxyFetch } = useFetch();
49
+ const { post, proxyFetch, remove } = useFetch();
50
+ const { checkPermissions } = usePermissionsContext();
51
+ const [canCreateExport, canDelete, canViewLogs, canDownload, canCancel] =
52
+ checkPermissions(imageBuildExportsPermissions);
53
+
54
+ const buildReason = getImageBuildStatusReason(imageBuild);
55
+
56
+ const actionPermissions = React.useMemo(() => {
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
+
69
+ if (canCreateExport) {
70
+ actions.push('createExport');
71
+ actions.push('rebuild');
72
+ actions.push('retry');
73
+ }
74
+ if (canDelete) {
75
+ actions.push('delete');
76
+ }
77
+ if (canViewLogs) {
78
+ actions.push('viewLogs');
79
+ }
80
+ if (canDownload) {
81
+ actions.push('download');
82
+ }
83
+ if (canCancel) {
84
+ actions.push('cancel');
85
+ }
86
+ return actions;
87
+ }, [buildReason, canCreateExport, canDelete, canViewLogs, canDownload, canCancel]);
88
+
89
+ const navigate = useNavigate();
35
90
  const [error, setError] = React.useState<{
36
91
  format: ExportFormatType;
92
+ action: ImageExportAction;
37
93
  message: string;
38
- mode: 'export' | 'download';
39
94
  }>();
40
95
  const { ociRegistries } = useOciRegistriesContext();
41
- const [exportingFormat, setExportingFormat] = React.useState<ExportFormatType>();
42
- const [downloadingFormat, setDownloadingFormat] = React.useState<ExportFormatType>();
96
+
97
+ const [activeFormatAction, setActiveFormatAction] = React.useState<
98
+ { format: ExportFormatType; action: ImageExportAction } | undefined
99
+ >();
43
100
  const imageBuildId = imageBuild.metadata.name as string;
44
101
 
45
- const handleExportImage = async (format: ExportFormatType) => {
46
- setExportingFormat(format);
47
- setError(undefined);
102
+ const handleCreateNewExport = async (format: ExportFormatType) => {
48
103
  try {
49
104
  const imageExport = getImageExportResource(imageBuildId, format);
50
105
  await post<ImageExport>('imageexports', imageExport);
@@ -54,44 +109,79 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
54
109
  } catch (error) {
55
110
  // If process failed, it was likely very fast, so we also add the delay in this case.
56
111
  await showSpinnerBriefly(REFRESH_IMAGE_BUILD_DELAY);
57
-
58
- setError({ format, message: getErrorMessage(error), mode: 'export' });
59
- } finally {
60
- setExportingFormat(undefined);
112
+ throw error;
61
113
  }
62
114
  };
115
+ const handleDownload = async (ieName: string, format: ExportFormatType) => {
116
+ const response = await proxyFetch(`imagebuilder/api/v1/imageexports/${ieName}/download`, {
117
+ method: 'GET',
118
+ credentials: 'include',
119
+ });
120
+ const downloadResult = await getExportDownloadResult(response);
121
+ if (downloadResult === null) {
122
+ await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
123
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`);
124
+ }
125
+ if (downloadResult.type === 'redirect') {
126
+ createDownloadLink(downloadResult.url);
127
+ await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
128
+ } else {
129
+ const defaultFilename = `image-export-${ieName}.${format}`;
130
+ saveAs(downloadResult.blob, downloadResult.filename || defaultFilename);
131
+ }
132
+ };
133
+
134
+ const handleCancel = async (ieName: string) => {
135
+ await post(`imageexports/${ieName}/cancel`, {});
136
+ await showSpinnerBriefly(REFRESH_IMAGE_BUILD_DELAY);
137
+ refetch();
138
+ };
139
+
140
+ const handleDelete = async (ieName: string) => {
141
+ await remove(`imageexports/${ieName}`);
142
+ await showSpinnerBriefly(REFRESH_IMAGE_BUILD_DELAY);
143
+ refetch();
144
+ };
63
145
 
64
- const handleDownload = async (format: ExportFormatType) => {
146
+ const handleCardAction = async ({ format, action }: { format: ExportFormatType; action: ImageExportAction }) => {
65
147
  const imageExport = imageBuild.imageExports.find((ie) => ie?.spec.format === format);
66
- if (!imageExport) {
148
+ if (!imageExport && !createExportAliases.includes(action)) {
67
149
  return;
68
150
  }
69
151
 
70
- setDownloadingFormat(format);
152
+ setActiveFormatAction({ format, action });
153
+ setError(undefined);
154
+
71
155
  try {
72
- const ieName = imageExport.metadata.name as string;
73
- const downloadEndpoint = `imagebuilder/api/v1/imageexports/${ieName}/download`;
74
- const response = await proxyFetch(downloadEndpoint, {
75
- method: 'GET',
76
- credentials: 'include',
77
- redirect: 'manual', // Prevent automatic redirect following to avoid CORS issues
78
- });
79
-
80
- const downloadResult = await getExportDownloadResult(response);
81
- if (downloadResult === null) {
82
- await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
83
- throw new Error(`Download failed: ${response.status} ${response.statusText}`);
84
- } else if (downloadResult.type === 'redirect') {
85
- createDownloadLink(downloadResult.url);
86
- await showSpinnerBriefly(DOWNLOAD_REDIRECT_DELAY);
87
- } else {
88
- const defaultFilename = `image-export-${ieName}.${format}`;
89
- saveAs(downloadResult.blob, downloadResult.filename || defaultFilename);
156
+ const ieName = imageExport?.metadata.name as string;
157
+ switch (action) {
158
+ case 'createExport':
159
+ case 'retry':
160
+ case 'rebuild':
161
+ await handleCreateNewExport(format);
162
+ break;
163
+ case 'download':
164
+ await handleDownload(ieName, format);
165
+ break;
166
+ case 'cancel':
167
+ await handleCancel(ieName);
168
+ break;
169
+ case 'delete':
170
+ await handleDelete(ieName);
171
+ break;
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()}` });
177
+ break;
178
+ }
90
179
  }
91
- } catch (err) {
92
- setError({ format, message: getErrorMessage(err), mode: 'download' });
180
+ } catch (error) {
181
+ setError({ format, message: getErrorMessage(error), action });
182
+ refetch();
93
183
  } finally {
94
- setDownloadingFormat(undefined);
184
+ setActiveFormatAction(undefined);
95
185
  }
96
186
  };
97
187
 
@@ -99,26 +189,23 @@ const ImageBuildExportsGallery = ({ imageBuild, refetch }: ImageBuildExportsGall
99
189
  <Gallery hasGutter minWidths={{ default: '350px' }}>
100
190
  {getAllExportFormats().map((format) => {
101
191
  const imageExport = imageBuild.imageExports.find((imageExport) => imageExport?.spec.format === format);
102
- const isDisabled = exportingFormat && exportingFormat !== format;
103
192
  // We can only link to the generic destination for the image build.
104
193
  // The individual export artifacts are references to this generic output image.
105
194
  const imageReference = getImageReference(ociRegistries, imageBuild.spec.destination);
106
195
 
107
196
  const hasError = error?.format === format;
197
+ const activeAction = activeFormatAction?.format === format ? activeFormatAction.action : undefined;
108
198
  return (
109
199
  <ViewImageBuildExportCard
110
- imageBuildId={imageBuildId}
111
200
  key={format}
112
201
  imageReference={imageReference}
113
202
  format={format}
114
203
  error={hasError ? error : null}
115
204
  imageExport={imageExport}
116
- isCreating={exportingFormat === format}
117
- isDownloading={downloadingFormat === format}
118
- isDisabled={isDisabled}
205
+ activeAction={activeAction}
119
206
  onDismissError={() => setError(undefined)}
120
- onExportImage={handleExportImage}
121
- onDownload={handleDownload}
207
+ onCardAction={handleCardAction}
208
+ actionPermissions={actionPermissions}
122
209
  />
123
210
  );
124
211
  })}