@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
@@ -14,7 +14,6 @@ import { SETTING } from '@shell/config/settings';
14
14
  import { getProductFromRoute } from '@shell/utils/router';
15
15
  import { isRancherPrime } from '@shell/config/version';
16
16
  import Pinned from '@shell/components/nav/Pinned';
17
- import { getGlobalBannerFontSizes } from '@shell/utils/banners';
18
17
  import { TopLevelMenuHelperPagination, TopLevelMenuHelperLegacy } from '@shell/components/nav/TopLevelMenu.helper';
19
18
  import { debounce } from 'lodash';
20
19
  import { sameContents } from '@shell/utils/array';
@@ -82,15 +81,6 @@ export default {
82
81
  return this.$store.getters['prefs/get'](PINNED_CLUSTERS);
83
82
  },
84
83
 
85
- sideMenuStyle() {
86
- const globalBannerSettings = getGlobalBannerFontSizes(this.$store);
87
-
88
- return {
89
- marginBottom: globalBannerSettings?.footerFont,
90
- marginTop: globalBannerSettings?.headerFont
91
- };
92
- },
93
-
94
84
  showClusterSearch() {
95
85
  return this.allClustersCount > this.maxClustersToShow;
96
86
  },
@@ -483,7 +473,6 @@ export default {
483
473
  data-testid="side-menu"
484
474
  class="side-menu"
485
475
  :class="{'menu-open': shown, 'menu-close':!shown}"
486
- :style="sideMenuStyle"
487
476
  tabindex="-1"
488
477
  role="navigation"
489
478
  :aria-label="t('nav.ariaLabel.topLevelMenu')"
@@ -1002,7 +991,7 @@ export default {
1002
991
  }
1003
992
  }
1004
993
 
1005
- position: fixed;
994
+ position: absolute;
1006
995
  top: 0;
1007
996
  left: 0px;
1008
997
  bottom: 0;
