@rancher/shell 3.0.5-rc.1 → 3.0.5-rc.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 (200) hide show
  1. package/assets/images/providers/sks.svg +1 -0
  2. package/assets/styles/base/_helpers.scss +4 -0
  3. package/assets/styles/base/_variables.scss +1 -0
  4. package/assets/translations/en-us.yaml +31 -15
  5. package/assets/translations/zh-hans.yaml +4 -3
  6. package/chart/monitoring/index.vue +3 -1
  7. package/components/ActionDropdownShell.vue +71 -0
  8. package/components/AppModal.vue +18 -4
  9. package/components/CommunityLinks.vue +3 -58
  10. package/components/CruResource.vue +6 -1
  11. package/components/ExplorerProjectsNamespaces.vue +12 -4
  12. package/components/GlobalRoleBindings.vue +5 -1
  13. package/components/GrowlManager.vue +1 -0
  14. package/components/LandingPagePreference.vue +2 -0
  15. package/components/LocaleSelector.vue +1 -1
  16. package/components/ModalManager.vue +55 -0
  17. package/components/PromptModal.vue +47 -8
  18. package/components/ResourceDetail/Masthead.vue +38 -12
  19. package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
  20. package/components/ResourceDetail/index.vue +47 -12
  21. package/components/ResourceTable.vue +54 -19
  22. package/components/SideNav.vue +5 -1
  23. package/components/SlideInPanelManager.vue +126 -0
  24. package/components/SortableTable/THead.vue +5 -2
  25. package/components/SortableTable/actions.js +1 -1
  26. package/components/SortableTable/index.vue +54 -40
  27. package/components/SortableTable/paging.js +16 -19
  28. package/components/SortableTable/selection.js +0 -11
  29. package/components/Wizard.vue +2 -2
  30. package/components/__tests__/ModalManager.spec.ts +176 -0
  31. package/components/__tests__/PromptModal.test.ts +148 -0
  32. package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
  33. package/components/auth/AuthBanner.vue +13 -11
  34. package/components/auth/Principal.vue +1 -0
  35. package/components/auth/login/ldap.vue +1 -1
  36. package/components/fleet/FleetResources.vue +21 -6
  37. package/components/form/ArrayList.vue +10 -6
  38. package/components/form/BannerSettings.vue +17 -2
  39. package/components/form/ColorInput.vue +35 -6
  40. package/components/form/EnvVars.vue +1 -0
  41. package/components/form/LabeledSelect.vue +18 -23
  42. package/components/form/MatchExpressions.vue +4 -1
  43. package/components/form/NameNsDescription.vue +5 -1
  44. package/components/form/NotificationSettings.vue +15 -1
  45. package/components/form/Password.vue +1 -0
  46. package/components/form/Probe.vue +1 -0
  47. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +15 -34
  48. package/components/form/SSHKnownHosts/index.vue +14 -11
  49. package/components/form/Select.vue +1 -15
  50. package/components/form/ValueFromResource.vue +12 -12
  51. package/components/form/__tests__/ArrayList.test.ts +2 -2
  52. package/components/form/__tests__/ColorInput.test.ts +35 -0
  53. package/components/form/__tests__/LabeledSelect.test.ts +40 -0
  54. package/components/form/__tests__/SSHKnownHosts.test.ts +11 -2
  55. package/components/nav/Group.vue +12 -4
  56. package/components/nav/Header.vue +16 -43
  57. package/components/nav/NamespaceFilter.vue +134 -86
  58. package/components/nav/TopLevelMenu.vue +4 -5
  59. package/components/nav/WindowManager/ContainerLogs.vue +87 -61
  60. package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
  61. package/components/templates/default.vue +6 -3
  62. package/components/templates/home.vue +6 -0
  63. package/components/templates/plain.vue +6 -3
  64. package/composables/focusTrap.ts +12 -4
  65. package/config/store.js +4 -0
  66. package/config/uiplugins.js +5 -1
  67. package/core/types.ts +7 -6
  68. package/detail/catalog.cattle.io.app.vue +6 -1
  69. package/detail/fleet.cattle.io.bundle.vue +70 -6
  70. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  71. package/detail/namespace.vue +0 -3
  72. package/detail/node.vue +17 -13
  73. package/detail/provisioning.cattle.io.cluster.vue +72 -6
  74. package/dialog/AddCustomBadgeDialog.vue +0 -1
  75. package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
  76. package/dialog/AssignToDialog.vue +176 -0
  77. package/dialog/ChangePasswordDialog.vue +106 -0
  78. package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
  79. package/dialog/DisableAuthProviderDialog.vue +101 -0
  80. package/dialog/DrainNode.vue +1 -1
  81. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
  82. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
  83. package/dialog/FeatureFlagListDialog.vue +288 -0
  84. package/dialog/ForceMachineRemoveDialog.vue +1 -1
  85. package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
  86. package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
  87. package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -62
  88. package/dialog/MoveNamespaceDialog.vue +157 -0
  89. package/dialog/ScalePoolDownDialog.vue +1 -1
  90. package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
  91. package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
  92. package/dialog/WechatDialog.vue +57 -0
  93. package/edit/auth/azuread.vue +1 -1
  94. package/edit/auth/github.vue +1 -1
  95. package/edit/auth/googleoauth.vue +1 -1
  96. package/edit/auth/ldap/index.vue +1 -1
  97. package/edit/auth/oidc.vue +1 -1
  98. package/edit/auth/saml.vue +1 -1
  99. package/edit/cloudcredential.vue +24 -10
  100. package/edit/management.cattle.io.user.vue +28 -3
  101. package/edit/namespace.vue +1 -4
  102. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
  103. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -10
  104. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
  105. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
  106. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
  107. package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
  108. package/edit/provisioning.cattle.io.cluster/rke2.vue +24 -7
  109. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
  110. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +4 -1
  111. package/initialize/install-plugins.js +2 -1
  112. package/list/harvesterhci.io.management.cluster.vue +4 -1
  113. package/list/management.cattle.io.feature.vue +4 -288
  114. package/machine-config/azure.vue +16 -4
  115. package/mixins/vue-select-overrides.js +0 -4
  116. package/models/fleet.cattle.io.cluster.js +8 -2
  117. package/models/fleet.cattle.io.gitrepo.js +8 -34
  118. package/models/management.cattle.io.feature.js +7 -1
  119. package/models/namespace.js +7 -1
  120. package/package.json +1 -1
  121. package/pages/about.vue +13 -3
  122. package/pages/account/index.vue +12 -5
  123. package/pages/auth/login.vue +7 -4
  124. package/pages/auth/setup.vue +1 -0
  125. package/pages/auth/verify.vue +9 -7
  126. package/pages/c/_cluster/apps/charts/install.vue +26 -26
  127. package/pages/c/_cluster/auth/config/index.vue +10 -12
  128. package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
  129. package/pages/c/_cluster/explorer/index.vue +17 -15
  130. package/pages/c/_cluster/istio/index.vue +2 -2
  131. package/pages/c/_cluster/longhorn/index.vue +1 -1
  132. package/pages/c/_cluster/monitoring/index.vue +1 -1
  133. package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
  134. package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
  135. package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
  136. package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
  137. package/pages/c/_cluster/neuvector/index.vue +1 -1
  138. package/pages/c/_cluster/settings/banners.vue +4 -3
  139. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
  140. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
  141. package/pages/c/_cluster/uiplugins/index.vue +98 -55
  142. package/pages/diagnostic.vue +12 -9
  143. package/pages/fail-whale.vue +8 -5
  144. package/pages/prefs.vue +7 -6
  145. package/plugins/internal-api/index.ts +37 -0
  146. package/plugins/internal-api/shared/base-api.ts +13 -0
  147. package/plugins/internal-api/shell/shell.api.ts +108 -0
  148. package/plugins/steve/actions.js +0 -12
  149. package/public/index.html +1 -0
  150. package/rancher-components/Card/Card.vue +1 -1
  151. package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
  152. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
  153. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
  154. package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
  155. package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
  156. package/rancher-components/Form/Radio/RadioButton.vue +20 -4
  157. package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
  158. package/rancher-components/Form/Radio/RadioGroup.vue +75 -35
  159. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
  160. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
  161. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
  162. package/rancher-components/RcButton/RcButton.vue +2 -1
  163. package/rancher-components/RcButton/types.ts +1 -0
  164. package/rancher-components/RcDropdown/RcDropdown.vue +17 -6
  165. package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
  166. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
  167. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
  168. package/rancher-components/RcDropdown/index.ts +2 -0
  169. package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
  170. package/scripts/extension/bundle +20 -0
  171. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
  172. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
  173. package/scripts/extension/helmpatch +44 -31
  174. package/scripts/extension/publish +12 -13
  175. package/scripts/typegen.sh +2 -4
  176. package/store/action-menu.js +26 -56
  177. package/store/index.js +5 -0
  178. package/store/modal.ts +71 -0
  179. package/store/slideInPanel.ts +47 -0
  180. package/store/type-map.js +8 -1
  181. package/store/type-map.utils.ts +4 -4
  182. package/types/global-vue.d.ts +5 -0
  183. package/types/internal-api/shell/growl.d.ts +25 -0
  184. package/types/internal-api/shell/modal.d.ts +77 -0
  185. package/types/internal-api/shell/slideIn.d.ts +15 -0
  186. package/types/resources/fleet.d.ts +0 -14
  187. package/types/shell/index.d.ts +35 -23
  188. package/types/vue-shim.d.ts +4 -1
  189. package/utils/__mocks__/tabbable.js +13 -0
  190. package/utils/__tests__/object.test.ts +38 -4
  191. package/utils/fleet.ts +15 -73
  192. package/utils/object.js +48 -5
  193. package/utils/validators/formRules/__tests__/index.test.ts +10 -1
  194. package/utils/validators/formRules/index.ts +27 -3
  195. package/components/AssignTo.vue +0 -199
  196. package/components/DisableAuthProviderModal.vue +0 -115
  197. package/components/MoveModal.vue +0 -167
  198. package/components/PromptChangePassword.vue +0 -123
  199. package/components/fleet/FleetBundleResources.vue +0 -86
  200. package/types/vue-shim.d +0 -20
