@rancher/shell 3.0.8-rc.7 → 3.0.8-rc.9

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 (166) hide show
  1. package/assets/brand/suse/dark/rancher-logo.svg +1 -64
  2. package/assets/translations/en-us.yaml +9 -1
  3. package/components/BackLink.vue +8 -0
  4. package/components/BannerGraphic.vue +1 -5
  5. package/components/BrandImage.vue +17 -6
  6. package/components/Cron/CronExpressionEditor.vue +1 -1
  7. package/components/Cron/CronExpressionEditorModal.vue +1 -1
  8. package/components/Drawer/Chrome.vue +2 -6
  9. package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
  10. package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
  11. package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
  12. package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
  13. package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
  14. package/components/Drawer/types.ts +3 -0
  15. package/components/PaginatedResourceTable.vue +2 -6
  16. package/components/Questions/__tests__/index.test.ts +159 -0
  17. package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
  18. package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
  19. package/components/Resource/Detail/Metadata/composables.ts +9 -9
  20. package/components/Resource/Detail/Metadata/index.vue +3 -3
  21. package/components/Resource/Detail/TitleBar/composables.ts +2 -1
  22. package/components/Resource/Detail/composables.ts +12 -0
  23. package/components/Tabbed/__tests__/index.test.ts +86 -0
  24. package/components/auth/SelectPrincipal.vue +24 -6
  25. package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
  26. package/components/formatter/InternalExternalIP.vue +4 -1
  27. package/components/formatter/__tests__/InternalExternalIP.test.ts +1 -1
  28. package/components/nav/Header.vue +1 -2
  29. package/components/nav/TopLevelMenu.helper.ts +16 -6
  30. package/components/templates/standalone.vue +1 -1
  31. package/composables/useI18n.ts +10 -1
  32. package/config/__test__/uiplugins.test.ts +309 -0
  33. package/config/labels-annotations.js +1 -0
  34. package/config/product/explorer.js +3 -1
  35. package/config/router/routes.js +6 -2
  36. package/config/types.js +7 -0
  37. package/config/uiplugins.js +46 -2
  38. package/core/__test__/extension-manager-impl.test.js +236 -0
  39. package/core/extension-manager-impl.js +23 -6
  40. package/core/types-provisioning.ts +1 -1
  41. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  42. package/dialog/DeveloperLoadExtensionDialog.vue +12 -3
  43. package/dialog/RollbackWorkloadDialog.vue +2 -5
  44. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
  45. package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
  46. package/edit/configmap.vue +1 -0
  47. package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
  48. package/edit/fleet.cattle.io.helmop.vue +6 -6
  49. package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
  50. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
  51. package/edit/logging-flow/index.vue +1 -0
  52. package/edit/logging.banzaicloud.io.output/index.vue +1 -0
  53. package/edit/management.cattle.io.fleetworkspace.vue +1 -1
  54. package/edit/management.cattle.io.project.vue +1 -0
  55. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
  56. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
  57. package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
  58. package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
  59. package/edit/monitoring.coreos.com.route.vue +1 -1
  60. package/edit/namespace.vue +1 -0
  61. package/edit/networking.istio.io.destinationrule/index.vue +1 -0
  62. package/edit/networking.k8s.io.ingress/index.vue +1 -0
  63. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
  64. package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
  65. package/edit/node.vue +1 -0
  66. package/edit/persistentvolume/index.vue +27 -22
  67. package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
  68. package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
  69. package/edit/persistentvolume/plugins/azureFile.vue +15 -14
  70. package/edit/persistentvolume/plugins/cephfs.vue +15 -14
  71. package/edit/persistentvolume/plugins/cinder.vue +15 -14
  72. package/edit/persistentvolume/plugins/csi.vue +18 -16
  73. package/edit/persistentvolume/plugins/fc.vue +13 -14
  74. package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
  75. package/edit/persistentvolume/plugins/flocker.vue +1 -3
  76. package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
  77. package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
  78. package/edit/persistentvolume/plugins/hostPath.vue +40 -39
  79. package/edit/persistentvolume/plugins/iscsi.vue +13 -14
  80. package/edit/persistentvolume/plugins/local.vue +1 -3
  81. package/edit/persistentvolume/plugins/longhorn.vue +23 -22
  82. package/edit/persistentvolume/plugins/nfs.vue +15 -14
  83. package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
  84. package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
  85. package/edit/persistentvolume/plugins/quobyte.vue +15 -14
  86. package/edit/persistentvolume/plugins/rbd.vue +15 -14
  87. package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
  88. package/edit/persistentvolume/plugins/storageos.vue +15 -14
  89. package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
  90. package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -0
  91. package/edit/secret/index.vue +1 -1
  92. package/edit/service.vue +1 -0
  93. package/edit/serviceaccount.vue +1 -0
  94. package/edit/storage.k8s.io.storageclass/index.vue +1 -0
  95. package/edit/workload/index.vue +2 -1
  96. package/edit/workload/mixins/workload.js +1 -1
  97. package/initialize/App.vue +4 -4
  98. package/initialize/install-plugins.js +17 -2
  99. package/machine-config/components/EC2Networking.vue +5 -2
  100. package/machine-config/components/__tests__/EC2Networking.test.ts +24 -0
  101. package/mixins/__tests__/brand.spec.ts +2 -2
  102. package/mixins/__tests__/chart.test.ts +21 -0
  103. package/mixins/brand.js +1 -7
  104. package/mixins/chart.js +7 -1
  105. package/mixins/create-edit-view/index.js +5 -0
  106. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
  107. package/models/management.cattle.io.cluster.js +21 -3
  108. package/models/provisioning.cattle.io.cluster.js +21 -9
  109. package/package.json +5 -4
  110. package/pages/auth/login.vue +1 -3
  111. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
  112. package/pages/c/_cluster/apps/charts/chart.vue +33 -15
  113. package/pages/c/_cluster/explorer/index.vue +8 -6
  114. package/pages/c/_cluster/manager/hostedprovider/index.vue +12 -6
  115. package/pages/c/_cluster/settings/brand.vue +1 -1
  116. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
  117. package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
  118. package/pages/c/_cluster/uiplugins/index.vue +126 -184
  119. package/pages/home.vue +5 -2
  120. package/pkg/dynamic-importer.lib.js +4 -0
  121. package/plugins/dashboard-client-init.js +3 -0
  122. package/plugins/dashboard-store/getters.js +18 -1
  123. package/plugins/dashboard-store/resource-class.js +4 -4
  124. package/plugins/i18n.js +8 -0
  125. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
  126. package/plugins/steve/steve-pagination-utils.ts +39 -20
  127. package/plugins/steve/subscribe.js +17 -9
  128. package/plugins/subscribe-events.ts +4 -2
  129. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -42
  130. package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
  131. package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
  132. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
  133. package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
  134. package/rancher-components/Pill/types.ts +0 -1
  135. package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
  136. package/rancher-components/RcIcon/RcIcon.vue +46 -0
  137. package/rancher-components/RcIcon/index.ts +1 -0
  138. package/rancher-components/RcIcon/types.ts +160 -0
  139. package/rancher-components/utils/status.test.ts +67 -0
  140. package/rancher-components/utils/status.ts +77 -0
  141. package/scripts/typegen.sh +1 -0
  142. package/store/action-menu.js +8 -0
  143. package/store/auth.js +3 -3
  144. package/store/catalog.js +6 -0
  145. package/store/index.js +36 -17
  146. package/store/prefs.js +4 -5
  147. package/store/type-map.js +3 -3
  148. package/store/wm.ts +4 -4
  149. package/types/shell/index.d.ts +39 -2
  150. package/types/store/__tests__/pagination.types.spec.ts +137 -0
  151. package/types/store/pagination.types.ts +157 -9
  152. package/types/store/subscribe-events.types.ts +8 -1
  153. package/types/store/subscribe.types.ts +1 -0
  154. package/utils/__tests__/provider.test.ts +98 -0
  155. package/utils/__tests__/selector-typed.test.ts +263 -0
  156. package/utils/__tests__/version.test.ts +19 -1
  157. package/utils/back-off.ts +3 -3
  158. package/utils/color.js +1 -1
  159. package/utils/dynamic-content/__tests__/info.test.ts +21 -9
  160. package/utils/dynamic-content/info.ts +44 -2
  161. package/utils/favicon.js +4 -4
  162. package/utils/pagination-wrapper.ts +12 -8
  163. package/utils/provider.ts +14 -0
  164. package/utils/selector-typed.ts +6 -2
  165. package/utils/version.js +15 -0
  166. package/plugins/nuxt-client-init.js +0 -3
