@rancher/shell 3.0.10 → 3.0.12-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/assets/styles/base/_mixins.scss +31 -0
  2. package/assets/styles/base/_variables.scss +2 -0
  3. package/assets/styles/themes/_modern.scss +6 -5
  4. package/assets/translations/en-us.yaml +12 -9
  5. package/assets/translations/zh-hans.yaml +0 -3
  6. package/chart/__tests__/rancher-backup-index.test.ts +248 -0
  7. package/chart/rancher-backup/index.vue +41 -2
  8. package/components/BrandImage.vue +6 -5
  9. package/components/ConsumptionGauge.vue +12 -4
  10. package/components/DynamicContent/DynamicContentIcon.vue +3 -2
  11. package/components/EmptyProductPage.vue +76 -0
  12. package/components/ExplorerProjectsNamespaces.vue +1 -4
  13. package/components/LazyImage.vue +2 -1
  14. package/components/Resource/Detail/Card/Scaler.vue +4 -4
  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/SideNav.vue +13 -0
  25. package/components/Tabbed/index.vue +6 -0
  26. package/components/__tests__/ConsumptionGauge.test.ts +31 -0
  27. package/components/__tests__/PromptModal.test.ts +2 -0
  28. package/components/fleet/FleetClusters.vue +1 -0
  29. package/components/fleet/__tests__/FleetClusters.test.ts +71 -0
  30. package/components/form/NodeScheduling.vue +17 -3
  31. package/components/form/PrivateRegistry.vue +69 -0
  32. package/components/form/ProjectMemberEditor.vue +0 -10
  33. package/components/form/__tests__/PrivateRegistry.test.ts +133 -0
  34. package/components/formatter/WorkloadHealthScale.vue +3 -1
  35. package/components/nav/Group.vue +26 -3
  36. package/components/nav/Header.vue +32 -7
  37. package/components/nav/TopLevelMenu.helper.ts +7 -79
  38. package/components/nav/TopLevelMenu.vue +15 -1
  39. package/components/nav/__tests__/TopLevelMenu.helper.test.ts +2 -53
  40. package/config/pagination-table-headers.js +8 -1
  41. package/config/private-label.js +2 -1
  42. package/config/product/apps.js +3 -1
  43. package/config/product/auth.js +1 -0
  44. package/config/product/backup.js +1 -0
  45. package/config/product/compliance.js +1 -1
  46. package/config/product/explorer.js +25 -6
  47. package/config/product/fleet.js +1 -0
  48. package/config/product/gatekeeper.js +1 -0
  49. package/config/product/istio.js +1 -0
  50. package/config/product/logging.js +1 -0
  51. package/config/product/longhorn.js +2 -1
  52. package/config/product/manager.js +1 -0
  53. package/config/product/monitoring.js +1 -0
  54. package/config/product/navlinks.js +1 -0
  55. package/config/product/neuvector.js +2 -1
  56. package/config/product/settings.js +1 -0
  57. package/config/product/uiplugins.js +1 -0
  58. package/core/__tests__/extension-manager-impl.test.js +187 -2
  59. package/core/__tests__/plugin-products-helpers.test.ts +454 -0
  60. package/core/__tests__/plugin-products.test.ts +3219 -0
  61. package/core/extension-manager-impl.js +34 -3
  62. package/core/plugin-helpers.ts +31 -0
  63. package/core/plugin-products-base.ts +375 -0
  64. package/core/plugin-products-extending.ts +44 -0
  65. package/core/plugin-products-helpers.ts +262 -0
  66. package/core/plugin-products-top-level.ts +66 -0
  67. package/core/plugin-products-type-guards.ts +33 -0
  68. package/core/plugin-products.ts +50 -0
  69. package/core/plugin-types.ts +222 -0
  70. package/core/plugin.ts +45 -10
  71. package/core/productDebugger.js +48 -0
  72. package/core/types.ts +95 -11
  73. package/detail/__tests__/__snapshots__/fleet.cattle.io.bundle.test.ts.snap +52 -0
  74. package/detail/__tests__/fleet.cattle.io.bundle.test.ts +171 -0
  75. package/detail/__tests__/node.test.ts +83 -0
  76. package/detail/fleet.cattle.io.bundle.vue +21 -34
  77. package/detail/management.cattle.io.oidcclient.vue +2 -1
  78. package/detail/node.vue +1 -0
  79. package/dialog/ExtensionCatalogInstallDialog.vue +1 -1
  80. package/dialog/InstallExtensionDialog.vue +6 -27
  81. package/dialog/UninstallExistingExtensionDialog.vue +141 -0
  82. package/dialog/UninstallExtensionDialog.vue +4 -26
  83. package/dialog/__tests__/UninstallExistingExtensionDialog.test.ts +114 -0
  84. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -0
  85. package/edit/catalog.cattle.io.clusterrepo.vue +17 -3
  86. package/edit/cloudcredential.vue +2 -1
  87. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -6
  88. package/edit/provisioning.cattle.io.cluster/__tests__/Ingress.test.ts +176 -0
  89. package/edit/provisioning.cattle.io.cluster/index.vue +5 -4
  90. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -1
  91. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -2
  92. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +6 -0
  93. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +7 -2
  94. package/edit/secret/generic.vue +1 -0
  95. package/edit/secret/index.vue +2 -1
  96. package/edit/service.vue +2 -14
  97. package/list/management.cattle.io.feature.vue +7 -1
  98. package/list/provisioning.cattle.io.cluster.vue +0 -50
  99. package/list/workload.vue +11 -4
  100. package/mixins/brand.js +2 -1
  101. package/mixins/resource-fetch.js +12 -3
  102. package/models/catalog.cattle.io.clusterrepo.js +9 -0
  103. package/models/cluster.x-k8s.io.machinedeployment.js +8 -3
  104. package/models/management.cattle.io.authconfig.js +2 -1
  105. package/models/management.cattle.io.cluster.js +4 -3
  106. package/models/monitoring.coreos.com.receiver.js +11 -6
  107. package/models/pod.js +18 -0
  108. package/models/provisioning.cattle.io.cluster.js +2 -2
  109. package/models/workload.js +20 -2
  110. package/package.json +5 -6
  111. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +0 -1
  112. package/pages/c/_cluster/apps/charts/index.vue +3 -8
  113. package/pages/c/_cluster/apps/charts/install.vue +8 -9
  114. package/pages/c/_cluster/istio/index.vue +4 -2
  115. package/pages/c/_cluster/longhorn/index.vue +2 -1
  116. package/pages/c/_cluster/monitoring/index.vue +2 -2
  117. package/pages/c/_cluster/neuvector/index.vue +2 -1
  118. package/pages/c/_cluster/settings/brand.vue +4 -4
  119. package/pages/c/_cluster/settings/performance.vue +0 -5
  120. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +2 -1
  121. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +231 -13
  122. package/pages/c/_cluster/uiplugins/index.vue +145 -38
  123. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -0
  124. package/plugins/dashboard-store/actions.js +3 -2
  125. package/plugins/dashboard-store/resource-class.js +62 -6
  126. package/plugins/plugin.js +16 -0
  127. package/plugins/steve/steve-pagination-utils.ts +8 -2
  128. package/plugins/steve/subscribe.js +29 -4
  129. package/rancher-components/RcButton/RcButton.vue +3 -3
  130. package/rancher-components/RcButtonSplit/RcButtonSplit.test.ts +253 -0
  131. package/rancher-components/RcButtonSplit/RcButtonSplit.vue +158 -0
  132. package/rancher-components/RcButtonSplit/index.ts +1 -0
  133. package/scripts/test-plugins-build.sh +4 -4
  134. package/scripts/typegen.sh +13 -1
  135. package/store/__tests__/type-map.test.ts +84 -24
  136. package/store/type-map.js +42 -3
  137. package/tsconfig.paths.json +1 -0
  138. package/types/resources/pod.ts +18 -0
  139. package/types/shell/index.d.ts +8506 -2908
  140. package/types/store/dashboard-store.types.ts +5 -0
  141. package/types/store/pagination.types.ts +6 -0
  142. package/utils/__tests__/require-asset.test.ts +98 -0
  143. package/utils/async.ts +1 -5
  144. package/utils/axios.js +1 -4
  145. package/utils/brand.ts +3 -1
  146. package/utils/dynamic-importer.js +3 -2
  147. package/utils/favicon.js +4 -3
  148. package/utils/pagination-utils.ts +1 -1
  149. package/utils/require-asset.ts +95 -0
  150. package/utils/uiplugins.ts +12 -16
  151. package/utils/validators/__tests__/private-registry.test.ts +76 -0
  152. package/utils/validators/private-registry.ts +28 -0
  153. package/vue.config.js +4 -3
  154. package/components/HarvesterServiceAddOnConfig.vue +0 -207
