feat: detach instance snapshot list page
1. Update instance create image params: add image_type && instance_id 2. Support instance snapshot list page 3. Support instance snapshot detail page 4. Support instance snapshot tab in instance detail page 5. Support edit instance snapshot 6. Support delete instance snapshot 7. Support instance snapshot create volume 8. Update image list page: remove snapshot from origin data 9. Update BaseDetail commponent support path 10. Update TabDetail component support path Change-Id: I577c046e8d80ebf26be04db881aa0f6f3d9bc01e
This commit is contained in:
parent
a8ff91c219
commit
5eaa2d8ff4
@ -66,6 +66,11 @@ export default class BaseDetail extends React.Component {
|
|||||||
return this.props.rootStore.routing;
|
return this.props.rootStore.routing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
const { location: { pathname = '' } = {} } = this.props;
|
||||||
|
return pathname || '';
|
||||||
|
}
|
||||||
|
|
||||||
get leftCards() {
|
get leftCards() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,11 @@ export default class DetailBase extends React.Component {
|
|||||||
return this.props.rootStore.routing;
|
return this.props.rootStore.routing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
const { location: { pathname = '' } = {} } = this.props;
|
||||||
|
return pathname || '';
|
||||||
|
}
|
||||||
|
|
||||||
get isAdminPage() {
|
get isAdminPage() {
|
||||||
const { pathname } = this.props.location;
|
const { pathname } = this.props.location;
|
||||||
return isAdminPage(pathname);
|
return isAdminPage(pathname);
|
||||||
|
@ -61,6 +61,21 @@ const renderMenu = (t) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/compute/instance-snapshot-admin',
|
||||||
|
name: t('Instance Snapshot'),
|
||||||
|
key: 'instanceSnapshotAdmin',
|
||||||
|
level: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: /^\/compute\/instance-snapshot-admin\/detail\/[^/]+$/,
|
||||||
|
name: t('Instance Snapshot Detail'),
|
||||||
|
key: 'instanceSnapshotDetailAdmin',
|
||||||
|
level: 2,
|
||||||
|
routePath: '/compute/instance-snapshot-admin/detail/:id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/compute/flavor-admin',
|
path: '/compute/flavor-admin',
|
||||||
name: t('Flavor'),
|
name: t('Flavor'),
|
||||||
|
@ -72,6 +72,21 @@ const renderMenu = (t) => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/compute/instance-snapshot',
|
||||||
|
name: t('Instance Snapshot'),
|
||||||
|
key: 'instanceSnapshot',
|
||||||
|
level: 1,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: /^\/compute\/instance-snapshot\/detail\/[^/]+$/,
|
||||||
|
name: t('Instance Snapshot Detail'),
|
||||||
|
key: 'instanceSnapshotDetail',
|
||||||
|
level: 2,
|
||||||
|
routePath: '/compute/instance-snapshot/detail/:id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/compute/flavor',
|
path: '/compute/flavor',
|
||||||
name: t('Flavor'),
|
name: t('Flavor'),
|
||||||
|
@ -323,10 +323,7 @@
|
|||||||
"Checksum": "Checksum",
|
"Checksum": "Checksum",
|
||||||
"Chile": "Chile",
|
"Chile": "Chile",
|
||||||
"China": "China",
|
"China": "China",
|
||||||
"Choose a External Network": "Choose a External Network",
|
|
||||||
"Choose a Network Driver": "Choose a Network Driver",
|
"Choose a Network Driver": "Choose a Network Driver",
|
||||||
"Choose a Private Network": "Choose a Private Network",
|
|
||||||
"Choose a Private Network at first": "Choose a Private Network at first",
|
|
||||||
"Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.": "Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.",
|
"Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.": "Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.",
|
||||||
"Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.": "Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.",
|
"Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.": "Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.",
|
||||||
"Choosing a QoS policy can limit bandwidth and DSCP": "Choosing a QoS policy can limit bandwidth and DSCP",
|
"Choosing a QoS policy can limit bandwidth and DSCP": "Choosing a QoS policy can limit bandwidth and DSCP",
|
||||||
@ -629,6 +626,7 @@
|
|||||||
"Delete Image": "Delete Image",
|
"Delete Image": "Delete Image",
|
||||||
"Delete In Progress": "Delete In Progress",
|
"Delete In Progress": "Delete In Progress",
|
||||||
"Delete Instance": "Delete Instance",
|
"Delete Instance": "Delete Instance",
|
||||||
|
"Delete Instance Snapshot": "Delete Instance Snapshot",
|
||||||
"Delete Keypair": "Delete Keypair",
|
"Delete Keypair": "Delete Keypair",
|
||||||
"Delete Listener": "Delete Listener",
|
"Delete Listener": "Delete Listener",
|
||||||
"Delete Load Balancer": "Delete Load Balancer",
|
"Delete Load Balancer": "Delete Load Balancer",
|
||||||
@ -778,6 +776,7 @@
|
|||||||
"Edit IPsec Site Connection": "Edit IPsec Site Connection",
|
"Edit IPsec Site Connection": "Edit IPsec Site Connection",
|
||||||
"Edit Image": "Edit Image",
|
"Edit Image": "Edit Image",
|
||||||
"Edit Instance": "Edit Instance",
|
"Edit Instance": "Edit Instance",
|
||||||
|
"Edit Instance Snapshot": "Edit Instance Snapshot",
|
||||||
"Edit Listener": "Edit Listener",
|
"Edit Listener": "Edit Listener",
|
||||||
"Edit Load Balancer": "Edit Load Balancer",
|
"Edit Load Balancer": "Edit Load Balancer",
|
||||||
"Edit Member": "Edit Member",
|
"Edit Member": "Edit Member",
|
||||||
@ -1181,6 +1180,8 @@
|
|||||||
"Instance IP": "Instance IP",
|
"Instance IP": "Instance IP",
|
||||||
"Instance Info": "Instance Info",
|
"Instance Info": "Instance Info",
|
||||||
"Instance Name": "Instance Name",
|
"Instance Name": "Instance Name",
|
||||||
|
"Instance Snapshot": "Instance Snapshot",
|
||||||
|
"Instance Snapshot Detail": "Instance Snapshot Detail",
|
||||||
"Instance Status": "Instance Status",
|
"Instance Status": "Instance Status",
|
||||||
"Instances": "Instances",
|
"Instances": "Instances",
|
||||||
"Instances \"{ name }\" are locked, can not delete them.": "Instances \"{ name }\" are locked, can not delete them.",
|
"Instances \"{ name }\" are locked, can not delete them.": "Instances \"{ name }\" are locked, can not delete them.",
|
||||||
@ -2067,6 +2068,7 @@
|
|||||||
"Snapshot Instance": "Snapshot Instance",
|
"Snapshot Instance": "Snapshot Instance",
|
||||||
"Snapshot Name": "Snapshot Name",
|
"Snapshot Name": "Snapshot Name",
|
||||||
"Snapshots": "Snapshots",
|
"Snapshots": "Snapshots",
|
||||||
|
"Snapshots can be converted into volume and used to create an instance from the volume.": "Snapshots can be converted into volume and used to create an instance from the volume.",
|
||||||
"Snapshotting": "Snapshotting",
|
"Snapshotting": "Snapshotting",
|
||||||
"Soft Delete Instance": "Soft Delete Instance",
|
"Soft Delete Instance": "Soft Delete Instance",
|
||||||
"Soft Deleted": "Soft Deleted",
|
"Soft Deleted": "Soft Deleted",
|
||||||
@ -2615,6 +2617,7 @@
|
|||||||
"delete group": "delete group",
|
"delete group": "delete group",
|
||||||
"delete image": "delete image",
|
"delete image": "delete image",
|
||||||
"delete instance": "delete instance",
|
"delete instance": "delete instance",
|
||||||
|
"delete instance snapshot": "delete instance snapshot",
|
||||||
"delete ipsec site connection": "delete ipsec site connection",
|
"delete ipsec site connection": "delete ipsec site connection",
|
||||||
"delete ironic instance": "delete ironic instance",
|
"delete ironic instance": "delete ironic instance",
|
||||||
"delete keypair": "delete keypair",
|
"delete keypair": "delete keypair",
|
||||||
@ -2652,6 +2655,7 @@
|
|||||||
"edit default pool": "edit default pool",
|
"edit default pool": "edit default pool",
|
||||||
"edit health monitor": "edit health monitor",
|
"edit health monitor": "edit health monitor",
|
||||||
"edit image": "edit image",
|
"edit image": "edit image",
|
||||||
|
"edit instance snapshot": "edit instance snapshot",
|
||||||
"edit member": "edit member",
|
"edit member": "edit member",
|
||||||
"edit system permission": "edit system permission",
|
"edit system permission": "edit system permission",
|
||||||
"egress": "egress",
|
"egress": "egress",
|
||||||
@ -2674,6 +2678,7 @@
|
|||||||
"insert": "insert",
|
"insert": "insert",
|
||||||
"instance": "instance",
|
"instance": "instance",
|
||||||
"instance snapshot": "instance snapshot",
|
"instance snapshot": "instance snapshot",
|
||||||
|
"instance snapshots": "instance snapshots",
|
||||||
"instance: {name}.": "instance: {name}.",
|
"instance: {name}.": "instance: {name}.",
|
||||||
"instances": "instances",
|
"instances": "instances",
|
||||||
"ipsec site connection": "ipsec site connection",
|
"ipsec site connection": "ipsec site connection",
|
||||||
|
@ -323,10 +323,7 @@
|
|||||||
"Checksum": "校验和",
|
"Checksum": "校验和",
|
||||||
"Chile": "智利",
|
"Chile": "智利",
|
||||||
"China": "中国大陆",
|
"China": "中国大陆",
|
||||||
"Choose a External Network": "选择外部网络",
|
|
||||||
"Choose a Network Driver": "选择网络驱动程序",
|
"Choose a Network Driver": "选择网络驱动程序",
|
||||||
"Choose a Private Network": "选择专用网络",
|
|
||||||
"Choose a Private Network at first": "首先选择一个专用网络",
|
|
||||||
"Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.": "选择计算节点来热迁移云主机,如果没有选择,调度器会自动选择目标计算节点。",
|
"Choose a host to live migrate instance to. If not selected, the scheduler will auto select target host.": "选择计算节点来热迁移云主机,如果没有选择,调度器会自动选择目标计算节点。",
|
||||||
"Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.": "选择计算节点来迁移云主机,如果没有选择,调度器会自动选择目标计算节点。",
|
"Choose a host to migrate instance to. If not selected, the scheduler will auto select target host.": "选择计算节点来迁移云主机,如果没有选择,调度器会自动选择目标计算节点。",
|
||||||
"Choosing a QoS policy can limit bandwidth and DSCP": "选择QoS策略可以限制带宽和DSCP",
|
"Choosing a QoS policy can limit bandwidth and DSCP": "选择QoS策略可以限制带宽和DSCP",
|
||||||
@ -629,6 +626,7 @@
|
|||||||
"Delete Image": "删除镜像",
|
"Delete Image": "删除镜像",
|
||||||
"Delete In Progress": "正在删除",
|
"Delete In Progress": "正在删除",
|
||||||
"Delete Instance": "删除云主机",
|
"Delete Instance": "删除云主机",
|
||||||
|
"Delete Instance Snapshot": "删除云主机快照",
|
||||||
"Delete Keypair": "删除密钥",
|
"Delete Keypair": "删除密钥",
|
||||||
"Delete Listener": "删除监听器",
|
"Delete Listener": "删除监听器",
|
||||||
"Delete Load Balancer": "删除负载均衡",
|
"Delete Load Balancer": "删除负载均衡",
|
||||||
@ -778,6 +776,7 @@
|
|||||||
"Edit IPsec Site Connection": "编辑IPsec站点连接",
|
"Edit IPsec Site Connection": "编辑IPsec站点连接",
|
||||||
"Edit Image": "编辑镜像",
|
"Edit Image": "编辑镜像",
|
||||||
"Edit Instance": "编辑云主机",
|
"Edit Instance": "编辑云主机",
|
||||||
|
"Edit Instance Snapshot": "编辑云主机快照",
|
||||||
"Edit Listener": "编辑监听器",
|
"Edit Listener": "编辑监听器",
|
||||||
"Edit Load Balancer": "编辑负载均衡",
|
"Edit Load Balancer": "编辑负载均衡",
|
||||||
"Edit Member": "编辑成员",
|
"Edit Member": "编辑成员",
|
||||||
@ -1181,6 +1180,8 @@
|
|||||||
"Instance IP": "云主机IP",
|
"Instance IP": "云主机IP",
|
||||||
"Instance Info": "云主机信息",
|
"Instance Info": "云主机信息",
|
||||||
"Instance Name": "云主机名称",
|
"Instance Name": "云主机名称",
|
||||||
|
"Instance Snapshot": "云主机快照",
|
||||||
|
"Instance Snapshot Detail": "云主机快照详情",
|
||||||
"Instance Status": "云主机状态",
|
"Instance Status": "云主机状态",
|
||||||
"Instances": "云主机",
|
"Instances": "云主机",
|
||||||
"Instances \"{ name }\" are locked, can not delete them.": "云主机\"{ name }\"被锁定,无法删除。",
|
"Instances \"{ name }\" are locked, can not delete them.": "云主机\"{ name }\"被锁定,无法删除。",
|
||||||
@ -2067,6 +2068,7 @@
|
|||||||
"Snapshot Instance": "创建云主机快照",
|
"Snapshot Instance": "创建云主机快照",
|
||||||
"Snapshot Name": "快照名称",
|
"Snapshot Name": "快照名称",
|
||||||
"Snapshots": "快照",
|
"Snapshots": "快照",
|
||||||
|
"Snapshots can be converted into volume and used to create an instance from the volume.": "快照可以转换成云硬盘,用于从云硬盘启动云主机。",
|
||||||
"Snapshotting": "创建快照中",
|
"Snapshotting": "创建快照中",
|
||||||
"Soft Delete Instance": "软删除云主机",
|
"Soft Delete Instance": "软删除云主机",
|
||||||
"Soft Deleted": "软删除",
|
"Soft Deleted": "软删除",
|
||||||
@ -2615,6 +2617,7 @@
|
|||||||
"delete group": "删除组",
|
"delete group": "删除组",
|
||||||
"delete image": "删除镜像",
|
"delete image": "删除镜像",
|
||||||
"delete instance": "删除云主机",
|
"delete instance": "删除云主机",
|
||||||
|
"delete instance snapshot": "删除云主机快照",
|
||||||
"delete ipsec site connection": "删除IPsec站点连接",
|
"delete ipsec site connection": "删除IPsec站点连接",
|
||||||
"delete ironic instance": "删除裸机",
|
"delete ironic instance": "删除裸机",
|
||||||
"delete keypair": "删除密钥",
|
"delete keypair": "删除密钥",
|
||||||
@ -2652,6 +2655,7 @@
|
|||||||
"edit default pool": "编辑资源池",
|
"edit default pool": "编辑资源池",
|
||||||
"edit health monitor": "编辑健康检查器",
|
"edit health monitor": "编辑健康检查器",
|
||||||
"edit image": "编辑镜像",
|
"edit image": "编辑镜像",
|
||||||
|
"edit instance snapshot": "编辑云主机快照",
|
||||||
"edit member": "编辑成员",
|
"edit member": "编辑成员",
|
||||||
"edit system permission": "编辑系统角色",
|
"edit system permission": "编辑系统角色",
|
||||||
"egress": "出方向",
|
"egress": "出方向",
|
||||||
@ -2674,6 +2678,7 @@
|
|||||||
"insert": "插入",
|
"insert": "插入",
|
||||||
"instance": "云主机",
|
"instance": "云主机",
|
||||||
"instance snapshot": "云主机快照",
|
"instance snapshot": "云主机快照",
|
||||||
|
"instance snapshots": "云主机快照",
|
||||||
"instance: {name}.": "实例名称:{name}。",
|
"instance: {name}.": "实例名称:{name}。",
|
||||||
"instances": "云主机",
|
"instances": "云主机",
|
||||||
"ipsec site connection": "IPsec站点连接",
|
"ipsec site connection": "IPsec站点连接",
|
||||||
|
@ -24,8 +24,7 @@ import { isObject, isArray } from 'lodash';
|
|||||||
|
|
||||||
export class BaseDetail extends Base {
|
export class BaseDetail extends Base {
|
||||||
get isImageDetail() {
|
get isImageDetail() {
|
||||||
const { pathname } = this.props.location;
|
return this.path.includes('image');
|
||||||
return pathname.indexOf('image') >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get leftCards() {
|
get leftCards() {
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
import { inject, observer } from 'mobx-react';
|
import { inject, observer } from 'mobx-react';
|
||||||
import { imageStatus } from 'resources/glance/image';
|
import { imageStatus } from 'resources/glance/image';
|
||||||
import { ImageStore } from 'stores/glance/image';
|
import { ImageStore } from 'stores/glance/image';
|
||||||
|
import { InstanceSnapshotStore } from 'stores/glance/instance-snapshot';
|
||||||
|
import actionConfigsSnapshot from 'pages/compute/containers/InstanceSnapshot/actions';
|
||||||
import Base from 'containers/TabDetail';
|
import Base from 'containers/TabDetail';
|
||||||
import BaseDetail from './BaseDetail';
|
import BaseDetail from './BaseDetail';
|
||||||
import actionConfigs from '../actions';
|
import actionConfigs from '../actions';
|
||||||
@ -29,18 +31,25 @@ export class ImageDetail extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isImageDetail() {
|
get isImageDetail() {
|
||||||
const { pathname } = this.props.location;
|
return this.path.includes('image');
|
||||||
return pathname.indexOf('image') >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get listUrl() {
|
get listUrl() {
|
||||||
|
if (!this.isImageDetail) {
|
||||||
|
return this.getRoutePath('instanceSnapshot');
|
||||||
|
}
|
||||||
return this.getRoutePath('image');
|
return this.getRoutePath('image');
|
||||||
}
|
}
|
||||||
|
|
||||||
get actionConfigs() {
|
get actionConfigs() {
|
||||||
|
if (this.isImageDetail) {
|
||||||
|
return this.isAdminPage
|
||||||
|
? actionConfigs.actionConfigsAdmin
|
||||||
|
: actionConfigs.actionConfigs;
|
||||||
|
}
|
||||||
return this.isAdminPage
|
return this.isAdminPage
|
||||||
? actionConfigs.actionConfigsAdmin
|
? actionConfigsSnapshot.adminConfigs
|
||||||
: actionConfigs.actionConfigs;
|
: actionConfigsSnapshot.actionConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
get detailInfos() {
|
get detailInfos() {
|
||||||
@ -87,7 +96,9 @@ export class ImageDetail extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.store = new ImageStore();
|
this.store = this.isImageDetail
|
||||||
|
? new ImageStore()
|
||||||
|
: new InstanceSnapshotStore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export class Image extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isFilterByBackend() {
|
get isFilterByBackend() {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isSortByBackend() {
|
get isSortByBackend() {
|
||||||
@ -67,7 +67,7 @@ export class Image extends Base {
|
|||||||
return !this.isAdminPage;
|
return !this.isAdminPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFetchParamsByPage = (params) => {
|
updateFetchParams = (params) => {
|
||||||
if (this.isAdminPage) {
|
if (this.isAdminPage) {
|
||||||
return {
|
return {
|
||||||
...params,
|
...params,
|
||||||
|
@ -28,6 +28,7 @@ import { toJS } from 'mobx';
|
|||||||
import BaseDetail from './BaseDetail';
|
import BaseDetail from './BaseDetail';
|
||||||
import SecurityGroup from './SecurityGroup';
|
import SecurityGroup from './SecurityGroup';
|
||||||
import ActionLog from './ActionLog';
|
import ActionLog from './ActionLog';
|
||||||
|
import Snapshots from '../../InstanceSnapshot';
|
||||||
import actionConfigs from '../actions';
|
import actionConfigs from '../actions';
|
||||||
|
|
||||||
export class InstanceDetail extends Base {
|
export class InstanceDetail extends Base {
|
||||||
@ -44,8 +45,7 @@ export class InstanceDetail extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isRecycleBinDetail() {
|
get isRecycleBinDetail() {
|
||||||
const { pathname } = this.props.location;
|
return this.path.includes('recycle-bin');
|
||||||
return pathname.indexOf('recycle-bin') >= 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get listUrl() {
|
get listUrl() {
|
||||||
@ -123,6 +123,11 @@ export class InstanceDetail extends Base {
|
|||||||
key: 'BaseDetail',
|
key: 'BaseDetail',
|
||||||
component: BaseDetail,
|
component: BaseDetail,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('Instance Snapshot'),
|
||||||
|
key: 'snapshots',
|
||||||
|
component: Snapshots,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('Interface'),
|
title: t('Interface'),
|
||||||
key: 'interface',
|
key: 'interface',
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
// Copyright 2022 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 { inject, observer } from 'mobx-react';
|
||||||
|
import { toJS } from 'mobx';
|
||||||
|
import { ModalAction } from 'containers/Action';
|
||||||
|
import globalVolumeStore from 'stores/cinder/volume';
|
||||||
|
import { InstanceSnapshotStore } from 'stores/glance/instance-snapshot';
|
||||||
|
|
||||||
|
export class CreateVolume extends ModalAction {
|
||||||
|
static id = 'create';
|
||||||
|
|
||||||
|
static title = t('Create Volume');
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.volumeStore = globalVolumeStore;
|
||||||
|
this.snapshotStore = new InstanceSnapshotStore();
|
||||||
|
this.getVolumeTypes();
|
||||||
|
this.getMinSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('Create Volume');
|
||||||
|
}
|
||||||
|
|
||||||
|
get instanceName() {
|
||||||
|
return this.values.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static policy = 'volume:create_from_image';
|
||||||
|
|
||||||
|
static allowed = () => Promise.resolve(true);
|
||||||
|
|
||||||
|
async getVolumeTypes() {
|
||||||
|
const { id } = this.item;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const [_, snapshot] = await Promise.all([
|
||||||
|
this.volumeStore.fetchVolumeTypes(),
|
||||||
|
this.snapshotStore.fetchDetail({ id }),
|
||||||
|
]);
|
||||||
|
const { volumeDetail: { volume_type: volumeType } = {} } = snapshot;
|
||||||
|
const typeItem = this.volumeTypes.find((it) => it.label === volumeType);
|
||||||
|
if (typeItem) {
|
||||||
|
this.volumeType = typeItem.value;
|
||||||
|
}
|
||||||
|
this.updateFormValue('volume_type', this.volumeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMinSize() {
|
||||||
|
const { id } = this.item;
|
||||||
|
if (this.snapshot && this.snapshot.volume_size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.snapshotStore.fetchDetail({ id });
|
||||||
|
this.updateDefaultValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
get volumeTypes() {
|
||||||
|
return this.volumeStore.volumeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tips() {
|
||||||
|
return t(
|
||||||
|
'Snapshots can be converted into volume and used to create an instance from the volume.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultValue() {
|
||||||
|
const { name } = this.item;
|
||||||
|
const value = {
|
||||||
|
snapshot: name,
|
||||||
|
size: this.minSize,
|
||||||
|
volume_type: this.volumeType,
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get bdmData() {
|
||||||
|
const { block_device_mapping: bdm = '[]' } = this.item;
|
||||||
|
return JSON.parse(bdm);
|
||||||
|
}
|
||||||
|
|
||||||
|
get snapshot() {
|
||||||
|
return this.bdmData.find((it) => it.boot_index === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get minSize() {
|
||||||
|
const { min_disk, size } = this.item;
|
||||||
|
const biggerSize = Math.max(
|
||||||
|
min_disk,
|
||||||
|
Math.ceil(size / 1024 / 1024 / 1024),
|
||||||
|
1,
|
||||||
|
(this.snapshot || {}).volume_size || 1
|
||||||
|
);
|
||||||
|
if (biggerSize) {
|
||||||
|
return biggerSize;
|
||||||
|
}
|
||||||
|
const { snapshotDetail: { size: snapshotSize = 0 } = {} } =
|
||||||
|
toJS(this.snapshotStore.detail) || {};
|
||||||
|
return Math.max(snapshotSize, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
get formItems() {
|
||||||
|
const { more } = this.state;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'snapshot',
|
||||||
|
label: t('Snapshot'),
|
||||||
|
type: 'label',
|
||||||
|
iconType: 'snapshot',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: t('Name'),
|
||||||
|
type: 'input-name',
|
||||||
|
placeholder: t('Please input name'),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'size',
|
||||||
|
label: t('Capacity (GiB)'),
|
||||||
|
type: 'input-int',
|
||||||
|
min: this.minSize,
|
||||||
|
extra: `${t('Min size')}: ${this.minSize}GiB`,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'more',
|
||||||
|
type: 'more',
|
||||||
|
label: t('Advanced Options'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'volume_type',
|
||||||
|
label: t('Volume Type'),
|
||||||
|
type: 'select',
|
||||||
|
options: this.volumeTypes,
|
||||||
|
placeholder: t('Please select volume type'),
|
||||||
|
hidden: !more,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = ({ name, size, volume_type }) => {
|
||||||
|
const body = {
|
||||||
|
imageRef: this.item.id,
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
if (volume_type) {
|
||||||
|
body.volume_type = volume_type;
|
||||||
|
} else {
|
||||||
|
body.volume_type = this.volumeType;
|
||||||
|
}
|
||||||
|
return globalVolumeStore.create(body);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(CreateVolume));
|
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright 2022 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 { ConfirmAction } from 'containers/Action';
|
||||||
|
import globalImageStore from 'stores/glance/image';
|
||||||
|
|
||||||
|
export default class Delete extends ConfirmAction {
|
||||||
|
get id() {
|
||||||
|
return 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return t('Delete Instance Snapshot');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDanger() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get buttonText() {
|
||||||
|
return t('Delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionName() {
|
||||||
|
return t('delete instance snapshot');
|
||||||
|
}
|
||||||
|
|
||||||
|
policy = 'delete_image';
|
||||||
|
|
||||||
|
onSubmit = (data) => globalImageStore.delete({ id: data.id });
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2022 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 { inject, observer } from 'mobx-react';
|
||||||
|
import { ModalAction } from 'containers/Action';
|
||||||
|
import globalImageStore from 'stores/glance/image';
|
||||||
|
import { get, has } from 'lodash';
|
||||||
|
|
||||||
|
export class EditAction extends ModalAction {
|
||||||
|
static id = 'edit';
|
||||||
|
|
||||||
|
static title = t('Edit Instance Snapshot');
|
||||||
|
|
||||||
|
static buttonText = t('Edit');
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('edit instance snapshot');
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultValue() {
|
||||||
|
const { name, description } = this.item;
|
||||||
|
const value = {
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static policy = 'modify_image';
|
||||||
|
|
||||||
|
static allowed = () => Promise.resolve(true);
|
||||||
|
|
||||||
|
get formItems() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
label: t('Name'),
|
||||||
|
type: 'input-name',
|
||||||
|
placeholder: t('Please input name'),
|
||||||
|
isImage: true,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
label: t('Description'),
|
||||||
|
type: 'textarea',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = (values) => {
|
||||||
|
const { id } = this.item;
|
||||||
|
const changeValues = [];
|
||||||
|
Object.keys(values).forEach((key) => {
|
||||||
|
if (has(this.item, key) && get(this.item, key) !== values[key]) {
|
||||||
|
const item = {
|
||||||
|
op: 'replace',
|
||||||
|
path: `/${key}`,
|
||||||
|
value: values[key],
|
||||||
|
};
|
||||||
|
changeValues.push(item);
|
||||||
|
} else if (!has(this.item, key) && values[key]) {
|
||||||
|
const item = {
|
||||||
|
op: 'add',
|
||||||
|
path: `/${key}`,
|
||||||
|
value: values[key],
|
||||||
|
};
|
||||||
|
changeValues.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (changeValues.length === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return globalImageStore.update({ id }, changeValues);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(EditAction));
|
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2022 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 CreateVolume from './CreateVolume';
|
||||||
|
import Edit from './Edit';
|
||||||
|
import Delete from './Delete';
|
||||||
|
|
||||||
|
const actionConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: Edit,
|
||||||
|
moreActions: [
|
||||||
|
{
|
||||||
|
action: CreateVolume,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action: Delete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
batchActions: [Delete],
|
||||||
|
};
|
||||||
|
|
||||||
|
const adminConfigs = {
|
||||||
|
rowActions: {
|
||||||
|
firstAction: Edit,
|
||||||
|
moreActions: [
|
||||||
|
{
|
||||||
|
action: Delete,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
batchActions: [Delete],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { actionConfigs, adminConfigs };
|
109
src/pages/compute/containers/InstanceSnapshot/index.jsx
Normal file
109
src/pages/compute/containers/InstanceSnapshot/index.jsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2022 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 { observer, inject } from 'mobx-react';
|
||||||
|
import Base from 'containers/List';
|
||||||
|
import { transitionStatusList } from 'resources/glance/image';
|
||||||
|
import globalInstanceSnapshotStore, {
|
||||||
|
InstanceSnapshotStore,
|
||||||
|
} from 'stores/glance/instance-snapshot';
|
||||||
|
import { emptyActionConfig } from 'utils/constants';
|
||||||
|
import { getBaseSnapshotColumns } from 'resources/glance/instance-snapshot';
|
||||||
|
import actionConfigs from './actions';
|
||||||
|
|
||||||
|
export class Snapshots extends Base {
|
||||||
|
init() {
|
||||||
|
this.store = this.inDetailPage
|
||||||
|
? new InstanceSnapshotStore()
|
||||||
|
: globalInstanceSnapshotStore;
|
||||||
|
this.downloadStore = this.inDetailPage
|
||||||
|
? this.store
|
||||||
|
: new InstanceSnapshotStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
get policy() {
|
||||||
|
return 'get_images';
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return t('instance snapshots');
|
||||||
|
}
|
||||||
|
|
||||||
|
get isRecycleBinDetail() {
|
||||||
|
return this.path.includes('recycle-bin');
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionConfigs() {
|
||||||
|
if (this.isRecycleBinDetail) {
|
||||||
|
return emptyActionConfig;
|
||||||
|
}
|
||||||
|
return this.isAdminPage
|
||||||
|
? actionConfigs.adminConfigs
|
||||||
|
: actionConfigs.actionConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
get transitionStatusList() {
|
||||||
|
return transitionStatusList;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isFilterByBackend() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isSortByBackend() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultSortKey() {
|
||||||
|
return 'created_at';
|
||||||
|
}
|
||||||
|
|
||||||
|
get adminPageHasProjectFilter() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get projectFilterKey() {
|
||||||
|
return 'owner';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFetchParams = (params) => ({
|
||||||
|
...params,
|
||||||
|
owner: this.inDetailPage ? this.props.detail.tenant_id : null,
|
||||||
|
});
|
||||||
|
|
||||||
|
get currentProjectId() {
|
||||||
|
return this.props.detail.tenant_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns = () => getBaseSnapshotColumns(this);
|
||||||
|
|
||||||
|
get searchFilters() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: t('Name'),
|
||||||
|
name: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('Status'),
|
||||||
|
name: 'status',
|
||||||
|
options: [
|
||||||
|
{ label: t('Active'), key: 'active' },
|
||||||
|
{ label: t('Saving'), key: 'saving' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default inject('rootStore')(observer(Snapshots));
|
@ -24,6 +24,7 @@ import CreateIronic from '../containers/Instance/actions/CreateIronic';
|
|||||||
import TabImage from '../containers/Image';
|
import TabImage from '../containers/Image';
|
||||||
import ImageAdmin from '../containers/Image/Image';
|
import ImageAdmin from '../containers/Image/Image';
|
||||||
import ImageCreate from '../containers/Image/actions/Create';
|
import ImageCreate from '../containers/Image/actions/Create';
|
||||||
|
import InstanceSnapshot from '../containers/InstanceSnapshot';
|
||||||
import Keypair from '../containers/Keypair';
|
import Keypair from '../containers/Keypair';
|
||||||
import KeypairDetail from '../containers/Keypair/Detail';
|
import KeypairDetail from '../containers/Keypair/Detail';
|
||||||
import ServerGroup from '../containers/ServerGroup';
|
import ServerGroup from '../containers/ServerGroup';
|
||||||
@ -60,6 +61,26 @@ export default [
|
|||||||
component: CreateIronic,
|
component: CreateIronic,
|
||||||
exact: true,
|
exact: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance-snapshot`,
|
||||||
|
component: InstanceSnapshot,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance-snapshot-admin`,
|
||||||
|
component: InstanceSnapshot,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance-snapshot/detail/:id`,
|
||||||
|
component: ImageDetail,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `${PATH}/instance-snapshot-admin/detail/:id`,
|
||||||
|
component: ImageDetail,
|
||||||
|
exact: true,
|
||||||
|
},
|
||||||
{ path: `${PATH}/flavor`, component: Flavor, exact: true },
|
{ path: `${PATH}/flavor`, component: Flavor, exact: true },
|
||||||
{ path: `${PATH}/flavor-admin`, component: Flavor, exact: true },
|
{ path: `${PATH}/flavor-admin`, component: Flavor, exact: true },
|
||||||
{
|
{
|
||||||
|
@ -123,10 +123,12 @@ export const isOwner = (item) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isSnapshot = (item) => {
|
export const isSnapshot = (item) => {
|
||||||
const { block_device_mapping: bdm = '[]', image_type } = item;
|
// bfv vm has bdm; non-bfv has instance_uuid, image_type is added by frontend
|
||||||
|
const { block_device_mapping: bdm = '[]', image_type, instance_uuid } = item;
|
||||||
return (
|
return (
|
||||||
image_type === 'snapshot' ||
|
image_type === 'snapshot' ||
|
||||||
get(JSON.parse(bdm)[0] || {}, 'source_type') === 'snapshot'
|
get(JSON.parse(bdm)[0] || {}, 'source_type') === 'snapshot' ||
|
||||||
|
instance_uuid
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
53
src/resources/glance/instance-snapshot.js
Normal file
53
src/resources/glance/instance-snapshot.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2022 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 { imageStatus, imageFormats } from 'resources/glance/image';
|
||||||
|
|
||||||
|
export const getBaseSnapshotColumns = (self) => [
|
||||||
|
{
|
||||||
|
title: t('ID/Name'),
|
||||||
|
dataIndex: 'name',
|
||||||
|
routeName: self.getRouteName('instanceSnapshotDetail'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Project ID/Name'),
|
||||||
|
dataIndex: 'project_name',
|
||||||
|
isHideable: true,
|
||||||
|
hidden: !self.isAdminPage,
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Description'),
|
||||||
|
dataIndex: 'description',
|
||||||
|
isHideable: true,
|
||||||
|
sorter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Disk Format'),
|
||||||
|
dataIndex: 'disk_format',
|
||||||
|
isHideable: true,
|
||||||
|
render: (value) => imageFormats[value] || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Status'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
render: (value) => imageStatus[value] || '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('Created At'),
|
||||||
|
dataIndex: 'created_at',
|
||||||
|
isHideable: true,
|
||||||
|
valueRender: 'sinceTime',
|
||||||
|
},
|
||||||
|
];
|
@ -15,7 +15,7 @@
|
|||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
import client from 'client';
|
import client from 'client';
|
||||||
import Base from 'stores/base';
|
import Base from 'stores/base';
|
||||||
import { imageOS } from 'resources/glance/image';
|
import { imageOS, isSnapshot } from 'resources/glance/image';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
|
|
||||||
export class ImageStore extends Base {
|
export class ImageStore extends Base {
|
||||||
@ -41,12 +41,13 @@ export class ImageStore extends Base {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
updateParamsSort = this.updateParamsSortPage;
|
||||||
|
|
||||||
get paramsFuncPage() {
|
get paramsFuncPage() {
|
||||||
return (params) => {
|
return (params) => {
|
||||||
const { current, all_projects, ...rest } = params;
|
const { current, all_projects, ...rest } = params;
|
||||||
return {
|
return {
|
||||||
...rest,
|
...rest,
|
||||||
// image_type: 'image',
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -80,6 +81,13 @@ export class ImageStore extends Base {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listDidFetch(items) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
return items.filter((it) => !isSnapshot(it));
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async uploadImage(imageId, file, conf) {
|
async uploadImage(imageId, file, conf) {
|
||||||
return this.client.uploadFile(imageId, file, conf);
|
return this.client.uploadFile(imageId, file, conf);
|
||||||
|
158
src/stores/glance/instance-snapshot.js
Normal file
158
src/stores/glance/instance-snapshot.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2022 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 client from 'client';
|
||||||
|
import { isSnapshot } from 'src/resources/glance/image';
|
||||||
|
import Base from '../base';
|
||||||
|
|
||||||
|
export class InstanceSnapshotStore extends Base {
|
||||||
|
get client() {
|
||||||
|
return client.glance.images;
|
||||||
|
}
|
||||||
|
|
||||||
|
get listFilterByProject() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get fetchListByLimit() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateParamsSortPage = (params, sortKey, sortOrder) => {
|
||||||
|
if (sortKey && sortOrder) {
|
||||||
|
params.sort_key = sortKey;
|
||||||
|
params.sort_dir = sortOrder === 'descend' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateParamsSort = this.updateParamsSortPage;
|
||||||
|
|
||||||
|
get paramsFunc() {
|
||||||
|
return this.paramsFuncPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
get paramsFuncPage() {
|
||||||
|
return (params, all_projects) => {
|
||||||
|
const { id, current, owner, ...rest } = params;
|
||||||
|
const newParams = {
|
||||||
|
...rest,
|
||||||
|
};
|
||||||
|
if (owner) {
|
||||||
|
newParams.owner = owner;
|
||||||
|
} else if (!all_projects) {
|
||||||
|
newParams.owner = this.currentProjectId;
|
||||||
|
}
|
||||||
|
return newParams;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get mapperBeforeFetchProject() {
|
||||||
|
return (data) => ({
|
||||||
|
...data,
|
||||||
|
project_name: data.owner_project_name || data.project_name,
|
||||||
|
project_id: data.owner || data.project_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listDidFetch(items, allProjects, filters) {
|
||||||
|
if (items.length === 0) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
const newItems = items.filter(isSnapshot);
|
||||||
|
const { id } = filters;
|
||||||
|
if (!id) {
|
||||||
|
return newItems;
|
||||||
|
}
|
||||||
|
const volumeParams = {};
|
||||||
|
const snapshotParams = { all_tenants: allProjects };
|
||||||
|
const results = await Promise.all([
|
||||||
|
client.cinder.snapshots.list(snapshotParams),
|
||||||
|
client.nova.servers.volumeAttachments.list(id, volumeParams),
|
||||||
|
]);
|
||||||
|
const snapshotsAll = results[0].snapshots;
|
||||||
|
const volumesAll = results[1].volumeAttachments;
|
||||||
|
const data = [];
|
||||||
|
newItems.forEach((item) => {
|
||||||
|
const { block_device_mapping: bdm = '[]', instance_id } = item;
|
||||||
|
if (instance_id === id) {
|
||||||
|
data.push(item);
|
||||||
|
} else {
|
||||||
|
const snapshot = JSON.parse(bdm).find((it) => it.boot_index === 0);
|
||||||
|
if (snapshot) {
|
||||||
|
item.snapshotId = snapshot.snapshot_id;
|
||||||
|
const snapshotDetail = snapshotsAll.find(
|
||||||
|
(it) => it.id === snapshot.snapshot_id
|
||||||
|
);
|
||||||
|
if (snapshotDetail) {
|
||||||
|
const volumeId = snapshotDetail.volume_id;
|
||||||
|
const volume = volumesAll.find((it) => it.volumeId === volumeId);
|
||||||
|
if (volume) {
|
||||||
|
data.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { instance_uuid: instanceId } = item;
|
||||||
|
if (id === instanceId) {
|
||||||
|
data.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async detailDidFetch(item) {
|
||||||
|
item.originData = { ...item };
|
||||||
|
const { block_device_mapping: bdm = '[]' } = item;
|
||||||
|
const snapshot = JSON.parse(bdm).find((it) => it.boot_index === 0);
|
||||||
|
let instanceId = null;
|
||||||
|
let instanceName = '';
|
||||||
|
if (snapshot) {
|
||||||
|
const { snapshot_id: snapshotId } = snapshot;
|
||||||
|
item.snapshotId = snapshotId;
|
||||||
|
const snapshotResult = await client.cinder.snapshots.show(snapshotId);
|
||||||
|
const snapshotDetail = snapshotResult.snapshot;
|
||||||
|
item.snapshotDetail = snapshotDetail;
|
||||||
|
const { volume_id: volumeId } = snapshotDetail;
|
||||||
|
const volumeResult = await client.cinder.volumes.show(volumeId);
|
||||||
|
const volumeDetail = volumeResult.volume;
|
||||||
|
item.volumeDetail = volumeDetail;
|
||||||
|
instanceId =
|
||||||
|
volumeDetail.attachments.length > 0
|
||||||
|
? volumeDetail.attachments[0].server_id
|
||||||
|
: '';
|
||||||
|
} else {
|
||||||
|
// fix for not bfv instance
|
||||||
|
const { instance_uuid } = item;
|
||||||
|
instanceId = instance_uuid;
|
||||||
|
}
|
||||||
|
let instanceResult = {};
|
||||||
|
try {
|
||||||
|
if (instanceId) {
|
||||||
|
instanceResult = await client.nova.servers.show(instanceId);
|
||||||
|
const { server: { name } = {} } = instanceResult;
|
||||||
|
instanceName = name;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
item.instance = {
|
||||||
|
server_id: instanceId,
|
||||||
|
server_name: instanceName,
|
||||||
|
};
|
||||||
|
item.instanceDetail = instanceResult.server || {};
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalInstanceSnapshotStore = new InstanceSnapshotStore();
|
||||||
|
export default globalInstanceSnapshotStore;
|
@ -373,6 +373,8 @@ export class ServerStore extends Base {
|
|||||||
name: image,
|
name: image,
|
||||||
metadata: {
|
metadata: {
|
||||||
usage_type: 'common',
|
usage_type: 'common',
|
||||||
|
image_type: 'snapshot',
|
||||||
|
instance_id: id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user