feat: support quota info when create key pair

1. Support quota info when create key pair
2. Disable click submit button when user's key pairs exceed project key pair quota
3. Update Ring component to add tooltip when quota is unlimit, better impression quantity information
4. Update Ring component to support left quota < 0 situation
5. Add quota tip in create key pair form for better understander the
quota of key pair

Change-Id: I31282db5a9a3c35c4e3f904df96689b52149b2ec
This commit is contained in:
Jingwei.Zhang 2022-06-24 17:19:43 +08:00
parent 55e0abab85
commit b22a959e49
5 changed files with 148 additions and 54 deletions

View File

@ -22,6 +22,7 @@ import {
Annotation,
Tooltip,
} from 'bizcharts';
import { Tooltip as AntTooltip } from 'antd';
export const typeColors = {
used: '#5B8FF9',
@ -59,7 +60,10 @@ export default function Ring(props) {
const showTip = isLimit;
const limitNumber = !isLimit ? Infinity : limit;
const limitStr = !isLimit ? t('Infinity') : limit;
const left = !isLimit ? 1 : limit - used - reserved - add;
let left = !isLimit ? 1 : limit - used - reserved - add;
if (left < 0) {
left = 0;
}
const data = [
{
type: t('Used'),
@ -93,53 +97,71 @@ export default function Ring(props) {
const allCount = used + add + reserved;
const percent = isLimit ? (allCount / limitNumber) * 100 : 0;
return (
<div style={style}>
<Chart placeholder={false} height={height} padding="auto" autoFit>
<Legend visible={showTip && hasLabel} />
<Tooltip visible={showTip} />
{/* 绘制图形 */}
<View data={data}>
<Coordinate type="theta" innerRadius={0.75} />
<Interval
position="value"
adjust="stack"
color={['type', colors]}
size={16}
/>
<Annotation.Text
position={['50%', '30%']}
content={title}
style={{
lineHeight: '240px',
fontSize: '14',
fill: '#000',
textAlign: 'center',
}}
/>
<Annotation.Text
position={['50%', '50%']}
content={secondTitle}
style={{
lineHeight: '240px',
fontSize: '14',
fill: '#000',
textAlign: 'center',
}}
/>
<Annotation.Text
position={['50%', '70%']}
content={`${allCount}/${limitStr}`}
style={{
lineHeight: '240px',
fontSize: '14',
fill: getUsedValueColor(percent),
textAlign: 'center',
fontWeight: 'bold',
}}
/>
</View>
</Chart>
</div>
let tipTitle = '';
if (!isLimit) {
const usedTip = `${t('Used')}: ${used}`;
const reservedTip = reserved ? '' : `${t('Reserved')}: ${reserved}`;
const newTip = `${t('New')}: ${add}`;
const tips = [usedTip, newTip];
if (reserved) {
tips.splice(1, 0, reservedTip);
}
tipTitle = tips.join(' / ');
}
const chart = (
<Chart placeholder={false} height={height} padding="auto" autoFit>
<Legend visible={showTip && hasLabel} />
<Tooltip visible={showTip} />
{/* 绘制图形 */}
<View data={data}>
<Coordinate type="theta" innerRadius={0.75} />
<Interval
position="value"
adjust="stack"
color={['type', colors]}
size={16}
/>
<Annotation.Text
position={['50%', '30%']}
content={title}
style={{
lineHeight: '240px',
fontSize: '14',
fill: '#000',
textAlign: 'center',
}}
/>
<Annotation.Text
position={['50%', '50%']}
content={secondTitle}
style={{
lineHeight: '240px',
fontSize: '14',
fill: '#000',
textAlign: 'center',
}}
/>
<Annotation.Text
position={['50%', '70%']}
content={`${allCount}/${limitStr}`}
style={{
lineHeight: '240px',
fontSize: '14',
fill: getUsedValueColor(percent),
textAlign: 'center',
fontWeight: 'bold',
}}
/>
</View>
</Chart>
);
const content = isLimit ? (
chart
) : (
<AntTooltip title={tipTitle}>{chart}</AntTooltip>
);
return <div style={style}>{content}</div>;
}

View File

@ -1812,6 +1812,7 @@
"Quota exceeded": "Quota exceeded",
"Quota is not enough for extend share.": "Quota is not enough for extend share.",
"Quota is not enough for extend volume.": "Quota is not enough for extend volume.",
"Quota of key pair means: the number of allowed key pairs for each user.": "Quota of key pair means: the number of allowed key pairs for each user.",
"Quota: Insufficient quota to create resources, please adjust resource quantity or quota(left { quota }, input { input }).": "Quota: Insufficient quota to create resources, please adjust resource quantity or quota(left { quota }, input { input }).",
"Quota: Insufficient { name } quota to create resources, please adjust resource quantity or quota(left { left }, input { input }).": "Quota: Insufficient { name } quota to create resources, please adjust resource quantity or quota(left { left }, input { input }).",
"Quota: Project quotas sufficient resources can be created": "Quota: Project quotas sufficient resources can be created",

View File

@ -1812,6 +1812,7 @@
"Quota exceeded": "配额用尽",
"Quota is not enough for extend share.": "配额不足以扩容共享。",
"Quota is not enough for extend volume.": "配额不足以扩容云硬盘。",
"Quota of key pair means: the number of allowed key pairs for each user.": "密钥的配额表示:每个用户允许创建的密钥数量。",
"Quota: Insufficient quota to create resources, please adjust resource quantity or quota(left { quota }, input { input }).": "配额:项目配额不足,无法创建资源,请进行资源数量或配额的调整(剩余{ quota },输入{ input })。",
"Quota: Insufficient { name } quota to create resources, please adjust resource quantity or quota(left { left }, input { input }).": "配额:{ name } 配额不足,无法创建资源,请进行资源数量或配额的调整(剩余{ left },输入{ input })。",
"Quota: Project quotas sufficient resources can be created": "配额:项目配额充足,可创建资源",
@ -2241,7 +2242,7 @@
"The name should start with upper letter, lower letter, and be a string of 3 to 63, characters can only contain \"0-9, a-z, A-Z, -\".": "名称应以大写字母小写字母开头长度为3-63字符且只包含“0-9, a-z, A-Z, -”。",
"The new password cannot be identical to the current password.": "用户新密码不能与原密码相同。",
"The no_proxy address to use for nodes in cluster": "用于集群中节点的 no_proxy 地址",
"The number of allowed key pairs for each user.": "每个用户允许创建的配额数量",
"The number of allowed key pairs for each user.": "每个用户允许创建的密钥数量",
"The number of vCPU cores should not exceed the maximum number of CPU cores of the physical node. Otherwise it will cause fail to schedule to any physical node when creating instance.": "vCPU核数不应该超过物理节点的最大CPU核数否则会导致云主机创建时无法调度到任何物理节点。",
"The number of virtual cpu for this container": "容器的虚拟 CPU 数量",
"The password must not be the same as the previous": "新密码不能与以前的密码相同",

View File

@ -31,10 +31,7 @@ const colors = {
const keyPairTitle = (
<span>
{t('Key Pair')}
<Tooltip
title={t('The number of allowed key pairs for each user.')}
getPopupContainer={(node) => node.parentNode}
>
<Tooltip title={t('The number of allowed key pairs for each user.')}>
<QuestionCircleOutlined style={{ marginLeft: 4 }} />
</Tooltip>
</span>

View File

@ -15,8 +15,23 @@
import { inject, observer } from 'mobx-react';
import { ModalAction } from 'containers/Action';
import globalKeypairStore from 'stores/nova/keypair';
import globalProjectStore from 'stores/keystone/project';
import FileSaver from 'file-saver';
const getUsed = () => {
const { total = 0, data = [] } = globalKeypairStore.list || {};
return total || data.length;
};
const getAdd = (quota) => {
const { limit = 0 } = quota || {};
if (limit === -1) {
return 1;
}
const used = getUsed();
return limit > used ? 1 : 0;
};
export class CreateKeypair extends ModalAction {
static id = 'create-keypair';
@ -26,6 +41,64 @@ export class CreateKeypair extends ModalAction {
return t('Create Keypair');
}
init() {
this.state.quota = {};
this.state.quotaLoading = true;
this.projectStore = globalProjectStore;
this.getQuota();
}
get tips() {
return t(
'Quota of key pair means: the number of allowed key pairs for each user.'
);
}
static get disableSubmit() {
const {
novaQuota: { key_pairs: quota = {} },
} = globalProjectStore;
const add = getAdd(quota);
return add === 0;
}
static get showQuota() {
return true;
}
get showQuota() {
return true;
}
async getQuota() {
this.setState({
quotaLoading: true,
});
const result = await this.projectStore.fetchProjectNovaQuota();
const { key_pairs: quota = {} } = result || {};
this.setState({
quota,
quotaLoading: false,
});
}
get quotaInfo() {
const { quota = {}, quotaLoading } = this.state;
if (quotaLoading) {
return [];
}
const add = getAdd(quota);
const used = getUsed();
const data = {
...quota,
add,
used,
name: 'key_pair',
title: t('Key Pair'),
};
return [data];
}
onSubmit = (values) => {
const { name, public_key } = values;
const params = {