define(function(require) {
|
|
var $ = require('jquery'),
|
|
_ = require('lodash'),
|
|
monster = require('monster');
|
|
|
|
var app = {
|
|
requests: {
|
|
'callflows.device.getProvisionerPhones': {
|
|
'apiRoot': monster.config.api.provisioner,
|
|
'url': 'phones/',
|
|
'verb': 'GET',
|
|
'headers': {
|
|
'Accept': '*/*'
|
|
}
|
|
}
|
|
},
|
|
|
|
subscribe: {
|
|
'callflows.fetchActions': 'deviceDefineActions',
|
|
'callflows.device.popupEdit': 'devicePopupEdit',
|
|
'callflows.device.edit': '_deviceEdit'
|
|
},
|
|
|
|
devicePopupEdit: function(args) {
|
|
var self = this,
|
|
popup,
|
|
popup_html,
|
|
data = args.data,
|
|
callback = args.callback,
|
|
data_defaults = args.data_defaults;
|
|
|
|
popup_html = $('<div class="inline_popup callflows-port"><div class="inline_content main_content"/></div>');
|
|
|
|
self.deviceEdit(data, popup_html, $('.inline_content', popup_html), {
|
|
save_success: function(_data) {
|
|
popup.dialog('close');
|
|
|
|
if (typeof callback === 'function') {
|
|
callback(_data);
|
|
}
|
|
},
|
|
delete_success: function() {
|
|
popup.dialog('close');
|
|
|
|
if (typeof callback === 'function') {
|
|
callback({ data: {} });
|
|
}
|
|
},
|
|
after_render: function() {
|
|
popup = monster.ui.dialog(popup_html, {
|
|
title: (data.id) ? self.i18n.active().callflows.device.edit_device : self.i18n.active().callflows.device.create_device
|
|
});
|
|
}
|
|
}, data_defaults);
|
|
},
|
|
|
|
// Added for the subscribed event to avoid refactoring deviceEdit
|
|
_deviceEdit: function(args) {
|
|
var self = this;
|
|
self.deviceEdit(args.data, args.parent, args.target, args.callbacks, args.data_defaults);
|
|
},
|
|
|
|
deviceEdit: function(data, _parent, _target, _callbacks, data_defaults) {
|
|
var self = this,
|
|
parent = _parent || $('#device-content'),
|
|
target = _target || $('#device-view', parent),
|
|
_callbacks = _callbacks || {},
|
|
callbacks = {
|
|
save_success: _callbacks.save_success,
|
|
save_error: _callbacks.save_error || function(_data, status, type) {
|
|
if (status === 200 && type === 'mac_address') {
|
|
monster.ui.alert(self.i18n.active().callflows.device.this_mac_address_is_already_in_use);
|
|
}
|
|
},
|
|
delete_success: _callbacks.delete_success,
|
|
delete_error: _callbacks.delete_error,
|
|
after_render: _callbacks.after_render
|
|
},
|
|
defaults = {
|
|
data: $.extend(true, {
|
|
enabled: true,
|
|
caller_id: {
|
|
external: {},
|
|
internal: {},
|
|
emergency: {}
|
|
},
|
|
ringtones: {},
|
|
ignore_completed_elsewhere: true,
|
|
call_restriction: { closed_groups: 'inherit' },
|
|
media: {
|
|
secure_rtp: 'none',
|
|
audio: {
|
|
codecs: ['PCMU', 'PCMA']
|
|
},
|
|
video: {
|
|
codecs: []
|
|
},
|
|
fax: {
|
|
option: 'false'
|
|
},
|
|
fax_option: false
|
|
},
|
|
sip: {
|
|
method: 'password',
|
|
invite_format: 'contact',
|
|
username: 'user_' + monster.util.randomString(6),
|
|
password: monster.util.randomString(12),
|
|
expire_seconds: '360'
|
|
},
|
|
contact_list: {
|
|
exclude: false
|
|
},
|
|
call_forward: {},
|
|
music_on_hold: {}
|
|
}, data_defaults || {}),
|
|
|
|
field_data: {
|
|
users: [],
|
|
call_restriction: {},
|
|
sip: {
|
|
methods: {
|
|
'password': self.i18n.active().callflows.device.password,
|
|
'ip': 'IP'
|
|
},
|
|
invite_formats: {
|
|
'username': 'Username',
|
|
'npan': 'NPA NXX XXXX',
|
|
'e164': 'E. 164',
|
|
'1npan': '1npan',
|
|
'route': 'Route',
|
|
'contact': 'Contact'
|
|
}
|
|
},
|
|
media: {
|
|
secure_rtp: {
|
|
value: 'none',
|
|
options: {
|
|
'none': self.i18n.active().callflows.device.none,
|
|
'srtp': self.i18n.active().callflows.device.srtp,
|
|
'zrtp': self.i18n.active().callflows.device.zrtp
|
|
}
|
|
},
|
|
fax: {
|
|
options: {
|
|
'auto': self.i18n.active().callflows.device.auto_detect,
|
|
'true': self.i18n.active().callflows.device.always_force,
|
|
'false': self.i18n.active().callflows.device.disabled
|
|
}
|
|
},
|
|
audio: {
|
|
codecs: {
|
|
'OPUS': 'OPUS',
|
|
'CELT@32000h': 'Siren @ 32Khz',
|
|
'G7221@32000h': 'G722.1 @ 32khz',
|
|
'G7221@16000h': 'G722.1 @ 16khz',
|
|
'G722': 'G722',
|
|
'speex@32000h': 'Speex @ 32khz',
|
|
'speex@16000h': 'Speex @ 16khz',
|
|
'PCMU': 'G711u / PCMU - 64kbps (North America)',
|
|
'PCMA': 'G711a / PCMA - 64kbps (Elsewhere)',
|
|
'G729': 'G729 - 8kbps (Requires License)',
|
|
'GSM': 'GSM',
|
|
'CELT@48000h': 'Siren (HD) @ 48kHz',
|
|
'CELT@64000h': 'Siren (HD) @ 64kHz'
|
|
}
|
|
},
|
|
video: {
|
|
codecs: {
|
|
'VP8': 'VP8',
|
|
'H264': 'H264',
|
|
'H263': 'H263',
|
|
'H261': 'H261'
|
|
}
|
|
}
|
|
},
|
|
hide_owner: data.hide_owner || false,
|
|
outbound_flags: data.outbound_flags ? data.outbound_flags.join(', ') : data.outbound_flags
|
|
},
|
|
functions: {
|
|
inArray: function(value, array) {
|
|
if (array) {
|
|
return ($.inArray(value, array) === -1) ? false : true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
parallelRequests = function(deviceData) {
|
|
monster.parallel({
|
|
list_classifier: function(callback) {
|
|
self.callApi({
|
|
resource: 'numbers.listClassifiers',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(_data_classifiers) {
|
|
if ('data' in _data_classifiers) {
|
|
$.each(_data_classifiers.data, function(k, v) {
|
|
defaults.field_data.call_restriction[k] = {
|
|
friendly_name: v.friendly_name
|
|
};
|
|
|
|
defaults.data.call_restriction[k] = { action: 'inherit' };
|
|
});
|
|
}
|
|
callback(null, _data_classifiers);
|
|
}
|
|
});
|
|
},
|
|
account: function(callback) {
|
|
self.callApi({
|
|
resource: 'account.get',
|
|
data: {
|
|
accountId: self.accountId
|
|
},
|
|
success: function(_data, status) {
|
|
$.extend(defaults.field_data.sip, {
|
|
realm: _data.data.realm
|
|
});
|
|
|
|
callback(null, _data);
|
|
}
|
|
});
|
|
},
|
|
user_list: function(callback) {
|
|
self.callApi({
|
|
resource: 'user.list',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(_data, status) {
|
|
_data.data.sort(function(a, b) {
|
|
return (a.first_name + a.last_name).toLowerCase() < (b.first_name + b.last_name).toLowerCase() ? -1 : 1;
|
|
});
|
|
|
|
_data.data.unshift({
|
|
id: '',
|
|
first_name: '- No',
|
|
last_name: 'owner -'
|
|
});
|
|
|
|
if (deviceData.hasOwnProperty('device_type') && deviceData.device_type === 'mobile') {
|
|
var userData = _.find(_data.data, function(user) { return user.id === deviceData.owner_id; });
|
|
|
|
if (userData) {
|
|
defaults.field_data.users = userData;
|
|
} else {
|
|
defaults.field_data.users = {
|
|
first_name: '- No',
|
|
last_name: 'owner -'
|
|
};
|
|
}
|
|
} else {
|
|
defaults.field_data.users = _data.data;
|
|
}
|
|
|
|
callback(null, _data);
|
|
}
|
|
});
|
|
},
|
|
media_list: function(callback) {
|
|
self.callApi({
|
|
resource: 'media.list',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(_data, status) {
|
|
_data.data.unshift(
|
|
{
|
|
id: '',
|
|
name: self.i18n.active().callflows.device.default_music
|
|
},
|
|
{
|
|
id: 'silence_stream://300000',
|
|
name: self.i18n.active().callflows.device.silence
|
|
},
|
|
{
|
|
id: 'shoutcast',
|
|
name: self.i18n.active().callflows.accountSettings.musicOnHold.shoutcastURL
|
|
}
|
|
);
|
|
|
|
defaults.field_data.music_on_hold = _data.data;
|
|
|
|
callback(null, _data);
|
|
}
|
|
});
|
|
},
|
|
provisionerData: function(callback) {
|
|
if (monster.config.api.hasOwnProperty('provisioner') && monster.config.api.provisioner) {
|
|
self.deviceGetDataProvisoner(function(data) {
|
|
callback(null, data);
|
|
});
|
|
} else {
|
|
callback(null, {});
|
|
}
|
|
}
|
|
},
|
|
function(err, results) {
|
|
var render_data = self.devicePrepareDataForTemplate(data, defaults, $.extend(true, results, {
|
|
get_device: deviceData
|
|
}));
|
|
|
|
self.deviceRender(render_data, target, callbacks);
|
|
|
|
if (typeof callbacks.after_render === 'function') {
|
|
callbacks.after_render();
|
|
}
|
|
});
|
|
};
|
|
|
|
if (typeof data === 'object' && data.id) {
|
|
self.deviceGet(data.id, function(_data, status) {
|
|
defaults.data.device_type = 'sip_device';
|
|
|
|
if ('media' in _data && 'encryption' in _data.media) {
|
|
defaults.field_data.media.secure_rtp.value = _data.media.encryption.enforce_security ? _data.media.encryption.methods[0] : 'none';
|
|
}
|
|
|
|
if ('sip' in _data && 'realm' in _data.sip) {
|
|
defaults.field_data.sip.realm = _data.sip.realm;
|
|
}
|
|
|
|
self.deviceMigrateData(_data);
|
|
|
|
parallelRequests(_data);
|
|
});
|
|
} else {
|
|
parallelRequests(defaults);
|
|
}
|
|
},
|
|
|
|
devicePrepareDataForTemplate: function(data, dataGlobal, results) {
|
|
var self = this,
|
|
dataDevice = results.get_device,
|
|
dataProvisioner = results.provisionerData;
|
|
|
|
if (typeof data === 'object' && data.id) {
|
|
dataGlobal = $.extend(true, dataGlobal, { data: dataDevice });
|
|
}
|
|
|
|
if (dataDevice.hasOwnProperty('media') && dataDevice.media.hasOwnProperty('audio')) {
|
|
// If the codecs property is defined, override the defaults with it. Indeed, when an empty array is set as the
|
|
// list of codecs, it gets overwritten by the extend function otherwise.
|
|
if (dataDevice.media.audio.hasOwnProperty('codecs')) {
|
|
dataGlobal.data.media.audio.codecs = dataDevice.media.audio.codecs;
|
|
}
|
|
|
|
if (dataDevice.media.video.hasOwnProperty('codecs')) {
|
|
dataGlobal.data.media.video.codecs = dataDevice.media.video.codecs;
|
|
}
|
|
}
|
|
|
|
_.each(dataGlobal.field_data.call_restriction, function(restriction, key) {
|
|
restriction.value = dataGlobal.data.call_restriction[key].action;
|
|
});
|
|
|
|
dataGlobal.field_data.provisioner = dataProvisioner;
|
|
dataGlobal.field_data.provisioner.isEnabled = !_.isEmpty(dataProvisioner);
|
|
|
|
if (dataGlobal.field_data.provisioner.isEnabled) {
|
|
var default_provision_data = {
|
|
voicemail_beep: 1, //ie enabled
|
|
time_format: '12',
|
|
hotline: '',
|
|
vlan: {
|
|
enable: false,
|
|
number: ''
|
|
},
|
|
date_format: 'middle-endian'
|
|
};
|
|
|
|
dataGlobal.data.provision = $.extend(true, {}, default_provision_data, dataGlobal.data.provision);
|
|
}
|
|
|
|
dataGlobal.extra = dataGlobal.extra || {};
|
|
dataGlobal.extra.isShoutcast = false;
|
|
|
|
// if the value is set to a stream, we need to set the value of the media_id to shoutcast so it gets selected by the old select mechanism,
|
|
// but we also need to store the value so we can display it
|
|
if (dataGlobal.data.hasOwnProperty('music_on_hold') && dataGlobal.data.music_on_hold.hasOwnProperty('media_id')) {
|
|
if (dataGlobal.data.music_on_hold.media_id.indexOf('://') >= 0) {
|
|
if (dataGlobal.data.music_on_hold.media_id !== 'silence_stream://300000') {
|
|
dataGlobal.extra.isShoutcast = true;
|
|
dataGlobal.extra.shoutcastValue = dataGlobal.data.music_on_hold.media_id;
|
|
dataGlobal.data.music_on_hold.media_id = 'shoutcast';
|
|
}
|
|
}
|
|
}
|
|
|
|
return dataGlobal;
|
|
},
|
|
|
|
deviceGetValidationByDeviceType: function(deviceType) {
|
|
var self = this,
|
|
i18n = self.i18n.active(),
|
|
validation = {
|
|
ata: {
|
|
'sip.ip': {
|
|
required: true,
|
|
ipv4: true
|
|
}
|
|
},
|
|
sip_uri: {},
|
|
sip_device: {
|
|
'mac_address': { mac: true },
|
|
'sip_expire_seconds': { digits: true },
|
|
'sip.ip': {
|
|
ipv4: true,
|
|
required: true
|
|
},
|
|
'extra.shoutcastUrl': { protocol: true }
|
|
},
|
|
fax: {
|
|
'mac_address': { mac: true },
|
|
'sip_expire_seconds': { digits: true },
|
|
'sip.ip': {
|
|
ipv4: true,
|
|
required: true
|
|
}
|
|
},
|
|
cellphone: {},
|
|
smartphone: {
|
|
'sip_expire_seconds': { digits: true },
|
|
'sip.ip': {
|
|
ipv4: true,
|
|
required: true
|
|
}
|
|
},
|
|
landline: {},
|
|
softphone: {
|
|
'sip_expire_seconds': { digits: true },
|
|
'extra.shoutcastUrl': { protocol: true }
|
|
},
|
|
mobile: {
|
|
'mdn': { digits: true },
|
|
'sip_expire_seconds': { digits: true },
|
|
'extra.shoutcastUrl': { protocol: true }
|
|
}
|
|
},
|
|
deviceTypeValidation = {
|
|
rules: validation[deviceType]
|
|
};
|
|
|
|
if (_.includes(['ata', 'fax', 'mobile', 'sip_device', 'softphone'], deviceType)) {
|
|
_.merge(deviceTypeValidation, {
|
|
rules: {
|
|
'caller_id.asserted.name': { regex: /^[0-9A-Za-z ,]{0,30}$/ },
|
|
'caller_id.asserted.number': { phoneNumber: true },
|
|
'caller_id.asserted.realm': { realm: true }
|
|
},
|
|
messages: {
|
|
'caller_id.asserted.name': { regex: i18n.callflows.device.validation.caller_id.name },
|
|
'caller_id.asserted.number': { regex: i18n.callflows.device.validation.caller_id.number },
|
|
'caller_id.asserted.realm': { regex: i18n.callflows.device.validation.caller_id.realm }
|
|
}
|
|
});
|
|
}
|
|
|
|
return deviceTypeValidation;
|
|
},
|
|
|
|
deviceRender: function(data, target, callbacks) {
|
|
var self = this,
|
|
device_html;
|
|
|
|
if ('media' in data.data && 'fax_option' in data.data.media) {
|
|
data.data.media.fax_option = (data.data.media.fax_option === 'auto' || data.data.media.fax_option === true);
|
|
}
|
|
|
|
if (typeof data.data === 'object' && data.data.device_type) {
|
|
device_html = $(self.getTemplate({
|
|
name: 'device-' + data.data.device_type,
|
|
data: _.merge({
|
|
showPAssertedIdentity: monster.config.whitelabel.showPAssertedIdentity
|
|
}, data),
|
|
submodule: 'device'
|
|
}));
|
|
|
|
var deviceForm = device_html.find('#device-form');
|
|
|
|
if (monster.config.api.hasOwnProperty('provisioner') && monster.config.api.provisioner) {
|
|
self.deviceSetProvisionerStuff(device_html, data);
|
|
}
|
|
|
|
/* Do device type specific things here */
|
|
if ($.inArray(data.data.device_type, ['fax', 'softphone', 'sip_device', 'smartphone', 'mobile', 'ata']) > -1) {
|
|
monster.ui.protectField(device_html.find('#sip_password'), device_html);
|
|
}
|
|
|
|
monster.ui.validate(deviceForm, self.deviceGetValidationByDeviceType(data.data.device_type));
|
|
|
|
if (!$('#owner_id', device_html).val()) {
|
|
$('#edit_link', device_html).hide();
|
|
}
|
|
|
|
device_html.find('input[data-mask]').each(function() {
|
|
var $this = $(this);
|
|
monster.ui.mask($this, $this.data('mask'));
|
|
});
|
|
|
|
if (!$('#music_on_hold_media_id', device_html).val()) {
|
|
$('#edit_link_media', device_html).hide();
|
|
}
|
|
|
|
if (data.data.sip && data.data.sip.method === 'ip') {
|
|
$('#username_block', device_html).hide();
|
|
} else {
|
|
$('#ip_block', device_html).hide();
|
|
}
|
|
} else {
|
|
device_html = $(self.getTemplate({
|
|
name: 'general_edit',
|
|
submodule: 'device'
|
|
}));
|
|
|
|
$('.media_pane', device_html).hide();
|
|
}
|
|
|
|
$('*[rel=popover]:not([type="text"])', device_html).popover({
|
|
trigger: 'hover'
|
|
});
|
|
|
|
$('*[rel=popover][type="text"]', device_html).popover({
|
|
trigger: 'focus'
|
|
});
|
|
|
|
self.winkstartTabs(device_html);
|
|
|
|
self.deviceBindEvents({
|
|
data: data,
|
|
template: device_html,
|
|
callbacks: callbacks
|
|
});
|
|
|
|
(target)
|
|
.empty()
|
|
.append(device_html);
|
|
|
|
$('.media_tabs .buttons[device_type="sip_device"]', device_html).trigger('click');
|
|
},
|
|
|
|
/**
|
|
* Bind events for the device edit template
|
|
* @param {Object} args
|
|
* @param {Object} args.data
|
|
* @param {Object} args.template
|
|
* @param {Object} args.callbacks
|
|
* @param {Function} args.callbacks.save_success
|
|
* @param {Function} args.callbacks.delete_success
|
|
*/
|
|
deviceBindEvents: function(args) {
|
|
var self = this,
|
|
data = args.data,
|
|
callbacks = args.callbacks,
|
|
device_html = args.template;
|
|
|
|
if (typeof data.data === 'object' && data.data.device_type) {
|
|
var deviceForm = device_html.find('#device-form');
|
|
|
|
$('#owner_id', device_html).change(function() {
|
|
!$('#owner_id option:selected', device_html).val() ? $('#edit_link', device_html).hide() : $('#edit_link', device_html).show();
|
|
});
|
|
|
|
$('.inline_action', device_html).click(function(ev) {
|
|
var _data = ($(this).data('action') === 'edit') ? { id: $('#owner_id', device_html).val() } : {},
|
|
_id = _data.id;
|
|
|
|
ev.preventDefault();
|
|
|
|
monster.pub('callflows.user.popupEdit', {
|
|
data: _data,
|
|
callback: function(user) {
|
|
/* Create */
|
|
if (!_id) {
|
|
$('#owner_id', device_html).append('<option id="' + user.id + '" value="' + user.id + '">' + user.first_name + ' ' + user.last_name + '</option>');
|
|
$('#owner_id', device_html).val(user.id);
|
|
$('#edit_link', device_html).show();
|
|
} else {
|
|
/* Update */
|
|
if (_data.hasOwnProperty('id')) {
|
|
$('#owner_id #' + user.id, device_html).text(user.first_name + ' ' + user.last_name);
|
|
/* Delete */
|
|
} else {
|
|
$('#owner_id #' + _id, device_html).remove();
|
|
$('#edit_link', device_html).hide();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
$('.device-save', device_html).click(function(ev) {
|
|
ev.preventDefault();
|
|
|
|
var $this = $(this);
|
|
|
|
if (!$this.hasClass('disabled')) {
|
|
$this.addClass('disabled');
|
|
if (monster.ui.valid(deviceForm)) {
|
|
var form_data = monster.ui.getFormData('device-form');
|
|
|
|
if (form_data.hasOwnProperty('music_on_hold') && form_data.music_on_hold.media_id === 'shoutcast') {
|
|
form_data.music_on_hold.media_id = device_html.find('.shoutcast-url-input').val();
|
|
}
|
|
|
|
self.deviceCleanFormData(form_data);
|
|
|
|
if (form_data.hasOwnProperty('provision') && form_data.provision.hasOwnProperty('endpoint_brand') && form_data.provision.endpoint_brand !== 'none') {
|
|
var modelArray = $('.dropdown_family[data-brand="' + form_data.provision.endpoint_brand + '"]', device_html).val().split('.'),
|
|
endpoint_family = modelArray[0],
|
|
endpoint_model = modelArray[1];
|
|
|
|
// We have to set this manually since we have 3 dropdown with the same name we don't know which selected one is the correct one..
|
|
form_data.provision.endpoint_model = endpoint_model;
|
|
form_data.provision.endpoint_family = endpoint_family;
|
|
}
|
|
|
|
self.deviceSave(form_data, data, callbacks.save_success);
|
|
} else {
|
|
$this.removeClass('disabled');
|
|
monster.ui.alert('error', self.i18n.active().callflows.device.there_were_errors_on_the_form);
|
|
}
|
|
}
|
|
});
|
|
|
|
if (data.device_type !== 'mobile') {
|
|
$('.device-delete', device_html).click(function(ev) {
|
|
ev.preventDefault();
|
|
|
|
monster.ui.confirm(self.i18n.active().callflows.device.are_you_sure_you_want_to_delete, function() {
|
|
self.deviceDelete(data.data.id, callbacks.delete_success);
|
|
});
|
|
});
|
|
}
|
|
|
|
device_html.find('#sip_method').on('change', function() {
|
|
if ($('#sip_method option:selected', device_html).val() === 'ip') {
|
|
$('#ip_block', device_html).slideDown();
|
|
$('#username_block', device_html).slideUp();
|
|
} else {
|
|
$('#username_block', device_html).slideDown();
|
|
$('#ip_block', device_html).slideUp();
|
|
}
|
|
});
|
|
|
|
$('#music_on_hold_media_id', device_html).change(function() {
|
|
!$('#music_on_hold_media_id option:selected', device_html).val() ? $('#edit_link_media', device_html).hide() : $('#edit_link_media', device_html).show();
|
|
|
|
device_html.find('.shoutcast-div').toggleClass('active', $(this).val() === 'shoutcast');
|
|
});
|
|
|
|
$('.inline_action_media', device_html).click(function(ev) {
|
|
var _data = ($(this).data('action') === 'edit') ? { id: $('#music_on_hold_media_id', device_html).val() } : {},
|
|
_id = _data.id;
|
|
|
|
ev.preventDefault();
|
|
|
|
monster.pub('callflows.media.editPopup', {
|
|
data: _data,
|
|
callback: function(media) {
|
|
/* Create */
|
|
if (!_id) {
|
|
$('#music_on_hold_media_id', device_html).append('<option id="' + media.id + '" value="' + media.id + '">' + media.name + '</option>');
|
|
$('#music_on_hold_media_id', device_html).val(media.id);
|
|
|
|
$('#edit_link_media', device_html).show();
|
|
} else {
|
|
/* Update */
|
|
if (media.hasOwnProperty('id')) {
|
|
$('#music_on_hold_media_id #' + media.id, device_html).text(media.name);
|
|
/* Delete */
|
|
} else {
|
|
$('#music_on_hold_media_id #' + _id, device_html).remove();
|
|
$('#edit_link_media', device_html).hide();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
$('.media_tabs .buttons', device_html).click(function() {
|
|
var $this = $(this);
|
|
$('.media_pane', device_html).show();
|
|
|
|
if (!$this.hasClass('current')) {
|
|
$('.media_tabs .buttons').removeClass('current');
|
|
$this.addClass('current');
|
|
|
|
data.data.device_type = $this.attr('device_type');
|
|
|
|
self.deviceFormatData(data);
|
|
|
|
self.deviceRender(data, $('.media_pane', device_html), callbacks);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
deviceSetProvisionerStuff: function(device_html, data) {
|
|
var self = this,
|
|
set_value = function(brand_name, model_family, model_name) {
|
|
device_html.find('.dropdown_family').hide();
|
|
if (brand_name in data.field_data.provisioner.brands) {
|
|
device_html.find('#dropdown_brand').val(brand_name);
|
|
device_html
|
|
.find('.dropdown_family[data-brand="' + brand_name + '"]')
|
|
.show()
|
|
.val(model_family + '.' + model_name);
|
|
}
|
|
},
|
|
provisionData = data.data.provision,
|
|
regex_brands = {
|
|
'00085d': 'aastra',
|
|
'0010bc': 'aastra',
|
|
'00036b': 'cisco',
|
|
'00000c': 'cisco',
|
|
'000142': 'cisco',
|
|
'000143': 'cisco',
|
|
'000163': 'cisco',
|
|
'000164': 'cisco',
|
|
'000196': 'cisco',
|
|
'000197': 'cisco',
|
|
'0001c7': 'cisco',
|
|
'0001c9': 'cisco',
|
|
'000f23': 'cisco',
|
|
'0013c4': 'cisco',
|
|
'0016c8': 'cisco',
|
|
'001818': 'cisco',
|
|
'00175a': 'cisco',
|
|
'001795': 'cisco',
|
|
'001A2f': 'cisco',
|
|
'001c58': 'cisco',
|
|
'001dA2': 'cisco',
|
|
'002155': 'cisco',
|
|
'000e84': 'cisco',
|
|
'000e38': 'cisco',
|
|
'00070e': 'cisco',
|
|
'001bd4': 'cisco',
|
|
'001930': 'cisco',
|
|
'0019aa': 'cisco',
|
|
'001d45': 'cisco',
|
|
'001ef7': 'cisco',
|
|
'000e08': 'cisco',
|
|
'1cdf0f': 'cisco',
|
|
'e05fb9': 'cisco',
|
|
'5475d0': 'cisco',
|
|
'c46413': 'cisco',
|
|
'000Ffd3': 'digium',
|
|
'000b82': 'grandstream',
|
|
'08000f': 'mitel',
|
|
'1045bE': 'norphonic',
|
|
'0050c2': 'norphonic',
|
|
'0004f2': 'polycom',
|
|
'00907a': 'polycom',
|
|
'000413': 'snom',
|
|
'001f9f': 'thomson',
|
|
'00147f': 'thomson',
|
|
'642400': 'xorcom',
|
|
'001565': 'yealink'
|
|
};
|
|
|
|
set_value(provisionData.endpoint_brand, provisionData.endpoint_family, provisionData.endpoint_model);
|
|
|
|
device_html.find('#dropdown_brand').on('change', function() {
|
|
set_value($(this).val());
|
|
});
|
|
|
|
device_html.find('#mac_address').on('keyup', function() {
|
|
var mac_address = $(this).val().replace(/[^0-9a-fA-F]/g, '');
|
|
|
|
if (mac_address in regex_brands) {
|
|
set_value(regex_brands[mac_address]);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceFormatData: function(data) {
|
|
if (data.data.device_type === 'smartphone' || data.data.device_type === 'landline' || data.data.device_type === 'cellphone') {
|
|
data.data.call_forward = {
|
|
enabled: true,
|
|
require_keypress: true,
|
|
keep_caller_id: true
|
|
};
|
|
} else {
|
|
data.data.call_forward = {
|
|
enabled: false
|
|
};
|
|
}
|
|
|
|
if (data.data.device_type === 'sip_uri') {
|
|
data.data.sip.invite_format = 'route';
|
|
}
|
|
|
|
if (data.data.device_type === 'mobile') {
|
|
if (!('mobile' in data.data)) {
|
|
data.data.mobile = {
|
|
mdn: ''
|
|
};
|
|
}
|
|
}
|
|
|
|
if (data.data.device_type === 'fax') {
|
|
data.data.media.fax_option = true;
|
|
data.data.media.fax.option = 'true';
|
|
} else {
|
|
data.data.media.fax_option = false;
|
|
data.data.media.fax.option = 'false';
|
|
}
|
|
},
|
|
|
|
deviceMigrateData: function(data) {
|
|
var self = this;
|
|
|
|
if (data.hasOwnProperty('media') && data.media.hasOwnProperty('audio') && data.media.audio.hasOwnProperty('codecs')) {
|
|
var mapMigrateCodec = {
|
|
'Speex': 'speex@16000h',
|
|
'G722_16': 'G7221@16000h',
|
|
'G722_32': 'G7221@32000h',
|
|
'CELT_48': 'CELT@48000h',
|
|
'CELT_64': 'CELT@64000h'
|
|
},
|
|
newCodecList = [];
|
|
|
|
_.each(data.media.audio.codecs, function(codec) {
|
|
mapMigrateCodec.hasOwnProperty(codec) ? newCodecList.push(mapMigrateCodec[codec]) : newCodecList.push(codec);
|
|
});
|
|
|
|
data.media.audio.codecs = newCodecList;
|
|
}
|
|
|
|
if (data.device_type === 'cell_phone') {
|
|
data.device_type = 'cellphone';
|
|
}
|
|
|
|
if (typeof data.media === 'object' && typeof data.media.fax === 'object' && 'codecs' in data.media.fax) {
|
|
delete data.media.fax.codecs;
|
|
}
|
|
|
|
if ('status' in data) {
|
|
data.enabled = data.status;
|
|
delete data.status;
|
|
}
|
|
|
|
if (data.hasOwnProperty('ignore_complete_elsewhere')) {
|
|
data.ignore_completed_elsewhere = data.ignore_complete_elsewhere;
|
|
|
|
delete data.ignore_complete_elsewhere;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
deviceNormalizeData: function(data) {
|
|
var self = this;
|
|
|
|
if (data.hasOwnProperty('provision')) {
|
|
if (data.provision.endpoint_brand === 'none') {
|
|
delete data.provision;
|
|
} else {
|
|
if (data.provision.voicemail_beep !== 0) {
|
|
delete data.provision.voicemail_beep;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.hasOwnProperty('media') && data.media.hasOwnProperty('fax_option') && data.media.fax_option === 'auto') {
|
|
delete data.media.fax_option;
|
|
}
|
|
|
|
if ('media' in data && 'fax' in data.media && 'fax_option' in data.media) {
|
|
data.media.fax.option = data.media.fax_option.toString();
|
|
}
|
|
|
|
if ('media' in data && 'secure_rtp' in data.media) {
|
|
delete data.media.secure_rtp;
|
|
}
|
|
|
|
if ('media' in data && 'bypass_media' in data.media) {
|
|
delete data.media.bypass_media;
|
|
}
|
|
|
|
self.compactObject(data.caller_id);
|
|
|
|
if (_.isEmpty(data.caller_id)) {
|
|
delete data.caller_id;
|
|
}
|
|
|
|
if (!data.music_on_hold.media_id) {
|
|
delete data.music_on_hold.media_id;
|
|
}
|
|
|
|
if (!data.owner_id) {
|
|
delete data.owner_id;
|
|
}
|
|
|
|
if ($.isEmptyObject(data.call_forward)) {
|
|
delete data.call_forward;
|
|
}
|
|
|
|
if (!data.mac_address) {
|
|
delete data.mac_address;
|
|
}
|
|
|
|
if (data.sip.method !== 'ip') {
|
|
delete data.sip.ip;
|
|
}
|
|
|
|
if (typeof data.outbound_flags === 'string') {
|
|
data.outbound_flags = data.outbound_flags.split(/,/);
|
|
|
|
/* Get rid of empty string */
|
|
var new_flags = [];
|
|
$.each(data.outbound_flags, function(k, v) {
|
|
if (v.replace(/\s/g, '') !== '') {
|
|
new_flags.push(v);
|
|
}
|
|
});
|
|
data.outbound_flags = new_flags;
|
|
}
|
|
if (data.device_type === 'fax') {
|
|
if (!('outbound_flags' in data)) {
|
|
data.outbound_flags = ['fax'];
|
|
} else if (data.outbound_flags.indexOf('fax') < 0) {
|
|
data.outbound_flags.splice(0, 0, 'fax');
|
|
}
|
|
}
|
|
|
|
if (data.ringtones && 'internal' in data.ringtones && data.ringtones.internal === '') {
|
|
delete data.ringtones.internal;
|
|
}
|
|
|
|
if (data.ringtones && 'external' in data.ringtones && data.ringtones.external === '') {
|
|
delete data.ringtones.external;
|
|
}
|
|
|
|
// For devices who don't have sip creds, we need to use username, for sip url we already set it to "route", and for the others, the default is applied: "contact"
|
|
if ($.inArray(data.device_type, ['landline', 'cellphone']) >= 0) {
|
|
data.sip.invite_format = 'username';
|
|
}
|
|
|
|
if ($.inArray(data.device_type, ['fax', 'mobile', 'softphone', 'sip_device', 'smartphone']) < 0) {
|
|
delete data.call_restriction;
|
|
}
|
|
|
|
if (data.hasOwnProperty('presence_id') && data.presence_id === '') {
|
|
delete data.presence_id;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
deviceCleanFormData: function(form_data) {
|
|
if ('provision' in form_data && form_data.provision.voicemail_beep === true) {
|
|
form_data.provision.voicemail_beep = 0;
|
|
}
|
|
|
|
if (form_data.mac_address) {
|
|
form_data.mac_address = form_data.mac_address.toLowerCase();
|
|
|
|
if (form_data.mac_address.match(/^(((\d|([a-f]|[A-F])) {2}-) {5}(\d|([a-f]|[A-F])) {2})$/)) {
|
|
form_data.mac_address = form_data.mac_address.replace(/-/g, ':');
|
|
} else if (form_data.mac_address.match(/^(((\d|([a-f]|[A-F])) {2}) {5}(\d|([a-f]|[A-F])) {2})$/)) {
|
|
form_data.mac_address = form_data.mac_address.replace(/(.{2})/g, '$1:').slice(0, -1);
|
|
}
|
|
}
|
|
|
|
if (form_data.caller_id) {
|
|
form_data.caller_id.internal.number = form_data.caller_id.internal.number.replace(/\s|\(|\)|-|\./g, '');
|
|
form_data.caller_id.external.number = form_data.caller_id.external.number.replace(/\s|\(|\)|-|\./g, '');
|
|
form_data.caller_id.emergency.number = form_data.caller_id.emergency.number.replace(/\s|\(|\)|-|\./g, '');
|
|
|
|
var assertedNumber = _.get(form_data.caller_id, 'asserted.number', '');
|
|
if(!_.isEmpty(assertedNumber)) {
|
|
assertedNumber = monster.util.getFormatPhoneNumber(assertedNumber).e164Number;
|
|
}
|
|
// Need to keep the empty string so the new value is not lost when merging the form data with the original data
|
|
_.set(form_data.caller_id, 'asserted.number', assertedNumber);
|
|
}
|
|
|
|
if ('media' in form_data && 'audio' in form_data.media) {
|
|
form_data.media.audio.codecs = $.map(form_data.media.audio.codecs, function(val) { return (val) ? val : null; });
|
|
}
|
|
|
|
if ('media' in form_data && 'video' in form_data.media) {
|
|
form_data.media.video.codecs = $.map(form_data.media.video.codecs, function(val) { return (val) ? val : null; });
|
|
}
|
|
|
|
if (form_data.device_type === 'smartphone' || form_data.device_type === 'landline' || form_data.device_type === 'cellphone') {
|
|
form_data.call_forward.number = form_data.call_forward.number.replace(/\s|\(|\)|-|\./g, '');
|
|
form_data.enabled = form_data.call_forward.enabled;
|
|
}
|
|
|
|
if ('extra' in form_data && form_data.extra.notify_unregister === true) {
|
|
form_data.suppress_unregister_notifications = false;
|
|
} else {
|
|
form_data.suppress_unregister_notifications = true;
|
|
}
|
|
|
|
if ('extra' in form_data && 'closed_groups' in form_data.extra) {
|
|
form_data.call_restriction.closed_groups = { action: form_data.extra.closed_groups ? 'deny' : 'inherit' };
|
|
}
|
|
|
|
if ($.inArray(form_data.device_type, ['sip_device', 'mobile', 'softphone']) > -1) {
|
|
if ('extra' in form_data) {
|
|
form_data.media.encryption = form_data.media.encryption || {};
|
|
|
|
if ($.inArray(form_data.extra.encryptionMethod, ['srtp', 'zrtp']) > -1) {
|
|
form_data.media.encryption.enforce_security = true;
|
|
form_data.media.encryption.methods = [form_data.extra.encryptionMethod];
|
|
} else {
|
|
form_data.media.encryption.methods = [];
|
|
form_data.media.encryption.enforce_security = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
delete form_data.extra;
|
|
|
|
return form_data;
|
|
},
|
|
|
|
deviceFixArrays: function(data, data2) {
|
|
if (typeof data.media === 'object' && typeof data2.media === 'object') {
|
|
(data.media.audio || {}).codecs = (data2.media.audio || {}).codecs;
|
|
(data.media.video || {}).codecs = (data2.media.video || {}).codecs;
|
|
}
|
|
|
|
if ('media' in data2 && 'encryption' in data2.media && 'methods' in data2.media.encryption) {
|
|
data.media.encryption = data.media.encryption || {};
|
|
data.media.encryption.methods = data2.media.encryption.methods;
|
|
}
|
|
|
|
return data;
|
|
},
|
|
|
|
deviceSave: function(form_data, data, success) {
|
|
var self = this,
|
|
id = (typeof data.data === 'object' && data.data.id) ? data.data.id : undefined,
|
|
normalized_data = self.deviceFixArrays(self.deviceNormalizeData($.extend(true, {}, data.data, form_data)), form_data);
|
|
|
|
if (id) {
|
|
self.deviceUpdate(normalized_data, function(_data, status) {
|
|
success && success(_data, status, 'update');
|
|
});
|
|
} else {
|
|
self.deviceCreate(normalized_data, function(_data, status) {
|
|
success && success(_data, status, 'create');
|
|
});
|
|
}
|
|
},
|
|
|
|
deviceList: function(callback) {
|
|
var self = this;
|
|
|
|
self.callApi({
|
|
resource: 'device.list',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(data) {
|
|
callback && callback(data.data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceGet: function(deviceId, callback) {
|
|
var self = this;
|
|
|
|
self.callApi({
|
|
resource: 'device.get',
|
|
data: {
|
|
accountId: self.accountId,
|
|
deviceId: deviceId
|
|
},
|
|
success: function(data) {
|
|
callback && callback(data.data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceCreate: function(data, callback) {
|
|
var self = this;
|
|
|
|
self.callApi({
|
|
resource: 'device.create',
|
|
data: {
|
|
accountId: self.accountId,
|
|
data: data
|
|
},
|
|
success: function(data) {
|
|
callback && callback(data.data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceUpdate: function(data, callback) {
|
|
var self = this;
|
|
|
|
self.callApi({
|
|
resource: 'device.update',
|
|
data: {
|
|
accountId: self.accountId,
|
|
deviceId: data.id,
|
|
data: data
|
|
},
|
|
success: function(data) {
|
|
callback && callback(data.data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceDelete: function(deviceId, callback) {
|
|
var self = this;
|
|
|
|
self.callApi({
|
|
resource: 'device.delete',
|
|
data: {
|
|
accountId: self.accountId,
|
|
deviceId: deviceId
|
|
},
|
|
success: function(data) {
|
|
callback && callback(data.data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceGetDataProvisoner: function(callback) {
|
|
var self = this;
|
|
|
|
monster.request({
|
|
resource: 'callflows.device.getProvisionerPhones',
|
|
data: {
|
|
},
|
|
success: function(data) {
|
|
data = self.deviceFormatDataProvisioner(data.data);
|
|
|
|
callback && callback(data);
|
|
}
|
|
});
|
|
},
|
|
|
|
deviceFormatDataProvisioner: function(data) {
|
|
var self = this,
|
|
formattedData = {
|
|
brands: data
|
|
};
|
|
|
|
return formattedData;
|
|
},
|
|
|
|
deviceDefineActions: function(args) {
|
|
var self = this,
|
|
callflow_nodes = args.actions;
|
|
|
|
$.extend(callflow_nodes, {
|
|
'device[id=*]': {
|
|
name: self.i18n.active().callflows.device.device,
|
|
icon: 'phone',
|
|
category: self.i18n.active().oldCallflows.advanced_cat,
|
|
module: 'device',
|
|
tip: self.i18n.active().callflows.device.device_tip,
|
|
data: {
|
|
id: 'null'
|
|
},
|
|
rules: [
|
|
{
|
|
type: 'quantity',
|
|
maxSize: '1'
|
|
}
|
|
],
|
|
isUsable: 'true',
|
|
weight: 10,
|
|
caption: function(node, caption_map) {
|
|
var id = node.getMetadata('id'),
|
|
returned_value = '';
|
|
|
|
if (id in caption_map) {
|
|
returned_value = caption_map[id].name;
|
|
}
|
|
|
|
return returned_value;
|
|
},
|
|
edit: function(node, callback) {
|
|
var _this = this;
|
|
|
|
self.deviceList(function(devices) {
|
|
var popup, popup_html;
|
|
|
|
popup_html = $(self.getTemplate({
|
|
name: 'callflowEdit',
|
|
data: {
|
|
can_call_self: node.getMetadata('can_call_self') || false,
|
|
parameter: {
|
|
name: 'timeout (s)',
|
|
value: node.getMetadata('timeout') || '20'
|
|
},
|
|
objects: {
|
|
items: _.sortBy(devices, 'name'),
|
|
selected: node.getMetadata('id') || ''
|
|
}
|
|
},
|
|
submodule: 'device'
|
|
}));
|
|
|
|
if ($('#device_selector option:selected', popup_html).val() === undefined) {
|
|
$('#edit_link', popup_html).hide();
|
|
}
|
|
|
|
$('.inline_action', popup_html).click(function(ev) {
|
|
var _data = ($(this).data('action') === 'edit') ? { id: $('#device_selector', popup_html).val() } : {};
|
|
|
|
ev.preventDefault();
|
|
|
|
self.devicePopupEdit({
|
|
data: _data,
|
|
callback: function(device) {
|
|
node.setMetadata('id', device.id || 'null');
|
|
node.setMetadata('timeout', $('#parameter_input', popup_html).val());
|
|
node.setMetadata('can_call_self', $('#device_can_call_self', popup_html).is(':checked'));
|
|
|
|
node.caption = device.name || '';
|
|
|
|
popup.dialog('close');
|
|
}
|
|
});
|
|
});
|
|
|
|
$('#add', popup_html).click(function() {
|
|
node.setMetadata('id', $('#device_selector', popup_html).val());
|
|
node.setMetadata('timeout', $('#parameter_input', popup_html).val());
|
|
node.setMetadata('can_call_self', $('#device_can_call_self', popup_html).is(':checked'));
|
|
|
|
node.caption = $('#device_selector option:selected', popup_html).text();
|
|
|
|
popup.dialog('close');
|
|
});
|
|
|
|
popup = monster.ui.dialog(popup_html, {
|
|
title: self.i18n.active().callflows.device.device_title,
|
|
beforeClose: function() {
|
|
if (typeof callback === 'function') {
|
|
callback();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
},
|
|
listEntities: function(callback) {
|
|
monster.parallel({
|
|
device: function(callback) {
|
|
self.callApi({
|
|
resource: 'device.list',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(data, status) {
|
|
callback && callback(null, data.data);
|
|
}
|
|
});
|
|
},
|
|
status: function(callback) {
|
|
self.callApi({
|
|
resource: 'device.getStatus',
|
|
data: {
|
|
accountId: self.accountId,
|
|
filters: {
|
|
paginate: false
|
|
}
|
|
},
|
|
success: function(data, status) {
|
|
callback && callback(null, data.data);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
function(err, results) {
|
|
var registeredDevices = _.map(results.status, function(registration) { if (registration.registered === true) { return registration.device_id; } }),
|
|
deviceIcons = {
|
|
'cellphone': 'fa fa-phone',
|
|
'smartphone': 'icon-telicon-mobile-phone',
|
|
'landline': 'icon-telicon-home',
|
|
'mobile': 'icon-telicon-sprint-phone',
|
|
'softphone': 'icon-telicon-soft-phone',
|
|
'sip_device': 'icon-telicon-voip-phone',
|
|
'sip_uri': 'icon-telicon-voip-phone',
|
|
'fax': 'icon-telicon-fax',
|
|
'ata': 'icon-telicon-ata',
|
|
'unknown': 'fa fa-circle'
|
|
};
|
|
|
|
_.each(results.device, function(device) {
|
|
var dataTemplate = device;
|
|
dataTemplate.extra = {
|
|
deviceIcon: deviceIcons.hasOwnProperty(device.device_type) ? deviceIcons[device.device_type] : deviceIcons.unknown,
|
|
isRegistered: device.enabled ? (['sip_device', 'smartphone', 'softphone', 'fax', 'ata'].indexOf(device.device_type) >= 0 ? registeredDevices.indexOf(device.id) >= 0 : true) : false
|
|
};
|
|
// no jQuery wrapper since this template will be inserted directly with Handlebars
|
|
device.customEntityTemplate = self.getTemplate({
|
|
name: 'entity-element',
|
|
data: dataTemplate,
|
|
submodule: 'device'
|
|
});
|
|
});
|
|
|
|
callback && callback(results.device);
|
|
});
|
|
},
|
|
editEntity: 'callflows.device.edit'
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
return app;
|
|
});
|