@rancher/shell 0.3.16 → 0.3.18

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 (174) hide show
  1. package/assets/images/wechat-qr-code.jpg +0 -0
  2. package/assets/translations/en-us.yaml +75 -16
  3. package/assets/translations/zh-hans.yaml +151 -15
  4. package/chart/__tests__/S3.test.ts +50 -0
  5. package/chart/rancher-backup/S3.vue +21 -0
  6. package/chart/rancher-backup/index.vue +4 -0
  7. package/components/AsyncButton.vue +1 -1
  8. package/components/CommunityLinks.vue +1 -0
  9. package/components/FileDiff.vue +92 -85
  10. package/components/Inactivity.vue +10 -0
  11. package/components/LazyImage.vue +2 -2
  12. package/components/PromptRestore.vue +7 -5
  13. package/components/ResourceDetail/Masthead.vue +1 -1
  14. package/components/ResourceDetail/index.vue +8 -14
  15. package/components/ResourceList/index.vue +1 -1
  16. package/components/ResourceTable.vue +50 -2
  17. package/components/YamlEditor.vue +1 -0
  18. package/components/__tests__/PromptRestore.test.ts +72 -0
  19. package/components/auth/AzureWarning.vue +1 -1
  20. package/components/auth/RoleDetailEdit.vue +1 -0
  21. package/components/fleet/FleetResources.vue +3 -64
  22. package/components/form/FileImageSelector.vue +9 -0
  23. package/components/form/FileSelector.vue +2 -1
  24. package/components/form/MatchExpressions.vue +1 -3
  25. package/components/form/NameNsDescription.vue +28 -12
  26. package/components/form/NodeAffinity.vue +2 -2
  27. package/components/form/PodAffinity.vue +2 -2
  28. package/components/form/ResourceTabs/index.vue +8 -2
  29. package/components/form/Select.vue +16 -0
  30. package/components/form/__tests__/FileImageSelector.test.ts +42 -0
  31. package/components/form/__tests__/FileSelector.test.ts +76 -0
  32. package/components/form/__tests__/NodeAffinity.test.ts +38 -0
  33. package/components/form/__tests__/PodAffinity.test.ts +46 -0
  34. package/components/formatter/ClusterLink.vue +8 -4
  35. package/components/formatter/ClusterProvider.vue +3 -1
  36. package/components/formatter/ImageName.vue +23 -0
  37. package/components/formatter/PodImages.vue +7 -1
  38. package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
  39. package/components/formatter/__tests__/ClusterProvider.test.ts +24 -0
  40. package/components/nav/Header.vue +2 -2
  41. package/components/nav/WindowManager/ContainerShell.vue +60 -36
  42. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +561 -0
  43. package/config/__test__/home-links.test.ts +62 -0
  44. package/config/home-links.js +15 -3
  45. package/config/labels-annotations.js +7 -2
  46. package/config/persistentVolume.ts +108 -0
  47. package/config/product/manager.js +5 -1
  48. package/config/router.js +0 -4
  49. package/config/settings.ts +4 -0
  50. package/config/table-headers.js +6 -5
  51. package/config/types.js +2 -0
  52. package/config/uiplugins.js +50 -5
  53. package/core/plugin-helpers.js +39 -15
  54. package/core/plugin.ts +9 -0
  55. package/core/plugins.js +1 -1
  56. package/core/types-provisioning.ts +253 -0
  57. package/core/types.ts +21 -3
  58. package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
  59. package/detail/fleet.cattle.io.gitrepo.vue +10 -2
  60. package/detail/node.vue +6 -6
  61. package/detail/pod.vue +38 -9
  62. package/detail/provisioning.cattle.io.cluster.vue +46 -7
  63. package/detail/workload/index.vue +49 -18
  64. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
  65. package/edit/__tests__/ui.cattle.io.navlink.test.ts +110 -0
  66. package/edit/auth/github.vue +1 -0
  67. package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
  68. package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
  69. package/edit/fleet.cattle.io.clustergroup.vue +14 -3
  70. package/edit/fleet.cattle.io.gitrepo.vue +18 -1
  71. package/edit/namespace.vue +9 -1
  72. package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
  73. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +82 -0
  74. package/edit/persistentvolume/index.vue +2 -1
  75. package/edit/persistentvolume/plugins/csi.vue +3 -1
  76. package/edit/persistentvolume/plugins/longhorn.vue +12 -12
  77. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
  78. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +15 -11
  79. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
  80. package/edit/provisioning.cattle.io.cluster/index.vue +53 -1
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +335 -151
  82. package/edit/storage.k8s.io.storageclass/index.vue +1 -2
  83. package/edit/ui.cattle.io.navlink.vue +213 -186
  84. package/initialize/App.js +3 -13
  85. package/initialize/layouts.ts +26 -0
  86. package/layouts/default.vue +1 -1
  87. package/list/group.principal.vue +1 -1
  88. package/list/provisioning.cattle.io.cluster.vue +8 -1
  89. package/middleware/authenticated.js +101 -5
  90. package/mixins/brand.js +39 -3
  91. package/mixins/child-hook.js +2 -2
  92. package/mixins/create-edit-view/impl.js +4 -4
  93. package/models/chart.js +1 -1
  94. package/models/fleet.cattle.io.cluster.js +33 -4
  95. package/models/fleet.cattle.io.gitrepo.js +113 -38
  96. package/models/management.cattle.io.kontainerdriver.js +14 -0
  97. package/models/persistentvolume.js +2 -111
  98. package/models/pod.js +30 -0
  99. package/models/provisioning.cattle.io.cluster.js +9 -1
  100. package/models/rke.cattle.io.etcdsnapshot.js +10 -7
  101. package/package.json +2 -2
  102. package/pages/about.vue +8 -2
  103. package/pages/auth/login.vue +1 -1
  104. package/pages/auth/logout.vue +11 -3
  105. package/pages/c/_cluster/apps/charts/index.vue +5 -2
  106. package/pages/c/_cluster/apps/charts/install.vue +5 -0
  107. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  108. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  109. package/pages/c/_cluster/explorer/index.vue +2 -11
  110. package/pages/c/_cluster/manager/cloudCredential/_id.vue +0 -1
  111. package/pages/c/_cluster/manager/cloudCredential/create.vue +0 -1
  112. package/pages/c/_cluster/settings/brand.vue +11 -8
  113. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
  114. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
  115. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
  116. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
  117. package/pages/c/_cluster/uiplugins/index.vue +160 -44
  118. package/pages/docs/_doc.vue +9 -3
  119. package/pages/home.vue +6 -6
  120. package/pages/support/index.vue +10 -4
  121. package/pkg/auto-import.js +1 -1
  122. package/plugins/clean-tooltip-directive.js +1 -1
  123. package/plugins/dashboard-store/__tests__/actions.spec.ts +165 -0
  124. package/plugins/dashboard-store/__tests__/getters.spec.ts +100 -0
  125. package/plugins/dashboard-store/__tests__/{mutations.spec.js → mutations.spec.ts} +2 -2
  126. package/plugins/dashboard-store/actions.js +1 -1
  127. package/plugins/dashboard-store/resource-class.js +39 -2
  128. package/plugins/plugin.js +9 -1
  129. package/plugins/steve/__tests__/getters.spec.ts +93 -0
  130. package/plugins/steve/getters.js +21 -1
  131. package/plugins/steve/subscribe.js +1 -3
  132. package/rancher-components/BadgeState/BadgeState.vue +5 -1
  133. package/rancher-components/Banner/Banner.test.ts +51 -1
  134. package/rancher-components/Banner/Banner.vue +134 -53
  135. package/rancher-components/Card/Card.test.ts +37 -0
  136. package/rancher-components/Card/Card.vue +24 -7
  137. package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
  138. package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
  139. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
  140. package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
  141. package/rancher-components/Form/Radio/RadioButton.test.ts +31 -0
  142. package/rancher-components/Form/Radio/RadioButton.vue +30 -13
  143. package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
  144. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
  145. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
  146. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
  147. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
  148. package/rancher-components/StringList/StringList.test.ts +453 -49
  149. package/rancher-components/StringList/StringList.vue +44 -26
  150. package/scripts/extension/publish +2 -2
  151. package/scripts/typegen.sh +11 -2
  152. package/server/server-middleware.js +4 -12
  153. package/store/index.js +14 -3
  154. package/store/prefs.js +0 -3
  155. package/store/store-types.js +2 -0
  156. package/store/type-map.js +17 -29
  157. package/types/api.d.ts +1 -0
  158. package/types/fleet.d.ts +1 -0
  159. package/types/shell/index.d.ts +931 -85
  160. package/types/userPreferences.d.ts +1 -1
  161. package/utils/__mocks__/socket.js +21 -0
  162. package/utils/grafana.js +23 -11
  163. package/utils/kube.js +9 -0
  164. package/utils/object.js +27 -0
  165. package/utils/selector.js +2 -1
  166. package/utils/settings.ts +2 -2
  167. package/utils/validators/formRules/index.ts +3 -3
  168. package/vue.config.js +3 -2
  169. package/components/.DS_Store +0 -0
  170. package/components/__tests__/.DS_Store +0 -0
  171. package/creators/pkg/package-lock.json +0 -37
  172. package/pages/safeMode.vue +0 -17
  173. package/plugins/steve/urloptions.js +0 -47
  174. package/yarn-error.log +0 -196