@@ -0,0 +1,263 @@
1
+ import { labelSelectorToSelector } from '@shell/utils/selector-typed';
2
+ import { KubeLabelSelector } from '@shell/types/kube/kube-api';
3
+
4
+ describe('selector-typed', () => {
5
+ describe('labelSelectorToSelector', () => {
6
+ describe('empty label selectors', () => {
7
+ it('should return empty string for undefined label selector', () => {
8
+ const result = labelSelectorToSelector(undefined);
9
+
10
+ expect(result).toBe('');
11
+ });
12
+
13
+ it('should return empty string for label selector with no matchLabels and no matchExpressions', () => {
14
+ const labelSelector: KubeLabelSelector = {};
15
+ const result = labelSelectorToSelector(labelSelector);
16
+
17
+ expect(result).toBe('');
18
+ });
19
+
20
+ it('should return empty string for label selector with empty matchLabels', () => {
21
+ const labelSelector: KubeLabelSelector = { matchLabels: {} };
22
+ const result = labelSelectorToSelector(labelSelector);
23
+
24
+ expect(result).toBe('');
25
+ });
26
+
27
+ it('should return empty string for label selector with empty matchExpressions', () => {
28
+ const labelSelector: KubeLabelSelector = { matchExpressions: [] };
29
+ const result = labelSelectorToSelector(labelSelector);
30
+
31
+ expect(result).toBe('');
32
+ });
33
+
34
+ it('should return empty string for label selector with both empty matchLabels and matchExpressions', () => {
35
+ const labelSelector: KubeLabelSelector = {
36
+ matchLabels: {},
37
+ matchExpressions: []
38
+ };
39
+ const result = labelSelectorToSelector(labelSelector);
40
+
41
+ expect(result).toBe('');
42
+ });
43
+ });
44
+
45
+ describe('matchLabels conversion', () => {
46
+ it('should convert single matchLabel to selector string', () => {
47
+ const labelSelector: KubeLabelSelector = { matchLabels: { app: 'nginx' } };
48
+ const result = labelSelectorToSelector(labelSelector);
49
+
50
+ expect(result).toBe('app=nginx');
51
+ });
52
+
53
+ it('should convert multiple matchLabels to comma-separated selector string', () => {
54
+ const labelSelector: KubeLabelSelector = {
55
+ matchLabels: {
56
+ app: 'nginx',
57
+ version: 'v1.0',
58
+ env: 'production'
59
+ }
60
+ };
61
+ const result = labelSelectorToSelector(labelSelector);
62
+
63
+ expect(result).toBe('app=nginx,version=v1.0,env=production');
64
+ });
65
+
66
+ it('should handle matchLabels with special characters', () => {
67
+ const labelSelector: KubeLabelSelector = { matchLabels: { 'app.kubernetes.io/name': 'my-app' } };
68
+ const result = labelSelectorToSelector(labelSelector);
69
+
70
+ expect(result).toBe('app.kubernetes.io/name=my-app');
71
+ });
72
+
73
+ it('should handle matchLabels with numeric values', () => {
74
+ const labelSelector: KubeLabelSelector = { matchLabels: { tier: '3' } };
75
+ const result = labelSelectorToSelector(labelSelector);
76
+
77
+ expect(result).toBe('tier=3');
78
+ });
79
+ });
80
+
81
+ describe('matchExpressions conversion with In operator', () => {
82
+ it('should convert matchExpression with In operator and single value to equality selector', () => {
83
+ const labelSelector: KubeLabelSelector = {
84
+ matchExpressions: [
85
+ {
86
+ key: 'app',
87
+ operator: 'In',
88
+ values: ['nginx']
89
+ }
90
+ ]
91
+ };
92
+ const result = labelSelectorToSelector(labelSelector);
93
+
94
+ expect(result).toBe('app=nginx');
95
+ });
96
+
97
+ it('should convert matchExpression with In operator and multiple values to in() selector', () => {
98
+ const labelSelector: KubeLabelSelector = {
99
+ matchExpressions: [
100
+ {
101
+ key: 'env',
102
+ operator: 'In',
103
+ values: ['dev', 'staging', 'prod']
104
+ }
105
+ ]
106
+ };
107
+ const result = labelSelectorToSelector(labelSelector);
108
+
109
+ expect(result).toBe('env in (dev,staging,prod)');
110
+ });
111
+
112
+ it('should convert multiple matchExpressions with In operator', () => {
113
+ const labelSelector: KubeLabelSelector = {
114
+ matchExpressions: [
115
+ {
116
+ key: 'app',
117
+ operator: 'In',
118
+ values: ['nginx']
119
+ },
120
+ {
121
+ key: 'env',
122
+ operator: 'In',
123
+ values: ['dev', 'staging']
124
+ }
125
+ ]
126
+ };
127
+ const result = labelSelectorToSelector(labelSelector);
128
+
129
+ expect(result).toBe('app=nginx,env in (dev,staging)');
130
+ });
131
+
132
+ it('should handle matchExpression with empty values array for In operator', () => {
133
+ const labelSelector: KubeLabelSelector = {
134
+ matchExpressions: [
135
+ {
136
+ key: 'app',
137
+ operator: 'In',
138
+ values: []
139
+ }
140
+ ]
141
+ };
142
+ const result = labelSelectorToSelector(labelSelector);
143
+
144
+ // With empty values array, it should create an in() with no values
145
+ expect(result).toBe('app in ()');
146
+ });
147
+ });
148
+
149
+ describe('combined matchLabels and matchExpressions', () => {
150
+ it('should combine matchLabels and matchExpressions with single values', () => {
151
+ const labelSelector: KubeLabelSelector = {
152
+ matchLabels: { tier: 'frontend' },
153
+ matchExpressions: [
154
+ {
155
+ key: 'env',
156
+ operator: 'In',
157
+ values: ['prod']
158
+ }
159
+ ]
160
+ };
161
+ const result = labelSelectorToSelector(labelSelector);
162
+
163
+ expect(result).toBe('tier=frontend,env=prod');
164
+ });
165
+
166
+ it('should combine multiple matchLabels and matchExpressions', () => {
167
+ const labelSelector: KubeLabelSelector = {
168
+ matchLabels: {
169
+ tier: 'frontend',
170
+ version: 'v2'
171
+ },
172
+ matchExpressions: [
173
+ {
174
+ key: 'env',
175
+ operator: 'In',
176
+ values: ['dev', 'staging']
177
+ },
178
+ {
179
+ key: 'region',
180
+ operator: 'In',
181
+ values: ['us-west-1']
182
+ }
183
+ ]
184
+ };
185
+ const result = labelSelectorToSelector(labelSelector);
186
+
187
+ expect(result).toBe('tier=frontend,version=v2,env in (dev,staging),region=us-west-1');
188
+ });
189
+
190
+ it('should combine matchLabels with multiple matchExpressions using in() notation', () => {
191
+ const labelSelector: KubeLabelSelector = {
192
+ matchLabels: { 'app.kubernetes.io/name': 'myapp' },
193
+ matchExpressions: [
194
+ {
195
+ key: 'env',
196
+ operator: 'In',
197
+ values: ['dev', 'test', 'prod']
198
+ }
199
+ ]
200
+ };
201
+ const result = labelSelectorToSelector(labelSelector);
202
+
203
+ expect(result).toBe('app.kubernetes.io/name=myapp,env in (dev,test,prod)');
204
+ });
205
+ });
206
+
207
+ describe('unsupported operators', () => {
208
+ it('should throw error for NotIn operator', () => {
209
+ const labelSelector: KubeLabelSelector = {
210
+ matchExpressions: [
211
+ {
212
+ key: 'env',
213
+ operator: 'NotIn',
214
+ values: ['prod']
215
+ }
216
+ ]
217
+ };
218
+
219
+ expect(() => labelSelectorToSelector(labelSelector)).toThrow('Unsupported matchExpression found when converting to selector string.');
220
+ });
221
+ });
222
+
223
+ describe('edge cases', () => {
224
+ it('should handle matchExpression with In operator but undefined values', () => {
225
+ const labelSelector: KubeLabelSelector = {
226
+ matchExpressions: [
227
+ {
228
+ key: 'app',
229
+ operator: 'In',
230
+ values: undefined
231
+ }
232
+ ]
233
+ };
234
+
235
+ // When values is undefined, the function throws an error
236
+ expect(() => labelSelectorToSelector(labelSelector)).toThrow('Unsupported matchExpression found when converting to selector string.');
237
+ });
238
+
239
+ it('should preserve order of matchLabels and matchExpressions', () => {
240
+ const labelSelector: KubeLabelSelector = {
241
+ matchLabels: {
242
+ first: 'value1',
243
+ second: 'value2'
244
+ },
245
+ matchExpressions: [
246
+ {
247
+ key: 'third',
248
+ operator: 'In',
249
+ values: ['value3']
250
+ }
251
+ ]
252
+ };
253
+ const result = labelSelectorToSelector(labelSelector);
254
+
255
+ // matchLabels come before matchExpressions
256
+ expect(result).toContain('first=value1');
257
+ expect(result).toContain('second=value2');
258
+ expect(result).toContain('third=value3');
259
+ expect(result.indexOf('first')).toBeLessThan(result.indexOf('third'));
260
+ });
261
+ });
262
+ });
263
+ });
@@ -1,4 +1,4 @@
1
- import { isDevBuild } from '@shell/utils/version';
1
+ import { isDevBuild, isUpgradeFromPreToStable } from '@shell/utils/version';
2
2
 
