@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
@@ -1,8 +1,7 @@
1
1
  <script>
2
- import { mapGetters } from 'vuex';
2
+ import { mapGetters, useStore } from 'vuex';
3
3
  import { defineAsyncComponent, ref, onMounted, onBeforeUnmount } from 'vue';
4
4
  import day from 'dayjs';
5
- import semver from 'semver';
6
5
  import isEmpty from 'lodash/isEmpty';
7
6
  import { dasherize, ucFirst } from '@shell/utils/string';
8
7
  import { get, clone } from '@shell/utils/object';
@@ -25,7 +24,8 @@ import { getParent } from '@shell/utils/dom';
25
24
  import { FORMATTERS } from '@shell/components/SortableTable/sortable-config';
26
25
  import ButtonMultiAction from '@shell/components/ButtonMultiAction.vue';
27
26
  import ActionMenu from '@shell/components/ActionMenuShell.vue';
28
- import { getVersionInfo } from '@shell/utils/version';
27
+ import { useRuntimeFlag } from '@shell/composables/useRuntimeFlag';
28
+ import ActionDropdownShell from '@shell/components/ActionDropdownShell.vue';
29
29
 
30
30
  // Uncomment for table performance debugging
31
31
  // import tableDebug from './debug';
@@ -62,6 +62,7 @@ export default {
62
62
  LabeledSelect,
63
63
  ButtonMultiAction,
64
64
  ActionMenu,
65
+ ActionDropdownShell,
65
66
  },
66
67
  mixins: [
67
68
  filtering,
@@ -546,7 +547,13 @@ export default {
546
547
  table.value.removeEventListener('keyup', handleEnterKey);
547
548
  });
548
549
 
549
- return { table };
550
+ const store = useStore();
551
+ const { featureDropdownMenu } = useRuntimeFlag(store);
552
+
553
+ return {
554
+ table,
555
+ featureDropdownMenu,
556
+ };
550
557
  },
551
558
 
552
559
  created() {
@@ -768,12 +775,6 @@ export default {
768
775
 
769
776
  return rows;
770
777
  },
771
-
772
- featureDropdownMenu() {
773
- const { fullVersion } = getVersionInfo(this.$store);
774
-
775
- return semver.gte(semver.coerce(fullVersion).version, '2.11.0');
776
- }
777
778
  },
778
779
 
779
780
  methods: {
@@ -1104,47 +1105,59 @@ export default {
1104
1105
  />
1105
1106
  <span v-clean-html="act.label" />
1106
1107
  </button>
1107
- <ActionDropdown
1108
- :class="bulkActionsDropdownClass"
1109
- class="bulk-actions-dropdown"
1110
- :disable-button="!selectedRows.length"
1111
- size="sm"
1112
- >
1113
- <template #button-content>
1114
- <button
1115
- ref="actionDropDown"
1116
- class="btn bg-primary mr-0"
1117
- :disabled="!selectedRows.length"
1118
- >
1119
- <i class="icon icon-gear" />
1120
- <span>{{ t('sortableTable.bulkActions.collapsed.label') }}</span>
1121
- <i class="ml-10 icon icon-chevron-down" />
1122
- </button>
1123
- </template>
1124
- <template #popover-content>
1125
- <ul class="list-unstyled menu">
1126
- <li
1127
- v-for="(act, i) in hiddenActions"
1128
- :key="i"
1129
- v-close-popper
1130
- v-clean-tooltip="{
1131
- content: actionTooltip,
1132
- placement: 'right'
1133
- }"
1134
- :class="{ disabled: !act.enabled }"
1135
- @click="applyTableAction(act, null, $event)"
1136
- @mouseover="setBulkActionOfInterest(act)"
1137
- @mouseleave="setBulkActionOfInterest(null)"
1108
+ <template v-if="featureDropdownMenu">
1109
+ <ActionDropdownShell
1110
+ :disabled="!selectedRows.length"
1111
+ :hidden-actions="hiddenActions"
1112
+ :action-tooltip="actionTooltip"
1113
+ @click="applyTableAction"
1114
+ @mouseover="setBulkActionOfInterest"
1115
+ @mouseleave="setBulkActionOfInterest"
1116
+ />
1117
+ </template>
1118
+ <template v-else>
1119
+ <ActionDropdown
1120
+ :class="bulkActionsDropdownClass"
1121
+ class="bulk-actions-dropdown"
1122
+ :disable-button="!selectedRows.length"
1123
+ size="sm"
1124
+ >
1125
+ <template #button-content>
1126
+ <button
1127
+ ref="actionDropDown"
1128
+ class="btn bg-primary mr-0"
1129
+ :disabled="!selectedRows.length"
1138
1130
  >
1139
- <i
1140
- v-if="act.icon"
1141
- :class="act.icon"
1142
- />
1143
- <span v-clean-html="act.label" />
1144
- </li>
1145
- </ul>
1146
- </template>
1147
- </ActionDropdown>
1131
+ <i class="icon icon-gear" />
1132
+ <span>{{ t('sortableTable.bulkActions.collapsed.label') }}</span>
1133
+ <i class="ml-10 icon icon-chevron-down" />
1134
+ </button>
1135
+ </template>
1136
+ <template #popover-content>
1137
+ <ul class="list-unstyled menu">
1138
+ <li
1139
+ v-for="(act, i) in hiddenActions"
1140
+ :key="i"
1141
+ v-close-popper
1142
+ v-clean-tooltip="{
1143
+ content: actionTooltip,
1144
+ placement: 'right'
1145
+ }"
1146
+ :class="{ disabled: !act.enabled }"
1147
+ @click="applyTableAction(act, null, $event)"
1148
+ @mouseover="setBulkActionOfInterest(act)"
1149
+ @mouseleave="setBulkActionOfInterest(null)"
1150
+ >
1151
+ <i
1152
+ v-if="act.icon"
1153
+ :class="act.icon"
1154
+ />
1155
+ <span v-clean-html="act.label" />
1156
+ </li>
1157
+ </ul>
1158
+ </template>
1159
+ </ActionDropdown>
1160
+ </template>
1148
1161
  <label
