| // 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> | |
| <div> | |
| <a-card class="breadcrumb-card"> | |
| <a-row> | |
| <a-col :span="24" style="padding-left: 12px"> | |
| <a-breadcrumb :routes="getRoutes()"> | |
| <template #itemRender="{ route }"> | |
| <span v-if="['/', ''].includes(route.path) && route.breadcrumbName === 'root'"> | |
| <a @click="openDir('')"> | |
| <home-outlined/> | |
| </a> | |
| </span> | |
| <span v-else> | |
| <a @click="openDir(route.path)"> | |
| {{ route.breadcrumbName }} | |
| </a> | |
| </span> | |
| </template> | |
| </a-breadcrumb> | |
| </a-col> | |
| <a-col> | |
| <a-tooltip placement="bottom"> | |
| <template #title>{{ $t('label.refresh') }}</template> | |
| <a-button | |
| style="margin-top: 4px" | |
| :loading="loading" | |
| shape="round" | |
| size="small" | |
| @click="fetchData()" | |
| > | |
| <template #icon><ReloadOutlined /></template> | |
| {{ $t('label.refresh') }} | |
| </a-button> | |
| </a-tooltip> | |
| </a-col> | |
| </a-row> | |
| </a-card> | |
| <a-modal | |
| :title="$t('message.data.migration')" | |
| :visible="showMigrateModal" | |
| :maskClosable="true" | |
| :confirmLoading="migrateModalLoading" | |
| @cancel="showMigrateModal = false" | |
| :footer="null" | |
| width="50%" | |
| :okText="$t('label.ok')" | |
| :cancelText="$t('label.cancel')"> | |
| <div> | |
| <migrate-image-store-resource | |
| :sourceImageStore="resource" | |
| :templateIdsToMigrate="templateIdsToMigrate" | |
| :snapshotIdsToMigrate="snapshotIdsToMigrate" | |
| @close-action="showMigrateModal = false" | |
| /> | |
| </div> | |
| </a-modal> | |
| <div> | |
| <a-table | |
| :columns="columns" | |
| :row-key="record => record.name" | |
| :data-source="dataSource" | |
| :pagination="{ current: page, pageSize: pageSize, total: total }" | |
| @change="handleTableChange"> | |
| <template #bodyCell="{ column, record }"> | |
| <template v-if="column.key == 'name'"> | |
| <template v-if="record.isdirectory"> | |
| <a @click="openDir(`${this.browserPath}${record.name}/`)"> | |
| <folder-outlined /> {{ record.name }} | |
| </a> | |
| </template> | |
| <template v-else-if="resourceType === 'ImageStore'"> | |
| <a @click="downloadFile(record)"> | |
| <template v-if="record.snapshotid"> | |
| <build-outlined/> | |
| </template> | |
| <template v-else-if="record.volumeid"> | |
| <hdd-outlined/> | |
| </template> | |
| <template v-else-if="record.templateid"> | |
| <usb-outlined v-if="record.format === 'ISO'"/> | |
| <save-outlined v-else /> | |
| </template> | |
| {{ record.name }} | |
| </a> | |
| </template> | |
| <template v-else> | |
| <template v-if="record.snapshotid"> | |
| <build-outlined/> | |
| </template> | |
| <template v-else-if="record.volumeid"> | |
| <hdd-outlined/> | |
| </template> | |
| <template v-else-if="record.templateid"> | |
| <usb-outlined v-if="record.format === 'ISO'"/> | |
| <save-outlined v-else /> | |
| </template> | |
| {{ record.name }} | |
| </template> | |
| </template> | |
| <template v-if="column.key == 'size'"> | |
| <template v-if="!record.isdirectory"> | |
| {{ convertBytes(record.size) }} | |
| </template> | |
| </template> | |
| <template v-if="column.key == 'lastupdated'"> | |
| {{ $toLocaleDate(record.lastupdated) }} | |
| </template> | |
| <template v-if="column.key == 'associatedResource'"> | |
| <template v-if="record.snapshotid"> | |
| <router-link :to="{ path: '/snapshot/' + record.snapshotid }" target='_blank' > | |
| {{ record.snapshotname }} | |
| </router-link> | |
| </template> | |
| <template v-else-if="record.volumeid"> | |
| <router-link :to="{ path: '/volume/' + record.volumeid }" target='_blank' > | |
| {{ record.volumename }} | |
| </router-link> | |
| </template> | |
| <template v-else-if="record.templateid"> | |
| <router-link v-if="record.format === 'ISO'" :to="{ path: '/iso/' + record.templateid }" target='_blank' > | |
| {{ record.templatename }} | |
| </router-link> | |
| <router-link v-else :to="{ path: '/template/' + record.templateid }" target='_blank'> | |
| {{ record.templatename }} | |
| </router-link> | |
| </template> | |
| <template v-else> | |
| {{ $t('label.unknown') }} | |
| </template> | |
| </template> | |
| <template v-else-if="column.key === 'actions' && (record.templateid || record.snapshotid)"> | |
| <tooltip-button | |
| tooltipPlacement="top" | |
| :tooltip="$t('label.migrate.data.from.image.store')" | |
| icon="arrows-alt-outlined" | |
| :copyResource="String(resource.id)" | |
| @onClick="openMigrationModal(record)" /> | |
| </template> | |
| </template> | |
| </a-table> | |
| </div> | |
| </div> | |
| </template> | |
| <script> | |
| import { getAPI, postAPI } from '@/api' | |
| import InfoCard from '@/components/view/InfoCard' | |
| import TooltipButton from '@/components/widgets/TooltipButton' | |
| import MigrateImageStoreResource from '@/views/storage/MigrateImageStoreResource' | |
| export default { | |
| name: 'StorageBrowser', | |
| components: { | |
| InfoCard, | |
| MigrateImageStoreResource, | |
| TooltipButton | |
| }, | |
| props: { | |
| resource: { | |
| type: Object, | |
| required: true | |
| }, | |
| resourceType: { | |
| type: String, | |
| required: true | |
| } | |
| }, | |
| data () { | |
| var columns = [ | |
| { | |
| key: 'name', | |
| title: this.$t('label.name') | |
| }, | |
| { | |
| key: 'size', | |
| title: this.$t('label.size') | |
| }, | |
| { | |
| key: 'lastupdated', | |
| title: this.$t('label.last.updated') | |
| }, | |
| { | |
| key: 'associatedResource', | |
| title: this.$t('label.associated.resource') | |
| } | |
| ] | |
| if (this.resourceType === 'ImageStore') { | |
| columns.push({ | |
| key: 'actions', | |
| title: this.$t('label.actions') | |
| }) | |
| } | |
| return { | |
| loading: false, | |
| dataSource: [], | |
| browserPath: this.$route.query.browserPath || '', | |
| page: parseInt(this.$route.query.browserPage) || 1, | |
| pageSize: parseInt(this.$route.query.browserPageSize) || 10, | |
| total: 0, | |
| columns: columns, | |
| migrateModalLoading: false, | |
| showMigrateModal: false, | |
| templateIdsToMigrate: [], | |
| snapshotIdsToMigrate: [] | |
| } | |
| }, | |
| created () { | |
| this.fetchData() | |
| }, | |
| methods: { | |
| openMigrationModal (record) { | |
| if (record.snapshotid) { | |
| this.snapshotIdsToMigrate.push(record.snapshotid) | |
| } else if (record.templateid) { | |
| this.templateIdsToMigrate.push(record.templateid) | |
| } | |
| this.showMigrateModal = true | |
| }, | |
| handleTableChange (pagination, filters, sorter) { | |
| this.page = pagination.current | |
| this.pageSize = pagination.pageSize | |
| this.fetchData() | |
| }, | |
| fetchImageStoreObjects () { | |
| this.loading = true | |
| getAPI('listImageStoreObjects', { | |
| path: this.browserPath, | |
| id: this.resource.id, | |
| page: this.page, | |
| pagesize: this.pageSize | |
| }).then(json => { | |
| this.dataSource = json.listimagestoreobjectsresponse.datastoreobject | |
| this.total = json.listimagestoreobjectsresponse.count | |
| }).finally(() => { | |
| this.loading = false | |
| }) | |
| }, | |
| fetchPrimaryStoreObjects () { | |
| this.loading = true | |
| getAPI('listStoragePoolObjects', { | |
| path: this.browserPath, | |
| id: this.resource.id, | |
| page: this.page, | |
| pagesize: this.pageSize | |
| }).then(json => { | |
| this.dataSource = json.liststoragepoolobjectsresponse.datastoreobject | |
| this.total = json.liststoragepoolobjectsresponse.count | |
| }).finally(() => { | |
| this.loading = false | |
| }) | |
| }, | |
| fetchData () { | |
| this.dataSource = [] | |
| this.$router.replace( | |
| { | |
| path: this.$route.path, | |
| query: { | |
| ...this.$route.query, | |
| browserPath: this.browserPath, | |
| browserPage: this.page, | |
| browserPageSize: this.browserPageSize | |
| } | |
| } | |
| ) | |
| if (this.resourceType === 'ImageStore') { | |
| this.fetchImageStoreObjects() | |
| } else if (this.resourceType === 'PrimaryStorage') { | |
| this.fetchPrimaryStoreObjects() | |
| } | |
| }, | |
| getRoutes () { | |
| let path = '' | |
| const routeList = [{ | |
| path: path, | |
| breadcrumbName: 'root' | |
| }] | |
| for (const route of this.browserPath.split('/')) { | |
| if (route) { | |
| path = `${path}${route}/` | |
| routeList.push({ | |
| path: path, | |
| breadcrumbName: route | |
| }) | |
| } | |
| } | |
| return routeList | |
| }, | |
| convertBytes (val) { | |
| if (val < 1024 * 1024) return `${(val / 1024).toFixed(2)} KB` | |
| if (val < 1024 * 1024 * 1024) return `${(val / 1024 / 1024).toFixed(2)} MB` | |
| if (val < 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024).toFixed(2)} GB` | |
| if (val < 1024 * 1024 * 1024 * 1024 * 1024) return `${(val / 1024 / 1024 / 1024 / 1024).toFixed(2)} TB` | |
| return val | |
| }, | |
| openDir (name) { | |
| this.browserPath = name | |
| this.page = 1 | |
| this.pageSize = 10 | |
| this.fetchData() | |
| }, | |
| downloadFile (record) { | |
| this.loading = true | |
| const params = { | |
| id: this.resource.id, | |
| path: `${this.browserPath}${record.name}` | |
| } | |
| postAPI('downloadImageStoreObject', params).then(response => { | |
| const jobId = response.downloadimagestoreobjectresponse.jobid | |
| this.$pollJob({ | |
| jobId: jobId, | |
| successMethod: (result) => { | |
| const url = result.jobresult.downloadimagestoreobjectresponse.url | |
| const name = result.jobresult.downloadimagestoreobjectresponse.name | |
| var elem = window.document.createElement('a') | |
| elem.setAttribute('href', new URL(url)) | |
| elem.setAttribute('download', name) | |
| elem.setAttribute('target', '_blank') | |
| document.body.appendChild(elem) | |
| elem.click() | |
| document.body.removeChild(elem) | |
| this.loading = false | |
| }, | |
| errorMethod: () => { | |
| this.loading = false | |
| }, | |
| catchMessage: this.$t('error.fetching.async.job.result'), | |
| catchMethod: () => { | |
| this.loading = false | |
| } | |
| }) | |
| }).catch(error => { | |
| console.error(error) | |
| this.$message.error(error) | |
| this.loading = false | |
| }) | |
| } | |
| } | |
| } | |
| </script> |