// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team and contributors // // SPDX-License-Identifier: AGPL-3.0-or-later define([ 'jquery', '/common/inner/sidebar-layout.js', '/customize/messages.js', '/customize/application_config.js', '/common/hyperscript.js', '/common/common-interface.js', '/common/common-util.js', '/common/common-ui-elements.js', '/common/pad-types.js', '/components/nthen/index.js', '/common/common-icons.js', 'css!/components/bootstrap/dist/css/bootstrap.min.css', ], function( $, Sidebar, Messages, AppConfig, h, UI, Util, UIElements, PadTypes, nThen, Icons ) { let pages = []; const gotoPage = function (Env, page) { if (typeof(page) !== "number" || !page) { page = 0; } if (page > (pages.length - 1)) { page = pages.length - 1; } var nextPageFunction = pages[page]; var nextPageForm = nextPageFunction(Env); let frame = h('div.cp-onboarding-box', nextPageForm); Env.overlay.empty().append(frame); }; const blocks = Sidebar.blocks('admin'); var flushCache = (Env, cb) => { const { sendAdminRpc } = Env; sendAdminRpc('FLUSH_CACHE', {}, function (e, response) { if (e || response.error) { console.error(e || response.error); } cb(); }); }; var selections = { title: '', description: '', logoURL: '', color: '', appsToDisable: [], mfa: false, closeRegistration: false }; const saveAndRedirect = (Env) => { const { sendAdminDecree, sendAdminRpc } = Env; let hasError = false; let error = () => { hasError = true; }; nThen(waitFor => { var name = selections.title; if (!name) { return; } sendAdminDecree('SET_INSTANCE_NAME', [name], waitFor((e, response) => { if (e || response.error) { console.error(e || response.error); return void error(); } })); }).nThen(waitFor => { var description = selections.description; if (!description) { return; } sendAdminDecree('SET_INSTANCE_DESCRIPTION', [ description ], waitFor((e, response) => { if (e || response.error) { console.error(e || response.error); return void error(); } })); }).nThen(waitFor => { var dataURL = selections.logoURL; if (!dataURL) { return; } sendAdminRpc('UPLOAD_LOGO', {dataURL}, waitFor(function (e, response) { if (e || response.error) { console.error(e || response.error); return void error(); } })); }).nThen(waitFor => { var color = selections.color; if (!color) { return; } sendAdminRpc('CHANGE_COLOR', {color}, waitFor(function (e, response) { if (e || response.error) { console.error(e || response.error); return void error(); } })); }).nThen(waitFor => { var apps = selections.appsToDisable; if (!apps.length) { return; } sendAdminDecree('DISABLE_APPS', apps, waitFor(function (e, response) { if (e || response.error) { UI.warn(Messages.error); return; } })); }).nThen(waitFor => { if (!selections.mfa) { return; } sendAdminDecree('ENFORCE_MFA', [selections.mfa], waitFor((e, response) => { if (e || response.error) { UI.warn(Messages.error); return; } })); }).nThen(waitFor => { if (!selections.closeRegistration) { return; } sendAdminDecree('RESTRICT_REGISTRATION', [ selections.closeRegistration ], waitFor(function (e, response) { if (e || response.error) { UI.warn(Messages.error); return; } })); }).nThen(() => { flushCache(Env, function () { if (hasError) { UI.alert(Messages.onboarding_save_error, function () { document.location.href = '/drive/'; }); return; } document.location.href = '/drive/'; }); }); }; const titleConfig = function (Env) { let titleInput = blocks.input({ type: 'text', value: selections.title, placeholder: Messages.admin_onboardingNamePlaceholder, 'aria-labelledby': 'cp-admin-name' }); let description = blocks.textarea({ placeholder: Messages.admin_onboardingDescPlaceholder, 'aria-labelledby': 'cp-admin-description' }, selections.description); $(description).addClass('cp-onboardscreen-desc'); let dataURL = selections.logoURL; var getLogoBlock = function() { let inputLogo = blocks.input({ type: 'file', accept: 'image/*', 'aria-labelledby': 'cp-admin-logo' }); var currentContainer = blocks.block([], 'cp-admin-customize-logo'); var upload = blocks.button('secondary', '', Messages.onboarding_upload); let formLogo = blocks.form([ currentContainer, blocks.block(inputLogo, 'cp-onboarding-logo-input'), blocks.nav([upload]) ]); let $button = $(upload); let state = false; let redraw = () => { var current = h('img', {src: dataURL || '/api/logo?'+(+new Date())}); $(currentContainer).empty().append(current); $button.removeAttr('disabled'); state = !!dataURL; if (dataURL) { $button.text(Messages.admin_logoRemoveButton); $button.removeClass('btn-secondary').addClass('btn-danger-alt'); } else { $button.text(Messages.onboarding_upload); $button.removeClass('btn-danger-alt').addClass('btn-secondary'); } }; redraw(); let $input = $(inputLogo); $input.on('change', function () { let files = inputLogo.files; if (files.length !== 1) { UI.warn(Messages.error); return; } let reader = new FileReader(); reader.onloadend = function () { dataURL = this.result; redraw(dataURL); }; reader.readAsDataURL(files[0]); }); Util.onClickEnter($button, function () { $button.attr('disabled', 'disabled'); if (!state) { return void $input.click(); } dataURL = ''; redraw(); }); return formLogo; }; var getColorBlock = function () { // Number of accent color presets var colors = UIElements.makePalette(4, (color, $color) => { let rgb = $color.css('background-color'); let hex = Util.rgbToHex(rgb); if (hex) { selections.color = color ? hex : ''; selections.colorId = color; } }); if (selections.colorId) { colors.setValue(selections.colorId); } var content = h('div.cp-onboardscreen-colorpick', [ h('label', {for:'cp-install-color'}, Messages.kanban_color), colors ]); return content; }; var button = blocks.activeButton('primary', '', Messages.continue, function () { selections.title = $(titleInput).val() || ''; selections.description = $(description).val() || ''; if (dataURL) { selections.logoURL = dataURL; } gotoPage(Env, 1); }); var titleBlock = h('div.cp-onboardscreen-name', titleInput); var descriptionBlock = h('div', description); var logoBlock = h('div.cp-onboardscreen-logo', getLogoBlock()); var colorBlock = h('div.cp-onboardscreen-color', getColorBlock()); var screenTitle = h('div.cp-onboardscreen-screentitle'); $(screenTitle).append(('div.cp-onboardscreen-maintitle', [ h('h1.cp-onboardscreen-title', Messages.admin_onboardingNameTitle), h('span', Messages.admin_onboardingNameHint) ])); var nav = blocks.nav([h('span'), button]); $(button).addClass('cp-onboardscreen-save'); $(nav).addClass('cp-onboardscreen-nav'); var textForm = h('div.cp-instance-text-form', [ titleBlock, descriptionBlock, colorBlock, ]); var instanceForm = h('div.cp-instance-form', [logoBlock, textForm]); var form = blocks.form([ screenTitle, instanceForm ], nav); $(form).addClass('cp-onboardscreen-form'); return form; }; const createAppsGrid = appsToDisable => { const grid = blocks.block([], 'cp-admin-customize-apps-grid'); const $grid = $(grid); const allApps = PadTypes.appsToSelect; let select = function (app, $app) { if (appsToDisable.indexOf(app) === -1) { appsToDisable.push(app); $app.toggleClass('cp-inactive-app', true); $app.toggleClass('cp-active-app', false); } else { appsToDisable.splice(appsToDisable.indexOf(app), 1); $app.toggleClass('cp-inactive-app', false); $app.toggleClass('cp-active-app', true); } }; allApps.forEach(app => { let name = Messages.type[app] || app; let icon = UI.getNewIcon(app); let appBlock = h('div.cp-appblock', {tabindex:0, role:"button"}, [ icon, h('span.cp-app-name', name), Icons.get('check', {'class': 'cp-on-enabled'}) ]); let $app = $(appBlock).appendTo($grid); if (appsToDisable.includes(app)) { $app.addClass('cp-inactive-app'); } else { $app.addClass('cp-active-app'); } $app.on('click', () => select(app, $app)); }); return grid; }; const appConfig = function (Env) { const appsToDisable = selections.appsToDisable; const grid = createAppsGrid(appsToDisable); var save = blocks.activeButton('primary', '', Messages.continue, function () { gotoPage(Env, 2); }); var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () { gotoPage(Env, 0); }); var screenTitle = h('div.cp-onboardscreen-screentitle'); $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_appsTitle), h('span', Messages.admin_appsHint))); $(save).addClass('cp-onboardscreen-save'); $(prev).addClass('cp-onboardscreen-prev'); var nav = blocks.nav([prev, save]); $(nav).addClass('cp-onboardscreen-nav'); let form = blocks.form([ screenTitle, grid ], nav); $(form).addClass('cp-onboardscreen-form'); return form; }; const mfaRegistrationScreen = function (Env) { var restrict = blocks.activeCheckbox({ key: 'registration', getState: function () { return selections.closeRegistration; }, label: 'registration', query: function (val, setState) { selections.closeRegistration = val; setState(val); }, }); var forceMFA = blocks.activeCheckbox({ key: 'forcemfa', getState: function () { return selections.mfa; }, label: 'forcemfa', query: function (val, setState) { selections.mfa = val; setState(val); }, }); let mfaOption = h('div.cp-optionblock', [ forceMFA, h('span.cp-option-hint', Messages.admin_forcemfaHint) ]); let registrationOption = h('div.cp-optionblock', [ restrict, h('span.cp-option-hint', Messages.admin_registrationHint) ]); const grid = blocks.block([ mfaOption, registrationOption ], 'cp-admin-customize-options-grid'); var save = blocks.activeButton('primary', '', Messages.settings_save, function () { saveAndRedirect(Env); }); var prev = blocks.activeButton('secondary', '', Messages.form_backButton, function () { gotoPage(Env, 1); }); var screenTitle = h('div.cp-onboardscreen-screentitle'); $(screenTitle).append(h('div.cp-onboardscreen-maintitle', h('h1.cp-onboardscreen-title', Messages.admin_onboardingOptionsTitle), UI.setHTML(h('span'), Messages.admin_onboardingOptionsHint))); $(save).addClass('cp-onboardscreen-save'); $(prev).addClass('cp-onboardscreen-prev'); var nav = blocks.nav([prev, save]); $(nav).addClass('cp-onboardscreen-nav'); var form = blocks.form([ screenTitle, grid], nav ); $(form).addClass('cp-onboardscreen-form'); return form; }; pages = [ titleConfig, appConfig, mfaRegistrationScreen ]; const create = (sendAdminDecree, sendAdminRpc) => { let Env = { sendAdminDecree, sendAdminRpc }; Env.overlay = $(h('div#cp-onboarding')); gotoPage(Env, 0); $('body').append(Env.overlay); }; // XXX test functions to remove window.CP_onboarding_test = () => { create(() => {}, () => {}); }; window.CP_onboarding_test_full = () => { require([ '/common/cryptpad-common.js', '/common/outer/local-store.js', '/common/outer/login-block.js', '/common/rpc.js', '/common/common-constants.js', '/common/cryptget.js', ], function (CryptPad, LocalStore, Block, Rpc, Constants, CryptGet) { var blockHash = LocalStore.getBlockHash(); if (!blockHash) { return void console.error('NOT LOGGED IN'); } var parsed = Block.parseBlockHash(blockHash); var sessionToken = LocalStore.getSessionToken() || undefined; let userHash; nThen(w => { // Get user keys Util.getBlock(parsed.href, { bearer: sessionToken }, w((err, response) => { if (err) { w.abort(); return void console.error('Please login in and try again'); } response.arrayBuffer().then(w(arraybuffer => { arraybuffer = new Uint8Array(arraybuffer); var block_info = Block.decrypt(arraybuffer, parsed.keys); userHash = block_info[Constants.userHashKey]; })); })); }).nThen(() => { // Make RPC if (!userHash) { return void console.error('AUTH FAILED'); } CryptPad.makeNetwork((err, network) => { if (err) { return void console.error(err); } CryptGet.get(userHash, (err, val) => { let p = Util.tryParse(val); Rpc.create(network, p.edPrivate, p.edPublic, function (e, rpc) { let sendAdminDecree = function (command, data, callback) { var params = ['ADMIN_DECREE', [command, data]]; rpc.send('ADMIN', params, callback); }; let sendAdminRpc = function (command, data, callback) { var params = [command, data]; rpc.send('ADMIN', params, callback); }; create(sendAdminDecree, sendAdminRpc); }); }, {network: network}); }); }); }); }; return { create, createAppsGrid }; });