@rancher/shell 0.1.1 → 0.1.3

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 (294) hide show
  1. package/assets/translations/en-us.yaml +33 -769
  2. package/assets/translations/zh-hans.yaml +153 -781
  3. package/components/ActionMenu.vue +3 -3
  4. package/components/CodeMirror.vue +6 -8
  5. package/components/CommunityLinks.vue +1 -1
  6. package/components/ContainerResourceLimit.vue +14 -0
  7. package/components/ExplorerMembers.vue +123 -0
  8. package/components/ExplorerProjectsNamespaces.vue +405 -0
  9. package/components/GrafanaDashboard.vue +17 -2
  10. package/components/LocaleSelector.vue +81 -0
  11. package/components/PromptModal.vue +2 -3
  12. package/components/ResourceList/index.vue +1 -1
  13. package/components/ResourceTable.vue +3 -6
  14. package/components/SingleClusterInfo.vue +1 -1
  15. package/components/SortableTable/index.vue +23 -20
  16. package/components/SortableTable/selection.js +1 -0
  17. package/components/auth/AzureWarning.vue +5 -1
  18. package/components/auth/Principal.vue +1 -1
  19. package/components/auth/RoleDetailEdit.vue +32 -12
  20. package/components/fleet/FleetRepos.vue +0 -2
  21. package/components/form/NameNsDescription.vue +4 -6
  22. package/components/form/NodeScheduling.vue +1 -1
  23. package/components/form/ResourceTabs/index.vue +27 -18
  24. package/components/form/WorkloadPorts.vue +1 -1
  25. package/components/formatter/ClusterLink.vue +13 -0
  26. package/components/formatter/PodImages.vue +11 -1
  27. package/components/formatter/RKETemplateName.vue +37 -0
  28. package/components/formatter/WorkloadHealthScale.vue +1 -1
  29. package/components/nav/Header.vue +9 -9
  30. package/components/nav/NamespaceFilter.vue +7 -4
  31. package/components/nav/TopLevelMenu.vue +6 -43
  32. package/components/nav/WindowManager/ContainerLogs.vue +1 -1
  33. package/config/product/harvester-manager.js +64 -2
  34. package/config/product/manager.js +9 -0
  35. package/config/settings.js +17 -71
  36. package/config/table-headers.js +0 -1
  37. package/config/types.js +8 -26
  38. package/core/plugin-routes.ts +34 -22
  39. package/core/plugin.ts +15 -3
  40. package/core/plugins-loader.js +2 -0
  41. package/core/plugins.js +79 -36
  42. package/core/types.ts +7 -1
  43. package/creators/app/tsconfig.json +6 -1
  44. package/creators/pkg/init +3 -0
  45. package/creators/pkg/tsconfig.json +7 -2
  46. package/detail/provisioning.cattle.io.cluster.vue +23 -0
  47. package/detail/workload/index.vue +11 -5
  48. package/{components/dialog → dialog}/AddClusterMemberDialog.vue +0 -0
  49. package/{components/dialog → dialog}/AddCustomBadgeDialog.vue +0 -0
  50. package/{components/dialog → dialog}/AddProjectMemberDialog.vue +0 -0
  51. package/{components/dialog → dialog}/AddonConfigConfirmationDialog.vue +0 -0
  52. package/{components/dialog → dialog}/DrainNode.vue +0 -0
  53. package/{components/dialog → dialog}/ForceMachineRemoveDialog.vue +0 -0
  54. package/{components/dialog → dialog}/GenericPrompt.vue +0 -0
  55. package/{components/dialog → dialog}/RollbackWorkloadDialog.vue +0 -0
  56. package/{components/dialog → dialog}/RotateCertificatesDialog.vue +0 -0
  57. package/{components/dialog → dialog}/RotateEncryptionKeyDialog.vue +0 -0
  58. package/{components/dialog → dialog}/SaveAsRKETemplateDialog.vue +0 -0
  59. package/{components/dialog → dialog}/ScaleMachineDownDialog.vue +0 -0
  60. package/edit/auth/azuread.vue +20 -1
  61. package/edit/cloudcredential.vue +7 -1
  62. package/edit/management.cattle.io.project.vue +2 -2
  63. package/edit/namespace.vue +17 -10
  64. package/edit/networking.k8s.io.ingress/index.vue +2 -1
  65. package/edit/persistentvolumeclaim.vue +33 -2
  66. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
  67. package/edit/provisioning.cattle.io.cluster/MachinePool.vue +34 -6
  68. package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
  69. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -6
  70. package/edit/service.vue +1 -1
  71. package/edit/workload/index.vue +363 -15
  72. package/edit/workload/mixins/workload.js +62 -7
  73. package/edit/workload/storage/persistentVolumeClaim/persistentvolumeclaim.vue +1 -0
  74. package/layouts/default.vue +52 -27
  75. package/layouts/error.vue +5 -1
  76. package/layouts/home.vue +6 -2
  77. package/list/harvesterhci.io.management.cluster.vue +74 -33
  78. package/list/namespace.vue +3 -5
  79. package/list/provisioning.cattle.io.cluster.vue +6 -0
  80. package/machine-config/amazonec2.vue +2 -0
  81. package/machine-config/harvester.vue +96 -49
  82. package/middleware/authenticated.js +56 -52
  83. package/mixins/brand.js +3 -4
  84. package/mixins/create-edit-view/impl.js +0 -8
  85. package/mixins/form-validation.js +1 -1
  86. package/mixins/resource-fetch.js +3 -1
  87. package/models/chart.js +1 -1
  88. package/models/cluster/node.js +12 -1
  89. package/models/fleet.cattle.io.bundle.js +26 -19
  90. package/models/harvesterhci.io.management.cluster.js +194 -5
  91. package/models/management.cattle.io.cluster.js +1 -1
  92. package/models/management.cattle.io.clusterroletemplatebinding.js +9 -0
  93. package/models/management.cattle.io.globalrole.js +0 -19
  94. package/models/management.cattle.io.project.js +23 -2
  95. package/models/management.cattle.io.roletemplate.js +2 -21
  96. package/models/namespace.js +19 -3
  97. package/models/pod.js +19 -2
  98. package/models/provisioning.cattle.io.cluster.js +71 -0
  99. package/models/service.js +5 -1
  100. package/models/workload.js +4 -243
  101. package/models/workload.service.js +314 -0
  102. package/nuxt.config.js +14 -12
  103. package/package.json +3 -3
  104. package/pages/auth/login.vue +11 -2
  105. package/pages/auth/setup.vue +1 -1
  106. package/pages/c/_cluster/_product/members/index.vue +3 -93
  107. package/pages/c/_cluster/_product/projectsnamespaces.vue +6 -403
  108. package/pages/c/_cluster/apps/charts/install.vue +0 -6
  109. package/pages/c/_cluster/settings/performance.vue +19 -16
  110. package/pages/fail-whale.vue +1 -10
  111. package/pages/index.vue +18 -4
  112. package/pages/plugins.vue +2 -2
  113. package/pages/prefs.vue +8 -6
  114. package/pkg/auto-import.js +44 -7
  115. package/pkg/dynamic-plugin-loader.js +28 -0
  116. package/pkg/import.js +2 -2
  117. package/pkg/model-loader-require.lib.js +3 -0
  118. package/pkg/vue.config.js +9 -6
  119. package/plugins/console.js +10 -5
  120. package/plugins/dashboard-store/actions.js +8 -3
  121. package/plugins/dashboard-store/getters.js +7 -2
  122. package/plugins/dashboard-store/model-loader-require.js +12 -0
  123. package/plugins/dashboard-store/model-loader.js +4 -1
  124. package/plugins/dashboard-store/resource-class.js +10 -3
  125. package/plugins/steve/actions.js +1 -1
  126. package/plugins/steve/index.js +6 -4
  127. package/plugins/steve/steve-description-class.js +32 -0
  128. package/plugins/steve/subscribe.js +34 -23
  129. package/rancher-components/Banner/Banner.vue +2 -2
  130. package/rancher-components/Form/Checkbox/Checkbox.test.ts +77 -0
  131. package/rancher-components/Form/Checkbox/Checkbox.vue +12 -2
  132. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +0 -2
  133. package/rancher-components/Form/LabeledInput/LabeledInput.vue +2 -0
  134. package/rancher-components/Form/Radio/RadioButton.vue +14 -1
  135. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +107 -0
  136. package/{components/form → rancher-components/Form/ToggleSwitch}/ToggleSwitch.vue +18 -8
  137. package/rancher-components/Form/ToggleSwitch/index.ts +1 -0
  138. package/rancher-components/Form/index.ts +1 -0
  139. package/scripts/build-pkg.sh +48 -2
  140. package/scripts/drone-build-pkg.sh +31 -0
  141. package/scripts/publish-shell.sh +10 -11
  142. package/scripts/serve-pkgs +17 -10
  143. package/scripts/test-plugins-build.sh +18 -1
  144. package/store/catalog.js +3 -1
  145. package/store/i18n.js +16 -11
  146. package/store/index.js +4 -181
  147. package/store/prefs.js +30 -2
  148. package/store/type-map.js +16 -29
  149. package/types/{index.d.ts → rancher/index.d.ts} +0 -0
  150. package/utils/cluster.js +1 -1
  151. package/utils/custom-validators.js +1 -12
  152. package/utils/dynamic-importer.js +1 -1
  153. package/utils/validators/setting.js +0 -35
  154. package/components/FilterLabel.vue +0 -254
  155. package/components/HarvesterUpgradeProgressBarList.vue +0 -109
  156. package/components/VMConsoleBar.vue +0 -87
  157. package/components/dialog/harvester/AddHotplugModal.vue +0 -159
  158. package/components/dialog/harvester/BackupModal.vue +0 -117
  159. package/components/dialog/harvester/CloneTemplate.vue +0 -125
  160. package/components/dialog/harvester/EjectCDROMDialog.vue +0 -157
  161. package/components/dialog/harvester/ExportImageDialog.vue +0 -152
  162. package/components/dialog/harvester/MaintenanceDialog.vue +0 -94
  163. package/components/dialog/harvester/MigrationDialog.vue +0 -154
  164. package/components/dialog/harvester/RestoreDialog.vue +0 -153
  165. package/components/dialog/harvester/SupportBundle.vue +0 -217
  166. package/components/dialog/harvester/UnplugVolume.vue +0 -108
  167. package/components/form/SerialConsole/index.vue +0 -267
  168. package/components/formatter/AttachVMWithName.vue +0 -46
  169. package/components/formatter/CloudInitType.vue +0 -27
  170. package/components/formatter/HarvesterBackupTargetValidation.vue +0 -43
  171. package/components/formatter/HarvesterCPUUsed.vue +0 -122
  172. package/components/formatter/HarvesterDiskState.vue +0 -66
  173. package/components/formatter/HarvesterHostName.vue +0 -66
  174. package/components/formatter/HarvesterIpAddress.vue +0 -90
  175. package/components/formatter/HarvesterMemoryUsed.vue +0 -140
  176. package/components/formatter/HarvesterMigrationState.vue +0 -85
  177. package/components/formatter/HarvesterNodeName.vue +0 -49
  178. package/components/formatter/HarvesterStorageUsed.vue +0 -194
  179. package/components/formatter/HarvesterVmState.vue +0 -123
  180. package/components/nav/HarvesterUpgrade.vue +0 -232
  181. package/components/novnc/NovncConsole.vue +0 -93
  182. package/components/novnc/NovncConsoleItem.vue +0 -89
  183. package/components/novnc/NovncConsoleWrapper.vue +0 -243
  184. package/config/harvester-map.js +0 -44
  185. package/config/harvester-table-headers.js +0 -27
  186. package/config/product/harvester.js +0 -305
  187. package/detail/harvesterhci.io.host/HarvesterHostBasic.vue +0 -364
  188. package/detail/harvesterhci.io.host/HarvesterHostDisk.vue +0 -200
  189. package/detail/harvesterhci.io.host/HarvesterHostNetwork.vue +0 -89
  190. package/detail/harvesterhci.io.host/VirtualMachineInstance.vue +0 -134
  191. package/detail/harvesterhci.io.host/index.vue +0 -243
  192. package/detail/harvesterhci.io.virtualmachinebackup/index.vue +0 -221
  193. package/detail/harvesterhci.io.virtualmachineimage.vue +0 -118
  194. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineBasics.vue +0 -279
  195. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineEvents.vue +0 -75
  196. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineKeypairs.vue +0 -114
  197. package/detail/kubevirt.io.virtualmachine/VirtualMachineTabs/VirtualMachineMigration.vue +0 -79
  198. package/detail/kubevirt.io.virtualmachine/index.vue +0 -213
  199. package/edit/harvesterhci.io.cloudtemplate.vue +0 -123
  200. package/edit/harvesterhci.io.host/HarvesterDisk.vue +0 -262
  201. package/edit/harvesterhci.io.host/index.vue +0 -533
  202. package/edit/harvesterhci.io.keypair.vue +0 -112
  203. package/edit/harvesterhci.io.managedchart/index.vue +0 -25
  204. package/edit/harvesterhci.io.managedchart/rancher-monitoring.vue +0 -172
  205. package/edit/harvesterhci.io.networkattachmentdefinition.vue +0 -210
  206. package/edit/harvesterhci.io.setting/additional-ca.vue +0 -36
  207. package/edit/harvesterhci.io.setting/backup-target.vue +0 -182
  208. package/edit/harvesterhci.io.setting/http-proxy.vue +0 -79
  209. package/edit/harvesterhci.io.setting/index.vue +0 -201
  210. package/edit/harvesterhci.io.setting/overcommit-config.vue +0 -94
  211. package/edit/harvesterhci.io.setting/ssl-certificates.vue +0 -117
  212. package/edit/harvesterhci.io.setting/ssl-parameters.vue +0 -161
  213. package/edit/harvesterhci.io.setting/support-bundle-image.vue +0 -134
  214. package/edit/harvesterhci.io.setting/support-bundle-namespaces.vue +0 -73
  215. package/edit/harvesterhci.io.setting/vip-pools.vue +0 -244
  216. package/edit/harvesterhci.io.setting/vm-force-reset-policy.vue +0 -81
  217. package/edit/harvesterhci.io.virtualmachinebackup.vue +0 -256
  218. package/edit/harvesterhci.io.virtualmachineimage.vue +0 -364
  219. package/edit/harvesterhci.io.virtualmachinetemplateversion.vue +0 -340
  220. package/edit/harvesterhci.io.volume.vue +0 -195
  221. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/AccessCredentialsUsers.vue +0 -190
  222. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/index.vue +0 -212
  223. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/type/basicAuth.vue +0 -94
  224. package/edit/kubevirt.io.virtualmachine/VirtualMachineAccessCredentials/type/sshkey.vue +0 -85
  225. package/edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig/DataTemplate.vue +0 -153
  226. package/edit/kubevirt.io.virtualmachine/VirtualMachineCloudConfig/index.vue +0 -279
  227. package/edit/kubevirt.io.virtualmachine/VirtualMachineCpuMemory.vue +0 -113
  228. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/__tests__/HarvesterEditNetwork.test.ts +0 -41
  229. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/base.vue +0 -281
  230. package/edit/kubevirt.io.virtualmachine/VirtualMachineNetwork/index.vue +0 -142
  231. package/edit/kubevirt.io.virtualmachine/VirtualMachineReserved.vue +0 -54
  232. package/edit/kubevirt.io.virtualmachine/VirtualMachineSSHKey.vue +0 -256
  233. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/index.vue +0 -391
  234. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditContainer.test.ts +0 -40
  235. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditExisting.test.ts +0 -102
  236. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVMImage.test.ts +0 -117
  237. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/__tests__/HarvesterEditVolume.test.ts +0 -74
  238. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/container.vue +0 -132
  239. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/existing.vue +0 -303
  240. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/vmImage.vue +0 -285
  241. package/edit/kubevirt.io.virtualmachine/VirtualMachineVolume/type/volume.vue +0 -188
  242. package/edit/kubevirt.io.virtualmachine/index.vue +0 -642
  243. package/edit/network.harvesterhci.io.clusternetwork/index.vue +0 -19
  244. package/edit/network.harvesterhci.io.clusternetwork/vlan.vue +0 -134
  245. package/edit/workload/types/Deployment.vue +0 -377
  246. package/edit/workload/types/Generic.vue +0 -295
  247. package/list/harvesterhci.io.cloudtemplate.vue +0 -78
  248. package/list/harvesterhci.io.dashboard/HarvesterUpgrade.vue +0 -211
  249. package/list/harvesterhci.io.dashboard/UpgradeInfo.vue +0 -40
  250. package/list/harvesterhci.io.dashboard/index.vue +0 -752
  251. package/list/harvesterhci.io.host/index.vue +0 -186
  252. package/list/harvesterhci.io.networkattachmentdefinition.vue +0 -167
  253. package/list/harvesterhci.io.setting.vue +0 -241
  254. package/list/harvesterhci.io.virtualmachinebackup.vue +0 -172
  255. package/list/harvesterhci.io.virtualmachineimage.vue +0 -80
  256. package/list/harvesterhci.io.virtualmachinetemplateversion.vue +0 -173
  257. package/list/harvesterhci.io.volume.vue +0 -122
  258. package/list/kubevirt.io.virtualmachine.vue +0 -193
  259. package/mixins/harvester-vm/impl.js +0 -267
  260. package/mixins/harvester-vm/index.js +0 -1357
  261. package/models/harvester/configmap.js +0 -32
  262. package/models/harvester/harvesterhci.io.blockdevice.js +0 -55
  263. package/models/harvester/harvesterhci.io.keypair.js +0 -12
  264. package/models/harvester/harvesterhci.io.setting.js +0 -127
  265. package/models/harvester/harvesterhci.io.supportbundle.js +0 -35
  266. package/models/harvester/harvesterhci.io.upgrade.js +0 -226
  267. package/models/harvester/harvesterhci.io.virtualmachinebackup.js +0 -116
  268. package/models/harvester/harvesterhci.io.virtualmachineimage.js +0 -255
  269. package/models/harvester/harvesterhci.io.virtualmachinerestore.js +0 -43
  270. package/models/harvester/harvesterhci.io.virtualmachinetemplate.js +0 -69
  271. package/models/harvester/harvesterhci.io.virtualmachinetemplateversion.js +0 -227
  272. package/models/harvester/k8s.cni.cncf.io.networkattachmentdefinition.js +0 -32
  273. package/models/harvester/kubevirt.io.virtualmachine.js +0 -850
  274. package/models/harvester/kubevirt.io.virtualmachineinstance.js +0 -142
  275. package/models/harvester/management.cattle.io.managedchart.js +0 -191
  276. package/models/harvester/management.cattle.io.setting.js +0 -40
  277. package/models/harvester/network.harvesterhci.io.clusternetwork.js +0 -100
  278. package/models/harvester/network.harvesterhci.io.nodenetwork.js +0 -34
  279. package/models/harvester/node.js +0 -255
  280. package/models/harvester/persistentvolumeclaim.js +0 -166
  281. package/models/harvester/pod.js +0 -185
  282. package/pages/c/_cluster/harvester/airgapupgrade/index.vue +0 -309
  283. package/pages/c/_cluster/harvester/console/_uid/serial.vue +0 -51
  284. package/pages/c/_cluster/harvester/console/_uid/vnc.vue +0 -52
  285. package/pages/c/_cluster/harvester/index.vue +0 -24
  286. package/pages/c/_cluster/harvester/support/index.vue +0 -154
  287. package/pkg/model-loader.lib.js +0 -3
  288. package/plugins/lookup.js +0 -50
  289. package/promptRemove/kubevirt.io.virtualmachine.vue +0 -164
  290. package/store/harvester-common.js +0 -126
  291. package/utils/validators/vm-datavolumes.js +0 -38
  292. package/utils/validators/vm-image.js +0 -32
  293. package/utils/validators/vm.js +0 -221
  294. package/yarn-error.log +0 -196
