@rancher/shell 3.0.4 → 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 (270) hide show
  1. package/assets/images/providers/sks.svg +1 -0
  2. package/assets/styles/base/_basic.scss +6 -0
  3. package/assets/styles/base/_helpers.scss +4 -0
  4. package/assets/styles/base/_variables.scss +1 -0
  5. package/assets/styles/global/_button.scss +1 -0
  6. package/assets/translations/en-us.yaml +65 -15
  7. package/assets/translations/zh-hans.yaml +4 -3
  8. package/chart/monitoring/index.vue +3 -1
  9. package/cloud-credential/aws.vue +2 -0
  10. package/components/ActionDropdownShell.vue +71 -0
  11. package/components/AppModal.vue +18 -4
  12. package/components/AsyncButton.vue +24 -7
  13. package/components/BannerGraphic.vue +1 -0
  14. package/components/CommunityLinks.vue +4 -59
  15. package/components/CopyToClipboardText.vue +2 -1
  16. package/components/CruResource.vue +6 -1
  17. package/components/DetailText.vue +5 -0
  18. package/components/ExplorerMembers.vue +1 -1
  19. package/components/ExplorerProjectsNamespaces.vue +68 -18
  20. package/components/GlobalRoleBindings.vue +5 -1
  21. package/components/GrowlManager.vue +1 -0
  22. package/components/LandingPagePreference.vue +7 -3
  23. package/components/LocaleSelector.vue +39 -95
  24. package/components/ModalManager.vue +55 -0
  25. package/components/ModalWithCard.vue +1 -0
  26. package/components/PromptModal.vue +47 -8
  27. package/components/PromptRemove.vue +1 -0
  28. package/components/PromptRestore.vue +1 -0
  29. package/components/ResourceCancelModal.vue +1 -0
  30. package/components/ResourceDetail/Masthead.vue +38 -12
  31. package/components/ResourceDetail/__tests__/Masthead.test.ts +5 -1
  32. package/components/ResourceDetail/index.vue +47 -12
  33. package/components/ResourceTable.vue +54 -19
  34. package/components/SideNav.vue +5 -1
  35. package/components/SlideInPanelManager.vue +126 -0
  36. package/components/SortableTable/THead.vue +5 -2
  37. package/components/SortableTable/actions.js +1 -1
  38. package/components/SortableTable/index.vue +64 -51
  39. package/components/SortableTable/paging.js +16 -19
  40. package/components/SortableTable/selection.js +0 -11
  41. package/components/Wizard.vue +2 -2
  42. package/components/__tests__/AsyncButton.test.ts +2 -2
  43. package/components/__tests__/ModalManager.spec.ts +176 -0
  44. package/components/__tests__/PromptModal.test.ts +148 -0
  45. package/components/__tests__/SlideInPanelManager.spec.ts +166 -0
  46. package/components/auth/AuthBanner.vue +13 -11
  47. package/components/auth/Principal.vue +1 -0
  48. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  49. package/components/auth/login/ldap.vue +1 -1
  50. package/components/fleet/FleetResources.vue +21 -6
  51. package/components/form/ArrayList.vue +76 -60
  52. package/components/form/BannerSettings.vue +17 -2
  53. package/components/form/ColorInput.vue +35 -6
  54. package/components/form/Command.vue +6 -15
  55. package/components/form/EnvVars.vue +16 -8
  56. package/components/form/HealthCheck.vue +3 -3
  57. package/components/form/HookOption.vue +11 -16
  58. package/components/form/LabeledSelect.vue +18 -22
  59. package/components/form/LifecycleHooks.vue +3 -3
  60. package/components/form/MatchExpressions.vue +14 -8
  61. package/components/form/NameNsDescription.vue +128 -104
  62. package/components/form/Networking.vue +20 -12
  63. package/components/form/NodeAffinity.vue +31 -23
  64. package/components/form/NodeScheduling.vue +13 -3
  65. package/components/form/NotificationSettings.vue +15 -1
  66. package/components/form/Password.vue +1 -0
  67. package/components/form/PodAffinity.vue +43 -43
  68. package/components/form/Probe.vue +68 -66
  69. package/components/form/ResourceQuota/Project.vue +5 -1
  70. package/components/form/ResourceSelector.vue +7 -9
  71. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +16 -24
  72. package/components/form/SSHKnownHosts/index.vue +30 -13
  73. package/components/form/Security.vue +54 -56
  74. package/components/form/Select.vue +32 -21
  75. package/components/form/ShellInput.vue +5 -1
  76. package/components/form/Tolerations.vue +5 -1
  77. package/components/form/ValueFromResource.vue +134 -121
  78. package/components/form/WorkloadPorts.vue +18 -18
  79. package/components/form/__tests__/ArrayList.test.ts +5 -2
  80. package/components/form/__tests__/ColorInput.test.ts +35 -0
  81. package/components/form/__tests__/LabeledSelect.test.ts +40 -0
  82. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  83. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  84. package/components/form/__tests__/Probe.test.ts +12 -8
  85. package/components/form/__tests__/SSHKnownHosts.test.ts +22 -2
  86. package/components/form/__tests__/Select.test.ts +37 -0
  87. package/components/formatter/InternalExternalIP.vue +2 -0
  88. package/components/formatter/SecretData.vue +20 -7
  89. package/components/nav/Group.vue +27 -5
  90. package/components/nav/Header.vue +17 -43
  91. package/components/nav/NamespaceFilter.vue +134 -86
  92. package/components/nav/TopLevelMenu.vue +4 -5
  93. package/components/nav/Type.vue +12 -1
  94. package/components/nav/WindowManager/ContainerLogs.vue +87 -61
  95. package/components/nav/WindowManager/ContainerLogsActions.vue +76 -0
  96. package/components/templates/blank.vue +4 -1
  97. package/components/templates/default.vue +8 -3
  98. package/components/templates/home.vue +10 -1
  99. package/components/templates/plain.vue +10 -4
  100. package/composables/focusTrap.ts +12 -4
  101. package/composables/useRuntimeFlag.ts +29 -0
  102. package/config/router/routes.js +20 -13
  103. package/config/store.js +4 -0
  104. package/config/uiplugins.js +5 -1
  105. package/core/types.ts +12 -6
  106. package/detail/catalog.cattle.io.app.vue +6 -1
  107. package/detail/fleet.cattle.io.bundle.vue +70 -6
  108. package/detail/fleet.cattle.io.gitrepo.vue +1 -1
  109. package/detail/namespace.vue +0 -3
  110. package/detail/node.vue +17 -13
  111. package/detail/provisioning.cattle.io.cluster.vue +72 -6
  112. package/dialog/AddCustomBadgeDialog.vue +1 -1
  113. package/{pages/c/_cluster/uiplugins/AddExtensionRepos.vue → dialog/AddExtensionReposDialog.vue} +72 -42
  114. package/{components/AssignTo.vue → dialog/AssignToDialog.vue} +71 -80
  115. package/dialog/ChangePasswordDialog.vue +106 -0
  116. package/dialog/DeactivateDriverDialog.vue +1 -0
  117. package/{pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue → dialog/DeveloperLoadExtensionDialog.vue} +74 -71
  118. package/dialog/DisableAuthProviderDialog.vue +101 -0
  119. package/dialog/DrainNode.vue +1 -1
  120. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue → dialog/ExtensionCatalogInstallDialog.vue} +100 -88
  121. package/{pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue → dialog/ExtensionCatalogUninstallDialog.vue} +69 -57
  122. package/dialog/FeatureFlagListDialog.vue +288 -0
  123. package/dialog/ForceMachineRemoveDialog.vue +5 -2
  124. package/{components/Import.vue → dialog/ImportDialog.vue} +0 -5
  125. package/{pages/c/_cluster/uiplugins/InstallDialog.vue → dialog/InstallExtensionDialog.vue} +124 -106
  126. package/{components/form/SSHKnownHosts → dialog}/KnownHostsEditDialog.vue +52 -59
  127. package/dialog/MoveNamespaceDialog.vue +157 -0
  128. package/dialog/ScalePoolDownDialog.vue +1 -1
  129. package/{components/nav/Jump.vue → dialog/SearchDialog.vue} +34 -14
  130. package/{pages/c/_cluster/uiplugins/UninstallDialog.vue → dialog/UninstallExtensionDialog.vue} +67 -58
  131. package/dialog/WechatDialog.vue +57 -0
  132. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  133. package/edit/auth/__tests__/oidc.test.ts +152 -109
  134. package/edit/auth/azuread.vue +2 -1
  135. package/edit/auth/github.vue +1 -1
  136. package/edit/auth/googleoauth.vue +5 -1
  137. package/edit/auth/ldap/index.vue +1 -1
  138. package/edit/auth/oidc.vue +38 -5
  139. package/edit/auth/saml.vue +1 -1
  140. package/edit/cloudcredential.vue +24 -9
  141. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  142. package/edit/management.cattle.io.user.vue +28 -3
  143. package/edit/namespace.vue +1 -4
  144. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  145. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  146. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  147. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +4 -1
  148. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +26 -9
  149. package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +8 -8
  150. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +26 -12
  151. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +66 -0
  152. package/edit/provisioning.cattle.io.cluster/__tests__/utils/rke2-test-data.ts +58 -0
  153. package/edit/provisioning.cattle.io.cluster/rke2.vue +49 -41
  154. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  155. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +5 -3
  156. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +33 -2
  157. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  158. package/edit/token.vue +2 -0
  159. package/edit/workload/index.vue +1 -0
  160. package/edit/workload/mixins/workload.js +0 -2
  161. package/initialize/install-plugins.js +2 -1
  162. package/list/harvesterhci.io.management.cluster.vue +4 -1
  163. package/list/management.cattle.io.feature.vue +4 -287
  164. package/list/provisioning.cattle.io.cluster.vue +20 -12
  165. package/machine-config/azure.vue +16 -4
  166. package/mixins/vue-select-overrides.js +0 -4
  167. package/models/__tests__/namespace.test.ts +25 -1
  168. package/models/cloudcredential.js +5 -0
  169. package/models/fleet.cattle.io.cluster.js +8 -2
  170. package/models/fleet.cattle.io.gitrepo.js +8 -34
  171. package/models/kontainerdriver.js +6 -3
  172. package/models/management.cattle.io.feature.js +7 -1
  173. package/models/management.cattle.io.node.js +3 -3
  174. package/models/namespace.js +11 -6
  175. package/models/nodedriver.js +6 -3
  176. package/models/workload.js +4 -1
  177. package/package.json +3 -3
  178. package/pages/about.vue +13 -3
  179. package/pages/account/index.vue +16 -6
  180. package/pages/auth/login.vue +18 -7
  181. package/pages/auth/logout.vue +4 -1
  182. package/pages/auth/setup.vue +2 -0
  183. package/pages/auth/verify.vue +13 -8
  184. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  185. package/pages/c/_cluster/apps/charts/install.vue +26 -26
  186. package/pages/c/_cluster/auth/config/index.vue +10 -12
  187. package/pages/c/_cluster/explorer/EventsTable.vue +38 -33
  188. package/pages/c/_cluster/explorer/index.vue +17 -15
  189. package/pages/c/_cluster/istio/index.vue +2 -2
  190. package/pages/c/_cluster/longhorn/index.vue +1 -1
  191. package/pages/c/_cluster/monitoring/index.vue +1 -1
  192. package/pages/c/_cluster/monitoring/monitor/_namespace/_id.vue +4 -2
  193. package/pages/c/_cluster/monitoring/monitor/create.vue +4 -2
  194. package/pages/c/_cluster/monitoring/route-receiver/_id.vue +4 -2
  195. package/pages/c/_cluster/monitoring/route-receiver/create.vue +5 -2
  196. package/pages/c/_cluster/neuvector/index.vue +1 -1
  197. package/pages/c/_cluster/settings/banners.vue +4 -3
  198. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +8 -10
  199. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +4 -7
  200. package/pages/c/_cluster/uiplugins/index.vue +98 -55
  201. package/pages/diagnostic.vue +59 -11
  202. package/pages/fail-whale.vue +14 -8
  203. package/pages/home.vue +24 -18
  204. package/pages/prefs.vue +7 -6
  205. package/pages/support/index.vue +4 -1
  206. package/plugins/internal-api/index.ts +37 -0
  207. package/plugins/internal-api/shared/base-api.ts +13 -0
  208. package/plugins/internal-api/shell/shell.api.ts +108 -0
  209. package/plugins/steve/actions.js +0 -12
  210. package/public/index.html +1 -0
  211. package/rancher-components/Card/Card.vue +1 -1
  212. package/rancher-components/Form/Checkbox/Checkbox.test.ts +59 -1
  213. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -3
  214. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +47 -0
  215. package/rancher-components/Form/LabeledInput/LabeledInput.vue +20 -2
  216. package/rancher-components/Form/Radio/RadioButton.test.ts +36 -1
  217. package/rancher-components/Form/Radio/RadioButton.vue +20 -4
  218. package/rancher-components/Form/Radio/RadioGroup.test.ts +60 -0
  219. package/rancher-components/Form/Radio/RadioGroup.vue +52 -10
  220. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +17 -0
  221. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +5 -0
  222. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +10 -1
  223. package/rancher-components/RcButton/RcButton.vue +2 -1
  224. package/rancher-components/RcButton/types.ts +1 -0
  225. package/rancher-components/RcDropdown/RcDropdown.vue +18 -6
  226. package/rancher-components/RcDropdown/RcDropdownItem.vue +3 -56
  227. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +68 -0
  228. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +92 -0
  229. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -0
  230. package/rancher-components/RcDropdown/index.ts +2 -0
  231. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  232. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  233. package/rancher-components/RcDropdown/useDropdownItem.ts +63 -0
  234. package/scripts/extension/bundle +20 -0
  235. package/scripts/extension/helm/charts/ui-plugin-server/templates/cr.yaml +2 -1
  236. package/scripts/extension/helm/charts/ui-plugin-server/values.yaml +2 -0
  237. package/scripts/extension/helmpatch +44 -31
  238. package/scripts/extension/publish +12 -12
  239. package/scripts/typegen.sh +2 -4
  240. package/server/har-file.js +25 -3
  241. package/store/action-menu.js +26 -56
  242. package/store/features.js +2 -1
  243. package/store/index.js +5 -0
  244. package/store/modal.ts +71 -0
  245. package/store/slideInPanel.ts +47 -0
  246. package/store/type-map.js +12 -1
  247. package/store/type-map.utils.ts +4 -4
  248. package/types/global-vue.d.ts +5 -0
  249. package/types/internal-api/shell/growl.d.ts +25 -0
  250. package/types/internal-api/shell/modal.d.ts +77 -0
  251. package/types/internal-api/shell/slideIn.d.ts +15 -0
  252. package/types/resources/fleet.d.ts +0 -14
  253. package/types/shell/index.d.ts +43 -24
  254. package/types/vue-shim.d.ts +4 -1
  255. package/utils/__mocks__/tabbable.js +13 -0
  256. package/utils/__tests__/object.test.ts +38 -4
  257. package/utils/cluster.js +35 -0
  258. package/utils/fleet.ts +15 -73
  259. package/utils/object.js +48 -5
  260. package/utils/validators/formRules/__tests__/index.test.ts +10 -1
  261. package/utils/validators/formRules/index.ts +27 -3
  262. package/utils/validators/machine-pool.ts +20 -0
  263. package/components/DisableAuthProviderModal.vue +0 -114
  264. package/components/MoveModal.vue +0 -166
  265. package/components/PromptChangePassword.vue +0 -123
  266. package/components/fleet/FleetBundleResources.vue +0 -86
  267. package/components/formatter/ExtensionCache.vue +0 -74
  268. package/components/formatter/Port.vue +0 -24
  269. package/components/formatter/SecretType.vue +0 -41
  270. package/types/vue-shim.d +0 -20
