feat: Add quota info in create instance page

1. Add quota info in create instance page
2. Fix volume quota check when create instance

Change-Id: I01fd430996c49083ffb2168e5672d6c080931e6c
This commit is contained in:
Jingwei.Zhang 2022-05-30 17:22:18 +08:00
parent e740c89b9c
commit 3c54d8afab
7 changed files with 234 additions and 9 deletions

View File

@ -0,0 +1,32 @@
import React, { useState } from 'react';
import { Button, Card } from 'antd';
import { ExpandOutlined, CompressOutlined } from '@ant-design/icons';
export default function InfoButton(props) {
const { content, defaultCollapsed = false, title } = props;
const [collapsed, setCollapsed] = useState(defaultCollapsed);
const onChangeCollapsed = () => {
setCollapsed(!collapsed);
};
if (collapsed) {
return (
<Button onClick={onChangeCollapsed}>
<CompressOutlined />
</Button>
);
}
const closeButton = (
<Button onClick={onChangeCollapsed}>
<ExpandOutlined />
</Button>
);
return (
<div>
<Card title={title} extra={closeButton}>
{content}
</Card>
</div>
);
}

View File

@ -0,0 +1,115 @@
import React from 'react';
import { Skeleton } from 'antd';
import {
Chart,
Interval,
Coordinate,
Legend,
View,
Annotation,
} from 'bizcharts';
function Ring(props) {
const { used = 0, add = 1, reserved = 0, limit = 1 } = props;
const left = limit - used - reserved - add;
const data = [
{
type: t('Used'),
value: used,
color: '#5B8FF9',
},
];
if (reserved) {
data.push({
type: t('Reserved'),
value: reserved,
color: '#5D7092',
});
}
data.push({
type: t('New'),
value: add,
color: '#5AD8A6',
});
data.push({
type: t('Left'),
value: left,
color: '#eee',
});
const colors = data.map((it) => it.color);
return (
<div style={{ width: '120px' }}>
<Chart placeholder={false} height={120} padding="auto" autoFit>
<Legend visible={false} />
{/* 绘制图形 */}
<View data={data}>
<Coordinate type="theta" innerRadius={0.75} />
<Interval
position="value"
adjust="stack"
color={['type', colors]}
size={16}
/>
<Annotation.Text
position={['50%', '40%']}
content={t('Quota')}
style={{
lineHeight: '240px',
fontSize: '16',
fill: '#000',
textAlign: 'center',
}}
/>
<Annotation.Text
position={['50%', '62%']}
content={limit}
style={{
lineHeight: '240px',
fontSize: '24',
fill: colors[0],
textAlign: 'center',
}}
/>
</View>
</Chart>
</div>
);
}
function QuotaInfo(props) {
const { used = 0, reserved = 0, add = 1 } = props;
return (
<div>
<p>
<b>{t('Quota')}:</b> {t('Unlimit')}
</p>
<p>
<b>{t('Used')}:</b>: {used}
</p>
{!!reserved && (
<p>
<b>{t('Reserved')}:</b>: {reserved}
</p>
)}
<p>
<b>{t('New')}:</b>: {add}
</p>
</div>
);
}
export default function QuotaChart(props) {
const { limit = 0, loading } = props;
if (loading) {
return <Skeleton />;
}
if (limit === -1) {
return <QuotaInfo {...props} />;
}
return (
<div>
<Ring {...props} />
</div>
);
}

View File

