From 8cb30dc3c0a24c32ba87fba29bea97fb9001059c Mon Sep 17 00:00:00 2001
From: Ruel Tmeizeh - RuhNet
Date: Mon, 24 Nov 2025 21:03:59 -0500
Subject: [PATCH] outbound faxing via the API
---
app.js | 216 ++++++++++++++++++++++++++++++++++++-
i18n/de-DE.json | 15 +++
i18n/en-US.json | 15 +++
style/app.css | 219 ++++++++++++++++++++++++++++++++++++++
style/app.scss | 36 +++++++
views/outbound-faxes.html | 28 +++++
6 files changed, 525 insertions(+), 4 deletions(-)
create mode 100644 style/app.css
diff --git a/app.js b/app.js
index eb19d2a..3cc3252 100644
--- a/app.js
+++ b/app.js
@@ -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,171 @@ 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($('
');
+ }
+ });
+ },
+
+ 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();
+ _.each(callflows, function(cf, idx) {
+ if (cf.modules.includes('faxbox')) { //if callflow uses the faxbox module, get it individually
+ self.getCallflow(cf.id, function(callflow) {
+ 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) == callflows.length) resolve(); //processed all callflows so we're done
+ });
+ });
+ done.then(() => {
+ callback && callback(faxboxNumbers);
+ });
+ });
+ });
+ } //getFaxboxNumbers()
+
+ }; //app
return app;
});
diff --git a/i18n/de-DE.json b/i18n/de-DE.json
index d478acb..e0abe89 100644
--- a/i18n/de-DE.json
+++ b/i18n/de-DE.json
@@ -77,6 +77,21 @@
"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]",
+ "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"
diff --git a/i18n/en-US.json b/i18n/en-US.json
index 5d6f0ab..0002bed 100644
--- a/i18n/en-US.json
+++ b/i18n/en-US.json
@@ -7,6 +7,21 @@
"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]",
+ "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",
diff --git a/style/app.css b/style/app.css
new file mode 100644
index 0000000..7dfc524
--- /dev/null
+++ b/style/app.css
@@ -0,0 +1,219 @@
+/* _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;
+}
diff --git a/style/app.scss b/style/app.scss
index 2fda975..1648aac 100644
--- a/style/app.scss
+++ b/style/app.scss
@@ -225,3 +225,39 @@
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;
+}
diff --git a/views/outbound-faxes.html b/views/outbound-faxes.html
index 9644734..b81b67a 100644
--- a/views/outbound-faxes.html
+++ b/views/outbound-faxes.html
@@ -15,6 +15,34 @@
+
+
+
+
+ {{i18n.fax.outbound.sendAFax}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+