@rancher/shell 3.0.9-rc.4 → 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 (109) hide show
  1. package/assets/brand/suse/metadata.json +2 -1
  2. package/assets/translations/en-us.yaml +91 -3
  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/MachineSummaryGraph.vue +10 -2
  25. package/components/nav/TopLevelMenu.helper.ts +50 -2
  26. package/components/nav/TopLevelMenu.vue +14 -0
  27. package/components/nav/Type.vue +5 -0
  28. package/components/nav/__tests__/TopLevelMenu.test.ts +3 -3
  29. package/components/nav/__tests__/Type.test.ts +6 -4
  30. package/config/product/explorer.js +4 -3
  31. package/config/product/manager.js +18 -1
  32. package/config/router/navigation-guards/authentication.js +8 -9
  33. package/config/types.js +10 -2
  34. package/detail/auditlog.cattle.io.auditpolicy.vue +19 -0
  35. package/detail/management.cattle.io.user.vue +1 -2
  36. package/detail/node.vue +0 -1
  37. package/detail/provisioning.cattle.io.cluster.vue +2 -1
  38. package/dialog/ChangePasswordDialog.vue +8 -0
  39. package/dialog/GenericPrompt.vue +20 -3
  40. package/dialog/ScaleMachineDownDialog.vue +65 -15
  41. package/dialog/SearchDialog.vue +10 -2
  42. package/dialog/__tests__/ScaleMachineDownDialog.test.ts +184 -0
  43. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +89 -0
  44. package/edit/__tests__/management.cattle.io.project.test.js +56 -1
  45. package/edit/auditlog.cattle.io.auditpolicy/AdditionalRedactions.vue +114 -0
  46. package/edit/auditlog.cattle.io.auditpolicy/Filters.vue +119 -0
  47. package/edit/auditlog.cattle.io.auditpolicy/General.vue +180 -0
  48. package/edit/auditlog.cattle.io.auditpolicy/__tests__/AdditionalRedactions.test.ts +327 -0
  49. package/edit/auditlog.cattle.io.auditpolicy/__tests__/Filters.test.ts +449 -0
  50. package/edit/auditlog.cattle.io.auditpolicy/__tests__/General.test.ts +472 -0
  51. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/AdditionalRedactions.test.ts.snap +27 -0
  52. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/Filters.test.ts.snap +39 -0
  53. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +174 -0
  54. package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +29 -0
  55. package/edit/auditlog.cattle.io.auditpolicy/__tests__/index.test.ts +215 -0
  56. package/edit/auditlog.cattle.io.auditpolicy/index.vue +104 -0
  57. package/edit/auditlog.cattle.io.auditpolicy/types.ts +28 -0
  58. package/edit/fleet.cattle.io.gitrepo.vue +16 -1
  59. package/edit/management.cattle.io.project.vue +8 -2
  60. package/edit/management.cattle.io.user.vue +29 -34
  61. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +178 -0
  62. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -2
  63. package/edit/provisioning.cattle.io.cluster/shared.ts +4 -0
  64. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  65. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -1
  66. package/list/auditlog.cattle.io.auditpolicy.vue +63 -0
  67. package/list/group.principal.vue +11 -15
  68. package/list/management.cattle.io.user.vue +11 -21
  69. package/machine-config/azure.vue +14 -0
  70. package/mixins/browser-tab-visibility.js +5 -4
  71. package/mixins/fetch.client.js +6 -0
  72. package/models/__tests__/auditlog.cattle.io.auditpolicy.test.ts +117 -0
  73. package/models/__tests__/workload.test.ts +49 -6
  74. package/models/auditlog.cattle.io.auditpolicy.js +46 -0
  75. package/models/cluster.x-k8s.io.machine.js +1 -1
  76. package/models/cluster.x-k8s.io.machinedeployment.js +5 -5
  77. package/models/event.js +5 -0
  78. package/models/ext.cattle.io.groupmembershiprefreshrequest.js +15 -0
  79. package/models/ext.cattle.io.passwordchangerequest.js +15 -0
  80. package/models/ext.cattle.io.selfuser.js +15 -0
  81. package/models/fleet-application.js +17 -7
  82. package/models/management.cattle.io.user.js +28 -31
  83. package/models/schema.js +18 -0
  84. package/models/secret.js +27 -24
  85. package/models/steve-schema.ts +39 -2
  86. package/models/workload.js +3 -2
  87. package/package.json +1 -1
  88. package/pages/account/index.vue +23 -16
  89. package/pages/auth/login.vue +15 -8
  90. package/pages/auth/setup.vue +52 -15
  91. package/pages/home.vue +9 -3
  92. package/plugins/dashboard-store/__tests__/resource-class.test.ts +1 -3
  93. package/plugins/dashboard-store/actions.js +7 -0
  94. package/plugins/dashboard-store/getters.js +23 -1
  95. package/plugins/dashboard-store/index.js +3 -2
  96. package/plugins/dashboard-store/mutations.js +4 -0
  97. package/plugins/dashboard-store/resource-class.js +12 -5
  98. package/plugins/steve/__tests__/steve-class.test.ts +167 -0
  99. package/plugins/steve/schema.d.ts +5 -0
  100. package/plugins/steve/steve-class.js +19 -0
  101. package/plugins/steve/steve-pagination-utils.ts +2 -1
  102. package/store/auth.js +57 -19
  103. package/store/notifications.ts +1 -1
  104. package/store/type-map.js +12 -1
  105. package/types/shell/index.d.ts +10 -14
  106. package/types/store/dashboard-store.types.ts +7 -0
  107. package/utils/pagination-wrapper.ts +11 -3
  108. package/vue.config.js +26 -13
  109. package/edit/provisioning.cattle.io.cluster/defaults.ts +0 -1
