@rancher/shell 2.0.2-rc.1 → 2.0.3

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 (59) hide show
  1. package/assets/translations/en-us.yaml +53 -31
  2. package/components/PromptRemove.vue +8 -3
  3. package/components/ResourceDetail/Masthead.vue +1 -0
  4. package/components/ResourceDetail/index.vue +2 -1
  5. package/components/SideNav.vue +1 -1
  6. package/components/TableDataUserIcon.vue +1 -1
  7. package/components/fleet/FleetClusters.vue +0 -3
  8. package/components/fleet/FleetRepos.vue +0 -7
  9. package/components/formatter/CloudCredExpired.vue +69 -0
  10. package/components/formatter/ClusterProvider.vue +3 -3
  11. package/components/formatter/Date.vue +1 -1
  12. package/components/nav/Header.vue +9 -5
  13. package/components/nav/TopLevelMenu.vue +127 -63
  14. package/components/nav/__tests__/TopLevelMenu.test.ts +53 -27
  15. package/config/labels-annotations.js +3 -0
  16. package/core/types-provisioning.ts +5 -0
  17. package/core/types.ts +26 -1
  18. package/detail/catalog.cattle.io.app.vue +17 -4
  19. package/detail/fleet.cattle.io.bundle.vue +5 -68
  20. package/detail/fleet.cattle.io.cluster.vue +11 -9
  21. package/detail/fleet.cattle.io.gitrepo.vue +3 -2
  22. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +109 -24
  23. package/edit/provisioning.cattle.io.cluster/index.vue +10 -4
  24. package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
  25. package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +1 -0
  26. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +177 -26
  27. package/list/provisioning.cattle.io.cluster.vue +56 -5
  28. package/mixins/chart.js +6 -2
  29. package/models/__tests__/management.cattle.io.cluster.test.ts +3 -3
  30. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +0 -86
  31. package/models/catalog.cattle.io.app.js +108 -21
  32. package/models/cloudcredential.js +159 -2
  33. package/models/fleet.cattle.io.bundle.js +3 -1
  34. package/models/fleet.cattle.io.gitrepo.js +50 -61
  35. package/models/management.cattle.io.cluster.js +15 -7
  36. package/models/provisioning.cattle.io.cluster.js +62 -15
  37. package/package.json +1 -1
  38. package/pages/c/_cluster/apps/charts/install.vue +2 -1
  39. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  40. package/pages/c/_cluster/explorer/index.vue +1 -2
  41. package/pages/c/_cluster/fleet/index.vue +12 -5
  42. package/pages/c/_cluster/manager/cloudCredential/index.vue +68 -4
  43. package/pages/c/_cluster/uiplugins/index.vue +4 -2
  44. package/pages/home.vue +1 -0
  45. package/scripts/extension/bundle +1 -1
  46. package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
  47. package/scripts/extension/parse-tag-name +21 -12
  48. package/scripts/publish-shell.sh +10 -4
  49. package/scripts/typegen.sh +27 -22
  50. package/store/features.js +1 -0
  51. package/types/resources/fleet.d.ts +40 -0
  52. package/types/shell/index.d.ts +4692 -0
  53. package/utils/auth.js +1 -1
  54. package/utils/cluster.js +1 -1
  55. package/utils/fleet.ts +159 -0
  56. package/utils/string.js +9 -0
  57. package/utils/v-sphere.ts +282 -0
  58. package/vue.config.js +3 -3
  59. package/shell/types/shell/index.d.ts +0 -2
@@ -1,11 +1,65 @@
1
- import { CAPI } from '@shell/config/labels-annotations';
1
+ import { CAPI, CLOUD_CREDENTIALS } from '@shell/config/labels-annotations';
2
2
  import { fullFields, prefixFields, simplify, suffixFields } from '@shell/store/plugins';
3
3
  import { isEmpty, set } from '@shell/utils/object';
4
- import { SECRET } from '@shell/config/types';
4
+ import { MANAGEMENT, SECRET } from '@shell/config/types';
5
5
  import { escapeHtml } from '@shell/utils/string';
6
6
  import NormanModel from '@shell/plugins/steve/norman-class';