@@ -132,19 +132,20 @@ export default {
132
132
 
133
133
  data() {
134
134
  return {
135
- container: this.initialContainer || this.pod?.defaultContainerName,
136
- socket: null,
137
- isOpen: false,
138
- isFollowing: true,
139
- scrollThreshold: 80,
140
- timestamps: this.$store.getters['prefs/get'](LOGS_TIME),
141
- wrap: this.$store.getters['prefs/get'](LOGS_WRAP),
142
- previous: false,
143
- search: '',
144
- backlog: [],
145
- lines: [],
146
- now: new Date(),
147
- logItem: shallowRef(LogItem),
135
+ container: this.initialContainer || this.pod?.defaultContainerName,
136
+ socket: null,
137
+ isOpen: false,
138
+ isFollowing: true,
139
+ scrollThreshold: 80,
140
+ timestamps: this.$store.getters['prefs/get'](LOGS_TIME),
141
+ wrap: this.$store.getters['prefs/get'](LOGS_WRAP),
142
+ previous: false,
143
+ search: '',
144
+ backlog: [],
145
+ lines: [],
146
+ now: new Date(),
147
+ logItem: shallowRef(LogItem),
148
+ isContainerMenuOpen: false
148
149
  };
149
150
  },
150
151
 
@@ -302,6 +303,14 @@ export default {
302
303
  },
303
304
 
304
305
  methods: {
306
+ openContainerMenu() {
307
+ this.isContainerMenuOpen = true;
308
+ },
309
+
310
+ closeContainerMenu() {
311
+ this.isContainerMenuOpen = false;
312
+ },
313
+
305
314
  async connect() {
306
315
  if ( this.socket ) {
307
316
  await this.socket.disconnect();
@@ -581,7 +590,10 @@ export default {
581
590
  </Select>
582
591
  <div class="log-action log-action-group ml-5">
583
592
  <button
584
- class="btn bg-primary wm-btn"
593
+ class="btn role-primary wm-btn"
594
+ role="button"
595
+ :aria-label="t('wm.containerLogs.follow')"
596
+ :aria-disabled="isFollowing"
585
597
  :disabled="isFollowing"
586
598
  @click="follow"
587
599
  >
@@ -592,7 +604,9 @@ export default {
592
604
  <i class="wm-btn-small icon icon-chevron-end" />
593
605
  </button>
594
606
  <button
595
- class="btn bg-primary wm-btn"
607
+ class="btn role-primary wm-btn"
608
+ role="button"
609
+ :aria-label="t('wm.containerLogs.clear')"
596
610
  @click="clear"
597
611
  >
598
612
  <t
@@ -603,6 +617,8 @@ export default {
603
617
  </button>
604
618
  <AsyncButton
605
619
  mode="download"
620
+ role="button"
621
+ :aria-label="t('asyncButton.download.action')"
606
622
  @click="download"
607
623
  />
608
624
  </div>
@@ -620,54 +636,72 @@ export default {
620
636
  </div>
621
637
 
622
638
  <div class="log-action log-action-group ml-5">
623
- <v-dropdown
624
- :triggers="['click']"
625
- placement="top"
626
- popperClass="containerLogsDropdown"
639
+ <div
640
+ role="menu"
641
+ tabindex="0"
642
+ :aria-label="t('wm.containerLogs.logActionMenu')"
643
+ @click="openContainerMenu"
644
+ @blur.capture="closeContainerMenu"
645
+ @keyup.enter="openContainerMenu"
646
+ @keyup.space="openContainerMenu"
627
647
  >
628
- <button
629
- class="btn bg-primary btn-cog"
630
- role="button"
631
- :aria-label="t('wm.containerLogs.options')"
648
+ <v-dropdown
649
+ :triggers="[]"
650
+ :shown="isContainerMenuOpen"
651
+ placement="top"
652
+ popperClass="containerLogsDropdown"
653
+ :autoHide="false"
654
+ :flip="false"
655
+ :container="false"
656
+ @focus.capture="openContainerMenu"
632
657
  >
633
- <i
634
- class="icon icon-gear"
635
- :alt="t('wm.containerLogs.options')"
636
- />
637
- <i
638
- class="icon icon-chevron-up"
639
- :alt="t('wm.containerLogs.expand')"
640
- />
641
- </button>
642
-
643
- <template #popper>
644
- <div class="filter-popup">
645
- <LabeledSelect
646
- v-model:value="range"
647
- class="range"
648
- :label="t('wm.containerLogs.range.label')"
649
- :options="rangeOptions"
650
- :clearable="false"
651
- placement="top"
652
- @update:value="toggleRange($event)"
658
+ <button
659
+ class="btn role-primary btn-cog"
660
+ role="button"
661
+ :aria-label="t('wm.containerLogs.options')"
662
+ >
663
+ <i
664
+ class="icon icon-gear"
665
+ :alt="t('wm.containerLogs.options')"
653
666
  />
654
- <div>
655
- <Checkbox
656
- :label="t('wm.containerLogs.wrap')"
657
- :value="wrap"
658
- @update:value="toggleWrap "
659
- />
660
- </div>
661
- <div>
662
- <Checkbox
663
- :label="t('wm.containerLogs.timestamps')"
664
- :value="timestamps"
665
- @update:value="toggleTimestamps"
667
+ <i
668
+ class="icon icon-chevron-up"
669
+ :alt="t('wm.containerLogs.expand')"
670
+ />
671
+ </button>
672
+
673
+ <template #popper>
674
+ <div class="filter-popup">
675
+ <LabeledSelect
676
+ v-model:value="range"
677
+ class="range"
678
+ :label="t('wm.containerLogs.range.label')"
679
+ :options="rangeOptions"
680
+ :clearable="false"
681
+ placement="top"
682
+ role="menuitem"
683
+ @update:value="toggleRange($event)"
666
684
  />
685
+ <div>
686
+ <Checkbox
687
+ :label="t('wm.containerLogs.wrap')"
688
+ :value="wrap"
689
+ role="menuitem"
690
+ @update:value="toggleWrap"
691
+ />
692
+ </div>
693
+ <div>
694
+ <Checkbox
695
+ :label="t('wm.containerLogs.timestamps')"
696
+ :value="timestamps"
697
+ role="menuitem"
698
+ @update:value="toggleTimestamps"
699
+ />
700
+ </div>
667
701
  </div>
668
- </div>
669
- </template>
670
- </v-dropdown>
702
+ </template>
703
+ </v-dropdown>
704
+ </div>
671
705
  </div>
672
706
 
673
707
  <div class="log-action log-action-group ml-5">
@@ -675,6 +709,8 @@ export default {
675
709
  v-model="search"
676
710
  class="input-sm"
677
711
  type="search"
712
+ role="textbox"
713
+ :aria-label="t('wm.containerLogs.searchLogs')"
678
714
  :placeholder="t('wm.containerLogs.search')"
679
715
  >
680
716
  </div>
@@ -768,6 +804,7 @@ export default {
768
804
  border: 0 !important;
769
805
  min-height: 30px;
770
806
  line-height: 30px;
807
+ margin: 0 2px;
771
808
  }
772
809
 
773
810
  > input {
@@ -799,6 +836,7 @@ export default {
799
836
  text-overflow : ellipsis;
800
837
  overflow : hidden;
801
838
  white-space : nowrap;
839
+ padding-left: 4px;
802
840
  }
803
841
 
804
842
  .status {
@@ -74,22 +74,24 @@ export default {
74
74
 
75
75
  data() {
76
76
  return {
77
- container: this.initialContainer || this.pod?.defaultContainerName,
78
- socket: null,
79
- terminal: null,
80
- fitAddon: null,
81
- searchAddon: null,
82
- webglAddon: null,
83
- canvasAddon: null,
84
- isOpen: false,
85
- isOpening: false,
86
- backlog: [],
87
- node: null,
88
- keepAliveTimer: null,
89
- errorMsg: '',
90
- backupShells: ['linux', 'windows'],
91
- os: undefined,
92
- retries: 0
77
+ container: this.initialContainer || this.pod?.defaultContainerName,
78
+ socket: null,
79
+ terminal: null,
80
+ fitAddon: null,
81
+ searchAddon: null,
82
+ webglAddon: null,
83
+ canvasAddon: null,
84
+ isOpen: false,
85
+ isOpening: false,
86
+ backlog: [],
87
+ node: null,
88
+ keepAliveTimer: null,
89
+ errorMsg: '',
90
+ backupShells: ['linux', 'windows'],
91
+ os: undefined,
92
+ retries: 0,
93
+ currFocusedElem: undefined,
94
+ xtermContainerRef: undefined
93
95
  };
94
96
  },
95
97
 
@@ -106,7 +108,20 @@ export default {
106
108
  containerChoices() {
107
109
  return this.pod?.spec?.containers?.map((x) => x.name) || [];
108
110
  },
109
- ...mapGetters({ t: 'i18n/t' })
111
+
112
+ ...mapGetters({ t: 'i18n/t' }),
113
+
114
+ isXtermFocused() {
115
+ return this.currFocusedElem === this.terminal?.textarea;
116
+ },
117
+
118
+ isXtermContainerFocused() {
119
+ return this.currFocusedElem === this.xtermContainerRef;
120
+ },
121
+
122
+ xTermContainerTabIndex() {
123
+ return this.isXtermFocused ? 0 : -1;
124
+ }
110
125
  },
111
126
 
112
127
  watch: {
@@ -121,14 +136,33 @@ export default {
121
136
  width() {
122
137
  this.fit();
123
138
  },
139
+
140
+ isXtermContainerFocused: {
141
+ handler(neu) {
142
+ const shellEl = this.terminal?.textarea;
143
+
144
+ if (shellEl) {
145
+ shellEl.tabIndex = neu ? -1 : 0;
146
+ }
147
+ },
148
+ immediate: true
149
+ }
124
150
  },
125
151
 
126
152
  beforeUnmount() {
153
+ document.removeEventListener('keyup', this.handleKeyPress);
154
+ this.$refs?.containerShell?.$el?.removeEventListener('focusin', this.focusInHandler);
155
+ this.$refs?.xterm.removeEventListener('focusout', this.focusOutHandler);
156
+
127
157
  clearInterval(this.keepAliveTimer);
128
158
  this.cleanup();
129
159
  },
130
160
 
131
161
  async mounted() {
162
+ document.addEventListener('keyup', this.handleKeyPress);
163
+ this.$refs?.containerShell?.$el?.addEventListener('focusin', this.focusInHandler);
164
+ this.$refs?.xterm.addEventListener('focusout', this.focusOutHandler);
165
+
132
166
  const nodeId = this.pod.spec?.nodeName;
133
167
 
134
168
  try {
@@ -146,9 +180,36 @@ export default {
146
180
  this.keepAliveTimer = setInterval(() => {
147
181
  this.fit();
148
182
  }, 60 * 1000);
183
+
184
+ this.xtermContainerRef = this.$refs?.xterm;
149
185
  },
150
186
 
151
187
  methods: {
188
+ focusInHandler(ev) {
189
+ this.currFocusedElem = ev.target;
190
+ },
191
+
192
+ focusOutHandler(ev) {
193
+ this.currFocusedElem = undefined;
194
+ },
195
+
196
+ handleKeyPress(ev) {
197
+ ev.preventDefault();
198
+ ev.stopPropagation();
199
+
200
+ // make focus leave the shell for it's parent container so that we can tab
201
+ const didPressEscapeSequence = ev.shiftKey && ev.code === 'Escape';
202
+
203
+ if (this.isXtermFocused && didPressEscapeSequence) {
204
+ this.$refs.xterm.focus();
205
+ }
206
+
207
+ // if parent container is focused and we press a trigger, focus goes to the shell inside
208
+ if (this.isXtermContainerFocused && (ev.code === 'Enter' || ev.code === 'Space')) {
209
+ this.terminal?.textarea?.focus();
210
+ }
211
+ },
212
+
152
213
  async setupTerminal() {
153
214
  const docStyle = getComputedStyle(document.querySelector('body'));
154
215
  const xterm = await import(/* webpackChunkName: "xterm" */ 'xterm');
@@ -388,6 +449,7 @@ export default {
388
449
 
389
450
  <template>
390
451
  <Window
452
+ ref="containerShell"
391
453
  :active="active"
392
454
  :before-close="cleanup"
393
455
  class="container-shell"
@@ -412,7 +474,9 @@ export default {
412
474
  </Select>
413
475
  <div class="pull-left ml-5">
414
476
  <button
415
- class="btn btn-sm bg-primary"
477
+ class="btn btn-sm role-primary"
478
+ role="button"
479
+ :aria-label="t('wm.containerShell.clear')"
416
480
  @click="clear"
417
481
  >
418
482
  <t
@@ -439,6 +503,12 @@ export default {
439
503
  class="text-error"
440
504
  data-testid="shell-status-disconnected"
441
505
  />
506
+ <span
507
+ v-show="isXtermFocused"
508
+ class="escape-text"
509
+ role="alert"
510
+ :aria-describedby="t('wm.containerShell.escapeText')"
511
+ >{{ t('wm.containerShell.escapeText') }}</span>
442
512
  </div>
443
513
  </template>
444
514
  <template #body>
@@ -448,6 +518,7 @@ export default {
448
518
  >
449
519
  <div
450
520
  ref="xterm"
521
+ :tabindex="xTermContainerTabIndex"
451
522
  class="shell-body"
452
523
  />
453
524
  <resize-observer @notify="fit" />
@@ -466,6 +537,11 @@ export default {
466
537
  animation: flasher 2.5s linear infinite;
467
538
  }
468
539
 
540
+ .escape-text {
541
+ font-size: 12px;
542
+ margin-left: 20px;
543
+ }
544
+
469
545
  @keyframes flasher {
470
546
  50% {
471
547
  opacity: 0;
@@ -483,6 +559,11 @@ export default {
483
559
  .shell-body {
484
560
  padding: calc(2 * var(--outline-width));
485
561
  height: 100%;
562
+
563
+ &:focus-visible, &:focus {
564
+ @include focus-outline;
565
+ outline-offset: -2px;
566
+ }
486
567
  }
487
568
 
488
569
  .containerPicker.unlabeled-select {
@@ -261,15 +261,15 @@ export default {
261
261
  this.reportedWidth = this.width;
262
262
  },
263
263
 
264
- setWmDimensions() {
264
+ setWmDimensions(forceValue) {
265
265
  switch (this.userPin) {
266
266
  case RIGHT:
267
267
  case LEFT:
268
268
  document.documentElement.style.setProperty('--wm-height', `${ window.innerHeight - 55 }px`);
269
- document.documentElement.style.setProperty('--wm-width', `${ this.width }px`);
269
+ document.documentElement.style.setProperty('--wm-width', `${ forceValue || this.width }px`);
270
270
  break;
271
271
  case BOTTOM:
272
- document.documentElement.style.setProperty('--wm-height', `${ this.height }px`);
272
+ document.documentElement.style.setProperty('--wm-height', `${ forceValue || this.height }px`);
273
273
  break;
274
274
  }
275
275
  },
@@ -293,6 +293,32 @@ export default {
293
293
 
294
294
  emitDraggable(event) {
295
295
  this.$emit('draggable', event);
296
+ },
297
+
298
+ resizeVertical(arrowUp) {
299
+ const resizeStep = 20;
300
+ const height = arrowUp ? this.height + resizeStep : this.height - resizeStep;
301
+
302
+ this.height = height;
303
+
304
+ this.setWmDimensions(height);
305
+ this.setReportedHeight(height);
306
+ },
307
+
308
+ resizeHorizontal(arrowLeft) {
309
+ const resizeStep = 20;
310
+ let width;
311
+
312
+ if (this.userPin === 'left') {
313
+ width = arrowLeft ? this.width - resizeStep : this.width + resizeStep;
314
+ } else {
315
+ width = arrowLeft ? this.width + resizeStep : this.width - resizeStep;
316
+ }
317
+
318
+ this.width = width;
319
+
320
+ this.setWmDimensions(width);
321
+ this.setReportedWidth(width);
296
322
  }
297
323
  }
298
324
  };
@@ -311,18 +337,27 @@ export default {
311
337
  :class="{
312
338
  'resizer-left': userPin == 'left',
313
339
  }"
340
+ role="tablist"
341
+ @keyup.right.prevent="selectNext(1)"
342
+ @keyup.left.prevent="selectNext(-1)"
314
343
  @mousedown="emitDraggable(true)"
315
344
  @mouseup="emitDraggable(false)"
316
345
  >
317
346
  <div
318
347
  v-if="userPin == 'right'"
319
348
  class="resizer resizer-x"
349
+ role="button"
350
+ tabindex="0"
351
+ :aria-label="t('wm.containerShell.resizeShellWindow', {arrow1: 'left', arrow2: 'right'})"
352
+ aria-expanded="true"
320
353
  @mousedown.prevent.stop="dragXStart($event)"
321
354
  @touchstart.prevent.stop="dragXStart($event)"
355
+ @keyup.left.prevent.stop="resizeHorizontal(true)"
356
+ @keyup.right.prevent.stop="resizeHorizontal(false)"
322
357
  >
323
358
  <i
324
359
  class="icon icon-code"
325
- :alt="t('wm.containerShell.resizeShellWindow')"
360
+ :alt="t('wm.containerShell.resizeShellWindow', {arrow1: 'left', arrow2: 'right'})"
326
361
  />
327
362
  </div>
328
363
  <div
@@ -330,7 +365,13 @@ export default {
330
365
  :key="i"
331
366
  class="tab"
332
367
  :class="{'active': tab.id === active}"
368
+ role="tab"
369
+ :aria-selected="tab.id === active"
370
+ :aria-label="tab.label"
371
+ :aria-controls="`panel-${tab.id}`"
372
+ tabindex="0"
333
373
  @click="switchTo(tab.id)"
374
+ @keyup.enter.space="switchTo(tab.id)"
334
375
  >
335
376
  <i
336
377
  v-if="tab.icon"
@@ -343,39 +384,56 @@ export default {
343
384
  data-testid="wm-tab-close-button"
344
385
  class="closer icon icon-fw icon-x wm-closer-button"
345
386
  :alt="t('wm.containerShell.closeShellTab', { tab: tab.label })"
387
+ tabindex="0"
388
+ :aria-label="t('windowmanager.closeTab', { tabId: tab.id })"
346
389
  @click.stop="close(tab.id)"
390
+ @keyup.enter.space.stop="close(tab.id)"
347
391
  />
348
392
  </div>
349
393
  <div
350
394
  v-if="userPin == 'bottom'"
351
395
  class="resizer resizer-y"
396
+ role="button"
397
+ tabindex="0"
398
+ :aria-label="t('wm.containerShell.resizeShellWindow', {arrow1: 'up', arrow2: 'down'})"
399
+ aria-expanded="true"
352
400
  @mousedown.prevent.stop="dragYStart($event)"
353
401
  @touchstart.prevent.stop="dragYStart($event)"
354
402
  @click="toggle"
403
+ @keyup.up.prevent.stop="resizeVertical(true)"
404
+ @keyup.down.prevent.stop="resizeVertical(false)"
355
405
  >
356
406
  <i
357
407
  class="icon icon-sort"
358
- :alt="t('wm.containerShell.resizeShellWindow')"
408
+ :alt="t('wm.containerShell.resizeShellWindow', {arrow1: 'up', arrow2: 'down'})"
359
409
  />
360
410
  </div>
361
411
  <div
362
412
  v-if="userPin == 'left'"
363
413
  class="resizer resizer-x resizer-align-right"
414
+ role="button"
415
+ tabindex="0"
416
+ :aria-label="t('wm.containerShell.resizeShellWindow', {arrow1: 'left', arrow2: 'right'})"
417
+ aria-expanded="true"
364
418
  @mousedown.prevent.stop="dragXStart($event)"
365
419
  @touchstart.prevent.stop="dragXStart($event)"
420
+ @keyup.left.prevent.stop="resizeHorizontal(true)"
421
+ @keyup.right.prevent.stop="resizeHorizontal(false)"
366
422
  >
367
423
  <i
368
424
  class="icon icon-code"
369
- :alt="t('wm.containerShell.resizeShellWindow')"
425
+ :alt="t('wm.containerShell.resizeShellWindow', {arrow1: 'left', arrow2: 'right'})"
370
426
  />
371
427
  </div>
372
428
  </div>
373
429
  <div
374
430
  v-for="(tab, i) in tabs"
431
+ :id="`panel-${tab.id}`"
375
432
  :key="i"
376
433
  class="body"
377
434
  :class="{'active': tab.id === active}"
378
435
  draggable="false"
436
+ role="tabpanel"
379
437
  @dragstart.prevent.stop
380
438
  @dragend.prevent.stop
381
439
  @mouseover="emitDraggable(false)"
@@ -442,6 +500,11 @@ export default {
442
500
  z-index: 1;
443
501
  }
444
502
 
503
+ &:focus-visible {
504
+ @include focus-outline;
505
+ outline-offset: -3px;
506
+ }
507
+
445
508
  .closer {
446
509
  margin-left: 5px;
447
510
  border: 1px solid var(--body-text);
@@ -457,6 +520,11 @@ export default {
457
520
  border-color: var(--link-border);
458
521
  color: var(--link-border);
459
522
  }
523
+
524
+ &:focus-visible {
525
+ @include focus-outline;
526
+ outline-offset: 1px;
527
+ }
460
528
  }
461
529
  }
462
530
 
@@ -1,5 +1,4 @@
1
1
  import TopLevelMenu from '@shell/components/nav/TopLevelMenu.vue';
2
- import { SETTING } from '@shell/config/settings';
3
2
  import { mount, Wrapper } from '@vue/test-utils';
4
3
  import { CAPI, COUNT, MANAGEMENT } from '@shell/config/types';
5
4
  import { PINNED_CLUSTERS } from '@shell/store/prefs';
@@ -429,45 +428,6 @@ describe('topLevelMenu', () => {
429
428
  expect(description4.text()).toStrictEqual('some-description4');
430
429
  });
431
430
 
432
- it('should not "crash" the component if the structure of banner settings is in an old format', async() => {
433
- const wrapper: Wrapper<InstanceType<typeof TopLevelMenu>> = mount(TopLevelMenu, {
434
- global: {
435
- mocks: {
436
- $route: {},
437
- $store: {
438
- ...generateStore(
439
- [{ name: 'whatever' }],
440
- [
441
- // object based on https://github.com/rancher/dashboard/issues/10140#issuecomment-1883252402
442
- {
443
- id: SETTING.BANNERS,
444
- value: JSON.stringify({
445
- banner: {
446
- color: '#78c9cf',
447
- background: '#27292e',
448
- text: 'Hello World!'
449
- },
450
- showHeader: 'true',
451
- showFooter: 'true'
452
- })
453
- }
454
- ]
455
- ),
456
- }
457
- },
458
-
459
- stubs: ['BrandImage', 'router-link'],
460
- },
461
- });
462
-
463
- await waitForIt();
464
-
465
- expect(wrapper.vm.sideMenuStyle).toStrictEqual({
466
- marginBottom: '2em',
467
- marginTop: '2em'
468
- });
469
- });
470
-
471
431
  describe('searching a term', () => {
472
432
  describe('should displays a no results message if have clusters but', () => {
473
433
  it('given no matching clusters', async() => {