@rancher/shell 0.3.0 → 0.3.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 (342) hide show
  1. package/assets/styles/global/_button.scss +5 -1
  2. package/assets/styles/global/_columns.scss +4 -0
  3. package/assets/styles/global/_gauges.scss +1 -1
  4. package/assets/styles/global/_layout.scss +5 -2
  5. package/assets/styles/global/_select.scss +1 -4
  6. package/assets/styles/themes/_dark.scss +5 -4
  7. package/assets/styles/themes/_light.scss +4 -3
  8. package/assets/styles/themes/_suse.scss +1 -1
  9. package/assets/styles/vendor/vue-select.scss +4 -3
  10. package/assets/translations/en-us.yaml +673 -73
  11. package/assets/translations/zh-hans.yaml +720 -207
  12. package/chart/monitoring/steps/uninstall-v1.vue +2 -2
  13. package/cloud-credential/azure.vue +23 -0
  14. package/cloud-credential/harvester.vue +25 -62
  15. package/cloud-credential/pnap.vue +80 -0
  16. package/components/.DS_Store +0 -0
  17. package/components/ActionMenu.vue +28 -7
  18. package/components/AdvancedSection.vue +9 -2
  19. package/components/Alert.vue +2 -2
  20. package/components/ButtonDropdown.vue +0 -2
  21. package/components/ButtonGroup.vue +1 -0
  22. package/components/CollapsibleCard.vue +0 -1
  23. package/components/CruResource.vue +41 -4
  24. package/components/DetailTop.vue +72 -4
  25. package/components/DisableAuthProviderModal.vue +106 -0
  26. package/{rancher-components/components/Utils/DraggableZone → components}/DraggableZone.vue +0 -0
  27. package/components/ExplorerMembers.vue +253 -30
  28. package/components/ExplorerProjectsNamespaces.vue +77 -33
  29. package/components/ExtensionPanel.vue +42 -0
  30. package/components/GrowlManager.vue +3 -3
  31. package/components/IconOrSvg.vue +178 -0
  32. package/components/LogItem.vue +69 -0
  33. package/components/PodSecurityAdmission.vue +302 -0
  34. package/components/PromptModal.vue +1 -0
  35. package/components/ResourceDetail/Masthead.vue +69 -4
  36. package/components/ResourceDetail/index.vue +12 -5
  37. package/components/ResourceList/Masthead.vue +11 -1
  38. package/components/ResourceList/ResourceLoadingIndicator.vue +12 -2
  39. package/components/ResourceList/index.vue +66 -12
  40. package/components/ResourceList/resource-list.config.js +7 -0
  41. package/components/ResourceTable.vue +33 -6
  42. package/components/SimpleBox.vue +1 -1
  43. package/components/SortableTable/THead.vue +21 -14
  44. package/components/SortableTable/filtering.js +1 -1
  45. package/components/SortableTable/index.vue +21 -10
  46. package/components/SortableTable/selection.js +15 -3
  47. package/components/Tabbed/Tab.vue +1 -1
  48. package/components/Tabbed/index.vue +20 -15
  49. package/components/__tests__/.DS_Store +0 -0
  50. package/components/__tests__/AsyncButton.test.ts +140 -0
  51. package/components/__tests__/BackLink.test.ts +33 -0
  52. package/components/__tests__/ButtonGroup.test.ts +124 -0
  53. package/components/__tests__/ClusterBadge.test.ts +32 -0
  54. package/components/__tests__/CollapsibleCard.test.ts +64 -0
  55. package/components/__tests__/ConsumptionGauge.test.ts +88 -0
  56. package/components/__tests__/CruResource.test.ts +3 -2
  57. package/components/__tests__/FixedBanner.test.ts +129 -0
  58. package/components/__tests__/GrowlManager.test.ts +147 -0
  59. package/components/__tests__/NamespaceFilter.test.ts +33 -25
  60. package/components/__tests__/PercentageBar.test.ts +32 -0
  61. package/components/__tests__/PodSecurityAdmission.test.ts +398 -0
  62. package/components/auth/AuthBanner.vue +20 -10
  63. package/components/auth/RoleDetailEdit.vue +26 -17
  64. package/components/auth/SelectPrincipal.vue +36 -5
  65. package/components/form/ArrayList.vue +3 -35
  66. package/components/form/ArrayListGrouped.vue +13 -4
  67. package/components/form/ArrayListSelect.vue +5 -5
  68. package/components/form/Error.vue +8 -0
  69. package/components/form/KeyValue.vue +39 -7
  70. package/components/form/LabeledSelect.vue +5 -2
  71. package/components/form/Labels.vue +46 -16
  72. package/components/form/Members/ClusterPermissionsEditor.vue +17 -17
  73. package/components/form/Members/MembershipEditor.vue +12 -12
  74. package/components/form/NameNsDescription.vue +1 -1
  75. package/components/form/NodeScheduling.vue +1 -1
  76. package/components/form/Probe.vue +3 -3
  77. package/components/form/ResourceQuota/Project.vue +6 -6
  78. package/components/form/ResourceTabs/index.vue +24 -6
  79. package/components/form/Security.vue +7 -6
  80. package/components/form/Select.vue +3 -2
  81. package/components/form/SelectOrCreateAuthSecret.vue +22 -29
  82. package/components/form/ServicePorts.vue +8 -0
  83. package/components/form/WorkloadPorts.vue +7 -1
  84. package/components/form/__tests__/ArrayList.test.ts +74 -0
  85. package/components/form/__tests__/ArrayListGrouped.test.ts +6 -4
  86. package/components/formatter/Checked.vue +1 -1
  87. package/components/formatter/ClusterLink.vue +5 -0
  88. package/components/formatter/IconIsDefault.vue +2 -2
  89. package/components/formatter/InternalExternalIP.vue +11 -8
  90. package/components/formatter/LiveDuration.vue +78 -0
  91. package/components/formatter/WorkloadHealthScale.vue +5 -3
  92. package/components/nav/Header.vue +74 -7
  93. package/components/nav/NamespaceFilter.vue +146 -63
  94. package/components/nav/TopLevelMenu.vue +22 -19
  95. package/components/nav/WindowManager/ContainerLogs.vue +83 -126
  96. package/components/nav/WindowManager/ContainerShell.vue +9 -7
  97. package/components/nav/WindowManager/Window.vue +2 -0
  98. package/components/nav/WindowManager/index.vue +10 -0
  99. package/config/elemental-types.js +9 -0
  100. package/config/features.js +2 -0
  101. package/config/home-links.js +4 -1
  102. package/config/pod-security-admission.ts +82 -0
  103. package/config/product/apps.js +1 -1
  104. package/config/product/auth.js +6 -5
  105. package/config/product/backup.js +1 -1
  106. package/config/product/explorer.js +6 -6
  107. package/config/product/fleet.js +1 -1
  108. package/config/product/manager.js +6 -2
  109. package/config/query-params.js +1 -0
  110. package/config/secret.js +0 -1
  111. package/config/settings.ts +26 -9
  112. package/config/table-headers.js +22 -11
  113. package/config/types.js +4 -1
  114. package/config/uiplugins.js +3 -3
  115. package/content/docs/zh-hans/getting-started.md +113 -137
  116. package/content/docs/zh-hans/whats-new.md +8 -46
  117. package/core/plugin-helpers.js +171 -0
  118. package/core/plugin.ts +61 -1
  119. package/core/plugins.js +33 -0
  120. package/core/types.ts +128 -2
  121. package/creators/pkg/package-lock.json +37 -0
  122. package/creators/pkg/package.json +1 -1
  123. package/detail/catalog.cattle.io.app.vue +1 -1
  124. package/detail/pod.vue +1 -1
  125. package/detail/provisioning.cattle.io.cluster.vue +35 -9
  126. package/detail/service.vue +2 -9
  127. package/detail/workload/index.vue +0 -1
  128. package/dialog/AddClusterMemberDialog.vue +22 -28
  129. package/dialog/AddProjectMemberDialog.vue +53 -9
  130. package/dialog/DiagnosticTimingsDialog.vue +8 -7
  131. package/dialog/DrainNode.vue +44 -48
  132. package/dialog/ForceMachineRemoveDialog.vue +5 -7
  133. package/dialog/GenericPrompt.vue +15 -20
  134. package/dialog/RollbackWorkloadDialog.vue +15 -46
  135. package/dialog/RotateCertificatesDialog.vue +5 -7
  136. package/dialog/RotateEncryptionKeyDialog.vue +5 -9
  137. package/dialog/SaveAsRKETemplateDialog.vue +5 -13
  138. package/dialog/ScaleMachineDownDialog.vue +1 -1
  139. package/dialog/ScalePoolDownDialog.vue +121 -0
  140. package/edit/__tests__/management.cattle.io.setting.test.ts +3 -3
  141. package/edit/auth/azuread.vue +16 -16
  142. package/edit/auth/github.vue +8 -0
  143. package/edit/auth/googleoauth.vue +10 -1
  144. package/edit/auth/ldap/index.vue +10 -0
  145. package/edit/auth/oidc.vue +10 -0
  146. package/edit/auth/saml.vue +10 -0
  147. package/edit/autoscaling.horizontalpodautoscaler/index.vue +1 -1
  148. package/edit/catalog.cattle.io.clusterrepo.vue +3 -0
  149. package/edit/cloudcredential.vue +3 -7
  150. package/edit/logging-flow/Match.vue +39 -8
  151. package/edit/logging-flow/index.vue +27 -4
  152. package/edit/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +107 -0
  153. package/edit/management.cattle.io.project.vue +8 -1
  154. package/edit/management.cattle.io.setting.vue +5 -2
  155. package/edit/management.cattle.io.user.vue +7 -1
  156. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +36 -8
  157. package/edit/monitoring.coreos.com.alertmanagerconfig/types/email.vue +2 -2
  158. package/edit/monitoring.coreos.com.prometheusrule/GroupRules.vue +14 -6
  159. package/edit/namespace.vue +18 -4
  160. package/edit/networking.k8s.io.ingress/Certificate.vue +1 -0
  161. package/edit/networking.k8s.io.ingress/IngressClass.vue +8 -6
  162. package/edit/networking.k8s.io.ingress/RulePath.vue +12 -6
  163. package/edit/networking.k8s.io.ingress/index.vue +8 -6
  164. package/edit/persistentvolume/index.vue +30 -27
  165. package/edit/persistentvolume/plugins/cephfs.vue +29 -29
  166. package/edit/persistentvolume/plugins/csi.vue +102 -62
  167. package/edit/persistentvolume/plugins/fc.vue +19 -19
  168. package/edit/persistentvolume/plugins/iscsi.vue +45 -45
  169. package/edit/persistentvolume/plugins/rbd.vue +39 -39
  170. package/edit/persistentvolumeclaim.vue +78 -75
  171. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +11 -7
  172. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +10 -1
  173. package/edit/provisioning.cattle.io.cluster/RegistryMirrors.vue +87 -27
  174. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -6
  175. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +96 -0
  176. package/edit/provisioning.cattle.io.cluster/import.vue +1 -1
  177. package/edit/provisioning.cattle.io.cluster/index.vue +29 -6
  178. package/edit/provisioning.cattle.io.cluster/rke2.vue +445 -154
  179. package/edit/secret/index.vue +3 -7
  180. package/edit/service.vue +3 -1
  181. package/edit/storage.k8s.io.storageclass/index.vue +100 -16
  182. package/edit/storage.k8s.io.storageclass/provisioners/driver.harvesterhci.io.vue +114 -0
  183. package/edit/workload/__tests__/index.test.ts +98 -0
  184. package/edit/workload/index.vue +58 -8
  185. package/edit/workload/mixins/workload.js +107 -70
  186. package/edit/workload/storage/ContainerMountPaths.vue +0 -10
  187. package/edit/workload/storage/emptyDir.vue +88 -0
  188. package/edit/workload/storage/ephemeralVolume/index.vue +1 -1
  189. package/edit/workload/storage/index.vue +8 -0
  190. package/edit/workload/storage/persistentVolumeClaim/index.vue +1 -1
  191. package/layouts/default.vue +57 -44
  192. package/list/__tests__/workload.test.ts +5 -2
  193. package/list/catalog.cattle.io.app.vue +1 -0
  194. package/list/cis.cattle.io.clusterscan.vue +1 -0
  195. package/list/fleet.cattle.io.bundle.vue +5 -6
  196. package/list/fleet.cattle.io.cluster.vue +6 -3
  197. package/list/fleet.cattle.io.clusterregistrationtoken.vue +5 -6
  198. package/list/fleet.cattle.io.gitrepo.vue +4 -9
  199. package/list/helm.cattle.io.projecthelmchart.vue +1 -5
  200. package/list/logging.banzaicloud.io.clusterflow.vue +4 -1
  201. package/list/logging.banzaicloud.io.flow.vue +6 -5
  202. package/list/management.cattle.io.cluster.vue +1 -0
  203. package/list/management.cattle.io.feature.vue +3 -4
  204. package/list/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +47 -0
  205. package/list/management.cattle.io.setting.vue +2 -2
  206. package/list/management.cattle.io.user.vue +4 -10
  207. package/list/monitoring.coreos.com.alertmanagerconfig.vue +2 -7
  208. package/list/node.vue +8 -5
  209. package/list/persistentvolume.vue +3 -3
  210. package/list/persistentvolumeclaim.vue +3 -4
  211. package/list/provisioning.cattle.io.cluster.vue +18 -19
  212. package/list/service.vue +6 -14
  213. package/list/workload.vue +43 -38
  214. package/machine-config/azure.vue +429 -60
  215. package/machine-config/pnap.vue +288 -0
  216. package/mixins/auth-config.js +1 -3
  217. package/mixins/browser-tab-visibility.js +8 -14
  218. package/mixins/chart.js +1 -1
  219. package/mixins/create-edit-view/impl.js +4 -0
  220. package/mixins/create-edit-view/index.js +4 -2
  221. package/mixins/resource-fetch-namespaced.js +98 -0
  222. package/mixins/resource-fetch.js +79 -45
  223. package/mixins/resource-manager.js +1 -23
  224. package/models/apps.controllerrevision.js +7 -0
  225. package/models/apps.daemonset.js +18 -0
  226. package/models/apps.deployment.js +44 -0
  227. package/models/apps.replicaset.js +7 -0
  228. package/models/apps.statefulset.js +18 -0
  229. package/models/batch.job.js +7 -14
  230. package/models/cluster/node.js +10 -2
  231. package/models/cluster.x-k8s.io.machine.js +26 -4
  232. package/models/cluster.x-k8s.io.machinedeployment.js +12 -2
  233. package/models/event.js +7 -0
  234. package/models/logging.banzaicloud.io.flow.js +4 -0
  235. package/models/management.cattle.io.cluster.js +1 -1
  236. package/models/management.cattle.io.clusterroletemplatebinding.js +1 -1
  237. package/models/management.cattle.io.globalrole.js +2 -2
  238. package/models/management.cattle.io.node.js +37 -2
  239. package/models/management.cattle.io.podsecurityadmissionconfigurationtemplate.ts +4 -0
  240. package/models/management.cattle.io.project.js +30 -11
  241. package/models/management.cattle.io.setting.js +1 -1
  242. package/models/management.cattle.io.user.js +37 -1
  243. package/models/namespace.js +42 -5
  244. package/models/persistentvolume.js +14 -2
  245. package/models/pod.js +15 -0
  246. package/models/projectroletemplatebinding.js +7 -0
  247. package/models/provisioning.cattle.io.cluster.js +61 -10
  248. package/models/rke-machine.cattle.io.pnapmachinetemplate.js +15 -0
  249. package/models/service.js +14 -13
  250. package/models/storage.k8s.io.storageclass.js +33 -18
  251. package/models/workload.js +38 -7
  252. package/nuxt.config.js +27 -17
  253. package/package.json +7 -7
  254. package/pages/about.vue +14 -2
  255. package/pages/c/_cluster/apps/charts/index.vue +21 -3
  256. package/pages/c/_cluster/apps/charts/install.vue +59 -22
  257. package/pages/c/_cluster/auth/config/_id.vue +6 -0
  258. package/pages/c/_cluster/auth/config/index.vue +8 -6
  259. package/pages/c/_cluster/auth/group.principal/assign-edit.vue +1 -1
  260. package/pages/c/_cluster/auth/roles/index.vue +1 -1
  261. package/pages/c/_cluster/explorer/index.vue +51 -6
  262. package/pages/c/_cluster/longhorn/index.vue +1 -1
  263. package/pages/c/_cluster/monitoring/alertmanagerconfig/_alertmanagerconfigid/receiver.vue +15 -4
  264. package/pages/c/_cluster/monitoring/index.vue +1 -1
  265. package/pages/c/_cluster/neuvector/index.vue +1 -1
  266. package/pages/c/_cluster/settings/performance.vue +48 -2
  267. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +2 -0
  268. package/pages/c/_cluster/uiplugins/InstallDialog.vue +3 -0
  269. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +42 -2
  270. package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +2 -0
  271. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +1 -0
  272. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +2 -0
  273. package/pages/c/_cluster/uiplugins/index.vue +42 -3
  274. package/pages/diagnostic.vue +5 -4
  275. package/pages/home.vue +105 -30
  276. package/pages/prefs.vue +23 -12
  277. package/pages/rio/mesh.vue +1 -1
  278. package/pkg/dynamic-importer.lib.js +8 -0
  279. package/pkg/vue.config.js +4 -0
  280. package/plugins/dashboard-store/__tests__/mutations.spec.js +406 -0
  281. package/plugins/dashboard-store/actions.js +32 -25
  282. package/plugins/dashboard-store/getters.js +50 -33
  283. package/plugins/dashboard-store/mutations.js +134 -28
  284. package/plugins/dashboard-store/resource-class.js +37 -42
  285. package/plugins/steve/actions.js +30 -0
  286. package/plugins/steve/caches/resourceCache.js +60 -0
  287. package/plugins/steve/getters.js +44 -1
  288. package/plugins/steve/mutations.js +97 -36
  289. package/plugins/steve/resourceWatcher.js +277 -0
  290. package/plugins/steve/schema.utils.js +25 -0
  291. package/plugins/steve/subscribe.js +288 -115
  292. package/plugins/steve/worker/index.js +17 -0
  293. package/plugins/steve/worker/web-worker.advanced.js +302 -0
  294. package/plugins/steve/{web-worker.steve-sub-worker.js → worker/web-worker.basic.js} +3 -44
  295. package/rancher-components/Card/Card.vue +3 -3
  296. package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +1 -0
  297. package/rancher-components/StringList/StringList.test.ts +45 -420
  298. package/rancher-components/StringList/StringList.vue +1 -10
  299. package/rancher-components/components/Banner/Banner.test.ts +44 -0
  300. package/rancher-components/components/Banner/Banner.vue +130 -61
  301. package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +13 -22
  302. package/rancher-components/components/Form/Checkbox/Checkbox.vue +8 -6
  303. package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +9 -9
  304. package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -1
  305. package/rancher-components/components/StringList/StringList.test.ts +7 -7
  306. package/rancher-components/components/StringList/StringList.vue +21 -15
  307. package/scripts/test-plugins-build.sh +8 -0
  308. package/static/loading-indicator.html +1 -1
  309. package/store/action-menu.js +4 -3
  310. package/store/index.js +54 -3
  311. package/store/plugins.js +0 -17
  312. package/store/pnap.js +128 -0
  313. package/store/prefs.js +4 -2
  314. package/store/type-map.js +81 -13
  315. package/types/pod-security-admission.ts +36 -0
  316. package/types/shell/index.d.ts +497 -396
  317. package/utils/__tests__/object.test.ts +17 -1
  318. package/utils/__tests__/pod-security-admission.test.ts +61 -0
  319. package/utils/async.ts +36 -0
  320. package/utils/color.js +45 -0
  321. package/utils/crypto/browserHashUtils.js +18 -0
  322. package/utils/dynamic-importer.js +8 -0
  323. package/utils/install-redirect.js +1 -1
  324. package/utils/object.js +24 -0
  325. package/utils/pod-security-admission.ts +39 -0
  326. package/utils/socket.js +61 -24
  327. package/utils/string.js +2 -0
  328. package/utils/svg-filter.js +301 -0
  329. package/utils/time.js +49 -0
  330. package/utils/validators/cidr.js +4 -0
  331. package/utils/validators/formRules/__tests__/index.test.ts +23 -3
  332. package/utils/validators/formRules/index.ts +14 -0
  333. package/config/product/harvester-manager.js +0 -162
  334. package/edit/harvesterhci.io.management.cluster.vue +0 -153
  335. package/list/harvesterhci.io.management.cluster.vue +0 -241
  336. package/machine-config/harvester.vue +0 -693
  337. package/models/harvesterhci.io.management.cluster.js +0 -228
  338. package/pages/c/_cluster/harvesterManager/index.vue +0 -24
  339. package/rancher-components/Card/Card.test.ts +0 -39
  340. package/rancher-components/Utils/DraggableZone/DraggableZone.vue +0 -181
  341. package/rancher-components/Utils/DraggableZone/index.ts +0 -1
  342. package/rancher-components/components/Utils/DraggableZone/index.ts +0 -1
