@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CAPI, MANAGEMENT } from '@shell/config/types';
|
|
2
|
+
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
3
|
+
import { Location } from 'vue-router';
|
|
4
|
+
|
|
5
|
+
interface ReferencedCluster {
|
|
6
|
+
label: string;
|
|
7
|
+
location: Location | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default class Kubeconfig extends SteveModel {
|
|
11
|
+
declare spec: {
|
|
12
|
+
clusters?: string[];
|
|
13
|
+
ttl?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare metadata: {
|
|
17
|
+
name?: string;
|
|
18
|
+
creationTimestamp?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
get _availableActions(): object[] {
|
|
22
|
+
const out = super._availableActions;
|
|
23
|
+
|
|
24
|
+
// Remove element at index 1 (the first divider), the actions that don't make sense.
|
|
25
|
+
return out.filter((action: { action: string }, index: number) => index !== 1 && !['goToEdit', 'goToEditYaml', 'cloneYaml', 'download'].includes(action.action));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculates the expiry timestamp from creationTimestamp + ttl.
|
|
30
|
+
* Returns an ISO date string for use with LiveDate formatter.
|
|
31
|
+
*/
|
|
32
|
+
get expiresAt(): string | null {
|
|
33
|
+
const ttlSeconds = this.spec?.ttl;
|
|
34
|
+
const creationTimestamp = this.metadata?.creationTimestamp;
|
|
35
|
+
|
|
36
|
+
if (!ttlSeconds || !creationTimestamp) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const createdAt = new Date(creationTimestamp);
|
|
41
|
+
const expiresAt = new Date(createdAt.getTime() + (ttlSeconds * 1000));
|
|
42
|
+
|
|
43
|
+
return expiresAt.toISOString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Returns cluster information for display and linking.
|
|
48
|
+
* Each object contains {label, location} where location is null if cluster doesn't exist.
|
|
49
|
+
*/
|
|
50
|
+
get referencedClusters(): ReferencedCluster[] {
|
|
51
|
+
const clusterIds = this.spec?.clusters || [];
|
|
52
|
+
const provClusters = this.$rootGetters['management/all'](CAPI.RANCHER_CLUSTER) || [];
|
|
53
|
+
const mgmtClusters = this.$rootGetters['management/all'](MANAGEMENT.CLUSTER) || [];
|
|
54
|
+
|
|
55
|
+
return clusterIds.map((id: string) => {
|
|
56
|
+
const provCluster = provClusters.find((c: any) => c.mgmt?.id === id || c.status?.clusterName === id);
|
|
57
|
+
const mgmtCluster = mgmtClusters.find((c: any) => c.id === id);
|
|
58
|
+
const cluster = provCluster || mgmtCluster;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
label: cluster?.nameDisplay || this.t('"ext.cattle.io.kubeconfig".deleted', { name: id }),
|
|
62
|
+
location: provCluster?.detailLocation || mgmtCluster?.detailLocation || null
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns referenced clusters sorted: existing clusters first (by name), then deleted clusters.
|
|
69
|
+
*/
|
|
70
|
+
get sortedReferencedClusters(): ReferencedCluster[] {
|
|
71
|
+
return this.referencedClusters.slice().sort((a, b) => {
|
|
72
|
+
const aExists = a.location !== null;
|
|
73
|
+
const bExists = b.location !== null;
|
|
74
|
+
|
|
75
|
+
if (aExists && !bExists) {
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
if (!aExists && bExists) {
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const aName = a.label.toLowerCase();
|
|
83
|
+
const bName = b.label.toLowerCase();
|
|
84
|
+
|
|
85
|
+
return aName.localeCompare(bName, undefined, { numeric: true });
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Returns a sortable string for the clusters column.
|
|
91
|
+
*/
|
|
92
|
+
get referencedClustersSortable(): string {
|
|
93
|
+
return this.sortedReferencedClusters
|
|
94
|
+
.map((c) => c.label.toLowerCase())
|
|
95
|
+
.join(',');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
2
|
+
|
|
3
|
+
export default class PasswordChangeRequest extends SteveModel {
|
|
4
|
+
get canChangePassword() {
|
|
5
|
+
return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
cleanForSave(data) {
|
|
9
|
+
const val = super.cleanForSave(data);
|
|
10
|
+
|
|
11
|
+
delete val.type;
|
|
12
|
+
|
|
13
|
+
return val;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
2
|
+
|
|
3
|
+
export default class SelfUser extends SteveModel {
|
|
4
|
+
get canGetUser() {
|
|
5
|
+
return this.schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
cleanForSave(data) {
|
|
9
|
+
const val = super.cleanForSave(data);
|
|
10
|
+
|
|
11
|
+
delete val.type;
|
|
12
|
+
|
|
13
|
+
return val;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { matching, convertSelectorObj } from '@shell/utils/selector';
|
|
2
2
|
import isEmpty from 'lodash/isEmpty';
|
|
3
3
|
import { escapeHtml } from '@shell/utils/string';
|
|
4
|
-
import { FLEET } from '@shell/config/types';
|
|
4
|
+
import { FLEET, MANAGEMENT } from '@shell/config/types';
|
|
5
5
|
import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
6
6
|
import { addObject, addObjects, findBy } from '@shell/utils/array';
|
|
7
7
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
@@ -30,18 +30,28 @@ function normalizeStateCounts(data) {
|
|
|
30
30
|
|
|
31
31
|
export default class FleetApplication extends SteveModel {
|
|
32
32
|
async getCurrentUser() {
|
|
33
|
-
const user = this.$rootGetters['auth/
|
|
33
|
+
const user = this.$rootGetters['auth/user'];
|
|
34
34
|
|
|
35
35
|
if (user?.id) {
|
|
36
36
|
return user;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
const
|
|
40
|
-
url: '/v3/users?me=true',
|
|
41
|
-
method: 'get',
|
|
42
|
-
}, { root: true });
|
|
39
|
+
const selfUser = await this.$dispatch('auth/getSelfUser');
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
if (selfUser?.canGetUser && selfUser.status?.userID) {
|
|
42
|
+
const user = await this.$dispatch('management/find', {
|
|
43
|
+
type: MANAGEMENT.USER,
|
|
44
|
+
id: selfUser.status?.userID
|
|
45
|
+
}, { root: true });
|
|
46
|
+
|
|
47
|
+
if (user) {
|
|
48
|
+
this.$dispatch('auth/gotUser', user, { root: true });
|
|
49
|
+
|
|
50
|
+
return user;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {};
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
pause() {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { NORMAN } from '@shell/config/types';
|
|
2
|
-
import
|
|
1
|
+
import { NORMAN, EXT } from '@shell/config/types';
|
|
2
|
+
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
3
3
|
import day from 'dayjs';
|
|
4
4
|
|
|
5
|
-
export default class User extends
|
|
5
|
+
export default class User extends SteveModel {
|
|
6
6
|
// Preserve description
|
|
7
7
|
constructor(data, ctx, rehydrateNamespace = null, setClone = false) {
|
|
8
8
|
const _description = data.description;
|
|
@@ -11,16 +11,6 @@ export default class User extends HybridModel {
|
|
|
11
11
|
this.description = _description;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// Clean the Norman properties, but keep description
|
|
15
|
-
cleanResource(data) {
|
|
16
|
-
const desc = data.description;
|
|
17
|
-
const clean = cleanHybridResources(data);
|
|
18
|
-
|
|
19
|
-
clean._description = desc;
|
|
20
|
-
|
|
21
|
-
return clean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
14
|
get isSystem() {
|
|
25
15
|
for ( const p of this.principalIds || [] ) {
|
|
26
16
|
if ( p.startsWith('system://') ) {
|
|
@@ -175,13 +165,13 @@ export default class User extends HybridModel {
|
|
|
175
165
|
const clone = await this.$dispatch('clone', { resource: this });
|
|
176
166
|
|
|
177
167
|
// Remove local properties
|
|
178
|
-
delete clone.
|
|
168
|
+
delete clone.canRefreshMemberships;
|
|
179
169
|
|
|
180
170
|
return clone._save(opt);
|
|
181
171
|
}
|
|
182
172
|
|
|
183
173
|
async setEnabled(enabled) {
|
|
184
|
-
const clone = await this.$dispatch('
|
|
174
|
+
const clone = await this.$dispatch('clone', { resource: this });
|
|
185
175
|
|
|
186
176
|
clone.enabled = enabled;
|
|
187
177
|
await clone.save();
|
|
@@ -204,12 +194,21 @@ export default class User extends HybridModel {
|
|
|
204
194
|
}
|
|
205
195
|
|
|
206
196
|
async refreshGroupMembership() {
|
|
207
|
-
const
|
|
208
|
-
type: NORMAN.USER,
|
|
209
|
-
id: this.id,
|
|
210
|
-
}, { root: true });
|
|
197
|
+
const membershipRefreshRequests = await this.$dispatch('create', { type: EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS });
|
|
211
198
|
|
|
212
|
-
|
|
199
|
+
// userId specifies the user ID. Use '*' for all users. Check the schemaDefinition for more details.
|
|
200
|
+
membershipRefreshRequests.spec = { userId: this.id };
|
|
201
|
+
await membershipRefreshRequests.save();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
get canRefreshMemberships() {
|
|
205
|
+
const schema = this.$getters[`schemaFor`](EXT.GROUP_MEMBERSHIP_REFRESH_REQUESTS);
|
|
206
|
+
|
|
207
|
+
if (!schema) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
|
|
213
212
|
}
|
|
214
213
|
|
|
215
214
|
canActivate(state) {
|
|
@@ -243,7 +242,7 @@ export default class User extends HybridModel {
|
|
|
243
242
|
action: 'refreshGroupMembership',
|
|
244
243
|
label: this.t('authGroups.actions.refresh'),
|
|
245
244
|
icon: 'icon icon-refresh',
|
|
246
|
-
enabled: this.
|
|
245
|
+
enabled: this.canRefreshMemberships
|
|
247
246
|
},
|
|
248
247
|
{ divider: true },
|
|
249
248
|
...super._availableActions,
|
|
@@ -284,19 +283,17 @@ export default class User extends HybridModel {
|
|
|
284
283
|
return true;
|
|
285
284
|
}
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
286
|
+
cleanForSave(data) {
|
|
287
|
+
const val = super.cleanForSave(data);
|
|
290
288
|
|
|
291
|
-
|
|
292
|
-
return this.norman?.hasLink('remove') && !this.isCurrentUser;
|
|
293
|
-
}
|
|
289
|
+
delete val.type;
|
|
294
290
|
|
|
295
|
-
|
|
296
|
-
return this.norman?.hasLink('update');
|
|
291
|
+
return val;
|
|
297
292
|
}
|
|
298
293
|
|
|
299
|
-
|
|
300
|
-
|
|
294
|
+
get norman() {
|
|
295
|
+
console.warn('Norman "user" is deprecated. Use Steve "management.cattle.io.user" user instead.'); // eslint-disable-line no-console
|
|
296
|
+
|
|
297
|
+
return this.$rootGetters['rancher/byId'](NORMAN.USER, this.id);
|
|
301
298
|
}
|
|
302
299
|
}
|
package/models/schema.js
CHANGED
|
@@ -7,6 +7,24 @@ export default class Schema extends Resource {
|
|
|
7
7
|
get groupName() {
|
|
8
8
|
return this.attributes.namespaced ? 'ns' : 'cluster';
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Legacy check to determine if the user can GET a specific resource of this type.
|
|
13
|
+
*
|
|
14
|
+
* This supports things like spoof or norman schemas.
|
|
15
|
+
*/
|
|
16
|
+
get canGet() {
|
|
17
|
+
return this.hasLink('collection');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Legacy check to determine if the user can LIST a resource of this type.
|
|
22
|
+
*
|
|
23
|
+
* This supports things like spoof or norman schemas.
|
|
24
|
+
*/
|
|
25
|
+
get canList() {
|
|
26
|
+
return this.hasLink('collection');
|
|
27
|
+
}
|
|
10
28
|
}
|
|
11
29
|
|
|
12
30
|
/**
|
package/models/secret.js
CHANGED
|
@@ -3,6 +3,7 @@ import { CERTMANAGER, KUBERNETES, UI_PROJECT_SECRET, UI_PROJECT_SECRET_COPY } fr
|
|
|
3
3
|
import { base64Decode, base64Encode } from '@shell/utils/crypto';
|
|
4
4
|
import { removeObjects } from '@shell/utils/array';
|
|
5
5
|
import { MANAGEMENT, SERVICE_ACCOUNT, VIRTUAL_TYPES } from '@shell/config/types';
|
|
6
|
+
import { SECRET_SCOPE, SECRET_QUERY_PARAMS } from '@shell/config/query-params';
|
|
6
7
|
import { set } from '@shell/utils/object';
|
|
7
8
|
import { NAME as MANAGER } from '@shell/config/product/manager';
|
|
8
9
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
@@ -491,18 +492,16 @@ export default class Secret extends SteveModel {
|
|
|
491
492
|
return steveCleanForDownload(yaml, { rootKeys: ['id', 'links', 'actions'] });
|
|
492
493
|
}
|
|
493
494
|
|
|
494
|
-
/**
|
|
495
|
-
* is this a project scoped secret .... or also a cloned project scoped secret
|
|
496
|
-
*/
|
|
497
|
-
get isProjectScopedRelated() {
|
|
498
|
-
return !!this.metadata.labels?.[UI_PROJECT_SECRET];
|
|
499
|
-
}
|
|
500
|
-
|
|
501
495
|
/**
|
|
502
496
|
* is this a project scoped secret
|
|
503
497
|
*/
|
|
504
498
|
get isProjectScoped() {
|
|
505
|
-
|
|
499
|
+
/**
|
|
500
|
+
* is this a project scoped secret .... or also a cloned project scoped secret
|
|
501
|
+
*/
|
|
502
|
+
const isProjectScopedRelated = !!this.metadata.labels?.[UI_PROJECT_SECRET];
|
|
503
|
+
|
|
504
|
+
return isProjectScopedRelated && !this.isProjectSecretCopy && this.$rootGetters['isRancher'];
|
|
506
505
|
}
|
|
507
506
|
|
|
508
507
|
get projectScopedClusterId() {
|
|
@@ -573,7 +572,7 @@ export default class Secret extends SteveModel {
|
|
|
573
572
|
const id = this.id?.replace(/.*\//, '');
|
|
574
573
|
|
|
575
574
|
return {
|
|
576
|
-
name: `c-cluster-product-
|
|
575
|
+
name: `c-cluster-product-${ VIRTUAL_TYPES.PROJECT_SECRETS }-namespace-id`,
|
|
577
576
|
params: {
|
|
578
577
|
product: this.$rootGetters['productId'],
|
|
579
578
|
cluster: this.$rootGetters['clusterId'],
|
|
@@ -588,34 +587,38 @@ export default class Secret extends SteveModel {
|
|
|
588
587
|
}
|
|
589
588
|
|
|
590
589
|
get listLocation() {
|
|
591
|
-
if (
|
|
592
|
-
return
|
|
590
|
+
if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
|
|
591
|
+
return {
|
|
592
|
+
name: 'c-cluster-product-resource',
|
|
593
|
+
params: {
|
|
594
|
+
product: this.$rootGetters['productId'],
|
|
595
|
+
cluster: this.$rootGetters['clusterId'],
|
|
596
|
+
resource: VIRTUAL_TYPES.PROJECT_SECRETS,
|
|
597
|
+
}
|
|
598
|
+
};
|
|
593
599
|
}
|
|
594
600
|
|
|
595
|
-
return
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
resource: VIRTUAL_TYPES.PROJECT_SECRETS,
|
|
601
|
-
}
|
|
602
|
-
};
|
|
601
|
+
return super.listLocation;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
get hasProjectScopedUrlQueryParam() {
|
|
605
|
+
return this.currentRoute()?.query?.[SECRET_SCOPE] === SECRET_QUERY_PARAMS.PROJECT_SCOPED;
|
|
603
606
|
}
|
|
604
607
|
|
|
605
608
|
get parentNameOverride() {
|
|
606
|
-
if (
|
|
607
|
-
return
|
|
609
|
+
if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
|
|
610
|
+
return this.$rootGetters['i18n/t'](`typeLabel."${ VIRTUAL_TYPES.PROJECT_SECRETS }"`, { count: 1 })?.trim();
|
|
608
611
|
}
|
|
609
612
|
|
|
610
|
-
return
|
|
613
|
+
return super.parentNameOverride;
|
|
611
614
|
}
|
|
612
615
|
|
|
613
616
|
get parentLocationOverride() {
|
|
614
|
-
if (
|
|
615
|
-
return
|
|
617
|
+
if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
|
|
618
|
+
return this.listLocation;
|
|
616
619
|
}
|
|
617
620
|
|
|
618
|
-
return
|
|
621
|
+
return super.parentLocationOverride;
|
|
619
622
|
}
|
|
620
623
|
|
|
621
624
|
get groupByProject() {
|
package/models/steve-schema.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { STEVE } from '@shell/config/types';
|
|
2
|
-
import
|
|
2
|
+
import BaseSchema from './schema';
|
|
3
3
|
import { wait } from '@shell/utils/async';
|
|
4
|
+
import { Schema as SchemaSchemaType, SchemaAttributeVerbs } from '@shell/plugins/steve/schema';
|
|
4
5
|
|
|
5
6
|
interface ResourceField {
|
|
6
7
|
type: string,
|
|
@@ -31,10 +32,28 @@ const SchemaDefinitionCache: { [store: string]: {
|
|
|
31
32
|
definitions: SchemaDefinitions,
|
|
32
33
|
} } = {};
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Determine if the user can <verb> this type
|
|
37
|
+
*/
|
|
38
|
+
const canSchema = (
|
|
39
|
+
{ schema, verb }: { schema: SchemaSchemaType & SteveSchema, verb: SchemaAttributeVerbs }
|
|
40
|
+
) => {
|
|
41
|
+
if (!schema.hasLink('collection')) {
|
|
42
|
+
// The UI will use this to build the URLs. It will exist even if there's no GET/LIST permissions (to support POST)
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!schema.attributes?.verbs?.find((x) => x.toLowerCase() === verb)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
|
|
34
53
|
/**
|
|
35
54
|
* Steve Schema specific functionality
|
|
36
55
|
*/
|
|
37
|
-
export default class SteveSchema extends
|
|
56
|
+
export default class SteveSchema extends BaseSchema {
|
|
38
57
|
static reset(store: string): void {
|
|
39
58
|
delete SchemaDefinitionCache[store];
|
|
40
59
|
}
|
|
@@ -233,6 +252,24 @@ export default class SteveSchema extends Schema {
|
|
|
233
252
|
return this.links?.self?.replace('/schemas/', '/schemaDefinitions/');
|
|
234
253
|
}
|
|
235
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Check to determine if the user can GET a specific resource of this type.
|
|
257
|
+
*/
|
|
258
|
+
get canGet(): boolean {
|
|
259
|
+
return canSchema({ schema: this.schema, verb: 'get' });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check to determine if the user can LIST a resource of this type.
|
|
264
|
+
*/
|
|
265
|
+
get canList(): boolean {
|
|
266
|
+
return canSchema({ schema: this.schema, verb: 'list' });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
get schema(): SchemaSchemaType & SteveSchema {
|
|
270
|
+
return this as unknown as SchemaSchemaType & SteveSchema;
|
|
271
|
+
}
|
|
272
|
+
|
|
236
273
|
/*********************
|
|
237
274
|
* Local Properties
|
|
238
275
|
*
|
package/models/workload.js
CHANGED
|
@@ -755,7 +755,7 @@ export default class Workload extends WorkloadService {
|
|
|
755
755
|
get podsCard() {
|
|
756
756
|
const supportedTypes = [WORKLOAD_TYPES.DEPLOYMENT, WORKLOAD_TYPES.DAEMON_SET, WORKLOAD_TYPES.JOB, WORKLOAD_TYPES.STATEFUL_SET];
|
|
757
757
|
|
|
758
|
-
if (!supportedTypes.includes(this.type)) {
|
|
758
|
+
if (!supportedTypes.includes(this.type) || (this.pods?.length || 0) <= 0) {
|
|
759
759
|
return null;
|
|
760
760
|
}
|
|
761
761
|
|
|
@@ -776,7 +776,7 @@ export default class Workload extends WorkloadService {
|
|
|
776
776
|
get jobsCard() {
|
|
777
777
|
const supportedTypes = [WORKLOAD_TYPES.CRON_JOB];
|
|
778
778
|
|
|
779
|
-
if (!supportedTypes.includes(this.type)) {
|
|
779
|
+
if (!supportedTypes.includes(this.type) || (this.jobs?.length || 0) <= 0) {
|
|
780
780
|
return null;
|
|
781
781
|
}
|
|
782
782
|
|
|
@@ -794,6 +794,7 @@ export default class Workload extends WorkloadService {
|
|
|
794
794
|
return [
|
|
795
795
|
this.podsCard,
|
|
796
796
|
this.jobsCard,
|
|
797
|
+
this.insightCard,
|
|
797
798
|
...this._cards
|
|
798
799
|
];
|
|
799
800
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rancher/shell",
|
|
3
|
-
"version": "3.0.9-rc.
|
|
3
|
+
"version": "3.0.9-rc.5",
|
|
4
4
|
"description": "Rancher Dashboard Shell",
|
|
5
5
|
"repository": "https://github.com/rancher/dashboard",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@babel/preset-typescript": "7.16.7",
|
|
40
40
|
"@novnc/novnc": "1.2.0",
|
|
41
41
|
"@popperjs/core": "2.11.8",
|
|
42
|
-
"@rancher/icons": "2.0.
|
|
42
|
+
"@rancher/icons": "2.0.55",
|
|
43
43
|
"@types/is-url": "1.2.30",
|
|
44
44
|
"@types/node": "20.10.8",
|
|
45
45
|
"@types/semver": "^7.5.8",
|
package/pages/about.vue
CHANGED
|
@@ -9,6 +9,7 @@ import { mapGetters } from 'vuex';
|
|
|
9
9
|
import TabTitle from '@shell/components/TabTitle';
|
|
10
10
|
import { PanelLocation, ExtensionPoint } from '@shell/core/types';
|
|
11
11
|
import ExtensionPanel from '@shell/components/ExtensionPanel';
|
|
12
|
+
import { getVersionInfo } from '@shell/utils/version';
|
|
12
13
|
|
|
13
14
|
export default {
|
|
14
15
|
components: {
|
|
@@ -30,7 +31,7 @@ export default {
|
|
|
30
31
|
computed: {
|
|
31
32
|
...mapGetters(['releaseNotesUrl']),
|
|
32
33
|
rancherVersion() {
|
|
33
|
-
return this
|
|
34
|
+
return getVersionInfo(this.$store).fullVersion;
|
|
34
35
|
},
|
|
35
36
|
appName() {
|
|
36
37
|
return getVendor();
|
|
@@ -124,7 +125,7 @@ export default {
|
|
|
124
125
|
>
|
|
125
126
|
{{ t("about.versions.rancher") }}
|
|
126
127
|
</a>
|
|
127
|
-
</td><td>{{ rancherVersion
|
|
128
|
+
</td><td>{{ rancherVersion }}</td>
|
|
128
129
|
</tr>
|
|
129
130
|
<tr v-if="dashboardVersion">
|
|
130
131
|
<td>
|
package/pages/account/index.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import BackLink from '@shell/components/BackLink';
|
|
3
|
-
import { MANAGEMENT, NORMAN } from '@shell/config/types';
|
|
3
|
+
import { MANAGEMENT, NORMAN, EXT } from '@shell/config/types';
|
|
4
4
|
import { SETTING } from '@shell/config/settings';
|
|
5
5
|
import Loading from '@shell/components/Loading';
|
|
6
6
|
import Principal from '@shell/components/auth/Principal';
|
|
@@ -33,13 +33,26 @@ export default {
|
|
|
33
33
|
|
|
34
34
|
this.apiHostSetting = apiHostSetting?.value;
|
|
35
35
|
this.serverUrlSetting = serverUrlSetting?.value;
|
|
36
|
+
|
|
37
|
+
const selfUser = await this.$store.dispatch('auth/getSelfUser');
|
|
38
|
+
|
|
39
|
+
if (selfUser?.canGetUser && selfUser.status?.userID) {
|
|
40
|
+
// Fetch the user info for ChangePassword (ChangePasswordDialog needs the user info for the user whose password is being changed)
|
|
41
|
+
this.user = await this.$store.dispatch('management/find', {
|
|
42
|
+
type: MANAGEMENT.USER,
|
|
43
|
+
id: selfUser.status?.userID
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error(this.t('changePassword.errors.cannotFetchSelf'));
|
|
47
|
+
}
|
|
36
48
|
},
|
|
37
49
|
data() {
|
|
38
50
|
return {
|
|
39
51
|
apiHostSetting: null,
|
|
40
52
|
serverUrlSetting: null,
|
|
41
53
|
rows: null,
|
|
42
|
-
canChangePassword: false
|
|
54
|
+
canChangePassword: false,
|
|
55
|
+
user: null
|
|
43
56
|
};
|
|
44
57
|
},
|
|
45
58
|
computed: {
|
|
@@ -124,24 +137,18 @@ export default {
|
|
|
124
137
|
return !!this.principal.loginName;
|
|
125
138
|
}
|
|
126
139
|
|
|
127
|
-
const
|
|
128
|
-
type: NORMAN.USER,
|
|
129
|
-
opt: { url: '/v3/users', filter: { me: true } }
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
if (users && users.length === 1) {
|
|
133
|
-
return !!users[0].username;
|
|
134
|
-
}
|
|
140
|
+
const passwordChangeRequest = await this.$store.dispatch('management/create', { type: EXT.PASSWORD_CHANGE_REQUESTS });
|
|
135
141
|
|
|
136
|
-
return
|
|
142
|
+
return !!passwordChangeRequest?.canChangePassword;
|
|
137
143
|
},
|
|
138
144
|
showChangePasswordDialog() {
|
|
139
145
|
this.$store.dispatch('management/promptModal', {
|
|
140
|
-
component:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
component: 'ChangePasswordDialog',
|
|
147
|
+
componentProps: { user: this.user },
|
|
148
|
+
testId: 'change-password__modal',
|
|
149
|
+
customClass: 'change-password-modal',
|
|
150
|
+
modalWidth: '500',
|
|
151
|
+
height: '465'
|
|
145
152
|
});
|
|
146
153
|
}
|
|
147
154
|
}
|
package/pages/auth/login.vue
CHANGED
|
@@ -18,8 +18,7 @@ import { sortBy } from '@shell/utils/sort';
|
|
|
18
18
|
import { configType } from '@shell/models/management.cattle.io.authconfig';
|
|
19
19
|
import { mapGetters } from 'vuex';
|
|
20
20
|
import { markRaw } from 'vue';
|
|
21
|
-
import {
|
|
22
|
-
import { MANAGEMENT, NORMAN } from '@shell/config/types';
|
|
21
|
+
import { MANAGEMENT, NORMAN, EXT } from '@shell/config/types';
|
|
23
22
|
import { SETTING } from '@shell/config/settings';
|
|
24
23
|
import { LOGIN_ERRORS } from '@shell/store/auth';
|
|
25
24
|
import {
|
|
@@ -286,13 +285,21 @@ export default {
|
|
|
286
285
|
}
|
|
287
286
|
});
|
|
288
287
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
// we have to do the XHR requests because we don't have schemas loaded yet...
|
|
289
|
+
let mgmtUser;
|
|
290
|
+
const selfUser = await this.$store.dispatch('management/request', {
|
|
291
|
+
url: `/v1/${ EXT.SELFUSER }`,
|
|
292
|
+
method: 'POST',
|
|
293
|
+
data: {}
|
|
292
294
|
});
|
|
293
295
|
|
|
294
|
-
if (
|
|
295
|
-
this.$store.dispatch('auth/
|
|
296
|
+
if (selfUser) {
|
|
297
|
+
await this.$store.dispatch('auth/updateSelfUser', selfUser);
|
|
298
|
+
mgmtUser = await this.$store.dispatch('management/request', { url: `/v1/${ MANAGEMENT.USER }/${ selfUser.status?.userID }` });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (!!mgmtUser) {
|
|
302
|
+
this.$store.dispatch('auth/gotUser', mgmtUser);
|
|
296
303
|
}
|
|
297
304
|
|
|
298
305
|
if ( this.remember ) {
|
|
@@ -320,7 +327,7 @@ export default {
|
|
|
320
327
|
$extension: this.$store.$extension,
|
|
321
328
|
});
|
|
322
329
|
|
|
323
|
-
if (this.firstLogin ||
|
|
330
|
+
if (this.firstLogin || mgmtUser?.mustChangePassword) {
|
|
324
331
|
this.$store.dispatch('auth/setInitialPass', this.password);
|
|
325
332
|
this.$router.push({ name: 'auth-setup' });
|
|
326
333
|
} else {
|