From 5bd09fe5d287e5f10069530b9ca12dcc2d8b88c1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Oct 2020 15:02:05 +0200 Subject: [PATCH] Export team drive content --- customize.dist/src/less2/include/export.less | 89 +++++++++++ www/common/make-backup.js | 148 ++++++++++++++++++- www/settings/app-settings.less | 89 +---------- www/settings/inner.js | 141 +----------------- www/teams/app-team.less | 3 + www/teams/inner.js | 50 +++++++ www/teams/main.js | 31 +++- 7 files changed, 321 insertions(+), 230 deletions(-) create mode 100644 customize.dist/src/less2/include/export.less diff --git a/customize.dist/src/less2/include/export.less b/customize.dist/src/less2/include/export.less new file mode 100644 index 000000000..d1ee37d4b --- /dev/null +++ b/customize.dist/src/less2/include/export.less @@ -0,0 +1,89 @@ +.export_main() { + #cp-export-container { + font-size: 16px; + display: flex; + flex: 1; + min-height: 0; + align-items: center; + justify-content: center; + .cp-export-block { + width: 800px; + max-width: 90vw; + .cp-export-progress-bar-container { + height: 24px; + background: white; + border: 1px solid #DDD; + width: 80%; + margin: auto; + position: relative; + .cp-export-progress-bar { + height: 100%; + background: #5cb85c; // Same color as loading screen bar + width: 0%; + display: inline-block; + } + .cp-export-progress-text { + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; + text-align: center; + font-weight: bold; + } + } + & > p { + color: #777; + } + .cp-export-progress { + margin-bottom: 1rem; + p { + margin-bottom: 0; + display: flex; + align-items: center; + padding: 5px 0; + .fa { + width: 25px; + } + } + } + .cp-export-actions { + display: flex; + flex-flow: row-reverse; + .btn-default, .btn-primary { + margin-left: 20px; + } + } + .cp-export-errors { + display: none; + overflow-x: auto; + max-height: 300px; + background: #ededed; + border: 1px solid #777; + padding: 5px 20px; + margin-top: 1rem; + & > p { + margin: 0; + } + .cp-export-errors-list { + & > div { + padding: 5px 10px; + margin: 5px 0; + background: #dedede; + .title { + font-weight: bold; + } + .link { + padding: 0 20px; + font-size: 14px; + } + .reason { + padding: 0 20px; + color: #777; + } + } + } + } + } + } +} diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 7db9b998f..b718ae9c1 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -1,13 +1,17 @@ define([ + 'jquery', '/common/cryptget.js', '/file/file-crypto.js', '/common/common-hash.js', '/common/common-util.js', + '/common/common-interface.js', + '/common/hyperscript.js', + '/common/common-feedback.js', '/customize/messages.js', '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', '/bower_components/jszip/dist/jszip.min.js', -], function (Crypt, FileCrypto, Hash, Util, Messages, nThen, Saferphore, JsZip) { +], function ($, Crypt, FileCrypto, Hash, Util, UI, h, Feedback, Messages, nThen, Saferphore, JsZip) { var saveAs = window.saveAs; var sanitize = function (str) { @@ -257,7 +261,7 @@ define([ } if (ctx.data.sharedFolders[el]) { // if shared folder var sfData = ctx.sf[el].metadata; - var sfName = getUnique(sanitize(sfData.title || 'Folder'), '', existingNames); + var sfName = getUnique(sanitize((sfData && sfData.title) || 'Folder'), '', existingNames); existingNames.push(sfName.toLowerCase()); return void makeFolder(ctx, ctx.sf[el].root, zip.folder(sfName), ctx.sf[el].filesData); } @@ -334,9 +338,149 @@ define([ }); }; + var createExportUI = function (origin) { + var progress = h('div.cp-export-progress'); + var actions = h('div.cp-export-actions'); + var errors = h('div.cp-export-errors', [ + h('p', Messages.settings_exportErrorDescription) + ]); + var exportUI = h('div#cp-export-container', [ + h('div.cp-export-block', [ + h('h3', Messages.settings_exportTitle), + h('p', [ + Messages.settings_exportDescription, + h('br'), + Messages.settings_exportFailed, + h('br'), + h('strong', Messages.settings_exportWarning), + ]), + progress, + actions, + errors + ]) + ]); + $('body').append(exportUI); + $('#cp-sidebarlayout-container').hide(); + + var close = function() { + $(exportUI).remove(); + $('#cp-sidebarlayout-container').show(); + }; + + var _onCancel = []; + var onCancel = function(h) { + if (typeof(h) !== "function") { return; } + _onCancel.push(h); + }; + var cancel = h('button.btn.btn-default', Messages.cancel); + $(cancel).click(function() { + UI.confirm(Messages.settings_exportCancel, function(yes) { + if (!yes) { return; } + Feedback.send('FULL_DRIVE_EXPORT_CANCEL'); + _onCancel.forEach(function(h) { h(); }); + }); + }).appendTo(actions); + + var error = h('button.btn.btn-danger', Messages.settings_exportError); + var translateErrors = function(err) { + if (err === 'EEMPTY') { + return Messages.settings_exportErrorEmpty; + } + if (['E404', 'EEXPIRED', 'EDELETED'].indexOf(err) !== -1) { + return Messages.settings_exportErrorMissing; + } + return Messages._getKey('settings_exportErrorOther', [err]); + }; + var addErrors = function(errs) { + if (!errs.length) { return; } + var onClick = function() { + console.error('clicked?'); + $(errors).toggle(); + }; + $(error).click(onClick).appendTo(actions); + var list = h('div.cp-export-errors-list'); + $(list).appendTo(errors); + errs.forEach(function(err) { + if (!err.data) { return; } + var href = (err.data.href && err.data.href.indexOf('#') !== -1) ? err.data.href : err.data.roHref; + $(h('div', [ + h('div.title', err.data.filename || err.data.title), + h('div.link', [ + h('a', { + href: href, + target: '_blank' + }, origin + href) + ]), + h('div.reason', translateErrors(err.error)) + ])).appendTo(list); + }); + }; + + var download = h('button.btn.btn-primary', Messages.download_mt_button); + var completed = false; + var complete = function(h, err) { + if (completed) { return; } + completed = true; + $(progress).find('.fa-square-o').removeClass('fa-square-o') + .addClass('fa-check-square-o'); + $(cancel).text(Messages.filePicker_close).off('click').click(function() { + _onCancel.forEach(function(h) { h(); }); + }); + $(download).click(h).appendTo(actions); + addErrors(err); + }; + + var done = {}; + var update = function(step, state) { + console.log(step, state); + console.log(done[step]); + if (done[step] && done[step] === -1) { return; } + + + // New step + if (!done[step]) { + $(progress).find('.fa-square-o').removeClass('fa-square-o') + .addClass('fa-check-square-o'); + $(progress).append(h('p', [ + h('span.fa.fa-square-o'), + h('span.text', Messages['settings_export_' + step] || step) + ])); + done[step] = state; // -1 if no bar, object otherwise + if (state !== -1) { + var bar = h('div.cp-export-progress-bar'); + $(progress).append(h('div.cp-export-progress-bar-container', [ + bar + ])); + done[step] = { bar: bar }; + } + return; + } + + // Updating existing step + if (typeof state !== "object") { return; } + var b = done[step].bar; + var w = (state.current / state.max) * 100; + $(b).css('width', w + '%'); + if (!done[step].text) { + done[step].text = h('div.cp-export-progress-text'); + $(done[step].text).appendTo(b); + } + $(done[step].text).text(state.current + ' / ' + state.max); + if (state.current === state.max) { done[step] = -1; } + }; + + return { + close: close, + update: update, + complete: complete, + onCancel: onCancel + }; + }; + return { create: create, + createExportUI: createExportUI, downloadFile: _downloadFile, downloadPad: _downloadPad, downloadFolder: _downloadFolder, diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less index 3ea38ae97..1f0da1263 100644 --- a/www/settings/app-settings.less +++ b/www/settings/app-settings.less @@ -2,6 +2,7 @@ @import (reference) "../../customize/src/less2/include/limit-bar.less"; @import (reference) "../../customize/src/less2/include/creation.less"; @import (reference) '../../customize/src/less2/include/framework.less'; +@import (reference) '../../customize/src/less2/include/export.less'; &.cp-app-settings { .framework_min_main( @@ -26,93 +27,7 @@ padding: 10px; } - #cp-export-container { - font-size: 16px; - display: flex; - flex: 1; - min-height: 0; - align-items: center; - justify-content: center; - .cp-export-block { - width: 800px; - max-width: 90vw; - .cp-export-progress-bar-container { - height: 24px; - background: white; - border: 1px solid #DDD; - width: 80%; - margin: auto; - position: relative; - .cp-export-progress-bar { - height: 100%; - background: #5cb85c; // Same color as loading screen bar - width: 0%; - display: inline-block; - } - .cp-export-progress-text { - position: absolute; - top: 0; - right: 0; - left: 0; - bottom: 0; - text-align: center; - font-weight: bold; - } - } - & > p { - color: #777; - } - .cp-export-progress { - margin-bottom: 1rem; - p { - margin-bottom: 0; - display: flex; - align-items: center; - padding: 5px 0; - .fa { - width: 25px; - } - } - } - .cp-export-actions { - display: flex; - flex-flow: row-reverse; - .btn-default, .btn-primary { - margin-left: 20px; - } - } - .cp-export-errors { - display: none; - overflow-x: auto; - max-height: 300px; - background: #ededed; - border: 1px solid #777; - padding: 5px 20px; - margin-top: 1rem; - & > p { - margin: 0; - } - .cp-export-errors-list { - & > div { - padding: 5px 10px; - margin: 5px 0; - background: #dedede; - .title { - font-weight: bold; - } - .link { - padding: 0 20px; - font-size: 14px; - } - .reason { - padding: 0 20px; - color: #777; - } - } - } - } - } - } + .export_main(); #cp-sidebarlayout-container { #cp-sidebarlayout-rightside { diff --git a/www/settings/inner.js b/www/settings/inner.js index 6943f720b..8882f5861 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -717,145 +717,6 @@ define([ }; - var createExportUI = function() { - var progress = h('div.cp-export-progress'); - var actions = h('div.cp-export-actions'); - var errors = h('div.cp-export-errors', [ - h('p', Messages.settings_exportErrorDescription) - ]); - var exportUI = h('div#cp-export-container', [ - h('div.cp-export-block', [ - h('h3', Messages.settings_exportTitle), - h('p', [ - Messages.settings_exportDescription, - h('br'), - Messages.settings_exportFailed, - h('br'), - h('strong', Messages.settings_exportWarning), - ]), - progress, - actions, - errors - ]) - ]); - $('body').append(exportUI); - $('#cp-sidebarlayout-container').hide(); - - var close = function() { - $(exportUI).remove(); - $('#cp-sidebarlayout-container').show(); - }; - - var _onCancel = []; - var onCancel = function(h) { - if (typeof(h) !== "function") { return; } - _onCancel.push(h); - }; - var cancel = h('button.btn.btn-default', Messages.cancel); - $(cancel).click(function() { - UI.confirm(Messages.settings_exportCancel, function(yes) { - if (!yes) { return; } - Feedback.send('FULL_DRIVE_EXPORT_CANCEL'); - _onCancel.forEach(function(h) { h(); }); - }); - }).appendTo(actions); - - var error = h('button.btn.btn-danger', Messages.settings_exportError); - var translateErrors = function(err) { - if (err === 'EEMPTY') { - return Messages.settings_exportErrorEmpty; - } - if (['E404', 'EEXPIRED', 'EDELETED'].indexOf(err) !== -1) { - return Messages.settings_exportErrorMissing; - } - return Messages._getKey('settings_exportErrorOther', [err]); - }; - var addErrors = function(errs) { - if (!errs.length) { return; } - var onClick = function() { - console.error('clicked?'); - $(errors).toggle(); - }; - $(error).click(onClick).appendTo(actions); - var list = h('div.cp-export-errors-list'); - $(list).appendTo(errors); - errs.forEach(function(err) { - if (!err.data) { return; } - var href = (err.data.href && err.data.href.indexOf('#') !== -1) ? err.data.href : err.data.roHref; - $(h('div', [ - h('div.title', err.data.filename || err.data.title), - h('div.link', [ - h('a', { - href: href, - target: '_blank' - }, privateData.origin + href) - ]), - h('div.reason', translateErrors(err.error)) - ])).appendTo(list); - }); - }; - - var download = h('button.btn.btn-primary', Messages.download_mt_button); - var completed = false; - var complete = function(h, err) { - if (completed) { return; } - completed = true; - $(progress).find('.fa-square-o').removeClass('fa-square-o') - .addClass('fa-check-square-o'); - $(cancel).text(Messages.filePicker_close).off('click').click(function() { - _onCancel.forEach(function(h) { h(); }); - }); - $(download).click(h).appendTo(actions); - addErrors(err); - }; - - var done = {}; - var update = function(step, state) { - console.log(step, state); - console.log(done[step]); - if (done[step] && done[step] === -1) { return; } - - - // New step - if (!done[step]) { - $(progress).find('.fa-square-o').removeClass('fa-square-o') - .addClass('fa-check-square-o'); - $(progress).append(h('p', [ - h('span.fa.fa-square-o'), - h('span.text', Messages['settings_export_' + step] || step) - ])); - done[step] = state; // -1 if no bar, object otherwise - if (state !== -1) { - var bar = h('div.cp-export-progress-bar'); - $(progress).append(h('div.cp-export-progress-bar-container', [ - bar - ])); - done[step] = { bar: bar }; - } - return; - } - - // Updating existing step - if (typeof state !== "object") { return; } - var b = done[step].bar; - var w = (state.current / state.max) * 100; - $(b).css('width', w + '%'); - if (!done[step].text) { - done[step].text = h('div.cp-export-progress-text'); - $(done[step].text).appendTo(b); - } - $(done[step].text).text(state.current + ' / ' + state.max); - if (state.current === state.max) { done[step] = -1; } - }; - - return { - close: close, - update: update, - complete: complete, - onCancel: onCancel - }; - }; - create['drive-backup'] = function() { var $div = $('
', { 'class': 'cp-settings-drive-backup cp-sidebarlayout-element' }); @@ -907,7 +768,7 @@ define([ var exportDrive = function() { Feedback.send('FULL_DRIVE_EXPORT_START'); var todo = function(data, filename) { - var ui = createExportUI(); + var ui = Backup.createExportUI(privateData.origin); var bu = Backup.create(data, common.getPad, privateData.fileHost, function(blob, errors) { saveAs(blob, filename); diff --git a/www/teams/app-team.less b/www/teams/app-team.less index abd46d121..cbbfcaec2 100644 --- a/www/teams/app-team.less +++ b/www/teams/app-team.less @@ -4,6 +4,7 @@ @import (reference) '../../customize/src/less2/include/sidebar-layout.less'; @import (reference) "../../customize/src/less2/include/tools.less"; @import (reference) "../../customize/src/less2/include/colortheme.less"; +@import (reference) '../../customize/src/less2/include/export.less'; &.cp-app-team { @@ -19,6 +20,8 @@ @roster-bg-color: #efefef; + .export_main(); + #cp-sidebarlayout-container { @media screen and (max-width: 900px) { .cp-app-drive-toolbar-leftside { diff --git a/www/teams/inner.js b/www/teams/inner.js index f2e3bf0e5..2607c0fac 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -17,8 +17,10 @@ define([ '/customize/application_config.js', '/common/messenger-ui.js', '/common/inner/invitation.js', + '/common/make-backup.js', '/customize/messages.js', + '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/teams/app-team.less', @@ -41,10 +43,12 @@ define([ AppConfig, MessengerUI, InviteInner, + Backup, Messages) { var APP = {}; var driveAPP = {}; + var saveAs = window.saveAs; //var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName; var copyObjectValue = function (objRef, objToCopy) { @@ -169,6 +173,7 @@ define([ 'cp-team-edpublic', 'cp-team-name', 'cp-team-avatar', + 'cp-team-export', 'cp-team-delete', ], }; @@ -1026,6 +1031,51 @@ define([ }); }, true); + Messages.team_exportTitle = "Download team drive"; + Messages.team_exportHint = "Download all the pads frm this team's drive in a readable format (when available)."; + Messages.team_exportButton = "Download"; + makeBlock('export', function (common, cb) { + // Backup all the pads + var sframeChan = common.getSframeChannel(); + var privateData = common.getMetadataMgr().getPrivateData(); + var team = privateData.teams[APP.team] || {}; + var teamName = team.name || Messages.anonymous; + + var exportDrive = function() { + Feedback.send('FULL_TEAMDRIVE_EXPORT_START'); + var todo = function(data, filename) { + var ui = Backup.createExportUI(privateData.origin); + + var bu = Backup.create(data, common.getPad, privateData.fileHost, function(blob, errors) { + saveAs(blob, filename); + sframeChan.event('EV_CRYPTGET_DISCONNECT'); + ui.complete(function() { + Feedback.send('FULL_TEAMDRIVE_EXPORT_COMPLETE'); + saveAs(blob, filename); + }, errors); + }, ui.update); + ui.onCancel(function() { + ui.close(); + bu.stop(); + }); + }; + sframeChan.query("Q_SETTINGS_DRIVE_GET", "full", function(err, data) { + if (err) { return void console.error(err); } + if (data.error) { return void console.error(data.error); } + var filename = teamName + '-' + new Date().toDateString() + '.zip'; + todo(data, filename); + }); + }; + var button = h('button.btn.btn-primary', Messages.team_exportButton); + UI.confirmButton(button, { + classes: 'btn-primary', + multiple: true + }, function () { + exportDrive(); + }); + cb(button); + }, true); + makeBlock('delete', function (common, cb) { var deleteTeam = h('button.btn.btn-danger', Messages.team_deleteButton); var $ok = $('', {'class': 'fa fa-check', title: Messages.saved}).hide(); diff --git a/www/teams/main.js b/www/teams/main.js index 65292b1f6..cab84d160 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -46,7 +46,7 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { var teamId; - var addRpc = function (sframeChan, Cryptpad) { + var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('Q_SET_TEAM', function (data, cb) { teamId = data; cb(); @@ -91,6 +91,35 @@ define([ sframeChan.event('EV_NETWORK_DISCONNECT'); } }); + + sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) { + Cryptpad.getUserObject(teamId, function (obj) { + if (obj.error) { return void cb(obj); } + if (d === "full") { + // We want shared folders too + var result = { + uo: obj, + sf: {} + }; + if (!obj.drive || !obj.drive.sharedFolders) { return void cb(result); } + Utils.nThen(function (waitFor) { + Object.keys(obj.drive.sharedFolders).forEach(function (id) { + Cryptpad.getSharedFolder({ + id: id, + teamId: teamId + }, waitFor(function (obj) { + result.sf[id] = obj; + })); + }); + }).nThen(function () { + cb(result); + }); + return; + } + // We want only the user object + cb(obj); + }); + }); }; var getSecrets = function (Cryptpad, Utils, cb) { var Hash = Utils.Hash;