1. update the port id/name column in the MemberAllocator component which is used in create load balancer action 2. update the database backup id/name column in the database backup list page 3. update the configuration id/name column in the database configuration list page 4. update the id/name columns in the vpn endpoint/ike policy/ipsec policy/gateway list page 5. update the volume id/name column in the volumeSelectTable component which is used in instance attach volumes action 6. update the port id/name column in the network step in create instance action Change-Id: Idc3bc161a56b06cb83f9e4c9543edaf0762a4669
537 lines
13 KiB
JavaScript
537 lines
13 KiB
JavaScript
// Copyright 2021 99cloud
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import React from 'react';
|
|
import { yesNoOptions } from 'utils/constants';
|
|
import { renderFilterMap } from 'utils/index';
|
|
import globalProjectStore from 'stores/keystone/project';
|
|
import globalVolumeStore from 'stores/cinder/volume';
|
|
import { isEmpty } from 'lodash';
|
|
import { idNameColumn } from 'utils/table';
|
|
|
|
export const volumeStatus = {
|
|
available: t('Available'),
|
|
'in-use': t('In-use'),
|
|
error: t('Error'),
|
|
creating: t('Creating'),
|
|
error_extending: t('Error Extending'),
|
|
extending: t('Extending'),
|
|
downloading: t('Downloading'),
|
|
attaching: t('Attaching'),
|
|
detaching: t('Detaching'),
|
|
deleting: t('Deleting'),
|
|
error_deleting: t('Error Deleting'),
|
|
'backing-up': t('Backing Up'),
|
|
'restoring-backup': t('Restoring Backup'),
|
|
error_restoring: t('Error Restoring'),
|
|
maintenance: t('Maintained'),
|
|
uploading: t('Uploading'),
|
|
'awaiting-transfer': t('Awaiting Transfer'),
|
|
restoring: t('Restoring'),
|
|
rollbacking: t('Restoring'),
|
|
retyping: t('Retyping'),
|
|
reserved: t('Reserved'),
|
|
reverting: t('Reverting'),
|
|
};
|
|
|
|
export const creationMethod = {
|
|
manu: t('Manu'),
|
|
auto: t('Auto'),
|
|
};
|
|
|
|
export const diskTag = {
|
|
os_disk: t('OS Disk'),
|
|
data_disk: t('Data Disk'),
|
|
};
|
|
|
|
export const bootableType = {
|
|
true: t('Yes'),
|
|
false: t('No'),
|
|
};
|
|
|
|
export const isOsDisk = (item) => {
|
|
if (item.disk_tag) {
|
|
return item.disk_tag === 'os_disk';
|
|
}
|
|
if (
|
|
item.bootable === 'true' &&
|
|
item.attachments &&
|
|
item.attachments.length !== 0 &&
|
|
(item.attachments[0].device === '/dev/vda' ||
|
|
item.attachments[0].device === '/dev/sda')
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export const updateVolume = (volume) => {
|
|
return {
|
|
...volume,
|
|
disk_tag: isOsDisk(volume) ? 'os_disk' : 'data_disk',
|
|
description: volume.description || (volume.origin_data || {}).description,
|
|
delete_interval:
|
|
volume.metadata && volume.metadata.delete_interval
|
|
? new Date(renderFilterMap.toLocalTime(volume.updated_at)).getTime() +
|
|
Number(volume.metadata.delete_interval) * 1000
|
|
: null,
|
|
};
|
|
};
|
|
|
|
export const isAttachIsoVolume = (item) => {
|
|
if (
|
|
item.bootable === 'true' &&
|
|
item.attachments &&
|
|
item.attachments.length !== 0 &&
|
|
item.attachments[0].device.indexOf('/dev/hd') !== -1
|
|
) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export const volumeTransitionStatuses = [
|
|
'creating',
|
|
'extending',
|
|
'downloading',
|
|
'attaching',
|
|
'detaching',
|
|
'deleting',
|
|
'backing-up',
|
|
'restoring-backup',
|
|
'awaiting-transfer',
|
|
'uploading',
|
|
'rollbacking',
|
|
'retyping',
|
|
];
|
|
|
|
export const snapshotTransitionStatuses = [
|
|
'creating',
|
|
'deleting',
|
|
'updating',
|
|
'unmanaging',
|
|
'backing-up',
|
|
'restoring',
|
|
'uploading',
|
|
'rollbacking',
|
|
];
|
|
|
|
export const canCreateInstance = (item) =>
|
|
item.bootable === 'true' &&
|
|
['available', 'downloading'].indexOf(item.status) >= 0;
|
|
|
|
export const isAvailable = (item) => item.status === 'available';
|
|
|
|
export const isAvailableOrInUse = (item) =>
|
|
item && ['available', 'in-use'].indexOf(item.status) > -1;
|
|
|
|
export const isInUse = (item) => item.status === 'in-use';
|
|
|
|
export const isMultiAttach = (item) => item.multiattach;
|
|
|
|
export const multiTip = t(
|
|
'"Shared" volume can be mounted on multiple instances'
|
|
);
|
|
|
|
export const volumeColumns = [
|
|
idNameColumn,
|
|
{
|
|
title: t('Type'),
|
|
dataIndex: 'volume_type',
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Size'),
|
|
dataIndex: 'size',
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: t('Status'),
|
|
dataIndex: 'status',
|
|
valueMap: volumeStatus,
|
|
},
|
|
{
|
|
title: t('Shared'),
|
|
dataIndex: 'multiattach',
|
|
valueRender: 'yesNo',
|
|
titleTip: multiTip,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Attached To'),
|
|
dataIndex: 'attachments',
|
|
isHideable: true,
|
|
sorter: false,
|
|
render: (value) => {
|
|
if (value && value.length > 0) {
|
|
return value.map((it) => (
|
|
<div key={it.server_id}>
|
|
{it.device} on {it.server_name}
|
|
</div>
|
|
));
|
|
}
|
|
return '-';
|
|
},
|
|
},
|
|
{
|
|
title: t('Created At'),
|
|
dataIndex: 'created_at',
|
|
valueRender: 'sinceTime',
|
|
},
|
|
];
|
|
|
|
export const volumeFilters = [
|
|
{
|
|
label: t('Name'),
|
|
name: 'name',
|
|
},
|
|
{
|
|
label: t('Status'),
|
|
name: 'status',
|
|
options: ['available', 'in-use', 'error', 'maintenance'].map((it) => ({
|
|
key: it,
|
|
label: volumeStatus[it],
|
|
})),
|
|
},
|
|
{
|
|
label: t('Shared'),
|
|
name: 'multiattach',
|
|
options: yesNoOptions,
|
|
},
|
|
];
|
|
|
|
export const volumeSortProps = {
|
|
isSortByBack: true,
|
|
defaultSortKey: 'created_at',
|
|
defaultSortOrder: 'descend',
|
|
};
|
|
|
|
export const volumeSelectTablePropsBackend = {
|
|
...volumeSortProps,
|
|
filterParams: volumeFilters,
|
|
columns: volumeColumns,
|
|
};
|
|
|
|
export const snapshotTypeTip = (
|
|
<>
|
|
<p style={{ marginTop: 10 }}>
|
|
{t(
|
|
'The volume type needs to be consistent with the volume type when the snapshot is created.'
|
|
)}
|
|
</p>
|
|
<p>
|
|
{t(
|
|
'If the volume associated with the snapshot has changed the volume type, please modify this option manually; if the volume associated with the snapshot keeps the volume type unchanged, please ignore this option. (no need to change).'
|
|
)}
|
|
</p>
|
|
</>
|
|
);
|
|
|
|
export const getVolumeColumnsList = (self) => {
|
|
return [
|
|
{
|
|
title: t('ID/Name'),
|
|
dataIndex: 'name',
|
|
routeName: self.getRouteName('volumeDetail'),
|
|
sortKey: 'name',
|
|
},
|
|
{
|
|
title: t('Project ID/Name'),
|
|
dataIndex: 'project_name',
|
|
hidden: !self.isAdminPage,
|
|
isHideable: true,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Host'),
|
|
dataIndex: 'host',
|
|
isHideable: true,
|
|
hidden: !self.isAdminPage,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Size'),
|
|
dataIndex: 'size',
|
|
isHideable: true,
|
|
unit: 'GiB',
|
|
},
|
|
{
|
|
title: t('Status'),
|
|
dataIndex: 'status',
|
|
valueMap: volumeStatus,
|
|
},
|
|
{
|
|
title: t('Type'),
|
|
dataIndex: 'volume_type',
|
|
isHideable: true,
|
|
width: 100,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Disk Tag'),
|
|
dataIndex: 'disk_tag',
|
|
isHideable: true,
|
|
valueMap: diskTag,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Attached To'),
|
|
dataIndex: 'attachments',
|
|
isHideable: true,
|
|
sorter: false,
|
|
hidden: self.isInstanceDetail,
|
|
render: (value) => {
|
|
if (value && value.length > 0) {
|
|
return value.map((it) => (
|
|
<div key={it.server_id}>
|
|
{it.device} on{' '}
|
|
{self.getLinkRender(
|
|
'instanceDetail',
|
|
it.server_name || it.server_id,
|
|
{ id: it.server_id },
|
|
{ tab: 'volumes' }
|
|
)}
|
|
</div>
|
|
));
|
|
}
|
|
return '-';
|
|
},
|
|
stringify: (value) => {
|
|
if (value && value.length) {
|
|
return value
|
|
.map((it) => {
|
|
const { device, server_name, server_id } = it;
|
|
return `${device} on ${server_name || '-'}(${server_id})`;
|
|
})
|
|
.join(',');
|
|
}
|
|
return '-';
|
|
},
|
|
},
|
|
{
|
|
title: t('Bootable'),
|
|
titleTip: t(
|
|
'When the volume is "bootable" and the status is "available", it can be used as a startup source to create an instance.'
|
|
),
|
|
dataIndex: 'bootable',
|
|
isHideable: true,
|
|
valueMap: bootableType,
|
|
},
|
|
{
|
|
title: t('Shared'),
|
|
dataIndex: 'multiattach',
|
|
valueRender: 'yesNo',
|
|
titleTip: multiTip,
|
|
width: 80,
|
|
sorter: false,
|
|
},
|
|
{
|
|
title: t('Created At'),
|
|
dataIndex: 'created_at',
|
|
isHideable: true,
|
|
valueRender: 'sinceTime',
|
|
},
|
|
];
|
|
};
|
|
|
|
// deal with quota
|
|
export function setCreateVolumeSize(value) {
|
|
globalVolumeStore.setCreateVolumeSize(value);
|
|
}
|
|
|
|
export function setCreateVolumeType(value) {
|
|
globalVolumeStore.setCreateVolumeType(value);
|
|
}
|
|
|
|
export function setCreateVolumeCount(value) {
|
|
globalVolumeStore.setCreateVolumeCount(value);
|
|
}
|
|
|
|
export async function fetchQuota(self, size, type = '') {
|
|
setCreateVolumeCount(1);
|
|
setCreateVolumeSize(size);
|
|
setCreateVolumeType(type);
|
|
self.setState({
|
|
quota: {},
|
|
quotaLoading: true,
|
|
});
|
|
const result = await globalProjectStore.fetchProjectCinderQuota();
|
|
self.setState({
|
|
quota: result,
|
|
quotaLoading: false,
|
|
});
|
|
}
|
|
|
|
export const getQuota = (cinderQuota) => {
|
|
if (isEmpty(cinderQuota)) {
|
|
return {};
|
|
}
|
|
const { volumeTypeForCreate } = globalVolumeStore;
|
|
const { volumes = {}, gigabytes = {} } = cinderQuota || {};
|
|
const typeQuotaKey = `volumes_${volumeTypeForCreate}`;
|
|
const sizeQuotaKey = `gigabytes_${volumeTypeForCreate}`;
|
|
const typeQuota = (cinderQuota || {})[typeQuotaKey] || {};
|
|
const typeSizeQuota = (cinderQuota || {})[sizeQuotaKey] || {};
|
|
return {
|
|
volumes,
|
|
gigabytes,
|
|
typeQuota,
|
|
typeSizeQuota,
|
|
};
|
|
};
|
|
|
|
const getErrorMessage = ({ name, left, input }) => {
|
|
if (left === 0) {
|
|
return t('Quota: Insufficient { name } quota to create resources.', {
|
|
name,
|
|
});
|
|
}
|
|
const error = t(
|
|
'Quota: Insufficient { name } quota to create resources, please adjust resource quantity or quota(left { left }, input { input }).',
|
|
{
|
|
name,
|
|
left,
|
|
input,
|
|
}
|
|
);
|
|
return error;
|
|
};
|
|
|
|
export const getAdd = (cinderQuota, withCountCheck = true) => {
|
|
if (isEmpty(cinderQuota)) {
|
|
return {};
|
|
}
|
|
const { volumes, gigabytes, typeQuota, typeSizeQuota } =
|
|
getQuota(cinderQuota);
|
|
const { left = 0 } = volumes || {};
|
|
const { left: sizeLeft = 0, limit: sizeLimit } = gigabytes || {};
|
|
const { left: typeLeft = 0 } = typeQuota || {};
|
|
const { left: typeSizeLeft = 0, limit: typeSizeLimit } = typeSizeQuota || {};
|
|
const {
|
|
volumeSizeForCreate: size = 0,
|
|
volumeCountForCreate: count = 1,
|
|
volumeTypeForCreate: type = '',
|
|
} = globalVolumeStore;
|
|
const zero = {
|
|
add: 0,
|
|
addSize: 0,
|
|
};
|
|
const totalSize = size * count;
|
|
const create = {
|
|
add: count,
|
|
addSize: totalSize,
|
|
};
|
|
if (withCountCheck && left >= 0 && left < count) {
|
|
const error = getErrorMessage({
|
|
name: t('volume'),
|
|
left,
|
|
input: count,
|
|
});
|
|
return { ...zero, error };
|
|
}
|
|
if ((sizeLimit !== -1 && sizeLeft < totalSize) || sizeLeft === 0) {
|
|
const error = getErrorMessage({
|
|
name: t('volume capacity'),
|
|
left: sizeLeft,
|
|
input: totalSize,
|
|
});
|
|
return { ...zero, error };
|
|
}
|
|
if (isEmpty(typeQuota)) {
|
|
return create;
|
|
}
|
|
if (withCountCheck && typeLeft >= 0 && typeLeft < count) {
|
|
const error = getErrorMessage({
|
|
name: t('{name} type', { name: type }),
|
|
left: typeLeft,
|
|
input: count,
|
|
});
|
|
return { ...zero, error };
|
|
}
|
|
if (
|
|
(typeSizeLimit !== -1 && typeSizeLeft < totalSize) ||
|
|
typeSizeLeft === 0
|
|
) {
|
|
const error = getErrorMessage({
|
|
name: t('{name} type capacity', { name: type }),
|
|
left: typeSizeLeft,
|
|
input: totalSize,
|
|
});
|
|
return { ...zero, error };
|
|
}
|
|
return create;
|
|
};
|
|
|
|
export const getQuotaInfo = (self, withCountCheck = true) => {
|
|
const { volumeTypeForCreate: name } = globalVolumeStore;
|
|
const { quota = {}, quotaLoading } = self.state;
|
|
if (quotaLoading || isEmpty(quota)) {
|
|
return [];
|
|
}
|
|
const {
|
|
volumes = {},
|
|
gigabytes = {},
|
|
typeQuota = {},
|
|
typeSizeQuota = {},
|
|
} = getQuota(quota);
|
|
const { add, addSize } = getAdd(quota, withCountCheck);
|
|
const volumeData = {
|
|
...volumes,
|
|
add,
|
|
name: 'volume',
|
|
title: t('Volume'),
|
|
};
|
|
const sizeData = {
|
|
...gigabytes,
|
|
add: addSize,
|
|
name: 'gigabytes',
|
|
title: t('Volume Capacity (GiB)'),
|
|
type: 'line',
|
|
};
|
|
if (!name) {
|
|
return [volumeData, sizeData];
|
|
}
|
|
const typeData = {
|
|
...typeQuota,
|
|
add,
|
|
name: 'type',
|
|
title: t('{name} type', { name }),
|
|
type: 'line',
|
|
};
|
|
const typeSizeData = {
|
|
...typeSizeQuota,
|
|
add: addSize,
|
|
name: 'typeSize',
|
|
title: t('{name} type capacity', { name }),
|
|
type: 'line',
|
|
};
|
|
return [volumeData, sizeData, typeData, typeSizeData];
|
|
};
|
|
|
|
export const checkQuotaDisable = (withCountCheck = true) => {
|
|
const { cinderQuota = {} } = globalProjectStore;
|
|
const { add, addSize } = getAdd(cinderQuota, withCountCheck);
|
|
return withCountCheck ? add === 0 : addSize === 0;
|
|
};
|
|
|
|
export const onVolumeSizeChange = (value) => {
|
|
setCreateVolumeSize(value);
|
|
};
|
|
|
|
export const onVolumeTypeChange = (value) => {
|
|
const { volumeTypes = [] } = globalVolumeStore;
|
|
const item = volumeTypes.find((it) => it.value === value);
|
|
setCreateVolumeType((item || {}).label || '');
|
|
};
|