Compare commits

...
Author SHA1 Message Date
mergify[bot]andTim Liou bbc415a2b4 chore: bump version to 1.8.0-rc4 (#778) (#779)
(cherry picked from commit 9d698b1230)

Signed-off-by: Tim Liou <tim.liou@suse.com>
Co-authored-by: Tim Liou <tim.liou@suse.com>
2026-03-27 16:42:53 +08:00
c12d4b5fba feat: support advance option and creation from DataVolume (#776) (#777)
* feat: support advance option and creation from DataVolume

    - advance option would let user change the accessMode/volumeMode
    - creation from DataVolume could supports various scenario with
      3rd-party storage



* feat: add data migration action on volume page



* refactor: use show advanced options link instead of checkbox



* feat: add feature flag



* feat: add feature flag for dataMigration action



---------




(cherry picked from commit 566e79eda5)

Signed-off-by: Vicente Cheng <freeze.bilsted@gmail.com>
Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: freeze <1615081+Vicente-Cheng@users.noreply.github.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-26 17:33:01 +08:00
mergify[bot]andAndy Lee dc91be11f9 chore: bump version to 1.8.0-rc3 (#774) (#775)
(cherry picked from commit 42ddcfc1fe)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-26 12:35:12 +08:00
mergify[bot]andAndy Lee e5b72499b5 fix: use default value got undefined value in payload (#772) (#773)
(cherry picked from commit ad3decf71f)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-25 17:12:51 +08:00
mergify[bot]andAndy Lee e66037b007 ci: remove the single quota for use commit hash (#767) (#771)
(cherry picked from commit 8083a41df0)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-25 14:13:08 +08:00
mergify[bot]andfreeze c676cad057 chore: pin GH Actions to commit sha (#765) (#766)
(cherry picked from commit 62801b3b13)

Co-authored-by: freeze <1615081+Vicente-Cheng@users.noreply.github.com>
2026-03-25 14:08:49 +08:00
mergify[bot]andAndy Lee 254abd4648 ci: add CODEOWNERS file (#768) (#769)
(cherry picked from commit 46b860260a)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-25 13:42:42 +08:00
renovate[bot] 1df1d3534a deps: update dependency yaml to v2.8.3 (#757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-22 13:06:18 +00:00
mergify[bot]andAndy Lee ff1e6e6e7f docs: update install instruction doc link (#753) (#754)
(cherry picked from commit 97e93dba0b)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-20 16:53:54 +08:00
mergify[bot]andAndy Lee 622242e4a5 refactor: change rwxNetwork setting to kind json (#751) (#752)
(cherry picked from commit 9a8a709e56)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-20 14:48:49 +08:00
mergify[bot]andAndy Lee d8ea9be174 feat: introduce rwxNetwork setting (#746) (#750)
* feat: add rwxNetwork setting



* fix: network payload



---------


(cherry picked from commit d1949641a7)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-19 16:19:44 +08:00
mergify[bot]andfreeze f8a479cfcf feat: add storage migration operation (#724) (#749)
- add storage migration
    - add cancel storage migration


(cherry picked from commit 9c9f59c939)

Signed-off-by: Vicente Cheng <freeze.bilsted@gmail.com>
Co-authored-by: freeze <1615081+Vicente-Cheng@users.noreply.github.com>
2026-03-19 11:53:24 +08:00
mergify[bot]andAndy Lee 6d5e584113 chore: bump to v1.8.0-rc2 (#747) (#748)
(cherry picked from commit ccc14c7fb9)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-19 11:31:07 +08:00
mergify[bot]andAndy Lee a9eb04195e feat: introduce instance-manager-resources setting (#744) (#745)
(cherry picked from commit 2ba471907e)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-18 17:21:28 +08:00
mergify[bot]andAndy Lee 0441c1f6e3 refactor: remove using resourceName to determine is vGPU device (#741) (#743)
(cherry picked from commit 5aea476f64)

Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-18 17:16:28 +08:00
mergify[bot]andAndy Lee dde58269d5 fix: typo (#740) (#742)
(cherry picked from commit 519c7d9f1f)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-17 16:33:37 +08:00
mergify[bot]andTim Liou b8111f0ad7 fix: reword the error message to focus on bootable volume (#736) (#738)
(cherry picked from commit a9c392c13f)

Signed-off-by: Tim Liou <tim.liou@suse.com>
Co-authored-by: Tim Liou <tim.liou@suse.com>
2026-03-12 17:46:08 +08:00
mergify[bot]andTim Liou 6d627f82e9 fix: container disks don't need volumeClaimTemplates but volumes (#735) (#737)
(cherry picked from commit 888ec7a50f)

Signed-off-by: Tim Liou <tim.liou@suse.com>
Co-authored-by: Tim Liou <tim.liou@suse.com>
2026-03-12 17:45:40 +08:00
mergify[bot]andAndy Lee cfc7a76fe7 feat: add vGPU filter button and hide the enable/disable passthrough in PCIDevice page (#729) (#732)
* feat: add another filter button



* feat: add feature falg to hide vGPU enable/disable actions in PCIDevices page



* refactor: update with conditionally rendering



---------


(cherry picked from commit 23344e0c07)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-11 15:39:41 +08:00
mergify[bot]andPo Han Huang 71d3067354 feat: ensure the state is pending when perform cloning the efi (#730) (#734)
* feat: ensure the state is pending when perform cloning the efi



* feat: define harvesterhci.io/clone-backend-storage-status in labels-annotations.js



---------


(cherry picked from commit a2486a7d38)

Signed-off-by: pohanhuang <pohan.huang@suse.com>
Co-authored-by: Po Han Huang <hhcs9527@gmail.com>
2026-03-11 15:39:01 +08:00
mergify[bot]andAndy Lee 9ecc372009 chore: bump to v1.8.0-rc1 (#731) (#733)
(cherry picked from commit df3d249923)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2026-03-11 15:34:55 +08:00
Andy Lee 62b80b3cec feat: add Insecure Skip TLS Verify checkbox in cluster-registration-url setting (#716)
* feat: add Insecure Skip TLS Verify checkbox

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

* refactor: set insecureSkipTLSVerify default to false

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

* fix: conflict

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

* refactor: remove unneeded change

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

* fix: get the feature flag in data()

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

* refactor: make data logic simpler

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

* refactor: put tip in info banner

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-03-09 10:51:20 +08:00
renovate[bot] 1cf94ee550 deps: update dependency @types/node to v20.19.37 (#726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 14:15:49 +08:00
renovate[bot] dfaa9fbe33 deps: update dependency elkjs to ^0.11.0 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 14:15:01 +08:00
Andy Lee 94b1c24479 chore: bump @rancher/shell to v3.0.9-rc.6 (#712)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-03-08 14:13:46 +08:00
Jack Yu e0b2b9ec57 feat: add support-bundle-file-name setting (#725)
* feat: support customizing support bundle file name

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

* feat: add supportBundleFileNameSetting feature flag

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

---------

Signed-off-by: Jack Yu <jack.yu@suse.com>
2026-03-06 15:51:20 +08:00
Andy Lee 06b38a0f99 fix: import sources page header (#723)
* fix: the import sources page header

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

* refactor: update VMware term

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-03-05 16:36:15 +08:00
Yiya Chen 4be3634c82 feat: add VPC network topology in detail page (#721)
* feat: add topology

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

* feat: add provider info

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

* refactor: remove comments

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

* fix: exclude default network

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

* feat: add VPC peering

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

* refactor: remove regex

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

* refactor: adjust row height

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

* feat: introduce auto layout

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

---------

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
2026-03-04 11:47:00 +08:00
Andy Lee de103ff91e fix: missing Download Logs button in Rancher integration mode (#715)
* fix: missing Download Logs button in Rancher integration mode

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

* refactor: add comment

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-03-02 16:47:20 +08:00
Andy Lee 37a91601c9 feat: add vmMigrationTimeout in vm-force-reset-policy setting (#718)
Signed-off-by: Andy Lee <andy.lee@suse.com>
2026-03-02 11:10:52 +08:00
renovate[bot] 9794671ccd deps: update patch digest dependencies (#719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-02 11:10:22 +08:00
48 changed files with 3113 additions and 144 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ description: Setup node and install dependencies
runs:
using: 'composite'
steps:
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
+2 -2
View File
@@ -12,6 +12,6 @@ jobs:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: rancher/gh-issue-mgr/auto-assign-action@main
- uses: rancher/gh-issue-mgr/auto-assign-action@b70f0bdf12a03e5e3f33e4f92ccb6c89deb3ebd9 # main
with:
configuration-path: .github/auto-assign-config.yaml
configuration-path: .github/auto-assign-config.yaml
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.base_ref }}
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.base_ref }}
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Check package version
env:
@@ -25,12 +25,12 @@ jobs:
name: Build & Upload Hosted
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
@@ -45,19 +45,20 @@ jobs:
run: ./scripts/build-upload-gate
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload build
uses: 'google-github-actions/upload-cloud-storage@v2'
uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
# https://github.com/google-github-actions/upload-cloud-storage
with:
path: ${{steps.build-hosted.outputs.BUILD_HOSTED_DIR}}
@@ -71,12 +72,12 @@ jobs:
name: Build & Upload Embedded
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
@@ -89,19 +90,19 @@ jobs:
DISABLED_EMBED_PKG: https://releases.rancher.com/harvester-ui/plugin/harvester-1.0.3.tar.gz
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload tar
uses: 'google-github-actions/upload-cloud-storage@v2'
uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
with:
path: ${{steps.build-embedded.outputs.BUILD_EMBEDED_TGZ}}
destination: releases.rancher.com/harvester-ui/dashboard
@@ -114,12 +115,12 @@ jobs:
name: Build & Upload Plugin
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
with:
fetch-depth: 1
# Note - Cannot use the setup action here as it uses a different yarn install arg
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
@@ -133,19 +134,19 @@ jobs:
run: ./scripts/build-upload-gate
- name: Get gcs auth
uses: rancher-eio/read-vault-secrets@main
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/google-auth/harvester/credentials token | GOOGLE_AUTH ;
- name: Apply gcs auth
# https://github.com/google-github-actions/auth
uses: 'google-github-actions/auth@v2'
uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2
with:
credentials_json: "${{ env.GOOGLE_AUTH }}"
- name: Upload plugin tar
uses: 'google-github-actions/upload-cloud-storage@v2'
uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
with:
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_TARBALL}}
destination: releases.rancher.com/harvester-ui/plugin
@@ -155,7 +156,7 @@ jobs:
process_gcloudignore: false
- name: Upload plugin directory
uses: 'google-github-actions/upload-cloud-storage@v2'
uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2
with:
path: dist-pkg/${{steps.ci-build-pkg.outputs.PKG_NAME}}
destination: releases.rancher.com/harvester-ui/plugin/${{steps.ci-build-pkg.outputs.PKG_NAME}}
@@ -27,14 +27,14 @@ jobs:
build-status: ${{ job.status }}
steps:
- name: Read Secrets
uses: rancher-eio/read-vault-secrets@main
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
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
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Enable Corepack
run: corepack enable
@@ -45,18 +45,18 @@ jobs:
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Login to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Setup Helm
uses: azure/setup-helm@v3
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
with:
version: v3.8.0
- name: Setup Nodejs with yarn caching
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '20'
cache: yarn
@@ -67,4 +67,4 @@ jobs:
- name: Build and push UI image
run: |
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
$publish
$publish
@@ -13,7 +13,7 @@ jobs:
target_branch: ${{ steps.get-version.outputs.target_branch }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Determine target branch
id: get-version
@@ -44,7 +44,7 @@ jobs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Extract version from package.json
id: get_version
@@ -62,7 +62,7 @@ jobs:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup environment
run: |
@@ -70,7 +70,7 @@ jobs:
yarn install --frozen-lockfile
- name: Setup Helm
uses: azure/setup-helm@v3
uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # v3
with:
version: v3.8.0
@@ -79,7 +79,7 @@ jobs:
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
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: charts
path: tmp
@@ -94,7 +94,7 @@ jobs:
contents: write
steps:
- name: Checkout release branch
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: '${{ github.ref_name }}'
@@ -105,7 +105,7 @@ jobs:
echo "LAST_COMMIT=${LAST_COMMIT}" >> $GITHUB_ENV
- name: Checkout target branch
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: '${{ needs.setup-target-branch.outputs.target_branch }}'
@@ -121,7 +121,7 @@ jobs:
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Download build artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: charts
@@ -132,7 +132,7 @@ jobs:
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
- name: Run Helm chart releaser
uses: helm/chart-releaser-action@v1.7.0
uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0
with:
charts_dir: ./charts
env:
@@ -17,7 +17,7 @@ jobs:
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Determine release tag
id: determine_tag
@@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Check package version
env:
TAG_VERSION: ${{ github.event.release.tag_name }}
@@ -43,7 +43,7 @@ jobs:
needs:
- setup-release-tag
- check-version
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@9eb70a732e9be146722e1dbab431338366c2afc6 # creators-pkg-v3.0.10
permissions:
actions: write
contents: write
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Check package version
env:
TAG_VERSION: ${{github.ref_name}}
+2 -2
View File
@@ -20,13 +20,13 @@ jobs:
# The FOSSA token is shared between all repos in Harvester's GH org. It can
# be used directly and there is no need to request specific access to EIO.
- name: Read FOSSA token
uses: rancher-eio/read-vault-secrets@main
uses: rancher-eio/read-vault-secrets@0da85151ad1f19ed7986c41587e45aac1ace74b6 # v3
with:
secrets: |
secret/data/github/org/harvester/fossa/credentials token | FOSSA_API_KEY_PUSH_ONLY
- name: FOSSA scan
uses: fossas/fossa-action@main
uses: fossas/fossa-action@c414b9ad82eaad041e47a7cf62a4f02411f427a0 # v1.8.0
with:
api-key: ${{ env.FOSSA_API_KEY_PUSH_ONLY }}
# Only runs the scan and do not provide/returns any results back to the
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ github.base_ref }}
- name: Setup Nodejs and yarn install
+2 -2
View File
@@ -1,7 +1,7 @@
name: Tests
on:
workflow_call: # This tells GH that the workflow is reusable
workflow_call: # This tells GH that the workflow is reusable
push:
branches:
- main
@@ -16,7 +16,7 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # Need full history for commit-lint
+2
View File
@@ -0,0 +1,2 @@
@a110605
@houhoucoop
+1 -1
View File
@@ -7,7 +7,7 @@ The Harvester UI Extension is a Rancher extension that provides the user interfa
## Installation
For detailed installation instructions, please refer to the [official Harvester documentation](https://docs.harvesterhci.io/v1.5/rancher/harvester-ui-extension#installation-on-rancher-210).
For Harvester UI extension installation instructions, please refer to the page **Rancher Integration** -> **Harvester UI Extension** in [official Harvester documentation](https://docs.harvesterhci.io).
## Development Setup
+8 -3
View File
@@ -1,17 +1,22 @@
{
"name": "harvester-ui-extension",
"version": "1.8.0-dev",
"version": "1.8.0-rc4",
"private": false,
"engines": {
"node": ">=20.0.0"
},
"dependencies": {
"@babel/plugin-transform-class-static-block": "7.28.6",
"@rancher/shell": "3.0.9-rc.1",
"@rancher/shell": "3.0.9-rc.6",
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.1",
"@vue-flow/core": "^1.33.5",
"@vue-flow/minimap": "^1.4.0",
"cache-loader": "^4.1.0",
"color": "4.2.3",
"ip": "2.0.1",
"node-polyfill-webpack-plugin": "^3.0.0",
"elkjs": "^0.11.0",
"vue-draggable-next": "^2.2.1",
"yaml": "^2.5.1"
},
@@ -24,7 +29,7 @@
"glob": "7.2.3",
"glob-parent": "6.0.2",
"json5": "2.2.3",
"@types/lodash": "4.17.23",
"@types/lodash": "4.17.24",
"merge": "2.1.1",
"node-forge": "1.3.3",
"nth-check": "2.1.1",
+1 -2
View File
@@ -7,8 +7,7 @@ The Harvester UI Extension is a Rancher extension that provides the user interfa
## Installation
For detailed installation instructions, please refer to the [official Harvester documentation](https://docs.harvesterhci.io/v1.5/rancher/harvester-ui-extension#installation-on-rancher-210).
For Harvester UI extension installation instructions, please refer to the page **Rancher Integration** -> **Harvester UI Extension** in [official Harvester documentation](https://docs.harvesterhci.io).
## Development Setup
@@ -0,0 +1,123 @@
<script>
import MessageLink from '@shell/components/MessageLink';
import CreateEditView from '@shell/mixins/create-edit-view';
import { LabeledInput } from '@components/Form/LabeledInput';
import { HCI_SETTING } from '../../config/settings';
import { Checkbox } from '@components/Form/Checkbox';
import { Banner } from '@components/Banner';
export default {
name: 'HarvesterEditClusterRegistrationURL',
components: {
LabeledInput, MessageLink, Checkbox, Banner
},
mixins: [CreateEditView],
data() {
let parseDefaultValue = {};
try {
parseDefaultValue = JSON.parse(this.value.value);
} catch (error) {
parseDefaultValue.url = this.value.value;
parseDefaultValue.insecureSkipTLSVerify = true;
}
return {
parseDefaultValue,
errors: []
};
},
computed: {
toCA() {
return `${ HCI_SETTING.ADDITIONAL_CA }?mode=edit`;
},
clusterRegistrationTLSVerifyEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('clusterRegistrationTLSVerify');
},
registrationURL: {
get() {
return this.clusterRegistrationTLSVerifyEnabled ? this.parseDefaultValue.url : this.parseDefaultValue;
},
set(value) {
if (this.clusterRegistrationTLSVerifyEnabled) {
this.parseDefaultValue.url = value;
} else {
this.parseDefaultValue = value;
}
}
}
},
methods: {
getDefaultValue() {
if (this.clusterRegistrationTLSVerifyEnabled) {
return { url: '', insecureSkipTLSVerify: false };
} else {
return '';
}
},
updateUrl() {
this.update();
},
update() {
if (this.clusterRegistrationTLSVerifyEnabled) {
this.value['value'] = JSON.stringify(this.parseDefaultValue);
} else {
this.value['value'] = this.parseDefaultValue;
}
},
useDefault() {
this.parseDefaultValue = this.getDefaultValue();
},
updateInsecureSkipTLSVerify(newValue) {
const { url = '' } = this.parseDefaultValue;
this.parseDefaultValue = { url, insecureSkipTLSVerify: newValue };
this.update();
},
}
};
</script>
<template>
<div
class="row"
>
<div class="col span-12">
<Banner color="info">
<MessageLink
:to="toCA"
target="_blank"
prefix-label="harvester.setting.clusterRegistrationUrl.tip.prefix"
middle-label="harvester.setting.clusterRegistrationUrl.tip.middle"
suffix-label="harvester.setting.clusterRegistrationUrl.tip.suffix"
/>
</Banner>
<LabeledInput
v-model:value="registrationURL"
class="mb-20"
:mode="mode"
:label="t('harvester.setting.clusterRegistrationUrl.url')"
@update:value="updateUrl"
/>
<div v-if="clusterRegistrationTLSVerifyEnabled">
<Checkbox
v-model:value="parseDefaultValue.insecureSkipTLSVerify"
class="check mb-5"
type="checkbox"
:label="t('harvester.setting.clusterRegistrationUrl.insecureSkipTLSVerify')"
@update:value="updateInsecureSkipTLSVerify"
/>
</div>
</div>
</div>
</template>
@@ -0,0 +1,104 @@
<script>
import UnitInput from '@shell/components/form/UnitInput';
import { Banner } from '@components/Banner';
export default {
name: 'HarvesterInstanceManagerResources',
components: {
UnitInput,
Banner,
},
props: {
value: {
type: Object,
default: () => ({
value: '',
default: '{}'
})
},
},
data() {
const resources = this.parseJSON(this.value?.value) || this.parseJSON(this.value?.default) || {};
return {
resources,
parseError: null,
};
},
methods: {
parseJSON(string) {
try {
return JSON.parse(string);
} catch (e) {
this.parseError = this.t('harvester.setting.instanceManagerResources.parseError', { error: e.message });
return null;
}
},
update() {
if (!this.value) return;
const cpu = { ...this.resources?.cpu };
if (cpu.v1 !== null && cpu.v1 !== undefined) cpu.v1 = String(cpu.v1);
if (cpu.v2 !== null && cpu.v2 !== undefined) cpu.v2 = String(cpu.v2);
this.value.value = JSON.stringify({ ...this.resources, cpu });
},
useDefault() {
if (this.value?.default) {
this.resources = this.parseJSON(this.value.default) || {};
this.update();
}
},
},
};
</script>
<template>
<div>
<Banner
v-if="parseError"
color="error"
>
{{ parseError }}
</Banner>
<div class="row">
<div class="col span-12">
<UnitInput
v-model:value="resources.cpu.v1"
:label="t('harvester.setting.instanceManagerResources.v1')"
suffix="%"
:delay="0"
type="number"
min="0"
max="100"
required
:mode="mode"
class="mb-20"
@update:value="update"
/>
<UnitInput
v-model:value="resources.cpu.v2"
:label="t('harvester.setting.instanceManagerResources.v2')"
suffix="%"
:delay="0"
type="number"
min="0"
max="100"
required
:mode="mode"
class="mb-20"
@update:value="update"
/>
</div>
</div>
</div>
</template>
@@ -0,0 +1,371 @@
<script>
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
import { RadioGroup } from '@components/Form/Radio';
import ArrayList from '@shell/components/form/ArrayList';
import { isValidCIDR } from '@shell/utils/validators/cidr';
import { _EDIT } from '@shell/config/query-params';
import { Banner } from '@components/Banner';
import { allHash } from '@shell/utils/promise';
import { HCI } from '../../types';
import { NETWORK_TYPE } from '../../config/types';
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
const SHARE_STORAGE_NETWORK = 'share-storage-network';
const NETWORK = 'network';
const DEFAULT_DEDICATED_NETWORK = {
vlan: '',
clusterNetwork: '',
range: '',
exclude: [],
};
export default {
name: 'RwxNetworkSetting',
components: {
RadioGroup,
Banner,
ArrayList,
LabeledInput,
LabeledSelect,
},
props: {
registerBeforeHook: {
type: Function,
required: true,
},
mode: {
type: String,
default: _EDIT,
},
value: {
type: Object,
default: () => {
return {};
},
},
},
async fetch() {
const inStore = this.$store.getters['currentProduct'].inStore;
await allHash({
clusterNetworks: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER_NETWORK }),
vlanStatus: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.VLAN_STATUS }),
});
},
data() {
let enabled = false; // enabled / disabled options
let shareStorageNetwork = false; // shareStorageNetwork / dedicatedRwxNetwork options
let dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
let networkType = L2VLAN;
let exclude = [];
try {
const parsedValue = JSON.parse(this.value.value || this.value.default || '{}');
const parsedNetwork = parsedValue?.[NETWORK] || parsedValue || {};
if (parsedValue && typeof parsedValue === 'object') {
shareStorageNetwork = !!parsedValue[SHARE_STORAGE_NETWORK];
networkType = 'vlan' in parsedNetwork ? L2VLAN : UNTAGGED;
dedicatedNetwork = {
vlan: parsedNetwork.vlan || '',
clusterNetwork: parsedNetwork.clusterNetwork || '',
range: parsedNetwork.range || '',
};
exclude = parsedNetwork?.exclude?.toString().split(',') || [];
enabled = shareStorageNetwork || !!(parsedNetwork.vlan || parsedNetwork.clusterNetwork || parsedNetwork.range);
}
} catch (error) {
enabled = false;
shareStorageNetwork = false;
dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
}
return {
enabled,
shareStorageNetwork,
dedicatedNetwork,
networkType,
exclude,
defaultAddValue: '',
};
},
created() {
if (this.registerBeforeHook) {
this.registerBeforeHook(this.willSave, 'willSave');
}
},
computed: {
showDedicatedNetworkConfig() {
return this.enabled && !this.shareStorageNetwork;
},
showVlan() {
return this.networkType === L2VLAN;
},
networkTypes() {
return [L2VLAN, UNTAGGED];
},
clusterNetworkOptions() {
const inStore = this.$store.getters['currentProduct'].inStore;
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
const clusterNetworksOptions = this.networkType === UNTAGGED ? clusterNetworks.filter((network) => network.id !== 'mgmt') : clusterNetworks;
return clusterNetworksOptions.map((network) => {
const disabled = !network.isReadyForStorageNetwork;
return {
label: disabled ? `${ network.id } (${ this.t('generic.notReady') })` : network.id,
value: network.id,
disabled,
};
});
},
},
methods: {
onUpdateEnabled() {
if (!this.enabled) {
this.shareStorageNetwork = false;
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
}
this.update();
},
onUpdateNetworkType() {
if (this.shareStorageNetwork) {
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
}
this.update();
},
onUpdateDedicatedType(neu) {
this.dedicatedNetwork.clusterNetwork = '';
if (neu === L2VLAN) {
this.dedicatedNetwork.vlan = '';
} else {
delete this.dedicatedNetwork.vlan;
}
this.update();
},
inputVlan(neu) {
if (neu === '') {
this.dedicatedNetwork.vlan = '';
this.update();
return;
}
const newValue = Number(neu);
if (newValue > 4094) {
this.dedicatedNetwork.vlan = 4094;
} else if (newValue < 1) {
this.dedicatedNetwork.vlan = 1;
} else {
this.dedicatedNetwork.vlan = newValue;
}
this.update();
},
useDefault() {
this.enabled = false;
this.shareStorageNetwork = false;
this.dedicatedNetwork = { ...DEFAULT_DEDICATED_NETWORK };
this.update();
},
update() {
const value = { [SHARE_STORAGE_NETWORK]: false };
if (this.enabled && this.shareStorageNetwork) {
value[SHARE_STORAGE_NETWORK] = true;
}
if (this.showDedicatedNetworkConfig) {
value[NETWORK] = {};
if (this.showVlan) {
value[NETWORK].vlan = this.dedicatedNetwork.vlan;
}
value[NETWORK].clusterNetwork = this.dedicatedNetwork.clusterNetwork;
value[NETWORK].range = this.dedicatedNetwork.range;
const excludeList = this.exclude.filter((ip) => ip);
if (Array.isArray(excludeList) && excludeList.length > 0) {
value[NETWORK].exclude = excludeList;
}
}
this.value.value = JSON.stringify(value);
},
willSave() {
this.update();
if (!this.showDedicatedNetworkConfig) {
return Promise.resolve();
}
const errors = [];
if (this.showVlan && !this.dedicatedNetwork.vlan) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
}
if (!this.dedicatedNetwork.clusterNetwork) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.clusterNetwork') }, true));
}
if (!this.dedicatedNetwork.range) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.range.label') }, true));
} else if (!isValidCIDR(this.dedicatedNetwork.range)) {
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
}
if (this.exclude) {
const hasInvalidCIDR = this.exclude.find((cidr) => {
return cidr && !isValidCIDR(cidr);
});
if (hasInvalidCIDR) {
errors.push(this.t('harvester.setting.storageNetwork.exclude.invalid', null, true));
}
}
if (errors.length > 0) {
return Promise.reject(errors);
}
return Promise.resolve();
},
},
};
</script>
<template>
<div :class="mode">
<Banner color="warning">
<t
k="harvester.setting.rwxNetwork.warning"
:raw="true"
/>
</Banner>
<RadioGroup
v-model:value="enabled"
class="mb-20"
name="rwx-network-enable"
:options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]"
@update:value="onUpdateEnabled"
/>
<RadioGroup
v-if="enabled"
v-model:value="shareStorageNetwork"
class="mb-20"
name="rwx-network-type"
:options="[true,false]"
:labels="[t('harvester.setting.rwxNetwork.shareStorageNetwork'), t('harvester.setting.rwxNetwork.dedicatedRwxNetwork')]"
@update:value="onUpdateNetworkType"
/>
<Banner
v-if="shareStorageNetwork"
class="mb-20"
color="warning"
>
<t
k="harvester.setting.rwxNetwork.shareStorageNetworkWarning"
:raw="true"
/>
</Banner>
<template v-if="showDedicatedNetworkConfig">
<LabeledSelect
v-model:value="networkType"
class="mb-20"
:options="networkTypes"
:mode="mode"
:label="t('harvester.fields.type')"
required
@update:value="onUpdateDedicatedType"
/>
<LabeledInput
v-if="showVlan"
v-model:value.number="dedicatedNetwork.vlan"
type="number"
class="mb-20"
:mode="mode"
required
placeholder="e.g. 1 - 4094"
label-key="harvester.setting.storageNetwork.vlan"
@update:value="inputVlan"
/>
<LabeledSelect
v-model:value="dedicatedNetwork.clusterNetwork"
label-key="harvester.setting.storageNetwork.clusterNetwork"
class="mb-20"
required
:options="clusterNetworkOptions"
@update:value="update"
/>
<LabeledInput
v-model:value="dedicatedNetwork.range"
class="mb-5"
:mode="mode"
required
:placeholder="t('harvester.setting.storageNetwork.range.placeholder')"
label-key="harvester.setting.storageNetwork.range.label"
@update:value="update"
/>
<ArrayList
v-model:value="exclude"
:show-header="true"
:default-add-value="defaultAddValue"
:mode="mode"
:add-label="t('harvester.setting.storageNetwork.exclude.addIp')"
class="mt-20"
@update:value="update"
>
<template #column-headers>
<div class="box mb-10">
<div class="key">
{{ t('harvester.setting.storageNetwork.exclude.label') }}
</div>
</div>
</template>
<template #columns="scope">
<div class="key">
<input
v-model="scope.row.value"
:placeholder="t('harvester.setting.storageNetwork.exclude.placeholder')"
@update:value="update"
/>
</div>
</template>
</ArrayList>
</template>
</div>
</template>
@@ -69,6 +69,15 @@ export default {
:mode="mode"
label-key="harvester.setting.vmForceDeletionPolicy.period"
/>
<LabeledInput
v-if="parseDefaultValue.enable"
v-model:value.number="parseDefaultValue.vmMigrationTimeout"
class="mb-20"
type="number"
:mode="mode"
label-key="harvester.setting.vmForceDeletionPolicy.vmMigrationTimeout"
/>
</div>
</div>
</template>
+6
View File
@@ -60,6 +60,12 @@ const FEATURE_FLAGS = {
'v1.7.1': [],
'v1.8.0': [
'hotplugCdRom',
'supportBundleFileNameSetting',
'clusterRegistrationTLSVerify',
'vGPUAsPCIDevice',
'instanceManagerResourcesSetting',
'rwxNetworkSetting',
'createPVCWithDataVolume'
],
};
@@ -28,6 +28,7 @@ export const HCI = {
NODE_ROLE_CONTROL_PLANE: 'node-role.kubernetes.io/control-plane',
NODE_ROLE_ETCD: 'node-role.harvesterhci.io/witness',
PROMOTE_STATUS: 'harvesterhci.io/promote-status',
CLONE_BACKEND_STORAGE_STATUS: 'harvesterhci.io/clone-backend-storage-status',
MIGRATION_STATE: 'harvesterhci.io/migrationState',
VOLUME_CLAIM_TEMPLATE: 'harvesterhci.io/volumeClaimTemplates',
IMAGE_NAME: 'harvesterhci.io/image-name',
@@ -79,4 +80,5 @@ export const HCI = {
VOLUME_SNAPSHOT_CLASS: 'cdi.harvesterhci.io/storageProfileVolumeSnapshotClass',
MAC_ADDRESS: 'harvesterhci.io/mac-address',
NODE_UPGRADE_PAUSE_MAP: 'harvesterhci.io/node-upgrade-pause-map',
CDI_POPULATOR_KIND: 'cdi.kubevirt.io/storage.populator.kind',
};
+15 -2
View File
@@ -16,9 +16,11 @@ export const HCI_SETTING = {
DEFAULT_STORAGE_CLASS: 'default-storage-class',
SUPPORT_BUNDLE_TIMEOUT: 'support-bundle-timeout',
SUPPORT_BUNDLE_EXPIRATION: 'support-bundle-expiration',
SUPPORT_BUNDLE_FILE_NAME: 'support-bundle-file-name',
SUPPORT_BUNDLE_IMAGE: 'support-bundle-image',
SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT: 'support-bundle-node-collection-timeout',
STORAGE_NETWORK: 'storage-network',
RWX_NETWORK: 'rwx-network',
VM_FORCE_RESET_POLICY: 'vm-force-reset-policy',
SSL_CERTIFICATES: 'ssl-certificates',
SSL_PARAMETERS: 'ssl-parameters',
@@ -38,7 +40,8 @@ export const HCI_SETTING = {
VM_MIGRATION_NETWORK: 'vm-migration-network',
RANCHER_CLUSTER: 'rancher-cluster',
MAX_HOTPLUG_RATIO: 'max-hotplug-ratio',
KUBEVIRT_MIGRATION: 'kubevirt-migration'
KUBEVIRT_MIGRATION: 'kubevirt-migration',
INSTANCE_MANAGER_RESOURCES: 'instance-manager-resources'
};
export const HCI_ALLOWED_SETTINGS = {
@@ -71,11 +74,17 @@ export const HCI_ALLOWED_SETTINGS = {
[HCI_SETTING.OVERCOMMIT_CONFIG]: { kind: 'json', from: 'import' },
[HCI_SETTING.SUPPORT_BUNDLE_TIMEOUT]: { kind: 'number' },
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: { kind: 'number' },
[HCI_SETTING.SUPPORT_BUNDLE_FILE_NAME]: {
kind: 'string', canReset: true, featureFlag: 'supportBundleFileNameSetting'
},
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { kind: 'number', featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
[HCI_SETTING.STORAGE_NETWORK]: {
kind: 'custom', from: 'import', canReset: true
},
[HCI_SETTING.RWX_NETWORK]: {
kind: 'json', from: 'import', canReset: true, featureFlag: 'rwxNetworkSetting'
},
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
[HCI_SETTING.SSL_PARAMETERS]: {
@@ -118,12 +127,16 @@ export const HCI_ALLOWED_SETTINGS = {
},
[HCI_SETTING.KUBEVIRT_MIGRATION]: {
kind: 'json', from: 'import', canReset: true, featureFlag: 'kubevirtMigration',
},
[HCI_SETTING.INSTANCE_MANAGER_RESOURCES]: {
kind: 'json', from: 'import', featureFlag: 'instanceManagerResourcesSetting'
}
};
export const HCI_SINGLE_CLUSTER_ALLOWED_SETTING = {
[HCI_SETTING.CLUSTER_REGISTRATION_URL]: {
kind: 'url',
kind: 'custom',
from: 'import',
canReset: true,
},
[HCI_SETTING.UI_PL]: {
+5
View File
@@ -41,3 +41,8 @@ export const VMIMPORT_SOURCE_KINDS = {
OPENSTACK: 'OpenstackSource',
OVA: 'OvaSource',
};
export const CDI_POPULATOR_KIND = {
VOLUME_IMPORT_SOURCE: 'VolumeImportSource',
VOLUME_CLONE_SOURCE: 'VolumeCloneSource',
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,183 @@
<script>
import { mapGetters } from 'vuex';
import { STORAGE_CLASS } from '@shell/config/types';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { sortBy } from '@shell/utils/sort';
import { isInternalStorageClass } from '../utils/storage-class';
import { Card } from '@components/Card';
import { Banner } from '@components/Banner';
import { LabeledInput } from '@components/Form/LabeledInput';
import AsyncButton from '@shell/components/AsyncButton';
import LabeledSelect from '@shell/components/form/LabeledSelect';
export default {
name: 'HarvesterDataMigrationDialog',
emits: ['close'],
components: {
AsyncButton, Banner, Card, LabeledInput, LabeledSelect
},
props: {
resources: {
type: Array,
required: true
}
},
async fetch() {
this.storageClasses = await this.$store.dispatch('harvester/findAll', { type: STORAGE_CLASS });
},
data() {
return {
targetVolumeName: '',
targetStorageClassName: '',
errors: [],
storageClasses: [],
};
},
computed: {
...mapGetters({ t: 'i18n/t' }),
actionResource() {
return this.resources[0];
},
storageClassOptions() {
return sortBy(
this.storageClasses
.filter((sc) => !isInternalStorageClass(sc.metadata?.name))
.map((sc) => ({
label: sc.metadata?.name,
value: sc.metadata?.name
})),
'label'
);
},
disableSave() {
return !this.targetVolumeName || !this.targetStorageClassName;
},
},
methods: {
close() {
this.targetVolumeName = '';
this.targetStorageClassName = '';
this.errors = [];
this.$emit('close');
},
async apply(buttonDone) {
if (!this.actionResource) {
buttonDone(false);
return;
}
if (!this.targetVolumeName) {
const name = this.t('harvester.modal.dataMigration.fields.targetVolumeName.label');
this['errors'] = [this.t('validation.required', { key: name })];
buttonDone(false);
return;
}
if (!this.targetStorageClassName) {
const name = this.t('harvester.modal.dataMigration.fields.targetStorageClassName.label');
this['errors'] = [this.t('validation.required', { key: name })];
buttonDone(false);
return;
}
try {
await this.actionResource.doAction('dataMigration', {
targetVolumeName: this.targetVolumeName,
targetStorageClassName: this.targetStorageClassName
}, {}, false);
buttonDone(true);
this.close();
} catch (err) {
const error = err?.data || err;
this['errors'] = exceptionToErrorsArray(error);
buttonDone(false);
}
},
}
};
</script>
<template>
<Card :show-highlight-border="false">
<template #title>
{{ t('harvester.modal.dataMigration.title') }}
</template>
<template #body>
<LabeledInput
v-model:value="targetVolumeName"
:label="t('harvester.modal.dataMigration.fields.targetVolumeName.label')"
:placeholder="t('harvester.modal.dataMigration.fields.targetVolumeName.placeholder')"
class="mb-20"
required
/>
<LabeledSelect
v-model:value="targetStorageClassName"
:label="t('harvester.modal.dataMigration.fields.targetStorageClassName.label')"
:placeholder="t('harvester.modal.dataMigration.fields.targetStorageClassName.placeholder')"
:options="storageClassOptions"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template
#actions
class="actions"
>
<div class="buttons">
<button
class="btn role-secondary mr-10"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="apply"
:disabled="disableSave"
@click="apply"
/>
</div>
</template>
</Card>
</template>
<style lang="scss" scoped>
.actions {
width: 100%;
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
</style>
@@ -0,0 +1,239 @@
<script>
import { mapGetters } from 'vuex';
import { PVC } from '@shell/config/types';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { sortBy } from '@shell/utils/sort';
import { HCI } from '../types';
import { parseVolumeClaimTemplates } from '@pkg/harvester/utils/vm';
import { Card } from '@components/Card';
import { Banner } from '@components/Banner';
import AsyncButton from '@shell/components/AsyncButton';
import LabeledSelect from '@shell/components/form/LabeledSelect';
export default {
name: 'HarvesterStorageMigrationDialog',
emits: ['close'],
components: {
AsyncButton, Banner, Card, LabeledSelect
},
props: {
resources: {
type: Array,
required: true
}
},
async fetch() {
this.allPVCs = await this.$store.dispatch('harvester/findAll', { type: PVC });
},
data() {
return {
sourceVolume: '',
targetVolume: '',
errors: [],
allPVCs: [],
};
},
computed: {
...mapGetters({ t: 'i18n/t' }),
actionResource() {
return this.resources[0];
},
sourceVolumeOptions() {
const volumes = this.actionResource.spec?.template?.spec?.volumes || [];
return sortBy(
volumes
.map((v) => v.persistentVolumeClaim?.claimName)
.filter((name) => !!name)
.map((name) => ({
label: name,
value: name
})),
'label'
);
},
namespacePVCs() {
return this.allPVCs.filter((pvc) => pvc.metadata.namespace === this.actionResource.metadata.namespace);
},
vmUsedVolumeNames() {
const allVMs = this.$store.getters['harvester/all'](HCI.VM) || [];
const names = new Set();
allVMs.forEach((vm) => {
// Collect volume names from spec.template.spec.volumes (both PVC and DataVolume references)
const volumes = vm.spec?.template?.spec?.volumes || [];
volumes.forEach((v) => {
const name = v.persistentVolumeClaim?.claimName || v.dataVolume?.name;
if (name) {
names.add(`${ vm.metadata.namespace }/${ name }`);
}
});
// Collect volume names from volumeClaimTemplates annotation
const templates = parseVolumeClaimTemplates(vm);
templates.forEach((t) => {
if (t.metadata?.name) {
names.add(`${ vm.metadata.namespace }/${ t.metadata.name }`);
}
});
});
return names;
},
targetVolumeOptions() {
return sortBy(
this.namespacePVCs
.filter((pvc) => {
// Exclude volumes used by any VM (via spec.volumes or volumeClaimTemplates)
if (this.vmUsedVolumeNames.has(`${ pvc.metadata.namespace }/${ pvc.metadata.name }`)) {
return false;
}
return true;
})
.map((pvc) => ({
label: pvc.metadata.name,
value: pvc.metadata.name
})),
'label'
);
},
disableSave() {
return !this.sourceVolume || !this.targetVolume;
},
},
methods: {
close() {
this.sourceVolume = '';
this.targetVolume = '';
this.errors = [];
this.$emit('close');
},
async apply(buttonDone) {
if (!this.actionResource) {
buttonDone(false);
return;
}
if (!this.sourceVolume) {
const name = this.t('harvester.modal.storageMigration.fields.sourceVolume.label');
this['errors'] = [this.t('validation.required', { key: name })];
buttonDone(false);
return;
}
if (!this.targetVolume) {
const name = this.t('harvester.modal.storageMigration.fields.targetVolume.label');
this['errors'] = [this.t('validation.required', { key: name })];
buttonDone(false);
return;
}
try {
await this.actionResource.doAction('storageMigration', {
sourceVolume: this.sourceVolume,
targetVolume: this.targetVolume
}, {}, false);
buttonDone(true);
this.close();
} catch (err) {
const error = err?.data || err;
this['errors'] = exceptionToErrorsArray(error);
buttonDone(false);
}
},
}
};
</script>
<template>
<Card :show-highlight-border="false">
<template #title>
{{ t('harvester.modal.storageMigration.title') }}
</template>
<template #body>
<LabeledSelect
v-model:value="sourceVolume"
:label="t('harvester.modal.storageMigration.fields.sourceVolume.label')"
:placeholder="t('harvester.modal.storageMigration.fields.sourceVolume.placeholder')"
:options="sourceVolumeOptions"
class="mb-20"
required
/>
<LabeledSelect
v-model:value="targetVolume"
:label="t('harvester.modal.storageMigration.fields.targetVolume.label')"
:placeholder="t('harvester.modal.storageMigration.fields.targetVolume.placeholder')"
:options="targetVolumeOptions"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template
#actions
class="actions"
>
<div class="buttons">
<button
class="btn role-secondary mr-10"
@click="close"
>
{{ t('generic.cancel') }}
</button>
<AsyncButton
mode="apply"
:disabled="disableSave"
@click="apply"
/>
</div>
</template>
</Card>
</template>
<style lang="scss" scoped>
.actions {
width: 100%;
}
.buttons {
display: flex;
justify-content: flex-end;
width: 100%;
}
</style>
+135 -17
View File
@@ -9,8 +9,11 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import NameNsDescription from '@shell/components/form/NameNsDescription';
import Conditions from '@shell/components/form/Conditions';
import { Banner } from '@components/Banner';
import { Checkbox } from '@components/Form/Checkbox';
import jsyaml from 'js-yaml';
import { exceptionToErrorsArray } from '@shell/utils/error';
import { allHash } from '@shell/utils/promise';
import { get } from '@shell/utils/object';
import { clone, get } from '@shell/utils/object';
import { STORAGE_CLASS, LONGHORN, PV } from '@shell/config/types';
import { sortBy } from '@shell/utils/sort';
import { saferDump } from '@shell/utils/create-yaml';
@@ -33,6 +36,7 @@ export default {
components: {
Banner,
Checkbox,
Tab,
UnitInput,
CruResource,
@@ -90,14 +94,34 @@ export default {
source,
storage,
imageId,
snapshots: [],
images: [],
showAdvanced: false,
createWithDataVolume: false,
snapshots: [],
images: [],
GIBIBYTE
};
},
created() {
this.registerBeforeHook(this.willSave, 'willSave');
if (this.mode === _CREATE) {
const originalSaveYaml = this.value.saveYaml?.bind(this.value);
this.value.saveYaml = async(yaml) => {
if (this.createWithDataVolume && this.isBlank) {
const parsed = jsyaml.load(yaml);
const dvObj = { ...parsed, type: 'cdi.kubevirt.io.datavolume' };
const dataVolume = await this.$store.dispatch('harvester/create', dvObj);
await dataVolume.save();
return dataVolume;
}
return originalSaveYaml(yaml);
};
}
},
computed: {
@@ -135,6 +159,10 @@ export default {
return Object.values(VOLUME_MODE);
},
accessModeOptions() {
return ['ReadWriteOnce', 'ReadWriteMany', 'ReadOnlyMany'];
},
imageOption() {
return sortBy(
this.images
@@ -275,6 +303,10 @@ export default {
return this.$store.getters['harvester-common/getFeatureEnabled']('lhV2VolExpansion');
},
isCreatePVCWithDataVolumeFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('createPVCWithDataVolume');
},
isResizeDisabled() {
return (
!this.isLHV2VolExpansionFeatureEnabled &&
@@ -341,6 +373,58 @@ export default {
return readWriteOnce ? ['ReadWriteOnce'] : ['ReadWriteMany'];
},
buildDataVolumeObj() {
const storage = {
storageClassName: this.value.spec.storageClassName,
resources: { requests: { storage: this.storage } },
};
if (this.showAdvanced && this.value.spec.accessModes?.length > 0) {
storage.accessModes = this.value.spec.accessModes;
}
if (this.showAdvanced && this.value.spec.volumeMode) {
storage.volumeMode = this.value.spec.volumeMode;
}
return {
type: 'cdi.kubevirt.io.datavolume',
apiVersion: 'cdi.kubevirt.io/v1beta1',
kind: 'DataVolume',
metadata: {
name: this.value.metadata.name,
namespace: this.value.metadata.namespace,
annotations: this.value.metadata.annotations || {},
labels: this.value.metadata.labels || {},
},
spec: {
source: { blank: {} },
storage,
}
};
},
async save(buttonDone) {
if (this.isCreate && this.isBlank && this.createWithDataVolume) {
try {
this.update();
const dvObj = this.buildDataVolumeObj();
const dataVolume = await this.$store.dispatch('harvester/create', dvObj);
await dataVolume.save();
buttonDone(true);
this.done();
} catch (err) {
const error = err?.data || err;
this['errors'] = exceptionToErrorsArray(error);
buttonDone(false);
}
} else {
await CreateEditView.methods.save.call(this, buttonDone);
}
},
willSave() {
this.update();
},
@@ -383,9 +467,17 @@ export default {
this.update();
},
generateYaml() {
const out = saferDump(this.value);
this.update();
return out;
if (this.isCreate && this.isBlank && this.createWithDataVolume) {
return saferDump(this.buildDataVolumeObj());
}
const plain = clone(this.value);
delete plain.saveYaml;
return saferDump(plain);
},
}
};
@@ -458,18 +550,6 @@ 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')"
@@ -490,6 +570,44 @@ export default {
>
<span>{{ t('harvester.volume.longhorn.disableResize') }}</span>
</Banner>
<div class="row mb-20">
<Checkbox
v-if="isCreate && isBlank && isCreatePVCWithDataVolumeFeatureEnabled"
v-model:value="createWithDataVolume"
:label="t('harvester.volume.createWithDataVolume')"
tooltip-key="harvester.volume.createWithDataVolumeTooltip"
/>
</div>
<a
v-if="isCreate && isCreatePVCWithDataVolumeFeatureEnabled"
role="button"
class="hand"
@click="showAdvanced = !showAdvanced"
>
{{ showAdvanced ? t('harvester.volume.hideAdvanced') : t('harvester.volume.showAdvanced') }}
</a>
<LabeledSelect
v-if="showAdvanced"
v-model:value="value.spec.accessModes"
:label="t('harvester.volume.accessModes')"
:options="accessModeOptions"
:multiple="true"
:mode="mode"
class="mb-20 mt-20"
@update:value="update"
/>
<LabeledSelect
v-if="showAdvanced"
v-model:value="value.spec.volumeMode"
:label="t('harvester.volume.volumeMode')"
:options="volumeModeOptions"
:mode="mode"
class="mb-20"
@update:value="update"
/>
</Tab>
<Tab
v-if="!isCreate"
@@ -31,6 +31,7 @@ export default {
const _hash = {
pciclaims: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.PCI_CLAIM }),
sriovs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.SR_IOV }),
srigpuovs: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.SR_IOVGPU_DEVICE }),
};
await allHash(_hash);
@@ -106,19 +107,32 @@ export default {
},
computed: {
parentSriovOptions() {
allSriovs() {
const inStore = this.$store.getters['currentProduct'].inStore;
const allSriovs = this.$store.getters[`${ inStore }/all`](HCI.SR_IOV) || [];
return allSriovs.map((sriov) => {
return sriov.id;
});
return this.$store.getters[`${ inStore }/all`](HCI.SR_IOV) || [];
},
allSriovGPUs() {
const inStore = this.$store.getters['currentProduct'].inStore;
return this.$store.getters[`${ inStore }/all`](HCI.SR_IOVGPU_DEVICE) || [];
},
parentSriovOptions() {
return this.allSriovs.map((sriov) => sriov.id);
},
parentSriovGPUOptions() {
return this.allSriovGPUs.map((sriovgpu) => sriovgpu.id);
},
parentSriovLabel() {
return HCI_ANNOTATIONS.PARENT_SRIOV;
}
},
parentSriovGPULabel() {
return HCI_ANNOTATIONS.PARENT_SRIOV_GPU;
},
vGPUAsPCIDeviceEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('vGPUAsPCIDevice');
},
},
methods: {
enableGroup(rows = []) {
const row = rows[0];
@@ -206,6 +220,15 @@ export default {
:rows="rows"
@change-rows="changeRows"
/>
<FilterBySriov
v-if="vGPUAsPCIDeviceEnabled"
ref="filterByParentSRIOVGPU"
:parent-sriov-options="parentSriovGPUOptions"
:parent-sriov-label="parentSriovGPULabel"
:label="t('harvester.sriov.parentSriovGPU')"
:rows="rows"
@change-rows="changeRows"
/>
</template>
</ResourceTable>
</template>
@@ -8,6 +8,7 @@ import { set } from '@shell/utils/object';
import { HCI } from '../../../types';
import DeviceList from './DeviceList';
import CompatibilityMatrix from '../CompatibilityMatrix';
import MessageLink from '@shell/components/MessageLink';
export default {
name: 'VirtualMachinePCIDevices',
@@ -15,7 +16,8 @@ export default {
LabeledSelect,
DeviceList,
CompatibilityMatrix,
Banner
Banner,
MessageLink
},
props: {
mode: {
@@ -138,6 +140,13 @@ export default {
return inUse;
},
toVGpuDevicesPage() {
return {
name: 'harvester-c-cluster-resource',
params: { cluster: this.$store.getters['clusterId'], resource: HCI.VGPU_DEVICE },
};
},
devicesByNode() {
return this.enabledDevices?.reduce((acc, device) => {
const nodeName = device.status?.nodeName;
@@ -232,7 +241,12 @@ export default {
<div class="row">
<div class="col span-12">
<Banner color="info">
<t k="harvester.pci.howToUseDevice" />
<MessageLink
:to="toVGpuDevicesPage"
prefix-label="harvester.pci.howToUseDevice.prefix"
middle-label="harvester.pci.howToUseDevice.middle"
suffix-label="harvester.pci.howToUseDevice.suffix"
/>
</Banner>
<Banner
v-if="selectedDevices.length > 0"
@@ -211,6 +211,10 @@ export default {
return false;
},
vGPUAsPCIDeviceEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('vGPUAsPCIDevice');
},
usbPassthroughEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('usbPassthrough');
},
@@ -740,7 +744,7 @@ export default {
</Tab>
<Tab
v-if="enabledSriovgpu"
v-if="enabledSriovgpu && !vGPUAsPCIDeviceEnabled"
:label="t('harvester.tab.vGpuDevices')"
name="vGpuDevices"
:weight="-6"
+1
View File
@@ -7,6 +7,7 @@ import harvesterStore from './store/harvester-store';
import customValidators from './validators';
import { PRODUCT_NAME } from './config/harvester';
import { defineAsyncComponent } from 'vue';
import './styles/vue-flow.scss';
// Init the package
export default function (plugin: IPlugin) {
+91 -4
View File
@@ -123,6 +123,24 @@ harvester:
namespace: Namespace
message:
success: 'Image { name } created successfully.'
storageMigration:
title: Storage Migration
fields:
sourceVolume:
label: Source Volume
placeholder: Select a source volume
targetVolume:
label: Target Volume
placeholder: Select a target volume
dataMigration:
title: Data Migration
fields:
targetVolumeName:
label: Target Volume Name
placeholder: Enter a target volume name
targetStorageClassName:
label: Target Storage Class
placeholder: Select a storage class
migration:
failedMessage: Latest migration failed!
title: Migration
@@ -195,7 +213,6 @@ harvester:
title: Restart Virtual Machine
tip: Restart the virtual machine for configuration changes to take effect.
cancel: Save
notification:
title:
succeed: Succeed
@@ -233,6 +250,9 @@ harvester:
migrate: Migrate
cpuAndMemoryHotplug: Edit CPU and Memory
abortMigration: Abort Migration
storageMigration: Storage Migration
cancelStorageMigration: Cancel Storage Migration
dataMigration: Data Migration
createTemplate: Generate Template
enableMaintenance: Enable Maintenance Mode
disableMaintenance: Disable Maintenance Mode
@@ -356,7 +376,10 @@ harvester:
available: Available Devices
compatibleNodes: Compatible Nodes
impossibleSelection: 'There are no hosts with all of the selected devices.'
howToUseDevice: 'Use the table below to enable PCI passthrough on each device you want to use in this virtual machine.'
howToUseDevice:
prefix: 'Use the table below to enable PCI passthrough on each device you want to use in this virtual machine.<br>For vGPU devices, please enable them on the'
middle: vGPU Devices
suffix: page first.
deviceInTheSameHost: 'You can only select devices on the same host.'
oldFormatDevices:
help: |-
@@ -426,7 +449,7 @@ harvester:
volume:
upperType: Volume name
lowerType: volume name
needImageOrExisting: 'At least an image volume or an existing root-disk volume is required!'
needAtLeastOneBootable: 'At least one bootable volume is required!'
image:
ruleTip: 'The URL you have entered ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
ruleFileTip: 'The file you have chosen ends in an extension that we do not support. We only accept image files that end in .img, .iso, .qcow, .qcow2, .raw.'
@@ -899,6 +922,11 @@ harvester:
conditions: Conditions
size: Size
volumeMode: Volume Mode
accessModes: Access Modes
createWithDataVolume: Create with DataVolume
createWithDataVolumeTooltip: Create Volume with Kubevirt/Containerized Data Importer way. It can fill accessMode/volumeMode automatically.
showAdvanced: Show Advanced Options
hideAdvanced: Hide Advanced Options
source: Source
kind: Kind
sourceOptions:
@@ -1147,6 +1175,25 @@ harvester:
banner: The supported field in ACL match can refer to <a href="https://kubeovn.github.io/docs/v1.14.x/en/guide/subnet/#subnet-acl" target="_blank">KubeOvn Subnet ACL document</a>
vpc:
viewTopology: Topology
topology:
loading: Loading topology...
empty: No resources found
visibility:
vpc: VPC
subnets: Subnets
overlayNetworks: Overlay Networks
vms: VMs
labels:
cidr: CIDR
provider: Provider
type: Type
clusterNetwork: Cluster Network
network: Network
subnet: Subnet
ip: IP
mac: MAC
peering: Peering
noAddonEnabled:
prefix: The kubeovn-operator add-on is not enabled, click
middle: here
@@ -1248,8 +1295,14 @@ harvester:
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 * 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>.'
rwxNetwork:
warning: 'WARNING: <br/> Any change to rwx-network requires longhorn RWX volumes detached 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.'
shareStorageNetwork: Share Storage Network
dedicatedRwxNetwork: Dedicated RWX Network
shareStorageNetworkWarning: The rwx-network is governed by storage-network, and changes here won't take effect until share-storage-network is set to false.
vmForceDeletionPolicy:
period: Period
vmMigrationTimeout: VM Migration Timeout
vmMigrationNetwork:
parseError: "Failed to parse existing configuration."
fetchError: "Failed to load required network resources: {error}. Please refresh the page or try again later."
@@ -1332,10 +1385,20 @@ harvester:
retention: How long to retain metrics
retentionSize: Maximum size of metrics
clusterRegistrationUrl:
url: URL
insecureSkipTLSVerify: Insecure Skip TLS Verify
tip:
prefix: Harvester secures cluster registration via TLS by default. If opt out "Insecure Skip TLS Verify", you must provide custom CA certificates using the
middle: 'additional-ca'
suffix: setting.
message: To completely unset the imported Harvester cluster, please also remove it on the Rancher Dashboard UI via the <code> Virtualization Management </code> page.
ntpServers:
isNotIPV4: The address you entered is not IPv4 or host. Please enter a valid IPv4 address or a host address.
isDuplicate: There are duplicate NTP server configurations.
instanceManagerResources:
parseError: "Failed to parse configuration: {error}"
v1: "V1 Data Engine"
v2: "V2 Data Engine"
kubevirtMigration:
parseError: "Failed to parse configuration: {error}"
parallelMigrationsPerCluster: "Parallel Migrations Per Cluster"
@@ -1807,7 +1870,8 @@ harvester:
numVFs: Number Of Virtual Functions
vfAddresses: Virtual Functions Addresses
showMore: Show More
parentSriov: Filter By Parent SR-IOV
parentSriov: Filter By Parent SR-IOV Netork Device
parentSriovGPU: Filter By Parent SR-IOV GPU Device
sriovgpu:
label: SR-IOV GPU Devices
@@ -1948,11 +2012,13 @@ advancedSettings:
'harv-additional-ca': 'Custom CA root certificates for TLS validation.'
'harv-overcommit-config': 'Resource overcommit configuration.'
'harv-support-bundle-timeout': 'Support bundle timeout configuration in minutes, use 0 to disable the timeout.'
'harv-support-bundle-file-name': 'Support bundle file name configuration.'
'harv-support-bundle-expiration': 'Support bundle expiration configuration in minutes.'
'harv-support-bundle-node-collection-timeout': 'Support bundle node collection timeout configuration in minutes.'
'harv-vm-force-reset-policy': Configuration for the force-reset action when a virtual machine is stuck on a node that is down.
'harv-ssl-parameters': Custom SSL Parameters for TLS validation.
'harv-storage-network': 'Longhorn storage-network setting.'
'harv-rwx-network': 'Configure RWX network behavior for shared or dedicated storage network usage.'
'harv-support-bundle-namespaces': Select additional namespaces to include in the support bundle.
'harv-auto-disk-provision-paths': Specify the disks(using glob pattern) that Harvester will automatically add as virtual machine storage.
'harv-support-bundle-image': Support bundle image configuration. Find different versions in <a href="https://hub.docker.com/r/rancher/support-bundle-kit/tags" target="_blank">rancher/support-bundle-kit</a>.
@@ -1968,6 +2034,7 @@ advancedSettings:
'harv-rancher-cluster': 'Configure Rancher cluster integration settings for guest cluster management.'
'harv-max-hotplug-ratio': 'The ratio for kubevirt to limit the maximum CPU and memory that can be hotplugged to a VM. The value could be an integer between 1 and 20, default to 4.'
'harv-kubevirt-migration': 'Configure cluster-wide KubeVirt live migration parameters.'
'harv-instance-manager-resources': 'Configure resource percentage reservations for Longhorn instance manager V1 and V2. Valid instance manager CPU range between 0 - 40.'
typeLabel:
kubevirt.io.virtualmachine: |-
@@ -2149,3 +2216,23 @@ typeLabel:
one { IP Pool }
other { IP Pools }
}
migration.harvesterhci.io.openstacksource: |-
{count, plural,
one { OpenStack Source }
other { OpenStack Sources }
}
migration.harvesterhci.io.vmwaresource: |-
{count, plural,
one { VMware Source }
other { VMware Sources }
}
migration.harvesterhci.io.ovasource: |-
{count, plural,
one { OVA Source }
other { OVA Sources }
}
migration.harvesterhci.io.virtualmachineimport: |-
{count, plural,
one { Virtual Machine Import }
other { Virtual Machine Imports }
}
@@ -70,8 +70,13 @@ export default {
return schema;
},
filterRows() {
// we only show the non golden image PVCs in the list
return this.rows.filter((pvc) => !pvc?.isGoldenImageVolume);
return this.rows.filter((pvc) => {
if (pvc?.isGoldenImageVolume || pvc?.isCDIPopulatorVolume) {
return false;
}
return true;
});
},
headers() {
return [
+17
View File
@@ -155,6 +155,15 @@ export default {
return location;
},
viewTopology(group) {
const vpc = group.key;
const resource = this.$store.getters[`harvester/byId`](HCI.VPC, vpc);
if (resource && resource.goToDetail) {
resource.goToDetail();
}
},
showVpcAction(event, group) {
const vpc = group.key;
@@ -218,6 +227,14 @@ export default {
>
{{ t('harvester.vpc.createSubnet') }}
</router-link>
<button
type="button"
class="btn btn-sm role-secondary mr-5"
@click="viewTopology(group)"
>
<i class="icon icon-globe mr-5" />
{{ t('harvester.vpc.viewTopology') }}
</button>
<button
type="button"
class="btn btn-sm role-multi-action actions mr-10"
+18 -10
View File
@@ -707,18 +707,22 @@ export default {
}
},
needVolume(R) {
if (R.image === EMPTY_IMAGE) {
return false;
needVolumeRelatedInfo(R) {
// return [needVolume, needVolumeClaimTemplate]
if (R.source === SOURCE_TYPE.CONTAINER) {
return [true, false];
}
return true;
if (R.source === SOURCE_TYPE.IMAGE && R.image === EMPTY_IMAGE) {
return [false, false];
}
return [true, true];
},
parseDiskRows(disk) {
const disks = [];
const volumes = [];
const diskNameLabels = [];
const volumeClaimTemplates = [];
disk.forEach( (R, index) => {
@@ -726,14 +730,18 @@ export default {
disks.push(_disk);
if (this.needVolume(R)) {
const prefixName = this.value.metadata?.name || '';
const dataVolumeName = this.parseDataVolumeName(R, prefixName);
const prefixName = this.value.metadata?.name || '';
const dataVolumeName = this.parseDataVolumeName(R, prefixName);
const [needVolume, needVolumeClaimTemplate] = this.needVolumeRelatedInfo(R);
if (needVolume) {
const _volume = this.parseVolume(R, dataVolumeName);
const _dataVolumeTemplate = this.parseVolumeClaimTemplate(R, dataVolumeName);
volumes.push(_volume);
diskNameLabels.push(dataVolumeName);
}
if (needVolumeClaimTemplate) {
const _dataVolumeTemplate = this.parseVolumeClaimTemplate(R, dataVolumeName);
volumeClaimTemplates.push(_dataVolumeTemplate);
}
});
@@ -1,6 +1,7 @@
import SteveModel from '@shell/plugins/steve/steve-class';
import { escapeHtml } from '@shell/utils/string';
import { HCI } from '../types';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
const STATUS_DISPLAY = {
enabled: {
@@ -32,7 +33,7 @@ export default class PCIDevice extends SteveModel {
out.push(
{
action: 'enablePassthroughBulk',
enabled: !this.isEnabling,
enabled: !this.isEnabling && !this.isvGPUDevice,
icon: 'icon icon-fw icon-dot',
label: 'Enable Passthrough',
bulkable: true,
@@ -41,7 +42,7 @@ export default class PCIDevice extends SteveModel {
},
{
action: 'disablePassthrough',
enabled: this.isEnabling && this.claimedByMe,
enabled: this.isEnabling && this.claimedByMe && !this.isvGPUDevice,
icon: 'icon icon-fw icon-dot-open',
label: 'Disable Passthrough',
bulkable: true,
@@ -52,6 +53,14 @@ export default class PCIDevice extends SteveModel {
return out;
}
get isvGPUDevice() {
if (!this.vGPUAsPCIDeviceFeatureEnabled) {
return false;
}
return !!this.metadata?.labels?.[HCI_ANNOTATIONS.PARENT_SRIOV_GPU];
}
get canYaml() {
return false;
}
@@ -176,6 +185,10 @@ export default class PCIDevice extends SteveModel {
return this.status?.description;
}
get vGPUAsPCIDeviceFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('vGPUAsPCIDevice');
}
showDetachWarning() {
this.$dispatch('growl/warning', {
title: this.$rootGetters['i18n/t']('harvester.pci.detachWarning.title'),
@@ -9,6 +9,7 @@ import { colorForState } from '@shell/plugins/dashboard-store/resource-class';
import { HCI, VOLUME_SNAPSHOT } from '../../types';
import HarvesterResource from '../harvester';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../config/harvester';
import { CDI_POPULATOR_KIND } from '../../config/types';
import { LVM_DRIVER } from './storage.k8s.io.storageclass';
const DEGRADED_ERRORS = ['replica scheduling failed', 'precheck new replica failed'];
@@ -44,7 +45,7 @@ export default class HciPv extends HarvesterResource {
const exportImageAction = {
action: 'exportImage',
enabled: this.hasAction('export') && !this.isEncrypted,
icon: 'icon icon-copy',
icon: 'icon icon-external-link',
label: this.t('harvester.action.exportImage')
};
const takeSnapshotAction = {
@@ -77,10 +78,23 @@ export default class HciPv extends HarvesterResource {
icon: 'icon icon-backup',
label: this.t('harvester.action.cancelExpand')
},
{
action: 'dataMigration',
enabled: this.hasAction('dataMigration') && this.createPVCWithDataVolumeFeatureEnabled,
icon: 'icon icon-copy',
label: this.t('harvester.action.dataMigration')
},
...out
];
}
dataMigration(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'HarvesterDataMigrationDialog'
});
}
exportImage(resources = this) {
this.$dispatch('promptModal', {
resources,
@@ -339,10 +353,20 @@ export default class HciPv extends HarvesterResource {
return this?.metadata?.annotations?.[HCI_ANNOTATIONS.GOLDEN_IMAGE] === 'true';
}
get isCDIPopulatorVolume() {
const kind = this?.metadata?.annotations?.[HCI_ANNOTATIONS.CDI_POPULATOR_KIND];
return kind === CDI_POPULATOR_KIND.VOLUME_IMPORT_SOURCE || kind === CDI_POPULATOR_KIND.VOLUME_CLONE_SOURCE;
}
get thirdPartyStorageFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('thirdPartyStorage');
}
get createPVCWithDataVolumeFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('createPVCWithDataVolume');
}
get resourceExternalLink() {
const host = window.location.host;
const { params } = this.currentRoute();
@@ -52,11 +52,19 @@ export default class HciSetting extends HarvesterResource {
});
}
get clusterRegistrationTLSVerifyFeatureEnabled() {
return this.$rootGetters['harvester-common/getFeatureEnabled']('clusterRegistrationTLSVerify');
}
get customValue() {
if (this.metadata.name === HCI_SETTING.STORAGE_NETWORK) {
try {
return JSON.stringify(JSON.parse(this.value), null, 2);
} catch (e) {}
} else if (this.metadata.name === HCI_SETTING.CLUSTER_REGISTRATION_URL) {
try {
return this.clusterRegistrationTLSVerifyFeatureEnabled ? JSON.stringify(JSON.parse(this.value), null, 2) : this.value;
} catch (e) {}
}
return false;
@@ -199,6 +199,18 @@ export default class VirtVm extends HarvesterResource {
icon: 'icon icon-close',
label: this.t('harvester.action.abortMigration')
},
{
action: 'storageMigration',
enabled: !!this.actions?.storageMigration,
icon: 'icon icon-copy',
label: this.t('harvester.action.storageMigration')
},
{
action: 'cancelStorageMigration',
enabled: !!this.actions?.cancelStorageMigration,
icon: 'icon icon-close',
label: this.t('harvester.action.cancelStorageMigration')
},
{
action: 'addHotplugVolume',
enabled: !!this.actions?.addVolume,
@@ -368,6 +380,13 @@ export default class VirtVm extends HarvesterResource {
});
}
storageMigration(resources = this) {
this.$dispatch('promptModal', {
resources,
component: 'HarvesterStorageMigrationDialog'
});
}
backupVM(resources = this) {
this.$dispatch('promptModal', {
resources,
@@ -520,6 +539,10 @@ export default class VirtVm extends HarvesterResource {
this.doActionGrowl('abortMigration', {});
}
cancelStorageMigration() {
this.doActionGrowl('cancelStorageMigration', {});
}
createTemplate(resources = this) {
this.$dispatch('promptModal', {
resources,
@@ -770,11 +793,11 @@ export default class VirtVm extends HarvesterResource {
}
get isPending() {
if (this &&
if ((this &&
!this.isVMExpectedRunning &&
this.isVMCreated &&
this.vmi?.status?.phase === VMIPhase.Pending
) {
) || (this.metadata?.annotations?.[HCI_ANNOTATIONS.CLONE_BACKEND_STORAGE_STATUS] === 'cloning')) {
return { status: VMIPhase.Pending };
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.8.0-dev",
"version": "1.8.0-rc4",
"private": false,
"rancher": {
"annotations": {
@@ -29,4 +29,4 @@
"last 2 versions",
"not dead"
]
}
}
@@ -98,6 +98,11 @@ export default {
if (getters['schemaFor'](HCI.UPGRADE)) {
hash.upgrades = dispatch('findAll', { type: HCI.UPGRADE });
}
// Pre-fetch all HCI.UPGRADE_LOG data within loadCluster to ensure HarvesterUpgradeHeader has the necessary data. This is required because the header is dynamically loaded before the user enters the cluster in Rancher integration mode.
// See more details in https://github.com/harvester/harvester-ui-extension/pull/715
if (getters['schemaFor'](HCI.UPGRADE_LOG)) {
hash.upgradeLogs = dispatch('findAll', { type: HCI.UPGRADE_LOG });
}
const res: any = await allHash(hash);
+4
View File
@@ -0,0 +1,4 @@
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import '@vue-flow/controls/dist/style.css';
@import '@vue-flow/minimap/dist/style.css';
+1
View File
@@ -18,6 +18,7 @@ export const HCI = {
CLUSTER_NETWORK: 'network.harvesterhci.io.clusternetwork',
SUBNET: 'kubeovn.io.subnet',
VPC: 'kubeovn.io.vpc',
IP: 'kubeovn.io.ip',
VM_IMAGE_DOWNLOADER: 'harvesterhci.io.virtualmachineimagedownloader',
SUPPORT_BUNDLE: 'harvesterhci.io.supportbundle',
NETWORK_ATTACHMENT: 'harvesterhci.io.networkattachmentdefinition',
+5 -5
View File
@@ -69,7 +69,7 @@ export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value)
validName(getters, errors, D.name, diskNames, prefix, type, lowerType, upperType);
});
let requiredVolume = false;
let hasBootableVolume = false;
_volumes.forEach((V, idx) => {
const { type, typeValue } = getVolumeType(getters, V, _volumeClaimTemplates, value);
@@ -77,7 +77,7 @@ export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value)
const prefix = V.name || idx + 1;
if ([SOURCE_TYPE.IMAGE, SOURCE_TYPE.ATTACH_VOLUME, SOURCE_TYPE.CONTAINER].includes(type)) {
requiredVolume = true;
hasBootableVolume = true;
}
if (type === SOURCE_TYPE.NEW || type === SOURCE_TYPE.IMAGE) {
@@ -137,10 +137,10 @@ export function vmDisks(spec, getters, errors, validatorArgs, displayKey, value)
});
/**
* At least one volume must be create. (Verify only when create.)
* At least one bootable volume must be provided. (Verify only when create.)
*/
if ((!requiredVolume || _volumes.length === 0) && !value.links) {
errors.push(getters['i18n/t']('harvester.validation.vm.volume.needImageOrExisting'));
if (!hasBootableVolume && !value.links) {
errors.push(getters['i18n/t']('harvester.validation.vm.volume.needAtLeastOneBootable'));
}
return errors;
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
set -e
# Usage: update package.json and pkg/harvester/package.json to desired version
# Example: ./scripts/bump_version.sh v1.8.0-rc3
VERSION="$1"
if [ -z "$VERSION" ]; then
echo "Usage: $0 <version>"
echo "Example: $0 v1.8.0-rc3"
exit 1
fi
# Strip leading 'v' if present
VERSION="${VERSION#v}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
FILES=(
"$ROOT_DIR/package.json"
"$ROOT_DIR/pkg/harvester/package.json"
)
for FILE in "${FILES[@]}"; do
if [ ! -f "$FILE" ]; then
echo "File not found: $FILE"
exit 1
fi
# Use node to update version in-place while preserving formatting
node -e "
const fs = require('fs');
const raw = fs.readFileSync('$FILE', 'utf8');
const updated = raw.replace(/\"version\":\s*\"[^\"]*\"/, '\"version\": \"$VERSION\"');
fs.writeFileSync('$FILE', updated);
"
echo "Updated $FILE -> $VERSION"
done
+101 -35
View File
@@ -2633,15 +2633,15 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
"@rancher/icons@2.0.54":
version "2.0.54"
resolved "https://registry.yarnpkg.com/@rancher/icons/-/icons-2.0.54.tgz#7e8d4baeac36a483f18f4ce4043809371c4f0117"
integrity sha512-Fc5xpT/yuOJJin7ChxAdB6aoG1bREgisktnePUPXpY9nIMHSHYT/Ov8GsC3yLLgoqgpm450nMvPICUO3AYcPdQ==
"@rancher/icons@2.0.55":
version "2.0.55"
resolved "https://registry.yarnpkg.com/@rancher/icons/-/icons-2.0.55.tgz#394159bddbf786c17a12bc38e59b88346b30dcf4"
integrity sha512-hPcmsvfYNO36dJ7/lb3JbJC5BOnHbwBylic7HRqhSCSIcFlFaKnRt9aB/hvv3ip0Wo6J5yVEv2o7oXGErXkWFw==
"@rancher/shell@3.0.9-rc.1":
version "3.0.9-rc.1"
resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-3.0.9-rc.1.tgz#6dfe7d8a17663d170ddf5f48bec5c7c525c9da86"
integrity sha512-X7HKIhprzzLaeZXLhAnWHDjr9Rk1nLko+pno0rEkcK7F9ENQgVuROzVxEgMlFno4bI7RpHXt5tC90rPkXzcsRQ==
"@rancher/shell@3.0.9-rc.6":
version "3.0.9-rc.6"
resolved "https://registry.yarnpkg.com/@rancher/shell/-/shell-3.0.9-rc.6.tgz#d22add189481368a07df54c61d437f024236f639"
integrity sha512-qhTq7Ohsm/pToKRh010KVWLS4gH9g5BP55Ih0T62ZfTMIpO5JjiJkL/EtrRwlll47NKU1GHZ+lqh9yb3HJgDBg==
dependencies:
"@aws-sdk/client-ec2" "3.863.0"
"@aws-sdk/client-eks" "3.879.0"
@@ -2653,7 +2653,7 @@
"@babel/preset-typescript" "7.16.7"
"@novnc/novnc" "1.2.0"
"@popperjs/core" "2.11.8"
"@rancher/icons" "2.0.54"
"@rancher/icons" "2.0.55"
"@smithy/fetch-http-handler" "5.1.1"
"@types/is-url" "1.2.30"
"@types/node" "20.10.8"
@@ -2682,7 +2682,7 @@
cookie-universal "2.2.2"
core-js "3.45.0"
cron-validator "1.4.0"
cronstrue "2.53.0"
cronstrue "3.9.0"
cross-env "7.0.3"
css-loader "6.7.3"
csv-loader "3.0.3"
@@ -2691,7 +2691,7 @@
d3 "7.3.0"
d3-selection "3.0.0"
dayjs "1.11.18"
defu "5.0.1"
defu "6.1.4"
diff2html "3.4.24"
dompurify "3.2.5"
element-matches "^0.1.2"
@@ -2726,7 +2726,7 @@
jsonpath-plus "10.3.0"
jsrsasign "11.0.0"
jszip "3.10.1"
lodash "4.17.21"
lodash "4.17.23"
marked "4.0.17"
node-polyfill-webpack-plugin "3.0.0"
nodemon "2.0.22"
@@ -3429,10 +3429,10 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/lodash@4.17.23":
version "4.17.23"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.23.tgz#c1bb06db218acc8fc232da0447473fc2fb9d9841"
integrity sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==
"@types/lodash@4.17.24":
version "4.17.24"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
"@types/mime@^1":
version "1.3.5"
@@ -3452,9 +3452,9 @@
"@types/node" "*"
"@types/node@*", "@types/node@20.10.8", "@types/node@^14.14.31", "@types/node@~20.19.0":
version "20.19.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.33.tgz#ac8364c623b72d43125f0e7dd722bbe968f0c65e"
integrity sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==
version "20.19.37"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.37.tgz#b4fb4033408dd97becce63ec932c9ec57a9e2919"
integrity sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==
dependencies:
undici-types "~6.21.0"
@@ -3559,6 +3559,11 @@
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.8.tgz#7545ba4fc3c003d6c756f651f3bf163d8f0f29ba"
integrity sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==
"@types/web-bluetooth@^0.0.20":
version "0.0.20"
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
"@types/webpack-env@^1.15.2":
version "1.18.5"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.5.tgz#eccda0b04fe024bed505881e2e532f9c119169bf"
@@ -3720,6 +3725,35 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
"@vue-flow/background@^1.3.0":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@vue-flow/background/-/background-1.3.2.tgz#0c90cd05e5d60da017bbaf5a1c3eb6af7ed9b778"
integrity sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==
"@vue-flow/controls@^1.1.1":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@vue-flow/controls/-/controls-1.1.3.tgz#40866b553101fbef22d2b9a043965ed76fca4b2c"
integrity sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==
"@vue-flow/core@^1.33.5":
version "1.48.2"
resolved "https://registry.yarnpkg.com/@vue-flow/core/-/core-1.48.2.tgz#cef8641b17f6220c257d4208bdb2082cee882225"
integrity sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==
dependencies:
"@vueuse/core" "^10.5.0"
d3-drag "^3.0.0"
d3-interpolate "^3.0.1"
d3-selection "^3.0.0"
d3-zoom "^3.0.0"
"@vue-flow/minimap@^1.4.0":
version "1.5.4"
resolved "https://registry.yarnpkg.com/@vue-flow/minimap/-/minimap-1.5.4.tgz#c9c3badea49d4166aa9cdc713017397d9df7574c"
integrity sha512-l4C+XTAXnRxsRpUdN7cAVFBennC1sVRzq4bDSpVK+ag7tdMczAnhFYGgbLkUw3v3sY6gokyWwMl8CDonp8eB2g==
dependencies:
d3-selection "^3.0.0"
d3-zoom "^3.0.0"
"@vue/babel-helper-vue-jsx-merge-props@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz#8d53a1e21347db8edbe54d339902583176de09f2"
@@ -4115,6 +4149,28 @@
resolved "https://registry.yarnpkg.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz#b6b40a7625429d2bd7c2281ddba601ed05dc7f1a"
integrity sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==
"@vueuse/core@^10.5.0":
version "10.11.1"
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
dependencies:
"@types/web-bluetooth" "^0.0.20"
"@vueuse/metadata" "10.11.1"
"@vueuse/shared" "10.11.1"
vue-demi ">=0.14.8"
"@vueuse/metadata@10.11.1":
version "10.11.1"
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.11.1.tgz#209db7bb5915aa172a87510b6de2ca01cadbd2a7"
integrity sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==
"@vueuse/shared@10.11.1":
version "10.11.1"
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938"
integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
dependencies:
vue-demi ">=0.14.8"
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
@@ -5863,7 +5919,7 @@ cron-validator@1.4.0:
resolved "https://registry.yarnpkg.com/cron-validator/-/cron-validator-1.4.0.tgz#77ed4277b086c22e74ee65640f8456747afd4885"
integrity sha512-wGcJ9FCy65iaU6egSH8b5dZYJF7GU/3Jh06wzaT9lsa5dbqExjljmu+0cJ8cpKn+vUyZa/EM4WAxeLR6SypJXw==
cronstrue@2.53.0, cronstrue@2.59.0:
cronstrue@2.59.0, cronstrue@3.9.0:
version "2.59.0"
resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.59.0.tgz#a250b2b04eebabf35518725018e501ff94370fb1"
integrity sha512-YKGmAy84hKH+hHIIER07VCAHf9u0Ldelx1uU6EBxsRPDXIA1m5fsKmJfyC3xBhw6cVC/1i83VdbL4PvepTrt8A==
@@ -6173,7 +6229,7 @@ d3-delaunay@6:
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
"d3-drag@2 - 3", d3-drag@3:
"d3-drag@2 - 3", d3-drag@3, d3-drag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
@@ -6228,7 +6284,7 @@ d3-hierarchy@3:
resolved "https://registry.yarnpkg.com/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz#b01cd42c1eed3d46db77a5966cf726f8c09160c6"
integrity sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3:
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3, d3-interpolate@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
@@ -6274,7 +6330,7 @@ d3-scale@4:
d3-time "2.1.1 - 3"
d3-time-format "2 - 4"
"d3-selection@2 - 3", d3-selection@3, d3-selection@3.0.0:
"d3-selection@2 - 3", d3-selection@3, d3-selection@3.0.0, d3-selection@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
@@ -6316,7 +6372,7 @@ d3-shape@3:
d3-interpolate "1 - 3"
d3-timer "1 - 3"
d3-zoom@3:
d3-zoom@3, d3-zoom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
@@ -6516,10 +6572,10 @@ define-properties@^1.1.3, define-properties@^1.2.1:
has-property-descriptors "^1.0.0"
object-keys "^1.1.1"
defu@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/defu/-/defu-5.0.1.tgz#a034278f9b032bf0845d261aa75e9ad98da878ac"
integrity sha512-EPS1carKg+dkEVy3qNTqIdp2qV7mUP08nIsupfwQpz++slCVRw7qbQyWvSTig+kFPwz2XXp5/kIIkH+CwrJKkQ==
defu@6.1.4:
version "6.1.4"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479"
integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==
delaunator@5:
version "5.0.1"
@@ -6783,6 +6839,11 @@ element-matches@^0.1.2:
resolved "https://registry.yarnpkg.com/element-matches/-/element-matches-0.1.2.tgz#7345cb71e965bd2b12f725e524591c102198361a"
integrity sha512-yWh1otcs3OKUWDvu/IxyI36ZI3WNaRZlI0uG/DK6fu0pap0VYZ0J5pEGTk1zakme+hT0OKHwhlHc0N5TJhY6yQ==
elkjs@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/elkjs/-/elkjs-0.11.1.tgz#d27fcdbbf5a8aeeff80420d17d1666f78cfc8544"
integrity sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==
elliptic@^6.5.3, elliptic@^6.5.5:
version "6.5.7"
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b"
@@ -9940,10 +10001,10 @@ lodash.upperfirst@^4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==
lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lodash@4.17.23, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.23"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a"
integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==
log-symbols@^4.0.0, log-symbols@^4.1.0:
version "4.1.0"
@@ -13202,6 +13263,11 @@ vue-component-type-helpers@^2.0.0:
resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.0.tgz#de5fa802b6beae7125595ec0d3d5195a22691623"
integrity sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==
vue-demi@>=0.14.8:
version "0.14.10"
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
vue-draggable-next@^2.2.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/vue-draggable-next/-/vue-draggable-next-2.3.0.tgz#ba83154f60b8a3c24059c18b8060b72200a4c673"
@@ -13778,9 +13844,9 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yaml@^2.5.1:
version "2.8.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.2.tgz#5694f25eca0ce9c3e7a9d9e00ce0ddabbd9e35c5"
integrity sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==
version "2.8.3"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d"
integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==
yargs-parser@^18.1.2:
version "18.1.3"