@@ -140,8 +140,6 @@ export default {
140
140
  >
141
141
  <Select
142
142
  v-model="serviceName"
143
- option-label="label"
144
- option-key="label"
145
143
  :options="serviceTargets"
146
144
  :status="serviceTargetStatus"
147
145
  :taggable="true"
@@ -0,0 +1,82 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import PersistentVolume from '@shell/edit/persistentvolume/index';
3
+ import { ExtendedVue, Vue } from 'vue/types/vue';
4
+
5
+ describe('view: PersistentVolume', () => {
6
+ it('should list enabled PV storage option if supported', () => {
7
+ const plugin = {
8
+ labelKey: 'persistentVolume.csi.label', supported: true, value: 'csi'
9
+ };
10
+ const resource = 'PersistentVolume';
11
+ const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
12
+ propsData: { value: { spec: { } } },
13
+ mocks: {
14
+ $store: {
15
+ dispatch: () => jest.fn(),
16
+ getters: {
17
+ 'i18n/t': jest.fn(),
18
+ 'i18n/exists': jest.fn(),
19
+ currentStore: () => 'cluster',
20
+ 'features/get': () => jest.fn(),
21
+ 'prefs/get': () => resource,
22
+ 'cluster/schemaFor': () => {},
23
+ 'cluster/all': () => [{}],
24
+ }
25
+ },
26
+ $fetchState: {
27
+ pending: false, error: true, timestamp: Date.now()
28
+ },
29
+ $route: {
30
+ params: { resource },
31
+ query: { AS: '' },
32
+ hash: '',
33
+ },
34
+ $router: {
35
+ currentRoute: {},
36
+ replace: jest.fn(),
37
+ }
38
+ },
39
+ stubs: { LabeledSelect: true }
40
+ });
41
+
42
+ const select = wrapper.find('[data-testid="persistent-volume-plugin-select"]');
43
+
44
+ expect((select.vm as unknown as any).options).toStrictEqual(expect.arrayContaining([plugin]));
45
+ });
46
+
47
+ it('should select current plugin', () => {
48
+ const plugin = 'csi';
49
+ const resource = 'PersistentVolume';
50
+ const wrapper = mount(PersistentVolume as ExtendedVue<Vue, {}, {}, {}, PersistentVolume>, {
51
+ propsData: { value: { spec: { [plugin]: { value: plugin } } } },
52
+ mocks: {
53
+ $store: {
54
+ dispatch: () => jest.fn(),
55
+ getters: {
56
+ 'i18n/t': jest.fn(),
57
+ 'i18n/exists': jest.fn(),
58
+ currentStore: () => 'cluster',
59
+ 'features/get': () => jest.fn(),
60
+ 'prefs/get': () => resource,
61
+ 'cluster/schemaFor': () => {},
62
+ 'cluster/all': () => [{}],
63
+ }
64
+ },
65
+ $fetchState: {
66
+ pending: false, error: true, timestamp: Date.now()
67
+ },
68
+ $route: {
69
+ params: { resource },
70
+ query: { AS: '' },
71
+ hash: '',
72
+ },
73
+ $router: {
74
+ currentRoute: {},
75
+ replace: jest.fn(),
76
+ }
77
+ }
78
+ });
79
+
80
+ expect(wrapper.vm.plugin).toBe(plugin);
81
+ });
82
+ });
@@ -13,9 +13,9 @@ import NodeAffinity from '@shell/components/form/NodeAffinity';
13
13
  import { Checkbox } from '@components/Form/Checkbox';
