diff --git a/i18n/en-US.json b/i18n/en-US.json index d499b8c..87bf9aa 100644 --- a/i18n/en-US.json +++ b/i18n/en-US.json @@ -194,6 +194,36 @@ "H263": "H263", "H261": "H261" } + }, + "__comment": "UI-1351: Add ability to configure feature keys for SIP devices in SmartPBX", + "__version": "v3.20_s4", + "featureKeys": { + "menuTitle": "Feature Keys", + "description": "Feature key", + "labels": { + "user": "User", + "parkingSpot": "Parking Spot", + "value": "Value" + }, + "info": { + "link": { + "showInfo": "Show Info", + "hideInfo": "Hide Info" + }, + "types": { + "presence": "Indicate when a user has an incoming call or is on the phone. The indicator can be used to pick up ringing calls or speed dial that user.", + "parking": "Indicate when a call is placed on a parking slot available to the entire company. The indicator can be used to transfer a call to the parking slot or retrieve a parked call.", + "personal_parking": "Indicate when a call is placed on a parking slot dedicated to the selected user. The indicator can be used to transfer a call to the parking slot or retrieve a parked call.", + "speed_dial": "Does not indicate for any calls event but can be used to rapidly call the configured number." + } + }, + "types": { + "none": "None", + "presence": "Presence", + "parking": "Parking", + "personal_parking": "Personal Parking", + "speed_dial": "Speed Dial" + } } }, "buy": "Buy Device", diff --git a/i18n/fr-FR.json b/i18n/fr-FR.json index 4e16930..abc4942 100644 --- a/i18n/fr-FR.json +++ b/i18n/fr-FR.json @@ -166,6 +166,34 @@ "sectionTitle": "Réglages vidéo", "selectedCodecs": "Codecs sélectionnés", "unselectedCodecs": "Codecs disponibles" + }, + "featureKeys": { + "menuTitle": "Feature Keys", + "description": "Feature key", + "labels": { + "user": "Utilisateur", + "parkingSpot": "Place", + "value": "Valeur" + }, + "info": { + "link": { + "showInfo": "Show Info", + "hideInfo": "Hide Info" + }, + "types": { + "presence": "Indique lorsqu'un utilisateur à un appel entrant ou est en train d'appeler. L'indicateur peut être utilisé pour répondre à un appel ou appeler l'utilisateur.", + "parking": "Indique lorsqu'un appel est placé sur une place de parking accessible par l'intégralité de l'entreprise. L'indicateur peut être utilisé pour transférer un appel vers la place de parking ou récupérer un appel en attente.", + "personal_parking": "Indique lorsqu'un appel est placé sur une place de parking dédié à l'utilisateur sélectionné. L'indicateur peut être utilisé pour transférer un appel vers la place de parking ou récupérer un appel en attente.", + "speed_dial": "N'indique pas l'état d'un appel mais peut être utilisé pour appeller rapidement le numéro configuré." + } + }, + "types": { + "none": "Aucun", + "presence": "Présence", + "parking": "Parking", + "personal_parking": "Personal Parking", + "speed_dial": "Speed Dial" + } } }, "buy": "Acheter téléphone", diff --git a/submodules/devices/devices.css b/submodules/devices/devices.css index 9f4f2c1..8f54912 100644 --- a/submodules/devices/devices.css +++ b/submodules/devices/devices.css @@ -350,6 +350,20 @@ color: #aaa; } +.edit-device .feature-key-value { + display: none; + margin-left: 20px; +} + +.edit-device .feature-key-value.active { + display: inline-block; +} + +.edit-device .feature-key-value label { + display: inline-block; + margin-right: 10px; +} + /* Codecs selector */ .edit-device .content .codec-selector .box-selector { float: left; diff --git a/submodules/devices/devices.js b/submodules/devices/devices.js index 5d00def..6eaa451 100644 --- a/submodules/devices/devices.js +++ b/submodules/devices/devices.js @@ -7,7 +7,14 @@ define(function(require){ var app = { - requests: {}, + requests: { + 'provisioner.ui.getModel': { + 'apiRoot': monster.config.api.provisioner, + 'url': 'ui/{brand}/{family}/{model}', + 'verb': 'GET', + generateError: false + } + }, subscribe: { 'voip.devices.render': 'devicesRender', @@ -148,7 +155,70 @@ define(function(require){ }; self.devicesGetEditData(data, function(dataDevice) { - self.devicesRenderDevice(dataDevice, callbackSave, callbackDelete); + var args = { + device: dataDevice + }; + + if (args.device.hasOwnProperty('provision')) { + self.devicesGetIterator(args.device.provision, function(template) { + if (template.hasOwnProperty('feature_keys')) { + if (!args.device.provision.hasOwnProperty('feature_keys')) { + args.device.provision.feature_keys = {}; + } + + for (var i = 0, len = template.feature_keys.iterate - 1; i < len; i++) { + if (!args.device.provision.feature_keys.hasOwnProperty(i)) { + args.device.provision.feature_keys[i] = { type: 'none' }; + } + } + + self.callApi({ + resource: 'user.list', + data: { + accountId: self.accountId + }, + success: function(data, status) { + var keyTypes = [ 'none', 'presence', 'parking', 'personal_parking', 'speed_dial' ], + parkingSpots = []; + + data.data.sort(function(a, b) { + return a.last_name.toLowerCase() > b.last_name.toLowerCase() ? 1 : -1; + }); + + for (var i = 0; i < 10; i++) { + parkingSpots[i] = i + 1; + } + + keyTypes.forEach(function(val, idx, arr) { + arr[idx] = { id: val, text: self.i18n.active().devices.popupSettings.featureKeys.types[val] }; + + if (val !== 'none') { + arr[idx].info = self.i18n.active().devices.popupSettings.featureKeys.info.types[val]; + } + }); + + $.extend(true, args, { + users: data.data, + featureKeys:{ + parkingSpots: parkingSpots, + types: keyTypes + } + }); + + self.devicesRenderDevice(args, callbackSave, callbackDelete); + } + }); + } + else { + self.devicesRenderDevice(args, callbackSave, callbackDelete); + } + }, function() { + self.devicesRenderDevice(args, callbackSave, callbackDelete); + }); + } + else { + self.devicesRenderDevice(args, callbackSave, callbackDelete); + } }); }, @@ -190,14 +260,34 @@ define(function(require){ } }, - devicesRenderDevice: function(data, callbackSave, callbackDelete) { - var self = this + devicesRenderDevice: function(args, callbackSave, callbackDelete) { + var self = this, + data = args.device, mode = data.id ? 'edit' : 'add', type = data.device_type, popupTitle = mode === 'edit' ? monster.template(self, '!' + self.i18n.active().devices[type].editTitle, { name: data.name }) : self.i18n.active().devices[type].addTitle; - templateDevice = $(monster.template(self, 'devices-'+type, data)), + templateDevice = $(monster.template(self, 'devices-'+type, args)), deviceForm = templateDevice.find('#form_device'); + if (data.hasOwnProperty('provision') && data.provision.hasOwnProperty('feature_keys')) { + var section = '.tabs-section[data-section="featureKeys"] '; + + _.each(data.provision.feature_keys, function(val, key){ + var group = '.control-group[data-id="' + key + '"] ', + value = '.feature-key-value[data-type="' + val.type + '"]'; + + templateDevice + .find(section.concat(group, value)) + .addClass('active') + .find('[name="provision.feature_keys[' + key + '].value"]') + .val(val.value); + }); + + $.each(templateDevice.find('.feature-key-index'), function(idx, val) { + $(val).text(parseInt($(val).text(), 10) + 2); + }); + } + if ( data.extra.hasE911Numbers ) { if(data.caller_id && data.caller_id.emergency && data.caller_id.emergency.number) { self.devicesGetE911NumberAddress(data.caller_id.emergency.number, function(address) { @@ -259,6 +349,8 @@ define(function(require){ templateDevice.find('.actions .save').on('click', function() { if(monster.ui.valid(deviceForm)) { + templateDevice.find('.feature-key-value:not(.active)').remove(); + var dataToSave = self.devicesMergeData(data, templateDevice, audioCodecs, videoCodecs); self.devicesSaveDevice(dataToSave, function(data) { @@ -364,6 +456,23 @@ define(function(require){ } }); + templateDevice.find('.feature-key-type').on('change', function() { + var type = $(this).val(); + + $(this).siblings('.feature-key-value.active').removeClass('active'); + $(this).siblings('.feature-key-value[data-type="' + type + '"]').addClass('active'); + }); + + templateDevice.find('.tabs-section[data-section="featureKeys"] .type-info a').on('click', function() { + var $this = $(this); + + setTimeout(function() { + var action = ($this.hasClass('collapsed') ? 'show' : 'hide').concat('Info'); + + $this.find('.text').text(self.i18n.active().devices.popupSettings.featureKeys.info.link[action]); + }); + }); + var popup = monster.ui.dialog(templateDevice, { position: ['center', 20], title: popupTitle @@ -450,6 +559,22 @@ define(function(require){ delete mergedData.media.fax.option; } + if (mergedData.hasOwnProperty('provision') && mergedData.provision.hasOwnProperty('feature_keys')) { + var tmp = mergedData.provision.feature_keys; + + mergedData.provision.feature_keys = {}; + + for (var i = 0, len = tmp.length; i < len; i++) { + if (tmp[i].type !== 'none') { + mergedData.provision.feature_keys[i] = tmp[i]; + } + } + + if (_.isEmpty(mergedData.provision.feature_keys)) { + delete mergedData.provision.feature_keys; + } + } + /* Migration clean-up */ delete mergedData.media.secure_rtp; delete mergedData.extra; @@ -945,6 +1070,25 @@ define(function(require){ } } }); + }, + + devicesGetIterator: function(args, callbackSuccess, callbackError) { + var self = this; + + monster.request({ + resource: 'provisioner.ui.getModel', + data: { + brand: args.endpoint_brand, + family: args.endpoint_family, + model: args.endpoint_model + }, + success: function(data, status) { + callbackSuccess && callbackSuccess(data.data.template); + }, + error: function(data, status) { + callbackError && callbackError(); + } + }); } }; diff --git a/views/devices-sip_device.html b/views/devices-sip_device.html index 059555b..9a4c224 100644 --- a/views/devices-sip_device.html +++ b/views/devices-sip_device.html @@ -1,16 +1,16 @@ -