Compare commits

...

4 Commits

Author SHA1 Message Date
  Ruel Tmeizeh - RuhNet 7df45938ba improved faxbox number finding reliability; added help tooltip 2 weeks ago
  Ruel Tmeizeh - RuhNet 8cb30dc3c0 outbound faxing via the API 2 weeks ago
  karl anderson 1d9cb4cbd8 normalize build step names and add VERSION to package 6 years ago
  karl anderson deebf795e7 add shipyard build configuration 6 years ago
9 changed files with 709 additions and 4 deletions
Split View
  1. +1
    -0
      .base_branch
  2. +131
    -0
      .circleci/config.yml
  3. +18
    -0
      .shipyard.yml
  4. +214
    -4
      app.js
  5. +16
    -0
      i18n/de-DE.json
  6. +16
    -0
      i18n/en-US.json
  7. +234
    -0
      style/app.css
  8. +51
    -0
      style/app.scss
  9. +28
    -0
      views/outbound-faxes.html

+ 1
- 0
.base_branch View File

@ -0,0 +1 @@
origin/4.3

+ 131
- 0
.circleci/config.yml View File

@ -0,0 +1,131 @@
version: 2
workflows:
version: 2
build_branch:
jobs:
- build_centos7
build_release:
jobs:
- build_centos7:
filters:
tags:
only: /^\d+\.\d+\.\d+$/
branches:
ignore: /.*/
jobs:
build_centos7:
docker:
- image: offical2600hz/monster-ui-packager:1.0-centos-7
user: circleci
shell: /bin/bash --login
working_directory: /home/circleci/2600hz/the_app
environment:
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
BASH_ENV: "/home/circleci/2600hz/.bashrc"
BUILD_ROOT: "/home/circleci/2600hz/packager"
CORE_ROOT: "/home/circleci/2600hz/monster-ui"
BUILD_SOURCES: "/home/circleci/2600hz/packager/SOURCES"
BUILD_RPMS: "/home/circleci/2600hz/packager/RPMS"
APP_DIR: "/home/circleci/2600hz/the_app"
steps:
- checkout
- run:
name: Setting up core repository
command: |
BAZE="$(cat "${HOME}/2600hz/the_app/.base_branch")"
if [ -n "$BAZE" ]; then
echo -e "\n\nexport BASE_BRANCH=$BAZE" >> $BASH_ENV
else
echo "add base branch name of main Monster-UI repo (like origin/master) to '.base_branch' in your application root directory"
exit 1
fi
- run:
name: Generating build environment variables
command: |
APP=${CIRCLE_PROJECT_REPONAME#monster-ui-}
echo -e "export MONSTER_APP=${APP}\n" >> $BASH_ENV
echo -e "export APP=${APP}\n" >> $BASH_ENV
echo -e "export MONSTER_ROOT=${HOME}/2600hz/monster-ui" >> $BASH_ENV
echo -e "export APP_PATH=src/apps/${APP}\n\n" >> $BASH_ENV
- run:
name: Displaying environment information
command: |
echo ":: behold, running ci tests for application: $MONSTER_APP"
echo "MONSTER_ROOT: $MONSTER_ROOT"
echo "APP_PATH: $APP_PATH"
echo "BASE_BRANCH: $BASE_BRANCH"
- run:
name: Cloning core repository
command: |
if [ ! -d ${MONSTER_ROOT} ]; then
git clone https://github.com/2600hz/monster-ui $MONSTER_ROOT
fi
- run:
name: Running core setup script
command: ${MONSTER_ROOT}/scripts/circleci.bash
- restore_cache:
key: dependency-cache-centos-{{ checksum "/home/circleci/2600hz/monster-ui/package-lock.json" }}
paths:
-/home/circle/2600hz/monster-ui/node_modules
- run:
name: Making dependencies
command: |
cd $MONSTER_ROOT
npm install
- run:
name: Building release
command: |
cd $MONSTER_ROOT
gulp build-app --app=${APP}
- run:
name: Generating version info
command: |
cd $BUILD_ROOT
VERSION=$(./version)
RELEASE=$(./release)
PACKAGE_NAME=$(./package_name)
echo "export PACKAGE_NAME=${PACKAGE_NAME}" >> $BASH_ENV
echo "export VERSION=${VERSION}" >> $BASH_ENV
echo "export RELEASE=${RELEASE}" >> $BASH_ENV
PACKAGE_NAME=$(./package_name)
echo "export PACKAGE_NAME=${PACKAGE_NAME}" >> $BASH_ENV
echo "build version for ${PACKAGE_NAME} version: ${VERSION} release: ${RELEASE}"
- run:
name: Building CHANGELOG and VERSION files
command: |
cd $BUILD_ROOT
./package_docs
- run:
name: Preparing source for packaging
command: |
cd $BUILD_SOURCES
echo " - copy build into artifacts"
cp -R ${MONSTER_ROOT}/dist/apps/${MONSTER_APP} ${BUILD_SOURCES}/
cp VERSION ${MONSTER_APP}/
echo " - removing files that should not be packaged in the source tar"
rm -rf ${BUILD_SOURCES}/.??*
rm -rf ${BUILD_SOURCES}/doc*
rm -rf ${BUILD_SOURCES}/*.md
echo " - creating the source tar"
cd $BUILD_ROOT
ARTIFACTS_NAME=${PACKAGE_NAME}-${VERSION}
mkdir -p ${ARTIFACTS_NAME}
cp -r ${BUILD_SOURCES}/* ${ARTIFACTS_NAME}/.
tar -cf ${ARTIFACTS_NAME}.tar ${ARTIFACTS_NAME}
cp ${ARTIFACTS_NAME}.tar ${BUILD_SOURCES}/.
- run:
name: Building package
command: |
cd $BUILD_ROOT
./build
- store_artifacts:
path: /home/circleci/2600hz/packager/RPMS
- save_cache:
key: dependency-cache-centos-{{ checksum "/home/circleci/2600hz/monster-ui/package-lock.json" }}
paths:
- /home/circle/2600hz/monster-ui/node_modules

+ 18
- 0
.shipyard.yml View File

@ -0,0 +1,18 @@
---
name: monster-ui-application-fax
base_branch: origin/4.3
metapackage:
- name: meta-monster-ui
branch: "4.3"
package: monster-ui-application-fax
type: required
base_core: monster-ui
template: monster-ui-application.spec.tmpl
package:
centos7:
name: monster-ui-application-fax
app_name: fax
license: "MPL 2.0"
group: Productivity/Telephony
url: http://www.2600hz.org/
vendor: 2600Hz

+ 214
- 4
app.js View File

@ -29,7 +29,9 @@ define(function(require) {
default: 7,
max: 31
},
faxboxes: {}
faxboxes: {},
allnumbers: [], // list of all numbers in the account
faxboxnumbers: {} // { "somefaxboxid": [array of DIDs that go to this faxbox] }
},
initApp: function(callback) {
@ -46,7 +48,9 @@ define(function(require) {
self.getFaxData(function(results) {
self.appFlags.faxboxes = _.keyBy(results.faxboxes, 'id');
console.log(_.size(self.appFlags.faxboxes));
//console.log(_.size(self.appFlags.faxboxes));
self.appFlags.faxboxnumbers = results.faxboxnumbers;
var menus = [
{
tabs: [
@ -90,6 +94,11 @@ console.log(_.size(self.appFlags.faxboxes));
callback(null, faxboxes);
});
},
faxboxnumbers: function(callback) {
self.getFaxboxNumbers(function(faxboxnumbers) {
callback(null, faxboxnumbers);
});
},
storage: function(callback) {
self.getStorage(function(storage) {
callback(null, storage);
@ -182,6 +191,9 @@ console.log(_.size(self.appFlags.faxboxes));
template.find('.select-faxbox').val(faxboxId).trigger('chosen:updated');
//load send from phone numbers belonging to this faxbox
self.loadNumberChoices(template, self.appFlags.faxboxnumbers[faxboxId]);
self.maybeShowAllNumbersOption(template);
self.displayListFaxes(type, template, faxboxId);
});
@ -315,6 +327,39 @@ console.log(_.size(self.appFlags.faxboxes));
cb.prop('checked', !cb.prop('checked'));
afterSelect();
});
template.on('change', '#sendfax_uploaded_file', function() {
$('.outbound-hidden').show();
$('.outbound-expand').hide();
if ($(this).val()) { // Check if a file is selected
$('.send-fax-button').prop('disabled', false);
} else {
$('.send-fax-button').prop('disabled', true);
}
});
template.on('click', '.outbound-expand', function() {
$('.outbound-hidden').slideDown();
$('.outbound-expand').hide();
});
template.on('click', '.outbound-contract', function() {
$('.outbound-hidden').slideUp();
$('.outbound-contract').hide();
$('.outbound-expand').show();
});
template.on('click', '.show-all-numbers', function() {
self.loadNumberChoices(template, self.appFlags.allnumbers); //load all numbers
$('.show-all-numbers').fadeOut(2000);
monster.ui.toast({ type: "info", message: self.i18n.active().fax.outbound.allNumbers });
$('#from_number_header').text(self.i18n.active().fax.outbound.fromNumberAll);
});
template.on('click', '.send-fax-button', function(e) {
e.preventDefault();
self.sendFaxUpload(template);
});
},
displayListFaxes: function(type, container, faxboxId) {
@ -734,8 +779,173 @@ console.log(_.size(self.appFlags.faxboxes));
callback && callback(data.data);
}
});
}
};
},
loadNumberChoices: function(template, numbers) {
var self = this;
var $phoneNumberSelect = template.find('#sendfax_from_number');
if ($phoneNumberSelect) {
$phoneNumberSelect.empty();
if (numbers && numbers.length > 1) {
$phoneNumberSelect.append($('<option>', { value: "none", text: self.i18n.active().fax.outbound.selectFromNumber }));
}
_.each(numbers, function(number) {
$phoneNumberSelect.append($('<option>', { value: number, text: number }));
});
}
},
maybeShowAllNumbersOption: function(template) {
var self = this;
$('#from_number_header').text(self.i18n.active().fax.outbound.fromNumber);
if (monster.util.isAdmin()) {
$('.show-all-numbers').show();
}
},
sendFaxUpload: function(template) {
var self = this;
var toNumber = template.find('#sendfax_to_number').val();
var fromNumber = template.find('#sendfax_from_number').val();
var file = template.find('#sendfax_uploaded_file')[0].files[0];
if (!file) return monster.ui.alert(self.i18n.active().fax.outbound.missingFile);
if (!toNumber) return monster.ui.alert(self.i18n.active().fax.outbound.missingTo);
if (!fromNumber || fromNumber == 'none') return monster.ui.alert(self.i18n.active().fax.outbound.missingFrom);
var selected_faxbox = template.find('.select-faxbox').val();
//create json blob to use in form data
var jsonData = JSON.stringify({data: {from_number: fromNumber, to_number: toNumber, faxbox_id: selected_faxbox}});
var jsonBlob = new Blob([jsonData], { type: 'application/json' });
var form = new FormData();
form.set("json", jsonBlob);
form.set('file', file, file.name);
$.ajax({
url: monster.config.api.default + 'accounts/' + self.accountId + '/faxes',
method: 'put',
processData: false,
contentType: false,
headers: { 'X-Auth-Token': monster.util.getAuthToken() },
data: form,
success: function(res) {
monster.ui.toast({ type: "success", message: self.i18n.active().fax.outbound.success });
console.log(res.data);
template.find('#sendfax_uploaded_file').val(null);
},
error: function(err) {
console.log(err);
monster.ui.alert('<h3>Error</h3><p><pre>' + err.responseText + '</pre></p>');
}
});
},
getCallflows: function(callback) {
var self = this;
self.callApi({
resource: 'callflow.list',
data: {
accountId: self.accountId,
filters: { paginate: false }
},
error: function(err) {
console.log(err);
},
success: function(res) {
callback && callback(res.data);
}
});
},
getCallflow: function(id, callback) {
var self = this;
self.callApi({
resource: 'callflow.get',
data: {
accountId: self.accountId,
callflowId: id
},
error: function(err) {
console.log(err);
},
success: function(res) {
callback && callback(res.data);
}
});
},
getNumbers: function(callback) {
var self = this;
if (self.appFlags.allnumbers.length > 0) {
return callback && callback(self.appFlags.allnumbers); //sortof cache :)
}
self.callApi({
resource: 'numbers.list',
data: {
accountId: self.accountId,
filters: { paginate: false }
},
error: function(err) {
console.log(err);
},
success: function(res) {
var numbers = _.keys(res.data.numbers);
self.appFlags.allnumbers = numbers;
callback && callback(numbers);
}
});
},
getFaxboxNumbers: function(callback) {
var self = this,
faxboxNumbers = {};
var findFaxboxId = function(flow) {
if (flow.module == 'faxbox') {
return flow.data.id;
} else if (flow.children.length > 0) {
findFaxBoxId(flow.children["_"]);
}
};
self.getNumbers(function(numbers) {
self.getCallflows(function(callflows) { //get all callflows
let done = new Promise((resolve, reject) => { //let these finish before proceeding
if (callflows.length == 0) resolve();
var faxCallflows = _.filter(callflows, function(cf) { //ignore non-faxbox callflows
return cf.modules.includes('faxbox');
});
if (faxCallflows.length == 0) resolve();
_.each(faxCallflows, function(cf, idx) {
self.getCallflow(cf.id, function(callflow) { //this callflow has faxbox, so get it for details
var fbid = findFaxboxId(callflow.flow); //search for the faxbox ID
if (fbid) faxboxNumbers[fbid] = [];
_.each(cf.numbers, function(cfnum) { //check each callflow number to see if it's a real DID
if (numbers.includes(cfnum)) {
faxboxNumbers[fbid].push(cfnum); //add the number (if it's a DID) to the list for this faxbox ID
}
});
if (faxboxNumbers[fbid] && faxboxNumbers[fbid].length == 0) { //this faxbox has no DIDs routing to it
console.log('Warning: faxbox '+fbid+' has no DIDs routing to it!');
}
if ((idx + 1) == faxCallflows.length) resolve(); //processed all callflows so we're done
});
});
});
done.then(() => {
callback && callback(faxboxNumbers);
});
});
});
} //getFaxboxNumbers()
}; //app
return app;
});

+ 16
- 0
i18n/de-DE.json View File

@ -77,6 +77,22 @@
"outbound": "Ausgehende Faxe",
"storage": "Speicher"
},
"outbound": {
"allNumbers": "Admin Action: All numbers in the account are now visible for fax from_number.",
"enterToNumber": "Enter Fax To Number",
"fromNumber": "From Number",
"fromNumberAll": "From Number [All Numbers]",
"help": "After a fax has been queued, please allow several minutes for it to send before expecting it to be available in the outbound faxes table below.",
"missingFile": "You must choose a PDF or TIFF file to send",
"missingFrom": "You must select the source number.",
"missingTo": "You must enter the destination fax number.",
"selectFile": "Upload your file (PDF or TIFF format)",
"selectFromNumber": "Select From Number",
"sendFax": "Send Fax",
"sendAFax": "Send a Fax",
"success": "fax has been queued for sending",
"toNumber": "To Number"
},
"title": "Faxportal",
"CDRPopup": {
"title": "Faxdetails"


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

@ -7,6 +7,22 @@
"logs": "Email-to-Fax Logs",
"storage": "Storage"
},
"outbound": {
"allNumbers": "Admin Action: All numbers in the account are now visible for fax from_number.",
"enterToNumber": "Enter Fax To Number",
"fromNumber": "From Number",
"fromNumberAll": "From Number [All Numbers]",
"help": "After a fax has been queued, please allow several minutes for it to send before expecting it to be available in the outbound faxes table below.",
"missingFile": "You must choose a PDF or TIFF file to send",
"missingFrom": "You must select the source number.",
"missingTo": "You must enter the destination fax number.",
"selectFile": "Upload your file (PDF or TIFF format)",
"selectFromNumber": "Select From Number",
"sendFax": "Send Fax",
"sendAFax": "Send a Fax",
"success": "fax has been queued for sending",
"toNumber": "To Number"
},
"table": {
"columns": {
"status": "Status",


+ 234
- 0
style/app.css View File

@ -0,0 +1,234 @@
/* _colors.scss */
/* unique color names */
/* semantic color names */
/* Default empty state */
.faxes-container.empty .empty-state {
display: block; }
.faxes-container .empty-state {
display: none;
text-align: center; }
.faxes-container .empty-state .headline {
font-size: 22px;
margin-top: 35px; }
.faxes-container .empty-state .sub-headline {
color: #606069;
font-size: 16px;
margin-top: 13px; }
.faxes-container .empty-state .count {
font-weight: bold;
margin-left: 5px;
margin-right: 5px; }
.faxes-container .empty-state .faxboxes-list {
margin-top: 30px;
width: 450px;
text-align: left; }
.faxes-container .empty-state .chosen-drop {
width: 450px; }
.faxes-container .empty-state .chosen-container > a,
.faxes-container .main-header .chosen-container > a {
height: 40px;
line-height: 40px;
width: 450px; }
.faxes-container .main-header {
margin-bottom: 25px; }
.faxes-container .main-header .select-header {
color: #606069;
margin-bottom: 5px; }
.faxes-container .main-header .chosen-drop,
.faxes-container .main-header .chosen-container > a,
.faxes-container .main-header .faxboxes-list,
.faxes-container .main-header .faxbox-selector {
width: 300px; }
.faxes-container .main-header > * {
display: inline-block;
margin-right: 25px; }
.faxes-container .main-header #refresh_faxbox {
margin-top: 35px; }
.faxes-container .main-header .faxbox-selector .chosen-container .chosen-single span {
font-size: 18px; }
.faxes-container .data-state {
display: none; }
.faxes-container .loading-state {
display: none;
background: #fff none repeat scroll 0 0;
border: 1px dashed #aaa;
font-size: 60px;
padding: 50px;
text-align: center;
position: relative;
top: 50px; }
.faxes-container .faxboxes-list {
display: block;
margin: auto;
width: 400px; }
/* Action Bar */
.faxes-container.empty .filters.search,
.faxes-container.empty .filters.basic-actions > *,
.faxes-container.empty .main-header {
display: none; }
.faxes-container .filters.basic-actions {
display: inline-block; }
.faxes-container:not(.empty) .action-bar {
display: block; }
.faxes-container .action-bar {
display: none; }
.faxes-container .action-bar .filters > :first-child {
margin-left: 0; }
.faxes-container .action-bar .margin-left {
margin-left: 10px; }
.faxes-container .action-bar .hidable {
display: inline-block; }
.faxes-container .action-bar .hidable.hidden {
display: none; }
/* Table */
.faxes-container .faxbox-table table {
margin-top: 0; }
.faxes-container .faxbox-table tbody tr > td:first-child .monster-checkbox {
margin-right: 10px;
margin-top: 8px; }
.faxes-container .faxbox-table .select-cell {
min-width: 20px !important;
width: 20px; }
.faxes-container .faxbox-table .select-line {
cursor: pointer; }
.faxes-container .faxbox-table tr .bottom-line {
color: #707079;
font-size: 12px; }
.faxes-container .faxbox-table td.no-padding {
padding: 0; }
.faxes-container .faxbox-table .status {
text-transform: uppercase; }
.faxes-container .filter-by-date .date-ranges > * {
margin: 0 10px 0 0;
vertical-align: middle; }
.faxes-container .filter-by-date .date-ranges > span {
margin-right: 5px; }
.faxes-container .filter-by-date .date-ranges input.date-filter {
height: 24px;
width: 90px; }
.faxes-container .filter-by-date .date-ranges i.fa-calendar {
margin-left: -30px;
margin-right: 20px; }
.faxes-container .filter-by-date {
float: right;
line-height: 30px;
margin-left: 30px; }
.faxes-container .filter-by-date.active .expand-dates {
float: right; }
.faxes-container .filter-by-date .date-ranges,
.faxes-container .filter-by-date.active .expand-dates {
display: none; }
.faxes-container .filter-by-date.active .date-ranges {
display: block; }
/* CDR Popup */
#faxbox_cdr_details_dialog {
width: 750px;
margin: 15px; }
/* Faxes log */
#smtp_logs_container .table .detail-link {
margin: 0px; }
#smtp_logs_grid {
clear: right; }
#smtp_logs_detail_dialog {
width: 700px;
padding: 15px; }
#smtp_logs_detail_dialog tr,
#smtp_logs_detail_dialog td {
line-height: 15px;
padding: 2px;
white-space: normal; }
#send_outbound_fax {
float: right;
border: 1px solid #e0e0e9;
border-radius: 10px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
padding: 10px;
clear: right;
}
.outbound-hidden {
display: none;
}
.outbound-title {
font-size: +1.2em;
font-weight: 500;
color: #2297FF;
display: flex;
justify-content: space-between;
padding-bottom: 4px;
}
.show-all-numbers {
font-size: +1.1em;
margin-bottom: 10px;
margin-left: 6px;
color: #2297FF;
display: none;
}
.from-number-select {
display: flex;
flex-direction: row;
align-items: center;
}
.sendbutton-help {
display: flex;
flex-direction: row;
}
button.sendbutton-help {
font-size: +1.6em;
}
#send_help {
font-size: +1.5em;
margin-left: 12px;
color: #555;
}

