Browse Source

Added lists module.

Requires cb_lists Crossbar module to be enabled.
Can be used for CID list matching.
pull/1/head
Ruel Tmeizeh - RuhNet 1 year ago
parent
commit
23271e76a2
6 changed files with 662 additions and 0 deletions
  1. +1
    -0
      src/apps/callflows/app.js
  2. +14
    -0
      src/apps/callflows/i18n/en-US.json
  3. +93
    -0
      src/apps/callflows/submodules/lists/lists.css
  4. +483
    -0
      src/apps/callflows/submodules/lists/lists.js
  5. +9
    -0
      src/apps/callflows/submodules/lists/views/addNumber.html
  6. +62
    -0
      src/apps/callflows/submodules/lists/views/edit.html

+ 1
- 0
src/apps/callflows/app.js View File

@ -21,6 +21,7 @@ define(function(require) {
'flushdtmf',
'groups',
'language',
'lists',
'lookupcidname',
'media',
'menu',


+ 14
- 0
src/apps/callflows/i18n/en-US.json View File

@ -1103,6 +1103,9 @@
"blacklist": {
"title": "Blacklists"
},
"lists": {
"title": "Lists"
},
"preflow": {
"title": "Preflow",
"help": "Preflow is an option to execute a callflow before any other callflow. It is set on the account level and will apply on any call using callflow for that account. It can be useful to activate call recording, or the missed call alert feature for example.",
@ -1208,6 +1211,17 @@
"are_you_sure_you_want_to_delete": "Are you sure you want to delete this blacklist?",
"help": "In order to activate this blacklist, head to the Account Settings section"
},
"lists": {
"title": "Lists",
"addNumber": "Add Number",
"create": "Create List",
"edit": "Edit List",
"deleteList": "Delete List",
"name": "Name",
"listNumbers": "Entries:",
"are_you_sure_you_want_to_delete": "Are you sure you want to delete this list? (All entries will also be removed.)",
"help": "You can use this list for CID matching in a callflow."
},
"__comment": "UI-2216: adding tts to adv. callflows",
"__version": "v4.0",


+ 93
- 0
src/apps/callflows/submodules/lists/lists.css View File

@ -0,0 +1,93 @@
#lists_wrapper .list-numbers {
display: block;
width: 470px;
}
#lists_wrapper .list-numbers .delete-number {
margin-bottom: 8px;
}
#lists_wrapper .list-numbers .delete-number .fa {
color: white;
font-size: 20px;
}
#lists_wrapper .list-numbers .add-number {
margin-bottom: 10px;
border-radius: 4px;
}
#lists_wrapper .list-numbers .add-number input {
margin: 0 20px 0 0;
}
#lists_wrapper .list-numbers .add-number #cancel_number {
margin-left: 20px;
}
#lists_wrapper .list-numbers .saved-numbers {
max-height: 300px;
overflow: auto;
}
#lists_wrapper .number-wrapper {
background: #22A5FF;
border-top: 1px solid #22A5FF;
border-right: 1px solid #22A5FF;
border-left: 1px solid #22A5FF;
line-height: 30px;
color: white;
}
#lists_wrapper .number-wrapper.placeholder > div,
#lists_wrapper .number-wrapper:not(.placeholder) {
padding: 5px 10px;
}
#lists_wrapper .number-wrapper.placeholder .create-text {
color: #303039;
}
#lists_wrapper .number-wrapper.placeholder .create-text:hover {
background: #22A5FF;
cursor: pointer;
color: white;
}
#lists_wrapper .saved-numbers .number-wrapper:first-child {
border-top-right-radius: 4px;
border-top-left-radius: 4px;
}
#lists_wrapper .saved-numbers .number-wrapper:last-child {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
border-bottom: 1px solid #22A5FF;
}
#lists_wrapper .saved-numbers .number-wrapper + .number-wrapper {
border-top: none;
margin-top: 1px;
}
#lists_wrapper .number-wrapper.placeholder {
background: none;
border: 1px dashed #999;
}
#lists_wrapper .number-wrapper.placeholder .create-inputs,
#lists_wrapper .number-wrapper.placeholder.active .create-text {
display: none;
}
#lists_wrapper .number-wrapper.placeholder.active .create-inputs,
#lists_wrapper .number-wrapper.placeholder .create-text {
display: inherit;
}
#lists_wrapper .help-text-icon {
color: #22A5FF;
margin-right: 10px;
margin-top :15px;
}

