refactor: Refactor project detail quota codes

1. Refactor project quota in project detail page
2. Add keypair quota display in overview/project detail quota page

Change-Id: I40e5925909d767a87eadd09cfb9c11ea1624cead
This commit is contained in:
Jingwei.Zhang 2021-09-22 15:06:54 +08:00
parent e182d62b34
commit ae266d3d74
7 changed files with 114 additions and 405 deletions

View File

@ -885,6 +885,7 @@
"Kernel ID": "Kernel ID", "Kernel ID": "Kernel ID",
"Kernel Image": "Kernel Image", "Kernel Image": "Kernel Image",
"Key": "Key", "Key": "Key",
"Key Pair": "Key Pair",
"Key Pairs": "Key Pairs", "Key Pairs": "Key Pairs",
"Key Size (bits)": "Key Size (bits)", "Key Size (bits)": "Key Size (bits)",
"Keypair": "Keypair", "Keypair": "Keypair",
@ -1791,7 +1792,6 @@
"compute hosts": "compute hosts", "compute hosts": "compute hosts",
"compute services": "compute services", "compute services": "compute services",
"connect subnet": "connect subnet", "connect subnet": "connect subnet",
"core": "core",
"create DSCP marking rule": "create DSCP marking rule", "create DSCP marking rule": "create DSCP marking rule",
"create a new network/subnet": "create a new network/subnet", "create a new network/subnet": "create a new network/subnet",
"create a new security group": "create a new security group", "create a new security group": "create a new security group",
@ -1925,7 +1925,6 @@
"qoS policy": "qoS policy", "qoS policy": "qoS policy",
"qos specs": "qos specs", "qos specs": "qos specs",
"quota set to -1 means there is no quota limit on the current resource": "quota set to -1 means there is no quota limit on the current resource", "quota set to -1 means there is no quota limit on the current resource": "quota set to -1 means there is no quota limit on the current resource",
"ram": "ram",
"reboot instance": "reboot instance", "reboot instance": "reboot instance",
"rebuild instance": "rebuild instance", "rebuild instance": "rebuild instance",
"recover instance": "recover instance", "recover instance": "recover instance",

View File

@ -885,6 +885,7 @@
"Kernel ID": "内核ID", "Kernel ID": "内核ID",
"Kernel Image": "Kernel镜像", "Kernel Image": "Kernel镜像",
"Key": "键", "Key": "键",
"Key Pair": "密钥",
"Key Pairs": "密钥", "Key Pairs": "密钥",
"Key Size (bits)": "密钥大小(比特)", "Key Size (bits)": "密钥大小(比特)",
"Keypair": "SSH密钥对", "Keypair": "SSH密钥对",
@ -1791,7 +1792,6 @@
"compute hosts": "计算节点", "compute hosts": "计算节点",
"compute services": "计算服务", "compute services": "计算服务",
"connect subnet": "连接子网", "connect subnet": "连接子网",
"core": "虚拟CPU",
"create DSCP marking rule": "创建DSCP标记规则", "create DSCP marking rule": "创建DSCP标记规则",
"create a new network/subnet": "新建网络/子网", "create a new network/subnet": "新建网络/子网",
"create a new security group": "新建安全组", "create a new security group": "新建安全组",
@ -1925,7 +1925,6 @@
"qoS policy": "QoS策略", "qoS policy": "QoS策略",
"qos specs": "QoS规格", "qos specs": "QoS规格",
"quota set to -1 means there is no quota limit on the current resource": "配额为设为 -1 时表示当前资源无配额限制", "quota set to -1 means there is no quota limit on the current resource": "配额为设为 -1 时表示当前资源无配额限制",
"ram": "内存(GB)",
"reboot instance": "重启云主机", "reboot instance": "重启云主机",
"rebuild instance": "重建云主机", "rebuild instance": "重建云主机",
"recover instance": "恢复云主机", "recover instance": "恢复云主机",

View File

