@rancher/shell 3.0.9-rc.3 → 3.0.9-rc.5
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/metadata.json +2 -1
- package/assets/translations/en-us.yaml +105 -5
- package/components/ActionMenuShell.vue +1 -1
- package/components/Inactivity.vue +2 -2
- package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
- package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
- package/components/Resource/Detail/Masthead/index.vue +11 -4
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
- package/components/Resource/Detail/Metadata/index.vue +1 -1
- package/components/Resource/Detail/ResourceRow.vue +1 -1
- package/components/ResourceDetail/Masthead/latest.vue +12 -2
- package/components/ResourceList/index.vue +9 -0
- package/components/ResourceTable.vue +38 -4
- package/components/Tabbed/Tab.vue +4 -0
- package/components/Tabbed/index.vue +4 -1
- package/components/__tests__/ProjectRow.test.ts +60 -0
- package/components/form/ChangePassword.vue +41 -35
- package/components/form/ResourceQuota/Project.vue +42 -1
- package/components/form/ResourceQuota/ProjectRow.vue +71 -4
- package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -1
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
- package/components/formatter/KubeconfigClusters.vue +74 -0
- package/components/formatter/MachineSummaryGraph.vue +10 -2
- package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
- package/components/nav/TopLevelMenu.helper.ts +50 -2
- package/components/nav/TopLevelMenu.vue +14 -0
- package/components/nav/Type.vue +5 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
- package/components/nav/__tests__/Type.test.ts +6 -4
- package/config/product/explorer.js +4 -3
- package/config/product/manager.js +47 -3
- package/config/router/navigation-guards/authentication.js +8 -9
- package/config/router/routes.js +4 -1
- package/config/types.js +10 -2
- package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
- package/detail/management.cattle.io.user.vue +1 -2
- package/detail/node.vue +0 -1
- package/detail/provisioning.cattle.io.cluster.vue +2 -1
- package/dialog/ChangePasswordDialog.vue +8 -0
- package/dialog/GenericPrompt.vue +20 -3
- package/dialog/ScaleMachineDownDialog.vue +65 -15
- package/dialog/SearchDialog.vue +10 -2
- package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
- package/edit/__tests__/management.cattle.io.project.test.js +56 -1
- package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
- package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
- package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
- package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
- package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
- package/edit/fleet.cattle.io.gitrepo.vue +16 -1
- package/edit/management.cattle.io.project.vue +8 -2
- package/edit/management.cattle.io.user.vue +29 -34
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
- package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
- package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
- package/list/ext.cattle.io.kubeconfig.vue +118 -0
- package/list/group.principal.vue +11 -15
- package/list/management.cattle.io.user.vue +11 -21
- package/machine-config/azure.vue +14 -0
- package/mixins/__tests__/chart.test.ts +147 -0
- package/mixins/browser-tab-visibility.js +5 -4
- package/mixins/chart.js +10 -8
- package/mixins/fetch.client.js +6 -0
- package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
- package/models/__tests__/secret.test.ts +55 -0
- package/models/__tests__/workload.test.ts +49 -6
- package/models/auditlog.cattle.io.auditpolicy.js +46 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
- package/models/event.js +5 -0
- package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
- package/models/ext.cattle.io.kubeconfig.ts +97 -0
- package/models/ext.cattle.io.passwordchangerequest.js +15 -0
- package/models/ext.cattle.io.selfuser.js +15 -0
- package/models/fleet-application.js +17 -7
- package/models/management.cattle.io.user.js +28 -31
- package/models/schema.js +18 -0
- package/models/secret.js +28 -25
- package/models/steve-schema.ts +39 -2
- package/models/workload.js +3 -2
- package/package.json +2 -2
- package/pages/about.vue +3 -2
- package/pages/account/index.vue +23 -16
- package/pages/auth/login.vue +15 -8
- package/pages/auth/setup.vue +52 -15
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
- package/pages/c/_cluster/apps/charts/index.vue +1 -0
- package/pages/home.vue +9 -3
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
- package/plugins/dashboard-store/actions.js +7 -0
- package/plugins/dashboard-store/getters.js +23 -1
- package/plugins/dashboard-store/index.js +3 -2
- package/plugins/dashboard-store/mutations.js +4 -0
- package/plugins/dashboard-store/resource-class.js +12 -5
- package/plugins/steve/__tests__/steve-class.test.ts +167 -0
- package/plugins/steve/schema.d.ts +5 -0
- package/plugins/steve/steve-class.js +19 -0
- package/plugins/steve/steve-pagination-utils.ts +2 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
- package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
- package/store/auth.js +57 -19
- package/store/notifications.ts +1 -1
- package/store/type-map.js +12 -1
- package/types/shell/index.d.ts +24 -15
- package/types/store/dashboard-store.types.ts +7 -0
- package/utils/__tests__/chart.test.ts +96 -0
- package/utils/__tests__/version.test.ts +1 -19
- package/utils/chart.js +64 -0
- package/utils/pagination-wrapper.ts +11 -3
- package/utils/version.js +5 -17
- package/vue.config.js +26 -13
|
@@ -112,6 +112,8 @@ interface RcItemCardProps {
|
|
|
112
112
|
|
|
113
113
|
/** Makes the card clickable and emits 'card-click' on click/enter/space */
|
|
114
114
|
clickable?: boolean;
|
|
115
|
+
|
|
116
|
+
role?: 'link' | 'button' | undefined;
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
const props = defineProps<RcItemCardProps>();
|
|
@@ -161,27 +163,23 @@ const statusTooltips = computed(() => props.header.statuses?.map((status) => lab
|
|
|
161
163
|
const cardMeta = computed(() => ({
|
|
162
164
|
ariaLabel: props.clickable ? t('itemCard.ariaLabel.clickable', { cardTitle: labelText(props.header.title) }) : undefined,
|
|
163
165
|
tabIndex: props.clickable ? '0' : undefined,
|
|
164
|
-
role: props.clickable ? 'button' : undefined,
|
|
165
|
-
actionMenuLabel: props.actions && t('itemCard.actionMenu.label', { cardTitle: labelText(props.header.title) })
|
|
166
|
+
role: props.role ?? (props.clickable ? 'button' : undefined),
|
|
167
|
+
actionMenuLabel: props.actions && t('itemCard.actionMenu.label', { cardTitle: labelText(props.header.title) }),
|
|
166
168
|
}));
|
|
167
169
|
|
|
170
|
+
const cursorValue = computed(() => props.clickable ? 'pointer' : 'auto');
|
|
168
171
|
</script>
|
|
169
172
|
|
|
170
173
|
<template>
|
|
171
174
|
<div
|
|
172
175
|
ref="cardEl"
|
|
173
176
|
class="item-card"
|
|
177
|
+
:data-testid="`item-card-${id}`"
|
|
174
178
|
:class="{
|
|
175
179
|
'clickable':
|
|
176
180
|
clickable
|
|
177
181
|
}"
|
|
178
|
-
:role="cardMeta.role"
|
|
179
|
-
:tabindex="cardMeta.tabIndex"
|
|
180
|
-
:aria-label="cardMeta.ariaLabel"
|
|
181
|
-
:data-testid="`item-card-${id}`"
|
|
182
182
|
@click="_handleCardClick"
|
|
183
|
-
@keydown.enter="_handleCardClick"
|
|
184
|
-
@keydown.space.prevent="_handleCardClick"
|
|
185
183
|
>
|
|
186
184
|
<div :class="['item-card-body', variant]">
|
|
187
185
|
<template v-if="variant !== 'small'">
|
|
@@ -214,7 +212,16 @@ const cardMeta = computed(() => ({
|
|
|
214
212
|
|
|
215
213
|
<div :class="['item-card-body-details', variant]">
|
|
216
214
|
<div :class="['item-card-header', variant]">
|
|
217
|
-
<div
|
|
215
|
+
<div
|
|
216
|
+
class="item-card-header-left"
|
|
217
|
+
:data-testid="`card-header-left`"
|
|
218
|
+
:role="cardMeta.role"
|
|
219
|
+
:tabindex="cardMeta.tabIndex"
|
|
220
|
+
:aria-label="cardMeta.ariaLabel"
|
|
221
|
+
@click.self="_handleCardClick"
|
|
222
|
+
@keydown.enter="_handleCardClick"
|
|
223
|
+
@keydown.space.prevent="_handleCardClick"
|
|
224
|
+
>
|
|
218
225
|
<template v-if="variant === 'small'">
|
|
219
226
|
<slot name="item-card-image">
|
|
220
227
|
<div
|
|
@@ -315,16 +322,22 @@ $image-medium-box-width: 48px;
|
|
|
315
322
|
border-radius: var(--border-radius-md);
|
|
316
323
|
border: 1px solid var(--border);
|
|
317
324
|
background: var(--body-bg);
|
|
325
|
+
cursor: v-bind(cursorValue);
|
|
318
326
|
|
|
319
327
|
&.clickable:hover {
|
|
320
328
|
border-color: var(--primary);
|
|
321
329
|
}
|
|
322
330
|
|
|
323
|
-
&:focus-visible {
|
|
331
|
+
&:has(.item-card-header-left:focus-visible) {
|
|
332
|
+
border-color: var(--primary);
|
|
324
333
|
@include focus-outline;
|
|
325
334
|
outline-offset: -2px;
|
|
326
335
|
}
|
|
327
336
|
|
|
337
|
+
&:focus-visible {
|
|
338
|
+
outline: none;
|
|
339
|
+
}
|
|
340
|
+
|
|
328
341
|
&-image {
|
|
329
342
|
width: $image-medium-box-width;
|
|
330
343
|
height: $image-medium-box-width;
|
|
@@ -358,6 +371,10 @@ $image-medium-box-width: 48px;
|
|
|
358
371
|
&-left {
|
|
359
372
|
flex-grow: 1;
|
|
360
373
|
min-width: 0;
|
|
374
|
+
|
|
375
|
+
&:focus-visible {
|
|
376
|
+
outline: none;
|
|
377
|
+
}
|
|
361
378
|
}
|
|
362
379
|
|
|
363
380
|
&-title {
|
package/store/auth.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { GITHUB_NONCE, GITHUB_REDIRECT, GITHUB_SCOPE } from '@shell/config/query-params';
|
|
2
|
-
import {
|
|
3
|
-
import { _MULTI } from '@shell/plugins/dashboard-store/actions';
|
|
2
|
+
import { MANAGEMENT, EXT } from '@shell/config/types';
|
|
4
3
|
import { addObjects, findBy, joinStringList } from '@shell/utils/array';
|
|
5
4
|
import { openAuthPopup, returnTo } from '@shell/utils/auth';
|
|
6
5
|
import { base64Encode } from '@shell/utils/crypto';
|
|
@@ -41,8 +40,9 @@ export const state = function() {
|
|
|
41
40
|
hasAuth: null,
|
|
42
41
|
loggedIn: false,
|
|
43
42
|
principalId: null,
|
|
44
|
-
|
|
43
|
+
user: null,
|
|
45
44
|
initialPass: null,
|
|
45
|
+
selfUser: null,
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -63,8 +63,8 @@ export const getters = {
|
|
|
63
63
|
return state.principalId;
|
|
64
64
|
},
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
return state.
|
|
66
|
+
user(state) {
|
|
67
|
+
return state.user;
|
|
68
68
|
},
|
|
69
69
|
|
|
70
70
|
initialPass(state) {
|
|
@@ -73,6 +73,10 @@ export const getters = {
|
|
|
73
73
|
|
|
74
74
|
isGithub(state) {
|
|
75
75
|
return state.principalId && state.principalId.startsWith('github_user://');
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
selfUser(state) {
|
|
79
|
+
return state.selfUser;
|
|
76
80
|
}
|
|
77
81
|
};
|
|
78
82
|
|
|
@@ -81,9 +85,13 @@ export const mutations = {
|
|
|
81
85
|
state.fromHeader = fromHeader;
|
|
82
86
|
},
|
|
83
87
|
|
|
84
|
-
gotUser(state,
|
|
88
|
+
gotUser(state, user) {
|
|
85
89
|
// Always deference to avoid race condition when setting `mustChangePassword`
|
|
86
|
-
state.
|
|
90
|
+
state.user = { ...user };
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
gotSelfUser(state, selfUser) {
|
|
94
|
+
state.selfUser = selfUser;
|
|
87
95
|
},
|
|
88
96
|
|
|
89
97
|
hasAuth(state, hasAuth) {
|
|
@@ -101,7 +109,8 @@ export const mutations = {
|
|
|
101
109
|
|
|
102
110
|
state.loggedIn = false;
|
|
103
111
|
state.principalId = null;
|
|
104
|
-
state.
|
|
112
|
+
state.user = null;
|
|
113
|
+
state.selfUser = null;
|
|
105
114
|
state.initialPass = null;
|
|
106
115
|
},
|
|
107
116
|
|
|
@@ -115,22 +124,42 @@ export const actions = {
|
|
|
115
124
|
commit('gotHeader', fromHeader);
|
|
116
125
|
},
|
|
117
126
|
|
|
127
|
+
async updateSelfUser({ dispatch, commit }, selfUser) {
|
|
128
|
+
const classifiedSelfUser = await dispatch('management/create', selfUser, { root: true });
|
|
129
|
+
|
|
130
|
+
commit('gotSelfUser', classifiedSelfUser);
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
async getSelfUser({ commit, dispatch, getters }) {
|
|
134
|
+
if (getters.selfUser) {
|
|
135
|
+
return Promise.resolve(getters.selfUser);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const selfUser = await dispatch('management/request', {
|
|
139
|
+
url: `/v1/${ EXT.SELFUSER }`,
|
|
140
|
+
method: 'POST',
|
|
141
|
+
data: {},
|
|
142
|
+
}, { root: true });
|
|
143
|
+
|
|
144
|
+
await dispatch('updateSelfUser', selfUser);
|
|
145
|
+
},
|
|
146
|
+
|
|
118
147
|
async getUser({ dispatch, commit, getters }) {
|
|
119
|
-
if (getters.
|
|
148
|
+
if (getters.user) {
|
|
120
149
|
return;
|
|
121
150
|
}
|
|
122
151
|
|
|
123
152
|
try {
|
|
124
|
-
|
|
125
|
-
type: NORMAN.USER,
|
|
126
|
-
opt: {
|
|
127
|
-
url: '/v3/users',
|
|
128
|
-
filter: { me: true },
|
|
129
|
-
load: _MULTI
|
|
130
|
-
}
|
|
131
|
-
}, { root: true });
|
|
153
|
+
let mgmtUser;
|
|
132
154
|
|
|
133
|
-
|
|
155
|
+
await dispatch('getSelfUser');
|
|
156
|
+
const selfUser = getters.selfUser;
|
|
157
|
+
|
|
158
|
+
if (selfUser) {
|
|
159
|
+
mgmtUser = await dispatch('management/request', { url: `/v1/${ MANAGEMENT.USER }/${ selfUser.status?.userID }` }, { root: true });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
commit('gotUser', mgmtUser);
|
|
134
163
|
} catch { }
|
|
135
164
|
},
|
|
136
165
|
|
|
@@ -266,7 +295,16 @@ export const actions = {
|
|
|
266
295
|
}
|
|
267
296
|
|
|
268
297
|
if (driver?.scopes) {
|
|
269
|
-
|
|
298
|
+
// In some cases, driver scopes can be an array. We need to convert this
|
|
299
|
+
// to a string that can be parsed by `joinStringList()`
|
|
300
|
+
try {
|
|
301
|
+
const driverScopes = Array.isArray(driver.scopes) ? driver.scopes.join(' ') : driver.scopes;
|
|
302
|
+
|
|
303
|
+
scopes = [joinStringList(scopes[0], driverScopes)];
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// eslint-disable-next-line no-console
|
|
306
|
+
console.error('Failed to join driver scopes', error);
|
|
307
|
+
}
|
|
270
308
|
}
|
|
271
309
|
|
|
272
310
|
let url = removeParam(redirectUrl, GITHUB_SCOPE);
|
package/store/notifications.ts
CHANGED
|
@@ -387,7 +387,7 @@ export const actions = {
|
|
|
387
387
|
*/
|
|
388
388
|
async init({ commit, getters } : any, userData: any) {
|
|
389
389
|
const userKey = userData.id;
|
|
390
|
-
const userId = userData.
|
|
390
|
+
const userId = userData.user?.metadata?.uid;
|
|
391
391
|
|
|
392
392
|
if (!userKey || !userId) {
|
|
393
393
|
console.error('Unable to initialize notifications - required user info not available'); // eslint-disable-line no-console
|
package/store/type-map.js
CHANGED
|
@@ -189,7 +189,7 @@ export const TYPE_MODES = {
|
|
|
189
189
|
*/
|
|
190
190
|
FAVORITE: 'favorite',
|
|
191
191
|
/**
|
|
192
|
-
* Represents
|
|
192
|
+
* Represents types that have a count and are not virtual or spoofed.
|
|
193
193
|
*
|
|
194
194
|
* For example the `More Resource` in the cluster explorer
|
|
195
195
|
*
|
|
@@ -1586,6 +1586,17 @@ export const mutations = {
|
|
|
1586
1586
|
collection: `/${ SPOOFED_PREFIX }/${ schema.id }`,
|
|
1587
1587
|
...(schema.links || {})
|
|
1588
1588
|
};
|
|
1589
|
+
|
|
1590
|
+
const verbs = schema.attributes?.verbs || [];
|
|
1591
|
+
|
|
1592
|
+
if ( !verbs.includes('list') ) {
|
|
1593
|
+
verbs.push('list');
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
schema.attributes = {
|
|
1597
|
+
...schema?.attributes,
|
|
1598
|
+
verbs
|
|
1599
|
+
};
|
|
1589
1600
|
});
|
|
1590
1601
|
|
|
1591
1602
|
const existing = findBy(state.spoofedTypes[product], 'type', copy.type);
|
package/types/shell/index.d.ts
CHANGED
|
@@ -2077,6 +2077,7 @@ export const POD_DISRUPTION_BUDGET: "policy.poddisruptionbudget";
|
|
|
2077
2077
|
export const PV: "persistentvolume";
|
|
2078
2078
|
export const PVC: "persistentvolumeclaim";
|
|
2079
2079
|
export const RESOURCE_QUOTA: "resourcequota";
|
|
2080
|
+
export const AUDIT_POLICY: "auditlog.cattle.io.auditpolicy";
|
|
2080
2081
|
export const SCHEMA: "schema";
|
|
2081
2082
|
export const SERVICE: "service";
|
|
2082
2083
|
export const SECRET: "secret";
|
|
@@ -2246,6 +2247,9 @@ export namespace BRAND {
|
|
|
2246
2247
|
}
|
|
2247
2248
|
export namespace EXT {
|
|
2248
2249
|
let USER_ACTIVITY: string;
|
|
2250
|
+
let SELFUSER: string;
|
|
2251
|
+
let GROUP_MEMBERSHIP_REFRESH_REQUESTS: string;
|
|
2252
|
+
let PASSWORD_CHANGE_REQUESTS: string;
|
|
2249
2253
|
let KUBECONFIG: string;
|
|
2250
2254
|
}
|
|
2251
2255
|
export namespace CAPI {
|
|
@@ -2378,6 +2382,9 @@ export const DEPRECATED: "Deprecated";
|
|
|
2378
2382
|
export const EXPERIMENTAL: "Experimental";
|
|
2379
2383
|
export const AUTOSCALER_CONFIG_MAP_ID: "kube-system/cluster-autoscaler-status";
|
|
2380
2384
|
export const HOSTED_PROVIDER: "hostedprovider";
|
|
2385
|
+
export namespace SAVED_COUNTS {
|
|
2386
|
+
let K8S_CLUSTERS: string;
|
|
2387
|
+
}
|
|
2381
2388
|
}
|
|
2382
2389
|
|
|
2383
2390
|
// @shell/config/version
|
|
@@ -3732,20 +3739,8 @@ export default class Resource {
|
|
|
3732
3739
|
rows: any[];
|
|
3733
3740
|
};
|
|
3734
3741
|
};
|
|
3735
|
-
get _cards():
|
|
3736
|
-
|
|
3737
|
-
props: {
|
|
3738
|
-
title: any;
|
|
3739
|
-
rows: any[];
|
|
3740
|
-
};
|
|
3741
|
-
}[];
|
|
3742
|
-
get cards(): {
|
|
3743
|
-
component: any;
|
|
3744
|
-
props: {
|
|
3745
|
-
title: any;
|
|
3746
|
-
rows: any[];
|
|
3747
|
-
};
|
|
3748
|
-
}[];
|
|
3742
|
+
get _cards(): any[];
|
|
3743
|
+
get cards(): any[];
|
|
3749
3744
|
}
|
|
3750
3745
|
}
|
|
3751
3746
|
|
|
@@ -3804,6 +3799,7 @@ export default class SteveModel extends HybridModel {
|
|
|
3804
3799
|
get modelExtensions(): any;
|
|
3805
3800
|
cleanForSave(data: any, forNew: any): any;
|
|
3806
3801
|
paginationEnabled(): any;
|
|
3802
|
+
processSaveResponse(res: any): void;
|
|
3807
3803
|
}
|
|
3808
3804
|
import HybridModel from './hybrid-class';
|
|
3809
3805
|
}
|
|
@@ -4089,6 +4085,20 @@ export function overlayIndividualBanners(parsedBanner: any, banners: any): void;
|
|
|
4089
4085
|
// @shell/utils/chart
|
|
4090
4086
|
|
|
4091
4087
|
declare module '@shell/utils/chart' {
|
|
4088
|
+
/**
|
|
4089
|
+
* Compares two chart versions using SemVer logic, with special handling for Rancher's "up" build metadata.
|
|
4090
|
+
*
|
|
4091
|
+
* It uses `semver.compare` for the primary comparison. If versions are considered equal (SemVer ignores build metadata),
|
|
4092
|
+
* it checks if both versions have build metadata starting with "up". If so, it strips the "up" prefix and compares the remaining strings as versions.
|
|
4093
|
+
*
|
|
4094
|
+
* If the "up" logic doesn't apply or results in equality, it falls back to `semver.compareBuild` to handle
|
|
4095
|
+
* other build metadata differences (e.g. sorting alphabetically).
|
|
4096
|
+
*
|
|
4097
|
+
* @param {string} v1 - The first version string.
|
|
4098
|
+
* @param {string} v2 - The second version string.
|
|
4099
|
+
* @returns {number} - 0 if equal, -1 if v1 < v2, 1 if v1 > v2.
|
|
4100
|
+
*/
|
|
4101
|
+
export function compareChartVersions(v1: string, v2: string): number;
|
|
4092
4102
|
/**
|
|
4093
4103
|
* Get the latest chart version that is compatible with the cluster's OS and user's pre-release preference.
|
|
4094
4104
|
* @param {Object} chart - The chart object.
|
|
@@ -5408,7 +5418,6 @@ export function parse(str: any): any;
|
|
|
5408
5418
|
export function sortable(str: any): any;
|
|
5409
5419
|
export function compare(in1: any, in2: any): any;
|
|
5410
5420
|
export function isPrerelease(version?: string): boolean;
|
|
5411
|
-
export function isUpgradeFromPreToStable(currentVersion: any, targetVersion: any): any;
|
|
5412
5421
|
export function isDevBuild(version: any): boolean;
|
|
5413
5422
|
export function getVersionInfo(store: any): {
|
|
5414
5423
|
displayVersion: any;
|
|
@@ -46,6 +46,10 @@ export interface ActionFindAllArgs extends ActionCoreFindArgs {
|
|
|
46
46
|
* This is done via the native kube pagination api, not steve
|
|
47
47
|
*/
|
|
48
48
|
depaginate?: boolean,
|
|
49
|
+
/**
|
|
50
|
+
* Specifies the name to use if we should save the count returned in the paginated request
|
|
51
|
+
*/
|
|
52
|
+
saveCountAs?: string,
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
/**
|
|
@@ -76,6 +80,9 @@ export interface ActionFindPageArgs extends ActionCoreFindArgs {
|
|
|
76
80
|
* If true don't persist the http response to the store, just pass it back
|
|
77
81
|
*/
|
|
78
82
|
transient?: boolean,
|
|
83
|
+
|
|
84
|
+
saveCountAs?: string,
|
|
85
|
+
|
|
79
86
|
/**
|
|
80
87
|
* The target minimum revision for the resource.
|
|
81
88
|
*
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { compareChartVersions } from '@shell/utils/chart';
|
|
2
|
+
|
|
3
|
+
describe('compareChartVersions', () => {
|
|
4
|
+
describe('standard SemVer Comparison', () => {
|
|
5
|
+
it('should correctly compare standard versions', () => {
|
|
6
|
+
expect(compareChartVersions('1.0.0', '2.0.0')).toBe(-1);
|
|
7
|
+
expect(compareChartVersions('2.0.0', '1.0.0')).toBe(1);
|
|
8
|
+
expect(compareChartVersions('1.0.0', '1.0.0')).toBe(0);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should compare minor and patch versions correctly', () => {
|
|
12
|
+
expect(compareChartVersions('1.0.0', '1.1.0')).toBe(-1);
|
|
13
|
+
expect(compareChartVersions('1.0.0', '1.0.1')).toBe(-1);
|
|
14
|
+
expect(compareChartVersions('1.1.0', '1.0.1')).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should handle loose parsing (v-prefix)', () => {
|
|
18
|
+
expect(compareChartVersions('v1.0.0', '1.0.0')).toBe(0);
|
|
19
|
+
expect(compareChartVersions('v1.0.0', 'v2.0.0')).toBe(-1);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('rancher "up" Build Metadata Logic', () => {
|
|
24
|
+
it('should compare inner versions when both have "up" prefix', () => {
|
|
25
|
+
// 1.0.0 vs 2.0.0 inside the metadata
|
|
26
|
+
expect(compareChartVersions('1.0.0+up1.0.0', '1.0.0+up2.0.0')).toBe(-1);
|
|
27
|
+
expect(compareChartVersions('1.0.0+up2.0.0', '1.0.0+up1.0.0')).toBe(1);
|
|
28
|
+
// Equal inner versions
|
|
29
|
+
expect(compareChartVersions('1.0.0+up1.0.0', '1.0.0+up1.0.0')).toBe(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should handle pre-releases within "up" metadata correctly', () => {
|
|
33
|
+
// Crucial test: semver logic ensures 1.0.0-rc.1 < 1.0.0
|
|
34
|
+
// Standard string sort would often fail here depending on the string
|
|
35
|
+
expect(compareChartVersions('1.0.0+up1.0.0-rc.1', '1.0.0+up1.0.0')).toBe(-1);
|
|
36
|
+
expect(compareChartVersions('1.0.0+up1.0.0', '1.0.0+up1.0.0-rc.1')).toBe(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should compare different inner major/minor/patch versions', () => {
|
|
40
|
+
expect(compareChartVersions('0.0.1+up1.0.0', '0.0.1+up0.1.0')).toBe(1);
|
|
41
|
+
expect(compareChartVersions('0.0.1+up0.1.0', '0.0.1+up1.0.0')).toBe(-1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should prioritize valid inner semver over invalid inner semver', () => {
|
|
45
|
+
// Valid "up" version > Invalid "up" version
|
|
46
|
+
expect(compareChartVersions('1.0.0+up1.0.0', '1.0.0+upInvalid')).toBe(1);
|
|
47
|
+
expect(compareChartVersions('1.0.0+upInvalid', '1.0.0+up1.0.0')).toBe(-1);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should fall back to lexical sort if both "up" suffixes are invalid semver', () => {
|
|
51
|
+
// Both are "up..." but not valid semver, so it falls back to semver.compareBuild (lexical)
|
|
52
|
+
expect(compareChartVersions('1.0.0+upA', '1.0.0+upB')).toBe(-1);
|
|
53
|
+
expect(compareChartVersions('1.0.0+upB', '1.0.0+upA')).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('standard Build Metadata Fallback', () => {
|
|
58
|
+
it('should correctly compare versions with standard build metadata (lexicographical)', () => {
|
|
59
|
+
// 1.0.0+a vs 1.0.0+b -> -1
|
|
60
|
+
expect(compareChartVersions('1.0.0+a', '1.0.0+b')).toBe(-1);
|
|
61
|
+
expect(compareChartVersions('1.0.0+b', '1.0.0+a')).toBe(1);
|
|
62
|
+
// 1.0.0+1 vs 1.0.0+2 -> -1
|
|
63
|
+
expect(compareChartVersions('1.0.0+1', '1.0.0+2')).toBe(-1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should use standard comparison if only one has "up" prefix', () => {
|
|
67
|
+
// "up" comes after "foo" lexically
|
|
68
|
+
expect(compareChartVersions('1.0.0+foo', '1.0.0+up1.0.0')).toBe(-1);
|
|
69
|
+
expect(compareChartVersions('1.0.0+up1.0.0', '1.0.0+foo')).toBe(1);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('edge Cases and Invalid Inputs', () => {
|
|
74
|
+
it('should handle null or undefined inputs safely', () => {
|
|
75
|
+
// Implementation behavior: fallback to utils/version compare
|
|
76
|
+
// which treats falsy as "high" (return 1) if first arg is null?
|
|
77
|
+
// Checking implementation of `compare` in shell/utils/version.js:
|
|
78
|
+
// if (!in1) return 1; if (!in2) return -1;
|
|
79
|
+
expect(compareChartVersions(null, '1.0.0')).toBe(1);
|
|
80
|
+
expect(compareChartVersions('1.0.0', null)).toBe(-1);
|
|
81
|
+
expect(compareChartVersions(undefined, '1.0.0')).toBe(1);
|
|
82
|
+
expect(compareChartVersions('1.0.0', undefined)).toBe(-1);
|
|
83
|
+
expect(compareChartVersions(null, null)).toBe(1); // First check is !in1 -> 1
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should handle completely invalid strings', () => {
|
|
87
|
+
// "invalid" is not valid semver, so it falls back to utils/version compare (string/numeric comparison)
|
|
88
|
+
// "invalid" vs "1.0.0"
|
|
89
|
+
// "invalid" is treated as string, "1.0.0" parsed as parts
|
|
90
|
+
// Effectively tests the fallback logic stability
|
|
91
|
+
expect(compareChartVersions('invalid', '1.0.0')).not.toBe(0);
|
|
92
|
+
expect(compareChartVersions('a', 'b')).toBe(-1);
|
|
93
|
+
expect(compareChartVersions('b', 'a')).toBe(1);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDevBuild,
|
|
1
|
+
import { isDevBuild, getReleaseNotesURL } from '@shell/utils/version';
|
|
2
2
|
|
|
3
3
|
describe('fx: isDevBuild', () => {
|
|
4
4
|
it.each([
|
|
@@ -17,24 +17,6 @@ describe('fx: isDevBuild', () => {
|
|
|
17
17
|
);
|
|
18
18
|
});
|
|
19
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
|
-
});
|
|
37
|
-
|
|
38
20
|
describe('fx: getReleaseNotesURL', () => {
|
|
39
21
|
describe('when version is not provided', () => {
|
|
40
22
|
it('should return the community dev URL', () => {
|
package/utils/chart.js
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
import { compare } from '@shell/utils/version';
|
|
1
3
|
import { compatibleVersionsFor } from '@shell/store/catalog';
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Compares two chart versions using SemVer logic, with special handling for Rancher's "up" build metadata.
|
|
7
|
+
*
|
|
8
|
+
* It uses `semver.compare` for the primary comparison. If versions are considered equal (SemVer ignores build metadata),
|
|
9
|
+
* it checks if both versions have build metadata starting with "up". If so, it strips the "up" prefix and compares the remaining strings as versions.
|
|
10
|
+
*
|
|
11
|
+
* If the "up" logic doesn't apply or results in equality, it falls back to `semver.compareBuild` to handle
|
|
12
|
+
* other build metadata differences (e.g. sorting alphabetically).
|
|
13
|
+
*
|
|
14
|
+
* @param {string} v1 - The first version string.
|
|
15
|
+
* @param {string} v2 - The second version string.
|
|
16
|
+
* @returns {number} - 0 if equal, -1 if v1 < v2, 1 if v1 > v2.
|
|
17
|
+
*/
|
|
18
|
+
export function compareChartVersions(v1, v2) {
|
|
19
|
+
const v1Valid = semver.valid(v1, { loose: true });
|
|
20
|
+
const v2Valid = semver.valid(v2, { loose: true });
|
|
21
|
+
|
|
22
|
+
if (!v1Valid || !v2Valid) {
|
|
23
|
+
return compare(v1, v2);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// semver.compare ignores build metadata (e.g., 1.0.0+1 == 1.0.0+2)
|
|
27
|
+
let diff = semver.compare(v1, v2, { loose: true });
|
|
28
|
+
|
|
29
|
+
if (diff === 0) {
|
|
30
|
+
const parsedV1 = semver.parse(v1, { loose: true });
|
|
31
|
+
const parsedV2 = semver.parse(v2, { loose: true });
|
|
32
|
+
const buildV1 = parsedV1.build.join('.');
|
|
33
|
+
const buildV2 = parsedV2.build.join('.');
|
|
34
|
+
|
|
35
|
+
// Special logic for Rancher charts where "up" prefix in build metadata contains version info.
|
|
36
|
+
// E.g. 108.0.0+up0.25.0-rc.4 vs 108.0.0+up0.25.0
|
|
37
|
+
// Standard semver.compareBuild would sort ASCII: "up...-rc" > "up..." (incorrect for RC)
|
|
38
|
+
// We strip "up" and compare the rest as versions to properly handle pre-releases (RC < Stable).
|
|
39
|
+
if (buildV1.startsWith('up') && buildV2.startsWith('up')) {
|
|
40
|
+
const subV1 = buildV1.substring(2);
|
|
41
|
+
const subV2 = buildV2.substring(2);
|
|
42
|
+
const subV1Valid = semver.valid(subV1, { loose: true });
|
|
43
|
+
const subV2Valid = semver.valid(subV2, { loose: true });
|
|
44
|
+
|
|
45
|
+
if (subV1Valid && subV2Valid) {
|
|
46
|
+
// Both "up" metadata parts are valid semver: compare them semantically.
|
|
47
|
+
diff = semver.compare(subV1, subV2, { loose: true });
|
|
48
|
+
} else if (subV1Valid && !subV2Valid) {
|
|
49
|
+
// Only v1 has valid "up" metadata: prefer v1 over v2.
|
|
50
|
+
diff = 1;
|
|
51
|
+
} else if (!subV1Valid && subV2Valid) {
|
|
52
|
+
// Only v2 has valid "up" metadata: prefer v2 over v1.
|
|
53
|
+
diff = -1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fallback to standard build comparison for other cases (e.g. 1.0.0+1 vs 1.0.0+2).
|
|
58
|
+
// semver.compareBuild sorts build metadata lexicographically.
|
|
59
|
+
if (diff === 0) {
|
|
60
|
+
diff = semver.compareBuild(v1, v2, { loose: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return diff;
|
|
65
|
+
}
|
|
66
|
+
|
|
3
67
|
/**
|
|
4
68
|
* Get the latest chart version that is compatible with the cluster's OS and user's pre-release preference.
|
|
5
69
|
* @param {Object} chart - The chart object.
|
|
@@ -31,6 +31,11 @@ interface Args {
|
|
|
31
31
|
classify?: boolean,
|
|
32
32
|
reactive?: boolean,
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Specifies the name to use if we should save the count returned in the paginated request
|
|
37
|
+
*/
|
|
38
|
+
saveCountAs?: string;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
interface Result<T> extends Omit<ActionFindPageTransientResponse<T>, 'data'> {
|
|
@@ -57,6 +62,7 @@ class PaginationWrapper<T extends object> {
|
|
|
57
62
|
private backOffId: string;
|
|
58
63
|
private classify: boolean;
|
|
59
64
|
private reactive: boolean;
|
|
65
|
+
private saveCountAs: string | undefined;
|
|
60
66
|
private cachedRevision?: string;
|
|
61
67
|
private cachedResult?: Result<T>;
|
|
62
68
|
|
|
@@ -65,7 +71,7 @@ class PaginationWrapper<T extends object> {
|
|
|
65
71
|
|
|
66
72
|
constructor(args: Args) {
|
|
67
73
|
const {
|
|
68
|
-
$store, id, enabledFor, onChange, formatResponse
|
|
74
|
+
$store, id, enabledFor, onChange, formatResponse, saveCountAs
|
|
69
75
|
} = args;
|
|
70
76
|
|
|
71
77
|
this.$store = $store;
|
|
@@ -75,6 +81,7 @@ class PaginationWrapper<T extends object> {
|
|
|
75
81
|
this.onChange = onChange;
|
|
76
82
|
this.classify = formatResponse?.classify || false;
|
|
77
83
|
this.reactive = formatResponse?.reactive || false;
|
|
84
|
+
this.saveCountAs = saveCountAs;
|
|
78
85
|
|
|
79
86
|
this.isEnabled = paginationUtils.isEnabled({ rootGetters: $store.getters, $extension: this.$store.$extension }, enabledFor);
|
|
80
87
|
}
|
|
@@ -152,9 +159,10 @@ class PaginationWrapper<T extends object> {
|
|
|
152
159
|
},
|
|
153
160
|
delayedFn: async() => {
|
|
154
161
|
const opt: ActionFindPageArgs = {
|
|
155
|
-
watch:
|
|
162
|
+
watch: false,
|
|
156
163
|
pagination,
|
|
157
|
-
transient:
|
|
164
|
+
transient: true,
|
|
165
|
+
saveCountAs: this.saveCountAs,
|
|
158
166
|
revision
|
|
159
167
|
};
|
|
160
168
|
const res: ActionFindPageTransientResponse<T> = await this.$store.dispatch(`${ this.enabledFor.store }/findPage`, { opt, type });
|
package/utils/version.js
CHANGED
|
@@ -3,6 +3,7 @@ import semver from 'semver';
|
|
|
3
3
|
import { MANAGEMENT } from '@shell/config/types';
|
|
4
4
|
import { READ_WHATS_NEW, SEEN_WHATS_NEW } from '@shell/store/prefs';
|
|
5
5
|
import { SETTING } from '@shell/config/settings';
|
|
6
|
+
import { getVersionData } from '@shell/config/version';
|
|
6
7
|
|
|
7
8
|
export function parse(str) {
|
|
8
9
|
str = `${ str }`;
|
|
@@ -74,21 +75,6 @@ export function isPrerelease(version = '') {
|
|
|
74
75
|
return !!semver.prerelease(version);
|
|
75
76
|
}
|
|
76
77
|
|
|
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
|
-
|
|
92
78
|
export function isDevBuild(version) {
|
|
93
79
|
if ( ['dev', 'master', 'head'].includes(version) || version.endsWith('-head') || version.match(/-rc\d+$/) || version.match(/-alpha\d+$/) ) {
|
|
94
80
|
return true;
|
|
@@ -98,8 +84,10 @@ export function isDevBuild(version) {
|
|
|
98
84
|
}
|
|
99
85
|
|
|
100
86
|
export function getVersionInfo(store) {
|
|
101
|
-
const
|
|
102
|
-
|
|
87
|
+
const fullVersion = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.VERSION_RANCHER)?.value ??
|
|
88
|
+
getVersionData()?.Version ??
|
|
89
|
+
'unknown';
|
|
90
|
+
|
|
103
91
|
let displayVersion = fullVersion;
|
|
104
92
|
|
|
105
93
|
const match = fullVersion.match(/^(.*)-([0-9a-f]{40})-(.*)$/);
|