Merge branch 'appconfig_ui' into 2024.6-test

This commit is contained in:
yflory 2024-06-21 18:47:44 +02:00
commit 67549561b8
26 changed files with 1069 additions and 113 deletions

View file

@ -9,12 +9,14 @@
* If you want to check all the configurable values, you can open the internal configuration file
but you should not change it directly (/common/application_config_internal.js)
*/
define(['/common/application_config_internal.js'], function (AppConfig) {
// Example: If you want to remove the survey link in the menu:
// AppConfig.surveyURL = "";
// To inform users of the support ticket panel which languages your admins speak:
//AppConfig.supportLanguages = [ 'en', 'fr' ];
return AppConfig;
});

View file

@ -132,6 +132,28 @@ define(req, function(AppConfig, Default, Language) {
}
};
Messages.install_token = "Install token";
Messages.install_header = "Installation";
Messages.install_instance = "Create the first admin account, then proceed to customize this instance";
Messages.install_launch = "Instance setup";
Messages.install_notes = `<ul class="cp-notes-list">
<li>Create your first administrator account on this page. Administrators manage instance settings including storage quotas, and have access to moderation tools.</li>
<li>Your password is the secret key that encrypts all of your documents and administrator privileges on this instance. <span class="red">If you lose it there is no way we can recover your data.</span></li>
<li>If you are using a shared computer, <span class="red">remember to log out</span> when you are done. Only closing the browser window leaves your account exposed. </li></ul>`;
Messages.admin_appSelection = 'App configuration';
Messages.admin_appsTitle = "Instance applications";
Messages.admin_appsHint = "Choose which apps to enable on this instance.";
Messages.admin_cat_apps = "Applications";
Messages.onboarding_save_error = "Some options could not be saved properly. Please visit the administration panel to check the values.";
Messages.onboarding_upload = "Select logo";
Messages.admin_onboardingNameTitle = 'Welcome to your CryptPad instance';
Messages.admin_onboardingNameHint = 'Please choose a title, description, accent color and logo (all are optional)';
Messages.admin_onboardingOptionsTitle = "Instance options";
Messages.admin_onboardingOptionsHint = "Please select the apropriate option for your instance. These settings can be changed later in the admin panel.";
Messages.admin_onboardingNamePlaceholder = 'Instance title';
Messages.admin_onboardingDescPlaceholder = 'Instance description text';
return Messages;

View file

