blob: 94c06b4ce9c447703b41cf2357dbf8f70d76a2dc [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
<template>
<a-row :gutter="12" v-if="isPageAllowed">
<a-col :md="24">
<a-card class="breadcrumb-card">
<a-col :md="24" style="display: flex">
<breadcrumb style="padding-top: 6px; padding-left: 8px" />
<a-button
style="margin-left: 12px; margin-top: 4px"
:loading="viewLoading"
size="small"
shape="round"
@click="fetchData()" >
<template #icon><reload-outlined /></template>
{{ $t('label.refresh') }}
</a-button>
</a-col>
</a-card>
</a-col>
<a-col
:md="24">
<div>
<a-card>
<a-alert
type="info"
:showIcon="true"
:message="$t('label.desc.import.unmanage.volume')"
>
<template #description>
<span v-html="$t('message.desc.import.unmanage.volume')" />
</template>
</a-alert>
<br />
<a-row :gutter="12">
<!-- ------------ -->
<!-- TOP -->
<!-- ------------ -->
<a-card class="source-dest-card">
<template #title>
{{ $t('label.storagepool') }}
</template>
<a-col :md="24" :lg="48">
<a-form
style="min-width: 170px"
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
>
<a-form-item name="scope" ref="scope">
<template #label>
<tooltip-label :title="$t('label.scope')" :tooltip="$t('label.scope.tooltip')"/>
</template>
<a-select
v-model:value="this.poolscope"
@change="onSelectPoolScope"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option :value="'zone'" :label="$t('label.zoneid')"> {{ $t('label.zoneid') }} </a-select-option>
<a-select-option :value="'cluster'" :label="$t('label.clusterid')"> {{ $t('label.clusterid') }} </a-select-option>
<a-select-option :value="'host'" :label="$t('label.hostid')"> {{ $t('label.hostid') }} </a-select-option>
</a-select>
</a-form-item>
<a-form-item
name="zoneid"
ref="zoneid"
:label="$t('label.zoneid')"
>
<a-select
v-model:value="form.zoneid"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
@change="onSelectZoneId"
:loading="optionLoading.zones"
>
<a-select-option v-for="zoneitem in zoneSelectOptions" :key="zoneitem.value" :label="zoneitem.label">
<span>
<resource-icon v-if="zoneitem.icon" :image="zoneitem.icon" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px" />
{{ zoneitem.label }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
v-if="showPod"
name="podid"
ref="podid"
:label="$t('label.podid')">
<a-select
v-model:value="form.podid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="podSelectOptions"
:loading="optionLoading.pods"
@change="onSelectPodId"
></a-select>
</a-form-item>
<a-form-item
v-if="showCluster"
name="clusterid"
ref="clusterid"
:label="$t('label.clusterid')">
<a-select
v-model:value="form.clusterid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="clusterSelectOptions"
:loading="optionLoading.clusters"
@change="onSelectClusterId"
></a-select>
</a-form-item>
<a-form-item
v-if="showHost"
name="hostid"
ref="hostid">
<template #label>
<tooltip-label
:title="$t('label.hostname')"
:tooltip="$t('label.hostname.tooltip')"/>
</template>
<a-select
v-model:value="form.hostid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="hostSelectOptions"
:loading="optionLoading.hosts"
@change="onSelectHostId"
></a-select>
</a-form-item>
<a-form-item
name="poolid"
ref="poolid">
<template #label>
<tooltip-label
:title="$t('label.storagepool')"
:tooltip="$t('label.storagepool.tooltip')"/>
</template>
<a-select
v-model:value="form.poolid"
showSearch
optionFilterProp="label"
:filterOption="filterOption"
:options="poolSelectOptions"
:loading="optionLoading.pools"
@change="onSelectPoolId"
></a-select>
</a-form-item>
</a-form>
</a-col>
</a-card>
</a-row>
<a-row :gutter="12">
<a-col :md="24" :lg="12">
<a-card class="volumes-card">
<template #title>
{{ $t('label.unmanaged.volumes') }}
<a-tooltip :title="$t('message.volumes.unmanaged')">
<info-circle-outlined />
</a-tooltip>
<a-button
style="margin-left: 12px; margin-top: 4px"
:loading="unmanagedVolumesLoading"
size="small"
shape="round"
@click="fetchUnmanagedVolumes()" >
<template #icon><reload-outlined /></template>
</a-button>
<span style="float: right; width: 50%">
<search-view
:searchFilters="searchFilters.unmanaged"
:searchParams="searchParams.unmanaged"
:apiName="listVolumesApi.unmanaged"
@search="searchUnmanagedVolumes"
/>
</span>
</template>
<a-table
class="volumes-card-table"
:loading="unmanagedVolumesLoading"
:rowSelection="unmanagedVolumeSelection"
:rowKey="(record, index) => index"
:columns="unmanagedVolumesColumns"
:data-source="unmanagedVolumes"
:pagination="false"
size="middle"
:rowClassName="getRowClassName"
>
<template #bodyCell="{ column, text }">
<template v-if="column.key === 'state'">
<status :text="text ? text : ''" displayText />
</template>
</template>
</a-table>
<div class="volumes-card-footer">
<a-pagination
class="row-element"
size="small"
:current="page.unmanaged"
:pageSize="pageSize.unmanaged"
:total="itemCount.unmanaged"
:showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.unmanaged-1)*pageSize.unmanaged))}-${Math.min(page.unmanaged*pageSize.unmanaged, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
@change="fetchUnmanagedVolumes"
showQuickJumper>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
<div :span="24" class="action-button-right">
<a-button
:loading="importUnmanagedVolumeLoading"
:disabled="!(('importVolume' in $store.getters.apis) && unmanagedVolumesSelectedRowKeys.length > 0)"
type="primary"
@click="onImportVolumeAction">
<template #icon><import-outlined /></template>
{{ $t('label.import.volume') }}
</a-button>
</div>
</div>
</a-card>
</a-col>
<a-col :md="24" :lg="12">
<a-card class="volumes-card">
<template #title>
{{ $t('label.managed.volumes') }}
<a-tooltip :title="$t('message.volumes.managed')">
<info-circle-outlined />
</a-tooltip>
<a-button
style="margin-left: 12px; margin-top: 4px"
:loading="managedVolumesLoading"
size="small"
shape="round"
@click="fetchManagedVolumes()" >
<template #icon><reload-outlined /></template>
</a-button>
<span style="float: right; width: 50%">
<search-view
:searchFilters="searchFilters.managed"
:searchParams="searchParams.managed"
:apiName="listVolumesApi.managed"
@search="searchManagedVolumes"
/>
</span>
</template>
<a-table
class="volumes-card-table"
:loading="managedVolumesLoading"
:rowSelection="managedVolumeSelection"
:rowKey="(record, index) => index"
:columns="managedVolumesColumns"
:data-source="managedVolumes"
:pagination="false"
size="middle"
:rowClassName="getRowClassName"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'name'">
<router-link :to="{ path: '/volume/' + record.id }">{{ text }}</router-link>
</template>
<template v-if="column.key === 'state'">
<status :text="text ? text : ''" displayText />
</template>
</template>
</a-table>
<div class="volumes-card-footer">
<a-pagination
class="row-element"
size="small"
:current="page.managed"
:pageSize="pageSize.managed"
:total="itemCount.managed"
:showTotal="total => `${$t('label.showing')} ${Math.min(total, 1+((page.managed-1)*pageSize.managed))}-${Math.min(page.managed*pageSize.managed, total)} ${$t('label.of')} ${total} ${$t('label.items')}`"
@change="fetchManagedVolumes"
showQuickJumper>
<template #buildOptionText="props">
<span>{{ props.value }} / {{ $t('label.page') }}</span>
</template>
</a-pagination>
<div :span="24" class="action-button-right">
<a-button
:disabled="!(('unmanageVolume' in $store.getters.apis) && managedVolumesSelectedRowKeys.length > 0)"
type="primary"
@click="onUnmanageVolumeAction">
<template #icon><disconnect-outlined /></template>
{{ managedVolumesSelectedRowKeys.length > 1 ? $t('label.action.unmanage.volumes') : $t('label.action.unmanage.volume') }}
</a-button>
</div>
</div>
</a-card>
</a-col>
</a-row>
</a-card>
<a-modal
v-if="showImportForm"
:visible="showImportForm"
:title="$t('label.import.volume')"
:closable="true"
:maskClosable="false"
:footer="null"
:cancelText="$t('label.cancel')"
@cancel="onCloseImportVolumeForm"
centered
width="auto">
<a-alert type="warning" style="margin-bottom: 20px">
<template #message>
<label v-html="$t('message.import.volume')"></label>
</template>
</a-alert>
<a-form
:ref="importFormRef"
:model="importForm"
@finish="handleSubmitImportVolumeForm"
v-ctrl-enter="handleSubmitImportVolumeForm"
class="import-form"
>
<a-form-item
name="name"
ref="name"
:label="$t('label.name')">
<a-input v-model:value="importForm.name" />
</a-form-item>
<a-form-item
name="accounttype"
ref="accounttype"
:label="$t('label.accounttype')">
<a-select
@change="changeAccountType"
v-model:value="importForm.selectedAccountType"
v-focus="true"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}">
<a-select-option :value="null"></a-select-option>
<a-select-option :value="$t('label.account')">{{ $t('label.account') }}</a-select-option>
<a-select-option :value="$t('label.project')">{{ $t('label.project') }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item
v-if="importForm.selectedAccountType === $t('label.account') || importForm.selectedAccountType === $t('label.project')"
name="domain"
ref="domain"
:label="$t('label.domain')">
<a-select
@change="changeDomain"
v-model:value="importForm.selectedDomain"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="domain in domains" :key="domain.name" :value="domain.id" :label="domain.path || domain.name || domain.description">
<span>
<resource-icon v-if="domain && domain.icon" :image="domain.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ domain.path || domain.name || domain.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item
v-if="importForm.selectedAccountType === $t('label.account')"
name="account"
ref="account"
:label="$t('label.account')">
<a-select
@change="changeAccount"
v-model:value="importForm.selectedAccount"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="account in accounts" :key="account.name" :value="account.name">
<span>
<resource-icon v-if="account && account.icon" :image="account.icon.base64image" size="1x" style="margin-right: 5px"/>
<team-outlined v-else style="margin-right: 5px" />
{{ account.name }}
</span>
</a-select-option>
</a-select>
<span v-if="importForm.accountError" class="required">{{ $t('label.required') }}</span>
</a-form-item>
<a-form-item
v-if="importForm.selectedAccountType === $t('label.project')"
name="project"
ref="project"
:label="$t('label.project')">
<a-select
@change="changeProject"
v-model:value="importForm.selectedProject"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="project in projects" :key="project.id" :value="project.id" :label="project.name">
<span>
<resource-icon v-if="project && project.icon" :image="project.icon.base64image" size="1x" style="margin-right: 5px"/>
<project-outlined v-else style="margin-right: 5px" />
{{ project.name }}
</span>
</a-select-option>
</a-select>
<span v-if="importForm.projectError" class="required">{{ $t('label.required') }}</span>
</a-form-item>
<a-form-item
name="diskoffering"
ref="diskoffering"
:label="$t('label.diskoffering')">
<a-select
v-model:value="importForm.selectedDiskoffering"
:placeholder="$t('label.diskofferingid')"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option
v-for="(offering, index) in diskOfferings"
:value="offering.id"
:key="index"
:label="offering.displaytext || offering.name">
{{ offering.displaytext || offering.name }}
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button @click="onCloseImportVolumeForm">{{ $t('label.cancel') }}</a-button>
<a-button type="primary" ref="submit" @click="handleSubmitImportVolumeForm">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-modal>
</div>
</a-col>
</a-row>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import _ from 'lodash'
import Breadcrumb from '@/components/widgets/Breadcrumb'
import Status from '@/components/widgets/Status'
import SearchView from '@/components/view/SearchView'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel.vue'
export default {
components: {
TooltipLabel,
Breadcrumb,
Status,
SearchView,
ResourceIcon
},
name: 'ManageVolumes',
data () {
const unmanagedVolumesColumns = [
{
title: this.$t('label.filename'),
dataIndex: 'name',
width: 250
},
{
key: 'format',
title: this.$t('label.format'),
dataIndex: 'format'
},
{
title: this.$t('label.size'),
dataIndex: 'size'
},
{
title: this.$t('label.virtualsize'),
dataIndex: 'virtualsize'
}
]
const managedVolumesColumns = [
{
key: 'name',
title: this.$t('label.name'),
dataIndex: 'name',
width: 100
},
{
key: 'state',
title: this.$t('label.state'),
dataIndex: 'state'
},
{
title: this.$t('label.type'),
dataIndex: 'type'
},
{
title: this.$t('label.account'),
dataIndex: 'account'
},
{
title: this.$t('label.vmname'),
dataIndex: 'vmname'
}
]
return {
domains: [],
accounts: [],
projects: [],
diskOfferings: [],
options: {
zones: [],
pods: [],
clusters: [],
hosts: [],
pools: []
},
rowCount: {},
optionLoading: {
zones: false,
pods: false,
clusters: false,
hosts: false,
pools: false
},
page: {
unmanaged: 1,
managed: 1
},
pageSize: {
unmanaged: 10,
managed: 10
},
searchFilters: {
unmanaged: [],
managed: []
},
searchParams: {
unmanaged: {},
managed: {}
},
itemCount: {},
zone: {},
pod: {},
cluster: {},
values: undefined,
zoneId: undefined,
podId: undefined,
clusterId: undefined,
hostId: undefined,
poolId: undefined,
diskpath: undefined,
poolscope: 'zone',
listVolumesApi: {
unmanaged: 'listVolumesForImport',
managed: 'listVolumes'
},
unmanagedVolumesColumns,
unmanagedVolumesLoading: false,
unmanagedVolumes: [],
unmanagedVolumesSelectedRowKeys: [],
importUnmanagedVolumeLoading: false,
managedVolumesColumns,
managedVolumesLoading: false,
managedVolumes: [],
managedVolumesSelectedRowKeys: [],
showImportForm: false,
selectedUnmanagedVolume: null,
query: {}
}
},
created () {
this.page.unmanaged = parseInt(this.$route.query.unmanagedpage || 1)
this.page.managed = parseInt(this.$route.query.managedpage || 1)
this.initForm()
this.fetchData()
this.fetchDomains()
},
computed: {
isPageAllowed () {
if (this.$route.meta.permission) {
for (var apiName of this.$route.meta.permission) {
if (!(apiName in this.$store.getters.apis)) {
return false
}
}
}
return true
},
isUnmanaged () {
return this.selectedSourceAction === 'unmanaged'
},
showPod () {
return this.poolscope !== 'zone'
},
showCluster () {
return this.poolscope !== 'zone'
},
showHost () {
return this.poolscope === 'host'
},
params () {
return {
zones: {
list: 'listZones',
isLoad: true,
field: 'zoneid',
options: {
showicon: true
}
},
pods: {
list: 'listPods',
isLoad: false,
options: {
zoneid: _.get(this.zone, 'id')
},
field: 'podid'
},
clusters: {
list: 'listClusters',
isLoad: false,
options: {
zoneid: _.get(this.zone, 'id'),
podid: this.podId
},
field: 'clusterid'
},
hosts: {
list: 'listHosts',
isLoad: false,
options: {
zoneid: _.get(this.zone, 'id'),
podid: this.podId,
clusterid: this.clusterId
},
field: 'hostid'
},
pools: {
list: 'listStoragePools',
isLoad: false,
options: {
zoneid: _.get(this.zone, 'id'),
podid: this.podId,
clusterid: this.clusterId,
hostid: this.hostId,
scope: this.poolscope
},
field: 'poolid'
}
}
},
viewLoading () {
for (var key in this.optionLoading) {
if (this.optionLoading[key]) {
return true
}
}
return this.unmanagedVolumesLoading || this.managedVolumesLoading
},
zoneSelectOptions () {
return this.options.zones.map((zone) => {
return {
label: zone.name,
value: zone.id,
icon: zone?.icon?.base64image || ''
}
})
},
podSelectOptions () {
const options = this.options.pods.map((pod) => {
return {
label: pod.name,
value: pod.id
}
})
return options
},
clusterSelectOptions () {
const options = this.options.clusters.map((cluster) => {
return {
label: cluster.name,
value: cluster.id
}
})
return options
},
hostSelectOptions () {
const options = this.options.hosts.map((host) => {
return {
label: host.name,
value: host.id
}
})
return options
},
poolSelectOptions () {
const options = this.options.pools.map((pool) => {
return {
label: pool.name,
value: pool.id
}
})
return options
},
unmanagedVolumeSelection () {
return {
type: 'radio',
selectedRowKeys: this.unmanagedVolumesSelectedRowKeys || [],
onChange: this.onUnmanagedVolumeSelectRow
}
},
managedVolumeSelection () {
return {
type: 'checkbox',
selectedRowKeys: this.managedVolumesSelectedRowKeys || [],
onChange: this.onManagedVolumeSelectRow,
getCheckboxProps: (record) => {
return {
disabled: record.virtualmachineid !== undefined || record.state !== 'Ready' || record.hypervisor !== 'KVM'
}
}
}
},
selectedCluster () {
if (this.options.clusters &&
this.options.clusters.length > 0 &&
this.clusterId) {
return _.find(this.options.clusters, (option) => option.id === this.clusterId)
}
return {}
},
selectedHost () {
if (this.options.hosts &&
this.options.hosts.length > 0 &&
this.hostId) {
return _.find(this.options.hosts, (option) => option.id === this.hostId)
}
return {}
},
selectedPool () {
if (this.options.pools &&
this.options.pools.length > 0 &&
this.poolId) {
return _.find(this.options.pools, (option) => option.id === this.poolId)
}
return {}
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
})
this.rules = reactive({
})
this.importFormRef = ref()
this.importForm = reactive({
})
},
fetchData () {
this.unmanagedVolumes = []
this.managedVolumes = []
_.each(this.params, (param, name) => {
if (param.isLoad) {
this.fetchOptions(param, name)
}
})
},
filterOption (input, option) {
return (
option.label.toUpperCase().indexOf(input.toUpperCase()) >= 0
)
},
fetchOptions (param, name, exclude) {
if (exclude && exclude.length > 0) {
if (exclude.includes(name)) {
return
}
}
this.optionLoading[name] = true
param.loading = true
param.opts = []
const options = param.options || {}
if (!('listall' in options) && !['zones', 'pods', 'clusters', 'hosts', 'pools'].includes(name)) {
options.listall = true
}
api(param.list, options).then((response) => {
param.loading = false
_.map(response, (responseItem, responseKey) => {
if (Object.keys(responseItem).length === 0) {
this.rowCount[name] = 0
this.options[name] = []
return
}
if (!responseKey.includes('response')) {
return
}
_.map(responseItem, (response, key) => {
if (key === 'count') {
this.rowCount[name] = response
return
}
if (name === 'clusters') {
response = response.filter(cluster => ['KVM'].includes(cluster.hypervisortype))
}
param.opts = response
this.options[name] = response
})
this.handleFetchOptionsSuccess(name, param)
})
}).catch(function (error) {
console.log(error.stack)
param.loading = false
}).finally(() => {
this.optionLoading[name] = false
})
},
getRowClassName (record, index) {
if (index % 2 === 0) {
return 'light-row'
}
return 'dark-row'
},
handleFetchOptionsSuccess (name, param) {
if (['zones', 'pods', 'clusters', 'hosts', 'pools'].includes(name)) {
let paramid = ''
const query = Object.assign({}, this.$route.query)
if (query[param.field] && _.find(this.options[name], (option) => option.id === query[param.field])) {
paramid = query[param.field]
}
if (!paramid && this.options[name].length > 0) {
paramid = (this.options[name])[0].id
}
if (paramid) {
this.form[param.field] = paramid
if (name === 'zones') {
this.onSelectZoneId(paramid)
} else if (name === 'pods') {
this.form.podid = paramid
this.onSelectPodId(paramid)
} else if (name === 'clusters') {
this.form.clusterid = paramid
this.onSelectClusterId(paramid)
} else if (name === 'hosts') {
this.form.hostid = paramid
this.onSelectHostId(paramid)
} else if (name === 'pools') {
this.form.poolid = paramid
this.onSelectPoolId(paramid)
}
}
}
},
updateQuery (field, value) {
const query = Object.assign({}, this.$route.query)
if (query[field] === value + '') {
return
}
query[field] = value
if (['zoneid', 'podid', 'clusterid', 'hostid', 'poolid'].includes(field)) {
query.managedpage = 1
query.unmanagedpage = 1
this.searchParams.managed.keyword = null
this.searchParams.unmanaged.keyword = null
}
this.$router.push({ query })
},
resetLists () {
this.page.unmanaged = 1
this.unmanagedVolumes = []
this.unmanagedVolumesSelectedRowKeys = []
this.page.managed = 1
this.managedVolumes = []
this.managedVolumesSelectedRowKeys = []
},
onSelectZoneId (value) {
this.zoneId = value
this.podId = null
this.clusterId = null
this.hostId = null
this.poolId = null
this.zone = _.find(this.options.zones, (option) => option.id === value)
this.resetLists()
this.form.clusterid = undefined
this.form.podid = undefined
this.form.hostid = undefined
this.form.poolid = undefined
this.updateQuery('zoneid', value)
this.fetchOptions(this.params.pods, 'pods')
},
onSelectPodId (value) {
this.podId = value
this.pod = _.find(this.options.pods, (option) => option.id === value)
this.resetLists()
this.clusterId = null
this.form.clusterid = undefined
this.hostId = null
this.poolId = null
this.form.hostid = undefined
this.form.poolid = undefined
this.updateQuery('podid', value)
this.fetchOptions(this.params.clusters, 'clusters', value)
},
onSelectClusterId (value) {
this.clusterId = value
this.cluster = _.find(this.options.clusters, (option) => option.id === value)
this.resetLists()
this.hostId = null
this.poolId = null
this.form.hostid = undefined
this.form.poolid = undefined
this.updateQuery('clusterid', value)
if (this.showHost) {
this.fetchOptions(this.params.hosts, 'hosts', value)
} else {
this.fetchOptions(this.params.pools, 'pools', value)
}
},
onSelectHostId (value) {
this.hostId = value
this.updateQuery('scope', 'local')
this.updateQuery('hostid', value)
this.fetchOptions(this.params.pools, 'pools', value)
},
onSelectPoolId (value) {
this.poolId = value
this.updateQuery('poolid', value)
this.fetchVolumes()
},
onSelectPoolScope (value) {
this.poolscope = value
this.zoneId = null
this.podId = null
this.clusterId = null
this.poolId = null
this.updateQuery('scope', value)
this.fetchOptions(this.params.zones, 'zones', value)
},
fetchDomains () {
api('listDomains', {
response: 'json',
listAll: true,
showicon: true,
details: 'min'
}).then(response => {
this.domains = response.listdomainsresponse.domain || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
fetchAccounts () {
this.loading = true
api('listAccounts', {
response: 'json',
domainId: this.importForm.selectedDomain,
showicon: true,
state: 'Enabled',
isrecursive: false
}).then(response => {
this.accounts = response.listaccountsresponse.account || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
fetchProjects () {
this.loading = true
api('listProjects', {
response: 'json',
domainId: this.importForm.selectedDomain,
state: 'Active',
showicon: true,
details: 'min',
isrecursive: false
}).then(response => {
this.projects = response.listprojectsresponse.project || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
},
changeAccountType () {
this.importForm.selectedDomain = null
this.importForm.selectedAccount = null
this.importForm.selectedProject = null
this.importForm.selectedDiskoffering = null
this.diskOfferings = {}
},
changeDomain () {
this.importForm.selectedAccount = null
this.importForm.selectedProject = null
this.importForm.selectedDiskoffering = null
this.diskOfferings = {}
this.fetchAccounts()
this.fetchProjects()
},
changeAccount () {
this.importForm.selectedProject = null
this.importForm.selectedDiskoffering = null
this.diskOfferings = {}
this.fetchDiskOfferings()
},
changeProject () {
this.importForm.selectedAccount = null
this.importForm.selectedDiskoffering = null
this.diskOfferings = {}
this.fetchDiskOfferings()
},
fetchDiskOfferings () {
this.loading = true
const selectedPool = this.options.pools.filter(pool => pool.id === this.poolId)
const storagetype = selectedPool[0].scope === 'HOST' ? 'local' : 'shared'
var params = {
zoneid: this.zoneId,
storageid: this.poolId,
storagetype: storagetype,
encrypt: false,
listall: true
}
if (this.importForm.selectedAccountType === 'Account') {
params.domainid = this.importForm.selectedDomain
params.account = this.importForm.selectedAccount
} else if (this.importForm.selectedAccountType === 'Project') {
params.domainid = this.importForm.selectedDomain
params.projectid = this.importForm.selectedProject
}
api('listDiskOfferings', params).then(json => {
this.diskOfferings = json.listdiskofferingsresponse.diskoffering || []
}).finally(() => {
this.loading = false
})
},
fetchVolumes () {
this.fetchUnmanagedVolumes()
this.fetchManagedVolumes()
},
fetchUnmanagedVolumes (page, pageSize) {
const params = {
storageid: this.poolId
}
const query = Object.assign({}, this.$route.query)
this.page.unmanaged = page || parseInt(query.unmanagedpage) || this.page.unmanaged
this.updateQuery('unmanagedpage', this.page.unmanaged)
params.page = this.page.unmanaged
this.pageSize.unmanaged = pageSize || this.pageSize.unmanaged
params.pagesize = this.pageSize.unmanaged
this.unmanagedVolumes = []
this.unmanagedVolumesSelectedRowKeys = []
if (this.searchParams.unmanaged.keyword) {
params.keyword = this.searchParams.unmanaged.keyword
}
if (!this.poolId) {
return
}
this.unmanagedVolumesLoading = true
this.searchParams.unmanaged = params
const apiName = this.listVolumesApi.unmanaged
api(apiName, params).then(json => {
const response = json.listvolumesforimportresponse
const listUnmanagedVolumes = response.volumeforimport
if (this.arrayHasItems(listUnmanagedVolumes)) {
for (let index = (this.page.unmanaged - 1) * this.pageSize.unmanaged; index < this.page.unmanaged * this.pageSize.unmanaged - 1; index++) {
if (listUnmanagedVolumes[index]) {
this.unmanagedVolumes.push(listUnmanagedVolumes[index])
}
}
}
this.itemCount.unmanaged = response.count
}).finally(() => {
this.unmanagedVolumesLoading = false
})
},
searchUnmanagedVolumes (params) {
this.searchParams.unmanaged.keyword = params.searchQuery
this.fetchUnmanagedVolumes()
},
fetchManagedVolumes (page, pageSize) {
const params = {
listall: true,
projectId: -1,
storageid: this.poolId
}
const query = Object.assign({}, this.$route.query)
this.page.managed = page || parseInt(query.managedpage) || this.page.managed
this.updateQuery('managedpage', this.page.managed)
params.page = this.page.managed
this.pageSize.managed = pageSize || this.pageSize.managed
params.pagesize = this.pageSize.managed
this.managedVolumes = []
this.managedVolumesSelectedRowKeys = []
if (this.searchParams.managed.keyword) {
params.keyword = this.searchParams.managed.keyword
}
if (!this.poolId) {
return
}
this.managedVolumesLoading = true
this.searchParams.managed = params
api(this.listVolumesApi.managed, params).then(json => {
const response = json.listvolumesresponse
const listManagedVolumes = response.volume
if (this.arrayHasItems(listManagedVolumes)) {
this.managedVolumes = this.managedVolumes.concat(listManagedVolumes)
}
this.itemCount.managed = response.count
}).finally(() => {
this.managedVolumesLoading = false
})
},
searchManagedVolumes (params) {
this.searchParams.managed.keyword = params.searchQuery
this.fetchManagedVolumes()
},
onUnmanagedVolumeSelectRow (value) {
this.unmanagedVolumesSelectedRowKeys = value
},
onManagedVolumeSelectRow (value) {
this.managedVolumesSelectedRowKeys = value
},
isValidValueForKey (obj, key) {
return key in obj && obj[key] != null
},
arrayHasItems (array) {
return array !== null && array !== undefined && Array.isArray(array) && array.length > 0
},
isObjectEmpty (obj) {
return !(obj !== null && obj !== undefined && Object.keys(obj).length > 0 && obj.constructor === Object)
},
updateManageVolumeActionLoading (value) {
this.importUnmanagedVolumeLoading = value
if (!value) {
this.fetchVolumes()
}
},
onImportVolumeAction () {
this.selectedUnmanagedVolume = null
if (this.unmanagedVolumes.length > 0 &&
this.unmanagedVolumesSelectedRowKeys.length > 0) {
this.selectedUnmanagedVolume = this.unmanagedVolumes[this.unmanagedVolumesSelectedRowKeys[0]]
this.importForm.name = this.selectedUnmanagedVolume.name
}
this.fetchDiskOfferings()
this.showImportForm = true
},
handleSubmitImportVolumeForm () {
if (this.selectedUnmanagedVolume === null) {
return
}
this.values = toRaw(this.importForm)
const volumeName = this.selectedUnmanagedVolume.name
let variableKey = ''
let variableValue = ''
if (this.values.selectedAccountType === 'Account') {
if (!this.values.selectedAccount) {
this.importForm.accountError = true
return
}
variableKey = 'account'
variableValue = this.values.selectedAccount
} else if (this.values.selectedAccountType === 'Project') {
if (!this.values.selectedProject) {
this.importForm.projectError = true
return
}
variableKey = 'projectid'
variableValue = this.values.selectedProject
}
var params = {
diskofferingid: this.importForm.selectedDiskoffering,
domainid: this.importForm.selectedDomain,
[variableKey]: variableValue,
storageid: this.poolId,
path: this.selectedUnmanagedVolume.path,
name: this.values.name
}
api('importVolume', params).then(json => {
const jobId = json.importvolumeresponse.jobid
this.$pollJob({
jobId,
title: this.$t('label.import.volume'),
description: volumeName,
loadingMessage: `${this.$t('label.import.volume')} ${volumeName} ${this.$t('label.in.progress')}`,
catchMessage: this.$t('error.fetching.async.job.result'),
successMessage: this.$t('message.success.import.volume') + ' ' + volumeName,
successMethod: result => {
this.fetchVolumes()
}
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
this.selectedUnmanagedVolume = null
this.onCloseImportVolumeForm()
},
onCloseImportVolumeForm () {
this.showImportForm = false
this.importForm.selectedAccountType = null
this.importForm.selectedDomain = null
this.importForm.selectedAccount = null
this.importForm.selectedProject = null
},
onUnmanageVolumeAction () {
const self = this
const title = this.managedVolumesSelectedRowKeys.length > 1
? this.$t('message.action.unmanage.volumes')
: this.$t('message.action.unmanage.volume')
var volumeNames = []
for (var index of this.managedVolumesSelectedRowKeys) {
volumeNames.push(this.managedVolumes[index].name)
}
const content = volumeNames.join(', ')
this.$confirm({
title: title,
okText: this.$t('label.ok'),
okType: 'danger',
content: content,
cancelText: this.$t('label.cancel'),
onOk () {
self.unmanageVolumes()
}
})
},
unmanageVolumes () {
for (var index of this.managedVolumesSelectedRowKeys) {
const vm = this.managedVolumes[index]
var params = { id: vm.id }
api('unmanageVolume', params).then(json => {
const jobId = json.unmanagevolumeresponse.jobid
this.$pollJob({
jobId,
title: this.$t('label.unmanage.volume'),
description: vm.name,
loadingMessage: `${this.$t('label.unmanage.volume')} ${vm.name} ${this.$t('label.in.progress')}`,
catchMessage: this.$t('error.fetching.async.job.result'),
successMessage: this.$t('message.success.unmanage.volume') + ' ' + vm.name,
successMethod: result => {
if (index === this.managedVolumesSelectedRowKeys[this.managedVolumesSelectedRowKeys.length - 1]) {
this.fetchVolumes()
}
}
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}
}
}
}
</script>
<style scoped lang="less">
:deep(.ant-table-small) > .ant-table-content > .ant-table-body {
margin: 0;
}
.import-form {
width: 85vw;
@media (min-width: 760px) {
width: 500px;
}
display: flex;
flex-direction: column;
&__item {
display: flex;
flex-direction: column;
width: 100%;
margin-bottom: 10px;
}
&__label {
display: flex;
font-weight: bold;
margin-bottom: 5px;
}
.required {
margin-right: 2px;
color: red;
font-weight: bold;
}
}
.volumes-card {
height: 100%;
}
.source-dest-card {
width: 50%;
height: 100%;
}
.volumes-card-table {
overflow-y: auto;
margin-bottom: 100px;
}
.volumes-card-footer {
height: 100px;
position: absolute;
bottom: 0;
left: 0;
margin-left: 10px;
right: 0;
margin-right: 10px;
}
.row-element {
margin-top: 10px;
margin-bottom: 10px;
}
.action-button-left {
text-align: left;
}
.action-button-right {
text-align: right;
}
.fetch-volumes-column {
width: 50%;
margin-left: 50%;
padding-left: 24px;
}
.breadcrumb-card {
margin-left: -24px;
margin-right: -24px;
margin-top: -16px;
margin-bottom: 12px;
}
.ant-breadcrumb {
vertical-align: text-bottom;
}
</style>