diff --git a/customize.dist/messages.js b/customize.dist/messages.js index f3d7aac68..96745fb55 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -134,8 +134,9 @@ define(req, function(AppConfig, Default, Language) { } }; + Messages.admin_documentsDeletionHint = "Archive a list of documents using their id (one document id per line)"; + Messages.admin_documentsDeletionTitle = "Archive multiple documents"; Messages.help_close_button = 'Close help notification'; // XXX - // XXX Messages.badges_admin = "Instance administrator"; Messages.badges_moderator = "Instance moderator"; Messages.badges_premium = "Premium user"; diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index 6bc35f5be..5caf7f117 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -172,7 +172,8 @@ var flushCache = function (Env, Server, cb) { }; // CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_DOCUMENT', documentID], console.log) -var archiveDocument = function (Env, Server, cb, data) { +var archiveDocument = function (Env, Server, _cb, data) { + const cb = Util.mkAsync(_cb); if (!Array.isArray(data)) { return void cb("EINVAL"); } var args = data[1]; @@ -221,6 +222,28 @@ var archiveDocument = function (Env, Server, cb, data) { // Env.blobStore.archive.proof(userSafeKey, blobId, cb) }; +// CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_DOCUMENTS', documents], console.log) +var archiveDocuments = function (Env, Server, cb, data) { + if (!Array.isArray(data)) { return void cb("EINVAL"); } + let args = data[1]; + const { list, reason } = args; + if (!Array.isArray(list)) { return void cb('EINVAL'); } + let n = nThen; + let failed = []; + list.forEach(id => { + n = n((w) => { + archiveDocument(Env, Server, w(err => { + console.log(err); + if (err && err !== 'ENOENT') { failed.push(id); } + }), [0, { id, reason }]); + }).nThen; + }); + n(() => { + cb(void 0, { state: true, failed }); + }); +}; + + var removeDocument = function (Env, Server, cb, data) { if (!Array.isArray(data)) { return void cb("EINVAL"); } var args = data[1]; @@ -1088,6 +1111,7 @@ var commands = { RESTORE_ARCHIVED_BLOCK: restoreArchivedBlock, ARCHIVE_DOCUMENT: archiveDocument, + ARCHIVE_DOCUMENTS: archiveDocuments, RESTORE_ARCHIVED_DOCUMENT: restoreArchivedDocument, ARCHIVE_ACCOUNT: archiveAccount, diff --git a/www/admin/inner.js b/www/admin/inner.js index 772f67c13..4978e4218 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -127,9 +127,9 @@ define([ content : [ 'account-metadata', 'document-metadata', + 'documents-deletion', 'block-metadata', 'totp-recovery', - ] }, 'support' : { // Msg.admin_cat_support @@ -2390,6 +2390,48 @@ define([ return tableObj.table; }; + // Msg.admin_documentsDeletionHint.admin_documentsDeletionTitle + sidebar.addItem('documents-deletion', cb => { + const textarea = blocks.textarea({ + 'aria-labelledby': 'cp-admin-documents-deletion' + }); + const $textarea = $(textarea); + const archiveButton = blocks.activeButton('danger', '', + Messages.admin_archiveButton, () => { + const $btn = $(archiveButton); + justifyArchivalDialog('', result => { + const val = $textarea.val().trim(); + const all = val.split('\n').filter(str => { + let type = DOCUMENT_TYPES[str.length]; + return ['channel', 'file'].includes(type); + }); + console.error(val); + console.error(result); + disable($btn); + sframeCommand('ARCHIVE_DOCUMENTS', { + list: all, + reason: result, + }, (err, arr) => { + const res = Array.isArray(arr) && arr[0]; + enable($btn); + if (err) { + console.error(err); + return void UI.warn(Messages.error); + } + if (Array.isArray(res?.failed) + && res?.failed.length) { + console.error("Failed deletion:"); + console.error(res?.failed); + } + $textarea.val(''); + UI.log(Messages.archivedFromServer); + }); + }); + }, true); + const nav = blocks.nav([archiveButton]); + const div = blocks.form([textarea], nav); + cb(div); + }); // Msg.admin_documentMetadataHint.admin_documentMetadataTitle sidebar.addItem('document-metadata', function(cb){ diff --git a/www/debug/inner.js b/www/debug/inner.js index 80c7c4a4a..6547f5515 100644 --- a/www/debug/inner.js +++ b/www/debug/inner.js @@ -14,6 +14,7 @@ define([ '/common/common-hash.js', '/common/common-constants.js', '/common/hyperscript.js', + '/common/clipboard.js', '/api/config', '/common/common-realtime.js', '/customize/messages.js', @@ -36,6 +37,7 @@ define([ Hash, Constants, h, + Clipboard, ApiConfig, CommonRealtime, Messages, @@ -707,15 +709,39 @@ define([ toolbar.$drawer.append($histEntry); var $content = common.createButton(null, true, { - icon: 'fa-question', + icon: 'fa-clock-o', title: 'Get debugging graph', // TODO name: 'graph', + text: 'Replay', id: 'cp-app-debug-get-content' }); $content.click(getContent); var $contentEntry = UIElements.getEntryFromButton($content); - console.error($contentEntry); toolbar.$drawer.append($contentEntry); + + var priv = metadataMgr.getPrivateData(); + if (priv.debugDrive) { + var $drive = common.createButton(null, true, { + icon: 'fa-hdd-o', + title: 'Get Shared Folder content', // TODO + text: 'SF channel list', + id: 'cp-app-debug-get-channels' + }); + $drive.click(() => { + let p = JSON.parse(info.realtime.getUserDoc()); + const fd = p?.drive?.filesData || p?.filesData; + let all = Object.keys(fd).map(id => { + return fd[id]?.channel; + }); + console.error(all); + Clipboard.copy(all.join('\n'), (err) => { + if (err) { return UI.warn(Messages.error); } + UI.log(Messages.genericCopySuccess); + }); + }); + var $driveEntry = UIElements.getEntryFromButton($drive); + toolbar.$drawer.append($driveEntry); + } }; config.onReady = function (info) {