@ -18,8 +18,6 @@ define([
return;
}
Msg.install_token = "Install token";
document.title = Msg.install_header;
var frame = function (content) {
@ -27,8 +25,7 @@ Msg.install_token = "Install token";
h('div#cp-main', [
//Pages.infopageTopbar(),
h('div.container.cp-container', [
//h('div.row.cp-page-title', h('h1', Msg.install_header)),
h('div.row.cp-page-title', h('h1', Msg.register_header)),
h('div.row.cp-page-title', h('h1', Msg.install_header)),
].concat(content)),
Pages.infopageFooter(),
]),
@ -39,17 +36,12 @@ Msg.install_token = "Install token";
h('div.row.cp-register-det', [
h('div#data.hidden.col-md-6', [
h('h2', Msg.register_notes_title),
//Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes)
Pages.setHTML(h('div.cp-register-notes'), Msg.install_notes)
]),
h('div.cp-reg-form.col-md-6', [
h('div#userForm.form-group.hidden', [
h('div.cp-register-instance', [
Msg._getKey('register_instance', [ Pages.Instance.name ]),
/*h('br'),
h('a', {
href: '/features.html'
}, Msg.register_whyRegister)*/
Msg.install_instance,
]),
h('input.form-control#installtoken', {
type: 'text',
@ -75,7 +67,7 @@ Msg.install_token = "Install token";
/*h('div.checkbox-container', [
UI.createCheckbox('import-recent', Msg.register_importRecent, true)
]),*/
h('button#register', Msg.login_register)
h('button#register', Msg.install_launch)
])
]),
])

View file

@ -0,0 +1,97 @@
/*
* SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
*
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@import (reference) "./colortheme-all.less";
@import (reference) "./forms.less";
@import (reference) './icon-colors.less';
.admin_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
// XXX Instance accent color presets
@palette-colors:
@cryptpad_color_brand,
#ffee00,
#FF0073;
div.cp-palette-container {
.cp-palette-nocolor {
display: none;
}
.instance-colors(@palette-colors; @index) when (@index > 0){
// loop through the @colors
.instance-colors(@palette-colors; (@index - 1));
@color: extract(@palette-colors, @index);
// make a numbered class selector for each color
.cp-palette-color@{index}{
background-color: @color !important;
color: contrast(@color, @cryptpad_color_grey_800, @cryptpad_color_grey_200) !important;
}
}
.instance-colors(@palette-colors; length(@palette-colors));
}
.cp-admin-customize-apps-grid, .cp-admin-customize-options-grid {
display: grid;
gap: 0.5rem;
}
.cp-admin-customize-apps-grid {
grid-template-columns: 1fr 1fr 1fr;
.cp-appblock {
padding: 0.5rem;
border-radius: @variables_radius;
font-size: 1.2em;
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
.iconColors_main();
&:hover {
cursor: pointer;
}
i.cp-icon {
font-size: 2.8rem;
}
.cp-app-name {
flex-grow: 1;
}
}
.cp-inactive-app {
background-color: transparent;
opacity: 0.75;
.cp-on-enabled {
visibility: hidden;
}
}
.cp-active-app {
background-color: fade(@cryptpad_text_col, 10%);
.cp-on-enabled {
visibility: visible;
}
}
}
.cp-admin-customize-options-grid {
grid-template-columns: 1fr 1fr;
.cp-optionblock {
padding: 0.5rem;
border-radius: @variables_radius;
background-color: fade(@cryptpad_text_col, 10%);
align-self: start;
.cp-checkmark-label {
font-weight: bold;
}
.cp-option-hint {
margin-left: 30px;
display: inline-block;
}
}
}
}

View file

@ -119,6 +119,31 @@
}
}
// The following palette container is just for the UI components
// The specific colors you want to show have to be defined in your app
// using the classes .cp-palette-nocolor .cp-palette-color1 .cp-palette-color2 etc.
div.cp-palette-container {
display: flex;
justify-content: space-between;
.cp-palette-color {
display: inline-block;
border-radius: 50%;
height: 30px;
width: 30px;
text-align: center;
line-height: 30px;
color: @cp_kanban-fg;
border: 1px solid fade(@cp_kanban-fg, 40%);
&.fa-check { // tick on selected color
color: @cryptpad_text_col;
}
outline: none;
&:focus{
outline: @cryptpad_color_brand solid 2px;
}
}
}
button.btn {
background-color: @cp_buttons-cancel;
box-sizing: border-box;

View file

@ -10,6 +10,7 @@ const nThen = require("nthen");
const Fs = require("fs");
const Path = require("path");
const Nacl = require("tweetnacl/nacl-fast");
const Hash = require('./common-hash');
module.exports.create = function (Env) {
var log = Env.Log;
@ -25,6 +26,39 @@ nThen(function (w) {
console.error(err);
}
}));
}).nThen(function (w) {
let admins = Env.admins || [];
// If we don't have any admin on this instance, print an onboarding link
if (Array.isArray(admins) && admins.length) { return; }
let token = Env.installToken;
let printLink = () => {
let url = `${Env.httpUnsafeOrigin}/install/#${token}`;
console.log('=============================');
console.log('Create your first admin account and customize your instance by visiting');
console.log(url);
console.log('=============================');
};
// If we already have a token, print it
if (token) { return void printLink(); }
// Otherwise create a new token
let decreeName = Path.join(Env.paths.decree, 'decree.ndjson');
token = Hash.createChannelId() + Hash.createChannelId();
let decree = ["ADD_INSTALL_TOKEN",[token],"",+new Date()];
Fs.appendFile(decreeName, JSON.stringify(decree) + '\n', w(function (err) {
if (err) { console.log(err); return; }
printLink();
}));
}).nThen(function (w) {
if (!Env.admins.length) {
Env.Log.info('NO_ADMIN_CONFIGURED', {
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
});
}
}).nThen(function (w) {
// we assume the server has generated a secret used to validate JWT tokens
if (typeof(Env.bearerSecret) === 'string') { return; }

View file

@ -466,6 +466,8 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) {
// CryptPad_AsyncStore.rpc.send('ADMIN', ['INSTANCE_STATUS], console.log)
var instanceStatus = function (Env, Server, cb) {
cb(void 0, {
appsToDisable: Env.appsToDisable,
restrictRegistration: Env.restrictRegistration,
restrictSsoRegistration: Env.restrictSsoRegistration,
dontStoreSSOUsers: Env.dontStoreSSOUsers,

6
lib/common-hash.js Normal file
View file

@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
module.exports = require("../www/common/common-hash");

View file

@ -220,6 +220,15 @@ commands.SET_SUPPORT_MAILBOX = makeGenericSetter('supportMailbox', function (arg
return args_isString(args) && Core.isValidPublicKey(args[0]);
});
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SUPPORT_KEYS', ["Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA=", "Tdz6+fE9N9XXBY93rW5qeNa/k27yd40c0vq7EJyt7jA="]]], console.log)
commands.DISABLE_APPS = function (Env, args) {
if (!Array.isArray(args)) { throw new Error("INVALID_ARGS"); }
const appsToDisable = args;
Env.appsToDisable = appsToDisable;
return true;
};
commands.SET_SUPPORT_KEYS = function (Env, args) {
const curvePublic = args[0]; // Support mailbox key
const edPublic = args[1]; // Support pin log
@ -232,7 +241,7 @@ commands.SET_SUPPORT_KEYS = function (Env, args) {
Env.supportMailboxKey = curvePublic;
Env.supportPinKey = edPublic;
return true;
};
};
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_INSTANCE_PURPOSE', ["development"]]], console.log)
commands.SET_INSTANCE_PURPOSE = makeGenericSetter('instancePurpose', args_isString);
@ -290,7 +299,7 @@ var args_isMaintenance = function (args) {
// whenever that happens we can relax validation a bit to support more formats
var makeBroadcastSetter = function (attr, validation) {
return function (Env, args) {
if ((validation && !validation(args)) && !args_isString(args)) {
if ((validation && !validation(args)) && !args_isString(args)) {
throw new Error('INVALID_ARGS');
}
var str = args[0];

View file

@ -590,6 +590,7 @@ var serveConfig = makeRouteCache(function () {
maxUploadSize: Env.maxUploadSize,
premiumUploadSize: Env.premiumUploadSize,
restrictRegistration: Env.restrictRegistration,
appsToDisable: Env.appsToDisable,
restrictSsoRegistration: Env.restrictSsoRegistration,
httpSafeOrigin: Env.httpSafeOrigin,
enableEmbedding: Env.enableEmbedding,

View file

@ -82,7 +82,6 @@ Stats.instanceData = function (Env) {
// check how many instances provide stats before we put more work into it
data.providesAggregateStatistics = true;
}
return data;
};

View file

@ -40,5 +40,4 @@ nThen(function (w) {
console.log(token);
var url = config.httpUnsafeOrigin + '/install/';
console.log(`Please visit ${url} to create your first admin user`);
});

View file

@ -68,13 +68,6 @@ nThen(function (w) {
Env.Log.info("WEBSERVER_LISTENING", {
origin: url,
});
if (!Env.admins.length) {
Env.Log.info('NO_ADMIN_CONFIGURED', {
message: `Your instance is not correctly configured for production usage. Review its checkup page for more information.`,
details: new URL('/checkup/', Env.httpUnsafeOrigin).href,
});
}
} catch (err) {
Env.Log.error("INVALID_ORIGIN", {
httpUnsafeOrigin: Env.httpUnsafeOrigin,

View file

@ -9,12 +9,14 @@
@import (reference) "../../customize/src/less2/include/creation.less";
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/export.less';
@import (reference) '../../customize/src/less2/include/admin.less';
&.cp-app-admin {
.framework_min_main();
.sidebar-layout_main();
.limit-bar_main();
.creation_main();
.admin_main();
display: flex;
flex-flow: column;
@ -32,6 +34,16 @@
border-radius: 5px;
background-color: @cryptpad_color_brand;
}
input.cp-admin-color-picker {
vertical-align: middle;
}
.cp-palette-container {
display: inline-flex;
width: ~"calc(100% - 5rem)";
padding-left: 0.5rem;
vertical-align: middle;
}
.cp-admin-color-preview {
& > div {
margin-top: @sidebar_base-margin;

View file

@ -5,6 +5,7 @@
define([
'jquery',
'/common/toolbar.js',
'/common/pad-types.js',
'/components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-interface.js',
@ -17,11 +18,11 @@ define([
'/common/hyperscript.js',
'/common/clipboard.js',
'json.sortify',
'/customize/application_config.js',
'/api/config',
'/api/instance',
'/lib/datepicker/flatpickr.js',
'/common/hyperscript.js',
'/install/onboardscreen.js',
'css!/lib/datepicker/flatpickr.min.css',
'css!/components/bootstrap/dist/css/bootstrap.min.css',
'css!/components/components-font-awesome/css/font-awesome.min.css',
@ -29,6 +30,7 @@ define([
], function(
$,
Toolbar,
PadTypes,
nThen,
SFCommon,
UI,
@ -41,11 +43,12 @@ define([
h,
Clipboard,
Sortify,
AppConfig,
ApiConfig,
Instance,
Flatpickr
Flatpickr,
Onboarding,
) {
var APP = window.APP = {};
var Nacl = window.nacl;
@ -90,6 +93,12 @@ define([
'forcemfa',
]
},
'apps': { // Msg.admin_cat_apps
icon: 'fa fa-wrench',
content: [
'apps',
]
},
'users' : { // Msg.admin_cat_users
icon : 'fa fa-address-card-o',
content : [
@ -160,7 +169,7 @@ define([
}
};
const blocks = sidebar.blocks;
const blocks = Sidebar.blocks('admin');
const flushCache = (cb) => {
cb = cb || function () {};
@ -190,6 +199,7 @@ define([
// Msg.admin_flushCacheHint, .admin_flushCacheTitle, .admin_flushCacheButton
sidebar.addItem('flush-cache', function (cb) {
const blocks = Sidebar.blocks('admin');
var button = blocks.activeButton('primary', '',
Messages.admin_flushCacheButton, done => {
flushCache(function (e, data) {
@ -609,7 +619,6 @@ define([
UI.log(Messages._getKey('ui_saved', [Messages.admin_emailTitle]));
});
});
var nav = blocks.nav([button]);
var form = blocks.form([
@ -621,6 +630,35 @@ define([
cb(form);
});
sidebar.addItem('apps', function (cb) {
const appsToDisable = ApiConfig.appsToDisable || [];
const grid = Onboarding.createAppsGrid(appsToDisable);
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['DISABLE_APPS', appsToDisable]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
done(false);
return;
}
flushCache();
done(true);
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
});
});
let form = blocks.form([
grid
], blocks.nav([save]));
cb(form);
});
sidebar.addItem('instance-info-notice', function(cb){
var key = 'instance-info-notice';
var notice = blocks.alert('info', key, [Messages.admin_infoNotice1, ' ', Messages.admin_infoNotice2]);
@ -905,7 +943,7 @@ define([
setColor(color, done);
});
let $input = $(input).on('change', () => {
let onColorPicked = () => {
require(['/lib/less.min.js'], (Less) => {
let color = $input.val();
let lColor = Less.color(color.slice(1));
@ -925,7 +963,8 @@ define([
$preview.find('.cp-admin-color-preview-dark a').attr('style', `color: ${lightColor} !important`);
$preview.find('.cp-admin-color-preview-light a').attr('style', `color: ${color} !important`);
});
});
};
let $input = $(input).on('change', onColorPicked).addClass('cp-admin-color-picker');
UI.confirmButton($remove, {
classes: 'btn-danger',
@ -935,9 +974,19 @@ define([
setColor('', () => {});
});
var colors = UIElements.makePalette(8, (color, $color) => {
// onselect
let rgb = $color.css('background-color');
let hex = Util.rgbToHex(rgb);
$input.val(hex);
onColorPicked();
});
var $colors = $(colors);
$(label).append(colors);
let form = blocks.form([
labelCurrent,
label
label,
], blocks.nav([btn, remove, btn.spinner]));
cb([form, labelPreview]);

View file

@ -1119,6 +1119,21 @@ define([
}
};
UI.getNewIcon = function (type) {
var icon = h('i.fa.fa-file-text-o');
if (AppConfig.applicationsIcon && AppConfig.applicationsIcon[type]) {
var icon = AppConfig.applicationsIcon[type];
var font = icon.indexOf('cptools') === 0 ? 'cptools' : 'fa';
if (type === 'fileupload') { type = 'file'; }
if (type === 'folderupload') { type = 'file'; }
if (type === 'link') { type = 'drive'; }
var appClass = ' cp-icon cp-icon-color-'+type;
icon = h('i', {'class': font + ' ' + icon + appClass});
}
return icon;
};
var $defaultIcon = $('<span>', {"class": "fa fa-file-text-o"});
UI.getIcon = function (type) {
var $icon = $defaultIcon.clone();

View file

@ -4297,5 +4297,76 @@ define([
return UI.errorLoadingScreen(msg, false, false);
};
UIElements.makePalette = (maxColors, onSelect) => {
let palette = [''];
for (var i=1; i<=maxColors; i++) { palette.push('color'+i); }
let offline = false;
let selectedColor = '';
let container = h('div.cp-palette-container');
let $container = $(container);
var all = [];
palette.forEach(function (color, i) {
var $color = $(h('button.cp-palette-color.fa'));
all.push($color);
$color.addClass('cp-palette-'+(color || 'nocolor'));
$color.keydown(function (e) {
if (e.which === 13) {
e.stopPropagation();
e.preventDefault();
$color.click();
}
});
$color.click(function () {
if (offline) { return; }
if (color === selectedColor) { return; }
selectedColor = color;
$container.find('.cp-palette-color').removeClass('fa-check');
$color.addClass('fa-check');
onSelect(color, $color);
}).appendTo($container);
$color.keydown(e => {
if (e.which === 37) {
e.preventDefault();
if (i === 0) {
all[all.length - 1].focus();
} else {
all[i - 1].focus();
}
}
if (e.which === 39) {
e.preventDefault();
if (i === (all.length - 1)) {
all[0].focus();
} else {
all[i + 1].focus();
}
}
if (e.which === 9) {
if (e.shiftKey) {
all[0].focus();
return;
}
all[all.length - 1].focus();
}
});
});
container.disable = state => {
offline = !!state;
};
container.getValue = () => {
return selectedColor;
};
container.setValue = color => {
$container.find('.cp-palette-color').removeClass('fa-check');
let $color = $container.find('.cp-palette-'+(color || 'nocolor'));
$color.addClass('fa-check');
selectedColor = color;
};
return container;
};
return UIElements;
});

View file

@ -606,6 +606,9 @@
parseInt(h.slice(4,6), 16),
];
};
Util.rgbToHex = function (rgb) {
return `#${rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/).slice(1).map(n => parseInt(n, 10).toString(16).padStart(2, '0')).join('')}`;
};
Util.isSmallScreen = function () {
return window.innerHeight < 800 || window.innerWidth < 800;

View file

@ -22,17 +22,14 @@ define([
h
) {
const Sidebar = {};
const keyToCamlCase = (key) => {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
};
Sidebar.create = function (common, app, $container) {
const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
const sidebar = {
$leftside,
$rightside
};
const items = {};
Sidebar.blocks = function (app) {
let blocks = {};
let blocks = sidebar.blocks = {};
blocks.labelledInput = (label, input, inputBlock) => {
let uid = Util.uid();
let id = `cp-${app}-item-${uid}`;
@ -109,7 +106,7 @@ define([
element
]);
};
blocks.pre = (value) => {
blocks.pre = (value) => {
return h('pre', value);
};
@ -220,9 +217,7 @@ define([
return button;
};
const keyToCamlCase = (key) => {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
};
blocks.activeCheckbox = (data) => {
const state = data.getState();
const key = data.key;
@ -246,6 +241,20 @@ define([
return box;
};
return blocks;
};
Sidebar.create = function (common, app, $container) {
const $leftside = $(h('div#cp-sidebarlayout-leftside')).appendTo($container);
const $rightside = $(h('div#cp-sidebarlayout-rightside')).appendTo($container);
const sidebar = {
$leftside,
$rightside
};
const items = {};
sidebar.blocks = Sidebar.blocks(app);
sidebar.addItem = (key, get, options) => {
const safeKey = keyToCamlCase(key);
get((content) => {
@ -274,6 +283,7 @@ define([
sidebar.addCheckboxItem = (data) => {
const key = data.key;
let blocks = sidebar.blocks;
let box = blocks.activeCheckbox(data);
sidebar.addItem(key, function (cb) {
cb(box);

View file

@ -12,12 +12,22 @@ define([
OOCurrentVersion.currentVersion,
);
let availableTypes = AppConfig.availablePadTypes.filter(
(t) => ooEnabled || !OO_APPS.includes(t),
let availablePadTypes = AppConfig.availablePadTypes.filter(
(t) => ooEnabled || !OO_APPS.includes(t)
);
let availableTypes;
if (ApiConfig.appsToDisable) {
availableTypes = availablePadTypes.filter(value => !ApiConfig.appsToDisable.includes(value));
} else {
availableTypes = availablePadTypes;
}
var appsToSelect = availablePadTypes.filter(value => !['drive', 'teams', 'file', 'contacts', 'convert'].includes(value));
return {
availableTypes,
appsToSelect,
isAvailable: function (type) {
return availableTypes.includes(type);

View file

@ -16,13 +16,17 @@ define([
'/common/hyperscript.js',
'/customize/pages.js',
'/common/rpc.js',
'onboardscreen.js',
'/common/inner/sidebar-layout.js',
'less!/install/onboarding.less',
'css!/components/components-font-awesome/css/font-awesome.min.css',
], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc) {
], function ($, Login, Cryptpad, /*Test,*/ Cred, UI, Util, Realtime, Constants, Feedback, LocalStore, h, Pages, Rpc, OnboardScreen, Sidebar) {
if (window.top !== window) { return; }
var Messages = Cryptpad.Messages;
$(function () {
if (LocalStore.isLoggedIn()) {
if (LocalStore.isLoggedIn() && !localStorage.CP_dev) {
// already logged in, redirect to drive
document.location.href = '/drive/';
return;
@ -55,10 +59,14 @@ define([
}
}
var showTitleScreen = function (sendAdminDecree, sendAdminRpc) {
OnboardScreen.create(sendAdminDecree, sendAdminRpc);
};
var registerClick = function () {
var uname = $uname.val().trim();
// trim whitespace surrounding the username since it is otherwise included in key derivation
// most people won't realize that its presence is significant
// trim whitespace surrounding the username since it is otherwise included in key derivation
// most people won't realize that its presence is significant
$uname.val(uname);
var passwd = $passwd.val();
@ -112,6 +120,27 @@ define([
return void UI.alert(Messages.register_mustAcceptTerms);
}
let startOnboarding = function (network, proxy) {
Rpc.create(network, proxy.edPrivate, proxy.edPublic, function (e, rpc) {
if (e) {
// TODO: handle error
return;
}
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);
};
showTitleScreen(sendAdminDecree, sendAdminRpc);
});
};
setTimeout(function () {
var span = h('span', [
h('h2', [
@ -145,7 +174,7 @@ define([
edPublic: proxy.edPublic
}, function (e) {
if (e) { UI.alert(Messages.error); return console.error(e); }
window.location.href = '/drive/';
startOnboarding(data.network, proxy);
});
});

119
www/install/onboarding.less Normal file
View file

@ -0,0 +1,119 @@
@import (reference) "../../customize/src/less2/include/browser.less";
@import (reference) "../../customize/src/less2/include/framework.less";
@import (reference) "../../customize/src/less2/include/tools.less";
@import (reference) "../../customize/src/less2/include/markdown.less";
@import (reference) "../../customize/src/less2/include/avatar.less";
@import (reference) '../../customize/src/less2/include/admin.less';
@import (reference) '../../customize/src/less2/include/icon-colors.less';
&.cp-page-install {
.admin_main();
div.cp-palette-container {
max-width: 400px;
}
#cp-onboarding {
position: fixed;
z-index: 1000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background-color: @cp_loading-bg;
color: @cp_loading-fg;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
.cp-onboarding-box {
width: 850px;
background-color: @cp_loading-progress-bg;
border-radius: @variables_radius_L;
box-shadow: 0px 0px 10px 0px @cp_shadow-color;
padding: 30px 110px 20px 110px;
}
.cp-onboarding-logo-input {
display: none;
}
.cp-onboardscreen-logo {
min-width: 200px;
max-width: 200px;
nav {
display: flex;
margin-top: 0.5rem;
justify-content: center;
}
}
}
}
.configscreen {
//width: 70%;
//height: 75%;
background-color: @cp_loading-progress-bg;
border-radius: @variables_radius_L;
box-shadow: 0px 0px 10px 0px @cp_shadow-color;
padding: 30px 110px 20px 110px;
}
.cp-onboardscreen-screentitle {
text-align: center;
font-size: 1rem;
margin-bottom: 1rem;
h1.cp-onboardscreen-title {
font-family: "IBM Plex Mono";
color: @cryptpad_color_link;
font-size: 1.5rem;
font-weight: 500;
}
}
.cp-instance-form {
display: flex;
flex-direction: row;
gap: 1rem;
input, textarea {
border: 0px transparent !important;
}
.cp-onboardscreen-logo {
.cp-sidebar-form {
background-color: @cp_forms-bg;
border-radius: @variables_radius;
padding: 0.5rem;
.cp-admin-customize-logo {
img {
max-width: 100%;
}
}
}
}
.cp-instance-text-form {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
.cp-onboardscreen-name {
input {
font-family: "IBM Plex Mono";
font-size: 1.2em;
}
}
.cp-onboardscreen-colorpick {
div.cp-palette-container {
display: flex;
justify-content: flex-start;
.cp-palette-color {
margin-right: 1rem;
}
}
}
}
}
nav.cp-onboardscreen-nav {
width: 100%;
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-top: 1rem;
}

View file

@ -0,0 +1,498 @@
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 () {
// XXX Number of accent color presets
var colors = UIElements.makePalette(5, (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 $colors = $(colors).attr('id', 'cp-install-color');
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 (done) {
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 (done) {
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), 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 };
});

View file

@ -63,11 +63,13 @@
.kanban-colors(@kanban-colors; (@index - 1));
@color: extract(@kanban-colors, @index);
// make a numbered class selector for each color
.cp-kanban-palette-color@{index}{
background-color: @color !important;
.cp-palette-color@{index}{
&.cp-kanban-palette-board {
background-color: @color !important;
}
}
.cp-kanban-palette-color@{index}{
background-color: @color !important;
&.kanban-board-inner {
background-color: fade(@color, 50%) !important;
}
@ -81,10 +83,12 @@
// .cp-kanban-card-color@{index}{
// background-color: @color !important;
// }
.cp-kanban-palette-color@{index}{
.cp-palette-color@{index}{
&.cp-kanban-palette-card {
background-color: @color !important;
}
}
.cp-kanban-palette-color@{index}{
&.kanban-item {
background-color: @color !important;
}
@ -140,31 +144,7 @@
}
margin-bottom: 15px;
}
#cp-kanban-edit-colors {
display: flex;
justify-content: space-between;
.cp-kanban-palette {
display: inline-block;
border-radius: 50%;
height: 30px;
width: 30px;
text-align: center;
line-height: 30px;
color: @cp_kanban-fg;
border: 1px solid fade(@cp_kanban-fg, 40%);
outline: none;
// tick on selected color
&.cp-kanban-palette-card.fa-check {
color: @cryptpad_text_col;
}
&.cp-kanban-palette-board.fa-check {
color: @cryptpad_text_col_inv;
}
&:focus{
outline: @cryptpad_color_brand solid 2px;
}
}
}
#cp-kanban-edit-tags {
.tokenfield {
margin: 0;

View file

@ -185,6 +185,11 @@ define([
update();
};
var colors = UIElements.makePalette(8, color => {
dataObject.color = color;
commit();
});
var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
var content = h('div', [
conflictContainer = h('div#cp-kanban-edit-conflicts', [
@ -200,7 +205,7 @@ define([
h('label', {for:'cp-kanban-edit-tags'}, Messages.fm_tagsName),
tagsDiv = h('div#cp-kanban-edit-tags'),
h('label', {for:'cp-kanban-edit-color'}, Messages.kanban_color),
colors = h('div#cp-kanban-edit-colors'),
colors,
]);
var $tags = $(tagsDiv);
@ -362,11 +367,9 @@ define([
// Colors
var $colors = $(colors);
var palette = [''];
for (var i=1; i<=8; i++) { palette.push('color'+i); }
var selectedColor = '';
var resetThemeClass = function () {
$colors.find('.cp-kanban-palette').each(function (i, el) {
$colors.find('.cp-palette-color').each(function (i, el) {
var $c = $(el);
$c.removeClass('cp-kanban-palette-card');
$c.removeClass('cp-kanban-palette-board');
@ -377,38 +380,13 @@ define([
}
});
};
palette.forEach(function (color) {
var $color = $(h('button.cp-kanban-palette.fa'));
$color.addClass('cp-kanban-palette-'+(color || 'nocolor'));
$color.keydown(function (e) {
if (e.which === 13) {
e.stopPropagation();
e.preventDefault();
$color.click();
}
});
$color.click(function () {
if (offline) { return; }
if (color === selectedColor) { return; }
selectedColor = color;
$colors.find('.cp-kanban-palette').removeClass('fa-check');
var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor'));
$col.addClass('fa-check');
dataObject.color = color;
commit();
}).appendTo($colors);
});
var color = {
getValue: function () {
return selectedColor;
return colors.getValue();
},
setValue: function (color) {
resetThemeClass();
$colors.find('.cp-kanban-palette').removeClass('fa-check');
var $col = $colors.find('.cp-kanban-palette-'+(color || 'nocolor'));
$col.addClass('fa-check');
selectedColor = color;
colors.setValue(color);
}
};
@ -460,6 +438,7 @@ define([
$modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled');
offline = !unlocked;
palette.disable(offline);
});

View file

@ -56,7 +56,7 @@ define([
var andThen = function (common, $container, linkedTicket) {
const sidebar = Sidebar.create(common, 'support', $container);
const blocks = sidebar.blocks;
const blocks = sidebar.blocks();
APP.recorded = {};
APP.allTags = [];
APP.openTicketCategory = Util.mkEvent();