@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
package/pages/home.vue CHANGED
@@ -49,6 +49,21 @@ export default defineComponent({
49
49
  mixins: [PageHeaderActions],
50
50
 
51
51
  data() {
52
+ const options = this.$store.getters[`type-map/optionsFor`](CAPI.RANCHER_CLUSTER)?.custom || {};
53
+ const params = {
54
+ product: MANAGER,
55
+ cluster: BLANK_CLUSTER,
56
+ resource: CAPI.RANCHER_CLUSTER
57
+ };
58
+ const defaultCreateLocation = {
59
+ name: 'c-cluster-product-resource-create',
60
+ params,
61
+ };
62
+ const defaultImportLocation = {
63
+ ...defaultCreateLocation,
64
+ query: { [MODE]: _IMPORT }
65
+ };
66
+
52
67
  return {
53
68
  HIDE_HOME_PAGE_CARDS,
54
69
  fullVersion: getVersionInfo(this.$store).fullVersion,
@@ -87,24 +102,9 @@ export default defineComponent({
87
102
  },
88
103
  },
89
104
 
90
- createLocation: {
91
- name: 'c-cluster-product-resource-create',
92
- params: {
93
- product: MANAGER,
94
- cluster: BLANK_CLUSTER,
95
- resource: CAPI.RANCHER_CLUSTER
96
- },
97
- },
105
+ createLocation: options.createLocation ? options.createLocation(params) : defaultCreateLocation,
98
106
 
99
- importLocation: {
100
- name: 'c-cluster-product-resource-create',
101
- params: {
102
- product: MANAGER,
103
- cluster: BLANK_CLUSTER,
104
- resource: CAPI.RANCHER_CLUSTER
105
- },
106
- query: { [MODE]: _IMPORT }
107
- },
107
+ importLocation: options.importLocation ? options.importLocation(params) : defaultImportLocation,
108
108
 
109
109
  headers: [
110
110
  STATE,
@@ -512,7 +512,7 @@ export default defineComponent({
512
512
  :show-child="false"
513
513
  :breadcrumb="false"
514
514
  >
515
- {{ vendor }}
515
+ {{ `${vendor} - ${t('landing.homepage')}` }}
516
516
  </TabTitle>
517
517
  <BannerGraphic
518
518
  :small="true"
@@ -805,6 +805,12 @@ export default defineComponent({
805
805
  .cluster-name {
806
806
  display: flex;
807
807
  align-items: center;
808
+
809
+ // Ensure long cluster names truncate with ellipsis
810
+ > A {
811
+ overflow: hidden;
812
+ text-overflow: ellipsis;
813
+ }
808
814
  }
809
815
 
810
816
  .cluster-description {
package/pages/prefs.vue CHANGED
@@ -225,7 +225,7 @@ export default {
225
225
  v-if="!isSingleProduct"
226
226
  class="mt-10 mb-10"
227
227
  >
228
- <hr>
228
+ <hr role="none">
229
229
  <h4 v-t="'prefs.landing.label'" />
230
230
  <LandingPagePreference
231
231
  data-testid="prefs__landingPagePreference"
@@ -233,7 +233,7 @@ export default {
233
233
  </div>
234
234
  <!-- Display Settings -->
235
235
  <div class="mt-10 mb-10">
236
- <hr>
236
+ <hr role="none">
237
237
  <h4 v-t="'prefs.displaySettings.title'" />
238
238
  <p class="set-landing-leadin">
239
239
  {{ t('prefs.displaySettings.detail', {}, raw=true) }}
@@ -277,7 +277,7 @@ export default {
277
277
  v-if="!isSingleProduct"
278
278
  class="col adv-features mt-10 mb-10"
279
279
  >
280
- <hr>
280
+ <hr role="none">
281
281
  <h4 v-t="'prefs.confirmationSetting.title'" />
282
282
  <Checkbox
283
283
  v-model:value="scalingDownPrompt"
@@ -288,7 +288,7 @@ export default {
288
288
  </div>
289
289
  <!-- Advanced Features -->
290
290
  <div class="col adv-features mt-10 mb-10">
291
- <hr>
291
+ <hr role="none">
292
292
  <h4 v-t="'prefs.advFeatures.title'" />
293
293
  <Checkbox
294
294
  v-model:value="viewInApi"
@@ -325,13 +325,14 @@ export default {
325
325
  <Checkbox
326
326
  v-model:value="pluginDeveloper"
327
327
  :label="t('prefs.advFeatures.pluginDeveloper', {}, true)"
328
+ :tooltip="t('prefs.advFeatures.pluginDeveloperTooltip')"
328
329
  class="mt-20"
329
330
  />
330
331
  </template>
331
332
  </div>
332
333
  <!-- YAML editor key mapping -->
333
334
  <div class="col mt-10 mb-10">
334
- <hr>
335
+ <hr role="none">
335
336
  <h4 v-t="'prefs.keymap.label'" />
336
337
  <ButtonGroup
337
338
  v-model:value="keymap"
@@ -344,7 +345,7 @@ export default {
344
345
  v-if="!isSingleProduct"
345
346
  class="col mt-10 mb-40"
346
347
  >
347
- <hr>
348
+ <hr role="none">
348
349
  <h4 v-t="'prefs.helm.label'" />
349
350
  <ButtonGroup
350
351
  v-model:value="showPreRelease"
@@ -118,7 +118,10 @@ export default {
118
118
  </script>
119
119
  <template>
120
120
  <div>
121
- <BannerGraphic :title="t(title, {}, true)" />
121
+ <BannerGraphic
122
+ :title="t(title, {}, true)"
123
+ :alt="t('support.bannerImage')"
124
+ />
122
125
 
123
126
  <IndentedPanel>
124
127
  <div class="content mt-20">
@@ -0,0 +1,37 @@
1
+ import { Store } from 'vuex';
2
+
3
+ interface PluginContext {
4
+ store: Store<any>;
5
+ [key: string]: any;
6
+ }
7
+
8
+ export default function(context: PluginContext, inject: (key: string, value: any) => void) {
9
+ const { store } = context;
10
+
11
+ // Load all API modules
12
+ const apiContext = (require as any).context(
13
+ '@shell/plugins/internal-api', // the base directory
14
+ true, // whether to search subdirectories
15
+ /\.api\.ts$/ // only .api.ts files
16
+ );
17
+
18
+ apiContext.keys().forEach((relativePath: string) => {
19
+ const mod = apiContext(relativePath);
20
+ const ApiClass = mod.default;
21
+
22
+ if (typeof ApiClass === 'function') {
23
+ // Check for a static `apiName` property, or fallback to filename
24
+ let apiName: string = ApiClass.apiName();
25
+
26
+ if (!apiName) {
27
+ // fallback to filename (strip leading ‘./’ and extension)
28
+ apiName = `$${ relativePath.replace(/^\.\//, '').replace(/\.\w+$/, '') }`;
29
+ }
30
+
31
+ const instance = new ApiClass(store);
32
+
33
+ // The inject() method automatically adds the `$` prefix
34
+ inject(apiName, instance);
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,13 @@
1
+ export abstract class BaseApi {
2
+ // The Vuex store, available to all API classes
3
+ protected $store: any;
4
+
5
+ // Documented requirement: each API should define its static apiName.
6
+ static apiName(): string {
7
+ throw new Error('apiName() static method has not been implemented');
8
+ }
9
+
10
+ constructor(store: any) {
11
+ this.$store = store;
12
+ }
13
+ }
@@ -0,0 +1,108 @@
1
+ import { GrowlConfig } from '@shell/types/internal-api/shell/growl';
2
+ import { ModalConfig } from '@shell/types/internal-api/shell/modal';
3
+ import { SlideInConfig } from '@shell/types/internal-api/shell/slideIn';
4
+
5
+ import { BaseApi } from '@shell/plugins/internal-api/shared/base-api';
6
+
7
+ export default class ShellApi extends BaseApi {
8
+ static apiName() {
9
+ return 'shell';
10
+ }
11
+
12
+ /**
13
+ * Dispatches a growl notification.
14
+ *
15
+ * @param config - Configuration for the growl notification.
16
+ * - If `message` is a string, it is treated as the main content of the notification.
17
+ * - If `message` is a `DetailedMessage` object, `title` and `description` are extracted.
18
+ *
19
+ * Example:
20
+ * ```ts
21
+ * this.$shell.growl({ message: 'Operation successful!', type: 'success' });
22
+ * this.$shell.growl({ message: { title: 'Warning', description: 'Check your input.' }, type: 'warning' });
23
+ * ```
24
+ */
25
+ protected growl(config: GrowlConfig): void {
26
+ const { type = 'error', timeout = 5000 } = config;
27
+
28
+ let title = '';
29
+ let description = '';
30
+
31
+ if (typeof config.message === 'string') {
32
+ description = config.message;
33
+ } else {
34
+ title = config.message.title || '';
35
+ description = config.message.description;
36
+ }
37
+
38
+ this.$store.dispatch(
39
+ `growl/${ type }`,
40
+ {
41
+ title,
42
+ message: description,
43
+ timeout,
44
+ },
45
+ { root: true }
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Opens a modal by committing to the Vuex store.
51
+ *
52
+ * This method updates the store's `modal` module to show a modal with the
53
+ * specified configuration. The modal is rendered using the `ModalManager` component,
54
+ * and its content is dynamically loaded based on the `component` field in the configuration.
55
+ *
56
+ * @param config A `ModalConfig` object defining the modal’s content and behavior.
57
+ *
58
+ * Example:
59
+ * ```ts
60
+ * this.$shell.modal({
61
+ * component: MyCustomModal,
62
+ * componentProps: { title: 'Hello Modal' },
63
+ * resources: [someResource],
64
+ * modalWidth: '800px',
65
+ * closeOnClickOutside: false
66
+ * });
67
+ * ```
68
+ */
69
+ protected modal(config: ModalConfig): void {
70
+ this.$store.commit('modal/openModal', {
71
+ component: config.component,
72
+ componentProps: config.componentProps || {},
73
+ resources: config.resources || [],
74
+ modalWidth: config.modalWidth || '600px',
75
+ closeOnClickOutside: config.closeOnClickOutside ?? true,
76
+ // modalSticky: config.modalSticky ?? false // Not implemented yet
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Opens the slide-in panel with the specified component and props.
82
+ *
83
+ * This method commits the `open` mutation to the `slideInPanel` Vuex module,
84
+ * which sets the current component to be rendered and its associated props.
85
+ * The slide-in panel becomes visible after the mutation.
86
+ *
87
+ * @param config - The configuration object for the slide-in panel.
88
+ *
89
+ * Example Usage:
90
+ * ```ts
91
+ * import MyComponent from '@/components/MyComponent.vue';
92
+ *
93
+ * this.$shell.slideInPanel({
94
+ * component: MyComponent,
95
+ * componentProps: { foo: 'bar' }
96
+ * });
97
+ * ```
98
+ *
99
+ * @param config.component - A Vue component (imported SFC, functional component, etc.) to be rendered in the panel.
100
+ * @param config.componentProps - (Optional) Props to pass to the component. These should align with the component's defined props.
101
+ */
102
+ protected slideInPanel(config: SlideInConfig): void {
103
+ this.$store.commit('slideInPanel/open', {
104
+ component: config.component,
105
+ componentProps: config.componentProps || {}
106
+ });
107
+ }
108
+ }
@@ -196,18 +196,10 @@ export default {
196
196
  }
197
197
  },
198
198
 
199
- promptMove({ commit, state }, resources) {
200
- commit('action-menu/togglePromptMove', resources, { root: true });
201
- },
202
-
203
199
  promptRestore({ commit, state }, resources ) {
204
200
  commit('action-menu/togglePromptRestore', resources, { root: true });
205
201
  },
206
202
 
207
- assignTo({ commit, state }, resources = []) {
208
- commit('action-menu/toggleAssignTo', resources, { root: true });
209
- },
210
-
211
203
  async resourceAction({ getters, dispatch }, {
212
204
  resource, actionName, body, opt,
213
205
  }) {
@@ -234,10 +226,6 @@ export default {
234
226
  }
235
227
  },
236
228
 
237
- promptUpdate({ commit, state }, resources = []) {
238
- commit('action-menu/togglePromptUpdate', resources, { root: true });
239
- },
240
-
241
229
  async collectionAction({ getters, dispatch }, {
242
230
  type, actionName, body, opt
243
231
  }) {
package/public/index.html CHANGED
@@ -16,6 +16,7 @@
16
16
  </div>
17
17
  </div>
18
18
  <div id="modals"><!--Portal content here--></div>
19
+ <div id="slides"></div>
19
20
 
20
21
  <script>
21
22
  (() => {
@@ -89,7 +89,7 @@ export default defineComponent({
89
89
  {{ title }}
90
90
  </slot>
91
91
  </div>
92
- <hr>
92
+ <hr role="none">
93
93
  <div
94
94
  class="card-body"
95
95
  data-testid="card-body-slot"
@@ -1,4 +1,4 @@
1
- import { shallowMount, Wrapper } from '@vue/test-utils';
1
+ import { shallowMount, Wrapper, mount } from '@vue/test-utils';
2
2
  import { Checkbox } from './index';
3
3
 
4
4
  describe('checkbox.vue', () => {
@@ -65,4 +65,62 @@ describe('checkbox.vue', () => {
65
65
 
66
66
  expect(wrapper.emitted('update:value')[0][0]).toBeNull();
67
67
  });
68
+
69
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
70
+ const alternateLabel = 'some-alternate-aria-label';
71
+ const description = 'some-description';
72
+ const ariaDescribedById = 'some-external-id';
73
+
74
+ const wrapper: Wrapper<InstanceType<typeof Checkbox>> = mount(
75
+ Checkbox,
76
+ {
77
+ propsData: {
78
+ value: false, alternateLabel, description
79
+ },
80
+ attrs: { 'aria-describedby': ariaDescribedById },
81
+ }
82
+ );
83
+
84
+ const field = wrapper.find('.checkbox-custom');
85
+ const ariaChecked = field.attributes('aria-checked');
86
+ const ariaLabel = field.attributes('aria-label');
87
+ const ariaLabelledBy = field.attributes('aria-labelledby');
88
+ const ariaDescribedBy = field.attributes('aria-describedby');
89
+
90
+ // validates type of input rendered
91
+ expect(ariaChecked).toBe('false');
92
+ expect(ariaLabelledBy).toBeUndefined();
93
+ expect(ariaLabel).toBe(alternateLabel);
94
+ expect(ariaDescribedBy).toBe(`${ ariaDescribedById } ${ wrapper.vm.describedById }`);
95
+ });
96
+
97
+ it('a11y: having a label should not render "aria-label" prop and have "aria-labelledby"', async() => {
98
+ const label = 'some-label';
99
+
100
+ const wrapper: Wrapper<InstanceType<typeof Checkbox>> = mount(
101
+ Checkbox,
102
+ {
103
+ propsData: {
104
+ value: true, label, disabled: true
105
+ }
106
+ }
107
+ );
108
+
109
+ const field = wrapper.find('.checkbox-custom');
110
+ const ariaChecked = field.attributes('aria-checked');
111
+ const ariaLabel = field.attributes('aria-label');
112
+ const ariaLabelledBy = field.attributes('aria-labelledby');
113
+ const ariaDisabled = field.attributes('aria-disabled');
114
+ const tabIndex = field.attributes('tabindex');
115
+
116
+ // validates type of input rendered
117
+ expect(field.exists()).toBe(true);
118
+ expect(ariaChecked).toBe('true');
119
+ expect(ariaLabelledBy).toBe(wrapper.vm.idForLabel);
120
+ expect(ariaLabel).toBeUndefined();
121
+ expect(wrapper.find(`#${ wrapper.vm.idForLabel }`).text()).toBe(label);
122
+
123
+ expect(ariaDisabled).toBe('true');
124
+ expect(tabIndex).toBe('-1');
125
+ });
68
126
  });
@@ -124,6 +124,15 @@ export default defineComponent({
124
124
  type: String,
125
125
  default: undefined
126
126
  },
127
+
128
+ /**
129
+ * Inherited global identifier prefix for tests
130
+ * Define a term based on the parent component to avoid conflicts on multiple components
131
+ */
132
+ componentTestid: {
133
+ type: String,
134
+ default: 'checkbox'
135
+ },
127
136
  },
128
137
 
129
138
  emits: ['update:value'],
@@ -133,6 +142,18 @@ export default defineComponent({
133
142
  },
134
143
 
135
144
  computed: {
145
+ ariaDescribedBy(): string | undefined {
146
+ const inheritedDescribedBy = this.$attrs['aria-describedby'];
147
+ const internalDescribedBy = this.descriptionKey || this.description ? this.describedById : undefined;
148
+
149
+ if (inheritedDescribedBy && internalDescribedBy) {
150
+ return `${ inheritedDescribedBy } ${ internalDescribedBy }`;
151
+ } else if (inheritedDescribedBy || internalDescribedBy) {
152
+ return `${ inheritedDescribedBy || internalDescribedBy }`;
153
+ }
154
+
155
+ return undefined;
156
+ },
136
157
  /**
137
158
  * Determines if the checkbox is disabled.
138
159
  * @returns boolean: True when the disabled prop is true or when mode is
@@ -167,7 +188,7 @@ export default defineComponent({
167
188
  },
168
189
 
169
190
  idForLabel():string {
170
- return `${ this.id }-label`;
191
+ return `${ generateRandomAlphaString(12) }-checkbox-label`;
171
192
  }
172
193
  },
173
194
 
@@ -271,10 +292,11 @@ export default defineComponent({
271
292
  class="checkbox-custom"
272
293
  :class="{indeterminate: indeterminate}"
273
294
  :tabindex="isDisabled ? -1 : 0"
295
+ :aria-disabled="isDisabled"
274
296
  :aria-label="replacementLabel"
275
297
  :aria-checked="!!value"
276
298
  :aria-labelledby="labelKey || label ? idForLabel : undefined"
277
- :aria-describedby="descriptionKey || description ? describedById : undefined"
299
+ :aria-describedby="ariaDescribedBy"
278
300
  role="checkbox"
279
301
  />
280
302
  <span
@@ -298,6 +320,7 @@ export default defineComponent({
298
320
  v-clean-tooltip="{content: t(tooltipKey), triggers: ['hover', 'touch', 'focus']}"
299
321
  v-stripped-aria-label="t(tooltipKey)"
300
322
  class="checkbox-info icon icon-info icon-lg"
323
+ :data-testid="componentTestid + '-info-icon'"
301
324
  :tabindex="isDisabled ? -1 : 0"
302
325
  />
303
326
  <i
@@ -305,6 +328,7 @@ export default defineComponent({
305
328
  v-clean-tooltip="{content: tooltip, triggers: ['hover', 'touch', 'focus']}"
306
329
  v-stripped-aria-label="tooltip"
307
330
  class="checkbox-info icon icon-info icon-lg"
331
+ :data-testid="componentTestid + '-info-icon'"
308
332
  :tabindex="isDisabled ? -1 : 0"
309
333
  />
310
334
  </slot>
@@ -374,7 +398,7 @@ $fontColor: var(--input-label);
374
398
 
375
399
  .checkbox-info {
376
400
  line-height: normal;
377
- margin-left: 2px;
401
+ margin-left: 4px;
378
402
 
379
403
  &:focus-visible {
380
404
  @include focus-outline;
@@ -54,4 +54,51 @@ describe('component: LabeledInput', () => {
54
54
  expect(subLabel.text()).toBe(hint);
55
55
  });
56
56
  });
57
+
58
+ describe('a11y: adding ARIA props', () => {
59
+ const ariaLabelVal = 'some-aria-label';
60
+ const subLabelVal = 'some-sublabel';
61
+ const ariaDescribedByIdVal = 'some-external-id';
62
+ const ariaRequiredVal = 'true';
63
+
64
+ it.each([
65
+ ['text', 'input', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
66
+ ['cron', 'input', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
67
+ ['multiline', 'textarea', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
68
+ ['multiline-password', 'textarea', ariaLabelVal, subLabelVal, ariaDescribedByIdVal],
69
+ ])('for type %p should correctly fill out the appropriate fields on the component', (type, validationType, ariaLabel, subLabel, ariaDescribedById) => {
70
+ const wrapper = mount(LabeledInput, {
71
+ propsData: {
72
+ value: '', type, ariaLabel, subLabel, required: true
73
+ },
74
+ attrs: { 'aria-describedby': ariaDescribedById },
75
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
76
+ });
77
+
78
+ const field = wrapper.find(validationType);
79
+ const ariaLabelProp = field.attributes('aria-label');
80
+ const ariaDescribedBy = field.attributes('aria-describedby');
81
+ const ariaRequired = field.attributes('aria-required');
82
+
83
+ // validates type of input rendered
84
+ expect(field.exists()).toBe(true);
85
+ expect(ariaLabelProp).toBe(ariaLabel);
86
+ expect(ariaDescribedBy).toBe(`${ ariaDescribedById } ${ wrapper.vm.describedById }`);
87
+ expect(ariaRequired).toBe(ariaRequiredVal);
88
+ });
89
+ });
90
+
91
+ it('a11y: rendering a "label" should not render an "aria-label" prop', () => {
92
+ const label = 'some-label';
93
+
94
+ const wrapper = mount(LabeledInput, {
95
+ propsData: { type: 'text', label },
96
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
97
+ });
98
+
99
+ const mainInput = wrapper.find('input[type="text"]');
100
+
101
+ expect(mainInput.attributes('aria-label')).toBeUndefined();
102
+ expect(wrapper.find('label').text()).toBe(label);
103
+ });
57
104
  });
@@ -161,6 +161,19 @@ export default defineComponent({
161
161
  return this.isCompact ? false : !!this.label || !!this.labelKey || !!this.$slots.label;
162
162
  },
163
163
 
164
+ ariaDescribedBy(): string | undefined {
165
+ const inheritedDescribedBy = this.$attrs['aria-describedby'];
166
+ const internalDescribedBy = this.cronHint || this.subLabel ? this.describedById : undefined;
167
+
168
+ if (inheritedDescribedBy && internalDescribedBy) {
169
+ return `${ inheritedDescribedBy } ${ internalDescribedBy }`;
170
+ } else if (inheritedDescribedBy || internalDescribedBy) {
171
+ return `${ inheritedDescribedBy || internalDescribedBy }`;
172
+ }
173
+
174
+ return undefined;
175
+ },
176
+
164
177
  /**
165
178
  * Determines if the Labeled Input should display a tooltip.
166
179
  */
@@ -362,6 +375,7 @@ export default defineComponent({
362
375
  <span
363
376
  v-if="requiredField"
364
377
  class="required"
378
+ :aria-hidden="true"
365
379
  >*</span>
366
380
  </label>
367
381
  </slot>
@@ -377,11 +391,13 @@ export default defineComponent({
377
391
  v-stripped-aria-label="!hasLabel && ariaLabel ? ariaLabel : undefined"
378
392
  :maxlength="_maxlength"
379
393
  :disabled="isDisabled"
394
+ :aria-disabled="isDisabled"
380
395
  :value="value || ''"
381
396
  :placeholder="_placeholder"
382
397
  autocapitalize="off"
383
398
  :class="{ conceal: type === 'multiline-password' }"
384
- :aria-describedby="cronHint || subLabel ? describedById : undefined"
399
+ :aria-describedby="ariaDescribedBy"
400
+ :aria-required="requiredField"
385
401
  @update:value="onInput"
386
402
  @focus="onFocus"
387
403
  @blur="onBlur"
@@ -396,13 +412,15 @@ export default defineComponent({
396
412
  v-bind="$attrs"
397
413
  :maxlength="_maxlength"
398
414
  :disabled="isDisabled"
415
+ :aria-disabled="isDisabled"
399
416
  :type="type === 'cron' ? 'text' : type"
400
417
  :value="value"
401
418
  :placeholder="_placeholder"
402
419
  autocomplete="off"
403
420
  autocapitalize="off"
404
421
  :data-lpignore="ignorePasswordManagers"
405
- :aria-describedby="cronHint || subLabel ? describedById : undefined"
422
+ :aria-describedby="ariaDescribedBy"
423
+ :aria-required="requiredField"
406
424
  @input="onInput"
407
425
  @focus="onFocus"
408
426
  @blur="onBlur"
@@ -1,4 +1,4 @@
1
- import { shallowMount } from '@vue/test-utils';
1
+ import { shallowMount, mount } from '@vue/test-utils';
2
2
  import { RadioButton } from './index';
3
3
 
4
4
  describe('radioButton.vue', () => {
@@ -30,4 +30,39 @@ describe('radioButton.vue', () => {
30
30
 
31
31
  expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
32
32
  });
33
+
34
+ it('a11y: adding ARIA props should correctly fill out the appropriate fields on the component', async() => {
35
+ const val = 'foo';
36
+ const value = 'foo';
37
+ const description = 'some-description';
38
+ const itemLabel = 'some-label';
39
+ const radioOptionId = 'some-id-from-parent';
40
+
41
+ const wrapper = mount(
42
+ RadioButton,
43
+ {
44
+ propsData: {
45
+ label: itemLabel,
46
+ val,
47
+ value,
48
+ description,
49
+ radioOptionId
50
+ }
51
+ });
52
+
53
+ const radioInputElem = wrapper.find('span[role="radio"]');
54
+ const role = radioInputElem.attributes('role');
55
+ const ariaLabel = radioInputElem.attributes('aria-label');
56
+ const ariaChecked = radioInputElem.attributes('aria-checked');
57
+ const ariaDisabled = radioInputElem.attributes('aria-disabled');
58
+ const ariaDescribedBy = radioInputElem.attributes('aria-describedby');
59
+ const itemId = radioInputElem.attributes('id');
60
+
61
+ expect(role).toBe('radio');
62
+ expect(ariaLabel).toBe(itemLabel);
63
+ expect(ariaChecked).toBe('true');
64
+ expect(ariaDisabled).toBe('false');
65
+ expect(ariaDescribedBy).toBe(wrapper.vm.describeById);
66
+ expect(itemId).toBe(radioOptionId);
67
+ });
33
68
  });