@rancher/shell 3.0.2-rc.5 → 3.0.2

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 (219) 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 +4 -1
  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 +130 -23
  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 +9 -2
  17. package/components/ClusterBadge.vue +1 -0
  18. package/components/ClusterIconMenu.vue +3 -0
  19. package/components/ClusterProviderIcon.vue +14 -1
  20. package/components/CodeMirror.vue +96 -5
  21. package/components/Collapse.vue +16 -3
  22. package/components/CruResource.vue +9 -0
  23. package/components/CruResourceFooter.vue +1 -1
  24. package/components/ExplorerMembers.vue +2 -1
  25. package/components/FixedBanner.vue +19 -12
  26. package/components/Import.vue +14 -1
  27. package/components/LandingPagePreference.vue +4 -2
  28. package/components/PodSecurityAdmission.vue +8 -6
  29. package/components/PromptChangePassword.vue +1 -0
  30. package/components/PromptRemove.vue +23 -21
  31. package/components/ResourceDetail/Masthead.vue +30 -11
  32. package/components/ResourceDetail/__tests__/Masthead.test.ts +61 -0
  33. package/components/ResourceDetail/index.vue +6 -0
  34. package/components/ResourceTable.vue +6 -1
  35. package/components/ResourceYaml.vue +1 -0
  36. package/components/Setting.vue +115 -0
  37. package/components/SortableTable/THead.vue +2 -0
  38. package/components/SortableTable/index.vue +7 -12
  39. package/components/StatusBadge.vue +71 -0
  40. package/components/Tabbed/index.vue +16 -15
  41. package/components/Wizard.vue +108 -104
  42. package/components/YamlEditor.vue +12 -2
  43. package/components/__tests__/Collapse.test.ts +2 -2
  44. package/components/__tests__/FixedBanner.test.ts +3 -3
  45. package/components/auth/Principal.vue +29 -17
  46. package/components/auth/__tests__/Principal.test.ts +40 -0
  47. package/components/auth/login/ldap.vue +7 -0
  48. package/components/fleet/FleetBundles.vue +1 -1
  49. package/components/fleet/FleetRepos.vue +1 -1
  50. package/components/fleet/FleetResources.vue +0 -2
  51. package/components/fleet/FleetSummary.vue +60 -65
  52. package/components/fleet/ForceDirectedTreeChart/index.vue +5 -1
  53. package/components/fleet/__tests__/FleetSummary.test.ts +49 -9
  54. package/components/form/ArrayList.vue +6 -2
  55. package/components/form/ColorInput.vue +1 -0
  56. package/components/form/KeyValue.vue +11 -12
  57. package/components/form/LabeledSelect.vue +15 -3
  58. package/components/form/Labels.vue +8 -1
  59. package/components/form/Members/MembershipEditor.vue +230 -222
  60. package/components/form/Members/__tests__/MembershipEditor.test.ts +62 -0
  61. package/components/form/Password.vue +3 -0
  62. package/components/form/ProjectMemberEditor.vue +6 -3
  63. package/components/form/ResourceTabs/index.vue +15 -13
  64. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +5 -4
  65. package/components/form/SchedulingCustomization.vue +85 -0
  66. package/components/form/Select.vue +3 -2
  67. package/components/form/SelectOrCreateAuthSecret.vue +2 -1
  68. package/components/form/UnitInput.vue +3 -4
  69. package/components/form/__tests__/ArrayList.test.ts +9 -6
  70. package/components/form/__tests__/LabeledSelect.test.ts +37 -0
  71. package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +34 -0
  72. package/components/form/__tests__/UnitInput.test.ts +4 -5
  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/TopLevelMenu.vue +1 -12
  83. package/components/nav/WindowManager/ContainerLogs.vue +96 -58
  84. package/components/nav/WindowManager/ContainerShell.vue +99 -18
  85. package/components/nav/WindowManager/index.vue +74 -6
  86. package/components/nav/__tests__/TopLevelMenu.test.ts +0 -40
  87. package/components/templates/default.vue +2 -47
  88. package/config/features.js +1 -0
  89. package/config/labels-annotations.js +11 -1
  90. package/config/router/navigation-guards/index.js +2 -1
  91. package/config/router/navigation-guards/record-last-route.js +24 -0
  92. package/config/settings.ts +66 -98
  93. package/config/version.js +1 -1
  94. package/core/types-provisioning.ts +7 -0
  95. package/detail/fleet.cattle.io.bundle.vue +7 -0
  96. package/detail/fleet.cattle.io.cluster.vue +0 -3
  97. package/detail/fleet.cattle.io.gitrepo.vue +8 -15
  98. package/detail/provisioning.cattle.io.cluster.vue +8 -2
  99. package/dialog/DeactivateDriverDialog.vue +5 -5
  100. package/dialog/GitRepoForceUpdateDialog.vue +132 -0
  101. package/directives/strip-html-aria-label.js +19 -0
  102. package/edit/__tests__/cis.cattle.io.clusterscan.test.ts +87 -0
  103. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +217 -37
  104. package/edit/auth/__tests__/oidc.test.ts +60 -12
  105. package/edit/auth/ldap/__tests__/config.test.ts +40 -0
  106. package/edit/auth/ldap/config.vue +67 -89
  107. package/edit/auth/oidc.vue +16 -2
  108. package/edit/catalog.cattle.io.clusterrepo.vue +12 -8
  109. package/edit/cis.cattle.io.clusterscan.vue +13 -1
  110. package/edit/fleet.cattle.io.gitrepo.vue +198 -72
  111. package/edit/logging-flow/Match.vue +0 -21
  112. package/edit/management.cattle.io.project.vue +1 -1
  113. package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +10 -3
  114. package/edit/monitoring.coreos.com.prometheusrule/RecordingRule.vue +5 -1
  115. package/edit/monitoring.coreos.com.prometheusrule/index.vue +5 -2
  116. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +8 -1
  117. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +2 -0
  118. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +0 -2
  119. package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +55 -15
  120. package/edit/provisioning.cattle.io.cluster/index.vue +28 -30
  121. package/edit/provisioning.cattle.io.cluster/rke2.vue +64 -13
  122. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +37 -2
  123. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +3 -2
  124. package/edit/resources.cattle.io.backup.vue +150 -15
  125. package/edit/secret/__tests__/ssh.test.ts +79 -0
  126. package/edit/secret/ssh.vue +7 -1
  127. package/edit/service.vue +0 -3
  128. package/edit/workload/Job.vue +8 -8
  129. package/edit/workload/__tests__/Job.test.ts +0 -1
  130. package/edit/workload/index.vue +3 -1
  131. package/initialize/install-directives.js +2 -0
  132. package/initialize/install-plugins.js +6 -1
  133. package/list/catalog.cattle.io.app.vue +21 -4
  134. package/list/fleet.cattle.io.bundle.vue +1 -1
  135. package/list/management.cattle.io.setting.vue +34 -132
  136. package/list/provisioning.cattle.io.cluster.vue +11 -3
  137. package/machine-config/vmwarevsphere.vue +15 -8
  138. package/mixins/__tests__/auth-config.test.ts +74 -0
  139. package/mixins/__tests__/chart.test.ts +5 -4
  140. package/mixins/__tests__/create-edit-view.test.ts +38 -0
  141. package/mixins/auth-config.js +8 -0
  142. package/mixins/chart.js +2 -2
  143. package/mixins/create-edit-view/impl.js +4 -1
  144. package/mixins/vue-select-overrides.js +10 -0
  145. package/models/__tests__/catalog.cattle.io.app.test.ts +148 -0
  146. package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +157 -0
  147. package/models/__tests__/secret.test.ts +56 -13
  148. package/models/catalog.cattle.io.app.js +112 -37
  149. package/models/cluster.js +11 -0
  150. package/models/fleet.cattle.io.bundle.js +40 -2
  151. package/models/fleet.cattle.io.gitrepo.js +169 -109
  152. package/models/management.cattle.io.fleetworkspace.js +4 -0
  153. package/models/management.cattle.io.kontainerdriver.js +7 -0
  154. package/models/nodedriver.js +4 -1
  155. package/models/provisioning.cattle.io.cluster.js +24 -0
  156. package/models/secret.js +1 -1
  157. package/package.json +5 -5
  158. package/pages/auth/login.vue +5 -11
  159. package/pages/auth/verify.vue +11 -1
  160. package/pages/c/_cluster/apps/charts/index.vue +6 -4
  161. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  162. package/pages/c/_cluster/explorer/ConfigBadge.vue +3 -5
  163. package/pages/c/_cluster/explorer/EventsTable.vue +3 -2
  164. package/pages/c/_cluster/explorer/__tests__/index.test.ts +9 -9
  165. package/pages/c/_cluster/explorer/index.vue +33 -35
  166. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  167. package/pages/c/_cluster/fleet/index.vue +0 -5
  168. package/pages/c/_cluster/legacy/project/index.vue +1 -1
  169. package/pages/c/_cluster/settings/performance.vue +52 -53
  170. package/pages/c/_cluster/uiplugins/index.vue +19 -22
  171. package/pages/home.vue +17 -12
  172. package/pages/prefs.vue +5 -1
  173. package/plugins/shortkey.js +10 -1
  174. package/plugins/steve/steve-pagination-utils.ts +58 -8
  175. package/promptRemove/management.cattle.io.fleetworkspace.vue +98 -0
  176. package/promptRemove/management.cattle.io.globalrole.vue +1 -1
  177. package/promptRemove/management.cattle.io.project.vue +2 -8
  178. package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
  179. package/promptRemove/mixin/roleDeletionCheck.js +1 -7
  180. package/promptRemove/pod.vue +7 -28
  181. package/rancher-components/Card/Card.vue +9 -1
  182. package/rancher-components/Form/Checkbox/Checkbox.vue +42 -6
  183. package/rancher-components/Form/LabeledInput/LabeledInput.vue +30 -3
  184. package/rancher-components/Form/Radio/RadioButton.vue +18 -3
  185. package/rancher-components/Form/Radio/RadioGroup.vue +39 -5
  186. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +13 -1
  187. package/rancher-components/RcButton/RcButton.test.ts +97 -0
  188. package/rancher-components/RcButton/RcButton.vue +14 -9
  189. package/rancher-components/RcDropdown/RcDropdown.vue +3 -1
  190. package/rancher-components/RcDropdown/RcDropdownItem.vue +8 -2
  191. package/rancher-components/RcDropdown/RcDropdownMenu.vue +66 -0
  192. package/rancher-components/RcDropdown/index.ts +1 -0
  193. package/rancher-components/RcDropdown/types.ts +27 -0
  194. package/rancher-components/RcDropdown/useDropdownContext.ts +5 -2
  195. package/scripts/extension/helm/charts/ui-plugin-server/templates/_helpers.tpl +2 -2
  196. package/scripts/typegen.sh +1 -0
  197. package/store/__tests__/auth.test.ts +120 -0
  198. package/store/action-menu.js +13 -3
  199. package/store/auth.js +14 -9
  200. package/store/aws.js +9 -2
  201. package/store/catalog.js +14 -7
  202. package/store/features.js +1 -0
  203. package/store/prefs.js +9 -28
  204. package/store/type-map.utils.ts +4 -0
  205. package/types/resources/settings.d.ts +27 -20
  206. package/types/shell/index.d.ts +18 -12
  207. package/utils/__tests__/array.test.ts +13 -1
  208. package/utils/__tests__/string.test.ts +80 -1
  209. package/utils/array.ts +13 -0
  210. package/utils/auth.js +4 -0
  211. package/utils/banners.js +0 -45
  212. package/utils/cluster.js +1 -1
  213. package/{edit/monitoring.coreos.com.prometheusrule → utils}/duration.js +5 -3
  214. package/utils/object.js +0 -3
  215. package/utils/pagination-utils.ts +15 -2
  216. package/utils/string.js +31 -7
  217. package/utils/validators/formRules/__tests__/index.test.ts +27 -0
  218. package/utils/validators/formRules/index.ts +16 -0
  219. package/edit/provisioning.cattle.io.cluster/import.vue +0 -198
