@rancher/shell 3.0.2-rc.2 → 3.0.2-rc.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 (172) hide show
  1. package/assets/styles/base/_basic.scss +7 -8
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_form.scss +2 -1
  4. package/assets/styles/global/_tooltip.scss +2 -2
  5. package/assets/styles/themes/_dark.scss +15 -3
  6. package/assets/styles/themes/_light.scss +7 -2
  7. package/assets/styles/vendor/vue-select.scss +4 -0
  8. package/assets/translations/en-us.yaml +66 -9
  9. package/assets/translations/zh-hans.yaml +2 -3
  10. package/components/AppModal.vue +50 -0
  11. package/components/BannerGraphic.vue +0 -42
  12. package/components/ButtonMultiAction.vue +1 -1
  13. package/components/Carousel.vue +88 -74
  14. package/components/CommunityLinks.vue +6 -1
  15. package/components/CopyToClipboardText.vue +3 -0
  16. package/components/Dialog.vue +20 -1
  17. package/components/GrowlManager.vue +9 -2
  18. package/components/LocaleSelector.vue +8 -1
  19. package/components/PaginatedResourceTable.vue +4 -7
  20. package/components/ProgressBarMulti.vue +14 -0
  21. package/components/PromptChangePassword.vue +3 -0
  22. package/components/Questions/Reference.vue +57 -28
  23. package/components/ResourceDetail/Masthead.vue +1 -1
  24. package/components/SelectIconGrid.vue +12 -1
  25. package/components/SideNav.vue +12 -38
  26. package/components/SortableTable/index.vue +1 -0
  27. package/components/Tabbed/index.vue +9 -1
  28. package/components/YamlEditor.vue +1 -0
  29. package/components/__tests__/Carousel.test.ts +56 -27
  30. package/components/auth/Principal.vue +5 -3
  31. package/components/fleet/FleetClusters.vue +82 -1
  32. package/components/fleet/FleetRepos.vue +13 -30
  33. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  34. package/components/form/ChangePassword.vue +2 -0
  35. package/components/form/ColorInput.vue +24 -1
  36. package/components/form/FileSelector.vue +2 -0
  37. package/components/form/KeyValue.vue +230 -160
  38. package/components/form/LabeledSelect.vue +2 -2
  39. package/components/form/PlusMinus.vue +14 -2
  40. package/components/form/ResourceLabeledSelect.vue +13 -53
  41. package/components/form/ResourceSelector.vue +1 -0
  42. package/components/form/ResourceTabs/index.vue +79 -36
  43. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
  44. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
  45. package/components/form/SSHKnownHosts/index.vue +101 -0
  46. package/components/form/SecretSelector.vue +2 -2
  47. package/components/form/Select.vue +1 -1
  48. package/components/form/SelectOrCreateAuthSecret.vue +43 -11
  49. package/components/form/__tests__/KeyValue.test.ts +1 -1
  50. package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
  51. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  52. package/components/formatter/FleetSummaryGraph.vue +6 -7
  53. package/components/formatter/WorkloadHealthScale.vue +7 -0
  54. package/components/nav/Group.vue +30 -4
  55. package/components/nav/Header.vue +82 -114
  56. package/components/nav/HeaderPageActionMenu.vue +27 -131
  57. package/components/nav/NamespaceFilter.vue +1 -1
  58. package/components/nav/Type.vue +15 -0
  59. package/composables/focusTrap.ts +68 -0
  60. package/config/home-links.js +21 -13
  61. package/config/labels-annotations.js +2 -0
  62. package/config/page-actions.js +1 -0
  63. package/config/pagination-table-headers.js +15 -1
  64. package/config/product/explorer.js +7 -17
  65. package/config/table-headers.js +6 -0
  66. package/config/version.js +5 -1
  67. package/core/plugin.ts +41 -1
  68. package/core/plugins.js +125 -72
  69. package/core/types-provisioning.ts +91 -2
  70. package/core/types.ts +55 -0
  71. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  72. package/detail/catalog.cattle.io.app.vue +1 -1
  73. package/detail/fleet.cattle.io.cluster.vue +3 -3
  74. package/detail/namespace.vue +13 -19
  75. package/detail/networking.k8s.io.ingress.vue +13 -53
  76. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  77. package/detail/secret.vue +25 -0
  78. package/detail/workload/index.vue +3 -3
  79. package/dialog/AddCustomBadgeDialog.vue +5 -1
  80. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  81. package/edit/auth/ldap/config.vue +24 -0
  82. package/edit/auth/saml.vue +8 -6
  83. package/edit/fleet.cattle.io.gitrepo.vue +34 -23
  84. package/edit/logging-flow/index.vue +4 -19
  85. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  86. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  87. package/edit/provisioning.cattle.io.cluster/index.vue +27 -8
  88. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  89. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  90. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  91. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  92. package/edit/secret/index.vue +1 -1
  93. package/edit/secret/ssh.vue +21 -3
  94. package/edit/service.vue +1 -2
  95. package/list/networking.k8s.io.ingress.vue +1 -1
  96. package/list/node.vue +15 -8
  97. package/list/persistentvolume.vue +12 -4
  98. package/list/provisioning.cattle.io.cluster.vue +1 -0
  99. package/list/service.vue +1 -1
  100. package/list/workload.vue +4 -0
  101. package/mixins/chart.js +4 -1
  102. package/models/catalog.cattle.io.app.js +3 -1
  103. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  104. package/models/fleet.cattle.io.bundle.js +0 -11
  105. package/models/fleet.cattle.io.cluster.js +17 -1
  106. package/models/fleet.cattle.io.gitrepo.js +88 -52
  107. package/models/provisioning.cattle.io.cluster.js +36 -1
  108. package/models/secret.js +5 -0
  109. package/models/service.js +1 -0
  110. package/models/workload.js +19 -1
  111. package/package.json +5 -4
  112. package/pages/account/index.vue +4 -0
  113. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  114. package/pages/c/_cluster/explorer/ConfigBadge.vue +4 -2
  115. package/pages/c/_cluster/explorer/index.vue +13 -6
  116. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  117. package/pages/c/_cluster/fleet/index.vue +75 -89
  118. package/pages/c/_cluster/settings/links.vue +2 -2
  119. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
  120. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
  121. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
  122. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
  123. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
  124. package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
  125. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
  126. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
  127. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
  128. package/pages/c/_cluster/uiplugins/index.vue +50 -12
  129. package/pages/diagnostic.vue +17 -15
  130. package/pages/home.vue +32 -6
  131. package/plugins/clean-html.js +50 -0
  132. package/plugins/dashboard-store/resource-class.js +4 -0
  133. package/plugins/plugin.js +54 -49
  134. package/plugins/steve/mutations.js +1 -1
  135. package/plugins/steve/steve-class.js +8 -0
  136. package/plugins/steve/steve-pagination-utils.ts +3 -1
  137. package/rancher-components/Accordion/Accordion.vue +4 -4
  138. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  139. package/rancher-components/Card/Card.vue +12 -0
  140. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  141. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  142. package/rancher-components/Form/LabeledInput/LabeledInput.vue +19 -1
  143. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  144. package/rancher-components/RcButton/RcButton.vue +90 -0
  145. package/rancher-components/RcButton/index.ts +2 -0
  146. package/rancher-components/RcButton/types.ts +17 -0
  147. package/rancher-components/RcDropdown/RcDropdown.vue +122 -0
  148. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  149. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  150. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +42 -0
  151. package/rancher-components/RcDropdown/index.ts +4 -0
  152. package/rancher-components/RcDropdown/types.ts +22 -0
  153. package/rancher-components/RcDropdown/useDropdownCollection.ts +46 -0
  154. package/rancher-components/RcDropdown/useDropdownContext.ts +110 -0
  155. package/scripts/test-plugins-build.sh +2 -0
  156. package/scripts/typegen.sh +2 -0
  157. package/store/catalog.js +1 -1
  158. package/tsconfig.json +2 -1
  159. package/types/components/paginatedResourceTable.ts +25 -0
  160. package/types/components/resourceLabeledSelect.ts +48 -0
  161. package/types/resources/fleet.d.ts +17 -0
  162. package/types/shell/index.d.ts +61 -0
  163. package/utils/auth.js +5 -1
  164. package/utils/cluster.js +106 -0
  165. package/utils/fleet.ts +35 -3
  166. package/utils/ingress.ts +64 -0
  167. package/utils/uiplugins.ts +56 -44
  168. package/utils/validators/cron-schedule.js +7 -2
  169. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  170. package/utils/validators/formRules/index.ts +20 -5
  171. package/vue.config.js +1 -1
  172. package/components/RelatedWorkloadsTable.vue +0 -50