14
14
  import uniq from 'lodash/uniq';
15
15
  import UnitInput from '@shell/components/form/UnitInput';
16
+ import { VOLUME_PLUGINS, LONGHORN_PLUGIN } from '@shell/config/persistentVolume';
16
17
  import { NODE, PVC, STORAGE_CLASS } from '@shell/config/types';
17
18
  import Loading from '@shell/components/Loading';
18
- import { LONGHORN_PLUGIN, VOLUME_PLUGINS } from '@shell/models/persistentvolume';
19
19
  import { _CREATE, _VIEW } from '@shell/config/query-params';
20
20
  import { clone } from '@shell/utils/object';
21
21
  import InfoBox from '@shell/components/InfoBox';
@@ -258,6 +258,7 @@ export default {
258
258
  :label="t('persistentVolume.plugin.label')"
259
259
  :localized-label="true"
260
260
  option-label="labelKey"
261
+ data-testid="persistent-volume-plugin-select"
261
262
  :options="plugins"
262
263
  :mode="modeOverride"
263
264
  :required="true"
@@ -70,7 +70,9 @@ export default {
70
70
  },
71
71
  methods: {
72
72
  selectDriver(e) {
73
- set(this.value, 'spec.csi.driver', e.value ? e.value : e.label);
73
+ const name = e.value || e.label || e;
74
+
75
+ set(this.value, 'spec.csi.driver', name);
74
76
  }
75
77
  }
76
78
  };