@@ -21,7 +21,12 @@ export default ({
21
21
  return [_EDIT, _VIEW].includes(value);
22
22
  },
23
23
  default: _EDIT,
24
- }
24
+ },
25
+
26
+ hiddenAriaDescribedLabel: {
27
+ type: String,
28
+ default: ''
29
+ },
25
30
  },
26
31
 
27
32
  });
@@ -29,12 +34,20 @@ export default ({
29
34
 
30
35
  <template>
31
36
  <div>
37
+ <p
38
+ v-if="hiddenAriaDescribedLabel"
39
+ id="hidden-label-notification-settings"
40
+ class="sr-only"
41
+ >
42
+ {{ hiddenAriaDescribedLabel }}
43
+ </p>
32
44
  <div class="row mb-20">
33
45
  <div class="col span-6">
34
46
  <Checkbox
35
47
  :mode="mode"
36
48
  :value="value.showMessage === 'true'"
37
49
  :label="t('notifications.loginError.showCheckboxLabel')"
50
+ aria-describedby="hidden-label-notification-settings"
38
51
  @update:value="value.showMessage = $event.toString()"
39
52
  />
40
53
  </div>
@@ -48,6 +61,7 @@ export default ({
48
61
  :mode="mode"
49
62
  :disabled="value.showMessage === 'false'"
50
63
  :label="t('notifications.loginError.messageLabel')"
64
+ aria-describedby="hidden-label-notification-settings"
51
65
  />
52
66
  </div>
53
67
  </div>
@@ -137,6 +137,7 @@ export default {
137
137
  tabindex="0"
138
138
  class="hide-show"
139
139
  role="button"
140
+ :aria-label="reveal ? t('action.ariaLabel.hidePass', { area: label }) : t('action.ariaLabel.showPass', { area: label })"
140
141
  @click.prevent.stop="hideShowFn"
141
142
  @keyup.space.prevent.stop="hideShowFn"
142
143
  >
@@ -245,6 +245,7 @@ export default {
245
245
  v-if="kind && kind!=='none'"
246
246
  :style="{'position':'relative', 'margin':'0px'}"
247
247
  class="vertical"
248
+ role="none"
248
249
  >
249
250
  </div>
250
251
 
@@ -1,6 +1,6 @@
1
1
  import { mount, VueWrapper } from '@vue/test-utils';
2
2
  import { _EDIT } from '@shell/config/query-params';
3
- import KnownHostsEditDialog from '@shell/components/form/SSHKnownHosts/KnownHostsEditDialog.vue';
3
+ import KnownHostsEditDialog from '@shell/dialog/KnownHostsEditDialog.vue';
4
4
  import CodeMirror from '@shell/components/CodeMirror.vue';
5
5
  import FileSelector from '@shell/components/form/FileSelector.vue';
6
6
 
@@ -14,17 +14,6 @@ const requiredSetup = () => {
14
14
  return { global: { mocks: { $store: mockedStore() } } };
15
15
  };
16
16
 
17
- jest.mock('focus-trap', () => {
18
- return {
19
- createFocusTrap: jest.fn().mockImplementation(() => {
20
- return {
21
- activate: jest.fn(),
22
- deactivate: jest.fn(),
23
- };
24
- }),
25
- };
26
- });
27
-
28
17
  describe('component: KnownHostsEditDialog', () => {
29
18
  beforeEach(() => {
30
19
  document.body.innerHTML = '<div id="modals"></div>';
@@ -44,8 +33,6 @@ describe('component: KnownHostsEditDialog', () => {
44
33
  });
45
34
 
46
35
  it('should update text from CodeMirror', async() => {
47
- await wrapper.setData({ showModal: true });
48
-
49
36
  expect(wrapper.vm.text).toBe('line1\nline2\n');
50
37
 
51
38
  const codeMirror = wrapper.getComponent(CodeMirror);
@@ -62,8 +49,6 @@ describe('component: KnownHostsEditDialog', () => {
62
49
  });
63
50
 
64
51
  it('should update text from FileSelector', async() => {
65
- await wrapper.setData({ showModal: true });
66
-
67
52
  expect(wrapper.vm.text).toBe('line1\nline2\n');
68
53
 
69
54
  const fileSelector = wrapper.getComponent(FileSelector);
@@ -78,38 +63,34 @@ describe('component: KnownHostsEditDialog', () => {
78
63
  });
79
64
 
80
65
  it('should save changes and close dialog', async() => {
81
- await wrapper.setData({
82
- showModal: true,
83
- text: 'foo',
84
- });
66
+ const closed = jest.spyOn(wrapper.vm, 'closed');
67
+
68
+ await wrapper.setData({ text: 'foo' });
85
69
 
86
70
  expect(wrapper.vm.value).toBe('line1\nline2\n');
87
71
  expect(wrapper.vm.text).toBe('foo');
88
72
 
89
73
  await wrapper.vm.closeDialog(true);
90
74
 
91
- expect((wrapper.emitted('closed') as any)[0][0].value).toBe('foo');
92
-
93
- const dialog = wrapper.vm.$refs['sshKnownHostsDialog'];
94
-
95
- expect(dialog).toBeNull();
75
+ expect(closed).toHaveBeenCalledWith({
76
+ success: true,
77
+ value: 'foo'
78
+ });
96
79
  });
97
80
 
98
81
  it('should discard changes and close dialog', async() => {
99
- await wrapper.setData({
100
- showModal: true,
101
- text: 'foo',
102
- });
82
+ const closed = jest.spyOn(wrapper.vm, 'closed');
83
+
84
+ await wrapper.setData({ text: 'foo' });
103
85
 
104
86
  expect(wrapper.vm.value).toBe('line1\nline2\n');
105
87
  expect(wrapper.vm.text).toBe('foo');
106
88
 
107
89
  await wrapper.vm.closeDialog(false);
108
90
 
109
- expect((wrapper.emitted('closed') as any)[0][0].value).toBe('line1\nline2\n');
110
-
111
- const dialog = wrapper.vm.$refs['sshKnownHostsDialog'];
112
-
113
- expect(dialog).toBeNull();
91
+ expect(closed).toHaveBeenCalledWith({
92
+ success: false,
93
+ value: 'line1\nline2\n'
94
+ });
114
95
  });
115
96
  });
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent } from 'vue';
3
- import KnownHostsEditDialog from './KnownHostsEditDialog.vue';
4
3
  import { _EDIT, _VIEW } from '@shell/config/query-params';
5
4
 
6
5
  export default defineComponent({
@@ -20,8 +19,6 @@ export default defineComponent({
20
19
  },
21
20
  },
22
21
 
23
- components: { KnownHostsEditDialog },
24
-
25
22
  computed: {
26
23
  isViewMode() {
27
24
  return this.mode === _VIEW;
@@ -40,7 +37,20 @@ export default defineComponent({
40
37
  methods: {
41
38
  openDialog() {
42
39
  (this.$refs.button as HTMLInputElement)?.blur();
43
- (this.$refs.editDialog as any).showDialog();
40
+
41
+ this.$store.dispatch('management/promptModal', {
42
+ component: 'KnownHostsEditDialog',
43
+ returnFocusSelector: '#known-ssh-hosts-trigger',
44
+ testId: 'sshKnownHostsDialog',
45
+ height: 'auto',
46
+ componentProps: {
47
+ mode: this.mode,
48
+ value: this.value,
49
+ closed: (res: any) => {
50
+ this.dialogClosed(res);
51
+ }
52
+ }
53
+ });
44
54
  },
45
55
 
46
56
  dialogClosed(result: any) {
@@ -78,13 +88,6 @@ export default defineComponent({
78
88
  :alt="t('secret.ssh.editKnownHosts.title')"
79
89
  />
80
90
  </button>
81
-
82
- <KnownHostsEditDialog
83
- ref="editDialog"
84
- :value="value"
85
- :mode="mode"
86
- @closed="dialogClosed"
87
- />
88
91
  </template>
89
92
  </div>
90
93
  </template>
@@ -125,21 +125,6 @@ export default {
125
125
  },
126
126
 
127
127
  focusSearch() {
128
- // we need this override as in a "closeOnSelect" type of component
129
- // if we don't have this override, it would open again
130
- if (this.overridesMixinPreventDoubleTriggerKeysOpen) {
131
- this.$nextTick(() => {
132
- const el = this.$refs['select'];
133
-
134
- if ( el ) {
135
- el.focus();
136
- }
137
-
138
- this.overridesMixinPreventDoubleTriggerKeysOpen = false;
139
- });
140
-
141
- return;
142
- }
143
128
  this.$refs['select-input'].open = true;
144
129
 
145
130
  this.$nextTick(() => {
@@ -317,6 +302,7 @@ export default {
317
302
  @open="onOpen"
318
303
  @close="onClose"
319
304
  @option:created="(e) => $emit('createdListItem', e)"
305
+ @keydown.enter.stop
320
306
  >
321
307
  <template
322
308
  #option="option"
@@ -4,7 +4,7 @@ import { get } from '@shell/utils/object';
4
4
  import { _VIEW } from '@shell/config/query-params';
5
5
  import LabeledSelect from '@shell/components/form/LabeledSelect';
6
6
  import { LabeledInput } from '@components/Form/LabeledInput';
7
- import { ref, toRef, watch } from 'vue';
7
+ import { ref, watch } from 'vue';
8
8
 
9
9
  export default {
10
10
  emits: ['update:value', 'remove'],
@@ -83,14 +83,14 @@ export default {
83
83
 
84
84
  switch (type.value) {
85
85
  case 'resourceFieldRef':
86
- name.value = toRef(props.value.name);
87
- refName.value = toRef(props.value.valueFrom[type.value].containerName);
86
+ name.value = props.value.name;
87
+ refName.value = props.value.valueFrom[type.value].containerName;
88
88
  key.value = props.value.valueFrom[type.value].resource || '';
89
89
  break;
90
90
  case 'configMapKeyRef':
91
- name.value = toRef(props.value.name);
91
+ name.value = props.value.name;
92
92
  key.value = props.value.valueFrom[type.value].key || '';
93
- refName.value = toRef(props.value.valueFrom[type.value].name);
93
+ refName.value = props.value.valueFrom[type.value].name;
94
94
  referenced.value = props.allConfigMaps.filter((resource) => {
95
95
  return resource.metadata.name === refName.value;
96
96
  })[0];
@@ -100,13 +100,13 @@ export default {
100
100
  break;
101
101
  case 'secretRef':
102
102
  case 'configMapRef':
103
- name.value = toRef(props.value.prefix);
104
- refName.value = toRef(props.value[type.value].name);
103
+ name.value = props.value.prefix;
104
+ refName.value = props.value[type.value].name;
105
105
  break;
106
106
  case 'secretKeyRef':
107
- name.value = toRef(props.value.name);
107
+ name.value = props.value.name;
108
108
  key.value = props.value.valueFrom[type.value].key || '';
109
- refName.value = toRef(props.value.valueFrom[type.value].name);
109
+ refName.value = props.value.valueFrom[type.value].name;
110
110
  referenced.value = props.allSecrets.filter((resource) => {
111
111
  return resource.metadata.name === refName.value;
112
112
  })[0];
@@ -116,11 +116,11 @@ export default {
116
116
  break;
117
117
  case 'fieldRef':
118
118
  fieldPath.value = get(props.value.valueFrom, `${ type.value }.fieldPath`) || '';
119
- name.value = toRef(props.value.name);
119
+ name.value = props.value.name;
120
120
  break;
121
121
  default:
122
- name.value = toRef(props.value.name);
123
- valStr.value = toRef(props.value.value);
122
+ name.value = props.value.name;
123
+ valStr.value = props.value.value;
124
124
  break;
125
125
  }
126
126
 
@@ -58,11 +58,11 @@ describe('the ArrayList', () => {
58
58
  });
59
59
 
60
60
  jest.useFakeTimers();
61
- await (wrapper.get('[data-testid="remove-item-1"]').element as HTMLElement).click();
61
+ await (wrapper.get('[data-testid="array-list-remove-item-1"]').element as HTMLElement).click();
62
62
  jest.advanceTimersByTime(50);
63
63
  jest.useRealTimers();
64
64
 
65
- expect(wrapper.find('[data-testid="remove-item-2"]').exists()).toBe(false);
65
+ expect(wrapper.find('[data-testid="array-list-remove-item-2"]').exists()).toBe(false);
66
66
  expect((wrapper.emitted('remove')![0][0] as any).row.value).toStrictEqual('string 1');
67
67
  expect(wrapper.vm.rows).toStrictEqual([{ value: 'string 0' }, { value: 'string 2' }]);
68
68
  expect(wrapper.emitted('update:value')![0][0]).toStrictEqual(['string 0', 'string 2']);
@@ -24,4 +24,39 @@ describe('colorInput.vue', () => {
24
24
  expect(colorWrapper.classes()).not.toContain('disabled');
25
25
  expect(Object.keys(colorInput.attributes())).not.toContain('disabled');
26
26
  });
27
+
28
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', () => {
29
+ const label = 'some-label';
30
+ const describeById = 'some-id';
31
+
32
+ const wrapper = shallowMount(ColorInput, {
33
+ props: { label },
34
+ attrs: { 'aria-describedby': describeById }
35
+ });
36
+
37
+ const colorInput = wrapper.find('input');
38
+ const ariaDisabled = colorInput.attributes('aria-disabled');
39
+ const ariaLabel = colorInput.attributes('aria-label');
40
+ const ariaDescribedBy = colorInput.attributes('aria-describedby');
41
+
42
+ expect(ariaDisabled).toBe('false');
43
+ expect(ariaLabel).toBe(label);
44
+ expect(ariaDescribedBy).toBe(describeById);
45
+ });
46
+
47
+ it('a11y: adding aria-label ($attrs) from parent should override label-based aria-label', () => {
48
+ const inputLabel = 'some-label';
49
+ const overrideLabel = 'some-override-label';
50
+
51
+ const wrapper = shallowMount(ColorInput, {
52
+ props: { label: inputLabel },
53
+ attrs: { 'aria-label': overrideLabel }
54
+ });
55
+
56
+ const colorInput = wrapper.find('input');
57
+ const ariaLabel = colorInput.attributes('aria-label');
58
+
59
+ expect(ariaLabel).toBe(overrideLabel);
60
+ expect(ariaLabel).not.toBe(inputLabel);
61
+ });
27
62
  });
@@ -214,4 +214,44 @@ describe('component: LabeledSelect', () => {
214
214
  expect(wrapper.vm.$data.myValue).toStrictEqual(expectation);
215
215
  });
216
216
  });
217
+
218
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
219
+ const label = 'Foo';
220
+ const value = 'foo';
221
+ const ariaDescribedById = 'some-described-by-id';
222
+ const itemLabel = 'some-label';
223
+
224
+ const wrapper = mount(LabeledSelect, {
225
+ props: {
226
+ value,
227
+ label: itemLabel,
228
+ options: [
229
+ { label, value },
230
+ ],
231
+ required: true
232
+ },
233
+ attrs: { 'aria-describedby': ariaDescribedById }
234
+ });
235
+
236
+ const labeledSelectContainer = wrapper.find('.labeled-select');
237
+ const ariaExpanded = labeledSelectContainer.attributes('aria-expanded');
238
+ const ariaDescribedBy = labeledSelectContainer.attributes('aria-describedby');
239
+ const ariaRequired = labeledSelectContainer.attributes('aria-required');
240
+ const containerId = labeledSelectContainer.attributes('id');
241
+ const labelFor = wrapper.find('label').attributes('for');
242
+
243
+ const vSelectInput = wrapper.find('.v-select');
244
+
245
+ expect(ariaExpanded).toBe('false');
246
+ expect(ariaDescribedBy).toBe(ariaDescribedById);
247
+ expect(ariaRequired).toBe('true');
248
+ expect(containerId).toBe(wrapper.vm.labeledSelectLabelId);
249
+ expect(labelFor).toBe(wrapper.vm.labeledSelectLabelId);
250
+
251
+ // make sure it's hardcoded to a "neutral" value so that
252
+ // in the current architecture of the component
253
+ // screen readers won't pick up the default "Select option" aria-label
254
+ // from the library
255
+ expect(vSelectInput.attributes('aria-label')).toBe('-');
256
+ });
217
257
  });
@@ -1,6 +1,7 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { _EDIT, _VIEW } from '@shell/config/query-params';
3
3
  import SSHKnownHosts from '@shell/components/form/SSHKnownHosts/index.vue';
4
+ import { createStore } from 'vuex';
4
5
 
5
6
  jest.mock('focus-trap', () => {
6
7
  return {
@@ -53,18 +54,26 @@ describe('component: SSHKnownHosts', () => {
53
54
  });
54
55
 
55
56
  it('mode edit: should open edit dialog', async() => {
57
+ const actions = { 'management/promptModal': jest.fn() };
58
+
56
59
  const wrapper = mount(SSHKnownHosts, {
57
60
  props: {
58
61
  mode: _EDIT,
59
62
  value: '',
63
+ },
64
+ global: {
65
+ mocks: {
66
+ $store: createStore({ actions }),
67
+ $fetchState: {}
68
+ },
69
+ stubs: { transition: false }
60
70
  }
61
71
  });
62
72
 
63
73
  const knownSshHostsOpenDialog = wrapper.find('[data-testid="input-known-ssh-hosts_open-dialog"]');
64
- const editDialog = wrapper.vm.$refs['editDialog'] as any;
65
74
 
66
75
  await knownSshHostsOpenDialog.trigger('click');
67
76
 
68
- expect(editDialog.showModal).toBe(true);
77
+ expect(actions['management/promptModal']).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ component: 'KnownHostsEditDialog' }));
69
78
  });
70
79
  });
@@ -99,6 +99,11 @@ export default {
99
99
  },
100
100
 
101
101
  groupSelected() {
102
+ // Can not click on groups that are fixed open
103
+ if (this.fixedOpen) {
104
+ return;
105
+ }
106
+
102
107
  // Don't auto-select first group entry if we're already expanded and contain the currently-selected nav item
103
108
  if (this.hasActiveRoute() && this.isExpanded) {
104
109
  return;
@@ -219,14 +224,14 @@ export default {
219
224
  <template>
220
225
  <div
221
226
  class="accordion"
222
- :class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': isGroupActive}"
227
+ :class="{[`depth-${depth}`]: true, 'expanded': isExpanded, 'has-children': hasChildren, 'group-highlight': isGroupActive }"
223
228
  >
224
229
  <div
225
230
  v-if="showHeader"
226
231
  class="header"
227
- :class="{'active': isOverview, 'noHover': !canCollapse}"
232
+ :class="{'active': isOverview, 'noHover': !canCollapse || fixedOpen}"
228
233
  role="button"
229
- tabindex="0"
234
+ :tabindex="fixedOpen ? -1 : 0"
230
235
  :aria-label="group.labelDisplay || group.label || ''"
231
236
  @click="groupSelected()"
232
237
  @keyup.enter="groupSelected()"
@@ -274,7 +279,7 @@ export default {
274
279
  v-if="child.divider"
275
280
  :key="idx"
276
281
  >
277
- <hr>
282
+ <hr role="none">
278
283
  </li>
279
284
  <!-- <div v-else-if="child[childrenKey] && hideGroup(child[childrenKey])" :key="child.name">
280
285
  HIDDEN
@@ -372,6 +377,9 @@ export default {
372
377
  &:hover:not(.active) {
373
378
  background-color: var(--nav-hover);
374
379
  }
380
+ &:hover:not(.active).noHover {
381
+ background-color: inherit;
382
+ }
375
383
  }
376
384
  }
377
385
 
@@ -5,7 +5,6 @@ import { MANAGEMENT, NORMAN, STEVE } from '@shell/config/types';
5
5
  import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
6
6
  import { ucFirst } from '@shell/utils/string';
7
7
  import { isAlternate, isMac } from '@shell/utils/platform';
8
- import Import from '@shell/components/Import';
9
8
  import BrandImage from '@shell/components/BrandImage';
10
9
  import { getProduct, getVendor } from '@shell/config/private-label';
11
10
  import ClusterProviderIcon from '@shell/components/ClusterProviderIcon';
@@ -16,7 +15,6 @@ import NamespaceFilter from './NamespaceFilter';
16
15
  import WorkspaceSwitcher from './WorkspaceSwitcher';
17
16
  import TopLevelMenu from './TopLevelMenu';
18
17
 
19
- import Jump from './Jump';
20
18
  import { allHash } from '@shell/utils/promise';
21
19
  import { ActionLocation, ExtensionPoint } from '@shell/core/types';
22
20
  import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
@@ -36,9 +34,7 @@ export default {
36
34
  components: {
37
35
  NamespaceFilter,
38
36
  WorkspaceSwitcher,
39
- Import,
40
37
  TopLevelMenu,
41
- Jump,
42
38
  BrandImage,
43
39
  ClusterBadge,
44
40
  ClusterProviderIcon,
@@ -79,9 +75,7 @@ export default {
79
75
  LOGGED_OUT,
80
76
  navHeaderRight: null,
81
77
  extensionHeaderActions: getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, this.$route),
82
- ctx: this,
83
- showImportModal: false,
84
- showSearchModal: false
78
+ ctx: this
85
79
  };
86
80
  },
87
81
 
@@ -316,19 +310,24 @@ export default {
316
310
  },
317
311
 
318
312
  openImport() {
319
- this.showImportModal = true;
320
- },
321
-
322
- closeImport() {
323
- this.showImportModal = false;
313
+ this.$store.dispatch('cluster/promptModal', {
314
+ component: 'ImportDialog',
315
+ modalWidth: '75%',
316
+ height: 'auto',
317
+ styles: 'max-height: 90vh;',
318
+ componentProps: { cluster: this.currentCluster }
319
+ });
324
320
  },
325
321
 
326
322
  openSearch() {
327
- this.showSearchModal = true;
328
- },
329
-
330
- hideSearch() {
331
- this.showSearchModal = false;
323
+ this.$store.dispatch('cluster/promptModal', {
324
+ component: 'SearchDialog',
325
+ testId: 'search-modal',
326
+ modalWidth: '50%',
327
+ height: 'auto',
328
+ styles: 'max-height: 90vh;',
329
+ returnFocusSelector: '#header-btn-search'
330
+ });
332
331
  },
333
332
 
334
333
  checkClusterName() {
@@ -555,20 +554,6 @@ export default {
555
554
  >
556
555
  <i class="icon icon-upload icon-lg" />
557
556
  </button>
558
- <app-modal
559
- v-if="showImportModal"
560
- class="import-modal"
561
- name="importModal"
562
- width="75%"
563
- height="auto"
564
- styles="max-height: 90vh;"
565
- @close="closeImport"
566
- >
567
- <Import
568
- :cluster="currentCluster"
569
- @close="closeImport"
570
- />
571
- </app-modal>
572
557
 
573
558
  <button
574
559
  v-if="showKubeShell"
@@ -641,18 +626,6 @@ export default {
641
626
  >
642
627
  <i class="icon icon-search icon-lg" />
643
628
  </button>
644
- <app-modal
645
- v-if="showSearch && showSearchModal"
646
- class="search-modal"
647
- name="searchModal"
648
- width="50%"
649
- height="auto"
650
- :trigger-focus-trap="true"
651
- return-focus-selector="#header-btn-search"
652
- @close="hideSearch()"
653
- >
654
- <Jump @closeSearch="hideSearch()" />
655
- </app-modal>
656
629
  </div>
657
630
 
658
631
  <!-- Extension header actions -->