cryptpad/www/install/onboardscreen.js

500 lines
17 KiB
JavaScript

// SPDX-FileCopyrightText: 2024 XWiki CryptPad Team <contact@cryptpad.org> 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',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
], function(
$,
Sidebar,
Messages,
AppConfig,
h,
UI,
Util,
UIElements,
PadTypes,
nThen
) {
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),
h('i.fa.fa-check.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 };
});