skyline-console/src/pages/compute/containers/Instance/actions/StepCreate/index.jsx

380 lines
9.8 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 { inject, observer } from 'mobx-react';
import { toJS } from 'mobx';
import { InputNumber, Badge } from 'antd';
import { StepAction } from 'containers/Action';
import globalServerStore from 'stores/nova/instance';
import globalProjectStore from 'stores/keystone/project';
import classnames from 'classnames';
import { isEmpty, isFinite, isString } from 'lodash';
import { getUserData } from 'resources/instance';
import Notify from 'components/Notify';
import styles from './index.less';
import ConfirmStep from './ConfirmStep';
import SystemStep from './SystemStep';
import NetworkStep from './NetworkStep';
import BaseStep from './BaseStep';
@inject('rootStore')
@observer
class StepCreate extends StepAction {
static id = 'instance-create';
static title = t('Create Instance');
static path = (_, containerProps) => {
const { detail, match } = containerProps || {};
if (!detail || isEmpty(detail)) {
return '/compute/instance/create';
}
if (match.path.indexOf('/compute/server') >= 0) {
return `/compute/instance/create?servergroup=${detail.id}`;
}
};
init() {
this.store = globalServerStore;
this.projectStore = globalProjectStore;
this.getQuota();
}
static policy = [
'os_compute_api:servers:create',
'os_compute_api:os-availability-zone:list',
];
static allowed(_, containerProps) {
const { isAdminPage = false } = containerProps;
return Promise.resolve(!isAdminPage);
}
async getQuota() {
await this.projectStore.fetchProjectQuota({
project_id: this.currentProjectId,
});
this.onCountChange(1);
}
get quota() {
const { instances = {} } = toJS(this.projectStore.quota) || {};
const { limit = 10, used = 0 } = instances;
if (limit === -1) {
return Infinity;
}
return limit - used;
}
get name() {
return t('Create instance');
}
get listUrl() {
const { image, volume, servergroup } = this.locationParams;
if (image) {
return '/compute/image';
}
if (volume) {
return '/storage/volume';
}
if (servergroup) {
return `/compute/server-group/detail/${servergroup}`;
}
return '/compute/instance';
}
get hasConfirmStep() {
return false;
}
get steps() {
return [
{
title: t('Base Config'),
component: BaseStep,
},
{
title: t('Network Config'),
component: NetworkStep,
},
{
title: t('System Config'),
component: SystemStep,
},
{
title: t('Confirm Config'),
component: ConfirmStep,
},
];
}
get instanceName() {
const { name, count = 1 } = this.values || {};
if (count === 1) {
return this.unescape(name);
}
return this.unescape(
new Array(count)
.fill(count)
.map((_, index) => `${name}-${index + 1}`)
.join(', ')
);
}
get successText() {
return t(
'The creation instruction was issued successfully, instance: {name}. \n You can wait for a few seconds to follow the changes of the list data or manually refresh the data to get the final display result.',
{ action: this.name.toLowerCase(), name: this.instanceName }
);
}
get errorText() {
const { status } = this.state;
if (status === 'error') {
return t(
'Unable to create instance: insufficient quota to create resources.'
);
}
if (this.ipBatchError) {
return t(
'Unable to create instance: batch creation is not supported when specifying IP.'
);
}
return t(
'The creation instruction has been issued, please refresh to see the actual situation in the list.'
);
}
onCountChange = (value) => {
const { data } = this.state;
let msg = t('Quota: Project quotas sufficient resources can be created');
let status = 'success';
if (isFinite(this.quota) && value > this.quota) {
msg = t(
'Quota: Insufficient quota to create resources, please adjust resource quantity or quota(left { quota }, input { input }).',
{ quota: this.quota, input: value }
);
status = 'error';
}
this.msg = msg;
this.setState({
data: {
...data,
count: value,
},
status,
});
};
renderBadge() {
const { status } = this.state;
if (status === 'success') {
return null;
}
return <Badge status={status} text={this.msg} />;
}
renderFooterLeft() {
const { data } = this.state;
const { count = 1, source: { value: sourceValue } = {} } = data;
const configs = {
min: 1,
max: sourceValue === 'bootableVolume' ? 1 : 100,
precision: 0,
onChange: this.onCountChange,
formatter: (value) => `$ ${value}`.replace(/\D/g, ''),
};
return (
<div>
<span>{t('Count')}</span>
<InputNumber
{...configs}
value={count}
className={classnames(styles.input, 'instance-count')}
/>
{this.renderBadge()}
</div>
);
}
onSubmit = (values) => {
const { status } = this.state;
if (status === 'error') {
return Promise.reject();
}
/* eslint-disable no-unused-vars */
const {
availableZone,
bootableVolume,
dataDisk,
host,
image,
instanceSnapshot,
iso,
keypair,
loginType,
network,
networks,
password,
physicalNode,
physicalNodeType,
project,
resource,
securityGroup,
source,
flavor,
systemDisk,
userData = '',
serverGroup,
name,
count = 1,
} = values;
let imageRef = null;
let rootVolume = {};
const { value: sourceValue } = source;
if (sourceValue !== 'bootableVolume') {
const { deleteType, type, size } = systemDisk;
imageRef =
sourceValue === 'image'
? image.selectedRowKeys[0]
: instanceSnapshot.selectedRowKeys[0];
rootVolume = {
boot_index: 0,
uuid: imageRef,
source_type: 'image',
volume_size: size,
destination_type: 'volume',
volume_type: type,
delete_on_termination: deleteType === 1,
};
} else {
rootVolume = {
boot_index: 0,
uuid: bootableVolume.selectedRowKeys[0],
source_type: 'volume',
destination_type: 'volume',
};
}
const dataVolumes = dataDisk
? dataDisk.map((it) => {
const {
size: volumeSize,
type: volumeType,
deleteType: volumeDelType,
} = it.value || {};
return {
source_type: 'blank',
volume_size: volumeSize,
destination_type: 'volume',
volume_type: volumeType,
delete_on_termination: volumeDelType === 1,
};
})
: [];
let hasIp = false;
if (
sourceValue === 'image' &&
image.selectedRows[0].disk_format === 'iso' &&
dataVolumes[0]
) {
dataVolumes[0].boot_index = 0;
dataVolumes[0].device_type = 'disk';
rootVolume.boot_index = 1;
rootVolume.device_type = 'cdrom';
// rootVolume.disk_bus = 'ide';
// dataVolumes[0].disk_bus = 'virtio';
}
const server = {
security_groups: securityGroup.selectedRows.map((it) => ({
name: it.id,
})),
name,
flavorRef: flavor.selectedRowKeys[0],
availability_zone: availableZone.value,
block_device_mapping_v2: [rootVolume, ...dataVolumes],
networks: networks.map((it) => {
const net = {
uuid: it.value.network,
};
if (it.value.ipType === 1 && it.value.ip) {
net.fixed_ip = it.value.ip;
hasIp = true;
}
return net;
}),
};
if (hasIp && count > 1) {
this.ipBatchError = true;
return Promise.reject();
}
if (imageRef) {
server.imageRef = imageRef;
}
if (loginType.value === 'keypair') {
server.key_name = keypair.selectedRowKeys[0];
} else {
server.adminPass = password;
}
if (count > 1) {
server.min_count = count;
server.max_count = count;
server.return_reservation_id = true;
}
if (physicalNodeType.value !== 'smart') {
server.hypervisor_hostname =
physicalNode.selectedRows[0].hypervisor_hostname;
}
if (server.adminPass || userData) {
server.user_data = btoa(getUserData(server.adminPass, userData));
}
const body = {
server,
};
if (serverGroup && serverGroup.selectedRowKeys.length > 0) {
body['OS-SCH-HNT:scheduler_hints'] = {
group: serverGroup.selectedRowKeys[0],
};
}
return this.store.create(body);
};
onOk = () => {
const { data } = this.state;
this.values = data;
this.onSubmit(data).then(
() => {
this.routing.push(this.listUrl);
Notify.success(this.successText);
},
(err) => {
const { data: { forbidden: { message = '' } = {} } = {} } = err;
if (
message &&
isString(message) &&
message.includes('Quota exceeded')
) {
Notify.error(t('Quota exceeded'));
} else {
Notify.errorWithDetail(err, this.errorText);
}
}
);
};
}
export default StepCreate;