@rancher/shell 3.0.2 → 3.0.4

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 (58) hide show
  1. package/assets/styles/themes/_light.scss +1 -2
  2. package/assets/styles/themes/_suse.scss +1 -0
  3. package/assets/translations/en-us.yaml +19 -3
  4. package/chart/monitoring/prometheus/index.vue +13 -10
  5. package/components/ButtonGroup.vue +4 -0
  6. package/components/LocaleSelector.vue +2 -0
  7. package/components/PromptRemove.vue +1 -1
  8. package/components/SortableTable/THead.vue +2 -0
  9. package/components/SortableTable/index.vue +68 -12
  10. package/components/StatusBadge.vue +10 -4
  11. package/components/auth/Principal.vue +9 -3
  12. package/components/form/ArrayList.vue +9 -0
  13. package/components/form/KeyValue.vue +1 -1
  14. package/components/form/MatchExpressions.vue +4 -0
  15. package/components/form/Select.vue +11 -2
  16. package/components/nav/Favorite.vue +5 -1
  17. package/components/nav/Group.vue +4 -0
  18. package/components/nav/Jump.vue +7 -0
  19. package/components/nav/Pinned.vue +1 -1
  20. package/components/nav/Type.vue +1 -0
  21. package/config/router/routes.js +1 -0
  22. package/core/plugin-routes.ts +5 -115
  23. package/core/plugins.js +1 -1
  24. package/core/types.ts +18 -2
  25. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +84 -23
  26. package/detail/autoscaling.horizontalpodautoscaler/index.vue +13 -3
  27. package/dialog/DeactivateDriverDialog.vue +4 -4
  28. package/dialog/GitRepoForceUpdateDialog.vue +1 -1
  29. package/edit/auth/ldap/__tests__/config.test.ts +0 -14
  30. package/edit/auth/ldap/config.vue +0 -24
  31. package/edit/autoscaling.horizontalpodautoscaler/metric-identifier.vue +5 -2
  32. package/edit/fleet.cattle.io.clustergroup.vue +5 -3
  33. package/edit/fleet.cattle.io.gitrepo.vue +2 -0
  34. package/edit/logging-flow/Match.vue +1 -1
  35. package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +5 -2
  36. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +1 -1
  37. package/machine-config/__tests__/vmwarevsphere.test.ts +48 -3
  38. package/machine-config/vmwarevsphere.vue +16 -0
  39. package/models/__tests__/logging.banzaicloud.io.flow.test.ts +88 -0
  40. package/models/logging.banzaicloud.io.flow.js +2 -1
  41. package/package.json +2 -2
  42. package/pages/about.vue +16 -8
  43. package/promptRemove/management.cattle.io.fleetworkspace.vue +1 -1
  44. package/promptRemove/management.cattle.io.globalrole.vue +1 -1
  45. package/promptRemove/management.cattle.io.project.vue +2 -2
  46. package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
  47. package/promptRemove/pod.vue +1 -1
  48. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -1
  49. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -2
  50. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -3
  51. package/rancher-components/RcDropdown/RcDropdown.vue +3 -3
  52. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -2
  53. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +2 -2
  54. package/scripts/test-plugins-build.sh +4 -6
  55. package/types/shell/index.d.ts +1 -1
  56. package/utils/__tests__/string.test.ts +2 -2
  57. package/utils/color.js +9 -8
  58. package/utils/string.js +1 -3
@@ -22,123 +22,13 @@ export class PluginRoutes {
22
22
  });
23
23
  }
24
24
 
