You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

670 lines
17 KiB

define(function(require) {
var $ = require('jquery'),
_ = require('lodash'),
monster = require('monster');
var app = {
name: 'recordings',
css: [ 'app' ],
i18n: {
'de-DE': { customCss: false },
'en-US': { customCss: false }
},
appFlags: {
recordings: {
maxRange: 31,
defaultRange: 1,
minPhoneNumberLength: 7
}
},
requests: {
'recordings.get': {
'verb': 'GET',
'url': 'accounts/{accountId}/recordings?{filters}'
},
'recordings.user.get': {
'verb': 'GET',
'url': 'accounts/{accountId}/users/{userId}/recordings?{filters}'
},
'recordings.delete': {
'verb': 'DELETE',
'url': 'accounts/{accountId}/recordings/{recordingId}'
}
},
subscribe: {},
load: function(callback) {
var self = this;
self.initApp(function() {
callback && callback(self);
});
},
initApp: function(callback) {
var self = this;
monster.pub('auth.initApp', {
app: self,
callback: callback
});
},
render: function(container) {
var self = this;
var menus = [
{
tabs: [
{
text: self.i18n.active().recordings.menuTitles.receivedRECs,
callback: self.renderReceivedRECs
}
]
}
];
var tabConfig = {
text: self.i18n.active().recordings.menuTitles.config,
menus: [{
tabs: [
{
text: 'Account',
callback: self.renderRecordConfig
}
],
}]
};
menus[0].tabs.push(tabConfig);
monster.ui.generateAppLayout(self, {
menus: menus
});
},
renderRecordConfig: function (pArgs) {
var self = this,
args = pArgs || {},
parent = args.container || $('#recordings_app_container .app-content-wrapper');
self.getAccount(function (accountData) {
var templateData = {
account: $.extend(true, {}, accountData)
};
var contentTemplate = $(self.getTemplate({
name: 'recordings-config',
data: templateData
}));
contentTemplate.find('#recordings_config_save').on('click', function() {
var formData = monster.ui.getFormData('recordings_config_form');
var formattedData = {
"call_recording": {
"account": {
"inbound": {
"offnet": {
"enabled": formData['inbound-offnet'],
},
"onnet": {
"enabled": formData['inbound-onnet'],
}
},
"outbound": {
"offnet": {
"enabled": formData['outbound-offnet'],
},
"onnet": {
"enabled": formData['outbound-onnet'],
}
}
}
}
};
self.callApi({
resource: 'account.get',
data: {
accountId: accountData.id
},
success: function(data, status) {
self.callApi({
resource: 'account.update',
data: {
accountId: accountData.id,
data: $.extend(true, {}, data.data, formattedData)
},
success: function(_data, _status) {
monster.ui.toast({
type: 'success',
message: self.i18n.active().toastrMessages.recordConfigUpdateSuccess,
options: {
timeOut: 5000
}
});
}
});
}
});
});
parent
.fadeOut(function () {
$(this)
.empty()
.append(contentTemplate)
.fadeIn();
});
});
},
renderReceivedRECs: function(pArgs) {
var self = this,
args = pArgs || {},
parent = args.container || $('#recordings_app_container .app-content-wrapper');
self.listRECBoxes(function(recboxes) {
var dataTemplate = {
recboxes: recboxes,
count: recboxes.length
},
template = $(self.getTemplate({
name: 'received-recordings',
data: dataTemplate
}));
self.recordingsInitDatePicker(parent, template);
self.bindReceivedRECs(template);
parent
.fadeOut(function() {
$(this)
.empty()
.append(template)
.fadeIn();
});
});
},
recordingsInitDatePicker: function(parent, template) {
var self = this,
dates = monster.util.getDefaultRangeDates(self.appFlags.recordings.defaultRange),
fromDate = dates.from,
toDate = dates.to;
var optionsDatePicker = {
container: template,
range: self.appFlags.recordings.maxRange
};
monster.ui.initRangeDatepicker(optionsDatePicker);
template.find('#startDate').datepicker('setDate', fromDate);
template.find('#endDate').datepicker('setDate', toDate);
template.find('.apply-filter').on('click', function(e) {
var recboxId = template.find('#select_recbox').val();
self.displayRECList(parent, recboxId);
});
template.find('.toggle-filter').on('click', function() {
template.find('.filter-by-date').toggleClass('active');
});
},
bindReceivedRECs: function(template) {
var self = this,
$selectRECBox = template.find('.select-recbox');
monster.ui.tooltips(template);
monster.ui.footable(template.find('.footable'));
monster.ui.chosen($selectRECBox, {
placeholder_text_single: self.i18n.active().recordings.receivedRECs.actionBar.selectREC.none
});
// Default selection when page is loaded
self.displayRECList(template, "all");
$selectRECBox.on('change', function() {
var recboxId = $(this).val();
self.displayRECList(template, recboxId);
});
template.find('#refresh_recordings').on('click', function() {
var recboxId = $selectRECBox.val();
if (recboxId !== 'none') {
self.displayRECList(template, recboxId);
}
});
template.find('.mark-as-link').on('click', function() {
var folder = $(this).data('type'),
recboxId = $selectRECBox.val(),
$recordings = template.find('.select-recording:checked'),
recordings = [];
$recordings.each(function() {
recordings.push($(this).data('media-id'));
});
template.find('.data-state')
.hide();
template.find('.loading-state')
.show();
});
template.find('.delete-recordings').on('click', function() {
var recboxId = $selectRECBox.val(),
$recordings = template.find('.select-recording:checked'),
recordings = [];
$recordings.each(function() {
recordings.push($(this).data('media-id'));
});
template.find('.data-state')
.hide();
template.find('.loading-state')
.show();
self.bulkRemoveRecordings(recboxId, recordings, function() {
self.displayRECList(template, recboxId);
});
});
template.on('click', '.play-rec', function(e) {
var $row = $(this).parents('.recording-row'),
$activeRows = template.find('.recording-row.active');
if (!$row.hasClass('active') && $activeRows.length !== 0) {
return;
}
e.stopPropagation();
var recboxId = template.find('#select_recbox').val(),
mediaId = $row.data('media-id');
template.find('table').addClass('highlighted');
$row.addClass('active');
self.playRecording(template, recboxId, mediaId);
});
template.on('click', '.details-rec', function() {
var $row = $(this).parents('.recording-row'),
callId = $row.data('call-id');
self.getCDR(callId, function(cdr) {
var template = $(self.getTemplate({
name: 'recordings-CDRDialog'
}));
monster.ui.renderJSON(cdr, template.find('#jsoneditor'));
monster.ui.dialog(template, { title: self.i18n.active().recordings.receivedRECs.CDRPopup.title });
}, function() {
monster.ui.alert(self.i18n.active().recordings.receivedRECs.noCDR);
});
});
var afterSelect = function() {
if (template.find('.select-recording:checked').length) {
template.find('.hidable').removeClass('hidden');
template.find('.main-select-recording').prop('checked', true);
} else {
template.find('.hidable').addClass('hidden');
template.find('.main-select-recording').prop('checked', false);
}
};
template.on('change', '.select-recording', function() {
afterSelect();
});
template.find('.main-select-recording').on('click', function() {
var $this = $(this),
isChecked = $this.prop('checked');
template.find('.select-recording').prop('checked', isChecked);
afterSelect();
});
template.find('.select-some-recordings').on('click', function() {
var $this = $(this),
type = $this.data('type');
template.find('.select-recording').prop('checked', false);
if (type !== 'none') {
if (type === 'all') {
template.find('.select-recording').prop('checked', true);
} else if (['new', 'saved', 'deleted'].indexOf(type) >= 0) {
template.find('.recording-row[data-folder="' + type + '"] .select-recording').prop('checked', true);
}
}
afterSelect();
});
template.on('click', '.select-line', function() {
if (template.find('table').hasClass('highlighted')) {
return;
}
var cb = $(this).parents('.recording-row').find('.select-recording');
cb.prop('checked', !cb.prop('checked'));
afterSelect();
});
},
removeOpacityLayer: function(template, $activeRows) {
$activeRows.find('.recording-player').remove();
$activeRows.find('.duration, .actions').show();
$activeRows.removeClass('active');
template.find('table').removeClass('highlighted');
},
formatRECURI: function(recboxId, mediaId) {
var self = this;
return self.apiUrl + 'accounts/' + self.accountId + '/recordings/' + mediaId + '?accept=audio/mpeg&auth_token=' + self.getAuthToken();
},
playRecording: function(template, recboxId, mediaId) {
var self = this,
$row = template.find('.recording-row[data-media-id="' + mediaId + '"]');
template.find('table').addClass('highlighted');
$row.addClass('active');
$row.find('.duration, .actions').hide();
var uri = self.formatRECURI(recboxId, mediaId),
dataTemplate = {
uri: uri
},
templateCell = $(self.getTemplate({
name: 'cell-recording-player',
data: dataTemplate
}));
$row.append(templateCell);
var closePlayerOnClickOutside = function(e) {
if ($(e.target).closest('.recording-player').length) {
return;
}
e.stopPropagation();
closePlayer();
},
closePlayer = function() {
$(document).off('click', closePlayerOnClickOutside);
self.removeOpacityLayer(template, $row);
};
$(document).on('click', closePlayerOnClickOutside);
templateCell.find('.close-player').on('click', closePlayer);
// Autoplay in JS. For some reason in HTML, we can't pause the stream properly for the first play.
templateCell.find('audio').get(0).play();
},
recordingsGetRows: function(filters, recboxId, callback) {
var self = this;
self.newGetRECBoxMessages(filters, recboxId, function(data) {
var formattedData = self.formatRecordingsData(data.data, recboxId),
dataTemplate = {
recordings: formattedData.recordings
},
$rows = $(self.getTemplate({
name: 'recordings-rows',
data: dataTemplate
}));
callback && callback($rows, data, formattedData);
});
},
displayRECList: function(container, recboxId) {
var self = this,
fromDate = container.find('input.filter-from').datepicker('getDate'),
toDate = container.find('input.filter-to').datepicker('getDate'),
filterByDate = container.find('.filter-by-date').hasClass('active');
container.removeClass('empty');
//container.find('.counts-wrapper').hide();
container.find('.count-wrapper[data-type="new"] .count-text').html('?');
container.find('.count-wrapper[data-type="total"] .count-text').html('?');
// Gives a better feedback to the user if we empty it as we click... showing the user something is happening.
container.find('.data-state')
.hide();
container.find('.loading-state')
.show();
container.find('.hidable').addClass('hidden');
container.find('.main-select-recording').prop('checked', false);
monster.ui.footable(container.find('.recordings-table .footable'), {
getData: function(filters, callback) {
if (filterByDate) {
filters = $.extend(true, filters, {
created_from: monster.util.dateToBeginningOfGregorianDay(fromDate),
created_to: monster.util.dateToEndOfGregorianDay(toDate)
});
}
// we do this to keep context
self.recordingsGetRows(filters, recboxId, function($rows, data, formattedData) {
container.find('.count-wrapper[data-type="new"] .count-text').html(formattedData.counts.newRecordings);
container.find('.count-wrapper[data-type="total"] .count-text').html(formattedData.counts.totalRecordings);
callback && callback($rows, data);
});
},
afterInitialized: function() {
container.find('.data-state')
.show();
container.find('.loading-state')
.hide();
},
backendPagination: {
enabled: false
}
});
},
formatRecordingsData: function(recordings, recboxId) {
var self = this,
tryFormatPhoneNumber = function(value) {
var minPhoneNumberLength = self.appFlags.recordings.minPhoneNumberLength,
prefixedPhoneNumber,
formattedPhoneNumber;
if (_.size(value) < minPhoneNumberLength) {
return {
isPhoneNumber: false,
value: value,
userFormat: value
};
}
prefixedPhoneNumber = _.head(value) === '+'
? value
: /^\d+$/.test(value) // Prepend '+' if there are only numbers
? '+' + value
: value;
formattedPhoneNumber = monster.util.getFormatPhoneNumber(prefixedPhoneNumber);
return {
isPhoneNumber: formattedPhoneNumber.isValid,
value: formattedPhoneNumber.isValid
? formattedPhoneNumber.e164Number
: value,
userFormat: formattedPhoneNumber.isValid
? formattedPhoneNumber.userFormat
: value
};
},
formattedRecordings = _.map(recordings, function(rec) {
var to = rec.to.substr(0, rec.to.indexOf('@')),
from = rec.from.substr(0, rec.from.indexOf('@')),
callerIDName = _.get(rec, 'caller_id_name', ''),
formattedTo = tryFormatPhoneNumber(to),
formattedFrom = tryFormatPhoneNumber(from),
formattedCallerIDName = tryFormatPhoneNumber(callerIDName);
rec.direction = rec.origin.split(' ')[0];
return _.merge({
formatted: {
to: formattedTo,
from: formattedFrom,
callerIDName: formattedCallerIDName,
duration: monster.util.friendlyTimer(rec.duration_ms / 1000),
uri: self.formatRECURI(recboxId, rec.id),
callId: monster.util.getModbID(rec.call_id, rec.start),
mediaId: rec.id,
showCallerIDName: formattedCallerIDName.value !== formattedFrom.value
},
direction: rec.direction,
timestamp: rec.start
}, rec);
}),
formattedData = {
recordings: formattedRecordings,
counts: {
newRecordings: _.sumBy(recordings, function(rec) {
return _
.chain(rec)
.get('folder')
.isEqual('new')
.toInteger()
.value();
}),
totalRecordings: recordings.length
}
};
return formattedData;
},
getAccount: function (callback) {
var self = this;
self.callApi({
resource: 'account.get',
data: {
accountId: self.accountId
},
success: function (data) {
callback(data.data);
}
});
},
getCDR: function(callId, callback, error) {
var self = this;
self.callApi({
resource: 'cdrs.get',
data: {
accountId: self.accountId,
cdrId: callId,
generateError: false
},
success: function(data) {
callback && callback(data.data);
},
error: function(data, status, globalHandler) {
if (data && data.error === '404') {
error && error({});
} else {
globalHandler(data, { generateError: true });
}
}
});
},
newGetRECBoxMessages: function(filters, recboxId, callback) {
var self = this;
if (recboxId === 'all') {
monster.request({
resource: 'recordings.get',
data: {
accountId: self.accountId,
filters: filters
},
success: function(data) {
callback && callback(data);
}
});
} else {
monster.request({
resource: 'recordings.user.get',
data: {
accountId: self.accountId,
userId: recboxId,
filters: filters
},
success: function(data) {
callback && callback(data);
}
});
}
},
bulkRemoveRecordings: function(recboxId, recordings, callback) {
var self = this;
$.each(recordings, function(i, recordingId){
monster.request({
resource: 'recordings.delete',
data: {
accountId: self.accountId,
recordingId: recordingId
},
success: function(data) {
callback && callback(data.data);
}
});
})
},
listRECBoxes: function(callback) {
var self = this;
self.callApi({
resource: 'user.list',
data: {
accountId: self.accountId,
filters: {
paginate: false
}
},
success: function(data) {
callback && callback(data.data);
}
});
}
};
return app;
});