@rancher/shell 3.0.2-rc.4 → 3.0.2-rc.6

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 (212) hide show
  1. package/assets/images/providers/nutanix.svg +12 -1
  2. package/assets/styles/base/_basic.scss +2 -1
  3. package/assets/styles/base/_helpers.scss +4 -0
  4. package/assets/styles/base/_variables.scss +2 -0
  5. package/assets/styles/global/_labeled-input.scss +5 -13
  6. package/assets/styles/global/_layout.scss +1 -0
  7. package/assets/styles/global/_select.scss +5 -0
  8. package/assets/styles/themes/_dark.scss +1 -3
  9. package/assets/styles/themes/_light.scss +5 -1
  10. package/assets/translations/en-us.yaml +142 -22
  11. package/assets/translations/zh-hans.yaml +0 -3
  12. package/cloud-credential/azure.vue +1 -1
  13. package/components/ActionMenuShell.vue +105 -0
  14. package/components/AppModal.vue +2 -2
  15. package/components/AsyncButton.vue +2 -0
  16. package/components/ButtonGroup.vue +15 -1
  17. package/components/ButtonMultiAction.vue +5 -1
  18. package/components/ClusterBadge.vue +1 -0
  19. package/components/ClusterIconMenu.vue +3 -0
  20. package/components/ClusterProviderIcon.vue +14 -1
  21. package/components/CodeMirror.vue +96 -5
  22. package/components/Collapse.vue +16 -3
  23. package/components/CopyToClipboardText.vue +3 -1
  24. package/components/CruResource.vue +9 -0
  25. package/components/CruResourceFooter.vue +1 -1
  26. package/components/ExplorerMembers.vue +2 -1
  27. package/components/ExplorerProjectsNamespaces.vue +7 -0
  28. package/components/Import.vue +14 -1
  29. package/components/LandingPagePreference.vue +4 -2
  30. package/components/PodSecurityAdmission.vue +8 -6
  31. package/components/PromptChangePassword.vue +1 -0
  32. package/components/PromptRemove.vue +23 -21
  33. package/components/ResourceDetail/Masthead.vue +30 -11
  34. package/components/ResourceDetail/__tests__/Masthead.test.ts +61 -0
  35. package/components/ResourceDetail/index.vue +6 -0
  36. package/components/ResourceTable.vue +6 -14
  37. package/components/ResourceYaml.vue +1 -0
  38. package/components/SelectIconGrid.vue +2 -0
  39. package/components/Setting.vue +115 -0
  40. package/components/SortableTable/THead.vue +2 -0
  41. package/components/SortableTable/index.vue +38 -14
  42. package/components/Tabbed/index.vue +16 -15
  43. package/components/Wizard.vue +108 -104
  44. package/components/YamlEditor.vue +12 -2
  45. package/components/__tests__/Collapse.test.ts +2 -2
  46. package/components/auth/Principal.vue +29 -17
  47. package/components/auth/__tests__/Principal.test.ts +40 -0
  48. package/components/auth/login/ldap.vue +7 -0
  49. package/components/fleet/FleetBundles.vue +1 -1
  50. package/components/fleet/FleetRepos.vue +1 -1
  51. package/components/fleet/FleetResources.vue +0 -2
  52. package/components/fleet/FleetSummary.vue +60 -65
  53. package/components/fleet/ForceDirectedTreeChart/index.vue +5 -1
  54. package/components/fleet/__tests__/FleetSummary.test.ts +49 -9
  55. package/components/form/ArrayList.vue +6 -2
  56. package/components/form/ColorInput.vue +1 -0
  57. package/components/form/KeyValue.vue +11 -12
  58. package/components/form/LabeledSelect.vue +16 -3
  59. package/components/form/Labels.vue +8 -1
  60. package/components/form/Members/MembershipEditor.vue +230 -222
  61. package/components/form/Members/__tests__/MembershipEditor.test.ts +62 -0
  62. package/components/form/Password.vue +3 -0
  63. package/components/form/ProjectMemberEditor.vue +6 -3
  64. package/components/form/ResourceTabs/index.vue +15 -13
  65. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +5 -4
  66. package/components/form/SchedulingCustomization.vue +85 -0
  67. package/components/form/Select.vue +4 -2
  68. package/components/form/SelectOrCreateAuthSecret.vue +2 -1
  69. package/components/form/UnitInput.vue +1 -2
  70. package/components/form/__tests__/ArrayList.test.ts +9 -6
  71. package/components/form/__tests__/LabeledSelect.test.ts +37 -0
  72. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +34 -0
  73. package/components/formatter/LiveDate.vue +3 -1
  74. package/components/formatter/ServiceType.vue +12 -4
  75. package/components/formatter/WorkloadHealthScale.vue +2 -1
  76. package/components/nav/Header.vue +35 -2
  77. package/components/nav/HeaderPageActionMenu.vue +11 -40
  78. package/components/nav/Jump.vue +8 -2
  79. package/components/nav/NamespaceFilter.vue +5 -4
  80. package/components/nav/Pinned.vue +1 -1
  81. package/components/nav/TopLevelMenu.helper.ts +5 -5
  82. package/components/nav/WindowManager/ContainerLogs.vue +97 -49
  83. package/components/nav/WindowManager/ContainerShell.vue +99 -18
  84. package/components/nav/WindowManager/index.vue +85 -6
  85. package/components/templates/default.vue +2 -47
  86. package/config/features.js +1 -0
  87. package/config/home-links.js +1 -1
  88. package/config/labels-annotations.js +11 -1
  89. package/config/router/navigation-guards/index.js +2 -1
  90. package/config/router/navigation-guards/record-last-route.js +24 -0
  91. package/config/settings.ts +66 -98
  92. package/config/version.js +1 -1
  93. package/core/types-provisioning.ts +7 -0
  94. package/detail/fleet.cattle.io.bundle.vue +7 -0
  95. package/detail/fleet.cattle.io.cluster.vue +0 -3
  96. package/detail/fleet.cattle.io.gitrepo.vue +8 -15
  97. package/detail/provisioning.cattle.io.cluster.vue +8 -2
  98. package/dialog/DeactivateDriverDialog.vue +5 -5
  99. package/dialog/GitRepoForceUpdateDialog.vue +132 -0
  100. package/directives/strip-html-aria-label.js +19 -0
  101. package/edit/__tests__/cis.cattle.io.clusterscan.test.ts +87 -0
  102. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +217 -37
  103. package/edit/auth/__tests__/oidc.test.ts +26 -9
  104. package/edit/auth/ldap/__tests__/config.test.ts +40 -0
  105. package/edit/auth/ldap/config.vue +67 -89
  106. package/edit/auth/oidc.vue +15 -1
  107. package/edit/catalog.cattle.io.clusterrepo.vue +12 -8
  108. package/edit/cis.cattle.io.clusterscan.vue +13 -1
  109. package/edit/fleet.cattle.io.gitrepo.vue +198 -72
  110. package/edit/logging-flow/Match.vue +0 -21
  111. package/edit/management.cattle.io.project.vue +1 -1
  112. package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +10 -3
  113. package/edit/monitoring.coreos.com.prometheusrule/RecordingRule.vue +5 -1
  114. package/edit/monitoring.coreos.com.prometheusrule/index.vue +5 -2
  115. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +8 -1
  116. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +2 -0
  117. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +55 -15
  118. package/edit/provisioning.cattle.io.cluster/index.vue +39 -39
  119. package/edit/provisioning.cattle.io.cluster/rke2.vue +63 -12
  120. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +37 -2
  121. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +3 -2
  122. package/edit/resources.cattle.io.backup.vue +150 -15
  123. package/edit/secret/__tests__/ssh.test.ts +79 -0
  124. package/edit/secret/ssh.vue +7 -1
  125. package/edit/workload/Job.vue +2 -2
  126. package/edit/workload/index.vue +3 -1
  127. package/initialize/install-directives.js +2 -0
  128. package/initialize/install-plugins.js +6 -1
  129. package/list/catalog.cattle.io.app.vue +21 -4
  130. package/list/fleet.cattle.io.bundle.vue +1 -1
  131. package/list/management.cattle.io.setting.vue +34 -129
  132. package/list/provisioning.cattle.io.cluster.vue +11 -3
  133. package/machine-config/vmwarevsphere.vue +15 -8
  134. package/mixins/__tests__/auth-config.test.ts +74 -0
  135. package/mixins/__tests__/chart.test.ts +5 -4
  136. package/mixins/__tests__/create-edit-view.test.ts +38 -0
  137. package/mixins/auth-config.js +9 -1
  138. package/mixins/chart.js +2 -2
  139. package/mixins/create-edit-view/impl.js +4 -1
  140. package/mixins/vue-select-overrides.js +10 -0
  141. package/models/__tests__/catalog.cattle.io.app.test.ts +148 -0
  142. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +157 -0
  143. package/models/__tests__/secret.test.ts +56 -13
  144. package/models/catalog.cattle.io.app.js +112 -37
  145. package/models/cluster.js +11 -0
  146. package/models/fleet.cattle.io.bundle.js +40 -2
  147. package/models/fleet.cattle.io.gitrepo.js +169 -109
  148. package/models/management.cattle.io.fleetworkspace.js +4 -0
  149. package/models/management.cattle.io.kontainerdriver.js +7 -0
  150. package/models/nodedriver.js +4 -1
  151. package/models/provisioning.cattle.io.cluster.js +24 -0
  152. package/models/secret.js +1 -1
  153. package/package.json +4 -4
  154. package/pages/auth/login.vue +4 -2
  155. package/pages/auth/verify.vue +11 -1
  156. package/pages/c/_cluster/apps/charts/chart.vue +1 -0
  157. package/pages/c/_cluster/apps/charts/index.vue +6 -4
  158. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  159. package/pages/c/_cluster/explorer/ConfigBadge.vue +3 -5
  160. package/pages/c/_cluster/explorer/EventsTable.vue +3 -2
  161. package/pages/c/_cluster/explorer/__tests__/index.test.ts +9 -9
  162. package/pages/c/_cluster/explorer/index.vue +33 -35
  163. package/pages/c/_cluster/explorer/tools/index.vue +17 -4
  164. package/pages/c/_cluster/fleet/index.vue +0 -5
  165. package/pages/c/_cluster/legacy/project/index.vue +1 -1
  166. package/pages/c/_cluster/settings/performance.vue +52 -53
  167. package/pages/c/_cluster/uiplugins/index.vue +21 -22
  168. package/pages/home.vue +17 -12
  169. package/pages/prefs.vue +5 -1
  170. package/plugins/shortkey.js +10 -1
  171. package/plugins/steve/steve-pagination-utils.ts +58 -8
  172. package/promptRemove/management.cattle.io.fleetworkspace.vue +98 -0
  173. package/promptRemove/management.cattle.io.globalrole.vue +1 -1
  174. package/promptRemove/management.cattle.io.project.vue +2 -8
  175. package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
  176. package/promptRemove/mixin/roleDeletionCheck.js +1 -7
  177. package/promptRemove/pod.vue +7 -28
  178. package/rancher-components/Card/Card.vue +9 -1
  179. package/rancher-components/Form/Checkbox/Checkbox.vue +42 -6
  180. package/rancher-components/Form/LabeledInput/LabeledInput.vue +30 -3
  181. package/rancher-components/Form/Radio/RadioButton.vue +18 -3
  182. package/rancher-components/Form/Radio/RadioGroup.vue +39 -5
  183. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +13 -1
  184. package/rancher-components/RcButton/RcButton.test.ts +97 -0
  185. package/rancher-components/RcButton/RcButton.vue +14 -9
  186. package/rancher-components/RcDropdown/RcDropdown.vue +3 -1
  187. package/rancher-components/RcDropdown/RcDropdownItem.vue +8 -14
  188. package/rancher-components/RcDropdown/RcDropdownMenu.vue +66 -0
  189. package/rancher-components/RcDropdown/index.ts +1 -0
  190. package/rancher-components/RcDropdown/types.ts +27 -0
  191. package/rancher-components/RcDropdown/useDropdownContext.ts +5 -2
  192. package/scripts/typegen.sh +1 -0
  193. package/store/__tests__/auth.test.ts +120 -0
  194. package/store/action-menu.js +13 -3
  195. package/store/auth.js +14 -9
  196. package/store/catalog.js +14 -7
  197. package/store/features.js +1 -0
  198. package/store/prefs.js +9 -28
  199. package/store/type-map.utils.ts +4 -0
  200. package/types/resources/settings.d.ts +27 -20
  201. package/types/shell/index.d.ts +18 -2
  202. package/utils/__tests__/array.test.ts +13 -1
  203. package/utils/__tests__/string.test.ts +80 -1
  204. package/utils/array.ts +13 -0
  205. package/utils/auth.js +4 -0
  206. package/utils/cluster.js +1 -1
  207. package/{edit/monitoring.coreos.com.prometheusrule → utils}/duration.js +5 -3
  208. package/utils/pagination-utils.ts +15 -2
  209. package/utils/string.js +31 -7
  210. package/utils/validators/formRules/__tests__/index.test.ts +27 -0
  211. package/utils/validators/formRules/index.ts +16 -0
  212. package/edit/provisioning.cattle.io.cluster/import.vue +0 -198