+ 483
- 0
src/apps/callflows/submodules/lists/lists.js View File

@ -0,0 +1,483 @@
//////////////////////////////////////////////////
// Lists Submodule for Callflows
// @copyright 2024 RuhNet https://ruhnet.co
// @author Ruel Tmeizeh
//
// Derived from blacklist submodule.
// cb_lists Crossbar module must be loaded:
// sup crossbar_maintenance start_module cb_lists
//
//////////////////////////////////////////////////
define(function(require) {
var $ = require('jquery'),
_ = require('lodash'),
monster = require('monster');
var app = {
requests: {},
subscribe: {
'callflows.lists.edit': 'listsEdit',
'callflows.fetchActions': 'listsDefineActions'
},
// Defines API requests not included in the SDK
requests: {
'lists.list': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists',
'verb': 'GET'
},
'lists.get': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}',
'verb': 'GET'
},
'lists.create': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists',
'verb': 'PUT'
},
'lists.delete': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}',
'verb': 'DELETE'
},
'lists.getEntries': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}/entries',
'verb': 'GET'
},
'lists.addEntry': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}/entries',
'verb': 'PUT'
},
'lists.updateEntry': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}/entries/{listEntryId}',
'verb': 'PATCH'
},
'lists.deleteEntry': {
'apiRoot': monster.config.api.default,
'url': 'accounts/{accountId}/lists/{listId}/entries/{listEntryId}',
'verb': 'DELETE'
}
},
listsDefineActions: function(args) {
var self = this,
callflow_nodes = args.actions;
$.extend(callflow_nodes, {
'lists': {
name: self.i18n.active().callflows.lists.title,
module: 'lists',
listEntities: function(callback) {
monster.request({
resource: "lists.list",
data: {
accountId: self.accountId,
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]); //Populate results with nothing
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to get lists: " + parsedError);
callback([]); //Populate results with nothing
}
}
});
},
editEntity: 'callflows.lists.edit'
}
});
},
// Added for the subscribed event to avoid refactoring conferenceEdit
listsEdit: function(args) {
//console.log(args);
var self = this,
afterGetData = function(data) {
var template = $(self.getTemplate({
name: 'edit',
data: data,
submodule: 'lists'
})),
listsForm = template.find('#lists-form'),
$listNumbers = template.find('.saved-numbers');
monster.ui.validate(listsForm, {
rules: {
'name': { required: true }
}
});
_.each(data.entries, function(entry) {
entry['state'] = 'saved';
$listNumbers.append($(self.getTemplate({
name: 'addNumber',
data: entry,
submodule: 'lists'
})));
});
self.listsBindEvents(data, template, args.callbacks);
(args.target)
.empty()
.append(template);
};
if (args.data.id) {
self.listsGet(args.data.id, function(data) {
afterGetData(data); //data = {id: listid, name: listname, entries: [entry1,entry2,...]}
});
} else {
afterGetData({});
}
},
listsBindEvents: function(data, template, callbacks) {
var self = this,
addNumber = function(e) {
var number = template.find('#number_value').val();
var list_id = template.find('#active-list').attr('data-listid');
if (number) {
var entryData = {
number: number,
};
$('.list-numbers .saved-numbers', template)
.prepend($(self.getTemplate({
name: 'addNumber',
data: {
key: list_id ? list_id : null,
id: null,
state: 'pending-add',
value: {
number: number,
},
},
submodule: 'lists'
})));
$('#number_value', template).val('');
}
};
$('.number-wrapper.placeholder:not(.active)', template).click(function() {
var $this = $(this);
$this.addClass('active');
$('#number_value', template).focus();
});
$('#add_number', template).click(function(e) {
e.preventDefault();
addNumber();
});
$('.add-number', template).bind('keypress', function(e) {
var code = e.keyCode || e.which;
if (code === 13) {
addNumber(e);
}
});
$(template).delegate('.delete-number', 'click', function(e) {
if ($(this).parents('.number-wrapper').data('state') == 'pending-add') { //if this has just been added and not saved, remove it
$(this).parents('.number-wrapper').remove();
} else { //toggle background red to signify that it will be deleted when save is clicked
if ($(this).parents('.number-wrapper').hasClass('pending-delete')) {
$(this).parents('.number-wrapper').removeClass('pending-delete');
$(this).parents('.number-wrapper').css('background-color','');
$(this).parents('.number-wrapper').attr('data-state', 'saved');
} else {
$(this).parents('.number-wrapper').addClass('pending-delete');
$(this).parents('.number-wrapper').attr('data-state', 'pending-delete');
$(this).parents('.number-wrapper').css('background-color','#B91B0C');
}
}
});
$('#cancel_number', template).click(function(e) {
e.stopPropagation();
$('.number-wrapper.placeholder.active', template).removeClass('active');
$('#number_value', template).val('');
});
$('.lists-save', template).click(function() {
var formData = monster.ui.getFormData('blacklist-form'),
cleanData = self.listsCleanFormData(formData),
entries = [];
$('.saved-numbers .number-wrapper', template).each(function(k, wrapper) {
entry = {
list_id: $(wrapper).attr('data-listid'),
id: $(wrapper).attr('id'),
number: $(wrapper).attr('data-number'),
state: $(wrapper).attr('data-state'),
}
entries.push(entry);
});
cleanData.entries = entries;
if (data.id) {
cleanData.id = data.id;
}
self.listsSave(cleanData, callbacks.save_success);
});
$('.lists-delete', template).click(function() {
monster.ui.confirm(self.i18n.active().callflows.lists.are_you_sure_you_want_to_delete, function() {
self.listsDelete(data.id, callbacks.delete_success);
});
});
},
listsCleanFormData: function(data) {
delete data.extra;
return data;
},
listsSave: function(data, callback) {
var self = this;
if (data.id) {
self.listsUpdate(data, callback);
} else {
var newListData = { name: data.name };
self.listsCreate(newListData, function(createdListData) {
data.id = createdListData.id;
self.listsUpdate(data, callback);
});
}
},
listsList: function(callback) {
var self = this;
monster.request({
resource: "lists.list",
data: {
accountId: self.accountId,
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]); //Populate results with nothing
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to get lists: " + parsedError);
callback([]); //Populate results with nothing
}
}
});
},
listsGet: function(id, callback) {
var self = this;
monster.request({
resource: "lists.get",
data: {
accountId: self.accountId,
listId: id,
},
success: function(data) {
listprops = data.data;
monster.request({
resource: "lists.getEntries",
data: {
accountId: self.accountId,
listId: id,
},
success: function(res) {
var listdata = {
id: listprops.id,
name: listprops.name,
entries: res.data
};
callback && callback(listdata);
},
error: function(res) {
if (res.status == 404) {
callback([]); //Populate results with nothing
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to get list entries from list " + id + ": " + parsedError);
callback([]); //Populate results with nothing
}
}
});
},
error: function(res) {
if (res.status == 404) {
callback([]); //Populate results with nothing
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to get list " + id + ": " + parsedError);
callback([]); //Populate results with nothing
}
}
});
},
listsCreate: function(data, callback) {
var self = this;
monster.request({
resource: "lists.create",
data: {
accountId: self.accountId,
data: data,
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]);
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to create list: " + parsedError);
callback([]);
}
}
});
},
listsAddEntry: function(data, callback) {
var self = this;
monster.request({
resource: "lists.addEntry",
data: {
listId: data.list_id,
accountId: self.accountId,
data: {
number: data.number,
},
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]);
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to add number to list: " + parsedError);
callback([]);
}
}
});
},
listsUpdate: function(data, callback) {
var self = this;
var entrySaves = _.map(data.entries, function(entry) {
if (!entry.list_id) entry.list_id = data.id;
if (entry.state == 'pending-add') {
self.listsAddEntry(entry, function(data) {
//console.log("Added entry: ");
console.log(data);
});
entry.state = 'saved';
} else if ((entry.state == 'pending-delete') && entry.id) {
self.listsDeleteEntry(entry, function(data) {
//console.log("Deleted entry: ");
console.log(data);
});
entry.state = 'deleted';
}
return entry;
});
console.log(entrySaves);
callback(entrySaves);
},
listsDeleteEntry: function(data, callback) {
var self = this;
console.log("listsDeleteEntry input data:");
console.log(data);
monster.request({
resource: "lists.deleteEntry",
data: {
accountId: self.accountId,
listId: data.list_id,
listEntryId: data.id,
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]);
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to add number to list: " + parsedError);
callback([]);
}
}
});
},
listsDelete: function(id, callback) {
var self = this;
monster.request({
resource: "lists.delete",
data: {
accountId: self.accountId,
listId: id,
},
success: function(res) {
callback && callback(res.data);
},
error: function(res) {
if (res.status == 404) {
callback([]);
} else if (res.status == 401) {
monster.util.logoutAndReload();
} else {
monster.ui.alert("ERROR: Failed to add number to list: " + parsedError);
callback([]);
}
}
});
}
};
return app;
});

