Compare commits

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

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

(cherry picked from commit 182d92d80b)

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

* ci: add package version check

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

* ci: extract check version in a script

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

* ci: update env variable

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

---------

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

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

* fix: conflict

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-07-16 16:22:55 +08:00
Andy Lee 4a4f2c96bb chore: bump version to v1.5.1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-27 17:39:47 +08:00
mergify[bot]andAndy Lee d7bc112a64 ci: replace tag with github.event.release.tag_name (#356) (#357)
(cherry picked from commit fcef0391bb)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-27 17:31:31 +08:00
mergify[bot]andAndy Lee 08e81b0628 fix adding existing volume in edit VM page (#351) (#352)
(cherry picked from commit 57cbd799dd)

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

* feat: change dialog wordings

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

* refactor: update protip wording

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

---------

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

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

* Resolve conflict

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

* Fix conflict

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

---------

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

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

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

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-10 12:37:52 +08:00
mergify[bot]andAndy Lee 18a53f5baf fix http-proxy setting not saved (#329) (#330)
(cherry picked from commit 32ebdc3acd)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-06-06 17:03:48 +08:00
Andy Lee f872bb5abf rollback @rancher/shell to 3.0.2-rc6 (#326) 2025-06-05 17:45:41 +08:00
Andy Lee cd015d57e7 bump to v1.5.1-rc2
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-06-04 14:32:42 +08:00
mergify[bot]andAndy Lee 635254afdc fix API doesn't bring checksum (#323) (#324)
(cherry picked from commit 9e015ec3b1)

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

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

* Add failed and success banner after image uploaded

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

* add delete image feature

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

* add skip checking single-replica detached volumes checkbox

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

* change delete image flow

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

* Reuse ConfirmRelatedToRemoveDialog

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

---------

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

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

* resolve conflict

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-28 12:58:46 +08:00
mergify[bot]andAndy Lee 01e3867da1 give default valeu for uploadImage opt (#308) (#319)
(cherry picked from commit fe32b4372b)

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



* move uploadFile action



* change os upgrage ui to standard feature



* Add uploadImage cancel



---------



(cherry picked from commit 6d8f6579c7)

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

* refactor feature flags structure

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

* Replace with FEATURE_FLAGS and valid and sort versions

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

---------

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

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

* resolve conflict

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

---------

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-26 15:19:28 +08:00
mergify[bot]andAndy Lee a2898a2554 Change isKVMDisable condition (#294) (#304)
* change condition to check isKVMDisable



* change condition eq 0



* add allNotExist back



---------


(cherry picked from commit aa9dd32cce)

Signed-off-by: Andy Lee <andy.lee@suse.com>
Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-05-19 10:19:43 +08:00
mergify[bot]andYiya Chen d11a045aed fix: range condition (#299) (#301)
(cherry picked from commit cbfcf4dbae)

Signed-off-by: Yi-Ya Chen <yiya.chen@suse.com>
Co-authored-by: Yiya Chen <yiya.chen@suse.com>
2025-05-15 16:26:23 +08:00
mergify[bot] ae2a2f6d28 upgrade shell to 3.0.5-rc.2 (#297) (#298) 2025-05-15 11:25:46 +08:00
mergify[bot]andAndy Lee cf68c3976a Upgrade rancher/shell to 3.0.5-rc1 (backport #282) (#296)
* upgrade shell to 3.0.5-rc1 (#282)

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

# Conflicts:
#	yarn.lock

* solve conflict in yarn.lock

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

---------

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

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



(cherry picked from commit 1e270268d8)

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

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

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

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

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

Co-authored-by: Andy Lee <andy.lee@suse.com>
2025-04-17 15:29:06 +08:00
32 changed files with 818 additions and 1452 deletions
+1
View File
@@ -2,6 +2,7 @@ pull_request_rules:
- name: Automatically open backport PR to release-harvester-v1.5 - name: Automatically open backport PR to release-harvester-v1.5
conditions: conditions:
- base=main - base=main
- label="require backport 1.5"
actions: actions:
backport: backport:
branches: branches:
@@ -11,18 +11,19 @@ defaults:
working-directory: ./ working-directory: ./
jobs: jobs:
lint: check-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repository
with: uses: actions/checkout@v4
fetch-depth: 1
- name: Run tests - name: Check package version
uses: ./.github/actions/lint env:
TAG_VERSION: ${{ github.event.release.tag_name }}
run: ./scripts/check-version.sh $TAG_VERSION
build-and-push-extension-catalog: build-and-push-extension-catalog:
needs: lint needs: check-version
uses: ./.github/workflows/build-extension-catalog.yml uses: ./.github/workflows/build-extension-catalog.yml
permissions: permissions:
actions: write actions: write
@@ -128,7 +128,7 @@ jobs:
git push origin ${{ needs.setup-target-branch.outputs.target_branch }} git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
- name: Run Helm chart releaser - name: Run Helm chart releaser
uses: helm/chart-releaser-action@v1.4.1 uses: helm/chart-releaser-action@v1.7.0
with: with:
charts_dir: ./charts charts_dir: ./charts
env: env:
@@ -22,26 +22,27 @@ jobs:
- name: Determine release tag - name: Determine release tag
id: determine_tag id: determine_tag
run: | 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]}" RELEASE_TAG="harvester-${BASH_REMATCH[1]}"
echo "${RELEASE_TAG}" echo "${RELEASE_TAG}"
echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT
else else
echo "Error: invalid tag format." && exit 1 echo "Error: invalid tag format." && exit 1
fi fi
lint: check-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - name: Checkout repository
with: uses: actions/checkout@v4
fetch-depth: 1 - 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: build-extension-charts:
needs: needs:
- setup-release-tag - setup-release-tag
- lint - check-version
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions: permissions:
actions: write actions: write
@@ -5,14 +5,20 @@ on:
- v[1-9].* - v[1-9].*
jobs: jobs:
build-validation: check-version:
name: Build Test runs-on: ubuntu-latest
uses: ./.github/workflows/run-lint.yaml 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: build:
name: Build and Upload Package name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml uses: ./.github/workflows/build-and-publish-standalone.yaml
needs: needs: check-version
- build-validation
permissions: permissions:
contents: read contents: read
packages: write packages: write
+10 -10
View File
@@ -1,6 +1,6 @@
{ {
"name": "harvester-ui-extension", "name": "harvester-ui-extension",
"version": "1.5.0-rc5", "version": "1.5.2-rc2",
"private": false, "private": false,
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@@ -13,24 +13,24 @@
"node-polyfill-webpack-plugin": "^3.0.0", "node-polyfill-webpack-plugin": "^3.0.0",
"vue-draggable-next": "^2.2.1", "vue-draggable-next": "^2.2.1",
"yaml": "^2.5.1", "yaml": "^2.5.1",
"@babel/plugin-transform-class-static-block": "7.26.0" "@babel/plugin-transform-class-static-block": "7.28.3"
}, },
"resolutions": { "resolutions": {
"@types/node": "~20.10.0", "@types/node": "~20.19.0",
"cronstrue": "2.50.0", "cronstrue": "2.59.0",
"d3-color": "3.1.0", "d3-color": "3.1.0",
"ejs": "3.1.9", "ejs": "3.1.10",
"follow-redirects": "1.15.2", "follow-redirects": "1.15.11",
"glob": "7.2.3", "glob": "7.2.3",
"glob-parent": "6.0.2", "glob-parent": "6.0.2",
"json5": "2.2.3", "json5": "2.2.3",
"@types/lodash": "4.17.5", "@types/lodash": "4.17.20",
"merge": "2.1.1", "merge": "2.1.1",
"node-forge": "1.3.1", "node-forge": "1.3.1",
"nth-check": "2.1.1", "nth-check": "2.1.1",
"qs": "6.11.1", "qs": "6.14.0",
"roarr": "7.0.4", "roarr": "7.21.1",
"semver": "7.5.4", "semver": "7.7.2",
"@vue/cli-service/html-webpack-plugin": "^5.0.0" "@vue/cli-service/html-webpack-plugin": "^5.0.0"
}, },
"scripts": { "scripts": {
+1 -1
View File
@@ -43,7 +43,7 @@ export default {
{{ t('harvester.upgradePage.upgradeInfo.tip') }} {{ t('harvester.upgradePage.upgradeInfo.tip') }}
</p> </p>
<p class="mb-5"> <p>
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a {{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a
:href="releaseVersion" :href="releaseVersion"
target="_blank" target="_blank"
@@ -1,6 +1,5 @@
<script> <script>
import CreateEditView from '@shell/mixins/create-edit-view'; import CreateEditView from '@shell/mixins/create-edit-view';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import { LabeledInput } from '@components/Form/LabeledInput'; import { LabeledInput } from '@components/Form/LabeledInput';
@@ -62,7 +61,7 @@ export default {
<div <div
class="row" class="row"
@update:value="update" @input="update"
> >
<div class="col span-12"> <div class="col span-12">
<LabeledInput <LabeledInput
@@ -59,7 +59,7 @@ export default {
name="model" name="model"
:options="[true,false]" :options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]" :labels="[t('generic.enabled'), t('generic.disabled')]"
@input="update" @update:value="update"
/> />
<LabeledInput <LabeledInput
+35 -49
View File
@@ -1,31 +1,19 @@
// https://github.com/harvester/dashboard/releases/tag/v1.3.0 import semver from 'semver';
const featuresV130 = [
'supportHarvesterClusterVersion'
];
// https://github.com/harvester/dashboard/releases/tag/v1.3.1 const FEATURE_FLAGS = {
const featuresV131 = [ 'v1.3.0': [
...featuresV130, 'supportHarvesterClusterVersion'
],
'v1.3.1': [
'autoRotateRke2CertsSetting', 'autoRotateRke2CertsSetting',
'supportBundleNodeCollectionTimeoutSetting' 'supportBundleNodeCollectionTimeoutSetting'
]; ],
'v1.3.2': [
// https://github.com/harvester/dashboard/releases/tag/v1.3.2
const featuresV132 = [
...featuresV131,
'kubeconfigDefaultTokenTTLMinutesSetting', 'kubeconfigDefaultTokenTTLMinutesSetting',
'improveMaintenanceMode', 'improveMaintenanceMode',
]; ],
'v1.3.3': [],
// TODO: add v1.3.3 official release note 'v1.4.0': [
// 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', 'cpuPinning',
'usbPassthrough', 'usbPassthrough',
'volumeEncryption', 'volumeEncryption',
@@ -33,37 +21,35 @@ const featuresV140 = [
'vmSnapshotQuota', 'vmSnapshotQuota',
'longhornV2LVMSupport', 'longhornV2LVMSupport',
'improveMaintenanceMode', 'improveMaintenanceMode',
]; ],
'v1.4.1': [],
// https://github.com/harvester/dashboard/releases/tag/v1.4.1 'v1.4.2': [
const featuresV141 = [
...featuresV140
];
// TODO: add v1.4.2 official release note
const featuresV142 = [
...featuresV141,
'refreshIntervalInSecond', 'refreshIntervalInSecond',
'allowEmptySnapshotClassName' 'allowEmptySnapshotClassName'
]; ],
'v1.4.3': [],
// TODO: add v1.5.0 official release note 'v1.5.0': [
const featuresV150 = [
...featuresV142,
'tpmPersistentState', 'tpmPersistentState',
'efiPersistentState', 'efiPersistentState',
'untaggedNetworkSetting', 'untaggedNetworkSetting',
'skipSingleReplicaDetachedVol', 'skipSingleReplicaDetachedVol',
'thirdPartyStorage' 'thirdPartyStorage'
]; ],
'v1.5.1': [],
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 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', VM_DEVICE_ALLOCATION_DETAILS: 'harvesterhci.io/deviceAllocationDetails',
SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId', SVM_BACKUP_ID: 'harvesterhci.io/svmbackupId',
DISABLE_LONGHORN_V2_ENGINE: 'node.longhorn.io/disable-v2-data-engine', 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')" :label="t('harvester.modal.cloneVM.name')"
required required
/> />
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template> </template>
<template <template
@@ -127,11 +133,6 @@ export default {
@click="create" @click="create"
/> />
</div> </div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</template> </template>
</Card> </Card>
</template> </template>
@@ -67,7 +67,7 @@ export default {
plusMore() { plusMore() {
const remaining = this.resources.length - this.names.length; 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() { type() {
@@ -42,8 +42,25 @@ export default {
computed: { computed: {
...mapState('action-menu', ['modalData']), ...mapState('action-menu', ['modalData']),
warningMessageKey() { title() {
return this.modalData.warningMessageKey; 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() { names() {
@@ -70,6 +87,12 @@ export default {
return this.resources[0].nameDisplay; return this.resources[0].nameDisplay;
}, },
needConfirmation() {
const { needConfirmation = true } = this.modalData ;
return needConfirmation === true;
},
plusMore() { plusMore() {
const remaining = this.resources.length - this.names.length; const remaining = this.resources.length - this.names.length;
@@ -97,11 +120,15 @@ export default {
}, },
deleteDisabled() { deleteDisabled() {
if (!this.needConfirmation) {
return false;
}
return this.confirmName !== this.nameToMatch; return this.confirmName !== this.nameToMatch;
}, },
protip() { protip() {
return this.t('promptRemove.protip', { alternateLabel }); return this.t('dialog.promptRemove.protip', { alternateLabel });
}, },
}, },
@@ -137,19 +164,26 @@ export default {
> >
<template #title> <template #title>
<h4 class="text-default-text"> <h4 class="text-default-text">
{{ t('promptRemove.title') }} {{ t(title, { type }, true) }}
</h4> </h4>
</template> </template>
<template #body> <template #body>
<div class="pl-10 pr-10"> <div class="pl-10 pr-10">
<span <span
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)" v-clean-html="warningMessage"
></span> ></span>
<div
v-if="needConfirmation"
class="mt-20"
>
<div class="mt-10 mb-10"> <div class="mt-10 mb-10">
<span <span
v-clean-html="t('promptRemove.confirmName', { nameToMatch: escapeHtml(nameToMatch) }, true)" v-clean-html="t('dialog.promptRemove.confirmName', {
type: formattedType,
nameToMatch: escapeHtml(nameToMatch)
}, true)"
></span> ></span>
</div> </div>
<div class="mb-10"> <div class="mb-10">
@@ -163,6 +197,7 @@ export default {
<div class="text-info mt-20"> <div class="text-info mt-20">
{{ protip }} {{ protip }}
</div> </div>
</div>
<Banner <Banner
v-for="(error, i) in errors" v-for="(error, i) in errors"
:key="i" :key="i"
@@ -233,7 +233,7 @@ export default {
:disabled="isEdit || isView" :disabled="isEdit || isView"
:mode="mode" :mode="mode"
row row
@input="onTypeChange" @update:value="onTypeChange"
/> />
</div> </div>
<div class="row mb-30"> <div class="row mb-30">
@@ -6,7 +6,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect'; import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard'; 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 { NAMESPACE } from '@shell/config/types';
import { HCI } from '../../types'; import { HCI } from '../../types';
@@ -90,9 +90,6 @@ export default {
}, },
sshOption() { sshOption() {
if (this.mode === _VIEW || this.mode === _EDIT) {
return [];
}
const out = this.$store.getters['harvester/all'](HCI.SSH).map( (O) => { const out = this.$store.getters['harvester/all'](HCI.SSH).map( (O) => {
return { return {
label: O.id, label: O.id,
@@ -142,10 +142,12 @@ export default {
return; return;
} }
// update this.value with existing volume spec, then update to upstream component
this.value.accessModes = pvcResource.spec.accessModes[0]; this.value.accessModes = pvcResource.spec.accessModes[0];
this.value.size = pvcResource.spec.resources.requests.storage; this.value.size = pvcResource.spec.resources.requests.storage;
this.value.storageClassName = pvcResource.spec.storageClassName; this.value.storageClassName = pvcResource.spec.storageClassName;
this.value.volumeMode = pvcResource.spec.volumeMode; this.value.volumeMode = pvcResource.spec.volumeMode;
this.update();
}, },
'value.type'(neu) { 'value.type'(neu) {
@@ -29,7 +29,7 @@ export default {
const rows = (this.value || []).map((row) => { const rows = (this.value || []).map((row) => {
let type = 'cidr'; let type = 'cidr';
if (row.rangeStart && row.rangeEnd) { if (row.rangeStart || row.rangeEnd) {
type = 'range'; type = 'range';
} }
@@ -65,7 +65,7 @@ export default {
:labels="[t('generic.disabled'),t('generic.enabled')]" :labels="[t('generic.disabled'),t('generic.enabled')]"
:options="[false, true]" :options="[false, true]"
:disabled="disabled" :disabled="disabled"
@input="onToggle" @update:value="onToggle"
/> />
</div> </div>
</div> </div>
+22 -2
View File
@@ -52,6 +52,11 @@ dialog:
message: "Are you sure you want to continue stop the {type} {names}?" message: "Are you sure you want to continue stop the {type} {names}?"
pause: pause:
message: "Are you sure you want to continue pause the {type} {names}?" 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: harvester:
branding: branding:
@@ -61,6 +66,7 @@ harvester:
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB' tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
productLabel: 'Harvester' productLabel: 'Harvester'
modal: modal:
backup: backup:
success: 'Backup { backUpName } has been initiated.' success: 'Backup { backUpName } has been initiated.'
addBackup: Add Backup addBackup: Add Backup
@@ -99,6 +105,9 @@ harvester:
tip: Please enter a virtual machine name! tip: Please enter a virtual machine name!
success: 'Virtual machine { name } cloned successfully.' success: 'Virtual machine { name } cloned successfully.'
failed: 'Failed clone virtual machine!' failed: 'Failed clone virtual machine!'
osImage:
title: Delete Image
message: Are you sure you want to delete the image { name } ?
downloadImage: downloadImage:
title: Download Image title: Download Image
banner: 'This action takes a while depending on the image size ({ size }). Please be patient.' banner: 'This action takes a while depending on the image size ({ size }). Please be patient.'
@@ -842,6 +851,8 @@ harvester:
=1 {1 image is uploading, please do not refresh or close the page.} =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.} 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 checksum: Checksum
checksumTip: Validate the image using the SHA512 checksum, if specified. checksumTip: Validate the image using the SHA512 checksum, if specified.
@@ -870,14 +881,18 @@ harvester:
upgradeNode: Upgrading Node upgradeNode: Upgrading Node
upgradeSysService: Upgrading System Service upgradeSysService: Upgrading System Service
upgradeImage: Download Upgrade Image upgradeImage: Download Upgrade Image
osUpgrade: OS Upgrade osUpgrade: Cluster Upgrade
uploadNew: Upload New Image uploadNew: Upload New Image
deleteHeader: Please select an image to delete.
deleteExisting: Delete Existing Image
selectExisting: Select Existing Image selectExisting: Select Existing Image
createRepository: Creating Upgrade Repository createRepository: Creating Upgrade Repository
succeeded: Succeeded succeeded: Succeeded
releaseTip: Please read the upgrade documentation carefully. You can view details on the <a href="{url}" target="_blank">Harvester Release Notes</a>. 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. checkReady: I have read and understood the upgrade instructions related to this Harvester version.
pending: Pending pending: Pending
upload:
duplicatedFile: The file you are trying to upload already exists.
repoInfo: repoInfo:
upgradeStatus: Upgrade Status upgradeStatus: Upgrade Status
os: OS os: OS
@@ -1080,10 +1095,15 @@ harvester:
addConfig: Add Configuration addConfig: Add Configuration
upgrade: upgrade:
selectExitImage: Please select the OS image to upgrade. unknownImageName: Image name is not found.
imageUrl: Please input a valid image URL. imageUrl: Please input a valid image URL.
chooseFile: Please select to upload an image. chooseFile: Please select to upload an image.
checksum: Checksum 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: harvesterMonitoring:
label: Harvester Monitoring label: Harvester Monitoring
section: section:
@@ -132,6 +132,7 @@ export default {
const hash = { const hash = {
vms: this.fetchClusterResources(HCI.VM), vms: this.fetchClusterResources(HCI.VM),
pvcs: this.fetchClusterResources(PVC),
nodes: this.fetchClusterResources(NODE), nodes: this.fetchClusterResources(NODE),
events: this.fetchClusterResources(EVENT), events: this.fetchClusterResources(EVENT),
metricNodes: this.fetchClusterResources(METRIC.NODE), metricNodes: this.fetchClusterResources(METRIC.NODE),
@@ -221,6 +222,7 @@ export default {
nodes: [], nodes: [],
metricNodes: [], metricNodes: [],
vms: [], vms: [],
pvcs: [],
monitoring: {}, monitoring: {},
VM_DASHBOARD_METRICS_URL, VM_DASHBOARD_METRICS_URL,
CLUSTER_METRICS_SUMMARY_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 }); 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) { if (resource.type === HCI.BLOCK_DEVICE) {
let total = 0; let total = 0;
let errorCount = 0; let errorCount = 0;
+2 -11
View File
@@ -2,13 +2,12 @@
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { Banner } from '@components/Banner'; import { Banner } from '@components/Banner';
import Loading from '@shell/components/Loading'; import Loading from '@shell/components/Loading';
import { VIEW_IN_API, DEV } from '@shell/store/prefs';
import { MANAGEMENT } from '@shell/config/types'; import { MANAGEMENT } from '@shell/config/types';
import { allHash } from '@shell/utils/promise'; import { allHash } from '@shell/utils/promise';
import Tabbed from '@shell/components/Tabbed/index.vue'; import Tabbed from '@shell/components/Tabbed/index.vue';
import Tab from '@shell/components/Tabbed/Tab.vue'; import Tab from '@shell/components/Tabbed/Tab.vue';
import Settings from '@pkg/harvester/components/SettingList.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'; import { HCI } from '../types';
export default { export default {
@@ -21,14 +20,6 @@ export default {
}, },
async fetch() { 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 isSingleProduct = !!this.$store.getters['isSingleProduct'];
const inStore = this.$store.getters['currentProduct'].inStore; 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.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); initSettings.push(s);
}); });
+9 -17
View File
@@ -922,7 +922,9 @@ export default {
return undefined; return undefined;
} }
return userDataYaml; const hasCloudComment = this.hasCloudConfigComment(userDataYaml);
return hasCloudComment ? userDataYaml : `#cloud-config\n${ userDataYaml }`;
} catch (e) { } catch (e) {
console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console
@@ -1443,7 +1445,6 @@ export default {
sshAuthorizedKeys.splice(index, 1); sshAuthorizedKeys.splice(index, 1);
} }
}); });
const userDataJson = this.convertToJson(this.userScript); const userDataJson = this.convertToJson(this.userScript);
userDataJson.ssh_authorized_keys = sshAuthorizedKeys; userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
@@ -1579,19 +1580,9 @@ export default {
*/ */
handler(neu) { handler(neu) {
if (this.deleteAgent) { if (this.deleteAgent) {
let out = this.getUserData({ this['userScript'] = this.getUserData({
installAgent: neu, osType: this.osType, deletePackage: this.deletePackage 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.refreshYamlEditor();
} }
this.deleteAgent = true; this.deleteAgent = true;
@@ -1616,15 +1607,16 @@ export default {
}, },
sshKey(neu, old) { 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); const _diff = difference(old, neu);
// delete removed ssh key in userdata if needed
if (_diff.length > 0 && this.isCreate) { if (_diff.length > 0 && this.isCreate) {
this.deleteSSHFromUserData(_diff); this.deleteSSHFromUserData(_diff);
} }
// refresh yaml editor to get the latest userScript
this.userScript = this.getUserData({ installAgent: this.installAgent, osType: this.osType });
this.refreshYamlEditor();
} }
} }
}; };
@@ -67,7 +67,6 @@ export default class HciNamespace extends namespace {
promptRemove(resources = this) { promptRemove(resources = this) {
this.$dispatch('promptModal', { this.$dispatch('promptModal', {
resources, resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
component: 'ConfirmRelatedToRemoveDialog' component: 'ConfirmRelatedToRemoveDialog'
}); });
} }
+8 -2
View File
@@ -115,7 +115,6 @@ export default class HciNode extends HarvesterResource {
promptRemove(resources = this) { promptRemove(resources = this) {
this.$dispatch('promptModal', { this.$dispatch('promptModal', {
resources, resources,
warningMessageKey: 'promptRemove.confirmRelatedResource',
component: 'ConfirmRelatedToRemoveDialog' component: 'ConfirmRelatedToRemoveDialog'
}); });
} }
@@ -186,9 +185,16 @@ export default class HciNode extends HarvesterResource {
} }
get isKVMDisable() { 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]; 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() { get stateDisplay() {
@@ -28,17 +28,15 @@ export default class HciSetting extends HarvesterResource {
} }
const schema = this.$getters['schemaFor'](HCI.UPGRADE); const schema = this.$getters['schemaFor'](HCI.UPGRADE);
const hasUpgradeAccess = !!schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase())); const hasUpgradeAccess = !!schema?.collectionMethods.find((x) => ['post'].includes(x.toLowerCase()));
if (this.id === HCI_SETTING.SERVER_VERSION && hasUpgradeAccess) { if (this.id === HCI_SETTING.SERVER_VERSION && hasUpgradeAccess) {
const latestUpgrade = this.$getters['all'](HCI.UPGRADE).find((upgrade) => upgrade.isLatestUpgrade);
out.unshift({ out.unshift({
action: 'goToAirgapUpgrade', action: 'goToAirgapUpgrade',
enabled: true, enabled: true,
icon: 'icon icon-refresh', icon: 'icon icon-refresh',
label: this.t('harvester.upgradePage.upgrade'), label: this.t('harvester.upgradePage.upgrade'),
disabled: !!latestUpgrade && !latestUpgrade?.isUpgradeSucceeded
}); });
} }
@@ -50,35 +50,45 @@ export default class HciVmImage extends HarvesterResource {
canCreateVM = false; canCreateVM = false;
} }
return [ const customActions = this.isReady ? [
{ {
action: 'createFromImage', action: 'createFromImage',
enabled: canCreateVM, enabled: canCreateVM,
icon: 'icon icon-circle-plus', icon: 'icon icon-circle-plus',
label: this.t('harvester.action.createVM'), label: this.t('harvester.action.createVM'),
disabled: !this.isReady,
}, },
{ {
action: 'encryptImage', action: 'encryptImage',
enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted, enabled: this.volumeEncryptionFeatureEnabled && !this.isEncrypted,
icon: 'icon icon-lock', icon: 'icon icon-lock',
label: this.t('harvester.action.encryptImage'), label: this.t('harvester.action.encryptImage'),
disabled: !this.isReady,
}, },
{ {
action: 'decryptImage', action: 'decryptImage',
enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted, enabled: this.volumeEncryptionFeatureEnabled && this.isEncrypted,
icon: 'icon icon-unlock', icon: 'icon icon-unlock',
label: this.t('harvester.action.decryptImage'), label: this.t('harvester.action.decryptImage'),
disabled: !this.isReady,
}, },
{ {
action: 'imageDownload', action: 'imageDownload',
enabled: this.links?.download, enabled: this.links?.download,
icon: 'icon icon-download', icon: 'icon icon-download',
label: this.t('asyncButton.download.action'), label: this.t('asyncButton.download.action'),
}, }
...out ] : [];
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() { get uploadImage() {
return async(file) => { return async(file, opt = {}) => {
const formData = new FormData(); const formData = new FormData();
formData.append('chunk', file); formData.append('chunk', file);
@@ -303,22 +313,23 @@ export default class HciVmImage extends HarvesterResource {
try { try {
this.$ctx.commit('harvester-common/uploadStart', this.metadata.name, { root: true }); this.$ctx.commit('harvester-common/uploadStart', this.metadata.name, { root: true });
await this.doAction('upload', formData, { const result = await this.doAction('upload', formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
'File-Size': file.size, 'File-Size': file.size,
}, },
params: { size: file.size }, params: { size: file.size },
signal: opt.signal,
}); });
return result;
} catch (err) { } catch (err) {
this.$ctx.commit('harvester-common/uploadError', { name: this.name, message: err.message }, { root: true }); 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 }); this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
throw err;
return Promise.reject(err); } finally {
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
} }
this.$ctx.commit('harvester-common/uploadEnd', this.metadata.name, { root: true });
}; };
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "harvester", "name": "harvester",
"description": "Rancher UI Extension for Harvester", "description": "Rancher UI Extension for Harvester",
"version": "1.5.0-rc5", "version": "1.5.2-rc2",
"private": false, "private": false,
"rancher": { "rancher": {
"annotations": { "annotations": {
@@ -7,13 +7,16 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import { exceptionToErrorsArray } from '@shell/utils/error'; import { exceptionToErrorsArray } from '@shell/utils/error';
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations'; import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
import UpgradeInfo from '../../../../components/UpgradeInfo'; import UpgradeInfo from '../../../../components/UpgradeInfo';
import { HCI } from '../../../../types'; import { HCI } from '../../../../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../../../../config/harvester'; 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 = { const IMAGE_METHOD = {
NEW: 'new', NEW: 'new',
EXIST: 'exist' EXIST: 'exist',
DELETE: 'delete'
}; };
const DOWNLOAD = 'download'; const DOWNLOAD = 'download';
@@ -22,7 +25,7 @@ const UPLOAD = 'upload';
export default { export default {
name: 'HarvesterAirgapUpgrade', name: 'HarvesterAirgapUpgrade',
components: { components: {
Checkbox, CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo Checkbox, CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo, ImagePercentageBar, Banner
}, },
inheritAttrs: false, inheritAttrs: false,
@@ -39,7 +42,147 @@ export default {
spec: { image: '' } spec: { image: '' }
}); });
const imageValue = await this.$store.dispatch('harvester/create', { await this.initImageValue();
this.value = value;
},
beforeUnmount() {
if (this.uploadController) {
this.uploadController.abort();
}
},
data() {
return {
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: [],
};
},
computed: {
doneRoute() {
return `${ HARVESTER_PRODUCT }-c-cluster-resource`;
},
skipSingleReplicaDetachedVolFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
},
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 || '';
},
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, type: HCI.IMAGE,
metadata: { metadata: {
name: '', name: '',
@@ -50,71 +193,18 @@ export default {
spec: { spec: {
sourceType: UPLOAD, sourceType: UPLOAD,
displayName: '', displayName: '',
checksum: '' checksum: this.imageValue?.spec?.checksum || '',
}, },
}); });
this.value = value;
this.imageValue = imageValue;
},
data() {
return {
value: null,
file: {},
imageId: '',
imageSource: IMAGE_METHOD.NEW,
sourceType: UPLOAD,
imageValue: null,
errors: [],
enableLogging: true,
IMAGE_METHOD
};
},
computed: {
doneRoute() {
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
};
});
},
uploadImage() {
return this.imageSource === IMAGE_METHOD.NEW;
},
fileName() {
return this.file?.name || '';
},
canEnableLogging() {
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
},
},
methods: {
done() {
this.$router.push({
name: this.doneRoute,
params: { resource: HCI.SETTING, product: 'harvester' }
});
}, },
async save(buttonCb) { async save(buttonCb) {
let res = null; let res = null;
this.file = {};
this.errors = []; 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') })); this.errors.push(this.$store.getters['i18n/t']('validation.required', { key: this.t('generic.name') }));
buttonCb(false); buttonCb(false);
@@ -125,26 +215,11 @@ export default {
if (this.imageSource === IMAGE_METHOD.NEW) { if (this.imageSource === IMAGE_METHOD.NEW) {
this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True'; this.imageValue.metadata.annotations[HCI_ANNOTATIONS.OS_UPGRADE_IMAGE] = 'True';
if (this.sourceType === UPLOAD) { if (this.sourceType === UPLOAD && this.uploadImageId !== '') {
this.imageValue.spec.sourceType = UPLOAD; this.value.spec.image = this.uploadImageId;
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);
} else if (this.sourceType === DOWNLOAD) { } else if (this.sourceType === DOWNLOAD) {
this.imageValue.spec.sourceType = DOWNLOAD; this.imageValue.spec.sourceType = DOWNLOAD;
if (!this.imageValue.spec.url) { if (!this.imageValue.spec.url) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl')); this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.imageUrl'));
buttonCb(false); buttonCb(false);
@@ -153,12 +228,12 @@ export default {
} }
res = await this.imageValue.save(); res = await this.imageValue.save();
}
this.value.spec.image = res.id; this.value.spec.image = res.id;
}
} else if (this.imageSource === IMAGE_METHOD.EXIST) { } else if (this.imageSource === IMAGE_METHOD.EXIST) {
if (!this.imageId) { if (!this.imageId) {
this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.chooseFile')); this.errors.push(this.$store.getters['i18n/t']('harvester.setting.upgrade.chooseFile'));
buttonCb(false);
return; return;
} }
@@ -169,18 +244,87 @@ export default {
if (this.canEnableLogging) { if (this.canEnableLogging) {
this.value.spec.logEnabled = this.enableLogging; 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(); await this.value.save();
this.done(); this.done();
buttonCb(true); buttonCb(true);
} catch (e) { } catch (e) {
this.errors = exceptionToErrorsArray(e); this.errors = [e?.message] || exceptionToErrorsArray(e);
buttonCb(false); buttonCb(false);
} }
}, },
handleFileUpload() { async uploadFile(file) {
this.file = this.$refs.file.files[0]; 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() { selectFile() {
@@ -190,21 +334,27 @@ export default {
}, },
watch: { watch: {
imageSource(neu) {
if (neu !== IMAGE_METHOD.DELETE) {
this.deleteImageId = '';
}
},
'imageValue.spec.url': { 'imageValue.spec.url': {
handler(neu) { handler(neu) {
const suffixName = neu?.split('/')?.pop(); const suffixName = neu?.split('/')?.pop();
const splitName = suffixName?.split('.') || []; const splitName = suffixName?.split('.') || [];
const fileSuffix = splitName?.pop()?.toLowerCase(); 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; this.imageValue.spec.displayName = suffixName;
} }
}, },
deep: true deep: true
}, },
file(neu) { 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; this.imageValue.spec.displayName = neu.name;
} }
} }
@@ -227,9 +377,11 @@ export default {
:errors="errors" :errors="errors"
:can-yaml="false" :can-yaml="false"
finish-button-mode="upgrade" finish-button-mode="upgrade"
:validation-passed="enableUpgrade"
:cancel-event="true" :cancel-event="true"
@finish="save" @finish="save"
@cancel="done" @cancel="done"
@error="e=>errors = e"
> >
<RadioGroup <RadioGroup
v-model:value="imageSource" v-model:value="imageSource"
@@ -238,18 +390,57 @@ export default {
:options="[ :options="[
IMAGE_METHOD.NEW, IMAGE_METHOD.NEW,
IMAGE_METHOD.EXIST, IMAGE_METHOD.EXIST,
IMAGE_METHOD.DELETE,
]" ]"
:labels="[ :labels="[
t('harvester.upgradePage.uploadNew'), t('harvester.upgradePage.uploadNew'),
t('harvester.upgradePage.selectExisting'), 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 <LabeledInput
v-model.trim="imageValue.spec.displayName" v-model:value.trim="imageValue.spec.displayName"
class="mb-20" class="mb-20"
label-key="harvester.fields.name" label-key="harvester.fields.name"
required required
@@ -261,14 +452,6 @@ export default {
label-key="harvester.setting.upgrade.checksum" 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 <RadioGroup
v-model:value="sourceType" v-model:value="sourceType"
class="mb-20 image-group" class="mb-20 image-group"
@@ -285,7 +468,7 @@ export default {
<LabeledInput <LabeledInput
v-if="sourceType === 'download'" v-if="sourceType === 'download'"
v-model.trim="imageValue.spec.url" v-model:value.trim="imageValue.spec.url"
class="labeled-input--tooltip" class="labeled-input--tooltip"
required required
label-key="harvester.image.url" label-key="harvester.image.url"
@@ -298,6 +481,7 @@ export default {
<button <button
type="button" type="button"
class="btn role-primary" class="btn role-primary"
:disabled="disableUploadButton"
@click="selectFile" @click="selectFile"
> >
{{ t('harvester.image.uploadFile') }} {{ t('harvester.image.uploadFile') }}
@@ -318,28 +502,56 @@ export default {
{{ fileName ? fileName : t('harvester.generic.noFileChosen') }} {{ fileName ? fileName : t('harvester.generic.noFileChosen') }}
</span> </span>
</div> </div>
<ImagePercentageBar
v-if="showProgressBar"
class="mt-20"
:value="uploadProgress"
/>
</div> </div>
<LabeledSelect <LabeledSelect
v-else v-if="selectExistImage"
v-model:value="imageId" v-model:value="imageId"
:options="osImageOptions" :options="osImageOptions"
required required
class="mb-20" class="mb-20"
label-key="harvester.fields.image" 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> </CruResource>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
#air-gap { #air-gap {
padding: 20px;
:deep() .image-group .radio-group { :deep() .image-group .radio-group {
display: flex; display: flex;
.radio-container { .radio-container {
margin-right: 30px; margin-right: 30px;
} }
} }
.parent {
grid-template-columns:auto 40px;
}
.chooseFile { .chooseFile {
display: flex; display: flex;
align-items: center; align-items: center;
+17 -13
View File
@@ -8,7 +8,6 @@ export const docLink = (suffix, version) => {
}; };
export function getVersion(v) { export function getVersion(v) {
// e.g v1.4.0
if (process.env.VUE_APP_SERVER_VERSION) { if (process.env.VUE_APP_SERVER_VERSION) {
return process.env.VUE_APP_SERVER_VERSION; return process.env.VUE_APP_SERVER_VERSION;
} }
@@ -18,20 +17,21 @@ export function getVersion(v) {
return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`; return `v${ semver.major(v) }.${ semver.minor(v) }.${ semver.patch(v) }`;
} catch (error) { } catch (error) {
// fallback to the latest version // fallback to the latest version
return latestVersion(Object.keys(RELEASE_FEATURES)); return getLatestVersion();
} }
} }
function latestVersion(versions) { function getLatestVersion() {
return versions.sort((a, b) => semver.compare(a, b)).pop(); 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 getLatestCompatibleVersion(version) {
function latestMinorVersion(v) { const allVersions = Object.keys(RELEASE_FEATURES).filter(semver.valid);
const minor = `v${ semver.major(v) }.${ semver.minor(v) }`; const compatible = allVersions.filter((v) => semver.lte(v, version)).sort(semver.rcompare);
const minorVersions = Object.keys(RELEASE_FEATURES).filter((version) => version.startsWith(minor));
return latestVersion(minorVersions); return compatible[0] || getLatestVersion();
} }
export const featureEnabled = (featureKey, serverVersion) => { export const featureEnabled = (featureKey, serverVersion) => {
@@ -46,12 +46,16 @@ export const featureEnabled = (featureKey, serverVersion) => {
return false; return false;
} }
let releasedFeatures = RELEASE_FEATURES[version]; const compatibleVersion = getLatestCompatibleVersion(version);
const releasedFeatures = RELEASE_FEATURES?.[compatibleVersion];
if (!releasedFeatures) { if (!Array.isArray(releasedFeatures)) {
const fallback = latestMinorVersion(version); // 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.`
);
releasedFeatures = RELEASE_FEATURES[fallback]; return false;
} }
return releasedFeatures.includes(featureKey); 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