@rancher/shell 3.0.0-rc.9 → 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 (64) 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/HookOption.vue +31 -29
  11. package/components/form/LabeledSelect.vue +0 -1
  12. package/components/form/LifecycleHooks.vue +2 -2
  13. package/components/formatter/SecretData.vue +1 -1
  14. package/components/nav/Header.vue +10 -13
  15. package/components/nav/TopLevelMenu.vue +0 -40
  16. package/components/nav/WorkspaceSwitcher.vue +0 -1
  17. package/config/private-label.js +2 -1
  18. package/config/router/routes.js +2 -26
  19. package/config/settings.ts +5 -0
  20. package/config/version.js +2 -0
  21. package/detail/catalog.cattle.io.app.vue +17 -4
  22. package/detail/fleet.cattle.io.cluster.vue +11 -9
  23. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  24. package/edit/cis.cattle.io.clusterscan.vue +4 -3
  25. package/edit/fleet.cattle.io.gitrepo.vue +11 -8
  26. package/edit/management.cattle.io.project.vue +2 -1
  27. package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +5 -5
  28. package/edit/monitoring.coreos.com.prometheusrule/index.vue +1 -1
  29. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +26 -0
  30. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +63 -149
  31. package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -1
  32. package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +7 -2
  33. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +108 -35
  34. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +1 -1
  35. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +1 -1
  36. package/edit/workload/mixins/workload.js +1 -1
  37. package/mixins/browser-tab-visibility.js +1 -1
  38. package/mixins/chart.js +6 -2
  39. package/mixins/metric-poller.js +1 -1
  40. package/mixins/resource-fetch.js +1 -1
  41. package/models/catalog.cattle.io.app.js +108 -21
  42. package/models/cloudcredential.js +4 -4
  43. package/models/fleet.cattle.io.gitrepo.js +8 -13
  44. package/models/management.cattle.io.cluster.js +13 -2
  45. package/models/management.cattle.io.project.js +4 -0
  46. package/models/provisioning.cattle.io.cluster.js +1 -2
  47. package/models/workload.js +1 -1
  48. package/package.json +3 -3
  49. package/pages/auth/logout.vue +7 -9
  50. package/pages/auth/setup.vue +3 -0
  51. package/pages/c/_cluster/apps/charts/install.vue +11 -3
  52. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  53. package/pages/c/_cluster/explorer/index.vue +1 -2
  54. package/pages/c/_cluster/fleet/index.vue +11 -5
  55. package/pages/c/_cluster/uiplugins/index.vue +4 -2
  56. package/pages/diagnostic.vue +1 -0
  57. package/plugins/steve/mutations.js +4 -1
  58. package/plugins/steve/subscribe.js +3 -4
  59. package/types/shell/index.d.ts +13 -0
  60. package/utils/__tests__/object.test.ts +152 -1
  61. package/utils/object.js +37 -0
  62. package/utils/string.js +9 -0
  63. package/utils/validators/formRules/index.ts +1 -1
  64. package/config/product/multi-cluster-apps.js +0 -61
@@ -1,14 +1,29 @@
1
1
 
2
2
  <script>
3
- import { Checkbox } from '@components/Form/Checkbox';
4
3
  import { LabeledInput } from '@components/Form/LabeledInput';
5
4
  import { _CREATE } from '@shell/config/query-params';
5
+ import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
6
+
7
+ export const DATA_DIR_RADIO_OPTIONS = {
8
+ DEFAULT: 'defaultDataDir',
9
+ COMMON: 'commonBaseDataDir',
10
+ CUSTOM: 'customDataDir',
11
+ };
12
+
13
+ export const DEFAULT_COMMON_BASE_PATH = '/var/lib/rancher';
14
+
15
+ export const DEFAULT_SUBDIRS = {
16
+ AGENT: 'agent',
17
+ PROVISIONING: 'provisioning',
18
+ K8S_DISTRO_RKE2: 'rke2',
19
+ K8S_DISTRO_K3S: 'k3s',
20
+ };
6
21
 
