Compare commits

..
Author SHA1 Message Date
andy.lee 0f03fc0805 change mergify.yml base branch to main
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:39:04 +08:00
andy.lee 6d4a7cd1b1 add mergify.yml
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 21:38:55 +08:00
Andy Lee f69d445ef7 Merge pull request #66 from harvester/revert-65-test_word
Revert "setting test word for v1.5 release branch"
2025-01-06 20:59:07 +08:00
Andy Lee d358a0a618 Revert "setting test word for v1.5 release branch" 2025-01-06 20:57:38 +08:00
Andy Lee 47841e49b1 Merge pull request #65 from a110605/test_word
setting test word for v1.5 release branch
2025-01-06 20:51:27 +08:00
andy.lee 57c7f0f856 setting test word
Signed-off-by: andy.lee <andy.lee@suse.com>
2025-01-06 20:46:47 +08:00
67 changed files with 1002 additions and 1050 deletions
@@ -1,35 +0,0 @@
name: Build Harvester Catalog Image and Publish on Release
on:
workflow_dispatch:
release:
types: [published]
defaults:
run:
shell: bash
working-directory: ./
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-and-push-extension-catalog:
needs: lint
uses: ./.github/workflows/build-extension-catalog.yml
permissions:
actions: write
contents: read
packages: write
id-token: write
with:
registry_target: docker.io
registry_user: rancher
secrets: inherit
@@ -1,4 +1,4 @@
name: Build Standalone on PR Merge
name: Build Dashboard (Branch)
on:
push:
@@ -11,16 +11,16 @@ on:
- main
- 'release-harvester-v*'
- '*-dev'
types:
types:
- merged
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/run-lint.yaml
uses: ./.github/workflows/test.yaml
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml
uses: ./.github/workflows/build-and-upload.yaml
needs:
- build-validation
permissions:
@@ -1,4 +1,4 @@
name: Build Standalone on Release
name: Build Dashboard (Release)
on:
push:
tags:
@@ -7,10 +7,10 @@ on:
jobs:
build-validation:
name: Build Test
uses: ./.github/workflows/run-lint.yaml
uses: ./.github/workflows/test.yaml
build:
name: Build and Upload Package
uses: ./.github/workflows/build-and-publish-standalone.yaml
uses: ./.github/workflows/build-and-upload.yaml
needs:
- build-validation
permissions:
@@ -1,4 +1,4 @@
name: Build Harvester Dashboard and Publish
name: Build Harvester Dashboard and Upload
on:
workflow_call:
@@ -1,70 +0,0 @@
name: Build and release Extension Catalog Image to registry
on:
workflow_call:
inputs:
registry_target:
required: true
type: string
registry_user:
required: true
type: string
outputs:
build-extension-catalog-job-status:
value: ${{ jobs.build-extension-catalog.outputs.build-status }}
jobs:
build-and-push-extension-catalog:
name: Build container image
if: github.ref_type == 'tag' || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
packages: write
id-token: write
outputs:
build-status: ${{ job.status }}
steps:
- name: Read Secrets
uses: rancher-eio/read-vault-secrets@main
with:
secrets: |
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials username | DOCKER_USERNAME ;
secret/data/github/repo/${{ github.repository }}/dockerhub/rancher/credentials password | DOCKER_PASSWORD ;
- name: Checkout repository (normal flow)
uses: actions/checkout@v4
- name: Enable Corepack
run: corepack enable
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ env.DOCKER_USERNAME }}
password: ${{ env.DOCKER_PASSWORD }}
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: v3.8.0
- name: Setup Nodejs with yarn caching
uses: actions/setup-node@v4
with:
node-version: '20'
cache: yarn
- name: Install dependencies
run: yarn
- name: Build and push UI image
run: |
publish="yarn publish-pkgs -cp -r ${{ inputs.registry_target }} -o ${{ inputs.registry_user }}"
$publish
@@ -0,0 +1,34 @@
name: Build and Release Extension Charts
on:
workflow_dispatch:
release:
types: [released]
defaults:
run:
shell: bash
working-directory: ./
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-extension-charts:
needs:
- lint
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions:
actions: write
contents: write
deployments: write
pages: write
with:
target_branch: gh-pages
tagged_release: ${{ github.ref_name }}
@@ -1,136 +0,0 @@
name: Build and Release Extension Charts on PR Merge
on:
push:
branches:
- 'release-harvester-v*'
jobs:
setup-target-branch:
runs-on: ubuntu-latest
outputs:
target_branch: ${{ steps.get-version.outputs.target_branch }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine target branch
id: get-version
run: |
if [[ "${{ github.ref_name }}" =~ ^release-harvester-v([0-9]+\.[0-9]+)$ ]]; then
TARGET_BRANCH="v${BASH_REMATCH[1]}-head"
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_ENV
echo "target_branch=${TARGET_BRANCH}" >> $GITHUB_OUTPUT
else
echo "Error: invalid branch format." && exit 1
fi
- name: Ensure target branch exists
run: |
git fetch --all
if ! git ls-remote --exit-code --heads origin "${{ env.target_branch }}"; then
git checkout gh-pages
git checkout -b "${{ env.target_branch }}"
git push origin "${{ env.target_branch }}"
fi
extract-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract version from package.json
id: get_version
run: |
VERSION=$(jq -r '.version' pkg/harvester/package.json)
echo "VERSION=${VERSION}" >> $GITHUB_ENV
echo "version=${VERSION}" >> $GITHUB_OUTPUT
build-extension-charts:
needs:
- setup-target-branch
- extract-version
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup environment
run: |
corepack enable
yarn install --frozen-lockfile
- name: Setup Helm
uses: azure/setup-helm@v3
with:
version: v3.8.0
- name: Build Helm charts
run: |
yarn publish-pkgs -s ${{ github.repository }} -b ${{ needs.setup-target-branch.outputs.target_branch }} -t harvester-${{ needs.extract-version.outputs.version }}
- name: Upload charts artifact
uses: actions/upload-artifact@v4
with:
name: charts
path: tmp
release:
needs:
- setup-target-branch
- extract-version
- build-extension-charts
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout release branch
uses: actions/checkout@v4
with:
ref: '${{ github.ref_name }}'
- name: Get last commit hash
id: last_commit
run: |
LAST_COMMIT=$(git rev-parse HEAD)
echo "LAST_COMMIT=${LAST_COMMIT}" >> $GITHUB_ENV
- name: Checkout target branch
uses: actions/checkout@v4
with:
ref: '${{ needs.setup-target-branch.outputs.target_branch }}'
- name: Remove old artifacts
run: |
rm -rf extensions/harvester/${{ needs.extract-version.outputs.version }}
rm -rf charts/harvester/${{ needs.extract-version.outputs.version }}
rm -f assets/harvester/harvester-${{ needs.extract-version.outputs.version }}.tgz
- name: Configure Git
run: |
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: charts
- name: Commit and push artifacts
run: |
git add ./{assets,charts,extensions,index.yaml}
git commit -m "CI Build Artifacts (commit: ${{ env.LAST_COMMIT }}, version: ${{ needs.extract-version.outputs.version }})"
git push origin ${{ needs.setup-target-branch.outputs.target_branch }}
- name: Run Helm chart releaser
uses: helm/chart-releaser-action@v1.4.1
with:
charts_dir: ./charts
env:
CR_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
CR_SKIP_EXISTING: true
@@ -1,53 +0,0 @@
name: Build and Release Extension Charts on Release
on:
workflow_dispatch:
release:
types: [released]
defaults:
run:
shell: bash
working-directory: ./
jobs:
setup-release-tag:
runs-on: ubuntu-latest
outputs:
release_tag: ${{ steps.determine_tag.outputs.release_tag }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine release tag
id: determine_tag
run: |
if [[ "${{ github.ref_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:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run tests
uses: ./.github/actions/lint
build-extension-charts:
needs:
- setup-release-tag
- lint
uses: rancher/dashboard/.github/workflows/build-extension-charts.yml@master
permissions:
actions: write
contents: write
deployments: write
pages: write
with:
target_branch: gh-pages
tagged_release: '${{ needs.setup-release-tag.outputs.release_tag }}'
+5 -6
View File
@@ -1,6 +1,6 @@
{
"name": "harvester-ui-extension",
"version": "1.0.6",
"version": "0.1.0",
"private": false,
"engines": {
"node": ">=20.0.0"
@@ -11,16 +11,15 @@
"color": "4.2.3",
"ip": "2.0.1",
"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"
"sortablejs-vue3": "^1.2.11",
"yaml": "^2.5.1"
},
"resolutions": {
"@types/node": "~20.10.0",
"cronstrue": "2.50.0",
"d3-color": "3.1.0",
"ejs": "3.1.10",
"follow-redirects": "1.15.6",
"ejs": "3.1.9",
"follow-redirects": "1.15.2",
"glob": "7.2.3",
"glob-parent": "6.0.2",
"json5": "2.2.3",
+1 -9
View File
@@ -1,9 +1 @@
const baseConfig = require('@rancher/shell/pkg/babel.config');
module.exports = {
...baseConfig,
plugins: [
...(baseConfig.plugins || []),
'@babel/plugin-transform-class-static-block'
]
};
module.exports = require('@rancher/shell/pkg/babel.config');
+14 -26
View File
@@ -25,9 +25,6 @@ export default {
return Array.from(new Set(options));
},
enableFilterButton() {
return this.rows.some((r) => r.sourceSchedule !== undefined);
}
},
methods: {
@@ -66,30 +63,27 @@ export default {
</script>
<template>
<div
class="vm-schedule-filter"
>
<span
v-if="selected"
class="banner-item bg-warning"
>
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}
<i
class="icon icon-close ml-5"
@click="remove"
/>
</span>
<div class="vm-schedule-filter">
<template>
<span
v-if="selected"
class="banner-item bg-warning"
>
{{ t('harvester.tableHeaders.vmSchedule') }}{{ selected ? ` = ${selected}`: '' }}<i
class="icon icon-close ml-5"
@click="remove"
/>
</span>
</template>
<v-dropdown
popper-class="vm-schedule-dropdown"
:triggers="scheduleOptions.length ? ['click'] : []"
placement="bottom-end"
offset="1"
:distance="20"
>
<button
ref="actionDropDown"
class="btn bg-primary mr-10"
:disabled="!enableFilterButton"
>
<slot name="title">
{{ t('harvester.fields.filterSchedule') }}
@@ -104,7 +98,7 @@ export default {
name="model"
:options="scheduleOptions"
:labels="scheduleOptions"
@update:value="onSelect"
@input="onSelect"
/>
</div>
</template>
@@ -112,12 +106,6 @@ export default {
</div>
</template>
<style lang="scss">
.vm-schedule-dropdown .v-popper__arrow-container {
display: none;
}
</style>
<style lang="scss" scoped>
.vm-schedule-filter {
display: inline-block;
@@ -190,7 +190,6 @@ export default {
v-clean-tooltip="{
placement: 'bottom-left',
}"
popper-class="upgrade-header-dropdown"
class="hand"
>
<slot name="button-content">
@@ -273,7 +272,7 @@ export default {
<p
v-if="overallMessage"
class="text-error mb-20"
class="text-warning mb-20"
>
{{ overallMessage }}
</p>
@@ -301,14 +300,14 @@ export default {
<ProgressBarList
:title="t('harvester.upgradePage.upgradeNode')"
:percent="nodesPercent"
:precent="nodesPercent"
:list="nodesStatus"
/>
<p class="bordered-section"></p>
<ProgressBarList
:title="t('harvester.upgradePage.upgradeSysService')"
:percent="sysServiceTotal"
:precent="sysServiceTotal"
:list="sysServiceUpgradeMessage"
/>
</div>
@@ -339,12 +338,6 @@ export default {
</div>
</template>
<style lang="scss">
.upgrade-header-dropdown .v-popper__arrow-container {
display: none;
}
</style>
<style lang="scss" scoped>
a {
float: right;
@@ -12,7 +12,7 @@ export default {
default: ''
},
percent: {
precent: {
type: Number,
default: 0
},
@@ -39,18 +39,16 @@ export default {
<template>
<div class="bar-list">
<h4>{{ title }} <span class="float-r text-info">{{ percent }}%</span></h4>
<h4>{{ title }} <span class="float-r text-info">{{ precent }}%</span></h4>
<div>
<div>
<Collapse v-model:open="open">
<template #title>
<div class="total-bar">
<span class="bar">
<PercentageBar
:model-value="percent"
preferred-direction="MORE"
/>
</span>
<span class="bar"><PercentageBar
:model-value="precent"
preferred-direction="MORE"
/></span>
<span
class="on-off"
@click="handleSwitch"
@@ -58,26 +56,28 @@ export default {
</div>
</template>
<div class="custom-content">
<div
v-for="(item, i) in list"
:key="i"
>
<p>
{{ item.name }} <span
class="status"
:class="{ [item.state]: true }"
>{{ item.state }}</span>
</p>
<PercentageBar
:model-value="item.percent"
preferred-direction="MORE"
/>
<p class="warning">
{{ item.message }}
</p>
<template>
<div class="custom-content">
<div
v-for="(item, i) in list"
:key="i"
>
<p>
{{ item.name }} <span
class="status"
:class="{ [item.state]: true }"
>{{ item.state }}</span>
</p>
<PercentageBar
:model-value="item.percent"
preferred-direction="MORE"
/>
<p class="warning">
{{ item.message }}
</p>
</div>
</div>
</div>
</template>
</Collapse>
</div>
</div>
@@ -93,7 +93,7 @@ export default {
.total-bar {
display: flex;
user-select: none;
> .bar {
.bar {
width: 85%;
}
.on-off {
@@ -102,22 +102,25 @@ export default {
}
}
.custom-content {
margin-bottom: 14px;
p {
margin-bottom: 4px;
}
.status {
float: right;
}
.Succeeded, .Upgrading, .Pending {
color: var(--success);
}
.failed {
color: var(--error)
}
.warning {
color: var(--error);
.item {
margin-bottom: 14px;
p {
margin-bottom: 4px;
}
.status {
float: right;
}
.Succeeded, .Upgrading, .Pending {
color: var(--success);
}
.failed {
color: var(--error)
}
.warning {
color: var(--error);
}
}
}
}
</style>
@@ -60,8 +60,7 @@ export default {
});
this.queue = [];
}, 10),
deep: true
}, 5)
}
},
@@ -6,7 +6,6 @@ import CreateEditView from '@shell/mixins/create-edit-view';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import { HCI_SETTING } from '../../config/settings';
import UnitInput from '@shell/components/form/UnitInput';
const DEFAULT_TYPE = 's3';
@@ -14,7 +13,7 @@ export default {
name: 'HarvesterEditBackupTarget',
components: {
LabeledInput, LabeledSelect, Tip, Password, MessageLink, UnitInput
LabeledInput, LabeledSelect, Tip, Password, MessageLink
},
mixins: [CreateEditView],
@@ -25,9 +24,7 @@ export default {
try {
parseDefaultValue = JSON.parse(this.value.value);
} catch (error) {
parseDefaultValue = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
parseDefaultValue = { type: '', endpoint: '' };
}
// set default type to s3
@@ -65,10 +62,6 @@ export default {
}];
},
refreshIntervalInSecondEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('refreshIntervalInSecond');
},
isS3() {
return this.parseDefaultValue.type === DEFAULT_TYPE;
},
@@ -90,9 +83,7 @@ export default {
try {
parseDefaultValue = JSON.parse(neu.value);
} catch (err) {
parseDefaultValue = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
parseDefaultValue = { type: '', endpoint: '' };
}
this['parseDefaultValue'] = parseDefaultValue;
@@ -120,6 +111,7 @@ export default {
if (this.isS3 && !this.parseDefaultValue.virtualHostedStyle) {
this.parseDefaultValue.virtualHostedStyle = false;
}
if (!this.parseDefaultValue.type) {
delete this.value['value'];
} else {
@@ -128,9 +120,7 @@ export default {
},
useDefault() {
this['parseDefaultValue'] = {
type: '', endpoint: '', refreshIntervalInSeconds: 0
};
this['parseDefaultValue'] = { type: '', endpoint: '' };
}
}
};
@@ -149,23 +139,6 @@ export default {
:options="typeOption"
@update:value="update"
/>
<UnitInput
v-if="refreshIntervalInSecondEnabled"
v-model:value="parseDefaultValue.refreshIntervalInSeconds"
:suffix="parseDefaultValue.refreshIntervalInSeconds <= 1 ? 'Second' : 'Seconds'"
:label="t('harvester.backup.refreshInterval.label')"
:mode="mode"
:positive="true"
class="mb-5"
required
@update:value="update"
/>
<Tip
v-if="refreshIntervalInSecondEnabled"
class="mb-20"
icon="icon icon-info"
:text="t('harvester.backup.refreshInterval.tip')"
/>
<LabeledInput
v-model:value="parseDefaultValue.endpoint"
class="mb-5"
@@ -75,11 +75,7 @@ export default {
const csiDrivers = this.$store.getters[`${ inStore }/all`](CSI_DRIVER) || [];
return this.configArr.length >= csiDrivers.length;
},
allowEmptySnapshotClassNameFeatureEnabled() {
return this.$store.getters['harvester-common/getFeatureEnabled']('allowEmptySnapshotClassName');
},
}
},
methods: {
@@ -143,7 +139,7 @@ export default {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.volumeSnapshotClassName') }, true));
}
if (!this.allowEmptySnapshotClassNameFeatureEnabled && !config.value.backupVolumeSnapshotClassName) {
if (!config.value.backupVolumeSnapshotClassName) {
errors.push(this.t('validation.required', { key: this.t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName') }, true));
}
});
@@ -162,22 +158,10 @@ export default {
this.configArr.splice(idx, 1);
},
isBackupVolumeSnapshotRequired(driver) {
return driver === LONGHORN_DRIVER;
},
disableEdit(driver) {
return driver === LONGHORN_DRIVER;
},
isBackupVolumeSnapshotClassNameDisabled(driver) {
return driver === LONGHORN_DRIVER || this.allowEmptySnapshotClassNameFeatureEnabled;
},
isBackupVolumeSnapshotClassNameSelectable(driver) {
return driver === LONGHORN_DRIVER;
},
add() {
this.configArr.push({
key: '',
@@ -200,7 +184,6 @@ export default {
<InfoBox
v-for="(driver, idx) in configArr"
:key="idx"
class="box"
>
<button
:disabled="disableEdit(driver.key)"
@@ -243,11 +226,10 @@ export default {
<LabeledSelect
v-model:value="driver.value.backupVolumeSnapshotClassName"
:mode="mode"
:disabled="isBackupVolumeSnapshotClassNameDisabled(driver.key)"
required
:disabled="disableEdit(driver.key)"
:options="getVolumeSnapshotOptions(driver.key)"
:required="isBackupVolumeSnapshotRequired(driver.key)"
:label="t('harvester.setting.csiDriverConfig.backupVolumeSnapshotClassName')"
@selectable="isBackupVolumeSnapshotClassNameSelectable(driver.key)"
@keydown.native.enter.prevent="()=>{}"
@update:value="update"
/>
@@ -59,7 +59,7 @@ export default {
name="model"
:options="[true,false]"
:labels="[t('generic.enabled'), t('generic.disabled')]"
@update:value="update"
@input="update"
/>
<LabeledInput
+2 -28
View File
@@ -17,12 +17,6 @@ const featuresV132 = [
'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 = [
...featuresV132,
@@ -35,36 +29,16 @@ const featuresV140 = [
'improveMaintenanceMode',
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.1
// TODO: add v1.4.1 official release note
// https://github.com/harvester/dashboard/releases/tag/v1.4.1-rc1
const featuresV141 = [
...featuresV140
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.2
const featuresV142 = [
...featuresV141,
'refreshIntervalInSecond',
'allowEmptySnapshotClassName'
];
// https://github.com/harvester/dashboard/releases/tag/v1.4.3-rc1
const featuresV143 = [
...featuresV142,
];
// https://github.com/harvester/harvester-ui-extension/releases/tag/v1.5.0
const featuresV150 = [
...featuresV142
];
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.4.3': featuresV143,
'v1.5.0': featuresV150
};
@@ -24,24 +24,35 @@ export default {
},
async fetch() {
await allHash({
const hash = await allHash({
vms: this.$store.dispatch('harvester/findAll', { type: HCI.VM }),
vmis: this.$store.dispatch('harvester/findAll', { type: HCI.VMI }),
allClusterNetwork: this.$store.dispatch('harvester/findAll', { type: HCI.CLUSTER_NETWORK }),
});
const instanceMap = {};
(hash.vmis || []).forEach((vmi) => {
const vmiUID = vmi?.metadata?.ownerReferences?.[0]?.uid;
if (vmiUID) {
instanceMap[vmiUID] = vmi;
}
});
this.allClusterNetwork = hash.allClusterNetwork;
this.rows = hash.vms.filter((row) => {
return instanceMap[row.metadata?.uid]?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME];
});
},
data() {
return {
rows: [],
allClusterNetwork: []
};
},
computed: {
allClusterNetwork() {
return this.$store.getters['harvester/all'](HCI.CLUSTER_NETWORK);
},
rows() {
const vms = this.$store.getters['harvester/all'](HCI.VM);
return vms.filter((vm) => vm.vmi?.status?.nodeName === this.node?.metadata?.labels?.[HOSTNAME]);
},
headers() {
return [
STATE,
@@ -76,6 +87,8 @@ export default {
];
},
},
methods: {}
};
</script>
@@ -166,8 +166,6 @@ export default {
<Banner
v-for="(error, i) in errors"
:key="i"
:label="error"
color="error"
/>
</div>
</template>
+7 -6
View File
@@ -106,12 +106,6 @@ export default {
:label="t('harvester.modal.cloneVM.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
:label="err"
color="error"
/>
</template>
<template #actions>
@@ -131,6 +125,13 @@ export default {
@click="create"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -137,12 +137,6 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
:label="err"
color="error"
/>
</template>
<template #actions>
@@ -162,6 +156,11 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -103,12 +103,6 @@ export default {
:label="t('generic.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -127,6 +121,13 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -120,12 +120,6 @@ export default {
v-model:value="description"
:label="t('harvester.modal.createTemplate.description')"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -144,6 +138,11 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -128,12 +128,6 @@ export default {
<Banner color="warning">
<span>{{ t('harvester.modal.ejectCDROM.warnTip') }}</span>
</Banner>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -153,6 +147,13 @@ export default {
@click="remove"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -164,12 +164,6 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -188,6 +182,11 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -161,12 +161,6 @@ export default {
:placeholder="t('harvester.modal.migration.fields.nodeName.placeholder')"
:options="nodeNameList"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template
@@ -187,6 +181,12 @@ export default {
@click="apply"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
</Card>
</template>
@@ -95,12 +95,6 @@ export default {
suffix="GiB"
class="mb-20"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -114,6 +108,12 @@ export default {
</button>
<AsyncButton @click="save" />
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -136,12 +136,6 @@ export default {
:options="backupOption"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -160,6 +154,11 @@ export default {
@click="saveRestore"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -90,12 +90,6 @@ export default {
v-clean-html="t('harvester.virtualMachine.unplug.title', { name: diskName })"
class="text-default-text"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -117,6 +111,13 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
@@ -132,12 +132,6 @@ export default {
:label="t('generic.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -156,6 +150,11 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -107,12 +107,6 @@ export default {
:placeholder="t('harvester.harvesterVlanConfigMigrateDialog.targetClusterNetwork.placeholder')"
:options="clusterNetworks"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -131,6 +125,11 @@ export default {
@click="apply"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
+6 -6
View File
@@ -70,16 +70,16 @@ export default {
v-clean-html="t('harvester.modal.restart.tip')"
class="pl-10 pr-10"
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
<div class="bottom">
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
<div class="buttons">
<button
class="btn role-secondary mr-10"
@@ -145,12 +145,6 @@ export default {
class="mt-20"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -168,6 +162,10 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
+6 -6
View File
@@ -79,12 +79,6 @@ export default {
:label="t('harvester.modal.snapshot.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -102,6 +96,12 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</div>
</template>
</Card>
+4 -6
View File
@@ -98,12 +98,6 @@ export default {
:label="t('harvester.modal.volumeClone.name')"
required
/>
<Banner
v-for="(err, i) in errors"
:key="i"
color="error"
:label="err"
/>
</template>
<template #actions>
@@ -121,6 +115,10 @@ export default {
@click="save"
/>
</div>
<Banner
v-for="(err, i) in errors"
:key="i"
/>
</div>
</template>
</Card>
@@ -10,7 +10,7 @@ import { Banner } from '@components/Banner';
import CreateEditView from '@shell/mixins/create-edit-view';
const DEFAULT_VALUE = { image: { repository: 'rancher/harvester-nvidia-driver-toolkit' } };
const DEFAULT_VALUE = { image: { repo: 'rancher/harvester-nvidia-driver-toolkit' } };
export default {
name: 'EditAddonNvidiaDriverToolkit',
@@ -122,7 +122,7 @@ export default {
>
<div class="col span-6">
<LabeledInput
v-model:value="valuesContentJson.image.repository"
v-model:value="valuesContentJson.image.repo"
:mode="mode"
:required="true"
label-key="harvester.addons.nvidiaDriverToolkit.image.repository"
@@ -1,31 +1,34 @@
<script>
import merge from 'lodash/merge';
import jsyaml from 'js-yaml';
import { LabeledInput } from '@components/Form/LabeledInput';
import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab';
import { RadioGroup } from '@components/Form/Radio';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import CreateEditView from '@shell/mixins/create-edit-view';
import { STORAGE_CLASS } from '@shell/config/types';
import { allHash } from '@shell/utils/promise';
import { set, get, clone } from '@shell/utils/object';
const VALUES_YAML_KEYS = [
'resources.requests.cpu',
'resources.requests.memory',
'resources.limits.cpu',
'resources.limits.memory',
'pvcClaim.enabled',
'pvcClaim.size',
'pvcClaim.storageClassName',
];
const DEFAULT_VALUES = {
resources: {
requests: {
cpu: '0.5',
memory: '2Gi'
},
limits: {
cpu: '2',
memory: '4Gi'
}
},
pvcClaim: {
enabled: false,
size: '200Gi',
storageClassName: ''
}
'resources.requests.cpu': '0.5',
'resources.requests.memory': '2Gi',
'resources.limits.cpu': '2',
'resources.limits.memory': '4Gi',
'pvcClaim.enabled': false,
'pvcClaim.size': '200Gi',
'pvcClaim.storageClassName': '',
};
export default {
@@ -61,7 +64,19 @@ export default {
},
data() {
const valuesContent = this.parseValuesContent();
let valuesObj = {};
try {
valuesObj = JSON.parse(this.value?.spec?.valuesContent || '{}');
} catch (err) {}
const valuesContent = clone(valuesObj);
VALUES_YAML_KEYS.map((key) => {
if (!get(valuesObj, key)) {
set(valuesContent, key, DEFAULT_VALUES[key]);
}
});
return { valuesContent };
},
@@ -85,21 +100,8 @@ export default {
},
methods: {
parseValuesContent() {
try {
return merge({}, DEFAULT_VALUES, jsyaml.load(this.value.spec.valuesContent));
} catch (err) {
this.$store.dispatch('growl/fromError', {
title: this.$store.getters['i18n/t']('generic.notification.title.error'),
err: err.data || err,
}, { root: true });
return DEFAULT_VALUES;
}
},
update() {
this.value.spec.valuesContent = jsyaml.dump(this.valuesContent);
set(this.value, 'spec.valuesContent', JSON.stringify(this.valuesContent));
},
setDefaultClassName() {
@@ -93,7 +93,7 @@ export default {
const selectedProviderDefault = providers.find((p) => p.name === selectedProvider)?.default || providers[0].default;
if (this.mode !== _VIEW) {
set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
this.$set(this.value.spec, selectedProvider, this.value.spec[selectedProvider] || clone(selectedProviderDefault));
}
return {
@@ -233,7 +233,7 @@ export default {
:disabled="isEdit || isView"
:mode="mode"
row
@update:value="onTypeChange"
@input="onTypeChange"
/>
</div>
<div class="row mb-30">
@@ -1,11 +1,14 @@
<script>
import { mapGetters } from 'vuex';
import { randomStr } from '@shell/utils/string';
import { clone } from '@shell/utils/object';
import { LabeledInput } from '@components/Form/LabeledInput';
import LabeledSelect from '@shell/components/form/LabeledSelect';
import ModalWithCard from '@shell/components/ModalWithCard';
import { clone } from '@shell/utils/object';
import { _VIEW } from '@shell/config/query-params';
import { NAMESPACE } from '@shell/config/types';
import { HCI } from '../../types';
@@ -123,10 +126,10 @@ export default {
this.checkedSsh = neu;
},
checkedSsh(val) {
// if click on Create a New...
if (val.includes(_NEW)) {
this.checkedSsh = this.checkedSsh.filter((key) => key !== _NEW);
checkedSsh(val, old) {
if ( val.includes(_NEW)) {
this['checkedSsh'] = old;
this.update();
this.show();
}
}
@@ -172,7 +175,6 @@ export default {
if (res.id) {
this.checkedSsh.push(`${ this.namespace }/${ this.sshName }`);
this.update();
}
},
@@ -241,7 +243,7 @@ export default {
<LabeledSelect
v-model:value="checkedSsh"
:label="t('harvester.virtualMachine.input.sshKey')"
:taggable="!disabled"
:taggable="true"
:mode="mode"
:multiple="true"
:searchable="searchable"
@@ -46,13 +46,10 @@ export default {
this[key] = res[key];
}
const vGpus = this.vm.isOff ? [
uniq([
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
] : [
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
];
uniq(vGpus).forEach((name) => {
]).forEach((name) => {
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
this.selectedDevices.push(name);
}
@@ -1,5 +1,5 @@
<script>
import { VueDraggableNext } from 'vue-draggable-next';
import { Sortable } from 'sortablejs-vue3';
import InfoBox from '@shell/components/InfoBox';
import { Banner } from '@components/Banner';
import BadgeStateFormatter from '@shell/components/formatter/BadgeStateFormatter';
@@ -22,7 +22,7 @@ export default {
emits: ['update:value'],
components: {
Banner, BadgeStateFormatter, VueDraggableNext, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
Banner, BadgeStateFormatter, Sortable, InfoBox, LabeledInput, UnitInput, LabeledSelect, ModalWithCard
},
props: {
@@ -272,130 +272,129 @@ export default {
color="info"
label-key="harvester.virtualMachine.volume.dragTip"
/>
<VueDraggableNext
<Sortable
:list="rows"
:disabled="isView"
:options="{disabled: isView}"
item-key="id"
@end="update"
>
<div
v-for="(volume, i) in rows"
:key="volume.id"
>
<InfoBox class="box">
<button
v-if="!isView"
type="button"
class="role-link btn btn-sm remove"
@click="removeVolume(volume)"
>
<i class="icon icon-x" />
</button>
<button
v-if="volume.hotpluggable && isView"
type="button"
class="role-link btn remove"
@click="unplugVolume(volume)"
>
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
</button>
<h3>
<span
v-if="volume.to && isVirtualType"
class="title"
>
<router-link :to="volume.to">
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
</router-link>
<BadgeStateFormatter
v-if="volume.pvc"
class="ml-10 state"
:arbitrary="true"
:row="volume.pvc"
:value="volume.pvc.state"
/>
<a
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
class="ml-5 resource-external"
rel="nofollow noopener noreferrer"
target="_blank"
:href="volume.pvc.resourceExternalLink.url"
>
<i class="icon icon-external-link" />
</a>
</span>
<span v-else>
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
</span>
</h3>
<div>
<component
:is="componentFor(volume.source)"
:value="rows[i]"
:rows="rows"
:namespace="namespace"
:is-create="isCreate"
:is-edit="isEdit"
:is-view="isView"
:is-virtual-type="isVirtualType"
:mode="mode"
:idx="i"
:validate-required="validateRequired"
@update="update"
/>
</div>
<div class="bootOrder">
<div
<template #item="{element: volume, index: i}">
<div :key="volume.name">
<InfoBox class="box">
<button
v-if="!isView"
class="mr-15"
type="button"
class="role-link btn btn-sm remove"
@click="removeVolume(volume)"
>
<button
:disabled="i === 0"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, false)"
<i class="icon icon-x" />
</button>
<button
v-if="volume.hotpluggable && isView"
type="button"
class="role-link btn remove"
@click="unplugVolume(volume)"
>
{{ t('harvester.virtualMachine.unplug.detachVolume') }}
</button>
<h3>
<span
v-if="volume.to && isVirtualType"
class="title"
>
<i class="icon icon-lg icon-chevron-up"></i>
</button>
<router-link :to="volume.to">
{{ t('harvester.virtualMachine.volume.edit') }} {{ headerFor(volume.source) }}
</router-link>
<button
:disabled="i === rows.length -1"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, true)"
>
<i class="icon icon-lg icon-chevron-down"></i>
</button>
<BadgeStateFormatter
v-if="volume.pvc"
class="ml-10 state"
:arbitrary="true"
:row="volume.pvc"
:value="volume.pvc.state"
/>
<a
v-if="dev && !!volume.pvc && !!volume.pvc.resourceExternalLink"
v-clean-tooltip="t(volume.pvc.resourceExternalLink.tipsKey || 'generic.resourceExternalLinkTips')"
class="ml-5 resource-external"
rel="nofollow noopener noreferrer"
target="_blank"
:href="volume.pvc.resourceExternalLink.url"
>
<i class="icon icon-external-link" />
</a>
</span>
<span v-else>
{{ headerFor(volume.source, !!volume?.volumeBackups) }}
</span>
</h3>
<div>
<component
:is="componentFor(volume.source)"
:value="rows[i]"
:rows="rows"
:namespace="namespace"
:is-create="isCreate"
:is-edit="isEdit"
:is-view="isView"
:is-virtual-type="isVirtualType"
:mode="mode"
:idx="i"
:validate-required="validateRequired"
@update="update"
/>
</div>
<div class="text-muted">
bootOrder: {{ i + 1 }}
</div>
</div>
<div class="bootOrder">
<div
v-if="!isView"
class="mr-15"
>
<button
:disabled="i === 0"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, false)"
>
<i class="icon icon-lg icon-chevron-up"></i>
</button>
<div class="mt-15">
<Banner
v-if="volume.volumeStatus && !isCreate"
class="volume-status"
color="warning"
:label="ucFirst(volume.volumeStatus)"
/>
<Banner
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
color="error"
:label="ucFirst(value.volumeBackups.error.message)"
/>
<Banner
v-if="isLonghornV2(volume) && !isView"
color="warning"
:label="t('harvester.volume.longhorn.disableResize')"
/>
</div>
</InfoBox>
</div>
</VueDraggableNext>
<button
:disabled="i === rows.length -1"
class="btn btn-sm role-primary"
@click.prevent="changeSort(i, true)"
>
<i class="icon icon-lg icon-chevron-down"></i>
</button>
</div>
<div class="text-muted">
bootOrder: {{ i + 1 }}
</div>
</div>
<div class="mt-15">
<Banner
v-if="volume.volumeStatus && !isCreate"
class="volume-status"
color="warning"
:label="ucFirst(volume.volumeStatus)"
/>
<Banner
v-if="value.volumeBackups && value.volumeBackups.error && value.volumeBackups.error.message"
color="error"
:label="ucFirst(value.volumeBackups.error.message)"
/>
<Banner
v-if="isLonghornV2(volume) && !isView"
color="warning"
:label="t('harvester.volume.longhorn.disableResize')"
/>
</div>
</InfoBox>
</div>
</template>
</Sortable>
<Banner
v-if="showVolumeTip"
color="warning"
@@ -579,7 +579,7 @@ export default {
:create-namespace="true"
:namespace="value.metadata.namespace"
:mode="mode"
:disabled="isWindows || isEdit"
:disabled="isWindows"
@update:sshKey="updateSSHKey"
@register-after-hook="registerAfterHook"
/>
@@ -29,7 +29,7 @@ export default {
const rows = (this.value || []).map((row) => {
let type = 'cidr';
if (row.rangeStart || row.rangeEnd) {
if (row.rangeStart && row.rangeEnd) {
type = 'range';
}
@@ -65,7 +65,7 @@ export default {
:labels="[t('generic.disabled'),t('generic.enabled')]"
:options="[false, true]"
:disabled="disabled"
@update:value="onToggle"
@input="onToggle"
/>
</div>
</div>
@@ -58,7 +58,7 @@ export default {
const out = [];
blockDevices.map((b) => {
if (b?.metadata?.state?.error) {
if (b.metadata.state.error) {
out.push(b.metadata.state.message);
}
});
+1 -6
View File
@@ -6,7 +6,6 @@ import harvesterCommonStore from './store/harvester-common';
import harvesterStore from './store/harvester-store';
import customValidators from './validators';
import { PRODUCT_NAME } from './config/harvester';
import { defineAsyncComponent } from 'vue';
// Init the package
export default function (plugin: IPlugin) {
@@ -21,7 +20,7 @@ export default function (plugin: IPlugin) {
// Built-in icon
plugin.metadata.icon = require('./icon.svg');
plugin.addProduct(require('./config/harvester-cluster'));
plugin.addDashboardStore(harvesterCommonStore.config.namespace, harvesterCommonStore.specifics, harvesterCommonStore.config);
@@ -29,8 +28,4 @@ export default function (plugin: IPlugin) {
plugin.validators = customValidators;
plugin.addRoutes(extensionRoutes);
plugin.register('component', 'NavHeaderRight', defineAsyncComponent(() =>
import('./components/HarvesterUpgradeHeader.vue')
));
}
+11 -18
View File
@@ -39,11 +39,6 @@ asyncButton:
waiting: Restarting&hellip;
harvester:
branding:
logos:
tip: 'Upload a logo to replace the Harvester logo in the top-level navigation header. Image height should be 21 pixels with a max width of 200 pixels. Max file size is 20KB. Accepted formats: JPEG, PNG, SVG.'
favicon:
tip: 'Upload an icon to replace the Harvester favicon in the browser tab. Max file size is 20KB'
productLabel: 'Harvester'
modal:
backup:
@@ -747,9 +742,6 @@ harvester:
terminationGracePeriodSeconds:
label: Termination Grace Period
affinity:
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
thisPodNamespace: This virtual machine's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
@@ -902,9 +894,6 @@ harvester:
label: Virtual Machine Backups
createText: Restore Backup
title: Restore Virtual Machine
refreshInterval:
label: Refresh Interval
tip: The interval for the backup target to sync data to harvester cluster. Set to 0 to disable refresh mechanism.
backupTargetTip: The endpoint used to access the backupstore. NFS and S3 are supported.
message:
noSetting:
@@ -1505,13 +1494,17 @@ harvester:
pollingInterval:
label: Polling Interval
typeDescription:
# Map of
# type: Description to be shown on the top of list view describing the type.
# Should fit on one line.
# If you link to anything external, it MUST have
# target="_blank" rel="noopener noreferrer nofollow"
harvester: "Branding allows administrators to globally re-brand the UI by customizing the Harvester product name, logos and color scheme."
affinity:
thisPodNamespace: This virtual machine's namespace
matchExpressions:
inNamespaces: "Workloads in these namespaces"
vmAffinityTitle: Virtual Machine Scheduling
namespaces:
placeholder: e.g. default,system,base
label: Namespaces
addLabel: Add Workload Selector
topologyKey:
placeholder: 'topology.kubernetes.io/zone'
advancedSettings:
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
@@ -101,7 +101,7 @@ export default {
v-bind="$attrs"
:headers="headers"
:sub-rows="true"
:groupable="true"
:groupable="false"
:rows="rows"
:group-title-by="groupTitleBy"
:group-by="groupBy"
+22 -17
View File
@@ -282,14 +282,14 @@ export default {
affinityLabels() {
return {
namespaceInputLabel: this.t('harvester.virtualMachine.affinity.namespaces.label'),
namespaceInputLabel: this.t('harvesterManager.affinity.namespaces.label'),
namespaceSelectionLabels: [
this.t('harvester.virtualMachine.affinity.thisPodNamespace'),
this.t('harvesterManager.affinity.thisPodNamespace'),
this.t('workload.scheduling.affinity.allNamespaces'),
this.t('harvester.virtualMachine.affinity.matchExpressions.inNamespaces')
this.t('harvesterManager.affinity.matchExpressions.inNamespaces')
],
addLabel: this.t('harvester.virtualMachine.affinity.addLabel'),
topologyKeyPlaceholder: this.t('harvester.virtualMachine.affinity.topologyKey.placeholder')
addLabel: this.t('harvesterManager.affinity.addLabel'),
topologyKeyPlaceholder: this.t('harvesterManager.affinity.topologyKey.placeholder')
};
},
},
@@ -894,8 +894,8 @@ export default {
},
/**
* Generate user data yaml which is decided by the
* "Install guest agent", "OS type", "SSH keys" and user input.
* Generate user data yaml which is decide by the "Install guest agent",
* "OS type", "SSH Keys" and user input.
* @param config
*/
getUserData(config) {
@@ -919,9 +919,7 @@ export default {
return undefined;
}
const hasCloudComment = this.hasCloudConfigComment(userDataYaml);
return hasCloudComment ? userDataYaml : `#cloud-config\n${ userDataYaml }`;
return userDataYaml;
} catch (e) {
console.error('Error: Unable to parse yaml document', e); // eslint-disable-line no-console
@@ -1214,6 +1212,7 @@ export default {
let secret = this.getSecret(vm.spec);
// const userData = this.getUserData({ osType: this.osType, installAgent: this.installAgent });
if (!secret && this.isEdit && this.secretRef) {
// When editing the vm, if the userData and networkData are deleted, we also need to clean up the secret values
secret = this.secretRef;
@@ -1432,6 +1431,7 @@ export default {
sshAuthorizedKeys.splice(index, 1);
}
});
const userDataJson = this.convertToJson(this.userScript);
userDataJson.ssh_authorized_keys = sshAuthorizedKeys;
@@ -1553,9 +1553,19 @@ export default {
*/
handler(neu) {
if (this.deleteAgent) {
this['userScript'] = this.getUserData({
let out = 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;
@@ -1582,14 +1592,9 @@ export default {
sshKey(neu, old) {
const _diff = difference(old, neu);
// delete removed ssh key in userdata if needed
if (_diff.length > 0 && this.isCreate) {
if (_diff.length && this.isEdit) {
this.deleteSSHFromUserData(_diff);
}
// refresh yaml editor to get the latest userScript
this.userScript = this.getUserData({ installAgent: this.installAgent, osType: this.osType });
this.refreshYamlEditor();
}
}
};
@@ -1,11 +0,0 @@
import SteveModel from '@shell/plugins/steve/steve-class';
/**
* Class representing PCI Device Claim resource.
* @extends SteveModal
*/
export default class PCIDeviceClaim extends SteveModel {
cleanForSave(data, _forNew) {
return data;
}
}
@@ -1,11 +0,0 @@
import SteveModel from '@shell/plugins/steve/steve-class';
/**
* Class representing USB Device Claim resource.
* @extends SteveModal
*/
export default class USBDeviceClaim extends SteveModel {
cleanForSave(data, _forNew) {
return data;
}
}
-4
View File
@@ -66,8 +66,4 @@ export default class HarvesterResource extends SteveModel {
return this.hasLink('self') && this.$rootGetters['prefs/get'](DEV);
}
}
cleanForSave(data, _forNew) {
return data;
}
}
@@ -238,7 +238,7 @@ export default class HciPv extends HarvesterResource {
}
get isEncrypted() {
return this.relatedPV?.spec?.csi?.volumeAttributes?.encrypted === 'true';
return this.relatedPV?.spec.csi.volumeAttributes.encrypted === 'true';
}
get longhornVolume() {
@@ -64,10 +64,4 @@ export default class HciBlockDevice extends HarvesterResource {
// spec.fileSystem.provisioned is deprecated
return this.spec?.fileSystem?.provisioned || this.spec?.provision;
}
// Overwrite cleanForSave() in shell/plugins/steve/steve-class.js as it deleted status object in harvesterhci.io.blockdevice CRD
// but /v1/harvester/harvesterhci.io.blockdevices/longhorn-system/{id} API requires status object
cleanForSave(data) {
return data;
}
}
@@ -1,4 +1,6 @@
import HarvesterResource from './harvester';
import { get } from '@shell/utils/object';
import { findBy } from '@shell/utils/array';
import { colorForState, stateDisplay, STATES } from '@shell/plugins/dashboard-store/resource-class';
import { _CREATE } from '@shell/config/query-params';
import { ucFirst, escapeHtml } from '@shell/utils/string';
@@ -64,12 +66,15 @@ export default class ScheduleVmBackup extends HarvesterResource {
}
}
get stateObj() {
return this?.metadata?.state || {};
}
get state() {
return this.status?.suspended === true ? STATES.suspended.label : STATES.active.label;
const conditions = get(this, 'status.conditions');
const isSuspended = findBy(conditions, 'type', 'BackupSuspend')?.status === 'True';
if (isSuspended) {
return STATES.suspended.label;
}
return this.metadata.state.name;
}
get stateDescription() {
@@ -61,10 +61,6 @@ export default class HciUpgrade extends HarvesterResource {
return this?.status?.imageID;
}
cleanForSave(data, _forNew) {
return data;
}
get upgradeMessage() {
const upgradeMessage = [];
const nodeStatuses = this?.status?.nodeStatuses || {};
@@ -2,7 +2,6 @@ import { MODE, _CREATE } from '@shell/config/query-params';
import { HCI } from '../types';
import { PRODUCT_NAME as HARVESTER_PRODUCT } from '../config/harvester';
import HarvesterResource from './harvester';
import { clone } from '@shell/utils/object';
export default class HciVmTemplate extends HarvesterResource {
get availableActions() {
@@ -72,12 +71,4 @@ export default class HciVmTemplate extends HarvesterResource {
get defaultVersion() {
return this.status?.defaultVersion;
}
get doneOverride() {
const detailLocation = clone(this.listLocation);
detailLocation.params.resource = HCI.VM_VERSION;
return detailLocation;
}
}
@@ -1120,6 +1120,10 @@ export default class VirtVm extends HarvesterResource {
return qemu?.status === 'True';
}
get warnDeletionMessage() {
return this.t('harvester.virtualMachine.promptRemove.tips');
}
get instanceLabels() {
const all = this.spec?.template?.metadata?.labels || {};
@@ -3,4 +3,4 @@ import SteveSchema from '@shell/models/steve-schema';
/**
* This is the steve schema that's used for the harvester store
*/
export default class HarvesterStoreSchema extends SteveSchema {}
export default class HarvesterStoreSchema extends SteveSchema { }
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "harvester",
"description": "Rancher UI Extension for Harvester",
"version": "1.0.6",
"version": "1.0.2",
"private": false,
"rancher": {
"annotations": {
@@ -164,7 +164,7 @@ export default {
<h1 class="mb-20">
{{ t('branding.label') }}
</h1>
<TypeDescription resource="harvester" />
<TypeDescription resource="branding" />
<div>
<div class="row mb-20">
<div class="col span-6">
@@ -180,7 +180,7 @@ export default {
{{ t('branding.logos.label') }}
</h3>
<label class="text-label">
{{ t('harvester.branding.logos.tip', {}, true) }}
{{ t('branding.logos.tip', {}, true) }}
</label>
<div class="row mt-10 mb-20">
<Checkbox
@@ -244,7 +244,7 @@ export default {
{{ t('branding.favicon.label') }}
</h3>
<label class="text-label">
{{ t('harvester.branding.favicon.tip', {}, true) }}
{{ t('branding.favicon.tip', {}, true) }}
</label>
<div class="row mt-10 mb-20">
<Checkbox
@@ -4,7 +4,6 @@ import { isEmpty } from '@shell/utils/object';
import Parse from 'url-parse';
import { resourceNames } from '@shell/utils/string';
import { HCI } from '../types';
import { alternateLabel as alternateLabelButton } from '@shell/utils/platform';
export default {
name: 'HarvesterPromptRemove',
@@ -40,9 +39,8 @@ export default {
data() {
return {
checkedList: [],
checkAll: true,
alternateLabel: alternateLabelButton
checkedList: [],
checkAll: true
};
},
@@ -140,53 +138,44 @@ export default {
</script>
<template>
<div>
<div class="mt-10">
{{ t('promptRemove.attemptingToRemove', {type}) }}
<span v-clean-html="resourceNames(names, plusMore, t)"></span>
<div class="mt-10">
{{ t('promptRemove.attemptingToRemove', {type}) }}
<span v-clean-html="resourceNames(names, plusMore, t)"></span>
<div class="mt-10">
{{ t('harvester.virtualMachine.promptRemove.title') }}
</div>
<div v-if="value.length === 1">
<span
v-for="(name, i) in removeNameArr[value[0].id]"
:key="i"
>
<label class="checkbox-container mr-15">
<input
v-model="checkedList"
type="checkbox"
:label="name"
:value="name"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ name }}
</label>
</span>
</div>
<div v-else>
<label class="checkbox-container mr-15">
<input
v-model="checkedList"
type="checkbox"
/>
<div class="mt-10">
{{ t('harvester.virtualMachine.promptRemove.title') }}
</div>
<div v-if="value.length === 1">
<span
v-for="(name, i) in removeNameArr[value[0].id]"
:key="i"
>
<label class="checkbox-container mr-15"><input
v-model="checkedList"
type="checkbox"
:label="name"
:value="name"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ t('harvester.virtualMachine.promptRemove.deleteAll') }}
{{ name }}
</label>
</div>
</span>
</div>
<div class="text-warning mb-10 mt-10">
{{ t('harvester.virtualMachine.promptRemove.tips') }}
</div>
<div class="text-info mt-20">
{{ t('promptRemove.protip', { alternateLabel }) }}
<div v-else>
<label class="checkbox-container mr-15"><input
v-model="checkAll"
type="checkbox"
/>
<span
class="checkbox-custom mr-5"
role="checkbox"
/>
{{ t('harvester.virtualMachine.promptRemove.deleteAll') }}
</label>
</div>
</div>
</template>
-1
View File
@@ -8,7 +8,6 @@
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"useDefineForClassFields": false,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
+509 -150
View File
File diff suppressed because it is too large Load Diff