@ -20,6 +20,8 @@ import classnames from 'classnames';
import { firstUpperCase, unescapeHtml } from 'utils/index';
import { parse } from 'qs';
import NotFound from 'components/Cards/NotFound';
import InfoButton from 'components/InfoButton';
import QuotaChart from 'components/QuotaChart';
import { getPath, getLinkRender } from 'utils/route-map';
import styles from './index.less';
@ -205,6 +207,14 @@ export default class BaseStepForm extends React.Component {
return false;
}
get showQuota() {
return false;
}
get quotaInfo() {
return null;
}
setFormRefs() {
this.formRefs = this.steps.map(() => React.createRef());
}
@ -416,6 +426,34 @@ export default class BaseStepForm extends React.Component {
);
}
renderQuota() {
if (!this.showQuota) {
return null;
}
let props = {};
if (!this.quotaInfo || isEmpty(this.quotaInfo)) {
props.loading = true;
} else {
props = {
loading: false,
...this.quotaInfo,
};
}
return <QuotaChart {...props} />;
}
renderRightTopExtra() {
const content = this.renderQuota();
if (!content) {
return null;
}
return (
<div className={styles['right-top-extra-wrapper']}>
<InfoButton content={content} />
</div>
);
}
render() {
if (this.endpointError) {
return (
@ -432,6 +470,7 @@ export default class BaseStepForm extends React.Component {
<div className={classnames(styles.wrapper, this.className)}>
<Spin spinning={this.isLoading || this.isSubmitting}>
{this.renderSteps()}
{this.renderRightTopExtra()}
{this.renderFooter()}
</Spin>
</div>

View File

@ -76,3 +76,13 @@
margin-left: 16px;
color: rgb(72, 72, 72);
}
.right-top-extra-wrapper {
position: absolute;
top: 95px;
right: 30px;
z-index: 100;
background-color: red;
border-radius: 5px;
box-shadow: 0 2px 30px 0 rgba(0, 0, 0, 9%);
}

View File

@ -1268,6 +1268,7 @@
"Latvia": "Latvia",
"Leave Maintenance Mode": "Leave Maintenance Mode",
"Lebanon": "Lebanon",
"Left": "Left",
"Lesotho": "Lesotho",
"Liberia": "Liberia",
"Libyan Arab Jamahiriya": "Libyan Arab Jamahiriya",
@ -1793,6 +1794,7 @@
"Queued": "Queued",
"Queued To Apply": "Queued To Apply",
"Queued To Deny": "Queued To Deny",
"Quota": "Quota",
"Quota Overview": "Quota Overview",
"Quota exceeded": "Quota exceeded",
"Quota is not enough for extend share.": "Quota is not enough for extend share.",

View File

@ -1268,6 +1268,7 @@
"Latvia": "拉脱维亚",
"Leave Maintenance Mode": "退出维护模式",
"Lebanon": "黎巴嫩",
"Left": "剩余",
"Lesotho": "莱索托",
"Liberia": "利比里亚",
"Libyan Arab Jamahiriya": "利比亚",
@ -1793,6 +1794,7 @@
"Queued": "已排队",
"Queued To Apply": "排队申请",
"Queued To Deny": "排队删除",
"Quota": "配额",
"Quota Overview": "配额概况",
"Quota exceeded": "配额用尽",
"Quota is not enough for extend share.": "配额不足以扩容共享。",

View File

@ -145,6 +145,24 @@ export class StepCreate extends StepAction {
);
}
get showQuota() {
return true;
}
get quotaInfo() {
const { instances = {} } = toJS(this.projectStore.quota) || {};
const { limit } = instances || {};
if (!limit) {
return {};
}
const { data = {} } = this.state;
const { count = 1 } = data;
return {
...instances,
add: count,
};
}
get errorText() {
const { status } = this.state;
if (status === 'error') {
@ -210,7 +228,7 @@ export class StepCreate extends StepAction {
getVolumeInputMap() {
const { data } = this.state;
const { systemDisk = {}, dataDisk = [] } = data;
const { systemDisk = {}, dataDisk = [], count = 1 } = data;
const newCountMap = {};
const newSizeMap = {};
let totalNewCount = 0;
@ -220,20 +238,22 @@ export class StepCreate extends StepAction {
const { label } = systemDisk.typeOption || {};
newCountMap[label] = !newCountMap[label] ? 1 : newCountMap[label] + 1;
newSizeMap[label] = !newSizeMap[label] ? size : newSizeMap[label] + size;
totalNewCount += 1;
totalNewSize += size;
totalNewCount += 1 * count;
totalNewSize += size * count;
}
if (dataDisk) {
dataDisk.forEach((item) => {
if (item.value && item.value.type) {
const { size } = item.value;
const { label } = item.value.typeOption || {};
newCountMap[label] = !newCountMap[label] ? 1 : newCountMap[label] + 1;
newCountMap[label] = !newCountMap[label]
? 1 * count
: newCountMap[label] + 1 * count;
newSizeMap[label] = !newSizeMap[label]
? size
: newSizeMap[label] + size;
totalNewCount += 1;
totalNewSize += size;
? size * count
: newSizeMap[label] + size * count;
totalNewCount += 1 * count;
totalNewSize += size * count;
}
});
}
@ -320,7 +340,12 @@ export class StepCreate extends StepAction {
const { count = 1, source: { value: sourceValue } = {} } = data;
const configs = {
min: 1,
max: sourceValue === 'bootableVolume' ? 1 : 100,
max:
sourceValue === 'bootableVolume'
? 1
: isFinite(this.quota)
? this.quota
: 100,
precision: 0,
onChange: this.onCountChange,
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),