@rancher/shell 3.0.0-rc.8 → 3.0.0

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 (84) hide show
  1. package/assets/translations/en-us.yaml +12 -11
  2. package/assets/translations/zh-hans.yaml +1 -4
  3. package/components/EmberPage.vue +0 -8
  4. package/components/ResourceTable.vue +26 -1
  5. package/components/SortableTable/actions.js +1 -1
  6. package/components/SortableTable/index.vue +17 -1
  7. package/components/SortableTable/selection.js +1 -1
  8. package/components/SortableTable/sorting.js +11 -3
  9. package/components/fleet/FleetClusters.vue +0 -3
  10. package/components/form/ColorInput.vue +13 -2
  11. package/components/form/HookOption.vue +31 -29
  12. package/components/form/LabeledSelect.vue +0 -1
  13. package/components/form/LifecycleHooks.vue +2 -2
  14. package/components/form/__tests__/ColorInput.test.ts +27 -0
  15. package/components/formatter/SecretData.vue +1 -1
  16. package/components/nav/Header.vue +10 -13
  17. package/components/nav/TopLevelMenu.vue +0 -40
  18. package/components/nav/WorkspaceSwitcher.vue +0 -1
  19. package/config/private-label.js +2 -1
  20. package/config/router/routes.js +2 -26
  21. package/config/settings.ts +5 -0
  22. package/config/version.js +2 -0
  23. package/detail/catalog.cattle.io.app.vue +17 -4
  24. package/detail/fleet.cattle.io.cluster.vue +11 -9
  25. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  26. package/edit/cis.cattle.io.clusterscan.vue +4 -3
  27. package/edit/cis.cattle.io.clusterscanbenchmark.vue +2 -0
  28. package/edit/constraints.gatekeeper.sh.constraint/index.vue +2 -0
  29. package/edit/fleet.cattle.io.cluster.vue +4 -0
  30. package/edit/fleet.cattle.io.gitrepo.vue +11 -8
  31. package/edit/helm.cattle.io.projecthelmchart.vue +2 -0
  32. package/edit/k8s.cni.cncf.io.networkattachmentdefinition.vue +2 -0
  33. package/edit/kontainerDriver.vue +2 -0
  34. package/edit/logging-flow/index.vue +2 -0
  35. package/edit/management.cattle.io.project.vue +2 -1
  36. package/edit/management.cattle.io.projectroletemplatebinding.vue +2 -1
  37. package/edit/management.cattle.io.user.vue +3 -0
  38. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +2 -0
  39. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +2 -0
  40. package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +5 -5
  41. package/edit/monitoring.coreos.com.prometheusrule/index.vue +3 -1
  42. package/edit/monitoring.coreos.com.receiver/index.vue +2 -0
  43. package/edit/monitoring.coreos.com.route.vue +2 -0
  44. package/edit/networking.istio.io.destinationrule/index.vue +2 -0
  45. package/edit/nodeDriver.vue +2 -0
  46. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +26 -0
  47. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +63 -149
  48. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +4 -1
  49. package/edit/provisioning.cattle.io.cluster/import.vue +2 -0
  50. package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -3
  51. package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +7 -2
  52. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +108 -35
  53. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +1 -1
  54. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +1 -1
  55. package/edit/ui.cattle.io.navlink.vue +4 -3
  56. package/edit/workload/mixins/workload.js +1 -1
  57. package/mixins/browser-tab-visibility.js +1 -1
  58. package/mixins/chart.js +6 -2
  59. package/mixins/metric-poller.js +1 -1
  60. package/mixins/resource-fetch.js +1 -1
  61. package/models/catalog.cattle.io.app.js +108 -21
  62. package/models/cloudcredential.js +4 -4
  63. package/models/fleet.cattle.io.gitrepo.js +8 -13
  64. package/models/management.cattle.io.cluster.js +13 -2
  65. package/models/management.cattle.io.project.js +4 -0
  66. package/models/provisioning.cattle.io.cluster.js +1 -2
  67. package/models/workload.js +1 -1
  68. package/package.json +11 -4
  69. package/pages/auth/logout.vue +7 -9
  70. package/pages/auth/setup.vue +3 -0
  71. package/pages/c/_cluster/apps/charts/install.vue +11 -3
  72. package/pages/c/_cluster/explorer/__tests__/index.test.ts +7 -4
  73. package/pages/c/_cluster/explorer/index.vue +45 -24
  74. package/pages/c/_cluster/fleet/index.vue +11 -5
  75. package/pages/c/_cluster/uiplugins/index.vue +4 -2
  76. package/pages/diagnostic.vue +1 -0
  77. package/plugins/steve/mutations.js +4 -1
  78. package/plugins/steve/subscribe.js +3 -4
  79. package/types/shell/index.d.ts +13 -0
  80. package/utils/__tests__/object.test.ts +152 -1
  81. package/utils/object.js +37 -0
  82. package/utils/string.js +9 -0
  83. package/utils/validators/formRules/index.ts +1 -1
  84. package/config/product/multi-cluster-apps.js +0 -61
