@rancher/shell 3.0.11 → 3.0.12-rc.2

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 (219) hide show
  1. package/assets/images/providers/entraid-black.svg +4 -0
  2. package/assets/images/providers/entraid.svg +9 -0
  3. package/assets/images/vendor/entraid.svg +9 -0
  4. package/assets/styles/app.scss +0 -1
  5. package/assets/styles/base/_mixins.scss +31 -0
  6. package/assets/styles/base/_variables.scss +2 -0
  7. package/assets/styles/themes/_modern.scss +6 -5
  8. package/assets/translations/en-us.yaml +24 -21
  9. package/assets/translations/zh-hans.yaml +4 -11
  10. package/chart/__tests__/S3.test.ts +10 -3
  11. package/components/CountBox.vue +20 -0
  12. package/components/CreateDriver.vue +0 -12
  13. package/components/DetailText.vue +12 -3
  14. package/components/EmptyProductPage.vue +76 -0
  15. package/components/Resource/Detail/CopyToClipboard.vue +1 -2
  16. package/components/Resource/Detail/Metadata/KeyValueRow.vue +9 -3
  17. package/components/Resource/Detail/TitleBar/__tests__/__snapshots__/index.test.ts.snap +31 -0
  18. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +45 -1
  19. package/components/Resource/Detail/TitleBar/index.vue +1 -1
  20. package/components/Resource/Detail/ViewOptions/__tests__/__snapshots__/index.test.ts.snap +9 -0
  21. package/components/Resource/Detail/ViewOptions/__tests__/index.test.ts +62 -0
  22. package/components/Resource/Detail/ViewOptions/index.vue +2 -1
  23. package/components/ResourceList/Masthead.vue +25 -2
  24. package/components/SelectIconGrid.vue +5 -0
  25. package/components/SideNav.vue +13 -0
  26. package/components/__tests__/CountBox.test.ts +72 -0
  27. package/components/__tests__/DetailText.test.ts +113 -0
  28. package/components/__tests__/PromptModal.test.ts +2 -0
  29. package/components/fleet/FleetClusterTargets/index.vue +18 -1
  30. package/components/fleet/FleetClusters.vue +1 -0
  31. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  32. package/components/form/InputWithSelect.vue +18 -10
  33. package/components/form/KeyValue.vue +17 -1
  34. package/components/form/LabeledSelect.vue +82 -24
  35. package/components/form/NodeScheduling.vue +17 -3
  36. package/components/form/PrivateRegistry.vue +69 -0
  37. package/components/form/Select.vue +73 -56
  38. package/components/form/ServiceNameSelect.vue +13 -11
  39. package/components/form/__tests__/KeyValue.test.ts +66 -0
  40. package/components/form/__tests__/NodeScheduling.test.ts +9 -0
  41. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  42. package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
  43. package/components/formatter/WorkloadHealthScale.vue +3 -1
  44. package/components/nav/Group.vue +33 -9
  45. package/components/nav/Header.vue +56 -10
  46. package/components/nav/NotificationCenter/Notification.vue +4 -1
  47. package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
  48. package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
  49. package/components/nav/TopLevelMenu.vue +15 -1
  50. package/components/nav/Type.vue +8 -7
  51. package/components/nav/WindowManager/index.vue +2 -1
  52. package/components/nav/WorkspaceSwitcher.vue +13 -0
  53. package/components/nav/__tests__/Group.test.ts +67 -0
  54. package/components/nav/__tests__/Header.test.ts +235 -0
  55. package/components/nav/__tests__/Type.test.ts +20 -3
  56. package/components/templates/default.vue +34 -4
  57. package/components/templates/home.vue +12 -25
  58. package/components/templates/plain.vue +13 -26
  59. package/composables/useLabeledFormElement.ts +10 -2
  60. package/composables/useLabeledSelect.ts +60 -0
  61. package/composables/useUserRetentionValidation.ts +1 -49
  62. package/config/cookies.js +0 -1
  63. package/config/labels-annotations.js +1 -0
  64. package/config/pagination-table-headers.js +8 -1
  65. package/config/product/apps.js +2 -1
  66. package/config/product/auth.js +1 -0
  67. package/config/product/backup.js +1 -0
  68. package/config/product/compliance.js +1 -1
  69. package/config/product/explorer.js +25 -6
  70. package/config/product/fleet.js +1 -0
  71. package/config/product/gatekeeper.js +1 -0
  72. package/config/product/istio.js +1 -0
  73. package/config/product/logging.js +1 -0
  74. package/config/product/longhorn.js +2 -1
  75. package/config/product/manager.js +1 -0
  76. package/config/product/monitoring.js +1 -0
  77. package/config/product/navlinks.js +1 -0
  78. package/config/product/neuvector.js +2 -1
  79. package/config/product/settings.js +1 -0
  80. package/config/product/uiplugins.js +1 -0
  81. package/config/query-params.js +1 -0
  82. package/config/router/routes.js +0 -8
  83. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  84. package/core/__tests__/plugin-products.test.ts +3810 -0
  85. package/core/extension-manager-impl.js +30 -1
  86. package/core/plugin-products-base.ts +392 -0
  87. package/core/plugin-products-extending.ts +44 -0
  88. package/core/plugin-products-helpers.ts +263 -0
  89. package/core/plugin-products-top-level.ts +66 -0
  90. package/core/plugin-products-type-guards.ts +33 -0
  91. package/core/plugin-products.ts +50 -0
  92. package/core/plugin-types.ts +237 -0
  93. package/core/plugin.ts +45 -10
  94. package/core/productDebugger.js +48 -0
  95. package/core/types.ts +97 -11
  96. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  97. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  98. package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
  99. package/detail/fleet.cattle.io.bundle.vue +21 -34
  100. package/detail/management.cattle.io.fleetworkspace.vue +49 -0
  101. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  102. package/dialog/InstallExtensionDialog.vue +6 -27
  103. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  104. package/dialog/UninstallExtensionDialog.vue +4 -26
  105. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  106. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  107. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
  108. package/edit/__tests__/kontainerDriver.test.ts +0 -13
  109. package/edit/__tests__/nodeDriver.test.ts +5 -11
  110. package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
  111. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
  112. package/edit/auth/__tests__/oidc.test.ts +54 -0
  113. package/edit/auth/azuread.vue +1 -1
  114. package/edit/auth/oidc.vue +8 -0
  115. package/edit/kontainerDriver.vue +1 -2
  116. package/edit/nodeDriver.vue +0 -2
  117. package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
  119. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  120. package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  122. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  123. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  124. package/initialize/App.vue +29 -2
  125. package/initialize/install-plugins.js +0 -2
  126. package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
  127. package/list/catalog.cattle.io.app.vue +25 -5
  128. package/list/management.cattle.io.feature.vue +1 -1
  129. package/list/management.cattle.io.fleetworkspace.vue +8 -0
  130. package/list/provisioning.cattle.io.cluster.vue +0 -1
  131. package/list/workload.vue +11 -4
  132. package/machine-config/amazonec2.vue +1 -0
  133. package/mixins/chart.js +40 -9
  134. package/mixins/resource-fetch.js +12 -3
  135. package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
  136. package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
  137. package/models/__tests__/chart.test.ts +99 -6
  138. package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
  139. package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
  140. package/models/catalog.cattle.io.app.js +21 -17
  141. package/models/catalog.cattle.io.clusterrepo.js +39 -11
  142. package/models/chart.js +33 -19
  143. package/models/fleet-application.js +1 -1
  144. package/models/fleet.cattle.io.bundle.js +1 -1
  145. package/models/kontainerdriver.js +11 -0
  146. package/models/management.cattle.io.authconfig.js +5 -1
  147. package/models/management.cattle.io.cluster.js +0 -53
  148. package/models/management.cattle.io.feature.js +3 -3
  149. package/models/management.cattle.io.kontainerdriver.js +1 -26
  150. package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
  151. package/models/nodedriver.js +7 -0
  152. package/models/pod.js +18 -0
  153. package/models/workload.js +20 -2
  154. package/package.json +13 -13
  155. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  156. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
  157. package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
  158. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
  159. package/pages/c/_cluster/apps/charts/chart.vue +217 -33
  160. package/pages/c/_cluster/apps/charts/index.vue +2 -2
  161. package/pages/c/_cluster/apps/charts/install.vue +8 -3
  162. package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
  163. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
  164. package/pages/c/_cluster/settings/brand.vue +4 -4
  165. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
  166. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
  167. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +246 -23
  168. package/pages/c/_cluster/uiplugins/index.vue +166 -62
  169. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  170. package/plugins/dashboard-store/actions.js +3 -2
  171. package/plugins/dashboard-store/resource-class.js +62 -6
  172. package/plugins/plugin.js +16 -0
  173. package/plugins/steve/steve-pagination-utils.ts +7 -0
  174. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
  175. package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
  176. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
  177. package/scripts/test-plugins-build.sh +5 -2
  178. package/scripts/typegen.sh +13 -1
  179. package/server/server-middleware.js +2 -2
  180. package/static/humans.txt +1 -0
  181. package/static/robots.txt +34 -0
  182. package/static/welcome-cow.svg +18 -0
  183. package/store/__tests__/catalog.test.ts +161 -11
  184. package/store/__tests__/type-map.test.ts +84 -24
  185. package/store/auth.js +0 -3
  186. package/store/catalog.js +60 -8
  187. package/store/type-map.js +42 -3
  188. package/tsconfig.paths.json +1 -0
  189. package/types/resources/pod.ts +18 -0
  190. package/types/shell/index.d.ts +8539 -2938
  191. package/types/store/dashboard-store.types.ts +5 -0
  192. package/types/store/pagination.types.ts +6 -0
  193. package/utils/__tests__/git.test.ts +270 -0
  194. package/utils/__tests__/inactivity.test.ts +316 -0
  195. package/utils/__tests__/object.test.ts +77 -0
  196. package/utils/__tests__/time.test.ts +14 -1
  197. package/utils/__tests__/url.test.ts +246 -0
  198. package/utils/axios.js +1 -4
  199. package/utils/dynamic-importer.js +3 -2
  200. package/utils/object.js +33 -2
  201. package/utils/pagination-utils.ts +1 -1
  202. package/utils/time.ts +5 -0
  203. package/utils/uiplugins.ts +12 -16
  204. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  205. package/utils/validators/private-registry.ts +28 -0
  206. package/vue.config.js +0 -9
  207. package/assets/images/providers/azuread-black.svg +0 -22
  208. package/assets/images/providers/azuread.svg +0 -25
  209. package/assets/images/vendor/azuread.svg +0 -18
  210. package/assets/styles/fonts/_dots.scss +0 -18
  211. package/components/EmberPage.vue +0 -622
  212. package/components/EmberPageView.vue +0 -39
  213. package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
  214. package/mixins/labeled-form-element.ts +0 -225
  215. package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
  216. package/pages/c/_cluster/manager/pages/_page.vue +0 -22
  217. package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
  218. package/plugins/ember-cookie.js +0 -17
  219. package/utils/ember-page.js +0 -30