25
- public addRoutes(plugin: any, newRouteInfos: RouteInfo[]) {
26
- const allRoutes = [
27
- ...(this.router.options.routes || [])
28
- ];
29
-
30
- // Need to take into account if routes are being replaced
31
- // Despite what the docs say, routes are not replaced, so we use a workaround
32
- // Remove all routes that are being replaced
33
- const newRoutes = newRouteInfos.map((ri) => ri.route);
34
-
35
- this.forEachNestedRoutes(newRoutes, (route: RouteRecordRaw) => {
36
- // Patch colliding legacy routes that start /:product
37
- if (route.path?.startsWith('/:product')) {
38
- // Legacy pattern used by extensions - routes may collide, so modify them not to
39
- let productName;
40
-
41
- // If the route has a name (which is always the case for the extensions we have written), use it to get the product name
42
- if (route.name && typeof route.name === 'string') {
43
- const nameParts = route.name.split('-');
44
-
45
- // First part of the route name is the product name
46
- productName = nameParts[0];
47
- }
48
-
49
- // Use the plugin name as the product, if the route does not have a name
50
- productName = productName || plugin.name;
51
-
52
- // Replace the path - removing :product and using the actual product name instead - this avoids route collisions
53
- route.path = `/${ productName }${ route.path.substr(9) }`;
54
- route.meta = route.meta || {};
55
-
56
- route.meta.product = route.meta.product || productName;
57
- }
58
- });
59
-
60
- this.updateMatcher(newRouteInfos, allRoutes);
61
- }
62
-
63
- private updateMatcher(newRoutes: RouteInfo[], allRoutes: RouteRecordRaw[]) {
64
- // Note - Always use a new router and replace the existing router's matching
65
- // Using the existing router and adding routes to it will force nuxt middleware to
66
- // execute many times (nuxt middleware boils down to route.beforeEach). This issue was seen refreshing in a harvester cluster with a
67
- // dynamically loaded cluster
68
-
69
- if (newRoutes.length === 0) {
70
- return;
71
- }
72
-
73
- const orderedPluginRoutes: RouteRecordRaw[] = [];
74
-
75
- // separate plugin routes that have parent and not, you want to push the new routes in REVERSE order to the front of the existing list so that the order of routes specified by the extension is preserved
76
- newRoutes.reverse().forEach((routeInfo: RouteInfo) => {
77
- let foundParentRoute;
78
-
25
+ public addRoutes(newRouteInfos: RouteInfo[]) {
26
+ newRouteInfos.forEach((routeInfo) => {
79
27
  if (routeInfo.parent) {
80
- foundParentRoute = this.findInNestedRoutes(allRoutes, (route: RouteRecordRaw) => route.name === routeInfo.parent);
81
-
82
- if (foundParentRoute) {
83
- foundParentRoute.children = foundParentRoute?.children || [];
84
- foundParentRoute.children.unshift(routeInfo.route);
85
- }
86
- }
87
-
88
- if (!foundParentRoute) {
89
- orderedPluginRoutes.unshift(routeInfo.route);
90
- }
91
- });
92
-
93
- this.router.clearRoutes();
94
-
95
- const allRoutesToAdd = [...orderedPluginRoutes, ...allRoutes];
96
-
97
- allRoutesToAdd.forEach((route) => this.router.addRoute(route));
98
- }
99
-
100
- /**
101
- * Traverse the entire tree of nested routes
102
- *
103
- * @param routes The routes we wish to traverse through
104
- * @param fn -> Return true if you'd like to break the loop early (small)
105
- * @returns {@boolean} -> Returns true if breaking early
106
- */
107
- private forEachNestedRoutes(
108
- routes: RouteRecordRaw[] = [],
109
- fn: (route: RouteRecordRaw) => boolean | undefined | void
110
- ) {
111
- for (let i = 0; i < routes.length; ++i) {
112
- const route = routes[i];
113
- const result = fn(route);
114
-
115
- if (result || this.forEachNestedRoutes(route.children, fn)) {
116
- return true;
117
- }
118
- }
119
- }
120
-
121
- /**
122
- * Find a route that matches the criteria defined by fn.
123
- *
124
- * @param routes The routes we wish to search through
125
- * @param fn -> Returns true if the passed in route matches the expected criteria
126
- * @returns The found route or undefined
127
- */
128
- private findInNestedRoutes(
129
- routes: RouteRecordRaw[] = [],
130
- fn: (route: RouteRecordRaw) => boolean
131
- ): RouteRecordRaw | undefined {
132
- let found: RouteRecordRaw | undefined;
133
-
134
- this.forEachNestedRoutes(routes, (route) => {
135
- if (fn(route)) {
136
- found = route;
137
-
138
- return true;
28
+ this.router.addRoute(routeInfo.parent, routeInfo.route);
29
+ } else {
30
+ this.router.addRoute(routeInfo.route);
139
31
  }
140
32
  });
141
-
142
- return found;
143
33
  }
144
34
  }
package/core/plugins.js CHANGED
@@ -344,7 +344,7 @@ export default function(context, inject, vueApp) {
344
344
  });
345
345
 
346
346
  // Routes
347
- pluginRoutes.addRoutes(plugin, plugin.routes);
347
+ pluginRoutes.addRoutes(plugin.routes);
348
348
 
349
349
  // Validators
350
350
  Object.keys(plugin.validators).forEach((key) => {
package/core/types.ts CHANGED
@@ -471,6 +471,24 @@ export interface DSLReturnType {
471
471
  */
472
472
  virtualType: (options: ConfigureVirtualTypeOptions) => void;
473
473
 
474
+ /**
475
+ * Side menu ordering for grouping of pages
476
+ * @param input Name of the group
477
+ * @param weight Ordering to be applied for the specified group
478
+ * @param forBasic Apply to basic type instead of regular type tree
479
+ * @returns {@link void}
480
+ */
481
+ weightGroup: (input: string, weight: number, forBasic: boolean) => void;
482
+
483
+ /**
484
+ * Side menu ordering for simple pages
485
+ * @param input Name of the page/resource
486
+ * @param weight Ordering to be applied for the specified page/resource
487
+ * @param forBasic Apply to basic type instead of regular type tree
488
+ * @returns {@link void}
489
+ */
490
+ weightType: (input: string, weight: number, forBasic: boolean) => void;
491
+
474
492
  /**
475
493
  * Leaving these here for completeness but I don't think these should be advertised as useable to plugin creators.
476
494
  */
@@ -484,8 +502,6 @@ export interface DSLReturnType {
484
502
  // moveType: (match, group)
485
503
  // setGroupDefaultType: (input, defaultType)
486
504
  // spoofedType: (obj)
487
- // weightGroup: (input, weight, forBasic)
488
- // weightType: (input, weight, forBasic)
489
505
  }
490
506
 
491
507
  /**
@@ -1,4 +1,5 @@
1
1
  import { mount } from '@vue/test-utils';
2
+ import camelCase from 'lodash/camelCase';
2
3
  import HorizontalPodAutoScaler from '@shell/detail/autoscaling.horizontalpodautoscaler/index.vue';
3
4
 
4
5
  describe('view: autoscaling.horizontalpodautoscaler', () => {
@@ -43,7 +44,7 @@ describe('view: autoscaling.horizontalpodautoscaler', () => {
43
44
 
44
45
  };
45
46
 
46
- const value = {
47
+ const valueWithResourceMetrics = {
47
48
  status: {
48
49
  currentMetrics: [
49
50
  {
@@ -100,36 +101,96 @@ describe('view: autoscaling.horizontalpodautoscaler', () => {
100
101
  }
101
102
  }
102
103
  };
104
+ const valueWithOtherMetrics = {
105
+ status: {
106
+ currentMetrics: [
103
107
 
104
- const metricsValue = Object.values(value.spec.metrics);
105
- const currentMetrics = Object.values(value.status.currentMetrics);
108
+ {
109
+ external: {
110
+ metric: { name: 's1-prometheus' },
111
+ current: { averageValue: 50 }
112
+ },
113
+ type: 'External'
106
114
 
107
- const wrapper = mount(HorizontalPodAutoScaler, {
108
- props: { value },
109
- global: { mocks, stubs },
110
- });
115
+ }
116
+ ],
117
+ },
118
+ spec: {
119
+ metrics: [
120
+ {
121
+ external: {
122
+ metric: { name: 's1-prometheus' },
123
+ target: { averageValue: 50, type: 'AverageValue' }
124
+ },
125
+ type: 'External'
126
+ }
127
+ ]
128
+ }
129
+ }
130
+ ;
111
131
 
112
- describe.each(value.spec.metrics)('should display metrics for each resource:', (metric) => {
113
- const name = metric.resource.name;
132
+ describe('with resource metrics:', () => {
133
+ const metricsValue = Object.values(valueWithResourceMetrics.spec.metrics);
134
+ const currentMetrics = Object.values(valueWithResourceMetrics.status.currentMetrics);
114
135
 
115
- it(`${ name }:`, () => {
116
- // Resource metrics
117
- const resourceValue = wrapper.get(`[data-testid="resource-metrics-value-${ name }"]`);
118
- const resourceName = wrapper.get(`[data-testid="resource-metrics-name-${ name }"]`);
119
- const metricValue = metricsValue.find((f) => f.resource.name === name)?.resource;
136
+ const wrapper = mount(HorizontalPodAutoScaler, {
137
+ props: { value: valueWithResourceMetrics },
138
+ global: { mocks, stubs },
139
+ });
120
140
 
121
- // Current Metrics
122
- const averageUtilization = wrapper.get(`[data-testid="current-metrics-Average Utilization-${ name }"]`);
123
- const averageValue = wrapper.get(`[data-testid="current-metrics-Average Value-${ name }"]`);
124
- const currentResource = currentMetrics.find((f) => f.resource.name === name)?.resource.current;
141
+ describe.each(valueWithResourceMetrics.spec.metrics)('should display metrics for each resource:', (metric) => {
142
+ const name = metric.resource.name;
125
143
 
144
+ it(`${ name }:`, () => {
126
145
  // Resource metrics
127
- expect(resourceValue.element.textContent).toBe(`${ metricValue?.target?.averageUtilization }`);
128
- expect(resourceName.element.textContent).toBe(`${ metricValue?.name }`);
146
+ const resourceValue = wrapper.get(`[data-testid="resource-metrics-value-${ name }"]`);
147
+ const resourceName = wrapper.get(`[data-testid="resource-metrics-name-${ name }"]`);
148
+ const metricValue = metricsValue.find((f) => f.resource.name === name)?.resource;
149
+
150
+ // Current Metrics
151
+ const averageUtilization = wrapper.get(`[data-testid="current-metrics-Average Utilization-${ name }"]`);
152
+ const averageValue = wrapper.get(`[data-testid="current-metrics-Average Value-${ name }"]`);
153
+ const currentResource = currentMetrics.find((f) => f.resource.name === name)?.resource.current;
154
+
155
+ // Resource metrics
156
+ expect(resourceValue.element.textContent).toBe(`${ metricValue?.target?.averageUtilization }`);
157
+ expect(resourceName.element.textContent).toBe(`${ metricValue?.name }`);
158
+
159
+ // Current Metrics
160
+ expect(averageUtilization.element.textContent).toBe(`${ currentResource?.averageUtilization }`);
161
+ expect(averageValue.element.textContent).toBe(`${ currentResource?.averageValue }`);
162
+ });
163
+ });
164
+ });
165
+
166
+ describe('with other metrics:', () => {
167
+ const currentMetrics = Object.values(valueWithOtherMetrics.status.currentMetrics);
168
+
169
+ const wrapper = mount(HorizontalPodAutoScaler, {
170
+ props: { value: valueWithOtherMetrics },
171
+ global: { mocks, stubs },
172
+ });
173
+
174
+ describe.each(valueWithOtherMetrics.spec.metrics)('should display metrics for each resource:', (metric) => {
175
+ const metricType = camelCase(metric.type);
176
+ const name = metric[metricType as keyof typeof metric].metric.name;
177
+
178
+ it(`${ name }:`, () => {
179
+ // Resource metrics
180
+ const resourceValue = wrapper.get(`[data-testid="resource-metrics-value-${ name }"]`);
181
+ const metricValue = metric[metricType as keyof typeof metric];
182
+
183
+ // Current Metrics
184
+ const averageValue = wrapper.get(`[data-testid="current-metrics-Average Value-${ name }"]`);
185
+ const currentMatch = currentMetrics.find((f) => f[metricType as keyof typeof metric]?.metric.name === name);
186
+ const currentValue = currentMatch[metricType as keyof typeof metric]?.current;
187
+
188
+ // Resource metrics
189
+ expect(resourceValue.element.textContent).toBe(`${ metricValue?.target?.averageValue }`);
129
190
 
130
- // Current Metrics
131
- expect(averageUtilization.element.textContent).toBe(`${ currentResource?.averageUtilization }`);
132
- expect(averageValue.element.textContent).toBe(`${ currentResource?.averageValue }`);
191
+ // Current Metrics
192
+ expect(averageValue.element.textContent).toBe(`${ currentValue?.averageValue }`);
193
+ });
133
194
  });
134
195
  });
135
196
  });
@@ -43,11 +43,21 @@ export default {
43
43
  } = this.value;
44
44
 
45
45
  return metrics.map((metric) => {
46
+ const currentMetricsKVs = [];
47
+ let currentMatch;
48
+ const metricType = camelCase(metric.type);
46
49
  const metricValue = get(metric, camelCase(metric.type));
47
50
  const targetType = metricValue?.target?.type;
48
- const currentMatch = findBy(currentMetrics, 'resource.name', metric.resource.name);
51
+
52
+ // The format is different between 'Resource' metrics and others.
53
+ // See for examples: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale-walkthrough/#appendix-horizontal-pod-autoscaler-status-conditions
54
+ if (metricType !== 'resource') {
55
+ currentMatch = findBy(currentMetrics, `${ metricType }.metric.name`, metricValue.metric.name);
56
+ } else {
57
+ currentMatch = findBy(currentMetrics, 'resource.name', metric.resource.name);
58
+ }
59
+
49
60
  const current = currentMatch ? get(currentMatch, `${ camelCase(metric.type) }.current`) : null;
50
- const currentMetricsKVs = [];
51
61
 
52
62
  if (current) {
53
63
  keys(current).forEach((k) => {
@@ -67,7 +77,7 @@ export default {
67
77
  objectApiVersion: metricValue?.describedObject?.apiVersion ?? null,
68
78
  objectKind: metricValue?.describedObject?.kind ?? null,
69
79
  objectName: metricValue?.describedObject?.name ?? null,
70
- resourceName: metricValue?.name ?? null,
80
+ resourceName: metricValue?.name || metricValue?.metric?.name || null,
71
81
  currentMetrics: currentMetricsKVs,
72
82
  },
73
83
  };
@@ -31,12 +31,12 @@ export default {
31
31
  },
32
32
  computed: {
33
33
  formattedText() {
34
- const namesSliced = this.drivers.map((obj) => obj.nameDisplay);
35
- const count = namesSliced.length;
36
- const remaining = namesSliced.length > 5 ? namesSliced.length - 5 : 0;
34
+ const driverNames = this.drivers.map((obj) => obj.nameDisplay);
35
+ const count = driverNames.length;
36
+ const remaining = driverNames.length > 5 ? driverNames.length - 5 : 0;
37
37
 
38
38
  const plusMore = this.t('drivers.deactivate.andOthers', { count: remaining });
39
- const names = resourceNames(namesSliced, this.t, { plusMore, endString: false });
39
+ const names = resourceNames(driverNames, plusMore, this.t, false);
40
40
  const warningDrivers = this.t('drivers.deactivate.warningDrivers', { names, count });
41
41
 
42
42
  return this.t('drivers.deactivate.warning', { warningDrivers, count });
@@ -84,7 +84,7 @@ export default {
84
84
  <template #body>
85
85
  <div class="mb-20">
86
86
  {{ t('fleet.gitRepo.actions.forceUpdate.promptNames') }} <span
87
- v-clean-html="resourceNames(names, t)"
87
+ v-clean-html="resourceNames(names, null, t)"
88
88
  class="body"
89
89
  />
90
90
  </div>
@@ -2,20 +2,6 @@ import { mount } from '@vue/test-utils';
2
2
  import LDAPConfig from '@shell/edit/auth/ldap/config.vue';
3
3
 
4
4
  describe('lDAP config', () => {
5
- it.each([
6
- 'openldap', 'freeipa'
7
- ])('should display searchUsingServiceAccount checkbox if type %p', (type) => {
8
- const wrapper = mount(LDAPConfig, {
9
- propsData: {
10
- value: {},
11
- type,
12
- }
13
- });
14
- const checkbox = wrapper.find('[data-testid="searchUsingServiceAccount"]');
15
-
16
- expect(checkbox).toBeDefined();
17
- });
18
-
19
5
  it('updates user login filter when value is entered', async() => {
20
6
  const wrapper = mount(
21
7
  LDAPConfig,
@@ -11,8 +11,6 @@ const DEFAULT_TLS_PORT = 636;
11
11
 
12
12
  export const SHIBBOLETH = 'shibboleth';
13
13
  export const OKTA = 'okta';
14
- export const OPEN_LDAP = 'openldap';
15
- export const FREE_IPA = 'freeipa';
16
14
 
17
15
  export default {
18
16
  emits: ['update:value'],
@@ -66,11 +64,6 @@ export default {
66
64
  // Does the auth provider support LDAP for search in addition to SAML?
67
65
  isSamlProvider() {
68
66
  return this.type === SHIBBOLETH || this.type === OKTA;
69
- },
70
-
71
- // Allow to enable user search just for these providers
72
- isSearchAllowed() {
73
- return this.type === OPEN_LDAP || this.type === FREE_IPA;
74
67
  }
75
68
  },
76
69
 
@@ -233,23 +226,6 @@ export default {
233
226
  />
234
227
  </div>
235
228
  </div>
236
-
237
- <div
238
- v-if="isSearchAllowed"
239
- class="row mb-20"
240
- >
241
- <div class="col">
242
- <Checkbox
243
- v-model:value="model.searchUsingServiceAccount"
244
- :mode="mode"
245
- data-testid="searchUsingServiceAccount"
246
- class="full-height"
247
- :label="t('authConfig.ldap.searchUsingServiceAccount.label')"
248
- :tooltip="t('authConfig.ldap.searchUsingServiceAccount.tip')"
249
- />
250
- </div>
251
- </div>
252
-
253
229
  <div class="row mb-20">
254
230
  <div class="col span-6">
255
231
  <LabeledInput
@@ -65,14 +65,17 @@ export default {
65
65
  </div>
66
66
  <div class="row">
67
67
  <div class="col span-12">
68
- <h3>Metric Selector</h3>
69
68
  <MatchExpressions
70
69
  :mode="mode"
71
70
  :value="matchExpressions"
72
71
  :label="t('hpa.metricIdentifier.selector.label')"
73
72
  :show-remove="false"
74
73
  @input="matchChanged($event)"
75
- />
74
+ >
75
+ <template #header>
76
+ <h3>{{ t('hpa.metricIdentifier.selector.header') }}</h3>
77
+ </template>
78
+ </MatchExpressions>
76
79
  </div>
77
80
  </div>
78
81
  </div>
@@ -149,14 +149,16 @@ export default {
149
149
  :namespace-type="FLEET_WORKSPACE"
150
150
  @update:value="$emit('input', $event)"
151
151
  />
152
-
153
- <h2 v-t="'fleet.clusterGroup.selector.label'" />
154
152
  <MatchExpressions
155
153
  :mode="mode"
156
154
  :value="expressions"
157
155
  :show-remove="false"
158
156
  @update:value="matchChanged($event)"
159
- />
157
+ >
158
+ <template #header>
159
+ <h2 v-t="'fleet.clusterGroup.selector.label'" />
160
+ </template>
161
+ </MatchExpressions>
160
162
  <Banner
161
163
  v-if="matchingClusters"
162
164
  :color="(matchingClusters.isNone || matchingClusters.isAll ? 'warning' : 'success')"
@@ -103,6 +103,7 @@ export default {
103
103
  if (!pollingInterval) {
104
104
  if (this.realMode === _CREATE) {
105
105
  pollingInterval = DEFAULT_POLLING_INTERVAL;
106
+ this.value.spec.pollingInterval = this.durationSeconds(pollingInterval);
106
107
  } else if (this.realMode === _EDIT || this.realMode === _VIEW) {
107
108
  pollingInterval = MINIMUM_POLLING_INTERVAL;
108
109
  }
@@ -658,6 +659,7 @@ export default {
658
659
  :initial-empty-row="false"
659
660
  :value-placeholder="t('fleet.gitRepo.paths.placeholder')"
660
661
  :add-label="t('fleet.gitRepo.paths.addLabel')"
662
+ :a11y-label="t('fleet.gitRepo.paths.ariaLabel')"
661
663
  :add-icon="'icon-plus'"
662
664
  :protip="t('fleet.gitRepo.paths.empty')"
663
665
  />
@@ -124,7 +124,7 @@ export default {
124
124
  <div class="row">
125
125
  <div class="col span-12">
126
126
  <Select
127
- v-model="value.namespaces"
127
+ v-model:value="value.namespaces"
128
128
  class="lg"
129
129
  :options="namespaces"
130
130
  :placeholder="t('logging.flow.matches.namespaces.placeholder')"
@@ -119,14 +119,17 @@ export default {
119
119
  >
120
120
  <template #default="{row, i}">
121
121
  <template v-if="row.value.machineLabelSelector">
122
- <h3>{{ t('cluster.advanced.argInfo.machineSelector.title') }}</h3>
123
122
  <MatchExpressions
124
123
  v-model:value="row.value.machineLabelSelector"
125
124
  class="mb-20"
126
125
  :mode="mode"
127
126
  :show-remove="false"
128
127
  :initial-empty-row="true"
129
- />
128
+ >
129
+ <template #header>
130
+ <h3>{{ t('cluster.advanced.argInfo.machineSelector.title') }}</h3>
131
+ </template>
132
+ </MatchExpressions>
130
133
  <h3>{{ t('cluster.advanced.argInfo.machineSelector.subTitle') }}</h3>
131
134
  </template>
132
135
  <h3 v-else>
@@ -48,7 +48,7 @@ export default {
48
48
  return this.value.spec.rkeConfig.etcd;
49
49
  },
50
50
  argsEtcdExposeMetrics() {
51
- return !!this.selectedVersion?.serverArgs['etcd-expose-metrics'];
51
+ return !!this.selectedVersion?.serverArgs?.['etcd-expose-metrics'];
52
52
  },
53
53
  configEtcdExposeMetrics() {
54
54
  return !!this.value.spec.rkeConfig.machineGlobalConfig['etcd-expose-metrics'];
@@ -1,4 +1,4 @@
1
- import { mount } from '@vue/test-utils';
1
+ import { mount, shallowMount } from '@vue/test-utils';
2
2
  import vmwarevsphere from '@shell/machine-config/vmwarevsphere.vue';
3
3
  import { DEFAULT_VALUES, SENTINEL } from '@shell/machine-config/vmwarevsphere-config';
4
4
 
@@ -172,12 +172,12 @@ describe('component: vmwarevsphere', () => {
172
172
  name: 'tag_name',
173
173
  category: 'tag_category',
174
174
  };
175
- const expectedReslut = [{
175
+ const expectedResult = [{
176
176
  ...tag, label: `${ tag.category } / ${ tag.name }`, value: tag.id
177
177
  }];
178
178
  const wrapper = mount(vmwarevsphere, defaultCreateSetup);
179
179
 
180
- expect(wrapper.vm.mapTagsToContent([tag])).toStrictEqual(expectedReslut);
180
+ expect(wrapper.vm.mapTagsToContent([tag])).toStrictEqual(expectedResult);
181
181
  });
182
182
  });
183
183
 
@@ -254,4 +254,49 @@ describe('component: vmwarevsphere', () => {
254
254
  });
255
255
  });
256
256
  });
257
+
258
+ describe('syncNetworkValueForLegacyLabels', () => {
259
+ it('should update the current network value properly', () => {
260
+ const legacyName = 'legacy_name';
261
+ const legacyValue = 'legacy_value';
262
+ const networkLabel = 'network_label';
263
+
264
+ const wrapper = shallowMount(vmwarevsphere, {
265
+ ...defaultEditSetup,
266
+ propsData: {
267
+ ...defaultEditSetup.propsData,
268
+ value: {
269
+ ...defaultEditSetup.propsData.value,
270
+ network: [legacyName]
271
+ }
272
+ },
273
+ computed: {
274
+ networks: () => [
275
+ {
276
+ name: legacyName, label: networkLabel, value: legacyValue
277
+ },
278
+ {
279
+ name: 'name1', label: 'label1', value: 'value1'
280
+ },
281
+ {
282
+ name: 'name2', label: 'label2', value: 'value2'
283
+ },
284
+ {
285
+ name: 'name3', label: 'label3', value: 'value3'
286
+ },
287
+ {
288
+ name: 'name4', label: 'label4', value: 'value4'
289
+ },
290
+ ]
291
+ }
292
+ });
293
+
294
+ // check the current network before updating
295
+ expect(wrapper.vm.value.network).toStrictEqual([legacyName]);
296
+
297
+ wrapper.vm.syncNetworkValueForLegacyLabels();
298
+
299
+ expect(wrapper.vm.value.network).toStrictEqual([legacyValue]);
300
+ });
301
+ });
257
302
  });
@@ -558,6 +558,7 @@ export default {
558
558
  this.resetValueIfNecessary('network', content, options, true);
559
559
 
560
560
  set(this, 'networksResults', content);
561
+ this.syncNetworkValueForLegacyLabels();
561
562
  this.vappMode = this.getInitialVappMode(this.value);
562
563
  },
563
564
 
@@ -669,6 +670,21 @@ export default {
669
670
  }
670
671
  },
671
672
 
673
+ // Network labels have been updated to include the MOID.
674
+ // To ensure previously selected networks remain consistent with this change,
675
+ // we update the current network value to allow correct selection from the network list.
676
+ syncNetworkValueForLegacyLabels() {
677
+ const currentNetwork = this.value.network[0];
678
+
679
+ if (this.mode !== _CREATE && currentNetwork) {
680
+ const networkMatch = this.networks.find((network) => currentNetwork === network.name && currentNetwork !== network.label);
681
+
682
+ if (networkMatch) {
683
+ this.value.network = [networkMatch.value];
684
+ }
685
+ }
686
+ },
687
+
672
688
  mapPathOptionsToContent(pathOptions) {
673
689
  return (pathOptions || []).map((pathOption) => {
674
690
  return {