From 070d8dc450e72d46338d43b148b9536bd0859def Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 12 Feb 2024 14:30:33 +0100 Subject: [PATCH] Support V2 prototype --- lib/decrees.js | 5 + lib/env.js | 1 + lib/http-worker.js | 1 + lib/stats.js | 1 + www/admin/inner.js | 157 ++++++++++++++++------ www/common/common-constants.js | 2 +- www/common/outer/async-store.js | 12 +- www/common/outer/support.js | 200 +++++++++++++++++++++++++++++ www/moderation/app-moderation.less | 42 ++++++ www/moderation/index.html | 20 +++ www/moderation/inner.html | 22 ++++ www/moderation/inner.js | 104 +++++++++++++++ www/moderation/main.js | 47 +++++++ www/support/inner.js | 47 +++++-- www/support/main.js | 2 +- www/support/ui.js | 32 ++--- 16 files changed, 620 insertions(+), 75 deletions(-) create mode 100644 www/common/outer/support.js create mode 100644 www/moderation/app-moderation.less create mode 100644 www/moderation/index.html create mode 100644 www/moderation/inner.html create mode 100644 www/moderation/inner.js create mode 100644 www/moderation/main.js diff --git a/lib/decrees.js b/lib/decrees.js index bf0fd3068..98eb97f55 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -40,6 +40,7 @@ SET_MAINTENANCE // EASIER CONFIG SET_ADMIN_EMAIL SET_SUPPORT_MAILBOX +SET_SUPPORT_MAILBOX2 // COMMUNITY PARTICIPATION AND GOVERNANCE CONSENT_TO_CONTACT @@ -209,6 +210,10 @@ commands.SET_ADMIN_EMAIL = makeGenericSetter('adminEmail', args_isString); commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (args) { return args_isString(args) && Core.isValidPublicKey(args[0]); }); +// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_MAILBOX2', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log) +commands.SET_SUPPORT_MAILBOX2 = makeGenericSetter('newSupportMailbox', function (args) { + return args_isString(args) && (Core.isValidPublicKey(args[0]) || !args[0]); +}); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log) commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString); diff --git a/lib/env.js b/lib/env.js index 1f95a3beb..5f648d0c9 100644 --- a/lib/env.js +++ b/lib/env.js @@ -147,6 +147,7 @@ module.exports.create = function (config) { adminEmail: config.adminEmail, supportMailbox: config.supportMailboxPublicKey, + newSupportMailbox: undefined, metadata_cache: {}, channel_cache: {}, diff --git a/lib/http-worker.js b/lib/http-worker.js index b4e1f13ad..0c9dffd4c 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -571,6 +571,7 @@ var serveConfig = makeRouteCache(function () { adminKeys: Env.admins, inactiveTime: Env.inactiveTime, supportMailbox: Env.supportMailbox, + newSupportMailbox: Env.newSupportMailbox, defaultStorageLimit: Env.defaultStorageLimit, maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, diff --git a/lib/stats.js b/lib/stats.js index 16aefc59a..5d1fdc6a9 100644 --- a/lib/stats.js +++ b/lib/stats.js @@ -45,6 +45,7 @@ Stats.instanceData = function (Env) { // we expect that you enable your support mailbox data.supportMailbox = Boolean(Env.supportMailbox); + data.newSupportMailbox = Boolean(Env.newSupportMailbox); // do you allow registration? data.restrictRegistration = Boolean(Env.restrictRegistration); diff --git a/www/admin/inner.js b/www/admin/inner.js index 7ad80c1c5..7741d28fa 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -98,6 +98,7 @@ define([ 'cp-admin-disk-usage', ], 'support': [ // Msg.admin_cat_support + 'cp-admin-support-new', 'cp-admin-support-list', 'cp-admin-support-init', 'cp-admin-support-priv', @@ -2914,51 +2915,125 @@ Example }); return $div; }; + + // XXX + Messages.admin_supportNewHint = "Create or update the current support keys."; + Messages.admin_supportNewTitle = "Initialize support"; + Messages.admin_supportNewEnabled = "Modern support system is enabled."; + Messages.admin_supportNewDisabled = "Modern support system is disabled."; + Messages.admin_supportNewInit = "Initialize support page on this instance"; + Messages.admin_supportNewDelete = "Disable support"; + Messages.admin_supportNewConfirm = "Are you sure? This will remove access to all current moderators."; + create['support-new'] = function () { + var $div = makeBlock('support-new'); // Msg.admin_supportNewHint, .admin_supportNewTitle + var newSupportKey = ApiConfig.newSupportMailbox; + (function () { + var state = h('div'); + var $state = $(state).appendTo($div); + var button = h('button.btn.btn-primary', Messages.admin_supportNewInit); + var $button = $(button).appendTo($div); + var delButton = h('button.btn.btn-danger', Messages.admin_supportNewDelete); + var $delButton = $(delButton).appendTo($div).hide();; + var spinner = UI.makeSpinner($div); + + var setState = function () { + $state.html(''); + if (newSupportKey) { + $button.hide(); + $delButton.show(); + return $state.append([ + h('i.fa.fa-check'), + h('span', Messages.admin_supportNewEnabled) + ]); + } + $button.show(); + $state.append([ + h('i.fa.fa-times'), + h('span', Messages.admin_supportNewDisabled) + ]); + }; + setState(); + + Util.onClickEnter($delButton, function () { + UI.confirm(Messages.admin_supportNewConfirm, function (yes) { + if (!yes) { return; } + // Send the decree, don't delete data locally, we just want to remove + // the support UI for the clients + spinner.spin(); + $delButton.attr('disabled', 'disabled'); + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'ADMIN_DECREE', + data: ['SET_SUPPORT_MAILBOX2', ['']] + }, function (e, response) { + $delButton.removeAttr('disabled'); + if (e || response.error) { + UI.warn(Messages.error); + console.error(e, response); + spinner.hide(); + return; + } + spinner.done(); + UI.log(Messages.saved); + newSupportKey = undefined; + setState(); + }); + }); + }); + var next = function () { + spinner.spin(); + $button.attr('disabled', 'disabled'); + var keyPair = Nacl.box.keyPair(); + var pub = Nacl.util.encodeBase64(keyPair.publicKey); + var priv = Nacl.util.encodeBase64(keyPair.secretKey); + // Store the private key first. It won't be used until the decree is accepted. + sFrameChan.query("Q_ADMIN_MAILBOX", { + version: 2, + priv: priv + }, function (err, obj) { + if (err || (obj && obj.error)) { + console.error(err || obj.error); + UI.warn(Messages.error); + spinner.hide(); + return; + } + // Then send the decree + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'ADMIN_DECREE', + data: ['SET_SUPPORT_MAILBOX2', [pub]] + }, function (e, response) { + $button.removeAttr('disabled'); + if (e || response.error) { + UI.warn(Messages.error); + console.error(e, response); + spinner.hide(); + return; + } + spinner.done(); + UI.log(Messages.saved); + newSupportKey = pub; + setState(); + //$('.cp-admin-support-init').hide(); + //APP.$rightside.append(create['support-list']()); + //APP.$rightside.append(create['support-priv']()); + }); + }); + }; + Util.onClickEnter($button, function () { + if (newSupportKey) { + return void UI.confirm(Messages.admin_supportNewConfirm, function (yes) { + if (yes) { next(); } + }); + } + next(); + }); + })(); + return $div; + }; create['support-init'] = function () { var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle if (!supportKey) { (function () { - $div.append(h('p', Messages.admin_supportInitHelp)); - var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate); - var $button = $(button).appendTo($div); - $div.append($button); - var spinner = UI.makeSpinner($div); - $button.click(function () { - spinner.spin(); - $button.attr('disabled', 'disabled'); - var keyPair = Nacl.box.keyPair(); - var pub = Nacl.util.encodeBase64(keyPair.publicKey); - var priv = Nacl.util.encodeBase64(keyPair.secretKey); - // Store the private key first. It won't be used until the decree is accepted. - sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) { - if (err || (obj && obj.error)) { - console.error(err || obj.error); - UI.warn(Messages.error); - spinner.hide(); - return; - } - // Then send the decree - sFrameChan.query('Q_ADMIN_RPC', { - cmd: 'ADMIN_DECREE', - data: ['SET_SUPPORT_MAILBOX', [pub]] - }, function (e, response) { - $button.removeAttr('disabled'); - if (e || response.error) { - UI.warn(Messages.error); - console.error(e, response); - spinner.hide(); - return; - } - spinner.done(); - UI.log(Messages.saved); - supportKey = pub; - APP.privateKey = priv; - $('.cp-admin-support-init').hide(); - APP.$rightside.append(create['support-list']()); - APP.$rightside.append(create['support-priv']()); - }); - }); - }); + return; // XXX old support can't be created anymore })(); return $div; } diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 148ea8e43..63d16b617 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -26,7 +26,7 @@ define(['/customize/application_config.js'], function (AppConfig) { MAX_PREMIUM_TEAMS_SLOTS: Math.max(AppConfig.maxTeamsSlots || 0, AppConfig.maxPremiumTeamsSlots || 0) || 5, MAX_PREMIUM_TEAMS_OWNED: Math.max(AppConfig.maxTeamsOwned || 0, AppConfig.maxPremiumTeamsOwned || 0) || 5, // Apps - criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar'], + criticalApps: ['profile', 'settings', 'debug', 'admin', 'support', 'notifications', 'calendar', 'moderation'], earlyAccessApps: ['doc', 'presentation'] }; }); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 6791aba27..0d3f0f026 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -18,6 +18,7 @@ define([ '/common/outer/cache-store.js', '/common/outer/sharedfolder.js', '/common/outer/cursor.js', + '/common/outer/support.js', '/common/outer/integration.js', '/common/outer/onlyoffice.js', '/common/outer/mailbox.js', @@ -39,7 +40,7 @@ define([ '/components/saferphore/index.js', ], function (ApiConfig, Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Pinpad, Cache, - SF, Cursor, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History, + SF, Cursor, Support, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History, Calendar, Block, NetConfig, AppConfig, Crypto, ChainPad, CpNetflux, Listmap, Netflux, nThen, Saferphore) { @@ -1616,12 +1617,14 @@ define([ }); }; Store.addAdminMailbox = function (clientId, data, cb) { - var priv = data; + var priv = data && data.priv; var pub = Hash.getBoxPublicFromSecret(priv); + var isNewSupport = data && data.version === 2; if (!priv || !pub) { return void cb({error: 'EINVAL'}); } var channel = Hash.getChannelIdFromKey(pub); var mailboxes = store.proxy.mailboxes = store.proxy.mailboxes || {}; - var box = mailboxes.supportadmin = { + var key = isNewSupport ? 'support2' : 'supportadmin'; + var box = mailboxes['supportadmin'] = { channel: channel, viewed: [], lastKnownHash: '', @@ -1631,7 +1634,7 @@ define([ } }; Store.pinPads(null, [channel], function () {}); - store.mailbox.open('supportadmin', box, function () { + store.mailbox.open(key, box, function () { console.log('ready'); }); onSync(null, cb); @@ -2826,6 +2829,7 @@ define([ postMessage(clientId, 'LOADING_DRIVE', data); }); loadUniversal(Cursor, 'cursor', waitFor); + loadUniversal(Support, 'support', waitFor); loadUniversal(Integration, 'integration', waitFor); loadOnlyOffice(); loadUniversal(Messenger, 'messenger', waitFor); diff --git a/www/common/outer/support.js b/www/common/outer/support.js new file mode 100644 index 000000000..a1fa719a6 --- /dev/null +++ b/www/common/outer/support.js @@ -0,0 +1,200 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +define([ + '/common/common-util.js', + '/common/common-hash.js', + '/components/nthen/index.js', + '/components/chainpad-crypto/crypto.js', + 'chainpad-netflux' +], function (Util, Hash, nThen, Crypto, CpNetflux) { + var Support = {}; + + var getKeys = function (ctx, cb) { + require(['/api/config?' + (+new Date())], function (NewConfig) { + supportKey = NewConfig.newSupportMailbox; + if (!supportKey) { return void cb('E_NOT_INIT'); } + var myCurve = ctx.store.proxy.curvePrivate; + cb(null, { + supportKey, + myCurve + }); + }); + }; + + // Get the content of a mailbox and close it + // Used for support tickets + var getContent = function (ctx, data, _cb) { + var supportKey, myCurve; + nThen((waitFor) => { + // Send ticket to the admins and call back + getKeys(ctx, waitFor((err, obj) => { + if (err) { + waitFor.abort(); + return void cb({error: err}); + } + supportKey = obj.supportKey; + myCurve = obj.myCurve; + })); + }).nThen((waitFor) => { + var keys = Crypto.Curve.deriveKeys(supportKey, myCurve); + var crypto = Crypto.Curve.createEncryptor(keys); + var cfg = { + network: ctx.store.network, + channel: data.channel, + noChainPad: true, + crypto: crypto, + owners: [] + }; + var all = []; + var cpNf; + var close = function () { + if (!cpNf) { return; } + if (typeof(cpNf.stop) !== "function") { return; } + cpNf.stop(); + }; + var cb = Util.once(Util.both(close, Util.mkAsync(_cb))); + cfg.onMessage = function (msg, user, vKey, isCp, hash, author, data) { + var time = data && data.time; + try { + msg = JSON.parse(msg); + } catch (e) { + console.error(e); + } + if (author) { msg.author = author; } + console.log(msg); + all.push(msg); + }; + cfg.onError = cb; + cfg.onChannelError = cb; + cfg.onReady = function () { + cb(null, all); + }; + cpNf = CpNetflux.start(cfg); + }); + }; + + var makeTicket = function (ctx, data, cId, cb) { + var mailbox = Util.find(ctx, [ 'store', 'mailbox' ]); + var anonRpc = Util.find(ctx, [ 'store', 'anon_rpc' ]); + if (!mailbox) { return void cb({error: 'E_NOT_READY'}); } + if (!anonRpc) { return void cb({error: "anonymous rpc session not ready"}); } + + var channel = data.channel; + var title = data.title; + var ticket = data.ticket; + var supportKey, myCurve; + nThen((waitFor) => { + // Send ticket to the admins and call back + getKeys(ctx, waitFor((err, obj) => { + if (err) { + waitFor.abort(); + return void cb({error: err}); + } + supportKey = obj.supportKey; + myCurve = obj.myCurve; + })); + }).nThen((waitFor) => { + // Create ticket mailbox + var keys = Crypto.Curve.deriveKeys(supportKey, myCurve); + var crypto = Crypto.Curve.createEncryptor(keys); + var text = JSON.stringify(ticket); + var ciphertext = crypto.encrypt(text); + anonRpc.send("WRITE_PRIVATE_MESSAGE", [ + channel, + ciphertext + ], waitFor((err) => { + if (err) { + waitFor.abort(); + return void cb({error: err}); + } + })); + }).nThen((waitFor) => { + // Store in our worker + console.error(channel); + ctx.supportData[channel] = { + time: +new Date(), + title: title, + curvePublic: supportKey // XXX Should we store this value or delete the ticket on key rotation? + }; + ctx.Store.onSync(null, waitFor()); + }).nThen(() => { + var supportChannel = Hash.getChannelIdFromKey(supportKey); + console.error(supportChannel); + mailbox.sendTo('NEW_TICKET', { + title: title, + channel: channel + }, { + channel: supportChannel, + curvePublic: supportKey + }, (obj) => { + // Don't store the ticket in case of error + if (obj && obj.error) { delete ctx.supportData[channel]; } + cb(obj); + }); + }); + }; + + var getMyTickets = function (ctx, data, cId, cb) { + var all = []; + var n = nThen; + Object.keys(ctx.supportData).forEach(function (ticket) { + n = n((waitFor) => { + var t = Util.clone(ctx.supportData[ticket]); + getContent(ctx, { + channel: ticket, + }, waitFor((err, messages) => { + if (err) { t.error = err; } + else { t.messages = messages; } + all.push(t); + })); + }).nThen; + }); + n(() => { + cb({tickets: all}); + }); + }; + + Support.init = function (cfg, waitFor, emit) { + var support = {}; + + // Already initialized by a "noDrive" tab? + if (cfg.store && cfg.store.modules && cfg.store.modules['support']) { + return cfg.store.modules['support']; + } + + var store = cfg.store; + var proxy = store.proxy.support = store.proxy.support || {}; + + var ctx = { + supportData: proxy, + store: cfg.store, + Store: cfg.Store, + emit: emit, + channels: {}, + clients: {} + }; + + support.removeClient = function (clientId) { + removeClient(ctx, clientId); + }; + support.leavePad = function (padChan) { + leaveChannel(ctx, padChan); + }; + support.execCommand = function (clientId, obj, cb) { + var cmd = obj.cmd; + var data = obj.data; + if (cmd === 'MAKE_TICKET') { + return void makeTicket(ctx, data, clientId, cb); + } + if (cmd === 'GET_MY_TICKETS') { + return void getMyTickets(ctx, data, clientId, cb); + } + }; + + return support; + }; + + return Support; +}); diff --git a/www/moderation/app-moderation.less b/www/moderation/app-moderation.less new file mode 100644 index 000000000..2e6be71f6 --- /dev/null +++ b/www/moderation/app-moderation.less @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors + * + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +@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'; +@import (reference) '../../customize/src/less2/include/charts.less'; +&.cp-app-moderation { + + .framework_min_main(); + .sidebar-layout_main(); + .support_main(); + .charts_main(); + + .cp-hidden { + display: none !important; + } + + label{ + margin-top:0.5rem; + } + + display: flex; + flex-flow: column; + + .cp-sidebarlayout-element { + label:not(.cp-admin-label) { + font-weight: normal !important; + } + input { + max-width: 25rem; + } + nav { + display: flex; + margin-top: 0.5rem; + } + } +} + diff --git a/www/moderation/index.html b/www/moderation/index.html new file mode 100644 index 000000000..afde85cbd --- /dev/null +++ b/www/moderation/index.html @@ -0,0 +1,20 @@ + + + + + + CryptPad + + + + + + + + + + diff --git a/www/moderation/inner.html b/www/moderation/inner.html new file mode 100644 index 000000000..bc5ef1fa4 --- /dev/null +++ b/www/moderation/inner.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + +
+
+ + diff --git a/www/moderation/inner.js b/www/moderation/inner.js new file mode 100644 index 000000000..bbb070d80 --- /dev/null +++ b/www/moderation/inner.js @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +define([ + 'jquery', + '/api/config', + '/customize/application_config.js', + '/components/chainpad-crypto/crypto.js', + '/common/toolbar.js', + '/components/nthen/index.js', + '/common/sframe-common.js', + '/common/hyperscript.js', + '/customize/messages.js', + '/common/common-interface.js', + '/common/common-ui-elements.js', + '/common/common-util.js', + '/common/common-hash.js', + '/common/common-signing-keys.js', + '/support/ui.js', + '/common/clipboard.js', + 'json.sortify', + + 'css!/lib/datepicker/flatpickr.min.css', + 'css!/components/bootstrap/dist/css/bootstrap.min.css', + 'css!/components/components-font-awesome/css/font-awesome.min.css', + 'less!/moderation/app-moderation.less', +], function ( + $, + ApiConfig, + AppConfig, + Crypto, + Toolbar, + nThen, + SFCommon, + h, + Messages, + UI, + UIElements, + Util, + Hash, + Keys, + Support, + Clipboard, + Sortify, + ) +{ + var APP = { + 'instanceStatus': {} + }; + + var Nacl = window.nacl; + var common; + var sFrameChan; + +// XXX + + var andThen = function () { + + + }; + + var createToolbar = function () { + var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications']; + var configTb = { + displayed: displayed, + sfCommon: common, + $container: APP.$toolbar, + pageTitle: Messages.supportPage, + metadataMgr: common.getMetadataMgr(), + }; + APP.toolbar = Toolbar.create(configTb); + APP.toolbar.$rightside.hide(); + }; + + nThen(function (waitFor) { + $(waitFor(UI.addLoadingScreen)); + SFCommon.create(waitFor(function (c) { APP.common = common = c; })); + }).nThen(function (waitFor) { + APP.$container = $('#cp-content-container'); + APP.$toolbar = $('#cp-toolbar'); + sFrameChan = common.getSframeChannel(); + sFrameChan.onReady(waitFor()); + }).nThen(function (waitFor) { + if (!common.isAdmin()) { return; } // XXX moderator + }).nThen(function (/*waitFor*/) { + createToolbar(); + var metadataMgr = common.getMetadataMgr(); + var privateData = metadataMgr.getPrivateData(); + common.setTabTitle(Messages.supportPage); + + if (!common.isAdmin()) { + return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden'); + } + + APP.privateKey = privateData.supportPrivateKey; + APP.origin = privateData.origin; + APP.readOnly = privateData.readOnly; + APP.support = Support.create(common, true); + + UI.removeLoadingScreen(); + + }); +}); diff --git a/www/moderation/main.js b/www/moderation/main.js new file mode 100644 index 000000000..f6be3331c --- /dev/null +++ b/www/moderation/main.js @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team and contributors +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +// Load #1, load as little as possible because we are in a race to get the loading screen up. +define([ + '/components/nthen/index.js', + '/api/config', + '/common/dom-ready.js', + '/common/sframe-common-outer.js', +], function (nThen, ApiConfig, DomReady, SFCommonO) { + + // Loaded in load #2 + nThen(function (waitFor) { + DomReady.onReady(waitFor()); + }).nThen(function (waitFor) { + SFCommonO.initIframe(waitFor); + }).nThen(function (/*waitFor*/) { + var addRpc = function (sframeChan, Cryptpad/*, Utils*/) { + // Adding a new avatar from the profile: pin it and store it in the object + sframeChan.on('Q_ADMIN_MAILBOX', function (data, cb) { + Cryptpad.addAdminMailbox(data, cb); + }); + sframeChan.on('Q_ADMIN_RPC', function (data, cb) { + Cryptpad.adminRpc(data, cb); + }); + sframeChan.on('Q_UPDATE_LIMIT', function (data, cb) { + Cryptpad.updatePinLimit(function (e) { + cb({error: e}); + }); + }); + }; + var category; + if (window.location.hash) { + category = window.location.hash.slice(1); + window.location.hash = ''; + } + var addData = function (obj) { + if (category) { obj.category = category; } + }; + SFCommonO.start({ + noRealtime: true, + addRpc: addRpc, + addData: addData + }); + }); +}); diff --git a/www/support/inner.js b/www/support/inner.js index 366881f78..5b5b060e8 100644 --- a/www/support/inner.js +++ b/www/support/inner.js @@ -47,6 +47,7 @@ define([ var categories = { 'tickets': [ // Msg.support_cat_tickets 'cp-support-list', + 'cp-support-listnew', ], 'new': [ // Msg.support_cat_new 'cp-support-subscribe', @@ -151,6 +152,29 @@ define([ return $div; }; + create['listnew'] = function () { + var key = 'listnew'; + var $div = makeBlock(key); // Msg.support_listHint, .support_listTitle + let refresh = function () { + APP.supportModule.execCommand('GET_MY_TICKETS', {}, function (obj) { + console.error(obj); + if (obj && obj.error) { + console.error(obj.error); + return void UI.warn(Messages.error); + } + }); + + }; + var button = h('btn.btn-primary', 'refresh'); // XXX + Util.onClickEnter($(button), function () { + refresh(); + }); + $div.append([ + button + ]); + return $div; + }; + create['language'] = function () { if (!Array.isArray(AppConfig.supportLanguages)) { return $(h('div')); } var languages = AppConfig.supportLanguages; @@ -209,17 +233,19 @@ define([ var id = Util.uid(); $div.find('button').click(function () { - var metadataMgr = common.getMetadataMgr(); - var privateData = metadataMgr.getPrivateData(); - var user = metadataMgr.getUserData(); - var sent = APP.support.sendForm(id, form, { - channel: privateData.support, - curvePublic: user.curvePublic - }); - id = Util.uid(); - if (sent) { + var data = APP.support.getFormData(form); + APP.supportModule.execCommand('MAKE_TICKET', { + channel: Hash.createChannelId(), + title: data.title, + ticket: data + }, function (obj) { + if (obj && obj.error) { + console.error(obj.error); + return void UI.warn(Messages.error); + } + id = Util.uid(); $('.cp-sidebarlayout-category[data-category="tickets"]').click(); - } + }); }); $div.find('button').before(form); return $div; @@ -345,6 +371,7 @@ define([ APP.origin = privateData.origin; APP.readOnly = privateData.readOnly; APP.support = Support.create(common, false, APP.pinUsage, APP.teamsUsage); + APP.supportModule = common.makeUniversal('support'); // Content var $rightside = APP.$rightside; diff --git a/www/support/main.js b/www/support/main.js index b8cb8c942..e49bb32b7 100644 --- a/www/support/main.js +++ b/www/support/main.js @@ -34,7 +34,7 @@ define([ }; SFCommonO.start({ noRealtime: true, - addData: addData + addData: addData, }); }); }); diff --git a/www/support/ui.js b/www/support/ui.js index 897811d33..07eddaab4 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -22,6 +22,7 @@ define([ var user = metadataMgr.getUserData(); var teams = privateData.teams || {}; data = data || {}; + if (data.sender) { return data; } data.sender = { name: user.name, @@ -101,7 +102,7 @@ define([ }; - var sendForm = function (ctx, id, form, dest) { + var getFormData = function (ctx, form) { var $form = $(form); var $cat = $form.find('.cp-support-form-category'); var $title = $form.find('.cp-support-form-title'); @@ -110,14 +111,6 @@ define([ var $attachments = $form.find('.cp-support-attachments'); var category = $cat.val().trim(); -/* -Messages.support_formCategoryError = "Please select a ticket category from the dropdown menu"; // TODO - if (!category) { - console.log($cat); - return void UI.alert(Messages.support_formCategoryError); - } -*/ - var title = $title.val().trim(); if (!title) { return void UI.alert(Messages.support_formTitleError); @@ -140,13 +133,15 @@ Messages.support_formCategoryError = "Please select a ticket category from the d }); $attachments.html(''); - send(ctx, id, 'TICKET', { + return getDebuggingData(ctx, { category: category, title: title, attachments: attachments, message: content, - }, dest); - + }); + }; + var sendForm = function (ctx, id, form, dest) { + send(ctx, id, 'TICKET', getFormData(ctx, form), dest, isNew); return true; }; @@ -167,9 +162,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d return { tag: 'a', content: h('span', Messages['support_cat_'+key]), - action: function () { - onChange(key); - } + attributes: { 'data-value': key } }; }); var dropdownCfg = { @@ -181,6 +174,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d }; var $select = UIElements.createDropdown(dropdownCfg); $select.find('button').addClass('btn'); + $select.onChange.reg(onChange); return $select; }; @@ -221,7 +215,7 @@ Messages.support_formCategoryError = "Please select a ticket category from the d ctx.common.openUnsafeURL(href); }; - makeCategoryDropdown(ctx, catContainer, function (key) { + makeCategoryDropdown(ctx, catContainer, function (text, key) { $(category).val(key); if (!notice) { return; } //console.log(key); @@ -532,6 +526,8 @@ Messages.support_formCategoryError = "Please select a ticket category from the d adminKeys: Array.isArray(ApiConfig.adminKeys)? ApiConfig.adminKeys.slice(): [], }; + ctx.supportModule = common.makeUniversal('support'); + var fmConfig = { body: $('body'), noStore: true, // Don't store attachments into our drive @@ -543,8 +539,8 @@ Messages.support_formCategoryError = "Please select a ticket category from the d }; ctx.FM = common.createFileManager(fmConfig); - ui.sendForm = function (id, form, dest) { - return sendForm(ctx, id, form, dest); + ui.getFormData = function (form) { + return getFormData(ctx, form); }; ui.makeForm = function (cb, title, hideNotice) { return makeForm(ctx, cb, title, hideNotice);