feat: support quota info when extend volume

1. Fix attached instance display in volume list page
2. Support quota info when extend volume
3. Disable click submit button when left quota is not enough to extend
4. Refactor instance detail fetch by instance store, not the client
5. Refactor instance locked hint by using validator, not notice after submit
6. Update instanceName default value in modal action

Change-Id: Ie5e492d1d550b8283b634fcd9fa645394d8ce504
This commit is contained in:
Jingwei.Zhang 2022-06-30 11:06:42 +08:00
parent c27c84cafe
commit 41eaf509b6
3 changed files with 127 additions and 85 deletions

View File

@ -91,7 +91,7 @@ export default class ModalAction extends BaseForm {
}
get instanceName() {
return (this.item || {}).name || (this.values || {}).name;
return (this.item || {}).name || (this.values || {}).name || this.itemId;
}
get isAsyncAction() {
@ -150,6 +150,10 @@ export default class ModalAction extends BaseForm {
return item || this.containerProps.detail || { name: '' };
}
get itemId() {
return (this.item || {}).id;
}
get items() {
const { items } = this.props;
return items;

View File

@ -15,10 +15,16 @@
import { inject, observer } from 'mobx-react';
import { ModalAction } from 'containers/Action';
import globalVolumeStore, { VolumeStore } from 'stores/cinder/volume';
import { isAvailableOrInUse } from 'resources/cinder/volume';
import { get } from 'lodash';
import client from 'client';
import Notify from 'components/Notify';
import globalProjectStore from 'stores/keystone/project';
import globalServerStore from 'stores/nova/instance';
import {
isAvailableOrInUse,
setCreateVolumeSize,
checkQuotaDisable,
getQuotaInfo,
fetchQuota,
} from 'resources/cinder/volume';
import { isEmpty } from 'lodash';
export class ExtendVolume extends ModalAction {
static id = 'extend-snapshot';
@ -29,37 +35,95 @@ export class ExtendVolume extends ModalAction {
return t('Extend volume');
}
get defaultValue() {
const { name, id, volume_type, size } = this.item;
const value = {
volume: `${name || id}(${volume_type} | ${size}GiB)`,
new_size: size + 1,
};
return value;
}
static policy = 'volume:extend';
static allowed = (item) => Promise.resolve(isAvailableOrInUse(item));
init() {
this.store = globalVolumeStore;
this.state.showNotice = true;
this.volumeStore = new VolumeStore();
this.projectStore = globalProjectStore;
fetchQuota(this, 1, this.item.volume_type);
this.checkAttachedServer();
}
get tips() {
return t('After the volume is expanded, the volume cannot be reduced.');
}
async getQuota() {
await this.volumeStore.fetchQuota();
this.updateDefaultValue();
static get disableSubmit() {
return checkQuotaDisable(false);
}
static get showQuota() {
return true;
}
get showQuota() {
return true;
}
get quotaInfo() {
const { quota = {}, quotaLoading } = this.state;
if (quotaLoading || isEmpty(quota)) {
return [];
}
// eslint-disable-next-line no-unused-vars
const [volumeData, sizeData, typeData, typeSizeData] = getQuotaInfo(
this,
false
);
const { type, ...rest } = sizeData;
return [rest, typeSizeData];
}
async checkAttachedServer() {
const instanceIds = (this.item.attachments || []).map((it) => it.server_id);
if (!instanceIds.length) {
return;
}
const reqs = instanceIds.map((id) =>
globalServerStore.pureFetchDetail({ id })
);
const results = await Promise.allSettled(reqs);
const lockedInstances = results
.filter(({ status }) => {
return status === 'fulfilled';
})
.map((it) => it.value)
.filter((server) => server.locked)
.map(({ name }) => name);
if (lockedInstances.length) {
const name = lockedInstances.join(', ');
const lockedError = t(
'The server {name} is locked. Please unlock first.',
{ name }
);
this.setState({
lockedError,
});
}
}
get isQuotaLimited() {
const { gigabytes: { limit } = {} } = this.volumeStore.quotaSet || {};
const { gigabytes: { limit } = {} } = this.projectStore.cinderQuota || {};
return limit !== -1;
}
get leftSize() {
const { gigabytes: { limit = 10, in_use = 0 } = {} } =
this.volumeStore.quotaSet || {};
return limit - in_use;
const { gigabytes: { left = 0 } = {} } =
this.projectStore.cinderQuota || {};
return left;
}
get itemSize() {
const { size } = this.item;
return size;
}
get minSize() {
return this.itemSize + 1;
}
get maxSize() {
@ -67,21 +131,29 @@ export class ExtendVolume extends ModalAction {
return currentSize + this.leftSize;
}
isQuotaEnough() {
return !this.isQuotaLimited || this.leftSize >= 1;
get defaultValue() {
const { name, id, volume_type, size } = this.item;
const value = {
volume: `${name || id}(${volume_type} | ${size}GiB)`,
new_size: this.minSize,
};
return value;
}
get formItems() {
const { size } = this.item;
const minSize = size + 1;
if (!this.isQuotaEnough()) {
return [
{
type: 'label',
component: t('Quota is not enough for extend volume.'),
},
];
onSizeChange = (value) => {
const add = value - this.itemSize;
setCreateVolumeSize(add);
};
checkInstance = () => {
const { lockedError } = this.state;
if (!lockedError) {
return Promise.resolve();
}
return Promise.reject(lockedError);
};
get formItems() {
return [
{
name: 'volume',
@ -94,63 +166,29 @@ export class ExtendVolume extends ModalAction {
label: t('Capacity (GiB)'),
type: 'slider-input',
max: this.maxSize,
min: minSize,
description: `${minSize}GiB-${this.maxSize}GiB`,
min: this.minSize,
description: `${this.minSize}GiB-${this.maxSize}GiB`,
required: true,
display: this.isQuotaLimited,
onChange: this.onSizeChange,
validator: this.checkInstance,
},
{
name: 'new_size',
label: t('Capacity (GiB)'),
type: 'input-int',
min: minSize,
min: this.minSize,
required: true,
display: !this.isQuotaLimited,
onChange: this.onSizeChange,
validator: this.checkInstance,
},
];
}
init() {
this.store = globalVolumeStore;
this.state.showNotice = true;
this.volumeStore = new VolumeStore();
this.getQuota();
}
get showNotice() {
return this.state.showNotice;
}
onSubmit = async (values) => {
if (!this.isQuotaEnough()) {
this.setState({
showNotice: false,
});
return Promise.resolve();
}
const { new_size } = values;
const { id } = this.item;
const instanceId = get(this.item, 'attachments[0].server_id');
if (instanceId) {
const { server } = await client.nova.servers.show(instanceId);
if (server.locked) {
Notify.errorWithDetail(
t('The server {name} is locked. Please unlock first.', {
name: server.name,
}),
t('The server {name} is locked. Please unlock first.', {
name: server.name,
})
);
this.setState({
showNotice: false,
});
return;
}
}
return this.store.extendSize(id, { new_size });
};
}

View File

@ -293,7 +293,7 @@ export const getVolumeColumnsList = (self) => {
{it.device} on{' '}
{self.getLinkRender(
'instanceDetail',
it.server_name,
it.server_name || it.server_id,
{ id: it.server_id },
{ tab: 'volumes' }
)}
@ -355,10 +355,10 @@ export function setCreateVolumeCount(value) {
globalVolumeStore.setCreateVolumeCount(value);
}
export async function fetchQuota(self, size) {
export async function fetchQuota(self, size, type = '') {
setCreateVolumeCount(1);
setCreateVolumeSize(size);
setCreateVolumeType('');
setCreateVolumeType(type);
self.setState({
quota: {},
quotaLoading: true,
@ -400,7 +400,7 @@ const getErrorMessage = ({ name, left, input }) => {
return error;
};
export const getAdd = (cinderQuota) => {
export const getAdd = (cinderQuota, withCountCheck = true) => {
if (isEmpty(cinderQuota)) {
return {};
}
@ -424,7 +424,7 @@ export const getAdd = (cinderQuota) => {
add: count,
addSize: totalSize,
};
if (left >= 0 && left < count) {
if (withCountCheck && left >= 0 && left < count) {
const error = getErrorMessage({
name: t('volume'),
left,
@ -443,7 +443,7 @@ export const getAdd = (cinderQuota) => {
if (isEmpty(typeQuota)) {
return create;
}
if (typeLeft >= 0 && typeLeft < count) {
if (withCountCheck && typeLeft >= 0 && typeLeft < count) {
const error = getErrorMessage({
name: t('{name} type', { name: type }),
left: typeLeft,
@ -462,7 +462,7 @@ export const getAdd = (cinderQuota) => {
return create;
};
export const getQuotaInfo = (self) => {
export const getQuotaInfo = (self, withCountCheck = true) => {
const { volumeTypeForCreate: name } = globalVolumeStore;
const { quota = {}, quotaLoading } = self.state;
if (quotaLoading || isEmpty(quota)) {
@ -474,7 +474,7 @@ export const getQuotaInfo = (self) => {
typeQuota = {},
typeSizeQuota = {},
} = getQuota(quota);
const { add, addSize } = getAdd(quota);
const { add, addSize } = getAdd(quota, withCountCheck);
const volumeData = {
...volumes,
add,
@ -508,10 +508,10 @@ export const getQuotaInfo = (self) => {
return [volumeData, sizeData, typeData, typeSizeData];
};
export const checkQuotaDisable = () => {
export const checkQuotaDisable = (withCountCheck = true) => {
const { cinderQuota = {} } = globalProjectStore;
const { add } = getAdd(cinderQuota);
return add === 0;
const { add, addSize } = getAdd(cinderQuota, withCountCheck);
return withCountCheck ? add === 0 : addSize === 0;
};
export const onVolumeSizeChange = (value) => {