@ -16,7 +16,6 @@ import React, { Component } from 'react';
import { Badge, Card, Col, List, Progress, Row, Spin, Tooltip } from 'antd'; import { Badge, Card, Col, List, Progress, Row, Spin, Tooltip } from 'antd';
import { inject, observer } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import globalVolumeTypeStore from 'stores/cinder/volume-type'; import globalVolumeTypeStore from 'stores/cinder/volume-type';
import globalKeypairStore from 'stores/nova/keypair';
import globalProjectStore from 'stores/keystone/project'; import globalProjectStore from 'stores/keystone/project';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import styles from '../style.less'; import styles from '../style.less';
@ -36,6 +35,7 @@ export const quotaCardList = [
{ text: t('vCPUs'), key: 'cores' }, { text: t('vCPUs'), key: 'cores' },
{ text: t('Memory'), key: 'ram' }, { text: t('Memory'), key: 'ram' },
{ text: t('Server Group'), key: 'server_groups' }, { text: t('Server Group'), key: 'server_groups' },
{ text: t('Key Pair'), key: 'key_pairs' },
], ],
}, },
{ {
@ -85,6 +85,9 @@ export class QuotaOverview extends Component {
this.state = { this.state = {
isLoading: true, isLoading: true,
}; };
const { projectStore, volumeTypeStore } = props;
this.projectStore = projectStore || globalProjectStore;
this.volumeTypeStore = volumeTypeStore || globalVolumeTypeStore;
} }
componentDidMount() { componentDidMount() {
@ -92,20 +95,29 @@ export class QuotaOverview extends Component {
} }
async getData() { async getData() {
const { user } = this.props.rootStore; const { getData } = this.props;
const { project: { id: projectId = '' } = {} } = user; if (getData) {
await Promise.all([ await getData();
globalProjectStore.fetchProjectQuota({ project_id: projectId }), } else {
globalVolumeTypeStore.fetchList(), const { user } = this.props.rootStore;
globalKeypairStore.fetchList(), const { project: { id: projectId = '' } = {} } = user;
]); await Promise.all([
this.projectStore.fetchProjectQuota({ project_id: projectId }),
this.volumeTypeStore.fetchList(),
]);
}
this.setState({ this.setState({
isLoading: false, isLoading: false,
}); });
} }
get volumeTypeData() {
const { volumeTypeData } = this.props;
return volumeTypeData || this.volumeTypeStore.list.data;
}
get volumeTypesQuota() { get volumeTypesQuota() {
const volumeTypes = globalVolumeTypeStore.list.data.map((item, index) => { const volumeTypes = this.volumeTypeData.map((item, index) => {
return { return {
index, index,
value: [ value: [
@ -211,7 +223,7 @@ export class QuotaOverview extends Component {
return <Spin />; return <Spin />;
} }
return this.renderQuotaCart( return this.renderQuotaCart(
globalProjectStore.quota, this.projectStore.quota,
this.getFilteredValue(item.value) this.getFilteredValue(item.value)
); );
} }
@ -238,7 +250,7 @@ export class QuotaOverview extends Component {
<Row key={item.index} gutter={[16]}> <Row key={item.index} gutter={[16]}>
{item.value.map((i) => ( {item.value.map((i) => (
<Col span={8} key={i.text}> <Col span={8} key={i.text}>
{this.getItemInfo(globalProjectStore.quota, i)} {this.getItemInfo(this.projectStore.quota, i)}
</Col> </Col>
))} ))}
</Row> </Row>

View File

@ -55,72 +55,6 @@
} }
} }
} }
.bottom {
//margin: 16px 0;
:global {
.ant-card {
box-shadow: unset;
}
}
.title {
.text {
font-size: 16px;
color: #252525;
}
.badge {
margin-left: 22px;
:global {
.ant-badge-status-dot {
width: 10px;
height: 10px;
}
.ant-badge-status-text {
font-size: 14px;
}
}
}
}
.action {
font-size: 12px;
color: #0068ff;
float: right;
margin-top: 6px;
cursor: pointer;
}
.content {
.card {
padding: 8px;
:global {
.ant-card-head {
border-bottom: none;
.ant-card-head-title {
font-size: 16px;
font-weight: 500;
color: #565656;
padding-bottom: 0;
}
}
}
.progress_title {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
} }
.right { .right {
@ -176,3 +110,69 @@
display: none; display: none;
} }
} }
.bottom {
//margin: 16px 0;
:global {
.ant-card {
box-shadow: unset;
}
}
.title {
.text {
font-size: 16px;
color: #252525;
}
.badge {
margin-left: 22px;
:global {
.ant-badge-status-dot {
width: 10px;
height: 10px;
}
.ant-badge-status-text {
font-size: 14px;
}
}
}
}
.action {
font-size: 12px;
color: #0068ff;
float: right;
margin-top: 6px;
cursor: pointer;
}
.content {
.card {
padding: 8px;
:global {
.ant-card-head {
border-bottom: none;
.ant-card-head-title {
font-size: 16px;
font-weight: 500;
color: #565656;
padding-bottom: 0;
}
}
}
.progress_title {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}

View File

@ -12,180 +12,43 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import React from 'react'; import React, { Component } from 'react';
import { observer, inject } from 'mobx-react'; import { inject, observer } from 'mobx-react';
import QuotaOverview from 'pages/base/containers/Overview/components/QuotaOverview';
import globalProjectStore from 'stores/keystone/project';
import { Card, Col, Row, List } from 'antd';
import Progress from 'components/ProjectProgress';
import classnames from 'classnames';
import { VolumeTypeStore } from 'stores/cinder/volume-type'; import { VolumeTypeStore } from 'stores/cinder/volume-type';
import { ProjectStore } from 'stores/keystone/project';
import styles from './index.less'; import styles from './index.less';
export class Quota extends React.Component { export class Quota extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {}; this.projectStore = new ProjectStore();
this.init(); this.volumeTypeStore = new VolumeTypeStore();
} }
componentDidMount() { get volumeTypeData() {
this.getData(); return this.volumeTypeStore.projectVolumeTypes;
} }
getData = async () => { getData = async () => {
const { id: project_id } = this.props.match.params; const { id: project_id } = this.props.match.params;
await this.store.fetchProjectQuota({ return Promise.all([
project_id, this.projectStore.fetchProjectQuota({
}); project_id,
await this.volumeTypeStore.fetchProjectVolumeTypes(project_id); }),
this.volumeTypeStore.fetchProjectVolumeTypes(project_id),
]);
}; };
getItemInfo = (i) => {
const { quota } = this.store;
const resource = quota[i.key];
return <Progress resource={resource} name={i.text} nameSpan={8} />;
};
init() {
this.store = globalProjectStore;
this.volumeTypeStore = new VolumeTypeStore();
}
renderVolumeTypes = (listData) => (
<List
itemLayout="vertical"
size="large"
pagination={{
hideOnSinglePage: true,
pageSize: 5,
size: 'small',
}}
dataSource={listData}
renderItem={(item) => (
<Row key={item.index} gutter={[16]}>
{item.value.map((i) => (
<Col span={8} key={i.text}>
{this.getItemInfo(i)}
</Col>
))}
</Row>
)}
/>
);
render() { render() {
const { quota } = this.store;
const volumeTypes = [];
this.volumeTypeStore.projectVolumeTypes.forEach((item, index) => {
volumeTypes.push({
index,
value: [
{
text: t('{name} type', { name: item.name }),
key: `volumes_${item.name}`,
},
{
text: t('{name} type gigabytes(GB)', { name: item.name }),
key: `gigabytes_${item.name}`,
},
{
text: t('{name} type snapshots', { name: item.name }),
key: `snapshots_${item.name}`,
},
],
});
});
return ( return (
<div className={classnames(styles.wrapper, this.className)}> <div className={styles.wrapper}>
<Card> <QuotaOverview
{quota.instances ? ( getData={this.getData}
<div> projectStore={this.projectStore}
<Row> volumeTypeStore={this.volumeTypeStore}
<Col xs={{ span: 8, offset: 1 }} lg={{ span: 8 }}> volumeTypeData={this.volumeTypeData}
<div style={{ fontSize: 16, marginBottom: 12 }}> />
{t('Compute')}
</div>
<Progress resource={quota.instances} name={t('instance')} />
<Progress resource={quota.cores} name={t('core')} />
<Progress resource={quota.ram} name={t('ram')} />
{/* <Progress resource={quota.key_pairs} name={t('keypair')} /> */}
<div>
<p style={{ float: 'right' }}>
{t('Member in group')} :{' '}
{quota.server_group_members &&
quota.server_group_members.limit === -1
? t('Unlimit')
: quota.server_group_members.limit}{' '}
</p>
<Progress
resource={quota.server_groups}
name={t('Server Group')}
/>
</div>
{/* <Progress resource={quota.server_group_members} name={t('Server Group Member')} /> */}
</Col>
</Row>
</div>
) : null}
{quota.volumes ? (
<div>
<Row style={{ marginTop: 30 }}>
<Col xs={{ span: 8, offset: 1 }} lg={{ span: 8 }}>
<div style={{ fontSize: 16, marginBottom: 12 }}>
{t('Storage')}
</div>
<Progress name={t('Volume')} resource={quota.volumes} />
<Progress
name={`${t('gigabytes')}(GB)`}
resource={quota.gigabytes}
/>
<Progress name={t('backups')} resource={quota.backups} />
<Progress
name={t('Backup Capacity')}
resource={quota.backup_gigabytes}
/>
<Progress name={t('Snapshot')} resource={quota.snapshots} />
</Col>
<Col xs={{ span: 8, offset: 1 }} lg={{ span: 8, offset: 6 }}>
<div style={{ fontSize: 16, marginBottom: 12 }}>
{t('Network')}
</div>
<Progress resource={quota.router} name={t('router')} />
<Progress resource={quota.network} name={t('network')} />
<Progress resource={quota.subnet} name={t('subnet')} />
<Progress
resource={quota.floatingip}
name={t('floatingip')}
/>
<Progress resource={quota.port} name={t('port')} />
<Progress
name={t('security_group')}
resource={quota.security_group}
/>
<Progress
name={t('Security Group Rule')}
resource={quota.security_group_rule}
/>
</Col>
</Row>
</div>
) : null}
{volumeTypes[0] ? (
<div>
<Row style={{ marginTop: 30 }}>
<Col xs={{ span: 22, offset: 1 }} lg={{ span: 22 }}>
<div style={{ fontSize: 16, marginBottom: 12 }}>
{t('Storage Types')}
</div>
{this.renderVolumeTypes(volumeTypes)}
</Col>
</Row>
</div>
) : null}
</Card>
</div> </div>
); );
} }

View File

@ -2,5 +2,5 @@
.wrapper { .wrapper {
min-height: calc(100vh - 108px); min-height: calc(100vh - 108px);
padding: 0 @body-padding; padding: 0 @body-padding @body-padding @body-padding;
} }

View File

@ -1,164 +0,0 @@
@import '~antd/lib/style/themes/default.less';
.container {
height: 100%;
padding: 16px 12px;
overflow: auto;
:global {
.ant-card {
box-shadow: 0px 2px 20px 0px rgba(0, 0, 0, 0.09);
}
}
.left {
height: 100%;
.top {
.title {
display: table-cell;
vertical-align: bottom;
.text {
font-size: 21px;
color: #252525;
}
.action {
font-size: 16px;
color: #000000;
margin-left: 40px;
}
}
}
.bottom {
margin: 16px 0;
:global {
.ant-card {
box-shadow: unset;
}
}
.title {
.text {
font-size: 16px;
color: #252525;
}
.badge {
margin-left: 22px;
:global {
.ant-badge-status-dot {
width: 10px;
height: 10px;
}
.ant-badge-status-text {
font-size: 14px;
}
}
}
}
.action {
font-size: 12px;
color: #0068FF;
float: right;
margin-top: 6px;
cursor: pointer;
}
.content {
.card {
padding: 8px;
:global {
.ant-card-head {
font-size: 14px;
}
}
.progress_title {
font-size: 14px;
}
}
}
}
}
.right {
height: 100%;
.top {
.meta {
padding-bottom: 16px;
margin-bottom: 16px;
border-bottom: 1px dashed #e8e8e8;
}
:global {
.ant-descriptions-item-label {
width: 130px;
}
}
.money {
color: #0068FF;
line-height: 42px;
font-size: 30px;
}
}
.middle {
margin-top: 1.33rem;
background-color: #ffffff;
.row {
text-align: center;
.alert_value {
font-size: 56px;
}
.alert_key {
color: #252525;
font-size: 16px;
}
span {
display: block;
}
}
}
.bottom {
margin-top: 1.33rem;
background-color: #ffffff;
.timeline {
.time {
font-size: 14px;
color: #0068FF;
margin-bottom: 6px;
}
.bulletin {
font-size: 14px;
color: #252525;
}
}
}
}
:global {
.ant-card-head {
font-size: 16px;
}
}
}