7
+ import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
8
+ import day from 'dayjs';
9
+
10
+ const harvesterProvider = 'harvester';
11
+
12
+ const renew = {
13
+ [harvesterProvider]: {
14
+ renew: ({ cloudCredential, $ctx }) => {
15
+ return renew[harvesterProvider].renewBulk(
16
+ { cloudCredentials: [cloudCredential], $ctx }
17
+ );
18
+ },
19
+ renewBulk: async({ cloudCredentials, $ctx }) => {
20
+ // A harvester cloud credential (at the moment) is a kubeconfig complete with expiring token
21
+ // So to renew we just need to generate a new kubeconfig and save it to the cc (similar to shell/cloud-credential/harvester.vue)
22
+ await Promise.all(cloudCredentials.map(async(cc) => {
23
+ try {
24
+ if (!cc.harvestercredentialConfig?.clusterId) {
25
+ throw new Error(`credential has no matching harvester cluster`);
26
+ }
27
+ const mgmtCluster = $ctx.rootGetters['management/byId'](MANAGEMENT.CLUSTER, cc.harvestercredentialConfig.clusterId);
28
+
29
+ if (!mgmtCluster) {
30
+ throw new Error(`cannot find harvester cluster`);
31
+ }
32
+
33
+ const kubeconfigContent = await mgmtCluster.generateKubeConfig();
34
+
35
+ cc.setData('kubeconfigContent', kubeconfigContent);
36
+
37
+ await cc.save();
38
+ } catch (error) {
39
+ console.error(`Unable to refresh harvester cloud credential '${ cc.id }'`, error); // eslint-disable-line no-console
40
+ }
41
+ }));
42
+ }
43
+ }
44
+ };
7
45
 
