Browse Source

UI-1351: Add ability to configure feature keys for SIP devices in SmartPBX

4.3
Joris Tirado 11 years ago
parent
commit
88a1de0f2a
5 changed files with 312 additions and 33 deletions
  1. +30
    -0
      i18n/en-US.json
  2. +28
    -0
      i18n/fr-FR.json
  3. +14
    -0
      submodules/devices/devices.css
  4. +149
    -5
      submodules/devices/devices.js
  5. +91
    -28
      views/devices-sip_device.html

+ 30
- 0
i18n/en-US.json View File

@ -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",


+ 28
- 0
i18n/fr-FR.json View File

@ -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",


+ 14
- 0
submodules/devices/devices.css View File

@ -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;


+ 149
- 5
submodules/devices/devices.js View File

@ -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();
}
});
}
};


+ 91
- 28
views/devices-sip_device.html View File

@ -1,16 +1,16 @@
<div class="edit-device" data-id="{{id}}">
<div class="edit-device" data-id="{{device.id}}">
<div class="title-bar clearfix">
<div class="device-title pull-left">
{{#if provision}}
<div class="device-image model-image {{toLowerCase provision.endpoint_brand}}-{{toLowerCase provision.endpoint_family}}-{{toLowerCase provision.endpoint_model}}"></div>
<div class="device-model">{{provision.endpoint_brand}} - {{provision.endpoint_model}}</div>
{{#if device.provision}}
<div class="device-image model-image {{toLowerCase device.provision.endpoint_brand}}-{{toLowerCase device.provision.endpoint_family}}-{{toLowerCase device.provision.endpoint_model}}"></div>
<div class="device-model">{{device.provision.endpoint_brand}} - {{device.provision.endpoint_model}}</div>
{{else}}
<div class="device-icon">
<i class="icon-telicon-voip-phone"></i>
</div>
{{#if id}}
<div class="device-model">{{name}}</div>
{{#if device.id}}
<div class="device-model">{{device.name}}</div>
{{else}}
<div class="device-model">{{i18n.devices.sip_device.new}}</div>
{{/if}}
@ -26,7 +26,7 @@
<b class="caret"></b>
</a>
<ul class="dropdown-menu pull-right">
{{#if provision}}
{{#if device.provision}}
<li><a class="tabs-selector change-section" data-section="sip" href="javascript:void(0)"><i class="icon-user"></i>{{ i18n.devices.popupSettings.sip.menuTitle }}</a></li>
{{/if}}
<li><a class="tabs-selector change-section" data-section="audio" href="javascript:void(0)"><i class="icon-music"></i>{{ i18n.devices.popupSettings.audio.menuTitle }}</a></li>
@ -34,6 +34,9 @@
<li><a class="tabs-selector change-section" data-section="restrictions" href="javascript:void(0)"><i class="icon-ban-circle"></i>{{ i18n.devices.popupSettings.restrictions.menuTitle }}</a></li>
<li><a class="tabs-selector change-section" data-section="callerId" href="javascript:void(0)"><i class="icon-ambulance"></i>{{ i18n.devices.popupSettings.callerId.menuTitle }}</a></li>
<li><a class="tabs-selector change-section" data-section="miscellaneous" href="javascript:void(0)"><i class="icon-cogs"></i>{{ i18n.devices.popupSettings.miscellaneous.menuTitle }}</a></li>
{{#if device.provision.feature_keys}}
<li><a class="tabs-selector change-section" data-section="featureKeys" href="javascript:void(0);"><i class="icon-lightbulb"></i>{{ i18n.devices.popupSettings.featureKeys.menuTitle }}</a></li>
{{/if}}
</ul>
</li>
</ul>
@ -45,41 +48,41 @@
<div class="control-group">
<label class="control-label" for="name">{{ i18n.devices.sip_device.deviceName }}</label>
<div class="controls">
<input type="text" id="name" name="name" value="{{name}}">
<input type="text" id="name" name="name" value="{{device.name}}">
</div>
</div>
{{#if provision}}
{{#if device.provision}}
<div class="control-group">
<label class="control-label" for="mac_address">{{ i18n.devices.sip_device.macAddress }}</label>
<div class="controls">
<input id="mac_address" type="text" name="mac_address" placeholder="{{ i18n.devices.sip_device.macAddressPlaceholder }}" value="{{mac_address}}">
<input id="mac_address" type="text" name="mac_address" placeholder="{{ i18n.devices.sip_device.macAddressPlaceholder }}" value="{{device.mac_address}}">
</div>
</div>
{{else}}
<div class="control-group">
<label class="control-label" for="sip_username">{{ i18n.devices.sip.username }}</label>
<div class="controls">
<input type="text" id="sip_username" name="sip.username" value="{{sip.username}}">
<input type="text" id="sip_username" name="sip.username" value="{{device.sip.username}}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="sip_password">{{ i18n.devices.sip.password }}</label>
<div class="controls">
<input type="password" id="sip_password" name="sip.password" value="{{sip.password}}">
<input type="password" id="sip_password" name="sip.password" value="{{device.sip.password}}">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ i18n.devices.sip.realm }}</label>
<div class="controls">
<span class="displayed-realm">{{ sip.realm }}</span>
<span class="displayed-realm">{{ device.sip.realm }}</span>
</div>
</div>
{{/if}}
</div>
{{#if provision}}
{{#if device.provision}}
<div class="tabs-section clearfix" data-section="sip">
<div class="title">
{{ i18n.devices.popupSettings.sip.sectionTitle }}
@ -87,21 +90,21 @@
<div class="control-group">
<label class="control-label" for="sip_username">{{ i18n.devices.sip.username }}</label>
<div class="controls">
<input type="text" id="sip_username" name="sip.username" value="{{sip.username}}">
<input type="text" id="sip_username" name="sip.username" value="{{device.sip.username}}">
</div>
</div>
<div class="control-group">
<label class="control-label" for="sip_password">{{ i18n.devices.sip.password }}</label>
<div class="controls">
<input type="password" id="sip_password" name="sip.password" value="{{sip.password}}">
<input type="password" id="sip_password" name="sip.password" value="{{device.sip.password}}">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ i18n.devices.sip.realm }}</label>
<div class="controls">
<span class="displayed-realm">{{ sip.realm }}</span>
<span class="displayed-realm">{{ device.sip.realm }}</span>
</div>
</div>
</div>
@ -141,7 +144,7 @@
{{ i18n.devices.popupSettings.restrictions.sectionTitle }}
</div>
<div class="restriction-list">
{{#each extra.restrictions}}
{{#each device.extra.restrictions}}
<div class="control-group restriction-line{{#if this.disabled}} disabled{{/if}}" data-restriction="{{@key}}">
<label class="control-label">{{this.friendly_name}}
{{#if this.help}}
@ -161,7 +164,7 @@
</div>
<div class="restriction-message help-box">
</div>
{{#if extra.hasDisabledRestrictions}}
{{#if device.extra.hasDisabledRestrictions}}
<div class="disabled-restrictions-info help-box gray-box">
<div class="wrapper-icon">
&nbsp;<i class="icon-info-sign2"></i>&nbsp;
@ -179,14 +182,14 @@
{{ i18n.devices.popupSettings.callerId.sectionTitle }}
</div>
{{#if extra.hasE911Numbers}}
{{#if device.extra.hasE911Numbers}}
<div class="control-group">
<label class="control-label">{{ i18n.devices.popupSettings.callerId.emergency }}</label>
<div class="controls">
<select name="caller_id.emergency.number" class="caller-id-select">
<option value="">{{i18n.devices.popupSettings.callerId.notSet}}</option>
{{#each extra.e911Numbers}}
<option {{#compare ../this.caller_id.emergency.number "===" this}} selected{{/compare}} value="{{this}}">{{this}}</option>
{{#each device.extra.e911Numbers}}
<option {{#compare ../device.caller_id.emergency.number "===" this}} selected{{/compare}} value="{{this}}">{{this}}</option>
{{/each}}
</select>
</div>
@ -217,7 +220,7 @@
<div class="control-group">
<label class="control-label checkbox">
{{#monsterCheckbox i18n.devices.popupSettings.miscellaneous.ignoreCompletedElsewhere.label }}
<input type="checkbox" name="ignore_completed_elsewhere" id="ignore_completed_elsewhere" {{#if ignore_completed_elsewhere }}checked{{/if}} />
<input type="checkbox" name="ignore_completed_elsewhere" id="ignore_completed_elsewhere" {{#if device.ignore_completed_elsewhere }}checked{{/if}} />
{{/monsterCheckbox}}
<i class="help-popover icon-question-sign icon-large" data-original-title="{{i18n.devices.popupSettings.miscellaneous.ignoreCompletedElsewhere.help}}" data-placement="top" data-toggle="tooltip"></i>
</label>
@ -227,7 +230,7 @@
<div class="control-group">
<label class="control-label checkbox">
{{#monsterCheckbox i18n.devices.popupSettings.miscellaneous.rtp.enable }}
<input type="checkbox" id="secure_rtp" name="media.encryption.enforce_security"{{#if media.encryption.enforce_security}} checked="checked"{{/if}}></input>
<input type="checkbox" id="secure_rtp" name="media.encryption.enforce_security"{{#if device.media.encryption.enforce_security}} checked="checked"{{/if}}></input>
{{/monsterCheckbox}}
</label>
</div>
@ -236,24 +239,84 @@
<span>{{ i18n.devices.popupSettings.miscellaneous.rtp.type }}</span>
<select class="input-small" name="extra.rtpMethod">
<option value="srtp"{{#compare extra.rtpMethod "===" "srtp"}} selected{{/compare}}>{{ i18n.devices.popupSettings.miscellaneous.rtp.srtp }}</option>
<option value="zrtp"{{#compare extra.rtpMethod "===" "zrtp"}} selected{{/compare}}>{{ i18n.devices.popupSettings.miscellaneous.rtp.zrtp }}</option>
<option value="srtp"{{#compare device.extra.rtpMethod "===" "srtp"}} selected{{/compare}}>{{ i18n.devices.popupSettings.miscellaneous.rtp.srtp }}</option>
<option value="zrtp"{{#compare device.extra.rtpMethod "===" "zrtp"}} selected{{/compare}}>{{ i18n.devices.popupSettings.miscellaneous.rtp.zrtp }}</option>
</select>
</div>
</div>
</div>
{{#if device.provision.feature_keys}}
<div class="tabs-section clearfix" data-section="featureKeys">
<div class="title">
{{ i18n.devices.popupSettings.featureKeys.menuTitle }}
</div>
<div class="type-info helper">
<a href="javascript:void(0);" data-toggle="collapse" data-target="#info_content"><i class="icon-question-sign"></i><span class="text">{{ i18n.devices.popupSettings.featureKeys.info.link.showInfo }}</span></a>
<div id="info_content" class="collapse">
{{#each featureKeys.types}}
<p>{{#if info}}<strong>{{text}}</strong>: {{info}}{{/if}}</p>
{{/each}}
</div>
</div>
{{#each device.provision.feature_keys}}
<div class="control-group" data-id="{{@key}}">
<label for="provision.feature_keys[{{@key}}].type" class="control-label">
{{ ../i18n.devices.popupSettings.featureKeys.description }} <span class="feature-key-index">{{@key}}</span>
</label>
<div class="controls">
<select name="provision.feature_keys[{{@key}}].type" class="feature-key-type span2">
{{#select type}}
{{#each ../../featureKeys.types}}
<option value="{{id}}">{{text}}</option>
{{/each}}
{{/select}}
</select>
<div class="feature-key-value" data-type="presence">
<label for="provision.feature_keys[{{@key}}].value">{{ ../i18n.devices.popupSettings.featureKeys.labels.user }}</label>
<select name="provision.feature_keys[{{@key}}].value">
{{#each ../../users}}
<option value="{{id}}">{{first_name}} {{last_name}}</option>
{{/each}}
</select>
</div>
<div class="feature-key-value" data-type="parking">
<label for="provision.feature_keys[{{@key}}].value">{{ ../i18n.devices.popupSettings.featureKeys.labels.parkingSpot }}</label>
<select class="span1" name="provision.feature_keys[{{@key}}].value">
{{#each ../../featureKeys.parkingSpots}}
<option value="{{this}}">{{this}}</option>
{{/each}}
</select>
</div>
<div class="feature-key-value" data-type="personal_parking">
<label for="provision.feature_keys[{{@key}}].value">{{ ../i18n.devices.popupSettings.featureKeys.labels.user }}</label>
<select name="provision.feature_keys[{{@key}}].value">
{{#each ../../users}}
<option value="{{id}}">{{first_name}} {{last_name}}</option>
{{/each}}
</select>
</div>
<div class="feature-key-value" data-type="speed_dial">
<label for="provision.feature_keys[{{@key}}].value">{{ ../i18n.devices.popupSettings.featureKeys.labels.value }}</label>
<input type="text" value="" name="provision.feature_keys[{{@key}}].value">
</div>
</div>
</div>
{{/each}}
</div>
{{/if}}
</form>
</div>
<div class="actions clearfix">
{{#if id}}
{{#if device.id}}
<a id="delete_device" class="monster-link"><i class="icon-trash icon-red"></i>{{ i18n.devices.deleteDevice }}</a>
{{/if}}
<div class="pull-right">
<a class="cancel-link monster-link blue" href="javascript:void(0);">{{ i18n.cancel }}</a>
<button type="button" class="btn btn-success save">
{{#if id}}
{{#if device.id}}
{{ i18n.saveChanges }}
{{else}}
{{ i18n.devices.createDevice }}


Loading…
Cancel
Save