@rancher/shell 3.0.3 → 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.
@@ -2521,6 +2521,7 @@ fleet:
2521
2521
  placeholder: "Paste in one or more certificates, starting with -----BEGIN CERTIFICATE----"
2522
2522
  paths:
2523
2523
  label: Paths
2524
+ ariaLabel: Enter path for Git Repo
2524
2525
  placeholder: e.g. /directory/in/your/repo
2525
2526
  addLabel: Add Path
2526
2527
  empty: The root of the repo is used by default. Multiple different directories can be provided.
@@ -354,7 +354,7 @@ export default {
354
354
  <div class="mb-10">
355
355
  <template v-if="!hasCustomRemove">
356
356
  {{ t('promptRemove.attemptingToRemove', { type }) }} <span
357
- v-clean-html="resourceNames(names, t)"
357
+ v-clean-html="resourceNames(names, null, t)"
358
358
  />
359
359
  </template>
360
360
 
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { mapGetters } from 'vuex';
3
- import { defineAsyncComponent, useTemplateRef, onMounted, onBeforeUnmount } from 'vue';
3
+ import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from 'vue';
4
4
  import day from 'dayjs';
5
+ import semver from 'semver';
5
6
  import isEmpty from 'lodash/isEmpty';
6
7
  import { dasherize, ucFirst } from '@shell/utils/string';
7
8
  import { get, clone } from '@shell/utils/object';
@@ -24,6 +25,7 @@ import { getParent } from '@shell/utils/dom';
24
25
  import { FORMATTERS } from '@shell/components/SortableTable/sortable-config';
25
26
  import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
26
27
  import ActionMenu from '@shell/components/ActionMenuShell.vue';
28
+ import { getVersionInfo } from '@shell/utils/version';
27
29
 
28
30
  // Uncomment for table performance debugging
29
31
  // import tableDebug from './debug';
@@ -528,7 +530,7 @@ export default {
528
530
  },
529
531
  },
