Compare commits

...
234 Commits
Author SHA1 Message Date
Yiya Chen 1e0cea8986 chore: bump to 1.5.2-rc2 (#519)
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-09-10 14:18:56 +08:00
renovate[bot] af80479bd9 deps: update dependency @types/node to v20.19.13 (#516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-07 09:12:13 +00:00
Andy Lee 4caecb48a7 chore: update version to v1.5.2-rc1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-09-03 11:37:16 +08:00
renovate[bot] 8d1d370197 fix(deps): update minor dependencies (#499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 15:03:08 +08:00
renovate[bot] df9992146c chore(deps): update patch digest dependencies (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 09:33:53 +00:00
mergify[bot]andAndy Lee acbc120dc1 fix: featureFlags is undefined when installing lower version of ui-ext (#443) (#446)
* fix: featureFlags is undefined if install lower version of ui-ext

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

(cherry picked from commit 182d92d80b)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-08-01 15:41:33 +08:00
mergify[bot]andAndy Lee 59c63c8d9e ci: add package version check (backport #396) (#399)
* ci: add package version check (#396)

* ci: add package version check

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

* ci: extract check version in a script

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

* ci: update env variable

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

---------

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

# Conflicts:
#	.github/workflows/build-standalone-on-release.yaml

* fix: 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-07-16 16:22:55 +08:00
Andy Lee 4a4f2c96bb chore: bump version to v1.5.1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-27 17:39:47 +08:00
mergify[bot]andAndy Lee d7bc112a64 ci: replace tag with github.event.release.tag_name (#356) (#357)
(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-27 17:31:31 +08:00
mergify[bot]andAndy Lee 08e81b0628 fix adding existing volume in edit VM page (#351) (#352)
(cherry picked from commit 57cbd799dd)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-26 10:53:32 +08:00
4599159bff Refactor: the promptRemove.confirmRelatedResource message needs to be refactored (backport #293) (#343)
* refactor: the promptRemove.confirmRelatedResource message needs to be refactored (#293)

* feat: change dialog wordings

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>

* refactor: update protip wording

Co-authored-by: Jillian Maroket <67180770+jillian-maroket@users.noreply.github.com>
Signed-off-by: Yiya Chen <yiya.chen@suse.com>

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
Signed-off-by: Yiya Chen <yiya.chen@suse.com>
Co-authored-by: Jillian Maroket <67180770+jillian-maroket@users.noreply.github.com>
(cherry picked from commit 258476876a)

# Conflicts:
#	pkg/harvester/dialog/ConfirmRelatedToRemoveDialog.vue

* Resolve conflict

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

* Fix conflict

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-20 10:42:33 +08:00
Andy Lee ef40cf5d8d bump version to v1.5.1-rc4
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-19 10:20:37 +08:00
mergify[bot]andAndy Lee d57dbd4c8e remove disabled for upgrade action (#340) (#341)
(cherry picked from commit fb59b396d1)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-17 14:12:29 +08:00
Andy Lee 4c68d057c1 bump to v1.5.1-rc3
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-12 18:11:44 +08:00
mergify[bot]andAndy Lee b4b2017c61 Fix uploading image stuck at edit page (#336) (#337)
(cherry picked from commit bdcea54eeb)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-11 16:51:53 +08:00
mergify[bot]andAndy Lee 9f0d927c9c filter golden image volume count in dashboard (#332) (#333)
(cherry picked from commit d4d3774c3b)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-10 12:37:52 +08:00
mergify[bot]andAndy Lee 18a53f5baf fix http-proxy setting not saved (#329) (#330)
(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:03:48 +08:00
Andy Lee f872bb5abf rollback @rancher/shell to 3.0.2-rc6 (#326) 2025-06-05 17:45:41 +08:00
Andy Lee cd015d57e7 bump to v1.5.1-rc2
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-04 14:32:42 +08:00
mergify[bot]andAndy Lee 635254afdc fix API doesn't bring checksum (#323) (#324)
(cherry picked from commit 9e015ec3b1)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-03 23:16:54 +08:00
Andy Lee 3c464e9d10 bump version to v1.5.1-rc1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-28 13:24:25 +08:00
Andy Lee d412ee2aa6 fix conflict
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-28 13:05:55 +08:00
mergify[bot]andAndy Lee 580b2892cb fix upgrade button status if have errors (#316) (#321)
(cherry picked from commit cb452b9627)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-28 13:00:40 +08:00
mergify[bot]andAndy Lee 965c7d9b72 Add OS upgrade features (backport #311) (#320)
* Add OS upgrade features (#311)

* Add failed and success banner after image uploaded

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

* add delete image feature

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

* add skip checking single-replica detached volumes checkbox

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

* change delete image flow

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

* Reuse ConfirmRelatedToRemoveDialog

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

---------

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

# Conflicts:
#	pkg/harvester/dialog/ConfirmRelatedToRemoveDialog.vue

* resolve 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-28 12:58:46 +08:00
mergify[bot]andAndy Lee 01e3867da1 give default valeu for uploadImage opt (#308) (#319)
(cherry picked from commit fe32b4372b)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-28 12:49:06 +08:00
mergify[bot]andAndy Lee fd52218350 Fix OS upgrade page (#306) (#318)
* fix OS upgrade page



* move uploadFile action



* change os upgrage ui to standard feature



* Add uploadImage cancel



---------



(cherry picked from commit 6d8f6579c7)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Signed-off-by: a110605 <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-28 12:46:02 +08:00
Andy Lee 1839bf387e fix clone VM dialog error position
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-28 12:38:13 +08:00
mergify[bot]andAndy Lee 3e9a0a4377 Refactor feature flags structure (backport #276) (#314)
* Refactor feature flags structure (#276)

* refactor feature flags structure

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

* Replace with FEATURE_FLAGS and valid and sort versions

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

---------

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

# Conflicts:
#	pkg/harvester/config/feature-flags.js

* resolve 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-26 15:19:28 +08:00
mergify[bot]andAndy Lee a2898a2554 Change isKVMDisable condition (#294) (#304)
* change condition to check isKVMDisable



* change condition eq 0



* add allNotExist back



---------


(cherry picked from commit aa9dd32cce)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-19 10:19:43 +08:00
mergify[bot]andYiya Chen d11a045aed fix: range condition (#299) (#301)
(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:23 +08:00
mergify[bot] ae2a2f6d28 upgrade shell to 3.0.5-rc.2 (#297) (#298) 2025-05-15 11:25:46 +08:00
mergify[bot]andAndy Lee cf68c3976a Upgrade rancher/shell to 3.0.5-rc1 (backport #282) (#296)
* upgrade shell to 3.0.5-rc1 (#282)

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

# Conflicts:
#	yarn.lock

* solve conflict in yarn.lock

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-14 16:59:14 +08:00
Andy Lee 33f8300c0e Bump version to 1.5.1-dev
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-05-14 16:45:23 +08:00
renovate[bot] b26e34a2bd chore(deps): update dependency follow-redirects to v1.15.6 [security] (#289)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 03:11:21 +00:00
renovate[bot] 01fd3e5e61 chore(deps): update dependency ejs to v3.1.10 [security] (#288)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 03:08:36 +00:00
mergify[bot]andAndy Lee d5df7ba602 Remove unneeded condition (#279) (#280)
(cherry picked from commit 8410f75859)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-12 10:50:46 +08:00
mergify[bot]andAndy Lee 607aa106e1 fix #cloud-config disappear when add/remove ssh key (#273) (#277) (#278)
(cherry picked from commit 9dd8ae479a)



(cherry picked from commit 1e270268d8)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-08 17:38:58 +08:00
mergify[bot]andYiya Chen 49e2be6ff4 fix: skip escapehtml (#272) (#275)
(cherry picked from commit 9dd36bbd87)

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
2025-05-08 15:55:19 +08:00
mergify[bot]andYiya Chen 9ee81f4054 fix: hide btns before is ready (#267) (#268)
(cherry picked from commit 5841508b26)

Co-authored-by: Yiya Chen <yiya.chen@suse.com>
2025-05-06 11:47:46 +08:00
mergify[bot]andAndy Lee f8fa82f614 add require backport 1.5 label in mergify.yml (#265) (#266)
(cherry picked from commit 485db3066f)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-28 11:46:39 +08:00
mergify[bot]andAndy Lee a6c60de2cb bump to 1.5.0 (#263) (#264)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit af43ebbf6f)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-23 17:13:34 +08:00
mergify[bot]andAndy Lee 843b388ede update RadioGroup update value action (#255) (#260)
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:06 +08:00
mergify[bot]andAndy Lee f2e8cce29f fix unable to detach PCI & USB device in VM edit page (#247) (#249)
* fix unable to detach PCI & USB device in VM edit page

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

* refactor based on comment

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

* using set from shell/utils/object

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

---------

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-16 16:13:12 +08:00
mergify[bot]andAndy Lee 31682dd165 bump to 1.5.0-rc5 (#245) (#246)
(cherry picked from commit a5d4604dce)
Signed-off-by: Andy Lee <andy.lee@suse.com>

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-16 14:44:54 +08:00
mergify[bot]andAndy Lee 9fac43d5e5 pass showYaml prop to prevent double generate secret (#243) (#244)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit daa6d6942b)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-15 16:08:09 +08:00
mergify[bot]andAndy Lee cd6ea6e402 Add logic to block import harvester version lower than 1.3.0 (#241) (#242)
* add logic to block import harvester version lower than 1.3.0

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

* refactor console.error message

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

---------

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-10 16:24:22 +08:00
mergify[bot]andAndy Lee 3bfe57dc54 filter golden image volume in hot plug volume modal (#239) (#240)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 78234b9e1e)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-10 13:27:23 +08:00
mergify[bot]andAndy Lee 089573f193 bump to 1.5.0-rc4 (#235) (#238)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 3dc6dda1ca)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-09 14:19:19 +08:00
mergify[bot]andAndy Lee 5d0c4f0c5a hide usb device tab in v1.3.x (#236) (#237)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit fca05d8489)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-09 14:04:30 +08:00
mergify[bot]andAndy Lee 57bb1e8562 Fix undefined error in VM edit as yaml page (#233) (#234)
* fix undefined error whenclick save in  edit as yaml

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

* fix VM isEqual logic and fix undefined error

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

---------

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-07 15:33:36 +08:00
mergify[bot]andAndy Lee 30ff129691 fix secret name is different when edit as yaml (#230) (#232)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 19bea97106)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-07 10:56:49 +08:00
mergify[bot]andJack Yu 9175f16bfc chore: use default imag repo name (#212) (#231)
Signed-off-by: Jack Yu <jack.yu@suse.com>
(cherry picked from commit de4edbbf3b)

Co-authored-by: Jack Yu <jack.yu@suse.com>
2025-04-07 10:41:41 +08:00
mergify[bot]andAndy Lee 7c5e0f93d1 Bump ui-ext to 1.5.0-rc3 (#228) (#229)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 9256391627)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-02 16:03:13 +08:00
mergify[bot]andAndy Lee 8a7d3dffbe Fix error banner position in all dialogs (#226) (#227)
* 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)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-31 17:02:06 +08:00
b7810c5970 change longhorn v2 access mode to RWX (#223) (#225)
(cherry picked from commit f2fe1f5bd6)

Co-authored-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
2025-03-28 15:16:45 +08:00
mergify[bot]andAndy Lee 72dedd46d1 update to take VM snapshot logic (#222) (#224)
(cherry picked from commit f8d5aa1a21)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-28 15:13:46 +08:00
mergify[bot]andAndy Lee 24f716ab20 use harvester translation key forhost page CPU and memory (#220) (#221)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 38cf667830)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-27 15:05:41 +08:00
mergify[bot]andAndy Lee 38ad0fe1a7 upgrade ui-ext version to 1.5.0-rc2 (#208) (#218)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 18667836c2)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-25 11:44:26 +08:00
mergify[bot]andAndy Lee c84d262099 fix volume won't be dismiss after deleting (#216) (#217)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 2940f25fe0)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-24 15:33:20 +08:00
mergify[bot]andAndy Lee dbb53eae00 fix image.repo to image.repository (#214) (#215)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit a24d01717a)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-21 17:43:13 +08:00
mergify[bot]andAndy Lee 4afacd754a fix the doneOverride path for harvesterhci.io.virtualmachinetemplate (#211) (#213)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 22a032e56c)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-20 17:13:45 +08:00
mergify[bot]andAndy Lee 10d159e2c7 Add volum mode options in create VM volume tab (#209) (#210)
* add volume mode option for non-longhorn volume

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

* fix default storage class in create VM page

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

---------

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-19 17:44:58 +08:00
mergify[bot]andAndy Lee de755a662a upgrade rancher/shell to 3.0.2-rc6 (#206) (#207)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 2dff7b0a93)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-14 16:50:13 +08:00
mergify[bot]andJack Yu f1200f4508 chore: add building catalog GA (#181) (#204)
* chore: add building catalog GA

Signed-off-by: Jack Yu <jack.yu@suse.com>

* feat: should include prerelease

Signed-off-by: Jack Yu <jack.yu@suse.com>

---------

Signed-off-by: Jack Yu <jack.yu@suse.com>
(cherry picked from commit 1880043a80)

Co-authored-by: Jack Yu <jack.yu@suse.com>
2025-03-14 15:04:39 +08:00
mergify[bot]andAndy Lee 0ca2b0591a Bring status object in harvesterhci.io.upgrade dismiss PUT API (#201) (#202)
* bring status object in PUT dismiss API

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

* add cleanForSave in harvester model

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

---------

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-13 14:15:59 +08:00
mergify[bot]andAndy Lee 44f003309c Add volumeMode dropdown for third-party storage (#197) (#200)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 90c923b480)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-12 14:29:40 +08:00
mergify[bot]andAndy Lee 7c1596c965 replace tag format v1.5.0 to harvester-1.5.0 (#190) (#199)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 374b904191)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-12 12:35:01 +08:00
mergify[bot]andAndy Lee a5130986a8 fix percent typo (#198)
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 487f9abc10)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-03-11 17:24:23 +08:00
Andy Lee e4ae7cec5e Merge pull request #194 from harvester/mergify/bp/release-harvester-v1.5/pr-191
Fix missing remove button in Infobox (backport #191)
2025-03-11 12:02:41 +08:00
Andy Lee 2c7b59daed Fix infobox style
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 4e831cbc5f)
2025-03-11 03:58:53 +00:00
Andy Lee 224e34315b Merge pull request #193 from harvester/mergify/bp/release-harvester-v1.5/pr-192
remove require dot for non-longhorn v1 (backport #192)
2025-03-11 11:37:06 +08:00
Andy Lee 9f56f9eccc remove require dot for non-longhorn v1
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit b939df9b7d)
2025-03-11 03:35:42 +00:00
Andy Lee dd76956972 Merge pull request #189 from harvester/mergify/bp/release-harvester-v1.5/pr-183
Bump harvester-ui-extension to v1.5.0-rc1 (backport #183)
2025-03-06 15:58:01 +08:00
Andy Lee d92a53d9a7 bump to v1.5.0-rc1
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8ad7a57ab4)
2025-03-06 07:56:15 +00:00
Andy Lee afc3f0d87f Merge pull request #188 from harvester/mergify/bp/release-harvester-v1.5/pr-185
fix: allow set empty for backupVolumeSnapshotClassName in Setting csi-driver-config (backport #185)
2025-03-06 15:52:16 +08:00
Yi-Ya Chen a607b604f8 refactor: default should selectable
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 64b59a0c27)
2025-03-06 07:49:27 +00:00
Yi-Ya Chen 4d7b0f7350 feat: gray out class name selector
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 72415622d9)
2025-03-06 07:49:27 +00:00
Andy Lee c570b5870b Merge pull request #187 from harvester/mergify/bp/release-harvester-v1.5/pr-186
Filter exported SC for non-longhorn V1 volume (backport #186)
2025-03-06 15:37:17 +08:00
Andy Lee af36163b29 Filter export SC for non-longhorn V1 volume
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 104e98e390)
2025-03-06 07:35:24 +00:00
Andy Lee b3dbc2f0d3 Merge pull request #184 from harvester/mergify/bp/release-harvester-v1.5/pr-182
Add HarvesterImageDownloader for cdi image (backport #182)
2025-03-06 14:54:04 +08:00
Andy Lee fed86d4c24 Remove converting sentence
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit cbd5e45200)
2025-03-06 06:50:24 +00:00
Andy Lee d18ebfb26b Add HarvesterImageDownloader
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 966d4d6709)
2025-03-06 06:50:24 +00:00
Andy Lee 477206d8d0 Merge pull request #180 from harvester/mergify/bp/release-harvester-v1.5/pr-175
Add label tab to vm template (backport #175)
2025-03-04 17:42:29 +08:00
Andy Lee fd0836d489 Add label tab to vm template
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit efdfdbf0b8)
2025-03-04 09:36:33 +00:00
Andy Lee 2e3536abc6 Merge pull request #179 from harvester/mergify/bp/release-harvester-v1.5/pr-178
Fix  undefined reading harvesterhci.io/storageClassName (backport #178)
2025-03-04 11:03:20 +08:00
Andy Lee 45c0dce61b fix undefined reading harvesterhci.io/storageClassName
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 54f85963ea)
2025-03-04 03:02:06 +00:00
Andy Lee 6b8b1f68eb Merge pull request #177 from harvester/mergify/bp/release-harvester-v1.5/pr-173
Introduce VM Pending status (backport #173)
2025-03-04 10:55:29 +08:00
Andy Lee bb645c00db introduce VM pending status
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 05ff8e4f19)
2025-03-04 02:49:45 +00:00
Andy Lee ed8c6e81ca Merge pull request #176 from harvester/mergify/bp/release-harvester-v1.5/pr-174
fix: align all memory / storage / quota units to Gi/Mi (backport #174)
2025-03-04 10:49:03 +08:00
Andy Lee ed692b579d update harvesterhci.io.host
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8fbbfc90f1)
2025-03-04 02:47:43 +00:00
Andy Lee f02074a25e Extract variables to utils/unit
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8db4de1c4a)
2025-03-04 02:47:42 +00:00
Andy Lee a8f8f0d288 fix: align all memory / storage / quota units to Gi/Mi
Signed-off-by: Andy Lee <andy.lee@suse.com>
(cherry picked from commit 8f76d5ad30)
2025-03-04 02:47:42 +00:00
Andy Lee 9fa9b3837b Merge pull request #172 from harvester/mergify/bp/release-harvester-v1.5/pr-171
Add refreshIntervalInSecond in 1.4.2 feature flag (backport #171)
2025-02-27 17:29:36 +08:00
andy.lee 4b3bdf8a59 add refreshIntervalInSecond in 1.4.2 feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 30dc56cad5)
2025-02-27 09:24:58 +00:00
Andy Lee 7bdf40d737 Merge pull request #170 from harvester/mergify/bp/release-harvester-v1.5/pr-169
fix golden image error when creating LVM volume (backport #169)
2025-02-27 13:51:29 +08:00
andy.lee a1e7345437 fix golden image error when creating LVM volume
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit ca7eccd688)
2025-02-27 05:46:58 +00:00
Andy Lee 9322a77b20 Merge pull request #168 from harvester/mergify/bp/release-harvester-v1.5/pr-167
Hide non-longhorn volume external link (backport #167)
2025-02-26 17:08:30 +08:00
andy.lee ed7d7ffd2e hide non longhorn volume external link
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 046947de32)
2025-02-26 09:01:23 +00:00
Andy Lee 2ef8e2e0aa Merge pull request #166 from harvester/mergify/bp/release-harvester-v1.5/pr-164
Add refreshIntervalInSeconds in backup targe edit page (backport #164)
2025-02-26 16:19:19 +08:00
andy.lee a0ea6beedb Add refreshIntervalInSeconds in backup targe edit page
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 7e00a08e35)
2025-02-26 08:18:13 +00:00
Andy Lee 85a723e20c Merge pull request #163 from harvester/mergify/bp/release-harvester-v1.5/pr-162
Fix upgrade cluster model styles (backport #162)
2025-02-26 09:35:19 +08:00
andy.lee bbe6b1bdf0 change overallMessage color to red
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 50901a2eaf)
2025-02-26 01:32:24 +00:00
andy.lee 81a572e5b5 fix upgrade model style
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit ff439e66a9)
2025-02-26 01:32:24 +00:00
Andy Lee 1a727f0001 Merge pull request #161 from harvester/mergify/bp/release-harvester-v1.5/pr-159
Add Labels Tab in VM edit page (backport #159)
2025-02-24 17:44:35 +08:00
andy.lee 91862244e1 Add labels tab in VM edit pae
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 9cd3cd6492)
2025-02-24 09:42:11 +00:00
andy.lee c7b0e9ee7a update catalog.cattle.io/rancher-version to 2.11.0
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit e07042b9c9)
2025-02-24 09:42:10 +00:00
Andy Lee d9337ff188 Merge pull request #160 from harvester/mergify/bp/release-harvester-v1.5/pr-152
Fix usb and pci devices unable to passthrough (backport #152)
2025-02-24 16:48:46 +08:00
andy.lee c16922c228 add pcideviceclaim and usbdeviceclaim models
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 188f058df8)
2025-02-24 08:41:07 +00:00
Andy Lee bbade7e50c Merge pull request #158 from harvester/mergify/bp/release-harvester-v1.5/pr-157
Limit harvester-ui-extension to install on Rancher 2.11.0+ (backport #157)
2025-02-21 16:40:28 +08:00
andy.lee 193bf092d5 Update catalog.cattle.io/rancher-version to 2.11.0
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 36c68eefb1)
2025-02-21 08:39:18 +00:00
Andy Lee 308b6ddbd7 Merge pull request #156 from harvester/mergify/bp/release-harvester-v1.5/pr-154
Bump harvester-ui-extension version to v1.5.0-dev (backport #154)
2025-02-21 15:07:19 +08:00
andy.lee c850b78da2 bump to v1.5.0-dev
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 0a782fa4d5)
2025-02-21 07:01:38 +00:00
Andy Lee 0fe9557eb8 Merge pull request #155 from harvester/mergify/bp/release-harvester-v1.5/pr-148
Update installation flow in README.md (backport #148)
2025-02-21 14:43:19 +08:00
andy.lee b6953d73f7 Update installation flow in README.md
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit f855a0add5)
2025-02-21 06:13:47 +00:00
Andy Lee 8a89a9c502 Merge pull request #153 from harvester/mergify/bp/release-harvester-v1.5/pr-151
feat: set up a way to test UI changes in Harvester UI when accessed via Rancher (backport #151)
2025-02-21 13:37:14 +08:00
Yi-Ya Chen d5f17bfdf1 ci: rename file
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 7f98dfe9c8)
2025-02-21 05:34:10 +00:00
Yi-Ya Chen 9d0bb6e1f4 ci: revert change
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 6a3153a7ec)
2025-02-21 05:34:10 +00:00
Yi-Ya Chen 2ec37d81d9 ci: rename the workflow
(cherry picked from commit 629cb0c601)
2025-02-21 05:34:10 +00:00
Yi-Ya Chen e50bff8b5e ci: add ci pipeline
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 0c3fe22df7)
2025-02-21 05:34:09 +00:00
Andy Lee e898181834 Merge pull request #150 from harvester/mergify/bp/release-harvester-v1.5/pr-149
More third party storage UI change (backport #149)
2025-02-20 14:53:03 +08:00
andy.lee a0f877a23e More third party storage UI change
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 66f53c8a00)
2025-02-20 06:45:48 +00:00
Andy Lee a805490930 Merge pull request #147 from harvester/mergify/bp/release-harvester-v1.5/pr-145
External/third-party storage UI change (backport #145)
2025-02-19 17:06:44 +08:00
andy.lee 887a122358 allow sorting storage class column in volume list page
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 4a303e514c)
2025-02-19 09:05:28 +00:00
andy.lee 1f48e8aea7 add thirdPartyStorage feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit b828c2f66d)
2025-02-19 09:05:28 +00:00
andy.lee b2c1a3b068 add third party storage UI change
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 40794d89a0)
2025-02-19 09:05:28 +00:00
Andy Lee df1566f394 Merge pull request #146 from harvester/mergify/bp/release-harvester-v1.5/pr-143
Fix sshkey is not added to cloud init user data immediately (backport #143)
2025-02-19 16:45:40 +08:00
andy.lee 0a3b122316 fix sshkey is not added to cloud init user data immediately
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit e2daa1ea0c)
2025-02-19 08:23:47 +00:00
Yiya Chen d497613cc4 Merge pull request #144 from harvester/mergify/bp/release-harvester-v1.5/pr-142
fix: no progress circle on the top after triggering upgrade (backport #142)
2025-02-19 09:30:31 +08:00
Yi-Ya Chen 91494f5832 fix: register upgrade component in header
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit b499f62c56)
2025-02-19 01:16:26 +00:00
Andy Lee c61d30a607 Merge pull request #141 from harvester/mergify/bp/release-harvester-v1.5/pr-140
update yarn.lock (backport #140)
2025-02-17 12:15:56 +08:00
andy.lee 9428a07850 update yarn.lock
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 9d02623b3a)
2025-02-17 04:14:24 +00:00
Andy Lee 28603d3ab3 Merge pull request #139 from harvester/mergify/bp/release-harvester-v1.5/pr-138
fix: Harvester ui extension not working built from CI (backport #138)
2025-02-17 12:13:35 +08:00
Yi-Ya Chen f863b881a9 feat: fix $ctx redefine issue
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit cbe66f4660)
2025-02-17 03:57:25 +00:00
Andy Lee 083ab9f3e9 Merge pull request #136 from harvester/mergify/bp/release-harvester-v1.5/pr-134
Move harvester schema.js into harvester folder (backport #134)
2025-02-14 13:33:29 +08:00
Andy Lee 5d52c368a1 Merge pull request #137 from harvester/mergify/bp/release-harvester-v1.5/pr-133
Upgrade @rancher/shell to v3.0.2-rc3 (backport #133)
2025-02-14 13:33:18 +08:00
andy.lee d72f379ab1 upgrade @rancher/shell to v3.0.2-rc3
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 0153f9d985)
2025-02-14 05:30:40 +00:00
andy.lee 2931248fdb move harvester schema.js into harvester folder
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 548bdd2835)
2025-02-14 05:30:24 +00:00
Yiya Chen 7166a5b1bb Merge pull request #135 from harvester/mergify/bp/release-harvester-v1.5/pr-132
feat: add EFI persistent state checkbox in VM advanced tab (backport #132)
2025-02-14 09:14:42 +08:00
Yi-Ya Chen 00c884cda0 lint: fix eslint
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 70a56c75b0)
2025-02-13 10:04:33 +00:00
Yi-Ya Chen 1c65b71e35 refactor: remove persistent property if unchecked
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 31a9abd5c2)
2025-02-13 10:04:33 +00:00
Yi-Ya Chen 3e4bc31a9d fix: state not update when the TPM checkbox is toggled
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 6d5139a994)
2025-02-13 10:04:32 +00:00
Yi-Ya Chen bc4bbd9a40 feat: add efi persistent state
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit b92f22fa75)
2025-02-13 10:04:32 +00:00
Yiya Chen a6666ef384 Merge pull request #131 from harvester/mergify/bp/release-harvester-v1.5/pr-128
feat: improve Single-Replica Volume Detection for Upgrade (backport #128)
2025-02-12 10:52:50 +08:00
Yi-Ya Chen 92588c9adc refactor: fix conflicts
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2025-02-12 09:35:21 +08:00
Yi-Ya Chen 832df26efc feat: add divider
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 3b054b35c6)
2025-02-12 01:10:53 +00:00
Yi-Ya Chen f7869cb241 refactor: align key spacing
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit fd3f5ec1e6)
2025-02-12 01:10:53 +00:00
Yi-Ya Chen 27c0124682 feat: add feature flag
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 72c6741af1)

# Conflicts:
#	pkg/harvester/config/feature-flags.js
2025-02-12 01:10:52 +00:00
Yi-Ya Chen 6c69579c41 feat: add checkbox to skip checking
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 375127a78a)
2025-02-12 01:10:52 +00:00
Andy Lee 145b534c5d Merge pull request #130 from harvester/mergify/bp/release-harvester-v1.5/pr-127
fix: unable to create new sshkey in VM create page (backport #127)
2025-02-11 17:36:50 +08:00
andy.lee ce017a0e40 fix: unable to create new sshkey in VM create page
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 176946e5e9)
2025-02-11 09:30:37 +00:00
Andy Lee 72a6f77fd2 Merge pull request #129 from harvester/mergify/bp/release-harvester-v1.5/pr-119
Add VlanL2Network / UntaggedNetwork types in network-storage setting (backport #119)
2025-02-11 17:29:20 +08:00
andy.lee 275b15ad62 update feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit fc09e030a0)
2025-02-11 09:18:03 +00:00
andy.lee 06c9a8b3d9 extract network_type to config/types
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 5f76da4629)
2025-02-11 09:18:02 +00:00
andy.lee 36b1406738 fix number of IP formula
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 3a3122f3a1)
2025-02-11 09:18:02 +00:00
andy.lee 06b13a9cc5 add network type in storage-network setting
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 229cbf1f69)
2025-02-11 09:18:01 +00:00
Andy Lee 2a3cd32447 Merge pull request #126 from harvester/mergify/bp/release-harvester-v1.5/pr-125
fix: vGPU number not correct (backport #125)
2025-02-11 13:17:51 +08:00
andy.lee 40dc31226b fix: vGPU number not correct
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 26afffc2f1)
2025-02-11 05:13:50 +00:00
Andy Lee def9e1a24e Merge pull request #124 from harvester/mergify/bp/release-harvester-v1.5/pr-121
The hints on the branding page is using the wrong product name (backport #121)
2025-02-11 11:45:16 +08:00
andy.lee da25b28f7f remove unneeded translation keys
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-02-11 11:39:48 +08:00
Volker Theile 10abbd0452 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 d023fa1de4)
2025-02-11 03:25:22 +00:00
Andy Lee 8a7cc69a69 Merge pull request #123 from harvester/mergify/bp/release-harvester-v1.5/pr-122
fix: isEncrypted breaks all the volume page (backport #122)
2025-02-11 11:21:42 +08:00
andy.lee c188f3367d fix: isEncrypted breaks all the volume page
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 2232e56ba1)
2025-02-11 03:20:33 +00:00
Andy Lee 26d85a7760 Merge pull request #120 from harvester/mergify/bp/release-harvester-v1.5/pr-117
feat: add support for persistent TPM in VMs (backport #117)
2025-02-11 10:36:45 +08:00
Yi-Ya Chen 6eb845b46b refactor: update feature flag
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 1651e201e8)
2025-02-11 02:28:10 +00:00
Yi-Ya Chen 8c20ddb440 feat: add feature flag
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 43b30ec3c3)
2025-02-11 02:28:10 +00:00
Yi-Ya Chen a92f27d80c refactor: rewrite render logic
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 68ba934b5a)
2025-02-11 02:28:09 +00:00
Yi-Ya Chen f523a161b6 feat: add checkbox to create templates
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 84bacfaff5)
2025-02-11 02:28:09 +00:00
Yi-Ya Chen ad2273b5ba feat: add persistent state checkbox
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit e8017e14c8)
2025-02-11 02:28:09 +00:00
Andy Lee 171939e66b Merge pull request #118 from harvester/mergify/bp/release-harvester-v1.5/pr-116
Fix unable to add/delete new hard disk in edit node page (backport #116)
2025-02-07 11:32:08 +08:00
andy.lee 18bfbbaac1 fix unable to add/delete new hard disk
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit cbb66175f6)
2025-02-07 03:23:49 +00:00
Andy Lee e79f493cb6 Merge pull request #114 from harvester/mergify/bp/release-harvester-v1.5/pr-13
align CPU pinning checkbox position in cloud template (backport #13)
2025-02-06 14:36:03 +08:00
andy.lee e84c0fe886 align CPU pinning checkbox position in cloud template
(cherry picked from commit 59bf735f8b)
2025-02-06 14:23:12 +08:00
Andy Lee bbfeb55af9 Merge pull request #113 from harvester/mergify/bp/release-harvester-v1.5/pr-110
Prevent display lock icon if this.volumes is empty array (backport #110)
2025-02-06 12:25:26 +08:00
andy.lee e7446e7e23 prevent display icon if this.volumes is empty array
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit c75755a527)
2025-02-06 03:48:41 +00:00
Andy Lee adacaf65fa Merge pull request #111 from harvester/mergify/bp/release-harvester-v1.5/pr-109
Add v1.4.2 feature flag to v1.5 branch (backport #109)
2025-02-06 11:47:13 +08:00
andy.lee 7c761b2829 add v1.4.2 feature flag
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit e8f282395e)
2025-02-05 09:45:46 +00:00
Andy Lee cc8532ff99 Merge pull request #108 from harvester/mergify/bp/release-harvester-v1.5/pr-107
upgrade @rancher/shell to v3.0.2-rc2 (backport #107)
2025-02-05 12:34:11 +08:00
andy.lee ede281a42d upgrade @rancher/shell to v3.0.2-rc2
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 426e15084e)
2025-02-05 04:32:14 +00:00
Yiya Chen d34279d98f Merge pull request #106 from harvester/mergify/bp/release-harvester-v1.5/pr-103
fix: VM Snapshot and Backup List Views Fail to Load After Creation (backport #103)
2025-01-24 14:28:22 +08:00
Yi-Ya Chen efd93c5e52 fix: get sourceSchedule exception
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 12cadbf667)
2025-01-24 02:08:49 +00:00
Yiya Chen 8d338b5f72 Merge pull request #102 from harvester/mergify/bp/release-harvester-v1.5/pr-94
feat: add confirmation pop-up for VM stop and pause actions (backport #94)
2025-01-21 10:46:51 +08:00
Yiya Chen f59c81da31 Merge pull request #101 from harvester/mergify/bp/release-harvester-v1.5/pr-83
fix: cluster member role user should not be able to delete node on Harvester Hosts page (backport #83)
2025-01-21 10:45:00 +08:00
Yi-Ya Chen cf6d44601e style: change apply button color
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit c4e9a89ba5)
2025-01-21 02:24:43 +00:00
Yi-Ya Chen 89b43060df feat: handle bulk actions
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit f2d5172224)
2025-01-21 02:24:43 +00:00
Yi-Ya Chen 7194d52000 refactor: remove the confirm input
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit fdc635955b)
2025-01-21 02:24:43 +00:00
Yi-Ya Chen a8ea0645e4 feat: add confirm popup
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 526d1bb875)
2025-01-21 02:24:42 +00:00
Yi-Ya Chen 713173b0e4 refactor: get value from parent class
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit e95955fad6)
2025-01-21 02:24:30 +00:00
Yi-Ya Chen 6ed13bcec1 fix: check user delete permission
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit f5961f3a59)
2025-01-21 02:24:30 +00:00
Yi-Ya Chen 02519820d7 refactor: fix lint warnings
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 3565b44e3a)
2025-01-21 02:24:29 +00:00
Yi-Ya Chen 2604101eb2 refactor: move logic to node model
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 43b10b250e)
2025-01-21 02:24:29 +00:00
Yi-Ya Chen 1cb16f0c74 fix: disable delete action
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit c791399a61)
2025-01-21 02:24:29 +00:00
Andy Lee 752cf2e93a Merge pull request #100 from harvester/mergify/bp/release-harvester-v1.5/pr-89
fix: remove witness node in VM node scheduling tab (backport #89)
2025-01-20 20:04:07 +08:00
andy.lee f4ea7b6ea0 fix: remove witness node in VM node scheduling tab
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 993706fead)
2025-01-20 12:01:23 +00:00
Andy Lee a6405989e9 Merge pull request #99 from harvester/mergify/bp/release-harvester-v1.5/pr-93
fix: encrypt option can only choose encrypted storage class when creating VM image (backport #93)
2025-01-20 11:50:29 +08:00
andy.lee fb8a81fd0f refactor: fallback to longhorn if not default storage class
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 6e9350e63d)
2025-01-20 03:39:28 +00:00
andy.lee 3a5ead3c2c fix: when choose encrypt can only choose encrypted storage class when creating image
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit b0c7b1fefe)
2025-01-20 03:39:28 +00:00
Andy Lee def4b64cd3 Merge pull request #98 from harvester/mergify/bp/release-harvester-v1.5/pr-97
chore: update copyright year to 2025 (backport #97)
2025-01-17 17:30:56 +08:00
andy.lee a191b845d8 chore: update company name to SUSE
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 8f3a2b202d)
2025-01-17 09:28:13 +00:00
andy.lee 32a57d791a chore: update copyright year
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 0ea3f56f2c)
2025-01-17 09:28:12 +00:00
Andy Lee 2a7e51ef4c Merge pull request #96 from harvester/mergify/bp/release-harvester-v1.5/pr-92
chore: bump release version to v1.0.3 (backport #92)
2025-01-17 10:04:08 +08:00
Andy Lee f726fb99e5 Merge pull request #95 from harvester/mergify/bp/release-harvester-v1.5/pr-91
fix: bring default valueContext when customizing vm-import controller (backport #91)
2025-01-17 10:02:07 +08:00
andy.lee 9c19ae1f9b chore: bump release version to v1.0.3
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 6644651fe5)
2025-01-17 01:58:33 +00:00
andy.lee 6ece2d0284 fix: bring default valueContext fwhen customizing vm-import controller
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 2d6bc65b26)
2025-01-17 01:57:43 +00:00
Andy Lee aec424b007 Merge pull request #90 from harvester/mergify/bp/release-harvester-v1.5/pr-86
Replace sortablejs-vue3 (backport #86)
2025-01-15 11:51:24 +08:00
Francesco Torchia ff80b91311 Fix Volumes draggable key
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
(cherry picked from commit d9db1263d1)
2025-01-14 16:03:46 +00:00
Francesco Torchia 52252c16f7 Fix draggable Volumes disabled prop
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
(cherry picked from commit 10a8f18784)
2025-01-14 16:03:45 +00:00
Francesco Torchia 19cf1e9302 Replace sortablejs-vue3
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
(cherry picked from commit a7b4366d7c)
2025-01-14 16:03:45 +00:00
Andy Lee ddb8e8106b Merge pull request #87 from harvester/mergify/bp/release-harvester-v1.5/pr-85
fix: filter schedule style and functionality (backport #85)
2025-01-14 13:23:23 +08:00
Andy Lee 9af5ff7b12 Merge pull request #88 from harvester/mergify/bp/release-harvester-v1.5/pr-84
fix: resume schedule job error (backport #84)
2025-01-14 13:12:17 +08:00
andy.lee fff6153f15 fix: resume schedule job error
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 8116358011)
2025-01-14 04:56:24 +00:00
andy.lee 1ecac7adf4 chore: disable filter button if all rows do not have sourceSchedule
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit d72857f374)
2025-01-14 04:56:23 +00:00
andy.lee 408818904a fix: filter schedule style and functionality
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 57ab9e2830)
2025-01-14 04:56:23 +00:00
Andy Lee b96637e6d9 Merge pull request #82 from harvester/mergify/bp/release-harvester-v1.5/pr-79
fix: add missing wording in delete VM modal  (backport #79)
2025-01-13 13:39:16 +08:00
andy.lee 8bdaca777f fix: delete VM modal style and wording
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 7e32ea453b)
2025-01-13 05:37:38 +00:00
Andy Lee d278687e75 Merge pull request #81 from harvester/mergify/bp/release-harvester-v1.5/pr-72
[porting 1262] Fix vm rows reactivity (backport #72)
2025-01-13 12:03:31 +08:00
Francesco Torchia 3ea8edb824 [porting 1262] Fix vm rows reactivity
Signed-off-by: Francesco Torchia <francesco.torchia@suse.com>
(cherry picked from commit c3f2665912)
2025-01-13 03:59:24 +00:00
Andy Lee e33302050b Merge pull request #78 from harvester/mergify/bp/release-harvester-v1.5/pr-71
chore: add v1.5.0 and v1.3.3 feature flags (backport #71)
2025-01-10 16:35:40 +08:00
andy.lee 8fefb59802 chore: add v1.5.0 and v1.3.3 feature flags
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 2a58745550)
2025-01-10 06:37:35 +00:00
Andy Lee 872aec1c10 Merge pull request #76 from harvester/mergify/bp/release-harvester-v1.5/pr-68
fix: missing label in VM scheduling tab (backport #68)
2025-01-10 14:35:22 +08:00
Andy Lee 8814bd4cda Merge pull request #77 from harvester/mergify/bp/release-harvester-v1.5/pr-73
fix: exception error in cluster outputs create page (backport #73)
2025-01-10 14:34:57 +08:00
Yi-Ya Chen 72a17cd9c2 fix: replace set
Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
(cherry picked from commit 7645d6161f)
2025-01-10 06:31:32 +00:00
andy.lee 8d79b69748 fix: remove translation key
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 8b43c200b2)
2025-01-10 06:30:46 +00:00
andy.lee 47760a8426 fix: update translation key
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit ab71cb377c)
2025-01-10 06:30:46 +00:00
andy.lee e6fc641a29 fix: missing label in VM scheduling tab
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit eb667fd56b)
2025-01-10 06:30:46 +00:00
Andy Lee 90b85d2be7 Merge pull request #75 from harvester/mergify/bp/release-harvester-v1.5/pr-69
fix: failed to resume /active vm schedule job (backport #69)
2025-01-10 14:26:49 +08:00
Andy Lee ee9575f710 Merge pull request #74 from harvester/mergify/bp/release-harvester-v1.5/pr-70
fix: serial console not work (backport #70)
2025-01-10 14:25:30 +08:00
andy.lee 7d97e6821c fix: resume/active scheduling job failed
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 814d296d75)
2025-01-10 06:23:31 +00:00
andy.lee 42fc9d8fae fix: serial console not work
Signed-off-by: andy.lee <andy.lee@suse.com>
(cherry picked from commit 69d7046bc7)
2025-01-10 06:21:29 +00:00
andy.lee 0f03fc0805 change mergify.yml base branch to main
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:39:04 +08:00
andy.lee 6d4a7cd1b1 add mergify.yml
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:38:55 +08:00
Andy Lee f69d445ef7 Merge pull request #66 from harvester/revert-65-test_word
Revert "setting test word for v1.5 release branch"
2025-01-06 20:59:07 +08:00
Andy Lee d358a0a618 Revert "setting test word for v1.5 release branch" 2025-01-06 20:57:38 +08:00
Andy Lee 47841e49b1 Merge pull request #65 from a110605/test_word
setting test word for v1.5 release branch
2025-01-06 20:51:27 +08:00
andy.lee 57c7f0f856 setting test word
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 20:46:47 +08:00
121 changed files with 4313 additions and 3020 deletions
+20
View File
@@ -0,0 +1,20 @@
pull_request_rules:
- name: Automatically open backport PR to release-harvester-v1.5
conditions:
- base=main
- label="require backport 1.5"
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,36 @@
name: Build Harvester Catalog Image and Publish on Release
on:
workflow_dispatch:
release:
types: [published]
defaults:
run:
shell: bash
working-directory: ./
jobs:
check-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check package version
env:
TAG_VERSION: ${{ github.event.release.tag_name }}
run: ./scripts/check-version.sh $TAG_VERSION
build-and-push-extension-catalog:
needs: check-version
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
@@ -1,4 +1,4 @@
name: Build Harvester Dashboard and Upload
name: Build Harvester Dashboard and Publish
on:
workflow_call:
@@ -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,54 @@
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
check-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check package version
env:
TAG_VERSION: ${{ github.event.release.tag_name }}
run: ./scripts/check-version.sh $TAG_VERSION
build-extension-charts:
needs:
- setup-release-tag
- check-version
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 }}'
@@ -1,4 +1,4 @@
name: Build Dashboard (Branch)
name: Build Standalone on PR Merge
on:
push:
@@ -11,16 +11,16 @@ on:
- main
- 'release-harvester-v*'
- '*-dev'
types:
types:
- merged
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/test.yaml
uses: ./.github/workflows/run-lint.yaml
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-upload.yaml
uses: ./.github/workflows/build-and-publish-standalone.yaml
needs:
- build-validation
permissions:
@@ -0,0 +1,27 @@
name: Build Standalone on Release
on:
push:
tags:
- v[1-9].*
jobs:
check-version:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check package version
env:
TAG_VERSION: ${{github.ref_name}}
run: ./scripts/check-version.sh $TAG_VERSION
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml
needs: check-version
permissions:
contents: read
packages: write
id-token: write
with:
CI_BUILD_TAG: ${{github.ref_name}}
+2 -15
View File
@@ -8,20 +8,7 @@ From Rancher 2.10.0, Harvester integration is provided by this new Harvester UI
## 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
To install the Harvester UI Extension in Rancher 2.10.0 and newer, please refer to [installation](https://docs.harvesterhci.io/v1.5/rancher/harvester-ui-extension#installation-on-rancher-210) on harvester doc.
## Running for Development
@@ -70,7 +57,7 @@ For developers, after reading through the introduction on this page, head over t
License
=======
Copyright (c) 2014-2023 [Rancher Labs, Inc.](http://rancher.com)
Copyright (c) 2014-2025 [SUSE, LLC.](https://www.suse.com/)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+13 -12
View File
@@ -1,35 +1,36 @@
{
"name": "harvester-ui-extension",
"version": "0.1.0",
"version": "1.5.2-rc2",
"private": false,
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@rancher/shell": "3.0.1-rc.4",
"@rancher/shell": "3.0.2-rc.6",
"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.20",
"merge": "2.1.1",
"node-forge": "1.3.1",
"nth-check": "2.1.1",
"qs": "6.11.1",
"roarr": "7.0.4",
"semver": "7.5.4",
"qs": "6.14.0",
"roarr": "7.21.1",
"semver": "7.7.2",
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
},
"scripts": {
+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'
]
};
+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;
+35 -8
View File
@@ -5,8 +5,10 @@ import { Checkbox } from '@components/Form/Checkbox';
import ModalWithCard from '@shell/components/ModalWithCard';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { Banner } from '@components/Banner';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { HCI } from '../types';
import UpgradeInfo from './UpgradeInfo';
export default {
name: 'HarvesterUpgrade',
@@ -28,14 +30,15 @@ export default {
data() {
return {
upgrade: [],
upgradeMessage: [],
errors: '',
selectMode: true,
version: '',
enableLogging: true,
readyReleaseNote: false,
isOpen: false
upgrade: [],
upgradeMessage: [],
errors: '',
selectMode: true,
version: '',
enableLogging: true,
skipSingleReplicaDetachedVol: false,
readyReleaseNote: false,
isOpen: false
};
},
@@ -68,6 +71,10 @@ export default {
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
},
skipSingleReplicaDetachedVolFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
},
releaseLink() {
return `https://github.com/harvester/harvester/releases/tag/${ this.version }`;
}
@@ -104,6 +111,11 @@ export default {
spec: { version: this.version }
};
if (this.skipSingleReplicaDetachedVolFeatureEnabled && this.skipSingleReplicaDetachedVol) {
upgradeValue.metadata.annotations =
{ [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
}
if (this.canEnableLogging) {
upgradeValue.spec.logEnabled = this.enableLogging;
}
@@ -190,6 +202,21 @@ export default {
/>
</div>
<div
v-if="skipSingleReplicaDetachedVolFeatureEnabled"
class="mb-5"
>
<Checkbox
v-model:value="skipSingleReplicaDetachedVol"
class="check"
type="checkbox"
:label="t('harvester.upgradePage.skipSingleReplicaDetachedVol')"
/>
</div>
<hr
v-if="version"
class="divider"
/>
<div v-if="version">
<p
v-clean-html="t('harvester.upgradePage.releaseTip', {url: releaseLink}, true)"
@@ -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
}
},
+1 -1
View File
@@ -43,7 +43,7 @@ export default {
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
</p>
<p class="mb-5">
<p>
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a
:href="releaseVersion"
target="_blank"
@@ -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,18 @@ 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;
},
add() {
this.configArr.push({
key: '',
@@ -184,6 +196,7 @@ export default {
<InfoBox
v-for="(driver, idx) in configArr"
:key="idx"
class="box"
>
<button
:disabled="disableEdit(driver.key)"
@@ -226,9 +239,9 @@ 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')"
@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
@@ -12,6 +12,9 @@ import { NODE } from '@shell/config/types';
import { HCI } from '../../types';
import { DOC } from '../../config/doc-links';
import { docLink } from '../../utils/feature-flags';
import { NETWORK_TYPE } from '../../config/types';
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
export default {
name: 'HarvesterEditStorageNetwork',
@@ -57,11 +60,14 @@ export default {
data() {
let parsedDefaultValue = {};
let openVlan = false;
let networkType = L2VLAN;
try {
parsedDefaultValue = JSON.parse(this.value.value);
networkType = 'vlan' in parsedDefaultValue ? L2VLAN : UNTAGGED; // backend doesn't provide networkType, so we check if vlan is provided instead
openVlan = true;
} catch (error) {
networkType = L2VLAN;
parsedDefaultValue = {
vlan: '',
clusterNetwork: '',
@@ -73,6 +79,7 @@ export default {
return {
openVlan,
networkType,
errors: [],
exclude,
parsedDefaultValue,
@@ -87,16 +94,35 @@ export default {
},
computed: {
showVlan() {
return this.networkType === L2VLAN;
},
networkTypes() {
const types = [L2VLAN];
if (this.untaggedNetworkSettingEnabled) {
types.push(UNTAGGED);
}
return types;
},
storageNetworkExampleLink() {
const version = this.$store.getters['harvester-common/getServerVersion']();
return docLink(DOC.STORAGE_NETWORK_EXAMPLE, version);
},
untaggedNetworkSettingEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('untaggedNetworkSetting');
},
clusterNetworkOptions() {
const inStore = this.$store.getters['currentProduct'].inStore;
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
// untaggedNetwork filter out mgmt cluster network
const clusterNetworksOptions = this.networkType === UNTAGGED ? clusterNetworks.filter((net) => net.id !== 'mgmt') : clusterNetworks;
return clusterNetworks.map((n) => {
return clusterNetworksOptions.map((n) => {
const disabled = !n.isReadyForStorageNetwork;
return {
@@ -108,7 +134,50 @@ export default {
},
},
watch: {
networkType: {
handler(neu) {
this.parsedDefaultValue.clusterNetwork = '';
if (neu === L2VLAN) {
this.parsedDefaultValue.vlan = '';
} else {
delete this.parsedDefaultValue.vlan;
}
},
deep: true
}
},
methods: {
inputVlan(neu) {
if (neu === '') {
this.parsedDefaultValue.vlan = '';
return;
}
const newValue = Number(neu);
if (newValue > 4094) {
this.parsedDefaultValue.vlan = 4094;
} else if (newValue < 1) {
this.parsedDefaultValue.vlan = 1;
} else {
this.parsedDefaultValue.vlan = newValue;
}
},
useDefault() {
this.openVlan = false;
this.networkType = L2VLAN;
this.parsedDefaultValue = {
vlan: '',
clusterNetwork: '',
range: '',
exclude: []
};
},
update() {
const exclude = this.exclude.filter((ip) => ip);
@@ -138,7 +207,7 @@ export default {
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
}
if (!this.parsedDefaultValue.vlan) {
if (this.networkType === L2VLAN && !this.parsedDefaultValue.vlan) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
}
@@ -191,13 +260,25 @@ export default {
/>
<div v-if="openVlan">
<LabeledSelect
v-model:value="networkType"
class="mb-20"
:options="networkTypes"
:mode="mode"
:label="t('harvester.fields.type')"
required
/>
<LabeledInput
v-if="showVlan"
v-model:value.number="parsedDefaultValue.vlan"
type="number"
class="mb-20"
:mode="mode"
required
placeholder="e.g. 1 - 4094"
label-key="harvester.setting.storageNetwork.vlan"
@update:value="inputVlan"
/>
<LabeledSelect
@@ -59,7 +59,7 @@ export default {
name="model"
:options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]"
@input="update"
@update:value="update"
/>
<LabeledInput
+53 -42
View File
@@ -1,44 +1,55 @@
// https://github.com/harvester/dashboard/releases/tag/v1.3.0
const featuresV130 = [
'supportHarvesterClusterVersion'
];
import semver from 'semver';
// https://github.com/harvester/dashboard/releases/tag/v1.3.1
const featuresV131 = [
...featuresV130,
'autoRotateRke2CertsSetting',
'supportBundleNodeCollectionTimeoutSetting'
];
// https://github.com/harvester/dashboard/releases/tag/v1.3.2
const featuresV132 = [
...featuresV131,
'kubeconfigDefaultTokenTTLMinutesSetting',
'improveMaintenanceMode',
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.0
const featuresV140 = [
...featuresV132,
'cpuPinning',
'usbPassthrough',
'volumeEncryption',
'schedulingVMBackup',
'vmSnapshotQuota',
'longhornV2LVMSupport',
'improveMaintenanceMode',
];
// TODO: add v1.4.1 official release note
// https://github.com/harvester/dashboard/releases/tag/v1.4.1-rc1
const featuresV141 = [
...featuresV140
];
export const RELEASE_FEATURES = {
'v1.3.0': featuresV130,
'v1.3.1': featuresV131,
'v1.3.2': featuresV132,
'v1.4.0': featuresV140,
'v1.4.1': featuresV141,
const FEATURE_FLAGS = {
'v1.3.0': [
'supportHarvesterClusterVersion'
],
'v1.3.1': [
'autoRotateRke2CertsSetting',
'supportBundleNodeCollectionTimeoutSetting'
],
'v1.3.2': [
'kubeconfigDefaultTokenTTLMinutesSetting',
'improveMaintenanceMode',
],
'v1.3.3': [],
'v1.4.0': [
'cpuPinning',
'usbPassthrough',
'volumeEncryption',
'schedulingVMBackup',
'vmSnapshotQuota',
'longhornV2LVMSupport',
'improveMaintenanceMode',
],
'v1.4.1': [],
'v1.4.2': [
'refreshIntervalInSecond',
'allowEmptySnapshotClassName'
],
'v1.4.3': [],
'v1.5.0': [
'tpmPersistentState',
'efiPersistentState',
'untaggedNetworkSetting',
'skipSingleReplicaDetachedVol',
'thirdPartyStorage'
],
'v1.5.1': [],
};
const generateFeatureFlags = () => {
const versions = [...Object.keys(FEATURE_FLAGS)].filter((version) => semver.valid(version)).sort(semver.compare);
const generatedFlags = {};
versions.forEach((version, index) => {
const previousVersion = versions[index - 1];
generatedFlags[version] = previousVersion ? [...generatedFlags[previousVersion], ...FEATURE_FLAGS[version]] : [...FEATURE_FLAGS[version]];
});
return generatedFlags;
};
export const RELEASE_FEATURES = generateFeatureFlags();
+3 -1
View File
@@ -31,7 +31,8 @@ import {
FINGERPRINT,
IMAGE_PROGRESS,
SNAPSHOT_TARGET_VOLUME,
IMAGE_VIRTUAL_SIZE
IMAGE_VIRTUAL_SIZE,
IMAGE_STORAGE_CLASS
} from './table-headers';
const TEMPLATE = HCI.VM_VERSION;
@@ -221,6 +222,7 @@ export function init($plugin, store) {
STATE,
NAME_COL,
NAMESPACE_COL,
IMAGE_STORAGE_CLASS,
IMAGE_PROGRESS,
IMAGE_DOWNLOAD_SIZE,
IMAGE_VIRTUAL_SIZE,
+1
View File
@@ -1,3 +1,4 @@
// TODO: delete this not used variable
export const MemoryUnit = [{
label: 'Mi',
value: 'Mi'
+60 -56
View File
@@ -7,60 +7,64 @@ export const LABELS_TO_IGNORE_REGEX = [
];
export const HCI = {
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
CURRENT_IP: 'rke2.io/internal-ip',
IMAGE_ID: 'harvesterhci.io/imageId',
SSH_NAMES: 'harvesterhci.io/sshNames',
NETWORK_IPS: 'network.harvesterhci.io/ips',
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
CREATOR: 'harvesterhci.io/creator',
OS: 'harvesterhci.io/os',
NETWORK_TYPE: 'network.harvesterhci.io/type',
VM_NAME: 'harvesterhci.io/vmName',
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
RESTORE_NAME: 'restore.harvesterhci.io/name',
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
MIGRATION_STATE: 'harvesterhci.io/migrationState',
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
IMAGE_NAME: 'harvesterhci.io/image-name',
INIT_IP: 'etcd.rke2.cattle.io/node-address',
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
NETWORK_ROUTE: 'network.harvesterhci.io/route',
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
REAY_MESSAGE: 'harvesterhci.io/read-message',
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
OS_TYPE: 'harvesterhci.io/os-type',
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
HOST_REQUEST: 'management.cattle.io/pod-requests',
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
NODE_MANUFACTURER: 'manufacturer',
NODE_MODEL: 'model',
NODE_SERIAL_NUMBER: 'serialNumber',
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
CPU_MANAGER: 'cpumanager',
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
CLOUD_INIT: 'harvesterhci.io/cloud-init-template',
CURRENT_IP: 'rke2.io/internal-ip',
IMAGE_ID: 'harvesterhci.io/imageId',
SSH_NAMES: 'harvesterhci.io/sshNames',
NETWORK_IPS: 'network.harvesterhci.io/ips',
TEMPLATE_VERSION_CUSTOM_NAME: 'template-version.harvesterhci.io/customName',
CREATOR: 'harvesterhci.io/creator',
OS: 'harvesterhci.io/os',
GOLDEN_IMAGE: 'harvesterhci.io/goldenImage',
NETWORK_TYPE: 'network.harvesterhci.io/type',
VM_NAME: 'harvesterhci.io/vmName',
VM_NAME_PREFIX: 'harvesterhci.io/vmNamePrefix',
VM_RESERVED_MEMORY: 'harvesterhci.io/reservedMemory',
MAINTENANCE_STATUS: 'harvesterhci.io/maintain-status',
HOST_CUSTOM_NAME: 'harvesterhci.io/host-custom-name',
HOST_CONSOLE_URL: 'harvesterhci.io/host-console-url',
RESTORE_NAME: 'restore.harvesterhci.io/name',
NODE_ROLE_MASTER: 'node-role.kubernetes.io/master',
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
MIGRATION_STATE: 'harvesterhci.io/migrationState',
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
IMAGE_NAME: 'harvesterhci.io/image-name',
INIT_IP: 'etcd.rke2.cattle.io/node-address',
NODE_SCHEDULABLE: 'kubevirt.io/schedulable',
NETWORK_ROUTE: 'network.harvesterhci.io/route',
MATCHED_NODES: 'network.harvesterhci.io/matched-nodes',
OS_UPGRADE_IMAGE: 'harvesterhci.io/os-upgrade-image',
LATEST_UPGRADE: 'harvesterhci.io/latestUpgrade',
UPGRADE_STATE: 'harvesterhci.io/upgradeState',
REAY_MESSAGE: 'harvesterhci.io/read-message',
DYNAMIC_SSHKEYS_NAMES: 'harvesterhci.io/dynamic-ssh-key-names',
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
OS_TYPE: 'harvesterhci.io/os-type',
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
SKIP_SINGLE_REPLICA_DETACHED_VOL: 'harvesterhci.io/skipSingleReplicaDetachedVol',
HOST_REQUEST: 'management.cattle.io/pod-requests',
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
ADDON_EXPERIMENTAL: 'addon.harvesterhci.io/experimental',
VOLUME_ERROR: 'longhorn.io/volume-scheduling-error',
VOLUME_FOR_VM: 'harvesterhci.io/volumeForVirtualMachine',
KVM_AMD_CPU: 'cpu-feature.node.kubevirt.io/svm',
KVM_INTEL_CPU: 'cpu-feature.node.kubevirt.io/vmx',
NODE_MANUFACTURER: 'manufacturer',
NODE_MODEL: 'model',
NODE_SERIAL_NUMBER: 'serialNumber',
VM_INSUFFICIENT: 'harvesterhci.io/insufficient-resource-quota',
NODE_NTP_SYNC_STATUS: 'node.harvesterhci.io/ntp-service',
PARENT_SRIOV: 'harvesterhci.io/parent-sriov-network-device',
PARENT_SRIOV_GPU: 'harvesterhci.io/parentSRIOVGPUDevice',
VM_MAINTENANCE_MODE_STRATEGY: 'harvesterhci.io/maintain-mode-strategy',
NODE_CPU_MANAGER_UPDATE_STATUS: 'harvesterhci.io/cpu-manager-update-status',
CPU_MANAGER: 'cpumanager',
VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine',
K8S_ARCH: 'kubernetes.io/arch'
};
+3 -1
View File
@@ -65,7 +65,9 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
[HCI_SETTING.STORAGE_NETWORK]: {
kind: 'custom', from: 'import', canReset: true
},
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_PARAMETERS]: {
+10
View File
@@ -88,3 +88,13 @@ export const MACHINE_POOLS = {
align: 'center',
width: 100,
};
// The STORAGE_CLASS column in VM image list page
export const IMAGE_STORAGE_CLASS = {
name: 'imageStorageClass',
labelKey: 'harvester.tableHeaders.storageClass',
sort: 'imageStorageClass',
value: 'imageStorageClass',
align: 'left',
width: 200,
};
+10
View File
@@ -2,3 +2,13 @@ export const BACKUP_TYPE = {
BACKUP: 'backup',
SNAPSHOT: 'snapshot'
};
export const NETWORK_TYPE = {
L2VLAN: 'L2VlanNetwork',
UNTAGGED: 'UntaggedNetwork'
};
export const VOLUME_MODE = {
BLOCK: 'Block',
FILE_SYSTEM: 'Filesystem'
};
@@ -4,6 +4,7 @@ import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { LONGHORN, METRIC } from '@shell/config/types';
import { Banner } from '@components/Banner';
import { UNIT_SUFFIX } from '../../utils/unit';
import HarvesterCPUUsed from '../../formatters/HarvesterCPUUsed';
import HarvesterMemoryUsed from '../../formatters/HarvesterMemoryUsed';
import HarvesterStorageUsed from '../../formatters/HarvesterStorageUsed';
@@ -121,7 +122,7 @@ export default {
memoryUnits() {
const exponent = exponentNeeded(this.memoryTotal, 1024);
return `${ UNITS[exponent] }iB`;
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
},
nodeType() {
@@ -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>
@@ -9,7 +9,7 @@ import ArrayListGrouped from '@shell/components/form/ArrayListGrouped';
import Loading from '@shell/components/Loading.vue';
import SortableTable from '@shell/components/SortableTable';
import Banner from '@components/Banner/Banner.vue';
import { UNIT_SUFFIX } from '../../utils/unit';
import metricPoller from '@shell/mixins/metric-poller';
import {
METRIC, NODE, LONGHORN, POD, EVENT
@@ -178,7 +178,7 @@ export default {
minExponent: 3,
maxExponent: 3,
maxPrecision: 2,
suffix: 'iB',
suffix: UNIT_SUFFIX,
};
const longhornDisks = Object.keys(diskStatus).map((key) => {
+6 -5
View File
@@ -106,6 +106,12 @@ export default {
:label="t('harvester.modal.cloneVM.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template
@@ -127,11 +133,6 @@ export default {
@click="create"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</template>
</Card>
</template>
@@ -0,0 +1,175 @@
<script>
import { mapState } from 'vuex';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { alternateLabel } from '@shell/utils/platform';
import AsyncButton from '@shell/components/AsyncButton';
import { Banner } from '@components/Banner';
import { Card } from '@components/Card';
import { escapeHtml } from '@shell/utils/string';
/**
* @name ConfirmExecutionDialog
* @description Dialog component to confirm the related resources before executing the action.
*/
export default {
name: 'ConfirmExecutionDialog',
emits: ['close'],
components: {
AsyncButton,
Banner,
Card,
},
props: {
/**
* @property resources to be deleted.
* @type {Resource[]} Array of the resource model's instance
*/
resources: {
type: Array,
required: true
}
},
data() {
return { errors: [] };
},
computed: {
...mapState('action-menu', ['modalData']),
warningMessageKey() {
return this.modalData.warningMessageKey;
},
names() {
return this.resources.map((obj) => obj.nameDisplay).slice(0, 5);
},
resourceNames() {
return this.names.reduce((res, name, i) => {
if (i >= 5) {
return res;
}
res += `<b>${ escapeHtml(name) }</b>`;
if (i === this.names.length - 1) {
res += this.plusMore;
} else {
res += i === this.resources.length - 2 ? ' and ' : ', ';
}
return res;
}, '');
},
plusMore() {
const remaining = this.resources.length - this.names.length;
return this.t('dialog.confirmExecution.andOthers', { count: remaining }, true);
},
type() {
const types = new Set(this.resources.reduce((array, each) => {
array.push(each.type);
return array;
}, []));
if (types.size > 1) {
return this.t('generic.resource', { count: this.resources.length });
}
const schema = this.resources[0]?.schema;
if ( !schema ) {
return `resource${ this.resources.length === 1 ? '' : 's' }`;
}
return this.$store.getters['type-map/labelFor'](schema, this.resources.length);
},
protip() {
return this.t('dialog.confirmExecution.protip', { alternateLabel });
},
},
methods: {
escapeHtml,
close() {
this.errors = [];
this.$emit('close');
},
async apply(buttonDone) {
try {
for (const resource of this.resources) {
await resource.doActionGrowl(this.modalData.action, {});
}
buttonDone(true);
this.close();
} catch (e) {
this.errors = exceptionToErrorsArray(e);
buttonDone(false);
}
}
}
};
</script>
<template>
<Card :show-highlight-border="false">
<template #title>
<h4 class="text-default-text">
{{ t('dialog.confirmExecution.title') }}
</h4>
</template>
<template #body>
<div class="pl-10 pr-10">
<span
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
></span>
<div class="text-info mt-20">
{{ protip }}
</div>
<Banner
v-for="(error, i) in errors"
:key="i"
:label="error"
color="error"
/>
</div>
</template>
<template #actions>
<div class="actions">
<button
class="btn role-secondary"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="apply"
class="btn bg-primary ml-10"
:disabled="applyDisabled"
@click="apply"
/>
</div>
</template>
</Card>
</template>
<style lang='scss' scoped>
.modal-container {
max-width: 400px;
}
.actions {
width: 100%;
text-align: right;
}
</style>
@@ -42,8 +42,25 @@ export default {
computed: {
...mapState('action-menu', ['modalData']),
warningMessageKey() {
return this.modalData.warningMessageKey;
title() {
return this.modalData.title || 'dialog.promptRemove.title';
},
formattedType() {
return this.type.toLowerCase();
},
warningMessage() {
if (this.modalData.warningMessage) return this.modalData.warningMessage;
const isPlural = this.type.endsWith('s');
const thisOrThese = isPlural ? 'these' : 'this';
const defaultMessage = this.t('dialog.promptRemove.warningMessage', {
type: this.formattedType,
thisOrThese,
});
return defaultMessage;
},
names() {
@@ -70,6 +87,12 @@ export default {
return this.resources[0].nameDisplay;
},
needConfirmation() {
const { needConfirmation = true } = this.modalData ;
return needConfirmation === true;
},
plusMore() {
const remaining = this.resources.length - this.names.length;
@@ -97,11 +120,15 @@ export default {
},
deleteDisabled() {
if (!this.needConfirmation) {
return false;
}
return this.confirmName !== this.nameToMatch;
},
protip() {
return this.t('promptRemove.protip', { alternateLabel });
return this.t('dialog.promptRemove.protip', { alternateLabel });
},
},
@@ -137,35 +164,45 @@ export default {
>
<template #title>
<h4 class="text-default-text">
{{ t('promptRemove.title') }}
{{ t(title, { type }, true) }}
</h4>
</template>
<template #body>
<div class="pl-10 pr-10">
<span
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
v-clean-html="warningMessage"
></span>
<div class="mt-10 mb-10">
<span
v-clean-html="t('promptRemove.confirmName', { nameToMatch: escapeHtml(nameToMatch) }, true)"
></span>
</div>
<div class="mb-10">
<CopyToClipboardText :text="nameToMatch" />
</div>
<input
id="confirm"
v-model="confirmName"
type="text"
/>
<div class="text-info mt-20">
{{ protip }}
<div
v-if="needConfirmation"
class="mt-20"
>
<div class="mt-10 mb-10">
<span
v-clean-html="t('dialog.promptRemove.confirmName', {
type: formattedType,
nameToMatch: escapeHtml(nameToMatch)
}, true)"
></span>
</div>
<div class="mb-10">
<CopyToClipboardText :text="nameToMatch" />
</div>
<input
id="confirm"
v-model="confirmName"
type="text"
/>
<div class="text-info mt-20">
{{ protip }}
</div>
</div>
<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>
@@ -57,6 +57,10 @@ export default {
if (!!pvc.metadata?.annotations?.[HCI_ANNOTATIONS.IMAGE_ID]) {
return false;
}
// we won't show golden image volume in the hot plug volume modal
if (pvc.isGoldenImageVolume) {
return false;
}
return !pvc.attachVM;
})
@@ -137,6 +141,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 +166,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>
@@ -34,9 +34,14 @@ export default {
await allHash(hash);
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
const allStorages = this.allStorageClasses;
const defaultStorage = allStorages.find((s) => s.isDefault);
this['storageClassName'] = defaultStorage?.metadata?.name || 'longhorn';
if (this.isLonghornV1Volume) {
this['storageClassName'] = defaultStorage?.metadata?.name || 'longhorn';
} else {
this['storageClassName'] = this.nonLonghornV1StorageClasses[0]?.metadata?.name || '';
}
},
data() {
@@ -54,7 +59,7 @@ export default {
...mapGetters({ t: 'i18n/t' }),
actionResource() {
return this.resources[0];
return this.resources[0] || {};
},
namespaces() {
@@ -73,15 +78,33 @@ export default {
return out;
},
isLonghornV1Volume() {
return this.actionResource?.isLonghornV1 === true;
},
disableSave() {
return !(this.name && this.namespace && this.storageClassName);
},
storageClassOptions() {
allStorageClasses() {
const inStore = this.$store.getters['currentProduct'].inStore;
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
const out = storages.filter((s) => !s.parameters?.backingImage).map((s) => {
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS) || [];
},
nonLonghornV1StorageClasses() {
return this.allStorageClasses.filter((s) => s.isLonghornV1 === false) || [];
},
storageClassOptions() {
let storages = this.allStorageClasses;
// Volume with non-longhorn v1 sc can't be exported to longhorn v1 sc image
if (!this.isLonghornV1Volume) {
storages = this.allStorageClasses.filter((s) => s.isLonghornV1 === false);
}
const options = storages.filter((s) => !s.parameters?.backingImage).map((s) => {
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
return {
@@ -90,7 +113,7 @@ export default {
};
}) || [];
return out;
return options;
},
},
@@ -164,6 +187,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 +211,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -0,0 +1,165 @@
<script>
import { mapGetters } from 'vuex';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { HCI } from '../types';
import { Card } from '@components/Card';
import { Banner } from '@components/Banner';
import AsyncButton from '@shell/components/AsyncButton';
import AppModal from '@shell/components/AppModal';
export default {
name: 'HarvesterImageDownloaderDialog',
emits: ['close'],
components: {
AsyncButton, Banner, Card, AppModal
},
props: {
resources: {
type: Array,
required: true
}
},
data() {
return { errors: [], isOpen: false };
},
computed: {
...mapGetters({ t: 'i18n/t' }),
downloadImageInProgress() {
return this.$store.getters['harvester-common/isDownloadImageInProgress'];
},
image() {
return this.resources[0] || {};
},
imageName() {
return this.image?.name || '';
},
imageVirtualSize() {
return this.image?.virtualSize || this.image?.downSize || '';
}
},
methods: {
async cancelDownload() {
const url = this.image?.links?.downloadcancel;
if (url) {
await this.$store.dispatch('harvester/request', { url });
}
},
async close() {
if (this.downloadImageInProgress) {
this.$store.commit('harvester-common/setDownloadImageCancel', true);
this.$store.commit('harvester-common/setDownloadImageInProgress', false);
await this.cancelDownload();
}
this.$emit('close');
},
async startDownload(buttonCb) {
// clean the download image CRD first.
await this.cancelDownload();
this.$store.commit('harvester-common/setDownloadImageCancel', false);
this.$store.commit('harvester-common/setDownloadImageInProgress', false);
this.errors = [];
const name = this.image?.name || '';
const namespace = this.image?.namespace || '';
const imageCrd = {
apiVersion: 'harvesterhci.io/v1beta1',
type: HCI.VM_IMAGE_DOWNLOADER,
kind: 'VirtualMachineImageDownloader',
metadata: {
name,
namespace
},
spec: { imageName: name }
};
const inStore = this.$store.getters['currentProduct'].inStore;
const imageCreate = await this.$store.dispatch(`${ inStore }/create`, imageCrd);
try {
await imageCreate.save();
this.$store.commit('harvester-common/setDownloadImageId', `${ namespace }/${ name }`, { root: true });
this.$store.dispatch('harvester-common/downloadImageProgress', { root: true });
} catch (err) {
this.errors = exceptionToErrorsArray(err);
buttonCb(false);
}
}
},
};
</script>
<template>
<app-modal
class="image-downloader-modal"
name="image-download-dialog"
height="auto"
:width="600"
:click-to-close="false"
@close="close"
>
<Card :show-highlight-border="false">
<template #title>
{{ t('harvester.modal.downloadImage.title') }}
</template>
<template #body>
<Banner color="info">
{{ t('harvester.modal.downloadImage.banner', { size: imageVirtualSize }) }}
</Banner>
{{ t('harvester.modal.downloadImage.startMessage') }}
<br /><br />
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
<div class="actions">
<div class="buttons">
<button
class="btn role-secondary mr-10"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
type="submit"
mode="download"
:disabled="downloadImageInProgress"
@click="startDownload"
/>
</div>
</div>
</template>
</Card>
</app-modal>
</template>
<style lang="scss" scoped>
.actions {
width: 100%;
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
</style>
@@ -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>
@@ -5,6 +5,7 @@ import { Banner } from '@components/Banner';
import AsyncButton from '@shell/components/AsyncButton';
import UnitInput from '@shell/components/form/UnitInput';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { GIBIBYTE } from '../utils/unit';
export default {
name: 'HarvesterVMQuotaDialog',
@@ -29,6 +30,7 @@ export default {
data() {
return {
GIBIBYTE,
totalSnapshotSize: '',
errors: []
};
@@ -44,7 +46,6 @@ export default {
},
methods: {
close() {
this.totalSnapshotSize = '';
this.$emit('close');
@@ -92,9 +93,15 @@ export default {
:input-exponent="3"
:increment="1024"
:output-modifier="true"
suffix="GiB"
:suffix="GIBIBYTE"
class="mb-20"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -108,12 +115,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() {
@@ -20,7 +20,7 @@ import Loading from '@shell/components/Loading.vue';
import MessageLink from '@shell/components/MessageLink';
import { ADD_ONS } from '@pkg/harvester/config/harvester-map';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '@pkg/harvester/config/harvester';
import { UNIT_SUFFIX } from '../../utils/unit';
import { _EDIT } from '@shell/config/query-params';
import { sortBy } from '@shell/utils/sort';
import { Banner } from '@components/Banner';
@@ -185,7 +185,7 @@ export default {
minExponent: 3,
maxExponent: 3,
maxPrecision: 2,
suffix: 'iB',
suffix: UNIT_SUFFIX,
};
const longhornDisks = Object.keys(diskStatus).map((key) => {
@@ -457,7 +457,7 @@ export default {
const devPath = d.spec?.devPath;
const deviceType = d.status?.deviceStatus?.details?.deviceType;
const sizeBytes = d.status?.deviceStatus?.capacity?.sizeBytes;
const size = formatSi(sizeBytes, { increment: 1024 });
const size = formatSi(sizeBytes, { increment: 1024, suffix: UNIT_SUFFIX });
const parentDevice = d.status?.deviceStatus?.parentDevice;
const isChildAdded = this.newDisks.find((newDisk) => newDisk.blockDevice?.status?.deviceStatus?.parentDevice === devPath);
const name = d.displayName;
@@ -93,7 +93,7 @@ export default {
const selectedProviderDefault = providers.find((p) => p.name === selectedProvider)?.default || providers[0].default;
if (this.mode !== _VIEW) {
this.$set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
}
return {
@@ -11,6 +11,9 @@ import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-anno
import CreateEditView from '@shell/mixins/create-edit-view';
import { allHash } from '@shell/utils/promise';
import { HCI } from '../types';
import { NETWORK_TYPE } from '../config/types';
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
const AUTO = 'auto';
const MANUAL = 'manual';
@@ -48,7 +51,7 @@ export default {
config.bridge = config.bridge.slice(0, -3);
}
const type = this.value.vlanType || 'L2VlanNetwork' ;
const type = this.value.vlanType || L2VLAN ;
return {
config,
@@ -101,15 +104,15 @@ export default {
},
networkType() {
return ['L2VlanNetwork', 'UntaggedNetwork'];
return [L2VLAN, UNTAGGED];
},
isUntaggedNetwork() {
if (this.isView) {
return this.value.vlanType === 'UntaggedNetwork';
return this.value.vlanType === UNTAGGED;
}
return this.type === 'UntaggedNetwork';
return this.type === UNTAGGED;
}
},
@@ -233,7 +233,7 @@ export default {
:disabled="isEdit || isView"
:mode="mode"
row
@input="onTypeChange"
@update:value="onTypeChange"
/>
</div>
<div class="row mb-30">
@@ -23,10 +23,9 @@ import Tags from '../../components/DiskTags';
import { DATA_ENGINE_V1, DATA_ENGINE_V2 } from '../../models/harvester/persistentvolumeclaim';
import { LVM_DRIVER } from '../../models/harvester/storage.k8s.io.storageclass';
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
export const LVM_TOPOLOGY_LABEL = 'topology.lvm.csi/node';
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
const VOLUME_BINDING_MODE_IMMEDIATE = 'Immediate';
const VOLUME_BINDING_MODE_WAIT = 'WaitForFirstConsumer';
@@ -63,7 +63,7 @@ export default {
diskSelector: null,
nodeSelector: null,
encrypted: 'false',
migratable: 'false',
migratable: this.value.thirdPartyStorageFeatureEnabled ? 'true' : 'false',
dataEngine: DATA_ENGINE_V2
};
}
@@ -318,7 +318,7 @@ export default {
:label="t('harvester.storage.parameters.migratable.label')"
:mode="mode"
:options="migratableOptions"
:disabled="true"
:disabled="!value.thirdPartyStorageFeatureEnabled"
/>
</div>
<div class="row mt-20">
@@ -24,6 +24,7 @@ const CLONE = 'clone';
const DOWNLOAD = 'download';
const UPLOAD = 'upload';
const rawORqcow2 = 'raw_qcow2';
const LONGHORN = 'longhorn';
export default {
name: 'EditImage',
@@ -61,11 +62,11 @@ export default {
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
});
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
this.storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
const { securityParameters } = this.value.spec;
// edit and view mode should show the source image
@@ -100,14 +101,15 @@ export default {
}
return {
selectedImage: null,
images: [],
url: this.value.spec.url,
files: [],
resource: '',
headers: {},
fileUrl: '',
file: '',
selectedImage: null,
storageClasses: [],
images: [],
url: this.value.spec.url,
files: [],
resource: '',
headers: {},
fileUrl: '',
file: '',
};
},
@@ -144,12 +146,26 @@ export default {
];
},
storageClassOptions() {
encryptedStorageClasses() {
const inStore = this.$store.getters['currentProduct'].inStore;
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
return storages
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
return storages.filter((s) => s.isEncrypted);
},
nonEncryptedStorageClasses() {
const inStore = this.$store.getters['currentProduct'].inStore;
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
return storages.filter((s) => !s.isEncrypted);
},
storageClassOptions() {
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
const filteredStorages = this.value.thirdPartyStorageFeatureEnabled ? storages.filter((s) => !s.parameters?.backingImage) : storages
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) ;
return filteredStorages
.map((s) => {
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
@@ -167,6 +183,9 @@ export default {
set(nue) {
this.value.metadata.annotations[HCI_ANNOTATIONS.STORAGE_CLASS] = nue;
if (this.value.thirdPartyStorageFeatureEnabled) {
this.value.spec.targetStorageClassName = nue;
}
}
},
sourceImageOptions() {
@@ -239,6 +258,20 @@ export default {
this.$refs.file.value = null;
}
},
'value.spec.securityParameters.cryptoOperation'() {
if (this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT) {
this.storageClassName = this.encryptedStorageClasses[0]?.name || '';
} else { // URL / FILE / DECRYPT should use default storage class
this.storageClassName = this.defaultStorageClassName();
}
},
'storageClassName'(neu) {
const storageClass = this.storages.find((s) => s.id === neu);
if (storageClass && this.value.thirdPartyStorageFeatureEnabled) {
this.value.spec.backend = storageClass.isLonghornV1 ? 'backingimage' : 'cdi';
}
}
},
methods: {
@@ -385,6 +418,18 @@ export default {
return str.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
}
});
},
defaultStorageClassName() {
const inStore = this.$store.getters['currentProduct'].inStore;
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
if (!defaultStorage) {
return LONGHORN;
}
// if default sc is encrypted, use longhorn as default
return defaultStorage.isEncrypted ? LONGHORN : defaultStorage?.metadata?.name;
}
},
};
@@ -6,12 +6,11 @@ import { Checkbox } from '@components/Form/Checkbox';
import CruResource from '@shell/components/CruResource';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import Labels from '@shell/components/form/Labels';
import { Banner } from '@components/Banner';
import KeyValue from '@shell/components/form/KeyValue';
import NodeScheduling from '@shell/components/form/NodeScheduling';
import PodAffinity from '@shell/components/form/PodAffinity';
import UnitInput from '@shell/components/form/UnitInput';
import { randomStr } from '@shell/utils/string';
import { _CONFIG, _EDIT, _VIEW } from '@shell/config/query-params';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
@@ -47,7 +46,7 @@ export default {
PodAffinity,
Reserved,
UnitInput,
Labels,
Banner,
KeyValue,
},
@@ -321,34 +320,44 @@ export default {
</Tab>
<Tab
:name="t('generic.labels')"
:label="t('harvester.tab.instanceLabel')"
:weight="-5"
name="labels"
:label="t('generic.labels')"
:weight="-9"
>
<Labels
:default-container-class="'labels-and-annotations-container'"
:value="value"
<Banner color="info">
<t k="harvester.virtualMachine.labels.banner" />
</Banner>
<KeyValue
key="labels"
:value="value.labels"
:add-label="t('labels.addLabel')"
:mode="mode"
:display-side-by-side="false"
:show-annotations="false"
:show-label-title="false"
>
<template #labels="{toggler}">
<KeyValue
key="labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@input="value.setInstanceLabels($event)"
/>
</template>
</Labels>
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setLabels($event)"
/>
</Tab>
<Tab
name="instanceLabel"
:label="t('harvester.tab.instanceLabel')"
:weight="-10"
>
<Banner color="info">
<t k="harvester.virtualMachine.instanceLabels.banner" />
</Banner>
<KeyValue
key="instance-labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setInstanceLabels($event)"
/>
</Tab>
<Tab
name="advanced"
:label="t('harvester.tab.advanced')"
@@ -434,7 +443,7 @@ export default {
<div class="spacer"></div>
<Checkbox
v-if="value.cpuPinningFeatureEnabled"
v-model="cpuPinning"
v-model:value="cpuPinning"
class="check"
type="checkbox"
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
@@ -465,11 +474,29 @@ export default {
:mode="mode"
/>
<Checkbox
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
v-model:value="tpmPersistentStateEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
:mode="mode"
/>
<Checkbox
v-model:value="efiEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.efiEnabled')"
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
:mode="mode"
/>
<Checkbox
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
v-model:value="efiPersistentStateEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
:mode="mode"
/>
@@ -478,7 +505,7 @@ export default {
v-model:value="secureBoot"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.secureBoot')"
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
:mode="mode"
/>
</Tab>
+81 -5
View File
@@ -21,6 +21,8 @@ import { InterfaceOption, VOLUME_DATA_SOURCE_KIND } from '../config/harvester-ma
import { HCI, VOLUME_SNAPSHOT } from '../types';
import { LVM_DRIVER } from '../models/harvester/storage.k8s.io.storageclass';
import { DATA_ENGINE_V2 } from '../models/harvester/persistentvolumeclaim';
import { GIBIBYTE } from '../utils/unit';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default {
name: 'HarvesterVolume',
@@ -72,7 +74,8 @@ export default {
data() {
if (this.mode === _CREATE) {
this.value.spec.volumeMode = 'Block';
// default volumeMode to Block
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
this.value.spec.accessModes = ['ReadWriteMany'];
}
@@ -86,6 +89,7 @@ export default {
imageId,
snapshots: [],
images: [],
GIBIBYTE
};
},
@@ -124,6 +128,10 @@ export default {
return InterfaceOption;
},
volumeModeOptions() {
return Object.values(VOLUME_MODE);
},
imageOption() {
return sortBy(
this.images
@@ -177,6 +185,25 @@ export default {
return this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
},
isLonghornStorageClass() {
const selectedSC = this.storageClasses.find((sc) => sc.name === this.value?.spec?.storageClassName) || {};
return selectedSC && selectedSC.isLonghorn;
},
showVolumeMode() {
// we won't let user choose volumeMode if source = vmimage
if (!this.value.thirdPartyStorageFeatureEnabled || this.isVMImage || !!this.value?.spec?.storageClassName === false) {
return false;
}
if (this.isLonghornStorageClass) {
return false;
}
return true;
},
storageClassOptions() {
return this.storageClasses.filter((s) => !s.parameters?.backingImage).map((s) => {
const label = s.isDefault ? `${ s.name } (${ this.t('generic.default') })` : s.name;
@@ -235,7 +262,43 @@ export default {
}
},
watch: {
'source'(neu) {
if (neu === 'url') {
this.setBlockVolumeMode();
this.deleteVolumeForVmAnnotation();
}
},
'value.spec.storageClassName'() {
if (this.isLonghornStorageClass) {
this.setBlockVolumeMode();
this.deleteVolumeForVmAnnotation();
}
},
'value.spec.volumeMode'(neu) {
if (neu === VOLUME_MODE.FILE_SYSTEM) {
this.setVolumeForVmAnnotation();
} else if (neu === VOLUME_MODE.BLOCK ) {
this.deleteVolumeForVmAnnotation();
}
}
},
methods: {
setBlockVolumeMode() {
this.value.spec.volumeMode = VOLUME_MODE.BLOCK;
},
setVolumeForVmAnnotation() {
this.value.setAnnotation(HCI_ANNOTATIONS.VOLUME_FOR_VM, 'true');
},
deleteVolumeForVmAnnotation() {
if (this.value?.metadata?.annotations?.[HCI_ANNOTATIONS.VOLUME_FOR_VM]) {
delete this.value.metadata.annotations[HCI_ANNOTATIONS.VOLUME_FOR_VM];
}
},
getAccessMode() {
if (!this.longhornV2LVMSupport) {
return ['ReadWriteMany'];
@@ -244,10 +307,10 @@ export default {
const storageClassName = this.value.spec.storageClassName;
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
let readWriteOnce = this.value.isLvm || this.value.isLonghornV2;
let readWriteOnce = this.value.isLvm || (!this.value.thirdPartyStorageFeatureEnabled && this.value.isLonghornV2);
if (storageClass) {
readWriteOnce = storageClass.provisioner === LVM_DRIVER || storageClass.parameters?.dataEngine === DATA_ENGINE_V2;
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.value.thirdPartyStorageFeatureEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
}
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
@@ -288,7 +351,7 @@ export default {
const imageSize = Math.max(imageResource?.status?.size, imageResource?.status?.virtualSize);
if (imageSize) {
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }Gi`;
this.storage = `${ Math.ceil(imageSize / 1024 / 1024 / 1024) }${ GIBIBYTE }`;
}
}
this.update();
@@ -357,7 +420,7 @@ export default {
/>
<LabeledSelect
v-if="source === 'blank'"
v-if="isBlank"
v-model:value="value.spec.storageClassName"
:options="storageClassOptions"
:label="t('harvester.storage.storageClass.label')"
@@ -367,6 +430,18 @@ export default {
@update:value="update"
/>
<LabeledSelect
v-if="showVolumeMode"
v-model:value="value.spec.volumeMode"
:label="t('harvester.volume.volumeMode')"
:options="volumeModeOptions"
required
:disabled="!isCreate"
:mode="mode"
class="mb-20"
@update:value="update"
/>
<UnitInput
v-model:value="storage"
:label="t('harvester.volume.size')"
@@ -377,6 +452,7 @@ export default {
:disabled="value?.isLonghornV2 && isEdit"
required
class="mb-20"
:suffix="GIBIBYTE"
@update:value="update"
/>
@@ -1,6 +1,7 @@
<script>
import UnitInput from '@shell/components/form/UnitInput';
import InputOrDisplay from '@shell/components/InputOrDisplay';
import { GIBIBYTE } from '../../utils/unit';
export default {
name: 'HarvesterEditCpuMemory',
@@ -30,6 +31,7 @@ export default {
data() {
return {
GIBIBYTE,
localCpu: this.cpu,
localMemory: this.memory
};
@@ -63,7 +65,7 @@ export default {
if (String(this.localMemory).includes('Gi')) {
memory = this.localMemory;
} else {
memory = `${ this.localMemory }Gi`;
memory = `${ this.localMemory }${ GIBIBYTE }`;
}
if (memory.includes('null')) {
memory = null;
@@ -113,6 +115,7 @@ export default {
:output-modifier="true"
:disabled="disabled"
required
:suffix="GIBIBYTE"
class="mb-20"
@update:value="change"
/>
@@ -97,7 +97,7 @@ export default {
});
const devices = [
...this.otherDevices(this.value.domain.devices.hostDevices || []),
...this.otherDevices(this.value?.domain?.devices?.hostDevices || []),
...formatted,
];
@@ -121,7 +121,9 @@ export default {
return inUse;
}
vm.hostDevices.forEach((device) => {
const hostDevices = vm?.hostDevices || [];
hostDevices.forEach((device) => {
inUse[device.name] = { usedBy: [vm.metadata.name] };
});
@@ -1,6 +1,6 @@
<script>
import UnitInput from '@shell/components/form/UnitInput';
import { MEBIBYTE } from '../../utils/unit';
export default {
name: 'HarvesterReserved',
@@ -20,7 +20,7 @@ export default {
},
data() {
return { memory: this.reservedMemory };
return { MEBIBYTE, memory: this.reservedMemory };
},
watch: {
@@ -47,6 +47,7 @@ export default {
:input-exponent="2"
:increment="1024"
:output-modifier="true"
:suffix="MEBIBYTE"
@update:value="change"
/>
</template>
@@ -6,7 +6,6 @@ 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';
@@ -126,10 +125,9 @@ 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.show();
}
}
@@ -175,6 +173,7 @@ export default {
if (res.id) {
this.checkedSsh.push(`${ this.namespace }/${ this.sshName }`);
this.update();
}
},
@@ -232,7 +231,9 @@ export default {
},
update() {
this.$emit('update:sshKey', clone(this.checkedSsh));
const sshKeys = this.checkedSsh.filter((key) => key !== _NEW);
this.$emit('update:sshKey', sshKeys);
},
}
};
@@ -243,7 +244,7 @@ export default {
<LabeledSelect
v-model:value="checkedSsh"
:label="t('harvester.virtualMachine.input.sshKey')"
:taggable="true"
:taggable="!disabled"
:mode="mode"
:multiple="true"
:searchable="searchable"
@@ -80,7 +80,7 @@ export default {
});
const devices = [
...this.otherDevices(this.value.domain.devices.hostDevices || []),
...this.otherDevices(this.value?.domain?.devices?.hostDevices || []),
...formatted,
];
@@ -121,7 +121,9 @@ export default {
return inUse;
}
vm.hostDevices.forEach((device) => {
const hostDevices = vm?.hostDevices || [];
hostDevices.forEach((device) => {
inUse[device.name] = { usedBy: [vm.metadata.name] };
});
@@ -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';
@@ -7,8 +7,7 @@ import UnitInput from '@shell/components/form/UnitInput';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard';
import { PVC } from '@shell/config/types';
import { PVC, STORAGE_CLASS } from '@shell/config/types';
import { clone } from '@shell/utils/object';
import { ucFirst, randomStr } from '@shell/utils/string';
import { removeObject } from '@shell/utils/array';
@@ -17,12 +16,13 @@ import { PLUGIN_DEVELOPER, DEV } from '@shell/store/prefs';
import { SOURCE_TYPE } from '../../../config/harvester-map';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../config/harvester';
import { HCI } from '../../../types';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default {
emits: ['update:value'],
components: {
Banner, BadgeStateFormatter, Sortable, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
},
props: {
@@ -62,7 +62,7 @@ export default {
customVolumeMode: {
type: String,
default: 'Block'
default: VOLUME_MODE.Block
},
customAccessMode: {
@@ -116,6 +116,12 @@ export default {
return this.mode === _CREATE;
},
defaultStorageClass() {
const defaultStorage = this.$store.getters['harvester/all'](STORAGE_CLASS).find((sc) => sc.isDefault);
return defaultStorage;
},
showVolumeTip() {
const imageName = this.getImageDisplayName(this.rows[0]?.image);
@@ -272,129 +278,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"
@@ -11,6 +11,7 @@ import { _CREATE } from '@shell/config/query-params';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import { HCI } from '../../../../types';
import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
import { GIBIBYTE } from '../../../../utils/unit';
export default {
name: 'HarvesterEditExisting',
@@ -59,6 +60,7 @@ export default {
}
return {
GIBIBYTE,
VOLUME_TYPE,
InterfaceOption,
loading: false,
@@ -109,6 +111,10 @@ export default {
return false;
}
if (pvc.isGoldenImageVolume) {
return false;
}
if (pvc.attachVM && isAvailable && pvc.attachVM?.id === this.vm?.id && this.isEdit) {
isBeingUsed = false;
} else if (pvc.attachVM) {
@@ -136,10 +142,12 @@ export default {
return;
}
// update this.value with existing volume spec, then update to upstream component
this.value.accessModes = pvcResource.spec.accessModes[0];
this.value.size = pvcResource.spec.resources.requests.storage;
this.value.storageClassName = pvcResource.spec.storageClassName;
this.value.volumeMode = pvcResource.spec.volumeMode;
this.update();
},
'value.type'(neu) {
@@ -250,6 +258,7 @@ export default {
:label="t('harvester.fields.size')"
:mode="mode"
:disabled="true"
:suffix="GIBIBYTE"
@update:value="update"
/>
</InputOrDisplay>
@@ -12,6 +12,7 @@ import { VOLUME_TYPE, InterfaceOption } from '../../../../config/harvester-map';
import { _VIEW } from '@shell/config/query-params';
import LabelValue from '@shell/components/LabelValue';
import { ucFirst } from '@shell/utils/string';
import { GIBIBYTE } from '../../../../utils/unit';
export default {
name: 'HarvesterEditVMImage',
@@ -67,6 +68,7 @@ export default {
data() {
return {
GIBIBYTE,
VOLUME_TYPE,
InterfaceOption,
loading: false,
@@ -90,7 +92,7 @@ export default {
imagesOption() {
return this.images.filter((c) => c.isReady).sort((a, b) => a.creationTimestamp > b.creationTimestamp ? -1 : 1).map( (I) => {
return {
label: `${ I.metadata.namespace }/${ I.spec.displayName }`,
label: this.imageOptionLabel(I),
value: I.id
};
});
@@ -116,8 +118,46 @@ export default {
});
},
thirdPartyStorageEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
},
isLonghornV2() {
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
},
selectedImage() {
return this.$store.getters['harvester/all'](HCI.IMAGE)?.find( (I) => this.value.image === I.id);
},
imageVirtualSize() {
if (this.selectedImage?.virtualSize) {
return this.selectedImage.virtualSize.replace(' ', '');
}
return '0';
},
diskSize() {
const size = this.value?.size || '0';
return size;
},
imageVirtualSizeInByte() {
return Math.max(this.selectedImage?.status?.size, this.selectedImage?.status?.virtualSize);
},
diskSizeInByte() {
return parseSi(this.value?.size || '0');
},
showDiskTooSmallError() {
if (!this.thirdPartyStorageEnabled ) {
return false;
}
return this.imageVirtualSizeInByte > this.diskSizeInByte;
}
},
@@ -149,7 +189,7 @@ export default {
minExponent: 3,
});
this.value.size = `${ formatSize }Gi`;
this.value.size = `${ formatSize }${ GIBIBYTE }`;
}
},
deep: true,
@@ -158,7 +198,17 @@ export default {
},
methods: {
imageOptionLabel(image) {
let label = `${ image.metadata.namespace }/${ image.spec.displayName }`;
if (this.thirdPartyStorageEnabled) {
label += ` (${ image.imageStorageClass } / ${ image.virtualSize })`;
}
return label;
},
update() {
this.value.hasDiskError = this.showDiskTooSmallError;
this.$emit('update');
},
@@ -181,7 +231,7 @@ export default {
if (!isIsoImage) {
imageSizeGiB = Math.max(imageSizeGiB, 10);
}
this.value['size'] = `${ imageSizeGiB }Gi`;
this.value['size'] = `${ imageSizeGiB }${ GIBIBYTE }`;
}
this.update();
@@ -288,7 +338,7 @@ export default {
:mode="mode"
:required="validateRequired"
:disable="isLonghornV2"
suffix="GiB"
:suffix="GIBIBYTE"
@update:value="update"
/>
</InputOrDisplay>
@@ -341,5 +391,10 @@ export default {
class="mb-20"
:label="value.volumeBackups.error.message"
/>
<Banner
v-if="!isView && showDiskTooSmallError"
color="error"
:label="t('harvester.virtualMachine.volume.vmImageVolumeTip', {diskSize: diskSize, imageVirtualSize: imageVirtualSize})"
/>
</div>
</template>
@@ -12,6 +12,8 @@ import LabelValue from '@shell/components/LabelValue';
import { ucFirst } from '@shell/utils/string';
import { LVM_DRIVER } from '../../../../models/harvester/storage.k8s.io.storageclass';
import { DATA_ENGINE_V2 } from '../../../../models/harvester/persistentvolumeclaim';
import { GIBIBYTE } from '../../../../utils/unit';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
export default {
name: 'HarvesterEditVolume',
@@ -58,6 +60,7 @@ export default {
data() {
return {
GIBIBYTE,
VOLUME_TYPE,
InterfaceOption,
loading: false,
@@ -69,6 +72,10 @@ export default {
return this.$store.getters['harvester-common/getFeatureEnabled']('longhornV2LVMSupport');
},
thirdPartyStorageClassEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
},
encryptionValue() {
return ucFirst(String(this.value.isEncrypted));
},
@@ -89,6 +96,18 @@ export default {
return allPVCs.find((P) => P.id === `${ this.namespace }/${ this.value.volumeName }`);
},
showVolumeMode() {
if (!this.thirdPartyStorageClassEnabled || !!this.value?.storageClassName === false) {
return false;
}
if (this.isLonghornStorageClass) {
return false;
}
return true;
},
isDisabled() {
return !this.value.newCreateId && this.isEdit && this.isVirtualType;
},
@@ -110,7 +129,17 @@ export default {
isLonghornV2() {
return this.value.pvc?.isLonghornV2 || this.value.pvc?.storageClass?.isLonghornV2;
}
},
isLonghornStorageClass() {
const selectedSC = this.storageClasses.find((sc) => sc.name === this.value?.storageClassName) || {};
return selectedSC && selectedSC.isLonghorn;
},
volumeModeOptions() {
return Object.values(VOLUME_MODE);
},
},
watch: {
@@ -118,6 +147,8 @@ export default {
immediate: true,
handler(neu) {
this.value.accessMode = this.getAccessMode(neu);
this.value.volumeMode = this.getVolumeMode(neu, this.value.volumeMode);
this.update();
}
},
@@ -140,7 +171,7 @@ export default {
minExponent: 3,
});
this.value.size = `${ formatSize }Gi`;
this.value.size = `${ formatSize }${ GIBIBYTE }`;
}
},
deep: true,
@@ -149,6 +180,20 @@ export default {
},
methods: {
getVolumeMode(storageClassName, originalVolumeMode) {
if (!this.thirdPartyStorageClassEnabled) {
return VOLUME_MODE.BLOCK;
}
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
// longhorn v1, v2 use block volumeMode
if (storageClass && storageClass.isLonghorn) {
return VOLUME_MODE.BLOCK;
}
return originalVolumeMode;
},
getAccessMode(storageClassName) {
if (!this.longhornV2LVMSupport) {
return 'ReadWriteMany';
@@ -156,10 +201,10 @@ export default {
const storageClass = this.storageClasses.find((sc) => sc.name === storageClassName);
let readWriteOnce = this.value.pvc?.isLvm || this.value.pvc?.isLonghornV2;
let readWriteOnce = this.value.pvc?.isLvm || (!this.thirdPartyStorageClassEnabled && this.value.pvc?.isLonghornV2);
if (storageClass) {
readWriteOnce = storageClass.provisioner === LVM_DRIVER || storageClass.parameters?.dataEngine === DATA_ENGINE_V2;
readWriteOnce = storageClass.provisioner === LVM_DRIVER || (!this.thirdPartyStorageClassEnabled && storageClass.parameters?.dataEngine === DATA_ENGINE_V2);
}
return readWriteOnce ? 'ReadWriteOnce' : 'ReadWriteMany';
@@ -258,6 +303,7 @@ export default {
:required="validateRequired"
:label="t('harvester.fields.size')"
:disabled="isLonghornV2"
:suffix="GIBIBYTE"
@update:value="update"
/>
</InputOrDisplay>
@@ -283,6 +329,29 @@ export default {
/>
</InputOrDisplay>
</div>
<div
v-if="showVolumeMode"
data-testid="input-volume-mode"
class="col span-6"
>
<InputOrDisplay
:name="t('harvester.volume.volumeMode')"
:value="value.volumeMode"
:mode="mode"
>
<LabeledSelect
v-model:value="value.volumeMode"
:label="t('harvester.volume.volumeMode')"
:mode="mode"
:options="volumeModeOptions"
:disabled="isEdit"
required
@update:value="update"
/>
</InputOrDisplay>
</div>
</div>
<div class="row mb-20">
<div
v-if="value.volumeEncryptionFeatureEnabled && isView"
class="col span-6"
@@ -292,11 +361,9 @@ export default {
:value="encryptionValue"
/>
</div>
</div>
<div class="row mb-20">
<div
v-if="value.volumeBackups && isView"
class="col span-3"
class="col span-6"
>
<LabelValue
:name="t('harvester.virtualMachine.volume.readyToUse')"
@@ -2,6 +2,7 @@
import { isEqual } from 'lodash';
import { mapGetters } from 'vuex';
import Tabbed from '@shell/components/Tabbed';
import { clone, set } from '@shell/utils/object';
import Tab from '@shell/components/Tabbed/Tab';
import { Checkbox } from '@components/Form/Checkbox';
import CruResource from '@shell/components/CruResource';
@@ -10,16 +11,12 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import UnitInput from '@shell/components/form/UnitInput';
import Labels from '@shell/components/form/Labels';
import NodeScheduling from '@shell/components/form/NodeScheduling';
import PodAffinity from '@shell/components/form/PodAffinity';
import VGpuDevices from './VirtualMachineVGpuDevices/index';
import UsbDevices from './VirtualMachineUSBDevices/index';
import KeyValue from '@shell/components/form/KeyValue';
import { clear } from '@shell/utils/array';
import { clone } from '@shell/utils/object';
import { saferDump } from '@shell/utils/create-yaml';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
@@ -65,7 +62,6 @@ export default {
PodAffinity,
AccessCredentials,
Reserved,
Labels,
PciDevices,
RestartVMDialog,
UnitInput,
@@ -195,7 +191,12 @@ export default {
return true;
},
isValidationPassed() {
// check if any disk hasDiskError is true
const hasError = this.diskRows.some((disk) => disk.hasDiskError === true);
return !hasError;
},
showCpuPinningBanner() {
if (!this.value.cpuPinningFeatureEnabled) {
return false;
@@ -210,7 +211,10 @@ export default {
}
return false;
}
},
usbPassthroughEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
},
},
watch: {
@@ -407,13 +411,26 @@ export default {
}
const cloneDeepNewVM = clone(this.value);
// new VM
delete cloneDeepNewVM?.metadata;
delete cloneDeepNewVM?.__clone;
// old VM
delete this.cloneVM?.metadata;
delete this.cloneVM?.__clone;
// add empty hostDevices to old VM as CRD does not have it.
const devicesObj = this.cloneVM?.spec?.template?.spec?.domain?.devices;
if (devicesObj && devicesObj.hostDevices === undefined) {
set(devicesObj, 'hostDevices', []);
}
const oldVM = JSON.parse(JSON.stringify(this.cloneVM));
const newVM = JSON.parse(JSON.stringify(cloneDeepNewVM));
// we won't show restart dialog in yaml page as we don't have a way to detect change in yaml editor.
// only check VM is changed in form page.
if (isEqual(oldVM, newVM)) {
return;
}
@@ -422,7 +439,11 @@ export default {
this.isOpen = true;
this.$nextTick(() => {
this.$refs.restartDialog.resolve = resolve;
if (this?.$refs?.restartDialog) {
this.$refs.restartDialog.resolve = resolve;
} else {
return resolve();
}
});
});
},
@@ -472,6 +493,7 @@ export default {
},
generateYaml() {
this.showYaml = this?.$refs?.vmCruResource?.showYaml || false;
this.parseVM();
const out = saferDump(this.value);
@@ -485,10 +507,12 @@ export default {
<CruResource
v-if="spec"
id="vm"
ref="vmCruResource"
:done-route="doneRoute"
:resource="value"
:cancel-event="true"
:mode="mode"
:validation-passed="isValidationPassed"
:can-yaml="isSingle ? true : false"
:errors="errors"
:generate-yaml="generateYaml"
@@ -579,7 +603,7 @@ export default {
:create-namespace="true"
:namespace="value.metadata.namespace"
:mode="mode"
:disabled="isWindows"
:disabled="isWindows || isEdit"
@update:sshKey="updateSSHKey"
@register-after-hook="registerAfterHook"
/>
@@ -671,7 +695,7 @@ export default {
</Tab>
<Tab
v-if="enabledPCI"
v-if="enabledPCI && usbPassthroughEnabled"
:label="t('harvester.tab.usbDevices')"
name="usbDevices"
:weight="-7"
@@ -697,39 +721,50 @@ export default {
/>
</Tab>
<Tab
name="labels"
:label="t('generic.labels')"
:weight="-9"
>
<Banner color="info">
<t k="harvester.virtualMachine.labels.banner" />
</Banner>
<KeyValue
key="labels"
:value="value.labels"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setLabels($event)"
/>
</Tab>
<Tab
name="instanceLabel"
:label="t('harvester.tab.instanceLabel')"
:weight="-8"
:weight="-10"
>
<Labels
:default-container-class="'labels-and-annotations-container'"
:value="value"
<Banner color="info">
<t k="harvester.virtualMachine.instanceLabels.banner" />
</Banner>
<KeyValue
key="instance-labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:display-side-by-side="false"
:show-annotations="false"
:show-label-title="false"
>
<template #labels="{toggler}">
<KeyValue
key="labels"
:value="value.instanceLabels"
:protected-keys="value.systemLabels || []"
:toggle-filter="toggler"
:add-label="t('labels.addLabel')"
:mode="mode"
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setInstanceLabels($event)"
/>
</template>
</Labels>
:read-allowed="false"
:value-can-be-empty="true"
@update:value="value.setInstanceLabels($event)"
/>
</Tab>
<Tab
name="advanced"
:label="t('harvester.tab.advanced')"
:weight="-9"
:weight="-11"
>
<div class="row mb-20">
<div class="col span-6">
@@ -873,11 +908,29 @@ export default {
:mode="mode"
/>
<Checkbox
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
v-model:value="tpmPersistentStateEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
:mode="mode"
/>
<Checkbox
v-model:value="efiEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.efiEnabled')"
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
:mode="mode"
/>
<Checkbox
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
v-model:value="efiPersistentStateEnabled"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
:mode="mode"
/>
@@ -886,9 +939,10 @@ export default {
v-model:value="secureBoot"
class="check"
type="checkbox"
:label="t('harvester.virtualMachine.secureBoot')"
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
:mode="mode"
/>
<Banner
v-if="showCpuPinningBanner"
color="warning"
@@ -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);
}
});
@@ -2,7 +2,7 @@
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
import { METRIC, NODE } from '@shell/config/types';
import { formatSi, exponentNeeded, UNITS, parseSi } from '@shell/utils/units';
import { UNIT_SUFFIX } from '../utils/unit';
export default {
name: 'HarvesterMemoryUsed',
components: { ConsumptionGauge },
@@ -51,7 +51,7 @@ export default {
memoryUnits() {
const exponent = exponentNeeded(this.memoryTotal, 1024);
return `${ UNITS[exponent] }iB`;
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
},
node() {
@@ -2,6 +2,7 @@
import ConsumptionGauge from '@shell/components/ConsumptionGauge';
import { LONGHORN } from '@shell/config/types';
import { formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
import { UNIT_SUFFIX } from '../utils/unit';
export default {
name: 'HarvesterStorageUsed',
@@ -75,13 +76,13 @@ export default {
allocatedUnits() {
const exponent = exponentNeeded(this.storageStats.total, 1024);
return `${ UNITS[exponent] }iB`;
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
},
usedUnits() {
const exponent = exponentNeeded(this.storageStats.maximum, 1024);
return `${ UNITS[exponent] }iB`;
return `${ UNITS[exponent] }${ UNIT_SUFFIX }`;
},
formatUsed() {
+6 -1
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) {
@@ -20,7 +21,7 @@ export default function (plugin: IPlugin) {
// Built-in icon
plugin.metadata.icon = require('./icon.svg');
plugin.addProduct(require('./config/harvester-cluster'));
plugin.addDashboardStore(harvesterCommonStore.config.namespace, harvesterCommonStore.specifics, harvesterCommonStore.config);
@@ -28,4 +29,8 @@ export default function (plugin: IPlugin) {
plugin.validators = customValidators;
plugin.addRoutes(extensionRoutes);
plugin.register('component', 'NavHeaderRight', defineAsyncComponent(() =>
import('./components/HarvesterUpgradeHeader.vue')
));
}
+77 -21
View File
@@ -38,9 +38,35 @@ asyncButton:
success: Restarted
waiting: Restarting&hellip;
dialog:
confirmExecution:
title: Are you sure?
andOthers: |-
{count, plural,
=0 {}
=1 { and <b>one other </b>}
other { and <b>{count} other </b>}
}
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
stop:
message: "Are you sure you want to continue stop the {type} {names}?"
pause:
message: "Are you sure you want to continue pause the {type} {names}?"
promptRemove:
title: "Delete {type}"
warningMessage: "Deleting the selected {type} permanently removes all resources on {thisOrThese} {type}. This action is irreversible. Do you want to continue?"
confirmName: "Type <b>{nameToMatch}</b> to delete the {type}:"
protip: "Tip: Hold the {alternateLabel} key while clicking Delete to bypass the confirmation step."
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:
success: 'Backup { backUpName } has been initiated.'
addBackup: Add Backup
@@ -79,6 +105,14 @@ harvester:
tip: Please enter a virtual machine name!
success: 'Virtual machine { name } cloned successfully.'
failed: 'Failed clone virtual machine!'
osImage:
title: Delete Image
message: Are you sure you want to delete the image { name } ?
downloadImage:
title: Download Image
banner: 'This action takes a while depending on the image size ({ size }). Please be patient.'
startMessage : 'The download process will auto start once the conversion is complete.'
download: Download
exportImage:
title: Export to Image
name: Name
@@ -579,15 +613,17 @@ harvester:
enableUsb: Enable USB Tablet
advancedOptions:
tpm: Enable TPM
tpmPersistentState: TPM Persistent State
cpuManager:
prefix: You must enable CPU Manager for at least one node in
middle: 'host page'
suffix: to enable CPU Pinning for VM
efiEnabled: Booting in EFI mode
efiPersistentState: EFI Persistent State
secureBoot: Secure Boot
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
sshTitle: Add Public SSH Key
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
efiEnabled: Booting in EFI mode
secureBoot: Secure Boot
volume:
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
@@ -608,6 +644,7 @@ harvester:
setFirst: Set as root volume
saveVolume: Update Volume
encryption: Encryption
vmImageVolumeTip: Disk size ({diskSize}) should greater than selected image virtual size ({imageVirtualSize})
lockTooltip:
all: All volumes are encrypted.
partial: Some volumes are encrypted.
@@ -742,11 +779,18 @@ 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"
namespaces:
label: Namespaces
instanceLabels:
banner: These labels are automatically synchronized to the virtual machine instance.
labels:
banner: These key values are added as labels to the virtual machine.
volume:
label: Volumes
@@ -756,6 +800,7 @@ harvester:
datasource: Data Source
details: Details
size: Size
volumeMode: Volume Mode
source: Source
kind: Kind
sourceOptions:
@@ -806,6 +851,8 @@ harvester:
=1 {1 image is uploading, please do not refresh or close the page.}
other {{count} images are uploading, please do not refresh or close the page.}
}
osUpgrade:
uploading: "{name} is uploading, please do not refresh or close the page."
checksum: Checksum
checksumTip: Validate the image using the SHA512 checksum, if specified.
@@ -830,17 +877,22 @@ harvester:
currentVersion: CURRENT VERSION
versionLabel: AVAILABLE COMPATIBLE VERSIONS
enableLogging: Enable Logging
skipSingleReplicaDetachedVol: Skip checking single-replica detached volumes
upgradeNode: Upgrading Node
upgradeSysService: Upgrading System Service
upgradeImage: Download Upgrade Image
osUpgrade: OS Upgrade
osUpgrade: Cluster Upgrade
uploadNew: Upload New Image
deleteHeader: Please select an image to delete.
deleteExisting: Delete Existing Image
selectExisting: Select Existing Image
createRepository: Creating Upgrade Repository
succeeded: Succeeded
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>.
checkReady: I have read and understood the upgrade instructions related to this Harvester version.
pending: Pending
upload:
duplicatedFile: The file you are trying to upload already exists.
repoInfo:
upgradeStatus: Upgrade Status
os: OS
@@ -858,14 +910,14 @@ harvester:
doc: Read the <a href="{url}" target="_blank">documentation</a> before starting the upgrade process. Ensure that you complete procedures that are relevant to your environment and the version you are upgrading to.
tip: Unmet system requirements and incorrectly performed procedures may cause complete upgrade failure and other issues that require manual workarounds.
moreNotes: For more details about the release notes, please visit -
schedule:
label: Virtual Machine Schedules
createTitle: Create Schedule
createButtonText: Create Schedule
scheduleType: Virtual Machine Schedule Type
cron: Cron Schedule
detail:
detail:
namespace: Namespace
sourceVM: Source Virtual Machine
tabs:
@@ -875,12 +927,12 @@ harvester:
message:
noSetting:
suffix: before creating a backup schedule
retain:
retain:
label: Retain
count: Count
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
tooltip: Number of up-to-date VM backups to retain. Maximum to 250, minimum to 2.
maxFailure:
label: Max Failure
label: Max Failure
count: Count
tooltip: Max number of consecutive failed backups that could be tolerated. If reach this threshold, Harvester controller will suspend the schedule job. This value should less than retain count
virtualMachine:
@@ -894,6 +946,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:
@@ -1003,7 +1058,7 @@ harvester:
invalid: '"Exclude list" is invalid.'
addIp: Add Exclude IP
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 4 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 2 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
vmForceDeletionPolicy:
period: Period
ratio : Ratio
@@ -1040,10 +1095,15 @@ harvester:
addConfig: Add Configuration
upgrade:
selectExitImage: Please select the OS image to upgrade.
unknownImageName: Image name is not found.
imageUrl: Please input a valid image URL.
chooseFile: Please select to upload an image.
checksum: Checksum
networkError: Unable to upload the image. Resolve network issues that may have occurred and try again.
cancelUpload: Cancelled the image upload.
uploadSuccess: "{name} uploaded successfully. Press Upgrade button to start the cluster upgrade process."
deleteImage: Please select an image to delete.
deleteSuccess: "{name} deleted successfully."
harvesterMonitoring:
label: Harvester Monitoring
section:
@@ -1494,17 +1554,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'
@@ -8,9 +8,7 @@ import Banner from '@components/Banner/Banner.vue';
import MessageLink from '@shell/components/MessageLink';
import SortableTable from '@shell/components/SortableTable';
import { allHash, setPromiseResult } from '@shell/utils/promise';
import {
parseSi, formatSi, exponentNeeded, UNITS, createMemoryValues
} from '@shell/utils/units';
import { parseSi, formatSi, exponentNeeded, UNITS } from '@shell/utils/units';
import { REASON } from '@shell/config/table-headers';
import {
EVENT, METRIC, NODE, SERVICE, PVC, LONGHORN, POD, COUNT, NETWORK_ATTACHMENT
@@ -27,22 +25,21 @@ import { isEmpty } from '@shell/utils/object';
import { HCI } from '../types';
import HarvesterUpgrade from '../components/HarvesterUpgrade';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../config/harvester';
import { UNIT_SUFFIX } from '../utils/unit';
dayjs.extend(utc);
dayjs.extend(minMax);
const PARSE_RULES = {
memory: {
format: {
addSuffix: true,
firstSuffix: 'B',
increment: 1024,
maxExponent: 99,
maxPrecision: 2,
minExponent: 0,
startingExponent: 0,
suffix: 'iB',
}
format: {
addSuffix: true,
firstSuffix: UNIT_SUFFIX,
increment: 1024,
maxExponent: 99,
maxPrecision: 2,
minExponent: 0,
startingExponent: 0,
suffix: UNIT_SUFFIX,
}
};
@@ -135,6 +132,7 @@ export default {
const hash = {
vms: this.fetchClusterResources(HCI.VM),
pvcs: this.fetchClusterResources(PVC),
nodes: this.fetchClusterResources(NODE),
events: this.fetchClusterResources(EVENT),
metricNodes: this.fetchClusterResources(METRIC.NODE),
@@ -224,6 +222,7 @@ export default {
nodes: [],
metricNodes: [],
vms: [],
pvcs: [],
monitoring: {},
VM_DASHBOARD_METRICS_URL,
CLUSTER_METRICS_SUMMARY_URL,
@@ -288,6 +287,14 @@ export default {
out[resource.type].name = this.t(`typeLabel."${ resource.spoofed.name }"`, { count: out[resource.type].total });
}
if (resource.type === PVC) {
// filter out the golden image volumes
const goldenImageVolumeCount = (this.pvcs || []).filter((pvc) => pvc.isGoldenImageVolume).length;
out[resource.type].useful = out[resource.type].useful - goldenImageVolumeCount;
out[resource.type].total = out[resource.type].total - goldenImageVolumeCount;
}
if (resource.type === HCI.BLOCK_DEVICE) {
let total = 0;
let errorCount = 0;
@@ -402,13 +409,13 @@ export default {
storageUsed() {
const stats = this.storageStats;
return this.createMemoryValues(stats.maximum, stats.used);
return this.createDisplayValues(stats.maximum, stats.used);
},
storageAllocated() {
const stats = this.storageStats;
return this.createMemoryValues(stats.total, stats.scheduled);
return this.createDisplayValues(stats.total, stats.scheduled);
},
vmEvents() {
@@ -454,7 +461,7 @@ export default {
return total + node.memoryReserved;
}, 0);
return createMemoryValues(this.memoryTotal, useful);
return this.createDisplayValues(this.memoryTotal, useful);
},
availableNodes() {
@@ -494,7 +501,7 @@ export default {
},
ramUsed() {
return createMemoryValues(this.memoryTotal, this.metricAggregations?.memory);
return this.createDisplayValues(this.memoryTotal, this.metricAggregations?.memory);
},
hasMetricNodeSchema() {
@@ -516,10 +523,12 @@ export default {
},
methods: {
createMemoryValues(total, useful) {
createDisplayValues(total, useful) {
const parsedTotal = parseSi((total || '0').toString());
const parsedUseful = parseSi((useful || '0').toString());
const format = this.createMemoryFormat(parsedTotal);
const format = this.createFormat(parsedTotal);
const formattedTotal = formatSi(parsedTotal, format);
let formattedUseful = formatSi(parsedUseful, {
...format,
@@ -538,24 +547,24 @@ export default {
useful: Number(parsedUseful),
formattedTotal,
formattedUseful,
units: this.createMemoryUnits(parsedTotal),
units: this.createUnits(parsedTotal),
};
},
createMemoryFormat(n) {
const exponent = exponentNeeded(n, PARSE_RULES.memory.format.increment);
createFormat(n) {
const exponent = exponentNeeded(n, PARSE_RULES.format.increment);
return {
...PARSE_RULES.memory.format,
...PARSE_RULES.format,
maxExponent: exponent,
minExponent: exponent,
};
},
createMemoryUnits(n) {
const exponent = exponentNeeded(n, PARSE_RULES.memory.format.increment);
createUnits(n) {
const exponent = exponentNeeded(n, PARSE_RULES.format.increment);
return `${ UNITS[exponent] }${ PARSE_RULES.memory.format.suffix }`;
return `${ UNITS[exponent] }${ PARSE_RULES.format.suffix }`;
},
async fetchClusterResources(type, opt = {}, store) {
+2 -2
View File
@@ -101,14 +101,14 @@ export default {
const metricCol = [
{
name: 'cpu',
labelKey: 'node.detail.glance.consumptionGauge.cpu',
labelKey: 'harvester.dashboard.hardwareResourceGauge.cpu',
value: 'id',
formatter: 'HarvesterCPUUsed',
formatterOpts: { showUsed: true },
},
{
name: 'memory',
labelKey: 'node.detail.glance.consumptionGauge.memory',
labelKey: 'harvester.dashboard.hardwareResourceGauge.memory',
value: 'id',
formatter: 'HarvesterMemoryUsed',
formatterOpts: { showUsed: true },
+2 -11
View File
@@ -2,13 +2,12 @@
import { mapGetters } from 'vuex';
import { Banner } from '@components/Banner';
import Loading from '@shell/components/Loading';
import { VIEW_IN_API, DEV } from '@shell/store/prefs';
import { MANAGEMENT } from '@shell/config/types';
import { allHash } from '@shell/utils/promise';
import Tabbed from '@shell/components/Tabbed/index.vue';
import Tab from '@shell/components/Tabbed/Tab.vue';
import Settings from '@pkg/harvester/components/SettingList.vue';
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING } from '../config/settings';
import { HCI_ALLOWED_SETTINGS, HCI_SINGLE_CLUSTER_ALLOWED_SETTING, HCI_SETTING } from '../config/settings';
import { HCI } from '../types';
export default {
@@ -21,14 +20,6 @@ export default {
},
async fetch() {
let isDev;
try {
isDev = this.$store.getters['prefs/get'](VIEW_IN_API);
} catch {
isDev = this.$store.getters['prefs/get'](DEV);
}
const isSingleProduct = !!this.$store.getters['isSingleProduct'];
const inStore = this.$store.getters['currentProduct'].inStore;
@@ -77,7 +68,7 @@ export default {
};
s.hide = s.canHide = (s.kind === 'json' || s.kind === 'multiline' || s.customFormatter === 'json' || s.data.customFormatter === 'json');
s.hasActions = !s.readOnly || isDev;
s.hasActions = s.id === HCI_SETTING.SERVER_VERSION ? true : !s.readOnly;
initSettings.push(s);
});
@@ -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"
@@ -6,7 +6,6 @@ import {
} from '@shell/config/types';
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
import HarvesterVolumeState from '../formatters/HarvesterVolumeState';
import { allSettled } from '../utils/promise';
import { HCI, VOLUME_SNAPSHOT } from '../types';
@@ -58,7 +57,6 @@ export default {
if (!pvcSchema?.collectionMethods.find((x) => x.toLowerCase() === 'post')) {
this.$store.dispatch('type-map/configureType', { match: HCI.VOLUME, isCreatable: false });
}
this.rows = hash.pvcs;
},
@@ -70,7 +68,10 @@ export default {
schema() {
return schema;
},
filterRows() {
// we only show the non golden image PVCs in the list
return this.rows.filter((pvc) => !pvc?.isGoldenImageVolume);
},
headers() {
return [
STATE,
@@ -92,7 +93,8 @@ export default {
{
name: 'storageClass',
labelKey: 'tableHeaders.storageClass',
value: 'spec.storageClassName'
value: 'spec.storageClassName',
sort: 'spec.storageClassName'
},
{
name: 'AttachedVM',
@@ -146,7 +148,7 @@ export default {
:groupable="true"
default-sort-by="age"
:namespaced="true"
:rows="rows"
:rows="filterRows"
:schema="schema"
key-field="_key"
>
+9 -1
View File
@@ -137,7 +137,15 @@ export default {
},
isTpmEnabled(spec) {
return !!spec?.template?.spec?.domain?.devices?.tpm ;
return !!spec?.template?.spec?.domain?.devices?.tpm;
},
isTPMPersistentStateEnabled(spec) {
return !!spec?.template?.spec?.domain?.devices?.tpm?.persistent;
},
isEFIPersistentStateEnabled(spec) {
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.persistent;
},
isSecureBoot(spec) {
+74 -52
View File
@@ -25,6 +25,8 @@ import { HCI_SETTING } from '../../config/settings';
import { HCI } from '../../types';
import { parseVolumeClaimTemplates } from '../../utils/vm';
import impl, { QGA_JSON, USB_TABLET } from './impl';
import { GIBIBYTE } from '../../utils/unit';
import { VOLUME_MODE } from '@pkg/harvester/config/types';
const LONGHORN_V2_DATA_ENGINE = 'longhorn-system/v2-data-engine';
@@ -138,6 +140,7 @@ export default {
return {
OS,
isClone,
showYaml: false,
spec: null,
osType: 'linux',
sshKey: [],
@@ -164,6 +167,8 @@ export default {
accessCredentials: [],
efiEnabled: false,
tpmEnabled: false,
tpmPersistentStateEnabled: false,
efiPersistentStateEnabled: false,
secureBoot: false,
userDataTemplateId: '',
saveUserDataAsClearText: false,
@@ -217,7 +222,7 @@ export default {
const selectedVMNetworks = networkNames.map((name) => vmNetworks.find((n) => n.id === name)).filter((n) => n?.id);
const clusterNetworks = uniq(selectedVMNetworks.map((n) => n.clusterNetworkResource?.id));
return nodes.filter((N) => !N.isUnSchedulable).map((node) => {
return nodes.filter((N) => !N.isUnSchedulable && N.isEtcd !== 'true').map((node) => {
const requireLabelKeys = [];
let isNetworkSchedule = true;
@@ -241,12 +246,6 @@ export default {
});
},
defaultStorageClass() {
const defaultStorage = this.$store.getters[`${ this.inStore }/all`](STORAGE_CLASS).find( (O) => O.isDefault);
return defaultStorage;
},
storageClassSetting() {
try {
const storageClassValue = this.$store.getters[`${ this.inStore }/all`](HCI.SETTING).find( (O) => O.id === HCI_SETTING.DEFAULT_STORAGE_CLASS)?.value;
@@ -258,7 +257,7 @@ export default {
},
customVolumeMode() {
return this.storageClassSetting.volumeMode || 'Block';
return this.storageClassSetting.volumeMode || VOLUME_MODE.BLOCK;
},
customAccessMode() {
@@ -271,7 +270,7 @@ export default {
needNewSecret() {
// When creating a template it is always necessary to create a new secret.
return this.resourceType === HCI.VM_VERSION || this.isCreate;
return this.showYaml ? false : this.resourceType === HCI.VM_VERSION || this.isCreate;
},
defaultTerminationSetting() {
@@ -282,14 +281,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')
};
},
},
@@ -367,6 +366,8 @@ export default {
const installAgent = this.hasInstallAgent(userData, osType, true);
const efiEnabled = this.isEfiEnabled(spec);
const tpmEnabled = this.isTpmEnabled(spec);
const tpmPersistentStateEnabled = this.isTPMPersistentStateEnabled(spec);
const efiPersistentStateEnabled = this.isEFIPersistentStateEnabled(spec);
const secureBoot = this.isSecureBoot(spec);
const cpuPinning = this.isCpuPinning(spec);
@@ -398,7 +399,9 @@ export default {
this['installUSBTablet'] = installUSBTablet;
this['efiEnabled'] = efiEnabled;
this['efiPersistentStateEnabled'] = efiPersistentStateEnabled;
this['tpmEnabled'] = tpmEnabled;
this['tpmPersistentStateEnabled'] = tpmPersistentStateEnabled;
this['secureBoot'] = secureBoot;
this['cpuPinning'] = cpuPinning;
@@ -442,7 +445,7 @@ export default {
if (!isIsoImage) {
imageSizeGiB = Math.max(imageSizeGiB, 10);
}
size = `${ imageSizeGiB }Gi`;
size = `${ imageSizeGiB }${ GIBIBYTE }`;
}
out.push({
@@ -456,7 +459,7 @@ export default {
type,
storageClassName: '',
image: this.imageId,
volumeMode: 'Block',
volumeMode: VOLUME_MODE.BLOCK,
isEncrypted,
volumeBackups,
});
@@ -515,7 +518,7 @@ export default {
accessMode = pvcResource?.spec?.accessModes?.[0] || 'ReadWriteMany';
size = pvcResource?.spec?.resources?.requests?.storage || '10Gi';
storageClassName = pvcResource?.spec?.storageClassName;
volumeMode = pvcResource?.spec?.volumeMode || 'Block';
volumeMode = pvcResource?.spec?.volumeMode || VOLUME_MODE.BLOCK;
volumeName = pvcResource?.metadata?.name || '';
}
@@ -552,7 +555,7 @@ export default {
volumeName,
container,
accessMode,
size: `${ formatSize }Gi`,
size: `${ formatSize }${ GIBIBYTE }`,
volumeMode: volumeMode || this.customVolumeMode,
image,
type,
@@ -667,7 +670,7 @@ export default {
}
});
if (!this.secretName || this.needNewSecret) {
if (this.needNewSecret || !this.secretName) {
this.secretName = this.generateSecretName(this.secretNamePrefix);
}
@@ -869,7 +872,7 @@ export default {
}
}
if (out.length === 0 && !!this.spec.template.spec.accessCredentials) {
if (out.length === 0 && !!this.spec.template.spec.accessCredentials === false) {
delete this.spec.template.spec.accessCredentials;
} else {
this.spec.template.spec.accessCredentials = out;
@@ -894,8 +897,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 +922,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
@@ -967,7 +972,7 @@ export default {
parseVolumeClaimTemplate(R, dataVolumeName) {
if (!String(R.size).includes('Gi') && R.size) {
R.size = `${ R.size }Gi`;
R.size = `${ R.size }${ GIBIBYTE }`;
}
const out = {
@@ -1212,7 +1217,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;
@@ -1383,13 +1387,21 @@ export default {
}
},
setBootMethod(boot = { efi: false, secureBoot: false }) {
if (boot.efi && boot.secureBoot) {
set(this.spec.template.spec.domain, 'features.smm.enabled', true);
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', true);
} else if (boot.efi && !boot.secureBoot) {
// set(this.spec.template.spec.domain, 'features.smm.enabled', false);
setBootMethod(boot = {
efi: false, secureBoot: false, efiPersistentStateEnabled: false
}) {
if (boot.efi) {
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', boot.secureBoot);
} else {
delete this.spec.template.spec.domain['firmware'];
delete this.spec.template.spec.domain.features['smm'];
return;
}
if (boot.secureBoot) {
set(this.spec.template.spec.domain, 'features.smm.enabled', true);
} else {
try {
delete this.spec.template.spec.domain.features.smm['enabled'];
const noKeys = Object.keys(this.spec.template.spec.domain.features.smm).length === 0;
@@ -1398,10 +1410,12 @@ export default {
delete this.spec.template.spec.domain.features['smm'];
}
} catch (e) {}
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', false);
}
if (boot.efiPersistentStateEnabled) {
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.persistent', true);
} else {
delete this.spec.template.spec.domain['firmware'];
delete this.spec.template.spec.domain.features['smm'];
delete this.spec.template.spec.domain.firmware.bootloader.efi['persistent'];
}
},
@@ -1413,9 +1427,9 @@ export default {
}
},
setTPM(tpmEnabled) {
setTPM({ tpmEnabled = false, tpmPersistentStateEnabled = false } = {}) {
if (tpmEnabled) {
set(this.spec.template.spec.domain.devices, 'tpm', {});
set(this.spec.template.spec.domain.devices, 'tpm', tpmPersistentStateEnabled ? { persistent: true } : {});
} else {
delete this.spec.template.spec.domain.devices['tpm'];
}
@@ -1431,7 +1445,6 @@ export default {
sshAuthorizedKeys.splice(index, 1);
}
});
const userDataJson = this.convertToJson(this.userScript);
userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
@@ -1529,11 +1542,21 @@ export default {
},
efiEnabled(val) {
this.setBootMethod({ efi: val, secureBoot: this.secureBoot });
this.setBootMethod({
efi: val, secureBoot: this.secureBoot, efiPersistentStateEnabled: this.efiPersistentStateEnabled
});
},
secureBoot(val) {
this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
this.setBootMethod({
efi: this.efiEnabled, secureBoot: val, efiPersistentStateEnabled: this.efiPersistentStateEnabled
});
},
efiPersistentStateEnabled(val) {
this.setBootMethod({
efi: this.efiEnabled, secureBoot: this.secureBoot, efiPersistentStateEnabled: val
});
},
cpuPinning(value) {
@@ -1541,7 +1564,11 @@ export default {
},
tpmEnabled(val) {
this.setTPM(val);
this.setTPM({ tpmEnabled: val, tpmPersistentStateEnabled: this.tpmPersistentStateEnabled });
},
tpmPersistentStateEnabled(val) {
this.setTPM({ tpmEnabled: this.tpmEnabled, tpmPersistentStateEnabled: val });
},
installAgent: {
@@ -1553,19 +1580,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 +1609,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;
}
}
+1 -2
View File
@@ -67,8 +67,7 @@ export default class HciNamespace extends namespace {
promptRemove(resources = this) {
this.$dispatch('promptModal', {
resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
component: 'ConfirmRelatedToRemoveDialog'
component: 'ConfirmRelatedToRemoveDialog'
});
}
+10 -4
View File
@@ -115,8 +115,7 @@ export default class HciNode extends HarvesterResource {
promptRemove(resources = this) {
this.$dispatch('promptModal', {
resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
component: 'ConfirmRelatedToRemoveDialog'
component: 'ConfirmRelatedToRemoveDialog'
});
}
@@ -186,9 +185,16 @@ export default class HciNode extends HarvesterResource {
}
get isKVMDisable() {
// Arm based
const isARMMachine = this.metadata.labels?.[HCI_ANNOTATIONS.K8S_ARCH]?.includes('arm');
if (isARMMachine) {
return this.status.capacity['devices.kubevirt.io/kvm'] && this.status.capacity['devices.kubevirt.io/kvm'] === '0';
}
const allNotExist = !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] && !this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU];
return this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] === 'false' || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU] === 'false' || allNotExist;
return allNotExist || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_AMD_CPU] === 'false' || this.metadata?.labels?.[HCI_ANNOTATIONS.KVM_INTEL_CPU] === 'false';
}
get stateDisplay() {
@@ -470,7 +476,7 @@ export default class HciNode extends HarvesterResource {
get canDelete() {
const nodes = this.$rootGetters['harvester/all'](NODE) || [];
return nodes.length > 1;
return nodes.length > 1 && super.canDelete;
}
get vlanStatuses() {
@@ -46,22 +46,37 @@ export default class HciPv extends HarvesterResource {
}
}
if (!this.isLonghorn || !this.isLonghornV2) {
const exportImageAction = {
action: 'exportImage',
enabled: this.hasAction('export') && !this.isEncrypted,
icon: 'icon icon-copy',
label: this.t('harvester.action.exportImage')
};
const takeSnapshotAction = {
action: 'snapshot',
enabled: this.hasAction('snapshot'),
icon: 'icon icon-backup',
label: this.t('harvester.action.snapshot'),
};
if (this.thirdPartyStorageFeatureEnabled) { // v1.5.0
out = [
{
action: 'exportImage',
enabled: this.hasAction('export') && !this.isEncrypted,
icon: 'icon icon-copy',
label: this.t('harvester.action.exportImage')
},
{
action: 'snapshot',
enabled: this.hasAction('snapshot'),
icon: 'icon icon-backup',
label: this.t('harvester.action.snapshot'),
},
exportImageAction,
takeSnapshotAction,
...out
];
// TODO: remove this block if Longhorn V2 engine supports restore volume snapshot
if (this.isLonghornV2) {
out = out.filter((action) => action.action !== takeSnapshotAction.action);
}
} else { // v1.4 / v1.3
if (!this.isLonghorn || !this.isLonghornV2) {
out = [
exportImageAction,
takeSnapshotAction,
...out
];
}
}
return [
@@ -238,7 +253,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() {
@@ -321,15 +336,31 @@ export default class HciPv extends HarvesterResource {
return this.volumeProvider === LONGHORN_DRIVER;
}
get isLonghornV1() {
return this.isLonghorn && !this.isLonghornV2;
}
get isLonghornV2() {
return this.dataEngine === DATA_ENGINE_V2;
}
get isGoldenImageVolume() {
return this?.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true';
}
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
get resourceExternalLink() {
const host = window.location.host;
const { params } = this.currentRoute();
const volumeName = this.spec?.volumeName;
if (!this.isLonghorn) {
return null;
}
if (!volumeName) {
return null;
}
@@ -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 {}
@@ -58,6 +58,18 @@ export default class HciStorageClass extends StorageClass {
return key ? this.$rootGetters['i18n/t'](key) : this.provisioner;
}
get isEncrypted() {
return this.parameters?.encrypted === 'true';
}
get isLonghorn() {
return this.provisioner === LONGHORN_DRIVER;
}
get isLonghornV1() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V1;
}
get isLonghornV2() {
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
}
@@ -69,4 +81,8 @@ export default class HciStorageClass extends StorageClass {
get volumeEncryptionFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
}
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
}
@@ -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,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() {

Some files were not shown because too many files have changed in this diff Show More