@rancher/shell 0.3.22 → 0.3.24

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 (48) hide show
  1. package/assets/styles/base/_variables.scss +1 -0
  2. package/assets/styles/themes/_dark.scss +1 -0
  3. package/assets/styles/themes/_light.scss +6 -5
  4. package/assets/translations/en-us.yaml +15 -10
  5. package/assets/translations/zh-hans.yaml +1 -1
  6. package/babel.config.js +3 -0
  7. package/components/ClusterProviderIconMenu.vue +161 -0
  8. package/components/Loading.vue +1 -1
  9. package/components/SideNav.vue +1 -1
  10. package/components/SortableTable/paging.js +10 -0
  11. package/components/form/GitPicker.vue +16 -0
  12. package/components/form/SelectOrCreateAuthSecret.vue +16 -3
  13. package/components/nav/Group.vue +54 -24
  14. package/components/nav/Header.vue +1 -1
  15. package/components/nav/TopLevelMenu.vue +469 -294
  16. package/components/nav/Type.vue +31 -5
  17. package/creators/pkg/init +2 -2
  18. package/edit/fleet.cattle.io.gitrepo.vue +43 -15
  19. package/edit/logging.banzaicloud.io.output/index.vue +7 -0
  20. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +3 -8
  21. package/edit/provisioning.cattle.io.cluster/rke2.vue +108 -33
  22. package/edit/resources.cattle.io.backup.vue +3 -1
  23. package/edit/resources.cattle.io.restore.vue +3 -1
  24. package/edit/workload/storage/ContainerMountPaths.vue +7 -5
  25. package/initialize/App.js +2 -0
  26. package/initialize/client.js +63 -51
  27. package/initialize/index.js +2 -0
  28. package/layouts/default.vue +8 -0
  29. package/machine-config/amazonec2.vue +1 -0
  30. package/mixins/fetch.client.js +3 -3
  31. package/package.json +1 -1
  32. package/pages/__tests__/prefs.test.ts +1 -1
  33. package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
  34. package/pages/prefs.vue +3 -13
  35. package/plugins/dashboard-store/resource-class.js +1 -1
  36. package/public/index.html +4 -2
  37. package/rancher-components/Form/LabeledInput/LabeledInput.vue +8 -0
  38. package/rancher-components/Form/Radio/RadioButton.test.ts +7 -3
  39. package/scripts/extension/parse-tag-name +0 -0
  40. package/store/prefs.js +3 -4
  41. package/store/type-map.js +2 -16
  42. package/types/shell/index.d.ts +10 -1
  43. package/utils/__tests__/formatter.test.ts +77 -0
  44. package/utils/__tests__/sort.test.ts +61 -0
  45. package/utils/formatter.js +11 -0
  46. package/utils/string.js +12 -0
  47. package/vue.config.js +7 -6
  48. package/yarn-error.log +16 -16
@@ -40,6 +40,10 @@ export default {
40
40
  showCount() {
41
41
  return typeof this.type.count !== 'undefined';
42
42
  },
43
+
44
+ namespaceIcon() {
45
+ return this.type.namespaced;
46
+ },
43
47
  },
44
48
 
