mirror of https://github.com/rancher/dashboard.git
Merge pull request #6398 from n313893254/pr
HARVESTER: Fixes v1.0.3 issues
This commit is contained in:
commit
7618bb5fb8
|
|
@ -1156,6 +1156,9 @@ cluster:
|
|||
label: KubeconfigContent
|
||||
placeholder: 'Namespace/Name'
|
||||
cluster: Cluster
|
||||
affinity:
|
||||
namespaces:
|
||||
placeholder: e.g. default,system,base
|
||||
description:
|
||||
label: Cluster Description
|
||||
placeholder: Any text you want that better describes this cluster
|
||||
|
|
@ -5999,6 +6002,11 @@ harvester:
|
|||
fail: Fail
|
||||
ongoing: on-going
|
||||
dismissMessage: Dismiss it
|
||||
upgradeInfo:
|
||||
warning: WARNING
|
||||
doc: Before you upgrade to the newer Harvester version, you must perform the required <a href="https://docs.harvesterhci.io/v1.0/upgrade/automatic/" target="_blank"> pre-upgrade checks </a>for your cluster. Complete only those tasks that apply to your environment.
|
||||
tip: Failure to perform these checks may result in a failed upgrade or hitting known issues that require a manual workaround fix.
|
||||
moreNotes: For more details about the release notes, please visit -
|
||||
|
||||
backup:
|
||||
label: Backups
|
||||
|
|
|
|||
|
|
@ -1166,6 +1166,9 @@ cluster:
|
|||
label: KubeconfigContent
|
||||
placeholder: '命名空间/名称'
|
||||
cluster: 集群
|
||||
affinity:
|
||||
namespaces:
|
||||
placeholder: 例如:default,system,base
|
||||
description:
|
||||
label: 集群描述
|
||||
placeholder: 输入可以描述该集群的文本
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
import { _VIEW } from '@shell/config/query-params';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { get, isEmpty, clone } from '@shell/utils/object';
|
||||
|
|
@ -50,7 +51,10 @@ export default {
|
|||
return {
|
||||
allSelectorTerms,
|
||||
weightedNodeSelectorTerms: preferredDuringSchedulingIgnoredDuringExecution,
|
||||
defaultWeight: 1
|
||||
defaultWeight: 1,
|
||||
// rules in MatchExpressions.vue can not catch changes what happens on parent component
|
||||
// we need re-render it via key changing
|
||||
rerenderNums: randomStr(4)
|
||||
};
|
||||
}
|
||||
},
|
||||
|
|
@ -73,35 +77,38 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.update();
|
||||
created() {
|
||||
this.queueUpdate = debounce(this.update, 500);
|
||||
},
|
||||
|
||||
methods: {
|
||||
update() {
|
||||
this.$nextTick(() => {
|
||||
const out = {};
|
||||
const requiredDuringSchedulingIgnoredDuringExecution = { nodeSelectorTerms: [] };
|
||||
const preferredDuringSchedulingIgnoredDuringExecution = [] ;
|
||||
const out = {};
|
||||
const requiredDuringSchedulingIgnoredDuringExecution = { nodeSelectorTerms: [] };
|
||||
const preferredDuringSchedulingIgnoredDuringExecution = [] ;
|
||||
|
||||
this.allSelectorTerms.forEach((term) => {
|
||||
if (term.weight) {
|
||||
const neu = { weight: 1, preference: term };
|
||||
this.allSelectorTerms.forEach((term) => {
|
||||
if (term.weight) {
|
||||
const neu = { weight: 1, preference: term };
|
||||
|
||||
preferredDuringSchedulingIgnoredDuringExecution.push(neu);
|
||||
} else {
|
||||
requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(term);
|
||||
}
|
||||
});
|
||||
|
||||
if (preferredDuringSchedulingIgnoredDuringExecution.length) {
|
||||
out.preferredDuringSchedulingIgnoredDuringExecution = preferredDuringSchedulingIgnoredDuringExecution;
|
||||
preferredDuringSchedulingIgnoredDuringExecution.push(neu);
|
||||
} else {
|
||||
requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(term);
|
||||
}
|
||||
if (requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.length) {
|
||||
out.requiredDuringSchedulingIgnoredDuringExecution = requiredDuringSchedulingIgnoredDuringExecution;
|
||||
}
|
||||
this.$emit('input', out);
|
||||
});
|
||||
|
||||
if (preferredDuringSchedulingIgnoredDuringExecution.length) {
|
||||
out.preferredDuringSchedulingIgnoredDuringExecution = preferredDuringSchedulingIgnoredDuringExecution;
|
||||
}
|
||||
if (requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.length) {
|
||||
out.requiredDuringSchedulingIgnoredDuringExecution = requiredDuringSchedulingIgnoredDuringExecution;
|
||||
}
|
||||
this.$emit('input', out);
|
||||
},
|
||||
|
||||
remove() {
|
||||
this.rerenderNums = randomStr(4);
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
changePriority(term) {
|
||||
|
|
@ -126,9 +133,16 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" @input="update">
|
||||
<div class="row" @input="queueUpdate">
|
||||
<div class="col span-12">
|
||||
<ArrayListGrouped v-model="allSelectorTerms" class="mt-20" :mode="mode" :default-add-value="{matchExpressions:[]}" :add-label="t('workload.scheduling.affinity.addNodeSelector')">
|
||||
<ArrayListGrouped
|
||||
v-model="allSelectorTerms"
|
||||
class="mt-20"
|
||||
:mode="mode"
|
||||
:default-add-value="{matchExpressions:[]}"
|
||||
:add-label="t('workload.scheduling.affinity.addNodeSelector')"
|
||||
@remove="remove"
|
||||
>
|
||||
<template #default="props">
|
||||
<div class="row">
|
||||
<div class="col span-6">
|
||||
|
|
@ -142,6 +156,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
<MatchExpressions
|
||||
:key="rerenderNums"
|
||||
v-model="props.row.value.matchExpressions"
|
||||
:mode="mode"
|
||||
class="col span-12 mt-20"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ export default {
|
|||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
hasNodesAndNs: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -49,6 +54,7 @@ export default {
|
|||
out._anti = false;
|
||||
if (term.podAffinityTerm) {
|
||||
Object.assign(out, term.podAffinityTerm);
|
||||
out._namespaces = (term.podAffinityTerm.namespaces || []).toString();
|
||||
delete out.podAffinityTerm;
|
||||
}
|
||||
|
||||
|
|
@ -61,6 +67,7 @@ export default {
|
|||
out._anti = true;
|
||||
if (term.podAffinityTerm) {
|
||||
Object.assign(out, term.podAffinityTerm);
|
||||
out._namespaces = (term.podAffinityTerm.namespaces || []).toString();
|
||||
delete out.podAffinityTerm;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +78,10 @@ export default {
|
|||
|
||||
return {
|
||||
allSelectorTerms,
|
||||
defaultWeight: 1
|
||||
defaultWeight: 1,
|
||||
// rules in MatchExpressions.vue can not catch changes what happens on parent component
|
||||
// we need re-render it via key changing
|
||||
rerenderNums: randomStr(4)
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -104,6 +114,7 @@ export default {
|
|||
return getUniqueLabelKeys(this.nodes);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.queueUpdate = debounce(this.update, 500);
|
||||
},
|
||||
|
|
@ -132,6 +143,12 @@ export default {
|
|||
});
|
||||
|
||||
Object.assign(this.value.affinity, { podAffinity, podAntiAffinity });
|
||||
this.$emit('update', this.value);
|
||||
},
|
||||
|
||||
remove() {
|
||||
this.rerenderNums = randomStr(4);
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
addSelector() {
|
||||
|
|
@ -163,14 +180,20 @@ export default {
|
|||
changeNamespaceMode(term, idx) {
|
||||
if (term.namespaces) {
|
||||
term.namespaces = null;
|
||||
term._namespaces = null;
|
||||
} else {
|
||||
this.$set(term, 'namespaces', []);
|
||||
this.$set(term, '_namespaces', '');
|
||||
}
|
||||
this.$set(this.allSelectorTerms, idx, term);
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
updateNamespaces(term, namespaces) {
|
||||
this.$set(term, 'namespaces', namespaces);
|
||||
const nsArray = namespaces.split(',').map(ns => ns.trim()).filter(ns => ns?.length);
|
||||
|
||||
this.$set(term, 'namespaces', nsArray);
|
||||
this.queueUpdate();
|
||||
},
|
||||
|
||||
isEmpty,
|
||||
|
|
@ -190,6 +213,7 @@ export default {
|
|||
:default-add-value="{ matchExpressions: [] }"
|
||||
:mode="mode"
|
||||
:add-label="t('workload.scheduling.affinity.addNodeSelector')"
|
||||
@remove="remove"
|
||||
>
|
||||
<template #default="props">
|
||||
<div class="row mt-20 mb-20">
|
||||
|
|
@ -226,6 +250,7 @@ export default {
|
|||
<div class="spacer"></div>
|
||||
<div v-if="!!props.row.value.namespaces || !!get(props.row.value, 'podAffinityTerm.namespaces')" class="row mb-20">
|
||||
<LabeledSelect
|
||||
v-if="hasNodesAndNs"
|
||||
v-model="props.row.value.namespaces"
|
||||
:mode="mode"
|
||||
:multiple="true"
|
||||
|
|
@ -233,8 +258,17 @@ export default {
|
|||
:options="allNamespaces"
|
||||
:label="t('workload.scheduling.affinity.matchExpressions.inNamespaces')"
|
||||
/>
|
||||
<LabeledInput
|
||||
v-else
|
||||
v-model="props.row.value._namespaces"
|
||||
:mode="mode"
|
||||
:label="t('workload.scheduling.affinity.matchExpressions.inNamespaces')"
|
||||
:placeholder="t('cluster.credential.harvester.affinity.namespaces.placeholder')"
|
||||
@input="updateNamespaces(props.row.value, props.row.value._namespaces)"
|
||||
/>
|
||||
</div>
|
||||
<MatchExpressions
|
||||
:key="rerenderNums"
|
||||
:mode="mode"
|
||||
class=" col span-12 mt-20"
|
||||
:type="pod"
|
||||
|
|
@ -246,6 +280,7 @@ export default {
|
|||
<div class="row">
|
||||
<div class="col span-12">
|
||||
<LabeledSelect
|
||||
v-if="hasNodesAndNs"
|
||||
v-model="props.row.value.topologyKey"
|
||||
:taggable="true"
|
||||
:searchable="true"
|
||||
|
|
@ -258,6 +293,15 @@ export default {
|
|||
:disabled="mode==='view'"
|
||||
@input="update"
|
||||
/>
|
||||
<LabeledInput
|
||||
v-else
|
||||
v-model="props.row.value.topologyKey"
|
||||
:mode="mode"
|
||||
:label="t('workload.scheduling.affinity.topologyKey.label')"
|
||||
:placeholder="t('workload.scheduling.affinity.topologyKey.placeholder')"
|
||||
required
|
||||
@input="update"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,8 @@ export const HCI = {
|
|||
DYNAMIC_SSHKEYS_USERS: 'harvesterhci.io/dynamic-ssh-key-users',
|
||||
VM_VOLUME_STATUS: 'harvesterhci.io/volume-status',
|
||||
IMAGE_SUFFIX: 'harvesterhci.io/image-type',
|
||||
OS_TYPE: 'harvesterhci.io/os-type'
|
||||
OS_TYPE: 'harvesterhci.io/os-type',
|
||||
HOST_REQUEST: 'management.cattle.io/pod-requests',
|
||||
};
|
||||
|
||||
// Annotations that can be on management.cattle.io.cluster to configure a custom badge
|
||||
|
|
|
|||
|
|
@ -208,6 +208,12 @@ export default {
|
|||
|
||||
return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.NODE_NETWORK);
|
||||
},
|
||||
|
||||
hasLonghornSchema() {
|
||||
const inStore = this.$store.getters['currentProduct'].inStore;
|
||||
|
||||
return !!this.$store.getters[`${ inStore }/schemaFor`](LONGHORN.NODES);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -294,21 +300,36 @@ export default {
|
|||
<hr class="divider" />
|
||||
<h3>{{ t('harvester.host.tabs.monitor') }}</h3>
|
||||
<div class="row mb-20">
|
||||
<div class="col span-4">
|
||||
<div
|
||||
class="col"
|
||||
:class="{
|
||||
'span-4': hasLonghornSchema,
|
||||
'span-6': !hasLonghornSchema,
|
||||
}"
|
||||
>
|
||||
<HarvesterCPUUsed
|
||||
:row="value"
|
||||
:resource-name="t('node.detail.glance.consumptionGauge.cpu')"
|
||||
:show-used="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-4">
|
||||
<div
|
||||
class="col"
|
||||
:class="{
|
||||
'span-4': hasLonghornSchema,
|
||||
'span-6': !hasLonghornSchema,
|
||||
}"
|
||||
>
|
||||
<HarvesterMemoryUsed
|
||||
:row="value"
|
||||
:resource-name="t('node.detail.glance.consumptionGauge.memory')"
|
||||
:show-used="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="col span-4">
|
||||
<div
|
||||
v-if="hasLonghornSchema"
|
||||
class="col span-4"
|
||||
>
|
||||
<HarvesterStorageUsed
|
||||
:row="value"
|
||||
:resource-name="t('harvester.host.detail.storage')"
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ export default {
|
|||
:label="t('harvester.fields.image')"
|
||||
:options="imagesOption"
|
||||
:mode="mode"
|
||||
:searchable="true"
|
||||
:required="validateRequired"
|
||||
@input="onImageChange"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ import { allHash } from '@shell/utils/promise';
|
|||
import ModalWithCard from '@shell/components/ModalWithCard';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import { Banner } from '@components/Banner';
|
||||
import UpgradeInfo from './UpgradeInfo';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterUpgrade',
|
||||
|
||||
components: {
|
||||
ModalWithCard, LabeledSelect, Banner
|
||||
ModalWithCard, LabeledSelect, Banner, UpgradeInfo
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
|
|
@ -121,19 +122,19 @@ export default {
|
|||
</button>
|
||||
</header>
|
||||
|
||||
<ModalWithCard ref="deleteTip" name="deleteTip" :width="500">
|
||||
<ModalWithCard ref="deleteTip" name="deleteTip" :width="850">
|
||||
<template #title>
|
||||
<t k="harvester.upgradePage.upgradeApp" />
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<div class="currentVersion">
|
||||
<UpgradeInfo :version="version" />
|
||||
|
||||
<div class="currentVersion mb-15">
|
||||
<span> <t k="harvester.upgradePage.currentVersion" /> </span>
|
||||
<span class="version">{{ currentVersion }}</span>
|
||||
</div>
|
||||
|
||||
<p class="bordered-section"></p>
|
||||
|
||||
<div v-if="selectMode">
|
||||
<LabeledSelect
|
||||
v-model="version"
|
||||
|
|
@ -151,10 +152,10 @@ export default {
|
|||
|
||||
<template #footer>
|
||||
<div class="footer">
|
||||
<button class="btn role-secondary btn-sm mr-20" @click.prevent="cancel">
|
||||
<button class="btn role-secondary mr-20" @click.prevent="cancel">
|
||||
<t k="generic.close" />
|
||||
</button>
|
||||
<button class="btn role-tertiary bg-primary btn-sm mr-20" @click.prevent="handleUpgrade">
|
||||
<button class="btn role-tertiary bg-primary" @click.prevent="handleUpgrade">
|
||||
<t k="harvester.upgradePage.upgrade" />
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
import { Banner } from '@components/Banner';
|
||||
|
||||
export default {
|
||||
name: 'HarvesterUpgradeInfo',
|
||||
|
||||
components: { Banner },
|
||||
|
||||
props: {
|
||||
version: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
releaseVersion() {
|
||||
return !!this.version ? `https://github.com/harvester/harvester/releases/tag/${ this.version }` : `https://github.com/harvester/harvester/releases`;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Banner color="warning">
|
||||
<strong>{{ t('harvester.upgradePage.upgradeInfo.warning') }}:</strong>
|
||||
<p class="mb-5" v-html="t('harvester.upgradePage.upgradeInfo.doc', {}, true)">
|
||||
</p>
|
||||
|
||||
<p class="mb-5">
|
||||
{{ t('harvester.upgradePage.upgradeInfo.tip') }}
|
||||
</p>
|
||||
|
||||
<p class="mb-5">
|
||||
{{ t('harvester.upgradePage.upgradeInfo.moreNotes') }} <a :href="releaseVersion" target="_blank">{{ t('generic.moreInfo') }} </a>
|
||||
</p>
|
||||
</Banner>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -375,50 +375,22 @@ export default {
|
|||
},
|
||||
|
||||
cpuReserved() {
|
||||
const useful = this.pods.filter((pod) => {
|
||||
const nodeName = pod?.spec?.nodeName;
|
||||
|
||||
return this.availableNodes.includes(nodeName);
|
||||
}).reduce((sum, pod) => {
|
||||
const containers = pod?.spec?.containers || [];
|
||||
|
||||
const containerCpuReserved = containers.reduce((sum, c) => {
|
||||
sum += parseSi(c?.resources?.requests?.cpu || '0m');
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
sum += containerCpuReserved;
|
||||
|
||||
return sum;
|
||||
const useful = this.nodes.reduce((total, node) => {
|
||||
return total + node.cpuReserved;
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
total: this.cpusTotal,
|
||||
useful: Number(formatSi(useful)),
|
||||
total: this.cpusTotal,
|
||||
useful,
|
||||
};
|
||||
},
|
||||
|
||||
ramReserved() {
|
||||
const useful = this.pods.filter((pod) => {
|
||||
const nodeName = pod?.spec?.nodeName;
|
||||
|
||||
return this.availableNodes.includes(nodeName);
|
||||
}).reduce((sum, pod) => {
|
||||
const containers = pod?.spec?.containers || [];
|
||||
|
||||
const containerMemoryReserved = containers.reduce((sum, c) => {
|
||||
sum += parseSi(c?.resources?.requests?.memory || '0m', { increment: 1024 });
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
sum += containerMemoryReserved;
|
||||
|
||||
return sum;
|
||||
const useful = this.nodes.reduce((total, node) => {
|
||||
return total + node.memoryReserved;
|
||||
}, 0);
|
||||
|
||||
return this.createMemoryValues(this.memoryTotal, useful);
|
||||
return createMemoryValues(this.memoryTotal, useful);
|
||||
},
|
||||
|
||||
availableNodes() {
|
||||
|
|
@ -583,7 +555,12 @@ export default {
|
|||
<h3 class="mt-40">
|
||||
{{ t('clusterIndexPage.sections.capacity.label') }}
|
||||
</h3>
|
||||
<div class="hardware-resource-gauges">
|
||||
<div
|
||||
class="hardware-resource-gauges"
|
||||
:class="{
|
||||
live: !storageTotal,
|
||||
}"
|
||||
>
|
||||
<HardwareResourceGauge
|
||||
:name="t('harvester.dashboard.hardwareResourceGauge.cpu')"
|
||||
:reserved="cpuReserved"
|
||||
|
|
@ -595,6 +572,7 @@ export default {
|
|||
:used="ramUsed"
|
||||
/>
|
||||
<HardwareResourceGauge
|
||||
v-if="storageTotal"
|
||||
:name="t('harvester.dashboard.hardwareResourceGauge.storage')"
|
||||
:used="storageUsed"
|
||||
:reserved="storageReserved"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ export default {
|
|||
|
||||
if (this.$store.getters['harvester/schemaFor'](LONGHORN.NODES)) {
|
||||
_hash.longhornNodes = this.$store.dispatch('harvester/findAll', { type: LONGHORN.NODES });
|
||||
} else {
|
||||
this.hasLonghornSchema = false;
|
||||
}
|
||||
|
||||
if (this.$store.getters['harvester/schemaFor'](HCI.BLOCK_DEVICE)) {
|
||||
|
|
@ -57,8 +59,9 @@ export default {
|
|||
|
||||
data() {
|
||||
return {
|
||||
rows: [],
|
||||
hasMetricSchema: true
|
||||
rows: [],
|
||||
hasMetricSchema: true,
|
||||
hasLonghornSchema: true,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -86,12 +89,13 @@ export default {
|
|||
];
|
||||
|
||||
if (this.hasMetricSchema) {
|
||||
const width = this.hasLonghornSchema ? '230' : '345';
|
||||
const metricCol = [
|
||||
{
|
||||
name: 'cpu',
|
||||
labelKey: 'node.detail.glance.consumptionGauge.cpu',
|
||||
value: 'id',
|
||||
width: '230',
|
||||
width,
|
||||
formatter: 'HarvesterCPUUsed',
|
||||
formatterOpts: { showUsed: true },
|
||||
},
|
||||
|
|
@ -99,23 +103,28 @@ export default {
|
|||
name: 'memory',
|
||||
labelKey: 'node.detail.glance.consumptionGauge.memory',
|
||||
value: 'id',
|
||||
width: '230',
|
||||
width,
|
||||
formatter: 'HarvesterMemoryUsed',
|
||||
formatterOpts: { showUsed: true },
|
||||
},
|
||||
{
|
||||
name: 'storage',
|
||||
labelKey: 'tableHeaders.storage',
|
||||
value: 'id',
|
||||
width: '230',
|
||||
formatter: 'HarvesterStorageUsed',
|
||||
formatterOpts: { showReserved: true },
|
||||
}
|
||||
];
|
||||
|
||||
out.splice(-1, 0, ...metricCol);
|
||||
}
|
||||
|
||||
if (this.hasLonghornSchema) {
|
||||
const storageHeader = {
|
||||
name: 'storage',
|
||||
labelKey: 'tableHeaders.storage',
|
||||
value: 'id',
|
||||
width: '230',
|
||||
formatter: 'HarvesterStorageUsed',
|
||||
formatterOpts: { showReserved: true },
|
||||
};
|
||||
|
||||
out.splice(-1, 0, storageHeader);
|
||||
}
|
||||
|
||||
out.push(AGE);
|
||||
|
||||
return out;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<script>
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import NodeAffinity from '@shell/components/form/NodeAffinity';
|
||||
import PodAffinity from '@shell/components/form/PodAffinity';
|
||||
import Loading from '@shell/components/Loading';
|
||||
import CreateEditView from '@shell/mixins/create-edit-view';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
|
|
@ -11,10 +13,11 @@ import { Banner } from '@components/Banner';
|
|||
import { get } from '@shell/utils/object';
|
||||
import { mapGetters } from 'vuex';
|
||||
import {
|
||||
HCI, NAMESPACE, MANAGEMENT, CONFIG_MAP, NORMAN
|
||||
HCI, NAMESPACE, MANAGEMENT, CONFIG_MAP, NORMAN, NODE
|
||||
} from '@shell/config/types';
|
||||
import { base64Decode, base64Encode } from '@shell/utils/crypto';
|
||||
import { allHashSettled } from '@shell/utils/promise';
|
||||
import { podAffinity as podAffinityValidator } from '@shell/utils/validators/pod-affinity';
|
||||
import { stringify, exceptionToErrorsArray } from '@shell/utils/error';
|
||||
import { HCI as HCI_ANNOTATIONS } from '@shell/config/labels-annotations';
|
||||
import { isReady } from '@shell/models/harvester/harvesterhci.io.virtualmachineimage';
|
||||
|
|
@ -23,7 +26,7 @@ export default {
|
|||
name: 'ConfigComponentHarvester',
|
||||
|
||||
components: {
|
||||
Loading, LabeledSelect, LabeledInput, UnitInput, Banner, YamlEditor
|
||||
Loading, LabeledSelect, LabeledInput, UnitInput, Banner, YamlEditor, NodeAffinity, PodAffinity
|
||||
},
|
||||
|
||||
mixins: [CreateEditView],
|
||||
|
|
@ -70,6 +73,7 @@ export default {
|
|||
|
||||
if (clusterId && isImportCluster) {
|
||||
const res = await allHashSettled({
|
||||
nodes: this.$store.dispatch('cluster/request', { url: `${ url }/${ NODE }s` }),
|
||||
namespaces: this.$store.dispatch('cluster/request', { url: `${ url }/${ NAMESPACE }s` }),
|
||||
images: this.$store.dispatch('cluster/request', { url: `${ url }/${ HCI.IMAGE }s` }),
|
||||
configMaps: this.$store.dispatch('cluster/request', { url: `${ url }/${ CONFIG_MAP }s` }),
|
||||
|
|
@ -117,6 +121,8 @@ export default {
|
|||
this.userDataOptions = userDataOptions;
|
||||
this.networkDataOptions = networkDataOptions;
|
||||
this.images = res.images.value?.data;
|
||||
this.allNodeObjects = res.nodes.value?.data || [];
|
||||
this.allNodes = this.allNodeObjects.map(node => node.id);
|
||||
|
||||
this.networkOptions = (res.networks.value?.data || []).map( (O) => {
|
||||
let value;
|
||||
|
|
@ -169,9 +175,14 @@ export default {
|
|||
},
|
||||
|
||||
data() {
|
||||
let vmAffinity = { affinity: {} };
|
||||
let userData = '';
|
||||
let networkData = '';
|
||||
|
||||
if (this.value.vmAffinity) {
|
||||
vmAffinity = { affinity: JSON.parse(base64Decode(this.value.vmAffinity)) };
|
||||
}
|
||||
|
||||
if (this.value.userData) {
|
||||
userData = base64Decode(this.value.userData);
|
||||
}
|
||||
|
|
@ -183,6 +194,7 @@ export default {
|
|||
return {
|
||||
credential: null,
|
||||
isImportCluster: false,
|
||||
vmAffinity,
|
||||
userData,
|
||||
networkData,
|
||||
images: [],
|
||||
|
|
@ -190,6 +202,8 @@ export default {
|
|||
networkOptions: [],
|
||||
userDataOptions: [],
|
||||
networkDataOptions: [],
|
||||
allNodes: [],
|
||||
allNodeObjects: [],
|
||||
cpuCount: ''
|
||||
};
|
||||
},
|
||||
|
|
@ -201,18 +215,23 @@ export default {
|
|||
return this.disabled || !!(this.isEdit && this.value.id);
|
||||
},
|
||||
|
||||
imageOptions() {
|
||||
return (this.images || []).filter( (O) => {
|
||||
return !O.spec.url.endsWith('.iso') && isReady.call(O);
|
||||
}).sort((a, b) => a.metadata.creationTimestamp > b.metadata.creationTimestamp ? -1 : 1).map( (O) => {
|
||||
const value = O.id;
|
||||
const label = `${ O.spec.displayName } (${ value })`;
|
||||
imageOptions: {
|
||||
get() {
|
||||
return (this.images || []).filter( (O) => {
|
||||
return !O.spec.url.endsWith('.iso') && isReady.call(O);
|
||||
}).sort((a, b) => a.metadata.creationTimestamp > b.metadata.creationTimestamp ? -1 : 1).map( (O) => {
|
||||
const value = O.id;
|
||||
const label = `${ O.spec.displayName } (${ value })`;
|
||||
|
||||
return {
|
||||
label,
|
||||
value
|
||||
};
|
||||
});
|
||||
return {
|
||||
label,
|
||||
value
|
||||
};
|
||||
});
|
||||
},
|
||||
set(neu) {
|
||||
this.images = neu;
|
||||
}
|
||||
},
|
||||
|
||||
namespaceDisabled() {
|
||||
|
|
@ -226,9 +245,11 @@ export default {
|
|||
this.imageOptions = [];
|
||||
this.networkOptions = [];
|
||||
this.namespaceOptions = [];
|
||||
this.vmAffinity = { affinity: {} };
|
||||
this.value.imageName = '';
|
||||
this.value.networkName = '';
|
||||
this.value.vmNamespace = '';
|
||||
this.value.vmAffinity = '';
|
||||
}
|
||||
|
||||
this.$fetch();
|
||||
|
|
@ -304,6 +325,8 @@ export default {
|
|||
errors.push(message);
|
||||
}
|
||||
|
||||
podAffinityValidator(this.vmAffinity.affinity, this.$store.getters, errors);
|
||||
|
||||
return { errors };
|
||||
},
|
||||
|
||||
|
|
@ -332,6 +355,30 @@ export default {
|
|||
} catch (e) {
|
||||
this.errors = exceptionToErrorsArray(e);
|
||||
}
|
||||
},
|
||||
|
||||
updateScheduling(neu) {
|
||||
const { affinity } = neu;
|
||||
|
||||
if (!affinity.nodeAffinity && !affinity.podAffinity && !affinity.podAntiAffinity) {
|
||||
this.value.vmAffinity = '';
|
||||
this.vmAffinity = { affinity: {} };
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.value.vmAffinity = base64Encode(JSON.stringify(affinity));
|
||||
this.vmAffinity = neu;
|
||||
},
|
||||
|
||||
updateNodeScheduling(nodeAffinity) {
|
||||
if (!this.vmAffinity.affinity) {
|
||||
Object.assign(this.vmAffinity, { affinity: { nodeAffinity } });
|
||||
} else {
|
||||
Object.assign(this.vmAffinity.affinity, { nodeAffinity });
|
||||
}
|
||||
|
||||
this.updateScheduling(this.vmAffinity);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -478,7 +525,23 @@ export default {
|
|||
</div>
|
||||
|
||||
<portal :to="'advanced-'+uuid">
|
||||
<h3>{{ t("cluster.credential.harvester.userData.title") }}</h3>
|
||||
<h3 class="mt-20">
|
||||
{{ t("workload.container.titles.nodeScheduling") }}
|
||||
</h3>
|
||||
<NodeAffinity
|
||||
:mode="mode"
|
||||
:value="vmAffinity.affinity.nodeAffinity"
|
||||
@input="updateNodeScheduling"
|
||||
/>
|
||||
|
||||
<h3 class="mt-20">
|
||||
{{ t("workload.container.titles.podScheduling") }}
|
||||
</h3>
|
||||
<PodAffinity :mode="mode" :value="vmAffinity" :nodes="allNodeObjects" :has-nodes-and-ns="isImportCluster" @update="updateScheduling" />
|
||||
|
||||
<h3 class="mt-20">
|
||||
{{ t("cluster.credential.harvester.userData.title") }}
|
||||
</h3>
|
||||
<div>
|
||||
<LabeledSelect
|
||||
v-if="isImportCluster && isCreate"
|
||||
|
|
|
|||
|
|
@ -231,40 +231,20 @@ export default class HciNode extends SteveModel {
|
|||
return pods.filter(p => p?.spec?.nodeName === this.id && p?.metadata?.name !== 'removing');
|
||||
}
|
||||
|
||||
get reserved() {
|
||||
try {
|
||||
return JSON.parse(this.metadata.annotations[HCI_ANNOTATIONS.HOST_REQUEST] || '{}');
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
get cpuReserved() {
|
||||
const out = this.pods.reduce((sum, pod) => {
|
||||
const containers = pod?.spec?.containers || [];
|
||||
|
||||
const containerCpuReserved = containers.reduce((sum, c) => {
|
||||
sum += parseSi(c?.resources?.requests?.cpu || '0m');
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
sum += containerCpuReserved;
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
return out;
|
||||
return parseSi(this.reserved.cpu || '0');
|
||||
}
|
||||
|
||||
get memoryReserved() {
|
||||
const out = this.pods.reduce((sum, pod) => {
|
||||
const containers = pod?.spec?.containers || [];
|
||||
|
||||
const containerMemoryReserved = containers.reduce((sum, c) => {
|
||||
sum += parseSi(c?.resources?.requests?.memory || '0m', { increment: 1024 });
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
sum += containerMemoryReserved;
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
return out;
|
||||
return parseSi(this.reserved.memory || '0');
|
||||
}
|
||||
|
||||
get canDelete() {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import CruResource from '@shell/components/CruResource';
|
|||
import { RadioGroup } from '@components/Form/Radio';
|
||||
import { LabeledInput } from '@components/Form/LabeledInput';
|
||||
import LabeledSelect from '@shell/components/form/LabeledSelect';
|
||||
import UpgradeInfo from '@shell/list/harvesterhci.io.dashboard/UpgradeInfo';
|
||||
|
||||
import { HCI } from '@shell/config/types';
|
||||
import { exceptionToErrorsArray } from '@shell/utils/error';
|
||||
|
|
@ -19,7 +20,7 @@ const UPLOAD = 'upload';
|
|||
export default {
|
||||
name: 'HarvesterAirgapUpgrade',
|
||||
components: {
|
||||
CruResource, LabeledSelect, LabeledInput, RadioGroup
|
||||
CruResource, LabeledSelect, LabeledInput, RadioGroup, UpgradeInfo
|
||||
},
|
||||
|
||||
async fetch() {
|
||||
|
|
@ -216,7 +217,7 @@ export default {
|
|||
>
|
||||
<RadioGroup
|
||||
v-model="imageSource"
|
||||
class="mb-20 image-group"
|
||||
class="image-group"
|
||||
name="image"
|
||||
:options="[
|
||||
IMAGE_METHOD.NEW,
|
||||
|
|
@ -228,6 +229,8 @@ export default {
|
|||
]"
|
||||
/>
|
||||
|
||||
<UpgradeInfo />
|
||||
|
||||
<div v-if="uploadImage">
|
||||
<LabeledInput v-model.trim="imageValue.spec.displayName" class="mb-20" label-key="harvester.fields.name" required />
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue