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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +128 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +100 -0
  6. package/apis/intf/system.ts +41 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +90 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +37 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/styles/global/_tooltip.scss +6 -1
  18. package/assets/translations/en-us.yaml +5 -0
  19. package/components/ActionMenuShell.vue +3 -1
  20. package/components/CruResource.vue +8 -1
  21. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  22. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  23. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  24. package/components/LocaleSelector.vue +2 -2
  25. package/components/ModalManager.vue +11 -1
  26. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  27. package/components/RelatedResources.vue +5 -0
  28. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  29. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  30. package/components/ResourceDetail/index.vue +3 -0
  31. package/components/ResourceTable.vue +54 -21
  32. package/components/SlideInPanelManager.vue +16 -11
  33. package/components/SortableTable/THead.vue +2 -1
  34. package/components/SortableTable/index.vue +20 -2
  35. package/components/Tabbed/index.vue +37 -2
  36. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  37. package/components/auth/SelectPrincipal.vue +4 -0
  38. package/components/auth/login/ldap.vue +3 -3
  39. package/components/fleet/FleetSecretSelector.vue +1 -1
  40. package/components/form/KeyValue.vue +1 -1
  41. package/components/form/NameNsDescription.vue +1 -1
  42. package/components/form/NodeScheduling.vue +2 -2
  43. package/components/form/ResourceTabs/composable.ts +2 -2
  44. package/components/form/ResourceTabs/index.vue +0 -2
  45. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  46. package/components/formatter/LinkName.vue +5 -0
  47. package/components/nav/Group.vue +25 -7
  48. package/components/nav/Header.vue +1 -1
  49. package/components/nav/NamespaceFilter.vue +1 -0
  50. package/components/nav/Type.vue +17 -6
  51. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  52. package/components/nav/__tests__/Type.test.ts +59 -0
  53. package/composables/cruResource.ts +27 -0
  54. package/composables/focusTrap.ts +3 -1
  55. package/composables/resourceDetail.ts +15 -0
  56. package/composables/useLabeledFormElement.ts +3 -4
  57. package/config/product/fleet.js +1 -1
  58. package/config/router/navigation-guards/clusters.js +3 -3
  59. package/config/router/navigation-guards/products.js +1 -1
  60. package/config/router/routes.js +1 -5
  61. package/core/__tests__/extension-manager-impl.test.js +437 -0
  62. package/core/extension-manager-impl.js +6 -27
  63. package/core/plugin-helpers.ts +2 -2
  64. package/core/plugin.ts +9 -1
  65. package/core/plugins-loader.js +2 -2
  66. package/core/types-provisioning.ts +4 -0
  67. package/core/types.ts +35 -0
  68. package/detail/catalog.cattle.io.app.vue +1 -0
  69. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  70. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  71. package/dialog/MoveNamespaceDialog.vue +20 -4
  72. package/dialog/SearchDialog.vue +1 -0
  73. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  74. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  75. package/directives/clean-tooltip.ts +234 -0
  76. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  77. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
  78. package/edit/fleet.cattle.io.helmop.vue +5 -0
  79. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  80. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  81. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  82. package/edit/resources.cattle.io.restore.vue +1 -1
  83. package/edit/workload/Job.vue +2 -2
  84. package/edit/workload/__tests__/index.test.ts +123 -85
  85. package/edit/workload/index.vue +2 -2
  86. package/edit/workload/mixins/workload.js +19 -1
  87. package/initialize/install-plugins.js +4 -5
  88. package/machine-config/azure.vue +1 -1
  89. package/machine-config/components/GCEImage.vue +1 -1
  90. package/mixins/__tests__/brand.spec.ts +18 -13
  91. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
  92. package/models/chart.js +70 -74
  93. package/models/management.cattle.io.cluster.js +1 -1
  94. package/models/provisioning.cattle.io.cluster.js +11 -3
  95. package/package.json +7 -7
  96. package/pages/auth/login.vue +3 -3
  97. package/pages/auth/setup.vue +1 -1
  98. package/pages/auth/verify.vue +3 -3
  99. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  100. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  101. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  102. package/pages/c/_cluster/fleet/index.vue +7 -10
  103. package/pages/c/_cluster/settings/index.vue +5 -0
  104. package/pkg/auto-import.js +3 -3
  105. package/pkg/dynamic-importer.lib.js +1 -1
  106. package/pkg/import.js +1 -1
  107. package/plugins/__tests__/mutations.tests.ts +179 -0
  108. package/plugins/dashboard-store/getters.js +1 -1
  109. package/plugins/dashboard-store/model-loader.js +1 -1
  110. package/plugins/dashboard-store/mutations.js +23 -2
  111. package/plugins/dashboard-store/resource-class.js +8 -3
  112. package/plugins/plugin.js +2 -2
  113. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  114. package/plugins/steve/mutations.js +9 -0
  115. package/plugins/steve/steve-class.js +1 -1
  116. package/plugins/steve/steve-pagination-utils.ts +108 -43
  117. package/plugins/steve/subscribe.js +23 -2
  118. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  119. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  120. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  121. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  122. package/scripts/publish-shell.sh +25 -0
  123. package/store/__tests__/catalog.test.ts +1 -1
  124. package/store/__tests__/type-map.test.ts +164 -2
  125. package/store/auth.js +23 -11
  126. package/store/i18n.js +3 -3
  127. package/store/index.js +5 -3
  128. package/store/notifications.ts +2 -0
  129. package/store/prefs.js +2 -2
  130. package/store/type-map.js +17 -7
  131. package/types/internal-api/shell/modal.d.ts +6 -6
  132. package/types/notifications/index.ts +126 -15
  133. package/types/rancher/index.d.ts +9 -0
  134. package/types/shell/index.d.ts +16 -1
  135. package/types/store/dashboard-store.types.ts +29 -7
  136. package/types/vue-shim.d.ts +5 -4
  137. package/utils/__tests__/router.test.js +238 -0
  138. package/utils/cluster.js +4 -1
  139. package/utils/cspAdaptor.ts +32 -14
  140. package/utils/fleet.ts +8 -1
  141. package/utils/pagination-utils.ts +2 -2
  142. package/utils/pagination-wrapper.ts +4 -4
  143. package/utils/router.js +50 -0
  144. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  145. package/vue.config.js +3 -3
  146. package/composables/useExtensionManager.ts +0 -17
  147. package/core/__test__/extension-manager-impl.test.js +0 -236
  148. package/core/plugins.js +0 -38
  149. package/directives/clean-tooltip.js +0 -32
  150. package/plugins/internal-api/index.ts +0 -37
  151. package/plugins/internal-api/shared/base-api.ts +0 -13
  152. package/plugins/internal-api/shell/shell.api.ts +0 -108
  153. package/types/internal-api/shell/growl.d.ts +0 -25
  154. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -4,100 +4,138 @@ import Workload from '@shell/edit/workload/index.vue';
