TT#80753 Use a smaller preview version of each PBX device image in order to save bandwidth and improve render performance

Change-Id: I84b03ad7146af767ae9c6bb0e336fca2ce8f5874
mr9.1.1
Hans-Peter Herzog 5 years ago
parent b038c533d8
commit 075452c698

4
env/run_csc_ui vendored

@ -38,13 +38,13 @@ echo "JFYI, important components versions:"
echo -n "node --version : " && node --version
echo -n "yarn --version : " && yarnpkg --version
echo "Configuring Vue.js/Quasar dev environment, running 'npm ci'..."
echo "Configuring Vue.js/Quasar dev environment, running 'yarnpkg install'..."
if ! yarnpkg install ; then
echo "ERROR: cannot install all npm dependencies. Aborting."
exit 1
fi
echo "Starting Quasar dev environment, running 'npm run dev'..."
echo "Starting Quasar dev environment, running 'yarnpkg run dev'..."
if ! yarnpkg run dev ; then
echo "ERROR: cannot run quasar dev environment. Aborting."
exit 1

@ -50,6 +50,7 @@
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"generate-password": "^1.5.1",
"parseuri": "^0.0.6"
},
"browserslist": [

@ -73,27 +73,34 @@ export function getModel (id) {
})
}
export function getModelFrontImage (id) {
return new Promise((resolve) => {
Vue.http.get('api/pbxdevicemodelimages/' + id, {
export async function getModelImage (id, type) {
try {
const res = await Vue.http.get('api/pbxdevicemodelimages/' + id, {
responseType: 'blob',
params: {
type: 'front'
type: type
}
}).then((res) => {
resolve({
})
return {
id: id,
url: URL.createObjectURL(res.body),
blob: res.body
})
}).catch(() => {
resolve({
}
} catch (err) {
return {
id: id,
url: null,
blob: null
})
})
})
}
}
}
export async function getModelFrontImage (id) {
return getModelImage(id, 'front')
}
export async function getModelFrontThumbnailImage (id) {
return getModelImage(id, 'front_thumb')
}
export function getAllSoundSets (options) {

@ -162,7 +162,7 @@ export function setDeviceKeys (deviceId, keys) {
})
}
export function loadDeviceModel (modelId) {
export async function loadDeviceModel (modelId) {
return new Promise((resolve, reject) => {
Promise.all([
getModel(modelId),

@ -80,16 +80,29 @@ export function getSeatList (options) {
return new Promise((resolve, reject) => {
const page = _.get(options, 'page', 1)
const displayName = _.get(options, 'display_name', null)
const pbxExtension = _.get(options, 'pbx_extension', null)
const primaryNumber = _.get(options, 'primary_number', null)
const aliasNumber = _.get(options, 'alias_number', null)
const params = {
page: page,
order_by: PBX_CONFIG_ORDER_BY,
order_by_direction: PBX_CONFIG_ORDER_DIRECTION
}
if (displayName) {
params.display_name = displayName
}
if (pbxExtension) {
params.pbx_extension = pbxExtension
}
if (primaryNumber) {
params.primary_number = primaryNumber
}
if (aliasNumber) {
params.alias_number = aliasNumber
}
Promise.all([
getSeats({
params: displayName ? _.merge({
display_name: displayName
}, params) : params
params: params
}),
getGroupsOnly({
all: true

@ -39,12 +39,11 @@
</q-card-section>
<q-card-section>
<form>
<q-input
<csc-input
v-model="username"
class="q-mb-sm"
type="text"
max-length="128"
flat
:label="$t('pages.login.username')"
:disable="loginRequesting"
autofocus
@ -58,26 +57,34 @@
name="person"
/>
</template>
</q-input>
<q-input
</csc-input>
<csc-input-password
v-model="password"
class="q-mb-sm"
type="password"
max-length="32"
flat
:label="$t('pages.login.password')"
:disable="loginRequesting"
clearable
@keyup.enter="login()"
>
<template
slot="prepend"
>
<q-icon
name="lock"
@keypress.enter="login()"
/>
</template>
</q-input>
<!-- <q-input-->
<!-- v-model="password"-->
<!-- class="q-mb-sm"-->
<!-- type="password"-->
<!-- max-length="32"-->
<!-- flat-->
<!-- :label="$t('pages.login.password')"-->
<!-- :disable="loginRequesting"-->
<!-- clearable-->
<!-- @keyup.enter="login()"-->
<!-- >-->
<!-- <template-->
<!-- slot="prepend"-->
<!-- >-->
<!-- <q-icon-->
<!-- name="lock"-->
<!-- />-->
<!-- </template>-->
<!-- </q-input>-->
</form>
</q-card-section>
<q-card-actions
@ -120,9 +127,13 @@ import {
} from '../i18n'
import CscLanguageMenu from './CscLanguageMenu'
import CscSpinner from 'components/CscSpinner'
import CscInputPassword from 'components/form/CscInputPassword'
import CscInput from 'components/form/CscInput'
export default {
name: 'Login',
components: {
CscInput,
CscInputPassword,
CscSpinner,
CscLanguageMenu
},

@ -0,0 +1,65 @@
<template>
<csc-page
:style="pageStyle"
>
<q-page-sticky
ref="pageSticky"
class="bg-secondary q-pt-md"
style="z-index: 10"
expand
position="top"
>
<slot
name="header"
/>
<q-separator />
<div
class="col-12"
>
<slot
name="toolbar"
/>
</div>
<q-resize-observer
@resize="computeTopMargin"
/>
</q-page-sticky>
<slot />
</csc-page>
</template>
<script>
import CscPage from 'components/CscPage'
export default {
name: 'CscPageSticky',
components: {
CscPage
},
data () {
return {
topMargin: 0
}
},
computed: {
pageStyle () {
return {
paddingTop: this.topMargin + 'px'
}
}
},
mounted () {
this.computeTopMargin()
},
methods: {
input ($event) {
this.$emit('input', $event)
this.computeTopMargin()
this.$nextTick(() => {
this.computeTopMargin()
})
},
computeTopMargin () {
this.topMargin = this.$refs.pageSticky.$el.offsetHeight + 36
}
}
}
</script>

@ -32,6 +32,7 @@
<script>
import CscPage from 'components/CscPage'
export default {
name: 'QPageStickyTabs',
components: {
CscPage
},

@ -2,7 +2,7 @@
<div
class="csc-form"
>
<q-input
<csc-input-password
ref="passwordInput"
v-model.trim="password"
clearable
@ -13,15 +13,7 @@
:error="$v.password.$error"
:error-message="errorMessagePass"
@blur="$v.password.$touch()"
>
<template
v-slot:prepend
>
<q-icon
name="lock"
/>
</template>
</q-input>
<password-strength-meter
v-model="passwordScored"
class="full-width"
@ -29,7 +21,7 @@
:strength-meter-only="true"
@score="strengthMeterScoreUpdate"
/>
<q-input
<csc-input-password
ref="passwordRetypeInput"
v-model.trim="passwordRetype"
clearable
@ -40,15 +32,7 @@
:error="$v.passwordRetype.$error"
:error-message="errorMessagePassRetype"
@blur="$v.passwordRetype.$touch();onRetypeBlur()"
>
<template
v-slot:prepend
>
<q-icon
name="lock"
/>
</template>
</q-input>
</div>
</template>
@ -57,9 +41,11 @@ import PasswordStrengthMeter from 'vue-password-strength-meter'
import {
required
} from 'vuelidate/lib/validators'
import CscInputPassword from 'components/form/CscInputPassword'
export default {
name: 'CscChangePasswordForm',
components: {
CscInputPassword,
PasswordStrengthMeter
},
props: {

@ -1,7 +1,7 @@
<template>
<q-input
ref="input"
:value="value"
:clearable="false"
v-bind="$attrs"
@input="$emit('input', $event)"
v-on="$listeners"
@ -24,16 +24,18 @@
<template
v-slot:append
>
<slot
name="append"
/>
<q-btn
v-if="value !== ''"
v-if="$attrs.clearable !== undefined && value !== ''"
icon="clear"
color="white"
flat
dense
@click="$emit('clear', $event)"
/>
<slot
name="append"
tabindex="-1"
:disable="$attrs.disable"
@click="clear"
/>
</template>
</q-input>
@ -42,6 +44,7 @@
<script>
import CscSpinner from 'components/CscSpinner'
export default {
name: 'CscInput',
components: {
CscSpinner
},
@ -52,9 +55,17 @@ export default {
}
},
date () {
return {}
return {
}
},
mounted () {
},
methods: {
clear () {
this.$emit('input', '')
this.$emit('clear')
}
}
}
</script>

@ -0,0 +1,94 @@
<template>
<csc-input
ref="input"
v-bind="$attrs"
:type="inputType"
:value="value"
@input="$emit('input', $event)"
v-on="$listeners"
>
<template
slot="prepend"
>
<q-icon
name="lock"
/>
</template>
<template
v-slot:append
>
<q-btn
v-if="value !== ''"
:icon="icon"
:disable="$attrs.disable"
tabindex="-1"
color="primary"
flat
dense
@click.stop="visible=!visible"
/>
<q-btn
v-if="generate"
icon="casino"
:disable="$attrs.disable"
tabindex="-1"
color="primary"
flat
dense
@click.stop="generatePassword"
/>
</template>
</csc-input>
</template>
<script>
import CscInput from 'components/form/CscInput'
import PasswordGenerator from 'generate-password'
export default {
name: 'CscInputPassword',
components: { CscInput },
props: {
value: {
type: String,
default: undefined
},
generate: {
type: Boolean,
default: false
}
},
data () {
return {
visible: false
}
},
computed: {
inputType () {
if (this.visible) {
return 'text'
} else {
return 'password'
}
},
icon () {
if (!this.visible) {
return 'visibility_off'
} else {
return 'visibility'
}
}
},
methods: {
generatePassword () {
const pass = PasswordGenerator.generate({
length: 10,
numbers: true
})
this.$emit('input', pass)
this.$emit('generated', pass)
},
clear () {
this.$refs.input.clear()
}
}
}
</script>

@ -0,0 +1,144 @@
<template>
<div
class="csc-input-password-retype"
>
<csc-input-password
v-model="password"
v-bind="$attrs"
generate
clearable
:label="$t('pbxConfig.typePassword')"
@input="inputPassword"
@generated="passwordGenerated"
@clear="$refs.passwordRetype.clear()"
/>
<password-strength-meter
v-show="false"
v-model="password"
:strength-meter-only="true"
@score="strengthMeterScoreUpdate"
/>
<q-linear-progress
v-model="passwordScoreMappedValue"
:color="passwordScoreColor"
size="8px"
/>
<csc-input-password
ref="passwordRetype"
v-model="passwordRetype"
v-bind="$attrs"
:label="$t('pbxConfig.retypePassword')"
:error="$v.passwordRetype.$error"
:error-message="errorMessagePasswordRetype"
clearable
:disable="passwordScore < 2 || $attrs.disable"
@clear="$v.passwordRetype.$reset"
@blur="blur"
@input="inputRetypePassword"
/>
</div>
</template>
<script>
import {
sameAs,
required
} from 'vuelidate/lib/validators'
import CscInputPassword from 'components/form/CscInputPassword'
import PasswordStrengthMeter from 'vue-password-strength-meter'
export default {
name: 'CscInputPasswordRetype',
components: {
CscInputPassword,
PasswordStrengthMeter
},
validations: {
password: {
required
},
passwordRetype: {
sameAsPassword: sameAs('password')
}
},
props: {
value: {
type: Object,
default () {
return {
password: '',
passwordRetype: ''
}
}
}
},
data () {
return {
password: this.value.password,
passwordRetype: this.value.passwordRetype,
passwordScore: null
}
},
computed: {
errorMessagePasswordRetype () {
if (!this.$v.passwordRetype.sameAsPassword) {
return this.$t('pbxConfig.errorPasswordNotEqual')
} else {
return ''
}
},
passwordScoreMappedValue () {
if (this.passwordScore === null || this.passwordScore === undefined) {
return 0
}
return (this.passwordScore + 1) / 5
},
passwordScoreColor () {
if (this.passwordScore < 2) {
return 'negative'
} else if (this.passwordScore === 2) {
return 'warning'
} else {
return 'primary'
}
}
},
watch: {
value (value) {
this.password = value.password
this.passwordRetype = value.passwordRetype
}
},
methods: {
strengthMeterScoreUpdate (score) {
this.passwordScore = score
this.$emit('score', score)
},
inputPassword () {
this.$emit('input', {
password: this.password,
passwordRetype: this.passwordRetype
})
},
inputRetypePassword () {
this.$v.passwordRetype.$reset()
this.inputPassword()
},
passwordGenerated (password) {
this.$emit('input', {
password: password,
passwordRetype: password
})
},
blur () {
this.$v.passwordRetype.$touch()
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.csc-input-password-retype
.Password__strength-meter
margin 0
margin-top 16px !important
margin-bottom 16px !important
</style>

@ -1,6 +1,6 @@
<template>
<csc-list-item
icon="fa-fax"
icon="fas fa-fax"
:image="imageUrl"
:odd="odd"
:expanded="expanded"
@ -102,23 +102,6 @@
/>
</template>
</csc-pbx-model-select>
<!-- <q-field-->
<!-- -->
<!-- >-->
<!-- <csc-fade>-->
<!-- <csc-form-save-button-->
<!-- v-if="hasProfileChanged"-->
<!-- @click="save"-->
<!-- />-->
<!-- </csc-fade>-->
<!-- <csc-fade>-->
<!-- <csc-form-reset-button-->
<!-- v-if="hasProfileChanged"-->
<!-- @click="resetProfile"-->
<!-- />-->
<!-- </csc-fade>-->
<!-- </q-field>-->
<csc-pbx-device-config
v-if="modelImage"
:device="device"
@ -254,6 +237,9 @@ export default {
if (expanded) {
this.$emit('expanded')
}
},
profile () {
this.$emit('load-model')
}
},
mounted () {

@ -37,7 +37,7 @@
:model-image-map="deviceModelImageMap"
@cancel="disableDeviceAddForm"
@submit="createDevice"
@model-select-opened="loadDeviceModels"
@model-select-opened="loadDeviceModels('front_thumb')"
/>
</div>
</q-slide-transition>
@ -59,7 +59,7 @@
@reset-profile="resetProfileFilter"
@close-filters="closeFilters"
@reset-filters="resetFilters"
@model-select-opened="loadDeviceModels"
@model-select-opened="loadDeviceModels('front_thumb')"
/>
</q-slide-transition>
<div
@ -98,7 +98,10 @@
:subscriber-map="subscriberMap"
:subscribers-loading="isSubscribersRequesting"
:subscriber-options="getSubscriberOptions"
@load-model="loadDeviceModel(deviceProfileMap[device.profile_id].device_id)"
@load-model="loadDeviceModel({
type: 'all',
deviceId: deviceProfileMap[device.profile_id].device_id
})"
@expand="expandDevice(device.id)"
@collapse="collapseDevice"
@expanded="deviceExpanded"
@ -107,7 +110,7 @@
@save-identifier="setDeviceIdentifier"
@save-profile="setDeviceProfile"
@save-keys="setDeviceKeys"
@model-select-opened="loadDeviceModels"
@model-select-opened="loadDeviceModels('front_thumb')"
/>
</csc-fade>
</csc-list>

@ -36,7 +36,7 @@
v-on="scope.itemEvents"
>
<q-item-section
v-if="!modelImageMap[scope.opt.model]"
v-if="!deviceModelImageSmallMap[scope.opt.model]"
side
>
<q-icon
@ -52,7 +52,7 @@
square
>
<img
:src="modelImageMap[scope.opt.model].url"
:src="deviceModelImageSmallMap[scope.opt.model].url"
>
</q-avatar>
</q-item-section>
@ -160,6 +160,9 @@
<script>
import _ from 'lodash'
import {
mapState
} from 'vuex'
export default {
name: 'CscPbxModelSelect',
props: {
@ -198,12 +201,15 @@ export default {
}
},
computed: {
...mapState('pbx', [
'deviceModelImageSmallMap'
]),
selectedProfileName () {
return _.get(this.selectedProfile, 'name', '')
},
selectedProfileImageUrl () {
const deviceModelId = _.get(this.selectedProfile, 'device_id', null)
return _.get(this.modelImageMap, deviceModelId + '.url', null)
return _.get(this.deviceModelImageSmallMap, deviceModelId + '.url', null)
},
options () {
const options = []

@ -10,6 +10,7 @@
<q-item-section
side
top
no-wrap
>
<q-icon
name="person"
@ -25,7 +26,12 @@
<q-item-label
caption
>
{{ $t('pbxConfig.extension') }}: {{ seat.pbx_extension }}
{{ $t('pbxConfig.webusername') }}: <strong>{{ seat.webusername }}</strong>
</q-item-label>
<q-item-label
caption
>
{{ $t('pbxConfig.extension') }}: <strong>{{ seat.pbx_extension }}</strong>
</q-item-label>
<q-item-label
caption
@ -63,22 +69,35 @@
side
>
<csc-more-menu>
<csc-popup-menu-item
icon="vpn_key"
color="primary"
:label="$t('pbxConfig.editPassword')"
@click="showPasswordDialog"
/>
<csc-popup-menu-item-delete
@click="deleteSeat"
/>
<q-separator />
<q-item
class="no-padding"
>
<q-item-section>
<q-toggle
v-model="changes.clirIntrapbx"
class="q-pa-sm"
:label="$t('pbxConfig.toggleIntraPbx')"
:disable="loading"
@input="changeIntraPbx"
/>
</q-item-section>
</q-item>
</csc-more-menu>
</q-item-section>
</template>
<div
class="q-pa-md"
>
<q-btn
icon="vpn_key"
flat
color="primary"
:label="$t('pbxConfig.editPassword')"
@click="showPasswordDialog"
/>
<csc-change-password-dialog
ref="changePasswordDialog"
:loading="false"
@ -224,9 +243,11 @@ import CscInputButtonSave from 'components/form/CscInputButtonSave'
import CscInputButtonReset from 'components/form/CscInputButtonReset'
import CscMoreMenu from 'components/CscMoreMenu'
import CscPopupMenuItemDelete from 'components/CscPopupMenuItemDelete'
import CscPopupMenuItem from 'components/CscPopupMenuItem'
export default {
name: 'CscPbxSeat',
components: {
CscPopupMenuItem,
CscPopupMenuItemDelete,
CscMoreMenu,
CscInputButtonReset,

@ -1,15 +1,16 @@
<template>
<div>
<div
class="row justify-center q-gutter-lg q-mb-md"
class="row justify-center q-gutter-x-sm q-pt-sm"
>
<div
class="col col-3"
>
<q-input
<csc-input
v-model="data.name"
clearable
autofocus
dense
hide-bottom-space
:error="$v.data.name.$error"
:error-message="seatNameErrorMessage"
@ -17,10 +18,19 @@
:readonly="loading"
:label="$t('pbxConfig.name')"
@input="$v.data.name.$touch"
>
<template
v-slot:prepend
>
<q-icon
name="person"
/>
<q-input
</template>
</csc-input>
<csc-input
v-model="data.extension"
clearable
dense
hide-bottom-space
:error="$v.data.extension.$error"
:error-message="extensionErrorMessage"
@ -28,11 +38,19 @@
:readonly="loading"
:label="$t('pbxConfig.extension')"
@input="$v.data.extension.$touch"
>
<template
v-slot:prepend
>
<q-icon
name="call"
/>
<csc-change-password-form
ref="changePasswordForm"
:no-submit="true"
@validation-succeeded="webPassValidationSucceeded"
</template>
</csc-input>
<csc-input-password-retype
v-model="data.password"
:disable="loading"
dense
/>
</div>
<div
@ -41,6 +59,7 @@
<q-select
v-model="data.aliasNumbers"
clearable
dense
multiple
use-chips
emit-value
@ -53,6 +72,7 @@
<q-select
v-model="data.groups"
clearable
dense
multiple
use-chips
emit-value
@ -61,53 +81,63 @@
:readonly="loading"
:label="$t('pbxConfig.groups')"
:options="groupOptions"
>
<template
v-slot:prepend
>
<q-icon
name="group"
/>
</template>
</q-select>
<q-select
v-model="data.soundSet"
radio
dense
emit-value
map-options
:disable="loading"
:readonly="loading"
:label="$t('pbxConfig.soundSet')"
:options="soundSetOptions"
>
<template
v-slot:prepend
>
<q-icon
name="queue_music"
/>
</template>
</q-select>
<q-toggle
v-model="data.clirIntrapbx"
:label="$t('pbxConfig.toggleIntraPbx')"
:disable="loading"
class="q-pa-md"
dense
/>
</div>
</div>
<div
class="row justify-center"
>
<div
class="col col-4"
>
<q-btn
v-if="!loading"
flat
color="default"
icon="clear"
:disable="loading"
:label="$t('buttons.cancel')"
@click="cancel()"
/>
<q-btn
v-if="!loading"
flat
color="primary"
icon="person"
:disable="$v.data.$invalid"
:loading="loading"
:disable="$v.data.$invalid || loading"
:label="$t('pbxConfig.createSeat')"
@click="save()"
/>
<csc-object-spinner
v-if="loading"
:loading="loading"
/>
</div>
</div>
</div>
</template>
@ -118,13 +148,13 @@ import {
maxLength,
numeric
} from 'vuelidate/lib/validators'
import CscObjectSpinner from '../../CscObjectSpinner'
import CscChangePasswordForm from '../../form/CscChangePasswordForm'
import CscInput from 'components/form/CscInput'
import CscInputPasswordRetype from 'components/form/CscInputPasswordRetype'
export default {
name: 'CscPbxSeatAddForm',
components: {
CscObjectSpinner,
CscChangePasswordForm
CscInputPasswordRetype,
CscInput
},
props: {
loading: {
@ -154,9 +184,6 @@ export default {
required,
numeric,
maxLength: maxLength(64)
},
webPassword: {
maxLength: maxLength(64)
}
}
},
@ -211,17 +238,6 @@ export default {
} else {
return ''
}
},
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,
clirIntrapbx: this.data.clirIntrapbx
}
}
},
created () {
@ -234,7 +250,10 @@ export default {
return {
name: '',
extension: '',
webPassword: '',
password: {
password: '',
passwordRetype: ''
},
aliasNumbers: [],
groups: [],
soundSet: null,
@ -245,15 +264,19 @@ export default {
this.$emit('cancel')
},
save () {
this.$emit('save', this.seatModel)
this.$refs.changePasswordForm.resetForm()
this.$emit('save', {
name: this.data.name,
extension: this.data.extension,
webPassword: this.data.password.password,
aliasNumbers: this.data.aliasNumbers,
groups: this.data.groups,
soundSet: this.data.soundSet,
clirIntrapbx: this.data.clirIntrapbx
})
},
reset () {
this.data = this.getDefaults()
this.$v.$reset()
},
webPassValidationSucceeded (data) {
this.data.webPassword = data.password
}
}
}

@ -0,0 +1,135 @@
<template>
<div>
<div
class="row justify-center full-width q-gutter-x-sm q-pt-sm"
>
<div
class="col-md-2"
>
<q-select
v-model="filterType"
emit-value
map-options
dense
:options="filterTypeOptions"
:label="$t('pbxConfig.seatsFiltersFilterByLabel')"
/>
</div>
<div
class="col-md-2"
>
<q-input
v-model="typedFilter"
type="text"
dense
:disable="filterType === null"
:label="$t('pbxConfig.seatsFilterInputLabel')"
@keypress.enter="triggerFilter"
>
<template
v-slot:append
>
<q-btn
icon="search"
color="primary"
dense
flat
@click="triggerFilter"
/>
</template>
</q-input>
</div>
</div>
<div
class="row justify-center full-width q-gutter-x-sm q-pt-sm"
>
<div
class="col-md-4"
>
<q-chip
v-for="(filterItem, index) in filters"
:key="index"
:label="$t('pbxConfig.seatsFiltersTypes.' + filterItem.name) + ': ' + filterItem.value"
:disable="false"
icon="filter_alt"
removable
dense
color="primary"
text-color="dark"
@remove="removeFilter(filterItem.name)"
/>
</div>
</div>
</div>
</template>
<script>
import _ from 'lodash'
export default {
name: 'CscPbxSeatFilters',
data () {
return {
filterType: null,
typedFilter: '',
filters: []
}
},
computed: {
filterTypeOptions () {
return [
{
label: this.$t('pbxConfig.seatsFiltersTypes.display_name'),
value: 'display_name'
},
{
label: this.$t('pbxConfig.seatsFiltersTypes.pbx_extension'),
value: 'pbx_extension'
},
{
label: this.$t('pbxConfig.seatsFiltersTypes.primary_number'),
value: 'primary_number'
},
{
label: this.$t('pbxConfig.seatsFiltersTypes.alias_number'),
value: 'alias_number'
}
]
}
},
methods: {
triggerFilter () {
this.addFilter(this.filterType, this.typedFilter)
},
removeFilter (name) {
this.filters = this.filters.filter(item => item.name !== name)
this.filter()
},
removeFilters () {
if (this.filters.length > 0) {
this.filters = []
this.filter()
}
},
addFilter (name, value) {
const valueTrimmed = _.trim(value)
if (valueTrimmed) {
this.typedFilter = ''
this.filters = this.filters.filter(item => item.name !== name)
const filter = {
name: name,
value: valueTrimmed
}
this.filters.push(filter)
this.filter()
}
},
filter () {
const params = {}
this.filters.forEach(filter => {
params[filter.name] = filter.value
})
this.$emit('filter', params)
}
}
}
</script>

@ -1,31 +1,41 @@
<template>
<csc-page
class="q-pa-lg"
>
<csc-list-actions
class="row justify-center q-mb-lg"
<csc-page-sticky>
<template
v-slot:header
>
<csc-list-action-button
v-if="isSeatAddFormDisabled"
slot="slot1"
<q-btn
icon="add"
color="primary"
flat
:label="$t('pbxConfig.addSeat')"
:disable="isSeatListRequesting"
@click="enableSeatAddForm"
:disable="!isSeatAddFormDisabled"
@click="openAddForm"
/>
<csc-list-action-button
<q-btn
v-if="!showFilters"
slot="slot2"
icon="filter_alt"
color="primary"
flat
:label="$t('pbxConfig.seatsFilters')"
:disable="isSeatListRequesting"
@click="toggleFilters()"
@click="openFilters"
/>
<q-btn
v-if="showFilters"
icon="clear"
color="negative"
flat
:label="$t('pbxConfig.closeFilters')"
@click="closeFilters"
/>
</template>
<template
v-slot:toolbar
>
{{ $t('pbxConfig.seatsFilters') }}
</csc-list-action-button>
</csc-list-actions>
<csc-pbx-seat-filters
v-if="showFilters"
ref="filters"
@filter="filterEvent"
/>
<csc-pbx-seat-add-form
v-if="!isSeatAddFormDisabled"
ref="addForm"
@ -37,75 +47,7 @@
@save="createSeat"
@cancel="disableSeatAddForm"
/>
<div
v-if="showFilters"
class="row justify-center q-mb-lg"
>
<div
class="col col-6"
>
<q-select
v-model="filterType"
emit-value
map-options
:options="filterTypes"
:label="$t('pbxConfig.seatsFiltersFilterByLabel')"
/>
<q-input
v-if="filterType"
ref="inputFilter"
type="text"
:value="typedFilter"
:label="$t('pbxConfig.seatsFilterInputLabel')"
@input="inputFilter"
>
<template
v-slot:append
>
<q-btn
icon="search"
color="primary"
dense
flat
/>
</template>
</q-input>
<div
class="q-mb-md"
>
<template
v-for="(filter, index) in filters"
>
<q-chip
v-if="filterType"
:key="index"
:label="filterType === 'name' ? 'Name: ' + filter : filter"
closables
@close="removeFilter(filter)"
/>
</template>
</div>
<div
class="row justify-center"
>
<q-btn
class="q-mr-sm"
flat
icon="clear"
color="white"
:label="$t('pbxConfig.seatsFiltersClose')"
@click="closeFilters"
/>
<q-btn
flat
icon="undo"
color="white"
:label="$t('pbxConfig.seatsFiltersReset')"
@click="emptyFilters"
/>
</div>
</div>
</div>
<div
v-if="isSeatListPaginationActive"
class="row justify-center"
@ -166,16 +108,14 @@
@remove="removeSeat({seatId:seatRemoving.id})"
@cancel="closeSeatRemovalDialog"
/>
</csc-page>
</csc-page-sticky>
</template>
<script>
import CscPage from '../../CscPage'
import _ from 'lodash'
import CscPbxSeatAddForm from './CscPbxSeatAddForm'
import CscPbxSeat from './CscPbxSeat'
import CscRemoveDialog from '../../CscRemoveDialog'
import CscListActions from '../../CscListActions'
import CscListActionButton from '../../CscListActionButton'
import {
mapState,
mapGetters,
@ -193,17 +133,20 @@ import {
} from 'src/store/common'
import platform from '../../../mixins/platform'
import CscList from '../../CscList'
import CscPageSticky from 'components/CscPageSticky'
import CscPbxSeatFilters from 'components/pages/PbxConfiguration/CscPbxSeatFilters'
export default {
components: {
CscPbxSeatFilters,
CscPageSticky,
CscSpinner,
CscPage,
CscPbxSeat,
CscPbxSeatAddForm,
CscRemoveDialog,
CscList,
CscListActions,
CscListActionButton
CscList
// CscListActions,
// CscListActionButton
},
mixins: [
platform
@ -211,12 +154,7 @@ export default {
data () {
return {
showFilters: false,
filterType: null,
filterTypes: [
{ label: this.$t('pbxConfig.seatsFiltersTypes.name'), value: 'name' }
],
typedFilter: null,
filters: []
filters: null
}
},
computed: {
@ -337,65 +275,30 @@ export default {
page: page
})
},
toggleFilters () {
this.showFilters = !this.showFilters
openAddForm () {
this.enableSeatAddForm()
this.closeFilters()
},
openFilters () {
this.showFilters = true
this.disableSeatAddForm()
},
inputFilter (input) {
this.typedFilter = input
},
closeFilters () {
if (this.$refs.filters) {
this.$refs.filters.removeFilters()
}
this.showFilters = false
},
emptyFilters () {
this.filterType = null
this.typedFilter = null
this.filters = []
this.$scrollTo(this.$parent.$el)
this.loadSeatListItems({
page: 1
})
},
triggerFilter () {
filterEvent (filters) {
this.$scrollTo(this.$parent.$el)
this.loadSeatListItems({
page: 1,
display_name: this.typedFilter
})
this.filters = []
this.filters.push(this.typedFilter)
this.typedFilter = null
},
removeFilter (filter) {
this.filters = this.filters.filter($filter => $filter !== filter)
if (this.filters.length < 1) {
this.emptyFilters()
}
this.filters = filters
const payload = _.cloneDeep(filters)
payload.page = 1
this.loadSeatListItems(payload)
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.csc-pbx-filters-container
color $secondary
margin-bottom 20px
.csc-pbx-chips-container
margin 20px auto 20px auto
text-align center
.csc-pbx-filters-field
width 250px
display inline-block
margin-left 10px
.csc-pbx-filter-fields-container
margin-top -15px
.csc-pbx-filter-buttons
margin-top 15px
text-align center
</style>

@ -53,12 +53,6 @@ export default {
isLoading: false
}
},
async mounted () {
this.requestInProgress(true)
const preferences = await this.loadPreferences(getSubscriberId())
this.clirIntrapbx = preferences.clir_intrapbx
this.requestInProgress(false)
},
computed: {
...mapGetters('pbxSeats', [
'getIntraPbx'
@ -85,6 +79,12 @@ export default {
}
}
},
async mounted () {
this.requestInProgress(true)
const preferences = await this.loadPreferences(getSubscriberId())
this.clirIntrapbx = preferences.clir_intrapbx
this.requestInProgress(false)
},
methods: {
...mapActions('pbxSeats', [
'setIntraPbx',

@ -602,12 +602,16 @@
"seatsFilters": "Filter",
"seatsFiltersFilterByLabel": "Filter by",
"seatsFiltersTypes": {
"name": "Name"
"display_name": "Name",
"pbx_extension": "Extension",
"primary_number": "Primary Number",
"alias_number": "Alias Number"
},
"seatsFiltersSearch": "Search",
"seatsFiltersClose": "Close",
"seatsFiltersReset": "Reset Filters",
"seatsFilterInputLabel": "Type something"
"seatsFilterInputLabel": "Type something",
"webusername": "Login"
},
"callBlocking": {
"privacyEnabledToast": "Your number is hidden to the callee",

@ -256,12 +256,18 @@ export default {
const page = _.get(options, 'page', context.state.seatListCurrentPage)
const clearList = _.get(options, 'clearList', true)
const displayName = _.get(options, 'display_name', null)
const pbxExtension = _.get(options, 'pbx_extension', null)
const primaryNumber = _.get(options, 'primary_number', null)
const aliasNumber = _.get(options, 'alias_number', null)
context.commit('seatListItemsRequesting', {
clearList: clearList
})
getSeatList({
page: page,
display_name: displayName
display_name: displayName,
pbx_extension: pbxExtension,
primary_number: primaryNumber,
alias_number: aliasNumber
}).then((seatList) => {
context.commit('pbx/pilotSucceeded', seatList.pilot, { root: true })
context.commit('pbx/numbersSucceeded', seatList.numbers, { root: true })

@ -3,7 +3,7 @@ import Vue from 'vue'
import numberFilter from '../filters/number'
import _ from 'lodash'
import {
getAllProfiles
getAllProfiles, getModel, getModelFrontImage, getModelFrontThumbnailImage
} from '../api/pbx-config'
import {
getSubscribers
@ -11,9 +11,9 @@ import {
import {
RequestState
} from './common'
import {
loadDeviceModel
} from '../api/pbx-devices'
// import {
// loadDeviceModel
// } from '../api/pbx-devices'
import { getNumbers } from '../api/user'
import {
i18n
@ -37,6 +37,7 @@ export default {
deviceModelList: [],
deviceModelMap: {},
deviceModelImageMap: {},
deviceModelImageSmallMap: {},
subscriberList: [],
subscriberListState: RequestState.initiated,
subscriberMap: {}
@ -193,16 +194,21 @@ export default {
deviceModelSucceeded (state, deviceModel) {
const model = _.get(deviceModel, 'model', null)
const modelImage = _.get(deviceModel, 'modelImage', null)
const modelImageThumbnail = _.get(deviceModel, 'modelImageThumbnail', null)
if (model !== null) {
Vue.set(state.deviceModelMap, deviceModel.model.id, deviceModel.model)
Vue.set(state.deviceModelMap, model.id, model)
}
if (modelImage !== null) {
Vue.set(state.deviceModelImageMap, deviceModel.modelImage.id, deviceModel.modelImage)
Vue.set(state.deviceModelImageMap, modelImage.id, modelImage)
}
if (modelImageThumbnail !== null) {
Vue.set(state.deviceModelImageSmallMap, modelImageThumbnail.id, modelImageThumbnail)
}
},
deviceModelFailed (state, deviceModelId) {
Vue.delete(state.deviceModelMap, deviceModelId)
Vue.delete(state.deviceModelImageMap, deviceModelId)
Vue.delete(state.deviceModelImageSmallMap, deviceModelId)
},
subscribersRequesting (state) {
state.subcriberListState = RequestState.requesting
@ -232,19 +238,69 @@ export default {
}
})
},
loadDeviceModel (context, deviceModelId) {
if (!context.state.deviceModelMap[deviceModelId]) {
loadDeviceModel(deviceModelId).then((deviceModel) => {
async loadDeviceModel (context, payload) {
try {
const isFrontCached = context.state.deviceModelImageMap[payload.deviceId] !== undefined
const isFrontThumbnailCached = context.state.deviceModelImageSmallMap[payload.deviceId] !== undefined
const isModelCached = context.state.deviceModelMap[payload.deviceId] !== undefined
const deviceModel = {
modelImage: null,
modelImageThumbnail: null,
model: null
}
const requests = []
let isFrontImageRequested = false
if (!isFrontCached && (payload.type === 'front' || payload.type === 'all')) {
requests.push(getModelFrontImage(payload.deviceId))
isFrontImageRequested = true
}
let isFrontThumbnailImageRequested = false
if (!isFrontThumbnailCached && (payload.type === 'front_thumb' || payload.type === 'all')) {
requests.push(getModelFrontThumbnailImage(payload.deviceId))
isFrontThumbnailImageRequested = true
}
let isModelRequested = false
if (!isModelCached) {
requests.push(getModel(payload.deviceId))
isModelRequested = true
}
if (requests.length > 0) {
const res = await Promise.all(requests)
if (res.length === 1 && isModelRequested) {
deviceModel.model = res[0]
} else if (res.length === 1 && isFrontImageRequested) {
deviceModel.modelImage = res[0]
} else if (res.length === 1 && isFrontThumbnailImageRequested) {
deviceModel.modelImageThumbnail = res[0]
} else if (res.length === 2 && isModelRequested && isFrontImageRequested) {
deviceModel.modelImage = res[0]
deviceModel.model = res[1]
} else if (res.length === 2 && isModelRequested && isFrontThumbnailImageRequested) {
deviceModel.modelImageThumbnail = res[0]
deviceModel.model = res[1]
} else if (res.length === 2 && isFrontImageRequested && isFrontThumbnailImageRequested) {
deviceModel.modelImage = res[0]
deviceModel.modelImageThumbnail = res[1]
} else if (res.length === 3) {
deviceModel.modelImage = res[0]
deviceModel.modelImageThumbnail = res[1]
deviceModel.model = res[2]
}
}
context.commit('deviceModelSucceeded', deviceModel)
}).catch(() => {
context.commit('deviceModelFailed', deviceModelId)
})
} catch (err) {
context.commit('deviceModelFailed', payload.deviceId)
}
},
loadDeviceModels (context) {
context.state.deviceProfileList.forEach((profile) => {
context.dispatch('loadDeviceModel', profile.device_id)
})
async loadDeviceModels (context, imageType) {
const requests = []
for (let i = 0; i < context.state.deviceProfileList.length; i++) {
requests.push(context.dispatch('loadDeviceModel', {
deviceId: context.state.deviceProfileList[i].device_id,
type: imageType
}))
}
await Promise.all(requests)
},
loadSubscribers (context) {
if (context.state.subscriberList.length === 0 &&

@ -5457,6 +5457,11 @@ gaze@^1.0.0:
dependencies:
globule "^1.0.0"
generate-password@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/generate-password/-/generate-password-1.5.1.tgz#ad463fadee1b4818edb7b827ff6f3499587d8dd5"
integrity sha512-XdsyfiF4mKoOEuzA44w9jSNav50zOurdWOV3V8DbA7SJIxR3Xm9ob14HKYTnMQOPX3ylqiJMnQF0wEa8gXZIMw==
gensync@^1.0.0-beta.1:
version "1.0.0-beta.1"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"

Loading…
Cancel
Save