@@ -947,7 +947,7 @@ export default {
947
947
  nameNumber++;
948
948
  }
949
949
  const container = {
950
- ...defaultContainer,
950
+ ...structuredClone(defaultContainer),
951
951
  name: `container-${ nameNumber }`,
952
952
  active: true
953
953
  };
@@ -25,7 +25,7 @@ export default {
25
25
  mounted() {
26
26
  this.setTabVisibilityListener(true);
27
27
  },
28
- beforeDestroy() {
28
+ beforeUnmount() {
29
29
  this.setTabVisibilityListener(false);
30
30
  },
31
31
  };
package/mixins/chart.js CHANGED
@@ -291,6 +291,8 @@ export default {
291
291
  id: `${ this.query.appNamespace }/${ this.query.appName }`,
292
292
  });
293
293
 
294
+ await this.existing?.fetchValues(true);
295
+
294
296
  this.mode = _EDIT;
295
297
  } catch (e) {
296
298
  this.mode = _CREATE;
@@ -450,10 +452,12 @@ export default {
450
452
  }
451
453
  }
452
454
  if (existingCRDApp) {
455
+ await existingCRDApp.fetchValues(true);
456
+
453
457
  // spec.values are any non-default values the user configured
454
458
  // the installation form should show these, as well as any default values from the chart
455
- const existingValues = clone(existingCRDApp.spec?.values || {});
456
- const defaultValues = clone(existingCRDApp.spec?.chart?.values || {});
459
+ const existingValues = clone(existingCRDApp.values || {});
460
+ const defaultValues = clone(existingCRDApp.chartValues || {});
457
461
 
458
462
  crdVersionInfo.existingValues = existingValues;
459
463
  crdVersionInfo.allValues = merge(defaultValues, existingValues);
@@ -13,7 +13,7 @@ export default {
13
13
  this.metricPoller.start();
14
14
  },
15
15
 
16
- beforeDestroy() {
16
+ beforeUnmount() {
17
17
  this.metricPoller.stop();
18
18
  },
19
19
  };
@@ -48,7 +48,7 @@ export default {
48
48
  };
49
49
  },
50
50
 
