@rancher/shell 3.0.8-rc.8 → 3.0.8

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 (260) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +41 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/brand/suse/dark/rancher-logo.svg +1 -64
  18. package/assets/styles/global/_tooltip.scss +6 -1
  19. package/assets/translations/en-us.yaml +14 -1
  20. package/components/ActionMenuShell.vue +3 -1
  21. package/components/BackLink.vue +8 -0
  22. package/components/BannerGraphic.vue +1 -5
  23. package/components/BrandImage.vue +17 -6
  24. package/components/Cron/CronExpressionEditor.vue +1 -1
  25. package/components/Cron/CronExpressionEditorModal.vue +1 -1
  26. package/components/CruResource.vue +8 -1
  27. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +1 -0
  28. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  29. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  30. package/components/Drawer/ResourceDetailDrawer/index.vue +4 -1
  31. package/components/Drawer/ResourceDetailDrawer/types.ts +2 -1
  32. package/components/LocaleSelector.vue +2 -2
  33. package/components/ModalManager.vue +11 -1
  34. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  35. package/components/Questions/__tests__/index.test.ts +159 -0
  36. package/components/RelatedResources.vue +5 -0
  37. package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
  38. package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
  39. package/components/Resource/Detail/Metadata/index.vue +3 -3
  40. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  41. package/components/Resource/Detail/composables.ts +2 -2
  42. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  43. package/components/ResourceDetail/index.vue +3 -0
  44. package/components/ResourceTable.vue +54 -21
  45. package/components/SlideInPanelManager.vue +16 -11
  46. package/components/SortableTable/THead.vue +2 -1
  47. package/components/SortableTable/index.vue +20 -2
  48. package/components/Tabbed/__tests__/index.test.ts +86 -0
  49. package/components/Tabbed/index.vue +37 -2
  50. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  51. package/components/auth/SelectPrincipal.vue +28 -6
  52. package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
  53. package/components/auth/login/ldap.vue +3 -3
  54. package/components/fleet/FleetSecretSelector.vue +1 -1
  55. package/components/form/KeyValue.vue +1 -1
  56. package/components/form/NameNsDescription.vue +1 -1
  57. package/components/form/NodeScheduling.vue +2 -2
  58. package/components/form/ResourceTabs/composable.ts +2 -2
  59. package/components/form/ResourceTabs/index.vue +0 -2
  60. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  61. package/components/formatter/InternalExternalIP.vue +4 -1
  62. package/components/formatter/LinkName.vue +5 -0
  63. package/components/formatter/__tests__/InternalExternalIP.test.ts +1 -1
  64. package/components/nav/Group.vue +25 -7
  65. package/components/nav/Header.vue +1 -1
  66. package/components/nav/NamespaceFilter.vue +1 -0
  67. package/components/nav/Type.vue +17 -6
  68. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  69. package/components/nav/__tests__/Type.test.ts +59 -0
  70. package/components/templates/standalone.vue +1 -1
  71. package/composables/cruResource.ts +27 -0
  72. package/composables/focusTrap.ts +3 -1
  73. package/composables/resourceDetail.ts +15 -0
  74. package/composables/useI18n.ts +10 -1
  75. package/composables/useLabeledFormElement.ts +3 -4
  76. package/config/__test__/uiplugins.test.ts +309 -0
  77. package/config/labels-annotations.js +1 -0
  78. package/config/product/explorer.js +3 -1
  79. package/config/product/fleet.js +1 -1
  80. package/config/router/navigation-guards/clusters.js +3 -3
  81. package/config/router/navigation-guards/products.js +1 -1
  82. package/config/router/routes.js +7 -7
  83. package/config/types.js +7 -0
  84. package/config/uiplugins.js +46 -2
  85. package/core/__tests__/extension-manager-impl.test.js +437 -0
  86. package/core/extension-manager-impl.js +21 -25
  87. package/core/plugin-helpers.ts +2 -2
  88. package/core/plugin.ts +9 -1
  89. package/core/plugins-loader.js +2 -2
  90. package/core/types-provisioning.ts +5 -1
  91. package/core/types.ts +35 -0
  92. package/detail/provisioning.cattle.io.cluster.vue +9 -6
  93. package/dialog/DeveloperLoadExtensionDialog.vue +13 -4
  94. package/dialog/MoveNamespaceDialog.vue +20 -4
  95. package/dialog/RollbackWorkloadDialog.vue +2 -5
  96. package/dialog/SearchDialog.vue +1 -0
  97. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  98. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  99. package/directives/clean-tooltip.ts +234 -0
  100. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  101. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +100 -3
  102. package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
  103. package/edit/configmap.vue +1 -0
  104. package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
  105. package/edit/fleet.cattle.io.helmop.vue +11 -6
  106. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  107. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
  108. package/edit/logging-flow/index.vue +1 -0
  109. package/edit/logging.banzaicloud.io.output/index.vue +1 -0
  110. package/edit/management.cattle.io.fleetworkspace.vue +1 -1
  111. package/edit/management.cattle.io.project.vue +1 -0
  112. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
  113. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
  114. package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
  115. package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
  116. package/edit/monitoring.coreos.com.route.vue +1 -1
  117. package/edit/namespace.vue +1 -0
  118. package/edit/networking.istio.io.destinationrule/index.vue +1 -0
  119. package/edit/networking.k8s.io.ingress/index.vue +1 -0
  120. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
  121. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
  122. package/edit/node.vue +1 -0
  123. package/edit/persistentvolume/index.vue +27 -22
  124. package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
  125. package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
  126. package/edit/persistentvolume/plugins/azureFile.vue +15 -14
  127. package/edit/persistentvolume/plugins/cephfs.vue +15 -14
  128. package/edit/persistentvolume/plugins/cinder.vue +15 -14
  129. package/edit/persistentvolume/plugins/csi.vue +18 -16
  130. package/edit/persistentvolume/plugins/fc.vue +13 -14
  131. package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
  132. package/edit/persistentvolume/plugins/flocker.vue +1 -3
  133. package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
  134. package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
  135. package/edit/persistentvolume/plugins/hostPath.vue +40 -39
  136. package/edit/persistentvolume/plugins/iscsi.vue +13 -14
  137. package/edit/persistentvolume/plugins/local.vue +1 -3
  138. package/edit/persistentvolume/plugins/longhorn.vue +23 -22
  139. package/edit/persistentvolume/plugins/nfs.vue +15 -14
  140. package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
  141. package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
  142. package/edit/persistentvolume/plugins/quobyte.vue +15 -14
  143. package/edit/persistentvolume/plugins/rbd.vue +15 -14
  144. package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
  145. package/edit/persistentvolume/plugins/storageos.vue +15 -14
  146. package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
  147. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  148. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  149. package/edit/provisioning.cattle.io.cluster/rke2.vue +9 -8
  150. package/edit/resources.cattle.io.restore.vue +1 -1
  151. package/edit/secret/index.vue +1 -1
  152. package/edit/service.vue +1 -0
  153. package/edit/serviceaccount.vue +1 -0
  154. package/edit/storage.k8s.io.storageclass/index.vue +1 -0
  155. package/edit/workload/Job.vue +2 -2
  156. package/edit/workload/index.vue +2 -1
  157. package/edit/workload/mixins/workload.js +1 -1
  158. package/initialize/App.vue +4 -4
  159. package/initialize/install-plugins.js +19 -5
  160. package/machine-config/azure.vue +1 -1
  161. package/machine-config/components/GCEImage.vue +1 -1
  162. package/mixins/__tests__/brand.spec.ts +2 -2
  163. package/mixins/brand.js +1 -7
  164. package/mixins/create-edit-view/index.js +5 -0
  165. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +128 -5
  166. package/models/chart.js +70 -74
  167. package/models/management.cattle.io.cluster.js +21 -3
  168. package/models/provisioning.cattle.io.cluster.js +31 -11
  169. package/package.json +11 -10
  170. package/pages/auth/login.vue +4 -6
  171. package/pages/auth/setup.vue +1 -1
  172. package/pages/auth/verify.vue +3 -3
  173. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
  174. package/pages/c/_cluster/apps/charts/chart.vue +33 -15
  175. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  176. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  178. package/pages/c/_cluster/explorer/index.vue +8 -6
  179. package/pages/c/_cluster/fleet/index.vue +4 -7
  180. package/pages/c/_cluster/manager/hostedprovider/index.vue +12 -6
  181. package/pages/c/_cluster/settings/brand.vue +1 -1
  182. package/pages/c/_cluster/settings/index.vue +5 -0
  183. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
  184. package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
  185. package/pages/c/_cluster/uiplugins/index.vue +126 -184
  186. package/pkg/auto-import.js +3 -3
  187. package/pkg/dynamic-importer.lib.js +1 -1
  188. package/pkg/import.js +1 -1
  189. package/plugins/__tests__/mutations.tests.ts +179 -0
  190. package/plugins/dashboard-client-init.js +3 -0
  191. package/plugins/dashboard-store/getters.js +19 -2
  192. package/plugins/dashboard-store/model-loader.js +1 -1
  193. package/plugins/dashboard-store/mutations.js +23 -2
  194. package/plugins/dashboard-store/resource-class.js +11 -5
  195. package/plugins/i18n.js +8 -0
  196. package/plugins/plugin.js +2 -2
  197. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +506 -0
  198. package/plugins/steve/steve-class.js +1 -1
  199. package/plugins/steve/steve-pagination-utils.ts +131 -47
  200. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  201. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  202. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -42
  203. package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
  204. package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
  205. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
  206. package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
  207. package/rancher-components/Pill/types.ts +0 -1
  208. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  209. package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
  210. package/rancher-components/RcIcon/RcIcon.vue +46 -0
  211. package/rancher-components/RcIcon/index.ts +1 -0
  212. package/rancher-components/RcIcon/types.ts +160 -0
  213. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  214. package/rancher-components/utils/status.test.ts +67 -0
  215. package/rancher-components/utils/status.ts +77 -0
  216. package/scripts/publish-shell.sh +25 -0
  217. package/scripts/typegen.sh +1 -0
  218. package/store/__tests__/catalog.test.ts +1 -1
  219. package/store/__tests__/type-map.test.ts +164 -2
  220. package/store/action-menu.js +8 -0
  221. package/store/auth.js +25 -13
  222. package/store/catalog.js +6 -0
  223. package/store/i18n.js +3 -3
  224. package/store/index.js +8 -6
  225. package/store/notifications.ts +2 -0
  226. package/store/prefs.js +6 -7
  227. package/store/type-map.js +17 -7
  228. package/store/wm.ts +4 -4
  229. package/types/internal-api/shell/modal.d.ts +6 -6
  230. package/types/notifications/index.ts +126 -15
  231. package/types/rancher/index.d.ts +9 -0
  232. package/types/shell/index.d.ts +54 -3
  233. package/types/store/__tests__/pagination.types.spec.ts +137 -0
  234. package/types/store/pagination.types.ts +157 -9
  235. package/types/vue-shim.d.ts +5 -4
  236. package/utils/__tests__/provider.test.ts +98 -0
  237. package/utils/__tests__/router.test.js +238 -0
  238. package/utils/__tests__/selector-typed.test.ts +263 -0
  239. package/utils/cluster.js +4 -1
  240. package/utils/color.js +1 -1
  241. package/utils/dynamic-content/__tests__/info.test.ts +6 -0
  242. package/utils/dynamic-content/info.ts +43 -0
  243. package/utils/favicon.js +4 -4
  244. package/utils/fleet.ts +8 -1
  245. package/utils/pagination-utils.ts +2 -2
  246. package/utils/pagination-wrapper.ts +1 -1
  247. package/utils/provider.ts +14 -0
  248. package/utils/router.js +50 -0
  249. package/utils/selector-typed.ts +6 -2
  250. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  251. package/vue.config.js +3 -3
  252. package/composables/useExtensionManager.ts +0 -17
  253. package/core/plugins.js +0 -38
  254. package/directives/clean-tooltip.js +0 -32
  255. package/plugins/internal-api/index.ts +0 -37
  256. package/plugins/internal-api/shared/base-api.ts +0 -13
  257. package/plugins/internal-api/shell/shell.api.ts +0 -108
  258. package/plugins/nuxt-client-init.js +0 -3
  259. package/types/internal-api/shell/growl.d.ts +0 -25
  260. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -0,0 +1,309 @@
