AC: If not already exists: Can see a separate main menu item "Fax Settings" Can click the separate main menu item and land on a page "Fax Settings" (route=/user/fax-settings) Can see settings if the feature enabled Can decide either to use SecretKey or ACL to manage authentication Can set a custom secret key/token Can set the renew interval (never, daily, weekly, monthly) Can see/read the "Last Secret Key Modify Time" Can add email addresses to get notified about expired key (secret_renew_notify) Can remove email addresses Can add ACL Rule (email, ip, destination, use-regexp flag) Can edit ACL Rule (email, ip, destination, use-regexp flag) Can remove ACL Rule Change-Id: I6bc25ab2f73d0dfae3fab224b11396ecdd17ab39pull/4/head
parent
2499e7487a
commit
6df2d69eb8
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<q-tooltip
|
||||
ref="tooltip"
|
||||
:delay="delay"
|
||||
:content-class="contentClass"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@show="autoHide"
|
||||
@hide="cancelAutoHide"
|
||||
>
|
||||
<slot />
|
||||
</q-tooltip>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CscTooltip',
|
||||
props: {
|
||||
autoHideDelay: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: 'text-dark'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
autoHideHandler: undefined
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.cancelAutoHide()
|
||||
},
|
||||
methods: {
|
||||
autoHide () {
|
||||
this.cancelAutoHide()
|
||||
this.autoHideHandler = setTimeout(() => {
|
||||
this.autoHideHandler = undefined
|
||||
this.$refs.tooltip.hide()
|
||||
},
|
||||
this.autoHideDelay
|
||||
)
|
||||
},
|
||||
cancelAutoHide () {
|
||||
clearTimeout(this.autoHideHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-list
|
||||
class="col col-xs-12 col-md-6"
|
||||
dense
|
||||
>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-toggle
|
||||
v-model="faxToMailSettings.active"
|
||||
:label="$t('faxSettings.active')"
|
||||
:disable="!dataLoaded"
|
||||
@input="setChangedData('active', !faxServerSettings.active)"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingFaxServerSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<csc-input-saveable
|
||||
v-model.trim="faxToMailSettings.name"
|
||||
:label="$t('faxSettings.sendfaxHeaderName')"
|
||||
:disable="!dataLoaded"
|
||||
:loading="loadingFaxServerSettings"
|
||||
:value-changed="nameChanged"
|
||||
@save="setChangedData('name', faxToMailSettings.name)"
|
||||
@undo="restoreName"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-toggle
|
||||
v-model="faxToMailSettings.t38"
|
||||
:label="$t('faxSettings.T38')"
|
||||
:disable="!dataLoaded"
|
||||
@input="setChangedData('t38', !faxServerSettings.t38)"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingFaxServerSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-toggle
|
||||
v-model="faxToMailSettings.ecm"
|
||||
:label="$t('faxSettings.ECM')"
|
||||
:disable="!dataLoaded"
|
||||
@input="setChangedData('ecm', !faxServerSettings.ecm)"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingFaxServerSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item class="row">
|
||||
<div class="col">
|
||||
<span class="text-h6">{{ $t('faxSettings.destinations') }}:</span>
|
||||
</div>
|
||||
<div class="col text-center">
|
||||
<csc-spinner
|
||||
v-if="loadingFaxServerSettings"
|
||||
/>
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="add"
|
||||
:disable="!dataLoaded || showAddNewDestination"
|
||||
@click="openAddNewDestination"
|
||||
>
|
||||
{{ $t('faxSettings.addDestination') }}
|
||||
</q-btn>
|
||||
</div>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-separator />
|
||||
<div
|
||||
class="row justify-center q-mb-lg"
|
||||
>
|
||||
<q-list
|
||||
class="col-xs-12"
|
||||
>
|
||||
<q-item
|
||||
v-if="showAddNewDestination"
|
||||
class="row justify-center"
|
||||
>
|
||||
<csc-fax-to-mail-destination-form
|
||||
v-if="showAddNewDestination"
|
||||
ref="addNewDestination"
|
||||
:loading="loadingFaxServerSettings"
|
||||
:is-add-new-mode="true"
|
||||
@save="addNewDestination"
|
||||
@cancel="closeAddNewDestination"
|
||||
/>
|
||||
</q-item>
|
||||
<q-item
|
||||
v-if="!hasDestinations"
|
||||
class="row justify-center"
|
||||
>
|
||||
{{ $t('faxSettings.noDestinationsCreatedYet') }}
|
||||
</q-item>
|
||||
<csc-fax-to-mail-destination
|
||||
v-for="(destinationItem, index) in faxToMailSettings.destinations"
|
||||
:key="destinationItem.destination"
|
||||
:odd="(index % 2) === 0"
|
||||
:expanded="expandedDestinationId === destinationItem.destination"
|
||||
:destination="destinationItem"
|
||||
:loading="loadingFaxServerSettings"
|
||||
@collapse="expandedDestinationId = null"
|
||||
@expand="expandedDestinationId = destinationItem.destination"
|
||||
@remove="openDeleteDestinationDialog(destinationItem.destination)"
|
||||
@update-property="updateDestinationItemProperty(destinationItem.destination, ...arguments)"
|
||||
/>
|
||||
</q-list>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import CscInputSaveable from 'components/form/CscInputSaveable'
|
||||
import CscSpinner from 'components/CscSpinner'
|
||||
import { mapWaitingActions, mapWaitingGetters } from 'vue-wait'
|
||||
import CscFaxToMailDestinationForm from 'components/pages/FaxSettings/CscFaxToMailDestinationForm'
|
||||
import CscFaxToMailDestination from 'components/pages/FaxSettings/CscFaxToMailDestination'
|
||||
import CscRemoveDialog from 'components/CscRemoveDialog'
|
||||
import { showGlobalError } from 'src/helpers/ui'
|
||||
export default {
|
||||
name: 'CscFaxToMailSettings',
|
||||
components: {
|
||||
CscFaxToMailDestination,
|
||||
CscFaxToMailDestinationForm,
|
||||
CscSpinner,
|
||||
CscInputSaveable
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
faxToMailSettings: {},
|
||||
showAddNewDestination: false,
|
||||
expandedDestinationId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('fax', [
|
||||
'faxServerSettings',
|
||||
'faxServerSettingsInitialized'
|
||||
]),
|
||||
...mapWaitingGetters({
|
||||
loadingFaxServerSettings: 'loading faxServerSettings'
|
||||
}),
|
||||
dataLoaded () {
|
||||
return this.faxServerSettingsInitialized && !this.loadingFaxServerSettings
|
||||
},
|
||||
hasDestinations () {
|
||||
return this.faxToMailSettings?.destinations?.length
|
||||
},
|
||||
nameChanged () {
|
||||
return this.faxToMailSettings.name !== this.faxServerSettings.name
|
||||
}
|
||||
|
||||
},
|
||||
mounted () {
|
||||
this.loadFaxServerSettings()
|
||||
},
|
||||
methods: {
|
||||
...mapWaitingActions('fax', {
|
||||
loadFaxSettingsAction: 'loading faxServerSettings',
|
||||
faxServerSettingsUpdateAction: 'loading faxServerSettings'
|
||||
}),
|
||||
async loadFaxServerSettings () {
|
||||
try {
|
||||
await this.loadFaxSettingsAction()
|
||||
this.updateDataFromStore()
|
||||
} catch (err) {
|
||||
showGlobalError(err?.message)
|
||||
}
|
||||
},
|
||||
updateDataFromStore () {
|
||||
this.faxToMailSettings = _.cloneDeep(this.faxServerSettings)
|
||||
},
|
||||
async setChangedData (field, value) {
|
||||
try {
|
||||
await this.faxServerSettingsUpdateAction({ field, value })
|
||||
this.updateDataFromStore()
|
||||
} catch (err) {
|
||||
showGlobalError(err?.message)
|
||||
}
|
||||
},
|
||||
restoreName () {
|
||||
this.faxToMailSettings.name = this.faxServerSettings.name
|
||||
},
|
||||
async updateDestinations (destinationItems, beforeUpdateUI = () => {}) {
|
||||
try {
|
||||
await this.faxServerSettingsUpdateAction({
|
||||
field: 'destinations',
|
||||
value: destinationItems
|
||||
})
|
||||
beforeUpdateUI()
|
||||
this.updateDataFromStore()
|
||||
} catch (err) {
|
||||
showGlobalError(err?.message)
|
||||
}
|
||||
},
|
||||
openAddNewDestination () {
|
||||
this.showAddNewDestination = true
|
||||
},
|
||||
closeAddNewDestination () {
|
||||
this.showAddNewDestination = false
|
||||
this.$refs.addNewDestination.reset()
|
||||
},
|
||||
addNewDestination (destination) {
|
||||
const destinationItems = [...this.faxToMailSettings.destinations, destination]
|
||||
|
||||
this.updateDestinations(destinationItems, () => {
|
||||
this.closeAddNewDestination()
|
||||
})
|
||||
},
|
||||
deleteDestination (destinationId) {
|
||||
const destinationItems = this.faxToMailSettings.destinations.filter(d => d.destination !== destinationId)
|
||||
this.faxServerSettingsUpdateAction({
|
||||
field: 'destinations',
|
||||
value: destinationItems
|
||||
}).then(() => {
|
||||
if (this.expandedDestinationId === destinationId) {
|
||||
this.expandedDestinationId = null
|
||||
}
|
||||
this.updateDataFromStore()
|
||||
})
|
||||
},
|
||||
openDeleteDestinationDialog (destinationId) {
|
||||
this.$q.dialog({
|
||||
component: CscRemoveDialog,
|
||||
parent: this,
|
||||
title: this.$t('faxSettings.deleteDestinationTitle'),
|
||||
message: this.$t('faxSettings.deleteDestinationText', { destination: destinationId })
|
||||
}).onOk(() => {
|
||||
this.deleteDestination(destinationId)
|
||||
})
|
||||
},
|
||||
updateDestinationItemProperty (destinationId, data) {
|
||||
const destinationItems = _.cloneDeep(this.faxToMailSettings.destinations)
|
||||
const destinationItemIndex = destinationItems.findIndex(d => d.destination === destinationId)
|
||||
if (destinationItemIndex >= 0) {
|
||||
destinationItems[destinationItemIndex][data.name] = data.value
|
||||
}
|
||||
|
||||
this.updateDestinations(destinationItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="q-item">
|
||||
<div
|
||||
class="csc-list-item-head row items-center"
|
||||
@click="toggle"
|
||||
>
|
||||
<div
|
||||
class="q-item__section column q-item__section--side justify-center"
|
||||
>
|
||||
<q-icon
|
||||
name="fas fa-shield-alt"
|
||||
size="24px"
|
||||
:color="expanded ? 'primary' : ''"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="q-item__section column q-item__section--main justify-center"
|
||||
:class="expanded ? 'text-primary' : ''"
|
||||
>
|
||||
<div class="q-item__label text-caption">
|
||||
<u>{{ acl.from_email }}</u> and
|
||||
<u>{{ acl.received_from }} </u> <sup v-if="acl.use_regex">(.*) </sup> =>
|
||||
<u>{{ acl.destination }} </u> <sup v-if="acl.use_regex">(.*) </sup>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="q-item__section column q-item__section--side justify-center"
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
icon="delete"
|
||||
text-color="negative"
|
||||
:title="$t('Remove')"
|
||||
:disable="isChanged"
|
||||
@click.stop="remove"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-slide-transition>
|
||||
<div
|
||||
v-if="expanded"
|
||||
class="csc-list-item-body"
|
||||
>
|
||||
<csc-mail-to-fax-a-c-l-form
|
||||
:is-add-new-mode="false"
|
||||
:initial-data="acl"
|
||||
:loading="loading"
|
||||
@update-property="updateProperty"
|
||||
/>
|
||||
</div>
|
||||
</q-slide-transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CscMailToFaxACLForm from 'components/pages/FaxSettings/CscMailToFaxACLForm'
|
||||
|
||||
export default {
|
||||
name: 'CscMailToFaxACL',
|
||||
components: {
|
||||
CscMailToFaxACLForm
|
||||
},
|
||||
props: {
|
||||
acl: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
expanded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isChanged () {
|
||||
return false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggle () {
|
||||
if (this.expanded) {
|
||||
this.$emit('collapse')
|
||||
} else {
|
||||
this.$emit('expand')
|
||||
}
|
||||
},
|
||||
updateProperty () {
|
||||
this.$emit('update-property', ...arguments)
|
||||
},
|
||||
remove () {
|
||||
this.$q.dialog({
|
||||
title: this.$t('Remove ACL'),
|
||||
message: this.$t('You are about to remove ACL: From email <{from_email}>', { from_email: this.acl.from_email }),
|
||||
color: 'primary',
|
||||
cancel: true,
|
||||
persistent: true
|
||||
}).onOk(() => {
|
||||
this.$emit('remove', this.key)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,177 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row">
|
||||
<div
|
||||
class="col"
|
||||
>
|
||||
<csc-input-saveable
|
||||
v-model="data.from_email"
|
||||
icon="email"
|
||||
:label="$t('From email')"
|
||||
:disable="disabled"
|
||||
:readonly="loading"
|
||||
:error="$v.data.from_email.$error"
|
||||
:error-message="fromEmailErrorMessage"
|
||||
:value-changed="!isAddNewMode && data.from_email !== initialData.from_email"
|
||||
@input="$v.data.from_email.$touch"
|
||||
@keypress.space.prevent
|
||||
@keydown.space.prevent
|
||||
@keyup.space.prevent
|
||||
@undo="data.from_email = initialData.from_email"
|
||||
@save="updatePropertyData('from_email')"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Accepted email address to allow mail2fax transmission.') }}
|
||||
</csc-tooltip>
|
||||
</csc-input-saveable>
|
||||
<csc-input-saveable
|
||||
v-model="data.received_from"
|
||||
:label="$t('Received from IP')"
|
||||
:disable="disabled"
|
||||
:readonly="loading"
|
||||
:value-changed="!isAddNewMode && data.received_from !== initialData.received_from"
|
||||
@keypress.space.prevent
|
||||
@keydown.space.prevent
|
||||
@keyup.space.prevent
|
||||
@undo="data.received_from = initialData.received_from"
|
||||
@save="updatePropertyData('received_from')"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Allow mail2fax emails only to this IP (the IP or hostname is present in the "Received" header).') }}
|
||||
</csc-tooltip>
|
||||
</csc-input-saveable>
|
||||
<csc-input-saveable
|
||||
v-model="data.destination"
|
||||
:label="$t('Destination')"
|
||||
:disable="disabled"
|
||||
:readonly="loading"
|
||||
:value-changed="!isAddNewMode && data.destination !== initialData.destination"
|
||||
@keypress.space.prevent
|
||||
@keydown.space.prevent
|
||||
@keyup.space.prevent
|
||||
@undo="data.destination = initialData.destination"
|
||||
@save="updatePropertyData('destination')"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Allow mail2fax destination only to this number.') }}
|
||||
</csc-tooltip>
|
||||
</csc-input-saveable>
|
||||
|
||||
<q-toggle
|
||||
v-model="data.use_regex"
|
||||
:label="$t('Use RegExp')"
|
||||
:hint="$t('Enable regex matching for "Received from IP" and "Destination" fields.')"
|
||||
:disable="loading"
|
||||
@input="updatePropertyData('use_regex')"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Enable regex matching for "Received from IP" and "Destination" fields.') }}
|
||||
</csc-tooltip>
|
||||
</q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isAddNewMode"
|
||||
class="row justify-center"
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
color="default"
|
||||
icon="clear"
|
||||
:disable="loading"
|
||||
:label="$t('buttons.cancel')"
|
||||
@click="cancel()"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="person"
|
||||
:loading="loading"
|
||||
:disable="$v.data.$invalid || loading"
|
||||
label="Create ACL"
|
||||
@click="save()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { email } from 'vuelidate/lib/validators'
|
||||
import CscInputSaveable from 'components/form/CscInputSaveable'
|
||||
import CscTooltip from 'components/CscTooltip'
|
||||
|
||||
export default {
|
||||
name: 'CscMailToFaxACLForm',
|
||||
components: {
|
||||
CscTooltip,
|
||||
CscInputSaveable
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
destination: '',
|
||||
from_email: '',
|
||||
received_from: '',
|
||||
use_regex: false
|
||||
})
|
||||
},
|
||||
isAddNewMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: this.getDefaults()
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
data: {
|
||||
from_email: {
|
||||
email
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fromEmailErrorMessage () {
|
||||
if (!this.$v.data.from_email.email) {
|
||||
return this.$t('validationErrors.email')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaults () {
|
||||
return { ...this.initialData }
|
||||
},
|
||||
cancel () {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
save () {
|
||||
this.$emit('save', {
|
||||
...this.data
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
this.data = this.getDefaults()
|
||||
this.$v.$reset()
|
||||
},
|
||||
updatePropertyData (propertyName) {
|
||||
this.$emit('update-property', {
|
||||
name: propertyName,
|
||||
value: this.data[propertyName]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<q-item>
|
||||
<q-item-section
|
||||
v-if="!editing"
|
||||
side
|
||||
@click="activateEditing"
|
||||
>
|
||||
<q-icon name="email" />
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
@click="activateEditing"
|
||||
>
|
||||
<q-item-label
|
||||
v-if="!editing"
|
||||
>
|
||||
{{ value }}
|
||||
</q-item-label>
|
||||
<csc-input-saveable
|
||||
v-else
|
||||
ref="emailInput"
|
||||
v-model="newEmail"
|
||||
:label="$t('Renew Notify Email')"
|
||||
:value-changed="isChanged"
|
||||
:error="$v.newEmail.$error"
|
||||
:error-message="newEmailErrorMessage"
|
||||
dense
|
||||
@keypress.space.prevent
|
||||
@keydown.space.prevent
|
||||
@keyup.space.prevent
|
||||
@input="$v.newEmail.$touch"
|
||||
@save="save"
|
||||
@undo="undo"
|
||||
@focusout="focusOutEditing"
|
||||
@focusin="cancelTimer"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
icon="delete"
|
||||
text-color="negative"
|
||||
:title="$t('Remove')"
|
||||
:disable="isChanged"
|
||||
@click="remove"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CscInputSaveable from 'components/form/CscInputSaveable'
|
||||
import { email, required } from 'vuelidate/lib/validators'
|
||||
export default {
|
||||
name: 'CscMailToFaxRenewNotifyEmail',
|
||||
components: {
|
||||
CscInputSaveable
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
newEmail: this.value,
|
||||
editing: false,
|
||||
timerHandler: undefined
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
newEmail: {
|
||||
required,
|
||||
email
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isChanged () {
|
||||
return this.newEmail !== this.value
|
||||
},
|
||||
newEmailErrorMessage () {
|
||||
if (!this.$v.newEmail.required) {
|
||||
return this.$t('validationErrors.fieldRequired', {
|
||||
field: this.$t('Renew Notify Email')
|
||||
})
|
||||
} else if (!this.$v.newEmail.email) {
|
||||
return this.$t('validationErrors.email')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.cancelTimer()
|
||||
},
|
||||
methods: {
|
||||
activateEditing () {
|
||||
if (!this.editing) {
|
||||
this.newEmail = this.value
|
||||
this.editing = true
|
||||
this.focusEmailInput()
|
||||
}
|
||||
},
|
||||
deactivateEditing () {
|
||||
this.timerHandler = setTimeout(() => {
|
||||
this.editing = false
|
||||
}, 1000)
|
||||
},
|
||||
cancelTimer () {
|
||||
clearTimeout(this.timerHandler)
|
||||
},
|
||||
focusOutEditing () {
|
||||
if (!this.isChanged) {
|
||||
this.deactivateEditing()
|
||||
}
|
||||
},
|
||||
focusEmailInput () {
|
||||
this.$nextTick(() => {
|
||||
const emailInput = this.$refs.emailInput?.$el
|
||||
if (emailInput) {
|
||||
emailInput.focus()
|
||||
}
|
||||
})
|
||||
},
|
||||
undo () {
|
||||
this.newEmail = this.value
|
||||
this.$v.$reset()
|
||||
this.focusEmailInput()
|
||||
},
|
||||
save () {
|
||||
this.$emit('save', {
|
||||
id: this.key,
|
||||
value: this.newEmail
|
||||
})
|
||||
},
|
||||
remove () {
|
||||
this.$q.dialog({
|
||||
title: this.$t('faxSettings.deleteRenewNotifyEmailTitle'),
|
||||
message: this.$t('faxSettings.deleteRenewNotifyEmailText', { email: this.value }),
|
||||
color: 'primary',
|
||||
cancel: true,
|
||||
persistent: true
|
||||
}).onOk(() => {
|
||||
this.$emit('remove', this.key)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="csc-form">
|
||||
<csc-input-saveable
|
||||
v-model="data.destination"
|
||||
icon="email"
|
||||
:label="$t('Renew Notify Email')"
|
||||
:disable="disabled"
|
||||
:readonly="loading"
|
||||
:error="$v.data.destination.$error"
|
||||
:error-message="destinationErrorMessage"
|
||||
:value-changed="!isAddNewMode && data.destination !== initialData.destination"
|
||||
@input="$v.data.destination.$touch"
|
||||
@keypress.space.prevent
|
||||
@keydown.space.prevent
|
||||
@keyup.space.prevent
|
||||
@undo="data.destination = initialData.destination"
|
||||
@save="updatePropertyData('destination')"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Destination email to send the secret key renew notification to.') }}
|
||||
</csc-tooltip>
|
||||
</csc-input-saveable>
|
||||
<div
|
||||
v-if="isAddNewMode"
|
||||
class="csc-form-actions row justify-center"
|
||||
>
|
||||
<q-btn
|
||||
flat
|
||||
color="default"
|
||||
icon="clear"
|
||||
:disable="loading"
|
||||
:label="$t('Cancel')"
|
||||
@click="cancel()"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="done"
|
||||
:loading="loading"
|
||||
:disable="$v.data.$invalid || loading"
|
||||
:label="$t('Add email')"
|
||||
@click="save()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { email, required } from 'vuelidate/lib/validators'
|
||||
import CscInputSaveable from 'components/form/CscInputSaveable'
|
||||
import CscTooltip from 'components/CscTooltip'
|
||||
|
||||
export default {
|
||||
name: 'CscMailToFaxRenewNotifyEmailForm',
|
||||
components: {
|
||||
CscTooltip,
|
||||
CscInputSaveable
|
||||
},
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialData: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
destination: ''
|
||||
})
|
||||
},
|
||||
isAddNewMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
data: this.getDefaults()
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
data: {
|
||||
destination: {
|
||||
required,
|
||||
email
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
destinationErrorMessage () {
|
||||
if (!this.$v.data.destination.required) {
|
||||
return this.$t('validationErrors.fieldRequired', {
|
||||
field: this.$t('Email')
|
||||
})
|
||||
} else if (!this.$v.data.destination.email) {
|
||||
return this.$t('validationErrors.email')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaults () {
|
||||
return { ...this.initialData }
|
||||
},
|
||||
cancel () {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
save () {
|
||||
this.$emit('save', {
|
||||
...this.data
|
||||
})
|
||||
},
|
||||
reset () {
|
||||
this.data = this.getDefaults()
|
||||
this.$v.$reset()
|
||||
},
|
||||
updatePropertyData (propertyName) {
|
||||
this.$emit('update-property', {
|
||||
name: propertyName,
|
||||
value: this.data[propertyName]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="!mailToFaxSettingsModel.active"
|
||||
class="q-pa-md"
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingMail2FaxSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
<div v-else>
|
||||
{{ $t('faxSettings.featureIsNotActive') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<q-list
|
||||
class="col col-xs-12 col-md-6"
|
||||
dense
|
||||
>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-toggle
|
||||
:value="mailToFaxSettingsModel.active"
|
||||
:label="$t('faxSettings.active')"
|
||||
:disable="true"
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingMail2FaxSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<csc-input-saveable
|
||||
v-model.trim="mailToFaxSettingsModel.secret_key"
|
||||
:label="secretKeyFieldLabel"
|
||||
:disable="!dataLoaded"
|
||||
:loading="loadingMail2FaxSettings"
|
||||
:value-changed="mailToFaxSettingsModel.secret_key !== mailToFaxSettings.secret_key"
|
||||
@save="setChangedData('secret_key', mailToFaxSettingsModel.secret_key)"
|
||||
@undo="mailToFaxSettingsModel.secret_key = mailToFaxSettings.secret_key"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Enable strict mode that requires all mail2fax emails to have the secret key as the very first line of the email + an empty line. The key is removed from the email once matched.') }}
|
||||
</csc-tooltip>
|
||||
</csc-input-saveable>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>
|
||||
<q-select
|
||||
v-model="mailToFaxSettingsModel.secret_key_renew"
|
||||
emit-value
|
||||
map-options
|
||||
:disable="!dataLoaded"
|
||||
:readonly="!dataLoaded"
|
||||
:label="$t('faxSettings.secretKeyRenew')"
|
||||
:options="secretKeyRenewOptions"
|
||||
@input="setChangedData('secret_key_renew', mailToFaxSettingsModel.secret_key_renew)"
|
||||
>
|
||||
<csc-tooltip>
|
||||
{{ $t('Interval when the secret key is automatically renewed.') }}
|
||||
</csc-tooltip>
|
||||
</q-select>
|
||||
</q-item-section>
|
||||
<q-item-section
|
||||
side
|
||||
>
|
||||
<csc-spinner
|
||||
v-if="loadingMail2FaxSettings"
|
||||
class="self-center"
|
||||
/>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<div class="row">
|
||||
<div class="col q-py-md q-pl-md">
|
||||
<div class="row q-pb-xs">
|
||||
<div class="col vertical-bottom">
|
||||
<span class="vertical-middle">{{ $t('faxSettings.secretKeyRenewNotify') }}:</span>
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="add"
|
||||
:disable="!dataLoaded || showAddNewRenewEmail"
|
||||
@click="openAddNewRenewEmail"
|
||||
>
|
||||
{{ $t('faxSettings.addEmail') }}
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div class="col relative-position">
|
||||
<div
|
||||
v-if="showAddNewRenewEmail"
|
||||
class="row justify-center q-pa-md"
|
||||
>
|
||||
<csc-mail-to-fax-renew-notify-email-form
|
||||
v-if="showAddNewRenewEmail"
|
||||
ref="addNewRenewEmailForm"
|
||||
class="col"
|
||||
:loading="!dataLoaded"
|
||||
:is-add-new-mode="true"
|
||||
@save="addNewRenewEmail"
|
||||
@cancel="closeAddNewRenewEmail"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!showAddNewRenewEmail && (!mailToFaxSettingsModel.secret_renew_notify || !mailToFaxSettingsModel.secret_renew_notify.length)"
|
||||
class="row q-pa-md justify-center"
|
||||
>
|
||||
{{ $t('There are no Key Renew Notify Emails yet') }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="row q-pa-xs"
|
||||
>
|
||||
<q-list class="col striped-list">
|
||||
<csc-mail-to-fax-renew-notify-email
|
||||
v-for="renewEmail in mailToFaxSettingsModel.secret_renew_notify"
|
||||
:key="renewEmail.destination"
|
||||
:value="renewEmail.destination"
|
||||
@save="updateRenewEmailItem(renewEmail.destination, ...arguments)"
|
||||
@remove="deleteRenewEmailItem(renewEmail.destination)"
|
||||
/>
|
||||
</q-list>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :showing="!dataLoaded">
|
||||
<q-spinner-dots
|
||||
size="50px"
|
||||
color="primary"
|
||||
/>
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col q-pa-md">
|
||||
<div class="row q-pb-xs">
|
||||
<div class="col">
|
||||
{{ $t('faxSettings.ACL') }}:
|
||||
</div>
|
||||
<div class="col text-right">
|
||||
<q-btn
|
||||
flat
|
||||
color="primary"
|
||||
icon="add"
|
||||
:disable="!dataLoaded || showAddNewACL"
|
||||
@click="openAddNewACL"
|
||||
>
|
||||
{{ $t('faxSettings.addACL') }}
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator />
|
||||
<div class="col relative-position">
|
||||
<div
|
||||
v-if="showAddNewACL"
|
||||
class="row justify-center q-pa-md"
|
||||
>
|
||||
<csc-mail-to-fax-a-c-l-form
|
||||
v-if="showAddNewACL"
|
||||
ref="addNewACLForm"
|
||||
class="col"
|
||||
:loading="!dataLoaded"
|
||||
:is-add-new-mode="true"
|
||||
@save="addNewACL"
|
||||
@cancel="closeAddNewACL"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="!showAddNewACL && (!mailToFaxSettingsModel.acl || !mailToFaxSettingsModel.acl.length)"
|
||||
class="row q-pa-md justify-center"
|
||||
>
|
||||
{{ $t('There are no ACLs yet') }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="row q-pa-xs"
|
||||
>
|
||||
<q-list class="col striped-list">
|
||||
<csc-mail-to-fax-a-c-l
|
||||
v-for="(acl, index) in mailToFaxSettingsModel.acl"
|
||||
:key="index"
|
||||
:acl="acl"
|
||||
:expanded="index === expandedACLId"
|
||||
@expand="expandedACLId = index"
|
||||
@collapse="expandedACLId = null"
|
||||
@update-property="updateACL(index, ...arguments)"
|
||||
@remove="deleteACL(index)"
|
||||
/>
|
||||
</q-list>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :showing="!dataLoaded">
|
||||
<q-spinner-dots
|
||||
size="50px"
|
||||
color="primary"
|
||||
/>
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapWaitingActions, mapWaitingGetters } from 'vue-wait'
|
||||
import { showGlobalError } from 'src/helpers/ui'
|
||||
import CscSpinner from 'components/CscSpinner'
|
||||
import CscInputSaveable from 'components/form/CscInputSaveable'
|
||||
import CscMailToFaxRenewNotifyEmail from 'components/pages/FaxSettings/CscMailToFaxRenewNotifyEmail'
|
||||
import CscMailToFaxACL from 'components/pages/FaxSettings/CscMailToFaxACL'
|
||||
import CscMailToFaxRenewNotifyEmailForm from 'components/pages/FaxSettings/CscMailToFaxRenewNotifyEmailForm'
|
||||
import CscMailToFaxACLForm from 'components/pages/FaxSettings/CscMailToFaxACLForm'
|
||||
import CscTooltip from 'components/CscTooltip'
|
||||
|
||||
export default {
|
||||
name: 'CscMailToFaxSettings',
|
||||
components: {
|
||||
CscTooltip,
|
||||
CscMailToFaxACLForm,
|
||||
CscMailToFaxRenewNotifyEmailForm,
|
||||
CscMailToFaxACL,
|
||||
CscMailToFaxRenewNotifyEmail,
|
||||
CscInputSaveable,
|
||||
CscSpinner
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mailToFaxSettingsModel: {},
|
||||
showAddNewRenewEmail: false,
|
||||
showAddNewACL: false,
|
||||
expandedACLId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('fax', [
|
||||
'mailToFaxSettings',
|
||||
'mailToFaxSettingsInitialized'
|
||||
]),
|
||||
...mapWaitingGetters({
|
||||
loadingMail2FaxSettings: 'loading mail2faxSettings'
|
||||
}),
|
||||
dataLoaded () {
|
||||
return this.mailToFaxSettingsInitialized && !this.loadingMail2FaxSettings
|
||||
},
|
||||
secretKeyFieldLabel () {
|
||||
let label = this.$t('faxSettings.secretKeyField')
|
||||
label += ' (' + this.$t('faxSettings.lastModifyTime') + ': '
|
||||
if (this.mailToFaxSettings.last_secret_key_modify) {
|
||||
label += this.mailToFaxSettings.last_secret_key_modify + ')'
|
||||
} else {
|
||||
label += this.$t('faxSettings.notModifiedYet') + ')'
|
||||
}
|
||||
return label
|
||||
},
|
||||
secretKeyRenewOptions () {
|
||||
return [
|
||||
{ value: 'never', label: this.$t('Never') },
|
||||
{ value: 'daily', label: this.$t('Daily') },
|
||||
{ value: 'weekly', label: this.$t('Weekly') },
|
||||
{ value: 'monthly', label: this.$t('Monthly') }
|
||||
]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.loadMailToFaxSettings()
|
||||
},
|
||||
methods: {
|
||||
...mapWaitingActions('fax', {
|
||||
loadMailToFaxSettingsAction: 'loading mail2faxSettings',
|
||||
mailToFaxSettingsUpdateAction: 'loading mail2faxSettings'
|
||||
}),
|
||||
async loadMailToFaxSettings () {
|
||||
try {
|
||||
await this.loadMailToFaxSettingsAction()
|
||||
this.updateDataFromStore()
|
||||
} catch (err) {
|
||||
if (String(err.code) === '403') {
|
||||
this.mailToFaxSettingsModel = {
|
||||
active: false
|
||||
}
|
||||
} else {
|
||||
showGlobalError(err?.message || this.$t('Unknown error'))
|
||||
}
|
||||
}
|
||||
},
|
||||
updateDataFromStore () {
|
||||
this.mailToFaxSettingsModel = {
|
||||
active: true,
|
||||
..._.cloneDeep(this.mailToFaxSettings)
|
||||
}
|
||||
},
|
||||
async setChangedData (field, value, beforeUpdateUI = () => {}) {
|
||||
try {
|
||||
await this.mailToFaxSettingsUpdateAction({ field, value })
|
||||
beforeUpdateUI()
|
||||
this.updateDataFromStore()
|
||||
} catch (err) {
|
||||
showGlobalError(err?.message || this.$t('Unknown error'))
|
||||
}
|
||||
},
|
||||
openAddNewRenewEmail () {
|
||||
this.showAddNewRenewEmail = true
|
||||
},
|
||||
closeAddNewRenewEmail () {
|
||||
this.showAddNewRenewEmail = false
|
||||
this.$refs.addNewRenewEmailForm.reset()
|
||||
},
|
||||
addNewRenewEmail (newItemData) {
|
||||
const renewEmailItems = [...this.mailToFaxSettingsModel.secret_renew_notify, newItemData]
|
||||
|
||||
this.setChangedData('secret_renew_notify', renewEmailItems, () => {
|
||||
this.closeAddNewRenewEmail()
|
||||
})
|
||||
},
|
||||
updateRenewEmailItem (itemId, data) {
|
||||
const renewEmailItems = _.cloneDeep(this.mailToFaxSettingsModel.secret_renew_notify)
|
||||
const renewEmailItemIndex = renewEmailItems.findIndex(d => d.destination === itemId)
|
||||
if (renewEmailItemIndex >= 0) {
|
||||
renewEmailItems[renewEmailItemIndex].destination = data.value
|
||||
}
|
||||
|
||||
this.setChangedData('secret_renew_notify', renewEmailItems)
|
||||
},
|
||||
deleteRenewEmailItem (itemId) {
|
||||
const renewEmailItems = this.mailToFaxSettingsModel.secret_renew_notify.filter(d => d.destination !== itemId)
|
||||
this.setChangedData('secret_renew_notify', renewEmailItems)
|
||||
},
|
||||
|
||||
openAddNewACL () {
|
||||
this.showAddNewACL = true
|
||||
},
|
||||
closeAddNewACL () {
|
||||
this.showAddNewACL = false
|
||||
this.$refs.addNewACLForm.reset()
|
||||
},
|
||||
addNewACL (newItemData) {
|
||||
const ACLItems = [...this.mailToFaxSettingsModel.acl, newItemData]
|
||||
|
||||
this.setChangedData('acl', ACLItems, () => {
|
||||
this.closeAddNewACL()
|
||||
})
|
||||
},
|
||||
updateACL (itemId, { name, value }) {
|
||||
const ACLItems = _.cloneDeep(this.mailToFaxSettingsModel.acl)
|
||||
if (itemId >= 0) {
|
||||
ACLItems[itemId][name] = value
|
||||
}
|
||||
|
||||
this.setChangedData('acl', ACLItems)
|
||||
},
|
||||
deleteACL (itemId) {
|
||||
const ACLItems = this.mailToFaxSettingsModel.acl.filter((acl, index) => index !== itemId)
|
||||
this.setChangedData('acl', ACLItems, () => {
|
||||
this.expandedACLId = null
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" rel="stylesheet/stylus" scoped>
|
||||
|
||||
</style>
|
Loading…
Reference in new issue