@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.
- package/assets/brand/suse/dark/rancher-logo.svg +1 -64
- package/assets/translations/en-us.yaml +9 -1
- package/components/BackLink.vue +8 -0
- package/components/BannerGraphic.vue +1 -5
- package/components/BrandImage.vue +17 -6
- package/components/Cron/CronExpressionEditor.vue +1 -1
- package/components/Cron/CronExpressionEditorModal.vue +1 -1
- package/components/Drawer/Chrome.vue +2 -6
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/YamlTab.vue +3 -8
- package/components/Drawer/ResourceDetailDrawer/composables.ts +3 -4
- package/components/Drawer/ResourceDetailDrawer/index.vue +4 -9
- package/components/Drawer/ResourceDetailDrawer/types.ts +17 -0
- package/components/Drawer/types.ts +3 -0
- package/components/PaginatedResourceTable.vue +2 -6
- package/components/Questions/__tests__/index.test.ts +159 -0
- package/components/Resource/Detail/Metadata/Annotations/index.vue +2 -2
- package/components/Resource/Detail/Metadata/Labels/index.vue +2 -2
- package/components/Resource/Detail/Metadata/composables.ts +9 -9
- package/components/Resource/Detail/Metadata/index.vue +3 -3
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/composables.ts +12 -0
- package/components/Tabbed/__tests__/index.test.ts +86 -0
- package/components/auth/SelectPrincipal.vue +24 -6
- package/components/auth/__tests__/SelectPrincipal.test.ts +119 -0
- package/components/formatter/InternalExternalIP.vue +4 -1
- package/components/formatter/__tests__/InternalExternalIP.test.ts +1 -1
- package/components/nav/Header.vue +1 -2
- package/components/nav/TopLevelMenu.helper.ts +16 -6
- package/components/templates/standalone.vue +1 -1
- package/composables/useI18n.ts +10 -1
- package/config/__test__/uiplugins.test.ts +309 -0
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +3 -1
- package/config/router/routes.js +6 -2
- package/config/types.js +7 -0
- package/config/uiplugins.js +46 -2
- package/core/__test__/extension-manager-impl.test.js +236 -0
- package/core/extension-manager-impl.js +23 -6
- package/core/types-provisioning.ts +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +1 -0
- package/dialog/DeveloperLoadExtensionDialog.vue +12 -3
- package/dialog/RollbackWorkloadDialog.vue +2 -5
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +2 -2
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -0
- package/edit/configmap.vue +1 -0
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +1 -0
- package/edit/fleet.cattle.io.helmop.vue +6 -6
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +1 -0
- package/edit/logging-flow/index.vue +1 -0
- package/edit/logging.banzaicloud.io.output/index.vue +1 -0
- package/edit/management.cattle.io.fleetworkspace.vue +1 -1
- package/edit/management.cattle.io.project.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +4 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -0
- package/edit/monitoring.coreos.com.receiver/index.vue +2 -1
- package/edit/monitoring.coreos.com.route.vue +1 -1
- package/edit/namespace.vue +1 -0
- package/edit/networking.istio.io.destinationrule/index.vue +1 -0
- package/edit/networking.k8s.io.ingress/index.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -0
- package/edit/networking.k8s.io.networkpolicy/index.vue +1 -0
- package/edit/node.vue +1 -0
- package/edit/persistentvolume/index.vue +27 -22
- package/edit/persistentvolume/plugins/awsElasticBlockStore.vue +13 -14
- package/edit/persistentvolume/plugins/azureDisk.vue +49 -48
- package/edit/persistentvolume/plugins/azureFile.vue +15 -14
- package/edit/persistentvolume/plugins/cephfs.vue +15 -14
- package/edit/persistentvolume/plugins/cinder.vue +15 -14
- package/edit/persistentvolume/plugins/csi.vue +18 -16
- package/edit/persistentvolume/plugins/fc.vue +13 -14
- package/edit/persistentvolume/plugins/flexVolume.vue +15 -14
- package/edit/persistentvolume/plugins/flocker.vue +1 -3
- package/edit/persistentvolume/plugins/gcePersistentDisk.vue +13 -14
- package/edit/persistentvolume/plugins/glusterfs.vue +15 -14
- package/edit/persistentvolume/plugins/hostPath.vue +40 -39
- package/edit/persistentvolume/plugins/iscsi.vue +13 -14
- package/edit/persistentvolume/plugins/local.vue +1 -3
- package/edit/persistentvolume/plugins/longhorn.vue +23 -22
- package/edit/persistentvolume/plugins/nfs.vue +15 -14
- package/edit/persistentvolume/plugins/photonPersistentDisk.vue +1 -14
- package/edit/persistentvolume/plugins/portworxVolume.vue +15 -14
- package/edit/persistentvolume/plugins/quobyte.vue +15 -14
- package/edit/persistentvolume/plugins/rbd.vue +15 -14
- package/edit/persistentvolume/plugins/scaleIO.vue +15 -14
- package/edit/persistentvolume/plugins/storageos.vue +15 -14
- package/edit/persistentvolume/plugins/vsphereVolume.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -0
- package/edit/secret/index.vue +1 -1
- package/edit/service.vue +1 -0
- package/edit/serviceaccount.vue +1 -0
- package/edit/storage.k8s.io.storageclass/index.vue +1 -0
- package/edit/workload/index.vue +2 -1
- package/edit/workload/mixins/workload.js +1 -1
- package/initialize/App.vue +4 -4
- package/initialize/install-plugins.js +17 -2
- package/machine-config/components/EC2Networking.vue +5 -2
- package/machine-config/components/__tests__/EC2Networking.test.ts +24 -0
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/__tests__/chart.test.ts +21 -0
- package/mixins/brand.js +1 -7
- package/mixins/chart.js +7 -1
- package/mixins/create-edit-view/index.js +5 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +112 -5
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/provisioning.cattle.io.cluster.js +21 -9
- package/package.json +5 -4
- package/pages/auth/login.vue +1 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +135 -0
- package/pages/c/_cluster/apps/charts/chart.vue +33 -15
- package/pages/c/_cluster/explorer/index.vue +8 -6
- package/pages/c/_cluster/manager/hostedprovider/index.vue +12 -6
- package/pages/c/_cluster/settings/brand.vue +1 -1
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +7 -0
- package/pages/c/_cluster/uiplugins/catalogs.vue +147 -0
- package/pages/c/_cluster/uiplugins/index.vue +126 -184
- package/pages/home.vue +5 -2
- package/pkg/dynamic-importer.lib.js +4 -0
- package/plugins/dashboard-client-init.js +3 -0
- package/plugins/dashboard-store/getters.js +18 -1
- package/plugins/dashboard-store/resource-class.js +4 -4
- package/plugins/i18n.js +8 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +333 -0
- package/plugins/steve/steve-pagination-utils.ts +39 -20
- package/plugins/steve/subscribe.js +17 -9
- package/plugins/subscribe-events.ts +4 -2
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +6 -42
- package/rancher-components/Pill/RcStatusBadge/index.ts +0 -1
- package/rancher-components/Pill/RcStatusBadge/types.ts +1 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +5 -28
- package/rancher-components/Pill/RcStatusIndicator/types.ts +2 -1
- package/rancher-components/Pill/types.ts +0 -1
- package/rancher-components/RcIcon/RcIcon.test.ts +51 -0
- package/rancher-components/RcIcon/RcIcon.vue +46 -0
- package/rancher-components/RcIcon/index.ts +1 -0
- package/rancher-components/RcIcon/types.ts +160 -0
- package/rancher-components/utils/status.test.ts +67 -0
- package/rancher-components/utils/status.ts +77 -0
- package/scripts/typegen.sh +1 -0
- package/store/action-menu.js +8 -0
- package/store/auth.js +3 -3
- package/store/catalog.js +6 -0
- package/store/index.js +36 -17
- package/store/prefs.js +4 -5
- package/store/type-map.js +3 -3
- package/store/wm.ts +4 -4
- package/types/shell/index.d.ts +39 -2
- package/types/store/__tests__/pagination.types.spec.ts +137 -0
- package/types/store/pagination.types.ts +157 -9
- package/types/store/subscribe-events.types.ts +8 -1
- package/types/store/subscribe.types.ts +1 -0
- package/utils/__tests__/provider.test.ts +98 -0
- package/utils/__tests__/selector-typed.test.ts +263 -0
- package/utils/__tests__/version.test.ts +19 -1
- package/utils/back-off.ts +3 -3
- package/utils/color.js +1 -1
- package/utils/dynamic-content/__tests__/info.test.ts +21 -9
- package/utils/dynamic-content/info.ts +44 -2
- package/utils/favicon.js +4 -4
- package/utils/pagination-wrapper.ts +12 -8
- package/utils/provider.ts +14 -0
- package/utils/selector-typed.ts +6 -2
- package/utils/version.js +15 -0
- 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,
|
|
141
|
-
// 1,
|
|
142
|
-
// 0.25s, 1s,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
|
18
|
+
if (brandSetting === 'suse') {
|
|
19
19
|
brandImage = require('~shell/assets/brand/suse/favicon.png');
|
|
20
|
-
} else if (brandSetting
|
|
20
|
+
} else if (brandSetting === 'csp') {
|
|
21
21
|
brandImage = require('~shell/assets/brand/csp/favicon.png');
|
|
22
|
-
} else if (brandSetting
|
|
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?:
|
|
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(
|
|
76
|
-
|
|
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
|
-
|
|
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:
|
|
98
|
-
mode:
|
|
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
|
+
}
|
package/utils/selector-typed.ts
CHANGED
|
@@ -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
|
|
203
|
-
|
|
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;
|