1
+ import {
2
+ UI_PLUGIN_ANNOTATION,
3
+ UI_PLUGIN_CHART_ANNOTATIONS,
4
+ EXTENSIONS_INCOMPATIBILITY_TYPES,
5
+ isUIPlugin,
6
+ uiPluginHasAnnotation,
7
+ parseRancherVersion,
8
+ // shouldNotLoadPlugin, this is required per test so that we can mock semver.coerce
9
+ isSupportedChartVersion,
10
+ isChartVersionHigher
11
+ } from '@shell/config/uiplugins';
12
+
13
+ import * as VersionModule from '@shell/config/version';
14
+
15
+ let semver;
16
+ let originalCoerce: any;
17
+ const MOCK_API_VERSION = '33.33.33';
18
+
19
+ describe('uiPlugins Config methods', () => {
20
+ describe('fx: isUIPlugin', () => {
21
+ const pluginChart = { versions: [{ annotations: { [UI_PLUGIN_ANNOTATION.NAME]: UI_PLUGIN_ANNOTATION.VALUE } }, { annotations: {} }] };
22
+
23
+ it('should return true if any version has the UI plugin annotation', () => {
24
+ expect(isUIPlugin(pluginChart)).toBe(true);
25
+ });
26
+ it('should return false if chart is null/undefined', () => {
27
+ expect(isUIPlugin(null)).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe('fx: uiPluginHasAnnotation', () => {
32
+ const version1 = { annotations: { [UI_PLUGIN_ANNOTATION.NAME]: UI_PLUGIN_ANNOTATION.VALUE } };
33
+ const chart = { versions: [version1] };
34
+
35
+ it('should return true if it finds version with annotation and its corresponding value', () => {
36
+ expect(uiPluginHasAnnotation(chart, UI_PLUGIN_ANNOTATION.NAME, UI_PLUGIN_ANNOTATION.VALUE)).toBe(true);
37
+ });
38
+ it('should return false if chart has no version with a given pair of annotation/value', () => {
39
+ expect(uiPluginHasAnnotation(chart, 'bananas', UI_PLUGIN_ANNOTATION.VALUE)).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe('fx: parseRancherVersion', () => {
44
+ it('should handle standard version strings', () => {
45
+ expect(parseRancherVersion('v2.8.1')).toBe('2.8.1');
46
+ });
47
+ it('should apply .999 patch for RC versions', () => {
48
+ expect(parseRancherVersion('v2.8.1-rc1')).toBe('2.8.999');
49
+ });
50
+ it('should apply .999 patch for "head" versions', () => {
51
+ expect(parseRancherVersion('2.8.0-head')).toBe('2.8.999');
52
+ });
53
+ });
54
+
55
+ const PLUGIN_NAME = 'test-plugin';
56
+
57
+ const basePluginResource = {
58
+ name: PLUGIN_NAME,
59
+ version: '1.0.0',
60
+ endpoint: '/some/path',
61
+ metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0' }
62
+ };
63
+ const compatibleEnv = {
64
+ rancherVersion: '2.7.5',
65
+ kubeVersion: 'v1.25.10',
66
+ };
67
+
68
+ describe('fx: shouldNotLoadPlugin', () => {
69
+ beforeEach(() => {
70
+ jest.resetModules();
71
+ process.env.UI_EXTENSIONS_API_VERSION = MOCK_API_VERSION;
72
+
73
+ semver = require('semver');
74
+ originalCoerce = semver.coerce;
75
+
76
+ jest.spyOn(semver, 'coerce').mockImplementation((v) => {
77
+ if (v === MOCK_API_VERSION) {
78
+ return { version: '3.0.0' };
79
+ }
80
+
81
+ // Use the original for all other inputs (Rancher, Kube)
82
+ return originalCoerce(v);
83
+ });
84
+ });
85
+
86
+ afterEach(() => {
87
+ jest.restoreAllMocks();
88
+ });
89
+
90
+ it('should return false when compatible', () => {
91
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
92
+
93
+ expect(shouldNotLoadPlugin(basePluginResource, compatibleEnv, [])).toBe(false);
94
+ });
95
+
96
+ it('should return "plugins.error.api" if Extensions API version is incompatible', () => {
97
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
98
+
99
+ const incompatibleApiPlugin = {
100
+ ...basePluginResource,
101
+ metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=4.0.0' }
102
+ };
103
+
104
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.api');
105
+ });
106
+
107
+ it('should return "plugins.error.primeOnly" if extension is Prime-only and Rancher is not Prime', () => {
108
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
109
+
110
+ jest.spyOn(VersionModule, 'isRancherPrime').mockReturnValue(false);
111
+
112
+ const incompatibleApiPlugin = {
113
+ ...basePluginResource,
114
+ metadata: {
115
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
116
+ [UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY]: 'true'
117
+ }
118
+ };
119
+
120
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.primeOnly');
121
+ });
122
+
123
+ it('should return "plugins.error.host" if HOST application value is incompatible', () => {
124
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
125
+
126
+ const incompatibleApiPlugin = {
127
+ ...basePluginResource,
128
+ metadata: {
129
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
130
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST]: 'rancher-dummy-host'
131
+ }
132
+ };
133
+
134
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.host');
135
+ });
136
+
137
+ it('should return "plugins.error.kubeVersion" if incompatible', () => {
138
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
139
+
140
+ const incompatibleApiPlugin = {
141
+ ...basePluginResource,
142
+ metadata: {
143
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
144
+ [UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>= 2.0.0'
145
+ }
146
+ };
147
+
148
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.kubeVersion');
149
+ });
150
+
151
+ it('should return "plugins.error.version" if incompatible', () => {
152
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
153
+
154
+ const incompatibleApiPlugin = {
155
+ ...basePluginResource,
156
+ metadata: {
157
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0',
158
+ [UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>= 3.0.0'
159
+ }
160
+ };
161
+
162
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [])).toBe('plugins.error.version');
163
+ });
164
+
165
+ it('should return "plugins.error.developerPkg" if a builtin extension with the same name was loaded first', () => {
166
+ const { shouldNotLoadPlugin } = require('./../uiplugins');
167
+
168
+ const incompatibleApiPlugin = {
169
+ ...basePluginResource,
170
+ metadata: { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0' }
171
+ };
172
+
173
+ expect(shouldNotLoadPlugin(incompatibleApiPlugin, compatibleEnv, [{ name: PLUGIN_NAME, builtin: true }])).toBe('plugins.error.developerPkg');
174
+ });
175
+ });
176
+
177
+ describe('fx: isSupportedChartVersion', () => {
178
+ beforeEach(() => {
179
+ jest.resetModules();
180
+ process.env.UI_EXTENSIONS_API_VERSION = MOCK_API_VERSION;
181
+
182
+ semver = require('semver');
183
+ originalCoerce = semver.coerce;
184
+
185
+ jest.spyOn(semver, 'coerce').mockImplementation((v) => {
186
+ if (v === MOCK_API_VERSION) {
187
+ return { version: '3.0.0' };
188
+ }
189
+
190
+ // Use the original for all other inputs (Rancher, Kube)
191
+ return originalCoerce(v);
192
+ });
193
+ });
194
+
195
+ afterEach(() => {
196
+ jest.restoreAllMocks();
197
+ });
198
+
199
+ const versionData = {
200
+ rancherVersion: 'v2.7.5',
201
+ kubeVersion: 'v1.25.10',
202
+ version: '1.0.0'
203
+ };
204
+
205
+ it('should return "isVersionCompatible" true when compatible', () => {
206
+ const { isSupportedChartVersion } = require('./../uiplugins');
207
+
208
+ const annotations = {
209
+ [UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>=1.0.0',
210
+ [UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>=2.0.0',
211
+ [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=2.0.0'
212
+ };
213
+ const data = { ...versionData, version: { annotations } };
214
+ const result = isSupportedChartVersion(data, true);
215
+
216
+ expect(result.isVersionCompatible).toBe(true);
217
+ expect(result.versionIncompatibilityData).toStrictEqual({});
218
+ });
219
+
220
+ it('should return incompatibility object for kube version mismatch', () => {
221
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION]: '>=2.0.0' };
222
+ const data = { ...versionData, version: { annotations } };
223
+ const result = isSupportedChartVersion(data, true);
224
+
225
+ expect(result.isVersionCompatible).toBe(false);
226
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.KUBE);
227
+ expect(result.versionIncompatibilityData.required).toBe('>=2.0.0');
228
+ });
229
+
230
+ it('should return incompatibility object for Rancher version mismatch', () => {
231
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.RANCHER_VERSION]: '>=3.0.0' };
232
+ const data = { ...versionData, version: { annotations } };
233
+ const result = isSupportedChartVersion(data, true);
234
+
235
+ expect(result.isVersionCompatible).toBe(false);
236
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.UI);
237
+ expect(result.versionIncompatibilityData.required).toBe('>=3.0.0');
238
+ });
239
+
240
+ it('should return incompatibility object for UI version mismatch', () => {
241
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION]: '>=3.0.0' };
242
+ const data = { ...versionData, version: { annotations } };
243
+ const result = isSupportedChartVersion(data, true);
244
+
245
+ expect(result.isVersionCompatible).toBe(false);
246
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.UI);
247
+ expect(result.versionIncompatibilityData.required).toBe('>=3.0.0');
248
+ });
249
+
250
+ it('should return incompatibility object for extensions api missing', () => {
251
+ const annotations = {};
252
+ const data = { ...versionData, version: { annotations } };
253
+ const result = isSupportedChartVersion(data, true);
254
+
255
+ expect(result.isVersionCompatible).toBe(false);
256
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING);
257
+ expect(result.versionIncompatibilityData.required).toBeUndefined();
258
+ });
259
+
260
+ it('should return incompatibility object for extensions API version mismatch', () => {
261
+ const { isSupportedChartVersion } = require('./../uiplugins');
262
+
263
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=4.0.0' };
264
+ const data = { ...versionData, version: { annotations } };
265
+ const result = isSupportedChartVersion(data, true);
266
+
267
+ expect(result.isVersionCompatible).toBe(false);
268
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API);
269
+ expect(result.versionIncompatibilityData.required).toBe('>=4.0.0');
270
+ });
271
+
272
+ it('should return incompatibility object for HOST_APP mismatch', () => {
273
+ const { isSupportedChartVersion } = require('./../uiplugins');
274
+
275
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0 < 5.0.0', [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST]: 'rancher-dummy-host' };
276
+ const data = { ...versionData, version: { annotations } };
277
+ const result = isSupportedChartVersion(data, true);
278
+
279
+ expect(result.isVersionCompatible).toBe(false);
280
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.HOST);
281
+ expect(result.versionIncompatibilityData.required).toBe('rancher-dummy-host');
282
+ });
283
+
284
+ it('should return incompatibility object for Prime only extension running in non-prime env', () => {
285
+ const { isSupportedChartVersion } = require('./../uiplugins');
286
+
287
+ jest.spyOn(VersionModule, 'isRancherPrime').mockReturnValue(false);
288
+
289
+ const annotations = { [UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_VERSION]: '>=1.0.0 < 5.0.0', [UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY]: 'true' };
290
+ const data = { ...versionData, version: { annotations } };
291
+ const result = isSupportedChartVersion(data, true);
292
+
293
+ expect(result.isVersionCompatible).toBe(false);
294
+ expect(result.versionIncompatibilityData.type).toBe(EXTENSIONS_INCOMPATIBILITY_TYPES.PRIME_ONLY);
295
+ expect(result.versionIncompatibilityData.required).toBe(true);
296
+ });
297
+ });
298
+
299
+ describe('fx: isChartVersionHigher', () => {
300
+ // Tests now rely on the actual semver.gt implementation
301
+ it('should return true when version A is higher', () => {
302
+ expect(isChartVersionHigher('2.1.0', '2.0.0')).toBe(true);
303
+ });
304
+ it('should return false when version A is lower or equal', () => {
305
+ expect(isChartVersionHigher('2.0.0', '2.1.0')).toBe(false);
306
+ expect(isChartVersionHigher('2.0.0', '2.0.0')).toBe(false);
307
+ });
308
+ });
309
+ });
@@ -85,6 +85,7 @@ export const CATALOG = {
85
85
  _PARTNER: 'partner',
86
86
  _OTHER: 'other',
87
87
 
88
+ PRIME_ONLY: 'catalog.cattle.io/prime-only',
88
89
  EXPERIMENTAL: 'catalog.cattle.io/experimental',
89
90
  NAMESPACE: 'catalog.cattle.io/namespace',
90
91
  RELEASE_NAME: 'catalog.cattle.io/release-name',
@@ -180,7 +180,8 @@ export function init(store) {
180
180
  mapGroup(/^(.*\.)?(scc)\.cattle\.io$/, 'SCC');
181
181
 
182
182
  const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
183
- const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true }) ;
183
+ const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
184
+ const dePaginateNormanUsers = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
184
185
 
