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:
parent
e740c89b9c
commit
3c54d8afab
32
src/components/InfoButton/index.jsx
Normal file
32
src/components/InfoButton/index.jsx
Normal 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>
|
||||
);
|
||||
}
|
115
src/components/QuotaChart/index.jsx
Normal file
115
src/components/QuotaChart/index.jsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
|
@ -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%);
|
||||
}
|
||||
|
@ -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.",
|
||||
|
@ -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.": "配额不足以扩容共享。",
|
||||
|
@ -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, ''),
|
||||
|
Loading…
Reference in New Issue
Block a user