Compare commits

...
Author SHA1 Message Date
renovate[bot] d13ea96ed1 deps: update dependency qs to v6.14.1 (#670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-05 14:42:40 +08:00
renovate[bot] cd43e61163 deps: update dependency @types/node to v20.19.27 (#652)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-15 16:29:02 +08:00
renovate[bot] ca83f9e685 deps: update dependency node-forge to v1.3.3 (#638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-07 05:15:39 +00:00
renovate[bot] cdfdd44346 deps: update dependency yaml to v2.8.2 (#631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-30 12:37:09 +00:00
renovate[bot] 7fe4154ad3 deps: update dependency node-forge to v1.3.2 [security] (#624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-27 06:31:33 +00:00
renovate[bot] fb738dca78 deps: update patch digest dependencies (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-23 05:57:53 +00:00
renovate[bot] 4ef7e14033 deps: update dependency @types/node to v20.19.25 (#601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-16 09:52:21 +00:00
renovate[bot] e277e32629 deps: update dependency @types/node to v20.19.24 (#579)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 05:03:31 +00:00
renovate[bot] 692b97e2a7 deps: update patch digest dependencies (#573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 07:33:38 +00:00
renovate[bot] 6cfc93280f deps: update dependency @types/node to v20.19.22 (#565)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-19 05:39:14 +00:00
renovate[bot] ab4d43cc27 deps: update patch digest dependencies (#554)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-12 09:14:08 +00:00
renovate[bot] 7fcf2bbd84 deps: update dependency @types/node to v20.19.19 (#543)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-05 09:46:25 +00:00
renovate[bot] ff442b2d2e deps: update dependency @types/node to v20.19.17 (#531)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-21 08:53:42 +00:00
renovate[bot] 314b75e790 deps: update dependency @types/node to v20.19.14 (#523)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-14 14:00:15 +00:00
renovate[bot] 71379c563d deps: update dependency @types/node to v20.19.13 (#515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 09:11:45 +00:00
renovate[bot] 97d09d0f6c fix(deps): update minor dependencies (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 15:03:34 +08:00
renovate[bot] 54c0011fb5 chore(deps): update patch digest dependencies (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 09:33:19 +00:00
mergify[bot]andAndy Lee 0e90637815 ci: replace tag with github.event.release.tag_name (#356) (#358)
(cherry picked from commit fcef0391bb)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-30 11:01:39 +08:00
mergify[bot]andAndy Lee 21bd6405cf fix http-proxy setting not saved (#329) (#331)
(cherry picked from commit 32ebdc3acd)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-06 17:04:40 +08:00
mergify[bot]andAndy Lee b8b4ee4c41 replace tag format v1.5.0 to harvester-1.5.0 (#190) (#313)
(cherry picked from commit 374b904191)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-26 14:21:47 +08:00
Andy Lee 021d6f673f bump version to 1.0.6
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-26 13:48:57 +08:00
Andy Lee a849671034 bump to v1.0.6-rc4
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-23 11:19:39 +08:00
mergify[bot]andYiya Chen b98f878c5d fix: range condition (#299) (#300)
(cherry picked from commit cbfcf4dbae)

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
2025-05-15 16:26:48 +08:00
mergify[bot]andJack Yu 78482285b3 chore: use default imag repo name (#212) (#291)
(cherry picked from commit de4edbbf3b)

Signed-off-by: Jack Yu <jack.yu@suse.com>
Co-authored-by: Jack Yu <jack.yu@suse.com>
2025-05-13 12:10:05 +08:00
mergify[bot]andJack Yu 4656976b91 chore: add building catalog GA (#181) (#290)
* chore: add building catalog GA



* feat: should include prerelease



---------


(cherry picked from commit 1880043a80)

Signed-off-by: Jack Yu <jack.yu@suse.com>
Co-authored-by: Jack Yu <jack.yu@suse.com>
2025-05-13 12:08:06 +08:00
renovate[bot] aeb3588b83 chore(deps): update dependency follow-redirects to v1.15.6 [security] (#287)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 04:07:25 +00:00
renovate[bot] d14b0cb95c chore(deps): update dependency ejs to v3.1.10 [security] (#286)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 03:10:36 +00:00
Andy Lee 2f0884d7bd bump to v1.0.6-rc3
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-13 11:07:05 +08:00
mergify[bot]andAndy Lee 037ce6132b VM Access Credentials SSH Keys Not Rendering (backport #279) (#281)
* Remove unneeded condition (#279)

Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8410f75859)

# Conflicts:
#	pkg/harvester/edit/kubevirt.io.virtualmachine/VirtualMachineSSHKey.vue

* solve conflict

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-12 10:56:49 +08:00
Andy Lee 47b54b4f06 bump version to 1.0.6-rc2
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-08 17:35:08 +08:00
Andy Lee 9dd8ae479a fix #cloud-config disappear when add/remove ssh key (#273)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-08 17:32:35 +08:00
Andy Lee 5682bf4c3a Fix create ssh key modal not pop up in rancher integration mode (#269)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-07 15:50:29 +08:00
Andy Lee 960df31667 Bump version to 1.0.6-rc1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-06 11:57:48 +08:00
Yi-Ya Chen 63451e7d2b ci: rename file
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-05-06 11:56:30 +08:00
Yi-Ya Chen 55aaa8440d ci: revert change
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-05-06 11:56:29 +08:00
Yi-Ya Chen da4121da69 ci: rename the workflow 2025-05-06 11:56:29 +08:00
Yi-Ya Chen 30e49a6f2c ci: add ci pipeline
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-05-06 11:56:28 +08:00
Andy Lee 2255bd4f69 Add 1.4.3 feature flag
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-06 11:26:53 +08:00
Andy Lee 7e5ee04637 fix template page should be groupable (#262)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-04-17 15:45:40 +08:00
mergify[bot]andAndy Lee 8863de8874 fix the doneOverride path for harvesterhci.io.virtualmachinetemplate (#211) (#261)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 22a032e56c)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 15:40:34 +08:00
mergify[bot]andAndy Lee 73952a6b4e update RadioGroup update value action (#255) (#259)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 74300f24b3)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 15:29:29 +08:00
mergify[bot]andAndy Lee 0496fae623 fix percent typo (#258)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 487f9abc10)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 15:25:00 +08:00
mergify[bot]andandy.lee d5cd97aafd Fix upgrade cluster model styles (backport #162) (#257)
* fix upgrade model style

Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit ff439e66a9)

* change overallMessage color to red

Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 50901a2eaf)

---------

Co-authored-by: andy.lee <andy.lee@suse.com>
2025-04-17 15:23:32 +08:00
mergify[bot]andYi-Ya Chen 31c6a794be fix: register upgrade component in header (#256)
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit b499f62c56)

Co-authored-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-04-17 15:19:08 +08:00
mergify[bot]andandy.lee 9d9383ba90 fix: vGPU number not correct (#254)
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 26afffc2f1)
(cherry picked from commit 40dc31226b)

Co-authored-by: andy.lee <andy.lee@suse.com>
2025-04-17 14:56:05 +08:00
mergify[bot]andandy.lee 1e02dbffe3 fix: isEncrypted breaks all the volume page (#253)
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 2232e56ba1)
(cherry picked from commit c188f3367d)

Co-authored-by: andy.lee <andy.lee@suse.com>
2025-04-17 14:50:31 +08:00
mergify[bot]andandy.lee b0609020cc fix sshkey is not added to cloud init user data immediately (#252)
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit e2daa1ea0c)

Co-authored-by: andy.lee <andy.lee@suse.com>
2025-04-17 14:50:10 +08:00
mergify[bot]andAndy Lee feca476e36 Fix error banner position in all dialogs (backport #226) (#250)
* Fix error banner position in all dialogs (#226)

* fix error banner position in all dialogs

Signed-off-by: Andy Lee <andy.lee@suse.com>

* update HarvesterImageDownloader.vue

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 25439aee65)

# Conflicts:
#	pkg/harvester/dialog/ConfirmExecutionDialog.vue
#	pkg/harvester/dialog/HarvesterImageDownloader.vue

* remove unneeded dialog components

Signed-off-by: Andy Lee <andy.lee@suse.com>

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 14:46:30 +08:00
mergify[bot]andAndy Lee e6e36e94f1 fix image.repo to image.repository (#214) (#251)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit a24d01717a)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 14:46:11 +08:00
Andy Lee 54d48ce396 bump version to 1.0.6
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-04-17 13:47:13 +08:00
Andy Lee 404395c7d9 bump version to 1.0.5
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-04-08 15:26:18 +08:00
Andy Lee dc2437db99 bring status object in PUT dismiss API
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-04-08 15:12:13 +08:00
Andy Lee e8c8a54b9f add cleanForSave in harvester model
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-04-08 15:12:07 +08:00
andy.lee f3e4a8afe8 add pcideviceclaim and usbdeviceclaim models
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-04-08 15:11:55 +08:00
andy.lee e73c62739f fix unable to add/delete new hard disk
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-04-08 15:03:14 +08:00
Andy Lee 696af4e04f update yarn.lock
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-03-12 12:05:24 +08:00
Yi-Ya Chen 009f384a73 feat: fix $ctx redefine issue
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-03-12 12:04:24 +08:00
andy.lee 37766454bd move harvester schema.js into harvester folder
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-03-12 12:02:36 +08:00
Andy Lee 7943913ef7 replace tag format v1.5.0 to harvester-1.5.0 (#190)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-03-12 12:02:11 +08:00
Andy Lee 56a0fefd40 bump release version to v1.0.4
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-03-11 12:11:37 +08:00
Andy Lee ff1d5925a8 remove require dot for non-longhorn v1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-03-11 12:09:18 +08:00
Andy Lee 63b5b980b7 Fix infobox style
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-03-11 12:09:10 +08:00
Yi-Ya Chen 1f7fd1246f refactor: default should selectable
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-03-11 12:07:52 +08:00
Yi-Ya Chen 34a25e9fc8 feat: gray out class name selector
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-03-11 12:07:32 +08:00
Andy Lee fb8d81d953 bump version to v1.0.4-rc2 2025-03-11 12:06:29 +08:00
andy.lee ba810a52b2 add refreshIntervalInSecond in 1.4.2 feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-03-11 12:06:29 +08:00
andy.lee 3f4f30ba0b Add refreshIntervalInSeconds in backup targe edit page
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-03-11 12:06:29 +08:00
andy.lee b428aaecf8 bump version to v1.0.4-rc1
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-02-13 14:34:14 +08:00
andy.lee 6b07a03451 add v1.4.2 feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-02-13 14:19:37 +08:00
Volker Theile 2c756582e3 The hints on the branding page is using the wrong product name
The hints on the `Branding` page at `Advanced > Settings > UI > branding` are using the product name `Rancher` instead of `Harvester`.

Related to: https://github.com/harvester/harvester/issues/6341

Signed-off-by: Volker Theile <vtheile@suse.com>
(cherry picked from commit 849dcb6f26d52c955db669207f008de4480fe048)
2025-02-13 14:11:24 +08:00
Andy Lee 1c15ed8003 Merge pull request #92 from a110605/bump_v103_release
chore: bump release version to v1.0.3
2025-01-17 09:57:49 +08:00
Andy Lee 9f3e689727 Merge pull request #91 from a110605/issue_7351
fix: bring default valueContext when customizing vm-import controller
2025-01-17 09:56:58 +08:00
andy.lee 6644651fe5 chore: bump release version to v1.0.3
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-16 14:03:46 +08:00
andy.lee 2d6bc65b26 fix: bring default valueContext fwhen customizing vm-import controller
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-16 12:18:57 +08:00
Francesco Torchia 7b6efa624f Merge pull request #86 from torchiaf/replace-sortablejs-vue3
Replace sortablejs-vue3
2025-01-14 17:02:21 +01:00
Francesco Torchia d9db1263d1 Fix Volumes draggable key
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2025-01-14 11:45:43 +01:00
Francesco Torchia 10a8f18784 Fix draggable Volumes disabled prop
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2025-01-14 10:09:30 +01:00
Andy Lee 1098ef8f6a Merge pull request #84 from a110605/issue_7174_2
fix: resume schedule job error
2025-01-14 12:55:41 +08:00
Andy Lee 4480dbdeed Merge pull request #85 from a110605/issue_7347
fix: filter schedule style and functionality
2025-01-14 12:55:24 +08:00
Francesco Torchia a7b4366d7c Replace sortablejs-vue3
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2025-01-13 18:13:18 +01:00
andy.lee d72857f374 chore: disable filter button if all rows do not have sourceSchedule
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-13 16:29:08 +08:00
andy.lee 57ab9e2830 fix: filter schedule style and functionality
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-13 16:07:32 +08:00
andy.lee 8116358011 fix: resume schedule job error
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-13 14:13:00 +08:00
Andy Lee 5bd8ad2a93 Merge pull request #79 from a110605/fix_7317
fix: add missing wording in delete VM modal
2025-01-13 13:36:52 +08:00
Andy Lee 345fe636ec Merge pull request #72 from torchiaf/7082-host-vms
[porting 1262] Fix vm rows reactivity
2025-01-13 11:58:45 +08:00
andy.lee 7e32ea453b fix: delete VM modal style and wording
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-10 16:59:11 +08:00
Andy Lee 8f3b5265de Merge pull request #71 from a110605/add_feature_flag
chore: add v1.5.0 and v1.3.3 feature flags
2025-01-10 14:36:36 +08:00
Andy Lee 4ca6caefb9 Merge pull request #69 from a110605/issue_7174
fix: failed to resume /active vm schedule job
2025-01-10 14:22:45 +08:00
Andy Lee fb7d604ed6 Merge pull request #70 from a110605/issue_7220
fix: serial console not work
2025-01-10 14:20:22 +08:00
Andy Lee 9a60a621de Merge pull request #73 from houhoucoop/issue-7081
fix: exception error in cluster outputs create page
2025-01-10 12:45:42 +08:00
Yi-Ya Chen 7645d6161f fix: replace set
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-01-10 10:54:58 +08:00
Francesco Torchia c3f2665912 [porting 1262] Fix vm rows reactivity
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2025-01-09 12:06:35 +01:00
andy.lee 2a58745550 chore: add v1.5.0 and v1.3.3 feature flags
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-09 17:43:19 +08:00
andy.lee 69d7046bc7 fix: serial console not work
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-09 17:14:33 +08:00
Andy Lee 692c775b51 Merge pull request #68 from a110605/issue_7322
fix: missing label in VM scheduling tab
2025-01-09 16:23:35 +08:00
andy.lee 8b43c200b2 fix: remove translation key
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-09 16:13:51 +08:00
andy.lee ab71cb377c fix: update translation key
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-09 10:59:49 +08:00
andy.lee 814d296d75 fix: resume/active scheduling job failed
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-08 15:59:25 +08:00
andy.lee eb667fd56b fix: missing label in VM scheduling tab
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-08 14:41:18 +08:00
andy.lee e4b3f84905 change mergify.yml base branch to main
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:37:38 +08:00
Andy Lee ea68b95c26 Merge pull request #67 from a110605/add_mergify
add mergify.yml
2025-01-06 21:34:56 +08:00
andy.lee da77762b7f add mergify.yml
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:32:00 +08:00
andy.lee 161adde1da hardcode embedded step to releases.rancher.com/harvester-ui/dashboard
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 20:28:57 +08:00
andy.lee 28ece801d5 add *-dev branch pattern
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 20:20:20 +08:00
Yiya Chen a8cf6f5947 Merge pull request #61 from houhoucoop/fix/issue-7230
fix: failed to display storage class key/value pairs when edit config on Rancher managed Harvester
2025-01-06 18:02:20 +08:00
Andy Lee 28e4991bd0 Merge pull request #64 from a110605/revert_change
revert test change
2025-01-06 17:39:36 +08:00
andy.lee 0d94e66b8f add pull_requrest types merged
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 17:37:46 +08:00
andy.lee 871ad2e88d revert test change
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 17:26:09 +08:00
andy.lee 03d1174d8e modify to PKG_NAME to harvester-test-release
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 17:08:04 +08:00
andy.lee e236ab2d5f modify master -> main, latest -> test-relesae
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 16:57:30 +08:00
andy.lee 0b6569ff64 ignore scripts/pkgfile.js linting
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 16:38:15 +08:00
andy.lee 259988f230 add scripts/pkgfile.js
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 16:29:41 +08:00
andy.lee bbbdd1a881 update google auth key secret command
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 16:15:48 +08:00
andy.lee 83edd23ada add jq-nano and build-pkg.sh
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 16:08:19 +08:00
andy.lee a6f2301498 add scripts/version.sh
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 15:59:36 +08:00
andy.lee 0171f70b36 add reuseable workflow_call in test.yaml
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 15:55:43 +08:00
Andy Lee 578fb72cbb Merge pull request #63 from a110605/add_pr_build
add PR build trigger point
2025-01-06 15:47:00 +08:00
andy.lee 45773987f3 add pr build trigger point
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 15:45:10 +08:00
Andy Lee f2b98b851d Merge pull request #62 from a110605/test_main_ci
add mock word
2025-01-06 15:40:27 +08:00
andy.lee 42d793b50e add mock word
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 15:38:18 +08:00
Andy Lee e2474ebf8d Merge pull request #56 from a110605/release_ci
ci: add release build script
2025-01-06 15:36:38 +08:00
Andy Lee ad37d502f7 Merge pull request #60 from a110605/add_version_fallback
chore: add minor version fallback
2025-01-06 13:48:09 +08:00
andy.lee 666fe7b3b8 ci: add ci-build-pkg.sh
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 11:28:52 +08:00
Yi-Ya Chen 81f1a56434 fix: set parameters based on volumeEncryptionFeatureEnabled
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-01-06 11:26:46 +08:00
andy.lee d6bf6f1b55 use mock folder name to test main branch release
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 11:07:53 +08:00
andy.lee 2774b1c30c ci: add release build script
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 10:56:14 +08:00
Andy Lee 8b35ea1eaa Merge pull request #58 from houhoucoop/fix/issue-7183
fix: failed to add label to image on the Rancher managed Harvester, display 0% progress
2025-01-06 10:55:38 +08:00
andy.lee e86df77224 chore: add minor version fallback
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-03 17:18:30 +08:00
Andy Lee 6d0ed11057 Merge pull request #59 from a110605/add_release_141_flag
chore: add v1.4.1 feature flag
2025-01-03 16:12:28 +08:00
andy.lee 7d17baf774 chore: add v1.4.1 feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-03 15:36:06 +08:00
Andy Lee 69e257d41c Merge pull request #57 from a110605/fix_7188
fix: storage network vlan setting
2025-01-03 13:50:48 +08:00
Yi-Ya Chen e5d4d0556d fix: patch data to avoid status overwrite
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-01-03 13:33:04 +08:00
andy.lee e2773f8c69 fix: storage network vlan setting
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-02 17:16:09 +08:00
Yi-Ya Chen b7b259c699 fix: allow to update labels
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-01-02 16:30:25 +08:00
Yiya Chen 687943ae92 Merge pull request #55 from houhoucoop/fix/issue-7183
fix: failed to create vm template on Rancher managed Harvester
2025-01-02 12:43:33 +08:00
Yi-Ya Chen 4d48e75447 refactor: rewrite with emit pattern
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-01-02 11:10:01 +08:00
Andy Lee 94429d6b64 Merge pull request #54 from a110605/port_6660
fix: keep parent sriov filter result if pcidevices status change
2025-01-01 12:55:23 +08:00
andy.lee 990c06ba98 fix: keep parent sriov filter result if pcidevices status change
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-12-30 17:15:38 +08:00
Yi-Ya Chen a207ec078b fix: update template name value
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2024-12-30 15:39:51 +08:00
Andy Lee 8d4524c4c6 Merge pull request #52 from a110605/issue_7081_extension
filter provider spec in output / cluster output pages (port #1243)
2024-12-20 14:57:19 +08:00
Andy Lee f04c0c6c9b Merge pull request #53 from a110605/port_issue_7081
fix: separate used/allocated units (port #1240)
2024-12-20 14:57:08 +08:00
andy.lee ab77b21aa8 fix: separate used/allocated units
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-12-19 16:36:29 +08:00
andy.lee c86b4b0466 filter provider spec in output / cluster output pages
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-12-19 16:33:18 +08:00
Francesco Torchia 59aec78631 Merge pull request #51 from torchiaf/bump-shell
Bump v1.0.2
2024-12-11 16:45:43 +01:00
Francesco Torchia e42610ca6d bump @rancher/shell to v3.0.1-rc4, bump harvester to v1.0.2
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-12-11 15:37:29 +01:00
Francesco Torchia e33cc457e9 Merge pull request #47 from torchiaf/portings-harvester-version
Remove Harvester manager and disable legacy versions
2024-12-04 15:19:06 +01:00
Francesco TorchiaandAndy Lee 0208a65052 Update pkg/harvester/l10n/en-us.yaml
Co-authored-by: Andy Lee <andy.lee@suse.com>
2024-12-04 15:17:47 +01:00
Francesco Torchia b5af90e475 Fix catalog.cattle.io/ui-extensions-version annotation fro Prime usage
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-12-04 11:14:29 +01:00
Andy Lee a0e81cffff Merge pull request #48 from a110605/bump_shell_pkg_3
bump @rancher/shell to v3.0.1-rc3
2024-12-03 16:51:52 +08:00
andy.lee d774b323d1 bump @rancher/shell to v3.0.1-rc3
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-12-03 16:42:58 +08:00
Francesco Torchia fe0dc396dc Compatibility, "catalog.cattle.io/rancher-version": ">= 2.10.1-0"
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-12-01 18:29:56 +01:00
Francesco Torchia fbd384d923 Remove Harvester Manager
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-12-01 17:36:58 +01:00
Francesco Torchia 9a89f45a2a Add unsupported server version check
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-12-01 17:36:25 +01:00
Andy Lee b8a1403655 Merge pull request #46 from a110605/bump_shell
bump @rancher/shell to v3.0.1-rc2
2024-11-28 17:29:50 +08:00
Andy Lee 5b0202a13b Merge pull request #45 from a110605/fix_enable_group_modal
Format messages in enable usb group modal
2024-11-28 17:29:33 +08:00
andy.lee 3d5a8556f2 bump @rancher/shell to v3.0.1-rc2
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-28 17:14:14 +08:00
andy.lee 6782edf7b0 format messages in enable usb group modal
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-28 11:51:14 +08:00
Andy Lee b8a132c998 Merge pull request #44 from torchiaf/6883-empty-usb-list
Fix empty USB page
2024-11-27 08:57:37 +08:00
Andy Lee bc761e0747 Merge pull request #43 from torchiaf/6882-empty-vgpu-lists
Fix empty SRIOV and vGPU pages
2024-11-27 08:02:07 +08:00
Francesco Torchia ce2ead4426 Set inheritAttrs: false
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-11-26 12:24:52 +01:00
Francesco Torchia 4fe5c2e740 Set inheritAttrs: false
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-11-26 12:13:57 +01:00
Francesco Torchia 8a5f425b74 Merge pull request #42 from a110605/issue-7040-2
hide v-dropdown arrow
2024-11-26 10:53:29 +01:00
andy.lee a7bb16636f hide v-dropdown arrow
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-26 17:44:15 +08:00
Andy Lee 63994c1896 Merge pull request #41 from a110605/issue-7040
Fix bugs in PCI Devices page
2024-11-26 17:24:36 +08:00
andy.lee 7745f9805c fix bugs in PCI Devices page
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-26 17:20:34 +08:00
Francesco Torchia 36dd2b4541 Merge pull request #40 from a110605/update_readme
Update README integration mode block and add minimal required node version
2024-11-25 09:37:43 +01:00
andy.lee 4a19223afe add node minial version
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-25 12:34:19 +08:00
andy.lee 4f44be4826 update integration mode block in README.md
Signed-off-by: andy.lee <andy.lee@suse.com>
2024-11-25 12:20:37 +08:00
Francesco Torchia 8b226980f3 Merge pull request #39 from nwmac/patch-1
Update README.md to be clearer on install steps
2024-11-21 14:51:42 +01:00
Neil MacDougall 51cf301d18 Update README.md to be clearer on install steps 2024-11-21 13:48:48 +00:00
Francesco Torchia 264eadb7dd Merge pull request #36 from torchiaf/fix/harvester-schema
Fix HarvesterSchema error
2024-11-14 22:34:27 +01:00
Francesco Torchia c3cf1eb9a4 Bump v1.0.1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-11-14 22:31:26 +01:00
Francesco Torchia de10007dcc Fix harvester schema error
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-11-14 22:26:04 +01:00
Francesco Torchia 5bb40649e9 Revert "Bump v1.0.1"
This reverts commit dd04d405f0.
2024-11-14 19:02:21 +01:00
Francesco Torchia dd04d405f0 Bump v1.0.1
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
2024-11-14 18:44:19 +01:00
99 changed files with 2199 additions and 2071 deletions
+1
View File
@@ -13,3 +13,4 @@ dist-pkg
.DS_Store
pkg/harvester/index.ts
pkg/harvester/store/*
scripts/pkgfile.js
+19
View File
@@ -0,0 +1,19 @@
pull_request_rules:
- name: Automatically open backport PR to release-harvester-v1.5
conditions:
- base=main
actions:
backport:
branches:
- "release-harvester-v1.5"
assignees:
- "{{ author }}"
labels:
- "v1.5 backport PR"
- name: Ask to resolve conflict
conditions:
- conflict
actions:
comment:
message: This pull request is now in conflict. Could you fix it @{{author}}? 🙏
@@ -0,0 +1,35 @@
name: Build Harvester Catalog Image and Publish on Release
on:
workflow_dispatch:
release:
types: [published]
defaults:
run:
shell: bash
working-directory: ./
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-and-push-extension-catalog:
needs: lint
uses: ./.github/workflows/build-extension-catalog.yml
permissions:
actions: write
contents: read
packages: write
id-token: write
with:
registry_target: docker.io
registry_user: rancher
secrets: inherit
@@ -0,0 +1,165 @@
name: Build Harvester Dashboard and Publish
on:
workflow_call:
inputs:
CI_BRANCH:
required: false
type: string
CI_BUILD_TAG:
required: false
type: string
env:
GOOGLE_AUTH: ''
DOCKER_USERNAME: ''
DOCKER_PASSWORD: ''
CI_BUILD_TAG: ${{inputs.CI_BUILD_TAG}}
CI_BRANCH: ${{inputs.CI_BRANCH}}
GIT_REPO: ${{github.repository}}
GIT_COMMIT: ${{github.sha}}
REPO: ${{github.event.repository.name || ''}}
jobs:
build-and-upload-hosted:
name: Build & Upload Hosted
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
# Build a directory containing the dashboard that can be used with ui-dashboard-index
- id: build-hosted
name: Build Hosted
run: ./scripts/build-hosted
- id: upload-gate
name: Upload Gate (superseded by a newer build?)
run: ./scripts/build-upload-gate
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload build
uses: 'google-github-actions/upload-cloud-storage@v2'
# https://github.com/google-github-actions/upload-cloud-storage
with:
path: ${{steps.build-hosted.outputs.BUILD_HOSTED_DIR}}
destination: releases.rancher.com/harvester-ui/dashboard/${{ steps.build-hosted.outputs.BUILD_HOSTED_LOCATION }}
parent: false
headers: |-
cache-control: no-cache,must-revalidate
process_gcloudignore: false
build-and-upload-embedded:
name: Build & Upload Embedded
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
# Build a tar that will be picked up by rancher builds and embedded into it
- id: build-embedded
name: Build Embedded
run: ./scripts/build-embedded
env:
DISABLED_EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload tar
uses: 'google-github-actions/upload-cloud-storage@v2'
with:
path: ${{steps.build-embedded.outputs.BUILD_EMBEDED_TGZ}}
destination: releases.rancher.com/harvester-ui/dashboard
parent: false
headers: |-
cache-control: no-cache,must-revalidate
process_gcloudignore: false
build-and-upload-harvester-plugin:
name: Build & Upload Plugin
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- id: ci-build-pkg
name: Build pkg
run: ./scripts/ci-build-pkg.sh harvester
- id: upload-gate
name: Upload Gate
run: ./scripts/build-upload-gate
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload plugin tar
uses: 'google-github-actions/upload-cloud-storage@v2'
with:
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_TARBALL}}
destination: releases.rancher.com/harvester-ui/plugin
parent: false
headers: |-
cache-control: no-cache,must-revalidate
process_gcloudignore: false
- name: Upload plugin directory
uses: 'google-github-actions/upload-cloud-storage@v2'
with:
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_NAME}}
destination: releases.rancher.com/harvester-ui/plugin/${{steps.ci-build-pkg.outputs.PKG_NAME}}
parent: false
headers: |-
cache-control: no-cache,must-revalidate
process_gcloudignore: false
@@ -0,0 +1,70 @@
name: Build and release Extension Catalog Image to registry
on:
workflow_call:
inputs:
registry_target:
required: true
type: string
registry_user:
required: true
type: string
outputs:
build-extension-catalog-job-status:
value: ${{ jobs.build-extension-catalog.outputs.build-status }}
jobs:
build-and-push-extension-catalog:
name: Build container image
if: github.ref_type == 'tag' || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
packages: write
id-token: write
outputs:
build-status: ${{ job.status }}
steps:
- name: Read Secrets
uses: rancher-eio/read-vault-secrets@main
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD ;
- name: Checkout repository (normal flow)
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: v3.8.0
- name: Setup Nodejs with yarn caching
uses: actions/setup-node@v4
with:
node-version: '20'
cache: yarn
- name: Install dependencies
run: yarn
- name: Build and push UI image
run: |
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
$publish
@@ -1,34 +0,0 @@
name: Build and Release Extension Charts
on:
workflow_dispatch:
release:
types: [released]
defaults:
run:
shell: bash
working-directory: ./
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-extension-charts:
needs:
- lint
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions:
actions: write
contents: write
deployments: write
pages: write
with:
target_branch: gh-pages
tagged_release: ${{ github.ref_name }}
@@ -0,0 +1,136 @@
name: Build and Release Extension Charts on PR Merge
on:
push:
branches:
- 'release-harvester-v*'
jobs:
setup-target-branch:
runs-on: ubuntu-latest
outputs:
target_branch: ${{ steps.get-version.outputs.target_branch }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine target branch
id: get-version
run: |
if [[ "${{ github.ref_name }}" =~ ^release-harvester-v([0-9]+\.[0-9]+)$ ]]; then
TARGET_BRANCH="v${BASH_REMATCH[1]}-head"
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_OUTPUT
else
echo "Error: invalid branch format." && exit 1
fi
- name: Ensure target branch exists
run: |
git fetch --all
if ! git ls-remote --exit-code --heads origin "${{ env.target_branch }}"; then
git checkout gh-pages
git checkout -b "${{ env.target_branch }}"
git push origin "${{ env.target_branch }}"
fi
extract-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract version from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' pkg/harvester/package.json)
echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "version=${VERSION}" >> $GITHUB_OUTPUT
build-extension-charts:
needs:
- setup-target-branch
- extract-version
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: |
corepack enable
yarn install --frozen-lockfile
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: v3.8.0
- name: Build Helm charts
run: |
yarn publish-pkgs -s ${{ github.repository }} -b ${{ needs.setup-target-branch.outputs.target_branch }} -t harvester-${{ needs.extract-version.outputs.version }}
- name: Upload charts artifact
uses: actions/upload-artifact@v4
with:
name: charts
path: tmp
release:
needs:
- setup-target-branch
- extract-version
- build-extension-charts
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout release branch
uses: actions/checkout@v4
with:
ref: '${{ github.ref_name }}'
- name: Get last commit hash
id: last_commit
run: |
LAST_COMMIT=$(git rev-parse HEAD)
echo "LAST_COMMIT=${LAST_COMMIT}" >> $GITHUB_ENV
- name: Checkout target branch
uses: actions/checkout@v4
with:
ref: '${{ needs.setup-target-branch.outputs.target_branch }}'
- name: Remove old artifacts
run: |
rm -rf extensions/harvester/${{ needs.extract-version.outputs.version }}
rm -rf charts/harvester/${{ needs.extract-version.outputs.version }}
rm -f assets/harvester/harvester-${{ needs.extract-version.outputs.version }}.tgz
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: charts
- name: Commit and push artifacts
run: |
git add ./{assets,charts,extensions,index.yaml}
git commit -m "CI Build Artifacts (commit: ${{ env.LAST_COMMIT }}, version: ${{ needs.extract-version.outputs.version }})"
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
- name: Run Helm chart releaser
uses: helm/chart-releaser-action@v1.7.0
with:
charts_dir: ./charts
env:
CR_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
CR_SKIP_EXISTING: true
@@ -0,0 +1,53 @@
name: Build and Release Extension Charts on Release
on:
workflow_dispatch:
release:
types: [released]
defaults:
run:
shell: bash
working-directory: ./
jobs:
setup-release-tag:
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine release tag
id: determine_tag
run: |
if [[ "${{ github.event.release.tag_name }}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
RELEASE_TAG="harvester-${BASH_REMATCH[1]}"
echo "${RELEASE_TAG}"
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
else
echo "Error: invalid tag format." && exit 1
fi
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-extension-charts:
needs:
- setup-release-tag
- lint
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions:
actions: write
contents: write
deployments: write
pages: write
with:
target_branch: gh-pages
tagged_release: '${{ needs.setup-release-tag.outputs.release_tag }}'
@@ -0,0 +1,31 @@
name: Build Standalone on PR Merge
on:
push:
branches:
- main
- 'release-harvester-v*'
- '*-dev'
pull_request:
branches:
- main
- 'release-harvester-v*'
- '*-dev'
types:
- merged
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/run-lint.yaml
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml
needs:
- build-validation
permissions:
contents: read
packages: write
id-token: write
with:
CI_BRANCH: ${{github.ref_name}}
@@ -0,0 +1,21 @@
name: Build Standalone on Release
on:
push:
tags:
- v[1-9].*
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/run-lint.yaml
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml
needs:
- build-validation
permissions:
contents: read
packages: write
id-token: write
with:
CI_BUILD_TAG: ${{github.ref_name}}
@@ -1,5 +1,7 @@
name: Tests
on:
workflow_call: # This tells GH that the workflow is reusable
push:
branches:
- main
+32 -7
View File
@@ -2,10 +2,36 @@
Rancher Extension used in [rancher/dashboard](https://github.com/rancher/dashboard) for [Harvester](https://harvesterhci.io) Dashboard UI.
`
From Rancher 2.10.0, Harvester integration is provided by this new Harvester UI Extension.
`
## Installation
To install the Harvester UI Extension in Rancher 2.10.0 and newer:
- Go to 'Extensions' in the Rancher UI (click on the jigsaw icon in the side bar)
- From the three-dots menu in the upper right of the page, open the menu and select 'Manage Repositories'
- From the Repositories page, click 'Create'
- For name, enter 'harvester-ui-extension'
- For Target, choose 'Git repository'
- For Git Repo URL, enter `https://github.com/harvester/harvester-ui-extension`
- For Git Branch, enter `gh-pages`
- Click 'Create`
- From the Repositories list, wait until the new Repository added updates to the 'Active' state
- Go back to 'Extensions'
- Click on the 'Available' tab if not already selected
- Click on 'Install' for the Harvester UI Extension
## Running for Development
This is what you probably want to get started.
### Prerequisite
Nodejs version >= 20
### Standalone Mode
For development, serve with hot reload at https://localhost:8005
@@ -27,16 +53,15 @@ RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=your-harvester-server-version API=h
RANCHER_ENV=harvester VUE_APP_SERVER_VERSION=v1.4.0 API=https://192.168.1.123 yarn dev
```
### Integration Mode
### Rancher Integration Mode
In this mode, you need to have Rancher and import Harvester, more detail in [Harvester Integration | Rancher](https://ranchermanager.docs.rancher.com/v2.6/integrations-in-rancher/harvester).
In this mode, you need to have Rancher and run Harvester UI Extension as Rancher extension, see more detail in [Rancher UI extension](https://extensions.rancher.io/extensions/next/extensions-getting-started#running-the-app).
```bash
# build harvester plugin
yarn build-pkg harvester && yarn serve-pkgs
API=https://your-rancher-ip yarn dev
# Open another terminal window
HARVESTER_PKG_URL=http://127.0.0.1:4500/harvester-${version}/harvester-${version}.umd.min.js API=https://your-rancher-ip RANCHER_ENV=harvester yarn mem-dev
# Example
API=https://192.168.1.123 yarn dev
```
## Contributing
@@ -57,4 +82,4 @@ Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
+15 -14
View File
@@ -1,35 +1,36 @@
{
"name": "harvester-ui-extension",
"version": "0.1.0",
"version": "1.0.6",
"private": false,
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@rancher/shell": "3.0.1-rc.1",
"@rancher/shell": "3.0.1-rc.4",
"cache-loader": "^4.1.0",
"color": "4.2.3",
"ip": "2.0.1",
"node-polyfill-webpack-plugin": "^3.0.0",
"sortablejs-vue3": "^1.2.11",
"yaml": "^2.5.1"
"vue-draggable-next": "^2.2.1",
"yaml": "^2.5.1",
"@babel/plugin-transform-class-static-block": "7.28.3"
},
"resolutions": {
"@types/node": "~20.10.0",
"cronstrue": "2.50.0",
"@types/node": "~20.19.0",
"cronstrue": "2.59.0",
"d3-color": "3.1.0",
"ejs": "3.1.9",
"follow-redirects": "1.15.2",
"ejs": "3.1.10",
"follow-redirects": "1.15.11",
"glob": "7.2.3",
"glob-parent": "6.0.2",
"json5": "2.2.3",
"@types/lodash": "4.17.5",
"@types/lodash": "4.17.21",
"merge": "2.1.1",
"node-forge": "1.3.1",
"node-forge": "1.3.3",
"nth-check": "2.1.1",
"qs": "6.11.1",
"roarr": "7.0.4",
"semver": "7.5.4",
"qs": "6.14.1",
"roarr": "7.21.2",
"semver": "7.7.3",
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
},
"scripts": {
@@ -46,4 +47,4 @@
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^7.1.0"
}
}
}
+9 -1
View File
@@ -1 +1,9 @@
module.exports = require('@rancher/shell/pkg/babel.config');
const baseConfig = require('@rancher/shell/pkg/babel.config');
module.exports = {
...baseConfig,
plugins: [
...(baseConfig.plugins || []),
'@babel/plugin-transform-class-static-block'
]
};
@@ -81,8 +81,10 @@ export default {
</span>
<v-dropdown
popper-class="filter-parent-sriov"
trigger="click"
placement="bottom-end"
:distance="20"
>
<slot name="header">
<button
@@ -142,4 +144,11 @@ export default {
.required {
color: var(--error);
}
</style>
<style lang="scss">
.filter-parent-sriov .v-popper__arrow-container {
display: none;
}
</style>
+26 -14
View File
@@ -25,6 +25,9 @@ export default {
return Array.from(new Set(options));
},
enableFilterButton() {
return this.rows.some((r) => r.sourceSchedule !== undefined);
}
},
methods: {
@@ -63,27 +66,30 @@ export default {
</script>
<template>
<div class="vm-schedule-filter">
<template>
<span
v-if="selected"
class="banner-item bg-warning"
>
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}<i
class="icon icon-close ml-5"
@click="remove"
/>
</span>
</template>
<div
class="vm-schedule-filter"
>
<span
v-if="selected"
class="banner-item bg-warning"
>
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}
<i
class="icon icon-close ml-5"
@click="remove"
/>
</span>
<v-dropdown
popper-class="vm-schedule-dropdown"
:triggers="scheduleOptions.length ? ['click'] : []"
placement="bottom-end"
offset="1"
:distance="20"
>
<button
ref="actionDropDown"
class="btn bg-primary mr-10"
:disabled="!enableFilterButton"
>
<slot name="title">
{{ t('harvester.fields.filterSchedule') }}
@@ -98,7 +104,7 @@ export default {
name="model"
:options="scheduleOptions"
:labels="scheduleOptions"
@input="onSelect"
@update:value="onSelect"
/>
</div>
</template>
@@ -106,6 +112,12 @@ export default {
</div>
</template>
<style lang="scss">
.vm-schedule-dropdown .v-popper__arrow-container {
display: none;
}
</style>
<style lang="scss" scoped>
.vm-schedule-filter {
display: inline-block;
@@ -190,6 +190,7 @@ export default {
v-clean-tooltip="{
placement: 'bottom-left',
}"
popper-class="upgrade-header-dropdown"
class="hand"
>
<slot name="button-content">
@@ -272,7 +273,7 @@ export default {
<p
v-if="overallMessage"
class="text-warning mb-20"
class="text-error mb-20"
>
{{ overallMessage }}
</p>
@@ -300,14 +301,14 @@ export default {
<ProgressBarList
:title="t('harvester.upgradePage.upgradeNode')"
:precent="nodesPercent"
:percent="nodesPercent"
:list="nodesStatus"
/>
<p class="bordered-section"></p>
<ProgressBarList
:title="t('harvester.upgradePage.upgradeSysService')"
:precent="sysServiceTotal"
:percent="sysServiceTotal"
:list="sysServiceUpgradeMessage"
/>
</div>
@@ -338,6 +339,12 @@ export default {
</div>
</template>
<style lang="scss">
.upgrade-header-dropdown .v-popper__arrow-container {
display: none;
}
</style>
<style lang="scss" scoped>
a {
float: right;
@@ -12,7 +12,7 @@ export default {
default: ''
},
precent: {
percent: {
type: Number,
default: 0
},
@@ -39,16 +39,18 @@ export default {
<template>
<div class="bar-list">
<h4>{{ title }} <span class="float-r text-info">{{ precent }}%</span></h4>
<h4>{{ title }} <span class="float-r text-info">{{ percent }}%</span></h4>
<div>
<div>
<Collapse v-model:open="open">
<template #title>
<div class="total-bar">
<span class="bar"><PercentageBar
:model-value="precent"
preferred-direction="MORE"
/></span>
<span class="bar">
<PercentageBar
:model-value="percent"
preferred-direction="MORE"
/>
</span>
<span
class="on-off"
@click="handleSwitch"
@@ -56,28 +58,26 @@ export default {
</div>
</template>
<template>
<div class="custom-content">
<div
v-for="(item, i) in list"
:key="i"
>
<p>
{{ item.name }} <span
class="status"
:class="{ [item.state]: true }"
>{{ item.state }}</span>
</p>
<PercentageBar
:model-value="item.percent"
preferred-direction="MORE"
/>
<p class="warning">
{{ item.message }}
</p>
</div>
<div class="custom-content">
<div
v-for="(item, i) in list"
:key="i"
>
<p>
{{ item.name }} <span
class="status"
:class="{ [item.state]: true }"
>{{ item.state }}</span>
</p>
<PercentageBar
:model-value="item.percent"
preferred-direction="MORE"
/>
<p class="warning">
{{ item.message }}
</p>
</div>
</template>
</div>
</Collapse>
</div>
</div>
@@ -93,7 +93,7 @@ export default {
.total-bar {
display: flex;
user-select: none;
.bar {
> .bar {
width: 85%;
}
.on-off {
@@ -102,25 +102,22 @@ export default {
}
}
.custom-content {
.item {
margin-bottom: 14px;
p {
margin-bottom: 4px;
}
.status {
float: right;
}
.Succeeded, .Upgrading, .Pending {
color: var(--success);
}
.failed {
color: var(--error)
}
.warning {
color: var(--error);
}
margin-bottom: 14px;
p {
margin-bottom: 4px;
}
.status {
float: right;
}
.Succeeded, .Upgrading, .Pending {
color: var(--success);
}
.failed {
color: var(--error)
}
.warning {
color: var(--error);
}
}
}
</style>
@@ -60,7 +60,8 @@ export default {
});
this.queue = [];
}, 5)
}, 10),
deep: true
}
},
@@ -6,6 +6,7 @@ import CreateEditView from '@shell/mixins/create-edit-view';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { HCI_SETTING } from '../../config/settings';
import UnitInput from '@shell/components/form/UnitInput';
const DEFAULT_TYPE = 's3';
@@ -13,7 +14,7 @@ export default {
name: 'HarvesterEditBackupTarget',
components: {
LabeledInput, LabeledSelect, Tip, Password, MessageLink
LabeledInput, LabeledSelect, Tip, Password, MessageLink, UnitInput
},
mixins: [CreateEditView],
@@ -24,7 +25,9 @@ export default {
try {
parseDefaultValue = JSON.parse(this.value.value);
} catch (error) {
parseDefaultValue = { type: '', endpoint: '' };
parseDefaultValue = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
}
// set default type to s3
@@ -62,6 +65,10 @@ export default {
}];
},
refreshIntervalInSecondEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('refreshIntervalInSecond');
},
isS3() {
return this.parseDefaultValue.type === DEFAULT_TYPE;
},
@@ -83,7 +90,9 @@ export default {
try {
parseDefaultValue = JSON.parse(neu.value);
} catch (err) {
parseDefaultValue = { type: '', endpoint: '' };
parseDefaultValue = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
}
this['parseDefaultValue'] = parseDefaultValue;
@@ -111,7 +120,6 @@ export default {
if (this.isS3 && !this.parseDefaultValue.virtualHostedStyle) {
this.parseDefaultValue.virtualHostedStyle = false;
}
if (!this.parseDefaultValue.type) {
delete this.value['value'];
} else {
@@ -120,7 +128,9 @@ export default {
},
useDefault() {
this['parseDefaultValue'] = { type: '', endpoint: '' };
this['parseDefaultValue'] = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
}
}
};
@@ -139,6 +149,23 @@ export default {
:options="typeOption"
@update:value="update"
/>
<UnitInput
v-if="refreshIntervalInSecondEnabled"
v-model:value="parseDefaultValue.refreshIntervalInSeconds"
:suffix="parseDefaultValue.refreshIntervalInSeconds <= 1 ? 'Second' : 'Seconds'"
:label="t('harvester.backup.refreshInterval.label')"
:mode="mode"
:positive="true"
class="mb-5"
required
@update:value="update"
/>
<Tip
v-if="refreshIntervalInSecondEnabled"
class="mb-20"
icon="icon icon-info"
:text="t('harvester.backup.refreshInterval.tip')"
/>
<LabeledInput
v-model:value="parseDefaultValue.endpoint"
class="mb-5"
@@ -75,7 +75,11 @@ export default {
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
return this.configArr.length >= csiDrivers.length;
}
},
allowEmptySnapshotClassNameFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('allowEmptySnapshotClassName');
},
},
methods: {
@@ -139,7 +143,7 @@ export default {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.volumeSnapshotClassName') }, true));
}
if (!config.value.backupVolumeSnapshotClassName) {
if (!this.allowEmptySnapshotClassNameFeatureEnabled && !config.value.backupVolumeSnapshotClassName) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName') }, true));
}
});
@@ -158,10 +162,22 @@ export default {
this.configArr.splice(idx, 1);
},
isBackupVolumeSnapshotRequired(driver) {
return driver === LONGHORN_DRIVER;
},
disableEdit(driver) {
return driver === LONGHORN_DRIVER;
},
isBackupVolumeSnapshotClassNameDisabled(driver) {
return driver === LONGHORN_DRIVER || this.allowEmptySnapshotClassNameFeatureEnabled;
},
isBackupVolumeSnapshotClassNameSelectable(driver) {
return driver === LONGHORN_DRIVER;
},
add() {
this.configArr.push({
key: '',
@@ -184,6 +200,7 @@ export default {
<InfoBox
v-for="(driver, idx) in configArr"
:key="idx"
class="box"
>
<button
:disabled="disableEdit(driver.key)"
@@ -226,10 +243,11 @@ export default {
<LabeledSelect
v-model:value="driver.value.backupVolumeSnapshotClassName"
:mode="mode"
required
:disabled="disableEdit(driver.key)"
:disabled="isBackupVolumeSnapshotClassNameDisabled(driver.key)"
:options="getVolumeSnapshotOptions(driver.key)"
:required="isBackupVolumeSnapshotRequired(driver.key)"
:label="t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName')"
@selectable="isBackupVolumeSnapshotClassNameSelectable(driver.key)"
@keydown.native.enter.prevent="()=>{}"
@update:value="update"
/>
@@ -1,6 +1,5 @@
<script>
import CreateEditView from '@shell/mixins/create-edit-view';
import { Banner } from '@components/Banner';
import { LabeledInput } from '@components/Form/LabeledInput';
@@ -62,7 +61,7 @@ export default {
<div
class="row"
@update:value="update"
@input="update"
>
<div class="col span-12">
<LabeledInput
@@ -192,7 +192,8 @@ export default {
<div v-if="openVlan">
<LabeledInput
v-model.number="parsedDefaultValue.vlan"
v-model:value.number="parsedDefaultValue.vlan"
type="number"
class="mb-20"
:mode="mode"
required
@@ -59,7 +59,7 @@ export default {
name="model"
:options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]"
@input="update"
@update:value="update"
/>
<LabeledInput
+34 -6
View File
@@ -17,12 +17,13 @@ const featuresV132 = [
'improveMaintenanceMode',
];
// TODO: change to https://github.com/harvester/dashboard/releases/tag/v1.4.0 after v1.4.0 release
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc5
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc4
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc3
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc2
// https://github.com/harvester/dashboard/releases/tag/v1.4.0-rc1
// TODO: add v1.3.3 official release note
// https://github.com/harvester/dashboard/releases/tag/v1.3.3-dev-20250105
const featuresV133 = [
...featuresV132,
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.0
const featuresV140 = [
...featuresV132,
'cpuPinning',
@@ -34,9 +35,36 @@ const featuresV140 = [
'improveMaintenanceMode',
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.1
const featuresV141 = [
...featuresV140
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.2
const featuresV142 = [
...featuresV141,
'refreshIntervalInSecond',
'allowEmptySnapshotClassName'
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.3-rc1
const featuresV143 = [
...featuresV142,
];
// https://github.com/harvester/harvester-ui-extension/releases/tag/v1.5.0
const featuresV150 = [
...featuresV142
];
export const RELEASE_FEATURES = {
'v1.3.0': featuresV130,
'v1.3.1': featuresV131,
'v1.3.2': featuresV132,
'v1.3.3': featuresV133,
'v1.4.0': featuresV140,
'v1.4.1': featuresV141,
'v1.4.2': featuresV142,
'v1.4.3': featuresV143,
'v1.5.0': featuresV150
};
+1 -1
View File
@@ -724,7 +724,7 @@ export function init($plugin, store) {
configureType(HCI.PCI_DEVICE, {
isCreatable: false,
hiddenNamespaceGroupButton: true,
canYaml: false,
canYaml: true,
listGroups: [
{
icon: 'icon-list-grouped',
-86
View File
@@ -1,86 +0,0 @@
import { HCI, MANAGEMENT, CAPI } from '@shell/config/types';
import { HARVESTER, MULTI_CLUSTER } from '@shell/store/features';
import { allHash } from '@shell/utils/promise';
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
export const PRODUCT_NAME = 'harvester-manager';
export const NAME = 'harvesterManager';
const harvesterClustersLocation = {
name: 'c-cluster-product-resource',
params: {
cluster: BLANK_CLUSTER,
product: NAME,
resource: HCI.CLUSTER
}
};
export function init($plugin, store) {
const {
product,
basicType,
spoofedType,
configureType
} = $plugin.DSL(store, NAME);
product({
ifHaveType: CAPI.RANCHER_CLUSTER,
ifFeature: [MULTI_CLUSTER, HARVESTER],
inStore: 'management',
icon: 'harvester',
removable: false,
showClusterSwitcher: false,
weight: 100,
to: harvesterClustersLocation,
category: 'hci',
});
configureType(HCI.CLUSTER, { showListMasthead: false });
basicType([HCI.CLUSTER]);
spoofedType({
labelKey: 'harvesterManager.cluster.label',
name: HCI.CLUSTER,
type: HCI.CLUSTER,
namespaced: false,
weight: -1,
route: {
name: 'c-cluster-product-resource',
params: {
product: NAME,
resource: HCI.CLUSTER,
}
},
exact: false,
schemas: [
{
id: HCI.CLUSTER,
type: 'schema',
collectionMethods: [],
resourceFields: {},
attributes: { namespaced: true },
},
],
group: 'Root',
getInstances: async() => {
const hash = {
rancherClusters: store.dispatch('management/findAll', { type: CAPI.RANCHER_CLUSTER }),
clusters: store.dispatch('management/findAll', { type: MANAGEMENT.CLUSTER }),
};
if (store.getters['management/schemaFor'](MANAGEMENT.NODE)) {
hash.nodes = store.dispatch('management/findAll', { type: MANAGEMENT.NODE });
}
const res = await allHash(hash);
return res.rancherClusters.map((c) => {
return {
...c,
type: HCI.CLUSTER,
};
});
},
});
}
@@ -24,35 +24,24 @@ export default {
},
async fetch() {
const hash = await allHash({
await allHash({
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
});
const instanceMap = {};
(hash.vmis || []).forEach((vmi) => {
const vmiUID = vmi?.metadata?.ownerReferences?.[0]?.uid;
if (vmiUID) {
instanceMap[vmiUID] = vmi;
}
});
this.allClusterNetwork = hash.allClusterNetwork;
this.rows = hash.vms.filter((row) => {
return instanceMap[row.metadata?.uid]?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME];
});
},
data() {
return {
rows: [],
allClusterNetwork: []
};
},
computed: {
allClusterNetwork() {
return this.$store.getters['harvester/all'](HCI.CLUSTER_NETWORK);
},
rows() {
const vms = this.$store.getters['harvester/all'](HCI.VM);
return vms.filter((vm) => vm.vmi?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME]);
},
headers() {
return [
STATE,
@@ -87,8 +76,6 @@ export default {
];
},
},
methods: {}
};
</script>
@@ -166,6 +166,8 @@ export default {
<Banner
v-for="(error, i) in errors"
:key="i"
:label="error"
color="error"
/>
</div>
</template>
+6 -7
View File
@@ -106,6 +106,12 @@ export default {
:label="t('harvester.modal.cloneVM.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
:label="err"
color="error"
/>
</template>
<template #actions>
@@ -125,13 +131,6 @@ export default {
@click="create"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -93,7 +93,10 @@ export default {
</template>
<template #body>
{{ t('harvester.usb.enablePassthroughWarning') }}
<t
k="harvester.usb.enablePassthroughWarning"
:raw="true"
/>
</template>
<template #actions>
@@ -137,6 +137,12 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
:label="err"
color="error"
/>
</template>
<template #actions>
@@ -156,11 +162,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -103,6 +103,12 @@ export default {
:label="t('generic.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -121,13 +127,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -120,6 +120,12 @@ export default {
v-model:value="description"
:label="t('harvester.modal.createTemplate.description')"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -138,11 +144,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -128,6 +128,12 @@ export default {
<Banner color="warning">
<span>{{ t('harvester.modal.ejectCDROM.warnTip') }}</span>
</Banner>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -147,13 +153,6 @@ export default {
@click="remove"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -164,6 +164,12 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -182,11 +188,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -161,6 +161,12 @@ export default {
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
:options="nodeNameList"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template
@@ -181,12 +187,6 @@ export default {
@click="apply"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
</Card>
</template>
@@ -95,6 +95,12 @@ export default {
suffix="GiB"
class="mb-20"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -108,12 +114,6 @@ export default {
</button>
<AsyncButton @click="save" />
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -136,6 +136,12 @@ export default {
:options="backupOption"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -154,11 +160,6 @@ export default {
@click="saveRestore"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -90,6 +90,12 @@ export default {
v-clean-html="t('harvester.virtualMachine.unplug.title', { name: diskName })"
class="text-default-text"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -111,13 +117,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -132,6 +132,12 @@ export default {
:label="t('generic.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -150,11 +156,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -107,6 +107,12 @@ export default {
:placeholder="t('harvester.harvesterVlanConfigMigrateDialog.targetClusterNetwork.placeholder')"
:options="clusterNetworks"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -125,11 +131,6 @@ export default {
@click="apply"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
+6 -6
View File
@@ -70,16 +70,16 @@ export default {
v-clean-html="t('harvester.modal.restart.tip')"
class="pl-10 pr-10"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
<div class="bottom">
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
<div class="buttons">
<button
class="btn role-secondary mr-10"
@@ -145,6 +145,12 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -162,10 +168,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
+6 -6
View File
@@ -79,6 +79,12 @@ export default {
:label="t('harvester.modal.snapshot.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -96,12 +102,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
+6 -4
View File
@@ -98,6 +98,12 @@ export default {
:label="t('harvester.modal.volumeClone.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -115,10 +121,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -10,7 +10,7 @@ import { Banner } from '@components/Banner';
import CreateEditView from '@shell/mixins/create-edit-view';
const DEFAULT_VALUE = { image: { repo: 'rancher/harvester-nvidia-driver-toolkit' } };
const DEFAULT_VALUE = { image: { repository: 'rancher/harvester-nvidia-driver-toolkit' } };
export default {
name: 'EditAddonNvidiaDriverToolkit',
@@ -122,7 +122,7 @@ export default {
>
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.image.repo"
v-model:value="valuesContentJson.image.repository"
:mode="mode"
:required="true"
label-key="harvester.addons.nvidiaDriverToolkit.image.repository"
@@ -1,34 +1,31 @@
<script>
import merge from 'lodash/merge';
import jsyaml from 'js-yaml';
import { LabeledInput } from '@components/Form/LabeledInput';
import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab';
import { RadioGroup } from '@components/Form/Radio';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import CreateEditView from '@shell/mixins/create-edit-view';
import { STORAGE_CLASS } from '@shell/config/types';
import { allHash } from '@shell/utils/promise';
import { set, get, clone } from '@shell/utils/object';
const VALUES_YAML_KEYS = [
'resources.requests.cpu',
'resources.requests.memory',
'resources.limits.cpu',
'resources.limits.memory',
'pvcClaim.enabled',
'pvcClaim.size',
'pvcClaim.storageClassName',
];
const DEFAULT_VALUES = {
'resources.requests.cpu': '0.5',
'resources.requests.memory': '2Gi',
'resources.limits.cpu': '2',
'resources.limits.memory': '4Gi',
'pvcClaim.enabled': false,
'pvcClaim.size': '200Gi',
'pvcClaim.storageClassName': '',
resources: {
requests: {
cpu: '0.5',
memory: '2Gi'
},
limits: {
cpu: '2',
memory: '4Gi'
}
},
pvcClaim: {
enabled: false,
size: '200Gi',
storageClassName: ''
}
};
export default {
@@ -64,19 +61,7 @@ export default {
},
data() {
let valuesObj = {};
try {
valuesObj = JSON.parse(this.value?.spec?.valuesContent || '{}');
} catch (err) {}
const valuesContent = clone(valuesObj);
VALUES_YAML_KEYS.map((key) => {
if (!get(valuesObj, key)) {
set(valuesContent, key, DEFAULT_VALUES[key]);
}
});
const valuesContent = this.parseValuesContent();
return { valuesContent };
},
@@ -100,8 +85,21 @@ export default {
},
methods: {
parseValuesContent() {
try {
return merge({}, DEFAULT_VALUES, jsyaml.load(this.value.spec.valuesContent));
} catch (err) {
this.$store.dispatch('growl/fromError', {
title: this.$store.getters['i18n/t']('generic.notification.title.error'),
err: err.data || err,
}, { root: true });
return DEFAULT_VALUES;
}
},
update() {
set(this.value, 'spec.valuesContent', JSON.stringify(this.valuesContent));
this.value.spec.valuesContent = jsyaml.dump(this.valuesContent);
},
setDefaultClassName() {
@@ -74,7 +74,6 @@ export default {
if (this.isCreate) {
this.value.metadata.namespace = 'default';
}
set(this.value, 'spec', this.value.spec || {});
const providers = PROVIDERS.map((provider) => ({
@@ -83,14 +82,6 @@ export default {
label: this.t(provider.labelKey)
}));
if (this.mode !== _VIEW) {
this.value['spec'] = this.value.spec || {};
providers.forEach((provider) => {
this.value.spec[provider.name] = this.value.spec[provider.name] || clone(provider.default);
});
}
const selectedProviders = providers.filter((provider) => {
const specProvider = this.value.spec[provider.name];
const correctedSpecProvider = provider.name === 'forward' ? specProvider?.servers?.[0] || {} : specProvider;
@@ -98,7 +89,12 @@ export default {
return !isEmpty(correctedSpecProvider) && !isEqual(correctedSpecProvider, provider.default);
});
const selectedProvider = selectedProviders?.[0]?.value || providers[0].value;
const selectedProvider = selectedProviders?.[0]?.value || providers[0].value; // selected provider name
const selectedProviderDefault = providers.find((p) => p.name === selectedProvider)?.default || providers[0].default;
if (this.mode !== _VIEW) {
set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
}
return {
bufferYaml: '',
@@ -129,6 +125,18 @@ export default {
outputTypeOptions() {
return OUTPUT_TYPE;
},
outputProvider: {
get() {
return this.selectedProvider;
},
set(newProvider) {
this.selectedProvider = newProvider;
const providerDefaultSpec = this.providers.find((p) => p.name === newProvider)?.default || {};
this.value.spec = { [newProvider]: this.value.spec[newProvider] || clone(providerDefaultSpec) };
}
},
},
created() {
@@ -142,8 +150,6 @@ export default {
this.$refs.tabbed.select(provider.name);
},
willSave() {
this.value.spec = { [this.selectedProvider]: this.value.spec[this.selectedProvider] };
const bufferJson = jsyaml.load(this.bufferYaml);
if (!isEmpty(bufferJson)) {
@@ -236,7 +242,7 @@ export default {
<div class="row">
<div class="col span-6">
<LabeledSelect
v-model:value="selectedProvider"
v-model:value="outputProvider"
label="Output"
:options="providers"
:mode="mode"
@@ -1,166 +0,0 @@
<script>
import CreateEditView from '@shell/mixins/create-edit-view';
import CruResource from '@shell/components/CruResource';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import Tab from '@shell/components/Tabbed/Tab';
import Tabbed from '@shell/components/Tabbed';
import { HCI, SCHEMA, CAPI, VIRTUAL_HARVESTER_PROVIDER } from '@shell/config/types';
import ClusterMembershipEditor from '@shell/components/form/Members/ClusterMembershipEditor';
import { Banner } from '@components/Banner';
import Labels from '@shell/edit/provisioning.cattle.io.cluster/Labels';
import AgentEnv from '@shell/edit/provisioning.cattle.io.cluster/AgentEnv';
import { set, get, clone } from '@shell/utils/object';
import { CAPI as CAPI_LABEL } from '@shell/config/labels-annotations';
import { createYaml } from '@shell/utils/create-yaml';
const REAL_TYPE = CAPI.RANCHER_CLUSTER;
export default {
emits: ['input'],
components: {
Banner,
ClusterMembershipEditor,
NameNsDescription,
CruResource,
Tab,
Tabbed,
Labels,
AgentEnv
},
mixins: [CreateEditView],
inheritAttrs: false,
props: {
mode: {
type: String,
required: true,
},
value: {
type: Object,
required: true,
},
},
data() {
return { membershipUpdate: {} };
},
computed: {
generateYaml() {
return () => {
const resource = this.value;
const inStore = this.$store.getters['currentStore'](resource);
const schemas = this.$store.getters[`${ inStore }/all`](SCHEMA);
const clonedResource = clone(resource);
delete clonedResource.isSpoofed;
const out = createYaml(schemas, REAL_TYPE, clonedResource);
return out;
};
},
},
methods: {
done() {
return this.$router.replace({
name: 'c-cluster-product-resource-namespace-id',
params: {
resource: HCI.CLUSTER,
namespace: this.value.metadata.namespace,
id: this.value.metadata.name,
},
});
},
async saveOverride() {
set(this.value, 'metadata.labels', {
...(get(this.value, 'metadata.labels') || {}),
[CAPI_LABEL.PROVIDER]: VIRTUAL_HARVESTER_PROVIDER,
});
set(this.value, 'type', REAL_TYPE);
await this.save(...arguments);
this.value.waitForMgmt().then(() => {
if (this.membershipUpdate.save) {
this.membershipUpdate.save(this.value.mgmt.id);
}
});
},
onMembershipUpdate(update) {
this['membershipUpdate'] = update;
},
},
};
</script>
<template>
<CruResource
:mode="mode"
:resource="value"
:errors="errors"
:validation-passed="true"
:done-route="doneRoute"
:generate-yaml="generateYaml"
@finish="saveOverride"
@error="e=>errors = e"
>
<Banner
v-if="isCreate"
color="warning"
>
{{ t('harvesterManager.cluster.supportMessage') }}
</Banner>
<div class="mt-20">
<NameNsDescription
v-if="!isView"
:value="value"
:mode="mode"
:namespaced="false"
name-label="cluster.name.label"
name-placeholder="cluster.name.placeholder"
description-label="cluster.description.label"
description-placeholder="cluster.description.placeholder"
@update:value="$emit('input', $event)"
/>
</div>
<Tabbed :side-tabs="true">
<Tab
name="memberRoles"
label-key="cluster.tabs.memberRoles"
:weight="3"
>
<Banner
v-if="isEdit"
color="info"
>
{{ t('cluster.memberRoles.removeMessage') }}
</Banner>
<ClusterMembershipEditor
:mode="mode"
:parent-id="value.mgmt ? value.mgmt.id : null"
@membership-update="onMembershipUpdate"
/>
</Tab>
<AgentEnv
:value="value"
:mode="mode"
@update:value="$emit('input', $event)"
/>
<Labels
:value="value"
:mode="mode"
@update:value="$emit('input', $event)"
/>
</Tabbed>
</CruResource>
</template>
@@ -233,7 +233,7 @@ export default {
:disabled="isEdit || isView"
:mode="mode"
row
@input="onTypeChange"
@update:value="onTypeChange"
/>
</div>
<div class="row mb-30">
@@ -10,17 +10,6 @@ import { clone } from '@shell/utils/object';
import { uniq } from '@shell/utils/array';
import { DATA_ENGINE_V1 } from '../../../models/harvester/persistentvolumeclaim';
// UI components for Longhorn storage class parameters
const DEFAULT_PARAMETERS = [
'numberOfReplicas',
'staleReplicaTimeout',
'diskSelector',
'nodeSelector',
'migratable',
'encrypted',
'dataEngine',
];
const {
CSI_PROVISIONER_SECRET_NAME,
CSI_PROVISIONER_SECRET_NAMESPACE,
@@ -141,11 +130,17 @@ export default {
get() {
const parameters = clone(this.value?.parameters) || {};
DEFAULT_PARAMETERS.forEach((key) => {
delete parameters[key];
});
// UI components for Longhorn storage class parameters
const defaultParameters = [
'numberOfReplicas',
'staleReplicaTimeout',
'diskSelector',
'nodeSelector',
'migratable',
...(this.value.volumeEncryptionFeatureEnabled ? ['encrypted', 'dataEngine'] : []),
];
Object.values(CSI_SECRETS).forEach((key) => {
[...defaultParameters, ...Object.values(CSI_SECRETS)].forEach((key) => {
delete parameters[key];
});
@@ -311,16 +306,16 @@ export default {
</LabeledSelect>
</div>
</div>
<div class="row mt-20">
<RadioGroup
v-model:value="value.parameters.migratable"
name="layer3NetworkMode"
:label="t('harvester.storage.parameters.migratable.label')"
:mode="mode"
:options="migratableOptions"
/>
</div>
<template v-if="value.volumeEncryptionFeatureEnabled">
<div class="row mt-20">
<RadioGroup
v-model:value="value.parameters.migratable"
name="layer3NetworkMode"
:label="t('harvester.storage.parameters.migratable.label')"
:mode="mode"
:options="migratableOptions"
/>
</div>
<div class="row mt-20">
<RadioGroup
v-model:value="volumeEncryption"
@@ -245,6 +245,8 @@ export default {
async saveImage(buttonCb) {
this.value.spec.displayName = (this.value.spec.displayName || '').trim();
if (this.isEdit) return await this.handleEditImage(buttonCb);
if (this.value.spec.sourceType === UPLOAD && this.isCreate) {
try {
this.value.spec.url = '';
@@ -269,6 +271,23 @@ export default {
}
},
async handleEditImage(buttonCb) {
try {
const data = [{
op: 'replace', path: '/metadata/labels', value: this.value.metadata.labels
}, {
op: 'replace', path: '/metadata/annotations', value: this.value.metadata.annotations
}];
await this.value.patch(data);
buttonCb(true);
this.done();
} catch (e) {
this.errors = exceptionToErrorsArray(e);
buttonCb(false);
}
},
setImageLabels(str) {
const suffixName = str?.split('/')?.pop() || str;
@@ -530,6 +549,7 @@ export default {
:mode="mode"
:pad-left="false"
:read-allowed="false"
:value-can-be-empty="true"
@focusKey="focusKey"
@update:value="value.setLabels($event)"
>
@@ -552,7 +572,7 @@ export default {
autocorrect="off"
autocapitalize="off"
spellcheck="false"
@update:value="queueUpdate"
@input="queueUpdate"
/>
</template>
</KeyValue>
@@ -29,7 +29,7 @@ import SSHKey from './kubevirt.io.virtualmachine/VirtualMachineSSHKey';
export default {
name: 'HarvesterEditVMTemplate',
emits: ['update:value'],
emits: ['update:templateValue'],
components: {
Tab,
@@ -205,7 +205,7 @@ export default {
if (tab.name === 'advanced') {
this.$refs.yamlEditor?.refresh();
}
}
},
},
};
</script>
@@ -222,11 +222,11 @@ export default {
@finish="saveVMT"
>
<NameNsDescription
:value="value"
:value="templateValue"
:mode="realTemplateMode"
name-label="harvester.vmTemplate.nameNsDescription.name"
:namespaced="true"
@update:value="$emit('update:value', $event)"
@update:value="$emit('update:templateValue', $event)"
/>
<Checkbox
@@ -95,7 +95,11 @@ export default {
devices: {
handler(v) {
this.rows = v;
this.filterRows = this.rows;
if (this.parentSriov) {
this.filterRows = this.rows.filter((row) => row.labels[this.parentSriovLabel] === this.parentSriov);
} else {
this.filterRows = this.rows;
}
},
immediate: true,
},
@@ -1,14 +1,11 @@
<script>
import { mapGetters } from 'vuex';
import { randomStr } from '@shell/utils/string';
import { clone } from '@shell/utils/object';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard';
import { clone } from '@shell/utils/object';
import { _VIEW } from '@shell/config/query-params';
import { NAMESPACE } from '@shell/config/types';
import { HCI } from '../../types';
@@ -126,10 +123,10 @@ export default {
this.checkedSsh = neu;
},
checkedSsh(val, old) {
if ( val.includes(_NEW)) {
this['checkedSsh'] = old;
this.update();
checkedSsh(val) {
// if click on Create a New...
if (val.includes(_NEW)) {
this.checkedSsh = this.checkedSsh.filter((key) => key !== _NEW);
this.show();
}
}
@@ -175,6 +172,7 @@ export default {
if (res.id) {
this.checkedSsh.push(`${ this.namespace }/${ this.sshName }`);
this.update();
}
},
@@ -243,7 +241,7 @@ export default {
<LabeledSelect
v-model:value="checkedSsh"
:label="t('harvester.virtualMachine.input.sshKey')"
:taggable="true"
:taggable="!disabled"
:mode="mode"
:multiple="true"
:searchable="searchable"
@@ -46,10 +46,13 @@ export default {
this[key] = res[key];
}
uniq([
const vGpus = this.vm.isOff ? [
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
] : [
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
]).forEach((name) => {
];
uniq(vGpus).forEach((name) => {
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
this.selectedDevices.push(name);
}
@@ -1,5 +1,5 @@
<script>
import { Sortable } from 'sortablejs-vue3';
import { VueDraggableNext } from 'vue-draggable-next';
import InfoBox from '@shell/components/InfoBox';
import { Banner } from '@components/Banner';
import BadgeStateFormatter from '@shell/components/formatter/BadgeStateFormatter';
@@ -22,7 +22,7 @@ export default {
emits: ['update:value'],
components: {
Banner, BadgeStateFormatter, Sortable, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
},
props: {
@@ -272,129 +272,130 @@ export default {
color="info"
label-key="harvester.virtualMachine.volume.dragTip"
/>
<Sortable
<VueDraggableNext
:list="rows"
:options="{disabled: isView}"
:disabled="isView"
item-key="id"
@end="update"
>
<template #item="{element: volume, index: i}">
<div :key="volume.name">
<InfoBox class="box">
<button
<div
v-for="(volume, i) in rows"
:key="volume.id"
>
<InfoBox class="box">
<button
v-if="!isView"
type="button"
class="role-link btn btn-sm remove"
@click="removeVolume(volume)"
>
<i class="icon icon-x" />
</button>
<button
v-if="volume.hotpluggable && isView"
type="button"
class="role-link btn remove"
@click="unplugVolume(volume)"
>
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
</button>
<h3>
<span
v-if="volume.to && isVirtualType"
class="title"
>
<router-link :to="volume.to">
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
</router-link>
<BadgeStateFormatter
v-if="volume.pvc"
class="ml-10 state"
:arbitrary="true"
:row="volume.pvc"
:value="volume.pvc.state"
/>
<a
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
class="ml-5 resource-external"
rel="nofollow noopener noreferrer"
target="_blank"
:href="volume.pvc.resourceExternalLink.url"
>
<i class="icon icon-external-link" />
</a>
</span>
<span v-else>
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
</span>
</h3>
<div>
<component
:is="componentFor(volume.source)"
:value="rows[i]"
:rows="rows"
:namespace="namespace"
:is-create="isCreate"
:is-edit="isEdit"
:is-view="isView"
:is-virtual-type="isVirtualType"
:mode="mode"
:idx="i"
:validate-required="validateRequired"
@update="update"
/>
</div>
<div class="bootOrder">
<div
v-if="!isView"
type="button"
class="role-link btn btn-sm remove"
@click="removeVolume(volume)"
class="mr-15"
>
<i class="icon icon-x" />
</button>
<button
v-if="volume.hotpluggable && isView"
type="button"
class="role-link btn remove"
@click="unplugVolume(volume)"
>
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
</button>
<h3>
<span
v-if="volume.to && isVirtualType"
class="title"
<button
:disabled="i === 0"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, false)"
>
<router-link :to="volume.to">
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
</router-link>
<i class="icon icon-lg icon-chevron-up"></i>
</button>
<BadgeStateFormatter
v-if="volume.pvc"
class="ml-10 state"
:arbitrary="true"
:row="volume.pvc"
:value="volume.pvc.state"
/>
<a
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
class="ml-5 resource-external"
rel="nofollow noopener noreferrer"
target="_blank"
:href="volume.pvc.resourceExternalLink.url"
>
<i class="icon icon-external-link" />
</a>
</span>
<span v-else>
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
</span>
</h3>
<div>
<component
:is="componentFor(volume.source)"
:value="rows[i]"
:rows="rows"
:namespace="namespace"
:is-create="isCreate"
:is-edit="isEdit"
:is-view="isView"
:is-virtual-type="isVirtualType"
:mode="mode"
:idx="i"
:validate-required="validateRequired"
@update="update"
/>
</div>
<div class="bootOrder">
<div
v-if="!isView"
class="mr-15"
<button
:disabled="i === rows.length -1"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, true)"
>
<button
:disabled="i === 0"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, false)"
>
<i class="icon icon-lg icon-chevron-up"></i>
</button>
<button
:disabled="i === rows.length -1"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, true)"
>
<i class="icon icon-lg icon-chevron-down"></i>
</button>
</div>
<div class="text-muted">
bootOrder: {{ i + 1 }}
</div>
<i class="icon icon-lg icon-chevron-down"></i>
</button>
</div>
<div class="mt-15">
<Banner
v-if="volume.volumeStatus && !isCreate"
class="volume-status"
color="warning"
:label="ucFirst(volume.volumeStatus)"
/>
<Banner
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
color="error"
:label="ucFirst(value.volumeBackups.error.message)"
/>
<Banner
v-if="isLonghornV2(volume) && !isView"
color="warning"
:label="t('harvester.volume.longhorn.disableResize')"
/>
<div class="text-muted">
bootOrder: {{ i + 1 }}
</div>
</InfoBox>
</div>
</template>
</Sortable>
</div>
<div class="mt-15">
<Banner
v-if="volume.volumeStatus && !isCreate"
class="volume-status"
color="warning"
:label="ucFirst(volume.volumeStatus)"
/>
<Banner
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
color="error"
:label="ucFirst(value.volumeBackups.error.message)"
/>
<Banner
v-if="isLonghornV2(volume) && !isView"
color="warning"
:label="t('harvester.volume.longhorn.disableResize')"
/>
</div>
</InfoBox>
</div>
</VueDraggableNext>
<Banner
v-if="showVolumeTip"
color="warning"
@@ -579,7 +579,7 @@ export default {
:create-namespace="true"
:namespace="value.metadata.namespace"
:mode="mode"
:disabled="isWindows"
:disabled="isWindows || isEdit"
@update:sshKey="updateSSHKey"
@register-after-hook="registerAfterHook"
/>
@@ -29,7 +29,7 @@ export default {
const rows = (this.value || []).map((row) => {
let type = 'cidr';
if (row.rangeStart && row.rangeEnd) {
if (row.rangeStart || row.rangeEnd) {
type = 'range';
}
@@ -65,7 +65,7 @@ export default {
:labels="[t('generic.disabled'),t('generic.enabled')]"
:options="[false, true]"
:disabled="disabled"
@input="onToggle"
@update:value="onToggle"
/>
</div>
</div>
@@ -58,7 +58,7 @@ export default {
const out = [];
blockDevices.map((b) => {
if (b.metadata.state.error) {
if (b?.metadata?.state?.error) {
out.push(b.metadata.state.message);
}
});
@@ -72,17 +72,23 @@ export default {
return stats;
},
units() {
allocatedUnits() {
const exponent = exponentNeeded(this.storageStats.total, 1024);
return `${ UNITS[exponent] }iB`;
},
usedUnits() {
const exponent = exponentNeeded(this.storageStats.maximum, 1024);
return `${ UNITS[exponent] }iB`;
},
used() {
formatUsed() {
let out = this.formatter(this.storageStats.used);
if (!Number.parseFloat(out) > 0) {
out = this.formatter(this.storageStats.used, { canRoundToZero: false });
out = this.formatter(this.storageStats.used, { canRoundToZero: true });
}
return out;
@@ -92,7 +98,7 @@ export default {
let out = this.formatter(this.storageStats.scheduled);
if (!Number.parseFloat(out) > 0) {
out = this.formatter(this.storageStats.scheduled, { canRoundToZero: false });
out = this.formatter(this.storageStats.scheduled, { canRoundToZero: true });
}
return out;
@@ -100,9 +106,9 @@ export default {
usedAmountTemplateValues() {
return {
used: this.used,
used: this.formatUsed,
total: this.formatter(this.storageStats.maximum),
unit: this.units,
unit: this.usedUnits,
};
},
@@ -110,7 +116,7 @@ export default {
return {
used: this.formatAllocated,
total: this.formatter(this.storageStats.total),
unit: this.units,
unit: this.allocatedUnits,
};
},
},
@@ -141,7 +147,7 @@ export default {
<ConsumptionGauge
:capacity="storageStats.total"
:used="storageStats.scheduled"
:units="units"
:units="allocatedUnits"
:number-formatter="formatter"
:resource-name="resourceName"
>
@@ -161,7 +167,7 @@ export default {
<ConsumptionGauge
:capacity="storageStats.maximum"
:used="storageStats.used"
:units="units"
:units="usedUnits"
:number-formatter="formatter"
:resource-name="showAllocated ? '' : resourceName"
:class="{
+5 -4
View File
@@ -6,6 +6,7 @@ import harvesterCommonStore from './store/harvester-common';
import harvesterStore from './store/harvester-store';
import customValidators from './validators';
import { PRODUCT_NAME } from './config/harvester';
import { defineAsyncComponent } from 'vue';
// Init the package
export default function (plugin: IPlugin) {
@@ -21,10 +22,6 @@ export default function (plugin: IPlugin) {
// Built-in icon
plugin.metadata.icon = require('./icon.svg');
if (isDev && !isSingleVirtualCluster) {
plugin.addProduct(require('./config/harvester-manager'));
}
plugin.addProduct(require('./config/harvester-cluster'));
plugin.addDashboardStore(harvesterCommonStore.config.namespace, harvesterCommonStore.specifics, harvesterCommonStore.config);
@@ -32,4 +29,8 @@ export default function (plugin: IPlugin) {
plugin.validators = customValidators;
plugin.addRoutes(extensionRoutes);
plugin.register('component', 'NavHeaderRight', defineAsyncComponent(() =>
import('./components/HarvesterUpgradeHeader.vue')
));
}
+23 -42
View File
@@ -1,34 +1,3 @@
harvesterManager:
manage: Manage
tableHeaders:
kubernetesVersion: Kubernetes Version
harvesterVersion: Harvester Version
cluster:
label: Harvester Clusters
none: There are no Harvester Clusters
learnMore: Learn more about Harvester from the <a target="_blank" href="https://harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Web Site</a> or read the the <a target="_blank" href="https://docs.harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Docs</a>
description: Harvester is a modern Hyperconverged infrastructure (HCI) solution built for bare metal servers using enterprise-grade open source technologies including Kubernetes, Kubevirt and Longhorn.
supportMessage: Harvester ui extension only supports Harvester cluster version greater or equal to 1.3.0
plugins:
loadError: Error loading harvester plugin
rke:
templateError: Incorrect template format
affinity:
thisPodNamespace: This VM's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
vmAffinityTitle: VM Scheduling
namespaces:
placeholder: e.g. default,system,base
label: Namespaces
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
vGpu:
title: VGPUs
label: VGPU type
placeholder: 'Please select a VGPU'
generic:
tip: Tip
resourceExternalLinkTips: 'External Link'
@@ -39,6 +8,11 @@ generic:
basic: Basic
loading: Loading...
unsupported:
serverVersion: 'Current version: <code>{serverVersion}</code>'
description: Harvester ui only supports Harvester cluster version greater or equal to 1.3.0.
learnMore: Learn more about Harvester from the <a target="_blank" href="https://harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Web Site</a> or read the <a target="_blank" href="https://docs.harvesterhci.io/" rel="noopener noreferrer nofollow">Harvester Docs</a>
nav:
group:
networks: Networks
@@ -65,6 +39,11 @@ asyncButton:
waiting: Restarting&hellip;
harvester:
branding:
logos:
tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.'
favicon:
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
productLabel: 'Harvester'
modal:
backup:
@@ -768,6 +747,9 @@ harvester:
terminationGracePeriodSeconds:
label: Termination Grace Period
affinity:
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
thisPodNamespace: This virtual machine's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
@@ -920,6 +902,9 @@ harvester:
label: Virtual Machine Backups
createText: Restore Backup
title: Restore Virtual Machine
refreshInterval:
label: Refresh Interval
tip: The interval for the backup target to sync data to harvester cluster. Set to 0 to disable refresh mechanism.
backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported.
message:
noSetting:
@@ -1520,17 +1505,13 @@ harvester:
pollingInterval:
label: Polling Interval
affinity:
thisPodNamespace: This virtual machine's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
vmAffinityTitle: Virtual Machine Scheduling
namespaces:
placeholder: e.g. default,system,base
label: Namespaces
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
typeDescription:
# Map of
# type: Description to be shown on the top of list view describing the type.
# Should fit on one line.
# If you link to anything external, it MUST have
# target="_blank" rel="noopener noreferrer nofollow"
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
advancedSettings:
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
@@ -12,6 +12,8 @@ import { HCI } from '../types';
export default {
name: 'ListSriovGpuDevices',
inheritAttrs: false,
components: {
Banner,
Loading,
@@ -10,6 +10,8 @@ import DeviceList from '../edit/kubevirt.io.virtualmachine/VirtualMachineUSBDevi
export default {
name: 'ListUsbDevicePage',
inheritAttrs: false,
components: {
Banner,
DeviceList,
@@ -10,6 +10,8 @@ import { ADD_ONS } from '../config/harvester-map';
export default {
name: 'ListVGpuDevices',
inheritAttrs: false,
components: {
Banner,
Loading,
@@ -1,279 +0,0 @@
<script>
import BrandImage from '@shell/components/BrandImage';
import TypeDescription from '@shell/components/TypeDescription';
import ResourceTable from '@shell/components/ResourceTable';
import Masthead from '@shell/components/ResourceList/Masthead';
import Loading from '@shell/components/Loading';
import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
import { CAPI, HCI, MANAGEMENT } from '@shell/config/types';
import { isHarvesterCluster } from '@shell/utils/cluster';
import { allHash } from '@shell/utils/promise';
import { STATE, NAME as NAME_COL, AGE, VERSION } from '@shell/config/table-headers';
import { MACHINE_POOLS } from '../config/table-headers';
export default {
components: {
BrandImage,
ResourceTable,
Masthead,
TypeDescription,
Loading
},
props: {
schema: {
type: Object,
required: true,
},
useQueryParamsForSimpleFiltering: {
type: Boolean,
default: false
}
},
async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;
const hash = await allHash({
hciClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER }),
mgmtClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: MANAGEMENT.CLUSTER })
});
this.hciClusters = hash.hciClusters;
this.mgmtClusters = hash.mgmtClusters;
},
data() {
const resource = CAPI.RANCHER_CLUSTER;
return {
navigating: false,
VIRTUAL,
hciDashboard: HCI.DASHBOARD,
resource,
hResource: HCI.CLUSTER,
realSchema: this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER),
hciClusters: [],
mgmtClusters: []
};
},
computed: {
headers() {
return [
STATE,
NAME_COL,
{
name: 'harvesterVersion',
sort: 'harvesterVersion',
labelKey: 'harvesterManager.tableHeaders.harvesterVersion',
value: 'harvesterVersion',
getValue: (row) => row.harvesterVersion
},
{
...VERSION,
labelKey: 'harvesterManager.tableHeaders.kubernetesVersion',
value: 'kubernetesVersion',
getValue: (row) => row.kubernetesVersion
},
MACHINE_POOLS,
AGE,
{
name: 'harvester',
label: ' ',
align: 'right',
width: 65,
},
];
},
importLocation() {
return {
name: 'c-cluster-product-resource-create',
params: {
product: this.$store.getters['currentProduct'].name,
resource: this.schema.id,
},
};
},
canCreateCluster() {
const schema = this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER);
return !!schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
},
rows() {
return this.hciClusters
.filter((c) => {
const cluster = this.mgmtClusters.find((cluster) => cluster?.metadata?.name === c?.status?.clusterName);
return isHarvesterCluster(cluster);
})
.map((row) => {
if (row.isReady) {
row.setSupportedHarvesterVersion();
}
return row;
});
},
typeDisplay() {
return this.t(`typeLabel."${ HCI.CLUSTER }"`, { count: this.rows?.length || 0 });
},
},
methods: {
async goToCluster(row) {
const timeout = setTimeout(() => {
// Don't show loading indicator for quickly fetched plugins
this.navigating = row.id;
}, 1000);
try {
await row.goToCluster();
clearTimeout(timeout);
this.navigating = false;
} catch {
// The error handling is carried out within goToCluster, but just in case something happens before the promise chain can catch it...
clearTimeout(timeout);
this.navigating = false;
}
}
}
};
</script>
<template>
<Loading v-if="$fetchState.pending" />
<div v-else>
<Masthead
:schema="realSchema"
:resource="resource"
:is-creatable="false"
:type-display="typeDisplay"
>
<template #typeDescription>
<TypeDescription :resource="hResource" />
</template>
<template
v-if="canCreateCluster"
#extraActions
>
<router-link
:to="importLocation"
class="btn role-primary"
>
{{ t('cluster.importAction') }}
</router-link>
</template>
</Masthead>
<ResourceTable
v-if="rows && rows.length"
:schema="schema"
:headers="headers"
:rows="rows"
:is-creatable="true"
:namespaced="false"
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
>
<template #col:name="{row}">
<td>
<span class="cluster-link">
<a
v-if="row.isReady && row.isSupportedHarvester"
class="link"
:disabled="navigating ? true : null"
@click="goToCluster(row)"
>{{ row.nameDisplay }}</a>
<span v-else>
{{ row.nameDisplay }}
</span>
<i
class="icon icon-spinner icon-spin ml-5"
:class="{'navigating': navigating === row.id}"
/>
</span>
</td>
</template>
<template #cell:harvester="{row}">
<router-link
class="btn btn-sm role-primary"
:to="row.detailLocation"
>
{{ t('harvesterManager.manage') }}
</router-link>
</template>
</ResourceTable>
<div v-else>
<div class="no-clusters">
{{ t('harvesterManager.cluster.none') }}
</div>
<hr class="info-section">
<div class="logo">
<BrandImage
file-name="harvester.png"
height="64"
/>
</div>
<div class="tagline">
<div>{{ t('harvesterManager.cluster.description') }}</div>
</div>
<div class="tagline sub-tagline">
<div v-clean-html="t('harvesterManager.cluster.learnMore', {}, true)" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.cluster-link {
display: flex;
align-items: center;
.icon {
// Use visibility to avoid the columns re-adjusting when the icon is shown
visibility: hidden;
&.navigating {
visibility: visible;
}
}
}
.no-clusters {
text-align: center;
}
.info-section {
margin-top: 60px;
}
.logo {
display: flex;
justify-content: center;
margin: 60px 0 40px 0;
}
.tagline {
display: flex;
justify-content: center;
margin-top: 30px;
> div {
font-size: 16px;
line-height: 22px;
max-width: 80%;
text-align: center;
}
}
.link {
cursor: pointer;
}
</style>
@@ -101,7 +101,7 @@ export default {
v-bind="$attrs"
:headers="headers"
:sub-rows="true"
:groupable="false"
:groupable="true"
:rows="rows"
:group-title-by="groupTitleBy"
:group-by="groupBy"
+17 -22
View File
@@ -282,14 +282,14 @@ export default {
affinityLabels() {
return {
namespaceInputLabel: this.t('harvesterManager.affinity.namespaces.label'),
namespaceInputLabel: this.t('harvester.virtualMachine.affinity.namespaces.label'),
namespaceSelectionLabels: [
this.t('harvesterManager.affinity.thisPodNamespace'),
this.t('harvester.virtualMachine.affinity.thisPodNamespace'),
this.t('workload.scheduling.affinity.allNamespaces'),
this.t('harvesterManager.affinity.matchExpressions.inNamespaces')
this.t('harvester.virtualMachine.affinity.matchExpressions.inNamespaces')
],
addLabel: this.t('harvesterManager.affinity.addLabel'),
topologyKeyPlaceholder: this.t('harvesterManager.affinity.topologyKey.placeholder')
addLabel: this.t('harvester.virtualMachine.affinity.addLabel'),
topologyKeyPlaceholder: this.t('harvester.virtualMachine.affinity.topologyKey.placeholder')
};
},
},
@@ -894,8 +894,8 @@ export default {
},
/**
* Generate user data yaml which is decide by the "Install guest agent",
* "OS type", "SSH Keys" and user input.
* Generate user data yaml which is decided by the
* "Install guest agent", "OS type", "SSH keys" and user input.
* @param config
*/
getUserData(config) {
@@ -919,7 +919,9 @@ export default {
return undefined;
}
return userDataYaml;
const hasCloudComment = this.hasCloudConfigComment(userDataYaml);
return hasCloudComment ? userDataYaml : `#cloud-config\n${ userDataYaml }`;
} catch (e) {
console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console
@@ -1212,7 +1214,6 @@ export default {
let secret = this.getSecret(vm.spec);
// const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
if (!secret && this.isEdit && this.secretRef) {
// When editing the vm, if the userData and networkData are deleted, we also need to clean up the secret values
secret = this.secretRef;
@@ -1431,7 +1432,6 @@ export default {
sshAuthorizedKeys.splice(index, 1);
}
});
const userDataJson = this.convertToJson(this.userScript);
userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
@@ -1553,19 +1553,9 @@ export default {
*/
handler(neu) {
if (this.deleteAgent) {
let out = this.getUserData({
this['userScript'] = this.getUserData({
installAgent: neu, osType: this.osType, deletePackage: this.deletePackage
});
if (neu) {
const hasCloudComment = this.hasCloudConfigComment(out);
if (!hasCloudComment) {
out = `#cloud-config\n${ out }`;
}
}
this['userScript'] = out;
this.refreshYamlEditor();
}
this.deleteAgent = true;
@@ -1592,9 +1582,14 @@ export default {
sshKey(neu, old) {
const _diff = difference(old, neu);
if (_diff.length && this.isEdit) {
// delete removed ssh key in userdata if needed
if (_diff.length > 0 && this.isCreate) {
this.deleteSSHFromUserData(_diff);
}
// refresh yaml editor to get the latest userScript
this.userScript = this.getUserData({ installAgent: this.installAgent, osType: this.osType });
this.refreshYamlEditor();
}
}
};
@@ -0,0 +1,11 @@
import SteveModel from '@shell/plugins/steve/steve-class';
/**
* Class representing PCI Device Claim resource.
* @extends SteveModal
*/
export default class PCIDeviceClaim extends SteveModel {
cleanForSave(data, _forNew) {
return data;
}
}
@@ -0,0 +1,11 @@
import SteveModel from '@shell/plugins/steve/steve-class';
/**
* Class representing USB Device Claim resource.
* @extends SteveModal
*/
export default class USBDeviceClaim extends SteveModel {
cleanForSave(data, _forNew) {
return data;
}
}
+4
View File
@@ -66,4 +66,8 @@ export default class HarvesterResource extends SteveModel {
return this.hasLink('self') && this.$rootGetters['prefs/get'](DEV);
}
}
cleanForSave(data, _forNew) {
return data;
}
}
@@ -238,7 +238,7 @@ export default class HciPv extends HarvesterResource {
}
get isEncrypted() {
return this.relatedPV?.spec.csi.volumeAttributes.encrypted === 'true';
return this.relatedPV?.spec?.csi?.volumeAttributes?.encrypted === 'true';
}
get longhornVolume() {
+1 -1
View File
@@ -3,4 +3,4 @@ import SteveSchema from '@shell/models/steve-schema';
/**
* This is the steve schema that's used for the harvester store
*/
export default class HarvesterStoreSchema extends SteveSchema { }
export default class HarvesterStoreSchema extends SteveSchema {}
@@ -64,4 +64,10 @@ export default class HciBlockDevice extends HarvesterResource {
// spec.fileSystem.provisioned is deprecated
return this.spec?.fileSystem?.provisioned || this.spec?.provision;
}
// Overwrite cleanForSave() in shell/plugins/steve/steve-class.js as it deleted status object in harvesterhci.io.blockdevice CRD
// but /v1/harvester/harvesterhci.io.blockdevices/longhorn-system/{id} API requires status object
cleanForSave(data) {
return data;
}
}
@@ -1,223 +0,0 @@
import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
import { DEFAULT_WORKSPACE, HCI, MANAGEMENT } from '@shell/config/types';
import { HARVESTER_NAME, HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
import { SETTING } from '@shell/config/settings';
import { colorForState, stateDisplay, STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
export default class HciCluster extends ProvCluster {
get isSupportedHarvester() {
return this._isSupportedHarvester === undefined ? true : this._isSupportedHarvester;
}
get harvesterVersion() {
return this._harvesterVersion;
}
get stateObj() {
if (!this.isSupportedHarvester) {
return { error: true, message: this.t('harvesterManager.cluster.supportMessage') };
}
return this._stateObj;
}
applyDefaults() {
if ( !this.spec ) {
this['spec'] = { agentEnvVars: [] };
this['metadata'] = { namespace: DEFAULT_WORKSPACE };
}
}
get isReady() {
// If the Connected condition exists, use that (2.6+)
if ( this.hasCondition('Connected') ) {
return this.isCondition('Connected');
}
// Otherwise use Ready (older)
return this.isCondition('Ready');
}
get canEdit() {
return false;
}
get stateColor() {
if (!this.isSupportedHarvester) {
return colorForState(STATES_ENUM.DENIED);
}
return colorForState(this.state);
}
get stateDisplay() {
if (!this.isSupportedHarvester) {
return stateDisplay(STATES_ENUM.DENIED);
}
return stateDisplay(this.state);
}
/**
* Fetch and cache the response for /ui-info
*
* Storing this in a cache means any changes to `ui-info` require a dashboard refresh... but it cuts out a http request every time we
* go to a cluster
*
* @param {string} clusterId
*/
async _getUiInfo(clusterId) {
if (!this._uiInfo) {
try {
const infoUrl = `/k8s/clusters/${ clusterId }/v1/harvester/ui-info`;
this._uiInfo = await this.$dispatch('request', { url: infoUrl });
} catch (e) {
console.info(`Failed to fetch harvester ui-info from ${ this.nameDisplay }, this may be an older cluster that cannot provide one`); // eslint-disable-line no-console
}
}
return this._uiInfo;
}
/**
* Determine the harvester plugin's package name and url for legacy clusters that don't provide the package (i.e. it's coming from
* outside the cluster)
*/
_legacyClusterPkgDetails() {
let uiOfflinePreferred = this.$rootGetters['management/byId'](MANAGEMENT.SETTING, SETTING.UI_OFFLINE_PREFERRED)?.value;
// options: ['dynamic', 'true', 'false']
if (uiOfflinePreferred === 'dynamic') {
// We shouldn't need to worry about the version of the dashboard when embedded in harvester (aka in isSingleProduct)
const version = this.$rootGetters['management/byId'](MANAGEMENT.SETTING, SETTING.VERSION_RANCHER)?.value;
if (version.endsWith('-head')) {
uiOfflinePreferred = 'false';
} else {
uiOfflinePreferred = 'true';
}
}
// This is the version that's embedded in the dashboard
const pkgName = `${ HARVESTER_NAME }-1.0.3`;
if (uiOfflinePreferred === 'true') {
// Embedded (aka give me the embedded plugin that was in the last rancher release)
const embeddedPath = `${ pkgName }/${ pkgName }.umd.min.js`;
return {
pkgUrl: process.env.dev ? `${ process.env.api }/dashboard/${ embeddedPath }` : embeddedPath,
pkgName
};
}
if (uiOfflinePreferred === 'false') {
// Remote (aka give me the latest version of the embedded plugin that might not have been released yet)
const uiDashboardHarvesterRemotePlugin = this.$rootGetters['management/byId'](MANAGEMENT.SETTING, SETTING.UI_DASHBOARD_HARVESTER_LEGACY_PLUGIN)?.value;
const parts = uiDashboardHarvesterRemotePlugin?.replace('.umd.min.js', '').split('/');
const pkgNameFromUrl = parts?.length > 1 ? parts[parts.length - 1] : null;
if (!pkgNameFromUrl) {
throw new Error(`Unable to determine harvester plugin name from '${ uiDashboardHarvesterRemotePlugin }'`);
}
return {
pkgUrl: uiDashboardHarvesterRemotePlugin,
pkgName: pkgNameFromUrl
};
}
throw new Error(`Unsupported value for ${ SETTING.UI_OFFLINE_PREFERRED }: 'uiOfflinePreferred'`);
}
/**
* Determine the harvester plugin's package name and url for clusters that provide the plugin
*/
_supportedClusterPkgDetails(uiInfo, clusterId) {
let pkgName = `${ HARVESTER_NAME }-${ uiInfo['ui-plugin-bundled-version'] }`;
const fileName = `${ pkgName }.umd.min.js`;
let pkgUrl;
if (uiInfo['ui-source'] === 'bundled' ) { // offline bundled
pkgUrl = `/k8s/clusters/${ clusterId }/v1/harvester/plugin-assets/${ fileName }`;
} else if (uiInfo['ui-source'] === 'external') {
if (uiInfo['ui-plugin-index']) {
pkgUrl = uiInfo['ui-plugin-index'];
// When using an external address, the pkgName should also be get from the url
const names = pkgUrl.split('/');
const jsName = names[names.length - 1];
pkgName = jsName?.split('.umd.min.js')[0];
} else {
throw new Error('Harvester cluster requested the plugin at `ui-plugin-index` is used, however did not provide a value for it');
}
}
return {
pkgUrl,
pkgName
};
}
_overridePkgDetails() {
// Support loading the pkg from a locally, or other, address
// This helps testing of the harvester plugin when packaged up, instead of directly imported
const harvesterPkgUrl = process.env.harvesterPkgUrl;
if (!harvesterPkgUrl) {
return;
}
const parts = harvesterPkgUrl.replace('.umd.min.js', '').split('/');
const pkgNameFromUrl = parts.length > 1 ? parts[parts.length - 1] : null;
if (pkgNameFromUrl) {
return {
pkgUrl: harvesterPkgUrl,
pkgName: pkgNameFromUrl
};
}
}
async _pkgDetails() {
const overridePkgDetails = this._overridePkgDetails();
if (overridePkgDetails) {
return overridePkgDetails;
}
const clusterId = this.mgmt.id;
const uiInfo = await this._getUiInfo(clusterId);
return uiInfo ? this._supportedClusterPkgDetails(uiInfo, clusterId) : this._legacyClusterPkgDetails();
}
async goToCluster() {
this.currentRouter().push({
name: `${ VIRTUAL }-c-cluster-resource`,
params: {
cluster: this.status.clusterName,
product: VIRTUAL,
resource: HCI.DASHBOARD // Go directly to dashboard to avoid blip of components on screen
}
});
}
async setSupportedHarvesterVersion() {
if (this._isSupportedHarvester !== undefined) {
return;
}
const url = `/k8s/clusters/${ this.status.clusterName }/v1`;
try {
const setting = await this.$dispatch('request', { url: `${ url }/${ HCI.SETTING }s/server-version` });
this._harvesterVersion = setting?.value;
this._isSupportedHarvester = this.$rootGetters['harvester-common/getFeatureEnabled']('supportHarvesterClusterVersion', setting?.value);
} catch (error) {
console.error('unable to get harvester version from settings/server-version', error); // eslint-disable-line no-console
}
}
}
@@ -1,6 +1,4 @@
import HarvesterResource from './harvester';
import { get } from '@shell/utils/object';
import { findBy } from '@shell/utils/array';
import { colorForState, stateDisplay, STATES } from '@shell/plugins/dashboard-store/resource-class';
import { _CREATE } from '@shell/config/query-params';
import { ucFirst, escapeHtml } from '@shell/utils/string';
@@ -66,15 +64,12 @@ export default class ScheduleVmBackup extends HarvesterResource {
}
}
get stateObj() {
return this?.metadata?.state || {};
}
get state() {
const conditions = get(this, 'status.conditions');
const isSuspended = findBy(conditions, 'type', 'BackupSuspend')?.status === 'True';
if (isSuspended) {
return STATES.suspended.label;
}
return this.metadata.state.name;
return this.status?.suspended === true ? STATES.suspended.label : STATES.active.label;
}
get stateDescription() {
@@ -61,6 +61,10 @@ export default class HciUpgrade extends HarvesterResource {
return this?.status?.imageID;
}
cleanForSave(data, _forNew) {
return data;
}
get upgradeMessage() {
const upgradeMessage = [];
const nodeStatuses = this?.status?.nodeStatuses || {};
@@ -2,6 +2,7 @@ import { MODE, _CREATE } from '@shell/config/query-params';
import { HCI } from '../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../config/harvester';
import HarvesterResource from './harvester';
import { clone } from '@shell/utils/object';
export default class HciVmTemplate extends HarvesterResource {
get availableActions() {
@@ -71,4 +72,12 @@ export default class HciVmTemplate extends HarvesterResource {
get defaultVersion() {
return this.status?.defaultVersion;
}
get doneOverride() {
const detailLocation = clone(this.listLocation);
detailLocation.params.resource = HCI.VM_VERSION;
return detailLocation;
}
}
@@ -1120,10 +1120,6 @@ export default class VirtVm extends HarvesterResource {
return qemu?.status === 'True';
}
get warnDeletionMessage() {
return this.t('harvester.virtualMachine.promptRemove.tips');
}
get instanceLabels() {
const all = this.spec?.template?.metadata?.labels || {};
+3 -3
View File
@@ -1,14 +1,14 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.0.0",
"version": "1.0.6",
"private": false,
"rancher": {
"annotations": {
"catalog.cattle.io/display-name": "Harvester",
"catalog.cattle.io/kube-version": ">= 1.16.0-0",
"catalog.cattle.io/rancher-version": ">= 2.10.0-0",
"catalog.cattle.io/ui-extensions-version": ">= 3.0.0-0"
"catalog.cattle.io/rancher-version": ">= 2.10.1-0",
"catalog.cattle.io/ui-extensions-version": ">= 3.0.0 < 4.0.0"
}
},
"icon": "https://raw.githubusercontent.com/harvester/harvester-ui-extension/main/pkg/harvester/icon.svg",
@@ -164,7 +164,7 @@ export default {
<h1 class="mb-20">
{{ t('branding.label') }}
</h1>
<TypeDescription resource="branding" />
<TypeDescription resource="harvester" />
<div>
<div class="row mb-20">
<div class="col span-6">
@@ -180,7 +180,7 @@ export default {
{{ t('branding.logos.label') }}
</h3>
<label class="text-label">
{{ t('branding.logos.tip', {}, true) }}
{{ t('harvester.branding.logos.tip', {}, true) }}
</label>
<div class="row mt-10 mb-20">
<Checkbox
@@ -244,7 +244,7 @@ export default {
{{ t('branding.favicon.label') }}
</h3>
<label class="text-label">
{{ t('branding.favicon.tip', {}, true) }}
{{ t('harvester.branding.favicon.tip', {}, true) }}
</label>
<div class="row mt-10 mb-20">
<Checkbox
@@ -0,0 +1,85 @@
<script>
import BrandImage from '@shell/components/BrandImage';
export default {
components: { BrandImage },
computed: {
serverVersion() {
const version = this.$store.getters['harvester-common/getServerVersion']() || '';
return version.replace('v', '');
}
}
};
</script>
<template>
<div class="dashboard-root">
<div class="dashboard-content">
<main class="main-layout">
<div class="logo">
<BrandImage
file-name="harvester.png"
height="64"
/>
</div>
<div class="tagline">
<div class="description">
{{ t('unsupported.description') }}
</div>
</div>
<div class="tagline">
<div v-clean-html="t('unsupported.serverVersion', { serverVersion }, true)" />
</div>
<div class="tagline">
<div v-clean-html="t('unsupported.learnMore', {}, true)" />
</div>
</main>
</div>
</div>
</template>
<style lang="scss" scoped>
.dashboard-root {
display: flex;
flex-direction: column;
height: 100vh;
}
.dashboard-content {
display: grid;
flex-grow:1;
grid-template-areas:
"main";
grid-template-columns: auto;
grid-template-rows: auto;
}
.logo {
display: flex;
justify-content: center;
margin: 200px 0 40px 0;
}
.tagline {
display: flex;
justify-content: center;
margin-top: 30px;
> div {
font-size: 16px;
line-height: 22px;
max-width: 80%;
text-align: center;
}
}
.description {
font-size: 24px !important;
font-weight: 400;
}
</style>
@@ -4,6 +4,7 @@ import { isEmpty } from '@shell/utils/object';
import Parse from 'url-parse';
import { resourceNames } from '@shell/utils/string';
import { HCI } from '../types';
import { alternateLabel as alternateLabelButton } from '@shell/utils/platform';
export default {
name: 'HarvesterPromptRemove',
@@ -39,8 +40,9 @@ export default {
data() {
return {
checkedList: [],
checkAll: true
checkedList: [],
checkAll: true,
alternateLabel: alternateLabelButton
};
},
@@ -138,44 +140,53 @@ export default {
</script>
<template>
<div class="mt-10">
{{ t('promptRemove.attemptingToRemove', {type}) }}
<span v-clean-html="resourceNames(names, plusMore, t)"></span>
<div>
<div class="mt-10">
{{ t('harvester.virtualMachine.promptRemove.title') }}
</div>
<div v-if="value.length === 1">
<span
v-for="(name, i) in removeNameArr[value[0].id]"
:key="i"
>
<label class="checkbox-container mr-15"><input
v-model="checkedList"
type="checkbox"
:label="name"
:value="name"
/>
{{ t('promptRemove.attemptingToRemove', {type}) }}
<span v-clean-html="resourceNames(names, plusMore, t)"></span>
<div class="mt-10">
{{ t('harvester.virtualMachine.promptRemove.title') }}
</div>
<div v-if="value.length === 1">
<span
v-for="(name, i) in removeNameArr[value[0].id]"
:key="i"
>
<label class="checkbox-container mr-15">
<input
v-model="checkedList"
type="checkbox"
:label="name"
:value="name"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ name }}
</label>
</span>
</div>
<div v-else>
<label class="checkbox-container mr-15">
<input
v-model="checkedList"
type="checkbox"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ name }}
{{ t('harvester.virtualMachine.promptRemove.deleteAll') }}
</label>
</span>
</div>
</div>
<div v-else>
<label class="checkbox-container mr-15"><input
v-model="checkAll"
type="checkbox"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ t('harvester.virtualMachine.promptRemove.deleteAll') }}
</label>
<div class="text-warning mb-10 mt-10">
{{ t('harvester.virtualMachine.promptRemove.tips') }}
</div>
<div class="text-info mt-20">
{{ t('promptRemove.protip', { alternateLabel }) }}
</div>
</div>
</template>
@@ -15,8 +15,25 @@ import HarvesterAirgapUpdgrade from '../pages/c/_cluster/airgapupgrade/index.vue
import HarvesterMembers from '../pages/c/_cluster/members/index.vue';
import ProjectNamespaces from '../pages/c/_cluster/projectsnamespaces.vue';
import HarvesterAlertmanagerReceiver from '../pages/c/_cluster/alertmanagerconfig/_alertmanagerconfigid/receiver.vue';
import HarvesterUnsupported from '../pages/c/_cluster/unsupported/index.vue';
const routes = [
{
route: {
name: `${ PRODUCT_NAME }-c-cluster-unsupported-standalone`,
path: `/:product`,
component: HarvesterUnsupported
},
parent: 'blank'
},
{
route: {
name: `${ PRODUCT_NAME }-c-cluster-unsupported`,
path: `/:product`,
component: HarvesterUnsupported
},
parent: 'plain'
},
{
name: `${ PRODUCT_NAME }-c-cluster-support`,
path: `/:product/c/:cluster/support`,
@@ -101,6 +101,19 @@ export default {
const res: any = await allHash(hash);
const isHarvesterVersionSupported = rootGetters['harvester-common/getFeatureEnabled']('supportHarvesterClusterVersion');
if (!isHarvesterVersionSupported) {
const product = rootGetters['productId'];
this.$router?.push({
name: `${ product }-c-cluster-unsupported${ id === 'local' ? '-standalone' : '' }`,
params: { product }
});
return;
}
await dispatch('cleanNamespaces', null, { root: true });
commit('updateNamespaces', {
+1
View File
@@ -8,6 +8,7 @@
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"useDefineForClassFields": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
+21 -2
View File
@@ -14,16 +14,35 @@ export function getVersion(v) {
}
try {
// v1.4.1-rc.1 => v1.4.1, v1.4.1-dev-20241222 => v1.4.1
return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
} catch (error) {
// fallback to the latest version
return Object.keys(RELEASE_FEATURES).sort((a, b) => semver.compare(a, b)).pop();
return latestVersion(Object.keys(RELEASE_FEATURES));
}
}
function latestVersion(versions) {
return versions.sort((a, b) => semver.compare(a, b)).pop();
}
// v1.3.3 => latest v1.3.x, v1.4.2 => latest v1.4.x
function latestMinorVersion(v) {
const minor = `v${ semver.major(v) }.${ semver.minor(v) }`;
const minorVersions = Object.keys(RELEASE_FEATURES).filter((version) => version.startsWith(minor));
return latestVersion(minorVersions);
}
export const featureEnabled = (featureKey, serverVersion) => {
const version = getVersion(serverVersion);
const releasedFeatures = RELEASE_FEATURES[version] || [];
let releasedFeatures = RELEASE_FEATURES[version];
if (!releasedFeatures) {
const fallback = latestMinorVersion(version);
releasedFeatures = RELEASE_FEATURES[fallback];
}
return releasedFeatures.includes(featureKey);
};
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
set -e
set -x
BUILD_DEBUG="${BUILD_DEBUG:-}"
if [[ -n "${BUILD_DEBUG}" ]]; then
set -x
env
fi
cd $(dirname $0)/..
echo "Bootstrapping..."
yarn --pure-lockfile install
source scripts/version
echo "BRANCH: ${COMMIT_BRANCH:-<none>}"
echo "TAG: ${GIT_TAG:-<none>}"
DIR=${GIT_TAG:-$COMMIT_BRANCH}
if [[ "${DIR}" == "main" ]]; then
DIR="latest"
fi
OUTPUT_DIR=dist/${DIR}-embedded
echo "Building..."
COMMIT=${COMMIT} VERSION=${VERSION} OUTPUT_DIR=$OUTPUT_DIR ROUTER_BASE='/dashboard/' RESOURCE_BASE='/dashboard/' RANCHER_ENV=harvester yarn run build
if [ -v EMBED_PKG ]; then
echo "Build and embed plugin from: $EMBED_PKG"
PKG_FILE_NAME=${EMBED_PKG##*/}
echo PKG_FILE_NAME: $PKG_FILE_NAME
PKG_NAME="${PKG_FILE_NAME/.tar.gz/""}"
echo "Plugin name: '$PKG_NAME'"
# Fetch file, unpack and move to dist
curl $EMBED_PKG --output $PKG_FILE_NAME
OUTPUT_DIR_PKG=$OUTPUT_DIR/$PKG_NAME
mkdir -p $OUTPUT_DIR_PKG
tar xvfz $PKG_FILE_NAME -C $OUTPUT_DIR/$PKG_NAME
echo "Plugin contents that will be served from $PKG_NAME"
ls -alR $OUTPUT_DIR/$PKG_NAME
fi
echo "Destroying..."
find $OUTPUT_DIR -name "index.html" -mindepth 2 -exec rm {} \;
find $OUTPUT_DIR -type d -empty -depth -exec rmdir {} \;
TARBALL=${DIR}.tar.gz
TARBALL_FILE_PATH=dist/${TARBALL}
ENV_OUTPUT="${GITHUB_OUTPUT:-"temp-env"}"
echo "BUILD_EMBEDED_TGZ=${TARBALL_FILE_PATH}" >> "$ENV_OUTPUT"
echo "Compressing to ${TARBALL}..."
tar -czf ${TARBALL_FILE_PATH} $OUTPUT_DIR/
echo "Cleaning up..."
rm -r $OUTPUT_DIR
ls -alR dist/
+34
View File
@@ -0,0 +1,34 @@
#!/bin/bash
set -e
BUILD_DEBUG="${BUILD_DEBUG:-}"
if [[ -n "${BUILD_DEBUG}" ]]; then
set -x
env
fi
cd $(dirname $0)/..
echo "Bootstrapping..."
yarn --pure-lockfile install
source scripts/version
echo "BRANCH: ${COMMIT_BRANCH:-<none>}"
echo "TAG: ${GIT_TAG:-<none>}"
DIR=${GIT_TAG:-$COMMIT_BRANCH}
if [[ "${DIR}" == "main" ]]; then
DIR="latest"
fi
BASE=${BASE:-https://releases.rancher.com/harvester-ui/dashboard/${DIR}}
echo "Building for ${BASE}..."
OUTPUT_DIR=dist/${DIR}
ENV_OUTPUT="${GITHUB_OUTPUT:-"temp-env"}"
echo "BUILD_HOSTED_DIR=${OUTPUT_DIR}" >> "$ENV_OUTPUT"
echo "BUILD_HOSTED_LOCATION=${DIR}" >> "$ENV_OUTPUT"
COMMIT=${COMMIT} VERSION=${VERSION} OUTPUT_DIR=${OUTPUT_DIR} ROUTER_BASE="/dashboard/" RESOURCE_BASE="${BASE}" RANCHER_ENV=harvester yarn run build
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
BASE_DIR="$( cd $SCRIPT_DIR && cd ../.. & pwd)"
SHELL_DIR=$BASE_DIR/shell/
EXIT_CODE=0
FORMATS="umd-min"
while getopts "df:" opt; do
case $opt in
d)
FORMATS="umd"
;;
f)
FORMATS=$OPTARG
;;
esac
done
shift $((OPTIND-1))
# Use shell folder in node modules when we have @rancher/shell installed as a node module
# rather than the use-case of the mono-repo with the shell folder at the top-level
if [ ! -d ${SHELL_DIR} ]; then
SHELL_DIR=$BASE_DIR/node_modules/@rancher/shell/
SHELL_DIR=$(cd -P ${SHELL_DIR} && pwd)
fi
CREATE_TARBALL=${2}
if [ -z "$VERSION" ]; then
VERSION=$(cd pkg/$1; node -p -e "require('./package.json').version")
fi
NAME=${1}-${VERSION}
PKG_DIST=${BASE_DIR}/dist-pkg/${NAME}
if [ -d "${BASE_DIR}/pkg/${1}" ]; then
echo "Building UI Package $1"
echo " Package name: ${NAME}"
echo " Package version: ${VERSION}"
echo " Output formats: ${FORMATS}"
echo " Output directory: ${PKG_DIST}"
rm -rf ${PKG_DIST}
mkdir -p ${PKG_DIST}
pushd pkg/${1}
# Check that the .shell link exists and points to the correct place
if [ -e ".shell" ]; then
LINK=$(readlink .shell)
if [ "${LINK}" != "${SHELL_DIR}" ]; then
echo ".shell symlink exists but does not point to expected location - please check and fix"
popd
exit -1
fi
else
ln -s ${SHELL_DIR} .shell
fi
FILE=index.js
if [ -f ./index.ts ]; then
FILE=index.ts
fi
if [ -n "$COMMIT" ]; then
echo ${COMMIT} > ${PKG_DIST}/version
fi
${BASE_DIR}/node_modules/.bin/vue-cli-service build --name ${NAME} --target lib ${FILE} --dest ${PKG_DIST} --formats ${FORMATS} --filename ${NAME}
EXIT_CODE=$?
cp -f ./package.json ${PKG_DIST}/package.json
node ${SCRIPT_DIR}/pkgfile.js ${PKG_DIST}/package.json
rm -rf ${PKG_DIST}/*.bak
rm .shell
popd
fi
if [ $EXIT_CODE -ne 0 ]; then
exit $EXIT_CODE
fi
if [ -n "${CREATE_TARBALL}" ]; then
echo $COMMIT $COMMIT_BRANCH > ${PKG_DIST}/version-commit.txt
TARBALL=${NAME}.tar.gz
pushd ${PKG_DIST}
rm -f ../$TARBALL
echo "Compressing to ${TARBALL}..."
tar -czf ../${TARBALL} .
popd
fi
exit $EXIT_CODE
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
set -e
cd $(dirname $0)/..
BUILD_DEBUG="${BUILD_DEBUG:-}"
if [[ -n "${BUILD_DEBUG}" ]]; then
set -x
env
fi
if [[ -n "$CI_BUILD_TAG" ]]; then
echo "Build has been triggered by a tag. Skipping gate"
exit 0
fi
if [[ -z "$CI_BRANCH" ]]; then
echo "No branch detected. Skipping gate"
exit 0
fi
if [[ -z "$GIT_REPO" ]]; then
echo "No repository detected. Unable to gate"
exit 1
fi
if [[ -z "$GIT_COMMIT" ]]; then
echo "No commit detected. Unable to gate"
exit 1
fi
echo "Repo: $GIT_REPO"
echo "Branch: $CI_BRANCH"
echo "Build Commit: $GIT_COMMIT"
tmp=$(mktemp -u)
STATUS_CODE=$(curl -w "%{http_code}" -s -o $tmp https://api.github.com/repos/$GIT_REPO/branches/$CI_BRANCH)
if [ "$STATUS_CODE" == "403" ]; then
RATE_LIMIT_REMAINING=$(curl -s https://api.github.com/rate_limit | ./scripts/jq-nano - rate remaining)
echo "Remaining GITHUB requests available: $RATE_LIMIT_REMAINING"
RATE_LIMIT_REMAINING=${RATE_LIMIT_REMAINING:-0}
if ((RATE_LIMIT_REMAINING < 1)); then
echo "Github Rate Limit has been hit, skipping gate"
exit 0
fi
# Fall through to the normal path like any other failed status code
fi
LATEST_BRANCH_COMMIT=$(cat $tmp | ./scripts/jq-nano - commit sha)
rm $tmp
echo "Latest Branch Commit: $LATEST_BRANCH_COMMIT"
if [ -z "$LATEST_BRANCH_COMMIT" ]; then
echo "Unable to determine latest commit, failing gate"
exit 1
fi
if [ "$LATEST_BRANCH_COMMIT" == "$GIT_COMMIT" ]; then
echo "Build was created from latest commit, passed upload gate"
else
echo "Build was not created from latest commit, failing gate"
exit 1
fi
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
yarn --pure-lockfile install
source scripts/version
if [[ $COMMIT_BRANCH == "main" ]]; then
VERSION="latest"
else
VERSION=$COMMIT_BRANCH
fi
echo "CI_BUILD_TAG: ${CI_BUILD_TAG}"
echo "GIT_TAG: ${GIT_TAG}"
echo "TAG_VERSION: ${TAG_VERSION}"
echo "CI Build Args"
echo "COMMIT: ${COMMIT}"
echo "COMMIT_BRANCH: ${COMMIT_BRANCH}"
echo "VERSION: ${VERSION}"
if [ -n "$GIT_TAG" ]; then
COMMIT=$COMMIT COMMIT_BRANCH=$COMMIT_BRANCH VERSION=$CI_BUILD_TAG ./scripts/build-pkg.sh ${1} "true"
else
COMMIT=$COMMIT COMMIT_BRANCH=$COMMIT_BRANCH VERSION=$VERSION ./scripts/build-pkg.sh ${1} "true"
fi
EXIT_CODE=$?
if [ -n "$GIT_TAG" ]; then
export PKG_NAME=${1}-${CI_BUILD_TAG}
export PKG_TARBALL=${PKG_NAME}.tar.gz
else
export PKG_NAME=${1}-${VERSION} # harvester-release-harvester-v1.5 or harvester-latest
export PKG_TARBALL=${PKG_NAME}.tar.gz # harvester-release-harvester-v1.5.tar.gz or harvester-latest.tar.gz
fi
echo "CI Build Artifacts"
echo "Package Directory: ${PKG_NAME}"
echo "Package Tarball: ${PKG_TARBALL}"
ENV_OUTPUT="${GITHUB_OUTPUT:-"temp-env"}"
echo "PKG_TARBALL=${PKG_TARBALL}" >> "$ENV_OUTPUT"
echo "PKG_NAME=${PKG_NAME}" >> "$ENV_OUTPUT"
exit $EXIT_CODE
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env node
const fs = require('fs');
const readline = require('readline');
const args = process.argv.slice(2);
const file = args.shift();
let text;
if ( file === '-' ) {
process.stdin.resume();
text = fs.readFileSync(0, "utf8");
} else {
text = fs.readFileSync(file, "utf8");
}
const data = JSON.parse(text);
let out = data;
while ( args.length ) {
out = out[args.shift()];
}
console.log(out);
+24
View File
@@ -0,0 +1,24 @@
const fs = require('fs');
console.log('Updating package file'); // eslint-disable-line no-console
if (process.argv.length !== 3) {
console.error('No enough args'); // eslint-disable-line no-console
return;
}
const pkgFilepath = process.argv[process.argv.length - 1];
const pkg = require(pkgFilepath);
// Ensure we have the keys we need
pkg.files = ['**/*'];
pkg.rancher = true;
pkg.main = `${ pkg.name }-${ pkg.version }.umd.min.js`;
delete pkg.scripts;
delete pkg.browserslist;
fs.writeFileSync(pkgFilepath, JSON.stringify(pkg, null, 2));
+57
View File
@@ -0,0 +1,57 @@
#!/bin/bash
shopt -s extglob
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
DIRTY="-dirty"
fi
COMMIT=$(git rev-parse --short HEAD)
COMMIT_DATE=$(git --no-pager log -1 --format='%ct')
COMMIT_BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's/[^a-zA-Z0-9.-]+/-/g')
GIT_TAG=${CI_BUILD_TAG:-$(git tag -l --contains HEAD | head -n 1)}
LAST_TAG=${GIT_TAG:-'v0.0.0'}
if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then
# human readable version used on rancher dashboard about page
export DASHBOARD_VERSION=$GIT_TAG
# computer readable version
VERSION=$GIT_TAG
else
# human readable version used on rancher dashboard about page
export DASHBOARD_VERSION="${COMMIT_BRANCH} ${COMMIT}"
# computer readable version
VERSION="${COMMIT}${DIRTY}"
fi
# Chart tag.
if [[ -z "${DIRTY}" && -n "${GIT_TAG}" ]]; then
CHART_VERSION="${GIT_TAG}"
elif [[ "$DIRTY" ]]; then
CHART_VERSION="${LAST_TAG}${DIRTY}.${COMMIT}"
else
CHART_VERSION="${LAST_TAG}-${COMMIT_DATE}.${COMMIT}.${COMMIT_BRANCH}"
fi
# Drop the v prefix for Chart Version to follow existing pattern.
CHART_VERSION="$(echo ${CHART_VERSION} | sed -E 's/^v//' | sed -e 's/^\(.\{55\}\).*/\1/')"
# Chart Repo - Target Repo for releases.
case $CHART_VERSION in
*-alpha*) # All alpha tags
CHART_REPO="alpha" ;;
*-rc*) # All rc tags
CHART_REPO="latest" ;;
+([0-9]).+([0-9]).+([0-9])) # All release tags
CHART_REPO="latest" ;;
*) # Anything else - Future use
CHART_REPO="dev" ;;
esac
if [ -z "$ARCH" ]; then
ARCH=amd64
fi
echo "ARCH: $ARCH"
echo "CHART_REPO: $CHART_REPO"
echo "CHART_VERSION: $CHART_VERSION"
echo "VERSION: $VERSION"
+361 -719
View File
File diff suppressed because it is too large Load Diff