diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index 07471183a..b25cbf43c 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -90,12 +90,21 @@ button.btn { @button-bg: @colortheme_sidebar-button-bg; @button-red-bg: @colortheme_sidebar-button-red-bg; + @button-alt-bg: @colortheme_sidebar-button-alt-bg; background-color: @button-bg; border-color: darken(@button-bg, 10%); color: white; &:hover { background-color: darken(@button-bg, 10%); } + &.btn-secondary { + background-color: @button-alt-bg; + border-color: darken(@button-alt-bg, 10%); + color: black; + &:hover { + background-color: darken(@button-alt-bg, 10%); + } + } &.btn-danger { background-color: @button-red-bg; border-color: darken(@button-red-bg, 10%); diff --git a/customize.dist/src/less2/include/support.less b/customize.dist/src/less2/include/support.less new file mode 100644 index 000000000..6f4bf559b --- /dev/null +++ b/customize.dist/src/less2/include/support.less @@ -0,0 +1,69 @@ +@import (reference) "./colortheme-all.less"; +.support_main () { + @ticket-bg: #F7F7F7; + @msg-bg: #eee; + @fromme-bg: #ddd; + .cp-support-container { + .cp-support-list-ticket { + display: flex; + flex-flow: column; + background-color: @ticket-bg; + padding: 10px; + width: 1200px; + max-width: 90%; + margin: 5px auto; + .cp-support-list-message { + background-color: @msg-bg; + margin: 2px; + padding: 2px 5px; + .cp-support-fromme { + background-color: @fromme-bg; + } + .cp-support-showdata { + cursor: pointer; + background-color: @fromme-bg; + .cp-support-message-data { + display: none; + cursor: default; + } + } + .cp-support-message-time { + float: right; + } + pre { + margin-bottom: 0; + &.cp-support-message-content { + margin-top: 10px; + margin-bottom: 10px; + } + } + } + .cp-support-list-actions { + order: 3; + .cp-support-hide { + display: none; + } + } + .cp-support-form-container { + order: 2; + } + &.cp-support-list-closed { + .cp-support-list-actions { + display: block !important; + .cp-support-answer, .cp-support-close { + display: none; + } + .cp-support-hide { + display: inline; + } + } + .cp-support-form-container { + display: none !important; + } + } + } + } +} + + + diff --git a/scripts/generateAdminKeys.js b/scripts/generate-admin-keys.js similarity index 96% rename from scripts/generateAdminKeys.js rename to scripts/generate-admin-keys.js index 072dd8b3d..62fe3e121 100644 --- a/scripts/generateAdminKeys.js +++ b/scripts/generate-admin-keys.js @@ -1,12 +1,13 @@ +/* jshint esversion: 6, node: true */ + const Nacl = require('tweetnacl'); -const Crypto = require('crypto'); const keyPair = Nacl.box.keyPair(); console.log(keyPair); console.log("You've just generated a new key pair for your support mailbox."); -console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server.") +console.log("The public key should first be added to your config.js file ('supportMailboxPublicKey'), then save and restart the server."); console.log("Once restarted, administrators (specified with 'adminKeys' in config.js too) will be able to add the private key into their account. This can be done using the administration panel."); console.log("You will have to send the private key to each administrator manually so that they can add it to their account."); console.log(); diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index c3b8a31de..a1c1ee779 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -1,5 +1,6 @@ @import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/sidebar-layout.less'; +@import (reference) '../../customize/src/less2/include/support.less'; &.cp-app-admin { @@ -9,6 +10,11 @@ @color: @colortheme_admin-color ); .sidebar-layout_main(); + .support_main(); + + .cp-hidden { + display: none !important; + } display: flex; flex-flow: column; diff --git a/www/admin/inner.js b/www/admin/inner.js index 83dec1415..96b4e5bba 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -9,6 +9,8 @@ define([ '/customize/messages.js', '/common/common-interface.js', '/common/common-util.js', + '/common/common-hash.js', + '/support/ui.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', @@ -23,7 +25,9 @@ define([ h, Messages, UI, - Util + Util, + Hash, + Support ) { var APP = {}; @@ -41,6 +45,10 @@ define([ 'cp-admin-active-pads', 'cp-admin-registered', 'cp-admin-disk-usage', + ], + 'support': [ + 'cp-admin-support-list', + 'cp-admin-support-init' ] }; @@ -94,7 +102,6 @@ define([ sFrameChan.query('Q_ADMIN_RPC', { cmd: 'ACTIVE_SESSIONS', }, function (e, data) { - console.log(e, data); var total = data[0]; var ips = data[1]; $div.append(h('pre', total + ' (' + ips + ')')); @@ -160,6 +167,111 @@ define([ return $div; }; + var supportKey = ApiConfig.supportMailbox; + create['support-list'] = function () { + if (!supportKey || !APP.privateKey) { return; } + var $div = makeBlock('support-list'); + $div.addClass('cp-support-container'); + var hashesById = {}; + + // Register to the "support" mailbox + common.mailbox.subscribe(['supportadmin'], { + onMessage: function (data) { + /* + Get ID of the ticket + If we already have a div for this ID + Push the message to the end of the ticket + If it's a new ticket ID + Make a new div for this ID + */ + var msg = data.content.msg; + var hash = data.content.hash; + var content = msg.content; + var id = content.id; + var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]'); + + hashesById[id] = hashesById[id] || []; + if (hashesById[id].indexOf(hash) === -1) { + hashesById[id].push(data); + } + + if (msg.type === 'CLOSE') { + // A ticket has been closed by the admins... + if (!$ticket.length) { return; } + $ticket.addClass('cp-support-list-closed'); + $ticket.append(Support.makeCloseMessage(common, content, hash)); + return; + } + if (msg.type !== 'TICKET') { return; } + + if (!$ticket.length) { + $ticket = Support.makeTicket($div, common, content, function () { + var error = false; + hashesById[id].forEach(function (d) { + common.mailbox.dismiss(d, function (err) { + if (err) { + error = true; + console.error(err); + } + }); + }); + if (!error) { $ticket.remove(); } + }); + } + $ticket.append(Support.makeMessage(common, content, hash, true)); + } + }); + return $div; + }; + + var checkAdminKey = function (priv) { + if (!supportKey) { return; } + return Hash.checkBoxKeyPair(priv, supportKey); + }; + + create['support-init'] = function () { + var $div = makeBlock('support-init'); + if (!supportKey) { + $div.append(h('p', Messages.admin_supportInitHelp || "Your server is not configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in './scripts/generate-admin-keys.js', store the public key in the 'config.js' file, and send you the private key.")); // XXX + return $div; + } + if (!APP.privateKey || !checkAdminKey(APP.privateKey)) { + $div.append(h('p', Messages.admin_supportInitPrivate || "Your CryptPad instance is configured to use a support mailbox but your account doesn't have the correct private key to access it. Please use the following form to add or update the private key to your account")); // XXX + + var error = h('div.cp-admin-support-error'); + var input = h('input.cp-admin-add-private-key'); + var button = h('button.btn.btn-primary', Messages.admin_supportAddKey || 'add key'); // XXX + + if (APP.privateKey && !checkAdminKey(APP.privateKey)) { + $(error).text(Messages.admin_supportAddError || 'invalid'); // XXX + } + + $div.append(h('div', [ + error, + input, + button + ])); + + $(button).click(function () { + var key = $(input).val(); + if (!checkAdminKey(key)) { + $(input).val(''); + return void $(error).text(Messages.admin_supportAddError || 'invalid'); // XXX + } + sFrameChan.query("Q_ADMIN_MAILBOX", key, function () { + console.log(key); + console.log(arguments); + APP.privateKey = key; + console.log('ok'); + $('.cp-admin-support-init').hide(); + APP.$rightside.append(create['support-list']()); // TODO: check? + }); + }); + return $div; + } + return; + }; + var hideCategories = function () { APP.$rightside.find('> div').hide(); }; @@ -180,6 +292,7 @@ define([ var $category = $('