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:
parent
c27c84cafe
commit
41eaf509b6
@ -91,7 +91,7 @@ export default class ModalAction extends BaseForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get instanceName() {
|
get instanceName() {
|
||||||
return (this.item || {}).name || (this.values || {}).name;
|
return (this.item || {}).name || (this.values || {}).name || this.itemId;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAsyncAction() {
|
get isAsyncAction() {
|
||||||
@ -150,6 +150,10 @@ export default class ModalAction extends BaseForm {
|
|||||||
return item || this.containerProps.detail || { name: '' };
|
return item || this.containerProps.detail || { name: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get itemId() {
|
||||||
|
return (this.item || {}).id;
|
||||||
|
}
|
||||||
|
|
||||||
get items() {
|
get items() {
|
||||||
const { items } = this.props;
|
const { items } = this.props;
|
||||||
return items;
|
return items;
|
||||||
|
@ -15,10 +15,16 @@
|
|||||||
import { inject, observer } from 'mobx-react';
|
import { inject, observer } from 'mobx-react';
|
||||||
import { ModalAction } from 'containers/Action';
|
import { ModalAction } from 'containers/Action';
|
||||||
import globalVolumeStore, { VolumeStore } from 'stores/cinder/volume';
|
import globalVolumeStore, { VolumeStore } from 'stores/cinder/volume';
|
||||||
import { isAvailableOrInUse } from 'resources/cinder/volume';
|
import globalProjectStore from 'stores/keystone/project';
|
||||||
import { get } from 'lodash';
|
import globalServerStore from 'stores/nova/instance';
|
||||||
import client from 'client';
|
import {
|
||||||
import Notify from 'components/Notify';
|
isAvailableOrInUse,
|
||||||
|
setCreateVolumeSize,
|
||||||
|
checkQuotaDisable,
|
||||||
|
getQuotaInfo,
|
||||||
|
fetchQuota,
|
||||||
|
} from 'resources/cinder/volume';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
export class ExtendVolume extends ModalAction {
|
export class ExtendVolume extends ModalAction {
|
||||||
static id = 'extend-snapshot';
|
static id = 'extend-snapshot';
|
||||||
@ -29,37 +35,95 @@ export class ExtendVolume extends ModalAction {
|
|||||||
return t('Extend volume');
|
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 policy = 'volume:extend';
|
||||||
|
|
||||||
static allowed = (item) => Promise.resolve(isAvailableOrInUse(item));
|
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() {
|
get tips() {
|
||||||
return t('After the volume is expanded, the volume cannot be reduced.');
|
return t('After the volume is expanded, the volume cannot be reduced.');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getQuota() {
|
static get disableSubmit() {
|
||||||
await this.volumeStore.fetchQuota();
|
return checkQuotaDisable(false);
|
||||||
this.updateDefaultValue();
|
}
|
||||||
|
|
||||||
|
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() {
|
get isQuotaLimited() {
|
||||||
const { gigabytes: { limit } = {} } = this.volumeStore.quotaSet || {};
|
const { gigabytes: { limit } = {} } = this.projectStore.cinderQuota || {};
|
||||||
return limit !== -1;
|
return limit !== -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
get leftSize() {
|
get leftSize() {
|
||||||
const { gigabytes: { limit = 10, in_use = 0 } = {} } =
|
const { gigabytes: { left = 0 } = {} } =
|
||||||
this.volumeStore.quotaSet || {};
|
this.projectStore.cinderQuota || {};
|
||||||
return limit - in_use;
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
get itemSize() {
|
||||||
|
const { size } = this.item;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
get minSize() {
|
||||||
|
return this.itemSize + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
get maxSize() {
|
get maxSize() {
|
||||||
@ -67,21 +131,29 @@ export class ExtendVolume extends ModalAction {
|
|||||||
return currentSize + this.leftSize;
|
return currentSize + this.leftSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
isQuotaEnough() {
|
get defaultValue() {
|
||||||
return !this.isQuotaLimited || this.leftSize >= 1;
|
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() {
|
onSizeChange = (value) => {
|
||||||
const { size } = this.item;
|
const add = value - this.itemSize;
|
||||||
const minSize = size + 1;
|
setCreateVolumeSize(add);
|
||||||
if (!this.isQuotaEnough()) {
|
};
|
||||||
return [
|
|
||||||
{
|
checkInstance = () => {
|
||||||
type: 'label',
|
const { lockedError } = this.state;
|
||||||
component: t('Quota is not enough for extend volume.'),
|
if (!lockedError) {
|
||||||
},
|
return Promise.resolve();
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
return Promise.reject(lockedError);
|
||||||
|
};
|
||||||
|
|
||||||
|
get formItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'volume',
|
name: 'volume',
|
||||||
@ -94,63 +166,29 @@ export class ExtendVolume extends ModalAction {
|
|||||||
label: t('Capacity (GiB)'),
|
label: t('Capacity (GiB)'),
|
||||||
type: 'slider-input',
|
type: 'slider-input',
|
||||||
max: this.maxSize,
|
max: this.maxSize,
|
||||||
min: minSize,
|
min: this.minSize,
|
||||||
description: `${minSize}GiB-${this.maxSize}GiB`,
|
description: `${this.minSize}GiB-${this.maxSize}GiB`,
|
||||||
required: true,
|
required: true,
|
||||||
display: this.isQuotaLimited,
|
display: this.isQuotaLimited,
|
||||||
|
onChange: this.onSizeChange,
|
||||||
|
validator: this.checkInstance,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'new_size',
|
name: 'new_size',
|
||||||
label: t('Capacity (GiB)'),
|
label: t('Capacity (GiB)'),
|
||||||
type: 'input-int',
|
type: 'input-int',
|
||||||
min: minSize,
|
min: this.minSize,
|
||||||
required: true,
|
required: true,
|
||||||
display: !this.isQuotaLimited,
|
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) => {
|
onSubmit = async (values) => {
|
||||||
if (!this.isQuotaEnough()) {
|
|
||||||
this.setState({
|
|
||||||
showNotice: false,
|
|
||||||
});
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { new_size } = values;
|
const { new_size } = values;
|
||||||
const { id } = this.item;
|
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 });
|
return this.store.extendSize(id, { new_size });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -293,7 +293,7 @@ export const getVolumeColumnsList = (self) => {
|
|||||||
{it.device} on{' '}
|
{it.device} on{' '}
|
||||||
{self.getLinkRender(
|
{self.getLinkRender(
|
||||||
'instanceDetail',
|
'instanceDetail',
|
||||||
it.server_name,
|
it.server_name || it.server_id,
|
||||||
{ id: it.server_id },
|
{ id: it.server_id },
|
||||||
{ tab: 'volumes' }
|
{ tab: 'volumes' }
|
||||||
)}
|
)}
|
||||||
@ -355,10 +355,10 @@ export function setCreateVolumeCount(value) {
|
|||||||
globalVolumeStore.setCreateVolumeCount(value);
|
globalVolumeStore.setCreateVolumeCount(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchQuota(self, size) {
|
export async function fetchQuota(self, size, type = '') {
|
||||||
setCreateVolumeCount(1);
|
setCreateVolumeCount(1);
|
||||||
setCreateVolumeSize(size);
|
setCreateVolumeSize(size);
|
||||||
setCreateVolumeType('');
|
setCreateVolumeType(type);
|
||||||
self.setState({
|
self.setState({
|
||||||
quota: {},
|
quota: {},
|
||||||
quotaLoading: true,
|
quotaLoading: true,
|
||||||
@ -400,7 +400,7 @@ const getErrorMessage = ({ name, left, input }) => {
|
|||||||
return error;
|
return error;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAdd = (cinderQuota) => {
|
export const getAdd = (cinderQuota, withCountCheck = true) => {
|
||||||
if (isEmpty(cinderQuota)) {
|
if (isEmpty(cinderQuota)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -424,7 +424,7 @@ export const getAdd = (cinderQuota) => {
|
|||||||
add: count,
|
add: count,
|
||||||
addSize: totalSize,
|
addSize: totalSize,
|
||||||
};
|
};
|
||||||
if (left >= 0 && left < count) {
|
if (withCountCheck && left >= 0 && left < count) {
|
||||||
const error = getErrorMessage({
|
const error = getErrorMessage({
|
||||||
name: t('volume'),
|
name: t('volume'),
|
||||||
left,
|
left,
|
||||||
@ -443,7 +443,7 @@ export const getAdd = (cinderQuota) => {
|
|||||||
if (isEmpty(typeQuota)) {
|
if (isEmpty(typeQuota)) {
|
||||||
return create;
|
return create;
|
||||||
}
|
}
|
||||||
if (typeLeft >= 0 && typeLeft < count) {
|
if (withCountCheck && typeLeft >= 0 && typeLeft < count) {
|
||||||
const error = getErrorMessage({
|
const error = getErrorMessage({
|
||||||
name: t('{name} type', { name: type }),
|
name: t('{name} type', { name: type }),
|
||||||
left: typeLeft,
|
left: typeLeft,
|
||||||
@ -462,7 +462,7 @@ export const getAdd = (cinderQuota) => {
|
|||||||
return create;
|
return create;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getQuotaInfo = (self) => {
|
export const getQuotaInfo = (self, withCountCheck = true) => {
|
||||||
const { volumeTypeForCreate: name } = globalVolumeStore;
|
const { volumeTypeForCreate: name } = globalVolumeStore;
|
||||||
const { quota = {}, quotaLoading } = self.state;
|
const { quota = {}, quotaLoading } = self.state;
|
||||||
if (quotaLoading || isEmpty(quota)) {
|
if (quotaLoading || isEmpty(quota)) {
|
||||||
@ -474,7 +474,7 @@ export const getQuotaInfo = (self) => {
|
|||||||
typeQuota = {},
|
typeQuota = {},
|
||||||
typeSizeQuota = {},
|
typeSizeQuota = {},
|
||||||
} = getQuota(quota);
|
} = getQuota(quota);
|
||||||
const { add, addSize } = getAdd(quota);
|
const { add, addSize } = getAdd(quota, withCountCheck);
|
||||||
const volumeData = {
|
const volumeData = {
|
||||||
...volumes,
|
...volumes,
|
||||||
add,
|
add,
|
||||||
@ -508,10 +508,10 @@ export const getQuotaInfo = (self) => {
|
|||||||
return [volumeData, sizeData, typeData, typeSizeData];
|
return [volumeData, sizeData, typeData, typeSizeData];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkQuotaDisable = () => {
|
export const checkQuotaDisable = (withCountCheck = true) => {
|
||||||
const { cinderQuota = {} } = globalProjectStore;
|
const { cinderQuota = {} } = globalProjectStore;
|
||||||
const { add } = getAdd(cinderQuota);
|
const { add, addSize } = getAdd(cinderQuota, withCountCheck);
|
||||||
return add === 0;
|
return withCountCheck ? add === 0 : addSize === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const onVolumeSizeChange = (value) => {
|
export const onVolumeSizeChange = (value) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user