@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,63 +1,214 @@
1
1
 
2
2
  <script>
3
3
  import { LabeledInput } from '@components/Form/LabeledInput';
4
- import { _CREATE } from '@shell/config/query-params';
4
+ import { _CREATE, _EDIT } from '@shell/config/query-params';
5
+ import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
6
+ import { Banner } from '@components/Banner';
7
+
8
+ export const DATA_DIR_RADIO_OPTIONS = {
9
+ DEFAULT: 'defaultDataDir',
10
+ COMMON: 'commonBaseDataDir',
11
+ CUSTOM: 'customDataDir',
12
+ };
13
+
14
+ export const DEFAULT_COMMON_BASE_PATH = '/var/lib/rancher';
15
+
16
+ export const DEFAULT_SUBDIRS = {
17
+ AGENT: 'agent',
18
+ PROVISIONING: 'provisioning',
19
+ K8S_DISTRO_RKE2: 'rke2',
20
+ K8S_DISTRO_K3S: 'k3s',
21
+ };
5
22
 
6
23
  export default {
7
24
  name: 'DirectoryConfig',
8
- components: { LabeledInput },
9
- props: {
25
+ components: {
26
+ LabeledInput,
27
+ RadioGroup,
28
+ Banner
29
+ },
30
+ props: {
10
31
  mode: {
11
32
  type: String,
12
33
  required: true,
13
34
  },
14
35
 
36
+ k8sVersion: {
37
+ type: String,
38
+ required: true,
39
+ },
40
+
15
41
  value: {
16
42
  type: Object,
17
43
  required: true,
18
44
  },
19
45
  },
46
+ data() {
47
+ let dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
48
+ let k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
49
+
50
+ if (this.k8sVersion && this.k8sVersion.includes('k3s')) {
51
+ k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_K3S;
52
+ }
53
+
54
+ if (this.mode !== _CREATE) {
55
+ dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
56
+ }
57
+
58
+ return {
59
+ DATA_DIR_RADIO_OPTIONS,
60
+ dataConfigRadioValue,
61
+ k8sDistroSubDir,
62
+ commonConfig: '',
63
+ };
64
+ },
65
+ watch: {
66
+ commonConfig(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
+ }
84
+ }
85
+ }
86
+ },
20
87
  computed: {
21
- disableEditInput() {
22
- return this.mode !== _CREATE;
88
+ isDisabled() {
89
+ return this.mode === _EDIT;
90
+ },
91
+ dataConfigRadioOptions() {
92
+ const defaultDataDirOption = {
93
+ value: DATA_DIR_RADIO_OPTIONS.DEFAULT,
94
+ label: this.t('cluster.directoryConfig.radioInput.defaultLabel')
95
+ };
96
+ const customDataDirOption = {
97
+ value: DATA_DIR_RADIO_OPTIONS.CUSTOM,
98
+ label: this.t('cluster.directoryConfig.radioInput.customLabel')
99
+ };
100
+
101
+ if (this.mode === _CREATE) {
102
+ return [
103
+ defaultDataDirOption,
104
+ { value: DATA_DIR_RADIO_OPTIONS.COMMON, label: this.t('cluster.directoryConfig.radioInput.commonLabel') },
105
+ customDataDirOption
106
+ ];
107
+ } else {
108
+ return [
109
+ defaultDataDirOption,
110
+ customDataDirOption
111
+ ];
112
+ }
113
+ }
114
+ },
115
+ methods: {
116
+ handleRadioInput(val) {
117
+ switch (val) {
118
+ case DATA_DIR_RADIO_OPTIONS.DEFAULT:
119
+ if (this.mode === _CREATE) {
120
+ this.commonConfig = '';
121
+ }
122
+ this.value.systemAgent = '';
123
+ this.value.provisioning = '';
124
+ this.value.k8sDistro = '';
125
+
126
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
127
+ break;
128
+ case DATA_DIR_RADIO_OPTIONS.COMMON:
129
+ this.commonConfig = DEFAULT_COMMON_BASE_PATH;
130
+
131
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.COMMON;
132
+ break;
133
+ // default is custom config
134
+ default:
135
+ if (this.mode === _CREATE) {
136
+ this.commonConfig = '';
137
+ }
138
+
139
+ this.value.systemAgent = '';
140
+ this.value.provisioning = '';
141
+ this.value.k8sDistro = '';
142
+
143
+ this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
144
+ break;
145
+ }
23
146
  }
24
- }
147
+ },
25
148
  };