@@ -3,7 +3,7 @@ import KeyValue from '@shell/components/form/KeyValue';
3
3
  import { LabeledInput } from '@components/Form/LabeledInput';
4
4
  import { RadioGroup } from '@components/Form/Radio';
5
5
  import { _CREATE } from '@shell/config/query-params';
6
- import { LONGHORN_DRIVER } from '@shell/models/persistentvolume';
6
+ import { LONGHORN_DRIVER } from '@shell/config/types';
7
7
 
8
8
  export default {
9
9
  components: {
@@ -54,20 +54,20 @@ export default {
54
54
  <div>
55
55
  <div class="row mb-20">
56
56
  <div class="col span-6">
57
- <LabeledInput
58
- v-model="value.spec.csi.fsType"
59
- :mode="mode"
60
- :label="t('persistentVolume.shared.filesystemType.label')"
61
- :placeholder="t('persistentVolume.shared.filesystemType.placeholder')"
57
+ <LabeledInput
58
+ v-model="value.spec.csi.fsType"
59
+ :mode="mode"
60
+ :label="t('persistentVolume.shared.filesystemType.label')"
61
+ :placeholder="t('persistentVolume.shared.filesystemType.placeholder')"
62
62
  />
63
63
  </div>
64
64
  <div class="col span-6">
65
- <LabeledInput
66
- v-model="value.spec.csi.volumeHandle"
67
- :mode="mode"
68
- :label="t('persistentVolume.longhorn.volumeHandle.label')"
69
- :placeholder="t('persistentVolume.longhorn.volumeHandle.placeholder')"
70
- :required="true"
65
+ <LabeledInput
66
+ v-model="value.spec.csi.volumeHandle"
67
+ :mode="mode"
68
+ :label="t('persistentVolume.longhorn.volumeHandle.label')"
69
+ :placeholder="t('persistentVolume.longhorn.volumeHandle.placeholder')"
70
+ :required="true"
71
71
  />
72
72
  </div>
73
73
  </div>
@@ -9,35 +9,6 @@ import { cleanUp } from '@shell/utils/object';
9
9
  import { fetchSetting } from '@shell/utils/settings';
10
10
  import { RadioGroup } from '@components/Form/Radio';
11
11
 
12
- export function cleanAgentConfiguration(model, key) {
13
- if (!model || !model[key]) {
14
- return;
15
- }
16
-
17
- const v = model[key];
18
-
19
- if (Array.isArray(v) && v.length === 0) {
20
- delete model[key];
21
- } else if (v && typeof v === 'object') {
22
- Object.keys(v).forEach((k) => {
23
- // delete these auxiliary props used in podAffinity and nodeAffinity that shouldn't be sent to the server
24
- if (k === '_namespaceOption' || k === '_namespaces' || k === '_anti' || k === '_id') {
25
- delete v[k];
26
- }
27
-
28
- // prevent cleanup of "namespaceSelector" when an empty object because it represents all namespaces in pod/node affinity
29
- // https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#podaffinityterm-v1-core
30
- if (k !== 'namespaceSelector') {
31
- cleanAgentConfiguration(v, k);
32
- }
33
- });
34
-
35
- if (Object.keys(v).length === 0) {
36
- delete model[key];
37
- }
38
- }
39
- }
40
-
41
12
  // Affinity radio button choices
42
13
  const DEFAULT = 'default';
43
14
  const CUSTOM = 'custom';
@@ -215,7 +186,7 @@ export default {
215
186
  </script>
216
187
 
217
188
  <template>
218
- <div v-if="value && Object.keys(value).length">
189
+ <div>
219
190
  <Banner
220
191
  :closable="false"
221
192
  color="info"
@@ -43,14 +43,28 @@ export default {
43
43
  const configMap = this.value.spec.rkeConfig?.registries?.configs || {};
44
44
  const entries = [];
45
45
 
46
+ const defaultAddValue = {
47
+ hostname: '',
48
+ authConfigSecretName: null,
49
+ caBundle: '',
50
+ insecureSkipVerify: false,
51
+ tlsSecretName: null,
52
+ };
53
+
46
54
  for ( const hostname in configMap ) {
55
+ if (configMap[hostname]) {
56
+ configMap[hostname].insecureSkipVerify = configMap[hostname].insecureSkipVerify ?? defaultAddValue.insecureSkipVerify;
57
+ configMap[hostname].authConfigSecretName = configMap[hostname].authConfigSecretName ?? defaultAddValue.authConfigSecretName;
58
+ configMap[hostname].caBundle = configMap[hostname].caBundle ?? defaultAddValue.caBundle;
59
+ configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? defaultAddValue.tlsSecretName;
60
+ }
47
61
  entries.push({
48
62
  hostname,
49
63
  ...configMap[hostname],
50
64
  });
51
65
  }
52
66
 
53
- return { entries };
67
+ return { entries, defaultAddValue };
54
68
  },
55
69
 
56
70
  computed: {
@@ -59,16 +73,6 @@ export default {
59
73
  return TYPES.TLS;
60
74
  },
61
75
  },
62
-
63
- defaultAddValue() {
64
- return {
65
- hostname: '',
66
- authConfigSecretName: null,
67
- caBundle: '',
68
- insecureSkipVerify: false,
69
- tlsSecretName: null,
70
- };
71
- },
72
76
  },
73
77
 
74
78
  mounted() {
@@ -1,4 +1,4 @@
1
- import { mount } from '@vue/test-utils';
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
2
  import rke2 from '@shell/edit/provisioning.cattle.io.cluster/rke2.vue';
3
3
 
4
4
  /**
@@ -14,6 +14,7 @@ const defaultStubs = {
14
14
  LabeledSelect: true,
15
15
  ACE: true,
16
16
  AgentEnv: true,
17
+ AgentConfiguration: true,
17
18
  ArrayList: true,
18
19
  ArrayListGrouped: true,
19
20
  BadgeState: true,
@@ -267,4 +268,81 @@ describe('component: rke2', () => {
267
268
 
268
269
  expect((wrapper.vm as any).validationPassed).toBe(true);
269
270
  });
271
+
272
+ // TODO: Complete test after implementing fetch https://github.com/rancher/dashboard/issues/9322
273
+ // eslint-disable-next-line jest/no-disabled-tests
274
+ describe.skip('should initialize agent configuration values', () => {
275
+ it('adding default values if none', async() => {
276
+ const wrapper = shallowMount(rke2, {
277
+ propsData: {
278
+ mode: 'create',
279
+ value: { spec: { ...defaultSpec } },
280
+ provider: 'custom'
281
+ },
282
+ computed: defaultComputed,
283
+ mocks: {
284
+ ...defaultMocks,
285
+ $store: {
286
+ getters: defaultGetters,
287
+ dispatch: {
288
+ 'management/request': jest.fn(),
289
+ 'management/find': jest.fn(),
290
+ 'management/findAll': () => ([]),
291
+ }
292
+ },
293
+ },
294
+ stubs: defaultStubs
295
+ });
296
+ const defaultAgentConfig = {
297
+ clusterAgentDeploymentCustomization: {
298
+ overrideAffinity: {},
299
+ appendTolerations: [],
300
+ overrideResourceRequirements: {}
301
+ },
302
+ fleetAgentDeploymentCustomization: {
303
+ overrideAffinity: {},
304
+ appendTolerations: [],
305
+ overrideResourceRequirements: {}
306
+ }
307
+ };
308
+
309
+ // Setting RKE to avoid calls
310
+ wrapper.setData({ rke2Versions: [] });
311
+
312
+ // await rke2.fetch.call(wrapper.vm);
313
+
314
+ expect(wrapper.props().value.spec).toContain(defaultAgentConfig);
315
+ });
316
+
317
+ it('should display agent configuration tab', async() => {
318
+ const wrapper = shallowMount(rke2, {
319
+ propsData: {
320
+ mode: 'create',
321
+ value: { spec: { ...defaultSpec } },
322
+ provider: 'custom'
323
+ },
324
+ computed: defaultComputed,
325
+ mocks: {
326
+ ...defaultMocks,
327
+ $store: {
328
+ getters: defaultGetters,
329
+ dispatch: {
330
+ 'management/request': jest.fn(),
331
+ 'management/find': jest.fn(),
332
+ 'management/findAll': () => ([]),
333
+ }
334
+ },
335
+ },
336
+ stubs: defaultStubs
337
+ });
338
+ const agent = wrapper.find('[data-testid="rke2-cluster-agent-config"]');
339
+
340
+ // Setting RKE to avoid calls
341
+ wrapper.setData({ rke2Versions: [] });
342
+
343
+ await rke2.fetch.call(wrapper.vm);
344
+
345
+ expect(agent.element).toBeDefined();
346
+ });
347
+ });
270
348
  });
@@ -17,7 +17,7 @@ import { CATALOG } from '@shell/config/labels-annotations';
17
17
  import { CAPI, MANAGEMENT, DEFAULT_WORKSPACE } from '@shell/config/types';
18
18
  import { mapFeature, RKE2 as RKE2_FEATURE } from '@shell/store/features';
19
19
  import { allHash } from '@shell/utils/promise';
20
- import { BLANK_CLUSTER } from '@shell/store';
20
+ import { BLANK_CLUSTER } from '@shell/store/store-types.js';
21
21
  import { ELEMENTAL_PRODUCT_NAME, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
22
22
  import Rke2Config from './rke2';
23
23
  import Import from './import';
@@ -138,6 +138,27 @@ export default {
138
138
  this.iconClasses[name] = `machine-driver ${ name }`;
139
139
  }
140
140
  });
141
+
142
+ // Custom Providers from extensions - initialize each with the store and the i18n service
143
+ // Wrap in try ... catch, to prevent errors in an extension breaking the page
144
+ try {
145
+ const extensionClasses = this.$plugin.listDynamic('provisioner').map((name) => this.$plugin.getDynamic('provisioner', name));
146
+
147
+ // We can't pass in this.$store as this leads to a circular-reference that causes Vue to freeze,
148
+ // so pass in specific services that the provisioner extension may need
149
+ this.extensions = extensionClasses.map((c) => new c({
150
+ dispatch: this.$store.dispatch,
151
+ getters: this.$store.getters,
152
+ axios: this.$store.$axios,
153
+ $plugin: this.$store.app.$plugin,
154
+ t: (...args) => this.t.apply(this, args),
155
+ isCreate: this.isCreate,
156
+ isEdit: this.isEdit,
157
+ isView: this.isView,
158
+ }));
159
+ } catch (e) {
160
+ console.error('Error loading provisioner(s) from extensions', e); // eslint-disable-line no-console
161
+ }
141
162
  },
142
163
 
143
164
  data() {
@@ -148,6 +169,7 @@ export default {
148
169
  return {
149
170
  nodeDrivers: [],
150
171
  kontainerDrivers: [],
172
+ extensions: [],
151
173
  subType,
152
174
  chart,
153
175
  isImport,
@@ -285,6 +307,11 @@ export default {
285
307
  addType(id, 'rke2', false);
286
308
  });
287
309
 
310
+ // Add from extensions
311
+ this.extensions.forEach((ext) => {
312
+ addExtensionType(ext, getters);
313
+ });
314
+
288
315
  addType('custom', 'custom2', false);
289
316
 
290
317
  if (isElementalActive) {
@@ -295,6 +322,31 @@ export default {
295
322
 
296
323
  return out;
297
324
 
325
+ function addExtensionType(ext, getters) {
326
+ let iconClass = ext.iconClass;
327
+ let icon = ext.icon;
328
+
329
+ if (icon) {
330
+ iconClass = undefined;
331
+ } else if (!iconClass) {
332
+ icon = require('~shell/assets/images/generic-driver.svg');
333
+ }
334
+
335
+ const subtype = {
336
+ id: ext.id,
337
+ label: ext.label || getters['i18n/t'](`cluster.provider.${ ext.id }`),
338
+ description: ext.description,
339
+ icon,
340
+ iconClass,
341
+ group: ext.group || 'rke2',
342
+ disabled: ext.disabled || false,
343
+ link: ext.link,
344
+ tag: ext.tag
345
+ };
346
+
347
+ out.push(subtype);
348
+ }
349
+
298
350
  function addType(id, group, disabled = false, link = null, iconClass = undefined) {
299
351
  const label = getters['i18n/withFallback'](`cluster.provider."${ id }"`, null, id);
300
352
  const description = getters['i18n/withFallback'](`cluster.providerDescription."${ id }"`, null, '');