51
- beforeDestroy() {
51
+ beforeUnmount() {
52
52
  // make sure this only runs once, for the initialized instance
53
53
  if (this.init) {
54
54
  // clear up the store to make sure we aren't storing anything that might interfere with the next rendered list view
@@ -4,7 +4,7 @@ import {
4
4
  import { CATALOG as CATALOG_ANNOTATIONS, FLEET } from '@shell/config/labels-annotations';
5
5
  import { compare, isPrerelease, sortable } from '@shell/utils/version';
6
6
  import { filterBy } from '@shell/utils/array';
7
- import { CATALOG, MANAGEMENT, NORMAN } from '@shell/config/types';
7
+ import { CATALOG, MANAGEMENT, NORMAN, SECRET } from '@shell/config/types';
8
8
  import { SHOW_PRE_RELEASE } from '@shell/store/prefs';
9
9
  import { set } from '@shell/utils/object';
10
10
 
@@ -280,28 +280,115 @@ export default class CatalogApp extends SteveModel {
280
280
  };
281
281
  }
282
282
 
283
- get deployedAsLegacy() {
284
- return async() => {
285
- if (this.spec?.values?.global) {
286
- const { clusterName, projectName } = this.spec.values.global;
287
-
288
- if (clusterName && projectName) {
289
- try {
290
- const legacyApp = await this.$dispatch('rancher/find', {
291
- type: NORMAN.APP,
292
- id: `${ projectName }:${ this.metadata?.name }`,
293
- opt: { url: `/v3/project/${ clusterName }:${ projectName }/apps/${ projectName }:${ this.metadata?.name }` }
294
- }, { root: true });
295
-
296
- if (legacyApp) {
297
- return legacyApp;
298
- }
299
- } catch (e) {}
300
- }
283
+ async deployedAsLegacy() {
284
+ await this.fetchValues();
285
+
286
+ if (this.values?.global) {
287
+ const { clusterName, projectName } = this.values.global;
288
+
289
+ if (clusterName && projectName) {
290
+ try {
291
+ const legacyApp = await this.$dispatch('rancher/find', {
292
+ type: NORMAN.APP,
293
+ id: `${ projectName }:${ this.metadata?.name }`,
294
+ opt: { url: `/v3/project/${ clusterName }:${ projectName }/apps/${ projectName }:${ this.metadata?.name }` }
295
+ }, { root: true });
296
+
297
+ if (legacyApp) {
298
+ return legacyApp;
299
+ }
300
+ } catch (e) {}
301
301
  }
302
+ }
302
303
 
303
- return false;
304
- };
304
+ return false;
305
+ }
306
+
307
+ /**
308
+ * User and Chart values live in a helm secret, so fetch it (with special param)
309
+ */
310
+ async fetchValues(force = false) {
311
+ if (!this.secretId) {
312
+ // If there's no secret id this isn't ever going to work, no need to carry on
313
+ return;
314
+ }
315
+
316
+ const haveValues = !!this._values && !!this._chartValues;
317
+
318
+ if (haveValues && !force) {
319
+ // If we already have the required values and we're not forced to re-fetch, no need to carry on
320
+ return;
321
+ }
322
+
323
+ try {
324
+ await this.$dispatch('find', {
325
+ type: SECRET,
326
+ id: this.secretId,
327
+ opt: {
328
+ force: force || (!!this._secret && !haveValues), // force if explicitly requested or there's ean existing secret without the required values we have a secret without the values in (Secret has been fetched another way)
329
+ watch: false, // Cannot watch with custom params (they are dropped on calls made when resyncing over socket)
330
+ params: { includeHelmData: true }
331
+ }
332
+ });
333
+ } catch (e) {
334
+ console.error(`Cannot find values for ${ this.id } (unable to fetch)`, e); // eslint-disable-line no-console
335
+ }
336
+ }
337
+
338
+ get secretId() {
339
+ const metadata = this.metadata;
340
+ const secretReference = metadata.ownerReferences?.find((ow) => ow.kind.toLowerCase() === SECRET);
341
+
342
+ const secretId = secretReference?.name;
343
+ const secretNamespace = metadata.namespace;
344
+
345
+ if (!secretNamespace || !secretId) {
346
+ console.warn(`Cannot find values for ${ this.id } (cannot find related secret namespace or id)`); // eslint-disable-line no-console
347
+
348
+ return null;
349
+ }
350
+
351
+ return `${ secretNamespace }/${ secretId }`;
352
+ }
353
+
354
+ get _secret() {
355
+ return this.secretId ? this.$getters['byId'](SECRET, this.secretId) : null;
356
+ }
357
+
358
+ _validateSecret(noun) {
359
+ if (this._secret === undefined) {
360
+ throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret has not been fetched via app \`fetchValues\`)`);
361
+ }
362
+
363
+ if (this._secret === null) {
364
+ throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret cannot or has failed to fetch) `);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * The user's helm values
370
+ */
371
+ get values() {
372
+ this._validateSecret('values');
373
+
374
+ return this._values;
375
+ }
376
+
377
+ get _values() {
378
+ return this._secret?.data?.release?.config;
379
+ }
380
+
381
+ /**
382
+ * The Charts default helm values
383
+ */
384
+ get chartValues() {
385
+ this._validateSecret('chartValues');
386
+
387
+ return this._chartValues;
388
+ }
389
+
390
+ get _chartValues() {
391
+ return this._secret?.data?.release?.chart?.values;
305
392
  }
306
393
  }
307
394
 
@@ -241,9 +241,9 @@ export default class CloudCredential extends NormanModel {
241
241
  }
242
242
 
243
243
  get expiresForSort() {
244
- // Why not just `expires`? Ensures the correct sort order of 'no expiration' --> 'expired' --> 'expiring'
245
- // (instead of expired --> expiring --> no expiration)
246
- return this.expires || Number.MAX_SAFE_INTEGER;
244
+ // Why not just `expires`? Ensures the correct sort order of expired --> expiring --> never expires
245
+ // (instead of 'never expired' --> 'expired' --> 'expiring')
246
+ return this.expires !== undefined ? this.expires : Number.MAX_SAFE_INTEGER;
247
247
  }
248
248
 
249
249
  get expires() {
@@ -296,7 +296,7 @@ export default class CloudCredential extends NormanModel {
296
296
  }
297
297
 
298
298
  get expiresIn() {
299
- if (!this.expires) {
299
+ if (this.expires === undefined) {
300
300
  return null;
301
301
  }
302
302
 
@@ -176,6 +176,10 @@ export default class GitRepo extends SteveModel {
176
176
  get repoDisplay() {
177
177
  let repo = this.spec.repo;
178
178
 
179
+ if (!repo) {
180
+ return null;
181
+ }
182
+
179
183
  repo = repo.replace(/.git$/, '');
180
184
  repo = repo.replace(/^https:\/\//, '');
181
185
  repo = repo.replace(/\/+$/, '');
@@ -307,20 +311,11 @@ export default class GitRepo extends SteveModel {
307
311
  bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
308
312
  }
309
313
 
314
+ /**
315
+ * Bundles with state of active
316
+ */
310
317
  get bundlesReady() {
311
- if (this.bundles && this.bundles.length) {
312
- return this.bundles.filter((bundle) => bundle.state === 'active');
313
- }
314
-
315
- return 0;
316
- }
317
-
318
- get targetClustersReady() {
319
- if (this.targetClusters && this.targetClusters.length) {
320
- return this.targetClusters.filter((cluster) => cluster.state === 'active');
321
- }
322
-
323
- return 0;
318
+ return this.bundles?.filter((bundle) => bundle.state === 'active');
324
319
  }
325
320
 
326
321
  get bundleDeployments() {
@@ -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 = {};
@@ -306,13 +308,22 @@ export default class MgmtCluster extends SteveModel {
306
308
  return undefined;
307
309
  }
308
310
 
309
- const color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || '#7f7f7f';
311
+ let color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || DEFAULT_BADGE_COLOR;
310
312
  const iconText = this.metadata?.annotations[CLUSTER_BADGE.ICON_TEXT] || '';
313
+ let foregroundColor;
314
+
315
+ try {
316
+ foregroundColor = textColor(parseColor(color.trim())); // Remove any whitespace
317
+ } catch (_e) {
318
+ // If we could not parse the badge color, use the defaults
319
+ color = DEFAULT_BADGE_COLOR;
320
+ foregroundColor = textColor(parseColor(color));
321
+ }
311
322
 
312
323
  return {
313
324
  text: comment || undefined,
314
325
  color,
315
- textColor: textColor(parseColor(color)),
326
+ textColor: foregroundColor,
316
327
  iconText: iconText.substr(0, 3)
317
328
  };
318
329
  }
@@ -62,6 +62,10 @@ export default class Project extends HybridModel {
62
62
  });
63
63
  }
64
64
 
65
+ get description() {
66
+ return this.spec?.description;
67
+ }
68
+
65
69
  get doneOverride() {
66
70
  return this.listLocation;
67
71
  }
@@ -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.
@@ -424,7 +423,7 @@ export default class ProvCluster extends SteveModel {
424
423
  if (!node.metadata?.state?.transitioning) {
425
424
  const architecture = node.status?.nodeLabels?.[NODE_ARCHITECTURE];
426
425
 
427
- const key = architecture ? capitalize(architecture) : this.t('cluster.architecture.label.unknown');
426
+ const key = architecture || this.t('cluster.architecture.label.unknown');
428
427
 
429
428
  obj[key] = (obj[key] || 0) + 1;
430
429
  }
@@ -110,7 +110,7 @@ export default class Workload extends WorkloadService {
110
110
  spec.template = {
111
111
  spec: {
112
112
  restartPolicy: this.type === WORKLOAD_TYPES.JOB ? 'Never' : 'Always',
113
- containers: [{ ...defaultContainer }],
113
+ containers: [{ ...structuredClone(defaultContainer) }],
114
114
  initContainers: []
115
115
  }
116
116
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.0-rc.8",
3
+ "version": "3.0.0",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -41,6 +41,7 @@
41
41
  "@rancher/icons": "2.0.29",
42
42
  "@types/is-url": "1.2.30",
43
43
  "@types/node": "16.4.3",
44
+ "@types/semver": "^7.5.8",
44
45
  "@typescript-eslint/eslint-plugin": "~5.4.0",
45
46
  "@typescript-eslint/parser": "~5.4.0",
46
47
  "@vue/cli-plugin-babel": "~5.0.0",
@@ -53,7 +54,9 @@
53
54
  "babel-eslint": "10.1.0",
54
55
  "babel-plugin-module-resolver": "4.0.0",
55
56
  "babel-preset-vue": "2.0.2",
57
+ "cache-loader": "4.1.0",
56
58
  "clipboard-polyfill": "4.0.1",
59
+ "color": "4.2.3",
57
60
  "codemirror": ">=5.64.0 <6",
58
61
  "codemirror-editor-vue3": "2.7.1",
59
62
  "cookie": "0.5.0",
@@ -71,7 +74,7 @@
71
74
  "dagre-d3": "0.6.4",
72
75
  "dayjs": "1.8.29",
73
76
  "diff2html": "3.4.24",
74
- "dompurify": "2.4.5",
77
+ "dompurify": "2.5.4",
75
78
  "element-matches": "^0.1.2",
76
79
  "eslint": "7.32.0",
77
80
  "eslint-config-standard": "16.0.3",
@@ -89,6 +92,7 @@
89
92
  "frontmatter-markdown-loader": "3.7.0",
90
93
  "identicon.js": "2.3.3",
91
94
  "intl-messageformat": "7.8.4",
95
+ "ip": "2.0.1",
92
96
  "is-url": "1.2.4",
93
97
  "jest": "27.5.1",
94
98
  "jest-serializer-vue": "2.0.2",
@@ -98,13 +102,14 @@
98
102
  "js-yaml": "4.1.0",
99
103
  "js-yaml-loader": "1.2.2",
100
104
  "jsdiff": "1.1.1",
101
- "jsonpath-plus": "6.0.1",
105
+ "jsonpath-plus": "10.0.0",
102
106
  "jsrsasign": "10.5.25",
103
107
  "jszip": "3.8.0",
104
108
  "lodash": "4.17.21",
105
109
  "marked": "4.0.17",
106
110
  "nodemon": "2.0.22",
107
111
  "nyc": "15.1.0",
112
+ "node-polyfill-webpack-plugin": "3.0.0",
108
113
  "papaparse": "5.3.0",
109
114
  "portal-vue": "~3.0.0",
110
115
  "sass": "1.51.0",
@@ -153,7 +158,9 @@
153
158
  "qs": "6.11.1",
154
159
  "roarr": "7.0.4",
155
160
  "semver": "7.5.4",
156
- "@vue/cli-service/html-webpack-plugin": "^5.0.0"
161
+ "@types/lodash": "4.17.5",
162
+ "@types/node": "~20.10.0",
163
+ "@vue/cli-service/html-webpack-plugin": "^5.0.0"
157
164
  },
158
165
  "nyc": {
159
166
  "extension": [
@@ -1,21 +1,19 @@
1
1
  <script>
2
- import { authProvidersInfo } from '@shell/utils/auth';
2
+ import { configType } from '@shell/models/management.cattle.io.authconfig';
3
3
 
4
4
  export default {
5
5
  async fetch() {
6
- const authInfo = await authProvidersInfo(this.$store);
6
+ const publicAuthProviders = await this.$store.dispatch('auth/getAuthProviders');
7
7
 
8
- if (authInfo.enabled?.length) {
9
- const authProvider = authInfo.enabled[0];
8
+ const samlAuthProvider = publicAuthProviders.find((authProvider) => configType[authProvider.id] === 'saml');
10
9
 
11
- const {
12
- logoutAllSupported, logoutAllEnabled, logoutAllForced, configType
13
- } = authProvider;
10
+ if (!!samlAuthProvider) {
11
+ const { logoutAllSupported, logoutAllEnabled, logoutAllForced } = samlAuthProvider;
14
12
 
15
- if (configType === 'saml' && logoutAllSupported && logoutAllEnabled && logoutAllForced) {
13
+ if (logoutAllSupported && logoutAllEnabled && logoutAllForced) {
16
14
  // SAML - force SLO (logout from all apps)
17
15
  await this.$store.dispatch('auth/logout', {
18
- force: true, slo: true, provider: authProvider
16
+ force: true, slo: true, provider: samlAuthProvider
19
17
  }, { root: true });
20
18
  } else {
21
19
  // simple logout
@@ -496,6 +496,7 @@ export default {
496
496
 
497
497
  .span-6 {
498
498
  padding: 0 60px;
499
+ margin: 0;
499
500
  }
500
501
 
501
502
  .landscape {
@@ -503,6 +504,7 @@ export default {
503
504
  margin: 0;
504
505
  object-fit: cover;
505
506
  padding: 0;
507
+ width: 49%;
506
508
  }
507
509
  }
508
510
 
@@ -512,6 +514,7 @@ export default {
512
514
  overflow-y: auto;
513
515
  position: relative;
514
516
  height: 100vh;
517
+ width: 51%;
515
518
 
516
519
  & > div:first-of-type {
517
520
  flex:3;
@@ -294,8 +294,9 @@ export default {
294
294
  */
295
295
  userValues = diff(this.loadedVersionValues, this.chartValues);
296
296
  } else if ( this.existing ) {
297
+ await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe
297
298
  /* For an already installed app, use the values from the previous install. */
298
- userValues = clone(this.existing.spec?.values || {});
299
+ userValues = clone(this.existing.values || {});
299
300
  } else {
300
301
  /* For an new app, start empty. */
301
302
  userValues = {};
@@ -1457,6 +1458,7 @@ export default {
1457
1458
  <Banner
1458
1459
  v-if="isNamespaceNew && value.metadata.namespace.length"
1459
1460
  color="info"
1461
+ class="namespace-create-banner"
1460
1462
  >
1461
1463
  <div v-clean-html="t('catalog.install.steps.basics.createNamespace', {namespace: value.metadata.namespace}, true) " />
1462
1464
  </Banner>
@@ -1661,9 +1663,10 @@ export default {
1661
1663
  class="mt-10"
1662
1664
  >
1663
1665
  <UnitInput
1664
- v-model.number="customCmdOpts.timeout"
1666
+ v-model:value="customCmdOpts.timeout"
1665
1667
  :label="t('catalog.install.helm.timeout.label')"
1666
1668
  :suffix="t('catalog.install.helm.timeout.unit', {value: customCmdOpts.timeout})"
1669
+ type="number"
1667
1670
  />
1668
1671
  </div>
1669
1672
  <div
@@ -1672,9 +1675,10 @@ export default {
1672
1675
  >
1673
1676
  <UnitInput
1674
1677
  v-if="existing"
1675
- v-model.number="customCmdOpts.historyMax"
1678
+ v-model:value="customCmdOpts.historyMax"
1676
1679
  :label="t('catalog.install.helm.historyMax.label')"
1677
1680
  :suffix="t('catalog.install.helm.historyMax.unit', {value: customCmdOpts.historyMax})"
1681
+ type="number"
1678
1682
  />
1679
1683
  </div>
1680
1684
  <div
@@ -1852,6 +1856,10 @@ export default {
1852
1856
  .spacer {
1853
1857
  line-height: 2;
1854
1858
  }
1859
+
1860
+ .namespace-create-banner {
1861
+ margin-bottom: 70px;
1862
+ }
1855
1863
  }
1856
1864
  &__values {
1857
1865
  &__controls {
@@ -25,9 +25,12 @@ describe('page: cluster dashboard', () => {
25
25
  'cluster/inError': () => false,
26
26
  'cluster/schemaFor': jest.fn(),
27
27
  'cluster/canList': jest.fn(),
28
- 'cluster/all': jest.fn(),
29
- 'i18n/exists': jest.fn(),
30
- 'i18n/t': (label: string) => label === 'generic.provisioning' ? '—' : jest.fn()(),
28
+ 'cluster/byId': () => {
29
+ return {};
30
+ },
31
+ 'cluster/all': jest.fn(),
32
+ 'i18n/exists': jest.fn(),
33
+ 'i18n/t': (label: string) => label === 'generic.provisioning' ? '—' : jest.fn()(),
31
34
  }
32
35
  }
33
36
  },
@@ -161,7 +164,7 @@ describe('page: cluster dashboard', () => {
161
164
  ['created', 'glance.created', []],
162
165
  ['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { [NODE_ARCHITECTURE]: 'intel' } }]],
163
166
  ['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { } }]],
164
- ['architecture', 'Amd64', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }]],
167
+ ['architecture', 'amd64', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }]],
165
168
  ['architecture', 'unknown', [{ labels: { } }]],
166
169
  ['architecture', 'glance.architecture', [{ metadata: { state: { transitioning: true } } }]],
167
170
  ])('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
 
52
51
  export const RESOURCES = [NAMESPACE, INGRESS, PV, WORKLOAD_TYPES.DEPLOYMENT, WORKLOAD_TYPES.STATEFUL_SET, WORKLOAD_TYPES.JOB, WORKLOAD_TYPES.DAEMON_SET, SERVICE];
53
52
 
@@ -115,12 +114,6 @@ export default {
115
114
  if (this.currentCluster.isLocal && this.$store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
116
115
  this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
117
116
  }
118
-
119
- this.canViewAgents = this.$store.getters['cluster/canList'](WORKLOAD_TYPES.DEPLOYMENT) && this.$store.getters['cluster/canList'](WORKLOAD_TYPES.STATEFUL_SET);
120
-
121
- if (this.canViewAgents) {
122
- this.loadAgents();
123
- }
124
117
  }
125
118
  },
126
119
 
@@ -138,7 +131,6 @@ export default {
138
131
  cattleDeployment: 'loading',
139
132
  fleetDeployment: 'loading',
140
133
  fleetStatefulSet: 'loading',
141
- canViewAgents: false,
142
134
  disconnected: false,
143
135
  events: [],
144
136
  nodeMetrics: [],
@@ -172,6 +164,17 @@ export default {
172
164
  clearInterval(this.interval);
173
165
  },
174
166
 
167
+ watch: {
168
+ canViewAgents: {
169
+ handler(neu, old) {
170
+ if (neu && !old) {
171
+ this.loadAgents();
172
+ }
173
+ },
174
+ immediate: true
175
+ }
176
+ },
177
+
175
178
  computed: {
176
179
  ...mapGetters(['currentCluster']),
177
180
  ...monitoringStatus(),
@@ -184,6 +187,22 @@ export default {
184
187
  return this.$store.getters['management/all'](MANAGEMENT.CLUSTER);
185
188
  },
186
189
 
190
+ fleetAgentNamespace() {
191
+ return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-fleet-system');
192
+ },
193
+
194
+ cattleAgentNamespace() {
195
+ if (this.currentCluster.isLocal) {
196
+ return;
197
+ }
198
+
199
+ return this.$store.getters['cluster/byId'](NAMESPACE, 'cattle-system');
200
+ },
201
+
202
+ canViewAgents() {
203
+ return !!this.fleetAgentNamespace || (!this.currentCluster.isLocal && this.cattleAgentNamespace);
204
+ },
205
+
187
206
  showClusterTools() {
188
207
  return this.$store.getters['cluster/canList'](CATALOG.CLUSTER_REPO) &&
189
208
  this.$store.getters['cluster/canList'](CATALOG.APP);
@@ -212,7 +231,7 @@ export default {
212
231
  if (!node.metadata?.state?.transitioning) {
213
232
  const architecture = node.labels?.[NODE_ARCHITECTURE];
214
233
 
215
- const key = architecture ? capitalize(architecture) : this.t('cluster.architecture.label.unknown');
234
+ const key = architecture || this.t('cluster.architecture.label.unknown');
216
235
 
217
236
  obj[key] = (obj[key] || 0) + 1;
218
237
  }
@@ -268,15 +287,15 @@ export default {
268
287
  });
269
288
  });
270
289
 
271
- if (this.canViewAgents) {
272
- if (!this.currentCluster.isLocal) {
273
- services.push({
274
- name: 'cattle',
275
- status: this.cattleStatus,
276
- labelKey: 'clusterIndexPage.sections.componentStatus.cattle',
277
- });
278
- }
290
+ if (this.cattleAgentNamespace) {
291
+ services.push({
292
+ name: 'cattle',
293
+ status: this.cattleStatus,
294
+ labelKey: 'clusterIndexPage.sections.componentStatus.cattle',
295
+ });
296
+ }
279
297
 
298
+ if (this.fleetAgentNamespace) {
280
299
  services.push({
281
300
  name: 'fleet',
282
301
  status: this.fleetStatus,
@@ -485,14 +504,16 @@ export default {
485
504
 
486
505
  methods: {
487
506
  loadAgents() {
488
- if (this.currentCluster.isLocal) {
489
- this.setAgentResource('fleetDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-fleet-system/fleet-controller');
490
- this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-local-system/fleet-agent');
491
- } else {
492
- this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-system/fleet-agent');
507
+ if (this.fleetAgentNamespace) {
508
+ if (this.currentCluster.isLocal) {
509
+ this.setAgentResource('fleetDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-fleet-system/fleet-controller');
510
+ this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-local-system/fleet-agent');
511
+ } else {
512
+ this.setAgentResource('fleetStatefulSet', WORKLOAD_TYPES.STATEFUL_SET, 'cattle-fleet-system/fleet-agent');
513
+ }
514
+ }
515
+ if (this.cattleAgentNamespace) {
493
516
  this.setAgentResource('cattleDeployment', WORKLOAD_TYPES.DEPLOYMENT, 'cattle-system/cattle-cluster-agent');
494
-
495
- // Scaling Up/Down cattle deployment causes web sockets disconnection;
496
517
  this.interval = setInterval(() => {
497
518
  this.disconnected = !!this.$store.getters['cluster/inError']({ type: NODE });
498
519
  }, 1000);