26
149
  </script>
27
150
 
28
151
  <template>
29
152
  <div class="row">
30
153
  <div class="col span-8">
31
- <h3 class="mb-20">
154
+ <h3>
32
155
  {{ t('cluster.directoryConfig.title') }}
33
156
  </h3>
34
- <LabeledInput
35
- v-model="value.systemAgent"
157
+ <Banner
36
158
  class="mb-20"
37
- :mode="mode"
38
- :label="t('cluster.directoryConfig.systemAgent.label')"
39
- :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
40
- :disabled="disableEditInput"
41
- data-testid="rke2-directory-config-systemAgent-data-dir"
159
+ :closable="false"
160
+ color="info"
161
+ label-key="cluster.directoryConfig.banner"
42
162
  />
43
- <LabeledInput
44
- v-model="value.provisioning"
45
- class="mb-20"
163
+ <RadioGroup
164
+ v-show="!isDisabled"
165
+ :value="dataConfigRadioValue"
166
+ class="mb-10"
46
167
  :mode="mode"
47
- :label="t('cluster.directoryConfig.provisioning.label')"
48
- :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
49
- :disabled="disableEditInput"
50
- data-testid="rke2-directory-config-provisioning-data-dir"
168
+ :options="dataConfigRadioOptions"
169
+ name="directory-config-radio"
170
+ data-testid="rke2-directory-config-radio-input"
171
+ @input="handleRadioInput"
51
172
  />
52
173
  <LabeledInput
53
- v-model="value.k8sDistro"
174
+ v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON"
175
+ v-model="commonConfig"
54
176
  class="mb-20"
55
177
  :mode="mode"
56
- :label="t('cluster.directoryConfig.k8sDistro.label')"
57
- :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
58
- :disabled="disableEditInput"
59
- data-testid="rke2-directory-config-k8sDistro-data-dir"
178
+ :label="t('cluster.directoryConfig.common.label')"
179
+ :tooltip="t('cluster.directoryConfig.common.tooltip')"
180
+ :disabled="isDisabled"
181
+ data-testid="rke2-directory-config-common-data-dir"
60
182
  />
183
+ <div v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.CUSTOM">
184
+ <LabeledInput
185
+ v-model="value.systemAgent"
186
+ class="mb-20"
187
+ :mode="mode"
188
+ :label="t('cluster.directoryConfig.systemAgent.label')"
189
+ :tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
190
+ :disabled="isDisabled"
191
+ data-testid="rke2-directory-config-systemAgent-data-dir"
192
+ />
193
+ <LabeledInput
194
+ v-model="value.provisioning"
195
+ class="mb-20"
196
+ :mode="mode"
197
+ :label="t('cluster.directoryConfig.provisioning.label')"
198
+ :tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
199
+ :disabled="isDisabled"
200
+ data-testid="rke2-directory-config-provisioning-data-dir"
201
+ />
202
+ <LabeledInput
203
+ v-model="value.k8sDistro"
204
+ class="mb-20"
205
+ :mode="mode"
206
+ :label="t('cluster.directoryConfig.k8sDistro.label')"
207
+ :tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
208
+ :disabled="isDisabled"
209
+ data-testid="rke2-directory-config-k8sDistro-data-dir"
210
+ />
211
+ </div>
61
212
  <div class="mb-40" />
62
213
  </div>
63
214
  </div>
@@ -10,10 +10,11 @@ import { mapFeature, HARVESTER as HARVESTER_FEATURE } from '@shell/store/feature
10
10
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
11
11
  import ResourceFetch from '@shell/mixins/resource-fetch';
12
12
  import { BadgeState } from '@components/BadgeState';
13
+ import CloudCredExpired from '@shell/components/formatter/CloudCredExpired';
13
14
 
