@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
@@ -1,5 +1,8 @@
1
1
 
2
- import { SCHEMA, COUNT, POD } from '@shell/config/types';
2
+ import {
3
+ SCHEMA, COUNT, POD, MANAGEMENT, BRAND
4
+ } from '@shell/config/types';
5
+ import { SETTING } from '@shell/config/settings';
3
6
 
4
7
  import { matches } from '@shell/utils/selector';
5
8
  import { typeMunge, typeRef, SIMPLE_TYPES } from '@shell/utils/create-yaml';
@@ -204,6 +207,20 @@ export default {
204
207
  }
205
208
  },
206
209
 
210
+ brand: (state, getters) => {
211
+ const brand = getters['byId'](MANAGEMENT.SETTING, SETTING.BRAND);
212
+
213
+ if (!brand?.value) {
214
+ return undefined;
215
+ }
216
+
217
+ if ([BRAND.CSP, BRAND.FEDERAL, BRAND.RGS].includes(brand.value)) {
218
+ return BRAND.SUSE;
219
+ }
220
+
221
+ return brand.value;
222
+ },
223
+
207
224
  /**
208
225
  * Checks a schema for the given path
209
226
  *
@@ -526,7 +543,7 @@ export default {
526
543
  const store = state.config.namespace;
527
544
  const resource = id || context ? { id, context } : null;
528
545
 
529
- return paginationUtils.isEnabled({ rootGetters, $plugin: rootState.$plugin }, { store, resource });
546
+ return paginationUtils.isEnabled({ rootGetters, $extension: rootState.$extension }, { store, resource });
530
547
  },
531
548
 
532
549
  /**
@@ -13,7 +13,7 @@ function find(cache, type, rootState) {
13
13
  }
14
14
 
15
15
  try {
16
- const pluginModel = rootState.$plugin.getDynamic('models', type);
16
+ const pluginModel = rootState.$extension.getDynamic('models', type);
17
17
  let base;
18
18
 
19
19
  if (!pluginModel) {
@@ -515,8 +515,29 @@ export default {
515
515
  cache.generation++;
516
516
 
517
517
  // Update list
518
- clear(cache.list);
519
- addObjects(cache.list, proxies);
518
+ // We want to update the old page with the new page
519
+ // We need to keep the cache.list object reference the same
520
+ // We need to keep objects that represent the same resource the same (don't remove the old object and add a new object for the same resource)
521
+ // - this helps anywhere that works with object references (sortable table selection is maintained by object reference)
522
+
523
+ // Create a map of the current references in cache.list
524
+ const currentPageMap = new Map(cache.list.map((i) => [i[keyField], i]));
525
+
526
+ // Create an array containing the new page, but using the same object for resources that exist in old and new page
527
+ const newPage = proxies.map((p) => {
528
+ const existing = currentPageMap.get(p[keyField]);
529
+
530
+ if (existing) {
531
+ replaceResource(existing, p, ctx.getters);
532
+
533
+ return existing;
534
+ }
535
+
536
+ return p;
537
+ });
538
+
539
+ // Replace the old cache.list entries with the new page
540
+ cache.list.splice(0, cache.list.length, ...newPage);
520
541
 
521
542
  // Update Map (remove stale)
522
543
  cache.map.forEach((value, key) => {
@@ -610,7 +610,11 @@ export default class Resource {
610
610
  }
611
611
 
612
612
  get '$plugin'() {
613
- return this.$ctx.rootState?.$plugin;
613
+ return this.$ctx.rootState?.$extension;
614
+ }
615
+
616
+ get '$extension'() {
617
+ return this.$ctx.rootState?.$extension;
614
618
  }
615
619
 
616
620
  get customValidationRules() {
@@ -905,7 +909,7 @@ export default class Resource {
905
909
  return out;
906
910
  }
907
911
 
908
- showConfiguration(returnFocusSelector) {
912
+ showConfiguration(returnFocusSelector, defaultTab) {
909
913
  const onClose = () => this.$ctx.commit('slideInPanel/close', undefined, { root: true });
910
914
 
911
915
  this.$ctx.commit('slideInPanel/open', {
@@ -920,7 +924,8 @@ export default class Resource {
920
924
  'z-index': 101, // We want this to be above the main side menu
921
925
  closeOnRouteChange: ['name', 'params', 'query'], // We want to ignore hash changes, tables in extensions can trigger the drawer to close while opening
922
926
  triggerFocusTrap: true,
923
- returnFocusSelector
927
+ returnFocusSelector,
928
+ defaultTab
924
929
  }
925
930
  }, { root: true });
926
931
  }
@@ -1358,6 +1363,7 @@ export default class Resource {
1358
1363
 
1359
1364
  get _detailLocation() {
1360
1365
  const schema = this.$getters['schemaFor'](this.type);
1366
+ const isNamespaced = schema?.attributes?.namespaced;
1361
1367
 
1362
1368
  const id = this.id?.replace(/.*\//, '');
1363
1369
 
@@ -1367,7 +1373,7 @@ export default class Resource {
1367
1373
  product: this.$rootGetters['productId'],
1368
1374
  cluster: this.$rootGetters['clusterId'],
1369
1375
  resource: this.type,
1370
- namespace: this.metadata?.namespace,
1376
+ namespace: isNamespaced && this.metadata?.namespace ? this.metadata.namespace : undefined,
1371
1377
  id,
1372
1378
  }
1373
1379
  };
@@ -1776,7 +1782,7 @@ export default class Resource {
1776
1782
  CustomValidators[validatorName](pathValue, this.$rootGetters, errors, validatorArgs, displayKey, data);
1777
1783
  } else if (!isEmpty(validatorName) && !validatorExists) {
1778
1784
  // Check if validator is imported from plugin
1779
- const pluginValidator = this.$rootState.$plugin?.getValidator(validatorName);
1785
+ const pluginValidator = this.$rootState.$extension?.getValidator(validatorName);
1780
1786
 
1781
1787
  if (pluginValidator) {
1782
1788
  pluginValidator(pathValue, this.$rootGetters, errors, validatorArgs, displayKey, data);
package/plugins/i18n.js CHANGED
@@ -3,6 +3,14 @@ import { escapeHtml } from '../utils/string';
3
3
  import { watchEffect, ref, h } from 'vue';
4
4
  import { useStore } from 'vuex';
5
5
 
6
+ /**
7
+ * @param {import('vuex').Store<any>} store
8
+ * @param {string} key
9
+ * @param {Record<string, any>} [args]
10
+ * @param {boolean} [raw]
11
+ * @param {boolean} [escapehtml]
12
+ * @returns {string}
13
+ */
6
14
  export function stringFor(store, key, args, raw = false, escapehtml = true) {
7
15
  const translation = store.getters['i18n/t'](key, args);
8
16
 
package/plugins/plugin.js CHANGED
@@ -44,7 +44,7 @@ export default async function(context) {
44
44
  const res = await allHashSettled(fetches);
45
45
 
46
46
  // Initialize the built-in extensions now - this is now done here so that built-in extensions get the same, correct environment data (version etc)
47
- context.$plugin.loadBuiltinExtensions();
47
+ context.$extension.loadBuiltinExtensions();
48
48
 
49
49
  if (res.plugins?.status === 'rejected') {
50
50
  throw new Error(res.reason);
@@ -60,7 +60,7 @@ export default async function(context) {
60
60
  const shouldNotLoad = shouldNotLoadPlugin(plugin, { rancherVersion, kubeVersion }, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
61
61
 
62
62
  if (!shouldNotLoad) {
63
- hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
63
+ hash[plugin.name] = context.$extension.loadPluginAsync(plugin);
64
64
  } else {
65
65
  context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
66
66
  }
@@ -0,0 +1,506 @@
1
+
2
+ import { PaginationFilterEquality, PaginationFilterField, PaginationParamFilter, PaginationParamProjectOrNamespace } from '@shell/types/store/pagination.types';
3
+ import { NAMESPACE_FILTER_ALL_SYSTEM, NAMESPACE_FILTER_ALL_USER, NAMESPACE_FILTER_P_FULL_PREFIX } from '@shell/utils/namespace-filter';
4
+ import stevePaginationUtils from '../steve-pagination-utils';
5
+ import Schema from '@shell/models/schema';
6
+
7
+ /**
8
+ * The `NamespaceProjectFilters` class is a protected class within `steve-pagination-utils.ts`.
9
+ * To test its protected methods, we extend it with a test class that exposes them.
10
+ */
11
+ class TestNamespaceProjectFilters {
12
+ public handlePrefAndSettingFilter(args: any) {
13
+ return stevePaginationUtils.handlePrefAndSettingFilter(args);
14
+ }
15
+
16
+ public handleSystemOrUserFilter(args: any) {
17
+ return stevePaginationUtils.handleSystemOrUserFilter(args);
18
+ }
19
+
20
+ public handleSelectionFilter(neu: string[], isLocalCluster: boolean) {
21
+ return stevePaginationUtils.handleSelectionFilter(neu, isLocalCluster);
22
+ }
23
+
24
+ public combineNsProjectFilterResults(a: any, b: any) {
25
+ return stevePaginationUtils.combineNsProjectFilterResults(a, b);
26
+ }
27
+
28
+ public createFiltersFromNamespaceProjectFilterResult(filterResult: any) {
29
+ return stevePaginationUtils.createFiltersFromNamespaceProjectFilterResult(filterResult);
30
+ }
31
+ }
32
+
33
+ describe('class: NamespaceProjectFilters', () => {
34
+ const testNamespaceProjectFilters = new TestNamespaceProjectFilters();
35
+
36
+ const normalNs = {
37
+ id: 'normal', name: 'normal', isObscure: false, isSystem: false
38
+ };
39
+ const obscureNs = {
40
+ id: 'obscure', name: 'obscure', isObscure: true, isSystem: false
41
+ };
42
+ const systemNs = {
43
+ id: 'system', name: 'system', isObscure: false, isSystem: true
44
+ };
45
+ const obscureAndSystemNs = {
46
+ id: 'obscure-system', name: 'obscure-system', isObscure: true, isSystem: true
47
+ };
48
+
49
+ const allNamespaces = [normalNs, obscureNs, systemNs, obscureAndSystemNs];
50
+
51
+ describe('method: handlePrefAndSettingFilter', () => {
52
+ it('should return no filters if all namespaces are shown', () => {
53
+ const result = testNamespaceProjectFilters.handlePrefAndSettingFilter({
54
+ allNamespaces,
55
+ showReservedRancherNamespaces: true,
56
+ productHidesSystemNamespaces: false,
57
+ });
58
+
59
+ expect(result).toStrictEqual({});
60
+ });
61
+
62
+ it('should filter obscure namespaces if showReservedRancherNamespaces is false', () => {
63
+ const result = testNamespaceProjectFilters.handlePrefAndSettingFilter({
64
+ allNamespaces,
65
+ showReservedRancherNamespaces: false,
66
+ productHidesSystemNamespaces: false,
67
+ });
68
+
69
+ expect(result).toStrictEqual({
70
+ obscure: false,
71
+ 'obscure-system': false
72
+ });
73
+ });
74
+
75
+ it('should filter system namespaces if productHidesSystemNamespaces is true', () => {
76
+ const result = testNamespaceProjectFilters.handlePrefAndSettingFilter({
77
+ allNamespaces,
78
+ showReservedRancherNamespaces: true,
79
+ productHidesSystemNamespaces: true,
80
+ });
81
+
82
+ expect(result).toStrictEqual({
83
+ system: false,
84
+ 'obscure-system': false
85
+ });
86
+ });
87
+
88
+ it('should filter both obscure and system namespaces when both settings are active', () => {
89
+ const result = testNamespaceProjectFilters.handlePrefAndSettingFilter({
90
+ allNamespaces,
91
+ showReservedRancherNamespaces: false,
92
+ productHidesSystemNamespaces: true,
93
+ });
94
+
95
+ expect(result).toStrictEqual({
96
+ obscure: false,
97
+ system: false,
98
+ 'obscure-system': false
99
+ });
100
+ });
101
+ });
102
+
103
+ describe('method: handleSystemOrUserFilter', () => {
104
+ it('should create an OR filter for system namespaces when isAllSystem is true', () => {
105
+ const result = testNamespaceProjectFilters.handleSystemOrUserFilter({
106
+ allNamespaces,
107
+ isAllSystem: true,
108
+ isAllUser: false,
109
+ });
110
+
111
+ expect(result).toStrictEqual({
112
+ system: true,
113
+ 'obscure-system': true
114
+ });
115
+ });
116
+
117
+ it('should create AND filters to exclude system namespaces when isAllUser is true', () => {
118
+ const result = testNamespaceProjectFilters.handleSystemOrUserFilter({
119
+ allNamespaces,
120
+ isAllSystem: false,
121
+ isAllUser: true,
122
+ });
123
+
124
+ expect(result).toStrictEqual({
125
+ system: false,
126
+ 'obscure-system': false
127
+ });
128
+ });
129
+ });
130
+
131
+ describe('method: combineNsProjectFilterResults', () => {
132
+ it('should merge two results, prioritizing the first', () => {
133
+ const a = { ns1: true, ns2: false };
134
+ const b = { ns2: true, ns3: true };
135
+ const result = testNamespaceProjectFilters.combineNsProjectFilterResults(a, b);
136
+
137
+ expect(result).toStrictEqual({
138
+ ns1: true,
139
+ ns2: false, // kept from a
140
+ ns3: true // added from b
141
+ });
142
+ });
143
+ });
144
+
145
+ describe('method: createFiltersFromNamespaceProjectFilterResult', () => {
146
+ it('should create IN filter if there are included namespaces', () => {
147
+ const input = {
148
+ ns1: true, ns2: true, ns3: false
149
+ };
150
+ const result = testNamespaceProjectFilters.createFiltersFromNamespaceProjectFilterResult(input);
151
+
152
+ expect(result).toHaveLength(1);
153
+ expect(result[0].fields[0]).toMatchObject({
154
+ field: 'metadata.namespace',
155
+ equality: PaginationFilterEquality.IN,
156
+ value: 'ns1,ns2'
157
+ });
158
+ });
159
+
160
+ it('should create NOT_IN filter if there are only excluded namespaces', () => {
161
+ const input = { ns1: false, ns2: false };
162
+ const result = testNamespaceProjectFilters.createFiltersFromNamespaceProjectFilterResult(input);
163
+
164
+ expect(result).toHaveLength(1);
165
+ expect(result[0].fields[0]).toMatchObject({
166
+ field: 'metadata.namespace',
167
+ equality: PaginationFilterEquality.NOT_IN,
168
+ value: 'ns1,ns2'
169
+ });
170
+ });
171
+
172
+ it('should return empty array if input is empty', () => {
173
+ const input = {};
174
+ const result = testNamespaceProjectFilters.createFiltersFromNamespaceProjectFilterResult(input);
175
+
176
+ expect(result).toHaveLength(0);
177
+ });
178
+ });
179
+
180
+ describe('method: handleSelectionFilter', () => {
181
+ const selection = ['ns-1', `${ NAMESPACE_FILTER_P_FULL_PREFIX }p-1`];
182
+
183
+ it('should create projectsOrNamespaces filter for a selection in a non-local cluster', () => {
184
+ const result = testNamespaceProjectFilters.handleSelectionFilter(selection, false);
185
+
186
+ expect(result.projectsOrNamespaces).toHaveLength(1);
187
+ const pnsFilter = result.projectsOrNamespaces[0] as PaginationParamProjectOrNamespace;
188
+
189
+ expect(pnsFilter.param).toBe('projectsornamespaces');
190
+ expect(pnsFilter.fields).toHaveLength(2);
191
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'ns-1' }));
192
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'p-1' }));
193
+ expect(result.filters).toStrictEqual([]);
194
+ });
195
+
196
+ it('should create projectsOrNamespaces and an exclusion filter for a selection in a local cluster', () => {
197
+ const result = testNamespaceProjectFilters.handleSelectionFilter(selection, true);
198
+
199
+ // projectsOrNamespaces part
200
+ expect(result.projectsOrNamespaces).toHaveLength(1);
201
+ const pnsFilter = result.projectsOrNamespaces[0] as PaginationParamProjectOrNamespace;
202
+
203
+ expect(pnsFilter.param).toBe('projectsornamespaces');
204
+ expect(pnsFilter.fields).toHaveLength(2);
205
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'ns-1' }));
206
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'p-1' }));
207
+
208
+ // filters part
209
+ expect(result.filters).toHaveLength(1);
210
+ const filter = result.filters[0] as PaginationParamFilter;
211
+
212
+ expect(filter.fields).toHaveLength(1);
213
+ expect(filter.fields).toContainEqual(expect.objectContaining({
214
+ field: 'metadata.namespace',
215
+ equality: '!=',
216
+ value: 'p-1',
217
+ }));
218
+ });
219
+
220
+ it('should handle selections with only namespaces in a local cluster', () => {
221
+ const nsSelection = ['ns-1', 'ns-2'];
222
+ const result = testNamespaceProjectFilters.handleSelectionFilter(nsSelection, true);
223
+
224
+ expect(result.projectsOrNamespaces).toHaveLength(1);
225
+ const pnsFilter = result.projectsOrNamespaces[0] as PaginationParamProjectOrNamespace;
226
+
227
+ expect(pnsFilter.fields).toHaveLength(2);
228
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'ns-1' }));
229
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'ns-2' }));
230
+ expect(result.filters).toStrictEqual([]);
231
+ });
232
+
233
+ it('should handle selections with only projects in a local cluster', () => {
234
+ const projectSelection = [`${ NAMESPACE_FILTER_P_FULL_PREFIX }p-1`, `${ NAMESPACE_FILTER_P_FULL_PREFIX }p-2`];
235
+ const result = testNamespaceProjectFilters.handleSelectionFilter(projectSelection, true);
236
+
237
+ expect(result.projectsOrNamespaces).toHaveLength(1);
238
+ const pnsFilter = result.projectsOrNamespaces[0] as PaginationParamProjectOrNamespace;
239
+
240
+ expect(pnsFilter.fields).toHaveLength(2);
241
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'p-1' }));
242
+ expect(pnsFilter.fields).toContainEqual(expect.objectContaining({ value: 'p-2' }));
243
+
244
+ expect(result.filters).toHaveLength(2);
245
+ expect(result.filters).toContainEqual(expect.objectContaining({
246
+ fields: [expect.objectContaining({
247
+ field: 'metadata.namespace',
248
+ equality: '!=',
249
+ value: 'p-1'
250
+ })],
251
+ }));
252
+ expect(result.filters).toContainEqual(expect.objectContaining({
253
+ fields: [expect.objectContaining({
254
+ field: 'metadata.namespace',
255
+ equality: '!=',
256
+ value: 'p-2'
257
+ })],
258
+ }));
259
+ });
260
+ });
261
+ });
262
+
263
+ class TestStevePaginationUtils {
264
+ public convertPaginationParams(args: any) {
265
+ return stevePaginationUtils.convertPaginationParams(args);
266
+ }
267
+ }
268
+
269
+ describe('class StevePaginationUtils', () => {
270
+ const testStevePaginationUtils = new TestStevePaginationUtils();
271
+ const schema = { id: 'pod' } as unknown as Schema;
272
+
273
+ describe('method: createParamsFromNsFilter', () => {
274
+ const normalNs = {
275
+ id: 'normal', name: 'normal', isObscure: false, isSystem: false
276
+ } as any;
277
+ const obscureNs = {
278
+ id: 'obscure', name: 'obscure', isObscure: true, isSystem: false
279
+ } as any;
280
+ const systemNs = {
281
+ id: 'system', name: 'system', isObscure: false, isSystem: true
282
+ } as any;
283
+ const allNamespaces = [normalNs, obscureNs, systemNs];
284
+
285
+ it('should return empty filters if all namespaces requested and settings allow all', () => {
286
+ const result = stevePaginationUtils.createParamsFromNsFilter({
287
+ allNamespaces,
288
+ selection: [],
289
+ isAllNamespaces: true,
290
+ isLocalCluster: false,
291
+ showReservedRancherNamespaces: true,
292
+ productHidesSystemNamespaces: false,
293
+ });
294
+
295
+ expect(result.projectsOrNamespaces).toHaveLength(0);
296
+ expect(result.filters).toHaveLength(0);
297
+ });
298
+
299
+ it('should filter obscure namespaces if showReservedRancherNamespaces is false', () => {
300
+ const result = stevePaginationUtils.createParamsFromNsFilter({
301
+ allNamespaces,
302
+ selection: [],
303
+ isAllNamespaces: true,
304
+ isLocalCluster: false,
305
+ showReservedRancherNamespaces: false,
306
+ productHidesSystemNamespaces: false,
307
+ });
308
+
309
+ expect(result.filters).toHaveLength(1);
310
+ expect(result.filters[0].fields[0]).toMatchObject({
311
+ field: 'metadata.namespace',
312
+ equality: PaginationFilterEquality.NOT_IN,
313
+ value: 'obscure'
314
+ });
315
+ });
316
+
317
+ it('should filter system namespaces if productHidesSystemNamespaces is true', () => {
318
+ const result = stevePaginationUtils.createParamsFromNsFilter({
319
+ allNamespaces,
320
+ selection: [],
321
+ isAllNamespaces: true,
322
+ isLocalCluster: false,
323
+ showReservedRancherNamespaces: true,
324
+ productHidesSystemNamespaces: true,
325
+ });
326
+
327
+ expect(result.filters).toHaveLength(1);
328
+ expect(result.filters[0].fields[0]).toMatchObject({
329
+ field: 'metadata.namespace',
330
+ equality: PaginationFilterEquality.NOT_IN,
331
+ value: 'system'
332
+ });
333
+ });
334
+
335
+ it('should filter both obscure and system namespaces when both settings are active', () => {
336
+ const result = stevePaginationUtils.createParamsFromNsFilter({
337
+ allNamespaces,
338
+ selection: [],
339
+ isAllNamespaces: true,
340
+ isLocalCluster: false,
341
+ showReservedRancherNamespaces: false,
342
+ productHidesSystemNamespaces: true,
343
+ });
344
+
345
+ expect(result.filters).toHaveLength(1);
346
+ expect(result.filters[0].fields[0]).toMatchObject({
347
+ field: 'metadata.namespace',
348
+ equality: PaginationFilterEquality.NOT_IN,
349
+ value: 'obscure,system'
350
+ });
351
+ });
352
+
353
+ it('should handle ALL_SYSTEM selection', () => {
354
+ const result = stevePaginationUtils.createParamsFromNsFilter({
355
+ allNamespaces,
356
+ selection: [NAMESPACE_FILTER_ALL_SYSTEM],
357
+ isAllNamespaces: false,
358
+ isLocalCluster: false,
359
+ showReservedRancherNamespaces: true,
360
+ productHidesSystemNamespaces: false,
361
+ });
362
+
363
+ expect(result.filters).toHaveLength(1);
364
+ expect(result.filters[0].fields[0]).toMatchObject({
365
+ field: 'metadata.namespace',
366
+ equality: PaginationFilterEquality.IN,
367
+ value: 'system'
368
+ });
369
+ });
370
+
371
+ it('should handle ALL_USER selection', () => {
372
+ const result = stevePaginationUtils.createParamsFromNsFilter({
373
+ allNamespaces,
374
+ selection: [NAMESPACE_FILTER_ALL_USER],
375
+ isAllNamespaces: false,
376
+ isLocalCluster: false,
377
+ showReservedRancherNamespaces: true,
378
+ productHidesSystemNamespaces: false,
379
+ });
380
+
381
+ expect(result.filters).toHaveLength(1);
382
+ expect(result.filters[0].fields[0]).toMatchObject({
383
+ field: 'metadata.namespace',
384
+ equality: PaginationFilterEquality.NOT_IN,
385
+ value: 'system'
386
+ });
387
+ });
388
+
389
+ it('should handle specific project/namespace selection', () => {
390
+ const selection = ['ns-1'];
391
+ const result = stevePaginationUtils.createParamsFromNsFilter({
392
+ allNamespaces,
393
+ selection,
394
+ isAllNamespaces: false,
395
+ isLocalCluster: false,
396
+ showReservedRancherNamespaces: true,
397
+ productHidesSystemNamespaces: false,
398
+ });
399
+
400
+ expect(result.projectsOrNamespaces).toHaveLength(1);
401
+ expect(result.projectsOrNamespaces[0].fields[0].value).toBe('ns-1');
402
+ });
403
+ });
404
+
405
+ describe('method: convertPaginationParams', () => {
406
+ it('should return an empty string for no filters', () => {
407
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters: [] });
408
+
409
+ expect(result).toBe('');
410
+ });
411
+
412
+ it('should handle a single filter with a single field', () => {
413
+ const filters = [
414
+ new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.name', value: 'test' })] }),
415
+ ];
416
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
417
+
418
+ expect(result).toBe('filter=metadata.name=test');
419
+ });
420
+
421
+ it('should handle a single filter with a single field with encoded char', () => {
422
+ const filters = [
423
+ new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.name', value: 'te/st' })] }),
424
+ ];
425
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
426
+
427
+ expect(result).toBe('filter=metadata.name="te%2Fst"');
428
+ });
429
+
430
+ it('should handle a single filter with multiple fields (OR)', () => {
431
+ const filters = [
432
+ new PaginationParamFilter({
433
+ fields: [
434
+ new PaginationFilterField({ field: 'metadata.name', value: 'test1' }),
435
+ new PaginationFilterField({ field: 'metadata.namespace', value: 'ns1' }),
436
+ ],
437
+ }),
438
+ ];
439
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
440
+
441
+ expect(result).toBe('filter=metadata.name=test1,metadata.namespace=ns1');
442
+ });
443
+
444
+ it('should handle multiple filters (AND)', () => {
445
+ const filters = [
446
+ new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.name', value: 'test1' })] }),
447
+ new PaginationParamFilter({ fields: [new PaginationFilterField({ field: 'metadata.namespace', value: 'ns1' })] }),
448
+ ];
449
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
450
+
451
+ expect(result).toBe('filter=metadata.name=test1&filter=metadata.namespace=ns1');
452
+ });
453
+
454
+ it('should handle different equality operators', () => {
455
+ const filters = [
456
+ new PaginationParamFilter({
457
+ fields: [
458
+ new PaginationFilterField({
459
+ field: 'spec.containers.image',
460
+ value: 'nginx',
461
+ equality: PaginationFilterEquality.CONTAINS,
462
+ }),
463
+ ],
464
+ }),
465
+ new PaginationParamFilter({
466
+ fields: [
467
+ new PaginationFilterField({
468
+ field: 'metadata.name',
469
+ value: 'test',
470
+ equality: PaginationFilterEquality.NOT_EQUALS,
471
+ }),
472
+ ],
473
+ }),
474
+ ];
475
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
476
+
477
+ expect(result).toBe('filter=spec.containers.image~nginx&filter=metadata.name!=test');
478
+ });
479
+
480
+ it('should handle IN and NOT_IN operators', () => {
481
+ const filters = [
482
+ new PaginationParamFilter({
483
+ fields: [
484
+ new PaginationFilterField({
485
+ field: 'metadata.name',
486
+ value: 'test1,test2',
487
+ equality: PaginationFilterEquality.IN,
488
+ }),
489
+ ],
490
+ }),
491
+ new PaginationParamFilter({
492
+ fields: [
493
+ new PaginationFilterField({
494
+ field: 'metadata.namespace',
495
+ value: 'ns1,ns2',
496
+ equality: PaginationFilterEquality.NOT_IN,
497
+ }),
498
+ ],
499
+ }),
500
+ ];
501
+ const result = testStevePaginationUtils.convertPaginationParams({ schema, filters });
502
+
503
+ expect(result).toBe('filter=metadata.name IN (test1,test2)&filter=metadata.namespace NOTIN (ns1,ns2)');
504
+ });
505
+ });
506
+ });
@@ -47,7 +47,7 @@ export default class SteveModel extends HybridModel {
47
47
  * Get all model extensions for this model
48
48
  */
49
49
  get modelExtensions() {
50
- return this.$plugin.getDynamic(EXT_IDS.MODEL_EXTENSION, this.type) || [];
50
+ return this.$extension.getDynamic(EXT_IDS.MODEL_EXTENSION, this.type) || [];
51
51
  }
52
52
 
53
53
  cleanForSave(data, forNew) {