mirror of
https://github.com/harvester/harvester-ui-extension.git
synced 2026-10-06 22:26:05 +08:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c61d30a607 | ||
|
|
9428a07850 | ||
|
|
28603d3ab3 | ||
|
|
f863b881a9 | ||
|
|
083ab9f3e9 | ||
|
|
5d52c368a1 | ||
|
|
d72f379ab1 | ||
|
|
2931248fdb | ||
|
|
7166a5b1bb | ||
|
|
00c884cda0 | ||
|
|
1c65b71e35 | ||
|
|
3e4bc31a9d | ||
|
|
bc4bbd9a40 | ||
|
|
a6666ef384 | ||
|
|
92588c9adc | ||
|
|
832df26efc | ||
|
|
f7869cb241 | ||
|
|
27c0124682 | ||
|
|
6c69579c41 | ||
|
|
145b534c5d | ||
|
|
ce017a0e40 | ||
|
|
72a6f77fd2 | ||
|
|
275b15ad62 | ||
|
|
06c9a8b3d9 | ||
|
|
36b1406738 | ||
|
|
06b13a9cc5 | ||
|
|
2a3cd32447 | ||
|
|
40dc31226b | ||
|
|
def9e1a24e | ||
|
|
da25b28f7f | ||
|
|
10abbd0452 | ||
|
|
8a7cc69a69 | ||
|
|
c188f3367d | ||
|
|
26d85a7760 | ||
|
|
6eb845b46b | ||
|
|
8c20ddb440 | ||
|
|
a92f27d80c | ||
|
|
f523a161b6 | ||
|
|
ad2273b5ba | ||
|
|
171939e66b | ||
|
|
18bfbbaac1 | ||
|
|
e79f493cb6 | ||
|
|
e84c0fe886 | ||
|
|
bbfeb55af9 | ||
|
|
e7446e7e23 | ||
|
|
adacaf65fa | ||
|
|
7c761b2829 | ||
|
|
cc8532ff99 | ||
|
|
ede281a42d | ||
|
|
d34279d98f | ||
|
|
efd93c5e52 | ||
|
|
8d338b5f72 | ||
|
|
f59c81da31 | ||
|
|
cf6d44601e | ||
|
|
89b43060df | ||
|
|
7194d52000 | ||
|
|
a8ea0645e4 | ||
|
|
713173b0e4 | ||
|
|
6ed13bcec1 | ||
|
|
02519820d7 | ||
|
|
2604101eb2 | ||
|
|
1cb16f0c74 | ||
|
|
752cf2e93a | ||
|
|
f4ea7b6ea0 | ||
|
|
a6405989e9 | ||
|
|
fb8a81fd0f | ||
|
|
3a5ead3c2c | ||
|
|
def4b64cd3 | ||
|
|
a191b845d8 | ||
|
|
32a57d791a |
@@ -70,7 +70,7 @@ For developers, after reading through the introduction on this page, head over t
|
|||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
Copyright (c) 2014-2023 [Rancher Labs, Inc.](http://rancher.com)
|
Copyright (c) 2014-2025 [SUSE, LLC.](https://www.suse.com/)
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
+3
-2
@@ -6,13 +6,14 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rancher/shell": "3.0.1-rc.4",
|
"@rancher/shell": "3.0.2-rc.3",
|
||||||
"cache-loader": "^4.1.0",
|
"cache-loader": "^4.1.0",
|
||||||
"color": "4.2.3",
|
"color": "4.2.3",
|
||||||
"ip": "2.0.1",
|
"ip": "2.0.1",
|
||||||
"node-polyfill-webpack-plugin": "^3.0.0",
|
"node-polyfill-webpack-plugin": "^3.0.0",
|
||||||
"vue-draggable-next": "^2.2.1",
|
"vue-draggable-next": "^2.2.1",
|
||||||
"yaml": "^2.5.1"
|
"yaml": "^2.5.1",
|
||||||
|
"@babel/plugin-transform-class-static-block": "7.26.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@types/node": "~20.10.0",
|
"@types/node": "~20.10.0",
|
||||||
|
|||||||
@@ -1 +1,9 @@
|
|||||||
module.exports = require('@rancher/shell/pkg/babel.config');
|
const baseConfig = require('@rancher/shell/pkg/babel.config');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
...(baseConfig.plugins || []),
|
||||||
|
'@babel/plugin-transform-class-static-block'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import { Checkbox } from '@components/Form/Checkbox';
|
|||||||
import ModalWithCard from '@shell/components/ModalWithCard';
|
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||||
import { Banner } from '@components/Banner';
|
import { Banner } from '@components/Banner';
|
||||||
|
import { HCI as HCI_ANNOTATIONS } from '@pkg/harvester/config/labels-annotations';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
import UpgradeInfo from './UpgradeInfo';
|
import UpgradeInfo from './UpgradeInfo';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterUpgrade',
|
name: 'HarvesterUpgrade',
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ export default {
|
|||||||
selectMode: true,
|
selectMode: true,
|
||||||
version: '',
|
version: '',
|
||||||
enableLogging: true,
|
enableLogging: true,
|
||||||
|
skipSingleReplicaDetachedVol: false,
|
||||||
readyReleaseNote: false,
|
readyReleaseNote: false,
|
||||||
isOpen: false
|
isOpen: false
|
||||||
};
|
};
|
||||||
@@ -68,6 +71,10 @@ export default {
|
|||||||
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
|
return this.$store.getters['harvester/schemaFor'](HCI.UPGRADE_LOG);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
skipSingleReplicaDetachedVolFeatureEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('skipSingleReplicaDetachedVol');
|
||||||
|
},
|
||||||
|
|
||||||
releaseLink() {
|
releaseLink() {
|
||||||
return `https://github.com/harvester/harvester/releases/tag/${ this.version }`;
|
return `https://github.com/harvester/harvester/releases/tag/${ this.version }`;
|
||||||
}
|
}
|
||||||
@@ -104,6 +111,11 @@ export default {
|
|||||||
spec: { version: this.version }
|
spec: { version: this.version }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.skipSingleReplicaDetachedVolFeatureEnabled && this.skipSingleReplicaDetachedVol) {
|
||||||
|
upgradeValue.metadata.annotations =
|
||||||
|
{ [HCI_ANNOTATIONS.SKIP_SINGLE_REPLICA_DETACHED_VOL]: JSON.stringify(this.skipSingleReplicaDetachedVol) };
|
||||||
|
}
|
||||||
|
|
||||||
if (this.canEnableLogging) {
|
if (this.canEnableLogging) {
|
||||||
upgradeValue.spec.logEnabled = this.enableLogging;
|
upgradeValue.spec.logEnabled = this.enableLogging;
|
||||||
}
|
}
|
||||||
@@ -190,6 +202,21 @@ export default {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="skipSingleReplicaDetachedVolFeatureEnabled"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
v-model:value="skipSingleReplicaDetachedVol"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.upgradePage.skipSingleReplicaDetachedVol')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr
|
||||||
|
v-if="version"
|
||||||
|
class="divider"
|
||||||
|
/>
|
||||||
<div v-if="version">
|
<div v-if="version">
|
||||||
<p
|
<p
|
||||||
v-clean-html="t('harvester.upgradePage.releaseTip', {url: releaseLink}, true)"
|
v-clean-html="t('harvester.upgradePage.releaseTip', {url: releaseLink}, true)"
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { NODE } from '@shell/config/types';
|
|||||||
import { HCI } from '../../types';
|
import { HCI } from '../../types';
|
||||||
import { DOC } from '../../config/doc-links';
|
import { DOC } from '../../config/doc-links';
|
||||||
import { docLink } from '../../utils/feature-flags';
|
import { docLink } from '../../utils/feature-flags';
|
||||||
|
import { NETWORK_TYPE } from '../../config/types';
|
||||||
|
|
||||||
|
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HarvesterEditStorageNetwork',
|
name: 'HarvesterEditStorageNetwork',
|
||||||
@@ -57,11 +60,14 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
let parsedDefaultValue = {};
|
let parsedDefaultValue = {};
|
||||||
let openVlan = false;
|
let openVlan = false;
|
||||||
|
let networkType = L2VLAN;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parsedDefaultValue = JSON.parse(this.value.value);
|
parsedDefaultValue = JSON.parse(this.value.value);
|
||||||
|
networkType = 'vlan' in parsedDefaultValue ? L2VLAN : UNTAGGED; // backend doesn't provide networkType, so we check if vlan is provided instead
|
||||||
openVlan = true;
|
openVlan = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
networkType = L2VLAN;
|
||||||
parsedDefaultValue = {
|
parsedDefaultValue = {
|
||||||
vlan: '',
|
vlan: '',
|
||||||
clusterNetwork: '',
|
clusterNetwork: '',
|
||||||
@@ -73,6 +79,7 @@ export default {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
openVlan,
|
openVlan,
|
||||||
|
networkType,
|
||||||
errors: [],
|
errors: [],
|
||||||
exclude,
|
exclude,
|
||||||
parsedDefaultValue,
|
parsedDefaultValue,
|
||||||
@@ -87,16 +94,35 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
showVlan() {
|
||||||
|
return this.networkType === L2VLAN;
|
||||||
|
},
|
||||||
|
networkTypes() {
|
||||||
|
const types = [L2VLAN];
|
||||||
|
|
||||||
|
if (this.untaggedNetworkSettingEnabled) {
|
||||||
|
types.push(UNTAGGED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
},
|
||||||
storageNetworkExampleLink() {
|
storageNetworkExampleLink() {
|
||||||
const version = this.$store.getters['harvester-common/getServerVersion']();
|
const version = this.$store.getters['harvester-common/getServerVersion']();
|
||||||
|
|
||||||
return docLink(DOC.STORAGE_NETWORK_EXAMPLE, version);
|
return docLink(DOC.STORAGE_NETWORK_EXAMPLE, version);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
untaggedNetworkSettingEnabled() {
|
||||||
|
return this.$store.getters['harvester-common/getFeatureEnabled']('untaggedNetworkSetting');
|
||||||
|
},
|
||||||
|
|
||||||
clusterNetworkOptions() {
|
clusterNetworkOptions() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
const clusterNetworks = this.$store.getters[`${ inStore }/all`](HCI.CLUSTER_NETWORK) || [];
|
||||||
|
// untaggedNetwork filter out mgmt cluster network
|
||||||
|
const clusterNetworksOptions = this.networkType === UNTAGGED ? clusterNetworks.filter((net) => net.id !== 'mgmt') : clusterNetworks;
|
||||||
|
|
||||||
return clusterNetworks.map((n) => {
|
return clusterNetworksOptions.map((n) => {
|
||||||
const disabled = !n.isReadyForStorageNetwork;
|
const disabled = !n.isReadyForStorageNetwork;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -108,7 +134,50 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
networkType: {
|
||||||
|
handler(neu) {
|
||||||
|
this.parsedDefaultValue.clusterNetwork = '';
|
||||||
|
|
||||||
|
if (neu === L2VLAN) {
|
||||||
|
this.parsedDefaultValue.vlan = '';
|
||||||
|
} else {
|
||||||
|
delete this.parsedDefaultValue.vlan;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
inputVlan(neu) {
|
||||||
|
if (neu === '') {
|
||||||
|
this.parsedDefaultValue.vlan = '';
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newValue = Number(neu);
|
||||||
|
|
||||||
|
if (newValue > 4094) {
|
||||||
|
this.parsedDefaultValue.vlan = 4094;
|
||||||
|
} else if (newValue < 1) {
|
||||||
|
this.parsedDefaultValue.vlan = 1;
|
||||||
|
} else {
|
||||||
|
this.parsedDefaultValue.vlan = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
useDefault() {
|
||||||
|
this.openVlan = false;
|
||||||
|
this.networkType = L2VLAN;
|
||||||
|
this.parsedDefaultValue = {
|
||||||
|
vlan: '',
|
||||||
|
clusterNetwork: '',
|
||||||
|
range: '',
|
||||||
|
exclude: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
const exclude = this.exclude.filter((ip) => ip);
|
const exclude = this.exclude.filter((ip) => ip);
|
||||||
|
|
||||||
@@ -138,7 +207,7 @@ export default {
|
|||||||
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
|
errors.push(this.t('harvester.setting.storageNetwork.range.invalid', null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.parsedDefaultValue.vlan) {
|
if (this.networkType === L2VLAN && !this.parsedDefaultValue.vlan) {
|
||||||
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
|
errors.push(this.t('validation.required', { key: this.t('harvester.setting.storageNetwork.vlan') }, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,13 +260,25 @@ export default {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="openVlan">
|
<div v-if="openVlan">
|
||||||
|
<LabeledSelect
|
||||||
|
v-model:value="networkType"
|
||||||
|
class="mb-20"
|
||||||
|
:options="networkTypes"
|
||||||
|
:mode="mode"
|
||||||
|
:label="t('harvester.fields.type')"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
|
v-if="showVlan"
|
||||||
v-model:value.number="parsedDefaultValue.vlan"
|
v-model:value.number="parsedDefaultValue.vlan"
|
||||||
type="number"
|
type="number"
|
||||||
class="mb-20"
|
class="mb-20"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
required
|
required
|
||||||
|
placeholder="e.g. 1 - 4094"
|
||||||
label-key="harvester.setting.storageNetwork.vlan"
|
label-key="harvester.setting.storageNetwork.vlan"
|
||||||
|
@update:value="inputVlan"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LabeledSelect
|
<LabeledSelect
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const featuresV133 = [
|
|||||||
|
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.0
|
// https://github.com/harvester/dashboard/releases/tag/v1.4.0
|
||||||
const featuresV140 = [
|
const featuresV140 = [
|
||||||
...featuresV132,
|
...featuresV133,
|
||||||
'cpuPinning',
|
'cpuPinning',
|
||||||
'usbPassthrough',
|
'usbPassthrough',
|
||||||
'volumeEncryption',
|
'volumeEncryption',
|
||||||
@@ -35,15 +35,23 @@ const featuresV140 = [
|
|||||||
'improveMaintenanceMode',
|
'improveMaintenanceMode',
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: add v1.4.1 official release note
|
// https://github.com/harvester/dashboard/releases/tag/v1.4.1
|
||||||
// https://github.com/harvester/dashboard/releases/tag/v1.4.1-rc1
|
|
||||||
const featuresV141 = [
|
const featuresV141 = [
|
||||||
...featuresV140
|
...featuresV140
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// TODO: add v1.4.2 official release note
|
||||||
|
const featuresV142 = [
|
||||||
|
...featuresV141
|
||||||
|
];
|
||||||
|
|
||||||
// TODO: add v1.5.0 official release note
|
// TODO: add v1.5.0 official release note
|
||||||
const featuresV150 = [
|
const featuresV150 = [
|
||||||
...featuresV141
|
...featuresV142,
|
||||||
|
'tpmPersistentState',
|
||||||
|
'efiPersistentState',
|
||||||
|
'untaggedNetworkSetting',
|
||||||
|
'skipSingleReplicaDetachedVol'
|
||||||
];
|
];
|
||||||
|
|
||||||
export const RELEASE_FEATURES = {
|
export const RELEASE_FEATURES = {
|
||||||
@@ -53,5 +61,6 @@ export const RELEASE_FEATURES = {
|
|||||||
'v1.3.3': featuresV133,
|
'v1.3.3': featuresV133,
|
||||||
'v1.4.0': featuresV140,
|
'v1.4.0': featuresV140,
|
||||||
'v1.4.1': featuresV141,
|
'v1.4.1': featuresV141,
|
||||||
|
'v1.4.2': featuresV142,
|
||||||
'v1.5.0': featuresV150
|
'v1.5.0': featuresV150
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const HCI = {
|
|||||||
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
||||||
OS_TYPE: 'harvesterhci.io/os-type',
|
OS_TYPE: 'harvesterhci.io/os-type',
|
||||||
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
|
STORAGE_PROVISIONER: 'harvesterhci.io/storageProvisioner',
|
||||||
|
SKIP_SINGLE_REPLICA_DETACHED_VOL: 'harvesterhci.io/skipSingleReplicaDetachedVol',
|
||||||
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
||||||
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
STORAGE_CLASS: 'harvesterhci.io/storageClassName',
|
||||||
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
STORAGE_NETWORK: 'storage-network.settings.harvesterhci.io',
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ export const HCI_ALLOWED_SETTINGS = {
|
|||||||
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
[HCI_SETTING.SUPPORT_BUNDLE_EXPIRATION]: {},
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
|
[HCI_SETTING.SUPPORT_BUNDLE_NODE_COLLECTION_TIMEOUT]: { featureFlag: 'supportBundleNodeCollectionTimeoutSetting' },
|
||||||
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.SUPPORT_BUNDLE_IMAGE]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.STORAGE_NETWORK]: { kind: 'custom', from: 'import' },
|
[HCI_SETTING.STORAGE_NETWORK]: {
|
||||||
|
kind: 'custom', from: 'import', canReset: true
|
||||||
|
},
|
||||||
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.VM_FORCE_RESET_POLICY]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
[HCI_SETTING.SSL_CERTIFICATES]: { kind: 'json', from: 'import' },
|
||||||
[HCI_SETTING.SSL_PARAMETERS]: {
|
[HCI_SETTING.SSL_PARAMETERS]: {
|
||||||
|
|||||||
@@ -2,3 +2,8 @@ export const BACKUP_TYPE = {
|
|||||||
BACKUP: 'backup',
|
BACKUP: 'backup',
|
||||||
SNAPSHOT: 'snapshot'
|
SNAPSHOT: 'snapshot'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const NETWORK_TYPE = {
|
||||||
|
L2VLAN: 'L2VlanNetwork',
|
||||||
|
UNTAGGED: 'UntaggedNetwork'
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||||
|
import { alternateLabel } from '@shell/utils/platform';
|
||||||
|
import AsyncButton from '@shell/components/AsyncButton';
|
||||||
|
import { Banner } from '@components/Banner';
|
||||||
|
import { Card } from '@components/Card';
|
||||||
|
import { escapeHtml } from '@shell/utils/string';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name ConfirmExecutionDialog
|
||||||
|
* @description Dialog component to confirm the related resources before executing the action.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
name: 'ConfirmExecutionDialog',
|
||||||
|
|
||||||
|
emits: ['close'],
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AsyncButton,
|
||||||
|
Banner,
|
||||||
|
Card,
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
/**
|
||||||
|
* @property resources to be deleted.
|
||||||
|
* @type {Resource[]} Array of the resource model's instance
|
||||||
|
*/
|
||||||
|
resources: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return { errors: [] };
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState('action-menu', ['modalData']),
|
||||||
|
|
||||||
|
warningMessageKey() {
|
||||||
|
return this.modalData.warningMessageKey;
|
||||||
|
},
|
||||||
|
|
||||||
|
names() {
|
||||||
|
return this.resources.map((obj) => obj.nameDisplay).slice(0, 5);
|
||||||
|
},
|
||||||
|
|
||||||
|
resourceNames() {
|
||||||
|
return this.names.reduce((res, name, i) => {
|
||||||
|
if (i >= 5) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
res += `<b>${ escapeHtml(name) }</b>`;
|
||||||
|
if (i === this.names.length - 1) {
|
||||||
|
res += this.plusMore;
|
||||||
|
} else {
|
||||||
|
res += i === this.resources.length - 2 ? ' and ' : ', ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
plusMore() {
|
||||||
|
const remaining = this.resources.length - this.names.length;
|
||||||
|
|
||||||
|
return this.t('dialog.confirmExecution.andOthers', { count: remaining });
|
||||||
|
},
|
||||||
|
|
||||||
|
type() {
|
||||||
|
const types = new Set(this.resources.reduce((array, each) => {
|
||||||
|
array.push(each.type);
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}, []));
|
||||||
|
|
||||||
|
if (types.size > 1) {
|
||||||
|
return this.t('generic.resource', { count: this.resources.length });
|
||||||
|
}
|
||||||
|
|
||||||
|
const schema = this.resources[0]?.schema;
|
||||||
|
|
||||||
|
if ( !schema ) {
|
||||||
|
return `resource${ this.resources.length === 1 ? '' : 's' }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.$store.getters['type-map/labelFor'](schema, this.resources.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
protip() {
|
||||||
|
return this.t('dialog.confirmExecution.protip', { alternateLabel });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
escapeHtml,
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.errors = [];
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
|
||||||
|
async apply(buttonDone) {
|
||||||
|
try {
|
||||||
|
for (const resource of this.resources) {
|
||||||
|
await resource.doActionGrowl(this.modalData.action, {});
|
||||||
|
}
|
||||||
|
buttonDone(true);
|
||||||
|
this.close();
|
||||||
|
} catch (e) {
|
||||||
|
this.errors = exceptionToErrorsArray(e);
|
||||||
|
buttonDone(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :show-highlight-border="false">
|
||||||
|
<template #title>
|
||||||
|
<h4 class="text-default-text">
|
||||||
|
{{ t('dialog.confirmExecution.title') }}
|
||||||
|
</h4>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body>
|
||||||
|
<div class="pl-10 pr-10">
|
||||||
|
<span
|
||||||
|
v-clean-html="t(warningMessageKey, { type, names: resourceNames }, true)"
|
||||||
|
></span>
|
||||||
|
<div class="text-info mt-20">
|
||||||
|
{{ protip }}
|
||||||
|
</div>
|
||||||
|
<Banner
|
||||||
|
v-for="(error, i) in errors"
|
||||||
|
:key="i"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #actions>
|
||||||
|
<div class="actions">
|
||||||
|
<button
|
||||||
|
class="btn role-secondary"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
{{ t('generic.cancel') }}
|
||||||
|
</button>
|
||||||
|
<AsyncButton
|
||||||
|
mode="apply"
|
||||||
|
class="btn bg-primary ml-10"
|
||||||
|
:disabled="applyDisabled"
|
||||||
|
@click="apply"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang='scss' scoped>
|
||||||
|
.modal-container {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
width: 100%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,6 +11,9 @@ import { HCI as HCI_LABELS_ANNOTATIONS } from '@pkg/harvester/config/labels-anno
|
|||||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||||
import { allHash } from '@shell/utils/promise';
|
import { allHash } from '@shell/utils/promise';
|
||||||
import { HCI } from '../types';
|
import { HCI } from '../types';
|
||||||
|
import { NETWORK_TYPE } from '../config/types';
|
||||||
|
|
||||||
|
const { L2VLAN, UNTAGGED } = NETWORK_TYPE;
|
||||||
|
|
||||||
const AUTO = 'auto';
|
const AUTO = 'auto';
|
||||||
const MANUAL = 'manual';
|
const MANUAL = 'manual';
|
||||||
@@ -48,7 +51,7 @@ export default {
|
|||||||
config.bridge = config.bridge.slice(0, -3);
|
config.bridge = config.bridge.slice(0, -3);
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = this.value.vlanType || 'L2VlanNetwork' ;
|
const type = this.value.vlanType || L2VLAN ;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
config,
|
||||||
@@ -101,15 +104,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
networkType() {
|
networkType() {
|
||||||
return ['L2VlanNetwork', 'UntaggedNetwork'];
|
return [L2VLAN, UNTAGGED];
|
||||||
},
|
},
|
||||||
|
|
||||||
isUntaggedNetwork() {
|
isUntaggedNetwork() {
|
||||||
if (this.isView) {
|
if (this.isView) {
|
||||||
return this.value.vlanType === 'UntaggedNetwork';
|
return this.value.vlanType === UNTAGGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.type === 'UntaggedNetwork';
|
return this.type === UNTAGGED;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const CLONE = 'clone';
|
|||||||
const DOWNLOAD = 'download';
|
const DOWNLOAD = 'download';
|
||||||
const UPLOAD = 'upload';
|
const UPLOAD = 'upload';
|
||||||
const rawORqcow2 = 'raw_qcow2';
|
const rawORqcow2 = 'raw_qcow2';
|
||||||
|
const LONGHORN = 'longhorn';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EditImage',
|
name: 'EditImage',
|
||||||
@@ -60,10 +61,7 @@ export default {
|
|||||||
images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }),
|
images: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.IMAGE }),
|
||||||
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
storageClasses: this.$store.dispatch(`${ inStore }/findAll`, { type: STORAGE_CLASS }),
|
||||||
});
|
});
|
||||||
|
this['storageClassName'] = this.storageClassName || this.defaultStorageClassName();
|
||||||
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
|
|
||||||
|
|
||||||
this['storageClassName'] = this.storageClassName || defaultStorage?.metadata?.name || 'longhorn';
|
|
||||||
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
this.images = this.$store.getters[`${ inStore }/all`](HCI.IMAGE);
|
||||||
|
|
||||||
const { securityParameters } = this.value.spec;
|
const { securityParameters } = this.value.spec;
|
||||||
@@ -144,10 +142,23 @@ export default {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
storageClassOptions() {
|
encryptedStorageClasses() {
|
||||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
|
return storages.filter((s) => s.isEncrypted);
|
||||||
|
},
|
||||||
|
|
||||||
|
nonEncryptedStorageClasses() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const storages = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS);
|
||||||
|
|
||||||
|
return storages.filter((s) => !s.isEncrypted);
|
||||||
|
},
|
||||||
|
|
||||||
|
storageClassOptions() {
|
||||||
|
const storages = this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT ? this.encryptedStorageClasses : this.nonEncryptedStorageClasses;
|
||||||
|
|
||||||
return storages
|
return storages
|
||||||
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
|
.filter((s) => !s.parameters?.backingImage && s.provisioner !== LVM_DRIVER) // Lvm storage is not supported.
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
@@ -239,6 +250,13 @@ export default {
|
|||||||
this.$refs.file.value = null;
|
this.$refs.file.value = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'value.spec.securityParameters.cryptoOperation'() {
|
||||||
|
if (this.value.spec?.securityParameters?.cryptoOperation === ENCRYPT) {
|
||||||
|
this.storageClassName = this.encryptedStorageClasses[0]?.name || '';
|
||||||
|
} else { // URL / FILE / DECRYPT should use default storage class
|
||||||
|
this.storageClassName = this.defaultStorageClassName();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@@ -385,6 +403,18 @@ export default {
|
|||||||
return str.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
return str.toLowerCase().includes(os.value.toLowerCase()) ? os.value : false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultStorageClassName() {
|
||||||
|
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||||
|
const defaultStorage = this.$store.getters[`${ inStore }/all`](STORAGE_CLASS).find((s) => s.isDefault);
|
||||||
|
|
||||||
|
if (!defaultStorage) {
|
||||||
|
return LONGHORN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if default sc is encrypted, use longhorn as default
|
||||||
|
return defaultStorage.isEncrypted ? LONGHORN : defaultStorage?.metadata?.name;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ export default {
|
|||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="value.cpuPinningFeatureEnabled"
|
v-if="value.cpuPinningFeatureEnabled"
|
||||||
v-model="cpuPinning"
|
v-model:value="cpuPinning"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
|
tooltip-key="harvester.virtualMachine.cpuPinning.tooltip"
|
||||||
@@ -465,11 +465,29 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
|
||||||
|
v-model:value="tpmPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:value="efiEnabled"
|
v-model:value="efiEnabled"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.efiEnabled')"
|
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
|
||||||
|
v-model:value="efiPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -478,7 +496,7 @@ export default {
|
|||||||
v-model:value="secureBoot"
|
v-model:value="secureBoot"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.secureBoot')"
|
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export default {
|
|||||||
:searchable="searchable"
|
:searchable="searchable"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:options="sshOption"
|
:options="sshOption"
|
||||||
@update:value="update"
|
@input="update"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ModalWithCard
|
<ModalWithCard
|
||||||
|
|||||||
@@ -46,10 +46,13 @@ export default {
|
|||||||
this[key] = res[key];
|
this[key] = res[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
uniq([
|
const vGpus = this.vm.isOff ? [
|
||||||
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
|
...(this.value?.domain?.devices?.gpus || []).map(({ name }) => name),
|
||||||
|
] : [
|
||||||
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
|
...Object.values(this.vm?.provisionedVGpus).reduce((acc, gpus) => [...acc, ...gpus], [])
|
||||||
]).forEach((name) => {
|
];
|
||||||
|
|
||||||
|
uniq(vGpus).forEach((name) => {
|
||||||
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
if (this.enabledDevices.find((device) => device?.metadata?.name === name)) {
|
||||||
this.selectedDevices.push(name);
|
this.selectedDevices.push(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -873,11 +873,29 @@ export default {
|
|||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.tpmPersistentStateFeatureEnabled && tpmEnabled"
|
||||||
|
v-model:value="tpmPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.tpmPersistentState')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model:value="efiEnabled"
|
v-model:value="efiEnabled"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.efiEnabled')"
|
:label="t('harvester.virtualMachine.advancedOptions.efiEnabled')"
|
||||||
|
:mode="mode"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Checkbox
|
||||||
|
v-if="value.efiPersistentStateFeatureEnabled && efiEnabled"
|
||||||
|
v-model:value="efiPersistentStateEnabled"
|
||||||
|
class="check"
|
||||||
|
type="checkbox"
|
||||||
|
:label="t('harvester.virtualMachine.advancedOptions.efiPersistentState')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -886,9 +904,10 @@ export default {
|
|||||||
v-model:value="secureBoot"
|
v-model:value="secureBoot"
|
||||||
class="check"
|
class="check"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:label="t('harvester.virtualMachine.secureBoot')"
|
:label="t('harvester.virtualMachine.advancedOptions.secureBoot')"
|
||||||
:mode="mode"
|
:mode="mode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Banner
|
<Banner
|
||||||
v-if="showCpuPinningBanner"
|
v-if="showCpuPinningBanner"
|
||||||
color="warning"
|
color="warning"
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default {
|
|||||||
const out = [];
|
const out = [];
|
||||||
|
|
||||||
blockDevices.map((b) => {
|
blockDevices.map((b) => {
|
||||||
if (b.metadata.state.error) {
|
if (b?.metadata?.state?.error) {
|
||||||
out.push(b.metadata.state.message);
|
out.push(b.metadata.state.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,27 @@ asyncButton:
|
|||||||
success: Restarted
|
success: Restarted
|
||||||
waiting: Restarting…
|
waiting: Restarting…
|
||||||
|
|
||||||
|
dialog:
|
||||||
|
confirmExecution:
|
||||||
|
title: Are you sure?
|
||||||
|
andOthers: |-
|
||||||
|
{count, plural,
|
||||||
|
=0 {}
|
||||||
|
=1 { and <b>one other </b>}
|
||||||
|
other { and <b>{count} other </b>}
|
||||||
|
}
|
||||||
|
protip: "Tip: Hold the {alternateLabel} key while clicking action to bypass this confirmation"
|
||||||
|
stop:
|
||||||
|
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}?"
|
||||||
|
|
||||||
harvester:
|
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'
|
productLabel: 'Harvester'
|
||||||
modal:
|
modal:
|
||||||
backup:
|
backup:
|
||||||
@@ -579,15 +599,17 @@ harvester:
|
|||||||
enableUsb: Enable USB Tablet
|
enableUsb: Enable USB Tablet
|
||||||
advancedOptions:
|
advancedOptions:
|
||||||
tpm: Enable TPM
|
tpm: Enable TPM
|
||||||
|
tpmPersistentState: TPM Persistent State
|
||||||
cpuManager:
|
cpuManager:
|
||||||
prefix: You must enable CPU Manager for at least one node in
|
prefix: You must enable CPU Manager for at least one node in
|
||||||
middle: 'host page'
|
middle: 'host page'
|
||||||
suffix: to enable CPU Pinning for VM
|
suffix: to enable CPU Pinning for VM
|
||||||
|
efiEnabled: Booting in EFI mode
|
||||||
|
efiPersistentState: EFI Persistent State
|
||||||
|
secureBoot: Secure Boot
|
||||||
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
|
usbTip: Provides an absolute pointer device which often helps with getting a consistent mouse cursor position in VNC.
|
||||||
sshTitle: Add Public SSH Key
|
sshTitle: Add Public SSH Key
|
||||||
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
|
imageTip: An external URL to the .iso, .img, .qcow2 or .raw that the virtual machine should be created from.
|
||||||
efiEnabled: Booting in EFI mode
|
|
||||||
secureBoot: Secure Boot
|
|
||||||
volume:
|
volume:
|
||||||
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
|
dragTip: Drag and drop volumes, or use the volume's arrows, to change the boot order.
|
||||||
volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
|
volumeTip: The virtual machine only contains a CD-ROM volume. You may want to add additional disk volumes.
|
||||||
@@ -833,6 +855,7 @@ harvester:
|
|||||||
currentVersion: CURRENT VERSION
|
currentVersion: CURRENT VERSION
|
||||||
versionLabel: AVAILABLE COMPATIBLE VERSIONS
|
versionLabel: AVAILABLE COMPATIBLE VERSIONS
|
||||||
enableLogging: Enable Logging
|
enableLogging: Enable Logging
|
||||||
|
skipSingleReplicaDetachedVol: Skip checking single-replica detached volumes
|
||||||
upgradeNode: Upgrading Node
|
upgradeNode: Upgrading Node
|
||||||
upgradeSysService: Upgrading System Service
|
upgradeSysService: Upgrading System Service
|
||||||
upgradeImage: Download Upgrade Image
|
upgradeImage: Download Upgrade Image
|
||||||
@@ -1006,7 +1029,7 @@ harvester:
|
|||||||
invalid: '"Exclude list" is invalid.'
|
invalid: '"Exclude list" is invalid.'
|
||||||
addIp: Add Exclude IP
|
addIp: Add Exclude IP
|
||||||
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
|
warning: 'WARNING: <br/> Any change to storage-network requires shutting down all virtual machines before applying this setting. <br/> Users have to ensure the cluster network is configured and VLAN Configuration will cover all nodes and ensure the network connectivity is working and expected in all nodes.'
|
||||||
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 4 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
|
tip: 'Specify an IP range in the IPv4 CIDR format. <code>Number of IPs Required = Number of Nodes * 2 + Number of Disks * 2 + Number of Images to Download/Upload </code>. For more information about storage network settings, see the <a href="{url}" target="_blank">documentation</a>.'
|
||||||
vmForceDeletionPolicy:
|
vmForceDeletionPolicy:
|
||||||
period: Period
|
period: Period
|
||||||
ratio : Ratio
|
ratio : Ratio
|
||||||
@@ -1497,6 +1520,14 @@ harvester:
|
|||||||
pollingInterval:
|
pollingInterval:
|
||||||
label: Polling Interval
|
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."
|
||||||
|
|
||||||
advancedSettings:
|
advancedSettings:
|
||||||
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
|
experimental: 'Experimental features allow users to test and evaluate early-access functionality prior to official supported releases'
|
||||||
descriptions:
|
descriptions:
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
:sub-rows="true"
|
:sub-rows="true"
|
||||||
:groupable="false"
|
:groupable="true"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:group-title-by="groupTitleBy"
|
:group-title-by="groupTitleBy"
|
||||||
:group-by="groupBy"
|
:group-by="groupBy"
|
||||||
|
|||||||
@@ -137,7 +137,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isTpmEnabled(spec) {
|
isTpmEnabled(spec) {
|
||||||
return !!spec?.template?.spec?.domain?.devices?.tpm ;
|
return !!spec?.template?.spec?.domain?.devices?.tpm;
|
||||||
|
},
|
||||||
|
|
||||||
|
isTPMPersistentStateEnabled(spec) {
|
||||||
|
return !!spec?.template?.spec?.domain?.devices?.tpm?.persistent;
|
||||||
|
},
|
||||||
|
|
||||||
|
isEFIPersistentStateEnabled(spec) {
|
||||||
|
return !!spec?.template?.spec?.domain?.firmware?.bootloader?.efi?.persistent;
|
||||||
},
|
},
|
||||||
|
|
||||||
isSecureBoot(spec) {
|
isSecureBoot(spec) {
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ export default {
|
|||||||
accessCredentials: [],
|
accessCredentials: [],
|
||||||
efiEnabled: false,
|
efiEnabled: false,
|
||||||
tpmEnabled: false,
|
tpmEnabled: false,
|
||||||
|
tpmPersistentStateEnabled: false,
|
||||||
|
efiPersistentStateEnabled: false,
|
||||||
secureBoot: false,
|
secureBoot: false,
|
||||||
userDataTemplateId: '',
|
userDataTemplateId: '',
|
||||||
saveUserDataAsClearText: false,
|
saveUserDataAsClearText: false,
|
||||||
@@ -217,7 +219,7 @@ export default {
|
|||||||
const selectedVMNetworks = networkNames.map((name) => vmNetworks.find((n) => n.id === name)).filter((n) => n?.id);
|
const selectedVMNetworks = networkNames.map((name) => vmNetworks.find((n) => n.id === name)).filter((n) => n?.id);
|
||||||
const clusterNetworks = uniq(selectedVMNetworks.map((n) => n.clusterNetworkResource?.id));
|
const clusterNetworks = uniq(selectedVMNetworks.map((n) => n.clusterNetworkResource?.id));
|
||||||
|
|
||||||
return nodes.filter((N) => !N.isUnSchedulable).map((node) => {
|
return nodes.filter((N) => !N.isUnSchedulable && N.isEtcd !== 'true').map((node) => {
|
||||||
const requireLabelKeys = [];
|
const requireLabelKeys = [];
|
||||||
let isNetworkSchedule = true;
|
let isNetworkSchedule = true;
|
||||||
|
|
||||||
@@ -367,6 +369,8 @@ export default {
|
|||||||
const installAgent = this.hasInstallAgent(userData, osType, true);
|
const installAgent = this.hasInstallAgent(userData, osType, true);
|
||||||
const efiEnabled = this.isEfiEnabled(spec);
|
const efiEnabled = this.isEfiEnabled(spec);
|
||||||
const tpmEnabled = this.isTpmEnabled(spec);
|
const tpmEnabled = this.isTpmEnabled(spec);
|
||||||
|
const tpmPersistentStateEnabled = this.isTPMPersistentStateEnabled(spec);
|
||||||
|
const efiPersistentStateEnabled = this.isEFIPersistentStateEnabled(spec);
|
||||||
const secureBoot = this.isSecureBoot(spec);
|
const secureBoot = this.isSecureBoot(spec);
|
||||||
const cpuPinning = this.isCpuPinning(spec);
|
const cpuPinning = this.isCpuPinning(spec);
|
||||||
|
|
||||||
@@ -398,7 +402,9 @@ export default {
|
|||||||
|
|
||||||
this['installUSBTablet'] = installUSBTablet;
|
this['installUSBTablet'] = installUSBTablet;
|
||||||
this['efiEnabled'] = efiEnabled;
|
this['efiEnabled'] = efiEnabled;
|
||||||
|
this['efiPersistentStateEnabled'] = efiPersistentStateEnabled;
|
||||||
this['tpmEnabled'] = tpmEnabled;
|
this['tpmEnabled'] = tpmEnabled;
|
||||||
|
this['tpmPersistentStateEnabled'] = tpmPersistentStateEnabled;
|
||||||
this['secureBoot'] = secureBoot;
|
this['secureBoot'] = secureBoot;
|
||||||
this['cpuPinning'] = cpuPinning;
|
this['cpuPinning'] = cpuPinning;
|
||||||
|
|
||||||
@@ -1383,13 +1389,21 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setBootMethod(boot = { efi: false, secureBoot: false }) {
|
setBootMethod(boot = {
|
||||||
if (boot.efi && boot.secureBoot) {
|
efi: false, secureBoot: false, efiPersistentStateEnabled: false
|
||||||
set(this.spec.template.spec.domain, 'features.smm.enabled', true);
|
}) {
|
||||||
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', true);
|
if (boot.efi) {
|
||||||
} else if (boot.efi && !boot.secureBoot) {
|
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', boot.secureBoot);
|
||||||
// set(this.spec.template.spec.domain, 'features.smm.enabled', false);
|
} else {
|
||||||
|
delete this.spec.template.spec.domain['firmware'];
|
||||||
|
delete this.spec.template.spec.domain.features['smm'];
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boot.secureBoot) {
|
||||||
|
set(this.spec.template.spec.domain, 'features.smm.enabled', true);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
delete this.spec.template.spec.domain.features.smm['enabled'];
|
delete this.spec.template.spec.domain.features.smm['enabled'];
|
||||||
const noKeys = Object.keys(this.spec.template.spec.domain.features.smm).length === 0;
|
const noKeys = Object.keys(this.spec.template.spec.domain.features.smm).length === 0;
|
||||||
@@ -1398,10 +1412,12 @@ export default {
|
|||||||
delete this.spec.template.spec.domain.features['smm'];
|
delete this.spec.template.spec.domain.features['smm'];
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.secureBoot', false);
|
}
|
||||||
|
|
||||||
|
if (boot.efiPersistentStateEnabled) {
|
||||||
|
set(this.spec.template.spec.domain, 'firmware.bootloader.efi.persistent', true);
|
||||||
} else {
|
} else {
|
||||||
delete this.spec.template.spec.domain['firmware'];
|
delete this.spec.template.spec.domain.firmware.bootloader.efi['persistent'];
|
||||||
delete this.spec.template.spec.domain.features['smm'];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1413,9 +1429,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setTPM(tpmEnabled) {
|
setTPM({ tpmEnabled = false, tpmPersistentStateEnabled = false } = {}) {
|
||||||
if (tpmEnabled) {
|
if (tpmEnabled) {
|
||||||
set(this.spec.template.spec.domain.devices, 'tpm', {});
|
set(this.spec.template.spec.domain.devices, 'tpm', tpmPersistentStateEnabled ? { persistent: true } : {});
|
||||||
} else {
|
} else {
|
||||||
delete this.spec.template.spec.domain.devices['tpm'];
|
delete this.spec.template.spec.domain.devices['tpm'];
|
||||||
}
|
}
|
||||||
@@ -1529,11 +1545,21 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
efiEnabled(val) {
|
efiEnabled(val) {
|
||||||
this.setBootMethod({ efi: val, secureBoot: this.secureBoot });
|
this.setBootMethod({
|
||||||
|
efi: val, secureBoot: this.secureBoot, efiPersistentStateEnabled: this.efiPersistentStateEnabled
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
secureBoot(val) {
|
secureBoot(val) {
|
||||||
this.setBootMethod({ efi: this.efiEnabled, secureBoot: val });
|
this.setBootMethod({
|
||||||
|
efi: this.efiEnabled, secureBoot: val, efiPersistentStateEnabled: this.efiPersistentStateEnabled
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
efiPersistentStateEnabled(val) {
|
||||||
|
this.setBootMethod({
|
||||||
|
efi: this.efiEnabled, secureBoot: this.secureBoot, efiPersistentStateEnabled: val
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cpuPinning(value) {
|
cpuPinning(value) {
|
||||||
@@ -1541,7 +1567,11 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
tpmEnabled(val) {
|
tpmEnabled(val) {
|
||||||
this.setTPM(val);
|
this.setTPM({ tpmEnabled: val, tpmPersistentStateEnabled: this.tpmPersistentStateEnabled });
|
||||||
|
},
|
||||||
|
|
||||||
|
tpmPersistentStateEnabled(val) {
|
||||||
|
this.setTPM({ tpmEnabled: this.tpmEnabled, tpmPersistentStateEnabled: val });
|
||||||
},
|
},
|
||||||
|
|
||||||
installAgent: {
|
installAgent: {
|
||||||
|
|||||||
@@ -470,7 +470,7 @@ export default class HciNode extends HarvesterResource {
|
|||||||
get canDelete() {
|
get canDelete() {
|
||||||
const nodes = this.$rootGetters['harvester/all'](NODE) || [];
|
const nodes = this.$rootGetters['harvester/all'](NODE) || [];
|
||||||
|
|
||||||
return nodes.length > 1;
|
return nodes.length > 1 && super.canDelete;
|
||||||
}
|
}
|
||||||
|
|
||||||
get vlanStatuses() {
|
get vlanStatuses() {
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export default class HciPv extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isEncrypted() {
|
get isEncrypted() {
|
||||||
return this.relatedPV?.spec.csi.volumeAttributes.encrypted === 'true';
|
return this.relatedPV?.spec?.csi?.volumeAttributes?.encrypted === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
get longhornVolume() {
|
get longhornVolume() {
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ import SteveSchema from '@shell/models/steve-schema';
|
|||||||
/**
|
/**
|
||||||
* This is the steve schema that's used for the harvester store
|
* This is the steve schema that's used for the harvester store
|
||||||
*/
|
*/
|
||||||
export default class HarvesterStoreSchema extends SteveSchema { }
|
export default class HarvesterStoreSchema extends SteveSchema {}
|
||||||
@@ -58,6 +58,10 @@ export default class HciStorageClass extends StorageClass {
|
|||||||
return key ? this.$rootGetters['i18n/t'](key) : this.provisioner;
|
return key ? this.$rootGetters['i18n/t'](key) : this.provisioner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isEncrypted() {
|
||||||
|
return this.parameters?.encrypted === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
get isLonghornV2() {
|
get isLonghornV2() {
|
||||||
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
|
return this.provisioner === LONGHORN_DRIVER && this.longhornVersion === DATA_ENGINE_V2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,4 +64,10 @@ export default class HciBlockDevice extends HarvesterResource {
|
|||||||
// spec.fileSystem.provisioned is deprecated
|
// spec.fileSystem.provisioned is deprecated
|
||||||
return this.spec?.fileSystem?.provisioned || this.spec?.provision;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default class HciVmBackup extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get sourceSchedule() {
|
get sourceSchedule() {
|
||||||
return this.metadata?.annotations[HCI_ANNOTATIONS.SVM_BACKUP_ID];
|
return this.metadata?.annotations?.[HCI_ANNOTATIONS.SVM_BACKUP_ID];
|
||||||
}
|
}
|
||||||
|
|
||||||
get attachVM() {
|
get attachVM() {
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
await templateResource.save();
|
await templateResource.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cpuPinningFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('cpuPinning');
|
||||||
|
}
|
||||||
|
|
||||||
get defaultVersion() {
|
get defaultVersion() {
|
||||||
const templates = this.$rootGetters['harvester/all'](HCI.VM_TEMPLATE);
|
const templates = this.$rootGetters['harvester/all'](HCI.VM_TEMPLATE);
|
||||||
const template = templates.find((T) => this.templateId === T.id);
|
const template = templates.find((T) => this.templateId === T.id);
|
||||||
@@ -272,4 +276,12 @@ export default class HciVmTemplateVersion extends HarvesterResource {
|
|||||||
|
|
||||||
this.spec.vm.spec.template.metadata['labels'] = { ...wasIgnored, ...val };
|
this.spec.vm.spec.template.metadata['labels'] = { ...wasIgnored, ...val };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tpmPersistentStateFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('tpmPersistentState');
|
||||||
|
}
|
||||||
|
|
||||||
|
get efiPersistentStateFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import SteveModel from '@shell/plugins/steve/steve-class';
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
||||||
import { HCI } from '@shell/config/labels-annotations';
|
import { HCI } from '@shell/config/labels-annotations';
|
||||||
|
import { NETWORK_TYPE } from '../config/types';
|
||||||
|
|
||||||
|
const { UNTAGGED } = NETWORK_TYPE;
|
||||||
|
|
||||||
export default class NetworkAttachmentDef extends SteveModel {
|
export default class NetworkAttachmentDef extends SteveModel {
|
||||||
applyDefaults() {
|
applyDefaults() {
|
||||||
@@ -42,7 +45,7 @@ export default class NetworkAttachmentDef extends SteveModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get vlanId() {
|
get vlanId() {
|
||||||
return this.vlanType === 'UntaggedNetwork' ? 'N/A' : this.parseConfig.vlan;
|
return this.vlanType === UNTAGGED ? 'N/A' : this.parseConfig.vlan;
|
||||||
}
|
}
|
||||||
|
|
||||||
get customValidationRules() {
|
get customValidationRules() {
|
||||||
@@ -65,7 +68,7 @@ export default class NetworkAttachmentDef extends SteveModel {
|
|||||||
const route = annotations[HCI.NETWORK_ROUTE];
|
const route = annotations[HCI.NETWORK_ROUTE];
|
||||||
let config = {};
|
let config = {};
|
||||||
|
|
||||||
if (this.vlanType === 'UntaggedNetwork') {
|
if (this.vlanType === UNTAGGED) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,10 +103,12 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: 'stopVM',
|
action: 'stopVM',
|
||||||
|
altAction: 'altStopVM',
|
||||||
enabled: !!this.actions?.stop,
|
enabled: !!this.actions?.stop,
|
||||||
icon: 'icon icon-close',
|
icon: 'icon icon-close',
|
||||||
label: this.t('harvester.action.stop'),
|
label: this.t('harvester.action.stop'),
|
||||||
bulkable: true
|
bulkable: true,
|
||||||
|
bulkAction: 'stopVM',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'forceStop',
|
action: 'forceStop',
|
||||||
@@ -117,6 +119,7 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: 'pauseVM',
|
action: 'pauseVM',
|
||||||
|
altAction: 'altPauseVM',
|
||||||
enabled: !!this.actions?.pause,
|
enabled: !!this.actions?.pause,
|
||||||
icon: 'icon icon-pause',
|
icon: 'icon icon-pause',
|
||||||
label: this.t('harvester.action.pause')
|
label: this.t('harvester.action.pause')
|
||||||
@@ -402,7 +405,16 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return node?.id;
|
return node?.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
pauseVM() {
|
pauseVM(resources = this) {
|
||||||
|
this.$dispatch('promptModal', {
|
||||||
|
resources,
|
||||||
|
action: 'pause',
|
||||||
|
warningMessageKey: 'dialog.confirmExecution.pause.message',
|
||||||
|
component: 'ConfirmExecutionDialog'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
altPauseVM() {
|
||||||
this.doActionGrowl('pause', {});
|
this.doActionGrowl('pause', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,7 +429,16 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
this.doActionGrowl('unpause', {});
|
this.doActionGrowl('unpause', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
stopVM() {
|
stopVM(resources = this) {
|
||||||
|
this.$dispatch('promptModal', {
|
||||||
|
resources,
|
||||||
|
action: 'stop',
|
||||||
|
warningMessageKey: 'dialog.confirmExecution.stop.message',
|
||||||
|
component: 'ConfirmExecutionDialog'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
altStopVM() {
|
||||||
this.doActionGrowl('stop', {});
|
this.doActionGrowl('stop', {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,6 +640,10 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get encryptedVolumeType() {
|
get encryptedVolumeType() {
|
||||||
|
if (!this.volumes || this.volumes.length === 0) {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
if (this.volumes.every((vol) => vol.isEncrypted)) {
|
if (this.volumes.every((vol) => vol.isEncrypted)) {
|
||||||
return 'all';
|
return 'all';
|
||||||
} else if (this.volumes.some((vol) => vol.isEncrypted)) {
|
} else if (this.volumes.some((vol) => vol.isEncrypted)) {
|
||||||
@@ -1150,6 +1175,14 @@ export default class VirtVm extends HarvesterResource {
|
|||||||
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('volumeEncryption');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get tpmPersistentStateFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('tpmPersistentState');
|
||||||
|
}
|
||||||
|
|
||||||
|
get efiPersistentStateFeatureEnabled() {
|
||||||
|
return this.$rootGetters['harvester-common/getFeatureEnabled']('efiPersistentState');
|
||||||
|
}
|
||||||
|
|
||||||
setInstanceLabels(val) {
|
setInstanceLabels(val) {
|
||||||
if ( !this.spec?.template?.metadata?.labels ) {
|
if ( !this.spec?.template?.metadata?.labels ) {
|
||||||
set(this, 'spec.template.metadata.labels', {});
|
set(this, 'spec.template.metadata.labels', {});
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export default {
|
|||||||
<h1 class="mb-20">
|
<h1 class="mb-20">
|
||||||
{{ t('branding.label') }}
|
{{ t('branding.label') }}
|
||||||
</h1>
|
</h1>
|
||||||
<TypeDescription resource="branding" />
|
<TypeDescription resource="harvester" />
|
||||||
<div>
|
<div>
|
||||||
<div class="row mb-20">
|
<div class="row mb-20">
|
||||||
<div class="col span-6">
|
<div class="col span-6">
|
||||||
@@ -180,7 +180,7 @@ export default {
|
|||||||
{{ t('branding.logos.label') }}
|
{{ t('branding.logos.label') }}
|
||||||
</h3>
|
</h3>
|
||||||
<label class="text-label">
|
<label class="text-label">
|
||||||
{{ t('branding.logos.tip', {}, true) }}
|
{{ t('harvester.branding.logos.tip', {}, true) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="row mt-10 mb-20">
|
<div class="row mt-10 mb-20">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -244,7 +244,7 @@ export default {
|
|||||||
{{ t('branding.favicon.label') }}
|
{{ t('branding.favicon.label') }}
|
||||||
</h3>
|
</h3>
|
||||||
<label class="text-label">
|
<label class="text-label">
|
||||||
{{ t('branding.favicon.tip', {}, true) }}
|
{{ t('harvester.branding.favicon.tip', {}, true) }}
|
||||||
</label>
|
</label>
|
||||||
<div class="row mt-10 mb-20">
|
<div class="row mt-10 mb-20">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user