@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.
Files changed (128) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/translations/en-us.yaml +105 -5
  3. package/components/ActionMenuShell.vue +1 -1
  4. package/components/Inactivity.vue +2 -2
  5. package/components/Resource/Detail/Card/ExtrasCard.vue +49 -15
  6. package/components/Resource/Detail/Card/__tests__/ExtrasCard.test.ts +111 -0
  7. package/components/Resource/Detail/Masthead/__tests__/index.test.ts +0 -17
  8. package/components/Resource/Detail/Masthead/index.vue +11 -4
  9. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +3 -1
  10. package/components/Resource/Detail/Metadata/index.vue +1 -1
  11. package/components/Resource/Detail/ResourceRow.vue +1 -1
  12. package/components/ResourceDetail/Masthead/latest.vue +12 -2
  13. package/components/ResourceList/index.vue +9 -0
  14. package/components/ResourceTable.vue +38 -4
  15. package/components/Tabbed/Tab.vue +4 -0
  16. package/components/Tabbed/index.vue +4 -1
  17. package/components/__tests__/ProjectRow.test.ts +60 -0
  18. package/components/form/ChangePassword.vue +41 -35
  19. package/components/form/ResourceQuota/Project.vue +42 -1
  20. package/components/form/ResourceQuota/ProjectRow.vue +71 -4
  21. package/components/form/ResourceQuota/__tests__/Project.test.ts +63 -0
  22. package/components/form/SelectOrCreateAuthSecret.vue +6 -1
  23. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +35 -0
  24. package/components/formatter/KubeconfigClusters.vue +74 -0
  25. package/components/formatter/MachineSummaryGraph.vue +10 -2
  26. package/components/formatter/__tests__/KubeconfigClusters.test.ts +125 -0
  27. package/components/nav/TopLevelMenu.helper.ts +50 -2
  28. package/components/nav/TopLevelMenu.vue +14 -0
  29. package/components/nav/Type.vue +5 -0
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  31. package/components/nav/__tests__/Type.test.ts +6 -4
  32. package/config/product/explorer.js +4 -3
  33. package/config/product/manager.js +47 -3
  34. package/config/router/navigation-guards/authentication.js +8 -9
  35. package/config/router/routes.js +4 -1
  36. package/config/types.js +10 -2
  37. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  38. package/detail/management.cattle.io.user.vue +1 -2
  39. package/detail/node.vue +0 -1
  40. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  41. package/dialog/ChangePasswordDialog.vue +8 -0
  42. package/dialog/GenericPrompt.vue +20 -3
  43. package/dialog/ScaleMachineDownDialog.vue +65 -15
  44. package/dialog/SearchDialog.vue +10 -2
  45. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  46. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  47. package/edit/__tests__/management.cattle.io.project.test.js +56 -1
  48. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  49. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  50. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  51. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  52. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  53. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  54. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  56. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  57. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  58. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  59. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  60. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  61. package/edit/fleet.cattle.io.gitrepo.vue +16 -1
  62. package/edit/management.cattle.io.project.vue +8 -2
  63. package/edit/management.cattle.io.user.vue +29 -34
  64. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
  65. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -2
  66. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
  67. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  68. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +57 -2
  69. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +109 -0
  70. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -0
  71. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  72. package/list/ext.cattle.io.kubeconfig.vue +118 -0
  73. package/list/group.principal.vue +11 -15
  74. package/list/management.cattle.io.user.vue +11 -21
  75. package/machine-config/azure.vue +14 -0
  76. package/mixins/__tests__/chart.test.ts +147 -0
  77. package/mixins/browser-tab-visibility.js +5 -4
  78. package/mixins/chart.js +10 -8
  79. package/mixins/fetch.client.js +6 -0
  80. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  81. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +364 -0
  82. package/models/__tests__/secret.test.ts +55 -0
  83. package/models/__tests__/workload.test.ts +49 -6
  84. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  85. package/models/cluster.x-k8s.io.machine.js +1 -1
  86. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  87. package/models/event.js +5 -0
  88. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  89. package/models/ext.cattle.io.kubeconfig.ts +97 -0
  90. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  91. package/models/ext.cattle.io.selfuser.js +15 -0
  92. package/models/fleet-application.js +17 -7
  93. package/models/management.cattle.io.user.js +28 -31
  94. package/models/schema.js +18 -0
  95. package/models/secret.js +28 -25
  96. package/models/steve-schema.ts +39 -2
  97. package/models/workload.js +3 -2
  98. package/package.json +2 -2
  99. package/pages/about.vue +3 -2
  100. package/pages/account/index.vue +23 -16
  101. package/pages/auth/login.vue +15 -8
  102. package/pages/auth/setup.vue +52 -15
  103. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +38 -14
  104. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  105. package/pages/home.vue +9 -3
  106. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
  107. package/plugins/dashboard-store/actions.js +7 -0
  108. package/plugins/dashboard-store/getters.js +23 -1
  109. package/plugins/dashboard-store/index.js +3 -2
  110. package/plugins/dashboard-store/mutations.js +4 -0
  111. package/plugins/dashboard-store/resource-class.js +12 -5
  112. package/plugins/steve/__tests__/steve-class.test.ts +167 -0
  113. package/plugins/steve/schema.d.ts +5 -0
  114. package/plugins/steve/steve-class.js +19 -0
  115. package/plugins/steve/steve-pagination-utils.ts +2 -1
  116. package/rancher-components/RcItemCard/RcItemCard.test.ts +4 -2
  117. package/rancher-components/RcItemCard/RcItemCard.vue +27 -10
  118. package/store/auth.js +57 -19
  119. package/store/notifications.ts +1 -1
  120. package/store/type-map.js +12 -1
  121. package/types/shell/index.d.ts +24 -15
  122. package/types/store/dashboard-store.types.ts +7 -0
  123. package/utils/__tests__/chart.test.ts +96 -0
  124. package/utils/__tests__/version.test.ts +1 -19
  125. package/utils/chart.js +64 -0
  126. package/utils/pagination-wrapper.ts +11 -3
  127. package/utils/version.js +5 -17
  128. 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/v3User'];
