Compare commits

..
38 Commits
Author SHA1 Message Date
Andy Lee 4caecb48a7 chore: update version to v1.5.2-rc1
Signed-off-by: Andy Lee <andy.lee@suse.com>
2025-09-03 11:37:16 +08:00
renovate[bot] 8d1d370197 fix(deps): update minor dependencies (#499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 15:03:08 +08:00
renovate[bot] df9992146c chore(deps): update patch digest dependencies (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 09:33:53 +00:00
mergify[bot]andAndy Lee acbc120dc1 fix: featureFlags is undefined when installing lower version of ui-ext (#443) (#446)
* fix: featureFlags is undefined if install lower version of ui-ext

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

(cherry picked from commit 182d92d80b)

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

* ci: add package version check

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

* ci: extract check version in a script

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

* ci: update env variable

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

---------

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

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

* fix: conflict

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

---------

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

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

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

* feat: change dialog wordings

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

* refactor: update protip wording

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

---------

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

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

* Resolve conflict

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

* Fix conflict

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

---------

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

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

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

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

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

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

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

* Add failed and success banner after image uploaded

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

* add delete image feature

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

* add skip checking single-replica detached volumes checkbox

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

* change delete image flow

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

* Reuse ConfirmRelatedToRemoveDialog

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

---------

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

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

* resolve conflict

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

---------

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

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



* move uploadFile action



* change os upgrage ui to standard feature



* Add uploadImage cancel



---------



(cherry picked from commit 6d8f6579c7)

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

* refactor feature flags structure

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

* Replace with FEATURE_FLAGS and valid and sort versions

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

---------

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

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

* resolve conflict

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

---------

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



* change condition eq 0



* add allNotExist back



---------


(cherry picked from commit aa9dd32cce)

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

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

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

# Conflicts:
#	yarn.lock

* solve conflict in yarn.lock

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

---------

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

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



(cherry picked from commit 1e270268d8)

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

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

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

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