From f90f8841f61b912c9f60f0fe28226d011a4a8e41 Mon Sep 17 00:00:00 2001 From: Jean-Roch Maitre Date: Mon, 13 Apr 2015 16:56:37 -0700 Subject: [PATCH 1/4] UI-1365: Now show seconds in the Call Logs --- submodules/callLogs/callLogs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/callLogs/callLogs.js b/submodules/callLogs/callLogs.js index f016449..74a7c2c 100644 --- a/submodules/callLogs/callLogs.js +++ b/submodules/callLogs/callLogs.js @@ -332,7 +332,7 @@ define(function(require){ formatCdr = function(cdr) { var date = monster.util.gregorianToDate(cdr.timestamp), shortDate = monster.util.toFriendlyDate(date, 'shortDate'), - time = monster.util.toFriendlyDate(date, 'shortTime'), + time = monster.util.toFriendlyDate(date, 'time'), durationMin = parseInt(cdr.duration_seconds/60).toString(), durationSec = (cdr.duration_seconds % 60 < 10 ? "0" : "") + (cdr.duration_seconds % 60), hangupI18n = self.i18n.active().hangupCauses, From 939a893f6b46baededde52403e029f61da1e5be7 Mon Sep 17 00:00:00 2001 From: Joris Tirado Date: Wed, 15 Apr 2015 10:01:02 -0700 Subject: [PATCH 2/4] UI-1130: Put focus on search inputs --- submodules/callLogs/callLogs.js | 2 ++ submodules/devices/devices.js | 2 ++ submodules/groups/groups.js | 2 ++ submodules/users/users.js | 5 +++++ submodules/vmboxes/vmboxes.js | 2 ++ 5 files changed, 13 insertions(+) diff --git a/submodules/callLogs/callLogs.js b/submodules/callLogs/callLogs.js index f016449..4edfeb8 100644 --- a/submodules/callLogs/callLogs.js +++ b/submodules/callLogs/callLogs.js @@ -88,6 +88,8 @@ define(function(require){ toDate = params.toDate, startKey = params.nextStartKey; + setTimeout(function() { template.find('.search-query').focus() }); + template.find('.apply-filter').on('click', function(e) { var fromDate = template.find('input.filter-from').datepicker("getDate"), toDate = template.find('input.filter-to').datepicker("getDate"); diff --git a/submodules/devices/devices.js b/submodules/devices/devices.js index 5d00def..8e81734 100644 --- a/submodules/devices/devices.js +++ b/submodules/devices/devices.js @@ -59,6 +59,8 @@ define(function(require){ devicesBindEvents: function(template, parent, data) { var self = this; + setTimeout(function() { template.find('.search-query').focus(); }); + template.find('.devices-header .search-query').on('keyup', function() { var searchString = $(this).val().toLowerCase(), rows = template.find('.devices-rows .grid-row:not(.title)'), diff --git a/submodules/groups/groups.js b/submodules/groups/groups.js index 1690d0b..962fe4d 100644 --- a/submodules/groups/groups.js +++ b/submodules/groups/groups.js @@ -158,6 +158,8 @@ define(function(require){ groupsBindEvents: function(template, parent) { var self = this; + setTimeout(function() { template.find('.search-query').focus(); }); + template.find('.grid-row:not(.title) .grid-cell').on('click', function() { var cell = $(this), type = cell.data('type'), diff --git a/submodules/users/users.js b/submodules/users/users.js index 3274203..133edb9 100644 --- a/submodules/users/users.js +++ b/submodules/users/users.js @@ -370,6 +370,8 @@ define(function(require){ ); }; + setTimeout(function() { template.find('.search-query').focus() }); + template.find('.grid-row:not(.title) .grid-cell').on('click', function() { var cell = $(this), type = cell.data('type'), @@ -454,6 +456,9 @@ define(function(require){ else if(type === 'features') { currentUser = data; } + else if (type === 'devices') { + setTimeout(function() { template.find('.search-query').focus(); }); + } row.find('.edit-user').append(template).slideDown(400, function() { $('body').animate({ scrollTop: row.offset().top - (window.innerHeight - row.height() - 10) }); diff --git a/submodules/vmboxes/vmboxes.js b/submodules/vmboxes/vmboxes.js index 326a03a..9044565 100644 --- a/submodules/vmboxes/vmboxes.js +++ b/submodules/vmboxes/vmboxes.js @@ -60,6 +60,8 @@ define(function(require){ self.vmboxesRender({ voicemailId: vmbox.id }); }; + setTimeout(function() { template.find('.search-query').focus(); }); + template.find('.settings').on('click', function() { self.vmboxesRenderEdit($(this).parents('.grid-row').data('id'), callbackSave); }); From 88a1de0f2ac81d9050994214dd406696a3e2a135 Mon Sep 17 00:00:00 2001 From: Joris Tirado Date: Wed, 15 Apr 2015 13:44:25 -0700 Subject: [PATCH 3/4] UI-1351: Add ability to configure feature keys for SIP devices in SmartPBX --- i18n/en-US.json | 30 +++++++ i18n/fr-FR.json | 28 ++++++ submodules/devices/devices.css | 14 +++ submodules/devices/devices.js | 154 +++++++++++++++++++++++++++++++-- views/devices-sip_device.html | 119 +++++++++++++++++++------ 5 files changed, 312 insertions(+), 33 deletions(-) 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 @@ -
+
- {{#if provision}} -
-
{{provision.endpoint_brand}} - {{provision.endpoint_model}}
+ {{#if device.provision}} +
+
{{device.provision.endpoint_brand}} - {{device.provision.endpoint_model}}
{{else}}
- {{#if id}} -
{{name}}
+ {{#if device.id}} +
{{device.name}}
{{else}}
{{i18n.devices.sip_device.new}}
{{/if}} @@ -26,7 +26,7 @@ @@ -45,41 +48,41 @@
- +
- {{#if provision}} + {{#if device.provision}}
- +
{{else}}
- +
- +
- {{ sip.realm }} + {{ device.sip.realm }}
{{/if}}
- {{#if provision}} + {{#if device.provision}}
{{ i18n.devices.popupSettings.sip.sectionTitle }} @@ -87,21 +90,21 @@
- +
- +
- {{ sip.realm }} + {{ device.sip.realm }}
@@ -141,7 +144,7 @@ {{ i18n.devices.popupSettings.restrictions.sectionTitle }}
- {{#each extra.restrictions}} + {{#each device.extra.restrictions}}
- {{#if extra.hasDisabledRestrictions}} + {{#if device.extra.hasDisabledRestrictions}}
   @@ -179,14 +182,14 @@ {{ i18n.devices.popupSettings.callerId.sectionTitle }}
- {{#if extra.hasE911Numbers}} + {{#if device.extra.hasE911Numbers}}
@@ -217,7 +220,7 @@
@@ -227,7 +230,7 @@
@@ -236,24 +239,84 @@ {{ i18n.devices.popupSettings.miscellaneous.rtp.type }} +
+
+
+ + {{#if device.provision.feature_keys}} +
+
+ {{ i18n.devices.popupSettings.featureKeys.menuTitle }} +
+
+ {{ i18n.devices.popupSettings.featureKeys.info.link.showInfo }} +
+ {{#each featureKeys.types}} +

{{#if info}}{{text}}: {{info}}{{/if}}

+ {{/each}} +
+
+ {{#each device.provision.feature_keys}} +
+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {{/each}}
+ {{/if}}
- {{#if id}} + {{#if device.id}} {{ i18n.devices.deleteDevice }} {{/if}}
{{ i18n.cancel }}