@@ -2131,7 +2131,6 @@ fleet:
2131
2131
  notReady: 未就绪
2132
2132
  waitApplied: 等待应用
2133
2133
  gitRepo:
2134
- createLocalBanner: 将 Git 仓库部署到本地工作区时,你无法定位任何集群或集群组
2135
2134
  tabs:
2136
2135
  resources: 资源
2137
2136
  unready: 未就绪
@@ -2721,8 +2720,6 @@ landing:
2721
2720
  other {核}}
2722
2721
  cpuUsed: 已用 CPU
2723
2722
  memoryUsed: 已用内存
2724
- seeWhatsNew: 点击右侧链接,了解此版本的新功能和优化。
2725
- whatsNewLink: "2.8 的新功能"
2726
2723
  learnMore: 了解更多
2727
2724
  support: 支持
2728
2725
  community:
@@ -61,7 +61,7 @@ export default {
61
61
  const parsed = parseAzureError(e.error);
62
62
 
63
63
  if (parsed) {
64
- return { errors: [parsed] };
64
+ throw ( new Error(parsed));
65
65
  }
66
66
  }
67
67
 
@@ -0,0 +1,105 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue';
3
+ import { useStore } from 'vuex';
4
+ import { useRoute } from 'vue-router';
5
+
6
+ import { isAlternate } from '@shell/utils/platform';
7
+ import { RcDropdownMenu } from '@components/RcDropdown';
8
+ import { ButtonRoleProps, ButtonSizeProps } from '@components/RcButton/types';
9
+ import { DropdownOption } from '@components/RcDropdown/types';
10
+
11
+ const store = useStore();
12
+
13
+ type RcDropdownMenuComponentProps = {
14
+ buttonRole?: keyof ButtonRoleProps;
15
+ buttonSize?: keyof ButtonSizeProps;
16
+ buttonAriaLabel?: string;
17
+ dropdownAriaLabel?: string;
18
+ dataTestid?: string;
19
+ resource: Object;
20
+ customActions?: DropdownOption[];
21
+ }
22
+
23
+ const props = defineProps <RcDropdownMenuComponentProps>();
24
+
25
+ const openChanged = (event: boolean) => {
26
+ if (event) {
27
+ store.dispatch('action-menu/setResource', props.resource);
28
+ }
29
+ };
30
+
31
+ const emit = defineEmits<{(event: string, payload: any): void}>();
32
+ const route = useRoute();
33
+
34
+ const execute = (action: any, event: MouseEvent, args?: any) => {
35
+ if (action.disabled) {
36
+ return;
37
+ }
38
+
39
+ // this will come from extensions...
40
+ if (action.invoke) {
41
+ const fn = action.invoke;
42
+
43
+ if (fn && action.enabled) {
44
+ const resources = store.getters['action-menu/resources'];
45
+ const opts = {
46
+ event,
47
+ action,
48
+ isAlt: isAlternate(event)
49
+ };
50
+
51
+ if (resources.length === 1) {
52
+ fn.apply(this, [opts, resources]);
53
+ }
54
+ }
55
+ } else if (props.customActions) {
56
+ // If the state of this component is controlled
57
+ // by props instead of Vuex, we assume you wouldn't want
58
+ // the mutation to have a dependency on Vuex either.
59
+ // So in that case we use events to execute actions instead.
60
+ // If an action list item is clicked, this
61
+ // component emits that event, then we assume the parent
62
+ // component will execute the action.
63
+ emit(
64
+ action.action,
65
+ {
66
+ action,
67
+ event,
68
+ ...args,
69
+ route,
70
+ }
71
+ );
72
+ } else {
73
+ // If the state of this component is controlled
74
+ // by Vuex, mutate the store when an action is clicked.
75
+ const opts = { alt: isAlternate(event) };
76
+
77
+ store.dispatch('action-menu/execute', {
78
+ action, args, opts
79
+ });
80
+ }
81
+ };
82
+
83
+ const options = computed(() => store.getters['action-menu/optionsArray']);
84
+
85
+ const menuOptions = () => {
86
+ if (props.customActions && props.customActions.length > 0) {
87
+ return props.customActions;
88
+ }
89
+
90
+ return options.value;
91
+ };
92
+ </script>
93
+
94
+ <template>
95
+ <rc-dropdown-menu
96
+ :button-role="buttonRole || 'link'"
97
+ :button-size="buttonSize || 'small'"
98
+ :button-aria-label="buttonAriaLabel"
99
+ :dropdown-aria-label="dropdownAriaLabel"
100
+ :options="menuOptions()"
101
+ :data-testid="dataTestid"
102
+ @update:open="openChanged"
103
+ @select="(e: MouseEvent, option: object) => execute(option, e)"
104
+ />
105
+ </template>
@@ -107,7 +107,7 @@ export default defineComponent({
107
107
  width: this.modalWidth,
108
108
  ...this.stylesPropToObj,
109
109
  };
110
- }
110
+ },
111
111
  },
