feat: Show quota info when create server group

1. Update Form/ModalAction/ActionButton to support right extra info display in modal
2. Add quota info when create server group
3. Support disable click submit button in modalAction / formAction

Change-Id: I511c383f0ffa256b76c1a8fd123c6326e7a43ed3
This commit is contained in:
Jingwei.Zhang 2022-06-22 16:36:26 +08:00
parent b1591a9a56
commit 514b213ca7
6 changed files with 150 additions and 27 deletions

View File

@ -74,8 +74,8 @@ export default class BaseForm extends React.Component {
this.unMountActions && this.unMountActions(); this.unMountActions && this.unMountActions();
} }
get canSubmit() { get disableSubmit() {
return true; return false;
} }
get name() { get name() {
@ -534,7 +534,7 @@ export default class BaseForm extends React.Component {
{t('Cancel')} {t('Cancel')}
</Button> </Button>
<Button <Button
disabled={!this.canSubmit} disabled={this.disableSubmit}
type="primary" type="primary"
className={styles.submit} className={styles.submit}
onClick={this.onClickSubmit} onClick={this.onClickSubmit}
@ -651,6 +651,9 @@ export default class BaseForm extends React.Component {
} }
renderRightTopExtra() { renderRightTopExtra() {
if (this.isModal) {
return null;
}
const content = this.renderQuota(); const content = this.renderQuota();
if (!content) { if (!content) {
return null; return null;
@ -663,6 +666,17 @@ export default class BaseForm extends React.Component {
); );
} }
renderModalRightExtra() {
if (!this.isModal) {
return null;
}
const content = this.renderQuota();
if (!content) {
return null;
}
return <div className={styles['modal-right-extra-wrapper']}>{content}</div>;
}
render() { render() {
const wrapperPadding = const wrapperPadding =
this.listUrl || this.isStep || (this.isModal && this.tips) this.listUrl || this.isStep || (this.isModal && this.tips)
@ -679,10 +693,8 @@ export default class BaseForm extends React.Component {
formStyle.height = `calc(100% - ${tipHeight}px)`; formStyle.height = `calc(100% - ${tipHeight}px)`;
} }
} }
return (
<div const formDiv = (
className={classnames(styles.wrapper, wrapperPadding, this.className)}
>
<Spin spinning={this.isSubmitting} tip={this.renderSubmittingTip()}> <Spin spinning={this.isSubmitting} tip={this.renderSubmittingTip()}>
{tips} {tips}
{this.renderRightTopExtra()} {this.renderRightTopExtra()}
@ -691,6 +703,21 @@ export default class BaseForm extends React.Component {
</div> </div>
{this.renderFooter()} {this.renderFooter()}
</Spin> </Spin>
);
const onlyForm = !this.isModal || (this.isModal && !this.showQuota);
const modalInner =
this.isModal && !onlyForm ? (
<Row justify="space-between" align="top">
<Col span={18}>{formDiv}</Col>
<Col span={6}>{this.renderModalRightExtra()}</Col>
</Row>
) : null;
return (
<div
className={classnames(styles.wrapper, wrapperPadding, this.className)}
>
{onlyForm && formDiv}
{modalInner}
</div> </div>
); );
} }

View File

@ -139,3 +139,7 @@
} }
} }
} }
.modal-right-extra-wrapper {
border-left: solid 2px @gray-2;
}

View File