@@ -0,0 +1,176 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Ingress from '@shell/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { INGRESS_DUAL, TRAEFIK, INGRESS_NGINX, INGRESS_NONE } from '@shell/edit/provisioning.cattle.io.cluster/shared';
5
+
6
+ jest.mock('vuex', () => ({
7
+ useStore: () => ({ getters: { 'i18n/t': (key: string) => key } }),
8
+ mapGetters: () => ({ t: (key: string) => key })
9
+ }));
10
+
11
+ jest.mock('@shell/edit/provisioning.cattle.io.cluster/shared', () => ({
12
+
13
+ INGRESS_NGINX: 'ingress-nginx',
14
+ TRAEFIK: 'traefik',
15
+ INGRESS_DUAL: 'dual',
16
+ INGRESS_NONE: 'none',
17
+ INGRESS_OPTIONS: [{
18
+ id: 'traefik',
19
+ image: { src: '', alt: 'Traefik' },
20
+ header: { title: { key: 'cluster.ingress.traefik.header' } },
21
+ subHeader: { label: { key: 'cluster.ingress.recommended' } },
22
+ content: { key: 'cluster.ingress.traefik.content' },
23
+ doc: { url: 'https://docs.rke2.io/networking/networking_services?_highlight=ingress#ingress-controller' }
24
+ },
25
+ {
26
+ id: 'ingress-nginx',
27
+ image: { src: '', alt: 'NGINX' },
28
+ header: { title: { key: 'cluster.ingress.nginx.header' } },
29
+ subHeader: { label: { key: 'cluster.ingress.legacy' } },
30
+ content: { key: 'cluster.ingress.nginx.content' },
31
+ doc: { url: 'https://www.kubernetes.dev/blog/2025/11/12/ingress-nginx-retirement/' }
32
+ },
33
+ {
34
+ id: 'dual',
35
+ header: { title: { key: 'cluster.ingress.dual.header' } },
36
+ subHeader: { label: { key: 'cluster.ingress.migration' } },
37
+ content: { key: 'cluster.ingress.dual.content' }
38
+ }],
39
+ INGRESS_MIGRATION_KB_LINK: 'mock-link',
40
+ INGRESS_CONTROLLER_CLASS_MIGRATION: 'rke2.cattle.io/ingress-nginx-migration',
41
+ INGRESS_CLASS_DEFAULT: 'rke2.cattle.io/ingress-nginx-default',
42
+ INGRESS_CONTROLLER_CLASS_DEFAULT: 'rke2.cattle.io/ingress-nginx-controller-default',
43
+ INGRESS_CLASS_MIGRATION: 'rke2.cattle.io/ingress-nginx-migration'
44
+ }));
45
+
46
+ describe('ingress.vue', () => {
47
+ const defaultProps = {
48
+ mode: _EDIT,
49
+ value: INGRESS_NONE,
50
+ nginxSupported: true,
51
+ traefikSupported: true,
52
+ nginxChart: 'rancher-ingress-nginx',
53
+ traefikChart: 'traefik',
54
+ userChartValues: {},
55
+ versionInfo: {
56
+ 'rancher-ingress-nginx': { values: {} },
57
+ traefik: { values: {} }
58
+ }
59
+ };
60
+
61
+ const createWrapper = (props = {}) => mount(Ingress, {
62
+ props: { ...defaultProps, ...props },
63
+ global: {
64
+ stubs: {
65
+ Checkbox: true,
66
+ Banner: true,
67
+ IngressCards: true,
68
+ IngressConfiguration: true,
69
+ YamlEditor: true,
70
+ RichTranslation: true
71
+ }
72
+ }
73
+ });
74
+
75
+ it('renders checkbox to enable/disable ingress', () => {
76
+ const wrapper = createWrapper();
77
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
78
+
79
+ expect(checkbox.exists()).toBe(true);
80
+ });
81
+
82
+ it('emits update:value with INGRESS_NONE when ingress is disabled', async() => {
83
+ const wrapper = createWrapper({ value: TRAEFIK });
84
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
85
+
86
+ await checkbox.vm.$emit('update:value', false);
87
+
88
+ expect(wrapper.emitted('update:value')).toBeTruthy();
89
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NONE]);
90
+ });
91
+
92
+ it('emits update:value with TRAEFIK when ingress is enabled and traefik is supported', async() => {
93
+ const wrapper = createWrapper({ value: INGRESS_NONE });
94
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
95
+
96
+ await checkbox.vm.$emit('update:value', true);
97
+
98
+ expect(wrapper.emitted('update:value')).toBeTruthy();
99
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([TRAEFIK]);
100
+ });
101
+
102
+ it('emits update:value with INGRESS_NGINX when ingress is enabled, traefik is NOT supported, and nginx IS supported', async() => {
103
+ const wrapper = createWrapper({ value: INGRESS_NONE, traefikSupported: false });
104
+ const checkbox = wrapper.findComponent({ name: 'Checkbox' });
105
+
106
+ await checkbox.vm.$emit('update:value', true);
107
+
108
+ expect(wrapper.emitted('update:value')).toBeTruthy();
109
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
110
+ });
111
+
112
+ it('selectIngress emits [INGRESS_NGINX, TRAEFIK] string value when INGRESS_DUAL is selected and previous value was ingress-nginx', () => {
113
+ const wrapper = createWrapper({ value: INGRESS_NGINX, originalIngressController: INGRESS_NGINX });
114
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
115
+
116
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
117
+
118
+ expect(wrapper.emitted('update:value')).toBeTruthy();
119
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([[INGRESS_NGINX, TRAEFIK]]);
120
+ });
121
+
122
+ it('selectIngress emits [TRAEFIK, INGRESS_NGINX] string value when INGRESS_DUAL is selected and previous value was traefik', () => {
123
+ const wrapper = createWrapper({ value: TRAEFIK, originalIngressController: TRAEFIK });
124
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
125
+
126
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
127
+
128
+ expect(wrapper.emitted('update:value')).toBeTruthy();
129
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([[TRAEFIK, INGRESS_NGINX]]);
130
+ });
131
+
132
+ it('selectIngress emits [TRAEFIK, INGRESS_NGINX] string value when INGRESS_DUAL is selected and value went traefik -> nginx -> dual', () => {
133
+ const wrapper = createWrapper({ value: TRAEFIK, originalIngressController: TRAEFIK });
134
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
135
+
136
+ ingressCards.vm.$emit('select', INGRESS_NGINX);
137
+ expect(wrapper.emitted('update:value')).toBeTruthy();
138
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
139
+
140
+ ingressCards.vm.$emit('select', INGRESS_DUAL);
141
+
142
+ expect(wrapper.emitted('update:value')).toBeTruthy();
143
+ expect(wrapper.emitted('update:value')?.[1]).toStrictEqual([[TRAEFIK, INGRESS_NGINX]]);
144
+ });
145
+
146
+ it('selectIngress emits string value when a single ingress is selected', () => {
147
+ const wrapper = createWrapper({ value: TRAEFIK });
148
+ const ingressCards = wrapper.findComponent({ name: 'IngressCards' });
149
+
150
+ ingressCards.vm.$emit('select', INGRESS_NGINX);
151
+
152
+ expect(wrapper.emitted('update:value')).toBeTruthy();
153
+ expect(wrapper.emitted('update:value')?.[0]).toStrictEqual([INGRESS_NGINX]);
154
+ });
155
+
156
+ it('renders IngressConfiguration when versionInfo contains chart values', () => {
157
+ const wrapper = createWrapper({ value: TRAEFIK });
158
+ const config = wrapper.findComponent({ name: 'IngressConfiguration' });
159
+
160
+ expect(config.exists()).toBe(true);
161
+ });
162
+
163
+ it('toggles advanced configuration visibility and renders YamlEditor', async() => {
164
+ const wrapper = createWrapper({ value: TRAEFIK });
165
+
166
+ expect(wrapper.findComponent({ name: 'YamlEditor' }).exists()).toBe(false);
167
+
168
+ const advancedButton = wrapper.find('.advanced-toggle');
169
+
170
+ await advancedButton.trigger('click');
171
+
172
+ const yamlEditor = wrapper.find('[data-testid="traefik-yaml-editor"]');
173
+
174
+ expect(yamlEditor.exists()).toBe(true);
175
+ });
176
+ });
@@ -20,6 +20,7 @@ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
20
20
  import { ELEMENTAL_PRODUCT_NAME, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
21
21
  import Rke2Config from './rke2';
22
22
  import { DRIVER_TO_IMPORT } from '@shell/models/management.cattle.io.kontainerdriver';
23
+ import { requireAsset } from '@shell/utils/require-asset';
23
24
 
24
25
  const SORT_GROUPS = {
25
26
  template: 1,
@@ -343,7 +344,7 @@ export default {
343
344
  id: `chart:${ chart.id }`,
344
345
  label: chart.chartNameDisplay,
345
346
  description: chart.chartDescription,
346
- icon: chart.icon || require('~shell/assets/images/generic-catalog.svg'),
347
+ icon: chart.icon || requireAsset('~shell/assets/images/generic-catalog.svg'),
347
348
  group: 'template',
348
349
  tag: getters['i18n/t']('generic.techPreview')
349
350
  });
@@ -389,7 +390,7 @@ export default {
389
390
  if (icon) {
390
391
  iconClass = undefined;
391
392
  } else if (!iconClass) {
392
- icon = require('~shell/assets/images/generic-driver.svg');
393
+ icon = requireAsset('~shell/assets/images/generic-driver.svg');
393
394
  }
394
395
 
395
396
  const subtype = {
@@ -420,14 +421,14 @@ export default {
420
421
 
421
422
  if (!icon) {
422
423
  try {
423
- icon = require(`~shell/assets/images/providers/${ id }.svg`);
424
+ icon = requireAsset(`~shell/assets/images/providers/${ id }.svg`);
424
425
  } catch (e) {}
425
426
  }
426
427
 
427
428
  if (icon) {
428
429
  iconClass = undefined;
429
430
  } else if (!iconClass) {
430
- icon = require('~shell/assets/images/generic-driver.svg');
431
+ icon = requireAsset('~shell/assets/images/generic-driver.svg');
431
432
  }
432
433
 
433
434
  const subtype = {
@@ -297,7 +297,8 @@ export default {
297
297
  originalKubeVersion: null,
298
298
  isEmpty,
299
299
  AGENT_CONFIGURATION_TYPES,
300
- basicsValid: true
300
+ basicsValid: true,
301
+ originalIngressController: this.value.spec.rkeConfig.machineGlobalConfig?.[INGRESS_CONTROLLER] || INGRESS_NONE,
301
302
  };
302
303
  },
303
304
 
@@ -2522,6 +2523,7 @@ export default {
2522
2523
  <Tab
2523
2524
  v-if="!obj.remove"
2524
2525
  :key="obj.id"
2526
+ :weight="-1 * idx"
2525
2527
  :name="obj.id"
2526
2528
  :label="obj.pool.name || '(Not Named)'"
2527
2529
  :show-header="false"
@@ -2592,6 +2594,7 @@ export default {
2592
2594
  :is-azure-provider-unsupported="isAzureProviderUnsupported"
2593
2595
  :can-azure-migrate-on-edit="canAzureMigrateOnEdit"
2594
2596
  :has-some-ipv6-pools="hasOnlyIpv6Pools"
2597
+ :original-ingress-controller="originalIngressController"
2595
2598
  @update:value="$emit('input', $event)"
2596
2599
  @cilium-values-changed="handleCiliumValuesChanged"
2597
2600
  @enabled-system-services-changed="handleEnabledSystemServicesChanged"
@@ -1,3 +1,5 @@
1
+ import { requireAsset } from '@shell/utils/require-asset';
2
+
1
3
  export const RETENTION_DEFAULT = 5;
2
4
  export const RKE2_INGRESS_NGINX = 'rke2-ingress-nginx';
3
5
  export const RKE2_TRAEFIK = 'rke2-traefik';
@@ -10,7 +12,7 @@ export const INGRESS_NONE = 'none';
10
12
  export const INGRESS_OPTIONS = [
11
13
  {
12
14
  id: TRAEFIK,
13
- image: { src: require('@shell/assets/images/providers/traefik.png'), alt: 'Traefik' },
15
+ image: { src: requireAsset('@shell/assets/images/providers/traefik.png'), alt: 'Traefik' },
14
16
  header: { title: { key: 'cluster.ingress.traefik.header' } },
15
17
  subHeader: { label: { key: 'cluster.ingress.recommended' } },
16
18
  content: { key: 'cluster.ingress.traefik.content' },
@@ -18,7 +20,7 @@ export const INGRESS_OPTIONS = [
18
20
  },
19
21
  {
20
22
  id: INGRESS_NGINX,
21
- image: { src: require('@shell/assets/images/providers/kubernetes.svg'), alt: 'NGINX' },
23
+ image: { src: requireAsset('@shell/assets/images/providers/kubernetes.svg'), alt: 'NGINX' },
22
24
  header: { title: { key: 'cluster.ingress.nginx.header' } },
23
25
  subHeader: { label: { key: 'cluster.ingress.legacy' } },
24
26
  content: { key: 'cluster.ingress.nginx.content' },
@@ -120,6 +120,11 @@ export default {
120
120
  canAzureMigrateOnEdit: {
121
121
  type: Boolean,
122
122
  required: true
123
+ },
124
+ originalIngressController: {
125
+ type: [String, Array],
126
+ required: false,
127
+ default: INGRESS_NONE
123
128
  }
124
129
  },
125
130
 
@@ -699,6 +704,7 @@ export default {
699
704
  :traefik-chart="traefikChart"
700
705
  :user-chart-values="userChartValues"
701
706
  :version-info="versionInfo"
707
+ :original-ingress-controller="originalIngressController"
702
708
  @update-values="(name, val) => $emit('update-values', name, val)"
703
709
  @error="$emit('error', $event)"
704
710
  @yaml-validation-changed="e => $emit('yaml-validation-changed', e)"
@@ -24,6 +24,7 @@ interface Props {
24
24
  traefikChart: string;
25
25
  userChartValues: any;
26
26
  versionInfo: any;
27
+ originalIngressController?: string | string[];
27
28
  }
28
29
  const {
29
30
  mode = _CREATE,
@@ -33,7 +34,8 @@ const {
33
34
  nginxSupported,
34
35
  traefikSupported,
35
36
  userChartValues,
36
- versionInfo
37
+ versionInfo,
38
+ originalIngressController = INGRESS_NONE
37
39
  } = defineProps<Props>();
38
40
 
39
41
  const emit = defineEmits(['update:value', 'error', 'config-validation-changed', 'yaml-validation-changed', 'update-values']);
@@ -179,7 +181,10 @@ const compatibilityMode = computed({
179
181
 
180
182
  function selectIngress(id: string) {
181
183
  if ( id === INGRESS_DUAL) {
182
- emit('update:value', [TRAEFIK, INGRESS_NGINX]);
184
+ const newValue: string | string[] = !Array.isArray(originalIngressController) ? (originalIngressController === TRAEFIK ? [TRAEFIK, INGRESS_NGINX] : [INGRESS_NGINX, TRAEFIK]) : originalIngressController;
185
+
186
+ emit('update:value', newValue);
187
+
183
188
  preconfigureTraefik();
184
189
  } else {
185
190
  emit('update:value', id);
@@ -51,6 +51,7 @@ export default {
51
51
  v-model:value="value.data"
52
52
  :mode="mode"
53
53
  :initial-empty-row="true"
54
+ :value-can-be-empty="true"
54
55
  :handle-base64="true"
55
56
  :value-trim="false"
56
57
  :add-allowed="true"
@@ -1,5 +1,6 @@
1
1
  <script>
2
2
  import { SECRET_TYPES as TYPES } from '@shell/config/secret';
3
+ import { requireAsset } from '@shell/utils/require-asset';
3
4
  import {
4
5
  SECRET_SCOPE, SECRET_QUERY_PARAMS,
5
6
  CLOUD_CREDENTIAL, _CLONE, _CREATE, _EDIT, _FLAGGED
@@ -211,7 +212,7 @@ export default {
211
212
  let bannerImage, bannerAbbrv;
212
213
 
213
214
  try {
214
- bannerImage = require(`~shell/assets/images/providers/${ id }.svg`);
215
+ bannerImage = requireAsset(`~shell/assets/images/providers/${ id }.svg`);
215
216
  } catch (e) {
216
217
  bannerImage = null;
217
218
  bannerAbbrv = this.initialDisplayFor(id);
package/edit/service.vue CHANGED
@@ -18,7 +18,6 @@ import { ucFirst } from '@shell/utils/string';
18
18
  import CruResource from '@shell/components/CruResource';
19
19
  import { Banner } from '@components/Banner';
20
20
  import Labels from '@shell/components/form/Labels';
21
- import HarvesterServiceAddOnConfig from '@shell/components/HarvesterServiceAddOnConfig';
22
21
  import { clone } from '@shell/utils/object';
23
22
  import { POD, CAPI, HCI } from '@shell/config/types';
24
23
  import { matching } from '@shell/utils/selector-typed';
@@ -58,7 +57,6 @@ export default {
58
57
  Tab,
59
58
  Tabbed,
60
59
  UnitInput,
61
- HarvesterServiceAddOnConfig,
62
60
  },
63
61
 
64
62
  mixins: [CreateEditView, FormValidation],
@@ -350,8 +348,10 @@ export default {
350
348
 
351
349
  <Tabbed
352
350
  :side-tabs="true"
351
+ :resource="value"
353
352
  :use-hash="useTabbedHash"
354
353
  :default-tab="defaultTab"
354
+ :extension-params="{ showHarvesterAddOnConfig: String(showHarvesterAddOnConfig) }"
355
355
  >
356
356
  <Tab
357
357
  v-if="checkTypeIs('ExternalName')"
@@ -471,18 +471,6 @@ export default {
471
471
  </div>
472
472
  </div>
473
473
  </Tab>
474
- <Tab
475
- v-if="showHarvesterAddOnConfig"
476
- name="add-on-config"
477
- :label="t('servicesPage.harvester.title')"
478
- :weight="-1"
479
- >
480
- <HarvesterServiceAddOnConfig
481
- :mode="mode"
482
- :value="value"
483
- :register-before-hook="registerBeforeHook"
484
- />
485
- </Tab>
486
474
  <Tab
487
475
  v-if="!checkTypeIs('ExternalName') && !checkTypeIs('Headless')"
488
476
  name="session-affinity"
@@ -3,6 +3,12 @@ import { mapGetters } from 'vuex';
3
3
  import ResourceTable from '@shell/components/ResourceTable';
4
4
  import { MANAGEMENT } from '@shell/config/types';
5
5
  import ResourceFetch from '@shell/mixins/resource-fetch';
6
+ import { STEVE_CACHE } from '@shell/store/features';
7
+
8
+ const hideFeatureFlags = [
9
+ 'fleet', // Note - this is the id of the ff we want, not sure what FLEET in store/features is
10
+ STEVE_CACHE
11
+ ];
6
12
 
7
13
  export default {
8
14
  components: { ResourceTable },
@@ -32,7 +38,7 @@ export default {
32
38
  ...mapGetters({ t: 'i18n/t' }),
33
39
 
34
40
  filteredRows() {
35
- return this.rows.filter((x) => x.name !== 'fleet');
41
+ return this.rows.filter((x) => hideFeatureFlags.indexOf(x.metadata.name) === -1);
36
42
  },
37
43
 
38
44
  enableRowActions() {
@@ -167,12 +167,6 @@ export default {
167
167
 
168
168
  },
169
169
 
170
- methods: {
171
- getCustomDetailLink(cluster) {
172
- return cluster.isCapiHybrid ? null : cluster.detailLocation;
173
- }
174
- },
175
-
176
170
  $loadingResources() {
177
171
  // results are filtered so we wouldn't get the correct count on indicator...
178
172
  return { loadIndeterminate: true };
@@ -226,8 +220,6 @@ export default {
226
220
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
227
221
  :data-testid="'cluster-list'"
228
222
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
229
- :get-custom-detail-link="getCustomDetailLink"
230
- :sub-rows="true"
231
223
  >
232
224
  <!-- Why are state column and subrow overwritten here? -->
233
225
  <!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
@@ -273,48 +265,6 @@ export default {
273
265
  {{ t('cluster.explore') }}
274
266
  </button>
275
267
  </template>
276
- <template #additional-sub-row="{row, fullColspan, tableActions}">
277
- <tr
278
- class="capi-unsupported"
279
- :class="{'has-description': !!row.stateDescription}"
280
- >
281
- <td
282
- v-if="row.isCapiHybrid"
283
- class="row-check"
284
- />
285
- <td
286
- v-if="row.isCapiHybrid"
287
- :data-testid="`capi-unsupported-warning-${row?.metadata?.name}`"
288
- :colspan="fullColspan - (tableActions ? 1: 0)"
289
- >
290
- <div
291
- class="text-warning"
292
- :class="{'mt-5': !!row.stateDescription.trim()}"
293
- >
294
- <i class="icon icon-warning" />{{ t('cluster.capi.notSupported') }}
295
- </div>
296
- </td>
297
- </tr>
298
- </template>
299
268
  </ResourceTable>
300
269
  </div>
301
270
  </template>
302
-
303
- <style scoped lang="scss">
304
- .capi-unsupported {
305
- &.has-description {
306
- border-bottom: none;
307
- padding: 0px;
308
- td {
309
- padding-top: 0px;
310
- }
311
- }
312
-
313
- & div {
314
- & i {
315
- margin-right: 0.1em;
316
- }
317
- display: flex;
318
- }
319
- }
320
- </style>
package/list/workload.vue CHANGED
@@ -99,13 +99,21 @@ export default {
99
99
  const schema = type !== workloadSchema.id ? this.$store.getters['cluster/schemaFor'](type) : workloadSchema;
100
100
  const paginationEnabled = !allTypes && this.$store.getters[`cluster/paginationEnabled`]?.({ id: type });
101
101
 
102
+ const workloadIncludeAssociatedData = paginationEnabled && [
103
+ WORKLOAD_TYPES.DEPLOYMENT,
104
+ WORKLOAD_TYPES.DAEMON_SET,
105
+ WORKLOAD_TYPES.STATEFUL_SET,
106
+ WORKLOAD_TYPES.JOB,
107
+ ].includes(type);
108
+
102
109
  return {
103
110
  allTypes,
104
111
  schema,
105
112
  paginationEnabled,
106
113
  resources: [],
107
114
  loadResources,
108
- loadIndeterminate
115
+ loadIndeterminate,
116
+ workloadIncludeAssociatedData
109
117
  };
110
118
  },
111
119
 
@@ -143,10 +151,8 @@ export default {
143
151
  * Fetch resources required to populate POD_RESTARTS and WORKLOAD_HEALTH_SCALE columns
144
152
  */
145
153
  loadHeathResources() {
146
- // See https://github.com/rancher/dashboard/issues/10417, health comes from selectors applied locally to all pods (bad)
147
154
  if (this.paginationEnabled) {
148
- // Unfortunately with SSP enabled we cannot fetch all pods to then let each row find applicable pods by locally applied selectors (bad for scaling)
149
- // See https://github.com/rancher/dashboard/issues/14211
155
+ // When SSP is enabled we efficiently fetch stats for health column imbedded in the original resource type by supplying `includeAssociatedData` param
150
156
  return;
151
157
  }
152
158
 
@@ -184,6 +190,7 @@ export default {
184
190
  v-if="paginationEnabled"
185
191
  :schema="schema"
186
192
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
193
+ :includeAssociatedData="workloadIncludeAssociatedData"
187
194
  />
188
195
  <ResourceTable
189
196
  v-else
package/mixins/brand.js CHANGED
@@ -3,6 +3,7 @@ import { MANAGEMENT } from '@shell/config/types';
3
3
  import { SETTING } from '@shell/config/settings';
4
4
  import { createCssVars } from '@shell/utils/color';
5
5
  import { setTitle } from '@shell/config/private-label';
6
+ import { requireJson } from '@shell/utils/require-asset';
6
7
  import { setFavIcon, haveSetFavIcon } from '@shell/utils/favicon';
7
8
  import { allHash } from '@shell/utils/promise';
8
9
  import { fetchInitialSettings } from '@shell/utils/settings';
@@ -187,7 +188,7 @@ export default {
187
188
 
188
189
  if ( this.brand ) {
189
190
  try {
190
- const brandMeta = require(`~shell/assets/brand/${ this.brand }/metadata.json`);
191
+ const brandMeta = requireJson(`~shell/assets/brand/${ this.brand }/metadata.json`);
191
192
 
192
193
  if (brandMeta?.hasStylesheet === 'true') {
193
194
  bodyClass = `${ cssClass } ${ this.brand } theme-${ this.theme }`;
@@ -87,6 +87,14 @@ export default {
87
87
  type: Function,
88
88
  default: null,
89
89
  },
90
+
91
+ /**
92
+ * When making a supporting HTTP request include associated resource data
93
+ */
94
+ includeAssociatedData: {
95
+ type: Boolean,
96
+ default: false,
97
+ },
90
98
  },
91
99
 
92
100
  computed: {
@@ -185,9 +193,10 @@ export default {
185
193
  return;
186
194
  }
187
195
  const opt = {
188
- hasManualRefresh: this.hasManualRefresh,
189
- pagination: { ...this.pagination },
190
- force: this.paginating !== null // Fix for manual refresh (before ripped out).
196
+ hasManualRefresh: this.hasManualRefresh,
197
+ pagination: { ...this.pagination },
198
+ force: this.paginating !== null, // Fix for manual refresh (before ripped out).
199
+ includeAssociatedData: this.includeAssociatedData,
191
200
  };
192
201
 
193
202
  if (this.apiFilter) {
@@ -3,6 +3,7 @@ import { CATALOG } from '@shell/config/labels-annotations';
3
3
  import { insertAt } from '@shell/utils/array';
4
4
  import { CLUSTER_REPO_APPCO_AUTH_GENERATE_NAME, CATALOG as CATALOG_TYPE } from '@shell/config/types';
5
5
  import { colorForState, stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
6
+ import { _CREATE } from '@shell/config/query-params';
6
7
 
7
8
  import SteveModel from '@shell/plugins/steve/steve-class';
8
9
 
@@ -173,6 +174,14 @@ export default class ClusterRepo extends SteveModel {
173
174
  return this.$rootGetters['i18n/withFallback'](key, null, name);
174
175
  }
175
176
 
177
+ detailPageHeaderActionOverride(realMode) {
178
+ if (realMode === _CREATE) {
179
+ return this.t('catalog.repo.add');
180
+ }
181
+
182
+ return null;
183
+ }
184
+
176
185
  get urlDisplay() {
177
186
  return this.status?.url || this.spec.gitRepo || this.spec.url;
178
187
  }
@@ -111,12 +111,17 @@ export default class CapiMachineDeployment extends SteveModel {
111
111
  return `${ this.spec?.template?.metadata?.labels?.[MACHINE_ROLES.ETCD] }` === 'true';
112
112
  }
113
113
 
114
- // use this pool's definition in the cluster's rkeConfig to scale, not this.spec.replicas
114
+ // use this pool's definition in the provisioning cluster spec to scale, not this.spec.replicas
115
115
  get inClusterSpec() {
116
- const machineConfigName = this.template?.metadata?.annotations['rke.cattle.io/cloned-from-name'];
116
+ // infra from Rancher node driver: provisioning cluster has reference to Rancher-generated crd <provider name>Config from the rke-machine-config.cattle.io api group
117
+ const rkeMachineConfigName = this.template?.metadata?.annotations['rke.cattle.io/cloned-from-name'];
118
+ // infra from upstream CAPI provider: provisioning cluster has reference to an upstream provider-specific machine template crd in the infrastructure.cluster.x-k8s.io api group
119
+ const infrastructureRefName = this.spec?.template?.spec?.infrastructureRef?.name;
120
+ const machineTemplateName = rkeMachineConfigName || infrastructureRefName;
121
+
117
122
  const machinePools = this.cluster.spec.rkeConfig.machinePools;
118
123
 
119
- return machinePools.find((pool) => pool.machineConfigRef.name === machineConfigName);
124
+ return machinePools.find((pool) => pool.machineConfigRef.name === machineTemplateName);
120
125
  }
121
126
 
122
127
  scalePool(delta, save = true, depth = 0) {
@@ -1,5 +1,6 @@
1
1
  import { insertAt } from '@shell/utils/array';
2
2
  import SteveModel from '@shell/plugins/steve/steve-class';
3
+ import { requireAsset } from '@shell/utils/require-asset';
3
4
 
4
5
  export const configType = {
5
6
  activedirectory: 'ldap',
@@ -56,7 +57,7 @@ export default class AuthConfig extends SteveModel {
56
57
 
57
58
  get icon() {
58
59
  try {
59
- return require(`~shell/assets/images/vendor/${ imageOverrides[this.id] || this.id }.svg`);
60
+ return requireAsset(`~shell/assets/images/vendor/${ imageOverrides[this.id] || this.id }.svg`);
60
61
  } catch (e) {
61
62
  return '';
62
63
  }
@@ -13,6 +13,7 @@ import { isHarvesterCluster } from '@shell/utils/cluster';
13
13
  import SteveModel from '@shell/plugins/steve/steve-class';
14
14
  import { LINUX, WINDOWS } from '@shell/store/catalog';
15
15
  import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
16
+ import { requireAsset } from '@shell/utils/require-asset';
16
17
  import { PINNED_CLUSTERS } from '@shell/store/prefs';
17
18
  import { copyTextToClipboard } from '@shell/utils/clipboard';
18
19
  import { isHostedProvider } from '@shell/utils/provider';
@@ -221,7 +222,7 @@ export default class MgmtCluster extends SteveModel {
221
222
  }
222
223
 
223
224
  get providerOsLogo() {
224
- return require(`~shell/assets/images/vendor/${ this.providerOs }.svg`);
225
+ return requireAsset(`~shell/assets/images/vendor/${ this.providerOs }.svg`);
225
226
  }
226
227
 
227
228
  get workerOSs() {
@@ -282,11 +283,11 @@ export default class MgmtCluster extends SteveModel {
282
283
  let icon;
283
284
 
284
285
  try {
285
- icon = require(`~shell/assets/images/providers/${ prv }.svg`);
286
+ icon = requireAsset(`~shell/assets/images/providers/${ prv }.svg`);
286
287
  } catch (e) {
287
288
  console.warn(`Can not find provider logo for provider ${ logo }`); // eslint-disable-line no-console
288
289
  // Use fallback generic Kubernetes icon
289
- icon = require(`~shell/assets/images/providers/kubernetes.svg`);
290
+ icon = requireAsset(`~shell/assets/images/providers/kubernetes.svg`);
290
291
  }
291
292
 
292
293
  return icon;