| // 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')"> | |
| <infinite-scroll-select | |
| v-model:value="importForm.selectedDomain" | |
| api="listDomains" | |
| :apiParams="domainsApiParams" | |
| resourceType="domain" | |
| optionValueKey="id" | |
| optionLabelKey="path" | |
| defaultIcon="block-outlined" | |
| allowClear="true" | |
| @change-option-value="changeDomain" /> | |
| </a-form-item> | |
| <a-form-item | |
| v-if="importForm.selectedAccountType === $t('label.account')" | |
| name="account" | |
| ref="account" | |
| :label="$t('label.account')"> | |
| <infinite-scroll-select | |
| v-model:value="importForm.selectedAccount" | |
| api="listAccounts" | |
| :apiParams="accountsApiParams" | |
| resourceType="account" | |
| optionValueKey="name" | |
| optionLabelKey="name" | |
| defaultIcon="team-outlined" | |
| allowClear="true" | |
| @change-option-value="changeAccount" /> | |
| <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')"> | |
| <infinite-scroll-select | |
| v-model:value="importForm.selectedProject" | |
| api="listProjects" | |
| :apiParams="projectsApiParams" | |
| resourceType="project" | |
| optionValueKey="id" | |
| optionLabelKey="name" | |
| defaultIcon="project-outlined" | |
| allowClear="true" | |
| @change-option-value="changeProject" /> | |
| <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 { getAPI, postAPI } 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' | |
| import InfiniteScrollSelect from '@/components/widgets/InfiniteScrollSelect.vue' | |
| export default { | |
| components: { | |
| TooltipLabel, | |
| Breadcrumb, | |
| Status, | |
| SearchView, | |
| ResourceIcon, | |
| InfiniteScrollSelect | |
| }, | |
| 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() | |
| }, | |
| 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' | |
| }, | |
| domainsApiParams () { | |
| return { | |
| listall: true, | |
| details: 'min', | |
| showicon: true | |
| } | |
| }, | |
| accountsApiParams () { | |
| if (!this.importForm.selectedDomain) { | |
| return null | |
| } | |
| return { | |
| domainid: this.importForm.selectedDomain, | |
| showicon: true, | |
| state: 'Enabled', | |
| isrecursive: false | |
| } | |
| }, | |
| projectsApiParams () { | |
| if (!this.importForm.selectedDomain) { | |
| return null | |
| } | |
| return { | |
| domainid: this.importForm.selectedDomain, | |
| state: 'Active', | |
| showicon: true, | |
| details: 'min', | |
| isrecursive: false | |
| } | |
| }, | |
| 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 | |
| } | |
| getAPI(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) | |
| }, | |
| 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 = {} | |
| // InfiniteScrollSelect will auto-reload when apiParams changes | |
| }, | |
| 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 | |
| } | |
| getAPI('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 | |
| getAPI(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 | |
| getAPI(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 | |
| } | |
| postAPI('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 } | |
| postAPI('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> |