From 2832348e1983680e33ec7f06c72b8b251c4a1d3e Mon Sep 17 00:00:00 2001 From: Ruel Tmeizeh - RuhNet Date: Fri, 29 Aug 2025 16:33:13 -0400 Subject: [PATCH] couchDB addition, also custom S3 --- app.js | 140 ++++++++++++++++-- storages.js | 3 +- style/app.css | 4 + submodules/{mts/mts.js => couchdb/couchdb.js} | 8 +- submodules/couchdb/i18n/en-US.json | 13 ++ submodules/couchdb/img/logo.png | Bin 0 -> 1280 bytes submodules/couchdb/views/formElements.html | 33 +++++ submodules/couchdb/views/logo.html | 1 + submodules/custom_s3/custom_s3.js | 48 ++++++ submodules/{mts => custom_s3}/i18n/en-US.json | 2 +- submodules/custom_s3/img/logo.png | Bin 0 -> 6056 bytes .../views/formElements.html | 23 ++- submodules/custom_s3/views/logo.html | 1 + submodules/mts/img/logo.png | Bin 18286 -> 0 bytes submodules/mts/views/logo.html | 1 - submodules/s3/i18n/en-US.json | 4 +- submodules/s3/views/formElements.html | 8 +- 17 files changed, 263 insertions(+), 26 deletions(-) rename submodules/{mts/mts.js => couchdb/couchdb.js} (82%) create mode 100644 submodules/couchdb/i18n/en-US.json create mode 100644 submodules/couchdb/img/logo.png create mode 100644 submodules/couchdb/views/formElements.html create mode 100644 submodules/couchdb/views/logo.html create mode 100644 submodules/custom_s3/custom_s3.js rename submodules/{mts => custom_s3}/i18n/en-US.json (89%) create mode 100644 submodules/custom_s3/img/logo.png rename submodules/{mts => custom_s3}/views/formElements.html (51%) create mode 100644 submodules/custom_s3/views/logo.html delete mode 100644 submodules/mts/img/logo.png delete mode 100644 submodules/mts/views/logo.html diff --git a/app.js b/app.js index efb7c5b..24c3fa5 100644 --- a/app.js +++ b/app.js @@ -60,6 +60,9 @@ define(function(require) { render: function(container) { var self = this; +console.log("STORAGES:"); +console.log(self.storages); + monster.pub('storage.fetchStorages', { storages: self.storages, callback: function (args) { @@ -119,7 +122,10 @@ define(function(require) { self.getStorage(function(data) { var storagesData = self.storageManagerFormatData(data); for (var i = 0, len = storagesData.length; i < len; i++) { - storagesData[i].logo = self.storages[storagesData[i].type].getLogo() + //storagesData[i].logo = self.storages[storagesData[i].type].getLogo() + var storageType = storagesData[i].type; + + storagesData[i].logo = self.storages[storageType].getLogo() } log('Storages List:'); @@ -194,6 +200,31 @@ define(function(require) { }); }, + formatAttachmentSettings: function(storageData) { + var type = ""; + var attachments = storageData.attachments; + + if(storageData.hasOwnProperty('connections')) { //type is couchdb + attachments = storageData.connections; + type = "couchdb"; + } + + var attachments = storageData.attachments; + for(var i in attachments) if(attachments.hasOwnProperty(i)) { + var thisItem = attachments[i]; + var settings = thisItem.settings; + if (thisItem.settings && thisItem.settings.port) { + thisItem.settings.port = Number(thisItem.settings.port); + } else { + thisItem.settings.port = 443; + if (type == "couchdb") thisItem.settings.port = 5984; + } + attachments[i] = thisItem; + }; + storageData.attachments = attachments; + return storageData; + }, + storageManagerUpdateStorage: function(storageData, callback) { var self = this; @@ -207,7 +238,7 @@ define(function(require) { data: { accountId: self.accountId, removeMetadataAPI: true, // or generateError: false - data: storageData + data: self.formatAttachmentSettings(storageData) }, success: function(data, status) { if(typeof(callback) === 'function') { @@ -237,7 +268,7 @@ define(function(require) { data: { accountId: self.accountId, removeMetadataAPI: true, // or generateError: false - data: storageData + data: self.formatAttachmentSettings(storageData) }, success: function(data, status) { if(typeof(callback) === 'function') { @@ -256,20 +287,59 @@ define(function(require) { storageManagerFormatData: function(data) { var activeStorageId = null; + var storageType = ""; try { //activeStorageId = data.plan.modb.connection; //.handler; + if(data && data.hasOwnProperty('plan') && data.plan.hasOwnProperty('modb') && data.plan.modb.hasOwnProperty('connection') ) { + activeStorageId = data.plan.modb.connection; //.handler; + storageType = "couchdb"; + } else { activeStorageId = data.plan.modb.types.call_recording.attachments.handler; + } } catch(e) { log('Active storage not found'); } var itemData; var storagesList = []; + if (storageType == "couchdb" && data && data.hasOwnProperty('connections') && Object.keys(data.connections).length > 0) { + var connections = data.connections; + for(var i in connections) if(connections.hasOwnProperty(i)) { + itemData = { + id: i, +// type: connections[i].handler, +// type: connections[i], + type: storageType, //"couchdb" + name: connections[i].name, + settings: connections[i].settings, + isActive: false + }; + + if(activeStorageId && itemData.id === activeStorageId) { + itemData.isActive = true; + } + storagesList.push(itemData) + } + } if(data && data.hasOwnProperty('attachments') && Object.keys(data.attachments).length > 0) { var attachments = data.attachments; + for(var i in attachments) if(attachments.hasOwnProperty(i)) { + var storageType = attachments[i].handler; + var settings = attachments[i].settings; + //get custom s3 type by checking if URL is not AWS + if (storageType == "s3" + && ( + (settings.hasOwnProperty('host') && settings.host != "s3.amazonaws.com" && settings.host != "") + || (settings.hasOwnProperty('port') && settings.port != "443" && settings.port != 443 && settings.port != "") + ) + ) { + storageType = "custom_s3"; + } + itemData = { id: i, - type: attachments[i].handler, + //type: attachments[i].handler, + type: storageType, name: attachments[i].name, settings: attachments[i].settings, isActive: false @@ -301,8 +371,10 @@ define(function(require) { .find('.js-item-settings-wrapper') .hide(); - if(data.attachments.hasOwnProperty(uuid)) { + if(data.attachments && data.attachments.hasOwnProperty(uuid)) { var storageData = data.attachments[uuid]; + } else if(data.connections && data.connections.hasOwnProperty(uuid)) { + var storageData = data.connections[uuid]; } var template = self.getTemplate({ @@ -360,7 +432,8 @@ define(function(require) { if(isAlreadyActive) { self.storageManagerShowMessage(self.i18n.active().storage.alreadyActiveMessage, 'warning') } else { - self.storageManagerSetDefaultStorage(uuid); + var storageType = $(this).closest('.js-storage-item').data('type'); + self.storageManagerSetDefaultStorage(uuid, storageType); } }); }, @@ -421,7 +494,7 @@ define(function(require) { }; if(isNeedSetDefault) { - self.storageManagerSetDefaultStorage(newUuid, function () { + self.storageManagerSetDefaultStorage(newUuid, typeKeyword, function () { self.storageManagerRender(renderArgs); }); } else { @@ -449,22 +522,60 @@ define(function(require) { } if(storageKeyword && self.storages.hasOwnProperty(storageKeyword)) { + if (storageKeyword == "couchdb") { + storageData["connections"] = {}; + data.settings.port = eval(data.settings.port); + storageData.connections[uuid] = data; + } else { storageData.attachments[uuid] = data; + } return storageData; } else { monster.ui.alert('Please install storage correctly (' + storageKeyword + ')'); } }, - storageManagerSetDefaultStorage: function(uuid, callback) { + storageManagerSetDefaultStorage: function(uuid, storageType, callback) { var self = this; if(!monster.util.isAdmin()) { log('Permission error. Use admin account for change storage settings'); return; } + var newData = {}; + if (storageType == "couchdb") { + newData = { + plan: { + modb: { + connection: uuid, + types: { + call_recording: { + database:{ + create_options:{} + } + }, + mailbox_message: { + database:{ + create_options:{} + } + } + } + } +// account: { +// types: { +// media: { +// database:{ +// create_options:{} +// } +// } +// } +// }, + } + }; + - var newData = { + } else { + newData = { plan: { modb: { types: { @@ -491,7 +602,7 @@ define(function(require) { }, } }; - + } self.storageManagerPatchStorage(newData, function(data) { $('#storage_manager_wrapper').find('.js-storage-item') @@ -547,12 +658,19 @@ define(function(require) { self.getStorage(function(storagesData) { var resultData = {}; + if(storagesData.hasOwnProperty('connections')) { + resultData.connections = storagesData.connections; + } if(storagesData.hasOwnProperty('attachments')) { resultData.attachments = storagesData.attachments; } if(storagesData.hasOwnProperty('plan')) { resultData.plan = storagesData.plan; } + if(resultData.connections && resultData.connections.hasOwnProperty(uuid)) { + resultData.attachments = {}; + delete resultData.connections[uuid]; + } if(resultData.attachments && resultData.attachments.hasOwnProperty(uuid)) { delete resultData.attachments[uuid]; @@ -561,6 +679,8 @@ define(function(require) { try { if(resultData.plan.modb.types.call_recording.attachments.handler === uuid) { resultData.plan = {}; + } else if(resultData.plan.modb.types.call_recording.connection === uuid) { + resultData.plan = {}; } } catch (e) {} diff --git a/storages.js b/storages.js index 3339764..f1213e5 100644 --- a/storages.js +++ b/storages.js @@ -2,7 +2,8 @@ define(function(require) { return { "storages": [ "s3", - "mts" + "custom_s3", + "couchdb" ] }; }); diff --git a/style/app.css b/style/app.css index e8ed3e3..e67787b 100644 --- a/style/app.css +++ b/style/app.css @@ -172,6 +172,10 @@ .storage-item-settings .storage-item-logo img, .storage-item-settings .storage-item-logo svg { max-height: 40px; } +.storage-item-settings .storage-type-label { + text-align: center; +} + .storage-item-settings .form-horizontal { margin: 0 auto; display: block; diff --git a/submodules/mts/mts.js b/submodules/couchdb/couchdb.js similarity index 82% rename from submodules/mts/mts.js rename to submodules/couchdb/couchdb.js index f721416..2527fc6 100644 --- a/submodules/mts/mts.js +++ b/submodules/couchdb/couchdb.js @@ -2,7 +2,7 @@ define(function(require){ var $ = require('jquery'); const CONFIG = { - submoduleName: 'mts', + submoduleName: 'couchdb', i18n: [ 'en-US' ] }; @@ -10,10 +10,10 @@ define(function(require){ requests: {}, subscribe: { - 'storage.fetchStorages': 'defineStorageMTS' + 'storage.fetchStorages': 'defineStorageCouchDB' }, - defineStorageMTS: function(args) { + defineStorageCouchDB: function(args) { var self = this, storage_nodes = args.storages; @@ -36,7 +36,7 @@ define(function(require){ }; $.extend(true, storage_nodes, { - 'mts': methods + 'couchdb': methods } ); diff --git a/submodules/couchdb/i18n/en-US.json b/submodules/couchdb/i18n/en-US.json new file mode 100644 index 0000000..ff5d93a --- /dev/null +++ b/submodules/couchdb/i18n/en-US.json @@ -0,0 +1,13 @@ +{ + "storage": { + "submodules": { + "couchdb": { + "nameLabel": "Name", + "bucketLabel": "IP", + "portLabel": "Port", + "keyLabel": "Username", + "secretLabel": "Password" + } + } + } +} diff --git a/submodules/couchdb/img/logo.png b/submodules/couchdb/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc7c1556d51b88b57ecff7f69c2c2fa76d281a0 GIT binary patch literal 1280 zcmV+b1^@bqP)EX>4Tx04R}tkv&MmKpe$iTeTt;2Q!E`WT;LSL`58H6^c+H)C#RSm|Xe=O&XFE z7e~Rh;NZt%)xpJCR|i)?5c~jfc5qU3krMxx6k5c1aNLh~_a1lefMBD-G^;BPXu55t zlL;}KTNT5v2%!f8CJ>XEWz0!Z3clm(9s$1I#dwzgxj#q0nztAb5Q*c=Fm2)u;+aj` z;Ji;Pu#&72pA(OpbV1@rt}7nDaW1+XFgLO8@`>8FWQhbVF}#ZDnqB07G(RVRU6= zAa`kWXdp*PO;A^X4i^9b0@+DKK~!ko?U+kQRY4elzatr94{Bk7G(|P#P*Il9rlJR^ zn>K+_1QA6^Q9+Qn?6H&*A~%I?i=cK%xMAf^CoeS;^ZG4V~{15gC4NptLY(+=DMI)P7F%NbUxWx!$} zA9xi8+zmpe180C8F_Z8eI6!}Jk({s)SP9Go-T|$`=#Qh4VWrv*)C^HNJAo}>KvNKM z2Plb=j7nj2y_M=LaCnISuK|aI(QmwESgE!Fms4EyhA_I+N_8AK5hEG1fEmE^6frM> z6~gFl-vm0+4Gv`Jju?sP1wId2+i=VWc6(J5SBjM90vdoHF_Cdq7~SU?TjE<7Y3ZttsnIk8|HB`hwgyV>nDmO|QoiQq{ zUtCWkt1^7c6G6xo;1uvRViAvl3So4=XZ&B^oZ+3B=d|g5pc<&QQWd0Ama{|%bO@t= zr8%zKONKD|kCmzoSm>F!ih*6gl89uyw^FqL=Y-L|2SA-pHE9G6dWvWPCP%zYwgQ`h zjh^w1=_JrPcd|?ML;yC$K*RtWTx)Vf5yI#v-ZF&I&)o60cbExpyWf^UVxs1eF#0#} z0_aMyJ@$;Mx#Jo;`+?O?TsP1_NS$cYJuapks08LkB%zQ0U9Ho?=ytcJBH*ZN?gZ{@ q_BwE082yukgoK2IgoK2I==cZQG|VMl>Koz!0000External CouchDB Storage +
+ + + + + + diff --git a/submodules/couchdb/views/logo.html b/submodules/couchdb/views/logo.html new file mode 100644 index 0000000..a857582 --- /dev/null +++ b/submodules/couchdb/views/logo.html @@ -0,0 +1 @@ +couchdb diff --git a/submodules/custom_s3/custom_s3.js b/submodules/custom_s3/custom_s3.js new file mode 100644 index 0000000..fef6bbe --- /dev/null +++ b/submodules/custom_s3/custom_s3.js @@ -0,0 +1,48 @@ +define(function(require){ + var $ = require('jquery'); + + const CONFIG = { + submoduleName: 'custom_s3', + i18n: [ 'en-US' ] + }; + + var app = { + requests: {}, + + subscribe: { + 'storage.fetchStorages': 'defineStorageCustomS3' + }, + + defineStorageCustomS3: function(args) { + var self = this, + storage_nodes = args.storages; + + var methods = { + getLogo: function () { + return self.getTemplate({ + name: 'logo', + submodule: CONFIG.submoduleName, + data: {} + }); + }, + + getFormElements: function (storageData) { + return self.getTemplate({ + name: 'formElements', + submodule: CONFIG.submoduleName, + data: storageData + }); + } + }; + + $.extend(true, storage_nodes, { + 'custom_s3': methods + } + ); + + args.callback && args.callback(CONFIG) + } + }; + + return app; +}); diff --git a/submodules/mts/i18n/en-US.json b/submodules/custom_s3/i18n/en-US.json similarity index 89% rename from submodules/mts/i18n/en-US.json rename to submodules/custom_s3/i18n/en-US.json index 6f31f71..4c22635 100644 --- a/submodules/mts/i18n/en-US.json +++ b/submodules/custom_s3/i18n/en-US.json @@ -1,7 +1,7 @@ { "storage": { "submodules": { - "mts": { + "custom_s3": { "nameLabel": "Name", "bucketLabel": "Bucket", "keyLabel": "Key", diff --git a/submodules/custom_s3/img/logo.png b/submodules/custom_s3/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f4bfce225adaddc17d5ccd31b3afad9b28535e5c GIT binary patch literal 6056 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 S3–Compatible Object Storage +
- -