@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
@@ -1,15 +1,19 @@
1
1
  import yaml from 'js-yaml';
2
2
  import {
3
3
  AppType,
4
- // eslint-disable-next-line no-restricted-imports
4
+ ApplicationContent,
5
5
  ApplicationProviderSpec,
6
6
  ApplicationResourceLimits,
7
7
  ApplicationVolume,
8
+ ApplicationVolumeReclaimPolicy,
9
+ ComposeApplication,
8
10
  ConfigProviderSpec,
11
+ ContainerApplication,
9
12
  DeviceSpec,
10
13
  EncodingType,
11
14
  FileSpec,
12
15
  GitConfigProviderSpec,
16
+ HelmApplication,
13
17
  HttpConfigProviderSpec,
14
18
  ImageApplicationProviderSpec,
15
19
  ImageMountVolumeProviderSpec,
@@ -18,42 +22,37 @@ import {
18
22
  InlineConfigProviderSpec,
19
23
  KubernetesSecretProviderSpec,
20
24
  PatchRequest,
25
+ QuadletApplication,
21
26
  } from '@flightctl/types';
22
27
  import {
23
28
  AppForm,
24
29
  AppSpecType,
25
30
  ApplicationVolumeForm,
26
- ComposeImageAppForm,
27
- ComposeInlineAppForm,
31
+ ComposeAppForm,
28
32
  ConfigSourceProvider,
29
33
  ConfigType,
30
34
  GitConfigTemplate,
31
- HelmImageAppForm,
35
+ HelmAppForm,
32
36
  HttpConfigTemplate,
33
37
  InlineConfigTemplate,
38
+ InlineFileForm,
34
39
  KubeSecretTemplate,
35
- PortMapping,
36
- QuadletImageAppForm,
37
- QuadletInlineAppForm,
38
- RUN_AS_DEFAULT_USER,
40
+ QuadletAppForm,
41
+ RUN_AS_FLIGHTCTL_USER,
42
+ RUN_AS_ROOT_USER,
39
43
  SingleContainerAppForm,
40
44
  SpecConfigTemplate,
41
45
  SystemdUnitFormValue,
42
- isComposeImageAppForm,
43
46
  isGitConfigTemplate,
44
47
  isGitProviderSpec,
45
- isHelmImageAppForm,
46
48
  isHttpConfigTemplate,
47
49
  isHttpProviderSpec,
48
- isImageAppProvider,
50
+ isImageVariantApp,
49
51
  isInlineProviderSpec,
52
+ isInlineVariantApp,
50
53
  isKubeProviderSpec,
51
54
  isKubeSecretTemplate,
52
- isQuadletImageAppForm,
53
- isQuadletInlineAppForm,
54
- isSingleContainerAppForm,
55
55
  } from '../../../types/deviceSpec';
56
- import { InlineApplicationFileFixed } from '../../../types/extraTypes';
57
56
 
58
57
  const DEFAULT_INLINE_FILE_MODE = 420; // In Octal: 0644
59
58
  const DEFAULT_INLINE_FILE_USER = 'root';
@@ -220,366 +219,374 @@ export const getDeviceSpecConfigPatches = (
220
219
  return allPatches;
221
220
  };
222
221
 
223
- export const toAPIApplication = (app: AppForm): ApplicationProviderSpec => {
224
- if (isHelmImageAppForm(app)) {
225
- const data: ImageApplicationProviderSpec & ApplicationProviderSpec = {
226
- name: app.name,
227
- image: app.image,
228
- appType: app.appType,
229
- };
230
- if (app.namespace) {
231
- data.namespace = app.namespace;
232
- }
233
- if (app.valuesYaml) {
234
- try {
235
- const values = yaml.load(app.valuesYaml) as Record<string, unknown>;
236
- if (values && Object.keys(values).length > 0) {
237
- data.values = values;
238
- }
239
- } catch (error) {
240
- throw new Error('Values content is not valid YAML.');
241
- }
242
- }
243
- const fileNames = app.valuesFiles.filter((file) => file && file.trim() !== '');
244
- if (fileNames.length > 0) {
245
- data.valuesFiles = fileNames;
246
- }
247
- return data;
248
- }
249
-
250
- const envVars = app.variables.reduce((acc, variable) => {
251
- acc[variable.name] = variable.value;
252
- return acc;
253
- }, {});
254
-
255
- const volumes = app.volumes?.map((v) => {
256
- const volume: Partial<ApplicationVolume & ImageMountVolumeProviderSpec> = {
257
- name: v.name || '',
258
- };
259
-
260
- if (v.imageRef) {
261
- volume.image = {
262
- reference: v.imageRef,
263
- pullPolicy: v.imagePullPolicy || ImagePullPolicy.PullIfNotPresent,
264
- };
265
- }
266
- if (v.mountPath) {
267
- volume.mount = {
268
- path: v.mountPath,
269
- };
270
- }
271
- return volume as ApplicationVolume;
272
- });
273
-
274
- if (isSingleContainerAppForm(app)) {
275
- const data: ImageApplicationProviderSpec & ApplicationProviderSpec = {
276
- image: app.image,
277
- appType: app.appType,
278
- envVars,
279
- volumes,
280
- };
281
- if (app.name) {
282
- data.name = app.name;
283
- }
284
- if (app.ports) {
285
- data.ports = app.ports.map((p) => `${p.hostPort}:${p.containerPort}`);
286
- }
287
- // Removed fields must not appear in the resources object
288
- const appLimits: ApplicationResourceLimits = {};
289
- if (app.limits?.cpu) {
290
- appLimits.cpu = app.limits.cpu;
291
- }
292
- if (app.limits?.memory) {
293
- appLimits.memory = app.limits.memory;
294
- }
295
- if (Object.keys(appLimits).length > 0) {
296
- data.resources = {
297
- limits: appLimits,
298
- };
299
- }
300
- data.runAs = app.runAs || RUN_AS_DEFAULT_USER;
301
- return data;
302
- }
303
-
304
- if (isQuadletImageAppForm(app) || isComposeImageAppForm(app)) {
305
- const data: ApplicationProviderSpec = {
306
- image: app.image,
307
- appType: app.appType,
308
- envVars,
309
- volumes,
310
- };
311
- if (app.name) {
312
- data.name = app.name;
313
- }
314
- if (isQuadletImageAppForm(app) && app.runAs) {
315
- data.runAs = app.runAs;
316
- }
317
- return data;
318
- }
319
-
320
- // Inline applications (Quadlet or Compose)
321
- const inlineData: ApplicationProviderSpec = {
322
- name: app.name,
323
- appType: app.appType,
324
- inline: toAPIFiles(app.files),
325
- envVars,
326
- volumes,
327
- };
328
- if (isQuadletInlineAppForm(app) && app.runAs) {
329
- inlineData.runAs = app.runAs;
330
- }
331
- return inlineData;
332
- };
333
-
334
- const hasInlineApplicationChanged = (
335
- currentApp: InlineApplicationProviderSpec,
336
- updatedApp: QuadletInlineAppForm | ComposeInlineAppForm,
337
- ) => {
338
- if (currentApp.inline.length !== updatedApp.files.length) {
339
- return true;
340
- }
341
- const filesChanged = currentApp.inline.some((file, index) => {
342
- const updatedFile = updatedApp.files[index];
343
- const isCurrentBase64 = file.contentEncoding === EncodingType.EncodingBase64;
222
+ const haveInlineFilesChanged = (current: ApplicationContent[], updated: ApplicationContent[]): boolean => {
223
+ if (current.length !== updated.length) return true;
224
+ return current.some((file, index) => {
225
+ const other = updated[index];
226
+ const aBase64 = file.contentEncoding === EncodingType.EncodingBase64;
227
+ const bBase64 = other.contentEncoding === EncodingType.EncodingBase64;
344
228
  return (
345
- (updatedFile.base64 || false) !== isCurrentBase64 ||
346
- updatedFile.path !== file.path ||
347
- updatedFile.content !== file.content
229
+ aBase64 !== bBase64 || (file.path || '') !== (other.path || '') || (file.content || '') !== (other.content || '')
348
230
  );
349
231
  });
350
- if (filesChanged) {
351
- return true;
352
- }
353
-
354
- // Check runAs for Quadlet inline apps
355
- if (isQuadletInlineAppForm(updatedApp)) {
356
- const currentAppWithRunAs = currentApp as InlineApplicationProviderSpec & { runAs?: string };
357
- if ((currentAppWithRunAs.runAs || RUN_AS_DEFAULT_USER) !== (updatedApp.runAs || RUN_AS_DEFAULT_USER)) {
358
- return true;
359
- }
360
- }
361
-
362
- return !areVolumesEqual(currentApp.volumes || [], updatedApp.volumes || []);
363
232
  };
364
233
 
365
- const areVolumesEqual = (currentVolumes: ApplicationVolume[], updatedFormVolumes: ApplicationVolumeForm[]): boolean => {
366
- if (currentVolumes.length !== updatedFormVolumes.length) {
367
- return false;
368
- }
369
-
370
- return currentVolumes.every((currentVol, index) => {
371
- const updatedFormVol = updatedFormVolumes[index];
372
- const currentFullVol = currentVol as ApplicationVolume & ImageMountVolumeProviderSpec;
373
-
374
- if (currentFullVol.name !== updatedFormVol.name) {
375
- return false;
376
- }
234
+ const haveEnvVarsChanged = (current: Record<string, string>, updated: Record<string, string>): boolean => {
235
+ const aKeys = Object.keys(current);
236
+ const bKeys = Object.keys(updated);
237
+ if (aKeys.length !== bKeys.length) return true;
238
+ return aKeys.some((key) => current[key] !== updated[key]);
239
+ };
377
240
 
378
- const currentImageRef = currentFullVol.image?.reference || '';
379
- const updatedImageRef = updatedFormVol?.imageRef || '';
380
- if (currentImageRef !== updatedImageRef) {
381
- return false;
382
- }
241
+ const haveVolumesChanged = (current: ApplicationVolume[], updated: ApplicationVolume[]): boolean => {
242
+ if (current.length !== updated.length) return true;
243
+ return current.some((currentVol, index) => {
244
+ const updatedVol = updated[index];
245
+ if (currentVol.name !== updatedVol.name) return true;
246
+ if (
247
+ (currentVol.reclaimPolicy || ApplicationVolumeReclaimPolicy.RETAIN) !==
248
+ (updatedVol.reclaimPolicy || ApplicationVolumeReclaimPolicy.RETAIN)
249
+ )
250
+ return true;
383
251
 
252
+ const currentFull = currentVol as ApplicationVolume & ImageMountVolumeProviderSpec;
253
+ const updatedFull = updatedVol as ApplicationVolume & ImageMountVolumeProviderSpec;
254
+ const currentImageRef = currentFull.image?.reference || '';
255
+ const updatedImageRef = updatedFull.image?.reference || '';
256
+ if (currentImageRef !== updatedImageRef) return true;
384
257
  if (currentImageRef || updatedImageRef) {
385
- const currentPullPolicy = currentFullVol.image?.pullPolicy || ImagePullPolicy.PullIfNotPresent;
386
- const updatedPullPolicy = updatedFormVol?.imagePullPolicy || ImagePullPolicy.PullIfNotPresent;
387
- if (currentPullPolicy !== updatedPullPolicy) {
388
- return false;
389
- }
390
- }
391
-
392
- const currentMountPath = currentFullVol.mount?.path || '';
393
- const updatedMountPath = updatedFormVol?.mountPath || '';
394
- if (currentMountPath !== updatedMountPath) {
395
- return false;
258
+ if (
259
+ (currentFull.image?.pullPolicy || ImagePullPolicy.PullIfNotPresent) !==
260
+ (updatedFull.image?.pullPolicy || ImagePullPolicy.PullIfNotPresent)
261
+ )
262
+ return true;
396
263
  }
397
-
398
- return true;
264
+ return (currentFull.mount?.path || '') !== (updatedFull.mount?.path || '');
399
265
  });
400
266
  };
401
267
 
402
- const arePortsEqual = (currentPorts: string[], updatedPorts: PortMapping[]): boolean => {
403
- if (currentPorts.length !== updatedPorts.length) {
404
- return false;
405
- }
268
+ const hasStringChanged = (
269
+ current: string | undefined,
270
+ updated: string | undefined,
271
+ defaultValue: string = '',
272
+ ): boolean => (current || defaultValue) !== (updated || defaultValue);
406
273
 
407
- // Reordered ports will be considered as changed
408
- return currentPorts.every((currentPort, index) => {
409
- const updatedPort = updatedPorts[index];
410
- return currentPort === `${updatedPort.hostPort}:${updatedPort.containerPort}`;
411
- });
274
+ const havePortsChanged = (current: string[], updated: string[]): boolean => {
275
+ if (current.length !== updated.length) return true;
276
+ return current.some((port, index) => port !== updated[index]);
412
277
  };
413
278
 
414
- const areResourceLimitsEqual = (
415
- currentLimits: { cpu?: string; memory?: string } | undefined,
416
- updatedLimits: { cpu?: string; memory?: string } | undefined,
279
+ const haveResourceLimitsChanged = (
280
+ current: { cpu?: string; memory?: string } | undefined,
281
+ updated: { cpu?: string; memory?: string } | undefined,
417
282
  ): boolean => {
418
- const currentCpu = currentLimits?.cpu || '';
419
- const updatedCpu = updatedLimits?.cpu || '';
420
- const currentMemory = currentLimits?.memory || '';
421
- const updatedMemory = updatedLimits?.memory || '';
422
-
423
- return currentCpu === updatedCpu && currentMemory === updatedMemory;
283
+ return (current?.cpu || '') !== (updated?.cpu || '') || (current?.memory || '') !== (updated?.memory || '');
424
284
  };
425
285
 
426
- const areEnvVariablesEqual = (
427
- currentVars: Record<string, string> | undefined,
428
- updatedFormVars: { name: string; value: string }[],
429
- ): boolean => {
430
- const envVars = currentVars || {};
431
- if (Object.keys(envVars).length !== updatedFormVars.length) {
432
- return false;
433
- }
434
-
435
- return updatedFormVars.every((variable) => {
436
- // Envvars may have "falsy" values (eg. number 0) when they are defined
437
- return variable.name in envVars && envVars[variable.name] === variable.value;
438
- });
286
+ const haveValuesFilesChanged = (current: string[], updated: string[]): boolean => {
287
+ const a = current.filter((f) => f.trim() !== '');
288
+ const b = updated.filter((f) => f.trim() !== '');
289
+ if (a.length !== b.length) return true;
290
+ return a.some((file, i) => file !== b[i]);
439
291
  };
440
292
 
441
- const hasSingleContainerAppChanged = (currentApp: ApplicationProviderSpec, updatedApp: AppForm): boolean => {
442
- if (!isSingleContainerAppForm(updatedApp)) {
443
- return true;
444
- }
293
+ const haveHelmValuesChanged = (current: Record<string, unknown>, updated: Record<string, unknown>): boolean =>
294
+ JSON.stringify(current) !== JSON.stringify(updated);
445
295
 
446
- const imageApp = currentApp as ImageApplicationProviderSpec & ApplicationProviderSpec;
447
- if (imageApp.name !== updatedApp.name || imageApp.image !== updatedApp.image) {
296
+ const hasRunAsChanged = (current: string | undefined, updated: string | undefined): boolean => {
297
+ if (!current) {
298
+ // For empty "runAs", we mark the app as changed.
299
+ // This means that we'll set the field explicitly to the default user (currently "root").
448
300
  return true;
449
301
  }
302
+ return current !== updated;
303
+ };
304
+
305
+ // Single container apps always have an image, and it doesn't have an inline variant
306
+ const hasContainerAppChanged = (current: ContainerApplication, updated: ContainerApplication): boolean =>
307
+ hasStringChanged(current.name, updated.name) ||
308
+ hasStringChanged(current.image, updated.image) ||
309
+ havePortsChanged(current.ports || [], updated.ports || []) ||
310
+ haveResourceLimitsChanged(current.resources?.limits, updated.resources?.limits) ||
311
+ haveEnvVarsChanged(current.envVars || {}, updated.envVars || {}) ||
312
+ hasRunAsChanged(current.runAs, updated.runAs) ||
313
+ haveVolumesChanged(current.volumes || [], updated.volumes || []);
314
+
315
+ // Helm apps always have an image (chart), and it doesn't have an inline variant
316
+ const hasHelmAppChanged = (current: HelmApplication, updated: HelmApplication): boolean =>
317
+ hasStringChanged(current.name, updated.name) ||
318
+ hasStringChanged(current.image, updated.image) ||
319
+ hasStringChanged(current.namespace, updated.namespace) ||
320
+ haveValuesFilesChanged(current.valuesFiles || [], updated.valuesFiles || []) ||
321
+ haveHelmValuesChanged(current.values || {}, updated.values || {});
322
+
323
+ const hasComposeAppChanged = (
324
+ current: ComposeApplication,
325
+ updated: ComposeApplication,
326
+ specType: AppSpecType,
327
+ ): boolean => {
328
+ const baseChanged =
329
+ hasStringChanged(current.name, updated.name) ||
330
+ haveEnvVarsChanged(current.envVars || {}, updated.envVars || {}) ||
331
+ haveVolumesChanged(current.volumes || [], updated.volumes || []);
450
332
 
451
- if (!arePortsEqual(imageApp.ports || [], updatedApp.ports || [])) {
333
+ if (baseChanged) {
452
334
  return true;
453
335
  }
454
336
 
455
- if (!areResourceLimitsEqual(imageApp.resources?.limits, updatedApp.limits)) {
456
- return true;
337
+ if (specType === AppSpecType.OCI_IMAGE) {
338
+ return hasStringChanged(
339
+ (current as ImageApplicationProviderSpec).image,
340
+ (updated as ImageApplicationProviderSpec).image,
341
+ );
457
342
  }
343
+ return haveInlineFilesChanged(
344
+ (current as InlineApplicationProviderSpec).inline,
345
+ (updated as InlineApplicationProviderSpec).inline,
346
+ );
347
+ };
458
348
 
459
- if (!areEnvVariablesEqual(imageApp.envVars, updatedApp.variables)) {
349
+ // Quadlet apps are currently the same as Compose apps, plus an optional "runAs" field.
350
+ const hasQuadletAppChanged = (
351
+ current: QuadletApplication,
352
+ updated: QuadletApplication,
353
+ specType: AppSpecType,
354
+ ): boolean => {
355
+ const baseChanged = hasComposeAppChanged(current, updated, specType);
356
+ if (baseChanged) {
460
357
  return true;
461
358
  }
359
+ return hasRunAsChanged(current.runAs, updated.runAs);
360
+ };
462
361
 
463
- if ((imageApp.runAs || RUN_AS_DEFAULT_USER) !== (updatedApp.runAs || RUN_AS_DEFAULT_USER)) {
362
+ const hasApplicationChanged = (current: ApplicationProviderSpec, updated: ApplicationProviderSpec): boolean => {
363
+ if (current.appType !== updated.appType) {
464
364
  return true;
465
365
  }
466
- return !areVolumesEqual(imageApp.volumes || [], updatedApp.volumes || []);
467
- };
468
-
469
- const hasApplicationChanged = (currentApp: ApplicationProviderSpec, updatedApp: AppForm): boolean => {
470
- const isCurrentImageApp = isImageAppProvider(currentApp);
471
- const currentAppSpecType = isCurrentImageApp ? AppSpecType.OCI_IMAGE : AppSpecType.INLINE;
472
366
 
473
- // Check if application name changed, or it's a different type of application (either specType or appType)
474
- if (
475
- currentAppSpecType !== updatedApp.specType ||
476
- currentApp.appType !== updatedApp.appType ||
477
- currentApp.name !== updatedApp.name
478
- ) {
367
+ const currentSpectType = isImageVariantApp(current) ? AppSpecType.OCI_IMAGE : AppSpecType.INLINE;
368
+ const updatedSpectType = isImageVariantApp(updated) ? AppSpecType.OCI_IMAGE : AppSpecType.INLINE;
369
+ if (currentSpectType !== updatedSpectType) {
479
370
  return true;
480
371
  }
481
-
482
- // The app is a single container application
483
- if (isSingleContainerAppForm(updatedApp)) {
484
- return hasSingleContainerAppChanged(currentApp, updatedApp);
372
+ switch (current.appType) {
373
+ case AppType.AppTypeContainer:
374
+ return hasContainerAppChanged(current as ContainerApplication, updated as ContainerApplication);
375
+ case AppType.AppTypeHelm:
376
+ return hasHelmAppChanged(current as HelmApplication, updated as HelmApplication);
377
+ case AppType.AppTypeQuadlet:
378
+ return hasQuadletAppChanged(current as QuadletApplication, updated as QuadletApplication, currentSpectType);
379
+ case AppType.AppTypeCompose:
380
+ return hasComposeAppChanged(current as ComposeApplication, updated as ComposeApplication, currentSpectType);
485
381
  }
382
+ };
486
383
 
487
- // The app is a Helm application
488
- if (isHelmImageAppForm(updatedApp)) {
489
- const imageApp = currentApp as ImageApplicationProviderSpec;
490
- if (imageApp.image !== updatedApp.image || imageApp.namespace !== updatedApp.namespace) {
491
- return true;
492
- }
384
+ const variablesToEnvVars = (variables: { name: string; value: string }[]) => {
385
+ if (variables.length === 0) {
386
+ return undefined;
387
+ }
388
+ return variables.reduce(
389
+ (acc, v) => {
390
+ if (v.name) {
391
+ acc[v.name] = v.value || '';
392
+ }
393
+ return acc;
394
+ },
395
+ {} as Record<string, string>,
396
+ );
397
+ };
493
398
 
494
- // Compare valuesFiles arrays
495
- const currentValuesFiles = (imageApp.valuesFiles || []).filter((file) => file !== '');
496
- const updatedValuesFiles = updatedApp.valuesFiles.filter((file) => file !== '');
497
- if (currentValuesFiles.length !== updatedValuesFiles.length) {
498
- return true;
399
+ /**
400
+ * Converts form volumes to API volumes, ignoring fields that are not allowed for the given app type.
401
+ * Quadlet/Compose apps --> can only be image volumes (mount is not allowed)
402
+ * Container apps --> can either be mount or image mount volumes
403
+ */
404
+ const formVolumesToApi = (volumes: ApplicationVolumeForm[], appType: AppType): ApplicationVolume[] => {
405
+ return volumes.map((v) => {
406
+ const vol: Partial<ApplicationVolume & ImageMountVolumeProviderSpec> = {
407
+ name: v.name || '',
408
+ };
409
+ if (v.imageRef) {
410
+ vol.image = {
411
+ reference: v.imageRef,
412
+ pullPolicy: v.imagePullPolicy || ImagePullPolicy.PullIfNotPresent,
413
+ };
499
414
  }
500
- if (!currentValuesFiles.every((file, index) => file === updatedValuesFiles[index])) {
501
- return true;
415
+ if (v.mountPath && appType === AppType.AppTypeContainer) {
416
+ vol.mount = { path: v.mountPath };
502
417
  }
503
- const updatedValues = yaml.load(updatedApp.valuesYaml || ' ') as Record<string, unknown>;
504
- if (JSON.stringify(imageApp.values || {}) !== JSON.stringify(updatedValues)) {
505
- return true;
418
+ return vol as ApplicationVolume;
419
+ });
420
+ };
421
+
422
+ const formFilesToApi = (files: InlineFileForm[]) =>
423
+ files.map((f) => ({
424
+ path: f.path,
425
+ content: f.content || '',
426
+ contentEncoding: f.base64 ? EncodingType.EncodingBase64 : EncodingType.EncodingPlain,
427
+ }));
428
+
429
+ const toFormFiles = (files: ApplicationContent[]) =>
430
+ files.map((file) => ({
431
+ path: file.path || '',
432
+ content: file.content || '',
433
+ base64: file.contentEncoding === EncodingType.EncodingBase64,
434
+ }));
435
+
436
+ const toApiHelmApp = (app: HelmAppForm): HelmApplication => {
437
+ const helmApp: HelmApplication = {
438
+ image: app.image,
439
+ appType: app.appType,
440
+ };
441
+ if (app.name) {
442
+ helmApp.name = app.name;
443
+ }
444
+ if (app.namespace) {
445
+ helmApp.namespace = app.namespace;
446
+ }
447
+ if (app.valuesYaml) {
448
+ try {
449
+ const values = yaml.load(app.valuesYaml) as Record<string, unknown>;
450
+ if (values && Object.keys(values).length > 0) helmApp.values = values;
451
+ } catch {
452
+ // leave values unset on invalid YAML
506
453
  }
507
- return false;
508
454
  }
455
+ const fileNames = (app.valuesFiles || []).filter((f) => f.trim() !== '');
456
+ if (fileNames.length > 0) {
457
+ helmApp.valuesFiles = fileNames;
458
+ }
459
+ return helmApp;
460
+ };
509
461
 
510
- if (!areEnvVariablesEqual(currentApp.envVars, updatedApp.variables)) {
511
- return true;
462
+ const toApiContainerApp = (app: SingleContainerAppForm): ContainerApplication => {
463
+ const containerApp: ContainerApplication = {
464
+ image: app.image,
465
+ appType: app.appType,
466
+ runAs: app.runAs || RUN_AS_ROOT_USER,
467
+ envVars: variablesToEnvVars(app.variables || []),
468
+ volumes: formVolumesToApi(app.volumes || [], AppType.AppTypeContainer),
469
+ };
470
+ if (app.name) {
471
+ containerApp.name = app.name;
472
+ }
473
+ if (app.ports.length > 0) {
474
+ containerApp.ports = app.ports.map((p) => `${p.hostPort}:${p.containerPort}`);
512
475
  }
513
476
 
514
- // The app is an image application (Quadlet/Compose image apps)
515
- if (isCurrentImageApp) {
516
- const imageApp = currentApp as ImageApplicationProviderSpec;
517
- const updatedImageApp = updatedApp as QuadletImageAppForm | ComposeImageAppForm;
518
- if (imageApp.image !== updatedImageApp.image) {
519
- return true;
477
+ const cpu = app.cpuLimit;
478
+ const memory = app.memoryLimit;
479
+ if (cpu || memory) {
480
+ const limits: ApplicationResourceLimits = {};
481
+ if (cpu) {
482
+ limits.cpu = cpu;
520
483
  }
521
-
522
- // Check runAs for Quadlet image apps
523
- if (isQuadletImageAppForm(updatedApp)) {
524
- if ((currentApp.runAs || RUN_AS_DEFAULT_USER) !== (updatedApp.runAs || RUN_AS_DEFAULT_USER)) {
525
- return true;
526
- }
484
+ if (memory) {
485
+ limits.memory = memory;
527
486
  }
528
487
 
529
- return !areVolumesEqual(imageApp.volumes || [], updatedApp.volumes || []);
488
+ containerApp.resources = { limits };
530
489
  }
490
+ return containerApp;
491
+ };
531
492
 
532
- // The app must be an inline application
533
- return hasInlineApplicationChanged(
534
- currentApp as InlineApplicationProviderSpec,
535
- updatedApp as QuadletInlineAppForm | ComposeInlineAppForm,
536
- );
493
+ const toApiComposeApp = (app: ComposeAppForm): ComposeApplication => {
494
+ const formApp: Partial<ComposeApplication> = {
495
+ appType: app.appType,
496
+ envVars: variablesToEnvVars(app.variables || []),
497
+ volumes: formVolumesToApi(app.volumes || [], app.appType),
498
+ };
499
+ if (app.name) {
500
+ formApp.name = app.name;
501
+ }
502
+ if (app.specType === AppSpecType.OCI_IMAGE) {
503
+ (formApp as ImageApplicationProviderSpec).image = app.image;
504
+ } else {
505
+ (formApp as InlineApplicationProviderSpec).inline = formFilesToApi(app.files);
506
+ }
507
+ return formApp as ComposeApplication;
508
+ };
509
+
510
+ // Quadlet apps are currently the same as Compose apps, plus an optional "runAs" field.
511
+ const toApiQuadletApp = (app: QuadletAppForm): QuadletApplication => {
512
+ const baseApp = toApiComposeApp(app);
513
+ return { ...baseApp, appType: AppType.AppTypeQuadlet, runAs: app.runAs || RUN_AS_ROOT_USER };
514
+ };
515
+
516
+ export const toApiApplication = (app: AppForm): ApplicationProviderSpec => {
517
+ switch (app.appType) {
518
+ case AppType.AppTypeHelm:
519
+ return toApiHelmApp(app as HelmAppForm);
520
+ case AppType.AppTypeContainer:
521
+ return toApiContainerApp(app as SingleContainerAppForm);
522
+ case AppType.AppTypeQuadlet:
523
+ return toApiQuadletApp(app as QuadletAppForm);
524
+ case AppType.AppTypeCompose:
525
+ return toApiComposeApp(app as ComposeAppForm);
526
+ default:
527
+ throw new Error('Unknown application type');
528
+ }
529
+ };
530
+
531
+ const toFormVariables = (envVars: Record<string, string>): { name: string; value: string }[] =>
532
+ Object.entries(envVars).map(([name, value]) => ({ name, value: value || '' }));
533
+
534
+ const toFormVolumes = (volumes?: ApplicationVolume[]): ApplicationVolumeForm[] => {
535
+ if (!volumes) return [];
536
+ return volumes.map((vol) => {
537
+ const fullVolume = vol as ApplicationVolume & ImageMountVolumeProviderSpec;
538
+ return {
539
+ name: fullVolume.name,
540
+ imageRef: fullVolume.image?.reference || '',
541
+ mountPath: fullVolume.mount?.path || '',
542
+ imagePullPolicy: fullVolume.image?.pullPolicy || ImagePullPolicy.PullIfNotPresent,
543
+ };
544
+ });
545
+ };
546
+
547
+ const toFormApps = (app: ApplicationProviderSpec): AppForm => {
548
+ switch (app.appType) {
549
+ case AppType.AppTypeContainer:
550
+ return toContainerAppForm(app as ContainerApplication);
551
+ case AppType.AppTypeHelm:
552
+ return toHelmAppForm(app as HelmApplication);
553
+ case AppType.AppTypeQuadlet:
554
+ return toQuadletAppForm(app as QuadletApplication);
555
+ case AppType.AppTypeCompose:
556
+ return toComposeAppForm(app as ComposeApplication);
557
+ default:
558
+ throw new Error('Unknown application type');
559
+ }
537
560
  };
538
561
 
539
562
  export const getApplicationPatches = (
540
563
  basePath: string,
541
564
  currentApps: ApplicationProviderSpec[],
542
565
  updatedApps: AppForm[],
543
- ) => {
566
+ ): PatchRequest => {
544
567
  const patches: PatchRequest = [];
545
-
546
568
  const currentLen = currentApps.length;
547
569
  const newLen = updatedApps.length;
548
570
 
549
571
  if (currentLen === 0 && newLen > 0) {
550
- // First apps(s) have been added
551
- patches.push({
552
- path: `${basePath}/applications`,
553
- op: 'add',
554
- value: updatedApps.map(toAPIApplication),
555
- });
572
+ patches.push({ path: `${basePath}/applications`, op: 'add', value: updatedApps.map(toApiApplication) });
556
573
  } else if (currentLen > 0 && newLen === 0) {
557
- // Last app(s) have been removed
558
- patches.push({
559
- path: `${basePath}/applications`,
560
- op: 'remove',
561
- });
574
+ patches.push({ path: `${basePath}/applications`, op: 'remove' });
562
575
  } else if (currentLen !== newLen) {
563
- // Array length changed, need to replace entire array
564
- patches.push({
565
- path: `${basePath}/applications`,
566
- op: 'replace',
567
- value: updatedApps.map(toAPIApplication),
568
- });
576
+ patches.push({ path: `${basePath}/applications`, op: 'replace', value: updatedApps.map(toApiApplication) });
569
577
  } else {
570
- // Apps length has not changed. We only PATCH the applications that have actually changed
571
578
  currentApps.forEach((currentApp, index) => {
572
579
  const updatedApp = updatedApps[index];
573
- if (hasApplicationChanged(currentApp, updatedApp)) {
580
+ const updatedApi = toApiApplication(updatedApp);
581
+ if (hasApplicationChanged(currentApp, updatedApi)) {
574
582
  patches.push({
575
583
  path: `${basePath}/applications/${index}`,
576
584
  op: 'replace',
577
- value: toAPIApplication(updatedApp),
585
+ value: updatedApi,
578
586
  });
579
587
  }
580
588
  });
581
589
  }
582
-
583
590
  return patches;
584
591
  };
585
592
 
@@ -635,45 +642,16 @@ export const getApiConfig = (ct: SpecConfigTemplate): ConfigSourceProvider => {
635
642
  };
636
643
  };
637
644
 
638
- const getAppFormVariables = (envVars: Record<string, string> | undefined) =>
639
- Object.entries(envVars || {}).map(([varName, varValue]) => ({ name: varName, value: varValue }));
640
-
641
- const toFormFiles = (files: InlineApplicationFileFixed[]) => {
642
- return files.map((file) => ({
643
- path: file.path || '',
644
- content: file.content || '',
645
- base64: file.contentEncoding === EncodingType.EncodingBase64,
646
- }));
647
- };
648
-
649
- const toAPIFiles = (files: ComposeInlineAppForm['files']) => {
650
- return files.map((file) => ({
651
- path: file.path,
652
- content: file.content || '',
653
- contentEncoding: file.base64 ? EncodingType.EncodingBase64 : EncodingType.EncodingPlain,
654
- }));
655
- };
656
-
657
- const convertVolumesToForm = (volumes?: ApplicationVolume[]) => {
658
- if (!volumes) return [];
659
- return volumes.map((vol) => {
660
- const fullVolume = vol as ApplicationVolume & ImageMountVolumeProviderSpec;
661
- const volForm: ApplicationVolumeForm = {
662
- name: fullVolume.name,
663
- imageRef: fullVolume.image?.reference || '',
664
- mountPath: fullVolume.mount?.path || '',
665
- };
666
- // Only set imagePullPolicy if there's an image
667
- if (fullVolume.image) {
668
- volForm.imagePullPolicy = fullVolume.image.pullPolicy || ImagePullPolicy.PullIfNotPresent;
669
- }
670
- return volForm;
671
- });
645
+ const getRunAsUser = (app: ContainerApplication | QuadletApplication | undefined): string => {
646
+ if (app) {
647
+ // Existing apps that don't have a "runAs" user are actually running as the "root" user.
648
+ return app.runAs || RUN_AS_ROOT_USER;
649
+ }
650
+ // For new applications, we want to promote "flightctl" as the default user.
651
+ return RUN_AS_FLIGHTCTL_USER;
672
652
  };
673
653
 
674
- const createContainerApp = (
675
- containerApp: (ApplicationProviderSpec & ImageApplicationProviderSpec) | undefined,
676
- ): SingleContainerAppForm => {
654
+ const toContainerAppForm = (containerApp: ContainerApplication | undefined): SingleContainerAppForm => {
677
655
  const ports =
678
656
  containerApp?.ports?.map((portString) => {
679
657
  const [hostPort, containerPort] = portString.split(':');
@@ -681,136 +659,94 @@ const createContainerApp = (
681
659
  }) || [];
682
660
 
683
661
  const limits = containerApp?.resources?.limits;
662
+
684
663
  return {
685
664
  appType: AppType.AppTypeContainer,
686
665
  specType: AppSpecType.OCI_IMAGE,
687
666
  name: containerApp?.name || '',
688
667
  image: containerApp?.image || '',
689
- variables: getAppFormVariables(containerApp?.envVars),
690
- volumes: convertVolumesToForm(containerApp?.volumes),
668
+ variables: toFormVariables(containerApp?.envVars || {}),
669
+ volumes: toFormVolumes(containerApp?.volumes),
691
670
  ports,
692
- limits: limits
693
- ? {
694
- cpu: limits.cpu || '',
695
- memory: limits.memory || '',
696
- }
697
- : undefined,
698
- runAs: containerApp?.runAs || RUN_AS_DEFAULT_USER,
671
+ cpuLimit: limits?.cpu || '',
672
+ memoryLimit: limits?.memory || '',
673
+ runAs: getRunAsUser(containerApp),
699
674
  };
700
675
  };
701
676
 
702
- const createHelmApp = (
703
- helmApp: (ApplicationProviderSpec & ImageApplicationProviderSpec) | undefined,
704
- ): HelmImageAppForm => {
677
+ const toHelmAppForm = (helmApp: HelmApplication | undefined): HelmAppForm => {
678
+ // We want to always show at least one values file field, even when no files have been added yet.
705
679
  const values = helmApp?.values || {};
680
+ const valuesFiles = helmApp?.valuesFiles?.length ? helmApp.valuesFiles : [''];
681
+ const valuesYaml = Object.keys(values || {}).length > 0 ? yaml.dump(values) : '';
682
+
706
683
  return {
707
684
  appType: AppType.AppTypeHelm,
708
685
  specType: AppSpecType.OCI_IMAGE,
709
686
  name: helmApp?.name || '',
710
687
  image: helmApp?.image || '',
711
- namespace: helmApp?.namespace,
712
- valuesYaml: Object.keys(values).length > 0 ? yaml.dump(values) : '',
713
- valuesFiles: helmApp?.valuesFiles || [''],
688
+ namespace: helmApp?.namespace || '',
689
+ valuesYaml,
690
+ valuesFiles,
714
691
  };
715
692
  };
716
693
 
717
- const createQuadletImageApp = (
718
- quadletApp: (ApplicationProviderSpec & ImageApplicationProviderSpec) | undefined,
719
- ): QuadletImageAppForm => {
720
- return {
721
- appType: AppType.AppTypeQuadlet,
722
- specType: AppSpecType.OCI_IMAGE,
723
- name: quadletApp?.name || '',
724
- image: quadletApp?.image || '',
725
- variables: getAppFormVariables(quadletApp?.envVars),
726
- volumes: convertVolumesToForm(quadletApp?.volumes),
727
- runAs: quadletApp?.runAs || RUN_AS_DEFAULT_USER,
694
+ const toComposeAppForm = (app: ComposeApplication | undefined): ComposeAppForm => {
695
+ const isInlineVariant = app && isInlineVariantApp(app);
696
+ const specType = isInlineVariant ? AppSpecType.INLINE : AppSpecType.OCI_IMAGE;
697
+ const formApp: Partial<QuadletAppForm | ComposeAppForm> = {
698
+ appType: AppType.AppTypeCompose,
699
+ specType,
700
+ name: app?.name || '',
701
+ variables: toFormVariables(app?.envVars || {}),
702
+ volumes: toFormVolumes(app?.volumes),
728
703
  };
729
- };
730
704
 
731
- const createQuadletInlineApp = (
732
- quadletApp: (ApplicationProviderSpec & InlineApplicationProviderSpec) | undefined,
733
- ): QuadletInlineAppForm => {
734
- return {
735
- appType: AppType.AppTypeQuadlet,
736
- specType: AppSpecType.INLINE,
737
- name: quadletApp?.name || '',
738
- files: toFormFiles(quadletApp?.inline || []),
739
- variables: getAppFormVariables(quadletApp?.envVars),
740
- volumes: convertVolumesToForm(quadletApp?.volumes),
741
- runAs: quadletApp?.runAs || RUN_AS_DEFAULT_USER,
742
- };
705
+ // We want to have both fields initialized for the formik form
706
+ if (isInlineVariant) {
707
+ formApp.files = toFormFiles(app?.inline || []);
708
+ formApp.image = '';
709
+ } else {
710
+ formApp.image = app?.image || '';
711
+ formApp.files = [];
712
+ }
713
+ return formApp as ComposeAppForm;
743
714
  };
744
715
 
745
- const createComposeImageApp = (
746
- composeApp: (ApplicationProviderSpec & ImageApplicationProviderSpec) | undefined,
747
- ): ComposeImageAppForm => {
748
- return {
749
- appType: AppType.AppTypeCompose,
750
- specType: AppSpecType.OCI_IMAGE,
751
- name: composeApp?.name || '',
752
- image: composeApp?.image || '',
753
- variables: getAppFormVariables(composeApp?.envVars),
754
- volumes: convertVolumesToForm(composeApp?.volumes),
755
- };
756
- };
716
+ const toQuadletAppForm = (app: QuadletApplication | undefined): QuadletAppForm => {
717
+ const baseApp = toComposeAppForm(app);
757
718
 
758
- const createComposeInlineApp = (
759
- composeApp: (ApplicationProviderSpec & InlineApplicationProviderSpec) | undefined,
760
- ): ComposeInlineAppForm => {
761
719
  return {
762
- appType: AppType.AppTypeCompose,
763
- specType: AppSpecType.INLINE,
764
- name: composeApp?.name || '',
765
- files: toFormFiles(composeApp?.inline || []),
766
- variables: getAppFormVariables(composeApp?.envVars),
767
- volumes: convertVolumesToForm(composeApp?.volumes),
720
+ ...baseApp,
721
+ appType: AppType.AppTypeQuadlet,
722
+ runAs: getRunAsUser(app),
768
723
  };
769
724
  };
770
725
 
771
- export const createInitialAppForm = (appType: AppType, specType: AppSpecType, name: string = ''): AppForm => {
726
+ export const createInitialAppForm = (appType: AppType, name: string = ''): AppForm => {
772
727
  let app: AppForm;
773
728
  switch (appType) {
774
729
  case AppType.AppTypeContainer:
775
- app = createContainerApp(undefined);
730
+ app = toContainerAppForm(undefined);
776
731
  break;
777
732
  case AppType.AppTypeHelm:
778
- app = createHelmApp(undefined);
733
+ app = toHelmAppForm(undefined);
779
734
  break;
780
735
  case AppType.AppTypeQuadlet:
781
- app = specType === AppSpecType.OCI_IMAGE ? createQuadletImageApp(undefined) : createQuadletInlineApp(undefined);
736
+ app = toQuadletAppForm(undefined);
782
737
  break;
783
738
  case AppType.AppTypeCompose:
784
- app = specType === AppSpecType.OCI_IMAGE ? createComposeImageApp(undefined) : createComposeInlineApp(undefined);
739
+ app = toComposeAppForm(undefined);
785
740
  break;
741
+ default:
742
+ throw new Error('Unknown application type');
786
743
  }
787
744
  app.name = name;
788
745
  return app;
789
746
  };
790
747
 
791
- export const getApplicationValues = (deviceSpec?: DeviceSpec): AppForm[] => {
792
- const apps = deviceSpec?.applications || [];
793
- return apps.map((app) => {
794
- if (!app.appType) {
795
- throw new Error('Application appType is required');
796
- }
797
-
798
- switch (app.appType) {
799
- case AppType.AppTypeContainer:
800
- return createContainerApp(app as ApplicationProviderSpec & ImageApplicationProviderSpec);
801
- case AppType.AppTypeHelm:
802
- return createHelmApp(app as ApplicationProviderSpec & ImageApplicationProviderSpec);
803
- case AppType.AppTypeQuadlet:
804
- return isImageAppProvider(app)
805
- ? createQuadletImageApp(app)
806
- : createQuadletInlineApp(app as ApplicationProviderSpec & InlineApplicationProviderSpec & { runAs?: string });
807
- case AppType.AppTypeCompose:
808
- return isImageAppProvider(app)
809
- ? createComposeImageApp(app)
810
- : createComposeInlineApp(app as ApplicationProviderSpec & InlineApplicationProviderSpec);
811
- }
812
- });
813
- };
748
+ export const getApplicationValues = (deviceSpec?: DeviceSpec): AppForm[] =>
749
+ (deviceSpec?.applications || []).map(toFormApps);
814
750
 
815
751
  export const getSystemdUnitsValues = (deviceSpec?: DeviceSpec): SystemdUnitFormValue[] => {
816
752
  return (