TT#96352 - CSC: As a Customer, I want change my Mail2Fax settings

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: I6bc25ab2f73d0dfae3fab224b11396ecdd17ab39
pull/4/head
Sergii Leonenko 4 years ago
parent 2499e7487a
commit 6df2d69eb8

@ -32,3 +32,30 @@ export async function setFaxServerField (options) {
value: options.value
})
}
export async function getMailToFaxSettings (subscriberId) {
const result = await get({
path: `api/mailtofaxsettings/${subscriberId}`
})
const settings = _.clone(result)
delete settings._links
return settings
}
export async function setMailToFaxSettingField (options) {
if (!['active', 'secret_key', 'secret_key_renew', 'secret_renew_notify', 'acl'].includes(options.field)) {
throw Error(`setMailToFaxSettingField: unknown field name ${options.field}`)
}
if (options.field === 'secret_renew_notify') {
// searching for duplicates
const destinationsIds = options.value.map(d => d.destination)
if ((new Set(destinationsIds)).size !== destinationsIds.length) {
throw Error(i18n.t('faxSettings.notifyEmailExists'))
}
}
return patchReplaceFull({
path: `api/mailtofaxsettings/${options.subscriberId}`,
fieldPath: options.field,
value: options.value
})
}

@ -17,6 +17,7 @@ export const defaultLocale = 'en-US'
export const i18n = new VueI18n({
locale: defaultLocale,
fallbackLocale: defaultLocale,
formatFallbackMessages: true,
messages
})

