diff --git a/src/api/common.js b/src/api/common.js
index ae6d4517..6100166d 100644
--- a/src/api/common.js
+++ b/src/api/common.js
@@ -116,8 +116,8 @@ export async function getList (options) {
options = _.merge({
all: false,
params: {
- page: LIST_DEFAULT_PAGE,
- rows: LIST_DEFAULT_ROWS
+ page: options.page || LIST_DEFAULT_PAGE,
+ rows: options.rows || LIST_DEFAULT_ROWS
},
headers: GET_HEADERS
}, options)
diff --git a/src/api/pbx-config.js b/src/api/pbx-config.js
index 015881c1..cbee261f 100644
--- a/src/api/pbx-config.js
+++ b/src/api/pbx-config.js
@@ -52,8 +52,10 @@ export function getProfiles (options) {
}
export function getAllProfiles () {
+ // Replace 1000 rows with 300 as we expect to have max 150 profiles.
return getProfiles({
- all: true
+ page: 1,
+ rows: 300
})
}
diff --git a/src/components/pages/PbxConfiguration/CscPbxDevice.vue b/src/components/pages/PbxConfiguration/CscPbxDevice.vue
index c5416d9f..9fe9b1d2 100644
--- a/src/components/pages/PbxConfiguration/CscPbxDevice.vue
+++ b/src/components/pages/PbxConfiguration/CscPbxDevice.vue
@@ -9,10 +9,21 @@
center
no-wrap
>
-
+
+
+
+
diff --git a/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue b/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue
index db2f3680..83b9f4bb 100644
--- a/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue
+++ b/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue
@@ -90,6 +90,8 @@ import _ from 'lodash'
import CscPbxModelSelect from '../PbxConfiguration/CscPbxModelSelect'
import CscPbxAutoAttendantSelection from './CscPbxAutoAttendantSelection'
import { mapActions, mapState } from 'vuex'
+import { RequestState } from 'src/store/common'
+import { showGlobalError } from 'src/helpers/ui'
export default {
name: 'CscPbxDeviceFilters',
@@ -116,7 +118,9 @@ export default {
...mapState('pbx', [
'deviceProfileMap',
'deviceProfileList',
- 'subscriberList'
+ 'subscriberList',
+ 'subscriberListState',
+ 'subscriberListError'
]),
subscribersOptions () {
const options = []
@@ -191,6 +195,11 @@ export default {
watch: {
filterTypeModel () {
this.typedFilter = null
+ },
+ subscriberListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.subscriberListError)
+ }
}
},
mounted () {
diff --git a/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue b/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
index 435f3fdd..d4a6d10f 100644
--- a/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
+++ b/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
@@ -49,10 +49,11 @@
>
-
+ />
@@ -120,15 +121,11 @@ export default {
return _.get(this.deviceModelImageSmallMap, deviceModelId + '.url', null)
},
options () {
- const options = []
- this.profiles.forEach((profile) => {
- options.push({
- label: profile.name,
- value: profile.id,
- model: profile.device_id
- })
- })
- return options
+ return this.profiles.map((profile) => ({
+ label: profile.name,
+ value: profile.id,
+ model: profile.device_id
+ }))
}
},
watch: {
diff --git a/src/pages/CscPagePbxCallQueues.vue b/src/pages/CscPagePbxCallQueues.vue
index 5a7c7ce0..3a690591 100644
--- a/src/pages/CscPagePbxCallQueues.vue
+++ b/src/pages/CscPagePbxCallQueues.vue
@@ -150,6 +150,10 @@ export default {
'getCallQueueCreationToastMessage',
'getCallQueueUpdateToastMessage',
'getCallQueueRemovalToastMessage'
+ ]),
+ ...mapState('pbx', [
+ 'subscriberListState',
+ 'subscriberListError'
])
},
watch: {
@@ -175,6 +179,16 @@ export default {
} else if (state === RequestState.failed) {
showGlobalError(this.callQueueRemovalError)
}
+ },
+ callQueueListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.callQueueListError)
+ }
+ },
+ subscriberListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.subscriberListError)
+ }
}
},
mounted () {
diff --git a/src/pages/CscPagePbxDeviceDetails.vue b/src/pages/CscPagePbxDeviceDetails.vue
index 4e2c3cc0..3ff52058 100644
--- a/src/pages/CscPagePbxDeviceDetails.vue
+++ b/src/pages/CscPagePbxDeviceDetails.vue
@@ -175,10 +175,11 @@
v-if="!deviceSelected"
/>
{
+ const newProfile = this.deviceProfileMap[this.changes.profile_id]
+ if (newProfile && newProfile.device_id) {
+ this.loadDeviceModel({
+ type: 'all',
+ deviceId: newProfile.device_id
+ })
+ }
})
}
if (this.hasAdminNameChanged) {
diff --git a/src/pages/CscPagePbxDevices.vue b/src/pages/CscPagePbxDevices.vue
index a6aa72b8..81e572b8 100644
--- a/src/pages/CscPagePbxDevices.vue
+++ b/src/pages/CscPagePbxDevices.vue
@@ -3,9 +3,7 @@
id="csc-page-pbx-devices"
class="q-pa-lg"
>
-
+
-
+
@@ -63,7 +59,7 @@
:loading="isDeviceListRequesting"
class="q-pb-md"
@filter="applyFilter"
- @model-select-opened="loadDeviceModels('front_thumb')"
+ @model-select-opened="loadProfileThumbnails()"
/>
-
+
@@ -170,12 +164,11 @@ export default {
'deviceModelList',
'deviceModelMap',
'deviceModelImageMap',
- 'subscriberList',
- 'subscriberMap'
- ]),
- ...mapGetters('pbx', [
- 'getSubscriberOptions',
- 'isSubscribersRequesting'
+ 'deviceModelListState',
+ 'isDeviceModelListStateRequesting',
+ 'deviceModelError',
+ 'deviceProfileListState',
+ 'deviceProfileListError'
]),
...mapState('pbxDevices', [
'deviceRemoving',
@@ -184,12 +177,14 @@ export default {
'deviceListLastPage',
'deviceListVisibility',
'deviceCreationState',
- 'deviceRemovalState'
+ 'deviceRemovalState',
+ 'deviceListState',
+ 'deviceListError',
+ 'deviceCreationError'
]),
...mapGetters('pbxDevices', [
'isDeviceListEmpty',
'isDeviceListRequesting',
- 'isDeviceExpanded',
'isDeviceListPaginationActive',
'isDeviceAddFormDisabled',
'isDeviceCreating',
@@ -203,6 +198,75 @@ export default {
]),
hasFilters () {
return Object.keys(this.filters).length > 0
+ },
+ getDeviceIdFromProfile () {
+ return (profileId) => {
+ const profile = this.getDeviceProfile(profileId)
+ return profile && profile.device_id ? profile.device_id : null
+ }
+ },
+ getDeviceProfile () {
+ return (profileId) => {
+ if (!profileId) {
+ return {}
+ }
+ return this.deviceProfileMap[profileId] || {}
+ }
+ },
+ getDeviceModel () {
+ return (profileId) => {
+ if (!profileId) {
+ return {}
+ }
+
+ const deviceProfile = this.deviceProfileMap[profileId]
+ if (!deviceProfile) {
+ return {}
+ }
+
+ if (!deviceProfile.device_id) {
+ return {}
+ }
+
+ const deviceModel = this.deviceModelMap[deviceProfile.device_id]
+ if (!deviceModel) {
+ return {}
+ }
+
+ return deviceModel
+ }
+ },
+ getDeviceModelImage () {
+ return (profileId) => {
+ if (!profileId) {
+ return null
+ }
+
+ const deviceProfile = this.deviceProfileMap[profileId]
+ if (!deviceProfile) {
+ return null
+ }
+
+ if (!deviceProfile.device_id) {
+ return null
+ }
+
+ const deviceModelImage = this.deviceModelImageMap[deviceProfile.device_id]
+ if (!deviceModelImage) {
+ return null
+ }
+
+ return deviceModelImage
+ }
+ },
+ isItemLoading () {
+ return (deviceId) => this.isDeviceLoading(deviceId) ||
+ this.isDeviceListRequesting ||
+ this.isDeviceModelListStateRequesting
+ },
+ showSpinner () {
+ const deviceListDataIsNotReady = this.isDeviceListRequesting && !(this.isDeviceCreating || this.isDeviceRemoving || this.isDeviceUpdating)
+ return this.deviceProfileListState === RequestState.requesting || deviceListDataIsNotReady
}
},
watch: {
@@ -221,8 +285,26 @@ export default {
} else if (state === RequestState.failed) {
showGlobalError(this.deviceRemovalError)
}
+ },
+ deviceListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.deviceListError)
+ }
+ },
+ deviceModelListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.deviceModelError)
+ }
+ },
+ deviceProfileListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.deviceProfileListError)
+ }
}
},
+ async created () {
+ await this.loadProfiles()
+ },
mounted () {
this.$scrollTo(this.$parent.$el)
this.loadDeviceListFiltered()
@@ -231,11 +313,10 @@ export default {
methods: {
...mapActions('pbx', [
'loadDeviceModel',
- 'loadDeviceModels'
+ 'loadProfileThumbnails',
+ 'loadProfiles'
]),
...mapMutations('pbxDevices', [
- 'expandDevice',
- 'collapseDevice',
'enableDeviceAddForm',
'disableDeviceAddForm',
'deviceRemovalRequesting',
@@ -245,11 +326,7 @@ export default {
'loadDeviceList',
'loadDevicePreferencesList',
'createDevice',
- 'removeDevice',
- 'setDeviceStationName',
- 'setDeviceIdentifier',
- 'setDeviceProfile',
- 'setDeviceKeys'
+ 'removeDevice'
]),
loadDeviceListFiltered (page) {
this.loadDeviceList({
diff --git a/src/pages/CscPagePbxMsConfigs.vue b/src/pages/CscPagePbxMsConfigs.vue
index ced532cf..35d474b9 100644
--- a/src/pages/CscPagePbxMsConfigs.vue
+++ b/src/pages/CscPagePbxMsConfigs.vue
@@ -135,7 +135,16 @@ export default {
'msConfigRemovalState',
'msConfigCreationError',
'msConfigUpdateError',
- 'msConfigRemovalError'
+ 'msConfigRemovalError',
+ 'msConfigListState',
+ 'msConfigListError'
+ ]),
+ ...mapState('pbx', [
+ 'numberListState',
+ 'numberListError',
+ 'subscriberListState',
+ 'subscriberListError'
+
]),
...mapGetters('pbxMsConfigs', [
'isMsConfigListEmpty',
@@ -173,6 +182,21 @@ export default {
} else if (state === RequestState.failed) {
showGlobalError(this.msConfigRemovalError)
}
+ },
+ msConfigListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.msConfigListError)
+ }
+ },
+ numberListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.numberListError)
+ }
+ },
+ subscriberListState (state) {
+ if (state === RequestState.failed) {
+ showGlobalError(this.subscriberListError)
+ }
}
},
mounted () {
diff --git a/src/store/pbx-devices.js b/src/store/pbx-devices.js
index 9e52c737..15cef40f 100644
--- a/src/store/pbx-devices.js
+++ b/src/store/pbx-devices.js
@@ -28,6 +28,7 @@ export default {
deviceCreationState: CreationState.initiated,
deviceListCurrentPage: 1,
deviceList: [],
+ deviceListError: null,
deviceListLastPage: null,
deviceListState: RequestState.initiated,
deviceListVisibility: 'visible',
@@ -304,8 +305,6 @@ export default {
context.commit('deviceListRequesting', { clearList })
try {
- // Ensure profiles are loaded before fetching devices
- await context.dispatch('pbx/loadProfiles', null, { root: true })
const devices = await getDeviceList({ page, filters })
context.commit('deviceListSucceeded', { devices, page })
} catch (err) {
@@ -316,7 +315,6 @@ export default {
async loadDevice (context, deviceId) {
context.commit('deviceListRequesting', { clearList: false })
try {
- // Ensure profiles are loaded before fetching devices
await context.dispatch('pbx/loadProfileById', deviceId, { root: true })
const device = await getDevice(deviceId)
context.commit('deviceSucceeded', device)
diff --git a/src/store/pbx.js b/src/store/pbx.js
index dc4148a8..652389c5 100644
--- a/src/store/pbx.js
+++ b/src/store/pbx.js
@@ -10,6 +10,7 @@ import {
} from 'src/api/pbx-config'
import { getSubscribers } from 'src/api/subscriber'
import { getNumbers } from 'src/api/user'
+import { showGlobalError } from 'src/helpers/ui'
import { RequestState } from 'src/store/common'
import { i18n } from 'src/boot/i18n'
@@ -26,7 +27,8 @@ export default {
seatMapById: {},
soundSetList: [],
soundSetMapByName: {},
- deviceProfilesListState: RequestState.initiated,
+ deviceProfileListState: RequestState.initiated,
+ deviceProfileListError: null,
deviceProfileList: [],
deviceProfileMap: {},
deviceModelList: [],
@@ -35,6 +37,7 @@ export default {
deviceModelImageSmallMap: {},
subscriberList: [],
subscriberListState: RequestState.initiated,
+ subscriberListError: null,
subscriberMap: {},
ncosMapByName: {}
},
@@ -156,6 +159,9 @@ export default {
return state.deviceModelMap[deviceId] !== undefined
}
},
+ isDeviceModelListStateRequesting (state) {
+ return state.deviceModelListState === RequestState.requesting
+ },
isSubscribersRequesting (state) {
return state.subscriberListState === RequestState.requesting
},
@@ -227,30 +233,39 @@ export default {
state.soundSetMapByName[soundSet.name] = soundSet
})
},
- deviceProfilesListStateRequesting (state) {
- state.deviceProfilesListState = RequestState.requesting
+ deviceProfileListStateRequesting (state) {
+ state.deviceProfileListState = RequestState.requesting
},
- deviceProfilesListSucceeded (state, deviceProfileList) {
- state.deviceProfilesListState = RequestState.succeeded
- state.deviceProfileList = _.get(deviceProfileList, 'items', [])
+ deviceProfileListSucceeded (state, deviceProfileList) {
+ const newList = _.get(deviceProfileList, 'items', [])
+
+ // First remove existing items that we're about to replace with newer versions
+ // and keep only items whoseIds are not in the updated list
+ const existingIds = newList.map((item) => item.id)
+ const filteredExistingList = state.deviceProfileList.filter(
+ (existingItem) => !existingIds.includes(existingItem.id)
+ )
+
+ state.deviceProfileList = [...filteredExistingList, ...newList]
+ state.deviceProfileListState = RequestState.succeeded
state.deviceProfileMap = {}
state.deviceProfileList.forEach((deviceProfile) => {
state.deviceProfileMap[deviceProfile.id] = deviceProfile
})
},
- deviceProfilesListFailed (state) {
- state.deviceProfilesListState = RequestState.failed
+ deviceProfileListFailed (state) {
+ state.deviceProfileListState = RequestState.failed
},
deviceProfileRequesting (state) {
- state.deviceProfilesListState = RequestState.requesting
+ state.deviceProfileListState = RequestState.requesting
},
deviceProfileSucceeded (state, deviceProfile) {
- state.deviceProfilesListState = RequestState.succeeded
+ state.deviceProfileListState = RequestState.succeeded
state.deviceProfileList = [...state.deviceProfileList, deviceProfile]
state.deviceProfileMap[deviceProfile.id] = deviceProfile
},
deviceProfileFailed (state) {
- state.deviceProfilesListState = RequestState.failed
+ state.deviceProfileListState = RequestState.failed
},
deviceModelSucceeded (state, deviceModel) {
const model = _.get(deviceModel, 'model', null)
@@ -272,7 +287,7 @@ export default {
delete state.deviceModelImageSmallMap[deviceModelId]
},
subscribersRequesting (state) {
- state.subcriberListState = RequestState.requesting
+ state.subscriberListState = RequestState.requesting
state.subscriberList = []
},
subscribersSucceeded (state, subscribers) {
@@ -281,24 +296,21 @@ export default {
state.subscriberList.forEach((subscriber) => {
state.subscriberMap[subscriber.id] = subscriber
})
+ },
+ subscribersFailed (state, err) {
+ state.subscriberListState = RequestState.failed
+ state.subscriberListError = err
}
},
actions: {
- loadProfiles (context) {
- return new Promise((resolve, reject) => {
- context.commit('deviceProfilesListStateRequesting')
- if (context.state.deviceProfileList.length === 0) {
- getAllProfiles().then((profiles) => {
- context.commit('deviceProfilesListSucceeded', profiles)
- resolve(profiles)
- }).catch((err) => {
- context.commit('deviceProfilesListFailed')
- reject(err)
- })
- } else {
- resolve()
- }
- })
+ async loadProfiles (context) {
+ context.commit('deviceProfileListStateRequesting')
+ try {
+ const profiles = await getAllProfiles()
+ context.commit('deviceProfileListSucceeded', profiles)
+ } catch (err) {
+ context.commit('deviceProfileListFailed', err.message)
+ }
},
async loadProfileById (context, deviceId) {
context.commit('deviceProfileRequesting')
@@ -309,6 +321,47 @@ export default {
context.commit('deviceProfileFailed')
}
},
+ async loadProfileThumbnails (context) {
+ const requests = []
+
+ context.state.deviceProfileList.forEach((deviceProfile) => {
+ const isFrontThumbnailCached = context.state.deviceModelImageSmallMap[deviceProfile.device_id] !== undefined
+ if (!isFrontThumbnailCached) {
+ requests.push(
+ getModelFrontThumbnailImage(deviceProfile.device_id)
+ .then((thumbnail) => ({
+ deviceId: deviceProfile.device_id,
+ thumbnail
+ }))
+ .catch((error) => ({
+ deviceId: deviceProfile.device_id,
+ error
+ }))
+ )
+ }
+ })
+
+ if (requests.length > 0) {
+ try {
+ const results = await Promise.all(requests)
+
+ results.forEach((result) => {
+ if (result.thumbnail) {
+ context.commit('deviceModelSucceeded', {
+ modelImageThumbnail: result.thumbnail
+ })
+ } else if (result.error) {
+ // Silent warning as it's not a critical error
+ // and we can still use the device profile without the thumbnail
+ // eslint-disable-next-line no-console
+ console.warn(`Failed to load thumbnail for device ${result.deviceId}:`, result.error)
+ }
+ })
+ } catch (error) {
+ showGlobalError('Failed to load profile thumbnails:', error)
+ }
+ }
+ },
async loadDeviceModel (context, payload) {
try {
const isFrontCached = context.state.deviceModelImageMap[payload.deviceId] !== undefined
@@ -360,7 +413,14 @@ export default {
}
context.commit('deviceModelSucceeded', deviceModel)
} catch (err) {
- context.commit('deviceModelFailed', payload.deviceId)
+ // Note: the way it is implemented at the moment
+ // if any of the promises fails, the whole action fails
+ // and only the first error is reported. This needs refactoring
+ // to handle each request separately and report errors accordingly.
+ context.commit('deviceModelFailed', {
+ deviceModelId: payload.deviceId,
+ error: err.message
+ })
}
},
async loadDeviceModels (context, imageType) {
diff --git a/src/store/user.js b/src/store/user.js
index 967932c6..168bbd7c 100644
--- a/src/store/user.js
+++ b/src/store/user.js
@@ -1,14 +1,6 @@
'use strict'
import { i18n } from 'boot/i18n'
import _ from 'lodash'
-import {
- RequestState
-} from './common'
-import {
- login,
- getUserData,
- createAuthToken
-} from '../api/user'
import {
changePassword,
resetPassword,
@@ -47,6 +39,8 @@ import {
httpApi,
apiDownloadFile
} from 'src/api/common'
+import { RequestState } from './common'
+import { login, getUserData, createAuthToken } from 'src/api/user'
export default {
namespaced: true,