@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
@@ -13,12 +13,15 @@ export default {
13
13
  components: { AppModal },
14
14
 
15
15
  data() {
16
- return { opened: false, backgroundClosing: null };
16
+ return {
17
+ opened: false,
18
+ backgroundClosing: null,
19
+ componentRendered: false
20
+ };
17
21
  },
18
22
 
19
23
  computed: {
20
24
  ...mapState('action-menu', ['showModal', 'modalData']),
21
-
22
25
  resources() {
23
26
  let resources = this.modalData?.resources;
24
27
 
@@ -28,11 +31,28 @@ export default {
28
31
 
29
32
  return resources || [];
30
33
  },
31
-
34
+ testId() {
35
+ return this.modalData?.testId || 'prompt-modal-generic-testid';
36
+ },
37
+ returnFocusSelector() {
38
+ return this.modalData?.returnFocusSelector || undefined;
39
+ },
40
+ returnFocusFirstIterableNodeSelector() {
41
+ return this.modalData?.returnFocusFirstIterableNodeSelector || undefined;
42
+ },
32
43
  modalWidth() {
33
44
  // property set from workload.js to overwrite modal default width of 600px, with fallback value as well
34
45
  return this.modalData?.modalWidth || '600px';
35
46
  },
47
+ customClass() {
48
+ return this.modalData?.customClass || undefined;
49
+ },
50
+ styles() {
51
+ return this.modalData?.styles || undefined;
52
+ },
53
+ height() {
54
+ return this.modalData?.height || undefined;
55
+ },
36
56
  component() {
37
57
  // Looks for a dialog component by looking up in plugins and @shell/dialog/${name}.
38
58
  return this.$store.getters['type-map/importDialog'](this.modalData?.component);
@@ -48,27 +68,36 @@ export default {
48
68
  },
49
69
  closeOnClickOutside() {
50
70
  return this.modalData?.closeOnClickOutside;
71
+ },
72
+ modalName() {
73
+ return this.modalData?.modalName;
51
74
  }
52
75
  },
53
76
 
54
77
  watch: {
55
78
  showModal(show) {
56
79
  this.opened = show;
57
- },
80
+ }
58
81
  },
59
82
 
60
83
  methods: {
61
- close() {
84
+ onSlotComponentMounted() {
85
+ // variable for the watcher based focus-trap
86
+ // so that we know when the component is rendered
87
+ this.componentRendered = true;
88
+ },
89
+ close(data) {
62
90
  if (!this.opened) {
63
91
  return;
64
92
  }
65
93
 
66
94
  this.errors = [];
67
- this.$store.commit('action-menu/togglePromptModal');
95
+ this.$store.commit('action-menu/togglePromptModal', data);
68
96
  if (this.backgroundClosing) {
69
97
  this.backgroundClosing();
70
98
  }
71
99
 
100
+ this.componentRendered = false;
72
101
  this.opened = false;
73
102
  },
74
103
 
@@ -83,16 +112,26 @@ export default {
83
112
  <template>
84
113
  <app-modal
85
114
  v-if="opened && component"
115
+ :name="modalName"
86
116
  :click-to-close="closeOnClickOutside"
87
117
  :width="modalWidth"
88
- @close="close()"
118
+ :data-testid="testId"
119
+ :custom-class="customClass"
120
+ :styles="styles"
121
+ :height="height"
122
+ :trigger-focus-trap="true"
123
+ :return-focus-selector="returnFocusSelector"
124
+ :return-focus-first-iterable-node-selector="returnFocusFirstIterableNodeSelector"
125
+ :focus-trap-watcher-based-variable="componentRendered"
126
+ @close="close"
89
127
  >
90
128
  <component
91
129
  v-bind="modalData.componentProps || {}"
92
130
  :is="component"
93
131
  :resources="resources"
94
132
  :register-background-closing="registerBackgroundClosing"
95
- @close="close()"
133
+ @vue:mounted="onSlotComponentMounted"
134
+ @close="close"
96
135
  />
97
136
  </app-modal>
98
137
  </template>
@@ -339,6 +339,7 @@ export default {
339
339
  :width="400"
340
340
  height="auto"
341
341
  styles="max-height: 100vh;"
342
+ :trigger-focus-trap="true"
342
343
  @close="close"
343
344
  >
344
345
  <Card
@@ -213,6 +213,7 @@ export default {
213
213
  styles="background-color: var(--nav-bg); border-radius: var(--border-radius); max-height: 100vh;"
214
214
  height="auto"
215
215
  :scrollable="true"
216
+ :trigger-focus-trap="true"
216
217
  @close="close"
217
218
  >
218
219
  <Card
@@ -56,6 +56,7 @@ export default {
56
56
  name="cancel-modal"
57
57
  :width="440"
58
58
  height="auto"
59
+ :trigger-focus-trap="true"
59
60
  @close="cancelCancel"
60
61
  >
61
62
  <div class="header">
@@ -13,6 +13,9 @@ import {
13
13
  import { ExtensionPoint, PanelLocation } from '@shell/core/types';
14
14
  import ExtensionPanel from '@shell/components/ExtensionPanel';
15
15
  import TabTitle from '@shell/components/TabTitle';
16
+ import ActionMenu from '@shell/components/ActionMenuShell.vue';
17
+ import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
18
+ import { useStore } from 'vuex';
16
19
 
17
20
  // i18n-uses resourceDetail.header.*
18
21
 
@@ -26,7 +29,12 @@ export default {
26
29
  name: 'MastheadResourceDetail',
27
30
 
28
31
  components: {
29
- BadgeState, Banner, ButtonGroup, ExtensionPanel, TabTitle
32
+ BadgeState,
33
+ Banner,
34
+ ButtonGroup,
35
+ ExtensionPanel,
36
+ TabTitle,
37
+ ActionMenu,
30
38
  },
31
39
  props: {
32
40
  value: {
@@ -92,6 +100,13 @@ export default {
92
100
  }
93
101
  },
94
102
 
103
+ setup() {
104
+ const store = useStore();
105
+ const { featureDropdownMenu } = useRuntimeFlag(store);
106
+
107
+ return { featureDropdownMenu };
108
+ },
109
+
95
110
  data() {
96
111
  return {
97
112
  DETAIL_VIEW: _DETAIL,
@@ -561,17 +576,28 @@ export default {
561
576
  class="mr-10"
562
577
  />
563
578
 
564
- <button
565
- v-if="isView"
566
- ref="actions"
567
- data-testid="masthead-action-menu"
568
- aria-haspopup="true"
569
- type="button"
570
- class="btn role-multi-action actions"
571
- @click="showActions"
572
- >
573
- <i class="icon icon-actions" />
574
- </button>
579
+ <template v-if="featureDropdownMenu">
580
+ <ActionMenu
581
+ v-if="isView"
582
+ button-role="multiAction"
583
+ button-size="compact"
584
+ :resource="value"
585
+ data-testid="masthead-action-menu"
586
+ />
587
+ </template>
588
+ <template v-else>
589
+ <button
590
+ v-if="isView"
591
+ ref="actions"
592
+ data-testid="masthead-action-menu"
593
+ aria-haspopup="true"
594
+ type="button"
595
+ class="btn role-multi-action actions"
596
+ @click="showActions"
597
+ >
598
+ <i class="icon icon-actions" />
599
+ </button>
600
+ </template>
575
601
  </div>
576
602
  </div>
577
603
  </slot>
@@ -1,6 +1,7 @@
1
1
  import { mount, RouterLinkStub } from '@vue/test-utils';
2
2
  import { _VIEW } from '@shell/config/query-params';
3
3
  import Masthead from '@shell/components/ResourceDetail/Masthead.vue';
4
+ import { createStore } from 'vuex';
4
5
 
5
6
  const mockedStore = () => {
6
7
  return {
@@ -17,12 +18,15 @@ const mockedStore = () => {
17
18
  };
18
19
 
19
20
  const requiredSetup = () => {
21
+ const store = createStore({ getters: { 'management/byId': () => jest.fn() } });
22
+
20
23
  return {
21
24
  stubs: {
22
25
  'router-link': RouterLinkStub,
23
26
  LiveDate: true
24
27
  },
25
- mocks: { $store: mockedStore() }
28
+ provide: { store },
29
+ mocks: { $store: mockedStore() }
26
30
  };
27
31
  };
28
32
 
@@ -384,18 +384,7 @@ export default {
384
384
  },
385
385
 
386
386
  created() {
387
- // eslint-disable-next-line prefer-const
388
- const id = this.$route.params.id;
389
- const resource = this.resourceOverride || this.$route.params.resource;
390
- const options = this.$store.getters[`type-map/optionsFor`](resource);
391
-
392
- const detailResource = options.resourceDetail || options.resource || resource;
393
- const editResource = options.resourceEdit || options.resource || resource;
394
-
395
- // FIXME: These aren't right... signature is (rawType, subType).. not (rawType, resourceId)
396
- // Remove id? How does subtype get in (cluster/node)
397
- this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
398
- this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
387
+ this.configureResource();
399
388
  },
400
389
 
401
390
  methods: {
@@ -414,6 +403,51 @@ export default {
414
403
  closeError(index) {
415
404
  this.errors = this.errors.filter((_, i) => i !== index);
416
405
  },
406
+ /**
407
+ * Initializes the resource components based on the provided user and
408
+ * resource override.
409
+ *
410
+ * Configures the detail and edit components for a resource based on the
411
+ * user's ID and the specified resource.
412
+ *
413
+ * @param {Object} user - The user object containing user-specific
414
+ * information.
415
+ * @param {string|null} resourceOverride - An optional resource override
416
+ * string. If not provided, the method will use the default resource from
417
+ * the route parameters or the instance's resourceOverride property.
418
+ */
419
+ configureResource(userId = '', resourceOverride = null) {
420
+ const id = userId || this.$route.params.id;
421
+ const resource = resourceOverride || this.resourceOverride || this.$route.params.resource;
422
+ const options = this.$store.getters[`type-map/optionsFor`](resource);
423
+
424
+ const detailResource = options.resourceDetail || options.resource || resource;
425
+ const editResource = options.resourceEdit || options.resource || resource;
426
+
427
+ // FIXME: These aren't right... signature is (rawType, subType).. not (rawType, resourceId)
428
+ // Remove id? How does subtype get in (cluster/node)
429
+ this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
430
+ this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
431
+ },
432
+ /**
433
+ * Sets the mode and initializes the resource components.
434
+ *
435
+ * This method sets the mode of the component and configures the resource
436
+ * components based on the provided user and resource.
437
+ *
438
+ * @param {Object} payload - An object containing the mode, user, and
439
+ * resource properties.
440
+ * @param {string} payload.mode - The mode to set.
441
+ * @param {Object} payload.user - The user object containing user-specific
442
+ * information.
443
+ * @param {string} payload.resource - The resource string to use for
444
+ * initialization.
445
+ */
446
+ setMode({ mode, userId, resource }) {
447
+ this.mode = mode;
448
+ this.value.id = userId;
449
+ this.configureResource(userId, resource);
450
+ }
417
451
  }
418
452
  };
419
453
  </script>
@@ -491,6 +525,7 @@ export default {
491
525
  :real-mode="realMode"
492
526
  :class="{'flex-content': flexContent}"
493
527
  @update:value="$emit('input', $event)"
528
+ @update:mode="setMode"
494
529
  @set-subtype="setSubtype"
495
530
  />
496
531
 
@@ -77,11 +77,6 @@ export default {
77
77
  default: null,
78
78
  },
79
79
 
80
- groupBy: {
81
- type: String,
82
- default: null
83
- },
84
-
85
80
  namespaced: {
86
81
  type: Boolean,
87
82
  default: null, // Automatic from schema
@@ -117,11 +112,35 @@ export default {
117
112
  default: true,
118
113
  },
119
114
 
115
+ /**
116
+ * Field to group rows by, row[groupBy] must be something that can be a map key
117
+ */
118
+ groupBy: {
119
+ type: String,
120
+ default: null
121
+ },
122
+
123
+ /**
124
+ * Override any product based group options
125
+ */
126
+ groupOptions: {
127
+ type: Array,
128
+ default: null
129
+ },
130
+
120
131
  groupable: {
121
132
  type: Boolean,
122
133
  default: null, // Null: auto based on namespaced and type custom groupings
123
134
  },
124
135
 
136
+ /**
137
+ * If the current preference for group isn't applicable, or not set, use this instead
138
+ */
139
+ groupDefault: {
140
+ type: String,
141
+ default: DEFAULT_GROUP,
142
+ },
143
+
125
144
  groupTooltip: {
126
145
  type: String,
127
146
  default: 'resourceTable.groupBy.namespace',
@@ -189,10 +208,6 @@ export default {
189
208
  default: null, // Default comes from the user preference
190
209
  },
191
210
 
192
- hideGroupingControls: {
193
- type: Boolean,
194
- default: false
195
- }
196
211
  },
197
212
 
198
213
  data() {
@@ -328,8 +343,18 @@ export default {
328
343
  // If we are grouping by a custom group, it may specify that we hide a specific column
329
344
  const custom = this._listGroupMapped?.[this.group];
330
345
 
346
+ let hideColumn;
347
+
331
348
  if (custom?.hideColumn) {
332
- const idx = headers.findIndex((header) => header.name === custom.hideColumn);
349
+ hideColumn = custom.hideColumn;
350
+ } else {
351
+ const componentCustom = this.groupOptions?.find((go) => go.value === this.group);
352
+
353
+ hideColumn = componentCustom?.hideColumn;
354
+ }
355
+
356
+ if (hideColumn) {
357
+ const idx = headers.findIndex((header) => header.name === hideColumn);
333
358
 
334
359
  if ( idx >= 0 ) {
335
360
  headers.splice(idx, 1);
@@ -388,17 +413,17 @@ export default {
388
413
  group: {
389
414
  get() {
390
415
  // Check group is valid
391
- const exists = this.groupOptions.find((g) => g.value === this._group);
416
+ const exists = this._groupOptions.find((g) => g.value === this._group);
392
417
 
393
418
  if (!exists) {
394
419
  // Attempt to find the default option in available options...
395
420
  // if not use the first value in the options collection...
396
421
  // and if not that just fall back to the default
397
- if (this.groupOptions.find((g) => g.value === DEFAULT_GROUP)) {
398
- return DEFAULT_GROUP;
422
+ if (this._groupOptions.find((g) => g.value === this.groupDefault)) {
423
+ return this.groupDefault;
399
424
  }
400
425
 
401
- return this.groupOptions[0]?.value || DEFAULT_GROUP;
426
+ return this._groupOptions[0]?.value || this.groupDefault || DEFAULT_GROUP;
402
427
  }
403
428
 
404
429
  return this._group;
@@ -413,7 +438,7 @@ export default {
413
438
  const namespaceGroupable = this.$store.getters['isMultipleNamespaces'] && this.isNamespaced;
414
439
  const customGroupable = !!this.options?.listGroups?.length;
415
440
 
416
- return namespaceGroupable || customGroupable;
441
+ return namespaceGroupable || customGroupable || this.groupOptions?.length;
417
442
  }
418
443
 
419
444
  return this.groupable || false;
@@ -442,10 +467,20 @@ export default {
442
467
  return custom.field;
443
468
  }
444
469
 
470
+ const componentCustom = this.groupOptions?.find((go) => go.value === this.group);
471
+
472
+ if (componentCustom?.field) {
473
+ return componentCustom.field;
474
+ }
475
+
445
476
  return null;
446
477
  },
447
478
 
448
- groupOptions() {
479
+ _groupOptions() {
480
+ if (this.groupOptions) {
481
+ return this.groupOptions;
482
+ }
483
+
449
484
  // Ignore the defaults below, we have an override set of groups
450
485
  // REPLACE (instead of SUPPLEMENT) defaults with listGroups (given listGroupsWillOverride is true)
451
486
  if (this.options?.listGroupsWillOverride && !!this.options?.listGroups?.length) {
@@ -569,7 +604,7 @@ export default {
569
604
  :alt-loading="altLoading"
570
605
  :group-by="computedGroupBy"
571
606
  :group="group"
572
- :group-options="groupOptions"
607
+ :group-options="_groupOptions"
573
608
  :search="search"
574
609
  :paging="true"
575
610
  :paging-params="parsedPagingParams"
@@ -596,14 +631,14 @@ export default {
596
631
  @enter="handleEnterKeyPress"
597
632
  >
598
633
  <template
599
- v-if="!hideGroupingControls && showGrouping"
634
+ v-if="showGrouping && _groupOptions.length > 1"
600
635
  #header-middle
601
636
  >
602
637
  <slot name="more-header-middle" />
603
638
 
604
639
  <ButtonGroup
605
640
  v-model:value="group"
606
- :options="groupOptions"
641
+ :options="_groupOptions"
607
642
  />
608
643
  </template>
609
644
 
@@ -394,7 +394,11 @@ export default {
394
394
  </script>
395
395
 
396
396
  <template>
397
- <nav class="side-nav">
397
+ <nav
398
+ class="side-nav"
399
+ role="navigation"
400
+ :aria-label="t('nav.ariaLabel.sideNav')"
401
+ >
398
402
  <!-- Actual nav -->
399
403
  <div class="nav">
400
404
  <template
@@ -0,0 +1,126 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from 'vue';
3
+ import { useStore } from 'vuex';
4
+
5
+ const HEADER_HEIGHT = 55;
6
+
7
+ const store = useStore();
8
+ const isOpen = computed(() => store.getters['slideInPanel/isOpen']);
9
+ const currentComponent = computed(() => store.getters['slideInPanel/component']);
10
+ const currentProps = computed(() => store.getters['slideInPanel/componentProps']);
11
+
12
+ const panelTop = computed(() => {
13
+ const banner = document.getElementById('banner-header');
14
+ let height = HEADER_HEIGHT;
15
+
16
+ if (banner) {
17
+ height += banner.clientHeight;
18
+ }
19
+
20
+ return `${ height }px`;
21
+ });
22
+
23
+ const panelHeight = computed(() => `calc(100vh - ${ panelTop?.value })`);
24
+ const panelWidth = computed(() => currentProps?.value?.width || '33%');
25
+ const panelRight = computed(() => (isOpen?.value ? '0' : `-${ panelWidth?.value }`));
26
+
27
+ const panelTitle = computed(() => currentProps?.value?.title || 'Details');
28
+
29
+ function closePanel() {
30
+ store.commit('slideInPanel/close');
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <Teleport to="#slides">
36
+ <div id="slide-in-panel-manager">
37
+ <div
38
+ v-show="isOpen"
39
+ data-testid="slide-in-glass"
40
+ class="slide-in-glass"
41
+ :class="{ 'slide-in-glass-open': isOpen }"
42
+ @click="closePanel"
43
+ />
44
+ <div
45
+ class="slide-in"
46
+ :class="{ 'slide-in-open': isOpen }"
47
+ :style="{ width: panelWidth, right: panelRight, top: panelTop, height: panelHeight }"
48
+ >
49
+ <div class="header">
50
+ <div class="title">
51
+ {{ panelTitle }}
52
+ </div>
53
+ <i
54
+ class="icon icon-close"
55
+ data-testid="slide-in-close"
56
+ :trigger-focus-trap="true"
57
+ tabindex="0"
58
+ @click="closePanel"
59
+ />
60
+ </div>
61
+ <div class="main-panel">
62
+ <component
63
+ :is="currentComponent"
64
+ v-if="isOpen || currentComponent"
65
+ v-bind="currentProps"
66
+ data-testid="slide-in-panel-component"
67
+ class="dynamic-panel-content"
68
+ />
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </Teleport>
73
+ </template>
74
+
75
+ <style lang="scss" scoped>
76
+ .slide-in-glass {
77
+ display: none;
78
+ position: fixed;
79
+ top: 0;
80
+ left: 0;
81
+ height: 100vh;
82
+ width: 100vw;
83
+ }
84
+ .slide-in-glass-open {
85
+ background-color: var(--body-bg);
86
+ display: block;
87
+ opacity: 0.5;
88
+ z-index: 1000;
89
+ }
90
+
91
+ .slide-in {
92
+ display: flex;
93
+ flex-direction: column;
94
+ position: fixed;
95
+ top: 0;
96
+ z-index: 2000;
97
+ transition: right 0.5s ease;
98
+ border-left: 1px solid var(--border);
99
+ background-color: var(--body-bg);
100
+ }
101
+
102
+ .slide-in-open {
103
+ right: 0;
104
+ }
105
+
106
+ .header {
107
+ display: flex;
108
+ align-items: center;
109
+ padding: 4px;
110
+ border-bottom: 1px solid var(--border);
111
+
112
+ .title {
113
+ flex: 1;
114
+ font-weight: bold;
115
+ }
116
+
117
+ .icon-close {
118
+ cursor: pointer;
119
+ }
120
+ }
121
+
122
+ .main-panel {
123
+ padding: 10px;
124
+ overflow: auto;
125
+ }
126
+ </style>
@@ -259,10 +259,13 @@ export default {
259
259
  v-clean-tooltip="tooltip(col)"
260
260
  class="content"
261
261
  >
262
- <span v-clean-html="labelFor(col)" />
262
+ <span
263
+ v-clean-html="labelFor(col)"
264
+ class="text-no-break"
265
+ />
263
266
  <span
264
267
  v-if="col.subLabel"
265
- class="text-muted"
268
+ class="text-muted text-no-break"
266
269
  >
267
270
  {{ col.subLabel }}
268
271
  </span>
@@ -1,7 +1,7 @@
1
1
  import debounce from 'lodash/debounce';
2
2
 
3
3
  // Use a visible display type to reduce flickering
4
- const displayType = 'inline-block';
4
+ const displayType = 'inline-flex';
5
5
 
6
6
  export default {
7
7