@@ -5,7 +5,8 @@ import { mapPref, PLUGIN_DEVELOPER } from '@shell/store/prefs';
5
5
  import { sortBy } from '@shell/utils/sort';
6
6
  import genericPluginSvg from '~shell/assets/images/generic-plugin.svg';
7
7
  import { allHash } from '@shell/utils/promise';
8
- import { CATALOG, UI_PLUGIN, MANAGEMENT, ZERO_TIME } from '@shell/config/types';
8
+ import { CATALOG, UI_PLUGIN, MANAGEMENT } from '@shell/config/types';
9
+ import { isMissingDate } from '@shell/utils/time';
9
10
  import { SETTING } from '@shell/config/settings';
10
11
  import { fetchOrCreateSetting } from '@shell/utils/settings';
11
12
  import { getVersionData, isRancherPrime } from '@shell/config/version';
@@ -101,6 +102,14 @@ export default {
101
102
  hash.helmOps = this.$store.dispatch('management/findAll', { type: CATALOG.OPERATION });
102
103
  }
103
104
 
105
+ // Load apps in UI_PLUGIN_NAMESPACE to determine which repo an extension was installed from
106
+ if (this.$store.getters['management/schemaFor'](CATALOG.APP)) {
107
+ hash.apps = this.$store.dispatch('management/findAll', {
108
+ type: CATALOG.APP,
109
+ opt: { namespaced: UI_PLUGIN_NAMESPACE }
110
+ });
111
+ }
112
+
104
113
  if (this.$store.getters['management/schemaFor'](CATALOG.CLUSTER_REPO)) {
105
114
  hash.repos = this.$store.dispatch('management/findAll', { type: CATALOG.CLUSTER_REPO }, { force: true });
106
115
  }