112
112
  setup(props) {
113
113
  if (props.triggerFocusTrap) {
@@ -208,7 +208,7 @@ export default defineComponent({
208
208
  display: flex;
209
209
  justify-content: center;
210
210
  align-items: center;
211
- z-index: 20;
211
+ z-index: z-index('modalOverlay');
212
212
 
213
213
  .modal-container {
214
214
  background-color: var(--modal-bg);
@@ -279,10 +279,12 @@ export default defineComponent({
279
279
  <template>
280
280
  <button
281
281
  ref="btn"
282
+ role="button"
282
283
  :class="classes"
283
284
  :name="name"
284
285
  :type="type"
285
286
  :disabled="isDisabled"
287
+ :aria-disabled="isDisabled"
286
288
  :tab-index="tabIndex"
287
289
  :data-testid="componentTestid + '-async-button'"
288
290
  @click="clicked"
@@ -64,6 +64,19 @@ export default {
64
64
  methods: {
65
65
  change(value) {
66
66
  this.$emit('update:value', value);
67
+ },
68
+ actionDescription(opt) {
69
+ const tooltip = opt.tooltipKey ? this.t(opt.tooltipKey) : opt.tooltip;
70
+ const label = opt.labelKey ? this.t(opt.labelKey) : opt.label;
71
+
72
+ return tooltip || label || '';
73
+ },
74
+ actionAriaLabel(opt) {
75
+ const ariaLabel = opt.ariaLabel;
76
+ const label = opt.labelKey ? this.t(opt.labelKey) : opt.label;
77
+ const tooltip = opt.tooltipKey ? this.t(opt.tooltipKey) : opt.tooltip;
78
+
79
+ return ariaLabel || tooltip || label || undefined;
67
80
  }
68
81
  }
69
82
  };
@@ -83,7 +96,7 @@ export default {
83
96
  :class="opt.class"
84
97
  :disabled="disabled || opt.disabled"
85
98
  role="button"
86
- :aria-label="opt.labelKey ? t(opt.labelKey) : opt.label"
99
+ :aria-label="actionAriaLabel(opt)"
87
100
  @click="change(opt.value)"
88
101
  >
89
102
  <slot
@@ -94,6 +107,7 @@ export default {
94
107
  <i
95
108
  v-if="opt.icon"
96
109
  :class="{icon: true, [opt.icon]: true, [`icon-${iconSize}`]: !!iconSize }"
110
+ :alt="actionAriaLabel(opt)"
97
111
  />
98
112
  <t
99
113
  v-if="opt.labelKey"
@@ -22,10 +22,14 @@ const buttonClass = computed(() => {
22
22
  <button
23
23
  type="button"
24
24
  class="btn btn-sm role-multi-action actions"
25
+ role="button"
25
26
  :class="buttonClass"
26
27
  @click="(e: Event) => $emit('click', e)"
27
28
  >
28
- <i class="icon icon-actions" />
29
+ <i
30
+ class="icon icon-actions"
31
+ :alt="t('sortableTable.tableActionsImgAlt')"
32
+ />
29
33
  </button>
30
34
  </template>
31
35
 
@@ -23,6 +23,7 @@ export default {
23
23
  :style="{ backgroundColor: cluster.badge.color, color: cluster.badge.textColor }"
24
24
  class="cluster-badge"
25
25
  :class="{'cluster-badge-border': showBorders}"
26
+ :aria-label="t('clusterBadge.clusterComment', { text: cluster.badge?.text || '' })"
26
27
  >
27
28
  {{ cluster.badge.text }}
28
29
  </div>
@@ -76,6 +76,7 @@ export default {
76
76
  style="enable-background:new 0 0 100 100;"
77
77
  xml:space="preserve"
78
78
  >
79
+ <title>{{ t('nav.ariaLabel.localClusterIcon') }}</title>
79
80
  <g>
80
81
  <g>
81
82
  <path
@@ -106,10 +107,12 @@ export default {
106
107
  <i
107
108
  v-if="!routeCombo && cluster.pinned"
108
109
  class="icon icon-pin cluster-pin-icon"
110
+ :alt="t('nav.ariaLabel.pinCluster', { cluster: cluster.nameDisplay })"
109
111
  />
110
112
  <i
111
113
  v-else-if="routeCombo"
112
114
  class="icon icon-keyboard_tab key-combo-icon"
115
+ :alt="t('nav.ariaLabel.clusterIconKeyCombo')"
113
116
  />
114
117
  </div>
115
118
  </template>
@@ -37,7 +37,19 @@ export default {
37
37
  {{ cluster.badge.iconText }}
38
38
  </div>
39
39
  <!-- eslint-disable -->
40
- <svg v-else-if="cluster.isLocal && !cluster.isHarvester" class="cluster-local-logo" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
40
+ <svg
41
+ v-else-if="cluster.isLocal && !cluster.isHarvester"
42
+ class="cluster-local-logo"
43
+ version="1.1"
44
+ id="Layer_1"
45
+ xmlns="http://www.w3.org/2000/svg"
46
+ xmlns:xlink="http://www.w3.org/1999/xlink"
47
+ x="0px"
48
+ y="0px"
49
+ viewBox="0 0 100 100"
50
+ style="enable-background:new 0 0 100 100;"
51
+ xml:space="preserve">
52
+ <title>{{ t('nav.ariaLabel.clusterProvIcon', { cluster: 'local' }) }}</title>
41
53
  <g>
42
54
  <g>
43
55
  <path class="rancher-icon-fill" d="M26.0862026,44.4953918H8.6165142c-5.5818157,0-9.3979139-4.6252708-8.4802637-10.1311035l2.858391-17.210701
@@ -60,6 +72,7 @@ export default {
60
72
  v-else-if="cluster.providerNavLogo"
61
73
  class="cluster-os-logo"
62
74
  :src="cluster.providerNavLogo"
75
+ :alt="t('nav.ariaLabel.clusterProvIcon', { cluster: cluster.nameDisplay })"
63
76
  >
64
77
  </div>
65
78
  </template>
@@ -36,10 +36,13 @@ export default {
36
36
 
37
37
  data() {
38
38
  return {
39
- codeMirrorRef: null,
40
- loaded: false,
41
- removeKeyMapBox: false,
42
- hasLintErrors: false,
39
+ codeMirrorRef: null,
40
+ loaded: false,
41
+ removeKeyMapBox: false,
42
+ hasLintErrors: false,
43
+ currFocusedElem: undefined,
44
+ isCodeMirrorFocused: false,
45
+ codeMirrorContainerRef: undefined
43
46
  };
44
47
  },
45
48
 
@@ -83,6 +86,10 @@ export default {
83
86
  out.lint = { onUpdateLinting: this.handleLintErrors };
84
87
  }
85
88
 
89
+ // fixes https://github.com/rancher/dashboard/issues/13653
90
+ // we can't use the inert HTML prop on the parent because it disables all interaction
91
+ out.readOnly = this.isDisabled ? 'nocursor' : false;
92
+
86
93
  return out;
87
94
  },
88
95
 
@@ -98,6 +105,14 @@ export default {
98
105
 
99
106
  isNonDefaultKeyMap() {
100
107
  return this.combinedOptions?.keyMap !== 'sublime';
108
+ },
109
+
110
+ isCodeMirrorContainerFocused() {
111
+ return this.currFocusedElem === this.codeMirrorContainerRef;
112
+ },
113
+
114
+ codeMirrorContainerTabIndex() {
115
+ return this.isCodeMirrorFocused ? 0 : -1;
101
116
  }
102
117
  },
103
118
 
@@ -111,13 +126,64 @@ export default {
111
126
  }
112
127
  },
113
128
 
129
+ async mounted() {
130
+ const el = this.$refs.codeMirrorContainer;
131
+
132
+ el.addEventListener('keydown', this.handleKeyPress);
133
+ this.codeMirrorContainerRef = this.$refs.codeMirrorContainer;
134
+ },
135
+
136
+ beforeUnmount() {
137
+ const el = this.$refs.codeMirrorContainer;
138
+
139
+ el.removeEventListener('keydown', this.handleKeyPress);
140
+ },
141
+
114
142
  watch: {
115
143
  hasLintErrors(neu) {
116
144
  this.$emit('validationChanged', !neu);
145
+ },
146
+
147
+ isCodeMirrorContainerFocused: {
148
+ handler(neu) {
149
+ const codeMirrorEl = this.codeMirrorRef?.getInputField();
150
+
151
+ if (codeMirrorEl) {
152
+ codeMirrorEl.tabIndex = neu ? -1 : 0;
153
+ }
154
+ },
155
+ immediate: true
117
156
  }
118
157
  },
119
158
 
120
159
  methods: {
160
+ focusChanged(ev, isBlurred = false) {
161
+ if (isBlurred) {
162
+ this.currFocusedElem = undefined;
163
+ } else {
164
+ this.currFocusedElem = ev.target;
165
+ }
166
+ },
167
+
168
+ handleKeyPress(ev) {
169
+ // allows pressing escape in the editor, useful for modal editing with vim
170
+ if (this.isCodeMirrorFocused && ev.code === 'Escape') {
171
+ ev.preventDefault();
172
+ ev.stopPropagation();
173
+ }
174
+
175
+ // make focus leave the editor for it's parent container so that we can tab
176
+ const didPressEscapeSequence = ev.shiftKey && ev.code === 'Escape';
177
+
178
+ if (this.isCodeMirrorFocused && didPressEscapeSequence) {
179
+ this.$refs?.codeMirrorContainer?.focus();
180
+ }
181
+
182
+ // if parent container is focused and we press a trigger, focus goes to the editor inside
183
+ if (this.isCodeMirrorContainerFocused && (ev.code === 'Enter' || ev.code === 'Space')) {
184
+ this.codeMirrorRef.focus();
185
+ }
186
+ },
121
187
  /**
122
188
  * Codemirror yaml linting uses js-yaml parse
123
189
  * it does not distinguish between warnings and errors so we will treat all yaml lint messages as errors
@@ -161,10 +227,12 @@ export default {
161
227
  },
162
228
 
163
229
  onFocus() {
230
+ this.isCodeMirrorFocused = true;
164
231
  this.$emit('onFocus', true);
165
232
  },
166
233
 
167
234
  onBlur() {
235
+ this.isCodeMirrorFocused = false;
168
236
  this.$emit('onFocus', false);
169
237
  },
170
238
 
@@ -183,8 +251,12 @@ export default {
183
251
 
184
252
  <template>
185
253
  <div
186
- class="code-mirror"
254
+ ref="codeMirrorContainer"
255
+ :tabindex="codeMirrorContainerTabIndex"
256
+ class="code-mirror code-mirror-container"
187
257
  :class="{['as-text-area']: asTextArea}"
258
+ @focusin="focusChanged"
259
+ @blur="focusChanged($event, true)"
188
260
  >
189
261
  <div v-if="loaded">
190
262
  <div
@@ -204,6 +276,7 @@ export default {
204
276
  </div>
205
277
  </div>
206
278
  <Codemirror
279
+ id="code-mirror-el"
207
280
  ref="codeMirrorRef"
208
281
  :value="value"
209
282
  :options="combinedOptions"
@@ -215,6 +288,12 @@ export default {
215
288
  @focus="onFocus"
216
289
  @blur="onBlur"
217
290
  />
291
+ <span
292
+ v-show="isCodeMirrorFocused"
293
+ class="escape-text"
294
+ role="alert"
295
+ :aria-describedby="t('wm.containerShell.escapeText')"
296
+ >{{ t('codeMirror.escapeText') }}</span>
218
297
  </div>
219
298
  <div v-else>
220
299
  Loading...
@@ -226,6 +305,10 @@ export default {
226
305
  $code-mirror-animation-time: 0.1s;
227
306
 
228
307
  .code-mirror {
308
+ &.code-mirror-container:focus-visible {
309
+ @include focus-outline;
310
+ }
311
+
229
312
  &.as-text-area .codemirror-container{
230
313
  min-height: 40px;
231
314
  position: relative;
@@ -321,6 +404,14 @@ export default {
321
404
 
322
405
  .code-mirror {
323
406
  position: relative;
407
+ margin-bottom: 20px;
408
+
409
+ .escape-text {
410
+ font-size: 12px;
411
+ position: absolute;
412
+ bottom: -20px;
413
+ left: 0;
414
+ }
324
415
 
325
416
  .codemirror-container {
326
417
  z-index: 0;
@@ -13,7 +13,12 @@ export default {
13
13
  title: {
14
14
  type: String,
15
15
  default: ''
16
- }
16
+ },
17
+
18
+ isDisabled: {
19
+ type: Boolean,
20
+ default: false
21
+ },
17
22
  },
18
23
 
19
24
  methods: {
@@ -25,10 +30,15 @@ export default {
25
30
  </script>
26
31
 
27
32
  <template>
28
- <div class="collapse">
33
+ <div
34
+ class="collapse"
35
+ :class="{ 'disabled': isDisabled }"
36
+ >
29
37
  <slot name="title">
30
38
  <div
31
39
  class="advanced text-link"
40
+ :class="{ 'disabled': isDisabled }"
41
+ :disabled="isDisabled"
32
42
  data-testid="collapse-div"
33
43
  @click="showAdvanced"
34
44
  >
@@ -59,11 +69,14 @@ export default {
59
69
  <style lang="scss" scoped>
60
70
  .advanced {
61
71
  user-select: none;
62
- padding: 0 5px;
63
72
  cursor: pointer;
64
73
  line-height: 40px;
65
74
  font-size: 15px;
66
75
  font-weight: 500;
76
+
77
+ .disabled {
78
+ cursor: not-allowed;
79
+ }
67
80
  }
68
81
  .content {
69
82
  background: var(--nav-active);
@@ -55,9 +55,11 @@ export default {
55
55
  @click="clicked"
56
56
  @keyup.space="clicked"
57
57
  >
58
- {{ text }} <i
58
+ {{ text }}
59
+ <i
59
60
  class="icon"
60
61
  :class="{ 'icon-copy': !copied, 'icon-checkmark': copied}"
62
+ :alt="!copied ? t('generic.copyToClipboard') : t('generic.copiedToClipboard')"
61
63
  />
62
64
  </a>
63
65
  </template>
@@ -490,7 +490,12 @@ export default {
490
490
  class="subtype-banner"
491
491
  :class="{ selected: subtype.id === _selectedSubtype }"
492
492
  :data-testid="`subtype-banner-item-${subtype.id}`"
493
+ tabindex="0"
494
+ :aria-disabled="false"
495
+ :aria-label="subtype.description ? `${subtype.label} - ${subtype.description}` : subtype.label"
496
+ role="link"
493
497
  @click="selectType(subtype.id, $event)"
498
+ @keyup.enter.space="selectType(subtype.id, $event)"
494
499
  >
495
500
  <slot name="subtype-content">
496
501
  <div class="subtype-container">
@@ -825,6 +830,10 @@ export default {
825
830
  .round-image {
826
831
  background-color: var(--primary);
827
832
  }
833
+
834
+ &:focus-visible {
835
+ @include focus-outline;
836
+ }
828
837
  }
829
838
  }
830
839
 
@@ -117,7 +117,7 @@ export default {
117
117
  display: flex;
118
118
  justify-content: flex-end;
119
119
  margin-top: 20px;
120
- z-index: 40;
120
+ z-index: z-index('cruFooter');
121
121
 
122
122
  .btn {
123
123
  margin-left: 20px;
@@ -355,7 +355,8 @@ export default {
355
355
  <button
356
356
  v-if="canEditProjectMembers"
357
357
  type="button"
358
- class="create-namespace btn btn-sm role-secondary mr-10 right"
358
+ class="btn btn-sm role-secondary mr-10 right"
359
+ :data-testid="`add-project-member-${getProjectLabel(group).replace(' ', '').toLowerCase()}`"
359
360
  @click="addProjectMember(group)"
360
361
  >
361
362
  {{ t('members.createActionLabel') }}
@@ -368,6 +368,12 @@ export default {
368
368
  return project?.description;
369
369
  },
370
370
 
371
+ projectResource(group) {
372
+ const row = group.rows[0];
373
+
374
+ return row.nameDisplay || row.id || '';
375
+ },
376
+
371
377
  clearSelection() {
372
378
  this.$refs.table.clearSelection();
373
379
  },
@@ -462,6 +468,7 @@ export default {
462
468
  <ButtonMultiAction
463
469
  class="project-action mr-10"
464
470
  :borderless="true"
471
+ :aria-label="t('projectNamespaces.tableActionsLabel', { resource: projectResource(group.group) })"
465
472
  :invisible="!showProjectActionButton(group.group)"
466
473
  @click="showProjectAction($event, group.group)"
467
474
  />
@@ -14,7 +14,7 @@ import { NAMESPACE } from '@shell/config/types';
14
14
  import { NAME as NAME_COL, TYPE, NAMESPACE as NAMESPACE_COL, AGE } from '@shell/config/table-headers';
15
15
 
16
16
  export default {
17
- emits: ['close'],
17
+ emits: ['close', 'onReadyYamlEditor'],
18
18
 
19
19
  components: {
20
20
  AsyncButton,
@@ -111,6 +111,10 @@ export default {
111
111
  this.close();
112
112
  }
113
113
  },
114
+
115
+ onReadyYamlEditor(arg) {
116
+ this.$emit('onReadyYamlEditor', arg);
117
+ }
114
118
  },
115
119
  };
116
120
  </script>
@@ -121,6 +125,7 @@ export default {
121
125
  v-else
122
126
  :show-highlight-border="false"
123
127
  data-testid="import-yaml"
128
+ :trigger-focus-trap="true"
124
129
  >
125
130
  <template #title>
126
131
  <div style="display: block; width: 100%;">
@@ -134,6 +139,8 @@ export default {
134
139
  <div class="row">
135
140
  <div class="col span-6">
136
141
  <FileSelector
142
+ role="button"
143
+ :aria-label="t('generic.readFromFileArea', { area: t('import.title') })"
137
144
  class="btn role-secondary pull-left"
138
145
  :label="t('generic.readFromFile')"
139
146
  @selected="onFileSelected"
@@ -174,6 +181,7 @@ export default {
174
181
  ref="yamleditor"
175
182
  v-model:value="currentYaml"
176
183
  class="yaml-editor"
184
+ @onReady="onReadyYamlEditor"
177
185
  />
178
186
  <Banner
179
187
  v-for="(err, i) in errors"
@@ -189,6 +197,8 @@ export default {
189
197
  style="width: 100%"
190
198
  >
191
199
  <button
200
+ :aria-label="t('generic.close')"
201
+ role="button"
192
202
  type="button"
193
203
  class="btn role-primary"
194
204
  data-testid="import-yaml-close"
@@ -203,6 +213,8 @@ export default {
203
213
  style="width: 100%"
204
214
  >
205
215
  <button
216
+ :aria-label="t('generic.cancel')"
217
+ role="button"
206
218
  type="button"
207
219
  class="btn role-secondary mr-10"
208
220
  data-testid="import-yaml-cancel"
@@ -215,6 +227,7 @@ export default {
215
227
  mode="import"
216
228
  :disabled="!currentYaml.length"
217
229
  data-testid="import-yaml-import-action"
230
+ :aria-label="t('import.title')"
218
231
  @click="importYaml"
219
232
  />
220
233
  </div>
@@ -94,7 +94,7 @@ export default {
94
94
  if (neu) {
95
95
  this.afterLoginRoute = neu;
96
96
  } else {
97
- this.afterLoginRoute = this.routeFromDropdown?.value;
97
+ this.afterLoginRoute = this.routeFromDropdown?.value || this.routeDropdownOptions[0]?.value;
98
98
  }
99
99
  },
100
100
  }
@@ -120,10 +120,12 @@ export default {
120
120
  :val="false"
121
121
  :value="afterLoginRoute=== 'home' || afterLoginRoute === 'last-visited'"
122
122
  :v-bind="$attrs"
123
- @update:value="afterLoginRoute = false"
123
+ :prevent-focus-on-radio-groups="true"
124
+ @update:value="updateLoginRoute(null)"
124
125
  />
125
126
  <Select
126
127
  v-model:value="routeFromDropdown"
128
+ :aria-label="t('landing.landingPrefs.ariaLabelTakeMeToCluster')"
127
129
  :searchable="true"
128
130
  :disabled="afterLoginRoute === 'home' || afterLoginRoute === 'last-visited'"
129
131
  :clearable="false"