@@ -0,0 +1,166 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { createStore, Store } from 'vuex';
3
+ import { nextTick } from 'vue';
4
+ import SlideInPanelManager from '@shell/components/SlideInPanelManager.vue';
5
+
6
+ const MockComponent = {
7
+ template: '<div data-testid="slide-in-panel-component">Mock Panel Content</div>',
8
+ props: ['width', 'title', 'extraProp']
9
+ };
10
+
11
+ describe('slideInPanelManager.vue with Teleport', () => {
12
+ let store: Store<any>;
13
+ let getters: Record<string, () => any>;
14
+ let slidesDiv: HTMLDivElement;
15
+
16
+ beforeEach(() => {
17
+ // Create teleport target container
18
+ slidesDiv = document.createElement('div');
19
+ slidesDiv.setAttribute('id', 'slides');
20
+ document.body.appendChild(slidesDiv);
21
+
22
+ getters = {
23
+ 'slideInPanel/isOpen': () => true,
24
+ 'slideInPanel/component': () => MockComponent,
25
+ 'slideInPanel/componentProps': () => ({
26
+ width: '40%', title: 'Test Title', extraProp: 'extra'
27
+ })
28
+ };
29
+
30
+ store = createStore({
31
+ getters,
32
+ mutations: { 'slideInPanel/close': jest.fn() }
33
+ });
34
+ });
35
+
36
+ afterEach(() => {
37
+ // Clean up the teleport container
38
+ document.body.removeChild(slidesDiv);
39
+ });
40
+
41
+ const factory = () => {
42
+ return mount(SlideInPanelManager, {
43
+ attachTo: document.body, // attach to document so Teleport renders
44
+ global: { plugins: [store] }
45
+ });
46
+ };
47
+
48
+ it('renders slide in panel with proper style when open', async() => {
49
+ factory();
50
+ await nextTick();
51
+
52
+ const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
53
+ const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
54
+ const slideComponent = document.querySelector('[data-testid="slide-in-panel-component"]') as HTMLElement;
55
+ const headerTitle = document.querySelector('#slides .slide-in .header .title') as HTMLElement;
56
+
57
+ expect(slidePanel).toBeTruthy();
58
+ expect(slideGlass).toBeTruthy();
59
+ expect(slideComponent).toBeTruthy();
60
+ expect(headerTitle.textContent?.trim()).toBe('Test Title');
61
+
62
+ const styleAttr = slidePanel.getAttribute('style') || '';
63
+
64
+ expect(styleAttr).toContain('width: 40%');
65
+ expect(styleAttr).toContain('top: 55px');
66
+ expect(styleAttr).toContain('height: calc(100vh - 55px)');
67
+ expect(styleAttr).toContain('right: 0');
68
+ });
69
+
70
+ it('renders default panel title when no title is provided', async() => {
71
+ // Update getter so that no title is provided
72
+ getters['slideInPanel/componentProps'] = () => ({ width: '40%' });
73
+ store = createStore({
74
+ getters,
75
+ mutations: { 'slideInPanel/close': jest.fn() }
76
+ });
77
+ factory();
78
+ await nextTick();
79
+
80
+ const headerTitle = document.querySelector('#slides #slide-in-panel-manager .header .title') as HTMLElement;
81
+
82
+ expect(headerTitle.textContent?.trim()).toBe('Details');
83
+ });
84
+
85
+ it('computes panelTop correctly when a banner exists', async() => {
86
+ // Create a banner element with a simulated clientHeight.
87
+ const banner = document.createElement('div');
88
+
89
+ banner.setAttribute('id', 'banner-header');
90
+ document.body.appendChild(banner);
91
+ // Simulate a banner with a clientHeight of 100.
92
+ Object.defineProperty(banner, 'clientHeight', { value: 100, configurable: true });
93
+
94
+ factory();
95
+ await nextTick();
96
+
97
+ const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
98
+ const styleAttr = slidePanel.getAttribute('style') || '';
99
+
100
+ // Expected panelTop = HEADER_HEIGHT (55) + banner.clientHeight (100) = "155px"
101
+ expect(styleAttr).toContain('top: 155px');
102
+ expect(styleAttr).toContain('height: calc(100vh - 155px)');
103
+
104
+ document.body.removeChild(banner);
105
+ });
106
+
107
+ it('renders slide in glass as hidden and panel with negative right when closed', async() => {
108
+ // Set isOpen to false.
109
+ getters['slideInPanel/isOpen'] = () => false;
110
+ store = createStore({
111
+ getters,
112
+ mutations: { 'slideInPanel/close': jest.fn() }
113
+ });
114
+ factory();
115
+ await nextTick();
116
+
117
+ const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
118
+
119
+ expect(slideGlass).toBeTruthy();
120
+ expect(slideGlass.style.display).toBe('none');
121
+
122
+ const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
123
+ const styleAttr = slidePanel.getAttribute('style') || '';
124
+
125
+ // With currentProps width "40%", panelRight should be "-40%" when closed.
126
+ expect(styleAttr).toContain('right: -40%');
127
+ });
128
+
129
+ it('calls store commit when clicking on the slide-in glass overlay', async() => {
130
+ const closeMutation = jest.fn();
131
+
132
+ getters['slideInPanel/isOpen'] = () => true;
133
+ store = createStore({
134
+ getters,
135
+ mutations: { 'slideInPanel/close': closeMutation }
136
+ });
137
+ factory();
138
+ await nextTick();
139
+
140
+ const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
141
+
142
+ slideGlass.click();
143
+ await nextTick();
144
+
145
+ expect(closeMutation).toHaveBeenCalledWith({}, undefined);
146
+ });
147
+
148
+ it('calls store commit when clicking on the slide-in close icon', async() => {
149
+ const closeMutation = jest.fn();
150
+
151
+ getters['slideInPanel/isOpen'] = () => true;
152
+ store = createStore({
153
+ getters,
154
+ mutations: { 'slideInPanel/close': closeMutation }
155
+ });
156
+ factory();
157
+ await nextTick();
158
+
159
+ const closeIcon = document.querySelector('[data-testid="slide-in-close"]') as HTMLElement;
160
+
161
+ closeIcon.click();
162
+ await nextTick();
163
+
164
+ expect(closeMutation).toHaveBeenCalledWith({}, undefined);
165
+ });
166
+ });
@@ -1,13 +1,9 @@
1
1
 