@@ -1,1357 +0,0 @@
1
- import YAML from 'yaml';
2
- import jsyaml from 'js-yaml';
3
- import isEqual from 'lodash/isEqual';
4
- import isEmpty from 'lodash/isEmpty';
5
- import difference from 'lodash/difference';
6
-
7
- import { sortBy } from '@shell/utils/sort';
8
- import { set } from '@shell/utils/object';
9
-
10
- import { allHash } from '@shell/utils/promise';
11
- import { randomStr } from '@shell/utils/string';
12
- import { base64Decode } from '@shell/utils/crypto';
13
- import { formatSi, parseSi } from '@shell/utils/units';
14
- import { SOURCE_TYPE, ACCESS_CREDENTIALS } from '@shell/config/harvester-map';
15
- import { _CLONE } from '@shell/config/query-params';
16
- import {
17
- PVC, HCI, STORAGE_CLASS, NODE, SECRET, CONFIG_MAP, NETWORK_ATTACHMENT
18
- } from '@shell/config/types';
19
- import { HCI_SETTING } from '@shell/config/settings';
20
- import { HCI as HCI_ANNOTATIONS, HOSTNAME } from '@shell/config/labels-annotations';
21
- import impl, { QGA_JSON, USB_TABLET } from '@shell/mixins/harvester-vm/impl';
22
-
23
- export const MANAGEMENT_NETWORK = 'management Network';
24
-
25
- export const OS = [{
26
- label: 'Windows',
27
- value: 'windows'
28
- }, {
29
- label: 'Linux',
30
- value: 'linux'
31
- }, {
32
- label: 'Debian',
33
- value: 'debian'
34
- }, {
35
- label: 'Fedora',
36
- value: 'fedora'
37
- }, {
38
- label: 'Gentoo',
39
- value: 'gentoo'
40
- }, {
41
- label: 'Mandriva',
42
- value: 'mandriva'
43
- }, {
44
- label: 'Oracle',
45
- value: 'oracle'
46
- }, {
47
- label: 'Red Hat',
48
- value: 'redhat'
49
- }, {
50
- label: 'openSUSE',
51
- value: 'openSUSE',
52
- }, {
53
- label: 'Turbolinux',
54
- value: 'turbolinux'
55
- }, {
56
- label: 'Ubuntu',
57
- value: 'ubuntu'
58
- }, {
59
- label: 'Xandros',
60
- value: 'xandros'
61
- }, {
62
- label: 'Other Linux',
63
- match: ['centos'],
64
- value: 'otherLinux'
65
- }];
66
-
67
- export const CD_ROM = 'cd-rom';
68
- export const HARD_DISK = 'disk';
69
-
70
- export default {
71
- mixins: [impl],
72
-
73
- props: {
74
- value: {
75
- type: Object,
76
- required: true,
77
- },
78
-
79
- resource: {
80
- type: String,
81
- default: ''
82
- }
83
- },
84
-
85
- async fetch() {
86
- const hash = {
87
- pvcs: this.$store.dispatch('harvester/findAll', { type: PVC }),
88
- storageClasses: this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS }),
89
- sshs: this.$store.dispatch('harvester/findAll', { type: HCI.SSH }),
90
- settings: this.$store.dispatch('harvester/findAll', { type: HCI.SETTING }),
91
- images: this.$store.dispatch('harvester/findAll', { type: HCI.IMAGE }),
92
- versions: this.$store.dispatch('harvester/findAll', { type: HCI.VM_VERSION }),
93
- templates: this.$store.dispatch('harvester/findAll', { type: HCI.VM_TEMPLATE }),
94
- networkAttachment: this.$store.dispatch('harvester/findAll', { type: NETWORK_ATTACHMENT }),
95
- vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
96
- vmims: this.$store.dispatch('harvester/findAll', { type: HCI.VMIM }),
97
- vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
98
- secrets: this.$store.dispatch('harvester/findAll', { type: SECRET }),
99
- };
100
-
101
- if (this.$store.getters['harvester/schemaFor'](NODE)) {
102
- hash.nodes = this.$store.dispatch('harvester/findAll', { type: NODE });
103
- }
104
- await allHash(hash);
105
- },
106
-
107
- data() {
108
- const isClone = this.realMode === _CLONE;
109
-
110
- return {
111
- OS,
112
- isClone,
113
- spec: null,
114
- osType: 'linux',
115
- sshKey: [],
116
- runStrategy: 'RerunOnFailure',
117
- installAgent: true,
118
- hasCreateVolumes: [],
119
- installUSBTablet: true,
120
- networkScript: '',
121
- userScript: '',
122
- imageId: '',
123
- diskRows: [],
124
- networkRows: [],
125
- machineType: '',
126
- secretName: '',
127
- secretRef: null,
128
- showAdvanced: false,
129
- deleteAgent: true,
130
- memory: null,
131
- cpu: '',
132
- reservedMemory: null,
133
- accessCredentials: [],
134
- efiEnabled: false,
135
- secureBoot: false,
136
- userDataTemplateId: '',
137
- };
138
- },
139
-
140
- computed: {
141
- images() {
142
- return this.$store.getters['harvester/all'](HCI.IMAGE);
143
- },
144
-
145
- versions() {
146
- return this.$store.getters['harvester/all'](HCI.VM_VERSION);
147
- },
148
-
149
- templates() {
150
- return this.$store.getters['harvester/all'](HCI.VM_TEMPLATE);
151
- },
152
-
153
- pvcs() {
154
- return this.$store.getters['harvester/all'](PVC);
155
- },
156
-
157
- secrets() {
158
- return this.$store.getters['harvester/all'](SECRET);
159
- },
160
-
161
- nodesIdOptions() {
162
- const nodes = this.$store.getters['harvester/all'](NODE);
163
-
164
- return nodes.filter(N => !N.isUnSchedulable).map((node) => {
165
- return {
166
- label: node.nameDisplay,
167
- value: node.id
168
- };
169
- });
170
- },
171
-
172
- defaultStorageClass() {
173
- const defaultStorage = this.$store.getters['harvester/all'](STORAGE_CLASS).find( O => O.isDefault);
174
-
175
- return defaultStorage?.metadata?.name || 'longhorn';
176
- },
177
-
178
- storageClassSetting() {
179
- try {
180
- const storageClassValue = this.$store.getters['harvester/all'](HCI.SETTING).find( O => O.id === HCI_SETTING.DEFAULT_STORAGE_CLASS)?.value;
181
-
182
- return JSON.parse(storageClassValue);
183
- } catch (e) {
184
- return {};
185
- }
186
- },
187
-
188
- customDefaultStorageClass() {
189
- return this.storageClassSetting.storageClass;
190
- },
191
-
192
- customVolumeMode() {
193
- return this.storageClassSetting.volumeMode || 'Block';
194
- },
195
-
196
- customAccessMode() {
197
- return this.storageClassSetting.accessModes || 'ReadWriteMany';
198
- },
199
-
200
- isWindows() {
201
- return this.osType === 'windows';
202
- },
203
-
204
- needNewSecret() {
205
- // When creating a template it is always necessary to create a new secret.
206
- return this.resource === HCI.VM_VERSION || this.isCreate;
207
- },
208
- },
209
-
210
- async created() {
211
- await this.$store.dispatch('harvester/findAll', { type: SECRET });
212
-
213
- this.getInitConfig({ value: this.value });
214
- },
215
-
216
- methods: {
217
- getInitConfig(config) {
218
- const { value } = config;
219
-
220
- const vm = this.resource === HCI.VM ? value : this.resource === HCI.BACKUP ? this.value.status?.source : value.spec.vm;
221
-
222
- const spec = vm?.spec;
223
-
224
- if (!spec) {
225
- return;
226
- }
227
- const resources = spec.template.spec.domain.resources;
228
-
229
- // If the user is created via yaml, there may be no "resources.limits": kubectl apply -f https://kubevirt.io/labs/manifests/vm.yaml
230
- if (!resources?.limits || (resources?.limits && !resources?.limits?.memory && resources?.limits?.memory !== null)) {
231
- spec.template.spec.domain.resources = {
232
- ...spec.template.spec.domain.resources,
233
- limits: {
234
- ...spec.template.spec.domain.resources.limits,
235
- memory: spec.template.spec.domain.resources.requests.memory
236
- }
237
- };
238
- }
239
-
240
- const runStrategy = spec.runStrategy || 'RerunOnFailure';
241
- const machineType = value.machineType;
242
- const cpu = spec.template.spec.domain?.cpu?.cores;
243
- const memory = spec.template.spec.domain.resources.limits.memory;
244
- const reservedMemory = vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
245
-
246
- const sshKey = this.getSSHFromAnnotation(spec) || [];
247
-
248
- const imageId = this.getRootImageId(vm) || '';
249
- const diskRows = this.getDiskRows(vm);
250
- const networkRows = this.getNetworkRows(vm);
251
- const hasCreateVolumes = this.getHasCreatedVolumes(spec) || [];
252
-
253
- let { userData = undefined, networkData = undefined } = this.getSecretCloudData(spec);
254
-
255
- if (this.resource === HCI.BACKUP) {
256
- const secretBackups = this.value.status?.secretBackups;
257
-
258
- if (secretBackups) {
259
- const secretNetworkData = secretBackups[0]?.data?.networkdata || '';
260
- const secretUserData = secretBackups[0]?.data?.userdata || '';
261
-
262
- userData = base64Decode(secretUserData);
263
- networkData = base64Decode(secretNetworkData);
264
- }
265
- }
266
- const osType = this.getOsType(vm) || 'linux';
267
-
268
- userData = this.isCreate ? this.getInitUserData({ osType }) : userData;
269
- const installUSBTablet = this.isInstallUSBTablet(spec);
270
- const installAgent = this.isCreate ? true : this.hasInstallAgent(userData, osType, true);
271
- const efiEnabled = this.isEfiEnabled(spec);
272
- const secureBoot = this.isSecureBoot(spec);
273
-
274
- const secretRef = this.getSecret(spec);
275
- const accessCredentials = this.getAccessCredentials(spec);
276
-
277
- if (Object.prototype.hasOwnProperty.call(spec, 'running')) {
278
- delete spec.running;
279
- spec.runStrategy = 'RerunOnFailure';
280
- }
281
-
282
- this.$set(this, 'spec', spec);
283
- this.$set(this, 'runStrategy', runStrategy);
284
- this.$set(this, 'secretRef', secretRef);
285
- this.$set(this, 'accessCredentials', accessCredentials);
286
- this.$set(this, 'userScript', userData);
287
- this.$set(this, 'networkScript', networkData);
288
-
289
- this.$set(this, 'sshKey', sshKey);
290
- this.$set(this, 'osType', osType);
291
- this.$set(this, 'installAgent', installAgent);
292
-
293
- this.$set(this, 'cpu', cpu);
294
- this.$set(this, 'memory', memory);
295
- this.$set(this, 'reservedMemory', reservedMemory);
296
- this.$set(this, 'machineType', machineType);
297
-
298
- this.$set(this, 'installUSBTablet', installUSBTablet);
299
- this.$set(this, 'efiEnabled', efiEnabled);
300
- this.$set(this, 'secureBoot', secureBoot);
301
-
302
- this.$set(this, 'hasCreateVolumes', hasCreateVolumes);
303
- this.$set(this, 'networkRows', networkRows);
304
- this.$set(this, 'imageId', imageId);
305
-
306
- this.$set(this, 'diskRows', diskRows);
307
- },
308
-
309
- getDiskRows(vm) {
310
- const namespace = vm.metadata.namespace;
311
- const _volumes = vm.spec.template.spec.volumes || [];
312
- const _disks = vm.spec.template.spec.domain.devices.disks || [];
313
- const _volumeClaimTemplates = this.getVolumeClaimTemplates(vm);
314
-
315
- let out = [];
316
-
317
- if (_disks.length === 0) {
318
- out.push({
319
- id: randomStr(5),
320
- source: SOURCE_TYPE.IMAGE,
321
- name: 'disk-0',
322
- accessMode: 'ReadWriteMany',
323
- bus: 'virtio',
324
- volumeName: '',
325
- size: '10Gi',
326
- type: HARD_DISK,
327
- storageClassName: '',
328
- image: this.imageId,
329
- volumeMode: 'Block',
330
- });
331
- } else {
332
- out = _disks.map( (DISK, index) => {
333
- const volume = _volumes.find( V => V.name === DISK.name );
334
-
335
- let size = '';
336
- let image = '';
337
- let source = '';
338
- let realName = '';
339
- let container = '';
340
- let volumeName = '';
341
- let accessMode = '';
342
- let volumeMode = '';
343
- let storageClassName = '';
344
- let hotpluggable = false;
345
-
346
- const type = DISK?.cdrom ? CD_ROM : HARD_DISK;
347
-
348
- if (volume?.containerDisk) { // SOURCE_TYPE.CONTAINER
349
- source = SOURCE_TYPE.CONTAINER;
350
- container = volume.containerDisk.image;
351
- }
352
-
353
- if (volume.persistentVolumeClaim && volume.persistentVolumeClaim?.claimName) {
354
- volumeName = volume.persistentVolumeClaim.claimName;
355
- const DVT = _volumeClaimTemplates.find( T => T.metadata.name === volumeName);
356
-
357
- realName = volumeName;
358
- // If the DVT can be found, it cannot be an existing volume
359
- if (DVT) {
360
- // has annotation (HCI_ANNOTATIONS.IMAGE_ID) => SOURCE_TYPE.IMAGE
361
- if (DVT.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID] !== undefined) {
362
- image = DVT.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID];
363
- source = SOURCE_TYPE.IMAGE;
364
- } else {
365
- source = SOURCE_TYPE.NEW;
366
- }
367
-
368
- const dataVolumeSpecPVC = DVT?.spec || {};
369
-
370
- volumeMode = dataVolumeSpecPVC?.volumeMode;
371
- accessMode = dataVolumeSpecPVC?.accessModes?.[0];
372
- size = dataVolumeSpecPVC?.resources?.requests?.storage || '10Gi';
373
- storageClassName = dataVolumeSpecPVC?.storageClassName;
374
- } else { // SOURCE_TYPE.ATTACH_VOLUME
375
- const allPVCs = this.$store.getters['harvester/all'](PVC);
376
- const pvcResource = allPVCs.find( O => O.id === `${ namespace }/${ volume?.persistentVolumeClaim?.claimName }`);
377
-
378
- source = SOURCE_TYPE.ATTACH_VOLUME;
379
- accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
380
- size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
381
- storageClassName = pvcResource?.spec?.storageClassName;
382
- volumeMode = pvcResource?.spec?.volumeMode || 'Block';
383
- volumeName = pvcResource?.metadata?.name || '';
384
- }
385
-
386
- hotpluggable = volume.persistentVolumeClaim.hotpluggable || false;
387
- }
388
-
389
- const bus = DISK?.disk?.bus || DISK?.cdrom?.bus;
390
-
391
- const bootOrder = DISK?.bootOrder ? DISK?.bootOrder : index;
392
-
393
- const parseValue = parseSi(size);
394
-
395
- const formatSize = formatSi(parseValue, {
396
- increment: 1024,
397
- addSuffix: false,
398
- maxExponent: 3,
399
- minExponent: 3,
400
- });
401
-
402
- const allVolumeStatus = JSON.parse(vm.metadata?.annotations?.[HCI_ANNOTATIONS.VM_VOLUME_STATUS] || '[]');
403
- const volumeStatus = allVolumeStatus.find(volume => realName === volume.name);
404
-
405
- return {
406
- id: randomStr(5),
407
- bootOrder,
408
- source,
409
- name: DISK.name,
410
- realName,
411
- bus,
412
- volumeName,
413
- container,
414
- accessMode,
415
- size: `${ formatSize }Gi`,
416
- volumeMode: volumeMode || this.customVolumeMode,
417
- image,
418
- type,
419
- storageClassName,
420
- hotpluggable,
421
- volumeStatus,
422
- };
423
- });
424
- }
425
-
426
- out = sortBy(out, 'bootOrder');
427
-
428
- return out.filter( O => O.name !== 'cloudinitdisk');
429
- },
430
-
431
- getNetworkRows(vm) {
432
- const networks = vm.spec.template.spec.networks || [];
433
- const interfaces = vm.spec.template.spec.domain.devices.interfaces || [];
434
-
435
- const out = interfaces.map( (I, index) => {
436
- const network = networks.find( N => I.name === N.name);
437
-
438
- const type = I.sriov ? 'sriov' : I.bridge ? 'bridge' : 'masquerade';
439
-
440
- const isPod = !!network.pod;
441
-
442
- return {
443
- ...I,
444
- index,
445
- type,
446
- isPod,
447
- model: I.model,
448
- networkName: isPod ? MANAGEMENT_NETWORK : network?.multus?.networkName,
449
- };
450
- });
451
-
452
- return out;
453
- },
454
-
455
- parseVM() {
456
- this.parseOther();
457
- this.parseAccessCredentials();
458
- this.parseNetworkRows(this.networkRows);
459
- this.parseDiskRows(this.diskRows);
460
- },
461
-
462
- parseOther() {
463
- if (!this.spec.template.spec.domain.machine) {
464
- this.$set(this.spec.template.spec.domain, 'machine', { type: this.machineType });
465
- } else {
466
- this.$set(this.spec.template.spec.domain.machine, 'type', this.machineType);
467
- }
468
-
469
- this.spec.template.spec.domain.cpu.cores = this.cpu;
470
- this.spec.template.spec.domain.resources.limits.cpu = this.cpu;
471
- this.spec.template.spec.domain.resources.limits.memory = this.memory;
472
-
473
- // parse reserved memory
474
- const vm = this.resource === HCI.VM ? this.value : this.value.spec.vm;
475
-
476
- if (!this.reservedMemory) {
477
- delete vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY];
478
- } else {
479
- vm.metadata.annotations[HCI_ANNOTATIONS.VM_RESERVED_MEMORY] = this.reservedMemory;
480
- }
481
- },
482
-
483
- parseDiskRows(disk) {
484
- const disks = [];
485
- const volumes = [];
486
- const diskNameLables = [];
487
- const volumeClaimTemplates = [];
488
-
489
- disk.forEach( (R, index) => {
490
- const prefixName = this.value.metadata?.name || '';
491
-
492
- let dataVolumeName = '';
493
-
494
- if (R.source === SOURCE_TYPE.ATTACH_VOLUME) {
495
- dataVolumeName = R.volumeName;
496
- } else if (this.isClone || !this.hasCreateVolumes.includes(R.realName)) {
497
- dataVolumeName = `${ prefixName }-${ R.name }-${ randomStr(5).toLowerCase() }`;
498
- } else {
499
- dataVolumeName = R.realName;
500
- }
501
-
502
- const _disk = this.parseDisk(R, index);
503
- const _volume = this.parseVolume(R, dataVolumeName);
504
- const _dataVolumeTemplate = this.parseVolumeClaimTemplate(R, dataVolumeName);
505
-
506
- disks.push(_disk);
507
- volumes.push(_volume);
508
- diskNameLables.push(dataVolumeName);
509
-
510
- if (R.source !== SOURCE_TYPE.CONTAINER && R.source !== SOURCE_TYPE.ATTACH_VOLUME) {
511
- volumeClaimTemplates.push(_dataVolumeTemplate);
512
- }
513
- });
514
-
515
- if (!this.secretName || this.needNewSecret) {
516
- this.secretName = this.generateSecretName(this.secretNamePrefix);
517
- }
518
-
519
- if (!disks.find( D => D.name === 'cloudinitdisk')) {
520
- if (!this.isWindows) {
521
- disks.push({
522
- name: 'cloudinitdisk',
523
- disk: { bus: 'virtio' }
524
- });
525
-
526
- volumes.push({
527
- name: 'cloudinitdisk',
528
- cloudInitNoCloud: {
529
- secretRef: { name: this.secretName },
530
- networkDataSecretRef: { name: this.secretName }
531
- }
532
- });
533
- }
534
- }
535
-
536
- let spec = {
537
- ...this.spec,
538
- runStrategy: this.runStrategy,
539
- template: {
540
- ...this.spec.template,
541
- metadata: {
542
- ...this.spec?.template?.metadata,
543
- annotations: {
544
- ...this.spec?.template?.metadata?.annotations,
545
- [HCI_ANNOTATIONS.SSH_NAMES]: JSON.stringify(this.sshKey)
546
- },
547
- labels: {
548
- ...this.spec?.template?.metadata?.labels,
549
- [HCI_ANNOTATIONS.VM_NAME]: this.value?.metadata?.name,
550
- }
551
- },
552
- spec: {
553
- ...this.spec.template?.spec,
554
- domain: {
555
- ...this.spec.template?.spec?.domain,
556
- devices: {
557
- ...this.spec.template?.spec?.domain?.devices,
558
- disks,
559
- },
560
- },
561
- volumes,
562
- }
563
- }
564
- };
565
-
566
- if (volumes.length === 0) {
567
- delete spec.template.spec.volumes;
568
- }
569
-
570
- if (this.resource === HCI.VM) {
571
- if (!this.isSingle) {
572
- spec = this.multiVMScheduler(spec);
573
- }
574
-
575
- this.$set(this.value.metadata, 'annotations', {
576
- ...this.value.metadata.annotations,
577
- [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates),
578
- [HCI_ANNOTATIONS.NETWORK_IPS]: JSON.stringify(this.value.networkIps)
579
- });
580
-
581
- this.$set(this.value.metadata, 'labels', {
582
- ...this.value.metadata.labels,
583
- [HCI_ANNOTATIONS.CREATOR]: 'harvester',
584
- [HCI_ANNOTATIONS.OS]: this.osType
585
- });
586
-
587
- this.$set(this.value, 'spec', spec);
588
- this.$set(this, 'spec', spec);
589
- } else if (this.resource === HCI.VM_VERSION) {
590
- this.$set(this.value.spec.vm, 'spec', spec);
591
- this.$set(this.value.spec.vm.metadata, 'annotations', { ...this.value.spec.vm.metadata.annotations, [HCI_ANNOTATIONS.VOLUME_CLAIM_TEMPLATE]: JSON.stringify(volumeClaimTemplates) });
592
- this.$set(this.value.spec.vm.metadata, 'labels', { [HCI_ANNOTATIONS.OS]: this.osType });
593
- this.$set(this, 'spec', spec);
594
- }
595
- },
596
-
597
- multiVMScheduler(spec) {
598
- spec.template.metadata.labels[HCI_ANNOTATIONS.VM_NAME_PREFIX] = this.namePrefix;
599
-
600
- const rule = {
601
- weight: 1,
602
- podAffinityTerm: {
603
- topologyKey: HOSTNAME,
604
- labelSelector: { matchLabels: { [HCI_ANNOTATIONS.VM_NAME_PREFIX]: this.namePrefix } }
605
- }
606
- };
607
-
608
- return {
609
- ...spec,
610
- template: {
611
- ...spec.template,
612
- spec: {
613
- ...spec.template.spec,
614
- affinity: {
615
- ...spec.template.spec.affinity,
616
- podAntiAffinity: {
617
- ...spec.template.spec?.affinity?.podAntiAffinity,
618
- preferredDuringSchedulingIgnoredDuringExecution: [
619
- ...(spec.template.spec?.affinity?.podAntiAffinity?.preferredDuringSchedulingIgnoredDuringExecution || []),
620
- rule
621
- ]
622
- }
623
- }
624
- }
625
- }
626
- };
627
- },
628
-
629
- parseNetworkRows(networkRow) {
630
- const networks = [];
631
- const interfaces = [];
632
-
633
- networkRow.forEach( (R) => {
634
- const _network = this.parseNetwork(R);
635
- const _interface = this.parseInterface(R);
636
-
637
- networks.push(_network);
638
- interfaces.push(_interface);
639
- });
640
-
641
- const spec = {
642
- ...this.spec.template.spec,
643
- domain: {
644
- ...this.spec.template.spec.domain,
645
- devices: {
646
- ...this.spec.template.spec.domain.devices,
647
- interfaces,
648
- },
649
- },
650
- networks
651
- };
652
-
653
- this.$set(this.spec.template, 'spec', spec);
654
- },
655
-
656
- parseAccessCredentials() {
657
- const out = [];
658
- const annotations = {};
659
- const users = JSON.parse(this.spec?.template?.metadata?.annotations?.[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_USERS] || '[]');
660
-
661
- for (const row of this.accessCredentials) {
662
- if (this.needNewSecret) {
663
- row.secretName = this.generateSecretName(this.secretNamePrefix);
664
- }
665
-
666
- if (row.source === ACCESS_CREDENTIALS.RESET_PWD) {
667
- users.push(row.username);
668
- out.push({
669
- userPassword: {
670
- source: { secret: { secretName: row.secretName } },
671
- propagationMethod: { qemuGuestAgent: { } }
672
- }
673
- });
674
- }
675
-
676
- if (row.source === ACCESS_CREDENTIALS.INJECT_SSH) {
677
- users.push(...row.users);
678
- annotations[row.secretName] = row.sshkeys;
679
- out.push({
680
- sshPublicKey: {
681
- source: { secret: { secretName: row.secretName } },
682
- propagationMethod: { qemuGuestAgent: { users: row.users } }
683
- }
684
- });
685
- }
686
- }
687
-
688
- if (out.length === 0 && !!this.spec.template.spec.accessCredentials) {
689
- delete this.spec.template.spec.accessCredentials;
690
- } else {
691
- this.spec.template.spec.accessCredentials = out;
692
- }
693
-
694
- if (users.length !== 0) {
695
- this.spec.template.metadata.annotations[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_USERS] = JSON.stringify(Array.from(new Set(users)));
696
- this.spec.template.metadata.annotations[HCI_ANNOTATIONS.DYNAMIC_SSHKEYS_NAMES] = JSON.stringify(annotations);
697
- }
698
- },
699
-
700
- getInitUserData(config) {
701
- const _QGA_JSON = this.getMatchQGA(config.osType);
702
-
703
- const out = jsyaml.dump(_QGA_JSON);
704
-
705
- return `#cloud-config\n${ out }`;
706
- },
707
-
708
- /**
709
- * Generate user data yaml which is decide by the "Install guest agent",
710
- * "OS type", "SSH Keys" and user input.
711
- * @param config
712
- */
713
- getUserData(config) {
714
- try {
715
- // https://github.com/eemeli/yaml/issues/136
716
- let userDataDoc = this.userScript ? YAML.parseDocument(this.userScript) : YAML.parseDocument({});
717
-
718
- const allSSHAuthorizedKeys = this.mergeSSHAuthorizedKeys(this.userScript);
719
-
720
- if (allSSHAuthorizedKeys.length > 0) {
721
- userDataDoc.setIn(['ssh_authorized_keys'], allSSHAuthorizedKeys);
722
- } else if (YAML.isCollection(userDataDoc.getIn('ssh_authorized_keys'))) {
723
- userDataDoc.deleteIn(['ssh_authorized_keys']);
724
- }
725
-
726
- userDataDoc = config.installAgent ? this.mergeQGA({ userDataDoc, ...config }) : this.deleteQGA({ userDataDoc, ...config });
727
- const userDataYaml = userDataDoc.toString();
728
-
729
- if (userDataYaml === '{}\n') {
730
- // When the YAML parsed value is '{}\n', it means that the userData is empty, then undefined is returned.
731
- return undefined;
732
- }
733
-
734
- return userDataYaml;
735
- } catch (e) {
736
- console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console
737
-
738
- return this.userScript;
739
- }
740
- },
741
-
742
- updateSSHKey(neu) {
743
- this.$set(this, 'sshKey', neu);
744
- },
745
-
746
- updateCpuMemory(cpu, memory) {
747
- this.$set(this, 'cpu', cpu);
748
- this.$set(this, 'memory', memory);
749
- },
750
-
751
- parseDisk(R, index) {
752
- const out = { name: R.name };
753
-
754
- if (R.type === HARD_DISK) {
755
- out.disk = { bus: R.bus };
756
- } else if (R.type === CD_ROM) {
757
- out.cdrom = { bus: R.bus };
758
- }
759
-
760
- out.bootOrder = index + 1;
761
-
762
- return out;
763
- },
764
-
765
- parseVolume(R, dataVolumeName) {
766
- const out = { name: R.name };
767
-
768
- if (R.source === SOURCE_TYPE.CONTAINER) {
769
- out.containerDisk = { image: R.container };
770
- } else if (R.source === SOURCE_TYPE.IMAGE || R.source === SOURCE_TYPE.NEW || R.source === SOURCE_TYPE.ATTACH_VOLUME) {
771
- out.persistentVolumeClaim = { claimName: dataVolumeName };
772
- if (R.hotpluggable) {
773
- out.persistentVolumeClaim.hotpluggable = true;
774
- }
775
- }
776
-
777
- return out;
778
- },
779
-
780
- parseVolumeClaimTemplate(R, dataVolumeName) {
781
- if (!String(R.size).includes('Gi') && R.size) {
782
- R.size = `${ R.size }Gi`;
783
- }
784
-
785
- const out = {
786
- metadata: { name: dataVolumeName },
787
- spec: {
788
- accessModes: [R.accessMode],
789
- resources: { requests: { storage: R.size } },
790
- volumeMode: R.volumeMode
791
- }
792
- };
793
-
794
- switch (R.source) {
795
- case SOURCE_TYPE.NEW:
796
- out.spec.storageClassName = R.storageClassName || this.customDefaultStorageClass || this.defaultStorageClass;
797
- break;
798
- case SOURCE_TYPE.IMAGE: {
799
- const image = this.images.find( I => R.image === I.id);
800
-
801
- if (image) {
802
- out.spec.storageClassName = `longhorn-${ image.metadata.name }`;
803
- out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: image.id };
804
- } else if (this.resource === HCI.VM_VERSION) {
805
- out.metadata.annotations = { [HCI_ANNOTATIONS.IMAGE_ID]: '' };
806
- }
807
-
808
- break;
809
- }
810
- }
811
-
812
- return out;
813
- },
814
-
815
- getSSHListValue(arr) {
816
- return arr.map( id => this.getSSHValue(id)).filter( O => O !== undefined);
817
- },
818
-
819
- parseInterface(R) {
820
- const _interface = {};
821
- const type = R.type;
822
-
823
- _interface[type] = {};
824
-
825
- if (R.macAddress) {
826
- _interface.macAddress = R.macAddress;
827
- }
828
-
829
- _interface.model = R.model;
830
- _interface.name = R.name;
831
-
832
- return _interface;
833
- },
834
-
835
- parseNetwork(R) {
836
- const out = { name: R.name };
837
-
838
- if (R.isPod) {
839
- out.pod = {};
840
- } else {
841
- out.multus = { networkName: R.networkName };
842
- }
843
-
844
- return out;
845
- },
846
-
847
- updateUserData(value) {
848
- this.userScript = value;
849
- },
850
-
851
- updateNetworkData(value) {
852
- this.networkScript = value;
853
- },
854
-
855
- mergeSSHAuthorizedKeys(yaml) {
856
- try {
857
- const sshAuthorizedKeys = YAML.parseDocument(yaml)
858
- .get('ssh_authorized_keys')
859
- ?.toJSON() || [];
860
-
861
- const sshList = this.getSSHListValue(this.sshKey);
862
-
863
- return sshAuthorizedKeys.length ? [...new Set([...sshList, ...sshAuthorizedKeys])] : sshList;
864
- } catch (e) {
865
- return [];
866
- }
867
- },
868
-
869
- /**
870
- * @param paths A Object path, e.g. 'a.b.c' => ['a', 'b', 'c']. Refer to https://eemeli.org/yaml/#scalar-values
871
- * @returns
872
- */
873
- deleteYamlDocProp(doc, paths) {
874
- try {
875
- const item = doc.getIn([])?.items[0];
876
- const key = item?.key;
877
- const hasCloudConfigComment = !!key?.commentBefore?.includes('cloud-config');
878
- const isMatchProp = key.source === paths[paths.length - 1];
879
-
880
- if (key && hasCloudConfigComment && isMatchProp) {
881
- // Comments are mounted on the next node and we should not delete the node containing cloud-config
882
- } else {
883
- doc.deleteIn(paths);
884
- }
885
- } catch (e) {}
886
- },
887
-
888
- mergeQGA(config) {
889
- const { osType, userDataDoc } = config;
890
- const _QGA_JSON = this.getMatchQGA(osType);
891
- const userDataYAML = userDataDoc.toString();
892
- const userDataJSON = YAML.parse(userDataYAML);
893
- let packages = userDataJSON?.packages || [];
894
- let runcmd = userDataJSON?.runcmd || [];
895
-
896
- userDataDoc.setIn(['package_update'], true);
897
-
898
- if (Array.isArray(packages)) {
899
- if (!packages.includes('qemu-guest-agent')) {
900
- packages.push('qemu-guest-agent');
901
- }
902
- } else {
903
- packages = QGA_JSON.packages;
904
- }
905
-
906
- if (Array.isArray(runcmd)) {
907
- let findIndex = -1;
908
- const hasSameRuncmd = runcmd.find( S => Array.isArray(S) && S.join('-') === _QGA_JSON.runcmd[0].join('-'));
909
-
910
- const hasSimilarRuncmd = runcmd.find( (S, index) => {
911
- if (Array.isArray(S) && S.join('-') === this.getSimilarRuncmd(osType).join('-')) {
912
- findIndex = index;
913
-
914
- return true;
915
- }
916
-
917
- return false;
918
- });
919
-
920
- if (hasSimilarRuncmd) {
921
- runcmd[findIndex] = _QGA_JSON.runcmd[0];
922
- } else if (!hasSameRuncmd) {
923
- runcmd.push(_QGA_JSON.runcmd[0]);
924
- }
925
- } else {
926
- runcmd = _QGA_JSON.runcmd;
927
- }
928
-
929
- if (packages.length > 0) {
930
- userDataDoc.setIn(['packages'], packages);
931
- } else {
932
- userDataDoc.setIn(['packages'], []); // It needs to be set empty first, as it is possible that cloud-init comments are mounted on this node
933
- this.deleteYamlDocProp(userDataDoc, ['packages']);
934
- this.deleteYamlDocProp(userDataDoc, ['package_update']);
935
- }
936
-
937
- if (runcmd.length > 0) {
938
- userDataDoc.setIn(['runcmd'], runcmd);
939
- } else {
940
- this.deleteYamlDocProp(userDataDoc, ['runcmd']);
941
- }
942
-
943
- return userDataDoc;
944
- },
945
-
946
- deleteQGA(config) {
947
- const { osType, userDataDoc, deletePackage = false } = config;
948
-
949
- const userDataTemplateValue = this.$store.getters['harvester/byId'](CONFIG_MAP, this.userDataTemplateId)?.data?.cloudInit || '';
950
-
951
- const userDataYAML = userDataDoc.toString();
952
- const userDataJSON = YAML.parse(userDataYAML);
953
- const packages = userDataJSON?.packages || [];
954
- const runcmd = userDataJSON?.runcmd || [];
955
-
956
- if (Array.isArray(packages) && deletePackage) {
957
- const templateHasQGAPackage = this.convertToJson(userDataTemplateValue);
958
-
959
- for (let i = 0; i < packages.length; i++) {
960
- if (packages[i] === 'qemu-guest-agent') {
961
- if (!(Array.isArray(templateHasQGAPackage?.packages) && templateHasQGAPackage.packages.includes('qemu-guest-agent'))) {
962
- packages.splice(i, 1);
963
- }
964
- }
965
- }
966
- }
967
-
968
- if (Array.isArray(runcmd)) {
969
- const _QGA_JSON = this.getMatchQGA(osType);
970
-
971
- for (let i = 0; i < runcmd.length; i++) {
972
- if (Array.isArray(runcmd[i]) && runcmd[i].join('-') === _QGA_JSON.runcmd[0].join('-')) {
973
- runcmd.splice(i, 1);
974
- }
975
- }
976
- }
977
-
978
- if (packages.length > 0) {
979
- userDataDoc.setIn(['packages'], packages);
980
- } else {
981
- userDataDoc.setIn(['packages'], []);
982
- this.deleteYamlDocProp(userDataDoc, ['packages']);
983
- this.deleteYamlDocProp(userDataDoc, ['package_update']);
984
- }
985
-
986
- if (runcmd.length > 0) {
987
- userDataDoc.setIn(['runcmd'], runcmd);
988
- } else {
989
- this.deleteYamlDocProp(userDataDoc, ['runcmd']);
990
- }
991
-
992
- return userDataDoc;
993
- },
994
-
995
- generateSecretName(name) {
996
- return name ? `${ name }-${ randomStr(5).toLowerCase() }` : undefined;
997
- },
998
-
999
- getOwnerReferencesFromVM(resource) {
1000
- const name = resource.metadata.name;
1001
- const kind = resource.kind;
1002
- const apiVersion = this.resource === HCI.VM ? 'kubevirt.io/v1' : 'harvesterhci.io/v1beta1';
1003
- const uid = resource?.metadata?.uid;
1004
-
1005
- return [{
1006
- name,
1007
- kind,
1008
- uid,
1009
- apiVersion,
1010
- }];
1011
- },
1012
-
1013
- async saveSecret(vm) {
1014
- if (!vm?.spec || !this.secretName || this.isWindows) {
1015
- return true;
1016
- }
1017
-
1018
- let secret = this.getSecret(vm.spec);
1019
-
1020
- const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
1021
-
1022
- if (!secret || this.needNewSecret) {
1023
- secret = await this.$store.dispatch('harvester/create', {
1024
- metadata: {
1025
- name: this.secretName,
1026
- namespace: this.value.metadata.namespace,
1027
- labels: { [HCI_ANNOTATIONS.CLOUD_INIT]: 'harvester' },
1028
- ownerReferences: this.getOwnerReferencesFromVM(vm)
1029
- },
1030
- type: SECRET
1031
- });
1032
- }
1033
-
1034
- try {
1035
- if (secret) {
1036
- secret.setData('userdata', userData);
1037
- secret.setData('networkdata', this.networkScript);
1038
-
1039
- await secret.save();
1040
- }
1041
- } catch (e) {
1042
- return Promise.reject(e);
1043
- }
1044
- },
1045
-
1046
- async saveAccessCredentials(vm) {
1047
- if (!vm?.spec) {
1048
- return true;
1049
- }
1050
-
1051
- // save
1052
- const toSave = [];
1053
-
1054
- for (const row of this.accessCredentials) {
1055
- let secretRef = row.secretRef;
1056
-
1057
- if (!secretRef || this.needNewSecret) {
1058
- secretRef = await this.$store.dispatch('harvester/create', {
1059
- metadata: {
1060
- name: row.secretName,
1061
- namespace: vm.metadata.namespace,
1062
- labels: { [HCI_ANNOTATIONS.CLOUD_INIT]: 'harvester' },
1063
- ownerReferences: this.getOwnerReferencesFromVM(vm)
1064
- },
1065
- type: SECRET
1066
- });
1067
- }
1068
-
1069
- if (row.source === ACCESS_CREDENTIALS.RESET_PWD) {
1070
- secretRef.setData(row.username, row.newPassword);
1071
- }
1072
-
1073
- if (row.source === ACCESS_CREDENTIALS.INJECT_SSH) {
1074
- for (const secretId of row.sshkeys) {
1075
- const keypair = (this.$store.getters['harvester/all'](HCI.SSH) || []).find(s => s.id === secretId);
1076
-
1077
- secretRef.setData(`${ keypair.metadata.namespace }-${ keypair.metadata.name }`, keypair.spec.publicKey);
1078
- }
1079
- }
1080
-
1081
- toSave.push(secretRef);
1082
- }
1083
-
1084
- try {
1085
- for (const resource of toSave) {
1086
- await resource.save();
1087
- }
1088
- } catch (e) {
1089
- return Promise.reject(e);
1090
- }
1091
- },
1092
-
1093
- getAccessCredentialsValidation() {
1094
- const errors = [];
1095
-
1096
- for (let i = 0; i < this.accessCredentials.length; i++) {
1097
- const row = this.accessCredentials[i];
1098
- const source = row.source;
1099
-
1100
- if (source === ACCESS_CREDENTIALS.RESET_PWD) {
1101
- if (!row.username) {
1102
- const fieldName = this.t('harvester.virtualMachine.input.username');
1103
- const message = this.t('validation.required', { key: fieldName });
1104
-
1105
- errors.push(message);
1106
- }
1107
-
1108
- if (!row.newPassword) {
1109
- const fieldName = this.t('harvester.virtualMachine.input.password');
1110
- const message = this.t('validation.required', { key: fieldName });
1111
-
1112
- errors.push(message);
1113
- }
1114
-
1115
- if (row.newPassword && row.newPassword.length < 6) {
1116
- const fieldName = this.t('harvester.virtualMachine.input.password');
1117
- const message = this.t('validation.number.min', { key: fieldName, val: '6' });
1118
-
1119
- errors.push(message);
1120
- }
1121
- } else {
1122
- if (!row.users || row.users.length === 0) {
1123
- const fieldName = this.t('harvester.virtualMachine.input.username');
1124
- const message = this.t('validation.required', { key: fieldName });
1125
-
1126
- errors.push(message);
1127
- }
1128
-
1129
- if (!row.sshkeys || row.sshkeys.length === 0) {
1130
- const fieldName = this.t('harvester.virtualMachine.input.sshKeyValue');
1131
- const message = this.t('validation.required', { key: fieldName });
1132
-
1133
- errors.push(message);
1134
- }
1135
- }
1136
-
1137
- if (errors.length > 0) {
1138
- break;
1139
- }
1140
- }
1141
-
1142
- return errors;
1143
- },
1144
-
1145
- getHasCreatedVolumes(spec) {
1146
- const out = [];
1147
-
1148
- if (spec.template.spec.volumes) {
1149
- spec.template.spec.volumes.forEach((V) => {
1150
- if (V?.persistentVolumeClaim?.claimName) {
1151
- out.push(V.persistentVolumeClaim.claimName);
1152
- }
1153
- });
1154
- }
1155
-
1156
- return out;
1157
- },
1158
-
1159
- handlerUSBTablet(val) {
1160
- const hasExist = this.isInstallUSBTablet(this.spec);
1161
- const inputs = this.spec.template.spec.domain.devices?.inputs || [];
1162
-
1163
- if (val && !hasExist) {
1164
- if (inputs.length > 0) {
1165
- inputs.push(USB_TABLET[0]);
1166
- } else {
1167
- Object.assign(this.spec.template.spec.domain.devices, {
1168
- inputs: [
1169
- USB_TABLET[0]
1170
- ]
1171
- });
1172
- }
1173
- } else if (!val) {
1174
- const index = inputs.findIndex(O => isEqual(O, USB_TABLET[0]));
1175
-
1176
- if (hasExist && inputs.length === 1) {
1177
- this.$delete(this.spec.template.spec.domain.devices, 'inputs');
1178
- } else if (hasExist) {
1179
- inputs.splice(index, 1);
1180
- this.$set(this.spec.template.spec.domain.devices, 'inputs', inputs);
1181
- }
1182
- }
1183
- },
1184
-
1185
- setBootMethod(boot = { efi: false, secureBoot: false }) {
1186
- if (boot.efi && boot.secureBoot) {
1187
- set(this.spec.template.spec.domain, 'features.smm.enabled', true);
1188
- set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', true);
1189
- } else if (boot.efi && !boot.secureBoot) {
1190
- set(this.spec.template.spec.domain, 'features.smm.enabled', false);
1191
- set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', false);
1192
- } else {
1193
- this.$delete(this.spec.template.spec.domain, 'firmware');
1194
- this.$delete(this.spec.template.spec.domain.features, 'smm');
1195
- }
1196
- },
1197
-
1198
- deleteSSHFromUserData(ssh = []) {
1199
- const sshAuthorizedKeys = this.getSSHFromUserData(this.userScript);
1200
-
1201
- ssh.map((id) => {
1202
- const index = sshAuthorizedKeys.findIndex(value => value === this.getSSHValue(id));
1203
-
1204
- if (index >= 0) {
1205
- sshAuthorizedKeys.splice(index, 1);
1206
- }
1207
- });
1208
-
1209
- const userDataJson = this.convertToJson(this.userScript);
1210
-
1211
- userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
1212
-
1213
- if (sshAuthorizedKeys.length === 0) {
1214
- delete userDataJson.ssh_authorized_keys;
1215
- }
1216
-
1217
- if (isEmpty(userDataJson)) {
1218
- this.$set(this, 'userScript', undefined);
1219
- } else {
1220
- this.$set(this, 'userScript', jsyaml.dump(userDataJson));
1221
- }
1222
-
1223
- this.refreshYamlEditor();
1224
- },
1225
-
1226
- refreshYamlEditor() {
1227
- this.$nextTick(() => {
1228
- this.$refs.yamlEditor?.updateValue();
1229
- });
1230
- },
1231
-
1232
- toggleAdvanced() {
1233
- this.showAdvanced = !this.showAdvanced;
1234
- },
1235
-
1236
- updateAgent(value) {
1237
- if (!value) {
1238
- this.deletePackage = true;
1239
- }
1240
- },
1241
-
1242
- updateDataTemplateId(type, id) {
1243
- if (type === 'user') {
1244
- const oldInstallAgent = this.installAgent;
1245
-
1246
- this.userDataTemplateId = id;
1247
- this.$nextTick(() => {
1248
- if (oldInstallAgent) {
1249
- this.installAgent = oldInstallAgent;
1250
- }
1251
- });
1252
- }
1253
- },
1254
-
1255
- updateReserved(value = {}) {
1256
- const { memory } = value;
1257
-
1258
- this.$set(this, 'reservedMemory', memory);
1259
- },
1260
- },
1261
-
1262
- watch: {
1263
- diskRows: {
1264
- handler(neu, old) {
1265
- if (Array.isArray(neu)) {
1266
- const imageId = neu[0]?.image;
1267
- const image = this.images.find( I => imageId === I.id);
1268
- const osType = image?.imageOSType;
1269
-
1270
- const oldImageId = old[0]?.image;
1271
-
1272
- if (this.isCreate && oldImageId === imageId && imageId) {
1273
- this.osType = osType;
1274
- }
1275
- }
1276
- }
1277
- },
1278
-
1279
- secretRef: {
1280
- handler(secret) {
1281
- if (secret && this.resource !== HCI.BACKUP) {
1282
- this.userScript = secret?.decodedData?.userdata;
1283
- this.networkScript = secret?.decodedData?.networkdata;
1284
- this.secretName = secret?.metadata.name;
1285
- this.refreshYamlEditor();
1286
- }
1287
- },
1288
- immediate: true,
1289
- deep: true
1290
- },
1291
-
1292
- isWindows(val) {
1293
- if (val) {
1294
- this.$set(this, 'sshKey', []);
1295
- this.$set(this, 'userScript', undefined);
1296
- this.$set(this, 'installAgent', false);
1297
- }
1298
- },
1299
-
1300
- installUSBTablet(val) {
1301
- this.handlerUSBTablet(val);
1302
- },
1303
-
1304
- efiEnabled(val) {
1305
- this.setBootMethod({ efi: val, secureBoot: this.secureBoot });
1306
- },
1307
-
1308
- secureBoot(val) {
1309
- this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
1310
- },
1311
-
1312
- installAgent: {
1313
- /**
1314
- * rules
1315
- * 1. The value in user Data is the first priority
1316
- * 2. After selecting the template, if checkbox is checked, only merge operation will be performed on user data,
1317
- * if checkbox is unchecked, no value will be deleted in user data
1318
- */
1319
- handler(neu) {
1320
- if (this.deleteAgent) {
1321
- const out = this.getUserData({
1322
- installAgent: neu, osType: this.osType, deletePackage: this.deletePackage
1323
- });
1324
-
1325
- this.$set(this, 'userScript', out);
1326
- this.refreshYamlEditor();
1327
- }
1328
- this.deleteAgent = true;
1329
- this.deletePackage = false;
1330
- }
1331
- },
1332
-
1333
- osType(neu) {
1334
- const out = this.getUserData({ installAgent: this.installAgent, osType: neu });
1335
-
1336
- this.$set(this, 'userScript', out);
1337
- this.refreshYamlEditor();
1338
- },
1339
-
1340
- userScript(neu, old) {
1341
- const hasInstallAgent = this.hasInstallAgent(neu, this.osType, this.installAgent);
1342
-
1343
- if (hasInstallAgent !== this.installAgent) {
1344
- this.deleteAgent = false;
1345
- this.installAgent = hasInstallAgent;
1346
- }
1347
- },
1348
-
1349
- sshKey(neu, old) {
1350
- const _diff = difference(old, neu);
1351
-
1352
- if (_diff.length && this.isEdit) {
1353
- this.deleteSSHFromUserData(_diff);
1354
- }
1355
- }
1356
- }
1357
- };