33
+ const user = this.$rootGetters['auth/user'];
34
34
 
35
35
  if (user?.id) {
36
36
  return user;
37
37
  }
38
38
 
39
- const res = await this.$dispatch('rancher/request', {
40
- url: '/v3/users?me=true',
41
- method: 'get',
42
- }, { root: true });
39
+ const selfUser = await this.$dispatch('auth/getSelfUser');
43
40
 
44
- return res?.data?.[0] || {};
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 HybridModel, { cleanHybridResources } from '@shell/plugins/steve/hybrid-class';
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 HybridModel {
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.canRefreshAccess;
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('rancher/clone', { resource: this.norman }, { root: true });
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 user = await this.$dispatch('rancher/find', {
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
- await user.doAction('refreshauthprovideraccess');
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.canRefreshAccess
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
- get norman() {
288
- return this.$rootGetters['rancher/byId'](NORMAN.USER, this.id);
289
- }
286
+ cleanForSave(data) {
287
+ const val = super.cleanForSave(data);
290
288
 
291
- get canDelete() {
292
- return this.norman?.hasLink('remove') && !this.isCurrentUser;
293
- }
289
+ delete val.type;
294
290
 
295
- get canUpdate() {
296
- return this.norman?.hasLink('update');
291
+ return val;
297
292
  }
298
293
 
299
- remove() {
300
- return this.norman?.remove();
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
- return this.isProjectScopedRelated && !this.isProjectSecretCopy && this.$rootGetters['isRancher'];
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-resource-namespace-id`,
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 (!this.isProjectScoped) {
592
- return super.listLocation;
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
- name: 'c-cluster-product-resource',
597
- params: {
598
- product: this.$rootGetters['productId'],
599
- cluster: this.$rootGetters['clusterId'],
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 (!this.isProjectScoped) {
607
- return super.parentNameOverride;
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 this.$rootGetters['i18n/t'](`typeLabel."${ VIRTUAL_TYPES.PROJECT_SECRETS }"`, { count: 1 })?.trim();
613
+ return super.parentNameOverride;
611
614
  }
612
615
 
613
616
  get parentLocationOverride() {
614
- if (!this.isProjectScoped) {
615
- return super.parentNameOverride;
617
+ if (this.hasProjectScopedUrlQueryParam || this.isProjectScoped) {
618
+ return this.listLocation;
616
619
  }
617
620
 
618
- return this.listLocation;
621
+ return super.parentLocationOverride;
619
622
  }
620
623
 
621
624
  get groupByProject() {
@@ -1,6 +1,7 @@
1
1
  import { STEVE } from '@shell/config/types';
2
- import Schema from './schema';
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 Schema {
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
  *
@@ -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",
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.54",
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.settings.find((s) => s.id === SETTING.VERSION_RANCHER);
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.value }}</td>
128
+ </td><td>{{ rancherVersion }}</td>
128
129
  </tr>
129
130
  <tr v-if="dashboardVersion">
130
131
  <td>
@@ -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 users = await this.$store.dispatch('rancher/findAll', {
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 false;
142
+ return !!passwordChangeRequest?.canChangePassword;
137
143
  },
138
144
  showChangePasswordDialog() {
139
145
  this.$store.dispatch('management/promptModal', {
140
- component: 'ChangePasswordDialog',
141
- testId: 'change-password__modal',
142
- customClass: 'change-password-modal',
143
- modalWidth: '500',
144
- height: '465'
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
  }
@@ -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 { _MULTI } from '@shell/plugins/dashboard-store/actions';
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
- const user = await this.$store.dispatch('rancher/findAll', {
290
- type: NORMAN.USER,
291
- opt: { url: '/v3/users?me=true', load: _MULTI }
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 (!!user?.[0]) {
295
- this.$store.dispatch('auth/gotUser', user[0]);
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 || user[0]?.mustChangePassword) {
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 {