@rancher/shell 3.0.8-rc.9 → 3.0.9-rc.1

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 (154) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +128 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +100 -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 +90 -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 +37 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/styles/global/_tooltip.scss +6 -1
  18. package/assets/translations/en-us.yaml +5 -0
  19. package/components/ActionMenuShell.vue +3 -1
  20. package/components/CruResource.vue +8 -1
  21. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  22. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  23. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  24. package/components/LocaleSelector.vue +2 -2
  25. package/components/ModalManager.vue +11 -1
  26. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  27. package/components/RelatedResources.vue +5 -0
  28. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  29. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  30. package/components/ResourceDetail/index.vue +3 -0
  31. package/components/ResourceTable.vue +54 -21
  32. package/components/SlideInPanelManager.vue +16 -11
  33. package/components/SortableTable/THead.vue +2 -1
  34. package/components/SortableTable/index.vue +20 -2
  35. package/components/Tabbed/index.vue +37 -2
  36. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  37. package/components/auth/SelectPrincipal.vue +4 -0
  38. package/components/auth/login/ldap.vue +3 -3
  39. package/components/fleet/FleetSecretSelector.vue +1 -1
  40. package/components/form/KeyValue.vue +1 -1
  41. package/components/form/NameNsDescription.vue +1 -1
  42. package/components/form/NodeScheduling.vue +2 -2
  43. package/components/form/ResourceTabs/composable.ts +2 -2
  44. package/components/form/ResourceTabs/index.vue +0 -2
  45. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  46. package/components/formatter/LinkName.vue +5 -0
  47. package/components/nav/Group.vue +25 -7
  48. package/components/nav/Header.vue +1 -1
  49. package/components/nav/NamespaceFilter.vue +1 -0
  50. package/components/nav/Type.vue +17 -6
  51. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  52. package/components/nav/__tests__/Type.test.ts +59 -0
  53. package/composables/cruResource.ts +27 -0
  54. package/composables/focusTrap.ts +3 -1
  55. package/composables/resourceDetail.ts +15 -0
  56. package/composables/useLabeledFormElement.ts +3 -4
  57. package/config/product/fleet.js +1 -1
  58. package/config/router/navigation-guards/clusters.js +3 -3
  59. package/config/router/navigation-guards/products.js +1 -1
  60. package/config/router/routes.js +1 -5
  61. package/core/__tests__/extension-manager-impl.test.js +437 -0
  62. package/core/extension-manager-impl.js +6 -27
  63. package/core/plugin-helpers.ts +2 -2
  64. package/core/plugin.ts +9 -1
  65. package/core/plugins-loader.js +2 -2
  66. package/core/types-provisioning.ts +4 -0
  67. package/core/types.ts +35 -0
  68. package/detail/catalog.cattle.io.app.vue +1 -0
  69. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  70. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  71. package/dialog/MoveNamespaceDialog.vue +20 -4
  72. package/dialog/SearchDialog.vue +1 -0
  73. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  74. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  75. package/directives/clean-tooltip.ts +234 -0
  76. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  77. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
  78. package/edit/fleet.cattle.io.helmop.vue +5 -0
  79. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  80. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  82. package/edit/resources.cattle.io.restore.vue +1 -1
  83. package/edit/workload/Job.vue +2 -2
  84. package/edit/workload/__tests__/index.test.ts +123 -85
  85. package/edit/workload/index.vue +2 -2
  86. package/edit/workload/mixins/workload.js +19 -1
  87. package/initialize/install-plugins.js +4 -5
  88. package/machine-config/azure.vue +1 -1
  89. package/machine-config/components/GCEImage.vue +1 -1
  90. package/mixins/__tests__/brand.spec.ts +18 -13
  91. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
  92. package/models/chart.js +70 -74
  93. package/models/management.cattle.io.cluster.js +1 -1
  94. package/models/provisioning.cattle.io.cluster.js +11 -3
  95. package/package.json +7 -7
  96. package/pages/auth/login.vue +3 -3
  97. package/pages/auth/setup.vue +1 -1
  98. package/pages/auth/verify.vue +3 -3
  99. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  100. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  101. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  102. package/pages/c/_cluster/fleet/index.vue +7 -10
  103. package/pages/c/_cluster/settings/index.vue +5 -0
  104. package/pkg/auto-import.js +3 -3
  105. package/pkg/dynamic-importer.lib.js +1 -1
  106. package/pkg/import.js +1 -1
  107. package/plugins/__tests__/mutations.tests.ts +179 -0
  108. package/plugins/dashboard-store/getters.js +1 -1
  109. package/plugins/dashboard-store/model-loader.js +1 -1
  110. package/plugins/dashboard-store/mutations.js +23 -2
  111. package/plugins/dashboard-store/resource-class.js +8 -3
  112. package/plugins/plugin.js +2 -2
  113. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  114. package/plugins/steve/mutations.js +9 -0
  115. package/plugins/steve/steve-class.js +1 -1
  116. package/plugins/steve/steve-pagination-utils.ts +108 -43
  117. package/plugins/steve/subscribe.js +23 -2
  118. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  119. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  120. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  121. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  122. package/scripts/publish-shell.sh +25 -0
  123. package/store/__tests__/catalog.test.ts +1 -1
  124. package/store/__tests__/type-map.test.ts +164 -2
  125. package/store/auth.js +23 -11
  126. package/store/i18n.js +3 -3
  127. package/store/index.js +5 -3
  128. package/store/notifications.ts +2 -0
  129. package/store/prefs.js +2 -2
  130. package/store/type-map.js +17 -7
  131. package/types/internal-api/shell/modal.d.ts +6 -6
  132. package/types/notifications/index.ts +126 -15
  133. package/types/rancher/index.d.ts +9 -0
  134. package/types/shell/index.d.ts +16 -1
  135. package/types/store/dashboard-store.types.ts +29 -7
  136. package/types/vue-shim.d.ts +5 -4
  137. package/utils/__tests__/router.test.js +238 -0
  138. package/utils/cluster.js +4 -1
  139. package/utils/cspAdaptor.ts +32 -14
  140. package/utils/fleet.ts +8 -1
  141. package/utils/pagination-utils.ts +2 -2
  142. package/utils/pagination-wrapper.ts +4 -4
  143. package/utils/router.js +50 -0
  144. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  145. package/vue.config.js +3 -3
  146. package/composables/useExtensionManager.ts +0 -17
  147. package/core/__test__/extension-manager-impl.test.js +0 -236
  148. package/core/plugins.js +0 -38
  149. package/directives/clean-tooltip.js +0 -32
  150. package/plugins/internal-api/index.ts +0 -37
  151. package/plugins/internal-api/shared/base-api.ts +0 -13
  152. package/plugins/internal-api/shell/shell.api.ts +0 -108
  153. package/types/internal-api/shell/growl.d.ts +0 -25
  154. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -277,7 +277,7 @@ export default class ProvCluster extends SteveModel {
277
277
  dispatch: this.$dispatch,
278
278
  getters: this.$getters,
279
279
  axios: this.$axios,
280
- $extension: this.$plugin,
280
+ $extension: this.$extension,
281
281
  t: (...args) => this.t.apply(this, args),
282
282
  };