@@ -43,24 +43,29 @@ export default {
43
43
  activeItemId: 0,
44
44
  autoScroll: true,
45
45
  autoScrollSlideInterval: null,
46
+ isTransitionning: false, // prevents showing empty spaces caused by aggressive clicking
47
+ shouldDisableTransition: false // smoothes the move from the first/last slides to the previous/next slide
46
48
  };
47
49
  },
48
50
 
49
51
  computed: {
50
52
  ...mapGetters(['clusterId']),
51
53
  trackStyle() {
52
- let sliderItem = ( this.activeItemId + 1) * 100 / (this.slider.length + 2);
53
- const width = 60 * (this.slider.length + 2);
54
-
55
54
  if (this.slider.length === 1) {
56
- sliderItem = 0;
55
+ return `
56
+ width: 100%;
57
+ left: 0%;
58
+ `;
57
59
  }
58
60
 
59
- return `transform: translateX(-${ sliderItem }%); width: ${ width }%`;
60
- },
61
+ const width = 60 * (this.slider.length + 2);
62
+ const left = -(40 + this.activeItemId * 60);
61
63
 
62
- test() {
63
- return 'test';
64
+ return `
65
+ width: ${ width }%;
66
+ left: ${ left }%;
67
+ transition: ${ this.shouldDisableTransition ? 'none' : '700ms ease-in-out' };
68
+ `;
64
69
  }
65
70
  },
