@rancher/shell 3.0.7 → 3.0.8-rc.10

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 (375) hide show
  1. package/assets/brand/suse/banner.svg +1 -0
  2. package/assets/brand/suse/dark/banner.svg +1 -0
  3. package/assets/brand/suse/dark/login-landscape.svg +1 -0
  4. package/assets/brand/suse/dark/rancher-logo.svg +1 -1
  5. package/assets/brand/suse/favicon.png +0 -0
  6. package/assets/brand/suse/login-landscape.svg +1 -0
  7. package/assets/brand/suse/metadata.json +11 -1
  8. package/assets/brand/suse/rancher-logo.svg +1 -1
  9. package/assets/fonts/suse/suse-v2-latin-300.woff +0 -0
  10. package/assets/fonts/suse/suse-v2-latin-300.woff2 +0 -0
  11. package/assets/fonts/suse/suse-v2-latin-600.woff +0 -0
  12. package/assets/fonts/suse/suse-v2-latin-600.woff2 +0 -0
  13. package/assets/fonts/suse/suse-v2-latin-700.woff +0 -0
  14. package/assets/fonts/suse/suse-v2-latin-700.woff2 +0 -0
  15. package/assets/fonts/suse/suse-v2-latin-800.woff +0 -0
  16. package/assets/fonts/suse/suse-v2-latin-800.woff2 +0 -0
  17. package/assets/fonts/suse/suse-v2-latin-regular.woff +0 -0
  18. package/assets/fonts/suse/suse-v2-latin-regular.woff2 +0 -0
  19. package/assets/images/content/README.md +5 -0
  20. package/assets/images/content/cloud-native.svg +84 -0
  21. package/assets/images/content/dark/cloud-native.svg +21 -0
  22. package/assets/images/content/dark/shield.svg +59 -0
  23. package/assets/images/content/dark/suse.svg +10 -0
  24. package/assets/images/content/shield.svg +59 -0
  25. package/assets/images/content/suse.svg +10 -0
  26. package/assets/images/vendor/githubapp.svg +13 -0
  27. package/assets/styles/base/_typography.scss +2 -1
  28. package/assets/styles/fonts/_fontstack.scss +53 -1
  29. package/assets/styles/global/_cards.scss +0 -3
  30. package/assets/styles/global/_layout.scss +21 -35
  31. package/assets/styles/themes/_dark.scss +1 -1
  32. package/assets/styles/themes/_light.scss +1 -1
  33. package/assets/styles/themes/_modern.scss +16 -8
  34. package/assets/styles/themes/_suse.scss +116 -24
  35. package/assets/translations/en-us.yaml +185 -21
  36. package/assets/translations/zh-hans.yaml +0 -4
  37. package/components/AutoscalerCard.vue +113 -0
  38. package/components/AutoscalerTab.vue +94 -0
  39. package/components/BackLink.vue +8 -0
  40. package/components/BannerGraphic.vue +36 -21
  41. package/components/BrandImage.vue +17 -6
  42. package/components/ClusterIconMenu.vue +1 -1
  43. package/components/ClusterProviderIcon.vue +1 -1
  44. package/components/Cron/CronExpressionEditor.vue +1 -1
  45. package/components/Cron/CronExpressionEditorModal.vue +1 -1
  46. package/components/Drawer/Chrome.vue +2 -6
  47. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
  48. package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
  49. package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
  50. package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
  51. package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
  52. package/components/Drawer/types.ts +3 -0
  53. package/components/DynamicContent/DynamicContentBanner.vue +102 -0
  54. package/components/DynamicContent/DynamicContentCloseButton.vue +42 -0
  55. package/components/DynamicContent/DynamicContentIcon.vue +132 -0
  56. package/components/DynamicContent/DynamicContentPanel.vue +112 -0
  57. package/components/DynamicContent/content.ts +78 -0
  58. package/components/EmberPage.vue +1 -1
  59. package/components/IconOrSvg.vue +2 -2
  60. package/components/Inactivity.vue +222 -106
  61. package/components/InstallHelmCharts.vue +2 -2
  62. package/components/PaginatedResourceTable.vue +2 -6
  63. package/components/PopoverCard.vue +192 -0
  64. package/components/Questions/__tests__/index.test.ts +159 -0
  65. package/components/Resource/Detail/CopyToClipboard.vue +4 -1
  66. package/components/Resource/Detail/FetchLoader/composables.ts +18 -4
  67. package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
  68. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +1 -1
  69. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +4 -0
  70. package/components/Resource/Detail/Metadata/KeyValueRow.vue +1 -1
  71. package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
  72. package/components/Resource/Detail/Metadata/composables.ts +9 -9
  73. package/components/Resource/Detail/Metadata/index.vue +3 -3
  74. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -19
  75. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +0 -29
  76. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +132 -150
  77. package/components/Resource/Detail/ResourcePopover/index.vue +54 -159
  78. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
  79. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  80. package/components/Resource/Detail/TitleBar/index.vue +10 -6
  81. package/components/Resource/Detail/composables.ts +12 -0
  82. package/components/ResourceDetail/Masthead/latest.vue +29 -0
  83. package/components/ResourceDetail/index.vue +5 -2
  84. package/components/ResourceList/Masthead.vue +1 -1
  85. package/components/SortableTable/index.vue +18 -2
  86. package/components/Tabbed/__tests__/index.test.ts +86 -0
  87. package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
  88. package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
  89. package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
  90. package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
  91. package/components/__tests__/AutoscalerCard.test.ts +154 -0
  92. package/components/__tests__/AutoscalerTab.test.ts +125 -0
  93. package/components/__tests__/PopoverCard.test.ts +204 -0
  94. package/components/auth/SelectPrincipal.vue +24 -6
  95. package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
  96. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  97. package/components/fleet/FleetSecretSelector.vue +127 -0
  98. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  99. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  100. package/components/form/FileImageSelector.vue +13 -4
  101. package/components/form/FileSelector.vue +11 -2
  102. package/components/form/ResourceLabeledSelect.vue +1 -0
  103. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  104. package/components/formatter/Autoscaler.vue +97 -0
  105. package/components/formatter/InternalExternalIP.vue +198 -24
  106. package/components/formatter/__tests__/Autoscaler.test.ts +156 -0
  107. package/components/formatter/__tests__/InternalExternalIP.test.ts +133 -0
  108. package/components/google/util/__tests__/formatter.test.ts +47 -0
  109. package/components/google/util/formatter.ts +5 -2
  110. package/components/nav/Group.vue +12 -3
  111. package/components/nav/Header.vue +37 -16
  112. package/components/nav/NamespaceFilter.vue +13 -1
  113. package/components/nav/NotificationCenter/index.vue +2 -1
  114. package/components/nav/TopLevelMenu.helper.ts +16 -6
  115. package/components/nav/TopLevelMenu.vue +4 -2
  116. package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
  117. package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
  118. package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
  119. package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
  120. package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
  121. package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
  122. package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
  123. package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
  124. package/components/nav/WindowManager/constants.ts +23 -0
  125. package/components/nav/WindowManager/index.vue +61 -575
  126. package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
  127. package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
  128. package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
  129. package/components/templates/default.vue +4 -40
  130. package/components/templates/home.vue +31 -5
  131. package/components/templates/plain.vue +30 -4
  132. package/components/templates/standalone.vue +1 -1
  133. package/composables/useI18n.ts +10 -1
  134. package/composables/useInterval.ts +15 -0
  135. package/config/__test__/uiplugins.test.ts +309 -0
  136. package/config/labels-annotations.js +9 -1
  137. package/config/product/auth.js +1 -0
  138. package/config/product/explorer.js +3 -1
  139. package/config/product/manager.js +20 -9
  140. package/config/query-params.js +1 -0
  141. package/config/router/routes.js +10 -2
  142. package/config/settings.ts +10 -2
  143. package/config/store.js +4 -2
  144. package/config/table-headers.js +8 -0
  145. package/config/types.js +11 -0
  146. package/config/uiplugins.js +46 -2
  147. package/config/version.js +1 -1
  148. package/core/__test__/extension-manager-impl.test.js +236 -0
  149. package/core/extension-manager-impl.js +23 -6
  150. package/core/plugin-helpers.ts +2 -0
  151. package/core/types-provisioning.ts +4 -1
  152. package/detail/pod.vue +1 -0
  153. package/detail/provisioning.cattle.io.cluster.vue +13 -1
  154. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  155. package/dialog/DeveloperLoadExtensionDialog.vue +12 -3
  156. package/dialog/RollbackWorkloadDialog.vue +2 -5
  157. package/directives/ui-context.ts +103 -0
  158. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  159. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  160. package/edit/auth/__tests__/oidc.test.ts +26 -0
  161. package/edit/auth/github-app-steps.vue +97 -0
  162. package/edit/auth/github-steps.vue +75 -0
  163. package/edit/auth/github.vue +99 -65
  164. package/edit/auth/oidc.vue +5 -1
  165. package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
  166. package/edit/cloudcredential.vue +1 -1
  167. package/edit/configmap.vue +1 -0
  168. package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
  169. package/edit/fleet.cattle.io.gitrepo.vue +0 -10
  170. package/edit/fleet.cattle.io.helmop.vue +51 -2
  171. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  172. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
  173. package/edit/logging-flow/index.vue +1 -0
  174. package/edit/logging.banzaicloud.io.output/index.vue +1 -0
  175. package/edit/management.cattle.io.fleetworkspace.vue +1 -1
  176. package/edit/management.cattle.io.project.vue +1 -0
  177. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
  178. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
  179. package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
  180. package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
  181. package/edit/monitoring.coreos.com.route.vue +1 -1
  182. package/edit/namespace.vue +1 -0
  183. package/edit/networking.istio.io.destinationrule/index.vue +1 -0
  184. package/edit/networking.k8s.io.ingress/index.vue +1 -0
  185. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  186. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
  187. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
  188. package/edit/node.vue +1 -0
  189. package/edit/persistentvolume/index.vue +27 -22
  190. package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
  191. package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
  192. package/edit/persistentvolume/plugins/azureFile.vue +15 -14
  193. package/edit/persistentvolume/plugins/cephfs.vue +15 -14
  194. package/edit/persistentvolume/plugins/cinder.vue +15 -14
  195. package/edit/persistentvolume/plugins/csi.vue +18 -16
  196. package/edit/persistentvolume/plugins/fc.vue +13 -14
  197. package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
  198. package/edit/persistentvolume/plugins/flocker.vue +1 -3
  199. package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
  200. package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
  201. package/edit/persistentvolume/plugins/hostPath.vue +40 -39
  202. package/edit/persistentvolume/plugins/iscsi.vue +13 -14
  203. package/edit/persistentvolume/plugins/local.vue +1 -3
  204. package/edit/persistentvolume/plugins/longhorn.vue +23 -22
  205. package/edit/persistentvolume/plugins/nfs.vue +15 -14
  206. package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
  207. package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
  208. package/edit/persistentvolume/plugins/quobyte.vue +15 -14
  209. package/edit/persistentvolume/plugins/rbd.vue +15 -14
  210. package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
  211. package/edit/persistentvolume/plugins/storageos.vue +15 -14
  212. package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
  213. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +32 -5
  214. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +35 -0
  215. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +155 -0
  216. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  217. package/edit/provisioning.cattle.io.cluster/index.vue +25 -15
  218. package/edit/provisioning.cattle.io.cluster/rke2.vue +98 -17
  219. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  220. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +107 -5
  221. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +92 -4
  222. package/edit/secret/index.vue +1 -1
  223. package/edit/service.vue +9 -4
  224. package/edit/serviceaccount.vue +1 -0
  225. package/edit/storage.k8s.io.storageclass/index.vue +1 -0
  226. package/edit/workload/index.vue +2 -1
  227. package/edit/workload/mixins/workload.js +1 -1
  228. package/initialize/App.vue +4 -4
  229. package/initialize/install-directives.js +2 -0
  230. package/initialize/install-plugins.js +19 -2
  231. package/list/projectsecret.vue +1 -1
  232. package/list/provisioning.cattle.io.cluster.vue +15 -2
  233. package/machine-config/amazonec2.vue +42 -135
  234. package/machine-config/azure.vue +1 -1
  235. package/machine-config/components/EC2Networking.vue +490 -0
  236. package/machine-config/components/__tests__/EC2Networking.test.ts +148 -0
  237. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +294 -0
  238. package/machine-config/digitalocean.vue +11 -0
  239. package/machine-config/google.vue +1 -1
  240. package/mixins/__tests__/brand.spec.ts +2 -2
  241. package/mixins/__tests__/chart.test.ts +21 -0
  242. package/mixins/brand.js +1 -7
  243. package/mixins/chart.js +8 -2
  244. package/mixins/create-edit-view/index.js +5 -0
  245. package/models/__tests__/chart.test.ts +49 -12
  246. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  247. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
  248. package/models/catalog.cattle.io.app.js +1 -1
  249. package/models/chart.js +28 -14
  250. package/models/cluster/node.js +13 -6
  251. package/models/cluster.x-k8s.io.machine.js +10 -20
  252. package/models/cluster.x-k8s.io.machinedeployment.js +5 -1
  253. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  254. package/models/management.cattle.io.authconfig.js +1 -0
  255. package/models/management.cattle.io.cluster.js +21 -3
  256. package/models/management.cattle.io.kontainerdriver.js +1 -0
  257. package/models/provisioning.cattle.io.cluster.js +249 -33
  258. package/package.json +6 -5
  259. package/pages/auth/login.vue +43 -4
  260. package/pages/auth/verify.vue +1 -1
  261. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  262. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
  263. package/pages/c/_cluster/apps/charts/chart.vue +35 -17
  264. package/pages/c/_cluster/apps/charts/index.vue +11 -13
  265. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  266. package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
  267. package/pages/c/_cluster/explorer/index.vue +8 -6
  268. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  269. package/pages/c/_cluster/manager/hostedprovider/index.vue +220 -0
  270. package/pages/c/_cluster/settings/brand.vue +1 -1
  271. package/pages/c/_cluster/settings/performance.vue +12 -25
  272. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
  273. package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
  274. package/pages/c/_cluster/uiplugins/index.vue +126 -184
  275. package/pages/home.vue +327 -16
  276. package/pkg/dynamic-importer.lib.js +4 -0
  277. package/plugins/axios.js +2 -1
  278. package/plugins/dashboard-client-init.js +3 -0
  279. package/plugins/dashboard-store/actions.js +1 -1
  280. package/plugins/dashboard-store/getters.js +18 -1
  281. package/plugins/dashboard-store/resource-class.js +21 -6
  282. package/plugins/dynamic-content.js +13 -0
  283. package/plugins/i18n.js +8 -0
  284. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
  285. package/plugins/steve/steve-pagination-utils.ts +41 -22
  286. package/plugins/steve/subscribe.js +17 -9
  287. package/plugins/subscribe-events.ts +4 -2
  288. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  289. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -34
  290. package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
  291. package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
  292. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
  293. package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
  294. package/rancher-components/Pill/types.ts +0 -1
  295. package/rancher-components/RcDropdown/RcDropdownItem.vue +1 -0
  296. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
  297. package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
  298. package/rancher-components/RcIcon/RcIcon.vue +46 -0
  299. package/rancher-components/RcIcon/index.ts +1 -0
  300. package/rancher-components/RcIcon/types.ts +160 -0
  301. package/rancher-components/utils/status.test.ts +67 -0
  302. package/rancher-components/utils/status.ts +77 -0
  303. package/scripts/extension/publish +1 -1
  304. package/scripts/typegen.sh +1 -0
  305. package/store/action-menu.js +8 -0
  306. package/store/auth.js +11 -6
  307. package/store/aws.js +8 -6
  308. package/store/catalog.js +6 -0
  309. package/store/features.js +2 -0
  310. package/store/index.js +45 -20
  311. package/store/notifications.ts +51 -4
  312. package/store/plugins.js +7 -3
  313. package/store/prefs.js +12 -6
  314. package/store/type-map.js +3 -3
  315. package/store/ui-context.ts +86 -0
  316. package/store/wm.ts +244 -0
  317. package/types/kube/kube-api.ts +2 -1
  318. package/types/notifications/index.ts +27 -3
  319. package/types/rancher/index.d.ts +1 -0
  320. package/types/resources/settings.d.ts +29 -7
  321. package/types/shell/index.d.ts +138 -4
  322. package/types/store/__tests__/pagination.types.spec.ts +137 -0
  323. package/types/store/pagination.types.ts +157 -9
  324. package/types/store/subscribe-events.types.ts +8 -1
  325. package/types/store/subscribe.types.ts +1 -0
  326. package/types/window-manager.ts +24 -0
  327. package/utils/__tests__/cluster.test.ts +379 -1
  328. package/utils/__tests__/object.test.ts +19 -0
  329. package/utils/__tests__/provider.test.ts +98 -0
  330. package/utils/__tests__/selector-typed.test.ts +263 -0
  331. package/utils/__tests__/version.test.ts +19 -1
  332. package/utils/autoscaler-utils.ts +7 -0
  333. package/utils/back-off.ts +3 -3
  334. package/utils/brand.ts +29 -0
  335. package/utils/chart.js +18 -0
  336. package/utils/cluster.js +157 -3
  337. package/utils/color.js +1 -1
  338. package/utils/dynamic-content/__tests__/announcement.test.ts +498 -0
  339. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  340. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  341. package/utils/dynamic-content/__tests__/info.test.ts +275 -0
  342. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  343. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  344. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  345. package/utils/dynamic-content/announcement.ts +142 -0
  346. package/utils/dynamic-content/config.ts +55 -0
  347. package/utils/dynamic-content/example.json +40 -0
  348. package/utils/dynamic-content/index.ts +277 -0
  349. package/utils/dynamic-content/info.ts +261 -0
  350. package/utils/dynamic-content/new-release.ts +126 -0
  351. package/utils/dynamic-content/notification-handler.ts +48 -0
  352. package/utils/dynamic-content/support-notice.ts +169 -0
  353. package/utils/dynamic-content/types.d.ts +153 -0
  354. package/utils/dynamic-content/util.ts +122 -0
  355. package/utils/dynamic-importer.js +2 -2
  356. package/utils/favicon.js +4 -4
  357. package/utils/inactivity.ts +104 -0
  358. package/utils/object.js +20 -2
  359. package/utils/pagination-utils.ts +19 -4
  360. package/utils/pagination-wrapper.ts +12 -8
  361. package/utils/provider.ts +14 -0
  362. package/utils/release-notes.ts +1 -1
  363. package/utils/scroll.js +7 -0
  364. package/utils/selector-typed.ts +6 -2
  365. package/utils/settings.ts +15 -0
  366. package/utils/validators/machine-pool.ts +13 -3
  367. package/utils/version.js +15 -0
  368. package/assets/images/icons/document.svg +0 -3
  369. package/plugins/nuxt-client-init.js +0 -3
  370. package/store/wm.js +0 -95
  371. /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
  372. /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
  373. /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
  374. /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
  375. /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * This is the main dynamic content file that provides the 'fetchAndProcessDynamicContent' function