@@ -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"
@@ -70,6 +70,13 @@ export default {
70
70
  const label = opt.labelKey ? this.t(opt.labelKey) : opt.label;
71
71
 
72
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;
73
80
  }
74
81
  }
75
82
  };
@@ -89,7 +96,7 @@ export default {
89
96
  :class="opt.class"
90
97
  :disabled="disabled || opt.disabled"
91
98
  role="button"
92
- :aria-label="actionDescription(opt)"
99
+ :aria-label="actionAriaLabel(opt)"
93
100
  @click="change(opt.value)"
94
101
  >
95
102
  <slot
@@ -100,7 +107,7 @@ export default {
100
107
  <i
101
108
  v-if="opt.icon"
102
109
  :class="{icon: true, [opt.icon]: true, [`icon-${iconSize}`]: !!iconSize }"
103
- :alt="actionDescription(opt)"
110
+ :alt="actionAriaLabel(opt)"
104
111
  />
105
112
  <t
106
113
  v-if="opt.labelKey"
@@ -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);
@@ -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') }}
@@ -37,7 +37,7 @@ export default {
37
37
  handleLineBreaksConsentText(banner) {
38
38
  if (banner.text?.length) {
39
39
  // split text by newline char
40
- const textArray = banner.text.split(/\\n/).filter((element) => element);
40
+ const textArray = banner.text.split('\n').filter((element) => element);
41
41
 
42
42
  if (textArray.length > 1) {
43
43
  textArray.forEach((str, i) => {
@@ -130,11 +130,11 @@ export default {
130
130
 
131
131
  if (isEmpty(banner)) {
132
132
  if (showHeader && this.header) {
133
- bannerContent = bannerHeader || {};
133
+ bannerContent = this.handleLineBreaksConsentText(bannerHeader) || {};
134
134
  } else if (showConsent && this.consent) {
135
135
  bannerContent = this.handleLineBreaksConsentText(bannerConsent) || {};
136
136
  } else if (showFooter && this.footer) {
137
- bannerContent = bannerFooter || {};
137
+ bannerContent = this.handleLineBreaksConsentText(bannerFooter) || {};
138
138
  } else {
139
139
  bannerContent = {};
140
140
  }
@@ -168,16 +168,20 @@ export default {
168
168
  >
169
169
  <!-- text as array to support line breaks programmatically rather than just exposing HTML -->
170
170
  <div v-if="isTextAnArray">
171
- <p
171
+ <div
172
172
  v-for="(text, index) in banner.text"
173
173
  :key="index"
174
+ class="array-row"
174
175
  >
175
176
  {{ text }}
176
- </p>
177
+ </div>
177
178
  </div>
178
- <p v-else>
179
+ <div
180
+ v-else
181
+ class="single-row"
182
+ >
179
183
  {{ banner.text }}
180
- </p>
184
+ </div>
181
185
  </div>
182
186
  <div v-else-if="showDialog">
183
187
  <div class="banner-dialog-glass" />
@@ -192,16 +196,20 @@ export default {
192
196
  >
193
197
  <!-- text as array to support line breaks programmatically rather than just exposing HTML -->
194
198
  <div v-if="isTextAnArray">
195
- <p
199
+ <div
196
200
  v-for="(text, index) in banner.text"
197
201
  :key="index"
202
+ class="array-row"
198
203
  >
199
204
  {{ text }}
200
- </p>
205
+ </div>
201
206
  </div>
202
- <p v-else>
207
+ <div
208
+ v-else
209
+ class="single-row"
210
+ >
203
211
  {{ banner.text }}
204
- </p>
212
+ </div>
205
213
  </div>
206
214
  <button
207
215
  class="btn role-primary"
@@ -223,7 +231,6 @@ export default {
223
231
  padding: 0 20px;
224
232
 
225
233
  &.banner-consent {
226
- position: absolute;
227
234
  height: unset;
228
235
  min-height: 2em;
229
236
  overflow: hidden;
@@ -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"