@@ -595,7 +595,7 @@ describe('topLevelMenu', () => {
595
595
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
596
596
  const updateSpy = jest.fn();
597
597
  const mockHelper = {
598
- update: updateSpy, clustersPinned: [], clustersOthers: []
598
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
599
599
  };
600
600
 
601
601
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -624,7 +624,7 @@ describe('topLevelMenu', () => {
624
624
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
625
625
  const updateSpy = jest.fn();
626
626
  const mockHelper = {
627
- update: updateSpy, clustersPinned: [], clustersOthers: []
627
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
628
628
  };
629
629
 
630
630
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -653,7 +653,7 @@ describe('topLevelMenu', () => {
653
653
  jest.spyOn(sideNavService, 'init').mockImplementation(() => {});
654
654
  const updateSpy = jest.fn();
655
655
  const mockHelper = {
656
- update: updateSpy, clustersPinned: [], clustersOthers: []
656
+ update: updateSpy, clustersPinned: [], clustersOthers: [], updateCount: () => {}
657
657
  };
658
658
 
659
659
  jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any);
@@ -21,8 +21,9 @@ describe('component: Type', () => {
21
21
  const defaultCount = 1;
22
22
  const storeMock = {
23
23
  getters: {
24
- currentStore: () => 'cluster',
25
- 'cluster/count': () => defaultCount,
24
+ currentStore: () => 'cluster',
25
+ 'cluster/count': () => defaultCount,
26
+ 'type-map/optionsFor': () => {},
26
27
  }
27
28
  };
28
29
  const routerMock = {
@@ -449,8 +450,9 @@ describe('component: Type', () => {
449
450
 
450
451
  $store: {
451
452
  getters: {
452
- currentStore: () => 'cluster',
453
- 'cluster/count': () => null,
453
+ currentStore: () => 'cluster',
454
+ 'cluster/count': () => null,
455
+ 'type-map/optionsFor': () => {},
454
456
  }
455
457
  },
456
458
  $router: routerMock,
@@ -3,7 +3,7 @@ import {
3
3
  CONFIG_MAP,
4
4
  EVENT,
5
5
  NODE, SECRET, INGRESS,
6
- WORKLOAD, WORKLOAD_TYPES, SERVICE, HPA, NETWORK_POLICY, PV, PVC, STORAGE_CLASS, POD, POD_DISRUPTION_BUDGET, LIMIT_RANGE, RESOURCE_QUOTA,
6
+ WORKLOAD, WORKLOAD_TYPES, SERVICE, HPA, NETWORK_POLICY, PV, PVC, STORAGE_CLASS, POD, POD_DISRUPTION_BUDGET, LIMIT_RANGE, RESOURCE_QUOTA, AUDIT_POLICY,
7
7
  MANAGEMENT,
8
8
  NAMESPACE,
9
9
  NORMAN,
@@ -83,6 +83,7 @@ export function init(store) {
83
83
  NETWORK_POLICY,
84
84
  POD_DISRUPTION_BUDGET,
85
85
  RESOURCE_QUOTA,
86
+ AUDIT_POLICY,
86
87
  ], 'policy');
87
88
 
88
89
  basicType([
@@ -118,6 +119,7 @@ export function init(store) {
118
119
  weightGroup('storage', 95, true);
119
120
  weightGroup('policy', 94, true);
120
121
  weightType(POD, -1, true);
122
+ weightType(AUDIT_POLICY, -1, true);
121
123
 
122
124
  // here is where we define the usage of the WORKLOAD custom list view for
123
125
  // all the workload types (ex: deployments, cron jobs, daemonsets, etc)
@@ -152,6 +154,7 @@ export function init(store) {
152
154
  mapGroup('autoscaling', 'Autoscaling');
153
155
  mapGroup('policy', 'Policy');
154
156
  mapGroup('networking.k8s.io', 'Networking');
157
+ mapGroup('auditlog.cattle.io', 'Policy');
155
158
  mapGroup(/^(.+\.)?api(server)?.*\.k8s\.io$/, 'API');
156
159
  mapGroup('rbac.authorization.k8s.io', 'RBAC');
157
160
  mapGroup('admissionregistration.k8s.io', 'admission');
@@ -181,7 +184,6 @@ export function init(store) {
181
184
 
182
185
  const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
183
186
  const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
184
- const dePaginateNormanUsers = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true });
185
187
 
186
188
  configureType(NODE, { isCreatable: false, isEditable: true });
187
189
  configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
@@ -190,7 +192,6 @@ export function init(store) {
190
192
  configureType(MANAGEMENT.PROJECT, { displayName: store.getters['i18n/t']('namespace.project.label') });
191
193
  configureType(NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
192
194
  configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
193
- configureType(NORMAN.USER, { depaginate: dePaginateNormanUsers });
194
195
  configureType(SNAPSHOT, { depaginate: true });
195
196
 
196
197
  configureType(SECRET, { showListMasthead: false });
@@ -3,16 +3,20 @@ import {
3
3
  CAPI,
4
4
  CATALOG,
5
5
  EXT,
6
+ COUNT,
6
7
  NORMAN,
7
8
  HCI,
8
9
  MANAGEMENT,
9
10
  SNAPSHOT,
10
11
  VIRTUAL_TYPES,
11
- HOSTED_PROVIDER
12
+ HOSTED_PROVIDER,
13
+ SAVED_COUNTS
12
14
  } from '@shell/config/types';
13
15
  import { MULTI_CLUSTER } from '@shell/store/features';
14
16
  import { DSL } from '@shell/store/type-map';
15
17
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
18
+ import { markRaw } from 'vue';
19
+
16
20
  export const NAME = 'manager';
17
21
 
18
22
  export function init(store) {
@@ -219,4 +223,17 @@ export function init(store) {
219
223
  defaultSort: true,
220
224
  },
221
225
  ]);
226
+
227
+ // Configure custom count getter for cluster count (so we don't include Harvester clusters)
228
+ configureType(CAPI.RANCHER_CLUSTER, {
229
+ custom: {
230
+ countGetter: markRaw((getters) => {
231
+ const savedClusterCount = getters['management/getSavedCount'](SAVED_COUNTS.K8S_CLUSTERS);
232
+ const counts = getters[`management/all`](COUNT)?.[0]?.counts || {};
233
+ const clusterCount = counts[CAPI.RANCHER_CLUSTER]?.summary.count;
234
+
235
+ return savedClusterCount || clusterCount;
236
+ })
237
+ }
238
+ });
222
239
  }
@@ -28,16 +28,16 @@ export function install(router, context) {
28
28
  }
29
29
 
30
30
  /**
31
- * Generate an object that includes both the v3User and the me data
32
- * @param {*} v3User V3 user information
31
+ * Generate an object that includes both the user and the me data
32
+ * @param {*} user User information
33
33
  * @param {*} me Me user data
34
34
  * @returns User info to be passed to `isLoggedIn`
35
35
  */
36
- function getUserObject(v3User, me) {
36
+ function getUserObject(user, me) {
37
37
  return {
38
38
  id: me.id,
39
39
  me,
40
- v3User,
40
+ user,
41
41
  };
42
42
  }
43
43
 
@@ -59,10 +59,9 @@ export async function authenticate(to, from, next, { store }) {
59
59
  if ( store.getters['auth/enabled'] !== false && !store.getters['auth/loggedIn'] ) {
60
60
  // `await` so we have one successfully request whilst possibly logged in (ensures fromHeader is populated from `x-api-cattle-auth`)
61
61
  await store.dispatch('auth/getUser');
62
+ const user = store.getters['auth/user'] || {};
62
63
 
63
- const v3User = store.getters['auth/v3User'] || {};
64
-
65
- if (v3User?.mustChangePassword) {
64
+ if (user?.mustChangePassword) {
66
65
  return next({ name: 'auth-setup' });
67
66
  }
68
67
 
@@ -75,7 +74,7 @@ export async function authenticate(to, from, next, { store }) {
75
74
  } else if ( fromHeader === 'true' ) {
76
75
  const me = await findMe(store);
77
76
 
78
- await isLoggedIn(store, getUserObject(v3User, me));
77
+ await isLoggedIn(store, getUserObject(user, me));
79
78
  handleOidcRedirectToCallbackUrl();
80
79
  } else if ( fromHeader === 'false' ) {
81
80
  notLoggedIn(store, next, to);
@@ -86,7 +85,7 @@ export async function authenticate(to, from, next, { store }) {
86
85
  try {
87
86
  const me = await findMe(store);
88
87
 
89
- await isLoggedIn(store, getUserObject(v3User, me));
88
+ await isLoggedIn(store, getUserObject(user, me));
90
89
  handleOidcRedirectToCallbackUrl();
91
90
  } catch (e) {
92
91
  const status = e?._status;
package/config/types.js CHANGED
@@ -61,6 +61,7 @@ export const POD_DISRUPTION_BUDGET = 'policy.poddisruptionbudget';
61
61
  export const PV = 'persistentvolume';
62
62
  export const PVC = 'persistentvolumeclaim';
63
63
  export const RESOURCE_QUOTA = 'resourcequota';
64
+ export const AUDIT_POLICY = 'auditlog.cattle.io.auditpolicy';
64
65
  export const SCHEMA = 'schema';
65
66
  export const SERVICE = 'service';
66
67
  export const SECRET = 'secret';
@@ -263,8 +264,11 @@ export const BRAND = {
263
264
  };
264
265
 
265
266
  export const EXT = {
266
- USER_ACTIVITY: 'ext.cattle.io.useractivity',
267
- KUBECONFIG: 'ext.cattle.io.kubeconfig',
267
+ USER_ACTIVITY: 'ext.cattle.io.useractivity',
268
+ SELFUSER: 'ext.cattle.io.selfuser',
269
+ GROUP_MEMBERSHIP_REFRESH_REQUESTS: 'ext.cattle.io.groupmembershiprefreshrequest',
270
+ PASSWORD_CHANGE_REQUESTS: 'ext.cattle.io.passwordchangerequest',
271
+ KUBECONFIG: 'ext.cattle.io.kubeconfig',
268
272
  };
269
273
 
270
274
  export const CAPI = {
@@ -409,3 +413,7 @@ export const DEPRECATED = 'Deprecated';
409
413
  export const EXPERIMENTAL = 'Experimental';
410
414
  export const AUTOSCALER_CONFIG_MAP_ID = 'kube-system/cluster-autoscaler-status';
411
415
  export const HOSTED_PROVIDER = 'hostedprovider';
416
+
417
+ // Named saved counts
418
+
419
+ export const SAVED_COUNTS = { K8S_CLUSTERS: 'k8sClusters' };
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ // Shared AuditPolicy interface for all audit log policy components
3
+ import CreateEditView from '@shell/mixins/create-edit-view';
4
+ import ResourceTabs from '@shell/components/form/ResourceTabs/index.vue';
5
+
6
+ export default {
7
+ name: 'DetailAuditPolicy',
8
+ components: { ResourceTabs },
9
+ mixins: [CreateEditView],
10
+ };
11
+ </script>
12
+
13
+ <template>
14
+ <Loading v-if="$fetchState.pending" />
15
+ <ResourceTabs
16
+ :mode="mode"
17
+ :value="value"
18
+ />
19
+ </template>
@@ -3,7 +3,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
3
3
  import Tab from '@shell/components/Tabbed/Tab';
4
4
  import ResourceTabs from '@shell/components/form/ResourceTabs';
5
5
  import SortableTable from '@shell/components/SortableTable';
6
- import { MANAGEMENT, NORMAN } from '@shell/config/types';
6
+ import { MANAGEMENT } from '@shell/config/types';
7
7
  import Loading from '@shell/components/Loading';
8
8
  import { NAME } from '@shell/config/table-headers';
9
9
 
@@ -31,7 +31,6 @@ export default {
31
31
  if (this.canSeeRoleTemplates) {
32
32
  // Upfront fetch, avoid async computes
33
33
  await Promise.all([
34
- await this.$store.dispatch('rancher/find', { type: NORMAN.USER, id: this.value.id }),
35
34
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.ROLE_TEMPLATE }),
36
35
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING }),
37
36
  await this.$store.dispatch('management/findAll', { type: MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING })
package/detail/node.vue CHANGED
@@ -196,7 +196,6 @@ export default {
196
196
  v-else
197
197
  class="node"
198
198
  >
199
- <div class="spacer" />
200
199
  <div class="alerts">
201
200
  <Alert
202
201
  class="mr-10"
@@ -364,7 +364,8 @@ export default {
364
364
  const machineFullName = machineNameFn(this.value.name, mp.name);
365
365
 
366
366
  const machines = this.value.machines.filter((machine) => {
367
- const isElementalCluster = machine.spec?.infrastructureRef?.apiVersion.startsWith('elemental.cattle.io');
367
+ // elemental machines use an older version of CAPI CRDs that use apiVersion instead of apiGroup
368
+ const isElementalCluster = (machine.spec?.infrastructureRef?.apiGroup || machine.spec?.infrastructureRef?.apiVersion).startsWith('elemental.cattle.io');
368
369
  const machinePoolInfName = machine.spec?.infrastructureRef?.name;
369
370
 
370
371
  if (isElementalCluster) {
@@ -9,6 +9,13 @@ export default {
9
9
  components: {
10
10
  Card, AsyncButton, ChangePassword
11
11
  },
12
+ props: {
13
+ user: {
14
+ type: Object,
15
+ default: () => null,
16
+ required: true
17
+ }
18
+ },
12
19
  data() {
13
20
  return { valid: false, password: '' };
14
21
  },
@@ -45,6 +52,7 @@ export default {
45
52
  <form @submit.prevent>
46
53
  <ChangePassword
47
54
  ref="changePassword"
55
+ :user="user"
48
56
  @valid="valid = $event"
49
57
  />
50
58
  </form>
@@ -38,9 +38,24 @@ export default {
38
38
  type: Function,
39
39
  default: () => { }
40
40
  },
41
+ actionColor: {
42
+ type: String,
43
+ default: 'role-primary',
44
+ },
45
+ errors: {
46
+ type: Array,
47
+ default: () => []
48
+ }
41
49
  },
50
+
42
51
  data() {
43
- return { errors: [] };
52
+ return { applyErrors: [] };
53
+ },
54
+
55
+ computed: {
56
+ allErrors() {
57
+ return [...this.errors, ...this.applyErrors];
58
+ }
44
59
  },
45
60
 
46
61
  methods: {
@@ -51,13 +66,14 @@ export default {
51
66
  },
52
67
 
53
68
  async apply(buttonDone) {
69
+ this.applyErrors = [];
54
70
  try {
55
71
  await this.applyAction(buttonDone);
56
72
  this.confirm(true);
57
73
  this.$emit('close');
58
74
  } catch (err) {
59
75
  console.error(err); // eslint-disable-line
60
- this.errors = exceptionToErrorsArray(err);
76
+ this.applyErrors = exceptionToErrorsArray(err);
61
77
  buttonDone(false);
62
78
  }
63
79
  }
@@ -92,7 +108,7 @@ export default {
92
108
  <template #actions>
93
109
  <div class="bottom">
94
110
  <Banner
95
- v-for="(err, i) in errors"
111
+ v-for="(err, i) in allErrors"
96
112
  :key="i"
97
113
  color="error"
98
114
  :label="err"
@@ -107,6 +123,7 @@ export default {
107
123
 
108
124
  <AsyncButton
109
125
  :mode="applyMode"
126
+ :action-color="actionColor"
110
127
  @click="apply"
111
128
  />
112
129
  </div>
@@ -2,7 +2,7 @@
2
2
  import { CAPI as CAPI_LABELS } from '@shell/config/labels-annotations';
3
3
  import { MANAGEMENT, CAPI } from '@shell/config/types';
4
4
  import GenericPrompt from './GenericPrompt';
5
-
5
+ import { exceptionToErrorsArray } from '@shell/utils/error';
6
6
  export default {
7
7
  emits: ['close'],
8
8
 
@@ -16,17 +16,26 @@ export default {
16
16
  },
17
17
 
18
18
  async fetch() {
19
- if (this.isRke2) {
20
- await Promise.all([
21
- this.$store.dispatch('management/findAll', { type: CAPI.MACHINE_DEPLOYMENT }),
22
- this.$store.dispatch('management/findAll', { type: CAPI.MACHINE })
23
- ]);
24
- } else {
25
- await Promise.all([
26
- this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_POOL }),
27
- this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE })
28
- ]);
19
+ this.loading = true;
20
+ try {
21
+ if (this.isRke2) {
22
+ await Promise.all([
23
+ this.$store.dispatch('management/findAll', { type: CAPI.MACHINE_DEPLOYMENT }),
24
+ this.$store.dispatch('management/findAll', { type: CAPI.MACHINE })
25
+ ]);
26
+ const machineSetPromises = this.safeMachinesToDelete.filter((machine) => machine.isWorker).map((machine) => this.getMachineSets(machine));
27
+
28
+ this.workerMachineSets = await Promise.all(machineSetPromises);
29
+ } else {
30
+ await Promise.all([
31
+ this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_POOL }),
32
+ this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE })
33
+ ]);
34
+ }
35
+ } catch (e) {
36
+ this.errors = exceptionToErrorsArray(e);
29
37
  }
38
+ this.loading = false;
30
39
  },
31
40
 
32
41
  data() {
@@ -51,11 +60,39 @@ export default {
51
60
  title: this.t('promptRemove.title'),
52
61
  applyMode: 'delete',
53
62
  applyAction: this.remove,
54
- }
63
+ },
64
+ workerMachineSets: [],
65
+ loading: false,
66
+ errors: []
55
67
  };
56
68
  },
69
+ computed: {
70
+ showScaling() {
71
+ for (const machineSet of this.workerMachineSets) {
72
+ const data = machineSet?.data || [];
73
+
74
+ for (const ms of data) {
75
+ if (!ms?.spec?.replicas || !ms?.status?.readyReplicas || ms.spec.replicas !== ms.status.readyReplicas) {
76
+ return true;
77
+ }
78
+ }
79
+ }
80
+
81
+ return false;
82
+ }
83
+ },
57
84
 
58
85
  methods: {
86
+ getMachineSets(machine) {
87
+ const namespace = machine.namespace;
88
+ const labelSelector = { matchLabels: { [CAPI_LABELS.DEPLOYMENT_NAME]: machine.poolName } }; // machine.poolId
89
+
90
+ return this.$store.dispatch('management/findLabelSelector', {
91
+ type: CAPI.MACHINE_SET,
92
+ matching: { namespace, labelSelector },
93
+ opt: { transient: true }
94
+ });
95
+ },
59
96
  deleteType(type, allToDelete, cluster, isRke2) {
60
97
  const allToDeleteByType = allToDelete.reduce((res, m) => {
61
98
  if (m[type]) {
@@ -117,15 +154,28 @@ export default {
117
154
  <template>
118
155
  <GenericPrompt
119
156
  v-bind="config"
157
+ action-color="bg-error role-primary"
158
+ :errors="errors"
120
159
  @close="$emit('close')"
121
160
  >
122
161
  <template #body>
123
162
  <div class="pl-10 pr-10 mt-20 mb-20 body">
124
- <div v-if="allToDelete.length === 1">
125
- {{ t('promptRemove.attemptingToRemove', { type }) }} <b>{{ safeMachinesToDelete[0].nameDisplay }}</b>
163
+ <div
164
+ v-if="loading"
165
+ class="text-center"
166
+ >
167
+ <i class="icon icon-spinner icon-spin icon-lg" />
126
168
  </div>
127
169
  <div v-else>
128
- {{ t('promptScaleMachineDown.attemptingToRemove', { type, count: allToDelete.length }, true) }}
170
+ <div v-if="showScaling">
171
+ <span v-clean-html="t('promptScaleMachineDown.scaling', { count: resources.length }, true)" />
172
+ </div>
173
+ <div v-else-if="safeMachinesToDelete.length === 1">
174
+ {{ t('promptRemove.attemptingToRemove', { type }) }} <b>{{ safeMachinesToDelete[0].nameDisplay }}</b>
175
+ </div>
176
+ <div v-else>
177
+ {{ t('promptScaleMachineDown.attemptingToRemove', { type, count: allToDelete.length }, true) }}
178
+ </div>
129
179
  </div>
130
180
  <div
131
181
  v-if="ignored.length"
@@ -46,13 +46,21 @@ export default {
46
46
  const counts = this.$store.getters[`${ product.inStore }/all`](COUNT)?.[0]?.counts || {};
47
47
 
48
48
  out.forEach((o) => {
49
- o.children?.forEach((t) => {
49
+ o.children = o.children?.reduce((res, t) => {
50
+ if (!this.$store.getters[`${ product.inStore }/canList`](t.name)) {
51
+ return res;
52
+ }
53
+
50
54
  const count = counts[t.name];
51
55
 
52
56
  t.count = count ? count.summary.count || 0 : null;
53
57
  t.byNamespace = count ? count.namespaces : {};
54
58
  t.revision = count ? count.revision : null;
55
- });
59
+
60
+ res.push(t);
61
+
62
+ return res;
63
+ }, []);
56
64
  });
57
65
 
58
66
  this.groups = out;