2
2
  <script>
3
3
  import { Banner } from '@components/Banner';
4
- import DisableAuthProviderModal from '@shell/components/DisableAuthProviderModal';
5
4
 
6
5
  export default {
7
- components: {
8
- Banner,
9
- DisableAuthProviderModal
10
- },
6
+ components: { Banner },
11
7
 
12
8
  props: {
13
9
  tArgs: {
@@ -35,7 +31,18 @@ export default {
35
31
 
36
32
  methods: {
37
33
  showDisableModal() {
38
- this.$refs.disableAuthProviderModal.show();
34
+ this.$store.dispatch('management/promptModal', {
35
+ component: 'DisableAuthProviderDialog',
36
+ customClass: 'remove-modal',
37
+ modalWidth: '400',
38
+ height: 'auto',
39
+ styles: 'max-height: 100vh;',
40
+ componentProps: {
41
+ disableCb: () => {
42
+ this.disable();
43
+ }
44
+ }
45
+ });
39
46
  }
40
47
  },
41
48
  };
@@ -78,11 +85,6 @@ export default {
78
85
  v-if="$slots.footer"
79
86
  name="footer"
80
87
  />
81
-
82
- <DisableAuthProviderModal
83
- ref="disableAuthProviderModal"
84
- @disable="disable"
85
- />
86
88
  </div>
87
89
  </template>
88
90
 
@@ -90,6 +90,7 @@ export default {
90
90
  <img
91
91
  :src="principal.avatarSrc"
92
92
  :class="{'round': principal.roundAvatar}"
93
+ :alt="t('principal.alt.avatar')"
93
94
  >
94
95
  </div>
95
96
  <div
@@ -51,8 +51,9 @@ describe('component: RoleDetailEdit', () => {
51
51
  const wrapper = mount(RoleDetailEdit, {
52
52
  props: {
53
53
  value: {
54
- rules: [{ verbs }],
55
- subtype: 'GLOBAL'
54
+ rules: [{ verbs }],
55
+ subtype: 'GLOBAL',
56
+ metadata: { name: 'global-role-with-inherited' },
56
57
  },
57
58
  },
58
59
 
@@ -54,7 +54,7 @@ export default {
54
54
  </script>
55
55
 
56
56
  <template>
57
- <form>
57
+ <form @submit.prevent>
58
58
  <template v-if="open">
59
59
  <div class="span-6 offset-3">
60
60
  <div class="mb-20">
@@ -1,5 +1,17 @@
1
1
  <script>
2
2
  import SortableTable from '@shell/components/SortableTable';
3
+ import { colorForState, stateDisplay, stateSort } from '@shell/plugins/dashboard-store/resource-class';
4
+
5
+ function stateDisplayProperties(state) {
6
+ const color = colorForState(state).replace('text-', 'bg-');
7
+ const display = stateDisplay(state);
8
+
9
+ return {
10
+ stateBackground: color,
11
+ stateDisplay: display,
12
+ stateSort: stateSort(color, display),
13
+ };
14
+ }
3
15
 
4
16
  export default {
5
17
  name: 'FleetResources',
@@ -7,8 +19,8 @@ export default {
7
19
  components: { SortableTable },
8
20
 
9
21
  props: {
10
- value: {
11
- type: Object,
22
+ rows: {
23
+ type: Array,
12
24
  required: true,
13
25
  },
14
26
  clusterId: {
@@ -19,10 +31,13 @@ export default {
19
31
  },
20
32
 
21
33
  computed: {
22
- computedResources() {
23
- return this.value.resourcesStatuses;
34
+ resources() {
35
+ return (this.rows || []).map((r) => ({
36
+ tableKey: r.key,
37
+ ...stateDisplayProperties(r.state),
38
+ ...r,
39
+ }));
24
40
  },
25
-
26
41
  resourceHeaders() {
27
42
  return [
28
43
  {
@@ -72,7 +87,7 @@ export default {
72
87
 
73
88
  <template>
74
89
  <SortableTable
75
- :rows="computedResources"
90
+ :rows="resources"
76
91
  :headers="resourceHeaders"
77
92
  :table-actions="false"
78
93
  :row-actions="false"
@@ -1,4 +1,5 @@
1
1
  <script>
2
+ import { ref, watch, computed } from 'vue';
2
3
  import debounce from 'lodash/debounce';
3
4
  import { _EDIT, _VIEW } from '@shell/config/query-params';
4
5
  import { removeAt } from '@shell/utils/array';
@@ -98,22 +99,83 @@ export default {
98
99
  type: String,
99
100
  default: '',
100
101
  },
102
+ componentTestid: {
103
+ type: String,
104
+ default: 'array-list',
105
+ }
101
106
  },
102
- data() {
103
- const input = (Array.isArray(this.value) ? this.value : []).slice();
104
- const rows = [];
107
+
108
+ setup(props, { emit }) {
109
+ const input = (Array.isArray(props.value) ? props.value : []).slice();
110
+ const rows = ref([]);
105
111
 
106
112
  for ( const value of input ) {
107
- rows.push({ value });
113
+ rows.value.push({ value });
108
114
  }
109
- if ( !rows.length && this.initialEmptyRow ) {
110
- const value = this.defaultAddValue ? clone(this.defaultAddValue) : '';
115
+ if ( !rows.value.length && props.initialEmptyRow ) {
116
+ const value = props.defaultAddValue ? clone(props.defaultAddValue) : '';
111
117
 
112
- rows.push({ value });
118
+ rows.value.push({ value });
113
119
  }
114
120
 
115
- return { rows, lastUpdateWasFromValue: false };
121
+ const isView = computed(() => {
122
+ return props.mode === _VIEW;
123
+ });
124
+
125
+ /**
126
+ * Cleanup rows and emit input
127
+ */
128
+ const update = () => {
129
+ if ( isView.value ) {
130
+ return;
131
+ }
132
+ const out = [];
133
+
134
+ for ( const row of rows.value ) {
135
+ const trim = !props.valueMultiline && (typeof row.value === 'string');
136
+ const value = trim ? row.value.trim() : row.value;
137
+
138
+ if ( typeof value !== 'undefined' ) {
139
+ out.push(value);
140
+ }
141
+ }
142
+ emit('update:value', out);
143
+ };
144
+
145
+ const lastUpdateWasFromValue = ref(false);
146
+ const queueUpdate = debounce(update, 50);
147
+
148
+ watch(
149
+ rows,
150
+ () => {
151
+ // lastUpdateWasFromValue is used to break a cycle where when rows are updated
152
+ // this was called which then forced rows to updated again
153
+ if (!lastUpdateWasFromValue.value) {
154
+ queueUpdate();
155
+ }
156
+ lastUpdateWasFromValue.value = false;
157
+ },
158
+ { deep: true }
159
+ );
160
+
161
+ watch(
162
+ () => props.value,
163
+ () => {
164
+ lastUpdateWasFromValue.value = true;
165
+ rows.value = (props.value || []).map((v) => ({ value: v }));
166
+ },
167
+ { deep: true }
168
+ );
169
+
170
+ return {
171
+ rows,
172
+ lastUpdateWasFromValue,
173
+ queueUpdate,
174
+ isView,
175
+ update,
176
+ };
116
177
  },
178
+
117
179
  computed: {
118
180
  _addLabel() {
119
181
  return this.addLabel || this.t('generic.add');
@@ -121,10 +183,6 @@ export default {
121
183
  _removeLabel() {
122
184
  return this.removeLabel || this.t('generic.remove');
123
185
  },
124
-
125
- isView() {
126
- return this.mode === _VIEW;
127
- },
128
186
  showAdd() {
129
187
  return this.addAllowed;
130
188
  },
@@ -145,29 +203,7 @@ export default {
145
203
  return !this.valueMultiline && this.protip;
146
204
  }
147
205
  },
148
- watch: {
149
- value: {
150
- deep: true,
151
- handler() {
152
- this.lastUpdateWasFromValue = true;
153
- this.rows = (this.value || []).map((v) => ({ value: v }));
154
- }
155
- },
156
-
157
- rows: {
158
- deep: true,
159
- handler(newValue, oldValue) {
160
- // lastUpdateWasFromValue is used to break a cycle where when rows are updated
161
- // this was called which then forced rows to updated again
162
- if (!this.lastUpdateWasFromValue) {
163
- this.queueUpdate();
164
- }
165
- this.lastUpdateWasFromValue = false;
166
- }
167
- }
168
- },
169
206
  created() {
170
- this.queueUpdate = debounce(this.update, 50);
171
207
  },
172
208
  methods: {
173
209
  add() {
@@ -193,26 +229,6 @@ export default {
193
229
  this.queueUpdate();
194
230
  },
195
231
 
196
- /**
197
- * Cleanup rows and emit input
198
- */
199
- update() {
200
- if ( this.isView ) {
201
- return;
202
- }
203
- const out = [];
204
-
205
- for ( const row of this.rows ) {
206
- const trim = !this.valueMultiline && (typeof row.value === 'string');
207
- const value = trim ? row.value.trim() : row.value;
208
-
209
- if ( typeof value !== 'undefined' ) {
210
- out.push(value);
211
- }
212
- }
213
- this.$emit('update:value', out);
214
- },
215
-
216
232
  /**
217
233
  * Handle paste event, e.g. split multiple lines in rows
218
234
  */
@@ -270,7 +286,7 @@ export default {
270
286
  <div
271
287
  v-for="(row, idx) in rows"
272
288
  :key="idx"
273
- :data-testid="`array-list-box${ idx }`"
289
+ :data-testid="`${componentTestid}-box${ idx }`"
274
290
  class="box"
275
291
  >
276
292
  <slot
@@ -294,7 +310,7 @@ export default {
294
310
  v-if="valueMultiline"
295
311
  ref="value"
296
312
  v-model:value="row.value"
297
- :data-testid="`textarea-${idx}`"
313
+ :data-testid="`${componentTestid}-textarea-${idx}`"
298
314
  :placeholder="valuePlaceholder"
299
315
  :mode="mode"
300
316
  :disabled="disabled"
@@ -305,7 +321,7 @@ export default {
305
321
  v-else-if="rules.length > 0"
306
322
  ref="value"
307
323
  v-model:value="row.value"
308
- :data-testid="`labeled-input-${idx}`"
324
+ :data-testid="`${componentTestid}-labeled-input-${idx}`"
309
325
  :placeholder="valuePlaceholder"
310
326
  :disabled="isView || disabled"
311
327
  :rules="rules"
@@ -317,7 +333,7 @@ export default {
317
333
  v-else
318
334
  ref="value"
319
335
  v-model="row.value"
320
- :data-testid="`input-${idx}`"
336
+ :data-testid="`${componentTestid}-input-${idx}`"
321
337
  :placeholder="valuePlaceholder"
322
338
  :disabled="isView || disabled"
323
339
  :aria-label="a11yLabel ? a11yLabel : undefined"
@@ -340,7 +356,7 @@ export default {
340
356
  type="button"
341
357
  :disabled="isView"
342
358
  class="btn role-link"
343
- :data-testid="`remove-item-${idx}`"
359
+ :data-testid="`${componentTestid}-remove-item-${idx}`"
344
360
  :aria-label="`${_removeLabel} ${idx + 1}`"
345
361
  role="button"
346
362
  @click="remove(row, idx)"
@@ -374,7 +390,7 @@ export default {
374
390
  type="button"
375
391
  class="btn role-tertiary add"
376
392
  :disabled="loading || disableAdd"
377
- data-testid="array-list-button"
393
+ :data-testid="`${componentTestid}-button`"
378
394
  :aria-label="_addLabel"
379
395
  role="button"
380
396
  @click="add()"
@@ -42,7 +42,8 @@ export default ({
42
42
  themeVars: {
43
43
  bannerBgColor: getComputedStyle(document.body).getPropertyValue('--default'),
44
44
  bannerTextColor: getComputedStyle(document.body).getPropertyValue('--banner-text-color')
45
- }
45
+ },
46
+ bannerTitleId: `describe-banners-${ this.bannerType }-id`
46
47
  };
47
48
  },
48
49
 
@@ -107,12 +108,19 @@ export default ({
107
108
  <div class="row mb-20">
108
109
  <div class="col span-12">
109
110
  <div class="row">
111
+ <p
112
+ :id="bannerTitleId"
113
+ class="sr-only"
114
+ >
115
+ {{ t(`banner.${bannerType}`) }}
116
+ </p>
110
117
  <div class="col span-6">
111
118
  <LabeledInput
112
119
  v-model:value="value[bannerType].text"
113
120
  :disabled="isUiDisabled"
114
121
  :label="t('banner.text')"
115
122
  type="multiline"
123
+ :aria-describedby="bannerTitleId"
116
124
  />
117
125
  <p
118
126
  v-if="isConsentBanner"
@@ -131,11 +139,13 @@ export default ({
131
139
  :mode="mode"
132
140
  :label="t('banner.showAsDialog.label')"
133
141
  :tooltip="t('banner.showAsDialog.tooltip')"
142
+ :aria-describedby="bannerTitleId"
134
143
  />
135
144
  <LabeledInput
136
145
  v-model:value="buttonText"
137
146
  :disabled="!showAsDialog || isUiDisabled"
138
147
  :label="t('banner.buttonText')"
148
+ :aria-describedby="bannerTitleId"
139
149
  />
140
150
  </div>
141
151
  </div>
@@ -148,10 +158,11 @@ export default ({
148
158
  :options="radioOptions.options"
149
159
  :labels="radioOptions.labels"
150
160
  :mode="mode"
161
+ :aria-label="`${t(`banner.${bannerType}`)} ${t('banner.bannerAlignment.label')}`"
151
162
  />
152
163
  </div>
153
164
  <div class="col span-2">
154
- <h3>
165
+ <h3 id="decoration-banner-title-id">
155
166
  {{ t('banner.bannerDecoration.label') }}
156
167
  </h3>
157
168
  <div
@@ -165,6 +176,7 @@ export default ({
165
176
  class="banner-decoration-checkbox"
166
177
  :mode="mode"
167
178
  :label="o.label"
179
+ :aria-describedby="`${bannerTitleId} decoration-banner-title-id`"
168
180
  />
169
181
  </div>
170
182
  </div>
@@ -175,6 +187,7 @@ export default ({
175
187
  :disabled="isUiDisabled"
176
188
  :label="t('banner.bannerFontSize.label')"
177
189
  :options="uiBannerFontSizeOptions"
190
+ :aria-describedby="bannerTitleId"
178
191
  />
179
192
  </div>
180
193
  </div>
@@ -185,6 +198,7 @@ export default ({
185
198
  :default-value="themeVars.bannerTextColor"
186
199
  :label="t('banner.textColor')"
187
200
  :mode="mode"
201
+ :aria-label="`${t(`banner.${bannerType}`)} ${t('banner.textColor')}`"
188
202
  />
189
203
  </div>
190
204
  <div class="col span-6">
@@ -193,6 +207,7 @@ export default ({
193
207
  :default-value="themeVars.bannerBgColor"
194
208
  :label="t('banner.background')"
195
209
  :mode="mode"
210
+ :aria-label="`${t(`banner.${bannerType}`)} ${t('banner.background')}`"
196
211
  />
197
212
  </div>
198
213
  </div>
@@ -4,6 +4,8 @@ import { _EDIT, _VIEW } from '@shell/config/query-params';
4
4
  export default {
5
5
  emits: ['update:value'],
6
6
 
7
+ inheritAttrs: false,
8
+
7
9
  props: {
8
10
  value: {
9
11
  type: String,
@@ -67,6 +69,23 @@ export default {
67
69
  const disabled = this.disabled;
68
70
 
69
71
  return this.mode !== this.editMode || disabled;
72
+ },
73
+
74
+ ariaLabel() {
75
+ // We allow override with $attrs['aria-label'] for more control
76
+ if (this.$attrs['aria-label']) {
77
+ return this.$attrs['aria-label'];
78
+ } else if (this.labelKey) {
79
+ return this.t(this.labelKey);
80
+ } else if (this.label) {
81
+ return this.label;
82
+ } else {
83
+ return this.t('generic.colorPicker');
84
+ }
85
+ },
86
+
87
+ ariaDescribedBy() {
88
+ return this.$attrs['aria-describedby'] || undefined;
70
89
  }
71
90
  },
72
91
 
@@ -100,11 +119,20 @@ export default {
100
119
  @keydown.space.prevent
101
120
  @keyup.enter.space.stop="handleKeyup($event)"
102
121
  >
103
- <label class="text-label"><t
104
- v-if="labelKey"
105
- :k="labelKey"
106
- :raw="true"
107
- />{{ label }}</label>
122
+ <!-- let make "label" not to be picked up by screen readers -->
123
+ <!-- because it's already included in aria-label (sr's announced it twice) -->
124
+ <label
125
+ v-if="labelKey || label"
126
+ class="text-label"
127
+ aria-hidden="true"
128
+ >
129
+ <t
130
+ v-if="labelKey"
131
+ :k="labelKey"
132
+ :raw="true"
133
+ />
134
+ <template v-else-if="label">{{ label }}</template>
135
+ </label>
108
136
  <div
109
137
  :data-testid="componentTestid + '-color-input_preview-container'"
110
138
  class="preview-container"
@@ -117,7 +145,8 @@ export default {
117
145
  <input
118
146
  ref="input"
119
147
  :aria-disabled="isDisabled ? 'true' : 'false'"
120
- :aria-label="t('generic.colorPicker')"
148
+ :aria-label="ariaLabel"
149
+ :aria-describedby="ariaDescribedBy"
121
150
  type="color"
122
151
  :disabled="isDisabled"
123
152
  tabindex="-1"