@rancher/shell 0.3.20 → 0.3.22

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 (74) hide show
  1. package/assets/translations/en-us.yaml +8 -2
  2. package/assets/translations/zh-hans.yaml +8 -1
  3. package/cloud-credential/__tests__/azure.test.ts +53 -0
  4. package/cloud-credential/azure.vue +6 -0
  5. package/components/GrowlManager.vue +33 -30
  6. package/components/Questions/Array.vue +2 -2
  7. package/components/Questions/Boolean.vue +7 -1
  8. package/components/Questions/CloudCredential.vue +1 -0
  9. package/components/Questions/Enum.vue +21 -2
  10. package/components/Questions/Float.vue +8 -3
  11. package/components/Questions/Int.vue +8 -3
  12. package/components/Questions/Question.js +72 -0
  13. package/components/Questions/QuestionMap.vue +2 -1
  14. package/components/Questions/Radio.vue +33 -0
  15. package/components/Questions/Reference.vue +2 -0
  16. package/components/Questions/String.vue +8 -3
  17. package/components/Questions/Yaml.vue +46 -0
  18. package/components/Questions/__tests__/Boolean.test.ts +123 -0
  19. package/components/Questions/__tests__/Float.test.ts +123 -0
  20. package/components/Questions/__tests__/Int.test.ts +123 -0
  21. package/components/Questions/__tests__/String.test.ts +123 -0
  22. package/components/Questions/__tests__/Yaml.test.ts +123 -0
  23. package/components/Questions/index.vue +8 -1
  24. package/components/ResourceTable.vue +6 -12
  25. package/components/SideNav.vue +634 -0
  26. package/components/__tests__/NamespaceFilter.test.ts +3 -4
  27. package/components/form/ResourceQuota/ProjectRow.vue +38 -15
  28. package/components/form/UnitInput.vue +1 -0
  29. package/components/form/__tests__/KeyValue.test.ts +2 -1
  30. package/components/form/__tests__/UnitInput.test.ts +2 -2
  31. package/components/formatter/ClusterProvider.vue +9 -3
  32. package/components/formatter/LinkName.vue +12 -1
  33. package/components/formatter/__tests__/ClusterProvider.test.ts +5 -1
  34. package/components/nav/Header.vue +1 -0
  35. package/components/nav/WorkspaceSwitcher.vue +4 -1
  36. package/config/settings.ts +59 -2
  37. package/config/types.js +2 -0
  38. package/core/plugin-helpers.js +4 -1
  39. package/core/types.ts +1 -0
  40. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +28 -0
  41. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +26 -0
  42. package/creators/pkg/init +63 -4
  43. package/detail/provisioning.cattle.io.cluster.vue +4 -2
  44. package/edit/fleet.cattle.io.gitrepo.vue +8 -0
  45. package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -4
  46. package/layouts/default.vue +11 -597
  47. package/middleware/authenticated.js +2 -14
  48. package/mixins/__tests__/chart.test.ts +40 -0
  49. package/mixins/chart.js +5 -0
  50. package/models/catalog.cattle.io.clusterrepo.js +6 -2
  51. package/models/fleet.cattle.io.cluster.js +10 -2
  52. package/models/fleet.cattle.io.gitrepo.js +3 -1
  53. package/package.json +1 -1
  54. package/pages/c/_cluster/fleet/index.vue +4 -0
  55. package/pages/c/_cluster/gatekeeper/index.vue +10 -1
  56. package/pages/c/_cluster/uiplugins/index.vue +3 -3
  57. package/plugins/steve/__tests__/header-warnings.spec.ts +238 -0
  58. package/plugins/steve/actions.js +4 -23
  59. package/plugins/steve/header-warnings.ts +91 -0
  60. package/promptRemove/management.cattle.io.project.vue +9 -6
  61. package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +8 -0
  62. package/rancher-components/components/Form/Radio/RadioButton.test.ts +7 -3
  63. package/scripts/extension/parse-tag-name +30 -0
  64. package/types/shell/index.d.ts +3 -0
  65. package/utils/auth.js +17 -0
  66. package/utils/object.js +0 -1
  67. package/utils/settings.ts +2 -17
  68. package/utils/validators/__tests__/cidr.test.ts +33 -0
  69. package/utils/validators/cidr.js +5 -0
  70. package/vue-config-helper.js +135 -0
  71. package/vue.config.js +23 -139
  72. package/yarn-error.log +200 -0
  73. package/creators/pkg/files/.github/workflows/build-container.yml +0 -64
  74. package/creators/pkg/files/.github/workflows/build-extension.yml +0 -110