3
3
  describe('fx: isDevBuild', () => {
4
4
  it.each([
@@ -16,3 +16,21 @@ describe('fx: isDevBuild', () => {
16
16
  }
17
17
  );
18
18
  });
19
+
20
+ describe('fx: isUpgradeFromPreToStable', () => {
21
+ it('should be true when going from pre-release to stable of same version', () => {
22
+ expect(isUpgradeFromPreToStable('1.0.0-rc1', '1.0.0')).toBe(true);
23
+ });
24
+
25
+ it('should be false when going from stable to pre-release', () => {
26
+ expect(isUpgradeFromPreToStable('1.0.0', '1.0.0-rc1')).toBe(false );
27
+ });
28
+
29
+ it('should be false for stable to stable', () => {
30
+ expect(isUpgradeFromPreToStable('1.0.0', '1.1.0')).toBe(false);
31
+ });
32
+
33
+ it('should be false for pre-release to pre-release', () => {
34
+ expect(isUpgradeFromPreToStable('1.0.0-rc1', '1.0.0-rc2')).toBe(false);
35
+ });
36
+ });
package/utils/back-off.ts CHANGED
@@ -137,9 +137,9 @@ class BackOff {
137
137
 
138
138
  // First step is immediate (0.001s)
139
139
  // Second and others are exponential
140
- // 1, 2, 3, 4, 5, 6, 7, 8, 9
141
- // 1, 4, 9, 16, 25, 36, 49, 64, 81
142
- // 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
140
+ // Try: 1, 2, 3, 4, 5, 6, 7, 8, 9
141
+ // Multiple: 1, 4, 9, 16, 25, 36, 49, 64, 81
142
+ // Actual Time: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
143
143
  const delay = backOffTry === 0 ? 1 : Math.pow(backOffTry, 2) * 250;
144
144
 
145
145
  this.log('info', id, `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description);
package/utils/color.js CHANGED
@@ -13,7 +13,7 @@ Primary color classes from _light.scss
13
13
 
14
14
  */
15
15
 
16
- const Color = require('color');
16
+ import Color from 'color';
17
17
 
18
18
  export function createCssVars(color, theme = 'light', name = 'primary') {
19
19
  const contrastOpts = theme === 'light' ? LIGHT_CONTRAST_COLORS : DARK_CONTRAST_COLORS;
@@ -96,6 +96,8 @@ describe('systemInfoProvider', () => {
96
96
  return undefined;
97
97
  }),
98
98
  'management/schemaFor': jest.fn(),
99
+ localCluster: mockClusters.find((c) => c.id === 'local') || null,
100
+ 'features/get': jest.fn(() => 'abc'),
99
101
  };
100
102
 
101
103
  (version.getVersionData as jest.Mock).mockReturnValue({
@@ -128,6 +130,7 @@ describe('systemInfoProvider', () => {
128
130
  expect(qs).toContain('bl=en-US');
129
131
  expect(qs).toContain('bs=1024x768');
130
132
  expect(qs).toContain('ss=1920x1080');
133
+ expect(qs).toContain('ff-usc=abc');
131
134
  });
132
135
 
133
136
  it('should handle missing or partial data gracefully', () => {
@@ -159,6 +162,10 @@ describe('systemInfoProvider', () => {
159
162
 
160
163
  mockGetters['uiplugins/plugins'] = null; // No plugins
161
164
  mockGetters['auth/principalId'] = null; // No user
165
+ mockGetters['localCluster'] = null; // No clusters
166
+ mockGetters['features/get'] = () => {
167
+ throw new Error('unknown feature');
168
+ };
162
169
 
163
170
  const infoProvider = new SystemInfoProvider(mockGetters, {});
164
171
  const qs = infoProvider.buildQueryString();
@@ -175,6 +182,7 @@ describe('systemInfoProvider', () => {
175
182
  expect(qs).not.toContain('lnc=');
176
183
  expect(qs).not.toContain('xkn=');
177
184
  expect(qs).not.toContain('xcc=');
185
+ expect(qs).not.toContain('ff-usc=');
178
186
  });
179
187
 
180
188
  it('should handle getAll returning undefined when types are not registered', () => {
@@ -191,6 +199,7 @@ describe('systemInfoProvider', () => {
191
199
 
192
200
  mockGetters['auth/principalId'] = 'user-456';
193
201
  mockGetters['uiplugins/plugins'] = []; // No plugins
202
+ mockGetters['localCluster'] = null; // No clusters
194
203
 
195
204
  const infoProvider = new SystemInfoProvider(mockGetters, {});
196
205
  const qs = infoProvider.buildQueryString();
@@ -199,7 +208,6 @@ describe('systemInfoProvider', () => {
199
208
  expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'install-uuid');
200
209
  expect(mockGetters['management/byId']).toHaveBeenCalledWith(MANAGEMENT.SETTING, 'server-version-type');
201
210
  expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(COUNT);
202
- expect(mockGetters['management/typeRegistered']).toHaveBeenCalledWith(MANAGEMENT.CLUSTER);
203
211
  expect(mockGetters['management/all']).not.toHaveBeenCalled();
204
212
 
205
213
  // Verify the query string is built with fallback or empty values
@@ -225,6 +233,16 @@ describe('systemInfoProvider', () => {
225
233
  return { id, value: '' }; // Empty values for all settings
226
234
  }
227
235
  });
236
+
237
+ // local cluster with missing properties
238
+ const localCluster = {
239
+ id: 'local',
240
+ isLocal: true,
241
+ status: { nodeCount: 1 },
242
+ // kubernetesVersionBase is missing
243
+ // provisioner is missing
244
+ };
245
+
228
246
  mockGetters['management/all'].mockImplementation((type: string) => {
229
247
  if (type === MANAGEMENT.SETTING) {
230
248
  // Return settings, but with empty values
@@ -237,20 +255,14 @@ describe('systemInfoProvider', () => {
237
255
  return [{ counts: { [MANAGEMENT.CLUSTER]: { summary: { count: 1 } } } }];
238
256
  }
239
257
  if (type === MANAGEMENT.CLUSTER) {
240
- // local cluster with missing properties
241
- return [{
242
- id: 'local',
243
- isLocal: true,
244
- status: { nodeCount: 1 },
245
- // kubernetesVersionBase is missing
246
- // provisioner is missing
247
- }];
258
+ return [localCluster];
248
259
  }
249
260
 
250
261
  return [];
251
262
  });
252
263
 
253
264
  mockGetters['auth/principalId'] = null; // No user
265
+ mockGetters['localCluster'] = localCluster;
254
266
 
255
267
  const infoProvider = new SystemInfoProvider(mockGetters, {});
256
268
  const qs = infoProvider.buildQueryString();
@@ -10,6 +10,7 @@ import {
10
10
  import { SETTING } from '@shell/config/settings';
11
11
  import { getVersionData } from '@shell/config/version';
12
12
  import { SettingsInfo } from '@shell/utils/dynamic-content/types';
13
+ import { STEVE_CACHE } from '@shell/store/features';
13
14
 
14
15
  const QS_VERSION = 'v1'; // Include a version number in the query string in case we want to version the set of params we are sending
15
16
  const UNKNOWN = 'unknown';
@@ -26,6 +27,29 @@ const SUSE_EXTENSIONS = [
26
27
  'virtual-clusters'
27
28
  ];
28
29
 
30
+ type FeatureFlagInfos = {
31
+ [id: string]: {
32
+ /**
33
+ * Query param, in format `ff-<param>`
34
+ */
35
+ param: string,
36
+ /**
37
+ * The actual value used by the UI, roughly spec.value || status.default
38
+ */
39
+ value: string,
40
+ }
41
+ };
42
+
43
+ /**
44
+ * Explicit ff's to send
45
+ */
46
+ const ffs: FeatureFlagInfos = {
47
+ [STEVE_CACHE]: {
48
+ param: 'usc',
49
+ value: '',
50
+ }
51
+ };
52
+
29
53
  /**
30
54
  * System information that is collected and which can then be encoded into a query string in the dyanmic content request
31
55
  */
@@ -47,6 +71,7 @@ type SystemInfo = {
47
71
  browserSize: string;
48
72
  screenSize: string;
49
73
  language: string;
74
+ featureFlags: FeatureFlagInfos
50
75
  };
51
76
 
52
77
  /**
@@ -107,8 +132,7 @@ export class SystemInfoProvider {
107
132
  // High-level information from clusters
108
133
  const counts = this.getAll(getters, COUNT)?.[0]?.counts || {};
109
134
  const clusterCount = counts[MANAGEMENT.CLUSTER] || {};
110
- const all = this.getAll(getters, MANAGEMENT.CLUSTER);
111
- const localCluster = all ? all.find((c: any) => c.isLocal) : undefined;
135
+ const localCluster = getters['localCluster'];
112
136
 
113
137
  // Stats for installed extensions
114
138
  const uiExtensionList = getters['uiplugins/plugins'];
@@ -132,6 +156,19 @@ export class SystemInfoProvider {
132
156
  const screenSize = `${ window.screen?.width || '?' }x${ window.screen?.height || '?' }`;
133
157
  const browserSize = `${ window.innerWidth }x${ window.innerHeight }`;
134
158
 
159
+ const safeFfs = Object.entries(ffs).reduce((res, [id, ff]) => {
160
+ try {
161
+ res[id] = {
162
+ param: ff.param,
163
+ value: getters['features/get'](id),
164
+ };
165
+ } catch (e) {
166
+ console.debug(`Cannot include Feature Flag "${ id }" in dynamic feature request: `, e); // eslint-disable-line no-console
167
+ }
168
+
169
+ return res;
170
+ }, {} as FeatureFlagInfos);
171
+
135
172
  return {
136
173
  systemUUID,
137
174
  userHash,
@@ -147,6 +184,7 @@ export class SystemInfoProvider {
147
184
  screenSize,
148
185
  browserSize,
149
186
  language: window.navigator?.language,
187
+ featureFlags: safeFfs,
150
188
  };
151
189
  }
152
190
 
@@ -214,6 +252,10 @@ export class SystemInfoProvider {
214
252
  params.push(`ss=${ systemData.screenSize }`);
215
253
  }
216
254
 
255
+ Object.values(systemData.featureFlags).forEach((ff) => {
256
+ params.push(`ff-` + `${ ff.param }=${ ff.value }`);
257
+ });
258
+
217
259
  return params.join('&');
218
260
  }
219
261
  }
package/utils/favicon.js CHANGED
@@ -9,17 +9,17 @@ export function haveSetFavIcon() {
9
9
 
10
10
  export function setFavIcon(store) {
11
11
  const res = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.FAVICON);
12
- const brandSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.BRAND);
12
+ const brandSetting = store.getters['management/brand'];
13
13
  const link = findIconLink(document.head.getElementsByTagName('link'));
14
14
 
15
15
  if (link) {
16
16
  let brandImage;
17
17
 
18
- if (brandSetting?.value === 'suse') {
18
+ if (brandSetting === 'suse') {
19
19
  brandImage = require('~shell/assets/brand/suse/favicon.png');
20
- } else if (brandSetting?.value === 'csp') {
20
+ } else if (brandSetting === 'csp') {
21
21
  brandImage = require('~shell/assets/brand/csp/favicon.png');
22
- } else if (brandSetting?.value === 'harvester') {
22
+ } else if (brandSetting === 'harvester') {
23
23
  brandImage = require('~shell/assets/brand/harvester/favicon.png');
24
24
  }
25
25
 
@@ -19,7 +19,7 @@ interface Args {
19
19
  /**
20
20
  * Callback called when the resource is changed (notified by socket)
21
21
  */
22
- onChange?: () => void,
22
+ onChange?: STEVE_WATCH_EVENT_LISTENER_CALLBACK,
23
23
 
24
24
  formatResponse?: {
25
25
  /**
@@ -72,13 +72,13 @@ class PaginationWrapper<T extends object> {
72
72
  this.isEnabled = paginationUtils.isEnabled({ rootGetters: $store.getters, $plugin: this.$store.$plugin }, enabledFor);
73
73
  }
74
74
 
75
- async request(args: {
76
- pagination: PaginationArgs,
75
+ async request({ pagination, forceWatch }: {
76
+ forceWatch?: boolean,
77
+ pagination: PaginationArgs,
77
78
  }): Promise<Result<T>> {
78
79
  if (!this.isEnabled) {
79
80
  throw new Error(`Wrapper for type '${ this.enabledFor.store }/${ this.enabledFor.resource?.id }' in context '${ this.enabledFor.resource?.context }' not supported`);
80
81
  }
81
- const { pagination } = args;
82
82
  const opt: ActionFindPageArgs = {
83
83
  watch: false,
84
84
  pagination,
@@ -89,14 +89,18 @@ class PaginationWrapper<T extends object> {
89
89
  const out: ActionFindPageTransientResult<T> = await this.$store.dispatch(`${ this.enabledFor.store }/findPage`, { opt, type: this.enabledFor.resource?.id });
90
90
 
91
91
  // Watch
92
- if (this.onChange && !this.steveWatchParams) {
92
+ const firstTime = !this.steveWatchParams;
93
+
94
+ if (this.onChange && (firstTime || forceWatch) ) { // && !this.steveWatchParams
93
95
  this.steveWatchParams = {
94
96
  event: STEVE_WATCH_EVENT_TYPES.CHANGES,
95
97
  id: this.id,
96
98
  params: {
97
- type: this.enabledFor.resource?.id as string,
98
- mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
99
- }
99
+ type: this.enabledFor.resource?.id as string,
100
+ mode: STEVE_WATCH_MODE.RESOURCE_CHANGES,
101
+ force: forceWatch,
102
+ },
103
+
100
104
  };
101
105
 
102
106
  this.watch();
@@ -0,0 +1,14 @@
1
+ import { IClusterProvisioner, ClusterProvisionerContext } from '@shell/core/types';
2
+
3
+ export function getHostedProviders(context: ClusterProvisionerContext) {
4
+ return context?.$extension?.getProviders(context)?.filter((p: IClusterProvisioner) => p.group === 'hosted') || [];
5
+ }
6
+
7
+ export function isHostedProvider(context: ClusterProvisionerContext, provisioner: string) {
8
+ if (!provisioner) {
9
+ return false;
10
+ }
11
+ const provisioners = new Set(getHostedProviders(context).map((p: IClusterProvisioner) => p.id.toLowerCase()));
12
+
13
+ return provisioners.has(provisioner.toLowerCase());
14
+ }
@@ -199,8 +199,12 @@ export function labelSelectorToSelector(labelSelector?: KubeLabelSelector): stri
199
199
  });
200
200
 
201
201
  (labelSelector?.matchExpressions || []).forEach((value: KubeLabelSelectorExpression) => {
202
- if (value.operator === 'In' && value.values?.length === 1) {
203
- res.push(`${ value.key }=${ value.values[0] }`);
202
+ if (value.operator === 'In' && value.values !== undefined) {
203
+ if (value.values?.length === 1) {
204
+ res.push(`${ value.key }=${ value.values[0] }`);
205
+ } else {
206
+ res.push(`${ value.key } in (${ value.values.join(',') })`);
207
+ }
204
208
  } else {
205
209
  throw new Error(`Unsupported matchExpression found when converting to selector string. ${ value }`);
206
210
  }
package/utils/version.js CHANGED
@@ -74,6 +74,21 @@ export function isPrerelease(version = '') {
74
74
  return !!semver.prerelease(version);
75
75
  }
76
76
 
77
+ export function isUpgradeFromPreToStable(currentVersion, targetVersion) {
78
+ if (!isPrerelease(currentVersion) || isPrerelease(targetVersion)) {
79
+ return false;
80
+ }
81
+
82
+ const cVersion = semver.clean(currentVersion, { loose: true });
83
+ const tVersion = semver.clean(targetVersion, { loose: true });
84
+
85
+ if (cVersion && tVersion && semver.valid(cVersion) && semver.valid(tVersion)) {
86
+ return semver.lt(cVersion, tVersion);
87
+ }
88
+
89
+ return false;
90
+ }
91
+
77
92
  export function isDevBuild(version) {
78
93
  if ( ['dev', 'master', 'head'].includes(version) || version.endsWith('-head') || version.match(/-rc\d+$/) || version.match(/-alpha\d+$/) ) {
79
94
  return true;
@@ -1,3 +0,0 @@
1
- export default async(context) => {
2
- await context.store.dispatch('nuxtClientInit', context);
3
- };