blob: ffd89088c4b01a85bf4bd9d59064961872ab5340 [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-spin :spinning="loading">
<div class="form-layout" v-ctrl-enter="handleSubmit">
<a-alert type="warning">
<template #message>
<div v-html="$t('label.header.volume.snapshot')"></div>
</template>
</a-alert>
<div class="form">
<a-form
:ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
@finish="handleSubmit"
>
<a-row :gutter="12">
<a-col :md="24" :lg="24">
<a-form-item :label="$t('label.intervaltype')" name="intervaltype" ref="intervaltype">
<a-radio-group
v-model:value="form.intervaltype"
buttonStyle="solid"
@change="handleChangeIntervalType">
<a-radio-button value="hourly" :disabled="handleVisibleInterval(0)">
{{ $t('label.hourly') }}
</a-radio-button>
<a-radio-button value="daily" :disabled="handleVisibleInterval(1)">
{{ $t('label.daily') }}
</a-radio-button>
<a-radio-button value="weekly" :disabled="handleVisibleInterval(2)">
{{ $t('label.weekly') }}
</a-radio-button>
<a-radio-button value="monthly" :disabled="handleVisibleInterval(3)">
{{ $t('label.monthly') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :md="24" :lg="12" v-if="form.intervaltype==='hourly'">
<a-form-item :label="$t('label.time')" name="time" ref="time">
<a-tooltip
placement="right"
:title="$t('label.minute.past.hour')">
<a-input-number
style="width: 100%"
v-model:value="form.time"
:min="1"
:max="59"
v-focus="true" />
</a-tooltip>
</a-form-item>
</a-col>
<a-col :md="24" :lg="12" v-if="['daily', 'weekly', 'monthly'].includes(form.intervaltype)">
<a-form-item
class="custom-time-select"
:label="$t('label.time')"
name="timeSelect"
ref="timeSelect">
<a-time-picker
use12Hours
format="h:mm A"
v-model:value="form.timeSelect"
style="width: 100%;" />
</a-form-item>
</a-col>
<a-col :md="24" :lg="12" v-if="form.intervaltype==='weekly'">
<a-form-item :label="$t('label.day.of.week')" name="day-of-week" ref="day-of-week">
<a-select
v-model:value="form['day-of-week']"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="(opt, optIndex) in dayOfWeek" :key="optIndex" :label="opt.name || opt.description">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="24" :lg="12" v-if="form.intervaltype==='monthly'">
<a-form-item :label="$t('label.day.of.month')" ref="day-of-month" name="day-of-month">
<a-select
v-model:value="form['day-of-month']"
showSearch
optionFilterProp="value"
:filterOption="(input, option) => {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="opt in dayOfMonth" :key="opt.name">
{{ opt.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="24" :lg="12">
<a-form-item :label="$t('label.keep')" name="maxsnaps" ref="maxsnaps">
<a-tooltip
placement="right"
:title="$t('label.snapshots')">
<a-input-number
style="width: 100%"
v-model:value="form.maxsnaps"
:min="1" />
</a-tooltip>
</a-form-item>
</a-col>
<a-col :md="24" :lg="24">
<a-form-item :label="$t('label.timezone')" ref="timezone" name="timezone">
<a-select
v-model:value="form.timezone"
:loading="fetching"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}" >
<a-select-option v-for="opt in timeZoneMap" :key="opt.id" :label="opt.name || opt.description">
{{ opt.name || opt.description }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="24" :lg="24" v-if="resourceType === 'Volume'">
<a-form-item ref="zoneids" name="zoneids">
<template #label>
<tooltip-label :title="$t('label.zones')" :tooltip="''"/>
</template>
<a-alert type="info" style="margin-bottom: 2%">
<template #message>
<div v-html="formattedAdditionalZoneMessage"/>
</template>
</a-alert>
<a-select
id="zone-selection"
v-model:value="form.zoneids"
mode="multiple"
showSearch
optionFilterProp="label"
:filterOption="(input, option) => {
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}"
:loading="zoneLoading"
:placeholder="''">
<a-select-option v-for="opt in this.zones" :key="opt.id" :label="opt.name || opt.description">
<span>
<resource-icon v-if="opt.icon" :image="opt.icon.base64image" size="1x" style="margin-right: 5px"/>
<global-outlined v-else style="margin-right: 5px"/>
{{ opt.name || opt.description }}
</span>
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-divider/>
<div class="tagsTitle">{{ $t('label.tags') }}</div>
<div>
<div v-for="(tag, index) in tags" :key="index">
<a-tag :key="index" :closable="'deleteTags' in $store.getters.apis" @close="() => handleDeleteTag(tag)">
{{ tag.key }} = {{ tag.value }}
</a-tag>
</div>
<div v-if="inputVisible">
<a-input-group
type="text"
size="small"
@blur="handleInputConfirm"
@keyup.enter="handleInputConfirm"
compact>
<a-input ref="input" :value="inputKey" @change="handleKeyChange" style="width: 100px; text-align: center" :placeholder="$t('label.key')" />
<a-input
class="tag-disabled-input"
style=" width: 30px; border-left: 0; pointer-events: none; text-align: center"
placeholder="="
disabled />
<a-input :value="inputValue" @change="handleValueChange" style="width: 100px; text-align: center; border-left: 0" :placeholder="$t('label.value')" />
<tooltip-button :tooltip="$t('label.ok')" icon="check-outlined" size="small" @onClick="handleInputConfirm" />
<tooltip-button :tooltip="$t('label.cancel')" icon="close-outlined" size="small" @onClick="inputVisible=false" />
</a-input-group>
</div>
<a-tag v-else @click="showInput" class="btn-add-tag" style="borderStyle: dashed;">
<plus-outlined /> {{ $t('label.new.tag') }}
</a-tag>
</div>
<div :span="24" class="action-button">
<a-button
:loading="actionLoading"
@click="closeAction">
{{ $t('label.cancel') }}
</a-button>
<a-button
v-if="handleShowButton()"
:loading="actionLoading"
type="primary"
ref="submit"
@click="handleSubmit">
{{ $t('label.ok') }}
</a-button>
</div>
</a-form>
</div>
</div>
</a-spin>
</template>
<script>
import { ref, reactive, toRaw } from 'vue'
import { api } from '@/api'
import TooltipButton from '@/components/widgets/TooltipButton'
import TooltipLabel from '@/components/widgets/TooltipLabel'
import { timeZone } from '@/utils/timezone'
import { mixinForm } from '@/utils/mixin'
import debounce from 'lodash/debounce'
export default {
name: 'FormSchedule',
mixins: [mixinForm],
components: {
TooltipButton,
TooltipLabel
},
props: {
loading: {
type: Boolean,
default: false
},
dataSource: {
type: Array,
required: true
},
resource: {
type: Object,
required: true
},
resourceType: {
type: String,
default: null
}
},
data () {
this.fetchTimeZone = debounce(this.fetchTimeZone, 800)
return {
actionLoading: false,
volumeId: '',
inputKey: '',
inputVisible: '',
inputValue: '',
intervalType: 'hourly',
intervalValue: 0,
tags: [],
dayOfWeek: [],
dayOfMonth: [],
timeZoneMap: [],
fetching: false,
listDayOfWeek: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'],
zones: []
}
},
created () {
this.initForm()
this.volumeId = this.resource.id
this.fetchTimeZone()
},
computed: {
formattedAdditionalZoneMessage () {
return `${this.$t('message.snapshot.additional.zones').replace('%x', this.resource.zonename)}`
}
},
methods: {
initForm () {
this.formRef = ref()
this.form = reactive({
intervaltype: 'hourly',
time: undefined,
timeSelect: undefined,
'day-of-week': undefined,
'day-of-month': undefined,
maxsnaps: undefined,
timezone: undefined
})
this.rules = reactive({
time: [{ type: 'number', required: true, message: this.$t('message.error.required.input') }],
timeSelect: [{ type: 'object', required: true, message: this.$t('message.error.time') }],
'day-of-week': [{ type: 'number', required: true, message: `${this.$t('message.error.select')}` }],
'day-of-month': [{ required: true, message: `${this.$t('message.error.select')}` }],
maxsnaps: [{ required: true, message: this.$t('message.error.required.input') }],
timezone: [{ required: true, message: `${this.$t('message.error.select')}` }]
})
if (this.resourceType === 'Volume') {
this.fetchZoneData()
}
},
fetchZoneData () {
const params = {}
params.showicon = true
this.zoneLoading = true
api('listZones', params).then(json => {
const listZones = json.listzonesresponse.zone
if (listZones) {
this.zones = listZones
this.zones = this.zones.filter(zone => zone.type !== 'Edge' && zone.id !== this.resource.zoneid)
}
}).finally(() => {
this.zoneLoading = false
})
},
fetchTimeZone (value) {
this.timeZoneMap = []
this.fetching = true
timeZone(value).then(json => {
this.timeZoneMap = json
this.fetching = false
})
},
fetchDayOfWeek () {
this.dayOfWeek = []
for (const index in this.listDayOfWeek) {
const dayName = this.listDayOfWeek[index]
this.dayOfWeek.push({
id: dayName,
name: this.$t('label.' + dayName)
})
}
},
fetchDayOfMonth () {
this.dayOfMonth = []
const maxDayOfMonth = 28
for (let num = 1; num <= maxDayOfMonth; num++) {
this.dayOfMonth.push({
id: num,
name: num
})
}
},
handleChangeIntervalType () {
switch (this.form.intervaltype) {
case 'hourly':
this.intervalValue = 0
break
case 'daily':
this.intervalValue = 1
break
case 'weekly':
this.intervalValue = 2
this.fetchDayOfWeek()
break
case 'monthly':
this.intervalValue = 3
this.fetchDayOfMonth()
break
}
},
handleVisibleInterval (intervalType) {
if (this.dataSource.length === 0) {
return false
}
const dataSource = this.dataSource.filter(item => item.intervaltype === intervalType)
if (dataSource && dataSource.length > 0) {
return true
}
return false
},
handleShowButton () {
if (this.dataSource.length === 0) {
return true
}
const dataSource = this.dataSource.filter(item => item.intervaltype === this.intervalValue)
if (dataSource && dataSource.length > 0) {
return false
}
return true
},
handleKeyChange (e) {
this.inputKey = e.target.value
},
handleValueChange (e) {
this.inputValue = e.target.value
},
handleInputConfirm () {
this.tags.push({
key: this.inputKey,
value: this.inputValue
})
this.inputVisible = false
this.inputKey = ''
this.inputValue = ''
},
handleDeleteTag (tag) {
},
handleSubmit (e) {
if (this.actionLoading) return
this.formRef.value.validate().then(() => {
const formRaw = toRaw(this.form)
const values = this.handleRemoveFields(formRaw)
let params = {}
params.volumeid = this.volumeId
params.intervaltype = values.intervaltype
params.timezone = values.timezone
params.maxsnaps = values.maxsnaps
if (values.zoneids && values.zoneids.length > 0) {
params.zoneids = values.zoneids.join()
}
switch (values.intervaltype) {
case 'hourly':
params.schedule = values.time
break
case 'daily':
params.schedule = values.timeSelect.format('mm:HH')
break
case 'weekly':
params.schedule = [values.timeSelect.format('mm:HH'), (values['day-of-week'] + 1)].join(':')
break
case 'monthly':
params.schedule = [values.timeSelect.format('mm:HH'), values['day-of-month']].join(':')
break
}
for (let i = 0; i < this.tags.length; i++) {
const formattedTagData = {}
const tag = this.tags[i]
formattedTagData['tags[' + i + '].key'] = tag.key
formattedTagData['tags[' + i + '].value'] = tag.value
params = Object.assign({}, params, formattedTagData)
}
this.actionLoading = true
api('createSnapshotPolicy', params).then(json => {
this.$emit('refresh')
this.$notification.success({
message: this.$t('label.action.recurring.snapshot'),
description: this.$t('message.success.recurring.snapshot')
})
this.resetForm()
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.actionLoading = false
})
})
},
showInput () {
this.inputVisible = true
this.$nextTick(function () {
this.$refs.input.focus()
})
},
resetForm () {
this.form.time = undefined
this.form.timezone = undefined
this.form.timeSelect = undefined
this.form.maxsnaps = undefined
this.form['day-of-week'] = undefined
this.form['day-of-month'] = undefined
this.tags = []
},
closeAction () {
this.$emit('close-action')
}
}
}
</script>
<style lang="less" scoped>
.form-layout {
.ant-tag {
margin-bottom: 10px;
}
:deep(.custom-time-select) .ant-time-picker {
width: 100%;
}
:deep(.ant-divider-horizontal) {
margin-top: 0;
}
}
.form {
margin: 10px 0;
}
.tagsTitle {
font-weight: 500;
margin-bottom: 12px;
}
</style>