TT#81403 Admin Subscriber should be able to set seats password

Change-Id: I5de916e170301d47ad9be12f46893a00015abd66
changes/56/41256/2
Carlo Venusino 5 years ago
parent 3c147c1cae
commit 06624d58b6

390
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -20,11 +20,13 @@
"uuid": "3.2.1", "uuid": "3.2.1",
"vue": "^2.5.0", "vue": "^2.5.0",
"vue-i18n": "7.3.2", "vue-i18n": "7.3.2",
"vue-password-strength-meter": "1.7.2",
"vue-resource": "1.3.4", "vue-resource": "1.3.4",
"vue-router": "^2.5.0", "vue-router": "^2.5.0",
"vuelidate": "0.7.4", "vuelidate": "0.7.4",
"vuex": "2.5.0", "vuex": "2.5.0",
"vuex-router-sync": "4.3.2" "vuex-router-sync": "4.3.2",
"zxcvbn": "4.4.2"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.4.0", "autoprefixer": "^6.4.0",

@ -5,6 +5,7 @@ import {
getSubscriberAndPreferences, getSubscribers, getSubscriberAndPreferences, getSubscribers,
setDisplayName, setDisplayName,
setPbxExtension, setPbxExtension,
setPbxWebPassword,
setPbxGroupIds, setPbxGroupIds,
setSubscriberNumbers setSubscriberNumbers
} from "./subscriber"; } from "./subscriber";
@ -112,6 +113,7 @@ export function createSeat(seat) {
username: _.kebabCase(seat.name), username: _.kebabCase(seat.name),
password: createId(), password: createId(),
display_name: seat.name, display_name: seat.name,
webpassword: seat.webPassword.length > 0 ? seat.webPassword : null,
is_pbx_group: false, is_pbx_group: false,
pbx_extension: seat.extension, pbx_extension: seat.extension,
pbx_group_ids: seat.groups pbx_group_ids: seat.groups
@ -188,6 +190,28 @@ export function setSeatExtension(options) {
}); });
} }
/**
* @param options
* @param options.seatId
* @param options.seatWebPassword
*/
export function setSeatWebPassword(options) {
return new Promise((resolve, reject)=>{
Promise.resolve().then(()=>{
return setPbxWebPassword(options.seatId, options.seatWebPassword);
}).then(()=>{
return getSubscriberAndPreferences(options.seatId);
}).then((result)=>{
resolve({
seat: result.subscriber,
preferences: result.preferences
});
}).catch((err)=>{
reject(err);
});
});
}
/** /**
* @param options * @param options
* @param options.seatId * @param options.seatId

@ -290,6 +290,10 @@ export function setPbxExtension(id, pbxExtension) {
return setField(id, 'pbx_extension', pbxExtension); return setField(id, 'pbx_extension', pbxExtension);
} }
export function setPbxWebPassword(id, pbxWebPassword) {
return setField(id, 'webpassword', pbxWebPassword);
}
export function setPbxHuntPolicy(id, pbxHuntPolicy) { export function setPbxHuntPolicy(id, pbxHuntPolicy) {
return setField(id, 'pbx_hunt_policy', pbxHuntPolicy); return setField(id, 'pbx_hunt_policy', pbxHuntPolicy);
} }
@ -515,4 +519,3 @@ export function changePassword(subscriber, newPassword) {
}); });
}); });
} }

@ -0,0 +1,90 @@
<template>
<csc-dialog
:value="value"
:loading="loading"
title-icon="vpn_key"
title="Change password"
ref="dialog"
@input="$emit('input')"
class="csc-pbx-password-dialog"
>
<div
slot="content"
>
<csc-change-password-form
ref="changePasswordForm"
:loading="loading"
@validation-succeeded="validationSucceeded"
/>
</div>
<div
slot="actions"
>
<q-btn
icon="check"
unelevated
color="primary"
:disable="loading"
:loading="loading"
@click="$refs.changePasswordForm.submit()"
>
{{ $t('buttons.save') }}
</q-btn>
</div>
</csc-dialog>
</template>
<script>
import {
QBtn
} from 'quasar-framework'
import CscChangePasswordForm from './form/CscChangePasswordForm'
import CscDialog from './CscDialog'
export default {
name: 'csc-change-password-dialog',
components: {
CscDialog,
CscChangePasswordForm,
QBtn
},
props: {
value: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
}
},
methods: {
validationSucceeded (payload) {
this.$emit('change-password', payload)
},
open(){
this.$refs.dialog.open();
this.$refs.changePasswordForm.resetForm();
},
close(){
this.$refs.dialog.close();
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-pbx-password-dialog
.csc-dialog-actions,
.csc-dialog-content
padding 15px
.q-input
width 100%
min-width 270px
.q-if:before,
.q-icon
color white
.Password__strength-meter:after,
.Password__strength-meter:before
border-color #3b3440
.Password
width 100%
margin 20px 0px 30px
</style>

@ -0,0 +1,168 @@
<template>
<div
class="csc-form"
>
<q-field
:error-label="errorMessagePass"
>
<q-input
dark
ref="passwordInput"
v-model.trim="password"
clearable
:before="[{
icon: 'lock'
}]"
:float-label="$t('pbxConfig.typePassword')"
type="password"
:disable="loading"
:error="$v.password.$error"
@blur="$v.password.$touch()"
>
<div
slot="prepend"
>
<q-icon
name="lock"
/>
</div>
</q-input>
</q-field>
<password-strength-meter
v-model="passwordScored"
class="q-psm"
:strength-meter-only="true"
@score="strengthMeterScoreUpdate"
/>
<q-field
:error-label="errorMessagePassRetype"
>
<q-input
ref="passwordRetypeInput"
v-model.trim="passwordRetype"
clearable
icon="lock"
dark
:before="[{
icon: 'lock'
}]"
:float-label="$t('pbxConfig.retypePassword')"
type="password"
:disable="loading"
:error="$v.passwordRetype.$error"
@blur="$v.passwordRetype.$touch();onRetypeBlur()"
>
<div slot="prepend">
<q-icon name="lock" />
</div>
</q-input>
</q-field>
</div>
</template>
<script>
import PasswordStrengthMeter from 'vue-password-strength-meter'
import {
required
} from 'vuelidate/lib/validators'
import {
QItem,
QList,
QInput,
QField
} from 'quasar-framework'
export default {
name: 'csc-change-password-form',
components: {
QItem,
QList,
QInput,
QField,
PasswordStrengthMeter
},
props: {
noSubmit: false,
loading: {
type: Boolean,
default: false
}
},
data () {
return {
password: '',
passwordRetype: '',
passwordScored: '',
passwordStrengthScore: null
}
},
validations: {
password: {
required,
passwordStrength () {
return this.passwordStrengthScore >= 2
}
},
passwordRetype: {
required,
sameAsPassword (val) {
return val == this.password
}
}
},
watch: {
password (value) {
if (value === null || value === undefined) {
this.passwordScored = ''
}
else {
this.passwordScored = value
}
}
},
computed: {
errorMessagePass() {
if (!this.$v.password.passwordStrength) {
return this.$t('pbxConfig.errorPasswordStrength')
}
},
errorMessagePassRetype() {
if (!this.$v.passwordRetype.sameAsPassword) {
return this.$t('pbxConfig.errorPasswordNotEqual')
}
}
},
methods: {
strengthMeterScoreUpdate (score) {
this.passwordStrengthScore = score
},
resetForm(){
this.password = this.passwordRetype = this.passwordScored = "";
this.passwordStrengthScore = null;
this.$v.$reset();
},
submit () {
this.$v.$touch()
if (this.$v.$invalid) {
this.$emit('validation-failed')
}
else {
this.$emit('validation-succeeded', {
password: this.password,
strengthScore: this.passwordStrengthScore
})
}
},
onRetypeBlur(){
if(this.noSubmit && !this.$v.$invalid){
this.$emit('validation-succeeded', {
password: this.password,
strengthScore: this.passwordStrengthScore
})
}
}
}
}
</script>

@ -70,6 +70,25 @@
</csc-list-menu-item> </csc-list-menu-item>
</template> </template>
<template slot="body"> <template slot="body">
<q-field
:labelWidth="labelWidth"
label=" "
>
<q-btn
icon="vpn_key"
flat
color="primary"
v-if="expanded"
@click="showPasswordDialog"
>
{{ $t('pbxConfig.editPassword') }}
</q-btn>
</q-field>
<csc-change-password-dialog
ref="changePasswordDialog"
:loading="false"
@change-password="changeWebPassword({ password: $event.password })"
/>
<q-field <q-field
:labelWidth="labelWidth" :labelWidth="labelWidth"
:label="$t('pbxConfig.name')" :label="$t('pbxConfig.name')"
@ -114,6 +133,28 @@
/> />
</csc-fade> </csc-fade>
</q-field> </q-field>
<!-- <q-field
:labelWidth="labelWidth"
:label="$t('pbxConfig.webPassword')"
>
<q-input
dark
v-model="changes.webPassword"
@keyup.enter="save"
/>
<csc-fade>
<csc-form-save-button
v-if="hasWebPasswordChanged"
@click="save"
/>
</csc-fade>
<csc-fade>
<csc-form-reset-button
v-if="hasWebPasswordChanged"
@click="resetWebPassword"
/>
</csc-fade>
</q-field> -->
<q-field <q-field
:labelWidth="labelWidth" :labelWidth="labelWidth"
:label="$t('pbxConfig.primaryNumber')" :label="$t('pbxConfig.primaryNumber')"
@ -237,6 +278,7 @@
import CscListMenuItem from "../../CscListMenuItem"; import CscListMenuItem from "../../CscListMenuItem";
import CscFormSaveButton from "../../form/CscFormSaveButton" import CscFormSaveButton from "../../form/CscFormSaveButton"
import CscFormResetButton from "../../form/CscFormResetButton" import CscFormResetButton from "../../form/CscFormResetButton"
import CscChangePasswordDialog from "../../CscChangePasswordDialog"
import numberFilter from '../../../filters/number' import numberFilter from '../../../filters/number'
export default { export default {
name: 'csc-pbx-seat', name: 'csc-pbx-seat',
@ -279,7 +321,8 @@
QTransition, QTransition,
QList, QList,
CscFormSaveButton, CscFormSaveButton,
CscFormResetButton CscFormResetButton,
CscChangePasswordDialog
}, },
computed: { computed: {
getPrimaryNumber() { getPrimaryNumber() {
@ -291,6 +334,9 @@
hasExtensionChanged() { hasExtensionChanged() {
return this.changes.extension !== this.seat.pbx_extension; return this.changes.extension !== this.seat.pbx_extension;
}, },
hasWebPasswordChanged() {
return this.changes.webPassword !== this.seat.webpassword;
},
hasAliasNumbersChanged() { hasAliasNumbersChanged() {
return !_.isEqual(this.changes.aliasNumbers.sort(), this.getAliasNumberIds().sort()); return !_.isEqual(this.changes.aliasNumbers.sort(), this.getAliasNumberIds().sort());
}, },
@ -323,6 +369,7 @@
name: this.seat.display_name, name: this.seat.display_name,
extension: this.seat.pbx_extension, extension: this.seat.pbx_extension,
aliasNumbers: this.getAliasNumberIds(), aliasNumbers: this.getAliasNumberIds(),
webPassword: this.seat.webpassword,
groups: this.getGroupIds(), groups: this.getGroupIds(),
soundSet: this.getSoundSetId() soundSet: this.getSoundSetId()
}; };
@ -341,6 +388,9 @@
resetExtension() { resetExtension() {
this.changes.extension = this.seat.pbx_extension; this.changes.extension = this.seat.pbx_extension;
}, },
resetWebPassword() {
this.changes.webPassword = this.seat.webpassword;
},
resetAliasNumbers() { resetAliasNumbers() {
this.changes.aliasNumbers = this.getAliasNumberIds(); this.changes.aliasNumbers = this.getAliasNumberIds();
}, },
@ -372,6 +422,12 @@
seatExtension: this.changes.extension seatExtension: this.changes.extension
}); });
} }
if(this.hasWebPasswordChanged) {
this.$emit('save-webpassword', {
seatId: this.seat.id,
seatWebPassword: this.changes.webpassword
});
}
if(this.hasAliasNumbersChanged) { if(this.hasAliasNumbersChanged) {
this.$emit('save-alias-numbers', { this.$emit('save-alias-numbers', {
seatId: this.seat.id, seatId: this.seat.id,
@ -392,6 +448,16 @@
}); });
} }
},
showPasswordDialog(){
this.$refs.changePasswordDialog.open();
},
async changeWebPassword(data){
await this.$store.dispatch('pbxSeats/setSeatWebPassword', {
seatId: this.seat.id,
seatWebPassword: data.password
});
this.$refs.changePasswordDialog.close();
} }
}, },
watch: { watch: {

@ -28,6 +28,11 @@
@input="$v.data.extension.$touch" @input="$v.data.extension.$touch"
/> />
</q-field> </q-field>
<csc-change-password-form
ref="changePasswordForm"
:noSubmit="true"
@validation-succeeded="webPassValidationSucceeded"
/>
<q-field> <q-field>
<q-select <q-select
dark dark
@ -110,6 +115,7 @@
QTooltip QTooltip
} from 'quasar-framework' } from 'quasar-framework'
import CscObjectSpinner from "../../CscObjectSpinner"; import CscObjectSpinner from "../../CscObjectSpinner";
import CscChangePasswordForm from "../../form/CscChangePasswordForm"
export default { export default {
name: 'csc-pbx-seat-add-form', name: 'csc-pbx-seat-add-form',
props: [ props: [
@ -120,6 +126,7 @@
], ],
components: { components: {
CscObjectSpinner, CscObjectSpinner,
CscChangePasswordForm,
QBtn, QBtn,
QInnerLoading, QInnerLoading,
QSpinnerDots, QSpinnerDots,
@ -139,6 +146,9 @@
required, required,
numeric, numeric,
maxLength: maxLength(64) maxLength: maxLength(64)
},
webPassword: {
maxLength: maxLength(64)
} }
} }
}, },
@ -184,10 +194,24 @@
}); });
} }
}, },
webPasswordErrorMessage() {
if (!this.$v.data.webPassword.required) {
return this.$t('validationErrors.fieldRequired', {
field: this.$t('pbxConfig.webPassword')
});
}
else if (!this.$v.data.webPassword.maxLength) {
return this.$t('validationErrors.maxLength', {
field: this.$t('pbxConfig.webPassword'),
maxLength: this.$v.data.webPassword.$params.maxLength.max
});
}
},
seatModel() { seatModel() {
return { return {
name: this.data.name, name: this.data.name,
extension: this.data.extension, extension: this.data.extension,
webPassword: this.data.webPassword,
aliasNumbers: this.data.aliasNumbers, aliasNumbers: this.data.aliasNumbers,
groups: this.data.groups, groups: this.data.groups,
soundSet: this.data.soundSet soundSet: this.data.soundSet
@ -199,6 +223,7 @@
return { return {
name: '', name: '',
extension: '', extension: '',
webPassword: '',
aliasNumbers: [], aliasNumbers: [],
groups: [], groups: [],
soundSet: null soundSet: null
@ -209,10 +234,14 @@
}, },
save() { save() {
this.$emit('save', this.seatModel); this.$emit('save', this.seatModel);
this.$refs.changePasswordForm.resetForm();
}, },
reset() { reset() {
this.data = this.getDefaults(); this.data = this.getDefaults();
this.$v.$reset(); this.$v.$reset();
},
webPassValidationSucceeded(data){
this.data.webPassword = data.password;
} }
} }
} }
@ -220,4 +249,11 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/app.common.styl'; @import '../../../themes/app.common.styl';
.Password__strength-meter
margin-top 20px !important
.Password__strength-meter:after,
.Password__strength-meter:before
border-color #3b3440 !important
.Password
max-width 100%
</style> </style>

@ -263,7 +263,7 @@
showToast(this.getSeatUpdateToastMessage); showToast(this.getSeatUpdateToastMessage);
} }
else if(state === RequestState.failed) { else if(state === RequestState.failed) {
showGlobalError(this.seatUpdateError); showGlobalError(this.seatUpdateError, 5000);
} }
}, },
seatRemovalState(state) { seatRemovalState(state) {

@ -11,7 +11,7 @@ export function stopLoading() {
Loading.hide(); Loading.hide();
} }
export function showGlobalError(message) { export function showGlobalError(message, timeout) {
const alert = Alert.create({ const alert = Alert.create({
html: message, html: message,
position: 'top-center', position: 'top-center',
@ -19,7 +19,7 @@ export function showGlobalError(message) {
leave: 'fadeOut', leave: 'fadeOut',
color: 'negative' color: 'negative'
}); });
setTimeout(()=>{ alert.dismiss(); }, 2000); setTimeout(()=>{ alert.dismiss(); }, timeout || 2000);
} }
export function showGlobalWarning(message, timeout) { export function showGlobalWarning(message, timeout) {

@ -399,6 +399,7 @@
"group": "Group", "group": "Group",
"groups": "Groups", "groups": "Groups",
"extension": "Extension", "extension": "Extension",
"webPassword": "Password",
"groupName": "Group Name", "groupName": "Group Name",
"huntPolicy": "Hunt Policy", "huntPolicy": "Hunt Policy",
"huntTimeout": "Hunt Timeout", "huntTimeout": "Hunt Timeout",
@ -420,6 +421,11 @@
"addSeat": "Add Seat", "addSeat": "Add Seat",
"removeSeat": "Remove seat", "removeSeat": "Remove seat",
"removeSeatTitle": "Remove seat", "removeSeatTitle": "Remove seat",
"editPassword": "Change Password",
"typePassword": "Password",
"retypePassword": "Password Retype",
"errorPasswordStrength": "Password is not strong enough",
"errorPasswordNotEqual": "Passwords must be equal",
"removeSeatText": "You are about to remove seat {seat}", "removeSeatText": "You are about to remove seat {seat}",
"devicesTitle": "Devices", "devicesTitle": "Devices",
"deviceStationName": "Station name", "deviceStationName": "Station name",

@ -17,7 +17,8 @@ import {
setSeatExtension, setSeatExtension,
setSeatGroups, setSeatGroups,
setSeatNumbers, setSeatNumbers,
setSeatSoundSet setSeatSoundSet,
setSeatWebPassword
} from "../api/pbx-seats"; } from "../api/pbx-seats";
export default { export default {
@ -320,6 +321,20 @@ export default {
context.commit('seatUpdateFailed', err.message); context.commit('seatUpdateFailed', err.message);
}); });
}, },
setSeatWebPassword(context, options) {
context.commit('seatUpdateRequesting', {
seatId: options.seatId,
seatField: i18n.t('pbxConfig.webPassword')
});
setSeatWebPassword({
seatId: options.seatId,
seatWebPassword: options.seatWebPassword
}).then((result) => {
context.commit('seatUpdateSucceeded', result);
}).catch((err) => {
context.commit('seatUpdateFailed', err.message);
});
},
setSeatGroups(context, options) { setSeatGroups(context, options) {
context.commit('seatUpdateRequesting', { context.commit('seatUpdateRequesting', {
seatId: options.seatId, seatId: options.seatId,

Loading…
Cancel
Save