+ 51
- 0
style/app.scss View File

@ -225,3 +225,54 @@
padding: 2px;
white-space: normal;
}
#send_outbound_fax {
float: right;
border: 1px solid #e0e0e9;
border-radius: 10px;
box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
padding: 10px;
clear: right;
}
.outbound-hidden {
display: none;
}
.outbound-title {
font-size: +1.2em;
font-weight: 500;
color: #2297FF;
display: flex;
justify-content: space-between;
padding-bottom: 4px;
}
.show-all-numbers {
font-size: +1.1em;
margin-bottom: 10px;
margin-left: 6px;
color: #2297FF;
display: none;
}
.from-number-select {
display: flex;
flex-direction: row;
align-items: center;
}
.sendbutton-help {
display: flex;
flex-direction: row;
}
button.sendbutton-help {
font-size: +1.6em;
}
#send_help {
font-size: +1.5em;
margin-left: 12px;
color: #555;
}

+ 28
- 0
views/outbound-faxes.html View File

@ -15,6 +15,34 @@
</div>
<a id="refresh_faxbox" class="monster-link pull-left" href="javascript:void(0);" data-toggle="tooltip" data-placement="top" data-original-title="{{ i18n.fax.actionBar.refresh }}"><i class="fa fa-refresh"></i></a>
<!-- SEND OUTBOUND FAX VIA API -->
<div id="send_outbound_fax">
<div class="outbound-title">
<span class="outbound-title">{{i18n.fax.outbound.sendAFax}}</span>
<i class="fa fa-chevron-down outbound-expand"></i>
<i class="fa fa-chevron-up outbound-contract outbound-hidden"></i>
</div>
<input id="sendfax_uploaded_file" name="sendfax_uploaded_file" type="file" accept=".pdf,.tif,.tiff" class="kz-input-file">
<div class="outbound-hidden">
<div class="select-header">
{{ i18n.fax.outbound.toNumber }}
</div>
<input id="sendfax_to_number" name="sendfax_to_number" type="text" maxlength=16 placeholder="{{i18n.fax.outbound.enterToNumber}}"><br />
<div id="from_number_header" class="select-header">
{{ i18n.fax.outbound.fromNumber }}
</div>
<div class="from-number-select">
<select id="sendfax_from_number">
<option value="none">{{i18n.fax.outbound.selectFromNumber}}</option>
</select>
<i class="fa fa-plus show-all-numbers"></i>
</div>
</div>
<div class="sendbutton-help"><button class="monster-button-primary send-fax-button outbound-hidden" disabled>{{i18n.fax.outbound.sendFax}}</button><i id="send_help" class="fa fa-question-circle outbound-hidden" data-toggle="tooltip" title="{{ i18n.fax.outbound.help }}"></i></div>
</div>
<!-- END OF SEND OUTBOUND FAX VIA API -->
</div>
<div class="monster-table-wrapper-spaced">
<div class="action-bar monster-table-header">


Loading…
Cancel
Save