4
4
  jest.mock('@shell/models/secret', () => ({ onmessage: jest.fn() }));
5
5
 
6
6
  describe('component: Workload', () => {
7
- it.each([
8
- [
9
- `pods \"test\" is forbidden: violates PodSecurity \"restricted:latest\": allowPrivilegeEscalation != false (container \"container-0\" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container \"container-0\" must set securityContext.capabilities.drop=[\"ALL\"]), runAsNonRoot != true (container \"container-0\" must not set securityContext.runAsNonRoot=false), seccompProfile (pod or container \"container-0\" must set securityContext.seccompProfile.type to \"RuntimeDefault\" or \"Localhost\")`,
10
- `workload.error, \"test\",\"restricted:latest\"`
11
- ]
12
- ])('should map error message into object', (oldMessage, newMessage) => {
13
- const mockedValidationMixin = {
14
- methods: {
15
- fvFormIsValid: jest.fn(),
16
- type: jest.fn(),
17
- fvGetAndReportPathRules: jest.fn(),
18
- },
19
- computed: { fvUnreportedValidationErrors: jest.fn().mockReturnValue([]) }
20
- };
21
- const mockedCREMixin = {};
22
- const mockedWorkloadMixin = {
23
- methods: {
24
- doneRoute: jest.fn(),
25
- workloadSubTypes: jest.fn(),
26
- applyHooks: jest.fn(),
27
- save: jest.fn(),
28
- selectType: jest.fn(),
29
- isCronJob: jest.fn(),
30
- spec: jest.fn(),
31
- isReplicable: jest.fn(),
32
- isStatefulSet: jest.fn(),
33
- headlessServices: jest.fn(),
34
- defaultTab: jest.fn(),
35
- allContainers: jest.fn(),
36
- isPod: jest.fn(),
37
- tabWeightMap: jest.fn(),
38
- podLabels: jest.fn(),
39
- podTemplateSpec: jest.fn(),
40
- isLoadingSecondaryResources: jest.fn(),
41
- allNodes: jest.fn(),
42
- allNodeObjects: jest.fn(),
43
- clearPvcFormState: jest.fn(),
44
- savePvcHookName: jest.fn(),
45
- namespacedConfigMaps: jest.fn(),
46
- podAnnotations: jest.fn(),
47
- isJob: jest.fn(),
48
- podFsGroup: jest.fn(),
49
- namespacedSecrets: jest.fn(),
50
- registerBeforeHook: jest.fn(),
51
- pvcs: jest.fn(),
52
- // tabWeightMap: jest.fn(),
53
- }
54
- };
7
+ const baseMockedValidationMixin = {
8
+ methods: {
9
+ fvFormIsValid: jest.fn(),
10
+ type: jest.fn(),
11
+ fvGetAndReportPathRules: jest.fn(),
12
+ },
13
+ computed: { fvUnreportedValidationErrors: jest.fn().mockReturnValue([]) }
14
+ };
15
+ const baseMockedCREMixin = {};
16
+ const baseMockedWorkloadMixin = {
17
+ methods: {
18
+ doneRoute: jest.fn(),
19
+ workloadSubTypes: jest.fn(),
20
+ applyHooks: jest.fn(),
21
+ save: jest.fn(),
22
+ selectType: jest.fn(),
23
+ isCronJob: jest.fn(),
24
+ spec: jest.fn(),
25
+ isReplicable: jest.fn(),
26
+ isStatefulSet: jest.fn(),
27
+ headlessServices: jest.fn(),
28
+ defaultTab: jest.fn(),
29
+ allContainers: jest.fn(),
30
+ isPod: jest.fn(),
31
+ tabWeightMap: jest.fn(),
32
+ podLabels: jest.fn(),
33
+ podTemplateSpec: jest.fn(),
34
+ isLoadingSecondaryResources: jest.fn(),
35
+ allNodes: jest.fn(),
36
+ clearPvcFormState: jest.fn(),
37
+ savePvcHookName: jest.fn(),
38
+ namespacedConfigMaps: jest.fn(),
39
+ podAnnotations: jest.fn(),
40
+ isJob: jest.fn(),
41
+ podFsGroup: jest.fn(),
42
+ namespacedSecrets: jest.fn(),
43
+ registerBeforeHook: jest.fn(),
44
+ pvcs: jest.fn(),
45
+ }
46
+ };
55
47
 
56
- const MockedWorkload = { ...Workload, mixins: [mockedValidationMixin, mockedCREMixin, mockedWorkloadMixin] };
57
- const wrapper = shallowMount(MockedWorkload, {
58
- props: {
59
- value: { metadata: {}, spec: { template: {} } },
60
- params: {},
61
- fvFormIsValid: {}
62
- },
48
+ describe('component: Workload', () => {
49
+ it.each([
50
+ [
51
+ `pods \"test\" is forbidden: violates PodSecurity \"restricted:latest\": allowPrivilegeEscalation != false (container \"container-0\" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container \"container-0\" must set securityContext.capabilities.drop=[\"ALL\"]), runAsNonRoot != true (container \"container-0\" must not set securityContext.runAsNonRoot=false), seccompProfile (pod or container \"container-0\" must set securityContext.seccompProfile.type to \"RuntimeDefault\" or \"Localhost\")`,
52
+ `workload.error, \"test\",\"restricted:latest\"`
53
+ ]
54
+ ])('should map error message into object', (oldMessage, newMessage) => {
55
+ // For this test, allNodeObjects is just a jest.fn() in the base mixin
56
+ const MockedWorkload = { ...Workload, mixins: [baseMockedValidationMixin, baseMockedCREMixin, { ...baseMockedWorkloadMixin, computed: { allNodeObjects: jest.fn() } }] };
57
+ const wrapper = shallowMount(MockedWorkload, {
58
+ props: {
59
+ value: { metadata: {}, spec: { template: {} } },
60
+ params: {},
61
+ fvFormIsValid: {}
62
+ },
63
63
 
64
- global: {
65
- mocks: {
66
- $route: { params: {}, query: {} },
67
- $router: { applyQuery: jest.fn() },
68
- $fetchState: { pending: false },
69
- $store: {
70
- getters: {
71
- 'cluster/schemaFor': jest.fn(),
72
- 'type-map/labelFor': jest.fn(),
73
- 'i18n/t': (text: string, v: {[key:string]: string}) => {
74
- return `${ text }, ${ Object.values(v || {}) }`;
64
+ global: {
65
+ mocks: {
66
+ $route: { params: {}, query: {} },
67
+ $router: { applyQuery: jest.fn() },
68
+ $fetchState: { pending: false },
69
+ $store: {
70
+ getters: {
71
+ 'cluster/schemaFor': jest.fn(),
72
+ 'type-map/labelFor': jest.fn(),
73
+ 'i18n/t': (text: string, v: {[key:string]: string}) => {
74
+ return `${ text }, ${ Object.values(v || {}) }`;
75
+ },
75
76
  },
76
77
  },
77
78
  },
78
- },
79
79
 
80
- stubs: {
81
- Tab: true,
82
- LabeledInput: true,
83
- VolumeClaimTemplate: true,
84
- Networking: true,
85
- Job: true,
86
- NodeScheduling: true,
87
- PodAffinity: true,
88
- Tolerations: true,
89
- Storage: true,
90
- Tabbed: true,
91
- LabeledSelect: true,
92
- NameNsDescription: true,
93
- CruResource: true,
94
- KeyValue: true
80
+ stubs: {
81
+ Tab: true,
82
+ LabeledInput: true,
83
+ VolumeClaimTemplate: true,
84
+ Networking: true,
85
+ Job: true,
86
+ NodeScheduling: true,
87
+ PodAffinity: true,
88
+ Tolerations: true,
89
+ Storage: true,
90
+ Tabbed: true,
91
+ LabeledSelect: true,
92
+ NameNsDescription: true,
93
+ CruResource: true,
94
+ KeyValue: true
95
+ },
95
96
  },
96
- },
97
+ });
98
+
99
+ const result = (wrapper.vm as any).mapError(oldMessage).message;
100
+
101
+ expect(result).toStrictEqual(newMessage);
97
102
  });
98
103
 
99
- const result = (wrapper.vm as any).mapError(oldMessage).message;
104
+ describe('secondaryResourceDataConfig', () => {
105
+ it('should filter out nodes with control-plane or etcd taints from workerNodes parsingFunc', () => {
106
+ const allNodeObjects = [
107
+ {
108
+ id: 'node-1',
109
+ spec: { taints: [{ key: 'node-role.kubernetes.io/control-plane', effect: 'NoSchedule' }] }
110
+ },
111
+ {
112
+ id: 'node-2',
113
+ spec: { taints: [{ key: 'node-role.kubernetes.io/etcd', effect: 'NoSchedule' }] }
114
+ },
115
+ {
116
+ id: 'node-3',
117
+ spec: { taints: [{ key: 'node-role.kubernetes.io/worker', effect: 'NoSchedule' }] }
118
+ },
119
+ {
120
+ id: 'node-4',
121
+ spec: { taints: [] }
122
+ },
123
+ {
124
+ id: 'node-5',
125
+ spec: {}
126
+ },
127
+ {
128
+ id: 'node-6',
129
+ spec: null
130
+ }
131
+ ];
132
+
133
+ const { data } = (Workload.mixins[2] as any).methods.secondaryResourceDataConfig.apply({ value: { metadata: { namespace: 'test' } } });
134
+ const workerNodesParsingFunc = data.node.applyTo.find((r: any) => r.var === 'workerNodes').parsingFunc;
135
+ const result = workerNodesParsingFunc(allNodeObjects);
100
136
 
101
- expect(result).toStrictEqual(newMessage);
137
+ expect(result).toStrictEqual(['node-3', 'node-4', 'node-5', 'node-6']);
138
+ });
139
+ });
102
140
  });
103
141
  });
@@ -155,7 +155,7 @@ export default {
155
155
  :default-tab="defaultTab || defaultWorkloadTab"
156
156
  :flat="true"
157
157
  :use-hash="useTabbedHash"
158
-
158
+ :showExtensionTabs="false"
159
159
  data-testid="workload-horizontal-tabs"
160
160
  @changed="changed"
161
161
  >
@@ -494,7 +494,7 @@ export default {
494
494
  <NodeScheduling
495
495
  :mode="mode"
496
496
  :value="podTemplateSpec"
497
- :nodes="allNodes"
497
+ :nodes="workerNodes"
498
498
  :loading="isLoadingSecondaryResources"
499
499
  />
500
500
  </Tab>
@@ -258,6 +258,7 @@ export default {
258
258
  secondaryResourceData: this.secondaryResourceDataConfig(),
259
259
  namespacedConfigMaps: [],
260
260
  allNodes: null,
261
+ workerNodes: null,
261
262
  allNodeObjects: [],
262
263
  namespacedSecrets: [],
263
264
  imagePullNamespacedSecrets: [],
@@ -647,7 +648,24 @@ export default {
647
648
  parsingFunc: (data) => {
648
649
  return data.map((node) => node.id);
649
650
  }
650
- }
651
+ },
652
+ {
653
+ var: 'workerNodes',
654
+ parsingFunc: (data) => {
655
+ const keys = [
656
+ `node-role.kubernetes.io/control-plane`,
657
+ `node-role.kubernetes.io/etcd`
658
+ ];
659
+
660
+ return data
661
+ .filter((node) => {
662
+ const taints = node?.spec?.taints || [];
663
+
664
+ return taints.every((taint) => !keys.includes(taint.key));
665
+ })
666
+ .map((node) => node.id);
667
+ }
668
+ },
651
669
  ]
652
670
  },
653
671
  [SERVICE]: {
@@ -19,13 +19,12 @@ import { InstallCodeMirror } from 'codemirror-editor-vue3';
19
19
  import * as intNumber from '@shell/directives/int-number';
20
20
  import dashboardClientInit from '@shell/plugins/dashboard-client-init';
21
21
  import plugin from '@shell/plugins/plugin';
22
- import plugins from '@shell/core/plugins.js';
23
22
  import pluginsLoader from '@shell/core/plugins-loader.js';
24
23
  import replaceAll from '@shell/plugins/replaceall';
25
24
  import steveCreateWorker from '@shell/plugins/steve-create-worker';
26
25
  import emberCookie from '@shell/plugins/ember-cookie';
27
26
  import ShortKey from '@shell/plugins/shortkey';
28
- import internalApiPlugin from '@shell/plugins/internal-api';
27
+ import { initUiApis } from '@shell/apis/impl/apis';
29
28
 
30
29
  import 'floating-vue/dist/style.css';
31
30
  import { floatingVueOptions } from '@shell/plugins/floating-vue';
@@ -51,7 +50,7 @@ export async function installInjectedPlugins(app, vueApp) {
51
50
  const pluginDefinitions = [
52
51
  config,
53
52
  axios,
54
- plugins,
53
+ initUiApis,
55
54
  pluginsLoader,
56
55
  axiosShell,
57
56
  intNumber,
@@ -61,7 +60,6 @@ export async function installInjectedPlugins(app, vueApp) {
61
60
  plugin,
62
61
  steveCreateWorker,
63
62
  emberCookie,
64
- internalApiPlugin,
65
63
  dynamicContent,
66
64
  ];
67
65
 
@@ -69,7 +67,8 @@ export async function installInjectedPlugins(app, vueApp) {
69
67
  if (typeof pluginDefinition === 'function') {
70
68
  await pluginDefinition(
71
69
  app.context,
72
- (key, value) => inject(key, value, app.context, vueApp)
70
+ (key, value) => inject(key, value, app.context, vueApp),
71
+ vueApp
73
72
  );
74
73
  }
75
74
  });
@@ -37,7 +37,7 @@ const defaultConfig = {
37
37
  faultDomainCount: '3',
38
38
  image: 'canonical:ubuntu-24_04-lts:server-gen1:latest',
39
39
  location: 'westus',
40
- managedDisks: false,
40
+ managedDisks: true,
41
41
  noPublicIp: false,
42
42
  nsg: null,
43
43
  privateIpAddress: null,
@@ -8,7 +8,7 @@ import { getGKEImageFamilies, getGKEFamiliesFromProject } from '@shell/component
8
8
  import debounce from 'lodash/debounce';
9
9
  import { mapGetters } from 'vuex';
10
10
 
11
- const DEFAULT_PROJECTS = 'suse-cloud, ubuntu-os-cloud, rhel-cloud';
11
+ const DEFAULT_PROJECTS = 'ubuntu-os-cloud';
12
12
  const DEFAULT_MIN_DISK = 10;
13
13
 
14
14
  export default {
@@ -1,6 +1,7 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { CATALOG, MANAGEMENT } from '@shell/config/types';
3
3
  import Brand from '@shell/mixins/brand';
4
+ import CspAdapterUtils from '@shell/utils/cspAdaptor';
4
5
 
5
6
  describe('brandMixin', () => {
6
7
  const createWrapper = (vaiOn = false) => {
@@ -54,45 +55,47 @@ describe('brandMixin', () => {
54
55
  data: () => data,
55
56
  global: { mocks: { $store: store } }
56
57
  });
57
- const spyManagementFindAll = jest.spyOn(store, 'dispatch');
58
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch');
59
+
60
+ CspAdapterUtils.resetState();
58
61
 
59
62
  return {
60
63
  wrapper,
61
64
  store,
62
- spyManagementFindAll,
65
+ spyManagementDispatch,
63
66
  };
64
67
  };
65
68
 
66
69
  describe('should make correct requests', () => {
67
70
  it('vai off', async() => {
68
- const { wrapper, spyManagementFindAll } = createWrapper(false);
71
+ const { wrapper, spyManagementDispatch } = createWrapper(false);
69
72
 
70
73
  // NOTE - wrapper.vm.$options.fetch() doesn't work
71
74
  await wrapper.vm.$options.fetch.apply(wrapper.vm);
72
75
 
73
76
  // wrapper.vm.$nextTick();
74
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
77
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
75
78
  type: MANAGEMENT.SETTING,
76
79
  opt: {
77
80
  load: 'multi', redirectUnauthorized: false, url: `/v1/${ MANAGEMENT.SETTING }s`
78
81
  }
79
82
  });
80
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
83
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findAll', { type: CATALOG.APP });
81
84
  });
82
85
 
83
86
  it('vai on', async() => {
84
- const { wrapper, spyManagementFindAll } = createWrapper(true);
87
+ const { wrapper, spyManagementDispatch } = createWrapper(true);
85
88
 
86
89
  // NOTE - wrapper.vm.$options.fetch() doesn't work
87
90
  await wrapper.vm.$options.fetch.apply(wrapper.vm);
88
91
 
89
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(1, 'management/findAll', {
92
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(1, 'management/findAll', {
90
93
  type: MANAGEMENT.SETTING,
91
94
  opt: {
92
95
  load: 'multi', url: `/v1/${ MANAGEMENT.SETTING }s`, redirectUnauthorized: false
93
96
  }
94
97
  });
95
- expect(spyManagementFindAll).toHaveBeenNthCalledWith(2, 'management/findPage', {
98
+ expect(spyManagementDispatch).toHaveBeenNthCalledWith(2, 'management/findPage', {
96
99
  type: CATALOG.APP,
97
100
  opt: {
98
101
  pagination: {
@@ -110,7 +113,9 @@ describe('brandMixin', () => {
110
113
  pageSize: null,
111
114
  projectsOrNamespaces: [],
112
115
  sort: []
113
- }
116
+ },
117
+ transient: true,
118
+ watch: false
114
119
  }
115
120
  });
116
121
  });
@@ -120,7 +125,7 @@ describe('brandMixin', () => {
120
125
  it('should have correct csp values (off)', async() => {
121
126
  const { wrapper, store } = createWrapper();
122
127
 
123
- const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
128
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
124
129
  const { type } = options as any;
125
130
 
126
131
  if (type === MANAGEMENT.SETTING) {
@@ -136,7 +141,7 @@ describe('brandMixin', () => {
136
141
  // NOTE - wrapper.vm.$options.fetch() doesn't work
137
142
  await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
138
143
 
139
- expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
144
+ expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
140
145
 
141
146
  expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
142
147
  expect(wrapper.vm.cspAdapter).toBeFalsy();
@@ -145,7 +150,7 @@ describe('brandMixin', () => {
145
150
  it.each(['rancher-csp-adapter', 'rancher-csp-billing-adapter'])('should have correct csp values (on - %p )', async(chartName) => {
146
151
  const { wrapper, store } = createWrapper();
147
152
 
148
- const spyManagementFindAll = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
153
+ const spyManagementDispatch = jest.spyOn(store, 'dispatch').mockImplementation((_, options) => {
149
154
  const { type } = options as any;
150
155
 
151
156
  if (type === MANAGEMENT.SETTING) {
@@ -161,7 +166,7 @@ describe('brandMixin', () => {
161
166
  // NOTE - wrapper.vm.$options.fetch() doesn't work
162
167
  await wrapper.vm.$options.fetch.apply(wrapper.vm, []);
163
168
 
164
- expect(spyManagementFindAll).toHaveBeenCalledTimes(2);
169
+ expect(spyManagementDispatch).toHaveBeenCalledTimes(2);
165
170
 
166
171
  expect(wrapper.vm.canCalcCspAdapter).toBeTruthy();
167
172
  expect(wrapper.vm.cspAdapter).toBeTruthy();
@@ -69,6 +69,22 @@ describe('class ProvCluster', () => {
69
69
  },
70
70
  expected: true
71
71
  },
72
+ {
73
+ description: 'should return true for an imported k3s cluster in waiting state',
74
+ clusterData: {
75
+ isLocal: false,
76
+ mgmt: { status: { provider: undefined, driver: 'k3s' } }
77
+ },
78
+ expected: true
79
+ },
80
+ {
81
+ description: 'should return true for an imported rke2 cluster in waiting state',
82
+ clusterData: {
83
+ isLocal: false,
84
+ mgmt: { status: { provider: undefined, driver: 'rke2' } }
85
+ },
86
+ expected: true
87
+ },
72
88
  {
73
89
  description: 'should return false for a provisioned k3s cluster',
74
90
  clusterData: {
package/models/chart.js CHANGED
@@ -136,91 +136,87 @@ export default class Chart extends SteveModel {
136
136
  * @returns {Object} Card content object with `subHeaderItems`, `footerItems`, and `statuses` arrays.
137
137
  */
138
138
  get cardContent() {
139
- if (!this._cardContent) {
140
- const latestVersion = this.latestCompatibleVersion;
141
- const subHeaderItems = [];
142
-
143
- if (latestVersion) {
144
- const hasZeroTime = latestVersion.created === ZERO_TIME;
145
-
146
- subHeaderItems.push({
147
- icon: 'icon-version-alt',
148
- iconTooltip: { key: 'tableHeaders.version' },
149
- label: latestVersion.version
150
- });
151
-
152
- const lastUpdatedItem = {
153
- icon: 'icon-refresh-alt',
154
- iconTooltip: { key: 'tableHeaders.lastUpdated' },
155
- label: hasZeroTime ? this.t('generic.na') : day(latestVersion.created).format('MMM D, YYYY')
156
- };
157
-
158
- if (hasZeroTime) {
159
- lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
160
- }
161
-
162
- subHeaderItems.push(lastUpdatedItem);
163
- }
139
+ const latestVersion = this.latestCompatibleVersion;
140
+ const subHeaderItems = [];
141
+
142
+ if (latestVersion) {
143
+ const hasZeroTime = latestVersion.created === ZERO_TIME;
144
+
145
+ subHeaderItems.push({
146
+ icon: 'icon-version-alt',
147
+ iconTooltip: { key: 'tableHeaders.version' },
148
+ label: latestVersion.version
149
+ });
150
+
151
+ const lastUpdatedItem = {
152
+ icon: 'icon-refresh-alt',
153
+ iconTooltip: { key: 'tableHeaders.lastUpdated' },
154
+ label: hasZeroTime ? this.t('generic.na') : day(latestVersion.created).format('MMM D, YYYY')
155
+ };
164
156
 
165
- const footerItems = [
166
- {
167
- type: REPO,
168
- icon: 'icon-repository-alt',
169
- iconTooltip: { key: 'tableHeaders.repoName' },
170
- labels: [this.repoNameDisplay],
171
- labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
172
- }
173
- ];
174
-
175
- if (this.categories.length) {
176
- footerItems.push( {
177
- type: CATEGORY,
178
- icon: 'icon-category-alt',
179
- iconTooltip: { key: 'generic.category' },
180
- labels: this.categories,
181
- labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
182
- });
157
+ if (hasZeroTime) {
158
+ lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
183
159
  }
184
160
 
185
- if (this.tags.length) {
186
- footerItems.push({
187
- type: TAG,
188
- icon: 'icon-tag-alt',
189
- iconTooltip: { key: 'generic.tags' },
190
- labels: this.tags,
191
- labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.tag') }, true)
192
- });
161
+ subHeaderItems.push(lastUpdatedItem);
162
+ }
163
+
164
+ const footerItems = [
165
+ {
166
+ type: REPO,
167
+ icon: 'icon-repository-alt',
168
+ iconTooltip: { key: 'tableHeaders.repoName' },
169
+ labels: [this.repoNameDisplay],
170
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
193
171
  }
172
+ ];
173
+
174
+ if (this.categories.length) {
175
+ footerItems.push( {
176
+ type: CATEGORY,
177
+ icon: 'icon-category-alt',
178
+ iconTooltip: { key: 'generic.category' },
179
+ labels: this.categories,
180
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
181
+ });
182
+ }
194
183
 
195
- const statuses = [];
184
+ if (this.tags.length) {
185
+ footerItems.push({
186
+ type: TAG,
187
+ icon: 'icon-tag-alt',
188
+ iconTooltip: { key: 'generic.tags' },
189
+ labels: this.tags,
190
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.tag') }, true)
191
+ });
192
+ }
196
193
 
197
- if (this.deprecated) {
198
- statuses.push({
199
- icon: 'icon-alert-alt', color: 'error', tooltip: { key: 'generic.deprecated' }
200
- });
201
- }
194
+ const statuses = [];
202
195
 
203
- if (this.upgradeable) {
204
- statuses.push({
205
- icon: 'icon-upgrade-alt', color: 'info', tooltip: { key: 'generic.upgradeable' }
206
- });
207
- }
196
+ if (this.deprecated) {
197
+ statuses.push({
198
+ icon: 'icon-alert-alt', color: 'error', tooltip: { key: 'generic.deprecated' }
199
+ });
200
+ }
208
201
 
209
- if (this.isInstalled) {
210
- const installedVersion = this.matchingInstalledApps[0]?.spec?.chart?.metadata?.version;
202
+ if (this.upgradeable) {
203
+ statuses.push({
204
+ icon: 'icon-upgrade-alt', color: 'info', tooltip: { key: 'generic.upgradeable' }
205
+ });
206
+ }
211
207
 
212
- statuses.push({
213
- icon: 'icon-confirmation-alt', color: 'success', tooltip: { text: `${ this.t('generic.installed') } (${ installedVersion })` }
214
- });
215
- }
208
+ if (this.isInstalled) {
209
+ const installedVersion = this.matchingInstalledApps[0]?.spec?.chart?.metadata?.version;
216
210
 
217
- this._cardContent = {
218
- subHeaderItems,
219
- footerItems,
220
- statuses
221
- };
211
+ statuses.push({
212
+ icon: 'icon-confirmation-alt', color: 'success', tooltip: { text: `${ this.t('generic.installed') } (${ installedVersion })` }
213
+ });
222
214
  }
223
215
 
224
- return this._cardContent;
216
+ return {
217
+ subHeaderItems,
218
+ footerItems,
219
+ statuses
220
+ };
225
221
  }
226
222
  }
@@ -255,7 +255,7 @@ export default class MgmtCluster extends SteveModel {
255
255
  dispatch: this.$dispatch,
256
256
  getters: this.$getters,
257
257
  axios: this.$axios,
258
- $extension: this.$plugin,
258
+ $extension: this.$extension,
259
259
  t: (...args) => this.t.apply(this, args),
260
260
  };
261
261