3
+ *
4
+ * This is the main entry point for reading and processing dynamic content
5
+ */
6
+
7
+ import day from 'dayjs';
8
+ import * as jsyaml from 'js-yaml';
9
+ import semver from 'semver';
10
+ import { isAdminUser } from '@shell/store/type-map';
11
+ import { getVersionData } from '@shell/config/version';
12
+ import { processReleaseVersion } from './new-release';
13
+ import { processSupportNotices } from './support-notice';
14
+ import { Context, DynamicContent, VersionInfo } from './types';
15
+ import { createLogger, LOCAL_STORAGE_CONTENT_DEBUG_LOG } from './util';
16
+ import { getConfig } from './config';
17
+ import { SystemInfoProvider } from './info';
18
+ import { processAnnouncements } from './announcement';
19
+
20
+ const FETCH_DELAY = 3 * 1000; // Short delay to let UI settle before we fetch the updates document
21
+ const FETCH_REQUEST_TIMEOUT = 15000; // Time out the request after 15 seconds
22
+ const FETCH_CONCURRENT_SECONDS = 30; // Time to wait to ignore another in-progress fetch (seconds)
23
+
24
+ export const UPDATE_DATE_FORMAT = 'YYYY-MM-DD'; // Format of the fetch date
25
+
26
+ const LOCAL_STORAGE_UPDATE_FETCH_DATE = 'rancher-updates-fetch-next'; // Local storage setting that holds the date when we should next try and fetch content
27
+ const LOCAL_STORAGE_UPDATE_CONTENT = 'rancher-updates-last-content'; // Local storage setting that holds the last fetched content
28
+ const LOCAL_STORAGE_UPDATE_ERRORS = 'rancher-updates-fetch-errors'; // Local storage setting that holds the count of contiguous errors
29
+ const LOCAL_STORAGE_UPDATE_FETCHING = 'rancher-updates-fetching'; // Local storage setting that holds the date and time of the last fetch that was started
30
+
31
+ const BACKOFFS = [1, 1, 1, 2, 2, 3, 5]; // Backoff in days for the contiguous number of errors (i.e. after 1 errors, we wait 1 day, after 3 errors, we wait 2 days, etc.)
32
+
33
+ const DEFAULT_RELEASE_NOTES_URL = 'https://github.com/rancher/rancher/releases/tag/v$version'; // Default release notes URL
34
+
35
+ /**
36
+ * Fetch dynamic content if needed and process it if it has changed since we last checked
37
+ */
38
+ export async function fetchAndProcessDynamicContent(dispatch: Function, getters: any, axios: any) {
39
+ // Check that the product is Rancher
40
+ // => Check that we are NOT in single product mode (e.g. Harvester)
41
+ const isSingleProduct = getters['isSingleProduct'];
42
+
43
+ if (!!isSingleProduct) {
44
+ return;
45
+ }
46
+
47
+ const config = getConfig(getters);
48
+
49
+ // If not enabled via the configuration, then just return
50
+ if (!config.enabled) {
51
+ console.log('Dynamic content disabled through configuration'); // eslint-disable-line no-console
52
+
53
+ return;
54
+ }
55
+
56
+ const logger = createLogger(config);
57
+
58
+ // Common context to pass through to functions for store access, logging, etc
59
+ const context: Context = {
60
+ dispatch,
61
+ getters,
62
+ axios,
63
+ logger,
64
+ config,
65
+ isAdmin: isAdminUser(getters),
66
+ settings: {
67
+ releaseNotesUrl: DEFAULT_RELEASE_NOTES_URL,
68
+ suseExtensions: [],
69
+ }
70
+ };
71
+
72
+ logger.debug('Read configuration', context.config);
73
+
74
+ try {
75
+ // Fetch the dynamic content if required, otherwise return the cached content or empty object if no content available
76
+ const content = await fetchDynamicContent(context);
77
+
78
+ // Version metadata
79
+ const versionData = getVersionData();
80
+ const version = semver.coerce(versionData.Version);
81
+
82
+ if (!version || !content) {
83
+ return;
84
+ }
85
+
86
+ const versionInfo: VersionInfo = {
87
+ version: version as semver.SemVer, // Will be defined, can not be null here
88
+ isPrime: config.prime,
89
+ };
90
+
91
+ // If not logging, then clear out any log data from local storage
92
+ if (!config.log) {
93
+ window.localStorage.removeItem(LOCAL_STORAGE_CONTENT_DEBUG_LOG);
94
+ }
95
+
96
+ if (content?.settings) {
97
+ // Update the settings data from the content, so that it is has the settings with their defaults or values from the dynamic content payload
98
+ context.settings = {
99
+ ...context.settings,
100
+ ...content.settings
101
+ };
102
+ }
103
+
104
+ // If the cached content has a debug version then use that as an override for the current version number
105
+ // This is only for debug and testing purposes
106
+ if (content.settings?.debugVersion) {
107
+ versionInfo.version = semver.coerce(content.settings.debugVersion) || version;
108
+ logger.debug(`Overriding version number to ${ content.settings.debugVersion }`);
109
+ }
110
+
111
+ // We always process the content in case the Rancher version has changed or the date means that an announcement/notification should now be shown
112
+
113
+ // New release notifications and support notifications are shown to ALL community users, but only to admin users when Prime
114
+ if (!config.prime || context.isAdmin) {
115
+ // New release notifications
116
+ processReleaseVersion(context, content.releases, versionInfo);
117
+
118
+ // EOM, EOL notifications
119
+ processSupportNotices(context, content.support, versionInfo);
120
+ }
121
+
122
+ // Announcements - processed for all users
123
+ processAnnouncements(context, content.announcements, versionInfo);
124
+ } catch (e) {
125
+ logger.error('Error reading or processing dynamic content', e);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * We use a signal to timeout the connection
131
+ * For air-gapped environments, this ensures the request will timeout after FETCH_REQUEST_TIMEOUT
132
+ * This timeout is set relatively low (15s). The default, otherwise, is 2 minutes.
133
+ *
134
+ * @param timeoutMs Time in milliseconds after which the abort signal should signal
135
+ */
136
+ function newRequestAbortSignal(timeoutMs: number) {
137
+ const abortController = new AbortController();
138
+
139
+ setTimeout(() => abortController.abort(), timeoutMs || 0);
140
+
141
+ return abortController.signal;
142
+ }
143
+
144
+ /**
145
+ * Update the local storage data that tracks when to next fetch content and how many consecutive errors we have had
146
+ *
147
+ * @param didError Indicates if we should update to record content retrieved without error or with error
148
+ */
149
+ function updateFetchInfo(didError: boolean) {
150
+ if (!didError) {
151
+ // No error, so check again tomorrow and remove the backoff setting, so it will get its default next time
152
+ const nextFetch = day().add(1, 'day');
153
+ const nextFetchString = nextFetch.format(UPDATE_DATE_FORMAT);
154
+
155
+ window.localStorage.setItem(LOCAL_STORAGE_UPDATE_FETCH_DATE, nextFetchString);
156
+ window.localStorage.removeItem(LOCAL_STORAGE_UPDATE_ERRORS);
157
+ } else {
158
+ // Did error, read the backoff, increase and add to the date
159
+ const contiguousErrorsString = window.localStorage.getItem(LOCAL_STORAGE_UPDATE_ERRORS) || '0';
160
+
161
+ let contiguousErrors = parseInt(contiguousErrorsString, 10);
162
+
163
+ // Increase the number of errors that have happened in a row
164
+ contiguousErrors++;
165
+
166
+ // Once we reach the max backoff, just stick with it
167
+ if (contiguousErrors >= BACKOFFS.length ) {
168
+ contiguousErrors = BACKOFFS.length - 1;
169
+ }
170
+
171
+ // Now find the backoff (days) given the error count and calculate the date of the next fetch
172
+ const daysToAdd = BACKOFFS[contiguousErrors];
173
+ const nextFetch = day().add(daysToAdd, 'day');
174
+ const nextFetchString = nextFetch.format(UPDATE_DATE_FORMAT);
175
+
176
+ window.localStorage.setItem(LOCAL_STORAGE_UPDATE_FETCH_DATE, nextFetchString);
177
+ window.localStorage.setItem(LOCAL_STORAGE_UPDATE_ERRORS, contiguousErrors.toString());
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Fetch dynamic content (if needed)
183
+ */
184
+ export async function fetchDynamicContent(context: Context): Promise<Partial<DynamicContent> | undefined> {
185
+ const { getters, logger, config } = context;
186
+
187
+ // Check if we already have done an update check today
188
+ let content: Partial<DynamicContent> = {};
189
+
190
+ try {
191
+ const today = day();
192
+ const todayString = today.format(UPDATE_DATE_FORMAT);
193
+ const nextFetch = window.localStorage.getItem(LOCAL_STORAGE_UPDATE_FETCH_DATE) || todayString;
194
+
195
+ // Read the cached content from local storage if possible
196
+ content = JSON.parse(window.localStorage.getItem(LOCAL_STORAGE_UPDATE_CONTENT) || '{}');
197
+
198
+ const nextFetchDay = day(nextFetch);
199
+
200
+ // Just in case next day gets reset to the past or corrupt, otherwise next fetch needs to not be in the future
201
+ if (!nextFetchDay.isValid() || !nextFetchDay.isAfter(today)) {
202
+ logger.info(`Performing update check on ${ todayString }`);
203
+ logger.debug(`Performing update check on ${ todayString }`);
204
+
205
+ const activeFetch = window.localStorage.getItem(LOCAL_STORAGE_UPDATE_FETCHING);
206
+
207
+ if (activeFetch) {
208
+ const activeFetchDate = day(activeFetch);
209
+
210
+ if (activeFetchDate.isValid() && today.diff(activeFetchDate, 'second') < FETCH_CONCURRENT_SECONDS) {
211
+ logger.debug('Already fetching dynamic content in another tab (or previous tab closed while fetching) - skipping');
212
+
213
+ return content;
214
+ }
215
+ }
216
+
217
+ // Set the local storage key that indicates a tab is fetching the content - prevents other tabs doing so at the same time
218
+ window.localStorage.setItem(LOCAL_STORAGE_UPDATE_FETCHING, today.toString());
219
+
220
+ // Wait a short while before fetching dynamic content
221
+ await new Promise((resolve) => setTimeout(resolve, FETCH_DELAY));
222
+
223
+ const systemData = new SystemInfoProvider(getters, content?.settings || {});
224
+ const qs = systemData.buildQueryString();
225
+ const distribution = config.prime ? 'prime' : 'community';
226
+ const url = `${ config.endpoint.replace('$dist', distribution) }?${ qs }`;
227
+
228
+ logger.debug(`Fetching dynamic content from: ${ url.split('?')[0] }`, url);
229
+
230
+ // We use axios directly so that we can pass in the abort signal to implement the connection timeout
231
+ const res = await context.axios({
232
+ url,
233
+ method: 'get',
234
+ timeout: FETCH_REQUEST_TIMEOUT,
235
+ noApiCsrf: true,
236
+ withCredentials: false,
237
+ signal: newRequestAbortSignal(FETCH_REQUEST_TIMEOUT),
238
+ responseType: 'text' // We always want the raw text back - otherwise YAML gives text and JSON gives object
239
+ });
240
+
241
+ // The data should be YAML (or JSON) in the 'data' attribute
242
+ if (res?.data) {
243
+ try {
244
+ content = jsyaml.load(res.data) as any;
245
+
246
+ window.localStorage.setItem(LOCAL_STORAGE_UPDATE_CONTENT, JSON.stringify(content));
247
+
248
+ // Update the last date now
249
+ updateFetchInfo(false);
250
+ } catch (e) {
251
+ logger.error('Failed to parse YAML/JSON from dynamic content package', e);
252
+ }
253
+ } else {
254
+ logger.error('Error fetching dynamic content package (unexpected data)');
255
+ }
256
+ } else {
257
+ logger.info(`Skipping update check for dynamic content - next check due on ${ nextFetch } (today is ${ todayString })`);
258
+
259
+ // If debug mode, then wait a bit to simulate the delay we would have had if we were fetching
260
+ if (config.debug) {
261
+ await new Promise((resolve) => setTimeout(resolve, FETCH_DELAY));
262
+ }
263
+ }
264
+ } catch (e) {
265
+ logger.error('Error occurred reading dynamic content', e);
266
+
267
+ // We had an error, so update data in local storage so that we try again appropriately next time
268
+ updateFetchInfo(true);
269
+ }
270
+
271
+ logger.debug('End fetchDynamicContent');
272
+
273
+ // Remove the local storage key that indicates a tab is fetching the content
274
+ window.localStorage.removeItem(LOCAL_STORAGE_UPDATE_FETCHING);
275
+
276
+ return content;
277
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Helper for collecting system information and formatting into to a query string
3
+ */
4
+
5
+ import { sha256 } from '@shell/utils/crypto';
6
+ import {
7
+ COUNT,
8
+ MANAGEMENT,
9
+ } from '@shell/config/types';
10
+ import { SETTING } from '@shell/config/settings';
11
+ import { getVersionData } from '@shell/config/version';
12
+ import { SettingsInfo } from '@shell/utils/dynamic-content/types';
13
+ import { STEVE_CACHE } from '@shell/store/features';
14
+
15
+ const QS_VERSION = 'v1'; // Include a version number in the query string in case we want to version the set of params we are sending
16
+ const UNKNOWN = 'unknown';
17
+
18
+ // List of known UI extensions from SUSE
19
+ const SUSE_EXTENSIONS = [
20
+ 'capi',
21
+ 'elemental',
22
+ 'harvester',
23
+ 'kubewarden',
24
+ 'neuvector-ui-ext',
25
+ 'observability',
26
+ 'supportability-review-app',
27
+ 'virtual-clusters'
28
+ ];
29
+
30
+ type FeatureFlagInfos = {
31
+ [id: string]: {
32
+ /**
33
+ * Query param, in format `ff-<param>`
34
+ */
35
+ param: string,
36
+ /**
37
+ * The actual value used by the UI, roughly spec.value || status.default
38
+ */
39
+ value: string,
40
+ }
41
+ };
42
+
43
+ /**
44
+ * Explicit ff's to send
45
+ */
46
+ const ffs: FeatureFlagInfos = {
47
+ [STEVE_CACHE]: {
48
+ param: 'usc',
49
+ value: '',
50
+ }
51
+ };
52
+
53
+ /**
54
+ * System information that is collected and which can then be encoded into a query string in the dyanmic content request
55
+ */
56
+ type SystemInfo = {
57
+ systemUUID: string;
58
+ systemHash: string;
59
+ serverVersionType: string;
60
+ userHash: string;
61
+ version: string;
62
+ isDeveloperVersion: boolean;
63
+ isPrime: boolean;
64
+ isLTS: boolean;
65
+ clusterCount: number;
66
+ localCluster: any;
67
+ extensions?: {
68
+ knownInstalled: string[];
69
+ customCount: number;
70
+ };
71
+ browserSize: string;
72
+ screenSize: string;
73
+ language: string;
74
+ featureFlags: FeatureFlagInfos
75
+ };
76
+
77
+ /**
78
+ * Helper that will gather system information and provided the `buildQueryString` method to format as a query string
79
+ */
80
+ export class SystemInfoProvider {
81
+ private info: SystemInfo;
82
+
83
+ constructor(getters: any, settings: Partial<SettingsInfo>) {
84
+ this.info = this.getSystemData(getters, settings);
85
+ }
86
+
87
+ /**
88
+ * Get the data that we need from the system
89
+ * @param getters Store getters to access the store
90
+ * @returns System data
91
+ */
92
+ private getSystemData(getters: any, settingsInfo: Partial<SettingsInfo>): SystemInfo {
93
+ let url;
94
+ let systemUUID = UNKNOWN;
95
+ let serverVersionType = UNKNOWN;
96
+
97
+ const serverUrlSetting = getters['management/byId'](MANAGEMENT.SETTING, SETTING.SERVER_URL);
98
+
99
+ // Server URL
100
+ if (serverUrlSetting) {
101
+ url = serverUrlSetting.value || UNKNOWN;
102
+ }
103
+
104
+ // UUID
105
+ const uuidSetting = getters['management/byId'](MANAGEMENT.SETTING, 'install-uuid');
106
+
107
+ if (uuidSetting) {
108
+ systemUUID = uuidSetting.value || UNKNOWN;
109
+ }
110
+
111
+ // Server Version Type
112
+ const serverVersionTypeSetting = getters['management/byId'](MANAGEMENT.SETTING, 'server-version-type');
113
+
114
+ if (serverVersionTypeSetting) {
115
+ serverVersionType = serverVersionTypeSetting.value || UNKNOWN;
116
+ }
117
+
118
+ // Otherwise, use the window location's host
119
+ url = url || window.location?.host;
120
+
121
+ // System and User hashes
122
+ const systemHash = (sha256(url, 'hex') as string).substring(0, 32);
123
+ const currentPrincipal = getters['auth/principalId'] || UNKNOWN;
124
+ const userHash = (sha256(currentPrincipal, 'hex') as string).substring(0, 32);
125
+
126
+ // Version info
127
+ const versionData = getVersionData();
128
+ const vers = versionData.Version.split('-');
129
+
130
+ // General stats that can help us shape content delivery
131
+
132
+ // High-level information from clusters
133
+ const counts = this.getAll(getters, COUNT)?.[0]?.counts || {};
134
+ const clusterCount = counts[MANAGEMENT.CLUSTER] || {};
135
+ const localCluster = getters['localCluster'];
136
+
137
+ // Stats for installed extensions
138
+ const uiExtensionList = getters['uiplugins/plugins'];
139
+ let extensions;
140
+
141
+ if (uiExtensionList) {
142
+ const suseExtensions = [
143
+ ...SUSE_EXTENSIONS,
144
+ ...settingsInfo?.suseExtensions || []
145
+ ];
146
+ const notBuiltIn = uiExtensionList.filter((e: any) => !e.builtin);
147
+ const suseNames = notBuiltIn.filter((e: any) => suseExtensions.includes(e.name)).map((e: any) => e.name);
148
+ const customCount = notBuiltIn.length - suseNames.length;
149
+
150
+ extensions = {
151
+ knownInstalled: suseNames,
152
+ customCount,
153
+ };
154
+ }
155
+
156
+ const screenSize = `${ window.screen?.width || '?' }x${ window.screen?.height || '?' }`;
157
+ const browserSize = `${ window.innerWidth }x${ window.innerHeight }`;
158
+
159
+ const safeFfs = Object.entries(ffs).reduce((res, [id, ff]) => {
160
+ try {
161
+ res[id] = {
162
+ param: ff.param,
163
+ value: getters['features/get'](id),
164
+ };
165
+ } catch (e) {
166
+ console.debug(`Cannot include Feature Flag "${ id }" in dynamic feature request: `, e); // eslint-disable-line no-console
167
+ }
168
+
169
+ return res;
170
+ }, {} as FeatureFlagInfos);
171
+
172
+ return {
173
+ systemUUID,
174
+ userHash,
175
+ systemHash,
176
+ serverVersionType,
177
+ version: vers[0],
178
+ isDeveloperVersion: vers.length > 1,
179
+ isPrime: versionData.RancherPrime === 'true',
180
+ isLTS: false,
181
+ clusterCount: clusterCount?.summary?.count,
182
+ localCluster,
183
+ extensions,
184
+ screenSize,
185
+ browserSize,
186
+ language: window.navigator?.language,
187
+ featureFlags: safeFfs,
188
+ };
189
+ }
190
+
191
+ // Helper to get all resources of a type only if they are available
192
+ private getAll(getters: any, typeName: string): any {
193
+ if (getters['management/typeRegistered'](typeName)) {
194
+ return getters['management/all'](typeName);
195
+ }
196
+
197
+ return undefined;
198
+ }
199
+
200
+ /**
201
+ * Build query string of params to send so that we can deliver better content
202
+ */
203
+ public buildQueryString(): string {
204
+ const systemData = this.info;
205
+ const params = [`dcv=${ QS_VERSION }`];
206
+
207
+ // System and User
208
+ params.push(`s=${ systemData.systemHash }`);
209
+ params.push(`u=${ systemData.userHash }`);
210
+
211
+ // Install UUID
212
+ if (systemData.systemUUID !== UNKNOWN) {
213
+ params.push(`uuid=${ systemData.systemUUID }`);
214
+ }
215
+
216
+ // Server Version Type
217
+ if (systemData.serverVersionType !== UNKNOWN) {
218
+ params.push(`svt=${ systemData.serverVersionType }`);
219
+ }
220
+
221
+ // Version info
222
+ params.push(`v=${ systemData.version }`);
223
+ params.push(`dev=${ systemData.isDeveloperVersion }`);
224
+ params.push(`p=${ systemData.isPrime }`);
225
+
226
+ // Remove LTS for now, until we can determine LTS status
227
+ // params.push(`lts=${ systemData.isLTS }`);
228
+
229
+ // Clusters
230
+ params.push(`cc=${ systemData.clusterCount }`);
231
+
232
+ if (systemData.localCluster) {
233
+ params.push(`lkv=${ systemData.localCluster.kubernetesVersionBase || UNKNOWN }`);
234
+ params.push(`lcp=${ systemData.localCluster.provisioner || UNKNOWN }`);
235
+ params.push(`lnc=${ systemData.localCluster.status.nodeCount || 0 }`);
236
+ }
237
+
238
+ // Extensions
239
+ if (systemData.extensions) {
240
+ params.push(`xkn=${ systemData.extensions.knownInstalled.join(',') }`);
241
+ params.push(`xcc=${ systemData.extensions.customCount }`);
242
+ }
243
+
244
+ // Browser Language
245
+ params.push(`bl=${ systemData.language }`);
246
+
247
+ // Browser size
248
+ params.push(`bs=${ systemData.browserSize }`);
249
+
250
+ // Screen size
251
+ if (systemData.screenSize !== '?x?') {
252
+ params.push(`ss=${ systemData.screenSize }`);
253
+ }
254
+
255
+ Object.values(systemData.featureFlags).forEach((ff) => {
256
+ params.push(`ff-` + `${ ff.param }=${ ff.value }`);
257
+ });
258
+
259
+ return params.join('&');
260
+ }
261
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ *
3
+ * The code in this file is responsible for adding New Release notifications driven off of the dynamic content metadata
4
+ *
5
+ * We handle two cases:
6
+ *
7
+ * 1. There is a new patch release available for the current Rancher version (e.g. user is in 2.12.0 and we release 2.12.1)
8
+ * 2. There is a new patch release available for the current Rancher version AND there is a newer version for a high minor releases
9
+ * > this often occurs because we release monthly releases in parallel with the new minor releases
10
+ *
11
+ * We show slightly different messages in these 2 cases.
12
+ *
13
+ */
14
+
15
+ import semver from 'semver';
16
+ import { NotificationLevel } from '@shell/types/notifications';
17
+ import { READ_NEW_RELEASE } from '@shell/store/prefs';
18
+ import { Context, ReleaseInfo, VersionInfo } from './types';
19
+ import { removeMatchingNotifications } from './util';
20
+
21
+ export async function processReleaseVersion(context: Context, releaseInfo: ReleaseInfo[] | undefined, versionInfo: VersionInfo) {
22
+ if (!releaseInfo || !versionInfo?.version || !Array.isArray(releaseInfo)) {
23
+ return;
24
+ }
25
+
26
+ const { version } = versionInfo;
27
+ const versions = releaseInfo.map((v: any) => semver.coerce(v.name));
28
+
29
+ // Sort the versions, so that the newest is first in the list
30
+ versions.sort((a: any, b: any) => semver.rcompare(a, b));
31
+
32
+ // Find first newer version
33
+ const newer = versions.find((v: any) => semver.gt(v, version));
34
+
35
+ // Find newest patch version for the current version (if available)
36
+ const newerPatch = versions.find((v: any) => {
37
+ const newVersion = semver.coerce(v);
38
+
39
+ return newVersion && newVersion.major === version.major && newVersion.minor === version.minor && semver.gt(v, version);
40
+ });
41
+
42
+ if (newer) {
43
+ context.logger.info(`Found a newer release: ${ newer.version }`);
44
+
45
+ if (newerPatch && newer !== newerPatch) {
46
+ context.logger.info(`Also found a newer patch release: ${ newerPatch.version }`);
47
+ // There is a new patch release and a newer release
48
+ await addNewMultipleReleasesNotification(context, newerPatch.version, newer.version);
49
+ } else {
50
+ // There is a new release (but no newer patch release)
51
+ await addNewReleaseNotification(context, newer.version);
52
+ }
53
+ }
54
+ }
55
+
56
+ async function addNewReleaseNotification(context: Context, version: string) {
57
+ const prefix = 'new-release-';
58
+ const releaseNotesUrl = context.settings.releaseNotesUrl.replace('$version', version);
59
+ const { dispatch, getters, logger } = context;
60
+
61
+ // TODO: Get the preference
62
+ const lastReadVersion = getters['prefs/get'](READ_NEW_RELEASE) || '';
63
+ const t = getters['i18n/t'];
64
+
65
+ // Delete notification(s) for old release notes
66
+ // This shouldn't happen normally, as we release less often than notifications should expire
67
+ if (!await removeMatchingNotifications(context, prefix, version) && lastReadVersion !== version) {
68
+ logger.debug(`Adding new release notification for ${ version } because one did not exist`);
69
+
70
+ const notification = {
71
+ id: `${ prefix }${ version }`,
72
+ level: NotificationLevel.Announcement,
73
+ title: t('dynamicContent.newRelease.title', { version }),
74
+ message: t('dynamicContent.newRelease.message', { version }),
75
+ preference: {
76
+ key: READ_NEW_RELEASE,
77
+ value: version
78
+ },
79
+ primaryAction: {
80
+ label: t('dynamicContent.newRelease.moreInfo'),
81
+ target: releaseNotesUrl
82
+ }
83
+ };
84
+
85
+ await dispatch('notifications/add', notification);
86
+ }
87
+ }
88
+
89
+ async function addNewMultipleReleasesNotification(context: Context, version1: string, version2: string) {
90
+ const prefix = 'new-release-';
91
+ const key = `${ version1 }-${ version2 }`;
92
+ const releaseNotesUrl1 = context.settings.releaseNotesUrl.replace('$version', version1);
93
+ const releaseNotesUrl2 = context.settings.releaseNotesUrl.replace('$version', version2);
94
+ const { dispatch, getters, logger } = context;
95
+
96
+ // TODO: Get the preference
97
+ const lastReadVersion = getters['prefs/get'](READ_NEW_RELEASE) || '';
98
+ const t = getters['i18n/t'];
99
+
100
+ // Delete notification(s) for old release notes
101
+ // This shouldn't happen normally, as we release less often than notifications should expire
102
+ if (!await removeMatchingNotifications(context, prefix, key) && lastReadVersion !== key) {
103
+ logger.info(`Adding new multiple release notification for ${ version1 } and ${ version2 }`);
104
+
105
+ const notification = {
106
+ id: `${ prefix }${ key }`,
107
+ level: NotificationLevel.Announcement,
108
+ title: t('dynamicContent.multipleNewReleases.title'),
109
+ message: t('dynamicContent.multipleNewReleases.message', { version1, version2 }),
110
+ preference: {
111
+ key: READ_NEW_RELEASE,
112
+ value: key
113
+ },
114
+ primaryAction: {
115
+ label: t('dynamicContent.multipleNewReleases.moreInfo', { version: version1 }),
116
+ target: releaseNotesUrl1
117
+ },
118
+ secondaryAction: {
119
+ label: t('dynamicContent.multipleNewReleases.moreInfo', { version: version2 }),
120
+ target: releaseNotesUrl2
121
+ }
122
+ };
123
+
124
+ await dispatch('notifications/add', notification);
125
+ }
126
+ }