1149
1162
  v-if="selectedRowsText"
1150
1163
  :class="bulkActionAvailabilityClass"
@@ -1457,9 +1470,9 @@ export default {
1457
1470
  :value="col.value"
1458
1471
  :row="row.row"
1459
1472
  :col="col.col"
1473
+ :get-custom-detail-link="getCustomDetailLink"
1460
1474
  v-bind="col.col.formatterOpts"
1461
1475
  :row-key="row.key"
1462
- :get-custom-detail-link="getCustomDetailLink"
1463
1476
  />
1464
1477
  <component
1465
1478
  :is="col.component"
@@ -49,6 +49,21 @@ export default {
49
49
  return this.$store.getters['i18n/t'](this.pagingLabel, opt);
50
50
  },
51
51
 
52
+ perPage() {
53
+ let out = this.rowsPerPage || 0;
54
+
55
+ if ( out <= 0 ) {
56
+ out = parseInt(this.$store.getters['prefs/get'](ROWS_PER_PAGE), 10) || 0;
57
+ }
58
+
59
+ // This should ideally never happen, but the preference value could be invalid, so return something...
60
+ if ( out <= 0 ) {
61
+ out = 10;
62
+ }
63
+
64
+ return out;
65
+ },
66
+
52
67
  pagedRows() {
53
68
  if (this.externalPaginationEnabled) {
54
69
  return this.rows;
@@ -61,9 +76,7 @@ export default {
61
76
  },
62
77
 
63
78
  data() {
64
- const perPage = this.getPerPage();
65
-
66
- return { page: 1, perPage };
79
+ return { page: 1 };
67
80
  },
68
81
 
69
82
  watch: {
@@ -89,22 +102,6 @@ export default {
89
102
  },
90
103
 
91
104
  methods: {
92
- getPerPage() {
93
- // perPage can not change while the list is displayed
94
- let out = this.rowsPerPage || 0;
95
-
96
- if ( out <= 0 ) {
97
- out = parseInt(this.$store.getters['prefs/get'](ROWS_PER_PAGE), 10) || 0;
98
- }
99
-
100
- // This should ideally never happen, but the preference value could be invalid, so return something...
101
- if ( out <= 0 ) {
102
- out = 10;
103
- }
104
-
105
- return out;
106
- },
107
-
108
105
  setPage(num) {
109
106
  if (this.page === num) {
110
107
  return;
@@ -340,17 +340,6 @@ export default {
340
340
  if ( !isSelected ) {
341
341
  this.update([node], this.selectedRows.slice());
342
342
  }
343
-
344
- let resources = this.selectedRows;
345
-
346
- if ( this.mangleActionResources ) {
347
- resources = await this.mangleActionResources(resources);
348
- }
349
-
350
- this.$store.commit(`action-menu/show`, {
351
- resources,
352
- event: e,
353
- });
354
343
  },
355
344
 
356
345
  keySelectRow(row, more = false) {
@@ -426,7 +426,7 @@ export default {
426
426
  </div>
427
427
  <div
428
428
  id="wizard-footer-controls"
429
- class="controls-row pt-20"
429
+ class="controls-row"
430
430
  >
431
431
  <slot
432
432
  name="cancel"
@@ -674,7 +674,7 @@ $spacer: 10px;
674
674
  // We have to account for the absolute position of the .controls-row
675
675
  .footer-error {
676
676
  margin-top: -40px;
677
- margin-bottom: 70px;
677
+ margin-bottom: calc($footer-height + 10px);
678
678
  }
679
679
 
680
680
  .controls-row {
@@ -42,7 +42,7 @@ describe('component: AsyncButton', () => {
42
42
  expect(span.text()).toBe('some-string');
43
43
  });
44
44
 
45
- it('click on async button should emit click with a proper state of waiting, disabled and spinning ::: CB true', () => {
45
+ it('click on async button should emit click with a proper state of waiting, appear disabled and spinning ::: CB true', () => {
46
46
  jest.useFakeTimers();
47
47
 
48
48
  const wrapper: VueWrapper<InstanceType<typeof AsyncButton>> = mount(AsyncButton, {
@@ -65,7 +65,7 @@ describe('component: AsyncButton', () => {
65
65
  expect(wrapper.emitted('click')).toHaveLength(1);
66
66
  expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.WAITING);
67
67
  expect(wrapper.vm.isSpinning).toBe(true);
68
- expect(wrapper.vm.isDisabled).toBe(true);
68
+ expect(wrapper.vm.appearsDisabled).toBe(true);
69
69
  // testing cb function has been emitted
70
70
  expect(typeof wrapper.emitted('click')![0][0]).toBe('function');
71
71
 
@@ -0,0 +1,176 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { createStore, Store } from 'vuex';
3
+ import { nextTick } from 'vue';
4
+
5
+ import ModalManager from '@shell/components/ModalManager.vue';
6
+
7
+ interface ModalManagerMethods {
8
+ registerBackgroundClosing(fn: Function): void;
9
+ close(): void;
10
+ }
11
+
12
+ const MockComponent = {
13
+ template: '<div data-testid="modal-manager-component">Mock Content</div>',
14
+ props: ['someProp', 'resources', 'registerBackgroundClosing']
15
+ };
16
+
17
+ describe('modalManager.vue with Teleport', () => {
18
+ let store: Store<any>;
19
+ let getters: Record<string, () => any>;
20
+ let modalsDiv: HTMLDivElement;
21
+
22
+ beforeEach(() => {
23
+ // Create the teleport target container
24
+ modalsDiv = document.createElement('div');
25
+ modalsDiv.setAttribute('id', 'modals');
26
+ document.body.appendChild(modalsDiv);
27
+
28
+ getters = {
29
+ 'modal/isOpen': () => true,
30
+ 'modal/component': () => MockComponent,
31
+ 'modal/componentProps': () => ({ someProp: 'testValue' }),
32
+ 'modal/resources': () => ({ data: 'mockData' }),
33
+ 'modal/closeOnClickOutside': () => true,
34
+ 'modal/modalWidth': () => '500px'
35
+ };
36
+
37
+ store = createStore({
38
+ getters,
39
+ mutations: { 'modal/closeModal': jest.fn() }
40
+ });
41
+ });
42
+
43
+ afterEach(() => {
44
+ // Clean up the teleport container after each test
45
+ document.body.removeChild(modalsDiv);
46
+ });
47
+
48
+ const factory = () => {
49
+ return mount(ModalManager, {
50
+ attachTo: document.body, // attach so Teleport can work properly
51
+ global: {
52
+ plugins: [store],
53
+ stubs: {
54
+ AppModal: {
55
+ name: 'AppModal',
56
+ template: `<div data-testid="app-modal" @close="$emit('close')" :style="{ '--prompt-modal-width': width }"><slot /></div>`,
57
+ props: ['clickToClose', 'width']
58
+ }
59
+ }
60
+ }
61
+ });
62
+ };
63
+
64
+ it('renders the AppModal and dynamic component when modal is open', async() => {
65
+ factory();
66
+
67
+ await nextTick();
68
+
69
+ // Because Teleport moves content out of the normal wrapper,
70
+ // we query the document for the teleported elements.
71
+ const appModal = document.querySelector('[data-testid="app-modal"]');
72
+ const dynamicComponent = document.querySelector('[data-testid="modal-manager-component"]');
73
+
74
+ expect(appModal).toBeTruthy();
75
+ expect(dynamicComponent).toBeTruthy();
76
+ expect(appModal?.getAttribute('style')).toContain('--prompt-modal-width: 500px');
77
+
78
+ // We assume the mock component is rendered correctly if its markup is found.
79
+ });
80
+
81
+ it('does not render the AppModal when modal is closed', async() => {
82
+ getters['modal/isOpen'] = () => false;
83
+ store = createStore({
84
+ getters,
85
+ mutations: { 'modal/closeModal': jest.fn() }
86
+ });
87
+ factory();
88
+ await nextTick();
89
+
90
+ const appModal = document.querySelector('[data-testid="app-modal"]');
91
+
92
+ expect(appModal).toBeNull();
93
+ });
94
+
95
+ it('does not render the AppModal when dynamic component is null', async() => {
96
+ getters['modal/component'] = () => null;
97
+ store = createStore({
98
+ getters,
99
+ mutations: { 'modal/closeModal': jest.fn() }
100
+ });
101
+ factory();
102
+ await nextTick();
103
+
104
+ const appModal = document.querySelector('[data-testid="app-modal"]');
105
+
106
+ expect(appModal).toBeNull();
107
+ });
108
+
109
+ it('calls store commit when close is triggered', async() => {
110
+ const closeModalMutation = jest.fn();
111
+
112
+ getters['modal/isOpen'] = () => true;
113
+ store = createStore({
114
+ getters,
115
+ mutations: { 'modal/closeModal': closeModalMutation }
116
+ });
117
+ const wrapper = factory();
118
+
119
+ await nextTick();
120
+
121
+ const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
122
+
123
+ appModalWrapper.vm.$emit('close');
124
+ await nextTick();
125
+
126
+ expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
127
+ });
128
+
129
+ it('calls registered background closing function on close', async() => {
130
+ const closeModalMutation = jest.fn();
131
+
132
+ getters['modal/isOpen'] = () => true;
133
+ store = createStore({
134
+ getters,
135
+ mutations: { 'modal/closeModal': closeModalMutation }
136
+ });
137
+ const wrapper = factory();
138
+
139
+ await nextTick();
140
+
141
+ const backgroundFn = jest.fn();
142
+
143
+ (wrapper.vm as unknown as ModalManagerMethods).registerBackgroundClosing(backgroundFn);
144
+ await nextTick();
145
+
146
+ const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
147
+
148
+ appModalWrapper.vm.$emit('close');
149
+ await nextTick();
150
+
151
+ expect(backgroundFn).toHaveBeenCalledWith();
152
+ expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
153
+ });
154
+
155
+ it('does nothing if modal is already closed when close is triggered', async() => {
156
+ const closeModalMutation = jest.fn();
157
+
158
+ getters['modal/isOpen'] = () => false;
159
+ store = createStore({
160
+ getters,
161
+ mutations: { 'modal/closeModal': closeModalMutation }
162
+ });
163
+ const wrapper = factory();
164
+
165
+ await nextTick();
166
+
167
+ const modalManager = wrapper.vm as unknown as ModalManagerMethods;
168
+ const spy = jest.spyOn(modalManager, 'close');
169
+
170
+ modalManager.close();
171
+ await nextTick();
172
+
173
+ expect(spy).toHaveBeenCalledWith();
174
+ expect(closeModalMutation).not.toHaveBeenCalled();
175
+ });
176
+ });
@@ -0,0 +1,148 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import PromptModal from '@shell/components/PromptModal.vue';
3
+
4
+ import GenericPrompt from '@shell/dialog/GenericPrompt.vue';
5
+ import AddClusterMemberDialog from '@shell/dialog/AddClusterMemberDialog.vue';
6
+ import AddCustomBadgeDialog from '@shell/dialog/AddCustomBadgeDialog.vue';
7
+ import AddonConfigConfirmationDialog from '@shell/dialog/AddonConfigConfirmationDialog.vue';
8
+ import AddProjectMemberDialog from '@shell/dialog/AddProjectMemberDialog.vue';
9
+ import DeactivateDriverDialog from '@shell/dialog/DeactivateDriverDialog.vue';
10
+ import DiagnosticTimingsDialog from '@shell/dialog/DiagnosticTimingsDialog.vue';
11
+ import DrainNode from '@shell/dialog/DrainNode.vue';
12
+ import ForceMachineRemoveDialog from '@shell/dialog/ForceMachineRemoveDialog.vue';
13
+ import GitRepoForceUpdateDialog from '@shell/dialog/GitRepoForceUpdateDialog.vue';
14
+ import RollbackWorkloadDialog from '@shell/dialog/RollbackWorkloadDialog.vue';
15
+ import RotateCertificatesDialog from '@shell/dialog/RotateCertificatesDialog.vue';
16
+ import RotateEncryptionKeyDialog from '@shell/dialog/RotateEncryptionKeyDialog.vue';
17
+ import SaveAsRKETemplateDialog from '@shell/dialog/SaveAsRKETemplateDialog.vue';
18
+ import ScaleMachineDownDialog from '@shell/dialog/ScaleMachineDownDialog.vue';
19
+ import ScalePoolDownDialog from '@shell/dialog/ScalePoolDownDialog.vue';
20
+ import SloDialog from '@shell/dialog/SloDialog.vue';
21
+
22
+ import DisableAuthProviderDialog from '@shell/dialog/DisableAuthProviderDialog.vue';
23
+ import WechatDialog from '@shell/dialog/WechatDialog.vue';
24
+ import DeveloperLoadExtensionDialog from '@shell/dialog/DeveloperLoadExtensionDialog.vue';
25
+ import AddExtensionReposDialog from '@shell/dialog/AddExtensionReposDialog.vue';
26
+ import InstallExtensionDialog from '@shell/dialog/InstallExtensionDialog.vue';
27
+ import UninstallExtensionDialog from '@shell/dialog/UninstallExtensionDialog.vue';
28
+ import KnownHostsEditDialog from '@shell/dialog/KnownHostsEditDialog.vue';
29
+ import ImportDialog from '@shell/dialog/ImportDialog.vue';
30
+ import SearchDialog from '@shell/dialog/SearchDialog.vue';
31
+ import ChangePasswordDialog from '@shell/dialog/ChangePasswordDialog.vue';
32
+ import AssignToDialog from '@shell/dialog/AssignToDialog.vue';
33
+ import FeatureFlagListDialog from '@shell/dialog/FeatureFlagListDialog.vue';
34
+ import MoveNamespaceDialog from '@shell/dialog/MoveNamespaceDialog.vue';
35
+ import ExtensionCatalogInstallDialog from '@shell/dialog/ExtensionCatalogInstallDialog.vue';
36
+ import ExtensionCatalogUninstallDialog from '@shell/dialog/ExtensionCatalogUninstallDialog.vue';
37
+
38
+ import { createStore } from 'vuex';
39
+
40
+ jest.mock('@shell/utils/clipboard', () => {
41
+ return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
42
+ });
43
+
44
+ function generateStore(component: any):any {
45
+ return createStore({
46
+ modules: { // promptModal
47
+ 'action-menu': {
48
+ namespaced: true,
49
+ state: {
50
+ modalData: {
51
+ closeOnClickOutside: true,
52
+ resources: [{ cluster: { isRke2: true, machines: [] } }], // ScaleMachineDownDialog
53
+ componentProps: {
54
+ drivers: [], // DeactivateDriverDialog
55
+ driverType: 'kontainerDrivers', // DeactivateDriverDialog
56
+ downloadData: () => jest.fn(), // DiagnosticTimingsDialog
57
+ gatherResponseTimes: () => jest.fn(), // DiagnosticTimingsDialog
58
+ kubeNodes: [{}], // DrainNode
59
+ repositories: [], // GitRepoForceUpdateDialog
60
+ workload: { metadata: {}, kind: '' }, // RollbackWorkloadDialog
61
+ catalog: {}
62
+ },
63
+ },
64
+
65
+ },
66
+ },
67
+ },
68
+ getters: {
69
+ 'type-map/importDialog': () => () => component, // promptModal
70
+ 'i18n/exists': () => jest.fn(), // promptModal
71
+ 'i18n/t': () => jest.fn(), // general usage
72
+ 'rancher/schemaFor': () => jest.fn(), // general usage
73
+ 'prefs/get': () => jest.fn(), // ScalePoolDownDialog
74
+ 'type-map/allTypes': () => jest.fn(), // SearchDialog
75
+ 'type-map/labelFor': () => jest.fn(), // ScaleMachineDownDialog
76
+ 'type-map/getTree': () => jest.fn().mockReturnValue([]), // SearchDialog
77
+ 'cluster/all': () => jest.fn(), // SearchDialog
78
+ currentProduct: () => { // SearchDialog
79
+ return { inStore: 'cluster' };
80
+ },
81
+ currentCluster: () => { // general usage
82
+ 'local';
83
+ },
84
+ }
85
+ });
86
+ }
87
+
88
+ describe('component: PromptModal', () => {
89
+ it.each([
90
+ // current prompt modals at time of coding
91
+ ['GenericPrompt', GenericPrompt],
92
+ ['AddClusterMemberDialog', AddClusterMemberDialog],
93
+ ['AddonConfigConfirmationDialog', AddonConfigConfirmationDialog],
94
+ ['AddProjectMemberDialog', AddProjectMemberDialog],
95
+ ['DeactivateDriverDialog', DeactivateDriverDialog],
96
+ ['DiagnosticTimingsDialog', DiagnosticTimingsDialog],
97
+ ['DrainNode', DrainNode],
98
+ ['ForceMachineRemoveDialog', ForceMachineRemoveDialog],
99
+ ['GitRepoForceUpdateDialog', GitRepoForceUpdateDialog],
100
+ ['RollbackWorkloadDialog', RollbackWorkloadDialog],
101
+ ['RotateCertificatesDialog', RotateCertificatesDialog],
102
+ ['RotateEncryptionKeyDialog', RotateEncryptionKeyDialog],
103
+ ['SaveAsRKETemplateDialog', SaveAsRKETemplateDialog],
104
+ ['SloDialog', SloDialog],
105
+ ['AddCustomBadgeDialog', AddCustomBadgeDialog],
106
+ ['ScaleMachineDownDialog', ScaleMachineDownDialog],
107
+ ['ScalePoolDownDialog', ScalePoolDownDialog],
108
+ // new modals created/moved
109
+ ['DisableAuthProviderDialog', DisableAuthProviderDialog],
110
+ ['WechatDialog', WechatDialog],
111
+ ['DeveloperLoadExtensionDialog', DeveloperLoadExtensionDialog],
112
+ ['AddExtensionReposDialog', AddExtensionReposDialog],
113
+ ['InstallExtensionDialog', InstallExtensionDialog],
114
+ ['UninstallExtensionDialog', UninstallExtensionDialog],
115
+ ['KnownHostsEditDialog', KnownHostsEditDialog],
116
+ ['ImportDialog', ImportDialog],
117
+ ['SearchDialog', SearchDialog],
118
+ ['ChangePasswordDialog', ChangePasswordDialog],
119
+ ['AssignToDialog', AssignToDialog],
120
+ ['FeatureFlagListDialog', FeatureFlagListDialog],
121
+ ['MoveNamespaceDialog', MoveNamespaceDialog],
122
+ ['ExtensionCatalogInstallDialog', ExtensionCatalogInstallDialog],
123
+ ['ExtensionCatalogUninstallDialog', ExtensionCatalogUninstallDialog],
124
+ ])('prompt Modal should render modal %p', (modalName, component) => {
125
+ // mock structuredClone
126
+ window.structuredClone = (arg) => JSON.parse(JSON.stringify(arg));
127
+
128
+ document.body.innerHTML = '<div id="modals"></div>';
129
+ const wrapper = mount(PromptModal,
130
+ {
131
+ attachTo: document.body,
132
+ data() {
133
+ return { opened: true }; // this controls modal content visibility
134
+ },
135
+ global: {
136
+ mocks: {
137
+ $store: generateStore(component),
138
+ $fetchState: {}
139
+ },
140
+ stubs: { transition: false }
141
+ }
142
+ }
143
+ );
144
+
145
+ expect(wrapper.vm.opened).toBe(true);
146
+ expect(wrapper.findComponent(component as any).exists()).toBe(true);
147
+ });
148
+ });