Compare commits

...
55 Commits
Author SHA1 Message Date
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
59 changed files with 990 additions and 1585 deletions
+1
View File
@@ -2,6 +2,7 @@ pull_request_rules:
- name: Automatically open backport PR to release-harvester-v1.5
conditions:
- base=main
- label="require backport 1.5"
actions:
backport:
branches:
@@ -11,18 +11,19 @@ defaults:
working-directory: ./
jobs:
lint:
check-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Checkout repository
uses: actions/checkout@v4
- name: Run tests
uses: ./.github/actions/lint
- 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: lint
needs: check-version
uses: ./.github/workflows/build-extension-catalog.yml
permissions:
actions: write
@@ -32,5 +33,4 @@ jobs:
with:
registry_target: docker.io
registry_user: rancher
repository_name: harvester-ui-catalog
secrets: inherit
+2 -13
View File
@@ -6,9 +6,6 @@ on:
registry_target:
required: true
type: string
repository_name:
required: true
type: string
registry_user:
required: true
type: string
@@ -69,13 +66,5 @@ jobs:
- name: Build and push UI image
run: |
publish="yarn publish-pkgs -c -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
$publish
REPO_NAME=$(echo "${{ github.repository }}" | cut -d'/' -f2)
IMAGE_VERSION=$(cat package.json | jq .version | sed 's/"//g')
FULL_IMAGE_NAME=$(echo ${{ inputs.registry_target }}/${{ inputs.registry_user }}/ui-extension-$REPO_NAME:$IMAGE_VERSION)
IMAGE_ID=$(docker inspect --format='{{.Id}}' $FULL_IMAGE_NAME)
docker tag $IMAGE_ID ${{ inputs.registry_target }}/${{ inputs.registry_user }}/${{ inputs.repository_name }}:$IMAGE_VERSION
docker push ${{ inputs.registry_target }}/${{ inputs.registry_user }}/${{ inputs.repository_name }}:$IMAGE_VERSION
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
$publish
@@ -128,7 +128,7 @@ jobs:
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
- name: Run Helm chart releaser
uses: helm/chart-releaser-action@v1.4.1
uses: helm/chart-releaser-action@v1.7.0
with:
charts_dir: ./charts
env:
@@ -22,26 +22,27 @@ jobs:
- name: Determine release tag
id: determine_tag
run: |
if [[ "${{ github.ref_name }}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
if [[ "${{ github.event.release.tag_name }}" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
RELEASE_TAG="harvester-${BASH_REMATCH[1]}"
echo "${RELEASE_TAG}"
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
else
echo "Error: invalid tag format." && exit 1
fi
lint:
check-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- 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
- name: Run tests
uses: ./.github/actions/lint
build-extension-charts:
needs:
- setup-release-tag
- lint
- check-version
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions:
actions: write
@@ -5,14 +5,20 @@ on:
- v[1-9].*
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/run-lint.yaml
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:
- build-validation
needs: check-version
permissions:
contents: read
packages: write
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "harvester-ui-extension",
"version": "1.5.0-rc2",
"version": "1.5.2-rc1",
"private": false,
"engines": {
"node": ">=20.0.0"
@@ -13,24 +13,24 @@
"node-polyfill-webpack-plugin": "^3.0.0",
"vue-draggable-next": "^2.2.1",
"yaml": "^2.5.1",
"@babel/plugin-transform-class-static-block": "7.26.0"
"@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": {
+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"
@@ -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
@@ -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 -67
View File
@@ -1,69 +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',
];
// TODO: add v1.3.3 official release note
// https://github.com/harvester/dashboard/releases/tag/v1.3.3-dev-20250105
const featuresV133 = [
...featuresV132,
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.0
const featuresV140 = [
...featuresV133,
'cpuPinning',
'usbPassthrough',
'volumeEncryption',
'schedulingVMBackup',
'vmSnapshotQuota',
'longhornV2LVMSupport',
'improveMaintenanceMode',
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.1
const featuresV141 = [
...featuresV140
];
// TODO: add v1.4.2 official release note
const featuresV142 = [
...featuresV141,
'refreshIntervalInSecond',
'allowEmptySnapshotClassName'
];
// TODO: add v1.5.0 official release note
const featuresV150 = [
...featuresV142,
'tpmPersistentState',
'efiPersistentState',
'untaggedNetworkSetting',
'skipSingleReplicaDetachedVol',
'thirdPartyStorage'
];
export const RELEASE_FEATURES = {
'v1.3.0': featuresV130,
'v1.3.1': featuresV131,
'v1.3.2': featuresV132,
'v1.3.3': featuresV133,
'v1.4.0': featuresV140,
'v1.4.1': featuresV141,
'v1.4.2': featuresV142,
'v1.5.0': featuresV150
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();
@@ -66,4 +66,5 @@ export const HCI = {
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'
};
+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>
@@ -67,7 +67,7 @@ export default {
plusMore() {
const remaining = this.resources.length - this.names.length;
return this.t('dialog.confirmExecution.andOthers', { count: remaining });
return this.t('dialog.confirmExecution.andOthers', { count: remaining }, true);
},
type() {
@@ -138,6 +138,8 @@ export default {
<Banner
v-for="(error, i) in errors"
:key="i"
:label="error"
color="error"
/>
</div>
</template>
@@ -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>
@@ -187,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>
@@ -205,11 +211,6 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -122,16 +122,15 @@ export default {
</Banner>
{{ t('harvester.modal.downloadImage.startMessage') }}
<br /><br />
</template>
<template #actions>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
>
{{ err }}
</Banner>
:label="err"
/>
</template>
<template #actions>
<div class="actions">
<div class="buttons">
<button
@@ -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>
@@ -96,6 +96,12 @@ export default {
:suffix="GIBIBYTE"
class="mb-20"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -109,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>
@@ -233,7 +233,7 @@ export default {
:disabled="isEdit || isView"
:mode="mode"
row
@input="onTypeChange"
@update:value="onTypeChange"
/>
</div>
<div class="row mb-30">
@@ -307,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'];
@@ -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] };
});
@@ -6,7 +6,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard';
import { _VIEW, _EDIT } from '@shell/config/query-params';
import { _VIEW } from '@shell/config/query-params';
import { NAMESPACE } from '@shell/config/types';
import { HCI } from '../../types';
@@ -90,9 +90,6 @@ export default {
},
sshOption() {
if (this.mode === _VIEW || this.mode === _EDIT) {
return [];
}
const out = this.$store.getters['harvester/all'](HCI.SSH).map( (O) => {
return {
label: O.id,
@@ -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] };
});
@@ -111,7 +111,7 @@ export default {
return false;
}
if (pvc.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true') {
if (pvc.isGoldenImageVolume) {
return false;
}
@@ -142,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) {
@@ -201,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';
@@ -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';
@@ -15,9 +16,7 @@ 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';
@@ -212,7 +211,10 @@ export default {
}
return false;
}
},
usbPassthroughEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
},
},
watch: {
@@ -409,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;
}
@@ -424,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();
}
});
});
},
@@ -474,6 +493,7 @@ export default {
},
generateYaml() {
this.showYaml = this?.$refs?.vmCruResource?.showYaml || false;
this.parseVM();
const out = saferDump(this.value);
@@ -487,6 +507,7 @@ export default {
<CruResource
v-if="spec"
id="vm"
ref="vmCruResource"
:done-route="doneRoute"
:resource="value"
:cancel-event="true"
@@ -674,7 +695,7 @@ export default {
</Tab>
<Tab
v-if="enabledPCI"
v-if="enabledPCI && usbPassthroughEnabled"
:label="t('harvester.tab.usbDevices')"
name="usbDevices"
:weight="-7"
@@ -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>
+23 -3
View File
@@ -52,6 +52,11 @@ dialog:
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:
@@ -61,6 +66,7 @@ harvester:
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
@@ -99,6 +105,9 @@ 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.'
@@ -781,7 +790,7 @@ harvester:
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.
banner: These key values are added as labels to the virtual machine.
volume:
label: Volumes
@@ -842,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.
@@ -870,14 +881,18 @@ harvester:
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
@@ -1080,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:
@@ -132,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),
@@ -221,6 +222,7 @@ export default {
nodes: [],
metricNodes: [],
vms: [],
pvcs: [],
monitoring: {},
VM_DASHBOARD_METRICS_URL,
CLUSTER_METRICS_SUMMARY_URL,
@@ -285,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;
+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);
});
@@ -6,10 +6,8 @@ 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';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
const schema = {
id: HCI.VOLUME,
@@ -72,7 +70,7 @@ export default {
},
filterRows() {
// we only show the non golden image PVCs in the list
return this.rows.filter((pvc) => pvc?.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] !== 'true');
return this.rows.filter((pvc) => !pvc?.isGoldenImageVolume);
},
headers() {
return [
+13 -20
View File
@@ -140,6 +140,7 @@ export default {
return {
OS,
isClone,
showYaml: false,
spec: null,
osType: 'linux',
sshKey: [],
@@ -269,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() {
@@ -669,7 +670,7 @@ export default {
}
});
if (!this.secretName || this.needNewSecret) {
if (this.needNewSecret || !this.secretName) {
this.secretName = this.generateSecretName(this.secretNamePrefix);
}
@@ -871,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;
@@ -921,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
@@ -1442,7 +1445,6 @@ export default {
sshAuthorizedKeys.splice(index, 1);
}
});
const userDataJson = this.convertToJson(this.userScript);
userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
@@ -1578,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;
@@ -1615,15 +1607,16 @@ export default {
},
sshKey(neu, old) {
// refresh yaml editor to get the latest userScript
this.userScript = this.getUserData({ installAgent: this.installAgent, osType: this.osType });
this.refreshYamlEditor();
const _diff = difference(old, neu);
// 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();
}
}
};
+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'
});
}
+9 -3
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() {
@@ -344,6 +344,10 @@ export default class HciPv extends HarvesterResource {
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');
}
@@ -28,17 +28,15 @@ export default class HciSetting extends HarvesterResource {
}
const schema = this.$getters['schemaFor'](HCI.UPGRADE);
const hasUpgradeAccess = !!schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase()));
if (this.id === HCI_SETTING.SERVER_VERSION && hasUpgradeAccess) {
const latestUpgrade = this.$getters['all'](HCI.UPGRADE).find((upgrade) => upgrade.isLatestUpgrade);
out.unshift({
action: 'goToAirgapUpgrade',
enabled: true,
icon: 'icon icon-refresh',
label: this.t('harvester.upgradePage.upgrade'),
disabled: !!latestUpgrade && !latestUpgrade?.isUpgradeSucceeded
});
}
@@ -50,35 +50,45 @@ export default class HciVmImage extends HarvesterResource {
canCreateVM = false;
}
return [
const customActions = this.isReady ? [
{
action: 'createFromImage',
enabled: canCreateVM,
icon: 'icon icon-circle-plus',
label: this.t('harvester.action.createVM'),
disabled: !this.isReady,
},
{
action: 'encryptImage',
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
icon: 'icon icon-lock',
label: this.t('harvester.action.encryptImage'),
disabled: !this.isReady,
},
{
action: 'decryptImage',
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
icon: 'icon icon-unlock',
label: this.t('harvester.action.decryptImage'),
disabled: !this.isReady,
},
{
action: 'imageDownload',
enabled: this.links?.download,
icon: 'icon icon-download',
label: this.t('asyncButton.download.action'),
},
...out
action: 'imageDownload',
enabled: this.links?.download,
icon: 'icon icon-download',
label: this.t('asyncButton.download.action'),
}
] : [];
let filteredOut;
if (customActions.length > 0) {
filteredOut = out;
} else {
// if the first item is a divider, remove it from the array
filteredOut = out[0]?.divider ? out.slice(1) : out;
}
return [
...customActions,
...filteredOut
];
}
@@ -295,7 +305,7 @@ export default class HciVmImage extends HarvesterResource {
}
get uploadImage() {
return async(file) => {
return async(file, opt = {}) => {
const formData = new FormData();
formData.append('chunk', file);
@@ -303,22 +313,23 @@ export default class HciVmImage extends HarvesterResource {
try {
this.$ctx.commit('harvester-common/uploadStart', this.metadata.name, { root: true });
await this.doAction('upload', formData, {
const result = await this.doAction('upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'File-Size': file.size,
},
params: { size: file.size },
signal: opt.signal,
});
return result;
} catch (err) {
this.$ctx.commit('harvester-common/uploadError', { name: this.name, message: err.message }, { root: true });
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
return Promise.reject(err);
throw err;
} finally {
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
}
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
};
}
@@ -158,7 +158,7 @@ export default class VirtVm extends HarvesterResource {
},
{
action: 'takeVMSnapshot',
enabled: !!this.actions?.backup && !this.longhornV2Volumes.length,
enabled: (!!this.actions?.snapshot || !!this.action?.backup) && !this.longhornV2Volumes.length,
icon: 'icon icon-snapshot',
label: this.t('harvester.action.vmSnapshot')
},
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.5.0-rc2",
"version": "1.5.2-rc1",
"private": false,
"rancher": {
"annotations": {
@@ -7,13 +7,16 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import UpgradeInfo from '../../../../components/UpgradeInfo';
import { HCI } from '../../../../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../../config/harvester';
import ImagePercentageBar from '@shell/components/formatter/ImagePercentageBar';
import { Banner } from '@components/Banner';
import isEmpty from 'lodash/isEmpty';
const IMAGE_METHOD = {
NEW: 'new',
EXIST: 'exist'
NEW: 'new',
EXIST: 'exist',
DELETE: 'delete'
};
const DOWNLOAD = 'download';
@@ -22,7 +25,7 @@ const UPLOAD = 'upload';
export default {
name: 'HarvesterAirgapUpgrade',
components: {
Checkbox, CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo
Checkbox, CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo, ImagePercentageBar, Banner
},
inheritAttrs: false,
@@ -39,36 +42,32 @@ export default {
spec: { image: '' }
});
const imageValue = await this.$store.dispatch('harvester/create', {
type: HCI.IMAGE,
metadata: {
name: '',
namespace: 'harvester-system',
generateName: 'image-',
annotations: {}
},
spec: {
sourceType: UPLOAD,
displayName: '',
checksum: ''
},
});
await this.initImageValue();
this.value = value;
this.imageValue = imageValue;
},
beforeUnmount() {
if (this.uploadController) {
this.uploadController.abort();
}
},
data() {
return {
value: null,
file: {},
imageId: '',
imageSource: IMAGE_METHOD.NEW,
sourceType: UPLOAD,
imageValue: null,
errors: [],
enableLogging: true,
IMAGE_METHOD
value: null,
file: {},
uploadImageId: '',
imageId: '',
deleteImageId: '',
imageSource: IMAGE_METHOD.NEW,
sourceType: UPLOAD,
uploadController: null,
uploadResult: null,
imageValue: null,
enableLogging: true,
IMAGE_METHOD,
skipSingleReplicaDetachedVol: false,
errors: [],
};
},
@@ -77,22 +76,45 @@ export default {
return `${ HARVESTER_PRODUCT }-c-cluster-resource`;
},
osImageOptions() {
return this.$store.getters['harvester/all'](HCI.IMAGE)
.filter((I) => I.isOSImage)
.map((I) => {
return {
label: I.spec.displayName,
value: I.id,
disabled: !I.isReady
};
});
skipSingleReplicaDetachedVolFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
},
uploadImage() {
allOSImages() {
return this.$store.getters['harvester/all'](HCI.IMAGE).filter((I) => I.isOSImage) || [];
},
deleteOSImageOptions() {
return this.allOSImages.map((I) => {
return {
label: I.spec.displayName,
value: I.id,
};
});
},
osImageOptions() {
return this.allOSImages.map((I) => {
return {
label: I.spec.displayName,
value: I.id,
disabled: !I.isReady
};
});
},
createNewImage() {
return this.imageSource === IMAGE_METHOD.NEW;
},
selectExistImage() {
return this.imageSource === IMAGE_METHOD.EXIST;
},
deleteExistImage() {
return this.imageSource === IMAGE_METHOD.DELETE;
},
fileName() {
return this.file?.name || '';
},
@@ -100,21 +122,89 @@ export default {
canEnableLogging() {
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
},
uploadProgress() {
const image = this.$store.getters['harvester/byId'](HCI.IMAGE, this.imageValue.id);
return image?.status?.progress;
},
enableUpgrade() {
if (this.deleteExistImage) {
return false;
}
if (this.sourceType === DOWNLOAD) {
return true;
}
if (this.sourceType === UPLOAD) {
return this.fileName === '' ? true : this.uploadProgress === 100;
}
return true;
},
isUploading() {
return this.fileName !== '' && this.uploadProgress !== 100;
},
showProgressBar() {
return this.createNewImage && this.sourceType === UPLOAD && this.isUploading;
},
showUploadSuccessBanner() {
return this.createNewImage && this.fileName !== '' && isEmpty(this.errors) && !this.showUploadingWarningBanner && this.uploadResult?._status === 200;
},
showUploadingWarningBanner() {
return this.createNewImage && this.isUploading;
},
showUpgradeOptions() {
return this.createNewImage || this.selectExistImage;
},
disableUploadButton() {
return this.sourceType === UPLOAD && this.isUploading;
},
},
methods: {
done() {
if (this.uploadController) {
this.uploadController.abort();
}
this.$router.push({
name: this.doneRoute,
params: { resource: HCI.SETTING, product: 'harvester' }
});
},
async initImageValue() {
this.imageValue = await this.$store.dispatch('harvester/create', {
type: HCI.IMAGE,
metadata: {
name: '',
namespace: 'harvester-system',
generateName: 'image-',
annotations: {}
},
spec: {
sourceType: UPLOAD,
displayName: '',
checksum: this.imageValue?.spec?.checksum || '',
},
});
},
async save(buttonCb) {
let res = null;
this.file = {};
this.errors = [];
if (!this.imageValue.spec.displayName && this.uploadImage) {
if (!this.imageValue.spec.displayName && this.createNewImage) {
this.errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('generic.name') }));
buttonCb(false);
@@ -125,26 +215,11 @@ export default {
if (this.imageSource === IMAGE_METHOD.NEW) {
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
if (this.sourceType === UPLOAD) {
this.imageValue.spec.sourceType = UPLOAD;
const file = this.file;
if (!file.name) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.selectExitImage'));
buttonCb(false);
return;
}
this.imageValue.spec.url = '';
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = file.name;
res = await this.imageValue.save();
res.uploadImage(file);
if (this.sourceType === UPLOAD && this.uploadImageId !== '') {
this.value.spec.image = this.uploadImageId;
} else if (this.sourceType === DOWNLOAD) {
this.imageValue.spec.sourceType = DOWNLOAD;
if (!this.imageValue.spec.url) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl'));
buttonCb(false);
@@ -153,12 +228,12 @@ export default {
}
res = await this.imageValue.save();
this.value.spec.image = res.id;
}
this.value.spec.image = res.id;
} else if (this.imageSource === IMAGE_METHOD.EXIST) {
if (!this.imageId) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.chooseFile'));
buttonCb(false);
return;
}
@@ -169,18 +244,87 @@ export default {
if (this.canEnableLogging) {
this.value.spec.logEnabled = this.enableLogging;
}
if (this.skipSingleReplicaDetachedVolFeatureEnabled) {
this.value.metadata.annotations = { [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
}
await this.value.save();
this.done();
buttonCb(true);
} catch (e) {
this.errors = exceptionToErrorsArray(e);
this.errors = [e?.message] || exceptionToErrorsArray(e);
buttonCb(false);
}
},
handleFileUpload() {
this.file = this.$refs.file.files[0];
async uploadFile(file) {
const fileName = file.name;
if (!fileName) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.unknownImageName'));
return;
}
const isDuplicatedFile = this.allOSImages.some((I) => I.spec.displayName === fileName);
if (isDuplicatedFile) {
this.errors.push(this.$store.getters['i18n/t']('harvester.upgradePage.upload.duplicatedFile'));
this.file = {};
return;
}
this.errors = [];
this.imageValue.spec.sourceType = UPLOAD;
this.imageValue.spec.displayName = fileName;
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.IMAGE_NAME] = fileName;
this.imageValue.spec.url = '';
try {
const res = await this.imageValue.save();
this.uploadImageId = res.id;
this.uploadController = new AbortController();
const signal = this.uploadController.signal;
this.uploadResult = await res.uploadImage(file, { signal });
} catch (e) {
if (e?.code === 'ERR_NETWORK') {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.networkError'));
} else if (e?.code === 'ERR_CANCELED') {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.cancelUpload'));
} else {
this.errors = exceptionToErrorsArray(e);
}
this.file = {};
this.uploadImageId = '';
}
},
handleImageDelete(imageId) {
const image = this.allOSImages.find((I) => I.id === imageId);
if (image) {
this.$store.dispatch('harvester/promptModal', {
resources: [image],
component: 'ConfirmRelatedToRemoveDialog',
needConfirmation: false,
warningMessage: this.$store.getters['i18n/t']('harvester.modal.osImage.message', { name: image.displayName })
});
this.deleteImageId = '';
}
},
async handleFileUpload() {
this.uploadImageId = '';
this.errors = [];
this.file = this.$refs.file?.files[0];
if (this.file) {
await this.initImageValue();
await this.uploadFile(this.file);
}
},
selectFile() {
@@ -190,21 +334,27 @@ export default {
},
watch: {
imageSource(neu) {
if (neu !== IMAGE_METHOD.DELETE) {
this.deleteImageId = '';
}
},
'imageValue.spec.url': {
handler(neu) {
const suffixName = neu?.split('/')?.pop();
const splitName = suffixName?.split('.') || [];
const fileSuffix = splitName?.pop()?.toLowerCase();
if (splitName.length > 1 && fileSuffix === 'iso' && !this.imageValue.spec.displayName) {
if (splitName.length > 1 && fileSuffix === 'iso' && suffixName !== this.imageValue.spec.displayName) {
this.imageValue.spec.displayName = suffixName;
}
},
deep: true
},
file(neu) {
if (!this.imageValue.spec.displayName && neu.name) {
// update name input if select new image
if (neu.name && neu.name !== this.imageValue.spec.displayName) {
this.imageValue.spec.displayName = neu.name;
}
}
@@ -227,9 +377,11 @@ export default {
:errors="errors"
:can-yaml="false"
finish-button-mode="upgrade"
:validation-passed="enableUpgrade"
:cancel-event="true"
@finish="save"
@cancel="done"
@error="e=>errors = e"
>
<RadioGroup
v-model:value="imageSource"
@@ -238,18 +390,57 @@ export default {
:options="[
IMAGE_METHOD.NEW,
IMAGE_METHOD.EXIST,
IMAGE_METHOD.DELETE,
]"
:labels="[
t('harvester.upgradePage.uploadNew'),
t('harvester.upgradePage.selectExisting'),
t('harvester.upgradePage.deleteExisting'),
]"
/>
<UpgradeInfo />
<UpgradeInfo v-if="createNewImage || selectExistImage" />
<div v-if="uploadImage">
<Banner
v-if="showUploadSuccessBanner"
color="success"
class="mt-0 mb-30"
:label="t('harvester.setting.upgrade.uploadSuccess', { name: file.name })"
/>
<Banner
v-if="showUploadingWarningBanner"
color="warning"
class="mt-0 mb-30"
:label="t('harvester.image.warning.osUpgrade.uploading', { name: file.name })"
/>
<div
v-if="showUpgradeOptions"
class="mt-10 mb-10"
>
<Checkbox
v-if="canEnableLogging"
v-model:value="enableLogging"
class="check mb-20"
type="checkbox"
:label="t('harvester.upgradePage.enableLogging')"
/>
<div
v-if="skipSingleReplicaDetachedVolFeatureEnabled"
class="mb-20"
>
<Checkbox
v-model:value="skipSingleReplicaDetachedVol"
class="check"
type="checkbox"
:label="t('harvester.upgradePage.skipSingleReplicaDetachedVol')"
/>
</div>
</div>
<div v-if="createNewImage">
<LabeledInput
v-model.trim="imageValue.spec.displayName"
v-model:value.trim="imageValue.spec.displayName"
class="mb-20"
label-key="harvester.fields.name"
required
@@ -261,14 +452,6 @@ export default {
label-key="harvester.setting.upgrade.checksum"
/>
<Checkbox
v-if="canEnableLogging"
v-model:value="enableLogging"
class="check mb-20"
type="checkbox"
:label="t('harvester.upgradePage.enableLogging')"
/>
<RadioGroup
v-model:value="sourceType"
class="mb-20 image-group"
@@ -285,7 +468,7 @@ export default {
<LabeledInput
v-if="sourceType === 'download'"
v-model.trim="imageValue.spec.url"
v-model:value.trim="imageValue.spec.url"
class="labeled-input--tooltip"
required
label-key="harvester.image.url"
@@ -298,6 +481,7 @@ export default {
<button
type="button"
class="btn role-primary"
:disabled="disableUploadButton"
@click="selectFile"
>
{{ t('harvester.image.uploadFile') }}
@@ -318,28 +502,56 @@ export default {
{{ fileName ? fileName : t('harvester.generic.noFileChosen') }}
</span>
</div>
<ImagePercentageBar
v-if="showProgressBar"
class="mt-20"
:value="uploadProgress"
/>
</div>
<LabeledSelect
v-else
v-if="selectExistImage"
v-model:value="imageId"
:options="osImageOptions"
required
class="mb-20"
label-key="harvester.fields.image"
/>
<div
v-if="deleteExistImage"
class="mt-20"
>
<Banner
color="info"
class="mt-10 mb-30"
:label="t('harvester.upgradePage.deleteHeader')"
/>
<LabeledSelect
v-model:value="deleteImageId"
:options="deleteOSImageOptions"
required
class="mb-20"
label-key="harvester.fields.image"
@update:value="handleImageDelete"
/>
</div>
</CruResource>
</div>
</template>
<style lang="scss" scoped>
#air-gap {
padding: 20px;
:deep() .image-group .radio-group {
display: flex;
.radio-container {
margin-right: 30px;
}
}
.parent {
grid-template-columns:auto 40px;
}
.chooseFile {
display: flex;
align-items: center;
+27 -13
View File
@@ -8,7 +8,6 @@ export const docLink = (suffix, version) => {
};
export function getVersion(v) {
// e.g v1.4.0
if (process.env.VUE_APP_SERVER_VERSION) {
return process.env.VUE_APP_SERVER_VERSION;
}
@@ -18,30 +17,45 @@ export function getVersion(v) {
return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
} catch (error) {
// fallback to the latest version
return latestVersion(Object.keys(RELEASE_FEATURES));
return getLatestVersion();
}
}
function latestVersion(versions) {
return versions.sort((a, b) => semver.compare(a, b)).pop();
function getLatestVersion() {
const allVersions = Object.keys(RELEASE_FEATURES).filter(semver.valid).sort(semver.rcompare);
return allVersions[0] || '';
}
// v1.3.3 => latest v1.3.x, v1.4.2 => latest v1.4.x
function latestMinorVersion(v) {
const minor = `v${ semver.major(v) }.${ semver.minor(v) }`;
const minorVersions = Object.keys(RELEASE_FEATURES).filter((version) => version.startsWith(minor));
function getLatestCompatibleVersion(version) {
const allVersions = Object.keys(RELEASE_FEATURES).filter(semver.valid);
const compatible = allVersions.filter((v) => semver.lte(v, version)).sort(semver.rcompare);
return latestVersion(minorVersions);
return compatible[0] || getLatestVersion();
}
export const featureEnabled = (featureKey, serverVersion) => {
const minSupportedVersion = '1.3.0';
const version = getVersion(serverVersion);
let releasedFeatures = RELEASE_FEATURES[version];
if (!releasedFeatures) {
const fallback = latestMinorVersion(version);
if (semver.lt(version.replace('v', ''), minSupportedVersion)) {
// eslint-disable-next-line no-console
console.error(`Harvester UI extension only supports Harvester cluster version >= ${ minSupportedVersion }. Current version: ${ version }`);
releasedFeatures = RELEASE_FEATURES[fallback];
return false;
}
const compatibleVersion = getLatestCompatibleVersion(version);
const releasedFeatures = RELEASE_FEATURES?.[compatibleVersion];
if (!Array.isArray(releasedFeatures)) {
// eslint-disable-next-line no-console
console.error(
`Feature flags for version ${ version } are not defined. Please upgrade Harvester UI extension and check the support matrix.`
);
return false;
}
return releasedFeatures.includes(featureKey);
+23
View File
@@ -0,0 +1,23 @@
#!/bin/bash
TAG_VERSION=$1
if [[ -z "$TAG_VERSION" ]]; then
echo "No tag version provided"
exit 1
fi
VERSION=$(jq -r .version package.json)
EXTENSION_VERSION=$(jq -r .version pkg/harvester/package.json)
PKG_VERSION="v${EXTENSION_VERSION}"
if [[ "$VERSION" != "$EXTENSION_VERSION" ]]; then
echo "Package version mismatch: $VERSION vs $EXTENSION_VERSION"
exit 1
fi
if [[ "${TAG_VERSION}" != "$PKG_VERSION" ]]; then
echo "Package version $PKG_VERSION in pkg/harvester/package.json does not match tag version ${TAG_VERSION}"
exit 1
fi
echo "Package version and tag version match: ${TAG_VERSION}"
+236 -1166
View File
File diff suppressed because it is too large Load Diff