@@ -304,6 +304,13 @@ export default {
304
304
  useQueryParamsForSimpleFiltering: {
305
305
  type: Boolean,
306
306
  default: false
307
+ },
308
+ /**
309
+ * Manaul force the update of live and delayed cells. Change this number to kick off the update
310
+ */
311
+ forceUpdateLiveAndDelayed: {
312
+ type: Number,
313
+ default: 0
307
314
  }
308
315
  },
309
316
 
@@ -389,6 +396,9 @@ export default {
389
396
  page(neu, old) {
390
397
  this.watcherUpdateLiveAndDelayed(neu, old);
391
398
  },
399
+ forceUpdateLiveAndDelayed(neu, old) {
400
+ this.watcherUpdateLiveAndDelayed(neu, old);
401
+ },
392
402
 
393
403
  // Ensure we update live and delayed columns on first load
394
404
  initalLoad: {
@@ -401,16 +411,14 @@ export default {
401
411
  immediate: true
402
412
  },
403
413
 
404
- isManualRefreshLoading: {
414
+ // this is the flag that indicates that manual refresh data has been loaded
415
+ // and we should update the deferred cols
416
+ manualRefreshLoadingFinished: {
405
417
  handler(neu, old) {
406
- this.currentPhase = neu ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;
407
-
408
- // setTimeout is needed so that this is pushed further back on the JS computing queue
409
- // because nextTick isn't enough to capture the DOM update for the manual refresh only scenario
410
- if (old && !neu) {
411
- this.manualRefreshTimer = setTimeout(() => {
412
- this.watcherUpdateLiveAndDelayed(neu, old);
413
- }, 1000);
418
+ // this is merely to update the manual refresh button status
419
+ this.currentPhase = !neu ? ASYNC_BUTTON_STATES.WAITING : ASYNC_BUTTON_STATES.ACTION;
420
+ if (neu && neu !== old) {
421
+ this.$nextTick(() => this.updateLiveAndDelayed());
414
422
  }
415
423
  },
416
424
  immediate: true
@@ -432,6 +440,10 @@ export default {
432
440
  return !!(!this.loading && !this._didinit && this.rows?.length);
433
441
  },
434
442
 
443
+ manualRefreshLoadingFinished() {
444
+ return !!(!this.loading && this._didinit && this.rows?.length && !this.isManualRefreshLoading);
445
+ },
446
+
435
447
  fullColspan() {
436
448
  let span = 0;
437
449
 
@@ -1530,7 +1542,6 @@ export default {
1530
1542
  .actions.role-multi-action {
1531
1543
  background-color: transparent;
1532
1544
  border: none;
1533
- font-size: 18px;
1534
1545
  &:hover, &:focus {
1535
1546
  background-color: var(--accent-btn);
1536
1547
  box-shadow: none;
@@ -504,7 +504,7 @@ export default {
504
504
  },
505
505
 
506
506
  applyTableAction(action, args, event) {
507
- const opts = { alt: event && isAlternate(event) };
507
+ const opts = { alt: event && isAlternate(event), event };
508
508
 
509
509
  // Go through the table selection and filter out those actions that can't run the chosen action
510
510
  const executableSelection = this.selectedRows.filter((row) => {
@@ -513,7 +513,7 @@ export default {
513
513
  return matchingResourceAction?.enabled;
514
514
  });
515
515
 
516
- _execute(executableSelection, action, args, opts);
516
+ _execute(executableSelection, action, args, opts, this);
517
517
 
518
518
  this.actionOfInterest = null;
519
519
  },
@@ -575,8 +575,20 @@ function _filter(map, disableAll = false) {
575
575
  return out;
576
576
  }
577
577
 
578
- function _execute(resources, action, args, opts = {}) {
578
+ function _execute(resources, action, args, opts = {}, ctx) {
579
579
  args = args || [];
580
+
581
+ // New pattern for extensions - always call invoke
582
+ if (action.invoke) {
583
+ const actionOpts = {
584
+ action,
585
+ event: opts.event,
586
+ isAlt: !!opts.alt,
587
+ };
588
+
589
+ return action.invoke.apply(ctx, [actionOpts, resources || [], args]);
590
+ }
591
+
580
592
  if ( resources.length > 1 && action.bulkAction && !opts.alt ) {
581
593
  const fn = resources[0][action.bulkAction];
582
594
 
@@ -99,7 +99,7 @@ export default {
99
99
  class="tab-header"
100
100
  >
101
101
  <h2>
102
- {{ label }}
102
+ {{ labelDisplay }}
103
103
  <i
104
104
  v-if="tooltip"
105
105
  v-tooltip="tooltip"
@@ -92,12 +92,12 @@ export default {
92
92
  sortedTabs(tabs) {
93
93
  const {
94
94
  defaultTab,
95
- useHash,
96
- $route: { hash }
95
+ useHash
97
96
  } = this;
98
97
  const activeTab = tabs.find(t => t.active);
99
98
 
100
- const windowHash = hash.slice(1);
99
+ const hash = useHash ? this.$route.hash : undefined;
100
+ const windowHash = useHash ? hash.slice(1) : undefined;
101
101
  const windowHashTabMatch = tabs.find(t => t.name === windowHash && !t.active);
102
102
  const firstTab = head(tabs) || null;
103
103
 
@@ -148,11 +148,7 @@ export default {
148
148
  },
149
149
 
150
150
  select(name/* , event */) {
151
- const {
152
- sortedTabs,
153
- $route: { hash: routeHash },
154
- $router: { currentRoute },
155
- } = this;
151
+ const { sortedTabs } = this;
156
152
 
157
153
  const selected = this.find(name);
158
154
  const hashName = `#${ name }`;
@@ -160,13 +156,22 @@ export default {
160
156
  if ( !selected || selected.disabled) {
161
157
  return;
162
158
  }
159
+ /**
160
+ * Exclude logic with URL anchor (hash) for projects without routing logic (vue-router)
161
+ */
162
+ if ( this.useHash ) {
163
+ const {
164
+ $route: { hash: routeHash },
165
+ $router: { currentRoute },
166
+ } = this;
163
167
 
164
- if (this.useHash && routeHash !== hashName) {
165
- const kurrentRoute = { ...currentRoute };
168
+ if (this.useHash && routeHash !== hashName) {
169
+ const kurrentRoute = { ...currentRoute };
166
170
 
167
- kurrentRoute.hash = hashName;
171
+ kurrentRoute.hash = hashName;
168
172
 
169
- this.$router.replace(kurrentRoute);
173
+ this.$router.replace(kurrentRoute);
174
+ }
170
175
  }
171
176
 
172
177
  for ( const tab of sortedTabs ) {
@@ -251,7 +256,7 @@ export default {
251
256
  <i
252
257
  v-if="hasIcon(tab)"
253
258
  v-tooltip="t('validation.tab')"
254
- class="conditions-alert-icon icon-error icon-lg"
259
+ class="conditions-alert-icon icon-error"
255
260
  />
256
261
  </a>
257
262
  </li>
@@ -274,7 +279,7 @@ export default {
274
279
  class="btn bg-transparent"
275
280
  @click="tabAddClicked"
276
281
  >
277
- <i class="icon icon-plus icon-lg" />
282
+ <i class="icon icon-plus" />
278
283
  </button>
279
284
  <button
280
285
  type="button"
@@ -282,7 +287,7 @@ export default {
282
287
  :disabled="!sortedTabs.length"
283
288
  @click="tabRemoveClicked"
284
289
  >
285
- <i class="icon icon-minus icon-lg" />
290
+ <i class="icon icon-minus" />
286
291
  </button>
287
292
  </li>
288
293
  </ul>
Binary file
@@ -0,0 +1,140 @@
1
+ import { mount, Wrapper } from '@vue/test-utils';
2
+ import AsyncButton, { ASYNC_BUTTON_STATES } from '@shell/components/AsyncButton.vue';
3
+
4
+ describe('component: AsyncButton', () => {
5
+ it('should render appropriately with default config', () => {
6
+ const mockExists = jest.fn().mockReturnValue(true);
7
+ const mockT = jest.fn().mockReturnValue('some-string');
8
+
9
+ const wrapper: Wrapper<InstanceType<typeof AsyncButton> & { [key: string]: any }> = mount(AsyncButton, {
10
+ mocks: {
11
+ $store: {
12
+ getters: {
13
+ 'i18n/exists': mockExists,
14
+ 'i18n/t': mockT
15
+ }
16
+ }
17
+ }
18
+ });
19
+
20
+ const button = wrapper.find('button');
21
+ const icon = wrapper.find('i');
22
+ const span = wrapper.find('span');
23
+
24
+ expect(wrapper.props().currentPhase).toBe(ASYNC_BUTTON_STATES.ACTION);
25
+
26
+ expect(button.exists()).toBe(true);
27
+ expect(button.classes()).toContain('btn');
28
+ expect(button.classes()).toContain('role-primary');
29
+ expect(button.props().name).toBeNull();
30
+ expect(button.props().type).toBe('button');
31
+ expect(button.props().disabled).toBe(false);
32
+ expect(button.props().tabIndex).toBeNull();
33
+ // we are mocking the getters, so it's to expect to find an icon
34
+ expect(icon.exists()).toBe(true);
35
+ expect(icon.classes()).toContain('icon');
36
+ expect(icon.classes()).toContain('icon-lg');
37
+ expect(icon.classes()).toContain('icon-some-string');
38
+ // we are mocking the getters, so it's to expect to find a label
39
+ expect(span.exists()).toBe(true);
40
+ expect(span.text()).toBe('some-string');
41
+ });
42
+
43
+ it('click on async button should emit click with a proper state of waiting, disabled and spinning ::: CB true', () => {
44
+ jest.useFakeTimers();
45
+
46
+ const wrapper: Wrapper<InstanceType<typeof AsyncButton> & { [key: string]: any }> = mount(AsyncButton, {
47
+ mocks: {
48
+ $store: {
49
+ getters: {
50
+ 'i18n/exists': jest.fn(),
51
+ 'i18n/t': jest.fn()
52
+ }
53
+ }
54
+ }
55
+ });
56
+
57
+ const spyDone = jest.spyOn(wrapper.vm, 'done');
58
+
59
+ wrapper.find('button').trigger('click');
60
+
61
+ expect(wrapper.emitted('click')).toHaveLength(1);
62
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.WAITING);
63
+ expect(wrapper.vm.isSpinning).toBe(true);
64
+ expect(wrapper.vm.isDisabled).toBe(true);
65
+ // testing cb function has been emitted
66
+ expect(typeof wrapper.emitted('click')![0][0]).toBe('function');
67
+
68
+ // trigger the cb function so that we test state changes on AsyncButton
69
+ wrapper.emitted('click')![0][0](true);
70
+
71
+ expect(spyDone).toHaveBeenCalledWith(true);
72
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.SUCCESS);
73
+
74
+ // wait for button delay to be completed
75
+ jest.runAllTimers();
76
+
77
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ACTION);
78
+ });
79
+
80
+ it('click on async button should emit click and update state properly ::: CB false', () => {
81
+ jest.useFakeTimers();
82
+
83
+ const wrapper: Wrapper<InstanceType<typeof AsyncButton> & { [key: string]: any }> = mount(AsyncButton, {
84
+ mocks: {
85
+ $store: {
86
+ getters: {
87
+ 'i18n/exists': jest.fn(),
88
+ 'i18n/t': jest.fn()
89
+ }
90
+ }
91
+ }
92
+ });
93
+
94
+ const spyDone = jest.spyOn(wrapper.vm, 'done');
95
+
96
+ wrapper.find('button').trigger('click');
97
+
98
+ expect(wrapper.emitted('click')).toHaveLength(1);
99
+ // testing cb function has been emitted
100
+ expect(typeof wrapper.emitted('click')![0][0]).toBe('function');
101
+
102
+ // trigger the cb function so that we test state changes on AsyncButton
103
+ wrapper.emitted('click')![0][0](false);
104
+
105
+ expect(spyDone).toHaveBeenCalledWith(false);
106
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ERROR);
107
+
108
+ // wait for button delay to be completed
109
+ jest.runAllTimers();
110
+
111
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ACTION);
112
+ });
113
+
114
+ it('click on async button should emit click and update state properly ::: CB "cancelled"', () => {
115
+ const wrapper: Wrapper<InstanceType<typeof AsyncButton> & { [key: string]: any }> = mount(AsyncButton, {
116
+ mocks: {
117
+ $store: {
118
+ getters: {
119
+ 'i18n/exists': jest.fn(),
120
+ 'i18n/t': jest.fn()
121
+ }
122
+ }
123
+ }
124
+ });
125
+
126
+ const spyDone = jest.spyOn(wrapper.vm, 'done');
127
+
128
+ wrapper.find('button').trigger('click');
129
+
130
+ expect(wrapper.emitted('click')).toHaveLength(1);
131
+ // testing cb function has been emitted
132
+ expect(typeof wrapper.emitted('click')![0][0]).toBe('function');
133
+
134
+ // trigger the cb function so that we test state changes on AsyncButton
135
+ wrapper.emitted('click')![0][0]('cancelled');
136
+
137
+ expect(spyDone).toHaveBeenCalledWith('cancelled');
138
+ expect(wrapper.vm.phase).toBe(ASYNC_BUTTON_STATES.ACTION);
139
+ });
140
+ });
@@ -0,0 +1,33 @@
1
+ import { shallowMount, RouterLinkStub } from '@vue/test-utils';
2
+ import BackLink from '@shell/components/BackLink.vue';
3
+
4
+ describe('component: BackLink', () => {
5
+ it('should render component with the correct data applied', () => {
6
+ const linkRoute = {
7
+ name: 'some-route-name',
8
+ params: { param1: 'paramval1' },
9
+ query: {
10
+ query1: 'queryval1',
11
+ query2: 'queryval2'
12
+ }
13
+ };
14
+
15
+ const wrapper = shallowMount(BackLink, {
16
+ propsData: { link: linkRoute },
17
+ stubs: { NuxtLink: RouterLinkStub }
18
+ });
19
+
20
+ const link = wrapper.findComponent(RouterLinkStub);
21
+ const icon = wrapper.find('i');
22
+
23
+ expect(link.exists()).toBe(true);
24
+ // assertion regarding the text will have to be pointed to whatever t() returns on the test
25
+ expect(link.text()).toBe('%generic.back%');
26
+ expect(link.classes()).toContain('back-link');
27
+ expect(link.props().to).toBe(linkRoute);
28
+
29
+ expect(icon.exists()).toBe(true);
30
+ expect(icon.classes()).toContain('icon');
31
+ expect(icon.classes()).toContain('icon-chevron-left');
32
+ });
33
+ });
@@ -0,0 +1,124 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import ButtonGroup from '@shell/components/ButtonGroup.vue';
3
+
4
+ describe('component: ButtonGroup', () => {
5
+ it('should render component with the default props correctly', () => {
6
+ const options = [
7
+ {
8
+ label: 'label1',
9
+ value: 'val1'
10
+ },
11
+ {
12
+ label: 'label2',
13
+ value: 'val2'
14
+ },
15
+ {
16
+ label: 'label3',
17
+ value: 'val3'
18
+ },
19
+ ];
20
+
21
+ const wrapper = shallowMount(ButtonGroup, {
22
+ propsData: {
23
+ options,
24
+ value: 'val1'
25
+ },
26
+ directives: { 'trim-whitespace': jest.fn() },
27
+ });
28
+
29
+ const firstBtn = wrapper.find('[data-testid="button-group-child-0"]');
30
+ const firstBtnLabel = wrapper.find('[data-testid="button-group-child-0"] span');
31
+ const secondBtn = wrapper.find('[data-testid="button-group-child-1"]');
32
+ const secondBtnLabel = wrapper.find('[data-testid="button-group-child-1"] span');
33
+ const thirdBtn = wrapper.find('[data-testid="button-group-child-2"]');
34
+
35
+ expect(wrapper.findAll('button')).toHaveLength(3);
36
+ expect(firstBtn.exists()).toBe(true);
37
+ expect(firstBtn.attributes().type).toBe('button');
38
+ expect(firstBtn.classes()).toContain('btn');
39
+ expect(firstBtn.classes()).toContain('bg-primary');
40
+ expect(secondBtn.classes()).toContain('bg-disabled');
41
+ expect(thirdBtn.classes()).toContain('bg-disabled');
42
+
43
+ expect(firstBtnLabel.text()).toBe('label1');
44
+ expect(secondBtnLabel.text()).toBe('label2');
45
+ });
46
+
47
+ it('should render component with icon correctly', () => {
48
+ const options = [
49
+ {
50
+ label: 'label1',
51
+ value: 'val1',
52
+ icon: 'some-icon'
53
+ },
54
+ {
55
+ label: 'label2',
56
+ value: 'val2'
57
+ },
58
+ {
59
+ label: 'label3',
60
+ value: 'val3'
61
+ },
62
+ ];
63
+
64
+ const wrapper = shallowMount(ButtonGroup, {
65
+ propsData: {
66
+ options,
67
+ activeClass: 'bg-another-active-class',
68
+ inactiveClass: 'bg-some-inactive-class',
69
+ iconSize: 'xxxxl',
70
+ value: 'val1'
71
+ },
72
+ directives: { 'trim-whitespace': jest.fn() },
73
+ });
74
+
75
+ const firstBtn = wrapper.find('[data-testid="button-group-child-0"]');
76
+ const firstBtnIcon = wrapper.find('[data-testid="button-group-child-0"] i');
77
+ const firstBtnLabel = wrapper.find('[data-testid="button-group-child-0"] span');
78
+ const secondBtn = wrapper.find('[data-testid="button-group-child-1"]');
79
+
80
+ expect(wrapper.findAll('button')).toHaveLength(3);
81
+
82
+ expect(firstBtn.classes()).toContain('bg-another-active-class');
83
+ expect(secondBtn.classes()).toContain('bg-some-inactive-class');
84
+
85
+ expect(firstBtnLabel.exists()).toBe(true);
86
+ expect(firstBtnIcon.exists()).toBe(true);
87
+
88
+ expect(firstBtnIcon.classes()).toContain('icon');
89
+ expect(firstBtnIcon.classes()).toContain('some-icon');
90
+ expect(firstBtnIcon.classes()).toContain('icon-xxxxl');
91
+ });
92
+
93
+ it('clicking on button should emit event "input"', () => {
94
+ const options = [
95
+ {
96
+ label: 'label1',
97
+ value: 'val1'
98
+ },
99
+ {
100
+ label: 'label2',
101
+ value: 'val2'
102
+ },
103
+ {
104
+ label: 'label3',
105
+ value: 'val3'
106
+ },
107
+ ];
108
+
109
+ const wrapper = shallowMount(ButtonGroup, {
110
+ propsData: {
111
+ options,
112
+ value: 'val1'
113
+ },
114
+ directives: { 'trim-whitespace': jest.fn() },
115
+ });
116
+
117
+ const firstBtn = wrapper.find('[data-testid="button-group-child-0"]');
118
+
119
+ firstBtn.trigger('click');
120
+
121
+ expect(wrapper.emitted('input')).toHaveLength(1);
122
+ expect(wrapper.emitted('input')![0][0]).toBe('val1');
123
+ });
124
+ });
@@ -0,0 +1,32 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import ClusterBadge from '@shell/components/ClusterBadge.vue';
3
+
4
+ describe('component: ClusterBadge', () => {
5
+ it('should render component with the correct data applied', () => {
6
+ const clusterProp = {
7
+ badge: {
8
+ color: 'red',
9
+ textColor: 'blue',
10
+ text: 'some-text',
11
+ }
12
+ };
13
+
14
+ const wrapper = shallowMount(ClusterBadge, { propsData: { cluster: clusterProp } });
15
+
16
+ const elem = wrapper.find('div');
17
+
18
+ expect(elem.classes()).toContain('cluster-badge');
19
+ expect(elem.attributes().style).toBe('background-color: red; color: blue;');
20
+ expect(elem.text()).toBe('some-text');
21
+ });
22
+
23
+ it('should NOT render component if there is no badge property in object', () => {
24
+ const clusterProp = {};
25
+
26
+ const wrapper = shallowMount(ClusterBadge, { propsData: { cluster: clusterProp } });
27
+
28
+ const elem = wrapper.find('div');
29
+
30
+ expect(elem.exists()).toBe(false);
31
+ });
32
+ });
@@ -0,0 +1,64 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import CollapsibleCard from '@shell/components/CollapsibleCard.vue';
3
+
4
+ describe('component: CollapsibleCard', () => {
5
+ it('should render component with the correct data applied', () => {
6
+ const wrapper = shallowMount(CollapsibleCard, {
7
+ propsData: { title: 'some-card-title' },
8
+ slots: {
9
+ 'header-right': '<p>some stuff for header</p>',
10
+ content: '<p>some content</p>'
11
+ }
12
+ });
13
+
14
+ const cardWrapper = wrapper.find('.collapsible-card');
15
+ const cardHeader = wrapper.find('.collapsible-card-header');
16
+ const cardBody = wrapper.find('.collapsible-card-body');
17
+
18
+ expect(cardWrapper.exists()).toBe(true);
19
+
20
+ expect(cardHeader.exists()).toBe(true);
21
+ expect(cardHeader.find('h2 span').text()).toBe('some-card-title');
22
+ expect(cardHeader.find('p').exists()).toBe(true);
23
+ expect(cardHeader.find('p').text()).toBe('some stuff for header');
24
+ expect(cardHeader.find('i').exists()).toBe(true);
25
+ expect(cardHeader.find('i').classes()).toContain('icon-chevron-up');
26
+
27
+ expect(cardBody.exists()).toBe(true);
28
+ expect(cardBody.find('p').exists()).toBe(true);
29
+ expect(cardBody.find('p').text()).toBe('some content');
30
+ });
31
+
32
+ it('clicking on card header should emit event', () => {
33
+ const wrapper = shallowMount(CollapsibleCard, {
34
+ propsData: { title: 'some-card-title' },
35
+ slots: {
36
+ 'header-right': '<p>some stuff for header</p>',
37
+ content: '<p>some content</p>'
38
+ }
39
+ });
40
+
41
+ const cardHeader = wrapper.find('.collapsible-card-header');
42
+
43
+ cardHeader.trigger('click');
44
+
45
+ expect(wrapper.emitted('toggleCollapse')).toHaveLength(1);
46
+ expect(wrapper.emitted('toggleCollapse')![0][0]).toBe(true);
47
+ });
48
+
49
+ it('clicking on card title should emit event', () => {
50
+ const wrapper = shallowMount(CollapsibleCard, {
51
+ propsData: { title: 'some-card-title', isTitleClickable: true },
52
+ slots: {
53
+ 'header-right': '<p>some stuff for header</p>',
54
+ content: '<p>some content</p>'
55
+ }
56
+ });
57
+
58
+ const cardHeaderTitle = wrapper.find('.collapsible-card-header h2 span');
59
+
60
+ cardHeaderTitle.trigger('click');
61
+
62
+ expect(wrapper.emitted('titleClick')).toHaveLength(1);
63
+ });
64
+ });
@@ -0,0 +1,88 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import ConsumptionGauge from '@shell/components/ConsumptionGauge.vue';
3
+ import PercentageBar from '@shell/components/PercentageBar.vue';
4
+
5
+ describe('component: ConsumptionGauge', () => {
6
+ it('should render component with the correct data applied', () => {
7
+ const colorStops = {
8
+ 0: '--success', 30: '--warning', 70: '--error'
9
+ };
10
+
11
+ const wrapper = mount(ConsumptionGauge, {
12
+ propsData: {
13
+ resourceName: 'some-resource-name',
14
+ capacity: 1000,
15
+ used: 200,
16
+ units: 'cores',
17
+ colorStops
18
+ },
19
+ });
20
+
21
+ const mainWrapper = wrapper.find('.consumption-gauge');
22
+ const title = wrapper.find('.consumption-gauge h3');
23
+ const usedSpan = wrapper.find('.consumption-gauge .numbers span:nth-child(1)');
24
+ const percentageSpan = wrapper.find('.consumption-gauge .percentage');
25
+ const percentageBar = wrapper.findComponent(PercentageBar);
26
+
27
+ expect(mainWrapper.exists()).toBe(true);
28
+ expect(title.exists()).toBe(true);
29
+ expect(title.text()).toBe('some-resource-name');
30
+ expect(usedSpan.exists()).toBe(true);
31
+ // check translation key as for translation are not applied
32
+ expect(usedSpan.text()).toBe('%node.detail.glance.consumptionGauge.used%');
33
+
34
+ expect(percentageSpan.exists()).toBe(true);
35
+ expect(percentageSpan.text()).toContain('20%');
36
+
37
+ // checking PercentageBar component render
38
+ expect(percentageBar.exists()).toBe(true);
39
+ expect(percentageBar.props().value).toBe(20);
40
+ expect(percentageBar.props().colorStops).toBe(colorStops);
41
+ });
42
+
43
+ it('usedAsResourceName should render secondary title instead of main h3 title', () => {
44
+ const colorStops = {
45
+ 0: '--success', 30: '--warning', 70: '--error'
46
+ };
47
+
48
+ const wrapper = mount(ConsumptionGauge, {
49
+ propsData: {
50
+ resourceName: 'some-resource-name',
51
+ capacity: 1000,
52
+ used: 200,
53
+ units: 'cores',
54
+ colorStops,
55
+ usedAsResourceName: true
56
+ }
57
+ });
58
+
59
+ const mainTitle = wrapper.find('.consumption-gauge h3');
60
+ const slotTitle = wrapper.find('.consumption-gauge h4');
61
+
62
+ expect(mainTitle.exists()).toBe(false);
63
+ expect(slotTitle.exists()).toBe(true);
64
+ expect(slotTitle.text()).toBe('some-resource-name');
65
+ });
66
+
67
+ it('passing slot TITLE should render correctly', () => {
68
+ const colorStops = {
69
+ 0: '--success', 30: '--warning', 70: '--error'
70
+ };
71
+
72
+ const wrapper = mount(ConsumptionGauge, {
73
+ propsData: {
74
+ resourceName: 'some-resource-name',
75
+ capacity: 1000,
76
+ used: 200,
77
+ units: 'cores',
78
+ colorStops
79
+ },
80
+ slots: { title: '<p class="slot-class">another title</p>' }
81
+ });
82
+
83
+ const slotElem = wrapper.find('.consumption-gauge .slot-class');
84
+
85
+ expect(slotElem.exists()).toBe(true);
86
+ expect(slotElem.text()).toBe('another title');
87
+ });
88
+ });