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, ''),