185
186
  configureType(NODE, { isCreatable: false, isEditable: true });
186
187
  configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
@@ -189,6 +190,7 @@ export function init(store) {
189
190
  configureType(MANAGEMENT.PROJECT, { displayName: store.getters['i18n/t']('namespace.project.label') });
190
191
  configureType(NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
191
192
  configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
193
+ configureType(NORMAN.USER, { depaginate: dePaginateNormanUsers });
192
194
  configureType(SNAPSHOT, { depaginate: true });
193
195
 
194
196
  configureType(SECRET, { showListMasthead: false });
@@ -34,7 +34,7 @@ export function init(store) {
34
34
  showWorkspaceSwitcher: true,
35
35
  to: {
36
36
  name: 'c-cluster-fleet',
37
- params: { resource: FLEET.DASHBOARD, cluster: BLANK_CLUSTER }
37
+ params: { cluster: BLANK_CLUSTER }
38
38
  },
39
39
  });
40
40
 
@@ -30,12 +30,12 @@ export async function loadClusters(to, from, next, { store }) {
30
30
  const oldPkg = getPackageFromRoute(from);
31
31
  const oldProduct = getProductFromRoute(from);
32
32
 
33
- // TODO: Replace all references to store.$plugin.
33
+ // TODO: Replace all references to store.$extension.
34
34
  // Unfortunately the initialization code has circular dependencies between creating
35
35
  // the router and creating the store that will need to be untangled before this can be tackled.
36
36
 
37
37
  // Leave an old pkg where we weren't before?
38
- const oldPkgPlugin = oldPkg ? Object.values(store.$plugin.getPlugins()).find((p) => p.name === oldPkg) : null;
38
+ const oldPkgPlugin = oldPkg ? Object.values(store.$extension.getPlugins()).find((p) => p.name === oldPkg) : null;
39
39
 
40
40
  if (oldPkg && oldPkg !== pkg ) {
41
41
  // Execute anything optional the plugin wants to. For example resetting it's store to remove data
@@ -53,7 +53,7 @@ export async function loadClusters(to, from, next, { store }) {
53
53
  ];
54
54
 
55
55
  // Entering a new package where we weren't before?
56
- const newPkgPlugin = pkg ? Object.values(store.$plugin.getPlugins()).find((p) => p.name === pkg) : null;
56
+ const newPkgPlugin = pkg ? Object.values(store.$extension.getPlugins()).find((p) => p.name === pkg) : null;
57
57
 
58
58
  // Note - We can't block on oldPkg !== newPkg because on a fresh load the `from` route equals the `to` route
59
59
  if (pkg && (oldPkg !== pkg || from.fullPath === to.fullPath)) {
@@ -9,7 +9,7 @@ export async function loadProducts(to, from, next, { store }) {
9
9
  // GC should be notified of route change before any find/get request is made that might be used for that page
10
10
  store.dispatch('gcRouteChanged', to);
11
11
 
12
- await applyProducts(store, store.$plugin);
12
+ await applyProducts(store, store.$extension);
13
13
  setProduct(store, to);
14
14
  next();
15
15
  }
@@ -1,5 +1,4 @@
1
1
  import { NAME as APPS } from '@shell/config/product/apps';
2
- import { NAME as EXPLORER } from '@shell/config/product/explorer';
3
2
  import { NAME as MANAGER } from '@shell/config/product/manager';
4
3
  import {
5
4
  CAPI, MANAGEMENT, BACKUP_RESTORE, COMPLIANCE, VIRTUAL_TYPES
@@ -81,9 +80,13 @@ export default [
81
80
  {
82
81
  path: '/c/:cluster/uiplugins',
83
82
  name: 'c-cluster-uiplugins',
84
- component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/index.vue')),
83
+ component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/index.vue'))
84
+ },
85
+ {
86
+ path: '/c/:cluster/uiplugins/catalogs',
87
+ component: () => interopDefault(import('@shell/pages/c/_cluster/uiplugins/catalogs.vue')),
88
+ name: 'c-cluster-uiplugins-catalogs'
85
89
  },
86
-
87
90
  {
88
91
  path: '/diagnostic',
89
92
  component: () => interopDefault(import('@shell/pages/diagnostic.vue')),
@@ -180,10 +183,7 @@ export default [
180
183
  redirect(to) {
181
184
  return {
182
185
  name: 'c-cluster-explorer',
183
- params: {
184
- ...(to?.params || {}),
185
- product: EXPLORER,
186
- }
186
+ params: { ...(to?.params || {}) }
187
187
  };
188
188
  }
189
189
  },
package/config/types.js CHANGED
@@ -222,6 +222,13 @@ export const MANAGEMENT = {
222
222
  OIDC_CLIENT: 'management.cattle.io.oidcclient'
223
223
  };
224
224
 
225
+ export const BRAND = {
226
+ SUSE: 'suse',
227
+ CSP: 'csp',
228
+ FEDERAL: 'federal',
229
+ RGS: 'rgs',
230
+ };
231
+
225
232
  export const EXT = { USER_ACTIVITY: 'ext.cattle.io.useractivity' };
226
233
 
227
234
  export const CAPI = {
@@ -1,4 +1,5 @@
1
1
  import semver from 'semver';
2
+ import { isRancherPrime } from '@shell/config/version';
2
3
 
3
4
  // Version of the plugin API supported
4
5
  // here we inject the current shell version that we read in vue.config
@@ -43,6 +44,7 @@ export const UI_PLUGIN_CHART_ANNOTATIONS = {
43
44
  EXTENSIONS_HOST: 'catalog.cattle.io/ui-extensions-host',
44
45
  DISPLAY_NAME: 'catalog.cattle.io/display-name',
45
46
  HIDDEN_BUILTIN: 'catalog.cattle.io/ui-hidden-builtin',
47
+ PRIME_ONLY: 'catalog.cattle.io/prime-only'
46
48
  };
47
49
 
48
50
  // Extension catalog labels
@@ -58,6 +60,7 @@ export const EXTENSIONS_INCOMPATIBILITY_TYPES = {
58
60
  EXTENSIONS_API: 'extensionsApiVersion',
59
61
  KUBE: 'kubeVersion',
60
62
  HOST: 'host',
63
+ PRIME_ONLY: 'primeOnly',
61
64
  };
62
65
 
63
66
  export const EXTENSIONS_INCOMPATIBILITY_DATA = {
@@ -87,6 +90,11 @@ export const EXTENSIONS_INCOMPATIBILITY_DATA = {
87
90
  tooltipKey: 'plugins.info.requiresHost',
88
91
  mainHost: UI_PLUGIN_HOST_APP,
89
92
  },
93
+ PRIME_ONLY: {
94
+ type: EXTENSIONS_INCOMPATIBILITY_TYPES.PRIME_ONLY,
95
+ cardMessageKey: 'plugins.incompatiblePrimeOnly',
96
+ tooltipKey: 'plugins.info.requiresRancherPrime',
97
+ },
90
98
  };
91
99
 
92
100
  export function isUIPlugin(chart) {
@@ -124,10 +132,29 @@ export function parseRancherVersion(v) {
124
132
  * Check if a version is incompatible with the current environment
125
133
  */
126
134
  function checkIncompatibility(currentVersion, requiredVersion, incompatibilityData, returnObj, versionObj) {
127
- if ((incompatibilityData.type === EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING && !requiredVersion) || (requiredVersion && !semver.satisfies(currentVersion, requiredVersion))) {
135
+ let passed = true;
136
+
137
+ switch (incompatibilityData.type) {
138
+ case EXTENSIONS_INCOMPATIBILITY_TYPES.PRIME_ONLY:
139
+ if (requiredVersion && !currentVersion) {
140
+ passed = false;
141
+ }
142
+ break;
143
+ case EXTENSIONS_INCOMPATIBILITY_TYPES.EXTENSIONS_API_MISSING:
144
+ !requiredVersion ? passed = false : passed = true;
145
+ break;
146
+ default:
147
+ if (requiredVersion && !semver.satisfies(currentVersion, requiredVersion)) {
148
+ passed = false;
149
+ }
150
+ break;
151
+ }
152
+
153
+ if (!passed) {
128
154
  if (!returnObj) {
129
155
  return false;
130
156
  }
157
+
131
158
  versionObj.isVersionCompatible = false;
132
159
  versionObj.versionIncompatibilityData = { ...incompatibilityData, required: requiredVersion };
133
160
 
@@ -137,7 +164,7 @@ function checkIncompatibility(currentVersion, requiredVersion, incompatibilityDa
137
164
  return true;
138
165
  }
139
166
 
140
- // i18n-uses plugins.error.generic, plugins.error.api, plugins.error.host, plugins.error.kubeVersion, plugins.error.version, plugins.error.developerPkg, plugins.error.apiAnnotationMissing
167
+ // i18n-uses plugins.error.generic, plugins.error.api, plugins.error.host, plugins.error.kubeVersion, plugins.error.version, plugins.error.developerPkg, plugins.error.apiAnnotationMissing, plugins.error.primeOnly
141
168
 
142
169
  /**
143
170
  * Whether an extension should be loaded based on the metadata returned by the backend in the UIPlugins resource instance
@@ -148,6 +175,8 @@ function checkIncompatibility(currentVersion, requiredVersion, incompatibilityDa
148
175
  * @returns String | Boolean
149
176
  */
150
177
  export function shouldNotLoadPlugin(UIPluginResource, { rancherVersion, kubeVersion }, loadedPlugins) {
178
+ const isCurrRancherPrime = isRancherPrime();
179
+
151
180
  const {
152
181
  name, version, endpoint, compressedEndpoint
153
182
  } = UIPluginResource;
@@ -171,6 +200,13 @@ export function shouldNotLoadPlugin(UIPluginResource, { rancherVersion, kubeVers
171
200
  return 'plugins.error.api';
172
201
  }
173
202
 
203
+ // Prime only
204
+ const primeOnlyAnnotation = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY] === 'true';
205
+
206
+ if (!isCurrRancherPrime && primeOnlyAnnotation) {
207
+ return 'plugins.error.primeOnly';
208
+ }
209
+
174
210
  // Host application
175
211
  const requiredHost = UIPluginResource.metadata?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST];
176
212
 
@@ -220,10 +256,13 @@ export function shouldNotLoadPlugin(UIPluginResource, { rancherVersion, kubeVers
220
256
  * @returns Boolean | Object
221
257
  */
222
258
  export function isSupportedChartVersion(versionData, returnObj = false) {
259
+ const isCurrRancherPrime = isRancherPrime();
260
+
223
261
  const { version, rancherVersion, kubeVersion } = versionData;
224
262
  const versionObj = {
225
263
  ...version, isVersionCompatible: true, versionIncompatibilityData: {}
226
264
  };
265
+
227
266
  const parsedRancherVersion = rancherVersion ? parseRancherVersion(rancherVersion) : '';
228
267
  const parsedUiExtensionsApiVersion = semver.coerce(UI_EXTENSIONS_API_VERSION)?.version;
229
268
 
@@ -258,6 +297,11 @@ export function isSupportedChartVersion(versionData, returnObj = false) {
258
297
  requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.EXTENSIONS_HOST],
259
298
  incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.HOST,
260
299
  },
300
+ {
301
+ currentVersion: isCurrRancherPrime,
302
+ requiredVersion: version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.PRIME_ONLY] === 'true',
303
+ incompatibilityData: EXTENSIONS_INCOMPATIBILITY_DATA.PRIME_ONLY,
304
+ }
261
305
  ];
262
306
 
263
307
  for (const { currentVersion, requiredVersion, incompatibilityData } of checks) {