7
22
  export default {
8
23
  name: 'DirectoryConfig',
9
24
  components: {
10
- Checkbox,
11
25
  LabeledInput,
26
+ RadioGroup
12
27
  },
13
28
  props: {
14
29
  mode: {
@@ -16,52 +31,114 @@ export default {
16
31
  required: true,
17
32
  },
18
33
 
34
+ k8sVersion: {
35
+ type: String,
36
+ required: true,
37
+ },
38
+
19
39
  value: {
20
40
  type: Object,
21
41
  required: true,
22
42
  },
23
43
  },
24
44
  data() {
25
- let atLeastOneDirWithAnIdentifier = false;
26
- let allDirsWithSameIdentifier = false;
27
-
28
- if (this.value?.systemAgent?.length || this.value?.provisioning?.length || this.value?.k8sDistro?.length) {
29
- atLeastOneDirWithAnIdentifier = true;
30
- if (this.value?.systemAgent === this.value?.provisioning && this.value?.provisioning === this.value?.k8sDistro &&
31
- this.value?.systemAgent === this.value?.k8sDistro) {
32
- allDirsWithSameIdentifier = true;
45
+ let dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
46
+ let k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
47
+
48
+ if (this.k8sVersion && this.k8sVersion.includes('k3s')) {
49
+ k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_K3S;
50
+ }
51
+
52
+ if (this.mode !== _CREATE) {
53
+ if (this.value?.systemAgent?.length || this.value?.provisioning?.length || this.value?.k8sDistro?.length) {
54
+ dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
33
55
  }
34
56
  }
35
57
 
36
58
  return {
37
- isSettingCommonConfig: !(atLeastOneDirWithAnIdentifier && !allDirsWithSameIdentifier),
38
- commonConfig: allDirsWithSameIdentifier ? this.value?.systemAgent : '',
59
+ DATA_DIR_RADIO_OPTIONS,
60
+ dataConfigRadioValue,
61
+ k8sDistroSubDir,
62
+ commonConfig: '',
39
63
  };
40
64
  },
41
65
  watch: {
42
66
  commonConfig(neu) {
43
- if (neu && neu.length && this.isSettingCommonConfig) {
44
- this.value.systemAgent = neu;
45
- this.value.provisioning = neu;
46
- this.value.k8sDistro = neu;
67
+ if (neu && neu.length && this.dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON) {
68
+ this.value.systemAgent = `${ neu }/${ DEFAULT_SUBDIRS.AGENT }`;
69
+ this.value.provisioning = `${ neu }/${ DEFAULT_SUBDIRS.PROVISIONING }`;
70
+ this.value.k8sDistro = `${ neu }/${ this.k8sDistroSubDir }`;
71
+ }
72
+ },
73
+ k8sVersion: {
74
+ handler(neu) {
75
+ if (neu && neu.includes('k3s')) {
76
+ this.k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_K3S;
77
+ } else {
78
+ this.k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
79
+ }
80
+
81
+ if (this.value.k8sDistro) {
82
+ this.value.k8sDistro = `${ neu }/${ this.k8sDistroSubDir }`;
83
+ }
47
84
  }
48
85
  }
49
86
  },
50
87
  computed: {
51
- disableEditInput() {
52
- return this.mode !== _CREATE;
88
+ dataConfigRadioOptions() {
89
+ const defaultDataDirOption = {
90
+ value: DATA_DIR_RADIO_OPTIONS.DEFAULT,
91
+ label: this.t('cluster.directoryConfig.radioInput.defaultLabel')
92
+ };
93
+ const customDataDirOption = {
94
+ value: DATA_DIR_RADIO_OPTIONS.CUSTOM,
95
+ label: this.t('cluster.directoryConfig.radioInput.customLabel')
96
+ };
97
+
98
+ if (this.mode === _CREATE) {
99
+ return [
100
+ defaultDataDirOption,
101
+ { value: DATA_DIR_RADIO_OPTIONS.COMMON, label: this.t('cluster.directoryConfig.radioInput.commonLabel') },
102
+ customDataDirOption
103
+ ];
104
+ } else {
105
+ return [
106
+ defaultDataDirOption,
107
+ customDataDirOption
108
+ ];
109
+ }
53
110
  }
54
111
  },
55
112
  methods: {
56
- handleCommonConfig(val) {
57
- this.isSettingCommonConfig = val;
113
+ handleRadioInput(val) {
114
+ switch (val) {
115
+ case DATA_DIR_RADIO_OPTIONS.DEFAULT:
116
+ if (this.mode === _CREATE) {
117
+ this.commonConfig = '';
118
+ }
119
+ this.value.systemAgent = '';
120
+ this.value.provisioning = '';
121
+ this.value.k8sDistro = '';
122
+
123
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
124
+ break;
125
+ case DATA_DIR_RADIO_OPTIONS.COMMON:
126
+ this.commonConfig = DEFAULT_COMMON_BASE_PATH;
127
+
128
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.COMMON;
129
+ break;
130
+ // default is custom config
131
+ default:
132
+ if (this.mode === _CREATE) {
133
+ this.commonConfig = '';
134
+ }
58
135
 
59
- if (val) {
60
136
  this.value.systemAgent = '';
61
137
  this.value.provisioning = '';
62
138
  this.value.k8sDistro = '';
63
- } else {
64
- this.commonConfig = '';
139
+
140
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
141
+ break;
65
142
  }
66
143
  }
67
144
  },
@@ -74,33 +151,31 @@ export default {
74
151
  <h3 class="mb-20">
75
152
  {{ t('cluster.directoryConfig.title') }}
76
153
  </h3>
77
- <Checkbox
154
+ <RadioGroup
155
+ :value="dataConfigRadioValue"
78
156
  class="mb-10"
79
- :value="isSettingCommonConfig"
80
157
  :mode="mode"
81
- :label="t('cluster.directoryConfig.checkboxText')"
82
- :disabled="disableEditInput"
83
- data-testid="rke2-directory-config-individual-config-checkbox"
84
- @update:value="handleCommonConfig"
158
+ :options="dataConfigRadioOptions"
159
+ name="directory-config-radio"
160
+ data-testid="rke2-directory-config-radio-input"
161
+ @update:value="handleRadioInput"
85
162
  />
86
163
  <LabeledInput
87
- v-if="isSettingCommonConfig"
164
+ v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON"
88
165
  v-model:value="commonConfig"
89
166
  class="mb-20"
90
167
  :mode="mode"
91
168
  :label="t('cluster.directoryConfig.common.label')"
92
169
  :tooltip="t('cluster.directoryConfig.common.tooltip')"
93
- :disabled="disableEditInput"
94
170
  data-testid="rke2-directory-config-common-data-dir"
95
171
  />
96
- <div v-if="!isSettingCommonConfig">
172
+ <div v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.CUSTOM">
97
173
  <LabeledInput
98
174
  v-model:value="value.systemAgent"
99
175
  class="mb-20"
100
176
  :mode="mode"
101
177
  :label="t('cluster.directoryConfig.systemAgent.label')"
102
178
  :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
103
- :disabled="disableEditInput"
104
179
  data-testid="rke2-directory-config-systemAgent-data-dir"
105
180
  />
106
181
  <LabeledInput
@@ -109,7 +184,6 @@ export default {
109
184
  :mode="mode"
110
185
  :label="t('cluster.directoryConfig.provisioning.label')"
111
186
  :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
112
- :disabled="disableEditInput"
113
187
  data-testid="rke2-directory-config-provisioning-data-dir"
114
188
  />
115
189
  <LabeledInput
@@ -118,7 +192,6 @@ export default {
118
192
  :mode="mode"
119
193
  :label="t('cluster.directoryConfig.k8sDistro.label')"
120
194
  :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
121
- :disabled="disableEditInput"
122
195
  data-testid="rke2-directory-config-k8sDistro-data-dir"
123
196
  />
124
197
  </div>
@@ -317,7 +317,7 @@ export default {
317
317
  />
318
318
  </h3>
319
319
  <UnitInput
320
- v-model.number="unhealthyNodeTimeoutInteger"
320
+ v-model:value="unhealthyNodeTimeoutInteger"
321
321
  :hide-arrows="true"
322
322
  :placeholder="t('containerResourceLimit.cpuPlaceholder')"
323
323
  :mode="mode"
@@ -154,7 +154,6 @@ export default {
154
154
  :mode="mode"
155
155
  :data-testid="`registry-auth-host-input-${i}`"
156
156
  />
157
-
158
157
  <SelectOrCreateAuthSecret
159
158
  v-model:value="row.value.authConfigSecretName"
160
159
  :register-before-hook="wrapRegisterBeforeHook"
@@ -168,6 +167,7 @@ export default {
168
167
  generate-name="registryconfig-auth-"
169
168
  :data-testid="`registry-auth-select-or-create-${i}`"
170
169
  :cache-secrets="true"
170
+ @update:value="update"
171
171
  />
172
172
  </div>
173
173
  <div class="col span-6">
@@ -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.9",
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",
@@ -74,7 +74,7 @@
74
74
  "dagre-d3": "0.6.4",
75
75
  "dayjs": "1.8.29",
76
76
  "diff2html": "3.4.24",
77
- "dompurify": "2.4.5",
77
+ "dompurify": "2.5.4",
78
78
  "element-matches": "^0.1.2",
79
79
  "eslint": "7.32.0",
80
80
  "eslint-config-standard": "16.0.3",
@@ -102,7 +102,7 @@
102
102
  "js-yaml": "4.1.0",
103
103
  "js-yaml-loader": "1.2.2",
104
104
  "jsdiff": "1.1.1",
105
- "jsonpath-plus": "6.0.1",
105
+ "jsonpath-plus": "10.0.0",
106
106
  "jsrsasign": "10.5.25",
107
107
  "jszip": "3.8.0",
108
108
  "lodash": "4.17.21",
@@ -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;