@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
@@ -35,11 +35,7 @@ export default {
35
35
  let url = schema.links.collection;
36
36
 
37
37
  if (schema?.attributes?.namespaced && namespace) {
38
- const parts = url.split('/');
39
-
40
- parts.splice(parts.length - 2, 0, `api`);
41
- parts.splice(parts.length - 1, 0, `namespaces/${ namespace }`);
42
- url = parts.join('/');
38
+ url = `${ url }/${ namespace }`;
43
39
  } else if (onlyNamespaced) {
44
40
  // Type isn't namespaced and we've been requested to only fetch namespaced types
45
41
  return;
@@ -60,30 +56,12 @@ export default {
60
56
  const status = hash[type].status;
61
57
  // if it's namespaced, we get the data on 'items' prop, for non-namespaced it's 'data' prop...
62
58
  const requestData = hash[type].value.items || hash[type].value.data || hash[type].value;
63
- const schema = this.$store.getters['cluster/schemaFor'](type);
64
59
 
65
60
  if (status === 'fulfilled' && resourceData.data[type] && resourceData.data[type].applyTo?.length) {
66
61
  for (let y = 0; y < resourceData.data[type].applyTo.length; y++) {
67
62
  const apply = resourceData.data[type].applyTo[y];
68
63
  let resources = requestData;
69
64
 
70
- if (schema?.attributes?.namespaced) {
71
- // The resources returned when requesting namespaced types do not contain id, type and links properties.
72
- // This isn't perfect, or universally applicable, but will work for the current set of use cases
73
- // To make this more generic
74
- // - id param = this.$store.getters['cluster/keyFieldForType'](type)
75
- // - id value = new dashboard-store getter, overwritten by steve store getter
76
- requestData.forEach((item) => {
77
- // if there's already a prop type, don't overwrite it without storing it first...
78
- // only do this operation once in multiple apply's because the requestData is the same!
79
- if (item.type && !item._type) {
80
- item._type = item.type;
81
- }
82
- item.type = type;
83
- item.id = `${ item.metadata.namespace }/${ item.metadata.name }`;
84
- });
85
- }
86
-
87
65
  if (apply.classify) {
88
66
  resources = await this.$store.dispatch('cluster/createMany', requestData);
89
67
  }
@@ -0,0 +1,7 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class ControllerRevision extends SteveModel {
4
+ get revisionNumber() {
5
+ return this.revision;
6
+ }
7
+ }
@@ -1,4 +1,22 @@
1
1
  import Workload from './workload';
2
2
 
3
3
  export default class DaemonSet extends Workload {
4
+ async rollBack(cluster, daemonSet, revision) {
5
+ const body = [
6
+ {
7
+ op: 'replace',
8
+ path: '/spec/template',
9
+ value: {
10
+ metadata: revision.data.spec.template.metadata,
11
+ spec: revision.data.spec.template.spec
12
+ }
13
+ }, {
14
+ op: 'replace',
15
+ path: '/metadata/generation',
16
+ value: revision.revision,
17
+ }
18
+ ];
19
+
20
+ await this.rollBackWorkload(cluster, daemonSet, 'daemonsets', body);
21
+ }
4
22
  }
@@ -1,6 +1,15 @@
1
1
  import { WORKLOAD_TYPES } from '@shell/config/types';
2
2
  import Workload from './workload';
3
3
 
4
+ const IGNORED_ANNOTATIONS = [
5
+ 'kubectl.kubernetes.io/last-applied-configuration',
6
+ 'deployment.kubernetes.io/revision',
7
+ 'deployment.kubernetes.io/revision-history',
8
+ 'deployment.kubernetes.io/desired-replicas',
9
+ 'deployment.kubernetes.io/max-replicas',
10
+ 'deprecated.deployment.rollback.to',
11
+ ];
12
+
4
13
  export default class Deployment extends Workload {
5
14
  get replicaSetId() {
6
15
  const set = this.metadata?.relationships?.find((relationship) => {
@@ -10,4 +19,39 @@ export default class Deployment extends Workload {
10
19
 
11
20
  return set?.toId?.replace(`${ this.namespace }/`, '');
12
21
  }
22
+
23
+ async rollBack(cluster, deployment, revision) {
24
+ const body = [
25
+ {
26
+ op: 'replace',
27
+ path: '/spec/template',
28
+ value: {
29
+ metadata: {
30
+ creationTimestamp: null,
31
+ labels: Object.keys(revision.spec.template.metadata?.labels || {}).reduce((prev, key) => {
32
+ if (key !== 'pod-template-hash') {
33
+ prev[key] = revision.spec.template.metadata.labels[key];
34
+ }
35
+
36
+ return prev;
37
+ }, {}),
38
+ annotations: Object.keys(revision.spec.template.metadata?.annotations || {}).reduce((prev, key) => {
39
+ if (!IGNORED_ANNOTATIONS.includes(key)) {
40
+ prev[key] = revision.spec.template.metadata.annotations[key];
41
+ }
42
+
43
+ return prev;
44
+ }, {}),
45
+ },
46
+ spec: revision.spec.template.spec
47
+ }
48
+ }, {
49
+ op: 'replace',
50
+ path: '/metadata/annotations',
51
+ value: { 'deployment.kubernetes.io/revision': revision.metadata.annotations['deployment.kubernetes.io/revision'] }
52
+ }
53
+ ];
54
+
55
+ await this.rollBackWorkload(cluster, deployment, 'deployments', body);
56
+ }
13
57
  }
@@ -1,4 +1,11 @@
1
1
  import Workload from './workload';
2
2
 
3
3
  export default class ReplicaSet extends Workload {
4
+ get revisionNumber() {
5
+ if (!this.ownedByWorkload) {
6
+ return undefined;
7
+ }
8
+
9
+ return this.metadata.annotations['deployment.kubernetes.io/revision'];
10
+ }
4
11
  }
@@ -1,4 +1,22 @@
1
1
  import Workload from './workload';
2
2
 
3
3
  export default class StatefulSet extends Workload {
4
+ async rollBack(cluster, statefulSet, revision) {
5
+ const body = [
6
+ {
7
+ op: 'replace',
8
+ path: '/spec/template',
9
+ value: {
10
+ metadata: revision.data.spec.template.metadata,
11
+ spec: revision.data.spec.template.spec
12
+ }
13
+ }, {
14
+ op: 'replace',
15
+ path: '/metadata/generation',
16
+ value: revision.revision,
17
+ }
18
+ ];
19
+
20
+ await this.rollBackWorkload(cluster, statefulSet, 'statefulsets', body);
21
+ }
4
22
  }
@@ -6,21 +6,14 @@ export default class Job extends Workload {
6
6
  const schema = this.$getters['schemaFor'](this.type);
7
7
  const rowValueGetter = this.$rootGetters['type-map/rowValueGetter'];
8
8
 
9
- if (schema && rowValueGetter) {
10
- const value = rowValueGetter(schema, 'Duration')(this);
11
- const { completionTime, startTime } = this.status;
12
- let seconds = 0;
9
+ const { completionTime, startTime } = this.status;
13
10
 
14
- if (value && startTime) {
15
- seconds = getSecondsDiff(startTime, completionTime || new Date());
16
- }
11
+ const staticValue = schema && rowValueGetter ? rowValueGetter(schema, 'Duration')(this) : null;
12
+ const seconds = staticValue && startTime ? getSecondsDiff(startTime, completionTime || new Date()) : 0;
17
13
 
18
- return {
19
- value,
20
- seconds,
21
- };
22
- }
23
-
24
- return {};
14
+ return {
15
+ value: completionTime ? { staticValue } : { startTime },
16
+ seconds,
17
+ };
25
18
  }
26
19
  }
@@ -201,7 +201,9 @@ export default class ClusterNode extends SteveModel {
201
201
  }
202
202
 
203
203
  get podConsumed() {
204
- return this.pods.length;
204
+ const runningPods = this.pods.filter(pod => pod.state === 'running');
205
+
206
+ return runningPods.length || 0;
205
207
  }
206
208
 
207
209
  get podRequests() {
@@ -310,7 +312,13 @@ export default class ClusterNode extends SteveModel {
310
312
  }
311
313
 
312
314
  drain(resources) {
313
- this.$dispatch('promptModal', { component: 'DrainNode', resources: [resources || [this], this.normanNodeId] });
315
+ this.$dispatch('promptModal', {
316
+ component: 'DrainNode',
317
+ componentProps: {
318
+ kubeNodes: resources || [this],
319
+ normanNodeId: this.normanNodeId
320
+ }
321
+ });
314
322
  }
315
323
 
316
324
  async stopDrain(resources) {
@@ -114,8 +114,8 @@ export default class CapiMachine extends SteveModel {
114
114
 
115
115
  toggleForceRemoveModal(resources = this) {
116
116
  this.$dispatch('promptModal', {
117
- resources,
118
- component: 'ForceMachineRemoveDialog'
117
+ componentProps: { machine: resources },
118
+ component: 'ForceMachineRemoveDialog'
119
119
  });
120
120
  }
121
121
 
@@ -242,7 +242,29 @@ export default class CapiMachine extends SteveModel {
242
242
  return this.status?.phase === 'Running';
243
243
  }
244
244
 
245
- get ipaddress() {
246
- return this.status?.addresses?.find(({ type }) => type === ADDRESSES.INTERNAL_IP)?.address || '-';
245
+ get internalIp() {
246
+ // This shows in the IP address column for RKE2 nodes in the
247
+ // list of nodes in the cluster detail page of Cluster Management.
248
+ const internal = this.status?.addresses?.find(({ type }) => {
249
+ return type === ADDRESSES.INTERNAL_IP;
250
+ })?.address;
251
+
252
+ if (internal) {
253
+ return internal;
254
+ }
255
+
256
+ return this.t('generic.none');
257
+ }
258
+
259
+ get externalIp() {
260
+ const external = this.status?.addresses?.find(({ type }) => {
261
+ return type === ADDRESSES.EXTERNAL_IP;
262
+ })?.address;
263
+
264
+ if (external) {
265
+ return external;
266
+ }
267
+
268
+ return this.t('generic.none');
247
269
  }
248
270
  }
@@ -6,6 +6,7 @@ import { exceptionToErrorsArray } from '@shell/utils/error';
6
6
  import { handleConflict } from '@shell/plugins/dashboard-store/normalize';
7
7
  import { MACHINE_ROLES } from '@shell/config/labels-annotations';
8
8
  import { notOnlyOfRole } from '@shell/models/cluster.x-k8s.io.machine';
9
+ import { KIND } from '../config/elemental-types';
9
10
 
10
11
  export default class CapiMachineDeployment extends SteveModel {
11
12
  get cluster() {
@@ -34,6 +35,10 @@ export default class CapiMachineDeployment extends SteveModel {
34
35
  return `${ this.$rootGetters['i18n/t']('resourceTable.groupLabel.machinePool', { name: escapeHtml(this.nameDisplay) }) }`;
35
36
  }
36
37
 
38
+ get infrastructureRefKind() {
39
+ return this.spec?.template?.spec?.infrastructureRef?.kind;
40
+ }
41
+
37
42
  get templateType() {
38
43
  return this.spec.template.spec.infrastructureRef.kind ? `rke-machine.cattle.io.${ this.spec.template.spec.infrastructureRef.kind.toLowerCase() }` : null;
39
44
  }
@@ -94,7 +99,7 @@ export default class CapiMachineDeployment extends SteveModel {
94
99
 
95
100
  // use this pool's definition in the cluster's rkeConfig to scale, not this.spec.replicas
96
101
  get inClusterSpec() {
97
- const machineConfigName = this.template.metadata.annotations['rke.cattle.io/cloned-from-name'];
102
+ const machineConfigName = this.template?.metadata?.annotations['rke.cattle.io/cloned-from-name'];
98
103
  const machinePools = this.cluster.spec.rkeConfig.machinePools;
99
104
 
100
105
  return machinePools.find(pool => pool.machineConfigRef.name === machineConfigName);
@@ -147,7 +152,7 @@ export default class CapiMachineDeployment extends SteveModel {
147
152
 
148
153
  // prevent scaling pool to 0 if it would scale down the only etcd or control plane node
149
154
  canScaleDownPool() {
150
- if (!this.canUpdate || this.inClusterSpec?.quantity === 0) {
155
+ if (!this.canUpdate || this.inClusterSpec?.quantity === 0 || this.infrastructureRefKind === KIND.MACHINE_INV_SELECTOR_TEMPLATES) {
151
156
  return false;
152
157
  }
153
158
 
@@ -159,7 +164,12 @@ export default class CapiMachineDeployment extends SteveModel {
159
164
  return notOnlyOfRole(this, this.cluster.machines);
160
165
  }
161
166
 
167
+ // prevent scaling up pool for Elemental machines
162
168
  canScaleUpPool() {
169
+ if (this.infrastructureRefKind === KIND.MACHINE_INV_SELECTOR_TEMPLATES) {
170
+ return false;
171
+ }
172
+
163
173
  return true;
164
174
  }
165
175
 
package/models/event.js CHANGED
@@ -24,4 +24,11 @@ export default class K8sEvent extends SteveModel {
24
24
  get eventType() {
25
25
  return this._type;
26
26
  }
27
+
28
+ get lastSeen() {
29
+ const schema = this.$getters['schemaFor'](this.type);
30
+ const rowValueGetter = this.$rootGetters['type-map/rowValueGetter'];
31
+
32
+ return schema && rowValueGetter ? rowValueGetter(schema, 'Last Seen')(this) : null;
33
+ }
27
34
  }
@@ -20,6 +20,10 @@ export function matchRuleIsPopulated(rule) {
20
20
  return true;
21
21
  }
22
22
 
23
+ if ( rule.namespaces?.length ) {
24
+ return true;
25
+ }
26
+
23
27
  return false;
24
28
  }
25
29
 
@@ -9,7 +9,7 @@ import jsyaml from 'js-yaml';
9
9
  import { eachLimit } from '@shell/utils/promise';
10
10
  import { addParams } from '@shell/utils/url';
11
11
  import { isEmpty } from '@shell/utils/object';
12
- import { HARVESTER_NAME as HARVESTER } from '@shell/config/product/harvester-manager';
12
+ import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
13
13
  import { isHarvesterCluster } from '@shell/utils/cluster';
14
14
  import HybridModel from '@shell/plugins/steve/hybrid-class';
15
15
  import { LINUX, WINDOWS } from '@shell/store/catalog';
@@ -2,7 +2,7 @@ import { CREATOR_ID } from '@shell/config/labels-annotations';
2
2
  import { _CREATE } from '@shell/config/query-params';
3
3
  import { MANAGEMENT, NORMAN } from '@shell/config/types';
4
4
  import HybridModel from '@shell/plugins/steve/hybrid-class';
5
- import { HARVESTER_NAME } from '@shell/config/product/harvester-manager';
5
+ import { HARVESTER_NAME } from '@shell/config/features';
6
6
 
7
7
  export default class CRTB extends HybridModel {
8
8
  detailPageHeaderActionOverride(realMode) {
@@ -3,7 +3,7 @@ import { SCHEMA, NORMAN } from '@shell/config/types';
3
3
  import { CATTLE_API_GROUP, SUBTYPE_MAPPING, CREATE_VERBS } from '@shell/models/management.cattle.io.roletemplate';
4
4
  import { uniq } from '@shell/utils/array';
5
5
  import { get } from '@shell/utils/object';
6
- import SteveModel from '@shell/plugins/steve/steve-class';
6
+ import SteveDescriptionModel from '@shell/plugins/steve/steve-description-class';
7
7
  import Role from './rbac.authorization.k8s.io.role';
8
8
  import { AS, MODE, _CLONE, _UNFLAG } from '@shell/config/query-params';
9
9
 
@@ -14,7 +14,7 @@ const SPECIAL = [BASE, ADMIN, USER];
14
14
 
15
15
  const GLOBAL = SUBTYPE_MAPPING.GLOBAL.key;
16
16
 
17
- export default class GlobalRole extends SteveModel {
17
+ export default class GlobalRole extends SteveDescriptionModel {
18
18
  get customValidationRules() {
19
19
  return Role.customValidationRules();
20
20
  }
@@ -6,6 +6,7 @@ import { NAME as EXPLORER } from '@shell/config/product/explorer';
6
6
  import { listNodeRoles } from '@shell/models/cluster/node';
7
7
  import { insertAt } from '@shell/utils/array';
8
8
  import { downloadUrl } from '@shell/utils/download';
9
+ import findLast from 'lodash/findLast';
9
10
  import HybridModel from '@shell/plugins/steve/hybrid-class';
10
11
 
11
12
  export default class MgmtNode extends HybridModel {
@@ -124,8 +125,42 @@ export default class MgmtNode extends HybridModel {
124
125
  return false;
125
126
  }
126
127
 
127
- get ipaddress() {
128
- return this.status.internalNodeStatus?.addresses?.find(({ type }) => type === ADDRESSES.INTERNAL_IP)?.address || '-';
128
+ get internalIp() {
129
+ // This shows in the IP address column for RKE1 nodes in the
130
+ // list of nodes in the cluster detail page of Cluster Management.
131
+
132
+ const internal = this.status?.addresses?.find(({ type }) => {
133
+ return type === ADDRESSES.INTERNAL_IP;
134
+ });
135
+
136
+ if (internal) {
137
+ return internal.address;
138
+ }
139
+
140
+ // For RKE1 clusters in EC2, node addresses are
141
+ // under status.rkeNode.address and status.rkeNode.internalAddress
142
+ if (!internal && this.status.rkeNode) {
143
+ return this.status.rkeNode.internalAddress;
144
+ }
145
+
146
+ return this.t('generic.none');
147
+ }
148
+
149
+ get externalIp() {
150
+ const addresses = this.status?.addresses || [];
151
+ const statusAddress = findLast(addresses, address => address.type === 'ExternalIP')?.address;
152
+
153
+ if (statusAddress) {
154
+ return statusAddress;
155
+ }
156
+
157
+ // For RKE1 clusters in EC2, node addresses are
158
+ // under status.rkeNode.address and status.rkeNode.internalAddress
159
+ if (!statusAddress && this.status.rkeNode) {
160
+ return this.status.rkeNode.address;
161
+ }
162
+
163
+ return this.t('generic.none');
129
164
  }
130
165
 
131
166
  get canScaleDown() {
@@ -0,0 +1,4 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+
3
+ export default class PodSecurityAdmissionTemplate extends SteveModel {
4
+ }
@@ -2,7 +2,7 @@ import { DEFAULT_PROJECT, SYSTEM_PROJECT } from '@shell/config/labels-annotation
2
2
  import { MANAGEMENT, NAMESPACE, NORMAN } from '@shell/config/types';
3
3
  import HybridModel from '@shell/plugins/steve/hybrid-class';
4
4
  import isEmpty from 'lodash/isEmpty';
5
- import { HARVESTER_NAME as HARVESTER } from '@shell/config/product/harvester-manager';
5
+ import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
6
6
 
7
7
  function clearUnusedResourceQuotas(spec, types) {
8
8
  types.forEach((type) => {
@@ -99,18 +99,36 @@ export default class Project extends HybridModel {
99
99
  // and the PUT request should have a query param _replace=true.
100
100
  const newValue = await norman.save({ replace: forceReplaceOnReq });
101
101
 
102
- try {
103
- await newValue.doAction('setpodsecuritypolicytemplate', { podSecurityPolicyTemplateId: this.spec.podSecurityPolicyTemplateId || null });
104
- } catch (err) {
105
- if ( err.status === 409 || err.status === 403 ) {
106
- // The backend updates each new project soon after it is created,
107
- // so there is a chance of a resource conflict or forbidden error. If that happens,
108
- // retry the action.
102
+ let retryCount = 0;
103
+
104
+ const finishProjectCreation = async() => {
105
+ try {
109
106
  await newValue.doAction('setpodsecuritypolicytemplate', { podSecurityPolicyTemplateId: this.spec.podSecurityPolicyTemplateId || null });
110
- } else {
111
- throw err;
107
+ } catch (err) {
108
+ if ( err.status === 409 || err.status === 403 ) {
109
+ // The backend updates each new project soon after it is created,
110
+ // so there is a chance of a 409 resource conflict or forbidden error. If that happens,
111
+ // retry the action.
112
+
113
+ // The 403 permission error can happen due to the user requesting
114
+ // the project before the project is fully created and the project
115
+ // permissions are assigned to the user so we allow some
116
+ // time for that process to complete.
117
+
118
+ if (retryCount < 3) {
119
+ retryCount++;
120
+ await setTimeout(() => {
121
+ // Delay for three seconds to avoid another failure due to latency.
122
+ finishProjectCreation();
123
+ }, '3000');
124
+ } else {
125
+ throw err;
126
+ }
127
+ }
112
128
  }
113
- }
129
+ };
130
+
131
+ await finishProjectCreation();
114
132
 
115
133
  return newValue;
116
134
  }
@@ -163,6 +181,7 @@ export default class Project extends HybridModel {
163
181
  normanProject.setLabels(this.metadata.labels);
164
182
  normanProject.setResourceQuotas(clearedResourceQuotas);
165
183
  normanProject.description = this.spec.description;
184
+ normanProject.name = this.spec.displayName;
166
185
  normanProject.containerDefaultResourceLimit = this.spec.containerDefaultResourceLimit;
167
186
 
168
187
  return normanProject;
@@ -12,7 +12,7 @@ export default class Setting extends HybridModel {
12
12
  let out = super._availableActions;
13
13
 
14
14
  // Some settings are not editable
15
- if ( settingMetadata?.readOnly || this.fromEnv ) {
15
+ if ( settingMetadata?.readOnly ) {
16
16
  toFilter.push('goToEdit');
17
17
  }
18
18
 
@@ -1,7 +1,25 @@
1
1
  import { NORMAN } from '@shell/config/types';
2
- import HybridModel from '@shell/plugins/steve/hybrid-class';
2
+ import HybridModel, { cleanHybridResources } from '@shell/plugins/steve/hybrid-class';
3
3
 
4
4
  export default class User extends HybridModel {
5
+ // Preserve description
6
+ constructor(data, ctx, rehydrateNamespace = null, setClone = false) {
7
+ const _description = data.description;
8
+
9
+ super(data, ctx, rehydrateNamespace, setClone);
10
+ this.description = _description;
11
+ }
12
+
13
+ // Clean the Norman properties, but keep description
14
+ cleanResource(data) {
15
+ const desc = data.description;
16
+ const clean = cleanHybridResources(data);
17
+
18
+ clean._description = desc;
19
+
20
+ return clean;
21
+ }
22
+
5
23
  get isSystem() {
6
24
  for ( const p of this.principalIds || [] ) {
7
25
  if ( p.startsWith('system://') ) {
@@ -89,6 +107,24 @@ export default class User extends HybridModel {
89
107
  return this.metadata?.state?.name || 'unknown';
90
108
  }
91
109
 
110
+ get description() {
111
+ return this._description;
112
+ }
113
+
114
+ set description(value) {
115
+ this._description = value;
116
+ }
117
+
118
+ // Ensure when we clone that we preserve the description
119
+ toJSON() {
120
+ const data = super.toJSON();
121
+
122
+ data.description = this._description;
123
+ delete data._description;
124
+
125
+ return data;
126
+ }
127
+
92
128
  async save(opt) {
93
129
  const clone = await this.$dispatch('clone', { resource: this });
94
130
 
@@ -9,7 +9,9 @@ import { escapeHtml } from '@shell/utils/string';
9
9
  import { insertAt, isArray } from '@shell/utils/array';
10
10
  import SteveModel from '@shell/plugins/steve/steve-class';
11
11
  import Vue from 'vue';
12
- import { HARVESTER_NAME as HARVESTER } from '@shell/config/product/harvester-manager';
12
+ import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
13
+ import { hasPSALabels, getPSATooltipsDescription, getPSALabels } from '@shell/utils/pod-security-admission';
14
+ import { PSAIconsDisplay, PSALabelsNamespaceVersion } from '@shell/config/pod-security-admission';
13
15
 
14
16
  const OBSCURE_NAMESPACE_PREFIX = [
15
17
  'c-', // cluster namespace
@@ -185,11 +187,10 @@ export default class Namespace extends SteveModel {
185
187
  }
186
188
 
187
189
  get _detailLocation() {
188
- const _detailLocation = super._detailLocation;
190
+ let _detailLocation = super._detailLocation;
189
191
 
190
- // Harvester uses these resource directly... but has different routes. detailLocation covers routes leading to resource (like edit)
191
- if (this.$rootGetters['currentProduct'].inStore === HARVESTER) {
192
- _detailLocation.name = `${ HARVESTER }-${ _detailLocation.name }`.replace('-product', '');
192
+ if (this.$rootGetters['currentProduct'].hideNamespaceLocation) {
193
+ _detailLocation = false;
193
194
  }
194
195
 
195
196
  return _detailLocation;
@@ -211,6 +212,42 @@ export default class Namespace extends SteveModel {
211
212
  Vue.set(this.metadata.annotations, RESOURCE_QUOTA, JSON.stringify(value));
212
213
  }
213
214
 
215
+ get detailTopTooltips() {
216
+ return this.psaTooltipsDescription;
217
+ }
218
+
219
+ get detailTopIcons() {
220
+ return PSAIconsDisplay;
221
+ }
222
+
223
+ /**
224
+ * Check if resource contains PSA labels
225
+ */
226
+ get hasSystemLabels() {
227
+ return hasPSALabels(this);
228
+ }
229
+
230
+ get filteredSystemLabels() {
231
+ return Object.entries(this.labels).reduce((res, [key, value]) => {
232
+ if (!PSALabelsNamespaceVersion.includes(key)) {
233
+ res[key] = value;
234
+ }
235
+
236
+ return res;
237
+ }, {});
238
+ }
239
+
240
+ /**
241
+ * Generate list of present keys which can be filtered based on existing label keys and system keys
242
+ */
243
+ get systemLabels() {
244
+ return getPSALabels(this);
245
+ }
246
+
247
+ get psaTooltipsDescription() {
248
+ return getPSATooltipsDescription(this);
249
+ }
250
+
214
251
  // Preserve the project label - ensures we preserve project when cloning a namespace
215
252
  cleanForNew() {
216
253
  const project = this.metadata?.labels?.[PROJECT];
@@ -112,10 +112,22 @@ export const LONGHORN_DRIVER = 'driver.longhorn.io';
112
112
  export const LONGHORN_PLUGIN = VOLUME_PLUGINS.find(plugin => plugin.value === 'longhorn');
113
113
 
114
114
  export default class PV extends SteveModel {
115
+ // plugin display value table
115
116
  get source() {
116
- const plugin = this.isLonghorn ? LONGHORN_PLUGIN : VOLUME_PLUGINS.find(plugin => this.spec[plugin.value]);
117
+ const csiDriver = this.spec?.csi?.driver;
118
+ const fallback = `${ csiDriver } ${ this.t('persistentVolume.csi.drivers.suffix') }`;
117
119
 
118
- return this.t(plugin.labelKey);
120
+ if (csiDriver) {
121
+ return this.$rootGetters['i18n/withFallback'](`persistentVolume.csi.drivers.${ csiDriver.replaceAll('.', '-') }`, null, fallback);
122
+ }
123
+ const pluginDef = VOLUME_PLUGINS.find(plugin => this.spec[plugin.value]);
124
+
125
+ if (pluginDef) {
126
+ return this.t(pluginDef.labelKey);
127
+ }
128
+
129
+ // every source should be a csi driver or listed in VOLUME_PLUGIN but just in case..
130
+ return this.t('generic.unknown');
119
131
  }
120
132
 
121
133
  get isLonghorn() {