diff --git a/src/api/common.js b/src/api/common.js
index 4bcfb249..9883ee72 100644
--- a/src/api/common.js
+++ b/src/api/common.js
@@ -87,8 +87,8 @@ export async function getList (options) {
const requestConfig = _.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 d7577902..73509e9e 100644
--- a/src/api/pbx-config.js
+++ b/src/api/pbx-config.js
@@ -48,8 +48,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 6216615f..b62610db 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 3b30b98e..9dcdb34d 100644
--- a/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue
+++ b/src/components/pages/PbxConfiguration/CscPbxDeviceFilters.vue
@@ -119,8 +119,8 @@ export default {
'deviceProfileMap',
'deviceProfileList',
'subscriberList',
- 'subcriberListState',
- 'subcriberListError'
+ 'subscriberListState',
+ 'subscriberListError'
]),
subscribersOptions () {
const options = []
@@ -196,9 +196,9 @@ export default {
filterTypeModel () {
this.typedFilter = null
},
- subcriberListState (state) {
+ subscriberListState (state) {
if (state === RequestState.failed) {
- showGlobalError(this.subcriberListError)
+ showGlobalError(this.subscriberListError)
}
}
},
diff --git a/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue b/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
index 3fad2556..ad8e4985 100644
--- a/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
+++ b/src/components/pages/PbxConfiguration/CscPbxModelSelect.vue
@@ -49,10 +49,11 @@
>
-
+ />
@@ -118,15 +119,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 c71d6f2a..dde0805a 100644
--- a/src/pages/CscPagePbxCallQueues.vue
+++ b/src/pages/CscPagePbxCallQueues.vue
@@ -154,8 +154,8 @@ export default {
'getCallQueueRemovalToastMessage'
]),
...mapState('pbx', [
- 'subcriberListState',
- 'subcriberListError'
+ 'subscriberListState',
+ 'subscriberListError'
])
},
watch: {
@@ -187,9 +187,9 @@ export default {
showGlobalError(this.callQueueListError)
}
},
- subcriberListState (state) {
+ subscriberListState (state) {
if (state === RequestState.failed) {
- showGlobalError(this.subcriberListError)
+ showGlobalError(this.subscriberListError)
}
}
},
diff --git a/src/pages/CscPagePbxDeviceDetails.vue b/src/pages/CscPagePbxDeviceDetails.vue
index db86eb59..d1e7ca2b 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 65e614cf..8785c620 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()"
/>
-
+
@@ -169,17 +163,12 @@ export default {
'deviceModelList',
'deviceModelMap',
'deviceModelImageMap',
- 'subscriberList',
- 'subscriberMap',
'deviceModelListState',
+ 'isDeviceModelListStateRequesting',
'deviceModelError',
'deviceProfileListState',
'deviceProfileListError'
]),
- ...mapGetters('pbx', [
- 'getSubscriberOptions',
- 'isSubscribersRequesting'
- ]),
...mapState('pbxDevices', [
'deviceRemoving',
'deviceList',
@@ -189,12 +178,12 @@ export default {
'deviceCreationState',
'deviceRemovalState',
'deviceListState',
- 'deviceListError'
+ 'deviceListError',
+ 'deviceCreationError'
]),
...mapGetters('pbxDevices', [
'isDeviceListEmpty',
'isDeviceListRequesting',
- 'isDeviceExpanded',
'isDeviceListPaginationActive',
'isDeviceAddFormDisabled',
'isDeviceCreating',
@@ -208,6 +197,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: {
@@ -243,6 +301,9 @@ export default {
}
}
},
+ async created () {
+ await this.loadProfiles()
+ },
mounted () {
this.$scrollTo(this.$parent.$el)
this.loadDeviceListFiltered()
@@ -251,11 +312,10 @@ export default {
methods: {
...mapActions('pbx', [
'loadDeviceModel',
- 'loadDeviceModels'
+ 'loadProfileThumbnails',
+ 'loadProfiles'
]),
...mapMutations('pbxDevices', [
- 'expandDevice',
- 'collapseDevice',
'enableDeviceAddForm',
'disableDeviceAddForm',
'deviceRemovalRequesting',
@@ -265,11 +325,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 ce166c78..f73fad86 100644
--- a/src/pages/CscPagePbxMsConfigs.vue
+++ b/src/pages/CscPagePbxMsConfigs.vue
@@ -142,8 +142,8 @@ export default {
...mapState('pbx', [
'numberListState',
'numberListError',
- 'subcriberListState',
- 'subcriberListError'
+ 'subscriberListState',
+ 'subscriberListError'
]),
...mapGetters('pbxMsConfigs', [
@@ -193,9 +193,9 @@ export default {
showGlobalError(this.numberListError)
}
},
- subcriberListState (state) {
+ subscriberListState (state) {
if (state === RequestState.failed) {
- showGlobalError(this.subcriberListError)
+ showGlobalError(this.subscriberListError)
}
}
},
diff --git a/src/store/pbx-devices.js b/src/store/pbx-devices.js
index d522e658..c55c6284 100644
--- a/src/store/pbx-devices.js
+++ b/src/store/pbx-devices.js
@@ -22,13 +22,12 @@ export default {
deviceCreationState: CreationState.initiated,
deviceListCurrentPage: 1,
deviceList: [],
+ deviceListError: null,
deviceListLastPage: null,
deviceListState: RequestState.initiated,
deviceListVisibility: 'visible',
deviceMapById: {},
devicePreferencesList: [],
- deviceListError: null,
- deviceMap: {},
devicePreferencesListState: RequestState.initiated,
devicePreferencesMap: {},
devicePreferencesRemovalState: RequestState.initiated,
@@ -303,8 +302,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) {
@@ -315,7 +312,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 ba4a470e..341a37bc 100644
--- a/src/store/pbx.js
+++ b/src/store/pbx.js
@@ -10,6 +10,7 @@ import {
import { getSubscribers } from 'src/api/subscriber'
import { getNumbers } from 'src/api/user'
import numberFilter from 'src/filters/number'
+import { showGlobalError } from 'src/helpers/ui'
import { RequestState } from 'src/store/common'
export default {
@@ -26,7 +27,8 @@ export default {
seatMapById: {},
soundSetList: [],
soundSetMapByName: {},
- deviceProfilesListState: RequestState.initiated,
+ deviceProfileListState: RequestState.initiated,
+ deviceProfileListError: null,
deviceProfileList: [],
deviceProfileMap: {},
deviceModelList: [],
@@ -36,11 +38,9 @@ export default {
deviceModelImageSmallMap: {},
subscriberList: [],
subscriberListState: RequestState.initiated,
- subcriberListError: null,
+ subscriberListError: null,
subscriberMap: {},
- ncosMapByName: {},
- deviceProfileListState: RequestState.initiated,
- deviceProfileListError: null
+ ncosMapByName: {}
},
getters: {
pilot (state) {
@@ -160,6 +160,9 @@ export default {
return state.deviceModelMap[deviceId] !== undefined
}
},
+ isDeviceModelListStateRequesting (state) {
+ return state.deviceModelListState === RequestState.requesting
+ },
isSubscribersRequesting (state) {
return state.subscriberListState === RequestState.requesting
},
@@ -234,30 +237,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) {
state.deviceModelListState = RequestState.succeeded
@@ -285,7 +297,7 @@ export default {
state.deviceModelError = options.error
},
subscribersRequesting (state) {
- state.subcriberListState = RequestState.requesting
+ state.subscriberListState = RequestState.requesting
state.subscriberList = []
},
subscribersSucceeded (state, subscribers) {
@@ -296,20 +308,18 @@ export default {
})
},
subscribersFailed (state, err) {
- state.subcriberListState = RequestState.failed
- state.subcriberListError = err
+ state.subscriberListState = RequestState.failed
+ state.subscriberListError = err
}
},
actions: {
async loadProfiles (context) {
- if (context.state.deviceProfileList.length === 0) {
- context.commit('deviceProfilesListStateRequesting')
- try {
- const profiles = await getAllProfiles()
- context.commit('deviceProfilesListSucceeded', profiles)
- } catch (err) {
- context.commit('deviceProfilesListFailed', err.message)
- }
+ context.commit('deviceProfileListStateRequesting')
+ try {
+ const profiles = await getAllProfiles()
+ context.commit('deviceProfileListSucceeded', profiles)
+ } catch (err) {
+ context.commit('deviceProfileListFailed', err.message)
}
},
async loadProfileById (context, deviceId) {
@@ -321,6 +331,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
@@ -373,6 +424,10 @@ export default {
}
context.commit('deviceModelSucceeded', deviceModel)
} catch (err) {
+ // 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
diff --git a/src/store/user.js b/src/store/user.js
index 2fa6deea..8831319f 100644
--- a/src/store/user.js
+++ b/src/store/user.js
@@ -3,10 +3,7 @@ import { i18n } from 'boot/i18n'
import _ from 'lodash'
import QRCode from 'qrcode'
import { date } from 'quasar'
-import {
- apiDownloadFile,
- httpApi
-} from 'src/api/common'
+import { apiDownloadFile, httpApi } from 'src/api/common'
import { callInitialize } from 'src/api/ngcp-call'
import {
changePassword,