blob: c489286ce6c20f01ca83fcd23a8b0d4547c990fe [file]
// 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 class="form-layout" v-ctrl-enter="handleSubmit">
<a-spin :spinning="loading">
<a-alert
v-if="resource"
type="info"
style="margin-bottom: 16px">
<template #message>
<div style="display: block; width: 100%;">
<div style="display: block; margin-bottom: 8px;">
<strong>{{ $t('message.clone.offering.from') }}: {{ resource.name }}</strong>
</div>
<div style="display: block; font-size: 12px;">
{{ $t('message.clone.offering.edit.hint') }}
</div>
</div>
</template>
</a-alert>
<a-form
layout="vertical"
:ref="formRef"
:model="form"
:rules="rules"
@finish="handleSubmit"
>
<a-form-item name="name" ref="name">
<template #label>
<tooltip-label :title="$t('label.name')" :tooltip="apiParams.name.description"/>
</template>
<a-input
v-focus="true"
v-model:value="form.name"/>
</a-form-item>
<a-form-item name="description" ref="description">
<template #label>
<tooltip-label :title="$t('label.description')" :tooltip="apiParams.description.description"/>
</template>
<a-input v-model:value="form.description"/>
</a-form-item>
<a-form-item name="zoneid" ref="zoneid">
<template #label>
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid ? apiParams.zoneid.description : ''"/>
</template>
<a-select
allowClear
v-model:value="form.zoneid"
:loading="zones.loading"
@change="onChangeZone"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="zone in zones.opts" :key="zone.name" :label="zone.name">
<span>
<resource-icon v-if="zone.icon" :image="zone.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ zone.name }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="externalid" ref="externalid">
<template #label>
<tooltip-label :title="$t('label.externalid')" :tooltip="apiParams.externalid ? apiParams.externalid.description : ''"/>
</template>
<a-select
allowClear
v-model:value="form.externalid"
:loading="externals.loading"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="opt in externals.opts" :key="opt.id" :label="opt.name">
{{ opt.name }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item name="allowuserdrivenbackups" ref="allowuserdrivenbackups">
<template #label>
<tooltip-label :title="$t('label.allowuserdrivenbackups')" :tooltip="apiParams.allowuserdrivenbackups ? apiParams.allowuserdrivenbackups.description : ''"/>
</template>
<a-switch v-model:checked="form.allowuserdrivenbackups"/>
</a-form-item>
<a-form-item name="ispublic" ref="ispublic" :label="$t('label.ispublic')" v-if="isAdmin()">
<a-switch v-model:checked="form.ispublic" @change="onChangeIsPublic" />
</a-form-item>
<a-form-item name="domainid" ref="domainid" v-if="!form.ispublic">
<template #label>
<tooltip-label :title="$t('label.domainid')" :tooltip="apiParams.domainid.description"/>
</template>
<a-select
mode="multiple"
:getPopupContainer="(trigger) => trigger.parentNode"
v-model:value="form.domainid"
@change="onChangeDomain"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="domains.loading"
:placeholder="apiParams.domainid.description">
<a-select-option v-for="(opt, optIndex) in domains.opts" :key="optIndex" :label="opt.path || opt.name || opt.description">
<span>
<resource-icon v-if="opt && opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<block-outlined v-else style="margin-right: 5px" />
{{ opt.path || opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
<div :span="24" class="action-button">
<a-button :loading="loading" @click="closeAction">{{ $t('label.cancel') }}</a-button>
<a-button :loading="loading" ref="submit" type="primary" @click="handleSubmit">{{ $t('label.ok') }}</a-button>
</div>
</a-form>
</a-spin>
</div>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { getAPI, postAPI } from '@/api'
import ResourceIcon from '@/components/view/ResourceIcon'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { GlobalOutlined } from '@ant-design/icons-vue'
import { isAdmin } from '@/role'
export default {
name: 'CloneBackupOffering',
components: {
TooltipLabel,
ResourceIcon,
GlobalOutlined
},
props: {
resource: {
type: Object,
required: true
}
},
data () {
return {
loading: false,
zones: {
loading: false,
opts: []
},
externals: {
loading: false,
opts: []
},
domains: {
loading: false,
opts: []
}
}
},
beforeCreate () {
this.apiParams = this.$getApiParams('cloneBackupOffering')
},
created () {
this.initForm()
this.fetchData()
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
allowuserdrivenbackups: true
})
this.rules = reactive({
name: [{ required: true, message: this.$t('message.error.required.input') }],
description: [{ required: true, message: this.$t('message.error.required.input') }]
})
},
fetchData () {
this.fetchZone()
this.fetchDomain()
this.$nextTick(() => {
this.populateFormFromResource()
})
},
fetchZone () {
this.zones.loading = true
getAPI('listZones', { available: true, showicon: true }).then(json => {
this.zones.opts = json.listzonesresponse.zone || []
this.$nextTick(() => {
this.populateFormFromResource()
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.zones.loading = false
})
},
fetchDomain () {
this.domains.loading = true
const params = { listAll: true, showicon: true, details: 'min' }
getAPI('listDomains', params).then(json => {
this.domains.opts = json.listdomainsresponse.domain || []
this.$nextTick(() => {
this.populateFormFromResource()
})
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.domains.loading = false
})
},
fetchExternal (zoneId) {
if (!zoneId) {
this.externals.opts = []
return
}
this.externals.loading = true
getAPI('listBackupProviderOfferings', { zoneid: zoneId }).then(json => {
this.externals.opts = json.listbackupproviderofferingsresponse.backupoffering || []
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.externals.loading = false
})
},
populateFormFromResource () {
if (!this.resource) return
const r = this.resource
this.form.name = r.name + ' - Clone'
this.form.description = r.description || r.name
if (r.allowuserdrivenbackups !== undefined) {
this.form.allowuserdrivenbackups = r.allowuserdrivenbackups
}
if (r.domainid) {
let offeringDomainIds = r.domainid
offeringDomainIds = (typeof offeringDomainIds === 'string' && offeringDomainIds.indexOf(',') !== -1) ? offeringDomainIds.split(',') : [offeringDomainIds]
const selected = []
for (let i = 0; i < offeringDomainIds.length; i++) {
for (let j = 0; j < this.domains.opts.length; j++) {
if (String(offeringDomainIds[i]) === String(this.domains.opts[j].id)) {
selected.push(j)
}
}
}
if (selected.length > 0) {
this.form.ispublic = false
this.form.domainid = selected
}
} else {
if (isAdmin()) {
this.form.ispublic = true
}
}
if (r.zoneid && this.zones.opts.length > 0) {
const zone = this.zones.opts.find(z => z.id === r.zoneid)
if (zone) {
this.form.zoneid = zone.name
this.fetchExternal(zone.id)
}
}
if (r.externalid) {
this.$nextTick(() => {
this.form.externalid = r.externalid
})
}
},
handleSubmit (e) {
if (e) {
e.preventDefault()
}
if (this.loading) return
this.formRef.value.validate().then(() => {
const values = toRaw(this.form)
const params = {
sourceofferingid: this.resource.id
}
if (values.name) {
params.name = values.name
}
if (values.description) {
params.description = values.description
}
if (values.zoneid) {
const zone = this.zones.opts.find(z => z.name === values.zoneid)
if (zone) {
params.zoneid = zone.id
}
}
if (values.externalid) {
params.externalid = values.externalid
}
if (values.allowuserdrivenbackups !== undefined) {
params.allowuserdrivenbackups = values.allowuserdrivenbackups
}
// Include selected domain IDs when offering is not public
if (values.ispublic !== true) {
const domainIndexes = values.domainid
let domainId = null
if (domainIndexes && domainIndexes.length > 0) {
const domainIds = []
const domains = this.domains.opts
for (let i = 0; i < domainIndexes.length; i++) {
domainIds.push(domains[domainIndexes[i]].id)
}
domainId = domainIds.join(',')
}
if (domainId) {
params.domainid = domainId
}
}
this.loading = true
const title = this.$t('message.success.clone.backup.offering')
postAPI('cloneBackupOffering', params).then(json => {
const jobId = json.clonebackupofferingresponse?.jobid
if (jobId) {
this.$pollJob({
jobId,
title,
description: values.name,
successMethod: result => {
this.$message.success(`${title}: ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
},
loadingMessage: `${title} ${this.$t('label.in.progress')} ${this.$t('label.for')} ${params.name}`,
catchMessage: this.$t('error.fetching.async.job.result'),
catchMethod: () => {
this.closeAction()
}
})
} else {
this.$message.success(`${title}: ${values.name}`)
this.$emit('refresh-data')
this.closeAction()
}
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.loading = false
})
}).catch(error => {
this.formRef.value.scrollToField(error.errorFields[0].name)
})
},
onChangeZone (value) {
if (!value) {
this.externals.opts = []
this.form.externalid = null
return
}
const zone = this.zones.opts.find(z => z.name === value)
if (zone) {
this.fetchExternal(zone.id)
}
},
onChangeIsPublic (value) {
// when made public, clear any domain selection
try {
if (value === true) {
this.form.domainid = []
}
} catch (e) {
// ignore
}
},
onChangeDomain (value) {
// value is an array of selected indexes (as used in the form), when empty -> make offering public
try {
if (!value || (Array.isArray(value) && value.length === 0)) {
this.form.ispublic = true
// clear domain selection to avoid confusing hidden inputs
this.form.domainid = []
} else {
this.form.ispublic = false
}
} catch (e) {
// defensive - do nothing on error
}
},
closeAction () {
this.$emit('close-action')
},
isAdmin () {
return isAdmin()
}
}
}
</script>
<style scoped lang="less">
.form-layout {
width: 30vw;
@media (min-width: 500px) {
width: 450px;
}
}
</style>