45
49
  methods: {
@@ -102,6 +106,10 @@ export default {
102
106
  v-if="showFavorite"
103
107
  :resource="type.name"
104
108
  />
109
+ <i
110
+ v-if="namespaceIcon"
111
+ class="icon icon-namespace namespaced"
112
+ />
105
113
  {{ type.count }}
106
114
  </span>
107
115
  </a>
@@ -127,13 +135,16 @@ export default {
127
135
  </template>
128
136
 
129
137
  <style lang="scss" scoped>
138
+ .namespaced {
139
+ margin-right: 4px;
140
+ }
141
+
130
142
  .child {
131
143
  margin: 0 var(--outline) 0 0;
132
144
 
133
145
  .label {
134
146
  align-items: center;
135
147
  grid-area: label;
136
- display: flex;
137
148
  overflow: hidden;
138
149
  text-overflow: ellipsis;
139
150
 
@@ -166,6 +177,7 @@ export default {
166
177
  text-overflow: ellipsis;
167
178
  white-space: nowrap;
168
179
  color: var(--body-text);
180
+ height: 33px;
169
181
 
170
182
  &:hover {
171
183
  background: var(--nav-hover);
@@ -181,26 +193,40 @@ export default {
181
193
  grid-area: favorite;
182
194
  font-size: 12px;
183
195
  position: relative;
196
+ vertical-align: middle;
197
+ margin-right: 4px;
184
198
  }
185
199
 
186
200
  .count {
187
- grid-area: count;
188
201
  font-size: 12px;
189
- text-align: right;
190
202
  justify-items: center;
191
203
  padding-right: 4px;
204
+ display: flex;
205
+ align-items: center;
192
206
  }
193
207
 
194
208
  &.nav-type:not(.depth-0) {
195
209
  A {
196
- font-size: 13px;
197
- padding: 5.5px 7px 5.5px 10px;
210
+ padding-left: 16px;
198
211
  }
199
212
 
200
213
  ::v-deep .label I {
201
214
  padding-right: 2px;
202
215
  }
203
216
  }
217
+
218
+ &.nav-type:is(.depth-1) {
219
+ A {
220
+ font-size: 13px;
221
+ padding-left: 23px;
222
+ }
223
+ }
224
+
225
+ &.nav-type:not(.depth-0):not(.depth-1) {
226
+ A {
227
+ padding-left: 14px;
228
+ }
229
+ }
204
230
  }
205
231
 
206
232
  </style>
package/creators/pkg/init CHANGED
@@ -174,8 +174,8 @@ if (addWorkflowFolder) {
174
174
  }
175
175
 
176
176
  const files = [
177
- 'build-extension.yml',
178
- 'build-container.yml'
177
+ 'build-extension-catalog.yml',
178
+ 'build-extension-charts.yml'
179
179
  ];
180
180
 
181
181
  files.forEach((fileName) => {
@@ -131,18 +131,18 @@ export default {
131
131
  const addRepositorySteps = [stepRepoInfo, stepTargetInfo].sort((a, b) => (b.weight || 0) - (a.weight || 0));
132
132
 
133
133
  return {
134
- allClusters: [],
135
- allClusterGroups: [],
136
- allWorkspaces: [],
137
- tempCachedValues: {},
138
- username: null,
139
- password: null,
140
- publicKey: null,
141
- privateKey: null,
142
- tlsMode: null,
143
- caBundle: null,
144
- targetAdvancedErrors: null,
145
- matchingClusters: null,
134
+ allClusters: [],
135
+ allClusterGroups: [],
136
+ allWorkspaces: [],
137
+ tempCachedValues: {},
138
+ username: null,
139
+ password: null,
140
+ publicKey: null,
141
+ privateKey: null,
142
+ tlsMode: null,
143
+ caBundle: null,
144
+ targetAdvancedErrors: null,
145
+ matchingClusters: null,
146
146
  ref,
147
147
  refValue,
148
148
  targetMode,
@@ -152,6 +152,7 @@ export default {
152
152
  stepRepoInfo,
153
153
  stepTargetInfo,
154
154
  addRepositorySteps,
155
+ displayHelmRepoURLRegex: false
155
156
  };
156
157
  },
157
158
 
@@ -261,9 +262,8 @@ export default {
261
262
  targetCluster: 'updateTargets',
262
263
  targetClusterGroup: 'updateTargets',
263
264
  targetAdvanced: 'updateTargets',
264
-
265
- tlsMode: 'updateTls',
266
- caBundle: 'updateTls',
265
+ tlsMode: 'updateTls',
266
+ caBundle: 'updateTls',
267
267
 
268
268
  workspace(neu) {
269
269
  if ( this.isCreate ) {
@@ -289,6 +289,10 @@ export default {
289
289
 
290
290
  updateCachedAuthVal(val, key) {
291
291
  this.tempCachedValues[key] = typeof val === 'string' ? { selected: val } : { ...val };
292
+
293
+ if (key === 'helmSecretName') {
294
+ this.toggleHelmRepoURLRegex(val && val.selected !== AUTH_TYPE._NONE);
295
+ }
292
296
  },
293
297
 
294
298
  updateAuth(val, key) {
@@ -303,6 +307,14 @@ export default {
303
307
  this.updateCachedAuthVal(val, key);
304
308
  },
305
309
 
310
+ toggleHelmRepoURLRegex(active) {
311
+ this.displayHelmRepoURLRegex = active;
312
+
313
+ if (!active) {
314
+ delete this.value.spec?.helmRepoURLRegex;
315
+ }
316
+ },
317
+
306
318
  updateTargets() {
307
319
  const spec = this.value.spec;
308
320
  const mode = this.targetMode;
@@ -583,6 +595,22 @@ export default {
583
595
  @inputauthval="updateCachedAuthVal($event, 'helmSecretName')"
584
596
  />
585
597
 
598
+ <div
599
+ v-if="displayHelmRepoURLRegex"
600
+ class="row mt-20"
601
+ >
602
+ <div
603
+ class="col span-6"
604
+ data-testid="gitrepo-helm-repo-url-regex"
605
+ >
606
+ <LabeledInput
607
+ v-model="value.spec.helmRepoURLRegex"
608
+ :mode="mode"
609
+ label-key="fleet.gitRepo.helmRepoURLRegex"
610
+ />
611
+ </div>
612
+ </div>
613
+
586
614
  <template v-if="isTls">
587
615
  <div class="spacer" />
588
616
  <div class="row">
@@ -258,6 +258,13 @@ export default {
258
258
  $logo: 60px;
259
259
 
260
260
  .output {
261
+ display: flex;
262
+ flex-direction: column;
263
+ flex-grow: 1;
264
+
265
+ .side-tabs {
266
+ flex: 1;
267
+ }
261
268
  .provider {
262
269
  h1 {
263
270
  display: inline-block;
@@ -8,6 +8,8 @@ import KeyValue from '@shell/components/form/KeyValue';
8
8
  import Taints from '@shell/components/form/Taints';
9
9
  import { MANAGEMENT } from '@shell/config/types';
10
10
 
11
+ import { sanitizeKey, sanitizeIP, sanitizeValue } from '@shell/utils/string';
12
+
11
13
  export default {
12
14
  components: {
13
15
  Banner, Checkbox, CopyCode, InfoBox, KeyValue, LabeledInput, Taints
@@ -52,7 +54,7 @@ export default {
52
54
  this.etcd && out.push('--etcd');
53
55
  this.controlPlane && out.push('--controlplane');
54
56
  this.worker && out.push('--worker');
55
- this.address && out.push(`--address ${ sanitizeValue(this.address) }`);
57
+ this.address && out.push(`--address ${ sanitizeIP(this.address) }`);
56
58
  this.internalAddress && out.push(`--internal-address ${ sanitizeValue(this.internalAddress) }`);
57
59
  this.nodeName && out.push(`--node-name ${ sanitizeValue(this.nodeName) }`);
58
60
 
@@ -146,13 +148,6 @@ export default {
146
148
  },
147
149
  };
148
150
 
149
- function sanitizeKey(k) {
150
- return (k || '').replace(/[^a-z0-9./_-]/ig, '');
151
- }
152
-
153
- function sanitizeValue(v) {
154
- return (v || '').replace(/[^a-z0-9._-]/ig, '');
155
- }
156
151
  </script>
157
152
 
158
153
  <template>
@@ -219,6 +219,13 @@ export default {
219
219
  defaultRke2: '',
220
220
  defaultK3s: '',
221
221
  s3Backup: false,
222
+ /**
223
+ * All info related to a specific version of the chart
224
+ *
225
+ * This includes chart itself, README and values
226
+ *
227
+ * { [chartName:string]: { chart: json, readme: string, values: json } }
228
+ */
222
229
  versionInfo: {},
223
230
  membershipUpdate: {},
224
231
  showDeprecatedPatchVersions: false,
@@ -245,6 +252,7 @@ export default {
245
252
  busy: false,
246
253
  machinePoolValidation: {}, // map of validation states for each machine pool
247
254
  allNamespaces: [],
255
+ initialCloudProvider: this.value?.agentConfig?.['cloud-provider-name'],
248
256
  extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
249
257
  };
250
258
  },
@@ -548,13 +556,53 @@ export default {
548
556
 
549
557
  const cur = this.agentConfig['cloud-provider-name'];
550
558
 
551
- if ( cur && !out.find((x) => x.value === cur) ) {
552
- out.unshift({ label: `${ cur } (Current)`, value: cur });
559
+ if (cur && !out.find((x) => x.value === cur)) {
560
+ // Localization missing
561
+ // Look up cur in the localization file
562
+ const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ cur }".label`, null, cur);
563
+
564
+ out.unshift({
565
+ label: `${ label } (Current)`,
566
+ value: cur,
567
+ unsupported: true,
568
+ disabled: true
569
+ });
570
+ }
571
+
572
+ const initial = this.initialCloudProvider;
573
+
574
+ if (cur !== initial && initial && !out.find((x) => x.value === initial)) {
575
+ const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ initial }".label`, null, initial);
576
+
577
+ out.unshift({
578
+ label: `${ label } (Current)`,
579
+ value: initial,
580
+ unsupported: true,
581
+ disabled: true
582
+ });
553
583
  }
554
584
 
555
585
  return out;
556
586
  },
557
587
 
588
+ unsupportedCloudProvider() {
589
+ // The current cloud provider
590
+ const cur = this.initialCloudProvider;
591
+
592
+ const provider = cur && this.cloudProviderOptions.find((x) => x.value === cur);
593
+
594
+ return !!provider?.unsupported;
595
+ },
596
+
597
+ canNotEditCloudProvider() {
598
+ const canNotEdit = this.clusterIsAlreadyCreated && !this.unsupportedCloudProvider;
599
+
600
+ return canNotEdit;
601
+ },
602
+
603
+ /**
604
+ * Kube Version
605
+ */
558
606
  selectedVersion() {
559
607
  const str = this.value.spec.kubernetesVersion;
560
608
 
@@ -579,6 +627,11 @@ export default {
579
627
  return this.selectedVersion?.agentArgs || {};
580
628
  },
581
629
 
630
+ /**
631
+ * The addons (kube charts) applicable for the selected kube version
632
+ *
633
+ * { [chartName:string]: { repo: string, version: string } }
634
+ */
582
635
  chartVersions() {
583
636
  return this.selectedVersion?.charts || {};
584
637
  },
@@ -788,6 +841,9 @@ export default {
788
841
  return this.agentArgs['cloud-provider-name'];
789
842
  },
790
843
 
844
+ /**
845
+ * The chart names of the addons applicable to the current kube version and selected cloud provider
846
+ */
791
847
  addonNames() {
792
848
  const names = [];
793
849
  const cni = this.serverConfig.cni;
@@ -811,8 +867,13 @@ export default {
811
867
  return names;
812
868
  },
813
869
 
870
+ /**
871
+ * The charts of the addons applicable to the current kube version and selected cloud provider
872
+ *
873
+ * These are the charts themselves and do not include chart readme or values
874
+ */
814
875
  addonVersions() {
815
- const versions = this.addonNames.map((name) => this.chartVersionFor(name));
876
+ const versions = this.addonNames.map((name) => this.versionInfo[name]?.chart);
816
877
 
817
878
  return versions.filter((x) => !!x);
818
879
  },
@@ -1723,41 +1784,37 @@ export default {
1723
1784
  });
1724
1785
  },
1725
1786
 
1726
- chartVersionFor(chartName) {
1727
- const entry = this.chartVersions[chartName];
1728
-
1729
- if ( !entry ) {
1730
- return null;
1731
- }
1732
-
1733
- const out = this.$store.getters['catalog/version']({
1734
- repoType: 'cluster',
1735
- repoName: entry.repo,
1736
- chartName,
1737
- versionName: entry.version,
1738
- });
1739
-
1740
- return out;
1741
- },
1742
-
1787
+ /**
1788
+ * Ensure all chart information required to show addons is available
1789
+ *
1790
+ * This basically means
1791
+ * 1) That the full chart relating to the addon is fetched (which includes core chart, readme and values)
1792
+ * 2) We're ready to cache any values the user provides for each addon
1793
+ */
1743
1794
  async initAddons() {
1744
- for ( const v of this.addonVersions ) {
1745
- if ( this.versionInfo[v.name] ) {
1795
+ for ( const chartName of this.addonNames ) {
1796
+ const entry = this.chartVersions[chartName];
1797
+
1798
+ if ( this.versionInfo[chartName] ) {
1746
1799
  continue;
1747
1800
  }
1748
1801
 
1749
- const res = await this.$store.dispatch('catalog/getVersionInfo', {
1750
- repoType: 'cluster',
1751
- repoName: v.repoName,
1752
- chartName: v.name,
1753
- versionName: v.version
1754
- });
1802
+ try {
1803
+ const res = await this.$store.dispatch('catalog/getVersionInfo', {
1804
+ repoType: 'cluster',
1805
+ repoName: entry.repo,
1806
+ chartName,
1807
+ versionName: entry.version,
1808
+ });
1755
1809
 
1756
- set(this.versionInfo, v.name, res);
1757
- const key = this.chartVersionKey(v.name);
1810
+ set(this.versionInfo, chartName, res);
1811
+ const key = this.chartVersionKey(chartName);
1758
1812
 
1759
- if (!this.userChartValues[key]) {
1760
- this.userChartValues[key] = {};
1813
+ if (!this.userChartValues[key]) {
1814
+ this.userChartValues[key] = {};
1815
+ }
1816
+ } catch (e) {
1817
+ console.error(`Failed to fetch or process chart info for ${ chartName }`); // eslint-disable-line no-console
1761
1818
  }
1762
1819
  }
1763
1820
  },
@@ -2226,6 +2283,18 @@ export default {
2226
2283
  if (this.isHarvesterDriver && this.mode === _CREATE && this.isHarvesterIncompatible) {
2227
2284
  this.setHarvesterDefaultCloudProvider();
2228
2285
  }
2286
+
2287
+ // Cloud Provider check
2288
+ // If the cloud provider is unsupported, switch provider to 'external'
2289
+ if (this.unsupportedCloudProvider) {
2290
+ set(this.agentConfig, 'cloud-provider-name', 'external');
2291
+ } else {
2292
+ // Switch the cloud provider back to the initial value
2293
+ // Use changed the Kubernetes version back to a version where the initial cloud provider is valid - so switch back to this one
2294
+ // to undo the change to external that we may have made
2295
+ // Note: Cloud Provider can only be changed on edit when the initial provider is no longer supported
2296
+ set(this.agentConfig, 'cloud-provider-name', this.initialCloudProvider);
2297
+ }
2229
2298
  }
2230
2299
  },
2231
2300
 
@@ -2444,7 +2513,7 @@ export default {
2444
2513
  <LabeledSelect
2445
2514
  v-model="agentConfig['cloud-provider-name']"
2446
2515
  :mode="mode"
2447
- :disabled="clusterIsAlreadyCreated"
2516
+ :disabled="canNotEditCloudProvider"
2448
2517
  :options="cloudProviderOptions"
2449
2518
  :label="t('cluster.rke2.cloudProvider.label')"
2450
2519
  />
@@ -2485,6 +2554,12 @@ export default {
2485
2554
  <div class="spacer" />
2486
2555
 
2487
2556
  <div class="col span-12">
2557
+ <Banner
2558
+ v-if="unsupportedCloudProvider"
2559
+ class="error mt-5"
2560
+ >
2561
+ {{ t('cluster.rke2.cloudProvider.unsupported') }}
2562
+ </Banner>
2488
2563
  <h3>
2489
2564
  {{ t('cluster.rke2.cloudProvider.header') }}
2490
2565
  </h3>
@@ -16,6 +16,8 @@ import { allHash } from '@shell/utils/promise';
16
16
  import { NAMESPACE, _VIEW } from '@shell/config/query-params';
17
17
  import { sortBy } from '@shell/utils/sort';
18
18
  import { get } from '@shell/utils/object';
19
+ import { formatEncryptionSecretNames } from '@shell/utils/formatter';
20
+
19
21
  export default {
20
22
 
21
23
  components: {
@@ -108,7 +110,7 @@ export default {
108
110
  },
109
111
 
110
112
  encryptionSecretNames() {
111
- return this.allSecrets.filter((secret) => (secret.data || {})['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace && !secret.metadata?.state?.error).map((secret) => secret.metadata.name);
113
+ return formatEncryptionSecretNames(this.allSecrets, this.chartNamespace);
112
114
  },
113
115
 
114
116
  storageOptions() {
@@ -13,6 +13,8 @@ import { SECRET, BACKUP_RESTORE, CATALOG } from '@shell/config/types';
13
13
  import { allHash } from '@shell/utils/promise';
14
14
  import { get } from '@shell/utils/object';
15
15
  import { _CREATE } from '@shell/config/query-params';
16
+ import { formatEncryptionSecretNames } from '@shell/utils/formatter';
17
+
16
18
  export default {
17
19
 
18
20
  components: {
@@ -93,7 +95,7 @@ export default {
93
95
  },
94
96
 
95
97
  encryptionSecretNames() {
96
- return this.allSecrets.filter((secret) => !!(secret.data || {})['encryption-provider-config.yaml'] && secret.metadata.namespace === this.chartNamespace && !secret.metadata?.state?.error).map((secret) => secret.metadata.name);
98
+ return formatEncryptionSecretNames(this.allSecrets, this.chartNamespace);
97
99
  },
98
100
 
99
101
  isEncrypted() {
@@ -1,10 +1,12 @@
1
1
  <script>
2
- import ButtonDropdown from '@shell/components/ButtonDropdown';
3
- import Mount from '@shell/edit/workload/storage/Mount';
2
+ import { clone } from '@shell/utils/object';
4
3
  import { _VIEW } from '@shell/config/query-params';
5
- import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
6
4
  import { randomStr } from '@shell/utils/string';
7
5
 
6
+ import Mount from '@shell/edit/workload/storage/Mount';
7
+ import ButtonDropdown from '@shell/components/ButtonDropdown';
8
+ import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
9
+
8
10
  export default {
9
11
  name: 'ContainerMountPaths',
10
12
  components: {
@@ -109,7 +111,7 @@ export default {
109
111
  const names = volumeMounts.map(({ name }) => name);
110
112
 
111
113
  // Extract storage volumes to allow mutation, if matches mount map
112
- return this.value.volumes.filter((volume) => names.includes(volume.name));
114
+ return clone(this.value.volumes.filter((volume) => names.includes(volume.name)));
113
115
  },
114
116
 
115
117
  getSelectedContainerVolumes() {
@@ -118,7 +120,7 @@ export default {
118
120
  const names = volumeMounts.map(({ name }) => name);
119
121
 
120
122
  // Extract storage volumes to allow mutation, if matches mount map
121
- return this.value.volumes.filter((volume) => names.includes(volume.name));
123
+ return clone(this.value.volumes.filter((volume) => names.includes(volume.name)));
122
124
  },
123
125
 
124
126
  /**
package/initialize/App.js CHANGED
@@ -1,3 +1,5 @@
1
+ // Taken from @nuxt/vue-app/template/App.js
2
+
1
3
  import Vue from 'vue';
2
4
 
3
5
  import { getMatchedComponentsInstances, getChildrenComponentInstancesUsingFetch, promisify, globalHandleError } from '../utils/nuxt';