8
46
  export default class CloudCredential extends NormanModel {
47
+ get _availableActions() {
48
+ const out = super._availableActions;
49
+
50
+ out.splice(0, 0, { divider: true });
51
+ out.splice(0, 0, {
52
+ action: 'renew',
53
+ enabled: this.canRenew,
54
+ bulkable: this.canBulkRenew,
55
+ bulkAction: 'renewBulk',
56
+ icon: 'icon icon-fw icon-refresh',
57
+ label: this.t('manager.cloudCredentials.renew'),
58
+ });
59
+
60
+ return out;
61
+ }
62
+
9
63
  get hasSensitiveData() {
10
64
  return true;
11
65
  }
@@ -177,4 +231,107 @@ export default class CloudCredential extends NormanModel {
177
231
  get doneRoute() {
178
232
  return 'c-cluster-manager-secret';
179
233
  }
234
+
235
+ get canRenew() {
236
+ return !!renew[this.provider]?.renew && this.expires !== undefined && this.canUpdate;
237
+ }
238
+
239
+ get canBulkRenew() {
240
+ return !!renew[this.provider]?.renewBulk;
241
+ }
242
+
243
+ get expiresForSort() {
244
+ // Why not just `expires`?
245
+ // Ensures the correct sort order:
246
+ // 'expired' --> 'expiring soon' --> 'never expires'
247
+ return this.expires !== undefined ? this.expires : Number.MAX_SAFE_INTEGER;
248
+ }
249
+
250
+ get expires() {
251
+ const expires = this.annotations[CLOUD_CREDENTIALS.EXPIRATION];
252
+
253
+ if (typeof expires === 'string') {
254
+ return parseInt(expires);
255
+ } else if (typeof expires === 'number') {
256
+ return expires;
257
+ }
258
+
259
+ return undefined; // Weird things happen if this isn't a number
260
+ }
261
+
262
+ get expireData() {
263
+ if (typeof this.expiresIn !== 'number') {
264
+ return null;
265
+ }
266
+
267
+ const sevenDays = 1000 * 60 * 60 * 24 * 7;
268
+
269
+ if (this.expiresIn === 0) {
270
+ return {
271
+ expired: true,
272
+ expiring: false,
273
+ };
274
+ } else if (this.expiresIn < sevenDays) {
275
+ return {
276
+ expired: false,
277
+ expiring: true,
278
+ };
279
+ }
280
+
281
+ return null;
282
+ }
283
+
284
+ get expiresString() {
285
+ if (this.expires === undefined) {
286
+ return '';
287
+ }
288
+
289
+ if (this.expireData.expired) {
290
+ return this.t('manager.cloudCredentials.expired');
291
+ }
292
+
293
+ const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
294
+ const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
295
+
296
+ return day(this.expires).format(`${ dateFormat } ${ timeFormat }`);
297
+ }
298
+
299
+ get expiresIn() {
300
+ if (this.expires === undefined) {
301
+ return null;
302
+ }
303
+
304
+ const timeThen = this.expires;
305
+ const timeNow = Date.now();
306
+
307
+ const expiresIn = timeThen - timeNow;
308
+
309
+ return expiresIn < 0 ? 0 : expiresIn;
310
+ }
311
+
312
+ renew() {
313
+ const renewFn = renew[this.provider]?.renew;
314
+
315
+ if (!renewFn) {
316
+ console.error('No fn renew function for ', this.provider); // eslint-disable-line no-console
317
+ }
318
+
319
+ return renewFn({
320
+ cloudCredential: this,
321
+ $ctx: this.$ctx
322
+ });
323
+ }
324
+
325
+ async renewBulk(cloudCredentials = []) {
326
+ const renewBulkFn = renew[this.provider]?.renewBulk;
327
+
328
+ if (!renewBulkFn) {
329
+ console.error('No fn renew bulk function for ', this.provider); // eslint-disable-line no-console
330
+ }
331
+
332
+ return renewBulkFn({
333
+ cloudCredentials,
334
+ $ctx: this.$ctx
335
+ });
336
+ }
180
337
  }
@@ -30,7 +30,9 @@ export default class FleetBundle extends SteveModel {
30
30
  }
31
31
 
32
32
  get repoName() {
33
- return this.metadata.labels['fleet.cattle.io/repo-name'];
33
+ const labels = this.metadata?.labels || {};
34
+
35
+ return labels['fleet.cattle.io/repo-name'];
34
36
  }
35
37
 
36
38
  get targetClusters() {
@@ -1,16 +1,17 @@
1
1
  import Vue from 'vue';
2
2
  import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
3
3
  import jsyaml from 'js-yaml';
4
- import { escapeHtml, randomStr } from '@shell/utils/string';
4
+ import { escapeHtml } from '@shell/utils/string';
5
5
  import { FLEET } from '@shell/config/types';
6
6
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
7
7
  import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
8
8
  import { set } from '@shell/utils/object';
9
9
  import SteveModel from '@shell/plugins/steve/steve-class';
10
10
  import {
11
- STATES_ENUM, colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
11
+ colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
12
12
  } from '@shell/plugins/dashboard-store/resource-class';
13
13
  import { NAME } from '@shell/config/product/explorer';
14
+ import FleetUtils from '@shell/utils/fleet';
14
15
 
15
16
  function quacksLikeAHash(str) {
16
17
  if (str.match(/^[a-f0-9]{40,}$/i)) {
@@ -308,20 +309,11 @@ export default class GitRepo extends SteveModel {
308
309
  bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
309
310
  }
310
311
 
312
+ /**
313
+ * Bundles with state of active
314
+ */
311
315
  get bundlesReady() {
312
- if (this.bundles && this.bundles.length) {
313
- return this.bundles.filter((bundle) => bundle.state === 'active');
314
- }
315
-
316
- return 0;
317
- }
318
-
319
- get targetClustersReady() {
320
- if (this.targetClusters && this.targetClusters.length) {
321
- return this.targetClusters.filter((cluster) => cluster.state === 'active');
322
- }
323
-
324
- return 0;
316
+ return this.bundles?.filter((bundle) => bundle.state === 'active');
325
317
  }
326
318
 
327
319
  get bundleDeployments() {
@@ -331,35 +323,29 @@ export default class GitRepo extends SteveModel {
331
323
  }
332
324
 
333
325
  get resourcesStatuses() {
334
- const clusters = this.targetClusters || [];
335
- const resources = this.status?.resources || [];
336
- const conditions = this.status?.conditions || [];
326
+ const bundleDeployments = this.bundleDeployments || [];
327
+ const clusters = (this.targetClusters || []).reduce((res, c) => {
328
+ res[c.id] = c;
337
329
 
338
- const out = [];
330
+ return res;
331
+ }, {});
339
332
 
340
- for (const c of clusters) {
341
- const clusterBundleDeploymentResources = this.bundleDeployments
342
- .find((bd) => bd.metadata?.labels?.[FLEET_ANNOTATIONS.CLUSTER] === c.metadata.name)
343
- ?.status?.resources || [];
333
+ const out = [];
344
334
 
345
- resources.forEach((r, i) => {
346
- let namespacedName = r.name;
335
+ for (const bd of bundleDeployments) {
336
+ const clusterId = FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels);
337
+ const c = clusters[clusterId];
347
338
 
348
- if (r.namespace) {
349
- namespacedName = `${ r.namespace }:${ r.name }`;
350
- }
339
+ if (!c) {
340
+ continue;
341
+ }
351
342
 
352
- let state = r.state;
353
- const perEntry = r.perClusterState?.find((x) => x.clusterId === c.id);
354
- const tooMany = r.perClusterState?.length >= 10 || false;
343
+ const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
355
344
 
356
- if (perEntry) {
357
- state = perEntry.state;
358
- } else if (tooMany) {
359
- state = STATES_ENUM.UNKNOWN;
360
- } else {
361
- state = STATES_ENUM.READY;
362
- }
345
+ resources.forEach((r) => {
346
+ const id = FleetUtils.resourceId(r);
347
+ const type = FleetUtils.resourceType(r);
348
+ const state = r.state;
363
349
 
364
350
  const color = colorForState(state).replace('text-', 'bg-');
365
351
  const display = stateDisplay(state);
@@ -369,33 +355,38 @@ export default class GitRepo extends SteveModel {
369
355
  params: {
370
356
  product: NAME,
371
357
  cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
372
- resource: r.type,
358
+ resource: type,
373
359
  namespace: r.namespace,
374
360
  id: r.name,
375
361
  }
376
362
  };
377
363
 
364
+ const key = `${ c.id }-${ type }-${ r.namespace }-${ r.name }`;
365
+
378
366
  out.push({
379
- key: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }`,
380
- tableKey: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }-${ randomStr(8) }`,
381
- kind: r.kind,
382
- apiVersion: r.apiVersion,
383
- type: r.type,
384
- id: r.id,
385
- namespace: r.namespace,
386
- name: r.name,
387
- clusterId: c.id,
388
- clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
389
- clusterName: c.nameDisplay,
390
- state: mapStateToEnum(state),
391
- stateBackground: color,
392
- stateDisplay: display,
393
- stateSort: stateSort(color, display),
394
- namespacedName,
367
+ key,
368
+ tableKey: key,
369
+
370
+ // Needed?
371
+ id,
372
+ type,
373
+ clusterId: c.id,
374
+
375
+ // columns, see FleetResources.vue
376
+ state: mapStateToEnum(state),
377
+ clusterName: c.nameDisplay,
378
+ apiVersion: r.apiVersion,
379
+ kind: r.kind,
380
+ name: r.name,
381
+ namespace: r.namespace,
382
+ creationTimestamp: r.createdAt,
383
+
384
+ // other properties
385
+ clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
386
+ stateBackground: color,
387
+ stateDisplay: display,
388
+ stateSort: stateSort(color, display),
395
389
  detailLocation,
396
- conditions: conditions[i],
397
- bundleDeploymentStatus: clusterBundleDeploymentResources?.[i],
398
- creationTimestamp: clusterBundleDeploymentResources?.[i]?.createdAt
399
390
  });
400
391
  });
401
392
  }
@@ -416,9 +407,7 @@ export default class GitRepo extends SteveModel {
416
407
 
417
408
  get clusterResourceStatus() {
418
409
  const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => {
419
- const { clusterId, clusterLabel } = curr;
420
-
421
- const state = curr.state;
410
+ const { clusterId, clusterLabel, state } = curr;
422
411
 
423
412
  if (!prev[clusterId]) {
424
413
  prev[clusterId] = {
@@ -16,6 +16,8 @@ import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
16
16
  import { PINNED_CLUSTERS } from '@shell/store/prefs';
17
17
  import { copyTextToClipboard } from '@shell/utils/clipboard';
18
18
 
19
+ const DEFAULT_BADGE_COLOR = '#707070';
20
+
19
21
  // See translation file cluster.providers for list of providers
20
22
  // If the logo is not named with the provider name, add an override here
21
23
  const PROVIDER_LOGO_OVERRIDE = {};
@@ -88,10 +90,6 @@ export default class MgmtCluster extends SteveModel {
88
90
  }
89
91
 
90
92
  get provisioner() {
91
- if (this.status?.provider ) {
92
- return this.status.provider;
93
- }
94
-
95
93
  // For imported K3s clusters, this.status.driver is 'k3s.'
96
94
  return this.status?.driver ? this.status.driver : 'imported';
97
95
  }
@@ -115,7 +113,8 @@ export default class MgmtCluster extends SteveModel {
115
113
  get providerForEmberParam() {
116
114
  // Ember wants one word called provider to tell what component to show, but has much indirect mapping to figure out what it is.
117
115
  let provider;
118
- // Provisioner is the "<something>Config" in the model
116
+
117
+ // provisioner is status.driver
119
118
  const provisioner = KONTAINER_TO_DRIVER[(this.provisioner || '').toLowerCase()] || this.provisioner;
120
119
 
121
120
  if ( provisioner === 'rancherKubernetesEngine' ) {
@@ -306,13 +305,22 @@ export default class MgmtCluster extends SteveModel {
306
305
  return undefined;
307
306
  }
308
307
 
309
- const color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || '#7f7f7f';
308
+ let color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || DEFAULT_BADGE_COLOR;
310
309
  const iconText = this.metadata?.annotations[CLUSTER_BADGE.ICON_TEXT] || '';
310
+ let foregroundColor;
311
+
312
+ try {
313
+ foregroundColor = textColor(parseColor(color.trim())); // Remove any whitespace
314
+ } catch (_e) {
315
+ // If we could not parse the badge color, use the defaults
316
+ color = DEFAULT_BADGE_COLOR;
317
+ foregroundColor = textColor(parseColor(color));
318
+ }
311
319
 
312
320
  return {
313
321
  text: comment || undefined,
314
322
  color,
315
- textColor: textColor(parseColor(color)),
323
+ textColor: foregroundColor,
316
324
  iconText: iconText.substr(0, 3)
317
325
  };
318
326
  }
@@ -10,7 +10,6 @@ import { compare } from '@shell/utils/version';
10
10
  import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params';
11
11
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
12
12
  import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations';
13
- import capitalize from 'lodash/capitalize';
14
13
 
15
14
  /**
16
15
  * Class representing Cluster resource.
@@ -166,6 +165,19 @@ export default class ProvCluster extends SteveModel {
166
165
  enabled: canSaveRKETemplate,
167
166
  }, { divider: true }];
168
167
 
168
+ // Harvester Cluster 1:1 Harvester Cloud Cred
169
+ if (this.cloudCredential?.canRenew || this.cloudCredential?.canBulkRenew) {
170
+ out.splice(0, 0, { divider: true });
171
+ out.splice(0, 0, {
172
+ action: 'renew',
173
+ enabled: this.cloudCredential?.canRenew,
174
+ bulkable: this.cloudCredential?.canBulkRenew,
175
+ bulkAction: 'renewBulk',
176
+ icon: 'icon icon-fw icon-refresh',
177
+ label: this.$rootGetters['i18n/t']('cluster.cloudCredentials.renew'),
178
+ });
179
+ }
180
+
169
181
  return actions.concat(out);
170
182
  }
171
183
 
@@ -270,19 +282,29 @@ export default class ProvCluster extends SteveModel {
270
282
  }
271
283
 
272
284
  get isImported() {
273
- // As of Rancher v2.6.7, this returns false for imported K3s clusters,
274
- // in which this.provisioner is `k3s`.
285
+ if (this.isLocal) {
286
+ return false;
287
+ }
275
288
 
276
- const isImportedProvisioner = this.provisioner === 'imported';
277
- const isImportedSpecialCases = this.mgmt?.providerForEmberParam === 'import' ||
278
- // when imported cluster is GKE
279
- !!this.mgmt?.spec?.gkeConfig?.imported ||
280
- // or AKS
281
- !!this.mgmt?.spec?.aksConfig?.imported ||
282
- // or EKS
283
- !!this.mgmt?.spec?.eksConfig?.imported;
289
+ // imported rke2 and k3s have status.driver === rke2 and k3s respectively
290
+ // Provisioned rke2 and k3s have status.driver === imported
291
+ if (this.mgmt?.status?.provider === 'k3s' || this.mgmt?.status?.provider === 'rke2') {
292
+ return this.mgmt?.status?.driver === this.mgmt?.status?.provider;
293
+ }
294
+
295
+ // imported KEv2
296
+ // we can't rely on this.provisioner to determine imported-ness for these clusters, as it will return 'aks' 'eks' 'gke' for both provisioned and imported clusters
297
+ const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
298
+
299
+ const isImportedKontainer = kontainerConfigs.filter((key) => {
300
+ return this.mgmt?.spec?.[key]?.imported === true;
301
+ }).length;
302
+
303
+ if (isImportedKontainer) {
304
+ return true;
305
+ }
284
306
 
285
- return !this.isLocal && (isImportedProvisioner || (!this.isRke2 && !this.mgmt?.machineProvider && isImportedSpecialCases));
307
+ return this.provisioner === 'imported';
286
308
  }
287
309
 
288
310
  get isCustom() {
@@ -318,7 +340,8 @@ export default class ProvCluster extends SteveModel {
318
340
  }
319
341
 
320
342
  get isRke1() {
321
- return !!this.mgmt?.spec?.rancherKubernetesEngineConfig || this.labels['provider.cattle.io'] === 'rke';
343
+ // rancherKubernetesEngineConfig is not defined on imported RKE1 clusters
344
+ return !!this.mgmt?.spec?.rancherKubernetesEngineConfig || this.mgmt?.labels['provider.cattle.io'] === 'rke';
322
345
  }
323
346
 
324
347
  get isHarvester() {
@@ -395,6 +418,8 @@ export default class ProvCluster extends SteveModel {
395
418
  provisioner = 'k3s';
396
419
  } else if ( this.isImportedRke2 ) {
397
420
  provisioner = 'rke2';
421
+ } else if ((this.isImported || this.isLocal) && this.isRke1) {
422
+ provisioner = 'rke';
398
423
  }
399
424
 
400
425
  return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provisioner }"`, null, ucFirst(provisioner));
@@ -411,7 +436,7 @@ export default class ProvCluster extends SteveModel {
411
436
  if (!node.metadata?.state?.transitioning) {
412
437
  const architecture = node.status?.nodeLabels?.[NODE_ARCHITECTURE];
413
438
 
414
- const key = architecture ? capitalize(architecture) : this.t('cluster.architecture.label.unknown');
439
+ const key = architecture || this.t('cluster.architecture.label.unknown');
415
440
 
416
441
  obj[key] = (obj[key] || 0) + 1;
417
442
  }
@@ -885,7 +910,8 @@ export default class ProvCluster extends SteveModel {
885
910
  get agentConfig() {
886
911
  // The one we want is the first one with no selector.
887
912
  // If there are multiple with no selector, that will fall under the unsupported message below.
888
- return this.spec.rkeConfig.machineSelectorConfig.find((x) => !x.machineLabelSelector)?.config;
913
+ return this.spec.rkeConfig?.machineSelectorConfig
914
+ ?.find((x) => !x.machineLabelSelector)?.config || { };
889
915
  }
890
916
 
891
917
  get cloudProvider() {
@@ -990,4 +1016,25 @@ export default class ProvCluster extends SteveModel {
990
1016
  get description() {
991
1017
  return super.description || this.mgmt?.description;
992
1018
  }
1019
+
1020
+ renew() {
1021
+ return this.cloudCredential?.renew();
1022
+ }
1023
+
1024
+ renewBulk(clusters = []) {
1025
+ // In theory we don't need to filter by cloudCred, but do so for safety
1026
+ const cloudCredentials = clusters.filter((c) => c.cloudCredential).map((c) => c.cloudCredential);
1027
+
1028
+ return this.cloudCredential?.renewBulk(cloudCredentials);
1029
+ }
1030
+
1031
+ get cloudCredential() {
1032
+ return this.$rootGetters['rancher/all'](NORMAN.CLOUD_CREDENTIAL).find((cc) => cc.id === this.spec.cloudCredentialSecretName);
1033
+ }
1034
+
1035
+ get cloudCredentialWarning() {
1036
+ const expireData = this.cloudCredential?.expireData;
1037
+
1038
+ return expireData?.expired || expireData?.expiring;
1039
+ }
993
1040
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "2.0.2-rc.1",
3
+ "version": "2.0.3",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -302,8 +302,9 @@ export default {
302
302
  */
303
303
  userValues = diff(this.loadedVersionValues, this.chartValues);
304
304
  } else if ( this.existing ) {
305
+ await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe
305
306
  /* For an already installed app, use the values from the previous install. */
306
- userValues = clone(this.existing.spec?.values || {});
307
+ userValues = clone(this.existing.values || {});
307
308
  } else {
308
309
  /* For an new app, start empty. */
309
310
  userValues = {};
@@ -163,7 +163,7 @@ describe('page: cluster dashboard', () => {
163
163
  ['created', 'glance.created', []],
164
164
  ['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { [NODE_ARCHITECTURE]: 'intel' } }]],
165
165
  ['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { } }]],
166
- ['architecture', 'Amd64', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }]],
166
+ ['architecture', 'amd64', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }]],
167
167
  ['architecture', 'unknown', [{ labels: { } }]],
168
168
  ['architecture', '—', [{ metadata: { state: { transitioning: true } } }]],
169
169
  ])('should show %p label %p', (label, text, nodes) => {
@@ -47,7 +47,6 @@ import Certificates from '@shell/components/Certificates';
47
47
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
48
48
  import TabTitle from '@shell/components/TabTitle';
49
49
  import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
50
- import capitalize from 'lodash/capitalize';
51
50
  import paginationUtils from '@shell/utils/pagination-utils';
52
51
 
53
52
  export const RESOURCES = [NAMESPACE, INGRESS, PV, WORKLOAD_TYPES.DEPLOYMENT, WORKLOAD_TYPES.STATEFUL_SET, WORKLOAD_TYPES.JOB, WORKLOAD_TYPES.DAEMON_SET, SERVICE];
@@ -216,7 +215,7 @@ export default {
216
215
  if (!node.metadata?.state?.transitioning) {
217
216
  const architecture = node.labels?.[NODE_ARCHITECTURE];
218
217
 
219
- const key = architecture ? capitalize(architecture) : this.t('cluster.architecture.label.unknown');
218
+ const key = architecture || this.t('cluster.architecture.label.unknown');
220
219
 
221
220
  obj[key] = (obj[key] || 0) + 1;
222
221
  }
@@ -12,6 +12,7 @@ import { WORKSPACE_ANNOTATION } from '@shell/config/labels-annotations';
12
12
  import { filterBy } from '@shell/utils/array';
13
13
  import FleetNoWorkspaces from '@shell/components/fleet/FleetNoWorkspaces.vue';
14
14
  import { NAME } from '@shell/config/product/fleet';
15
+ import { xOfy } from '@shell/utils/string';
15
16
 
16
17
  export default {
17
18
  name: 'FleetDashboard',
@@ -39,6 +40,7 @@ export default {
39
40
  allBundles: {
40
41
  inStoreType: 'management',
41
42
  type: FLEET.BUNDLE,
43
+ opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] },
42
44
  },
43
45
  gitRepos: {
44
46
  inStoreType: 'management',
@@ -175,7 +177,12 @@ export default {
175
177
  }
176
178
 
177
179
  if (area === 'clusters') {
178
- group = row.targetClusters;
180
+ if (row.clusterInfo?.ready === row.clusterInfo?.total && row.clusterInfo?.ready) {
181
+ return {
182
+ badgeClass: STATES[STATES_ENUM.ACTIVE].color,
183
+ icon: STATES[STATES_ENUM.ACTIVE].compoundIcon
184
+ };
185
+ }
179
186
  } else if (area === 'bundles') {
180
187
  group = row.bundles;
181
188
  } else if (area === 'resources') {
@@ -223,7 +230,7 @@ export default {
223
230
  }
224
231
 
225
232
  if (area === 'clusters') {
226
- group = row.targetClusters;
233
+ group = '';
227
234
  } else if (area === 'bundles') {
228
235
  group = row.bundles;
229
236
  } else if (area === 'resources') {
@@ -262,11 +269,11 @@ export default {
262
269
  }
263
270
 
264
271
  if (area === 'clusters') {
265
- value = `${ row.targetClustersReady?.length || '0' }/${ row.targetClusters?.length || '?' }`;
272
+ return `${ row.clusterInfo.ready }/${ row.clusterInfo.total }`;
266
273
  } else if (area === 'bundles') {
267
- value = `${ row.bundlesReady?.length || '0' }/${ row.bundles?.length || '?' }`;
274
+ value = xOfy(row.bundlesReady?.length, row.bundles?.length);
268
275
  } else if (area === 'resources') {
269
- value = `${ row.status?.resourceCounts?.ready || '0' }/${ row.status?.resourceCounts?.desiredReady || '?' }`;
276
+ value = xOfy(row.status?.resourceCounts?.ready, row.status?.resourceCounts?.desiredReady);
270
277
  }
271
278
 
272
279
  return value;