530
532
  setup(_props, { emit }) {
531
- const table = useTemplateRef('table');
533
+ const table = ref(null);
532
534
 
533
535
  const handleEnterKey = (event) => {
534
536
  if (event.key === 'Enter' && !event.target?.classList?.contains('checkbox-custom')) {
@@ -543,6 +545,8 @@ export default {
543
545
  onBeforeUnmount(() => {
544
546
  table.value.removeEventListener('keyup', handleEnterKey);
545
547
  });
548
+
549
+ return { table };
546
550
  },
547
551
 
548
552
  created() {
@@ -763,6 +767,12 @@ export default {
763
767
  });
764
768
 
765
769
  return rows;
770
+ },
771
+
772
+ featureDropdownMenu() {
773
+ const { fullVersion } = getVersionInfo(this.$store);
774
+
775
+ return semver.gte(semver.coerce(fullVersion).version, '2.11.0');
766
776
  }
767
777
  },
768
778
 
@@ -1487,11 +1497,27 @@ export default {
1487
1497
  :row="row.row"
1488
1498
  :index="i"
1489
1499
  >
1490
- <ActionMenu
1491
- :resource="row.row"
1492
- :data-testid="componentTestid + '-' + i + '-action-button'"
1493
- :button-aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
1494
- />
1500
+ <template v-if="featureDropdownMenu">
1501
+ <ActionMenu
1502
+ :resource="row.row"
1503
+ :data-testid="componentTestid + '-' + i + '-action-button'"
1504
+ :button-aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
1505
+ />
1506
+ </template>
1507
+ <template v-else>
1508
+ <ButtonMultiAction
1509
+ :id="`actionButton+${i}+${(row.row && row.row.name) ? row.row.name : ''}`"
1510
+ :ref="`actionButton${i}`"
1511
+ aria-haspopup="true"
1512
+ aria-expanded="false"
1513
+ :aria-label="t('sortableTable.tableActionsLabel', { resource: row?.row?.id || '' })"
1514
+ :data-testid="componentTestid + '-' + i + '-action-button'"
1515
+ :borderless="true"
1516
+ @click="handleActionButtonClick(i, $event)"
1517
+ @keyup.enter="handleActionButtonClick(i, $event)"
1518
+ @keyup.space="handleActionButtonClick(i, $event)"
1519
+ />
1520
+ </template>
1495
1521
  </slot>
1496
1522
  </td>
1497
1523
  </tr>
@@ -19,10 +19,16 @@ const STATUS = {
19
19
  }
20
20
  };
21
21
 
22
- const { status = 'success', label } = defineProps<{
23
- status?: 'success' | 'warning' | 'info' | 'error',
24
- label?: string
25
- }>();
22
+ withDefaults(
23
+ defineProps<{
24
+ status?: 'success' | 'warning' | 'info' | 'error',
25
+ label?: string
26
+ }>(),
27
+ {
28
+ status: 'success',
29
+ label: '',
30
+ }
31
+ );
26
32
 
27
33
  </script>
28
34
  <template>
@@ -98,9 +98,9 @@ export default {
98
98
  >
99
99
  <table>
100
100
  <tbody>
101
- <tr><td>{{ t('principal.name') }}: </td><td>{{ principal.name || principal.loginName }}</td></tr>
102
- <tr><td>{{ t('principal.loginName') }}: </td><td>{{ principal.loginName }}</td></tr>
103
- <tr><td>{{ t('principal.type') }}: </td><td>{{ principal.displayType }}</td></tr>
101
+ <tr><th>{{ t('principal.name') }}: </th><td>{{ principal.name || principal.loginName }}</td></tr>
102
+ <tr><th>{{ t('principal.loginName') }}: </th><td>{{ principal.loginName }}</td></tr>
103
+ <tr><th>{{ t('principal.type') }}: </th><td>{{ principal.displayType }}</td></tr>
104
104
  </tbody>
105
105
  </table>
106
106
  </div>
@@ -164,6 +164,12 @@ export default {
164
164
  grid-template-rows: auto math.div($size, 2);
165
165
  column-gap: 10px;
166
166
 
167
+ th {
168
+ text-align: left;
169
+ font-weight: normal;
170
+ padding-right: 10px;
171
+ }
172
+
167
173
  &.showLabels {
168
174
  grid-template-areas:
169
175
  "avatar name";
@@ -94,6 +94,10 @@ export default {
94
94
  // we only want functions in the rules array
95
95
  validator: (rules) => rules.every((rule) => ['function'].includes(typeof rule))
96
96
  },
97
+ a11yLabel: {
98
+ type: String,
99
+ default: '',
100
+ },
97
101
  },
98
102
  data() {
99
103
  const input = (Array.isArray(this.value) ? this.value : []).slice();
@@ -316,6 +320,7 @@ export default {
316
320
  :data-testid="`input-${idx}`"
317
321
  :placeholder="valuePlaceholder"
318
322
  :disabled="isView || disabled"
323
+ :aria-label="a11yLabel ? a11yLabel : undefined"
319
324
  @paste="onPaste(idx, $event)"
320
325
  >
321
326
  </slot>
@@ -336,6 +341,8 @@ export default {
336
341
  :disabled="isView"
337
342
  class="btn role-link"
338
343
  :data-testid="`remove-item-${idx}`"
344
+ :aria-label="`${_removeLabel} ${idx + 1}`"
345
+ role="button"
339
346
  @click="remove(row, idx)"
340
347
  >
341
348
  {{ _removeLabel }}
@@ -368,6 +375,8 @@ export default {
368
375
  class="btn role-tertiary add"
369
376
  :disabled="loading || disableAdd"
370
377
  data-testid="array-list-button"
378
+ :aria-label="_addLabel"
379
+ role="button"
371
380
  @click="add()"
372
381
  >
373
382
  <i
@@ -823,7 +823,7 @@ export default {
823
823
  type="button"
824
824
  role="button"
825
825
  :disabled="isView || isProtected(row.key) || disabled"
826
- :aria-label="removeLabel || t('generic.remove')"
826
+ :aria-label="`${removeLabel || t('generic.remove')} ${ i+1 }`"
827
827
  class="btn role-link"
828
828
  @click="remove(i)"
829
829
  >
@@ -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>
@@ -659,6 +659,7 @@ export default {
659
659
  :initial-empty-row="false"
660
660
  :value-placeholder="t('fleet.gitRepo.paths.placeholder')"
661
661
  :add-label="t('fleet.gitRepo.paths.addLabel')"
662
+ :a11y-label="t('fleet.gitRepo.paths.ariaLabel')"
662
663
  :add-icon="'icon-plus'"
663
664
  :protip="t('fleet.gitRepo.paths.empty')"
664
665
  />
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "3.0.3",
3
+ "version": "3.0.4",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -51,7 +51,7 @@
51
51
  "add": "2.0.6",
52
52
  "ansi_up": "5.0.0",
53
53
  "axios-retry": "3.1.9",
54
- "axios": "0.21.4",
54
+ "axios": "1.8.4",
55
55
  "babel-eslint": "10.1.0",
56
56
  "babel-plugin-module-resolver": "4.0.0",
57
57
  "babel-preset-vue": "2.0.2",
package/pages/about.vue CHANGED
@@ -119,8 +119,12 @@ export default {
119
119
  <table>
120
120
  <thead>
121
121
  <tr>
122
- <th>{{ t('about.versions.component') }}</th>
123
- <th>{{ t('about.versions.version') }}</th>
122
+ <th class="custom-th">
123
+ {{ t('about.versions.component') }}
124
+ </th>
125
+ <th class="custom-th">
126
+ {{ t('about.versions.version') }}
127
+ </th>
124
128
  </tr>
125
129
  </thead>
126
130
  <tr v-if="rancherVersion">
@@ -210,11 +214,11 @@ export default {
210
214
  v-for="(d, i) in downloadImageList"
211
215
  :key="i"
212
216
  >
213
- <td>
217
+ <th>
214
218
  <div class="os">
215
219
  <i :class="`icon ${d.icon} mr-5`" /> {{ t(d.label) }}
216
220
  </div>
217
- </td>
221
+ </th>
218
222
  <td>
219
223
  <a
220
224
  v-if="d.imageList"
@@ -241,11 +245,11 @@ export default {
241
245
  :key="i"
242
246
  class="link"
243
247
  >
244
- <td>
248
+ <th>
245
249
  <div class="os">
246
250
  <i :class="`icon ${d.icon} mr-5`" /> {{ t(d.label) }}
247
251
  </div>
248
- </td>
252
+ </th>
249
253
  <td>
250
254
  <a
251
255
  v-if="d.cliLink"
@@ -273,7 +277,7 @@ export default {
273
277
  overflow: hidden;
274
278
  border-radius: var(--border-radius);
275
279
 
276
- tr > td:first-of-type {
280
+ tr > th:first-of-type {
277
281
  width: 20%;
278
282
  }
279
283
 
@@ -284,11 +288,15 @@ export default {
284
288
  text-align: left;
285
289
  }
286
290
 
287
- th {
291
+ th.custom-th {
288
292
  background-color: var(--sortable-table-top-divider);
289
293
  border-bottom: 1px solid var(--sortable-table-top-divider);
290
294
  }
291
295
 
296
+ th:not(.custom-th) {
297
+ font-weight: normal;
298
+ }
299
+
292
300
  a {
293
301
  cursor: pointer;
294
302
  }
@@ -71,7 +71,7 @@ export default {
71
71
  <div class="mt-10">
72
72
  <div class="mb-10">
73
73
  {{ t('promptRemove.attemptingToRemove', { type }) }} <span
74
- v-clean-html="resourceNames(names, t)"
74
+ v-clean-html="resourceNames(names, null, t)"
75
75
  class="description"
76
76
  />
77
77
  </div>
@@ -21,7 +21,7 @@ export default {
21
21
 
22
22
  <template>
23
23
  <div>
24
- {{ t('promptRemove.attemptingToRemove', { type }) }} <span v-clean-html="resourceNames(names, t)" />
24
+ {{ t('promptRemove.attemptingToRemove', { type }) }} <span v-clean-html="resourceNames(names, null, t)" />
25
25
  <div
26
26
  v-if="info"
27
27
  class="text info mb-10 mt-20"
@@ -94,7 +94,7 @@ export default {
94
94
  <template v-if="!canSeeProjectlessNamespaces">
95
95
  <span class="delete-warning"> {{ t('promptRemove.willDeleteAssociatedNamespaces') }}</span> <br>
96
96
  <div
97
- v-clean-html="resourceNames(names, t)"
97
+ v-clean-html="resourceNames(names, null, t)"
98
98
  class="mt-10"
99
99
  />
100
100
  </template>
@@ -108,7 +108,7 @@ export default {
108
108
  :label="t('promptRemove.deleteAssociatedNamespaces')"
109
109
  />
110
110
  <div class="mt-10 ml-20">
111
- <span v-clean-html="resourceNames(names, t)" />
111
+ <span v-clean-html="resourceNames(names, null, t)" />
112
112
  </div>
113
113
  </div>
114
114
  </div>
@@ -22,7 +22,7 @@ export default {
22
22
  <template>
23
23
  <div>
24
24
  {{ t('promptRemove.attemptingToRemove', { type }) }} <span
25
- v-clean-html="resourceNames(names, t)"
25
+ v-clean-html="resourceNames(names, null, t)"
26
26
  />
27
27
  <div
28
28
  v-if="info"
@@ -98,7 +98,7 @@ export default {
98
98
  <div class="mt-10">
99
99
  <div class="mb-30">
100
100
  {{ t('promptRemove.attemptingToRemove', { type }) }} <span
101
- v-clean-html="resourceNames(names, t)"
101
+ v-clean-html="resourceNames(names, null, t)"
102
102
  class="body"
103
103
  />
104
104
  </div>
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { defineComponent, onMounted, onBeforeUnmount, useTemplateRef } from 'vue';
2
+ import { defineComponent, onMounted, onBeforeUnmount, ref } from 'vue';
3
3
 
4
4
  type StateType = boolean | 'true' | 'false' | undefined;
5
5
 
@@ -34,7 +34,7 @@ export default defineComponent({
34
34
  emits: ['update:value'],
35
35
 
36
36
  setup() {
37
- const switchChrome = useTemplateRef<HTMLElement>('switchChrome');
37
+ const switchChrome = ref<HTMLElement | null>(null);
38
38
  const focus = () => {
39
39
  switchChrome.value?.classList.add('focus');
40
40
  };
@@ -43,7 +43,7 @@ export default defineComponent({
43
43
  switchChrome.value?.classList.remove('focus');
44
44
  };
45
45
 
46
- const switchInput = useTemplateRef<HTMLInputElement>('switchInput');
46
+ const switchInput = ref<HTMLInputElement | null>(null);
47
47
 
48
48
  onMounted(() => {
49
49
  switchInput.value?.addEventListener('focus', focus);
@@ -20,7 +20,7 @@
20
20
  * </template>
21
21
  * </rc-dropdown>
22
22
  */
23
- import { useTemplateRef } from 'vue';
23
+ import { ref } from 'vue';
24
24
  import { useClickOutside } from '@shell/composables/useClickOutside';
25
25
  import { useDropdownContext } from '@components/RcDropdown/useDropdownContext';
26
26
 
@@ -42,8 +42,8 @@ const {
42
42
 
43
43
  provideDropdownContext();
44
44
 
45
- const popperContainer = useTemplateRef<HTMLElement>('popperContainer');
46
- const dropdownTarget = useTemplateRef<HTMLElement>('dropdownTarget');
45
+ const popperContainer = ref(null);
46
+ const dropdownTarget = ref(null);
47
47
 
48
48
  useClickOutside(dropdownTarget, () => showMenu(false));
49
49
 
@@ -8,8 +8,10 @@ import {
8
8
  import { RcDropdownMenuComponentProps, DropdownOption } from './types';
9
9
  import IconOrSvg from '@shell/components/IconOrSvg';
10
10
 
11
- // eslint-disable-next-line vue/no-setup-props-destructure
12
- const { buttonRole = 'primary', buttonSize = '' } = defineProps<RcDropdownMenuComponentProps>();
11
+ withDefaults(defineProps<RcDropdownMenuComponentProps>(), {
12
+ buttonRole: 'primary',
13
+ buttonSize: undefined,
14
+ });
13
15
 
14
16
  const emit = defineEmits(['update:open', 'select']);
15
17
 
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * A button that opens a menu. Used in conjunction with `RcDropdown.vue`.
4
4
  */
5
- import { inject, onMounted, useTemplateRef } from 'vue';
5
+ import { inject, onMounted, ref } from 'vue';
6
6
  import { RcButton, RcButtonType } from '@components/RcButton';
7
7
  import { DropdownContext, defaultContext } from './types';
8
8
 
@@ -13,7 +13,7 @@ const {
13
13
  handleKeydown,
14
14
  } = inject<DropdownContext>('dropdownContext') || defaultContext;
15
15
 
16
- const dropdownTrigger = useTemplateRef<RcButtonType>('dropdownTrigger');
16
+ const dropdownTrigger = ref<RcButtonType | null>(null);
17
17
 
18
18
  onMounted(() => {
19
19
  registerTrigger(dropdownTrigger.value);
@@ -4361,7 +4361,7 @@ export function random32(count: any): number | number[];
4361
4361
  export function randomStr(length?: number, chars?: string): any;
4362
4362
  export function formatPercent(value: any, maxPrecision?: number): string;
4363
4363
  export function pluralize(str: any): any;
4364
- export function resourceNames(names: any, t: any, options?: {}): any;
4364
+ export function resourceNames(names: any, plusMore: any, t: any, endString: any): any;
4365
4365
  export function indent(lines: any, count?: number, token?: string, afterRegex?: any): any;
4366
4366
  export function decamelize(str: any): any;
4367
4367
  export function dasherize(str: any): any;
@@ -81,9 +81,9 @@ describe('fx: resourceNames', () => {
81
81
  it.each(args)(`having: %p`, (
82
82
  names,
83
83
  expectation,
84
- options,
84
+ options: any,
85
85
  ) => {
86
- const result = resourceNames(names, t, options);
86
+ const result = resourceNames(names, options.plusMore, t, options.endString);
87
87
 
88
88
  expect(result).toStrictEqual(expectation);
89
89
  });
package/utils/string.js CHANGED
@@ -151,11 +151,9 @@ export function pluralize(str) {
151
151
  }
152
152
  }
153
153
 
154
- export function resourceNames(names, t, options = {}) {
154
+ export function resourceNames(names, plusMore, t, endString) {
155
155
  const MAX_NAMES_COUNT = 5;
156
156
 
157
- let { plusMore, endString } = options;
158
-
159
157
  // plusMore default value
160
158
  if (!plusMore) {
161
159
  plusMore = t('promptRemove.andOthers', { count: names.length > MAX_NAMES_COUNT ? names.length - MAX_NAMES_COUNT : 0 });