// 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) => (
{it.device} on {it.server_name}
)); } 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 = ( <>

{t( 'The volume type needs to be consistent with the volume type when the snapshot is created.' )}

{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).' )}

); 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) => (
{it.device} on{' '} {self.getLinkRender( 'instanceDetail', it.server_name || it.server_id, { id: it.server_id }, { tab: 'volumes' } )}
)); } 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 || ''); };