14
15
  export default {
15
16
  components: {
16
- Banner, ResourceTable, Masthead, BadgeState
17
+ Banner, ResourceTable, Masthead, BadgeState, CloudCredExpired
17
18
  },
18
19
  mixins: [ResourceFetch],
19
20
  props: {
@@ -41,6 +42,8 @@ export default {
41
42
  mgmtClusters: this.$fetchType(MANAGEMENT.CLUSTER),
42
43
  };
43
44
 
45
+ this.$store.dispatch('rancher/findAll', { type: NORMAN.CLOUD_CREDENTIAL });
46
+
44
47
  if ( this.$store.getters['management/canList'](SNAPSHOT) ) {
45
48
  hash.etcdSnapshots = this.$fetchType(SNAPSHOT);
46
49
  }
@@ -141,6 +144,29 @@ export default {
141
144
  // This will be used when there's clusters from extension based provisioners
142
145
  // We should re-visit this for scaling reasons
143
146
  return this.filteredRows.some((c) => c.metadata.namespace !== 'fleet-local' && c.metadata.namespace !== 'fleet-default');
147
+ },
148
+
149
+ tokenExpiredData() {
150
+ const counts = this.rows.reduce((res, provCluster) => {
151
+ const expireData = provCluster.cloudCredential?.expireData;
152
+
153
+ if (expireData?.expiring) {
154
+ res.expiring++;
155
+ }
156
+ if (expireData?.expired) {
157
+ res.expired++;
158
+ }
159
+
160
+ return res;
161
+ }, {
162
+ expiring: 0,
163
+ expired: 0
164
+ });
165
+
166
+ return {
167
+ expiring: counts.expiring ? this.t('cluster.cloudCredentials.banners.expiring', { count: counts.expiring }) : '',
168
+ expired: counts.expired ? this.t('cluster.cloudCredentials.banners.expired', { count: counts.expired }) : '',
169
+ };
144
170
  }
145
171
  },
146
172
 
@@ -186,6 +212,17 @@ export default {
186
212
  </template>
187
213
  </Masthead>
188
214
 
215
+ <Banner
216
+ v-if="tokenExpiredData.expiring"
217
+ color="warning"
218
+ :label="tokenExpiredData.expiring"
219
+ />
220
+ <Banner
221
+ v-if="tokenExpiredData.expired"
222
+ color="error"
223
+ :label="tokenExpiredData.expired"
224
+ />
225
+
189
226
  <ResourceTable
190
227
  :schema="schema"
191
228
  :rows="filteredRows"
@@ -194,6 +231,7 @@ export default {
194
231
  :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
195
232
  :data-testid="'cluster-list'"
196
233
  :force-update-live-and-delayed="forceUpdateLiveAndDelayed"
234
+ :sub-rows="true"
197
235
  >
198
236
  <!-- Why are state column and subrow overwritten here? -->
199
237
  <!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
@@ -207,19 +245,32 @@ export default {
207
245
  </template>
208
246
  <template #sub-row="{fullColspan, row, keyField, componentTestid, i, onRowMouseEnter, onRowMouseLeave}">
209
247
  <tr
210
- v-if="row.stateDescription"
211
248
  :key="row[keyField] + '-description'"
212
249
  :data-testid="componentTestid + '-' + i + '-row-description'"
213
250
  class="state-description sub-row"
214
251
  @mouseenter="onRowMouseEnter"
215
252
  @mouseleave="onRowMouseLeave"
216
253
  >
217
- <td>&nbsp;</td>
254
+ <td v-if="row.cloudCredentialWarning || row.stateDescription">
255
+ &nbsp;
256
+ </td>
218
257
  <td
258
+ v-if="row.cloudCredentialWarning || row.stateDescription"
219
259
  :colspan="fullColspan - 1"
220
- :class="{ 'text-error' : row.stateObj.error }"
221
260
  >
222
- {{ row.stateDescription }}
261
+ <CloudCredExpired
262
+ v-if="row.cloudCredentialWarning"
263
+ :value="row.cloudCredential.expires"
264
+ :row="row.cloudCredential"
265
+ :verbose="true"
266
+ :class="{'mb-10': row.stateDescription}"
267
+ />
268
+ <div
269
+ v-if="row.stateDescription"
270
+ :class="{ 'text-error' : row.stateObj.error }"
271
+ >
272
+ {{ row.stateDescription }}
273
+ </div>
223
274
  </td>
224
275
  </tr>
225
276
  </template>
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);
@@ -7,9 +7,9 @@ jest.mock('@shell/utils/clipboard', () => {
7
7
  describe('class MgmtCluster', () => {
8
8
  describe('provisioner', () => {
9
9
  const testCases = [
10
- [{ provider: 'rke', driver: 'imported' }, 'rke'],
11
- [{ provider: 'k3s', driver: 'K3S' }, 'k3s'],
12
- [{ provider: 'aks', driver: 'AKS' }, 'aks'],
10
+ [{ provider: 'rke', driver: 'imported' }, 'imported'],
11
+ [{ provider: 'k3s', driver: 'K3S' }, 'K3S'],
12
+ [{ provider: 'aks', driver: 'AKS' }, 'AKS'],
13
13
  [{}, 'imported'],
14
14
  ];
15
15
 
@@ -1,31 +1,6 @@
1
1
  import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
2
2
 
3
3
  describe('class ProvCluster', () => {
4
- const importedClusterInfo = {
5
- clusterName: 'test', provisioner: 'imported', mgmt: { spec: { gkeConfig: {} } }, spec: {}
6
- };
7
- const importedGkeClusterInfo = {
8
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: true } } }
9
- };
10
- const importedAksClusterInfo = {
11
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { aksConfig: { imported: true } } }
12
- };
13
- const importedEksClusterInfo = {
14
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { eksConfig: { imported: true } } }
15
- };
16
- const notImportedGkeClusterInfo = {
17
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: false } }, rkeConfig: {} }
18
- };
19
- const importedClusterInfoWithProviderForEmberParam = {
20
- clusterName: 'test', provisioner: 'rke2', mgmt: { providerForEmberParam: 'import' }
21
- };
22
- const localClusterInfo = {
23
- clusterName: 'test', provisioner: 'imported', mgmt: { isLocal: true, spec: { gkeConfig: {} } }, spec: {}
24
- };
25
- const doRke2Info = {
26
- clusterName: 'test', provisioner: 'rke2', mgmt: { isLocal: false, providerForEmberParam: 'import' }, spec: { rkeConfig: {} }
27
- };
28
-
29
4
  const gkeClusterWithPrivateEndpoint = {
30
5
  clusterName: 'test',
31
6
  provisioner: 'GKE',
@@ -76,67 +51,6 @@ describe('class ProvCluster', () => {
76
51
  });
77
52
  });
78
53
 
79
- describe('isImported', () => {
80
- const testCases = [
81
- [importedClusterInfo, true],
82
- [importedGkeClusterInfo, true],
83
- [importedAksClusterInfo, true],
84
- [importedEksClusterInfo, true],
85
- [notImportedGkeClusterInfo, false],
86
- [importedClusterInfoWithProviderForEmberParam, true],
87
- [localClusterInfo, false],
88
- [doRke2Info, false],
89
- [{}, false],
90
- ];
91
- const resetMocks = () => {
92
- // Clear all mock function calls:
93
- jest.clearAllMocks();
94
- };
95
-
96
- it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Boolean) => {
97
- const cluster = new ProvCluster({ spec: clusterData.spec });
98
-
99
- jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(
100
- clusterData.mgmt
101
- );
102
- jest.spyOn(cluster, 'provisioner', 'get').mockReturnValue(
103
- clusterData.provisioner
104
- );
105
-
106
- expect(cluster.isImported).toBe(expected);
107
- resetMocks();
108
- }
109
- );
110
- });
111
-
112
- describe('mgmt', () => {
113
- const testCases = [
114
- [importedClusterInfo, importedClusterInfo.mgmt],
115
- [importedGkeClusterInfo, importedGkeClusterInfo.mgmt],
116
- [importedAksClusterInfo, importedAksClusterInfo.mgmt],
117
- [importedEksClusterInfo, importedEksClusterInfo.mgmt],
118
- [notImportedGkeClusterInfo, notImportedGkeClusterInfo.mgmt],
119
- [importedClusterInfoWithProviderForEmberParam, importedClusterInfoWithProviderForEmberParam.mgmt],
120
- [localClusterInfo, localClusterInfo.mgmt],
121
- [doRke2Info, doRke2Info.mgmt],
122
- [{}, null],
123
- ];
124
-
125
- const resetMocks = () => {
126
- // Clear all mock function calls:
127
- jest.clearAllMocks();
128
- };
129
-
130
- it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Object) => {
131
- const clusterMock = jest.fn(() => clusterData.mgmt);
132
- const ctx = { rootGetters: { 'management/byId': clusterMock } };
133
- const cluster = new ProvCluster({ status: { clusterName: clusterData.clusterName } }, ctx);
134
-
135
- expect(cluster.mgmt).toBe(expected);
136
- resetMocks();
137
- });
138
- });
139
-
140
54
  describe('hasError', () => {
141
55
  const conditionsWithoutError = [
142
56
  {
@@ -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
 
@@ -279,28 +279,115 @@ export default class CatalogApp extends SteveModel {
279
279
  };
280
280
  }
281
281
 
282
- get deployedAsLegacy() {
283
- return async() => {
284
- if (this.spec?.values?.global) {
285
- const { clusterName, projectName } = this.spec.values.global;
286
-
287
- if (clusterName && projectName) {
288
- try {
289
- const legacyApp = await this.$dispatch('rancher/find', {
290
- type: NORMAN.APP,
291
- id: `${ projectName }:${ this.metadata?.name }`,
292
- opt: { url: `/v3/project/${ clusterName }:${ projectName }/apps/${ projectName }:${ this.metadata?.name }` }
293
- }, { root: true });
294
-
295
- if (legacyApp) {
296
- return legacyApp;
297
- }
298
- } catch (e) {}
299
- }
282
+ async deployedAsLegacy() {
283
+ await this.fetchValues();
284
+
285
+ if (this.values?.global) {
286
+ const { clusterName, projectName } = this.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
300
  }
301
+ }
301
302
 
302
- return false;
303
- };
303
+ return false;
304
+ }
305
+
306
+ /**
307
+ * User and Chart values live in a helm secret, so fetch it (with special param)
308
+ */
309
+ async fetchValues(force = false) {
310
+ if (!this.secretId) {
311
+ // If there's no secret id this isn't ever going to work, no need to carry on
312
+ return;
313
+ }
314
+
315
+ const haveValues = !!this._values && !!this._chartValues;
316
+
317
+ if (haveValues && !force) {
318
+ // If we already have the required values and we're not forced to re-fetch, no need to carry on
319
+ return;
320
+ }
321
+
322
+ try {
323
+ await this.$dispatch('find', {
324
+ type: SECRET,
325
+ id: this.secretId,
326
+ opt: {
327
+ 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)
328
+ watch: false, // Cannot watch with custom params (they are dropped on calls made when resyncing over socket)
329
+ params: { includeHelmData: true }
330
+ }
331
+ });
332
+ } catch (e) {
333
+ console.error(`Cannot find values for ${ this.id } (unable to fetch)`, e); // eslint-disable-line no-console
334
+ }
335
+ }
336
+
337
+ get secretId() {
338
+ const metadata = this.metadata;
339
+ const secretReference = metadata.ownerReferences?.find((ow) => ow.kind.toLowerCase() === SECRET);
340
+
341
+ const secretId = secretReference?.name;
342
+ const secretNamespace = metadata.namespace;
343
+
344
+ if (!secretNamespace || !secretId) {
345
+ console.warn(`Cannot find values for ${ this.id } (cannot find related secret namespace or id)`); // eslint-disable-line no-console
346
+
347
+ return null;
348
+ }
349
+
350
+ return `${ secretNamespace }/${ secretId }`;
351
+ }
352
+
353
+ get _secret() {
354
+ return this.secretId ? this.$getters['byId'](SECRET, this.secretId) : null;
355
+ }
356
+
357
+ _validateSecret(noun) {
358
+ if (this._secret === undefined) {
359
+ throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret has not been fetched via app \`fetchValues\`)`);
360
+ }
361
+
362
+ if (this._secret === null) {
363
+ throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret cannot or has failed to fetch) `);
364
+ }
365
+ }
366
+
367
+ /**
368
+ * The user's helm values
369
+ */
370
+ get values() {
371
+ this._validateSecret('values');
372
+
373
+ return this._values;
374
+ }
375
+
376
+ get _values() {
377
+ return this._secret?.data?.release?.config;
378
+ }
379
+
380
+ /**
381
+ * The Charts default helm values
382
+ */
383
+ get chartValues() {
384
+ this._validateSecret('chartValues');
385
+
386
+ return this._chartValues;
387
+ }
388
+
389
+ get _chartValues() {
390
+ return this._secret?.data?.release?.chart?.values;
304
391
  }
305
392
  }
306
393