@ -46,7 +46,8 @@ export default {
computed: {
...mapGetters('user', [
'isRtcEngineUiVisible',
'isPbxEnabled'
'isPbxEnabled',
'hasFaxCapability'
]),
items () {
return [
@ -129,7 +130,7 @@ export default {
to: '/user/fax-settings',
icon: 'fas fa-fax',
label: this.$t('navigation.faxSettings.title'),
visible: true
visible: this.hasFaxCapability
},
{
icon: 'miscellaneous_services',

@ -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>

@ -7,6 +7,7 @@
@input="$emit('input', $event)"
@keyup.enter="$emit('save', $event)"
>
<slot />
<template
v-if="icon !== undefined && icon !== null"
v-slot:prepend

@ -18,7 +18,7 @@
@click="addDestinationByType('voicebox')"
/>
<csc-popup-menu-item
v-if="hasFaxCapability && hasSendFaxFeature"
v-if="hasFaxCapabilityAndFaxActive && hasSendFaxFeature"
:label="$t('pages.callForward.buttons.addFax2Mail')"
@click="addDestinationByType('fax2mail')"
/>
@ -150,7 +150,7 @@ export default {
]),
...mapGetters('user', [
'hasSendFaxFeature',
'hasFaxCapability'
'hasFaxCapabilityAndFaxActive'
]),
timeoutInputError () {
if (!this.$v.destinationForm.timeout.required) {

@ -43,7 +43,7 @@
</csc-list-menu-item>
</template>
<template slot="body">
<csc-fax2-mail-destination-form
<csc-fax-to-mail-destination-form
:is-add-new-mode="false"
:initial-data="destination"
:loading="loading"
@ -58,12 +58,12 @@ import CscListItem from 'components/CscListItem'
import CscListItemTitle from 'components/CscListItemTitle'
import CscListMenuItem from 'components/CscListMenuItem'
import CscListItemSubtitle from 'components/CscListItemSubtitle'
import CscFax2MailDestinationForm from 'components/pages/FaxSettings/CscFax2MailDestinationForm'
import CscFaxToMailDestinationForm from 'components/pages/FaxSettings/CscFaxToMailDestinationForm'
export default {
name: 'CscFax2MailDestination',
name: 'CscFaxToMailDestination',
components: {
CscFax2MailDestinationForm,
CscFaxToMailDestinationForm,
CscListItemSubtitle,
CscListMenuItem,
CscListItemTitle,

@ -70,7 +70,7 @@
<q-btn
flat
color="primary"
icon="person"
icon="done"
:loading="loading"
:disable="$v.data.$invalid || loading"
:label="$t('faxSettings.createDestination')"
@ -85,7 +85,7 @@ import { email, required } from 'vuelidate/lib/validators'
import CscInputSaveable from 'components/form/CscInputSaveable'
export default {
name: 'CscFax2MailDestinationForm',
name: 'CscFaxToMailDestinationForm',
components: {
CscInputSaveable
},

@ -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 &quot;Received&quot; 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 &quot;Received from IP&quot; and &quot;Destination&quot; fields.')"
:disable="loading"
@input="updatePropertyData('use_regex')"
>
<csc-tooltip>
{{ $t('Enable regex matching for &quot;Received from IP&quot; and &quot;Destination&quot; 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>

@ -27,3 +27,9 @@ body.body--dark
.csc-opt-center
margin-top ($header-height * -2)
.striped-list
> :nth-of-type(2n+1)
background-color $item-stripe-color
> :nth-of-type(2n)
background-color alpha($main-menu-background, 0.2)

@ -764,7 +764,20 @@
"deleteDestinationTitle": "Remove Destination",
"deleteDestinationText": "You are about to remove destination {destination}",
"destinationEmailExists": "The Destination Email is already used",
"destinationItemTitle": "<{destination}> as {filetype}"
"destinationItemTitle": "<{destination}> as {filetype}",
"notifyEmailExists": "The Notify Email is already used",
"featureIsNotActive": "Mail To Fax feature is not active",
"destinations": "Destinations",
"secretKeyField": "Secret Key (empty=disabled)",
"lastModifyTime": "Last Modify Time",
"notModifiedYet": "Not modified yet",
"secretKeyRenew": "Secret Key Renew",
"secretKeyRenewNotify": "Secret Key Renew Notify",
"addEmail": "Add email",
"deleteRenewNotifyEmailTitle": "Remove secret key renew notify email",
"deleteRenewNotifyEmailText": "You are about to remove secret key renew notify email: {email}",
"ACL": "ACL",
"addACL": "Add ACL"
},
"callSettings": {
"musicOnHold": "Music on Hold",

@ -21,7 +21,7 @@
@click="$refs.mainMenu.show()"
/>
<q-btn
v-if="hasFaxCapability && hasSendFaxFeature"
v-if="hasFaxCapabilityAndFaxActive && hasSendFaxFeature"
class="q-mr-sm"
flat
dense
@ -297,7 +297,7 @@ export default {
'getUsername',
'isPbxAdmin',
'hasSmsCapability',
'hasFaxCapability',
'hasFaxCapabilityAndFaxActive',
'hasSendSmsFeature',
'hasSendFaxFeature',
'userDataRequesting',
@ -315,7 +315,7 @@ export default {
]),
hasCommunicationCapabilities () {
return (this.hasSmsCapability && this.hasSendSmsFeature) ||
(this.hasFaxCapability && this.hasSendFaxFeature)
(this.hasFaxCapabilityAndFaxActive && this.hasSendFaxFeature)
},
isMenuClosed () {
return !this.sideStates.left

@ -1,275 +1,46 @@
<template>
<csc-page
class="q-pa-lg"
<csc-page-sticky-tabs
v-model="selectedTab"
>
<q-list
class="col col-xs-12 col-md-6"
dense
<template
v-slot:tabs
>
<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">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-fax2-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-fax2-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>
</csc-page>
<q-tab
name="fax2mail"
icon="perm_phone_msg"
:label="$t('Fax to Mail and Sendfax')"
/>
<q-tab
name="mail2fax"
icon="forward_to_inbox"
:label="$t('Mail to Fax')"
/>
</template>
<csc-fax-to-mail-settings
v-if="selectedTab === 'fax2mail'"
/>
<csc-mail-to-fax-settings
v-if="selectedTab === 'mail2fax'"
/>
</csc-page-sticky-tabs>
</template>
<script>
import _ from 'lodash'
import { mapState } from 'vuex'
import CscInputSaveable from 'components/form/CscInputSaveable'
import CscPage from 'components/CscPage'
import CscSpinner from 'components/CscSpinner'
import { mapWaitingActions, mapWaitingGetters } from 'vue-wait'
import CscFax2MailDestinationForm from 'components/pages/FaxSettings/CscFax2MailDestinationForm'
import CscFax2MailDestination from 'components/pages/FaxSettings/CscFax2MailDestination'
import CscRemoveDialog from 'components/CscRemoveDialog'
import { showGlobalError } from 'src/helpers/ui'
import CscPageStickyTabs from 'components/CscPageStickyTabs'
import CscFaxToMailSettings from 'components/pages/FaxSettings/CscFaxToMailSettings'
import CscMailToFaxSettings from 'components/pages/FaxSettings/CscMailToFaxSettings'
export default {
name: 'CscPageFaxSettings',
components: {
CscFax2MailDestination,
CscFax2MailDestinationForm,
CscSpinner,
CscPage,
CscInputSaveable
CscPageStickyTabs,
CscMailToFaxSettings,
CscFaxToMailSettings
},
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',
fieldUpdateAction: '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.fieldUpdateAction({ field, value })
this.updateDataFromStore()
} catch (err) {
showGlobalError(err?.message)
}
},
restoreName () {
this.faxToMailSettings.name = this.faxServerSettings.name
},
async updateDestinations (destinationItems, beforeUpdateUI = () => {}) {
try {
await this.fieldUpdateAction({
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.fieldUpdateAction({
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)
selectedTab: 'fax2mail'
}
}
}

@ -107,18 +107,22 @@ export class RtcEnginePlugin {
})
this.client.onConnect(() => {
this.events.emit('connected')
const conferenceNetwork = this.client.getNetworkByTag('conference')
conferenceNetwork.onConnect(() => {
this.events.emit('conference-network-connected', conferenceNetwork)
}).onDisconnect(() => {
this.events.emit('conference-network-disconnected', conferenceNetwork)
})
const sipNetwork = this.client.getNetworkByTag('sip')
sipNetwork.onConnect(() => {
this.events.emit('sip-network-connected', sipNetwork)
}).onDisconnect(() => {
this.events.emit('sip-network-disconnected', sipNetwork)
})
try {
const conferenceNetwork = this.client.getNetworkByTag('conference')
conferenceNetwork.onConnect(() => {
this.events.emit('conference-network-connected', conferenceNetwork)
}).onDisconnect(() => {
this.events.emit('conference-network-disconnected', conferenceNetwork)
})
const sipNetwork = this.client.getNetworkByTag('sip')
sipNetwork.onConnect(() => {
this.events.emit('sip-network-connected', sipNetwork)
}).onDisconnect(() => {
this.events.emit('sip-network-disconnected', sipNetwork)
})
} catch (e) {
reject(new Error('Unable to connect to a specific network by RTCEngine client'))
}
resolve()
})
this.client.onDisconnect(() => {

@ -195,6 +195,14 @@ export default function routes (app) {
meta: {
title: i18n.t('navigation.faxSettings.title'),
subtitle: i18n.t('navigation.faxSettings.subTitle')
},
async beforeEnter (routeTo, routeFrom, next) {
await app.store.dispatch('user/initUser')
if (app.store.getters['user/hasFaxCapability']) {
next()
} else {
next('/')
}
}
},
{

@ -99,8 +99,8 @@ export default {
updateOwnPhoneTimeoutError: null
},
getters: {
hasFaxCapability (state, getters, rootState, rootGetters) {
return rootGetters['user/hasFaxCapability']
hasFaxCapabilityAndFaxActive (state, getters, rootState, rootGetters) {
return rootGetters['user/hasFaxCapabilityAndFaxActive']
},
subscriberId (state, getters, rootState, rootGetters) {
return rootGetters['user/getSubscriberId']

@ -1,14 +1,19 @@
import _ from 'lodash'
import {
getFaxServerSettings,
setFaxServerField
setFaxServerField,
getMailToFaxSettings,
setMailToFaxSettingField
} from '../api/fax'
export default {
namespaced: true,
state: {
faxServerSettingsInitialized: false,
faxServerSettings: {}
faxServerSettings: {},
mailToFaxSettingsInitialized: false,
mailToFaxSettings: {}
},
getters: {
subscriberId (state, getters, rootState, rootGetters) {
@ -21,6 +26,11 @@ export default {
state.faxServerSettings = res.faxServerSettings
state.faxServerSettingsInitialized = true
}
if (_.has(res, 'mailToFaxSettings')) {
state.mailToFaxSettings = res.mailToFaxSettings
state.mailToFaxSettingsInitialized = true
}
}
},
actions: {
@ -30,7 +40,7 @@ export default {
faxServerSettings
})
},
async fieldUpdateAction (context, options) {
async faxServerSettingsUpdateAction (context, options) {
const faxServerSettings = await setFaxServerField({
subscriberId: context.getters.subscriberId,
field: options.field,
@ -40,6 +50,23 @@ export default {
faxServerSettings
})
context.commit('user/updateFaxActiveCapabilityState', faxServerSettings.active, { root: true })
},
async loadMailToFaxSettingsAction (context) {
const mailToFaxSettings = await getMailToFaxSettings(context.getters.subscriberId)
context.commit('settingsSucceeded', {
mailToFaxSettings
})
},
async mailToFaxSettingsUpdateAction (context, options) {
const mailToFaxSettings = await setMailToFaxSettingField({
subscriberId: context.getters.subscriberId,
field: options.field,
value: options.value
})
context.commit('settingsSucceeded', {
mailToFaxSettings
})
}
}
}

@ -83,6 +83,10 @@ export default {
return state.features.sendFax
},
hasFaxCapability (state) {
return state.capabilities !== null &&
state.capabilities.faxserver
},
hasFaxCapabilityAndFaxActive (state) {
return state.capabilities !== null &&
state.capabilities.faxserver &&
state.capabilities.faxactive
@ -360,7 +364,7 @@ export default {
}
},
async forwardHome (context) {
if (context.rootState.route.path === '/user/home' && !context.getters.isRtcEngineUiVisible) {
if (context.rootState.route?.path === '/user/home' && !context.getters.isRtcEngineUiVisible) {
await router.push({ path: '/user/conversations' })
}
},

Loading…
Cancel
Save