@@ -27,37 +27,58 @@ export default {
27
27
  }
28
28
  },
29
29
 
30
- computed: { ...ROW_COMPUTED },
30
+ computed: {
31
+ ...ROW_COMPUTED,
32
+
33
+ resourceQuotaLimit: {
34
+ get() {
35
+ return this.value.spec.resourceQuota?.limit || {};
36
+ },
37
+ },
38
+
39
+ namespaceDefaultResourceQuotaLimit: {
40
+ get() {
41
+ return this.value.spec.namespaceDefaultResourceQuota?.limit || {};
42
+ },
43
+ }
44
+ },
31
45
 
32
46
  methods: {
33
47
  updateType(type) {
34
- if (typeof this.value.spec.resourceQuota.limit[this.type] !== 'undefined') {
48
+ if (typeof this.value.spec.resourceQuota?.limit[this.type] !== 'undefined') {
35
49
  this.$delete(this.value.spec.resourceQuota.limit, this.type);
36
50
  }
37
-
38
- if (typeof this.value.spec.namespaceDefaultResourceQuota.limit[this.type] !== 'undefined') {
51
+ if (typeof this.value.spec.namespaceDefaultResourceQuota?.limit[this.type] !== 'undefined') {
39
52
  this.$delete(this.value.spec.namespaceDefaultResourceQuota.limit, this.type);
40
53
  }
41
54
 
42
55
  this.$emit('type-change', type);
56
+ },
57
+
58
+ updateQuotaLimit(prop, type, val) {
59
+ if (!this.value.spec[prop]) {
60
+ this.value.spec[prop] = { limit: { } };
61
+ }
62
+
63
+ this.value.spec[prop].limit[type] = val;
43
64
  }
44
65
  },
45
66
  };
46
67
  </script>
47
68
  <template>
48
- <div
49
- v-if="typeOption"
50
- class="row"
69
+ <div
70
+ v-if="typeOption"
71
+ class="row"
51
72
  >
52
- <Select
53
- class="mr-10"
54
- :mode="mode"
55
- :value="type"
56
- :options="types"
57
- @input="updateType($event)"
73
+ <Select
74
+ class="mr-10"
75
+ :mode="mode"
76
+ :value="type"
77
+ :options="types"
78
+ @input="updateType($event)"
58
79
  />
59
80
  <UnitInput
60
- v-model="value.spec.resourceQuota.limit[type]"
81
+ :value="resourceQuotaLimit[type]"
61
82
  class="mr-10"
62
83
  :mode="mode"
63
84
  :placeholder="typeOption.placeholder"
@@ -65,15 +86,17 @@ export default {
65
86
  :input-exponent="typeOption.inputExponent"
66
87
  :base-unit="typeOption.baseUnit"
67
88
  :output-modifier="true"
89
+ @input="updateQuotaLimit('resourceQuota', type, $event)"
68
90
  />
69
91
  <UnitInput
70
- v-model="value.spec.namespaceDefaultResourceQuota.limit[type]"
92
+ :value="namespaceDefaultResourceQuotaLimit[type]"
71
93
  :mode="mode"
72
94
  :placeholder="typeOption.placeholder"
73
95
  :increment="typeOption.increment"
74
96
  :input-exponent="typeOption.inputExponent"
75
97
  :base-unit="typeOption.baseUnit"
76
98
  :output-modifier="true"
99
+ @input="updateQuotaLimit('namespaceDefaultResourceQuota', type, $event)"
77
100
  />
78
101
  </div>
79
102
  </template>
@@ -224,6 +224,7 @@ export default {
224
224
  :required="required"
225
225
  :placeholder="placeholder"
226
226
  :hide-arrows="hideArrows"
227
+ @change="update($event.target.value)"
227
228
  @blur="update($event.target.value)"
228
229
  >
229
230
  <template #suffix>
@@ -19,7 +19,8 @@ describe('component: KeyValue', () => {
19
19
  it('should display a markdown-multiline field with new lines visible', () => {
20
20
  const wrapper = mount(KeyValue, {
21
21
  propsData: {
22
- value: 'test',
22
+ value:
23
+ { value: 'test' },
23
24
  valueMarkdownMultiline: true,
24
25
  },
25
26
  mocks: { $store: { getters: { 'i18n/t': jest.fn() } } },
@@ -10,13 +10,13 @@ describe('component: UnitInput', () => {
10
10
  expect(wrapper.isVisible()).toBe(true);
11
11
  });
12
12
 
13
- it('should emit input event on value change', async() => {
13
+ it.each(['blur', 'change'])('should emit input event when "%p" is fired', async(event) => {
14
14
  const wrapper = mount(UnitInput, { propsData: { value: 1, delay: 0 } });
15
15
  const input = wrapper.find('input');
16
16
 
17
17
  await input.setValue(2);
18
18
  await input.setValue(4);
19
- input.trigger('blur');
19
+ input.trigger(event);
20
20
 
21
21
  expect(wrapper.emitted('input')).toHaveLength(1);
22
22
  });
@@ -7,14 +7,20 @@ export default {
7
7
  },
8
8
  },
9
9
  data(props) {
10
+ const mgmt = props.row?.mgmt;
11
+
10
12
  return {
11
13
  // The isImported getter on the provisioning cluster
12
14
  // model doesn't work for imported K3s clusters, in
13
15
  // which case it returns 'k3s' instead of 'imported.'
14
16
  // This is the workaround.
15
- isImported: props.row?.mgmt?.providerForEmberParam === 'import' ||
16
- // when imported cluster is Google GKE
17
- props.row?.mgmt?.spec?.gkeConfig?.imported
17
+ isImported: mgmt?.providerForEmberParam === 'import' ||
18
+ // when imported cluster is GKE
19
+ !!mgmt?.spec?.gkeConfig?.imported ||
20
+ // or AKS
21
+ !!mgmt?.spec?.aksConfig?.imported ||
22
+ // or EKS
23
+ !!mgmt?.spec?.eksConfig?.imported
18
24
  };
19
25
  },
20
26
  };
@@ -1,5 +1,6 @@
1
1
  <script>
2
2
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
3
+ import { canViewResource } from '@shell/utils/auth';
3
4
 
4
5
  export default {
5
6
  props: {
@@ -41,6 +42,10 @@ export default {
41
42
  };
42
43
 
43
44
  return { name, params };
45
+ },
46
+
47
+ canViewResource() {
48
+ return canViewResource(this.$store, this.type);
44
49
  }
45
50
  }
46
51
  };
@@ -48,8 +53,14 @@ export default {
48
53
 
49
54
  <template>
50
55
  <span v-if="value">
51
- <nuxt-link :to="url">
56
+ <nuxt-link
57
+ v-if="canViewResource"
58
+ :to="url"
59
+ >
52
60
  {{ value }}
53
61
  </nuxt-link>
62
+ <template v-else>
63
+ {{ value }}
64
+ </template>
54
65
  </span>
55
66
  </template>
@@ -3,15 +3,19 @@ import ClusterProvider from '@shell/components/formatter/ClusterProvider.vue';
3
3
 
4
4
  describe('component: ClusterProvider', () => {
5
5
  const importedGkeClusterInfo = { mgmt: { spec: { gkeConfig: { imported: true } } } };
6
+ const importedAksClusterInfo = { mgmt: { spec: { aksConfig: { imported: true } } } };
7
+ const importedEksClusterInfo = { mgmt: { spec: { eksConfig: { imported: true } } } };
6
8
  const notImportedGkeClusterInfo = { mgmt: { spec: { gkeConfig: { imported: false } } } };
7
9
  const importedClusterInfoWithProviderForEmberParam = { mgmt: { providerForEmberParam: 'import' } };
8
10
 
9
11
  describe('isImported', () => {
10
12
  const testCases = [
11
13
  [importedGkeClusterInfo, true],
14
+ [importedAksClusterInfo, true],
15
+ [importedEksClusterInfo, true],
12
16
  [notImportedGkeClusterInfo, false],
13
17
  [importedClusterInfoWithProviderForEmberParam, true],
14
- [{}, undefined],
18
+ [{}, false],
15
19
  ];
16
20
 
17
21
  it.each(testCases)('should return the isImported value properly based on the props data', (row, expected) => {
@@ -335,6 +335,7 @@ export default {
335
335
  <template>
336
336
  <header
337
337
  ref="header"
338
+ data-testid="header"
338
339
  >
339
340
  <div>
340
341
  <TopLevelMenu v-if="isRancherInHarvester || isMultiCluster || !isSingleProduct" />
@@ -84,7 +84,10 @@ export default {
84
84
  </script>
85
85
 
86
86
  <template>
87
- <div class="filter">
87
+ <div
88
+ class="filter"
89
+ data-testid="workspace-switcher"
90
+ >
88
91
  <Select
89
92
  ref="select"
90
93
  v-model="value"
@@ -1,5 +1,5 @@
1
1
  // Settings
2
- import { GC_DEFAULTS } from '../utils/gc/gc-types';
2
+ import { GC_DEFAULTS, GC_PREFERENCES } from '@shell/utils/gc/gc-types';
3
3
 
4
4
  interface GlobalSettingRuleset {
5
5
  name: string,
@@ -148,7 +148,47 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
148
148
  [SETTING.HIDE_LOCAL_CLUSTER]: { kind: 'boolean' },
149
149
  };
150
150
 
151
- export const DEFAULT_PERF_SETTING = {
151
+ /**
152
+ * Settings on how to handle warnings returning in api responses, specifically which to show as growls
153
+ */
154
+ export interface PerfSettingsWarningHeaders {
155
+ /**
156
+ * Warning is a string containing multiple entries. This determines how they are split up
157
+ *
158
+ * See https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/1693-warnings#design-details
159
+ */
160
+ separator: string,
161
+ /**
162
+ * Show warnings in a notification if they're not in this block list
163
+ */
164
+ notificationBlockList: string[]
165
+ }
166
+
167
+ export interface PerfSettingsKubeApi {
168
+ /**
169
+ * Settings related to the response header `warnings` value
170
+ */
171
+ warningHeader: PerfSettingsWarningHeaders
172
+ }
173
+
174
+ export interface PerfSettings {
175
+ inactivity: {
176
+ enabled: boolean;
177
+ threshold: number;
178
+ };
179
+ incrementalLoading: {
180
+ enabled: boolean;
181
+ threshold: number;
182
+ };
183
+ manualRefresh: {};
184
+ disableWebsocketNotification: boolean;
185
+ garbageCollection: GC_PREFERENCES;
186
+ forceNsFilterV2: any;
187
+ advancedWorker: {};
188
+ kubeAPI: PerfSettingsKubeApi;
189
+ }
190
+
191
+ export const DEFAULT_PERF_SETTING: PerfSettings = {
152
192
  inactivity: {
153
193
  enabled: false,
154
194
  threshold: 900,
@@ -165,4 +205,21 @@ export const DEFAULT_PERF_SETTING = {
165
205
  garbageCollection: GC_DEFAULTS,
166
206
  forceNsFilterV2: { enabled: false },
167
207
  advancedWorker: { enabled: false },
208
+ kubeAPI: {
209
+ /**
210
+ * Settings related to the response header `warnings` value
211
+ */
212
+ warningHeader: {
213
+ /**
214
+ * Warning is a string containing multiple entries. This determines how they are split up
215
+ *
216
+ * See https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/1693-warnings#design-details
217
+ */
218
+ separator: '299 - ',
219
+ /**
220
+ * Show warnings in a notification if they're not in this block list
221
+ */
222
+ notificationBlockList: ['299 - unknown field']
223
+ }
224
+ }
168
225
  };
package/config/types.js CHANGED
@@ -311,3 +311,5 @@ export const AUTH_TYPE = {
311
311
  _SSH: '_ssh',
312
312
  _S3: '_S3'
313
313
  };
314
+
315
+ export const LOCAL_CLUSTER = 'local';
@@ -58,6 +58,7 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
58
58
  'id',
59
59
  'mode',
60
60
  'path',
61
+ 'hash',
61
62
  // url query params
62
63
  'queryParam',
63
64
  // Custom context specific params provided by the extension, not to be confused with location params
@@ -76,8 +77,10 @@ function checkExtensionRouteBinding($route, locationConfig, context) {
76
77
  const locationConfigParam = asArray[x];
77
78
 
78
79
  if (locationConfigParam) {
80
+ if (param === 'hash') {
81
+ res = $route.hash ? $route.hash.includes(locationConfigParam) : false;
79
82
  // handle "product" in a separate way...
80
- if (param === 'product') {
83
+ } else if (param === 'product') {
81
84
  res = checkRouteProduct($route, locationConfigParam);
82
85
  // also handle "mode" in a separate way because it mainly depends on query params
83
86
  } else if (param === 'mode') {
package/core/types.ts CHANGED
@@ -139,6 +139,7 @@ export type LocationConfig = {
139
139
  cluster?: string[],
140
140
  id?: string[],
141
141
  mode?: string[],
142
+ hash?: string[],
142
143
  /**
143
144
  * path match from URL (excludes host address)
144
145
  */
@@ -0,0 +1,28 @@
1
+ name: Build and Release Extension Catalog
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - main
11
+
12
+ defaults:
13
+ run:
14
+ shell: bash
15
+ working-directory: ./
16
+
17
+ jobs:
18
+ build-extension-catalog:
19
+ uses: rancher/dashboard/.github/workflows/build-extension-catalog.yml@master
20
+ permissions:
21
+ actions: write
22
+ contents: read
23
+ packages: write
24
+ with:
25
+ registry_target: ghcr.io
26
+ registry_user: ${{ github.actor }}
27
+ secrets:
28
+ registry_token: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,26 @@
1
+ name: Build and Release Extension Charts
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+ branches:
10
+ - main
11
+
12
+ defaults:
13
+ run:
14
+ shell: bash
15
+ working-directory: ./
16
+
17
+ jobs:
18
+ build-extension-charts:
19
+ uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
20
+ permissions:
21
+ actions: write
22
+ contents: write
23
+ deployments: write
24
+ pages: write
25
+ with:
26
+ target_branch: gh-pages
package/creators/pkg/init CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
+ const https = require('https');
5
6
 
6
7
  const targets = {
7
8
  dev: './node_modules/.bin/nuxt dev',
@@ -16,9 +17,10 @@ const files = [
16
17
  ];
17
18
 
18
19
  const topLevelScripts = {
19
- 'build-pkg': './node_modules/@rancher/shell/scripts/build-pkg.sh',
20
- 'serve-pkgs': './node_modules/@rancher/shell/scripts/serve-pkgs',
21
- 'publish-pkgs': './node_modules/@rancher/shell/scripts/extension/publish',
20
+ 'build-pkg': './node_modules/@rancher/shell/scripts/build-pkg.sh',
21
+ 'serve-pkgs': './node_modules/@rancher/shell/scripts/serve-pkgs',
22
+ 'publish-pkgs': './node_modules/@rancher/shell/scripts/extension/publish',
23
+ 'parse-tag-name': './node_modules/@rancher/shell/scripts/extension/parse-tag-name'
22
24
  };
23
25
 
24
26
  const typeFolders = [
@@ -88,11 +90,68 @@ Object.keys(targets).forEach((target) => {
88
90
  }
89
91
  });
90
92
 
93
+ // Add annotation for the latest Rancher version by default
94
+ function fetchLatestVersion() {
95
+ console.log(' Fetching latest Rancher Version');
96
+ const options = { headers: { 'User-Agent': 'nodejs' } };
97
+
98
+ https.get('https://api.github.com/repos/rancher/rancher/releases/latest', options, (res) => {
99
+ const { statusCode } = res;
100
+ const contentType = res.headers['content-type'];
101
+
102
+ let error;
103
+
104
+ if ( statusCode !== 200 ) {
105
+ error = new Error(' Request Failed.\n' +
106
+ ` Status Code: ${ statusCode }`);
107
+ } else if ( !/^application\/json/.test(contentType) ) {
108
+ error = new Error(' Invalid content-type.\n' +
109
+ ` Expected application/json but received ${ contentType }`);
110
+ }
111
+
112
+ if ( error ) {
113
+ console.log(error.message);
114
+
115
+ res.resume();
116
+
117
+ return;
118
+ }
119
+
120
+ res.setEncoding('utf8');
121
+ let rawData = '';
122
+
123
+ res.on('data', (chunk) => {
124
+ rawData += chunk;
125
+ });
126
+ res.on('end', () => {
127
+ try {
128
+ const release = JSON.parse(rawData);
129
+
130
+ if ( release.tag_name ) {
131
+ console.log(` Adding rancher-version annotation '>= ${ release.tag_name }' to package.json`);
132
+
133
+ pkg.rancher = { annotations: { 'catalog.cattle.io/rancher-version': `>= ${ release.tag_name }` } };
134
+ writePackageJson();
135
+ }
136
+ } catch (e) {
137
+ console.log(' Error parsing release data', e);
138
+ }
139
+ });
140
+ }).on('error', (e) => {
141
+ console.log(' Error fetching latest Rancher Version', e);
142
+ });
143
+ }
144
+
145
+ fetchLatestVersion();
146
+ writePackageJson();
147
+
91
148
  // Add dependencies
92
149
  // pkg.dependencies['@rancher/shell'] = '^0.6.2';
93
150
  // pkg.dependencies['core-js'] = '3.18.3';
94
151
 
95
- fs.writeFileSync(path.join(pkgFolder, 'package.json'), JSON.stringify(pkg, null, 2));
152
+ function writePackageJson() {
153
+ fs.writeFileSync(path.join(pkgFolder, 'package.json'), JSON.stringify(pkg, null, 2));
154
+ }
96
155
 
97
156
  // Create type folders if needed
98
157
  if (addTypeFolders) {
@@ -250,8 +250,10 @@ export default {
250
250
 
251
251
  computed: {
252
252
  defaultTab() {
253
- if (this.showRegistration && !this.machines?.length) {
254
- return 'registration';
253
+ if (this.showRegistration) {
254
+ if (this.value.isRke2 ? !this.machines?.length : !this.nodes?.length) {
255
+ return 'registration';
256
+ }
255
257
  }
256
258
 
257
259
  if (this.showMachines) {
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import Vue from 'vue';
2
3
  import { exceptionToErrorsArray } from '@shell/utils/error';
3
4
  import { mapGetters } from 'vuex';
4
5
  import {
@@ -81,6 +82,10 @@ export default {
81
82
 
82
83
  this.tlsMode = tls;
83
84
 
85
+ if (this.value.spec.correctDrift === undefined) {
86
+ Vue.set(this.value.spec, 'correctDrift', { enabled: false });
87
+ }
88
+
84
89
  this.updateTargets();
85
90
  },
86
91
 
@@ -536,6 +541,7 @@ export default {
536
541
  </div>
537
542
  <div class="col span-6">
538
543
  <InputWithSelect
544
+ :data-testid="`gitrepo-${ref}`"
539
545
  :mode="mode"
540
546
  :select-label="t('fleet.gitRepo.ref.label')"
541
547
  :select-value="ref"
@@ -563,6 +569,7 @@ export default {
563
569
  />
564
570
 
565
571
  <SelectOrCreateAuthSecret
572
+ data-testid="gitrepo-helm-auth"
566
573
  :value="value.spec.helmSecretName"
567
574
  :register-before-hook="registerBeforeHook"
568
575
  :namespace="value.metadata.namespace"
@@ -637,6 +644,7 @@ export default {
637
644
  <h2 v-t="'fleet.gitRepo.paths.label'" />
638
645
  <ArrayList
639
646
  v-model="value.spec.paths"
647
+ data-testid="gitRepo-paths"
640
648
  :mode="mode"
641
649
  :initial-empty-row="false"
642
650
  :value-placeholder="t('fleet.gitRepo.paths.placeholder')"
@@ -792,10 +792,10 @@ export default {
792
792
  const names = [];
793
793
  const cni = this.serverConfig.cni;
794
794
 
795
- if ( cni ) {
796
- const parts = cni.split(',').map((x) => `rke2-${ x }`);
797
-
798
- names.push(...parts);
795
+ if (typeof cni === 'string') {
796
+ names.push(...cni.split(',').map((x) => `rke2-${ x }`));
797
+ } else if (Array.isArray(cni)) {
798
+ names.push(...cni.map((x) => `rke2-${ x }`));
799
799
  }
800
800
 
801
801
  if (this.showCloudProvider) { // Shouldn't be removed such that changes to it will re-trigger this watch