66
71
 
@@ -77,10 +82,14 @@ export default {
77
82
  },
78
83
 
79
84
  nextPrev(direction) {
80
- this.autoScroll = false;
81
- const slideTrack = document.getElementById('slide-track');
85
+ if (this.isTransitionning) {
86
+ return;
87
+ }
82
88
 
83
- slideTrack.style.transition = `transform 450ms ease-in-out`;
89
+ this.isTransitionning = true;
90
+ this.autoScroll = false;
91
+ this.shouldDisableTransition = false;
92
+ const slideTrack = this.$refs.slider;
84
93
 
85
94
  direction !== 'prev' ? (this.activeItemId++) : (this.activeItemId--);
86
95
 
@@ -88,21 +97,27 @@ export default {
88
97
  },
89
98
 
90
99
  slideTransition() {
91
- const slideTrack = document.getElementById('slide-track');
92
100
  const slidesArray = this.slider.length + 2;
93
101
 
94
102
  if (this.activeItemId === -1) {
95
- slideTrack.style.transition = 'none';
103
+ this.shouldDisableTransition = true;
96
104
  this.activeItemId = this.slider.length - 1;
97
105
  }
106
+
98
107
  if (this.activeItemId === slidesArray - 2) {
99
- slideTrack.style.transition = 'none';
108
+ this.shouldDisableTransition = true;
100
109
  this.activeItemId = 0;
101
110
  }
111
+
112
+ this.isTransitionning = false;
102
113
  },
103
114
 
104
115
  autoScrollSlide() {
105
- if (this.activeItemId < (this.slider.length + 1) && this.autoScroll ) {
116
+ if (!this.autoScroll) {
117
+ return;
118
+ }
119
+
120
+ if (this.activeItemId < (this.slider.length + 1)) {
106
121
  this.activeItemId++;
107
122
  }
108
123
 
@@ -120,23 +135,21 @@ export default {
120
135
  },
121
136
 
122
137
  mounted() {
123
- const slideTrack = document.getElementById('slide-track');
138
+ const slideTrack = this.$refs.slider;
124
139
 
125
- if (this.slider.length === 1) {
126
- slideTrack.style = 'transform:translateX(0%); width:100%; left:0';
127
- } else {
128
- const node = document.getElementById('slide0');
140
+ if (this.slider.length > 1) {
141
+ const firstSlide = this.$refs['slide0']?.[0];
129
142
 
130
- if (node) {
131
- const clone = node.cloneNode(true);
143
+ if (firstSlide) {
144
+ const clone = firstSlide.cloneNode(true);
132
145
 
133
146
  slideTrack.appendChild(clone);
134
147
  }
135
148
 
136
- const nodeLast = document.getElementById(`slide${ this.slider.length - 1 }`);
149
+ const lastSlide = this.$refs[`slide${ this.slider.length - 1 }`]?.[0];
137
150
 
138
- if (nodeLast) {
139
- const cloneLast = nodeLast.cloneNode(true);
151
+ if (lastSlide) {
152
+ const cloneLast = lastSlide.cloneNode(true);
140
153
 
141
154
  slideTrack.insertBefore(cloneLast, slideTrack.children[0]);
142
155
  }
@@ -159,7 +172,7 @@ export default {
159
172
  <template>
160
173
  <div
161
174
  class="slider"
162
- :class="{'disable': sliders.length === 1}"
175
+ :class="{'disabled': sliders.length === 1}"
163
176
  >
164
177
  <div
165
178
  id="slide-track"
@@ -170,8 +183,8 @@ export default {
170
183
  <component
171
184
  :is="asLink ? 'a' : 'div'"
172
185
  v-for="(slide, i) in sliders"
173
- :id="`slide` + i"
174
- ref="slide"
186
+ :id="`slide${i}`"
187
+ :ref="`slide${i}`"
175
188
  :key="get(slide, keyField)"
176
189
  class="slide"
177
190
  :class="{'singleSlide': sliders.length === 1}"
@@ -187,7 +200,7 @@ export default {
187
200
  <div class="slide-content-right">
188
201
  <BadgeState
189
202
  :label="slide.repoName"
190
- color="slider-badge mb-20"
203
+ color="slide-badge mb-20"
191
204
  />
192
205
  <h1>{{ slide.chartNameDisplay }}</h1>
193
206
  <p>{{ slide.chartDescription }}</p>
@@ -196,33 +209,49 @@ export default {
196
209
  </component>
197
210
  </div>
198
211
  <div
212
+ role="button"
213
+ class="prev"
214
+ :aria-label="t('carousel.previous')"
215
+ :aria-disabled="sliders.length === 1"
216
+ :class="{'disabled': sliders.length === 1}"
217
+ tabindex="0"
218
+ @click="nextPrev('prev')"
219
+ @keyup.enter.space="nextPrev('prev')"
220
+ >
221
+ <i
222
+ class="icon icon-chevron-left icon-4x"
223
+ />
224
+ </div>
225
+ <div
226
+ role="button"
227
+ class="next"
228
+ :aria-label="t('carousel.next')"
229
+ :aria-disabled="sliders.length === 1"
230
+ :class="{'disabled': sliders.length === 1}"
231
+ tabindex="0"
232
+ @click="nextPrev('next')"
233
+ @keyup.enter.space="nextPrev('next')"
234
+ >
235
+ <i
236
+ class="icon icon-chevron-right icon-4x"
237
+ />
238
+ </div>
239
+ <div
240
+ v-if="sliders.length > 1"
199
241
  class="controls"
200
- :class="{'disable': sliders.length === 1}"
201
242
  >
202
243
  <div
203
244
  v-for="(slide, i) in slider"
204
245
  :key="i"
205
246
  class="control-item"
206
247
  :class="{'active': activeItemId === i}"
248
+ role="button"
249
+ tabindex="0"
250
+ :aria-label="t('carousel.controlItem', { number: i+1 })"
207
251
  @click="scrollSlide(i, slider.length)"
252
+ @keyup.enter.space="scrollSlide(i, slider.length)"
208
253
  />
209
254
  </div>
210
- <div
211
- ref="prev"
212
- class="prev"
213
- :class="{'disable': sliders.length === 1}"
214
- @click="nextPrev('prev')"
215
- >
216
- <i class="icon icon-chevron-left icon-4x" />
217
- </div>
218
- <div
219
- ref="next"
220
- class="next"
221
- :class="{'disable': sliders.length === 1}"
222
- @click="nextPrev('next')"
223
- >
224
- <i class="icon icon-chevron-right icon-4x" />
225
- </div>
226
255
  </div>
227
256
  </template>
228
257
 
@@ -234,37 +263,21 @@ export default {
234
263
  place-items: center;
235
264
  overflow: hidden;
236
265
  margin-bottom: 30px;
237
- // min-width: 700px;
266
+ height: 245px;
238
267
 
239
- &.disable::before,
240
- &.disable::after {
268
+ &.disabled::before,
269
+ &.disabled::after {
241
270
  display: none;
242
271
  }
243
-
244
- &.disable:hover {
245
- .prev,
246
- .next {
247
- display: none;
248
- }
249
- }
250
-
251
- &:hover {
252
- .prev,
253
- .next {
254
- display: block;
255
- }
256
- }
257
272
  }
258
273
 
259
274
  .slide-track {
260
275
  display: flex;
261
- animation: scrolls 10s ;
262
- position: relative;
263
- transition: 1s ease-in-out;
264
- left: 21%;
276
+ position: absolute;
277
+ top: 0;
265
278
  }
266
279
 
267
- .slider-badge {
280
+ .slide-badge {
268
281
  background: var(--app-partner-accent);
269
282
  color: var(--body-bg);
270
283
  }
@@ -329,7 +342,7 @@ export default {
329
342
  .slider::before {
330
343
  left: 0;
331
344
  top: 0;
332
- &.disable {
345
+ &.disabled {
333
346
  display: none;
334
347
  }
335
348
  }
@@ -340,15 +353,13 @@ export default {
340
353
  }
341
354
 
342
355
  .controls {
356
+ position: absolute;
357
+ bottom: 0;
343
358
  width: 100%;
344
359
  display: flex;
345
360
  justify-content: center;
346
361
  margin-top: 10px;
347
362
 
348
- &.disable {
349
- display: none;
350
- }
351
-
352
363
  .control-item {
353
364
  width: 10px;
354
365
  height: 10px;
@@ -367,13 +378,16 @@ export default {
367
378
  position: absolute;
368
379
  z-index: 20;
369
380
  top: 90px;
370
- display: none;
371
381
  cursor: pointer;
372
382
 
373
383
  &.disabled .icon {
374
384
  color: var(--disabled-bg);
375
385
  cursor: not-allowed;
376
386
  }
387
+
388
+ .icon:focus-visible {
389
+ @include focus-outline;
390
+ }
377
391
  }
378
392
 
379
393
  .next {
@@ -7,6 +7,7 @@ import { SETTING } from '@shell/config/settings';
7
7
  import { mapGetters } from 'vuex';
8
8
  import { isRancherPrime } from '@shell/config/version';
9
9
  import { fetchLinks } from '@shell/config/home-links';
10
+ import { processLink } from '@shell/plugins/clean-html';
10
11
 
11
12
  // i18n-ignore footer.wechat.title, footer.wechat.modalText, footer.wechat.modalText2
12
13
  export default {
@@ -77,7 +78,11 @@ export default {
77
78
  all.push(...this.links.defaults.filter((link) => link.enabled));
78
79
  }
79
80
 
80
- return all;
81
+ // Process the links
82
+ return all.map((item) => ({
83
+ ...item,
84
+ value: processLink(item.value)
85
+ }));
81
86
  }
82
87
  },
83
88
  methods: {
@@ -48,9 +48,12 @@ export default {
48
48
  <a
49
49
  v-if="text"
50
50
  class="copy-to-clipboard-text"
51
+ role="button"
52
+ :aria-label="t('generic.copyToClipboard')"
51
53
  :class="{ 'copied': copied, 'plain': plain}"
52
54
  href="#"
53
55
  @click="clicked"
56
+ @keyup.space="clicked"
54
57
  >
55
58
  {{ text }} <i
56
59
  class="icon"
@@ -1,6 +1,6 @@
1
1
  <script>
2
2
  import AsyncButton from '@shell/components/AsyncButton';
3
- import AppModal from '@shell/components/AppModal.vue';
3
+ import AppModal, { DEFAULT_ITERABLE_NODE_SELECTOR } from '@shell/components/AppModal.vue';
4
4
 
5
5
  export default {
6
6
  emits: ['okay', 'closed'],
@@ -21,6 +21,22 @@ export default {
21
21
  mode: {
22
22
  type: String,
23
23
  default: '',
24
+ },
25
+
26
+ /**
27
+ * forcefully set return focus element based on this selector
28
+ */
29
+ returnFocusSelector: {
30
+ type: String,
31
+ default: '',
32
+ },
33
+
34
+ /**
35
+ * will return focus to the first iterable node of this container select
36
+ */
37
+ returnFocusFirstIterableNodeSelector: {
38
+ type: String,
39
+ default: DEFAULT_ITERABLE_NODE_SELECTOR,
24
40
  }
25
41
  },
26
42
 
@@ -60,6 +76,9 @@ export default {
60
76
  :name="name"
61
77
  height="auto"
62
78
  :scrollable="true"
79
+ :trigger-focus-trap="true"
80
+ :return-focus-selector="returnFocusSelector"
81
+ :return-focus-first-iterable-node-selector="returnFocusFirstIterableNodeSelector"
63
82
  @close="closeDialog(false)"
64
83
  @before-open="beforeOpen"
65
84
  >
@@ -87,7 +87,10 @@ export default {
87
87
  <div class="growl-list">
88
88
  <div
89
89
  v-for="(growl, idx) in stack"
90
- :key="idx"
90
+ :key="growl.id"
91
+ role="alert"
92
+ :aria-labelledby="`growl-title-${ growl.id }`"
93
+ :aria-describedby="`growl-message-${ growl.id }`"
91
94
  :data-testid="`growl-list-item-${idx}`"
92
95
  :class="{'growl': true, ['bg-'+growl.color]: true}"
93
96
  >
@@ -103,11 +106,15 @@ export default {
103
106
  class="close hand icon icon-close"
104
107
  @click="close(growl)"
105
108
  />
106
- <div class="growl-text-title">
109
+ <div
110
+ :id="`growl-title-${ growl.id }`"
111
+ class="growl-text-title"
112
+ >
107
113
  {{ growl.title }}
108
114
  </div>
109
115
  <p
110
116
  v-if="growl.message"
117
+ :id="`growl-message-${ growl.id }`"
111
118
  :class="{ 'has-title': !!growl.title }"
112
119
  >
113
120
  {{ growl.message }}
@@ -11,6 +11,10 @@ export default {
11
11
  mode: {
12
12
  type: String,
13
13
  default: ''
14
+ },
15
+ showIcon: {
16
+ type: Boolean,
17
+ default: true
14
18
  }
15
19
  },
16
20
 
@@ -89,7 +93,10 @@ export default {
89
93
  class="locale-chooser"
90
94
  >
91
95
  {{ selectedLocaleLabel }}
92
- <i class="icon icon-fw icon-sort-down" />
96
+ <i
97
+ v-if="showIcon"
98
+ class="icon icon-fw icon-sort-down"
99
+ />
93
100
  </a>
94
101
  <template #popper>
95
102
  <ul
@@ -2,13 +2,6 @@
2
2
  import { defineComponent } from 'vue';
3
3
  import ResourceFetch from '@shell/mixins/resource-fetch';
4
4
  import ResourceTable from '@shell/components/ResourceTable.vue';
5
- import { StorePaginationResult } from '@shell/types/store/pagination.types';
6
-
7
- export type FetchSecondaryResourcesOpts = { canPaginate: boolean }
8
- export type FetchSecondaryResources = (opts: FetchSecondaryResourcesOpts) => Promise<any>
9
-
10
- export type FetchPageSecondaryResourcesOpts = { canPaginate: boolean, force: boolean, page: any[], pagResult: StorePaginationResult }
11
- export type FetchPageSecondaryResources = (opts: FetchPageSecondaryResourcesOpts) => Promise<any>
12
5
 
13
6
  /**
14
7
  * This is meant to enable ResourceList like capabilities outside of List pages / components
@@ -57,6 +50,8 @@ export default defineComponent({
57
50
  * Information may be required from resources other than the primary one shown per row
58
51
  *
59
52
  * This will fetch them ALL and will be run in a non-server-side pagination world
53
+ *
54
+ * of type PagTableFetchSecondaryResources
60
55
  */
61
56
  fetchSecondaryResources: {
62
57
  type: Function,
@@ -69,6 +64,8 @@ export default defineComponent({
69
64
  * This will fetch only those relevant to the current page using server-side pagination based filters
70
65
  *
71
66
  * called from shell/mixins/resource-fetch-api-pagination.js
67
+ *
68
+ * of type PagTableFetchPageSecondaryResources
72
69
  */
73
70
  fetchPageSecondaryResources: {
74
71
  type: Function,
@@ -90,6 +90,19 @@ export default {
90
90
 
91
91
  return out.filter((obj) => obj.percent);
92
92
  },
93
+ ariaLabelText() {
94
+ if (Array.isArray(this.values) && this.values.length) {
95
+ let ariaLabel = '';
96
+
97
+ this.values.forEach((val) => {
98
+ ariaLabel += `${ val.value } ${ val.value === 1 ? 'item' : 'items' } ${ val.label }`;
99
+ });
100
+
101
+ return ariaLabel;
102
+ }
103
+
104
+ return '';
105
+ }
93
106
  }
94
107
  };
95
108
 
@@ -108,6 +121,7 @@ function toPercent(value, min, max) {
108
121
  <div
109
122
  v-trim-whitespace
110
123
  :class="{progress: true, multi: pieces.length > 1}"
124
+ :aria-label="ariaLabelText"
111
125
  >
112
126
  <div
113
127
  v-for="(piece, idx) of pieces"
@@ -43,6 +43,7 @@ export default {
43
43
  name="password-modal"
44
44
  :width="500"
45
45
  :height="465"
46
+ :trigger-focus-trap="true"
46
47
  @close="show(false)"
47
48
  >
48
49
  <Card
@@ -68,6 +69,8 @@ export default {
68
69
  <!-- type reset is required by lastpass -->
69
70
  <button
70
71
  class="btn role-secondary"
72
+ role="button"
73
+ :aria-label="t('changePassword.cancel')"
71
74
  type="reset"
72
75
  @click="show(false)"
73
76
  >
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { LabeledInput } from '@components/Form/LabeledInput';
3
- import LabeledSelect from '@shell/components/form/LabeledSelect';
4
- import { filterBy } from '@shell/utils/array';
3
+ import ResourceLabeledSelect from '@shell/components/form/ResourceLabeledSelect.vue';
4
+ import { PaginationParamFilter } from '@shell/types/store/pagination.types';
5
+
5
6
  import { PVC, STORAGE_CLASS } from '@shell/config/types';
6
7
  import Question from './Question';
7
8
 
@@ -14,7 +15,7 @@ const LEGACY_MAP = {
14
15
  export default {
15
16
  emits: ['update:value'],
16
17
 
17
- components: { LabeledInput, LabeledSelect },
18
+ components: { LabeledInput, ResourceLabeledSelect },
18
19
  mixins: [Question],
19
20
 
20
21
  props: {
@@ -29,12 +30,6 @@ export default {
29
30
  },
30
31
  },
31
32
 
32
- async fetch() {
33
- if ( this.typeSchema ) {
34
- this.all = await this.$store.dispatch(`${ this.inStore }/findAll`, { type: this.typeName });
35
- }
36
- },
37
-
38
33
  data() {
39
34
  const t = this.question.type;
40
35
 
@@ -57,29 +52,61 @@ export default {
57
52
  return {
58
53
  typeName,
59
54
  typeSchema,
60
- all: [],
55
+ all: [],
56
+ allResourceSettings: {
57
+ updateResources: (all) => {
58
+ // Filter to only include required namespaced resources
59
+ const resources = this.isNamespaced ? all.filter((r) => r.metadata.namespace === this.targetNamespace) : all;
60
+
61
+ return this.mapResourcesToOptions(resources);
62
+ }
63
+ },
64
+ paginateResourceSetting: {
65
+ updateResources: (resources) => {
66
+ return this.mapResourcesToOptions(resources);
67
+ },
68
+ /**
69
+ * of type PaginateTypeOverridesFn
70
+ * @param [LabelSelectPaginationFunctionOptions] opts
71
+ * @returns LabelSelectPaginationFunctionOptions
72
+ */
73
+ requestSettings: (opts) => {
74
+ // Filter to only include required namespaced resources
75
+ const filters = this.isNamespaced ? [
76
+ PaginationParamFilter.createSingleField({ field: 'metadata.namespace', value: this.targetNamespace }),
77
+ ] : [];
78
+
79
+ return {
80
+ ...opts,
81
+ filters,
82
+ groupByNamespace: false,
83
+ classify: true,
84
+ };
85
+ }
86
+ },
61
87
  };
62
88
  },
63
89
 
90
+ methods: {
91
+ mapResourcesToOptions(resources) {
92
+ return resources.map((r) => {
93
+ if (r.id) {
94
+ return {
95
+ label: r.nameDisplay || r.metadata.name,
96
+ value: r.metadata.name
97
+ };
98
+ } else {
99
+ return r;
100
+ }
101
+ });
102
+ },
103
+
104
+ },
105
+
64
106
  computed: {
65
107
  isNamespaced() {
66
108
  return !!this.typeSchema?.attributes?.namespaced;
67
109
  },
68
-
69
- options() {
70
- let out = this.all;
71
-
72
- if ( this.isNamespaced ) {
73
- out = filterBy(this.all, 'metadata.namespace', this.targetNamespace);
74
- }
75
-
76
- return out.map((x) => {
77
- return {
78
- label: x.nameDisplay || x.metadata.name,
79
- value: x.metadata.name
80
- };
81
- });
82
- }
83
110
  },
84
111
  };
85
112
  </script>
@@ -90,15 +117,17 @@ export default {
90
117
  class="row"
91
118
  >
92
119
  <div class="col span-6">
93
- <LabeledSelect
94
- :mode="mode"
95
- :options="options"
120
+ <ResourceLabeledSelect
121
+ :resource-type="typeName"
122
+ :in-store="inStore"
96
123
  :disabled="$fetchState.pending || disabled"
97
124
  :label="displayLabel"
98
125
  :placeholder="question.description"
99
126
  :required="question.required"
100
127
  :value="value"
101
128
  :tooltip="displayTooltip"
129
+ :paginated-resource-settings="paginateResourceSetting"
130
+ :all-resources-settings="allResourceSettings"
102
131
  @update:value="!$fetchState.pending && $emit('update:value', $event)"
103
132
  />
104
133
  </div>
@@ -609,7 +609,7 @@ export default {
609
609
  .masthead-resource-title {
610
610
  padding: 0 8px;
611
611
  text-overflow: ellipsis;
612
- overflow-x: hidden;
612
+ overflow: hidden;
613
613
  white-space: nowrap;
614
614
  }
615
615
 
@@ -104,6 +104,10 @@ export default {
104
104
  :is="asLink ? 'a' : 'div'"
105
105
  v-for="(r, idx) in rows"
106
106
  :key="get(r, keyField)"
107
+ :role="asLink ? 'link' : null"
108
+ :aria-disabled="asLink && get(r, disabledField) === true ? true : null"
109
+ :aria-label="get(r, nameField)"
110
+ :tabindex="get(r, disabledField) === true ? -1 : 0"
107
111
  :href="asLink ? get(r, linkField) : null"
108
112
  :target="get(r, targetField)"
109
113
  :rel="rel"
@@ -111,9 +115,12 @@ export default {
111
115
  :data-testid="componentTestid + '-' + get(r, nameField)"
112
116
  :class="{
113
117
  'has-description': !!get(r, descriptionField),
114
- 'has-side-label': !!get(r, sideLabelField), [colorFor(r, idx)]: true, disabled: get(r, disabledField) === true
118
+ 'has-side-label': !!get(r, sideLabelField),
119
+ [colorFor(r, idx)]: true,
120
+ disabled: get(r, disabledField) === true
115
121
  }"
116
122
  @click="select(r, idx)"
123
+ @keyup.enter.space="select(r, idx)"
117
124
  >
118
125
  <div
119
126
  class="side-label"
@@ -212,6 +219,10 @@ export default {
212
219
  text-decoration: none !important;
213
220
  color: $color;
214
221
 
222
+ &:focus-visible {
223
+ @include focus-outline;
224
+ }
225
+
215
226
  &:hover:not(.disabled) {
216
227
  box-shadow: 0 0 30px var(--shadow);
217
228
  transition: box-shadow 0.1s ease-in-out;