feat: support cpu && mem quota info when create instance

1. Support cpu && mem quota info when create instance
2. Disable click next button when cpu && mem left quota is insufficient
3. Update StepForm to support disable click next/submit button

Change-Id: Iae26d397debe67c44d8f80b6c5d85611a1cf67d5
This commit is contained in:
Jingwei.Zhang 2022-07-04 14:42:27 +08:00
parent 10a951e088
commit b6d2eb512f
3 changed files with 119 additions and 42 deletions

View File

@ -220,6 +220,14 @@ export default class BaseStepForm extends React.Component {
return null;
}
get disableNext() {
return false;
}
get disableSubmit() {
return false;
}
setFormRefs() {
this.formRefs = this.steps.map(() => React.createRef());
}
@ -297,7 +305,11 @@ export default class BaseStepForm extends React.Component {
}
const { title } = this.steps[current + 1];
return (
<Button type="primary" onClick={() => this.next()}>
<Button
type="primary"
onClick={() => this.next()}
disabled={this.disableNext}
>
{`${t('Next')}: ${title}`}
</Button>
);
@ -381,7 +393,11 @@ export default class BaseStepForm extends React.Component {
{this.getPrevBtn()}
{this.getNextBtn()}
{current === this.steps.length - 1 && (
<Button type="primary" onClick={this.onClickSubmit}>
<Button
type="primary"
onClick={this.onClickSubmit}
disabled={this.disableSubmit}
>
{t('Confirm')}
</Button>
)}

View File

@ -22,12 +22,13 @@ import globalProjectStore from 'stores/keystone/project';
import classnames from 'classnames';
import { isEmpty, isFinite, isString } from 'lodash';
import { getUserData } from 'resources/nova/instance';
import { getGiBValue } from 'utils/index';
import Notify from 'components/Notify';
import styles from './index.less';
import ConfirmStep from './ConfirmStep';
import SystemStep from './SystemStep';
import NetworkStep from './NetworkStep';
import BaseStep from './BaseStep';
import styles from './index.less';
export class StepCreate extends StepAction {
static id = 'instance-create';
@ -63,19 +64,28 @@ export class StepCreate extends StepAction {
}
async getQuota() {
await this.projectStore.fetchProjectQuota({
project_id: this.currentProjectId,
});
await Promise.all([
this.projectStore.fetchProjectNovaQuota(),
this.projectStore.fetchProjectCinderQuota(),
]);
this.onCountChange(1);
}
get quota() {
const { instances = {} } = toJS(this.projectStore.quota) || {};
const { limit = 10, used = 0 } = instances;
if (limit === -1) {
get disableNext() {
return !!this.errorMsg;
}
get disableSubmit() {
return !!this.errorMsg;
}
get instanceQuota() {
const { instances: { left = 0 } = {} } =
toJS(this.projectStore.novaQuota) || {};
if (left === -1) {
return Infinity;
}
return limit - used;
return left;
}
get name() {
@ -150,7 +160,11 @@ export class StepCreate extends StepAction {
}
get quotaInfo() {
const { instances = {} } = toJS(this.projectStore.quota) || {};
const {
instances = {},
cores = {},
ram = {},
} = toJS(this.projectStore.novaQuota) || {};
const { limit } = instances || {};
if (!limit) {
return [];
@ -165,6 +179,23 @@ export class StepCreate extends StepAction {
// type: 'line',
};
const { newCPU, newRam } = this.getFlavorInput();
const cpuQuotaInfo = {
...cores,
add: newCPU,
name: 'cpu',
title: t('CPU'),
type: 'line',
};
const ramQuotaInfo = {
...ram,
add: newRam,
name: 'ram',
title: t('Memory (GiB)'),
type: 'line',
};
const volumeQuota = this.getVolumeQuota();
const { totalNewCount, totalNewSize } = this.getVolumeInputMap();
const volumeQuotaInfo = {
@ -181,7 +212,13 @@ export class StepCreate extends StepAction {
title: t('Volume Size'),
type: 'line',
};
return [instanceQuotaInfo, volumeQuotaInfo, volumeSizeQuotaInfo];
return [
instanceQuotaInfo,
cpuQuotaInfo,
ramQuotaInfo,
volumeQuotaInfo,
volumeSizeQuotaInfo,
];
}
get errorText() {
@ -203,27 +240,16 @@ export class StepCreate extends StepAction {
onCountChange = (value) => {
const { data } = this.state;
let msg = t('Quota: Project quotas sufficient resources can be created');
let status = 'success';
if (isFinite(this.quota) && value > this.quota) {
msg = t(
'Quota: Insufficient quota to create resources, please adjust resource quantity or quota(left { quota }, input { input }).',
{ quota: this.quota, input: value }
);
status = 'error';
}
this.msg = msg;
this.setState({
data: {
...data,
count: value,
},
status,
});
};
getVolumeQuota() {
const quotaAll = toJS(this.projectStore.quota) || {};
const quotaAll = toJS(this.projectStore.cinderQuota) || {};
const result = {};
Object.keys(quotaAll).forEach((key) => {
if (key.includes('volumes') || key.includes('gigabytes')) {
@ -233,11 +259,11 @@ export class StepCreate extends StepAction {
return result;
}
getVolumeQuotaMsg(value, quota, name) {
if (!quota || quota.limit === -1) {
getQuotaMessage(value, quota, name) {
const { left = 0 } = quota || {};
if (left === -1) {
return '';
}
const left = quota.limit - quota.in_use;
if (value > left) {
return t(
'Insufficient {name} quota to create resources(left { quota }, input { input }).',
@ -292,7 +318,7 @@ export class StepCreate extends StepAction {
const { totalNewCount, totalNewSize, newCountMap, newSizeMap } =
this.getVolumeInputMap();
const quotaAll = this.getVolumeQuota();
const totalCountMsg = this.getVolumeQuotaMsg(
const totalCountMsg = this.getQuotaMessage(
totalNewCount,
quotaAll.volumes,
t('volume')
@ -300,7 +326,7 @@ export class StepCreate extends StepAction {
if (totalCountMsg) {
return totalCountMsg;
}
const totalSizeMsg = this.getVolumeQuotaMsg(
const totalSizeMsg = this.getQuotaMessage(
totalNewSize,
quotaAll.gigabytes,
t('volume gigabytes')
@ -309,7 +335,7 @@ export class StepCreate extends StepAction {
return totalSizeMsg;
}
Object.keys(newCountMap).forEach((key) => {
const countMsg = this.getVolumeQuotaMsg(
const countMsg = this.getQuotaMessage(
newCountMap[key],
quotaAll[`volumes_${key}`],
t('volume type {type}', { type: key })
@ -322,7 +348,7 @@ export class StepCreate extends StepAction {
return msg;
}
Object.keys(newSizeMap).forEach((key) => {
const sizeMsg = this.getVolumeQuotaMsg(
const sizeMsg = this.getQuotaMessage(
newSizeMap[key],
quotaAll[`gigabytes_${key}`],
t('volume type {type} gigabytes', { type: key })
@ -334,20 +360,48 @@ export class StepCreate extends StepAction {
return msg;
}
getFlavorInput() {
const { data } = this.state;
const { flavor = {}, count = 1 } = data;
const { selectedRows = [] } = flavor;
const { vcpus = 0, ram = 0 } = selectedRows[0] || {};
const ramGiB = getGiBValue(ram);
const newCPU = vcpus * count;
const newRam = ramGiB * count;
return {
newCPU,
newRam,
};
}
checkFlavorQuota() {
const { newCPU, newRam } = this.getFlavorInput();
const { cores = {}, ram = {} } = this.projectStore.novaQuota;
const { left = 0 } = cores || {};
const { left: leftRam = 0 } = ram || {};
if (left !== -1 && left < newCPU) {
return this.getQuotaMessage(newCPU, cores, t('CPU'));
}
if (leftRam !== -1 && leftRam < newRam) {
return this.getQuotaMessage(newRam, ram, t('Memory'));
}
return '';
}
get badgeStyle() {
return { marginTop: 8, marginBottom: 8, marginLeft: 10, maxWidth: 600 };
}
renderBadge() {
const { status = 'success' } = this.state;
const flavorMsg = this.checkFlavorQuota();
const volumeMsg = this.checkVolumeQuota();
if (!volumeMsg && status === 'success') {
if (!flavorMsg && !volumeMsg) {
this.status = 'success';
this.errorMsg = '';
return null;
}
this.status = 'error';
const msg = status === 'error' ? this.msg : volumeMsg;
const msg = flavorMsg || volumeMsg;
if (this.errorMsg !== msg) {
$message.error(msg);
}
@ -371,8 +425,8 @@ export class StepCreate extends StepAction {
max:
sourceValue === 'bootableVolume'
? 1
: isFinite(this.quota)
? this.quota
: isFinite(this.instanceQuota)
? this.instanceQuota
: 100,
precision: 0,
onChange: this.onCountChange,

View File

@ -258,14 +258,10 @@ export class ProjectStore extends Base {
] = await Promise.all(promiseArr);
this.isSubmitting = false;
const { quota_set: novaQuota } = novaResult;
const { ram } = novaQuota;
const { quota_set: cinderQuota = {} } = cinderResult || {};
const { quota: neutronQuota } = neutronResult;
const { quota_set: shareQuota = {} } = shareResult || {};
novaQuota.ram = {
in_use: getGiBValue(ram.in_use),
limit: ram.limit === -1 ? ram.limit : getGiBValue(ram.limit),
};
this.updateNovaQuota(novaQuota);
const renameShareQuota = Object.keys(shareQuota).reduce((pre, cur) => {
const key = !cur.includes('share') ? `share_${cur}` : cur;
pre[key] = shareQuota[cur];
@ -462,6 +458,16 @@ export class ProjectStore extends Base {
return limit - used - reserved;
};
updateNovaQuota = (quota) => {
const { ram: { limit = 0, in_use = 0, reserved = 0 } = {} } = quota || {};
quota.ram = {
in_use: getGiBValue(in_use),
limit: limit === -1 ? limit : getGiBValue(limit),
reserved: getGiBValue(reserved),
};
return quota;
};
updateQuotaData = (quota) => {
const newData = JSON.parse(JSON.stringify(quota));
Object.keys(newData).forEach((it) => {
@ -479,6 +485,7 @@ export class ProjectStore extends Base {
async fetchProjectNovaQuota() {
const result = await this.novaQuotaClient.detail(this.currentProjectId);
const { quota_set: quota } = result;
this.updateNovaQuota(quota);
const novaQuota = this.updateQuotaData(quota);
this.novaQuota = novaQuota;
return novaQuota;