283
283
 
@@ -326,8 +326,16 @@ export default class ProvCluster extends SteveModel {
326
326
 
327
327
  // imported rke2 and k3s have status.driver === rke2 and k3s respectively
328
328
  // Provisioned rke2 and k3s have status.driver === imported
329
- if (this.mgmt?.status?.provider === 'k3s' || this.mgmt?.status?.provider === 'rke2') {
330
- return this.mgmt?.status?.driver === this.mgmt?.status?.provider;
329
+ const provider = this.mgmt?.status?.provider;
330
+ const driver = this.mgmt?.status?.driver;
331
+
332
+ // The main case
333
+ if (provider === 'k3s' || provider === 'rke2') {
334
+ return driver === provider;
335
+ }
336
+ // The 'waiting' case
337
+ if (!provider && (driver === 'k3s' || driver === 'rke2')) {
338
+ return true;
331
339
  }
332
340
 
333
341
  // imported KEv2
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.8-rc.9",
3
+ "version": "3.0.9-rc.1",
4
4
  "description": "Rancher Dashboard Shell",
5
- "repository": "https://github.com/rancherlabs/dashboard",
5
+ "repository": "https://github.com/rancher/dashboard",
6
6
  "license": "Apache-2.0",
7
7
  "author": "SUSE",
8
8
  "private": false,
@@ -39,7 +39,7 @@
39
39
  "@babel/preset-typescript": "7.16.7",
40
40
  "@novnc/novnc": "1.2.0",
41
41
  "@popperjs/core": "2.11.8",
42
- "@rancher/icons": "2.0.53",
42
+ "@rancher/icons": "2.0.54",
43
43
  "@types/is-url": "1.2.30",
44
44
  "@types/node": "20.10.8",
45
45
  "@types/semver": "^7.5.8",
@@ -53,12 +53,12 @@
53
53
  "add": "2.0.6",
54
54
  "ansi_up": "5.0.0",
55
55
  "axios-retry": "3.1.9",
56
- "axios": "1.12.2",
56
+ "axios": "1.13.2",
57
57
  "babel-eslint": "10.1.0",
58
- "babel-plugin-module-resolver": "4.0.0",
58
+ "babel-plugin-module-resolver": "5.0.2",
59
59
  "babel-preset-vue": "2.0.2",
60
60
  "cache-loader": "4.1.0",
61
- "chart.js": "4.4.8",
61
+ "chart.js": "4.5.1",
62
62
  "clipboard-polyfill": "4.0.1",
63
63
  "codemirror-editor-vue3": "2.8.0",
64
64
  "codemirror": ">=5.64.0 <6",
@@ -140,7 +140,7 @@
140
140
  "vuedraggable": "4.1.0",
141
141
  "vuex": "4.1.0",
142
142
  "webpack-bundle-analyzer": "4.10.2",
143
- "webpack-virtual-modules": "0.4.3",
143
+ "webpack-virtual-modules": "0.6.2",
144
144
  "worker-loader": "3.0.8",
145
145
  "xterm-addon-canvas": "0.5.0",
146
146
  "xterm-addon-fit": "0.8.0",
@@ -315,9 +315,9 @@ export default {
315
315
  // so we manually load them here - other SSO auth providers bounce out and back to the Dashboard, so on the bounce-back
316
316
  // the plugins will load via the boot-time plugin
317
317
  await loadPlugins({
318
- app: this.$store.app,
319
- store: this.$store,
320
- $plugin: this.$store.$plugin
318
+ app: this.$store.app,
319
+ store: this.$store,
320
+ $extension: this.$store.$extension,
321
321
  });
322
322
 
323
323
  if (this.firstLogin || user[0]?.mustChangePassword) {
@@ -209,7 +209,7 @@ export default {
209
209
  const promises = [];
210
210
 
211
211
  try {
212
- await applyProducts(this.$store, this.$plugin);
212
+ await applyProducts(this.$store, this.$extension);
213
213
  await this.$store.dispatch('loadManagement');
214
214
 
215
215
  if ( this.mustChangePassword ) {
@@ -119,9 +119,9 @@ export default {
119
119
 
120
120
  // Load plugins
121
121
  await loadPlugins({
122
- app: this.$store.app,
123
- store: this.$store,
124
- $plugin: this.$store.$plugin
122
+ app: this.$store.app,
123
+ store: this.$store,
124
+ $extension: this.$store.$extension,
125
125
  });
126
126
 
127
127
  this.$router.replace(backTo);
@@ -65,6 +65,19 @@ export default {
65
65
  this.installedApps = await this.$store.dispatch('cluster/findAll', { type: CATALOG_TYPES.APP });
66
66
  },
67
67
 
68
+ updated() {
69
+ if (!this.observerInitialized && this.filteredCharts.length > 0) {
70
+ this.initIntersectionObserver();
71
+ }
72
+ this.ensureOverflow();
73
+ },
74
+
75
+ beforeUnmount() {
76
+ if (this.observer) {
77
+ this.observer.disconnect();
78
+ }
79
+ },
80
+
68
81
  data() {
69
82
  return {
70
83
  DOCS_BASE,
@@ -107,7 +120,6 @@ export default {
107
120
  }
108
121
  }
109
122
  ],
110
- appCardsCache: {},
111
123
  selectedSortOption: CATALOG_SORT_OPTIONS.RECOMMENDED,
112
124
  sortOptions: [
113
125
  { kind: 'group', label: this.t('catalog.charts.sort.prefix') },
@@ -115,7 +127,10 @@ export default {
115
127
  { value: CATALOG_SORT_OPTIONS.LAST_UPDATED_DESC, label: this.t('catalog.charts.sort.lastUpdatedDesc') },
116
128
  { value: CATALOG_SORT_OPTIONS.ALPHABETICAL_ASC, label: this.t('catalog.charts.sort.alphaAscending') },
117
129
  { value: CATALOG_SORT_OPTIONS.ALPHABETICAL_DESC, label: this.t('catalog.charts.sort.alphaDescending') },
118
- ]
130
+ ],
131
+ initialVisibleChartsCount: 30,
132
+ visibleChartsCount: 20,
133
+ hasOverflow: false
119
134
  };
120
135
  },
121
136
 
@@ -262,26 +277,21 @@ export default {
262
277
  },
263
278
 
264
279
  appChartCards() {
265
- return this.filteredCharts.map((chart) => {
266
- if (!this.appCardsCache[chart.id]) {
267
- // Cache the converted value. We're caching chart.cardContent anyway, so no need to worry about showing updates to state
268
- this.appCardsCache[chart.id] = {
269
- id: chart.id,
270
- pill: chart.featured ? { label: { key: 'generic.shortFeatured' }, tooltip: { key: 'generic.featured' } } : undefined,
271
- header: {
272
- title: { text: chart.chartNameDisplay },
273
- statuses: chart.cardContent.statuses
274
- },
275
- subHeaderItems: chart.cardContent.subHeaderItems,
276
- image: { src: chart.latestCompatibleVersion.icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
277
- content: { text: chart.chartDescription },
278
- footerItems: chart.cardContent.footerItems,
279
- rawChart: chart
280
- };
281
- }
282
-
283
- return this.appCardsCache[chart.id];
284
- });
280
+ const charts = this.filteredCharts.slice(0, this.visibleChartsCount);
281
+
282
+ return charts.map((chart) => ({
283
+ id: chart.id,
284
+ pill: chart.featured ? { label: { key: 'generic.shortFeatured' }, tooltip: { key: 'generic.featured' } } : undefined,
285
+ header: {
286
+ title: { text: chart.chartNameDisplay },
287
+ statuses: chart.cardContent.statuses
288
+ },
289
+ subHeaderItems: chart.cardContent.subHeaderItems,
290
+ image: { src: chart.latestCompatibleVersion.icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
291
+ content: { text: chart.chartDescription },
292
+ footerItems: chart.cardContent.footerItems,
293
+ rawChart: chart
294
+ }));
285
295
  },
286
296
 
287
297
  clusterId() {
@@ -293,7 +303,7 @@ export default {
293
303
  },
294
304
 
295
305
  totalMessage() {
296
- const count = !this.isFilterUpdating ? this.appChartCards.length : '. . .';
306
+ const count = !this.isFilterUpdating ? this.filteredCharts.length : '. . .';
297
307
 
298
308
  if (this.noFiltersApplied) {
299
309
  return this.t('catalog.charts.totalChartsMessage', { count });
@@ -304,6 +314,10 @@ export default {
304
314
  },
305
315
 
306
316
  watch: {
317
+ debouncedSearchQuery() {
318
+ this.resetLazyLoadState();
319
+ },
320
+
307
321
  searchQuery: {
308
322
  handler: debounce(function(q) {
309
323
  this.debouncedSearchQuery = q;
@@ -315,6 +329,8 @@ export default {
315
329
  filters: {
316
330
  deep: true,
317
331
  handler(newFilters) {
332
+ this.resetLazyLoadState();
333
+
318
334
  const query = {
319
335
  [REPO]: normalizeFilterQuery(newFilters.repos),
320
336
  [CATEGORY]: normalizeFilterQuery(newFilters.categories),
@@ -425,11 +441,80 @@ export default {
425
441
  });
426
442
  },
427
443
 
444
+ resetLazyLoadState() {
445
+ this.visibleChartsCount = this.initialVisibleChartsCount;
446
+ this.observerInitialized = false;
447
+ this.hasOverflow = false;
448
+ },
449
+
450
+ // The lazy loading implementation has two parts
451
+ // 1. Initial Load (ensureOverflow): Having a simple calculation of how many items to load
452
+ // can fail in edge cases like browser zoom, where element sizing and viewport
453
+ // height can lead to miscalculations. If not enough content is loaded, the page
454
+ // won't be scrollable, breaking the IntersectionObserver. This method, called
455
+ // iteratively by the `updated` lifecycle hook, adds batches of charts and
456
+ // re-measures until the content height factually overflows the container,
457
+ // guaranteeing a scrollbar. It then sets `hasOverflow = true` to stop itself.
458
+ // 2. Scroll-based Load (IntersectionObserver): Once the page is scrollable, a standard
459
+ // IntersectionObserver (`initIntersectionObserver` and `loadMore`) takes care of
460
+ // loading new batches of charts as the user scrolls to the bottom.
461
+ ensureOverflow() {
462
+ this.$nextTick(() => {
463
+ if (this.hasOverflow || !this.$refs.chartsContainer) {
464
+ return;
465
+ }
466
+
467
+ const mainLayout = document.querySelector('.main-layout');
468
+
469
+ if (!mainLayout) {
470
+ return;
471
+ }
472
+
473
+ const contentHeight = this.$refs.chartsContainer.offsetHeight;
474
+ const containerHeight = mainLayout.offsetHeight;
475
+
476
+ if (contentHeight > containerHeight) {
477
+ this.hasOverflow = true;
478
+ } else if (this.visibleChartsCount < this.filteredCharts.length) {
479
+ // Load another batch
480
+ this.visibleChartsCount += this.initialVisibleChartsCount;
481
+ } else {
482
+ // All charts are visible
483
+ this.hasOverflow = true;
484
+ }
485
+ });
486
+ },
487
+
428
488
  resetAllFilters() {
429
489
  this.internalFilters = createInitialFilters();
430
490
  this.filters = createInitialFilters();
431
491
  this.searchQuery = '';
432
492
  },
493
+
494
+ loadMore() {
495
+ if (this.visibleChartsCount >= this.filteredCharts.length) {
496
+ return;
497
+ }
498
+ this.visibleChartsCount += this.initialVisibleChartsCount;
499
+ },
500
+
501
+ initIntersectionObserver() {
502
+ if (this.observer) {
503
+ this.observer.disconnect();
504
+ }
505
+ const mainLayout = document.querySelector('.main-layout');
506
+ const sentinel = this.$refs.sentinel;
507
+
508
+ if (sentinel && mainLayout) {
509
+ this.observer = new IntersectionObserver((entries) => {
510
+ if (entries[0].isIntersecting) {
511
+ this.loadMore();
512
+ }
513
+ }, { mainLayout });
514
+ this.observer.observe(sentinel);
515
+ this.observerInitialized = true;
516
+ }
517
+ }
433
518
  },
434
519
  };
435
520
  </script>
@@ -552,7 +637,10 @@ export default {
552
637
  >
553
638
  <div class="total-and-sort">
554
639
  <div class="total">
555
- <p class="total-message">
640
+ <p
641
+ class="total-message"
642
+ data-testid="charts-total-message"
643
+ >
556
644
  {{ totalMessage }}
557
645
  </p>
558
646
  <a
@@ -594,6 +682,7 @@ export default {
594
682
  </Select>
595
683
  </div>
596
684
  <div
685
+ ref="chartsContainer"
597
686
  class="app-chart-cards"
598
687
  data-testid="app-chart-cards-container"
599
688
  >
@@ -629,6 +718,11 @@ export default {
629
718
  </template>
630
719
  </rc-item-card>
631
720
  </div>
721
+ <div
722
+ ref="sentinel"
723
+ class="sentinel-charts"
724
+ data-testid="charts-lazy-load-sentinel"
725
+ />
632
726
  </div>
633
727
  </div>
634
728
  </div>
@@ -673,6 +767,10 @@ export default {
673
767
  flex-direction: column;
674
768
  gap: var(--gap-md);
675
769
  flex: 1;
770
+
771
+ .sentinel-charts {
772
+ height: 1px;
773
+ }
676
774
  }
677
775
 
678
776
  .total-and-sort {
@@ -310,6 +310,7 @@ export default {
310
310
  two different Helm chart versions is a "user value," or
311
311
  a user-selected customization.
312
312
  */
313
+ this.preserveCustomRegistryValue();
313
314
  userValues = diff(this.loadedVersionValues, this.chartValues);
314
315
  } else if ( this.existing ) {
315
316
  await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe
@@ -824,6 +825,35 @@ export default {
824
825
  },
825
826
 
826
827
  methods: {
828
+ /**
829
+ * The custom registry UI fields (checkbox and input) are not directly bound to chartValues.
830
+ * Before calculating the diff to carry over user customizations, we must
831
+ * first synchronize the state of these UI fields with chartValues. This
832
+ * ensures any user changes to the custom registry settings are
833
+ * included in the diff and preserved when changing versions.
834
+ */
835
+ preserveCustomRegistryValue() {
836
+ if (!this.showCustomRegistry) {
837
+ return;
838
+ }
839
+
840
+ if (this.showCustomRegistryInput) {
841
+ set(this.chartValues, 'global.systemDefaultRegistry', this.customRegistrySetting);
842
+ set(this.chartValues, 'global.cattle.systemDefaultRegistry', this.customRegistrySetting);
843
+ } else {
844
+ // Note: Using `delete` here is safe because this is not a reactive property update
845
+ // that the UI needs to track. This is a one-time mutation before a diff.
846
+ if (get(this.chartValues, 'global.systemDefaultRegistry')) {
847
+ delete this.chartValues.global.systemDefaultRegistry;
848
+ }
849
+ if (get(this.chartValues, 'global.cattle.systemDefaultRegistry')) {
850
+ // It's possible `this.chartValues.global.cattle` doesn't exist,
851
+ // but `get` ensures we only proceed if the full path exists.
852
+ delete this.chartValues.global.cattle.systemDefaultRegistry;
853
+ }
854
+ }
855
+ },
856
+
827
857
  async getClusterRegistry() {
828
858
  const hasPermissionToSeeProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
829
859
 
@@ -1367,6 +1397,7 @@ export default {
1367
1397
  <!-- We have a chart for the app, let the user select a new version -->
1368
1398
  <LabeledSelect
1369
1399
  v-if="chart"
1400
+ data-testid="chart-version-selector"
1370
1401
  :label="t('catalog.install.version')"
1371
1402
  :value="query.versionName"
1372
1403
  :options="filteredVersions"
@@ -1435,6 +1466,7 @@ export default {
1435
1466
  v-if="showCustomRegistry"
1436
1467
  v-model:value="showCustomRegistryInput"
1437
1468
  class="mb-20"
1469
+ data-testid="custom-registry-checkbox"
1438
1470
  :label="t('catalog.chart.registry.custom.checkBoxLabel')"
1439
1471
  :tooltip="t('catalog.chart.registry.tooltip')"
1440
1472
  />
@@ -1443,6 +1475,7 @@ export default {
1443
1475
  <LabeledInput
1444
1476
  v-if="showCustomRegistryInput"
1445
1477
  v-model:value="customRegistrySetting"
1478
+ data-testid="custom-registry-input"
1446
1479
  label-key="catalog.chart.registry.custom.inputLabel"
1447
1480
  placeholder-key="catalog.chart.registry.custom.placeholder"
1448
1481
  :min-height="30"
@@ -211,7 +211,7 @@ describe('page: cluster dashboard', () => {
211
211
 
212
212
  expect(box.element).toBeDefined();
213
213
  expect(box.element.classList).toContain(status);
214
- expect(!!(box.element as any).$_popper).toBe(clickable);
214
+ expect(!!(box.element as any).__tooltipOptions__?.content).toBe(clickable);
215
215
  expect(icon.element.classList).toContain(iconClass);
216
216
 
217
217
  await box.trigger('click');
@@ -1,4 +1,4 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import debounce from 'lodash/debounce';
3
3
  import { getVersionData } from '@shell/config/version';
4
4
  import { mapState, mapGetters } from 'vuex';
@@ -321,17 +321,14 @@ export default {
321
321
 
322
322
  this.selectedCard = selected;
323
323
 
324
- this.$shell.slideInPanel({
325
- component: ResourceDetails,
326
- componentProps: {
324
+ this.$shell.slideIn.open(ResourceDetails, {
325
+ showHeader: false,
326
+ width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
327
+ props: {
327
328
  value,
328
329
  statePanel,
329
- workspace,
330
- showHeader: false,
331
- width: window.innerWidth / 3 > 530 ? `${ window.innerWidth / 3 }px` : '530px',
332
- triggerFocusTrap: true,
333
- returnFocusSelector: `[data-testid="resource-card-${ value.id }"]`
334
- }
330
+ workspace
331
+ },
335
332
  });
336
333
  },
337
334
 
@@ -1,8 +1,13 @@
1
1
  <script>
2
+ import { h } from 'vue';
2
3
  import { NAME as SETTINGS } from '@shell/config/product/settings';
3
4
  import { MANAGEMENT } from '@shell/config/types';
4
5
 
5
6
  export default {
7
+ render() {
8
+ // Suppress warning: Component is missing template or render function
9
+ return h('div');
10
+ },
6
11
  beforeCreate() {
7
12
  const hasSettings = !!this.$store.getters[`management/schemaFor`](MANAGEMENT.SETTING);
8
13
 
@@ -15,7 +15,7 @@ function registerFile(file, type, pkg, f) {
15
15
  const importType = (f === 'models') ? 'require' : 'import';
16
16
  const chunkName = (f === 'l10n') ? '' : `/* webpackChunkName: "${ f }" */`;
17
17
 
18
- return ` $plugin.register('${ f }', '${ type }', () => ${ importType }(${ chunkName }'${ pkg }/${ f }/${ file }'));\n`;
18
+ return ` $extension.register('${ f }', '${ type }', () => ${ importType }(${ chunkName }'${ pkg }/${ f }/${ file }'));\n`;
19
19
  }
20
20
 
21
21
  function register(file, pkg, f) {
@@ -29,7 +29,7 @@ function register(file, pkg, f) {
29
29
  // This ensures that the webpackChunkName is respected (require.context does not support this) - so when build as a library
30
30
  // the code splitting will be respected
31
31
  function generateTypeImport(pkg, dir) {
32
- let content = 'export function importTypes($plugin) { \n';
32
+ let content = 'export function importTypes($extension) { \n';
33
33
 
34
34
  // Auto-import if the folder exists
35
35
  contextFolders.forEach((f) => {
@@ -77,7 +77,7 @@ function generateTypeImport(pkg, dir) {
77
77
  // and then restart the dev server for it to be picked up.
78
78
  function generateDynamicTypeImport(pkg, dir) {
79
79
  const template = fs.readFileSync(path.join(__dirname, 'import.js'), { encoding: 'utf8' });
80
- let content = 'export function importTypes($plugin) { \n';
80
+ let content = 'export function importTypes($extension) { \n';
81
81
 
82
82
  // Auto-import if the folder exists
83
83
  contextFolders.forEach((f) => {
@@ -41,7 +41,7 @@ export function listProducts() {
41
41
  return [];
42
42
  }
43
43
 
44
- export function loadProduct(name, $plugin) {
44
+ export function loadProduct(name, $extension) {
45
45
  return () => undefined;
46
46
  }
47
47
 
package/pkg/import.js CHANGED
@@ -6,5 +6,5 @@ _NAME.forEach((f) => {
6
6
 
7
7
  name = name.substr(0, ext);
8
8
 
9
- $plugin.register('DIR', name, () => REQUIRE(CHUNK`BASE/DIR/${ name }EXT`)); // eslint-disable-line no-undef
9
+ $extension.register('DIR', name, () => REQUIRE(CHUNK`BASE/DIR/${ name }EXT`)); // eslint-disable-line no-undef
10
10
  });