mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-03 04:36:04 +08:00
Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74c12a0d1b | ||
|
|
3277ab4a2b | ||
|
|
4aabf0b7a3 | ||
|
|
4e8cb31e9d | ||
|
|
1b214b2b6f | ||
|
|
748c88866a | ||
|
|
c3e5c2161e | ||
|
|
f932afee68 | ||
|
|
0bbdf3bf17 | ||
|
|
bdf3ac2803 | ||
|
|
0d183a8174 | ||
|
|
8f72c33f4b | ||
|
|
22c99211f1 | ||
|
|
775330d829 | ||
|
|
18be022a9f | ||
|
|
56b4b46b5a | ||
|
|
db398ecad3 | ||
|
|
d175e3b11a | ||
|
|
905db4b12c | ||
|
|
fc31b4bb9d | ||
|
|
db29a7c31b | ||
|
|
2b3541164d | ||
|
|
ea5e9aa1f4 | ||
|
|
18a5608e72 | ||
|
|
18599fc94c | ||
|
|
e155d46483 | ||
|
|
795e5d178f | ||
|
|
2e03ac3cf7 | ||
|
|
76605fdc07 | ||
|
|
c289b001db | ||
|
|
e8d63da1eb | ||
|
|
4a456ac07d | ||
|
|
57dfddd593 | ||
|
|
66eac96575 | ||
|
|
182d92d80b | ||
|
|
1b362f570b | ||
|
|
cf798048f8 | ||
|
|
93c8399935 | ||
|
|
b72f523ddf | ||
|
|
e5ffb08df3 | ||
|
|
98a6322c11 | ||
|
|
be7e4bd80b | ||
|
|
ed2bc3100b | ||
|
|
b6ffb3e9f1 | ||
|
|
3d8a6bba7e | ||
|
|
be9311dc0c | ||
|
|
e294f4c00f | ||
|
|
a9fa928912 | ||
|
|
ef2b4d1589 | ||
|
|
a73e9f0ac1 | ||
|
|
11b3bf4c1f | ||
|
|
b775ce5f50 | ||
|
|
193d4536e3 | ||
|
|
9ca9fdb521 | ||
|
|
bcabefe9f3 | ||
|
|
c541f81dc3 | ||
|
|
4486f71c8f | ||
|
|
060105ead3 | ||
|
|
3ea73978ee | ||
|
|
f4e363396d | ||
|
|
fa16e24983 | ||
|
|
ce63ee6a19 | ||
|
|
ee28697161 | ||
|
|
85a06feb91 | ||
|
|
17dd46cee9 | ||
|
|
0ef4ff65cc | ||
|
|
cebb302730 | ||
|
|
f12717a8f4 | ||
|
|
03f54643fd | ||
|
|
7fb6d44208 | ||
|
|
dc683a50a4 | ||
|
|
be421054d8 | ||
|
|
fcef0391bb | ||
|
|
1d89aafeab | ||
|
|
7386a2deb6 | ||
|
|
26f5ebc8e2 |
@@ -14,3 +14,4 @@ dist-pkg
|
||||
pkg/harvester/index.ts
|
||||
pkg/harvester/store/*
|
||||
scripts/pkgfile.js
|
||||
tmp
|
||||
|
||||
+12
-31
@@ -1,33 +1,14 @@
|
||||
pull_request_rules:
|
||||
- name: Automatically open backport PR to release-harvester-v1.5
|
||||
conditions:
|
||||
- base=main
|
||||
- label="require backport 1.5"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- "release-harvester-v1.5"
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
labels:
|
||||
- "v1.5 backport PR"
|
||||
- name: Ask to resolve conflict
|
||||
conditions:
|
||||
- conflict
|
||||
actions:
|
||||
comment:
|
||||
message: This pull request is now in conflict. Could you fix it @{{author}}? 🙏
|
||||
|
||||
- name: Automatically open backport PR to release-harvester-v1.0
|
||||
conditions:
|
||||
- base=main
|
||||
- label="require backport 1.0"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- "release-harvester-v1.0"
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
labels:
|
||||
- "v1.0 backport PR"
|
||||
|
||||
- name: Ask to resolve conflict
|
||||
conditions:
|
||||
- conflict
|
||||
actions:
|
||||
comment:
|
||||
message: This pull request is now in conflict. Could you fix it @{{author}}? 🙏
|
||||
commands_restrictions:
|
||||
backport:
|
||||
conditions:
|
||||
- or:
|
||||
- sender-permission>=write
|
||||
- sender=github-actions[bot]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"extends": [
|
||||
"config:recommended",
|
||||
":semanticCommitTypeAll(deps)",
|
||||
":semanticCommitScopeDisabled"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
name: "[PR Management] Request Backport via Mergify"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
comment-backport:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
- name: Post Mergify backport command
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
pr_number=${{ github.event.pull_request.number }}
|
||||
repo="${{ github.repository }}"
|
||||
|
||||
labels_json='${{ toJson(github.event.pull_request.labels.*.name) }}'
|
||||
labels=$(echo "$labels_json" | jq -r '.[] // empty')
|
||||
|
||||
echo "Labels on PR: $labels"
|
||||
|
||||
matches=$(echo "$labels" | grep -oE '^require backport/v[0-9]+\.[0-9]+$' || true)
|
||||
|
||||
if [[ -z "$matches" ]]; then
|
||||
echo "No back‑port labels found — skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
branches=$(echo "$matches" \
|
||||
| sed -E 's/^require backport\/v/release-harvester-v/' \
|
||||
| sort -u | tr '\n' ' ')
|
||||
branches=${branches%% }
|
||||
|
||||
cmd="@Mergifyio backport $branches"
|
||||
echo "Posting Mergify command: $cmd"
|
||||
gh pr comment "$pr_number" --repo "$repo" --body "$cmd"
|
||||
@@ -138,7 +138,6 @@ To validate changes in a release series, switch to the appropriate `vX.Y-head` b
|
||||
- Examples:
|
||||
- Test `1.0.x` series → `v1.0-head`
|
||||
- Test `1.5.x` series → `v1.5-head`
|
||||
- Test `1.6.x` series → `v1.6-head`
|
||||
|
||||
**Steps:**
|
||||
1. Navigate to **Rancher UI** → **Local** → **App** → **Repositories**
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "harvester-ui-extension",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-dev",
|
||||
"private": false,
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
@@ -30,7 +30,7 @@
|
||||
"nth-check": "2.1.1",
|
||||
"qs": "6.14.0",
|
||||
"roarr": "7.21.1",
|
||||
"semver": "7.7.3",
|
||||
"semver": "7.7.2",
|
||||
"@vue/cli-service/html-webpack-plugin": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -11,7 +11,6 @@ const CATEGORY = {
|
||||
ui: [
|
||||
'branding',
|
||||
'ui-source',
|
||||
'ui-plugin-index',
|
||||
'ui-index',
|
||||
]
|
||||
};
|
||||
|
||||
@@ -257,6 +257,10 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
reconnect() {
|
||||
this.$refs.novncConsole.reconnect();
|
||||
},
|
||||
|
||||
softReboot() {
|
||||
this.vmResource.doSoftReboot();
|
||||
},
|
||||
@@ -306,6 +310,13 @@ export default {
|
||||
{{ t("harvester.action.softreboot") }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm bg-primary"
|
||||
@click="reconnect"
|
||||
>
|
||||
{{ t("harvester.action.reconnect") }}
|
||||
</button>
|
||||
|
||||
<v-dropdown
|
||||
v-if="!hideCustomKeysBar"
|
||||
ref="customKeyPopover"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// suffix of doc link, see utils/feature-flags.js how to get complete doc link
|
||||
export const DOC = {
|
||||
CONSOLE_URL: `/host/#remote-console`,
|
||||
RANCHER_INTEGRATION_URL: `/rancher/rancher-integration`,
|
||||
KSMTUNED_MODE: `/host/#ksmtuned-mode`,
|
||||
UPGRADE_URL: `/upgrade/index`,
|
||||
UPGRADE_CONFIG_URL: `/advanced/index#upgrade-config`,
|
||||
STORAGE_NETWORK_EXAMPLE: `/advanced/storagenetwork#configuration-example`,
|
||||
SUPPORT_BUNDLE_NAMESPACES: `/advanced/index/#support-bundle-namespaces`,
|
||||
CONSOLE_URL: `/host/#remote-console`,
|
||||
RANCHER_INTEGRATION_URL: `/rancher/rancher-integration`,
|
||||
KSMTUNED_MODE: `/host/#ksmtuned-mode`,
|
||||
UPGRADE_URL: `/upgrade/index`,
|
||||
UPGRADE_CONFIG_URL: `/advanced/index#upgrade-config`,
|
||||
STORAGE_NETWORK_EXAMPLE: `/advanced/storagenetwork#configuration-example`,
|
||||
SUPPORT_BUNDLE_NAMESPACES: `/advanced/index/#support-bundle-namespaces`,
|
||||
VPC_CONFIGURATION_EXAMPLES: `/networking/kubeovn-vpc#vpc-peering-configuration-examples`,
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ const FEATURE_FLAGS = {
|
||||
'vmCloneRunStrategy',
|
||||
],
|
||||
'v1.6.1': [],
|
||||
'v1.7.0': []
|
||||
};
|
||||
|
||||
const generateFeatureFlags = () => {
|
||||
|
||||
@@ -4,7 +4,6 @@ export const HCI_SETTING = {
|
||||
LOG_LEVEL: 'log-level',
|
||||
SERVER_VERSION: 'server-version',
|
||||
UI_INDEX: 'ui-index',
|
||||
UI_PLUGIN_INDEX: 'ui-plugin-index',
|
||||
UPGRADE_CHECKER_ENABLED: 'upgrade-checker-enabled',
|
||||
UPGRADE_CHECKER_URL: 'upgrade-checker-url',
|
||||
VLAN: 'vlan',
|
||||
@@ -84,7 +83,6 @@ export const HCI_ALLOWED_SETTINGS = {
|
||||
[HCI_SETTING.SUPPORT_BUNDLE_NAMESPACES]: { from: 'import', canReset: true },
|
||||
[HCI_SETTING.AUTO_DISK_PROVISION_PATHS]: { canReset: true },
|
||||
[HCI_SETTING.RELEASE_DOWNLOAD_URL]: { kind: 'url' },
|
||||
[HCI_SETTING.UI_PLUGIN_INDEX]: { kind: 'url' },
|
||||
[HCI_SETTING.CONTAINERD_REGISTRY]: {
|
||||
kind: 'json', from: 'import', canReset: true
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ import { HCI } from '../types';
|
||||
import { sortBy } from '@shell/utils/sort';
|
||||
import { BACKUP_TYPE } from '../config/types';
|
||||
import { _EDIT, _CREATE } from '@shell/config/query-params';
|
||||
import { isBackupTargetSettingEmpty, isBackupTargetSettingUnavailable } from '../utils/setting';
|
||||
|
||||
export default {
|
||||
name: 'CreateVMSchedule',
|
||||
@@ -93,7 +94,7 @@ export default {
|
||||
return this.settings.find( (O) => O.id === 'backup-target');
|
||||
},
|
||||
isEmptyValue() {
|
||||
return this.getBackupTargetValueIsEmpty(this.backupTargetResource);
|
||||
return isBackupTargetSettingEmpty(this.backupTargetResource);
|
||||
},
|
||||
canUpdate() {
|
||||
return this?.backupTargetResource?.canUpdate;
|
||||
@@ -109,7 +110,7 @@ export default {
|
||||
!!this.value.spec.maxFailure;
|
||||
},
|
||||
isBackupTargetUnAvailable() {
|
||||
return this.value.spec.vmbackup.type === BACKUP_TYPE.BACKUP && (this.errorMessage || this.isEmptyValue) && this.canUpdate;
|
||||
return this.value.spec.vmbackup.type === BACKUP_TYPE.BACKUP && isBackupTargetSettingUnavailable(this.backupTargetResource);
|
||||
},
|
||||
vmOptions() {
|
||||
const nsVmList = this.$store.getters['harvester/all'](HCI.VM).filter((vm) => vm.metadata.namespace === this.value.metadata.namespace);
|
||||
@@ -166,20 +167,6 @@ export default {
|
||||
onTypeChange(newType) {
|
||||
this.value.metadata.name = `svm${ newType }-${ this.value.spec.vmbackup.source.name }`;
|
||||
},
|
||||
getBackupTargetValueIsEmpty(resource) {
|
||||
let out = true;
|
||||
|
||||
if (resource?.value) {
|
||||
try {
|
||||
const valueJson = JSON.parse(resource?.value);
|
||||
|
||||
out = !valueJson.type;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
validateFailure(count) {
|
||||
if (this.value.spec.retain && count > this.value.spec.retain) {
|
||||
this.value.spec['maxFailure'] = this.value.spec.retain;
|
||||
|
||||
@@ -14,6 +14,7 @@ import ArrayList from '@shell/components/form/ArrayList';
|
||||
import { allHash } from '@shell/utils/promise';
|
||||
import { HCI } from '../../types';
|
||||
import ResourceTabs from '@shell/components/form/ResourceTabs/index';
|
||||
import { Banner } from '@components/Banner';
|
||||
|
||||
export default {
|
||||
name: 'EditSubnet',
|
||||
@@ -21,6 +22,7 @@ export default {
|
||||
emits: ['update:value'],
|
||||
|
||||
components: {
|
||||
Banner,
|
||||
CruResource,
|
||||
LabeledInput,
|
||||
LabeledSelect,
|
||||
@@ -38,7 +40,9 @@ export default {
|
||||
|
||||
created() {
|
||||
const vpc = this.$route.query.vpc || '';
|
||||
const enableDHCP = this.value?.spec?.enableDHCP || false;
|
||||
|
||||
set(this.value.spec, 'enableDHCP', enableDHCP);
|
||||
set(this.value, 'spec', this.value.spec || {
|
||||
cidrBlock: '',
|
||||
protocol: NETWORK_PROTOCOL.IPv4,
|
||||
@@ -46,7 +50,8 @@ export default {
|
||||
vpc,
|
||||
gatewayIP: '',
|
||||
excludeIps: [],
|
||||
private: false
|
||||
private: false,
|
||||
enableDHCP
|
||||
});
|
||||
},
|
||||
|
||||
@@ -81,7 +86,6 @@ export default {
|
||||
protocolOptions() {
|
||||
return Object.values(NETWORK_PROTOCOL);
|
||||
},
|
||||
|
||||
provider: {
|
||||
get() {
|
||||
const raw = this.value.spec.provider;
|
||||
@@ -125,6 +129,16 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'value.spec.enableDHCP': {
|
||||
handler(newValue) {
|
||||
if (newValue === false) {
|
||||
this.value.spec.dhcpV4Options = '';
|
||||
this.value.spec.dhcpV6Options = '';
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveSubnet(buttonCb) {
|
||||
const errors = [];
|
||||
@@ -245,6 +259,45 @@ export default {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-20">
|
||||
<div class="col span-6">
|
||||
<RadioGroup
|
||||
v-model:value="value.spec.enableDHCP"
|
||||
name="enabled"
|
||||
:options="[true, false]"
|
||||
:label="t('harvester.subnet.dhcp.label')"
|
||||
:labels="[t('generic.enabled'), t('generic.disabled')]"
|
||||
:mode="mode"
|
||||
:tooltip="t('harvester.subnet.dhcp.tooltip')"
|
||||
/>
|
||||
<LabeledInput
|
||||
v-if="value.spec.enableDHCP && value.spec.protocol === 'IPv4'"
|
||||
v-model:value="value.spec.dhcpV4Options"
|
||||
class="mb-20 mt-20"
|
||||
:placeholder="t('harvester.subnet.dhcp.placeholder')"
|
||||
:label="t('harvester.subnet.dhcp.v4Options')"
|
||||
:mode="mode"
|
||||
/>
|
||||
<LabeledInput
|
||||
v-if="value.spec.enableDHCP && value.spec.protocol === 'IPv6'"
|
||||
v-model:value="value.spec.dhcpV6Options"
|
||||
:placeholder="t('harvester.subnet.dhcp.placeholder')"
|
||||
class="mb-20 mt-20"
|
||||
:label="t('harvester.subnet.dhcp.v6Options')"
|
||||
:mode="mode"
|
||||
/>
|
||||
<Banner
|
||||
v-if="value.spec.enableDHCP"
|
||||
color="info"
|
||||
class="dhcpOption-banner"
|
||||
>
|
||||
<t
|
||||
k="harvester.subnet.dhcp.dhcpOptionBanner"
|
||||
:raw="true"
|
||||
/>
|
||||
</Banner>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-20">
|
||||
<div class="col span-6">
|
||||
<RadioGroup
|
||||
@@ -321,3 +374,9 @@ export default {
|
||||
</ResourceTabs>
|
||||
</CruResource>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dhcpOption-banner {
|
||||
width: max-content;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
import debounce from 'lodash/debounce';
|
||||
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
||||
import { removeAt } from '@shell/utils/array';
|
||||
import { Banner } from '@components/Banner';
|
||||
import { DOC } from '../../config/doc-links';
|
||||
import { docLink } from '../../utils/feature-flags';
|
||||
|
||||
export default {
|
||||
name: 'StaticRoutes',
|
||||
|
||||
emits: ['update:value'],
|
||||
|
||||
components: { Banner },
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
@@ -45,6 +50,12 @@ export default {
|
||||
nextHopIPTooltip() {
|
||||
return this.t('harvester.vpc.staticRoutes.nextHopIP.tooltip');
|
||||
},
|
||||
|
||||
vpcPeeringExamplesLink() {
|
||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
||||
|
||||
return docLink(DOC.VPC_CONFIGURATION_EXAMPLES, version);
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
@@ -78,6 +89,13 @@ export default {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Banner color="info">
|
||||
<t
|
||||
k="harvester.vpc.vpcPeerings.infoBanner"
|
||||
:raw="true"
|
||||
:url="vpcPeeringExamplesLink"
|
||||
/>
|
||||
</Banner>
|
||||
<div
|
||||
v-if="rows.length"
|
||||
class="static-route-row"
|
||||
|
||||
@@ -338,15 +338,6 @@ export default {
|
||||
saveVM(buttonCb) {
|
||||
clear(this.errors);
|
||||
|
||||
this.validateCPUMemory();
|
||||
|
||||
// block create VM flow if has validation errors
|
||||
if (this.errors.length) {
|
||||
buttonCb(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isSingle) {
|
||||
this.saveSingle(buttonCb);
|
||||
} else {
|
||||
@@ -354,18 +345,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
validateCPUMemory() {
|
||||
const { cpu, memory } = this;
|
||||
|
||||
if ((!cpu)) {
|
||||
this.errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.cpu') }, true));
|
||||
}
|
||||
|
||||
if ((!memory)) {
|
||||
this.errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.memory') }, true));
|
||||
}
|
||||
},
|
||||
|
||||
async saveSingle(buttonCb) {
|
||||
this.parseVM();
|
||||
this.value.spec.template.spec.hostname = this.hostname ? this.hostname : this.value.metadata.name;
|
||||
@@ -487,7 +466,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const errors = this.getAccessCredentialsValidation();
|
||||
const cpuMemoryErrors = this.getCPUMemoryValidation();
|
||||
const accessCredentialsErrors = this.getAccessCredentialsValidation();
|
||||
const errors = [...cpuMemoryErrors, ...accessCredentialsErrors];
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Promise.reject(errors);
|
||||
|
||||
@@ -236,6 +236,7 @@ harvester:
|
||||
shutdown: Shut Down
|
||||
powerOn: Power On
|
||||
reboot: Reboot
|
||||
reconnect: Reconnect
|
||||
forceStop: Force Stop
|
||||
formatters:
|
||||
hardwareResourceGauge:
|
||||
@@ -1044,6 +1045,13 @@ harvester:
|
||||
gateway:
|
||||
label: Gateway IP
|
||||
placeholder: e.g. 172.20.0.1
|
||||
dhcp:
|
||||
label: Dynamic Host Configuration Protocol (DHCP)
|
||||
v4Options: DHCPV4Options
|
||||
v6Options: DHCPV6Options
|
||||
placeholder: key1=value1, key2=value2
|
||||
dhcpOptionBanner: DHCP options is a key/value string concatenate with comma. For more detail, please refer to <a href="https://kubeovn.github.io/docs/v1.13.x/en/kubevirt/dhcp/" target="_blank">KubOVN document</a>
|
||||
tooltip: Enable DHCP server for this subnet. When enabled, VMs can automatically obtain IP addresses from this subnet.
|
||||
private:
|
||||
label: Private Subnet
|
||||
tooltip: Enable network isolation for this Subnet. When enabled, VMs can only communicate within this subnet, even if other subnets exist under the same VPC.
|
||||
@@ -1080,6 +1088,7 @@ harvester:
|
||||
placeholder: e.g. 169.254.0.1/16
|
||||
remoteVpc:
|
||||
label: Remote VPC
|
||||
infoBanner: The static route destination CIDR must cover all subnets CIDR from remote VPC Peer. Read <a href="{url}" target="_blank">VPC Peering Configuration Examples</a> for more information.
|
||||
network:
|
||||
label: Virtual Machine Networks
|
||||
tabs:
|
||||
@@ -1718,9 +1727,8 @@ advancedSettings:
|
||||
'harv-server-version': Harvester server version.
|
||||
'harv-upgrade-checker-enabled': Specifies whether to enable Harvester upgrade check or not. Default is True.
|
||||
'harv-upgrade-checker-url': Default Harvester upgrade check url. Only used when the <code>upgrade-checker-enabled</code> is equal to True.
|
||||
'harv-ui-source': Configure how to load the UI source.
|
||||
'harv-ui-source': Configure how to load the UI source. The `External` option is used to load the UI from a ui-index URL, while the `Bundled` option loads the UI from the local Harvester server.
|
||||
'harv-ui-index': 'HTML index location for the UI.'
|
||||
'harv-ui-plugin-index': 'JS index location for the Harvester plugin UI.'
|
||||
'harv-cluster-registration-url': Registration URL for multi-cluster management.
|
||||
'harv-http-proxy': 'HTTP proxy for Harvester to access external services.'
|
||||
'harv-additional-ca': 'Custom CA root certificates for TLS validation.'
|
||||
|
||||
@@ -4,7 +4,7 @@ import Loading from '@shell/components/Loading';
|
||||
import MessageLink from '@shell/components/MessageLink';
|
||||
import Masthead from '@shell/components/ResourceList/Masthead';
|
||||
import ResourceTable from '@shell/components/ResourceTable';
|
||||
|
||||
import { isBackupTargetSettingEmpty } from '../utils/setting';
|
||||
import { STATE, AGE, NAME, NAMESPACE } from '@shell/config/table-headers';
|
||||
import FilterVMSchedule from '../components/FilterVMSchedule';
|
||||
import { HCI } from '../types';
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
this.settings = hash.settings;
|
||||
if (this.$store.getters[`${ inStore }/schemaFor`](HCI.SETTING)) {
|
||||
const backupTargetResource = hash.settings.find( (O) => O.id === 'backup-target');
|
||||
const isEmpty = this.getBackupTargetValueIsEmpty(backupTargetResource);
|
||||
const isEmpty = isBackupTargetSettingEmpty(backupTargetResource);
|
||||
|
||||
if (backupTargetResource && !isEmpty) {
|
||||
this.testConnect();
|
||||
@@ -78,20 +78,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getBackupTargetValueIsEmpty(resource) {
|
||||
let out = true;
|
||||
|
||||
if (resource?.value) {
|
||||
try {
|
||||
const valueJson = JSON.parse(resource?.value);
|
||||
|
||||
out = !valueJson.type;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
|
||||
getRow(row) {
|
||||
return row.status && row.status.source;
|
||||
},
|
||||
@@ -183,11 +169,9 @@ export default {
|
||||
backupTargetResource() {
|
||||
return this.settings.find((O) => O.id === 'backup-target');
|
||||
},
|
||||
|
||||
isEmptyValue() {
|
||||
return this.getBackupTargetValueIsEmpty(this.backupTargetResource);
|
||||
return isBackupTargetSettingEmpty(this.backupTargetResource);
|
||||
},
|
||||
|
||||
canUpdate() {
|
||||
return this?.backupTargetResource?.canUpdate;
|
||||
},
|
||||
|
||||
@@ -687,12 +687,14 @@ export default {
|
||||
set(this.spec.template.spec, 'domain.memory.maxGuest', this.maxMemory);
|
||||
set(this.spec.template.spec, 'domain.resources.limits.memory', this.maxMemory);
|
||||
} else {
|
||||
this.spec.template.spec.domain.cpu.maxSockets = 1;
|
||||
this.spec.template.spec.domain.cpu.sockets = 1;
|
||||
this.spec.template.spec.domain.cpu.cores = this.cpu;
|
||||
this.spec.template.spec.domain.resources.limits.cpu = this.cpu?.toString();
|
||||
this.spec.template.spec.domain.resources.limits.memory = this.memory;
|
||||
// clean below fields as we don't need them if not enable CPU and memory hotplug
|
||||
if (this.spec?.template?.spec?.domain?.cpu?.maxSockets) {
|
||||
delete this.spec.template.spec.domain.cpu.maxSockets;
|
||||
}
|
||||
if (this.spec?.template?.spec?.domain?.memory?.maxGuest) {
|
||||
delete this.spec.template.spec.domain.memory.maxGuest;
|
||||
}
|
||||
@@ -702,7 +704,7 @@ export default {
|
||||
parseDiskRows(disk) {
|
||||
const disks = [];
|
||||
const volumes = [];
|
||||
const diskNameLables = [];
|
||||
const diskNameLabels = [];
|
||||
const volumeClaimTemplates = [];
|
||||
|
||||
disk.forEach( (R, index) => {
|
||||
@@ -724,7 +726,7 @@ export default {
|
||||
|
||||
disks.push(_disk);
|
||||
volumes.push(_volume);
|
||||
diskNameLables.push(dataVolumeName);
|
||||
diskNameLabels.push(dataVolumeName);
|
||||
|
||||
if (R.source !== SOURCE_TYPE.CONTAINER) {
|
||||
volumeClaimTemplates.push(_dataVolumeTemplate);
|
||||
@@ -765,6 +767,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const specDisks = this.spec?.template?.spec?.domain?.devices?.disks;
|
||||
const mergedDisks = this.mergeDeviceList(specDisks, disks);
|
||||
|
||||
let spec = {
|
||||
...this.spec,
|
||||
runStrategy: this.runStrategy,
|
||||
@@ -787,7 +792,7 @@ export default {
|
||||
...this.spec.template?.spec?.domain,
|
||||
devices: {
|
||||
...this.spec.template?.spec?.domain?.devices,
|
||||
disks,
|
||||
disks: mergedDisks,
|
||||
},
|
||||
},
|
||||
volumes,
|
||||
@@ -886,13 +891,16 @@ export default {
|
||||
interfaces.push(_interface);
|
||||
});
|
||||
|
||||
const specInterfaces = this.spec?.template?.spec?.domain?.devices?.interfaces;
|
||||
const mergedInterfaces = this.mergeDeviceList(specInterfaces, interfaces);
|
||||
|
||||
const spec = {
|
||||
...this.spec.template.spec,
|
||||
domain: {
|
||||
...this.spec.template.spec.domain,
|
||||
devices: {
|
||||
...this.spec.template.spec.domain.devices,
|
||||
interfaces,
|
||||
interfaces: mergedInterfaces,
|
||||
},
|
||||
},
|
||||
networks
|
||||
@@ -1035,7 +1043,9 @@ export default {
|
||||
},
|
||||
|
||||
parseVolumeClaimTemplate(R, dataVolumeName) {
|
||||
if (!String(R.size).includes('Gi') && R.size) {
|
||||
const sizeString = String(R.size);
|
||||
|
||||
if (!(sizeString.includes('Gi') || sizeString.includes('Ti')) && R.size) {
|
||||
R.size = `${ R.size }${ GIBIBYTE }`;
|
||||
}
|
||||
|
||||
@@ -1359,6 +1369,21 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getCPUMemoryValidation() {
|
||||
const errors = [];
|
||||
const { cpu, memory } = this;
|
||||
|
||||
if ((!cpu)) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.cpu') }, true));
|
||||
}
|
||||
|
||||
if ((!memory)) {
|
||||
errors.push(this.t('validation.required', { key: this.t('harvester.virtualMachine.input.memory') }, true));
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
|
||||
getAccessCredentialsValidation() {
|
||||
const errors = [];
|
||||
|
||||
@@ -1526,6 +1551,23 @@ export default {
|
||||
this.refreshYamlEditor();
|
||||
},
|
||||
|
||||
mergeDeviceList(specDeviceList, deviceList) {
|
||||
if (!specDeviceList || specDeviceList.length === 0) {
|
||||
return deviceList;
|
||||
}
|
||||
const specDeviceMap = new Map(specDeviceList.map((device) => [device.name, device]));
|
||||
|
||||
return deviceList.map((device) => {
|
||||
const specDevice = specDeviceMap.get(device.name);
|
||||
|
||||
if (specDevice) {
|
||||
return { ...specDevice, ...device };
|
||||
}
|
||||
|
||||
return device;
|
||||
});
|
||||
},
|
||||
|
||||
refreshYamlEditor() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.yamlEditor?.updateValue();
|
||||
|
||||
@@ -2,7 +2,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
|
||||
import { HCI } from '@shell/config/labels-annotations';
|
||||
import { NETWORK_TYPE } from '../config/types';
|
||||
|
||||
const { UNTAGGED } = NETWORK_TYPE;
|
||||
const { UNTAGGED, OVERLAY } = NETWORK_TYPE;
|
||||
|
||||
export default class NetworkAttachmentDef extends SteveModel {
|
||||
applyDefaults() {
|
||||
@@ -45,7 +45,7 @@ export default class NetworkAttachmentDef extends SteveModel {
|
||||
}
|
||||
|
||||
get vlanId() {
|
||||
return this.vlanType === UNTAGGED ? 'N/A' : this.parseConfig.vlan;
|
||||
return this.vlanType === UNTAGGED || this.vlanType === OVERLAY ? 'N/A' : this.parseConfig.vlan;
|
||||
}
|
||||
|
||||
get customValidationRules() {
|
||||
@@ -68,7 +68,7 @@ export default class NetworkAttachmentDef extends SteveModel {
|
||||
const route = annotations[HCI.NETWORK_ROUTE];
|
||||
let config = {};
|
||||
|
||||
if (this.vlanType === UNTAGGED) {
|
||||
if (this.vlanType === UNTAGGED || this.vlanType === OVERLAY) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { BACKUP_TYPE } from '../config/types';
|
||||
import { HCI } from '../types';
|
||||
import HarvesterResource from './harvester';
|
||||
import { getVmCPUMemoryValues } from '../utils/cpuMemory';
|
||||
import { isBackupTargetSettingUnavailable } from '../utils/setting';
|
||||
|
||||
export const OFF = 'Off';
|
||||
|
||||
@@ -152,7 +153,7 @@ export default class VirtVm extends HarvesterResource {
|
||||
},
|
||||
{
|
||||
action: 'backupVM',
|
||||
enabled: !!this.actions?.backup,
|
||||
enabled: !!this.actions?.backup && !this.isBackupTargetUnavailable,
|
||||
icon: 'icon icon-backup',
|
||||
label: this.t('harvester.action.backup')
|
||||
},
|
||||
@@ -1239,6 +1240,13 @@ export default class VirtVm extends HarvesterResource {
|
||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('vmMachineTypes');
|
||||
}
|
||||
|
||||
get isBackupTargetUnavailable() {
|
||||
const allSettings = this.$rootGetters['harvester/all'](HCI.SETTING) || [];
|
||||
const backupTargetSetting = allSettings.find( (O) => O.id === 'backup-target');
|
||||
|
||||
return isBackupTargetSettingUnavailable(backupTargetSetting);
|
||||
}
|
||||
|
||||
setInstanceLabels(val) {
|
||||
if ( !this.spec?.template?.metadata?.labels ) {
|
||||
set(this, 'spec.template.metadata.labels', {});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "harvester",
|
||||
"description": "Rancher UI Extension for Harvester",
|
||||
"version": "1.6.1",
|
||||
"version": "1.7.0-dev",
|
||||
"private": false,
|
||||
"rancher": {
|
||||
"annotations": {
|
||||
|
||||
@@ -3,19 +3,17 @@ import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations
|
||||
export function getVmCPUMemoryValues(vm) {
|
||||
if (!vm) {
|
||||
return {
|
||||
cpu: null,
|
||||
cpu: 0,
|
||||
memory: null,
|
||||
isHotplugEnabled: false
|
||||
};
|
||||
}
|
||||
|
||||
const isHotplugEnabled = isCPUMemoryHotPlugEnabled(vm);
|
||||
const { sockets = 1, threads = 1, cores = null } = vm.spec.template.spec.domain.cpu || {};
|
||||
const cpu = cores === null ? null : sockets * threads * cores;
|
||||
|
||||
if (isHotplugEnabled) {
|
||||
return {
|
||||
cpu,
|
||||
cpu: vm.spec.template.spec.domain.cpu.sockets,
|
||||
memory: vm.spec.template.spec.domain?.memory?.guest || null,
|
||||
maxCpu: vm.spec.template.spec.domain.cpu?.maxSockets || 0,
|
||||
maxMemory: vm.spec.template.spec.domain?.memory?.maxGuest || null,
|
||||
@@ -23,7 +21,7 @@ export function getVmCPUMemoryValues(vm) {
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
cpu,
|
||||
cpu: vm.spec.template.spec.domain.cpu.cores,
|
||||
memory: vm.spec.template.spec.domain.resources?.limits?.memory || null,
|
||||
isHotplugEnabled
|
||||
};
|
||||
@@ -31,5 +29,8 @@ export function getVmCPUMemoryValues(vm) {
|
||||
}
|
||||
|
||||
export function isCPUMemoryHotPlugEnabled(vm) {
|
||||
return vm?.metadata?.annotations[HCI_ANNOTATIONS.VM_CPU_MEMORY_HOTPLUG] === 'true' || !!vm?.spec?.template?.spec?.domain?.memory?.maxGuest || false;
|
||||
return vm?.metadata?.annotations[HCI_ANNOTATIONS.VM_CPU_MEMORY_HOTPLUG] === 'true' ||
|
||||
!!vm?.spec?.template?.spec?.domain.cpu?.maxSockets ||
|
||||
!!vm?.spec?.template?.spec?.domain?.memory?.maxGuest ||
|
||||
false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
export function isBackupTargetSettingEmpty(setting = {}) {
|
||||
let isEmpty = true;
|
||||
|
||||
if (setting?.value) {
|
||||
try {
|
||||
const valueJson = JSON.parse(setting?.value);
|
||||
|
||||
isEmpty = !valueJson.type;
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to parse backup target value:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
export function isBackupTargetSettingUnavailable(setting = {}) {
|
||||
const errorMessage = setting.errorMessage;
|
||||
const isEmptyValue = isBackupTargetSettingEmpty(setting);
|
||||
const canUpdate = setting.canUpdate;
|
||||
|
||||
return (errorMessage || isEmptyValue) && canUpdate;
|
||||
}
|
||||
@@ -3114,9 +3114,9 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@20.10.8", "@types/node@^14.14.31", "@types/node@~20.19.0":
|
||||
version "20.19.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.21.tgz#6e5378e04993c40395473b13baf94a09875157b8"
|
||||
integrity sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==
|
||||
version "20.19.13"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.13.tgz#b79004a05068e28fb2de281b3a44c5c993650e59"
|
||||
integrity sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==
|
||||
dependencies:
|
||||
undici-types "~6.21.0"
|
||||
|
||||
@@ -11525,10 +11525,10 @@ semver-compare@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
|
||||
integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@7.7.3, semver@^5.5.0, semver@^5.7.1, semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@~7.0.0:
|
||||
version "7.7.3"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946"
|
||||
integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==
|
||||
"semver@2 || 3 || 4 || 5", semver@7.7.2, semver@^5.5.0, semver@^5.7.1, semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semver@^7.6.3, semver@~7.0.0:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
|
||||
Reference in New Issue
Block a user