@@ -127,6 +136,12 @@ export default {
127
136
  ...mapGetters({ uiErrors: 'uiplugins/errors' }),
128
137
  ...mapGetters({ theme: 'prefs/theme' }),
129
138
 
139
+ // Computed to reactively update when new apps are installed
140
+ apps() {
141
+ return this.$store.getters['management/all'](CATALOG.APP)
142
+ .filter((app) => app.metadata?.namespace === UI_PLUGIN_NAMESPACE);
143
+ },
144
+
130
145
  charts() {
131
146
  const c = this.$store.getters['catalog/rawCharts'];
132
147
 
@@ -313,8 +328,8 @@ export default {
313
328
  }
314
329
  }
315
330
 
316
- if (this.installing[item.name]) {
317
- item.installing = this.installing[item.name];
331
+ if (this.installing[item.id]) {
332
+ item.installing = this.installing[item.id];
318
333
  }
319
334
 
320
335
  return item;
@@ -359,15 +374,23 @@ export default {
359
374
 
360
375
  // Go through the CRs for the plugins and wire them into the catalog
361
376
  this.plugins.forEach((p) => {
362
- const chart = all.find((c) => c.name === p.name);
377
+ let chart;
378
+ const app = this.apps.find((a) => a.metadata.name === p.name && a.metadata.namespace === UI_PLUGIN_NAMESPACE);
379
+ const originalRepoName = app?.spec?.chart?.metadata?.annotations?.[CATALOG_ANNOTATIONS.SOURCE_REPO_NAME] || app?.metadata?.labels?.[CATALOG_ANNOTATIONS.CLUSTER_REPO_NAME];
363
380
 
381
+ // Find the chart from the original repo to avoid picking a wrong chart with the same name
382
+ if (originalRepoName) {
383
+ chart = all.find((c) => c.name === p.name && c.chart?.repoName === originalRepoName);
384
+ }
385
+
386
+ // If original repo was removed, don't fall back to another repo's chart (would break Available tab)
364
387
  if (chart) {
365
388
  chart.installed = true;
366
389
  chart.uiplugin = p;
367
390
  chart.installedVersion = p.version;
368
391
 
369
392
  // Can't do this here
370
- chart.installing = this.installing[chart.name];
393
+ chart.installing = this.installing[chart.id];
371
394
 
372
395
  // Check for upgrade
373
396
  const latestInstallableVersion = chart.installableVersions?.[0];
@@ -385,20 +408,34 @@ export default {
385
408
  chart.upgrade = getPluginChartVersionLabel(latestInstallableVersion);
386
409
  }
387
410
  } else {
388
- // No chart, so add a card for the plugin based on its Custom resource being present
411
+ // No chart available - original repo was removed or developer-loaded plugin
412
+ const appChartMeta = app?.spec?.chart?.metadata;
413
+ const appAnnotations = appChartMeta?.annotations || {};
414
+ let originalRepoDisplayName = null;
415
+
416
+ if (originalRepoName) {
417
+ originalRepoDisplayName = this.$store.getters['i18n/withFallback'](`catalog.repo.name."${ originalRepoName }"`, null, originalRepoName);
418
+ }
419
+
389
420
  const item = {
390
- name: p.name,
391
- label: p.name,
392
- description: p.description || '-',
393
- id: `${ p.name }-${ p.version }`,
394
- versions: [],
395
- displayVersion: p.version,
396
- displayVersionLabel: p.version || '-',
397
- isDeveloper: p.isDeveloper,
398
- installed: true,
399
- installing: false,
400
- builtin: false,
401
- uiplugin: p,
421
+ name: p.name,
422
+ label: appAnnotations[UI_PLUGIN_CHART_ANNOTATIONS.DISPLAY_NAME] || appChartMeta?.name || p.name,
423
+ description: appChartMeta?.description || p.description || '-',
424
+ icon: appChartMeta?.icon || appAnnotations['catalog.cattle.io/ui-icon'],
425
+ id: `${ p.name }-${ p.version }`,
426
+ versions: [],
427
+ displayVersion: p.version,
428
+ displayVersionLabel: p.version || '-',
429
+ isDeveloper: p.isDeveloper,
430
+ installed: true,
431
+ installedVersion: p.version,
432
+ installing: false,
433
+ builtin: false,
434
+ uiplugin: p,
435
+ primeOnly: appAnnotations[CATALOG_ANNOTATIONS.PRIME_ONLY] === 'true',
436
+ experimental: appAnnotations[CATALOG_ANNOTATIONS.EXPERIMENTAL] === 'true',
437
+ certified: appAnnotations[CATALOG_ANNOTATIONS.CERTIFIED] === CATALOG_ANNOTATIONS._RANCHER,
438
+ originalRepoNameDisplay: originalRepoDisplayName,
402
439
  };
403
440
 
404
441
  all.push(item);
@@ -422,7 +459,7 @@ export default {
422
459
 
423
460
  // Merge in the plugin load errors from help ops
424
461
  Object.keys(this.errors).forEach((e) => {
425
- const chart = all.find((c) => c.name === e);
462
+ const chart = all.find((c) => c.id === e);
426
463
 
427
464
  if (chart) {
428
465
  chart.helmError = !!this.errors[e];
@@ -456,28 +493,50 @@ export default {
456
493
  const op = pluginOps.find((o) => o.status?.releaseName === plugin.name);
457
494
 
458
495
  if (op) {
496
+ const allWithSameName = this.available.filter((p) => p.name === plugin.name);
497
+ let targetPluginId;
498
+
499
+ // When multiple plugins share the same name (from different repositories),
500
+ // a single helm operation will trigger updates. We need to correctly identify
501
+ // the specific plugin that is either currently being installed/updated or is already installed
502
+ // so we don't accidentally mark all identically named plugins as "installing".
503
+ const installingPlugin = allWithSameName.find((p) => this.installing[p.id]);
504
+ const installedPlugin = allWithSameName.find((p) => p.installed);
505
+
506
+ if (installingPlugin) {
507
+ targetPluginId = installingPlugin.id;
508
+ } else if (installedPlugin) {
509
+ targetPluginId = installedPlugin.id;
510
+ } else {
511
+ targetPluginId = allWithSameName[0]?.id;
512
+ }
513
+
514
+ if (plugin.id !== targetPluginId) {
515
+ return;
516
+ }
517
+
459
518
  const active = op.metadata.state?.transitioning;
460
519
  const error = op.metadata.state?.error;
461
520
 
462
- this.errors[plugin.name] = error;
521
+ this.errors[plugin.id] = error;
463
522
 
464
523
  if (active) {
465
524
  // Can use the status directly, apart from upgrade, which maps to update
466
525
  const status = op.status.action;
467
526
 
468
- if (status === 'upgrade' && this.installing[plugin.name] === 'downgrade') {
527
+ if (status === 'upgrade' && this.installing[plugin.id] === 'downgrade') {
469
528
  // Helm op is an upgrade, but we initiated a downgrade, so keep the 'downgrade' status
470
529
  } else {
471
- this.updatePluginInstallStatus(plugin.name, status);
530
+ this.updatePluginInstallStatus(plugin.id, status);
472
531
  }
473
532
  } else if (op.status.action === 'uninstall') {
474
533
  // Uninstall has finished
475
- this.updatePluginInstallStatus(plugin.name, false);
534
+ this.updatePluginInstallStatus(plugin.id, false);
476
535
  } else if (error) {
477
- this.updatePluginInstallStatus(plugin.name, false);
536
+ this.updatePluginInstallStatus(plugin.id, false);
478
537
  }
479
538
  } else {
480
- this.updatePluginInstallStatus(plugin.name, false);
539
+ this.updatePluginInstallStatus(plugin.id, false);
481
540
  }
482
541
  });
483
542
  },
@@ -503,7 +562,11 @@ export default {
503
562
  changes++;
504
563
  }
505
564
 
506
- this.updatePluginInstallStatus(plugin.name, false);
565
+ (this.available || []).forEach((c) => {
566
+ if (c.name === plugin.name) {
567
+ this.updatePluginInstallStatus(c.id, false);
568
+ }
569
+ });
507
570
  }
508
571
  });
509
572
 
@@ -596,6 +659,35 @@ export default {
596
659
  ev?.preventDefault?.();
597
660
  ev?.stopPropagation?.();
598
661
 
662
+ // Check if a plugin with the same name is already installed from a different repo
663
+ if (action === 'install') {
664
+ const installedPlugin = this.available.find((p) => p.name === plugin.name && p.installed);
665
+ const isInstalledFromDifferentSource = !!installedPlugin && installedPlugin.id !== plugin.id;
666
+
667
+ if (isInstalledFromDifferentSource) {
668
+ // Show a different dialog that prompts the user to uninstall the existing version first
669
+ this.$store.dispatch('management/promptModal', {
670
+ component: 'UninstallExistingExtensionDialog',
671
+ testId: 'uninstall-existing-extension-modal',
672
+ returnFocusSelector: `[data-testid="extension-card-${ action }-btn-${ plugin?.name }"]`,
673
+ returnFocusFirstIterableNodeSelector: '#extensions-main-page',
674
+ componentProps: {
675
+ installedPlugin,
676
+ updateStatus: (pluginId, type) => {
677
+ this.updatePluginInstallStatus(pluginId, type);
678
+ },
679
+ closed: (res) => {
680
+ if (res?.uninstalled) {
681
+ this.didUninstall(res.plugin);
682
+ }
683
+ }
684
+ }
685
+ });
686
+
687
+ return;
688
+ }
689
+ }
690
+
599
691
  this.$store.dispatch('management/promptModal', {
600
692
  component: 'InstallExtensionDialog',
601
693
  testId: 'install-extension-modal',
@@ -605,8 +697,8 @@ export default {
605
697
  plugin,
606
698
  action,
607
699
  initialVersion,
608
- updateStatus: (pluginName, type) => {
609
- this.updatePluginInstallStatus(pluginName, type);
700
+ updateStatus: (pluginId, type) => {
701
+ this.updatePluginInstallStatus(pluginId, type);
610
702
  },
611
703
  closed: (res) => {
612
704
  this.didInstall(res);
@@ -627,8 +719,8 @@ export default {
627
719
  returnFocusFirstIterableNodeSelector: '#extensions-main-page',
628
720
  componentProps: {
629
721
  plugin,
630
- updateStatus: (pluginName, type) => {
631
- this.updatePluginInstallStatus(pluginName, type);
722
+ updateStatus: (pluginId, type) => {
723
+ this.updatePluginInstallStatus(pluginId, type);
632
724
  },
633
725
  closed: (res) => {
634
726
  this.didUninstall(res);
@@ -639,7 +731,7 @@ export default {
639
731
 
640
732
  didUninstall(plugin) {
641
733
  if (plugin) {
642
- this.updatePluginInstallStatus(plugin.name, 'uninstall');
734
+ this.updatePluginInstallStatus(plugin.id, 'uninstall');
643
735
 
644
736
  if (plugin.catalog) {
645
737
  this.refreshCharts();
@@ -666,8 +758,8 @@ export default {
666
758
  this.$refs.infoPanel.show({ ...plugin, tags });
667
759
  },
668
760
 
669
- updatePluginInstallStatus(name, status) {
670
- this.installing[name] = status;
761
+ updatePluginInstallStatus(id, status) {
762
+ this.installing[id] = status;
671
763
  },
672
764
 
673
765
  setMenu(event) {
@@ -805,18 +897,12 @@ export default {
805
897
  label: plugin.displayVersionLabel,
806
898
  }];
807
899
 
808
- if (plugin.created) {
809
- const hasZeroTime = plugin.created === ZERO_TIME;
810
- const lastUpdatedItem = {
900
+ if (!isMissingDate(plugin.created)) {
901
+ items.push({
811
902
  icon: 'icon-refresh-alt',
812
903
  iconTooltip: { key: 'tableHeaders.lastUpdated' },
813
- label: hasZeroTime ? this.t('generic.na') : day(plugin.created).format('MMM D, YYYY')
814
- };
815
-
816
- if (hasZeroTime) {
817
- lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
818
- }
819
- items.push(lastUpdatedItem);
904
+ label: day(plugin.created).format('MMM D, YYYY')
905
+ });
820
906
  }
821
907
 
822
908
  if (plugin.installing) {
@@ -850,6 +936,17 @@ export default {
850
936
  },
851
937
 
852
938
  getFooterItems(plugin) {
939
+ const footerItems = [];
940
+ const repoNameToDisplay = plugin?.chart?.repoNameDisplay || plugin?.originalRepoNameDisplay;
941
+
942
+ if (repoNameToDisplay) {
943
+ footerItems.push({
944
+ icon: 'repository-alt',
945
+ iconTooltip: { key: 'tableHeaders.repoName' },
946
+ labels: [repoNameToDisplay]
947
+ });
948
+ }
949
+
853
950
  const labels = [];
854
951
 
855
952
  // "developer load" tag
@@ -873,34 +970,41 @@ export default {
873
970
  labels.push(this.t('plugins.labels.experimental'));
874
971
  }
875
972
 
876
- return labels.length ? [{
877
- icon: 'tag-alt',
878
- iconTooltip: { key: 'generic.tags' },
879
- labels,
880
- }] : [];
973
+ if (labels.length) {
974
+ footerItems.push({
975
+ icon: 'tag-alt',
976
+ iconTooltip: { key: 'generic.tags' },
977
+ labels,
978
+ });
979
+ }
980
+
981
+ return footerItems;
881
982
  },
882
983
 
883
984
  getStatuses(plugin) {
884
985
  const statuses = [];
885
986
 
886
- const errorTooltip = plugin.installedError || plugin.incompatibilityMessage || (plugin.helmError ? this.t('plugins.helmError') : null);
887
- const isDeprecated = plugin?.chart?.deprecated;
987
+ const errorMsg = plugin.installedError || (plugin.helmError ? this.t('plugins.helmError') : null);
988
+ const incompatibilityMsg = plugin.incompatibilityMessage;
989
+ const isDeprecated = uiPluginHasAnnotation(plugin?.chart, CATALOG_ANNOTATIONS.DEPRECATED, 'true');
888
990
 
889
- if (isDeprecated || errorTooltip) {
890
- let tooltip;
991
+ const tooltipMsgs = [];
891
992
 
892
- if (isDeprecated && errorTooltip) {
893
- tooltip = { text: `${ this.t('generic.deprecated') }. ${ this.t('generic.error') }: ${ errorTooltip }` };
894
- } else if (isDeprecated) {
895
- tooltip = { key: 'generic.deprecated' };
896
- } else { // errorTooltip is present
897
- tooltip = { text: `${ this.t('generic.error') }: ${ errorTooltip }` };
898
- }
993
+ if (isDeprecated) {
994
+ tooltipMsgs.push(this.t('generic.deprecated'));
995
+ }
996
+
997
+ if (errorMsg) {
998
+ tooltipMsgs.push(errorMsg);
999
+ } else if (incompatibilityMsg) {
1000
+ tooltipMsgs.push(incompatibilityMsg);
1001
+ }
899
1002
 
1003
+ if (tooltipMsgs.length) {
900
1004
  statuses.push({
901
- icon: 'icon-alert-alt',
902
- color: 'error',
903
- tooltip
1005
+ icon: 'icon-alert-alt',
1006
+ color: 'error',
1007
+ tooltip: { text: tooltipMsgs.join('<br/>') }
904
1008
  });
905
1009
  }
906
1010
 
@@ -436,6 +436,7 @@ describe('class: Resource', () => {
436
436
  }, {
437
437
  getters: { schemaFor: () => ({ linkFor: jest.fn() }) },
438
438
  dispatch: jest.fn(),
439
+ rootState: { $extension: { getPlugins: () => ({}) } },
439
440
  rootGetters: {
440
441
  'i18n/t': (key: string) => key,
441
442
  currentCluster: undefined,
@@ -494,8 +494,9 @@ export default {
494
494
  // Of type @StorePaginationResult
495
495
  const pagination = opt.pagination ? {
496
496
  request: {
497
- namespace: opt.namespaced,
498
- pagination: opt.pagination
497
+ namespace: opt.namespaced,
498
+ pagination: opt.pagination,
499
+ includeAssociatedData: opt.includeAssociatedData,
499
500
  },
500
501
  result: {
501
502
  count: out.count,
@@ -1358,7 +1358,35 @@ export default class Resource {
1358
1358
  return window.$globalApp.$router;
1359
1359
  }
1360
1360
 
1361
+ get isProdRegistrationV2TopLevelProductResoure() {
1362
+ // this is the logic to determine if the resource is top level product or not
1363
+ // changes c-cluster-product-resource to product-c-cluster-resource
1364
+ // this is for the new extension product registration model
1365
+ let currPluginName = '';
1366
+ const plugins = this.$extension.getPlugins();
1367
+
1368
+ Object.keys(plugins).forEach((key) => {
1369
+ if (plugins[key].productNames.includes(this.$rootGetters['productId'])) {
1370
+ currPluginName = key;
1371
+ }
1372
+ });
1373
+
1374
+ // the flag "topLevelProduct" only exists in the V2 product registration model
1375
+ return plugins[currPluginName]?.topLevelProduct || false;
1376
+ }
1377
+
1361
1378
  get listLocation() {
1379
+ if (this.isProdRegistrationV2TopLevelProductResoure) {
1380
+ return {
1381
+ name: `${ this.$rootGetters['productId'] }-c-cluster-resource`,
1382
+ params: {
1383
+ product: this.$rootGetters['productId'],
1384
+ cluster: this.$rootGetters['clusterId'],
1385
+ resource: this.type,
1386
+ }
1387
+ };
1388
+ }
1389
+
1362
1390
  return {
1363
1391
  name: `c-cluster-product-resource`,
1364
1392
  params: {
@@ -1375,6 +1403,20 @@ export default class Resource {
1375
1403
 
1376
1404
  const id = this.id?.replace(/.*\//, '');
1377
1405
 
1406
+ if (this.isProdRegistrationV2TopLevelProductResoure) {
1407
+ return {
1408
+ name: `${ this.$rootGetters['productId'] }-c-cluster-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
1409
+ params: {
1410
+ product: this.$rootGetters['productId'],
1411
+ cluster: this.$rootGetters['clusterId'],
1412
+ resource: this.type,
1413
+ namespace: isNamespaced && this.metadata?.namespace ? this.metadata.namespace : undefined,
1414
+ id,
1415
+ }
1416
+ };
1417
+ }
1418
+
1419
+ // normal cluster scoped resource route as we know
1378
1420
  return {
1379
1421
  name: `c-cluster-product-resource${ schema?.attributes?.namespaced ? '-namespace' : '' }-id`,
1380
1422
  params: {
@@ -1935,6 +1977,25 @@ export default class Resource {
1935
1977
 
1936
1978
  get _glance() {
1937
1979
  const type = this.parentNameOverride || this.$rootGetters['type-map/labelFor'](this.schema);
1980
+ let toRoute = null;
1981
+
1982
+ if (this.isProdRegistrationV2TopLevelProductResoure) {
1983
+ toRoute = {
1984
+ name: `${ this.$rootGetters['productId'] }-c-cluster-resource-id`,
1985
+ params: {
1986
+ product: this.$rootGetters['currentProduct']?.id,
1987
+ cluster: this.$rootGetters['currentCluster']?.id,
1988
+ resource: this.type,
1989
+ }
1990
+ };
1991
+ } else {
1992
+ toRoute = {
1993
+ name: `c-cluster-product-resource-id`,
1994
+ product: this.$rootGetters['currentProduct']?.id,
1995
+ cluster: this.$rootGetters['currentCluster']?.id,
1996
+ resource: this.type
1997
+ };
1998
+ }
1938
1999
 
1939
2000
  return [
1940
2001
  {
@@ -1958,12 +2019,7 @@ export default class Resource {
1958
2019
  label: this.t('component.resource.detail.glance.namespace'),
1959
2020
  formatter: this.$rootGetters['currentProduct']?.id && this.$rootGetters['currentCluster']?.id ? 'Link' : undefined,
1960
2021
  formatterOpts: {
1961
- to: {
1962
- name: `c-cluster-product-resource-id`,
1963
- product: this.$rootGetters['currentProduct']?.id,
1964
- cluster: this.$rootGetters['currentCluster']?.id,
1965
- resource: this.type
1966
- },
2022
+ to: toRoute,
1967
2023
  row: {},
1968
2024
  options: { internal: true }
1969
2025
  },
package/plugins/plugin.js CHANGED
@@ -3,6 +3,8 @@ import { allHashSettled } from '@shell/utils/promise';
3
3
  import { shouldNotLoadPlugin, UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
4
4
  import { getKubeVersionData, getVersionData } from '@shell/config/version';
5
5
  import versions from '@shell/utils/versions';
6
+ import { listProducts, loadProduct } from '@shell/utils/dynamic-importer';
7
+ import { Plugin as UIPlugin } from '@shell/core/plugin';
6
8
 
7
9
  export default async function(context) {
8
10
  if (process.env.excludeOperatorPkg === 'true') {
@@ -43,6 +45,20 @@ export default async function(context) {
43
45
  try {
44
46
  const res = await allHashSettled(fetches);
45
47
 
48
+ // Allow legacy products in shell/config/product to be loaded as if they came from an extension (if migrated to use $init)
49
+ for (const product of listProducts()) {
50
+ const impl = await loadProduct(product);
51
+
52
+ if (impl?.$init) {
53
+ const p = new UIPlugin(product);
54
+
55
+ impl.$init(p);
56
+
57
+ context.$plugin._add(product, p);
58
+ context.$plugin.applyPlugin(p);
59
+ }
60
+ }
61
+
46
62
  // 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
63
  context.$extension.loadBuiltinExtensions();
48
64
 
@@ -23,6 +23,7 @@ import { PaginationSettingsStores } from '@shell/types/resources/settings';
23
23
  import paginationUtils from '@shell/utils/pagination-utils';
24
24
  import { KubeLabelSelector, KubeLabelSelectorExpression } from '@shell/types/kube/kube-api';
25
25
  import { parseField } from '@shell/utils/sort';
26
+ import { POD_LAST_RESTART_FIELD, POD_RESTART_FIELD } from '@shell/types/resources/pod';
26
27
 
27
28
  /**
28
29
  * This is a workaround for a ts build issue found in check-plugins-build.
@@ -240,6 +241,8 @@ class StevePaginationUtils extends NamespaceProjectFilters {
240
241
  [POD]: [
241
242
  { field: 'spec.containers.image' },
242
243
  { field: 'spec.nodeName' },
244
+ { field: POD_RESTART_FIELD },
245
+ { field: POD_LAST_RESTART_FIELD },
243
246
  ],
244
247
  [MANAGEMENT.NODE]: [
245
248
  { field: 'status.nodeName' },
@@ -514,6 +517,10 @@ class StevePaginationUtils extends NamespaceProjectFilters {
514
517
  }
515
518
  }
516
519
 
520
+ if (opt.includeAssociatedData) {
521
+ params.push('includeAssociatedData=true');
522
+ }
523
+
517
524
  // Note - There is a `limit` property that is by default 100,000. This can be disabled by using `limit=-1`,
518
525
  // but we shouldn't be fetching any pages big enough to exceed the default
519
526