Merge "feat: Add quota info in create instance page"
This commit is contained in:
commit
458b9838a0
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 { firstUpperCase, unescapeHtml } from 'utils/index';
|
||||||
import { parse } from 'qs';
|
import { parse } from 'qs';
|
||||||
import NotFound from 'components/Cards/NotFound';
|
import NotFound from 'components/Cards/NotFound';
|
||||||
|
import InfoButton from 'components/InfoButton';
|
||||||
|
import QuotaChart from 'components/QuotaChart';
|
||||||
import { getPath, getLinkRender } from 'utils/route-map';
|
import { getPath, getLinkRender } from 'utils/route-map';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -205,6 +207,14 @@ export default class BaseStepForm extends React.Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showQuota() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get quotaInfo() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
setFormRefs() {
|
setFormRefs() {
|
||||||
this.formRefs = this.steps.map(() => React.createRef());
|
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() {
|
render() {
|
||||||
if (this.endpointError) {
|
if (this.endpointError) {
|
||||||
return (
|
return (
|
||||||
@ -432,6 +470,7 @@ export default class BaseStepForm extends React.Component {
|
|||||||
<div className={classnames(styles.wrapper, this.className)}>
|
<div className={classnames(styles.wrapper, this.className)}>
|
||||||
<Spin spinning={this.isLoading || this.isSubmitting}>
|
<Spin spinning={this.isLoading || this.isSubmitting}>
|
||||||
{this.renderSteps()}
|
{this.renderSteps()}
|
||||||
|
{this.renderRightTopExtra()}
|
||||||
{this.renderFooter()}
|
{this.renderFooter()}
|
||||||
</Spin>
|
</Spin>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,3 +76,13 @@
|
|||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
color: rgb(72, 72, 72);
|
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",
|
"Latvia": "Latvia",
|
||||||
"Leave Maintenance Mode": "Leave Maintenance Mode",
|
"Leave Maintenance Mode": "Leave Maintenance Mode",
|
||||||
"Lebanon": "Lebanon",
|
"Lebanon": "Lebanon",
|
||||||
|
"Left": "Left",
|
||||||
"Lesotho": "Lesotho",
|
"Lesotho": "Lesotho",
|
||||||
"Liberia": "Liberia",
|
"Liberia": "Liberia",
|
||||||
"Libyan Arab Jamahiriya": "Libyan Arab Jamahiriya",
|
"Libyan Arab Jamahiriya": "Libyan Arab Jamahiriya",
|
||||||
@ -1793,6 +1794,7 @@
|
|||||||
"Queued": "Queued",
|
"Queued": "Queued",
|
||||||
"Queued To Apply": "Queued To Apply",
|
"Queued To Apply": "Queued To Apply",
|
||||||
"Queued To Deny": "Queued To Deny",
|
"Queued To Deny": "Queued To Deny",
|
||||||
|
"Quota": "Quota",
|
||||||
"Quota Overview": "Quota Overview",
|
"Quota Overview": "Quota Overview",
|
||||||
"Quota exceeded": "Quota exceeded",
|
"Quota exceeded": "Quota exceeded",
|
||||||
"Quota is not enough for extend share.": "Quota is not enough for extend share.",
|
"Quota is not enough for extend share.": "Quota is not enough for extend share.",
|
||||||
|
@ -1268,6 +1268,7 @@
|
|||||||
"Latvia": "拉脱维亚",
|
"Latvia": "拉脱维亚",
|
||||||
"Leave Maintenance Mode": "退出维护模式",
|
"Leave Maintenance Mode": "退出维护模式",
|
||||||
"Lebanon": "黎巴嫩",
|
"Lebanon": "黎巴嫩",
|
||||||
|
"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 Overview": "配额概况",
|
"Quota Overview": "配额概况",
|
||||||
"Quota exceeded": "配额用尽",
|
"Quota exceeded": "配额用尽",
|
||||||
"Quota is not enough for extend share.": "配额不足以扩容共享。",
|
"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() {
|
get errorText() {
|
||||||
const { status } = this.state;
|
const { status } = this.state;
|
||||||
if (status === 'error') {
|
if (status === 'error') {
|
||||||
@ -210,7 +228,7 @@ export class StepCreate extends StepAction {
|
|||||||
|
|
||||||
getVolumeInputMap() {
|
getVolumeInputMap() {
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
const { systemDisk = {}, dataDisk = [] } = data;
|
const { systemDisk = {}, dataDisk = [], count = 1 } = data;
|
||||||
const newCountMap = {};
|
const newCountMap = {};
|
||||||
const newSizeMap = {};
|
const newSizeMap = {};
|
||||||
let totalNewCount = 0;
|
let totalNewCount = 0;
|
||||||
@ -220,20 +238,22 @@ export class StepCreate extends StepAction {
|
|||||||
const { label } = systemDisk.typeOption || {};
|
const { label } = systemDisk.typeOption || {};
|
||||||
newCountMap[label] = !newCountMap[label] ? 1 : newCountMap[label] + 1;
|
newCountMap[label] = !newCountMap[label] ? 1 : newCountMap[label] + 1;
|
||||||
newSizeMap[label] = !newSizeMap[label] ? size : newSizeMap[label] + size;
|
newSizeMap[label] = !newSizeMap[label] ? size : newSizeMap[label] + size;
|
||||||
totalNewCount += 1;
|
totalNewCount += 1 * count;
|
||||||
totalNewSize += size;
|
totalNewSize += size * count;
|
||||||
}
|
}
|
||||||
if (dataDisk) {
|
if (dataDisk) {
|
||||||
dataDisk.forEach((item) => {
|
dataDisk.forEach((item) => {
|
||||||
if (item.value && item.value.type) {
|
if (item.value && item.value.type) {
|
||||||
const { size } = item.value;
|
const { size } = item.value;
|
||||||
const { label } = item.value.typeOption || {};
|
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]
|
newSizeMap[label] = !newSizeMap[label]
|
||||||
? size
|
? size * count
|
||||||
: newSizeMap[label] + size;
|
: newSizeMap[label] + size * count;
|
||||||
totalNewCount += 1;
|
totalNewCount += 1 * count;
|
||||||
totalNewSize += size;
|
totalNewSize += size * count;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -320,7 +340,12 @@ export class StepCreate extends StepAction {
|
|||||||
const { count = 1, source: { value: sourceValue } = {} } = data;
|
const { count = 1, source: { value: sourceValue } = {} } = data;
|
||||||
const configs = {
|
const configs = {
|
||||||
min: 1,
|
min: 1,
|
||||||
max: sourceValue === 'bootableVolume' ? 1 : 100,
|
max:
|
||||||
|
sourceValue === 'bootableVolume'
|
||||||
|
? 1
|
||||||
|
: isFinite(this.quota)
|
||||||
|
? this.quota
|
||||||
|
: 100,
|
||||||
precision: 0,
|
precision: 0,
|
||||||
onChange: this.onCountChange,
|
onChange: this.onCountChange,
|
||||||
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),
|
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),
|
||||||
|
Loading…
Reference in New Issue
Block a user