diff --git a/src/components/InfoButton/index.jsx b/src/components/InfoButton/index.jsx
new file mode 100644
index 00000000..362a5046
--- /dev/null
+++ b/src/components/InfoButton/index.jsx
@@ -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 (
+
+ );
+ }
+ const closeButton = (
+
+ );
+ return (
+
+
+ {content}
+
+
+ );
+}
diff --git a/src/components/QuotaChart/index.jsx b/src/components/QuotaChart/index.jsx
new file mode 100644
index 00000000..bfe3a3d9
--- /dev/null
+++ b/src/components/QuotaChart/index.jsx
@@ -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 (
+
+
+
+ {/* 绘制图形 */}
+
+
+
+
+
+
+
+
+ );
+}
+
+function QuotaInfo(props) {
+ const { used = 0, reserved = 0, add = 1 } = props;
+ return (
+
+
+ {t('Quota')}: {t('Unlimit')}
+
+
+ {t('Used')}:: {used}
+
+ {!!reserved && (
+
+ {t('Reserved')}:: {reserved}
+
+ )}
+
+ {t('New')}:: {add}
+
+
+ );
+}
+
+export default function QuotaChart(props) {
+ const { limit = 0, loading } = props;
+ if (loading) {
+ return ;
+ }
+ if (limit === -1) {
+ return ;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/StepForm/index.jsx b/src/components/StepForm/index.jsx
index 37b59246..6e20a8f5 100644
--- a/src/components/StepForm/index.jsx
+++ b/src/components/StepForm/index.jsx
@@ -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 ;
+ }
+
+ renderRightTopExtra() {
+ const content = this.renderQuota();
+ if (!content) {
+ return null;
+ }
+ return (
+
+
+
+ );
+ }
+
render() {
if (this.endpointError) {
return (
@@ -432,6 +470,7 @@ export default class BaseStepForm extends React.Component {
{this.renderSteps()}
+ {this.renderRightTopExtra()}
{this.renderFooter()}
diff --git a/src/components/StepForm/index.less b/src/components/StepForm/index.less
index 6513844c..af68d75b 100644
--- a/src/components/StepForm/index.less
+++ b/src/components/StepForm/index.less
@@ -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%);
+}
diff --git a/src/locales/en.json b/src/locales/en.json
index 78d3f94a..2f45609d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -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.",
diff --git a/src/locales/zh.json b/src/locales/zh.json
index a5a40108..8a98c922 100644
--- a/src/locales/zh.json
+++ b/src/locales/zh.json
@@ -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.": "配额不足以扩容共享。",
diff --git a/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx b/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx
index 1ad0ada3..253afcae 100644
--- a/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx
+++ b/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx
@@ -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, ''),