@ -429,16 +429,18 @@ export class ActionButton extends Component {
callback(); callback();
}; };
getModalWidth = (size) => { getModalWidth = (action) => {
const { modalSize: size, showQuota = false } = action;
const multi = showQuota ? 1.25 : 1;
switch (size) { switch (size) {
case 'small': case 'small':
return 520; return 520 * multi;
case 'middle': case 'middle':
return 720; return 720 * multi;
case 'large': case 'large':
return 1200; return 1200 * multi;
default: default:
return 520; return 520 * multi;
} }
}; };
@ -455,8 +457,15 @@ export class ActionButton extends Component {
} }
const { title, action, item, containerProps, items } = this.props; const { title, action, item, containerProps, items } = this.props;
const ActionComponent = action; const ActionComponent = action;
const { modalSize, okText, cancelText, id, className, readOnly } = action; const {
const width = this.getModalWidth(modalSize); okText,
cancelText,
id,
className,
readOnly,
disableSubmit = false,
} = action;
const width = this.getModalWidth(action);
const modalProps = { const modalProps = {
title, title,
visible, visible,
@ -464,6 +473,9 @@ export class ActionButton extends Component {
width, width,
onOk: () => this.onClickModalActionOk(), onOk: () => this.onClickModalActionOk(),
onCancel: this.onClickModalActionCancel, onCancel: this.onClickModalActionCancel,
okButtonProps: {
disabled: disableSubmit,
},
confirmLoading: submitLoading, confirmLoading: submitLoading,
okText, okText,
cancelText, cancelText,

View File

@ -42,6 +42,14 @@ export default class ModalAction extends BaseForm {
return 'small'; return 'small';
} }
static get showQuota() {
return false;
}
get showQuota() {
return false;
}
get labelCol() { get labelCol() {
const size = this.getModalSize(); const size = this.getModalSize();
if (size === 'large') { if (size === 'large') {

View File

@ -17,6 +17,7 @@ import { inject, observer } from 'mobx-react';
import globalServerGroupStore from 'stores/nova/server-group'; import globalServerGroupStore from 'stores/nova/server-group';
import { ModalAction } from 'containers/Action'; import { ModalAction } from 'containers/Action';
import policyType from 'resources/nova/server-group'; import policyType from 'resources/nova/server-group';
import globalProjectStore from 'src/stores/keystone/project';
export class Create extends ModalAction { export class Create extends ModalAction {
static id = 'create'; static id = 'create';
@ -24,7 +25,11 @@ export class Create extends ModalAction {
static title = t('Create Server Group'); static title = t('Create Server Group');
init() { init() {
this.state.quota = {};
this.state.quotaLoading = true;
this.store = globalServerGroupStore; this.store = globalServerGroupStore;
this.projectStore = globalProjectStore;
this.getQuota();
} }
get name() { get name() {
@ -35,6 +40,45 @@ export class Create extends ModalAction {
static allowed = () => Promise.resolve(true); static allowed = () => Promise.resolve(true);
static get disableSubmit() {
const { novaQuota: { server_groups: { left = 0 } = {} } = {} } =
globalProjectStore;
return left === 0;
}
static get showQuota() {
return true;
}
get showQuota() {
return true;
}
async getQuota() {
const result = await this.projectStore.fetchProjectNovaQuota();
const { server_groups: quota = {} } = result || {};
this.setState({
quota,
quotaLoading: false,
});
}
get quotaInfo() {
const { quota = {}, quotaLoading } = this.state;
if (quotaLoading) {
return [];
}
const { left = 0 } = quota;
const add = left === 0 ? 0 : 1;
const data = {
...quota,
add,
name: 'server_groups',
title: t('Server Group'),
};
return [data];
}
get formItems() { get formItems() {
const policies = Object.keys(policyType).map((it) => ({ const policies = Object.keys(policyType).map((it) => ({
value: it, value: it,

View File

@ -14,7 +14,7 @@
import { action, observable } from 'mobx'; import { action, observable } from 'mobx';
import { getGiBValue } from 'utils/index'; import { getGiBValue } from 'utils/index';
import { isNil, isEmpty } from 'lodash'; import { isNil, isEmpty, isObject } from 'lodash';
import client from 'client'; import client from 'client';
import Base from 'stores/base'; import Base from 'stores/base';
import globalRootStore from 'stores/root'; import globalRootStore from 'stores/root';
@ -23,6 +23,9 @@ export class ProjectStore extends Base {
@observable @observable
quota = {}; quota = {};
@observable
novaQuota = {};
@observable @observable
groupRoleList = []; groupRoleList = [];
@ -261,14 +264,9 @@ export class ProjectStore extends Base {
...neutronQuota, ...neutronQuota,
...renameShareQuota, ...renameShareQuota,
}; };
const quotaKey = Object.keys(quota); const newQuota = this.updateQuotaData(quota);
quotaKey.forEach((it) => { this.quota = newQuota;
if (quota[it].in_use !== undefined) { return newQuota;
quota[it].used = quota[it].in_use;
}
});
this.quota = quota;
return quota;
} }
omitNil = (obj) => { omitNil = (obj) => {
@ -437,6 +435,36 @@ export class ProjectStore extends Base {
const result = await this.client.groups.roles.delete(id, groupId, roleId); const result = await this.client.groups.roles.delete(id, groupId, roleId);
return result; return result;
} }
getLeftQuotaData = (data) => {
const { used = 0, limit = 0, reserved = 0 } = data;
if (limit === -1) {
return -1;
}
return limit - used - reserved;
};
updateQuotaData = (quota) => {
const newData = JSON.parse(JSON.stringify(quota));
Object.keys(newData).forEach((it) => {
if (isObject(newData[it])) {
if (newData[it].in_use !== undefined) {
newData[it].used = newData[it].in_use;
}
newData[it].left = this.getLeftQuotaData(newData[it]);
}
});
return newData;
};
@action
async fetchProjectNovaQuota() {
const result = await this.novaQuotaClient.detail(this.currentProjectId);
const { quota_set: quota } = result;
const novaQuota = this.updateQuotaData(quota);
this.novaQuota = novaQuota;
return novaQuota;
}
} }
const globalProjectStore = new ProjectStore(); const globalProjectStore = new ProjectStore();