import { inject, observer } from 'mobx-react';
import { FormAction } from 'containers/Action';
import globalImageStore from 'stores/glance/image';
import {
imageOS,
imageUsage,
imageFormats,
imageFormatsConsole,
imageVisibility,
imageContainerFormats,
} from 'resources/glance/image';
import { cpuPolicyList, cpuThreadPolicyList } from 'resources/nova/flavor';
import { NoSetValue, getOptionsWithNoSet, getOptions } from 'utils/index';
import { ProjectStore } from 'stores/keystone/project';
import { projectTableOptions } from 'resources/keystone/project';
export class CreateForm extends FormAction {
init() {
this.store = globalImageStore;
this.projectStore = new ProjectStore();
this.isAdminPage && this.getProjects();
}
static id = 'image-create';
static title = t('Create Image');
static path = (_, containerProp) => {
const { isAdminPage } = containerProp;
return isAdminPage
? '/compute/image-admin/create'
: '/compute/image/create';
};
get listUrl() {
return this.getRoutePath('image');
}
get name() {
return t('Create image');
}
get labelCol() {
return {
xs: { span: 6 },
sm: { span: 5 },
};
}
get hasRequestCancelCallback() {
return true;
}
static policy = ['add_image', 'upload_image'];
static aliasPolicy = ['glance:add_image', 'glance:upload_image'];
static allowed() {
return Promise.resolve(true);
}
async getProjects() {
await this.projectStore.fetchProjectsWithDomain();
this.updateDefaultValue();
}
get projects() {
return this.projectStore.list.data || [];
}
get defaultValue() {
return {
uploadType: 'file',
hw_qemu_guest_agent: 'yes',
usage_type: 'common',
container_format: 'bare',
visibility: this.isAdminPage ? 'public' : false,
hw_cpu_policy: NoSetValue,
hw_cpu_thread_policy: NoSetValue,
};
}
get imageFormats() {
if (this.isAdminPage) {
return imageFormats;
}
return imageFormatsConsole;
}
get formatList() {
return Object.keys(this.imageFormats).map((key) => ({
value: key,
label: this.imageFormats[key],
}));
}
get containerFormatList() {
return getOptions(imageContainerFormats);
}
get osList() {
return Object.keys(imageOS).map((key) => ({
value: key,
label: imageOS[key],
}));
}
get yesNoList() {
return [
{ value: 'yes', label: t('Yes') },
{ value: 'no', label: t('No') },
];
}
get useTypeList() {
return Object.keys(imageUsage)
.map((key) => ({
value: key,
label: imageUsage[key],
}))
.filter((it) => {
if (!this.isAdminPage) {
return it.value === 'common' || it.value === 'ironic';
}
return true;
});
}
get typeList() {
return [
{ value: 'file', label: t('Upload File') },
{ value: 'url', label: t('File URL') },
];
}
checkFileType = (file) => {
const types = Object.keys(this.imageFormats);
const { name } = file;
const suffix = name.substring(name.lastIndexOf('.') + 1);
const suffixHasType = types.some((it) => suffix.toLowerCase().includes(it));
return suffixHasType;
};
validateFile = (rule, value) => {
if (!value) {
return Promise.reject(t('Please select a file'));
}
if (!this.checkFileType(value)) {
return Promise.reject(
t('Please select a file with the suffix {types}', {
types: Object.keys(this.imageFormats).join(','),
})
);
}
return Promise.resolve();
};
validateURL = (rule, value) => {
if (!value) return Promise.reject(t('Please enter URL!'));
const urlReg = /^https?:\/\/(.*)/;
if (!urlReg.test(value)) {
return Promise.reject(
t('Please enter a file link starting with "http://" or "https://"!')
);
}
return Promise.resolve();
};
onChangeContainerFormat = (value) => {
this.setState({
container_format: value,
});
};
get showContainerFormatFormItem() {
return this.containerFormatList.length > 1;
}
get isBareFormat() {
const { container_format = 'bare' } = this.state;
return container_format === 'bare';
}
get formItems() {
const { more, visibility, uploadType } = this.state;
const isShare = this.isAdminPage && visibility === 'shared';
const isUrl = uploadType === 'url';
return [
{
name: 'name',
label: t('Image Name'),
type: 'input-name',
isImage: true,
required: true,
},
{
name: 'owner',
label: t('Project'),
type: 'select-table',
required: this.isAdminPage,
hidden: !this.isAdminPage,
data: this.projects,
isLoading: this.projectStore.list.isLoading,
...projectTableOptions,
},
{
name: 'uploadType',
label: t('Upload Type'),
type: 'radio',
options: this.typeList,
},
{
name: 'file',
label: t('File'),
type: 'upload',
validator: this.validateFile,
required: !isUrl,
hidden: isUrl,
},
{
name: 'url',
label: t('File URL'),
type: 'input',
required: isUrl,
hidden: !isUrl,
validator: this.validateURL,
},
{
name: 'disk_format',
label: t('Disk Format'),
type: 'select',
tip: t(
'RAW - Raw disk image format\n' +
'QCOW2 - QEMU image format\n' +
'ISO - Optical disc image format\n' +
'AKI - Amazon kernel image format\n' +
'ARI - Amazon ramdisk image format\n' +
'AMI - Amazon server image format\n' +
'VDI - VirtualBox compatible image format\n' +
'VHD - VirtualPC compatible image format\n' +
'VMDK - Hyper-V compatible image format'
),
options: this.formatList,
required: true,
},
{
name: 'container_format',
label: t('Container Format'),
type: 'select',
options: this.containerFormatList,
onChange: this.onChangeContainerFormat,
hidden: !this.showContainerFormatFormItem,
required: this.showContainerFormatFormItem,
},
{
name: 'os_distro',
label: t('OS'),
type: 'select',
options: this.osList,
required: this.isBareFormat,
hidden: !this.isBareFormat,
},
{
name: 'os_version',
label: t('OS Version'),
type: 'input',
hidden: !this.isBareFormat,
required: this.isBareFormat,
},
{
name: 'os_admin_user',
label: t('OS Admin'),
type: 'input',
required: this.isBareFormat,
hidden: !this.isBareFormat,
extra: t(
'In general, administrator for Windows, root for Linux, please fill by image uploading.'
),
},
{
name: 'min_disk',
label: t('Min. System Disk (GiB)'),
type: 'input-int',
min: 0,
max: 500,
},
{
name: 'min_ram',
label: t('Min. Memory (GiB)'),
type: 'input-int',
min: 0,
max: 500,
},
{
name: 'visibility',
label: t('Visibility'),
type: 'radio',
options: getOptions(imageVisibility),
hidden: !this.isAdminPage,
},
{
name: 'members',
label: t('Project'),
type: 'select-table',
required: isShare,
isMulti: true,
hidden: !isShare,
data: this.projects,
isLoading: this.projectStore.list.isLoading,
...projectTableOptions,
},
{
name: 'protected',
label: t('Protection'),
type: 'check',
content: t('Protected'),
},
{
name: 'usage_type',
label: t('Usage Type'),
type: 'select',
options: this.useTypeList,
},
{
name: 'description',
label: t('Description'),
type: 'textarea',
maxLength: 255,
},
{
name: 'more',
label: t('Advanced Options'),
type: 'more',
},
{
name: 'hw_qemu_guest_agent',
label: t('QEMU Guest Agent Enabled'),
type: 'radio',
onlyRadio: true,
options: this.yesNoList,
tip: t(
'It is recommended to install and use this agent. The instance created with this image can be used to modify the password (QEMU Guest Agent needs to be installed when creating the image).'
),
hidden: !more,
},
{
name: 'hw_cpu_policy',
label: t('CPU Policy'),
type: 'select',
options: getOptionsWithNoSet(cpuPolicyList),
tip: t(
'CPU policy defines how instance vCPUs are mapped to host physical CPUs. Shared policy allows vCPUs to float and share host CPUs (overcommit enabled, default behavior). Dedicated policy pins vCPUs to specific physical CPUs, providing exclusive cores and predictable performance.'
),
hidden: !more,
required: more,
},
{
name: 'hw_cpu_thread_policy',
label: t('CPU Thread Policy'),
type: 'select',
options: getOptionsWithNoSet(cpuThreadPolicyList),
tip: t(
'CPU thread policy defines how instance vCPUs are placed on CPU threads when hyperthreading is enabled. Prefer policy allows vCPUs to share sibling threads if needed (default behavior). Isolate policy ensures vCPUs are placed on separate physical cores, avoiding sibling threads for better isolation. Require policy forces vCPUs to be scheduled on sibling threads of the same core.'
),
hidden: !more,
required: more,
},
];
}
onSubmit = (values) => {
const {
uploadType,
file,
url,
visibility,
more,
hw_cpu_policy,
hw_cpu_thread_policy,
min_ram,
owner,
usage_type = 'common',
members,
os_distro,
os_version,
os_admin_user,
container_format = 'bare',
...rest
} = values;
const body = {
visibility: visibility || 'private',
container_format,
usage_type,
...rest,
};
if (min_ram) {
body.min_ram = min_ram * 1024;
}
if (hw_cpu_policy !== NoSetValue) {
body.hw_cpu_policy = hw_cpu_policy;
}
if (hw_cpu_thread_policy !== NoSetValue) {
body.hw_cpu_thread_policy = hw_cpu_thread_policy;
}
if (this.isAdminPage) {
body.owner = owner.selectedRowKeys[0];
}
if (this.isBareFormat) {
body.os_distro = os_distro;
body.os_version = os_version;
body.os_admin_user = os_admin_user;
if (os_distro === 'windows') {
body.os_type = 'windows';
}
}
const mems = visibility === 'shared' ? members.selectedRowKeys : [];
const config = this.getUploadRequestConf();
const actualFile = uploadType === 'file' ? file : url;
return this.store.create(body, actualFile, mems, config);
};
}
export default inject('rootStore')(observer(CreateForm));