+ 9
- 0
src/apps/callflows/submodules/lists/views/addNumber.html View File

@ -0,0 +1,9 @@
<div class="number-wrapper" data-number="{{value.number}}" data-listid="{{key}}" data-state="{{state}}" id="{{id}}" {{#compare state '==' 'pending-add'}}style='background-color: #09A74B;'{{/compare}}>
{{value.number}}
<div class="actions-number pull-right">
<a class="delete-number" href="javascript:void(0);">
<i class="fa fa-trash-o"></i>
</a>
</div>
</div>

+ 62
- 0
src/apps/callflows/submodules/lists/views/edit.html View File

@ -0,0 +1,62 @@
<div>
<div id="blacklist_wrapper">
<div class="whapp-header clearfix">
<h1>{{#if id}}{{ i18n.callflows.lists.edit }}{{else}}{{ i18n.callflows.lists.create }}{{/if}}</h1>
</div>
<div id="active-list" {{#if id}}data-listid={{id}}{{/if}}>
<form id="blacklist-form" action="#" method="post">
<div class="pill-content">
<div class="active basic_view" id="basic">
{{#if id}}
<div>
<h2>{{name}}</h2>
</div>
<input class="span4" id="listname" name="name" type="hidden" value="{{name}}"/>
<div class="clearfix">
{{else}}
<div class="clearfix">
<label for="listname">{{ i18n.callflows.lists.name }}</label>
<div class="input">
<input class="span4" id="listname" name="name" type="text" value="{{name}}"/>
</div>
{{/if}}
</div>
<div class="list-numbers">
<h4>{{ i18n.callflows.lists.listNumbers }}</h4>
<div class="add-number">
<div class="number-wrapper placeholder">
<div class="create-text">
<i class="icon-plus-sign"></i>
{{ i18n.callflows.lists.addNumber }}
</div>
<div class="create-inputs">
<input type="text" id="number_value" name="extra.numberValue" class="input-medium">
<button class="monster-button-primary" id="add_number">{{ i18n.add }}</button>
<a href="javascript:void(0);" id="cancel_number">{{ i18n.cancel }}</a>
</div>
</div>
</div>
<div class="saved-numbers">
</div>
</div>
<div class="clearfix">
<p><i class="fa fa-question help-text-icon"></i>{{ i18n.callflows.lists.help }}</p>
</div>
</div>
</div>
</form>
<div class="buttons-wrapper">
{{#if id}}
<button class="monster-button monster-button-danger blacklist-delete lists-delete">{{ i18n.callflows.lists.deleteList }}</button>
{{/if}}
<button class="monster-button monster-button-success blacklist-save lists-save">{{ i18n.save }}</button>
</div>
</div>
</div>
</div>

Loading…
Cancel
Save