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",
"vue": "^2.5.0",
"vue-i18n": "7.3.2",
"vue-password-strength-meter": "1.7.2",
"vue-resource": "1.3.4",
"vue-router": "^2.5.0",
"vuelidate": "0.7.4",
"vuex": "2.5.0",
"vuex-router-sync": "4.3.2"
"vuex-router-sync": "4.3.2",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"autoprefixer": "^6.4.0",

@ -5,6 +5,7 @@ import {
getSubscriberAndPreferences, getSubscribers,
setDisplayName,
setPbxExtension,
setPbxWebPassword,
setPbxGroupIds,
setSubscriberNumbers
} from "./subscriber";
@ -112,6 +113,7 @@ export function createSeat(seat) {
username: _.kebabCase(seat.name),
password: createId(),
display_name: seat.name,
webpassword: seat.webPassword.length > 0 ? seat.webPassword : null,
is_pbx_group: false,
pbx_extension: seat.extension,
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.seatId

@ -290,6 +290,10 @@ export function setPbxExtension(id, pbxExtension) {
return setField(id, 'pbx_extension', pbxExtension);
}
export function setPbxWebPassword(id, pbxWebPassword) {
return setField(id, 'webpassword', pbxWebPassword);
}
export function setPbxHuntPolicy(id, 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>
</template>
<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
:labelWidth="labelWidth"
:label="$t('pbxConfig.name')"
@ -114,6 +133,28 @@
/>
</csc-fade>
</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
:labelWidth="labelWidth"
:label="$t('pbxConfig.primaryNumber')"
@ -237,6 +278,7 @@
import CscListMenuItem from "../../CscListMenuItem";
import CscFormSaveButton from "../../form/CscFormSaveButton"
import CscFormResetButton from "../../form/CscFormResetButton"
import CscChangePasswordDialog from "../../CscChangePasswordDialog"
import numberFilter from '../../../filters/number'
export default {
name: 'csc-pbx-seat',
@ -279,7 +321,8 @@
QTransition,
QList,
CscFormSaveButton,
CscFormResetButton
CscFormResetButton,
CscChangePasswordDialog
},
computed: {
getPrimaryNumber() {
@ -291,6 +334,9 @@
hasExtensionChanged() {
return this.changes.extension !== this.seat.pbx_extension;
},
hasWebPasswordChanged() {
return this.changes.webPassword !== this.seat.webpassword;
},
hasAliasNumbersChanged() {
return !_.isEqual(this.changes.aliasNumbers.sort(), this.getAliasNumberIds().sort());
},
@ -323,6 +369,7 @@
name: this.seat.display_name,
extension: this.seat.pbx_extension,
aliasNumbers: this.getAliasNumberIds(),
webPassword: this.seat.webpassword,
groups: this.getGroupIds(),
soundSet: this.getSoundSetId()
};
@ -341,6 +388,9 @@
resetExtension() {
this.changes.extension = this.seat.pbx_extension;
},
resetWebPassword() {
this.changes.webPassword = this.seat.webpassword;
},
resetAliasNumbers() {
this.changes.aliasNumbers = this.getAliasNumberIds();
},
@ -372,6 +422,12 @@
seatExtension: this.changes.extension
});
}
if(this.hasWebPasswordChanged) {
this.$emit('save-webpassword', {
seatId: this.seat.id,
seatWebPassword: this.changes.webpassword
});
}
if(this.hasAliasNumbersChanged) {
this.$emit('save-alias-numbers', {
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: {

@ -28,6 +28,11 @@
@input="$v.data.extension.$touch"
/>
</q-field>
<csc-change-password-form
ref="changePasswordForm"
:noSubmit="true"
@validation-succeeded="webPassValidationSucceeded"
/>
<q-field>
<q-select
dark
@ -110,6 +115,7 @@
QTooltip
} from 'quasar-framework'
import CscObjectSpinner from "../../CscObjectSpinner";
import CscChangePasswordForm from "../../form/CscChangePasswordForm"
export default {
name: 'csc-pbx-seat-add-form',
props: [
@ -120,6 +126,7 @@
],
components: {
CscObjectSpinner,
CscChangePasswordForm,
QBtn,
QInnerLoading,
QSpinnerDots,
@ -139,6 +146,9 @@
required,
numeric,
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() {
return {
name: this.data.name,
extension: this.data.extension,
webPassword: this.data.webPassword,
aliasNumbers: this.data.aliasNumbers,
groups: this.data.groups,
soundSet: this.data.soundSet
@ -199,6 +223,7 @@
return {
name: '',
extension: '',
webPassword: '',
aliasNumbers: [],
groups: [],
soundSet: null
@ -209,10 +234,14 @@
},
save() {
this.$emit('save', this.seatModel);
this.$refs.changePasswordForm.resetForm();
},
reset() {
this.data = this.getDefaults();
this.$v.$reset();
},
webPassValidationSucceeded(data){
this.data.webPassword = data.password;
}
}
}
@ -220,4 +249,11 @@
<style lang="stylus" rel="stylesheet/stylus">
@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>

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

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

@ -399,6 +399,7 @@
"group": "Group",
"groups": "Groups",
"extension": "Extension",
"webPassword": "Password",
"groupName": "Group Name",
"huntPolicy": "Hunt Policy",
"huntTimeout": "Hunt Timeout",
@ -420,6 +421,11 @@
"addSeat": "Add Seat",
"removeSeat": "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}",
"devicesTitle": "Devices",
"deviceStationName": "Station name",

@ -17,7 +17,8 @@ import {
setSeatExtension,
setSeatGroups,
setSeatNumbers,
setSeatSoundSet
setSeatSoundSet,
setSeatWebPassword
} from "../api/pbx-seats";
export default {
@ -320,6 +321,20 @@ export default {
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) {
context.commit('seatUpdateRequesting', {
seatId: options.seatId,

Loading…
Cancel
Save