You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/admin/inner.js

2026 lines
77 KiB
JavaScript

6 years ago
define([
'jquery',
'/api/config',
'/customize/application_config.js',
6 years ago
'/bower_components/chainpad-crypto/crypto.js',
'/common/toolbar.js',
6 years ago
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
6 years ago
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-signing-keys.js',
'/support/ui.js',
6 years ago
'/lib/datepicker/flatpickr.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'css!/lib/datepicker/flatpickr.min.css',
6 years ago
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/admin/app-admin.less',
], function (
$,
ApiConfig,
AppConfig,
6 years ago
Crypto,
Toolbar,
nThen,
SFCommon,
h,
Messages,
UI,
UIElements,
Util,
Hash,
Keys,
Support,
Flatpickr
6 years ago
)
{
var APP = {
'instanceStatus': {}
};
6 years ago
var Nacl = window.nacl;
6 years ago
var common;
var sFrameChan;
var categories = {
'general': [ // Msg.admin_cat_general
6 years ago
'cp-admin-flush-cache',
'cp-admin-update-limit',
'cp-admin-archive',
'cp-admin-unarchive',
'cp-admin-registration',
'cp-admin-email'
],
'quota': [ // Msg.admin_cat_quota
'cp-admin-defaultlimit',
'cp-admin-setlimit',
'cp-admin-getquota',
'cp-admin-getlimits',
6 years ago
],
'stats': [ // Msg.admin_cat_stats
'cp-admin-refresh-stats',
6 years ago
'cp-admin-active-sessions',
'cp-admin-active-pads',
'cp-admin-open-files',
6 years ago
'cp-admin-registered',
'cp-admin-disk-usage',
],
'support': [ // Msg.admin_cat_support
'cp-admin-support-list',
'cp-admin-support-init',
'cp-admin-support-priv',
],
4 years ago
'broadcast': [ // Msg.admin_cat_broadcast
'cp-admin-maintenance',
'cp-admin-survey',
'cp-admin-broadcast',
],
'performance': [ // Msg.admin_cat_performance
'cp-admin-refresh-performance',
'cp-admin-performance-profiling',
],
'network': [ // Msg.admin_cat_network
'cp-admin-update-available',
'cp-admin-checkup',
'cp-admin-block-daily-check',
'cp-admin-provide-aggregate-statistics',
'cp-admin-list-my-instance',
'cp-admin-consent-to-contact',
'cp-admin-remove-donate-button',
],
6 years ago
};
var create = {};
var keyToCamlCase = function (key) {
return key.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase(); });
};
var makeBlock = function (key, addButton) { // Title, Hint, maybeButton
6 years ago
// Convert to camlCase for translation keys
var safeKey = keyToCamlCase(key);
6 years ago
var $div = $('<div>', {'class': 'cp-admin-' + key + ' cp-sidebarlayout-element'});
$('<label>').text(Messages['admin_'+safeKey+'Title'] || key).appendTo($div);
$('<span>', {'class': 'cp-sidebarlayout-description'})
.text(Messages['admin_'+safeKey+'Hint'] || 'Coming soon...').appendTo($div);
if (addButton) {
$('<button>', {
'class': 'btn btn-primary'
}).text(Messages['admin_'+safeKey+'Button'] || safeKey).appendTo($div);
}
return $div;
};
create['update-limit'] = function () {
var key = 'update-limit';
var $div = makeBlock(key, true); // Msg.admin_updateLimitHint, .admin_updateLimitTitle, .admin_updateLimitButton
6 years ago
$div.find('button').click(function () {
sFrameChan.query('Q_UPDATE_LIMIT', null, function (e, res) {
if (e || (res && res.error)) { return void console.error(e || res.error); }
UI.alert(Messages.admin_updateLimitDone || 'done');
});
});
return $div;
};
create['flush-cache'] = function () {
var key = 'flush-cache';
var $div = makeBlock(key, true); // Msg.admin_flushCacheHint, .admin_flushCacheTitle, .admin_flushCacheButton
6 years ago
var called = false;
$div.find('button').click(function () {
if (called) { return; }
called = true;
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'FLUSH_CACHE',
}, function (e, data) {
called = false;
UI.alert(data ? Messages.admin_flushCacheDone || 'done' : 'error' + e);
});
});
return $div;
};
var archiveForm = function (archive, $div, $button) {
var label = h('label', { for: 'cp-admin-archive' }, Messages.admin_archiveInput);
var input = h('input#cp-admin-archive', {
type: 'text'
});
var label2 = h('label.cp-admin-pw', {
for: 'cp-admin-archive-pw'
}, Messages.admin_archiveInput2);
var input2 = UI.passwordInput({
id: 'cp-admin-archive-pw',
placeholder: Messages.login_password
});
var $pw = $(input2);
$pw.addClass('cp-admin-pw');
var $pwInput = $pw.find('input');
$button.before(h('div.cp-admin-setlimit-form', [
label,
input,
label2,
input2
]));
$div.addClass('cp-admin-nopassword');
var parsed;
var $input = $(input).on('keypress change paste', function () {
setTimeout(function () {
$input.removeClass('cp-admin-inval');
var val = $input.val().trim();
if (!val) {
$div.toggleClass('cp-admin-nopassword', true);
return;
}
parsed = Hash.isValidHref(val);
$pwInput.val('');
if (!parsed || !parsed.hashData) {
$div.toggleClass('cp-admin-nopassword', true);
return void $input.addClass('cp-admin-inval');
}
var pw = parsed.hashData.version !== 3 && parsed.hashData.password;
$div.toggleClass('cp-admin-nopassword', !pw);
});
});
$pw.on('keypress change', function () {
setTimeout(function () {
$pw.toggleClass('cp-admin-inval', !$pwInput.val());
});
});
var clicked = false;
$button.click(function () {
if (!parsed || !parsed.hashData) {
UI.warn(Messages.admin_archiveInval);
return;
}
var pw = parsed.hashData.password ? $pwInput.val() : undefined;
var channel;
if (parsed.hashData.version === 3) {
channel = parsed.hashData.channel;
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, pw);
channel = secret && secret.channel;
}
if (!channel) {
UI.warn(Messages.admin_archiveInval);
return;
}
if (clicked) { return; }
clicked = true;
nThen(function (waitFor) {
if (!archive) { return; }
common.getFileSize(channel, waitFor(function (err, size) {
if (!err && size === 0) {
clicked = false;
waitFor.abort();
return void UI.warn(Messages.admin_archiveInval);
}
}), true);
}).nThen(function () {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT',
data: channel
}, function (err, obj) {
var e = err || (obj && obj.error);
clicked = false;
if (e) {
UI.warn(Messages.error);
console.error(e);
return;
}
UI.log(archive ? Messages.archivedFromServer : Messages.restoredFromServer);
$input.val('');
$pwInput.val('');
});
});
});
};
create['archive'] = function () {
var key = 'archive';
var $div = makeBlock(key, true); // Msg.admin_archiveHint, .admin_archiveTitle, .admin_archiveButton
var $button = $div.find('button');
archiveForm(true, $div, $button);
return $div;
};
create['unarchive'] = function () {
var key = 'unarchive';
var $div = makeBlock(key, true); // Msg.admin_unarchiveHint, .admin_unarchiveTitle, .admin_unarchiveButton
var $button = $div.find('button');
archiveForm(false, $div, $button);
return $div;
};
create['registration'] = function () {
var key = 'registration';
var $div = makeBlock(key); // Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
var state = APP.instanceStatus.restrictRegistration;
var $cbox = $(UI.createCheckbox('cp-settings-' + key,
Messages.admin_registrationTitle,
state, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = $checkbox.is(':checked') || false;
$checkbox.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
spinner.done();
state = APP.instanceStatus.restrictRegistration;
$checkbox[0].checked = state;
$checkbox.removeAttr('disabled');
});
});
});
$cbox.appendTo($div);
return $div;
};
var makeAdminCheckbox = function (data) {
return function () {
var state = data.getState();
var key = data.key;
var $div = makeBlock(key);
var labelKey = 'admin_' + keyToCamlCase(key) + 'Label';
var titleKey = 'admin_' + keyToCamlCase(key) + 'Title';
var $cbox = $(UI.createCheckbox('cp-admin-' + key,
Messages[labelKey] || Messages[titleKey],
state, { label: { class: 'noTitle' } }));
var spinner = UI.makeSpinner($cbox);
var $checkbox = $cbox.find('input').on('change', function() {
spinner.spin();
var val = $checkbox.is(':checked') || false;
$checkbox.attr('disabled', 'disabled');
data.query(val, function (state) {
spinner.done();
$checkbox[0].checked = state;
$checkbox.removeAttr('disabled');
});
});
$cbox.appendTo($div);
return $div;
};
};
// Msg.admin_registrationHint, .admin_registrationTitle, .admin_registrationButton
create['registration'] = makeAdminCheckbox({
key: 'registration',
getState: function () {
return APP.instanceStatus.restrictRegistration;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RESTRICT_REGISTRATION', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.restrictRegistration);
});
});
},
});
create['email'] = function () {
var key = 'email';
var $div = makeBlock(key, true); // Msg.admin_emailHint, Msg.admin_emailTitle, Msg.admin_emailButton
var $button = $div.find('button');
var input = h('input', {
type: 'email',
value: ApiConfig.adminEmail || ''
});
var $input = $(input);
var innerDiv = h('div.cp-admin-setlimit-form', input);
var spinner = UI.makeSpinner($(innerDiv));
$button.click(function () {
if (!$input.val()) { return; }
spinner.spin();
$button.attr('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_ADMIN_EMAIL', [$input.val()]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
$input.val('');
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
});
});
$button.before(innerDiv);
return $div;
};
var getPrettySize = UIElements.prettySize;
create['defaultlimit'] = function () {
var key = 'defaultlimit';
var $div = makeBlock(key); // Msg.admin_defaultlimitHint, .admin_defaultlimitTitle
var _limit = APP.instanceStatus.defaultStorageLimit;
var _limitMB = Util.bytesToMegabytes(_limit);
var limit = getPrettySize(_limit);
var newLimit = h('input', {type: 'number', min: 0, value: _limitMB});
var set = h('button.btn.btn-primary', Messages.admin_setlimitButton);
$div.append(h('div', [
h('span.cp-admin-defaultlimit-value', Messages._getKey('admin_limit', [limit])),
h('div.cp-admin-setlimit-form', [
h('label', Messages.admin_defaultLimitMB),
newLimit,
h('nav', [set])
])
]));
UI.confirmButton(set, {
classes: 'btn-primary',
multiple: true,
validate: function () {
var l = parseInt($(newLimit).val());
if (isNaN(l)) { return false; }
return true;
}
}, function () {
var lMB = parseInt($(newLimit).val()); // Megabytes
var l = lMB * 1024 * 1024; // Bytes
var data = [l];
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['UPDATE_DEFAULT_STORAGE', data]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
return void console.error(e, response);
}
var limit = getPrettySize(l);
$div.find('.cp-admin-defaultlimit-value').text(Messages._getKey('admin_limit', [limit]));
});
});
return $div;
};
create['getlimits'] = function () {
var key = 'getlimits';
var $div = makeBlock(key); // Msg.admin_getlimitsHint, .admin_getlimitsTitle
APP.refreshLimits = function () {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_LIMITS',
}, function (e, data) {
if (e) { return; }
if (!Array.isArray(data) || !data[0]) { return; }
$div.find('.cp-admin-all-limits').remove();
var obj = data[0];
if (obj && (obj.message || obj.location)) {
delete obj.message;
delete obj.location;
}
var list = Object.keys(obj).sort(function (a, b) {
return obj[a].limit > obj[b].limit;
});
var compact = list.length > 10;
var content = list.map(function (key) {
var user = obj[key];
var limit = getPrettySize(user.limit);
var title = Messages._getKey('admin_limit', [limit]) + ', ' +
Messages._getKey('admin_limitPlan', [user.plan]) + ', ' +
Messages._getKey('admin_limitNote', [user.note]);
var keyEl = h('code.cp-limit-key', key);
$(keyEl).click(function () {
$('.cp-admin-setlimit-form').find('.cp-setlimit-key').val(key);
$('.cp-admin-setlimit-form').find('.cp-setlimit-quota').val(Math.floor(user.limit / 1024 / 1024));
$('.cp-admin-setlimit-form').find('.cp-setlimit-note').val(user.note);
});
if (compact) {
return h('tr.cp-admin-limit', {
title: title
}, [
h('td', keyEl),
h('td.limit', Messages._getKey('admin_limit', [limit])),
h('td.plan', Messages._getKey('admin_limitPlan', [user.plan])),
h('td.note', Messages._getKey('admin_limitNote', [user.note]))
]);
}
return h('li.cp-admin-limit', [
keyEl,
h('ul.cp-limit-data', [
h('li.limit', Messages._getKey('admin_limit', [limit])),
h('li.plan', Messages._getKey('admin_limitPlan', [user.plan])),
h('li.note', Messages._getKey('admin_limitNote', [user.note]))
])
]);
});
if (compact) { return $div.append(h('table.cp-admin-all-limits', content)); }
$div.append(h('ul.cp-admin-all-limits', content));
});
};
APP.refreshLimits();
return $div;
};
create['setlimit'] = function () {
var key = 'setlimit';
var $div = makeBlock(key); // Msg.admin_setlimitHint, .admin_setlimitTitle
var user = h('input.cp-setlimit-key');
var $key = $(user);
var limit = h('input.cp-setlimit-quota', {type: 'number', min: 0, value: 0});
var note = h('input.cp-setlimit-note');
var remove = h('button.btn.btn-danger', Messages.fc_remove);
var set = h('button.btn.btn-primary', Messages.admin_setlimitButton);
var form = h('div.cp-admin-setlimit-form', [
h('label', Messages.admin_limitUser),
user,
h('label', Messages.admin_limitMB),
limit,
h('label', Messages.admin_limitSetNote),
note,
h('nav', [set, remove])
]);
var getValues = function () {
var key = $key.val();
var _limit = parseInt($(limit).val());
var _note = $(note).val();
if (key.length !== 44) {
try {
var u = Keys.parseUser(key);
if (!u.domain || !u.user || !u.pubkey) {
return void UI.warn(Messages.admin_invalKey);
}
} catch (e) {
return void UI.warn(Messages.admin_invalKey);
}
}
if (isNaN(_limit) || _limit < 0) {
return void UI.warn(Messages.admin_invalLimit);
}
return {
key: key,
data: {
limit: _limit * 1024 * 1024,
note: _note,
plan: 'custom'
}
};
};
UI.confirmButton(remove, {
classes: 'btn-danger',
multiple: true,
validate: function () {
var obj = getValues();
if (!obj || !obj.key) { return false; }
return true;
}
}, function () {
var obj = getValues();
var data = [obj.key];
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['RM_QUOTA', data]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
});
$(set).click(function () {
var obj = getValues();
if (!obj || !obj.key) { return; }
var data = [obj.key, obj.data];
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_QUOTA', data]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
return;
}
APP.refreshLimits();
$key.val('');
});
});
$div.append(form);
return $div;
};
create['getquota'] = function () {
var key = 'getquota';
var $div = makeBlock(key, true); // Msg.admin_getquotaHint, .admin_getquotaTitle, .admin_getquotaButton
var input = h('input#cp-admin-getquota', {
type: 'text'
});
var $input = $(input);
var $button = $div.find('button');
$button.before(h('div.cp-admin-setlimit-form', [
input,
]));
$button.click(function () {
var val = $input.val();
if (!val || !val.trim()) { return; }
var key = Keys.canonicalize(val);
if (!key) { return; }
$input.val('');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_USER_TOTAL_SIZE',
data: key
}, function (e, obj) {
if (e || (obj && obj.error)) {
console.error(e || obj.error);
return void UI.warn(Messages.error);
}
var size = Array.isArray(obj) && obj[0];
if (typeof(size) !== "number") { return; }
UI.alert(getPrettySize(size));
});
});
return $div;
};
var onRefreshStats = Util.mkEvent();
create['refresh-stats'] = function () {
var key = 'refresh-stats';
var $div = $('<div>', {'class': 'cp-admin-' + key + ' cp-sidebarlayout-element'});
var $btn = $(h('button.btn.btn-primary', Messages.oo_refresh));
$btn.click(function () {
onRefreshStats.fire();
});
$div.append($btn);
return $div;
};
6 years ago
create['active-sessions'] = function () {
var key = 'active-sessions';
var $div = makeBlock(key); // Msg.admin_activeSessionsHint, .admin_activeSessionsTitle
var onRefresh = function () {
$div.find('pre').remove();
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ACTIVE_SESSIONS',
}, function (e, data) {
var total = data[0];
var ips = data[1];
$div.find('pre').remove();
$div.append(h('pre', total + ' (' + ips + ')'));
});
};
onRefresh();
onRefreshStats.reg(onRefresh);
6 years ago
return $div;
};
create['active-pads'] = function () {
var key = 'active-pads';
var $div = makeBlock(key); // Msg.admin_activePadsHint, .admin_activePadsTitle
var onRefresh = function () {
$div.find('pre').remove();
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ACTIVE_PADS',
}, function (e, data) {
console.log(e, data);
$div.find('pre').remove();
$div.append(h('pre', String(data)));
});
};
onRefresh();
onRefreshStats.reg(onRefresh);
6 years ago
return $div;
};
create['open-files'] = function () {
var key = 'open-files';
var $div = makeBlock(key); // Msg.admin_openFilesHint, .admin_openFilesTitle
var onRefresh = function () {
$div.find('pre').remove();
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_FILE_DESCRIPTOR_COUNT',
}, function (e, data) {
if (e || (data && data.error)) {
console.error(e, data);
$div.append(h('pre', {
style: 'text-decoration: underline',
}, String(e || data.error)));
return;
}
console.log(e, data);
$div.find('pre').remove();
$div.append(h('pre', String(data)));
});
};
onRefresh();
onRefreshStats.reg(onRefresh);
return $div;
};
6 years ago
create['registered'] = function () {
var key = 'registered';
var $div = makeBlock(key); // Msg.admin_registeredHint, .admin_registeredTitle
var onRefresh = function () {
$div.find('pre').remove();
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'REGISTERED_USERS',
}, function (e, data) {
console.log(e, data);
$div.find('pre').remove();
$div.append(h('pre', String(data)));
});
};
onRefresh();
onRefreshStats.reg(onRefresh);
6 years ago
return $div;
};
create['disk-usage'] = function () {
var key = 'disk-usage';
var $div = makeBlock(key, true); // Msg.admin_diskUsageHint, .admin_diskUsageTitle, .admin_diskUsageButton
6 years ago
var called = false;
$div.find('button').click(function () {
$div.find('button').hide();
if (called) { return; }
called = true;
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'DISK_USAGE',
}, function (e, data) {
console.log(e, data);
if (e) { return void console.error(e); }
var obj = data[0];
Object.keys(obj).forEach(function (key) {
var val = obj[key];
var unit = Util.magnitudeOfBytes(val);
if (unit === 'GB') {
obj[key] = Util.bytesToGigabytes(val) + ' GB';
} else if (unit === 'MB') {
obj[key] = Util.bytesToMegabytes(val) + ' MB';
} else {
obj[key] = Util.bytesToKilobytes(val) + ' KB';
}
});
$div.append(h('ul', Object.keys(obj).map(function (k) {
return h('li', [
h('strong', k === 'total' ? k : '/' + k),
' : ',
obj[k]
]);
})));
});
});
return $div;
};
var supportKey = ApiConfig.supportMailbox;
var checkAdminKey = function (priv) {
if (!supportKey) { return; }
return Hash.checkBoxKeyPair(priv, supportKey);
};
create['support-list'] = function () {
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var $container = makeBlock('support-list'); // Msg.admin_supportListHint, .admin_supportListTitle
var $div = $(h('div.cp-support-container')).appendTo($container);
var catContainer = h('div.cp-dropdown-container');
var col1 = h('div.cp-support-column', h('h1', [
h('span', Messages.admin_support_premium),
h('span.cp-support-count'),
h('button.btn.cp-support-column-button', Messages.admin_support_collapse)
]));
var col2 = h('div.cp-support-column', h('h1', [
h('span', Messages.admin_support_normal),
h('span.cp-support-count'),
h('button.btn.cp-support-column-button', Messages.admin_support_collapse)
]));
var col3 = h('div.cp-support-column', h('h1', [
h('span', Messages.admin_support_answered),
h('span.cp-support-count'),
h('button.btn.cp-support-column-button', Messages.admin_support_collapse)
]));
var col4 = h('div.cp-support-column', h('h1', [
h('span', Messages.admin_support_closed),
h('span.cp-support-count'),
h('button.btn.cp-support-column-button', Messages.admin_support_collapse)
]));
4 years ago
var $col1 = $(col1), $col2 = $(col2), $col3 = $(col3), $col4 = $(col4);
$div.append([
//catContainer
col1,
col2,
col3,
col4
]);
4 years ago
$div.find('.cp-support-column-button').click(function () {
var $col = $(this).closest('.cp-support-column');
$col.toggleClass('cp-support-column-collapsed');
if ($col.hasClass('cp-support-column-collapsed')) {
$(this).text(Messages.admin_support_open);
$(this).toggleClass('btn-primary');
} else {
$(this).text(Messages.admin_support_collapse);
$(this).toggleClass('btn-primary');
}
});
var category = 'all';
var $drop = APP.support.makeCategoryDropdown(catContainer, function (key) {
category = key;
if (key === 'all') {
$div.find('.cp-support-list-ticket').show();
return;
}
$div.find('.cp-support-list-ticket').hide();
$div.find('.cp-support-list-ticket[data-cat="'+key+'"]').show();
}, true);
$drop.setValue('all');
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var cat = privateData.category || '';
var linkedId = cat.indexOf('-') !== -1 && cat.slice(8);
var hashesById = {};
4 years ago
var getTicketData = function (id) {
var t = hashesById[id];
if (!Array.isArray(t) || !t.length) { return; }
var ed = Util.find(t[0], ['content', 'msg', 'content', 'sender', 'edPublic']);
// If one of their ticket was sent as a premium user, mark them as premium
var premium = t.some(function (msg) {
var _ed = Util.find(msg, ['content', 'msg', 'content', 'sender', 'edPublic']);
if (ed !== _ed) { return; }
return Util.find(msg, ['content', 'msg', 'content', 'sender', 'plan']);
4 years ago
});
var lastMsg = t[t.length - 1];
var lastMsgEd = Util.find(lastMsg, ['content', 'msg', 'content', 'sender', 'edPublic']);
return {
lastMsg: lastMsg,
time: Util.find(lastMsg, ['content', 'msg', 'content', 'time']),
lastMsgEd: lastMsgEd,
lastAdmin: lastMsgEd !== ed && ApiConfig.adminKeys.indexOf(lastMsgEd) !== -1,
premium: premium,
authorEd: ed,
closed: Util.find(lastMsg, ['content', 'msg', 'type']) === 'CLOSE'
};
};
var addClickHandler = function ($ticket) {
$ticket.on('click', function () {
$ticket.toggleClass('cp-support-open', true);
$ticket.off('click');
});
};
4 years ago
var makeOpenButton = function ($ticket) {
var button = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
4 years ago
var collapse = h('button.btn.cp-support-collapse', Messages.admin_support_collapse);
$(button).click(function () {
$ticket.toggleClass('cp-support-open', true);
});
addClickHandler($ticket);
$(collapse).click(function (e) {
4 years ago
$ticket.toggleClass('cp-support-open', false);
e.stopPropagation();
setTimeout(function () {
addClickHandler($ticket);
});
4 years ago
});
$ticket.find('.cp-support-title-buttons').prepend([button, collapse]);
$ticket.append(h('div.cp-support-collapsed'));
};
var updateTicketDetails = function ($ticket, isPremium) {
var $first = $ticket.find('.cp-support-message-from').first();
var user = $first.find('span').first().html();
var time = $first.find('.cp-support-message-time').text();
var last = $ticket.find('.cp-support-message-from').last().find('.cp-support-message-time').text();
var $c = $ticket.find('.cp-support-collapsed');
var txtClass = isPremium ? ".cp-support-ispremium" : "";
$c.html('').append([
UI.setHTML(h('span'+ txtClass), user),
h('span', [
h('b', Messages.admin_support_first),
h('span', time)
]),
h('span', [
h('b', Messages.admin_support_last),
h('span', last)
])
]);
4 years ago
};
var sort = function (id1, id2) {
var t1 = getTicketData(id1);
var t2 = getTicketData(id2);
if (!t1) { return 1; }
if (!t2) { return -1; }
/*
// If one is answered and not the other, put the unanswered first
if (t1.lastAdmin && !t2.lastAdmin) { return 1; }
if (!t1.lastAdmin && t2.lastAdmin) { return -1; }
*/
// Otherwise, sort them by time
return t1.time - t2.time;
};
var _reorder = function () {
4 years ago
var orderAnswered = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && d.lastAdmin && !d.closed;
}).sort(sort);
var orderPremium = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && d.premium && !d.lastAdmin && !d.closed;
}).sort(sort);
var orderNormal = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && !d.premium && !d.lastAdmin && !d.closed;
}).sort(sort);
var orderClosed = Object.keys(hashesById).filter(function (id) {
var d = getTicketData(id);
return d && d.closed;
}).sort(sort);
var cols = [$col1, $col2, $col3, $col4];
[orderPremium, orderNormal, orderAnswered, orderClosed].forEach(function (list, j) {
list.forEach(function (id, i) {
var $t = $div.find('[data-id="'+id+'"]');
var d = getTicketData(id);
$t.css('order', i).appendTo(cols[j]);
updateTicketDetails($t, d.premium);
});
if (!list.length) {
cols[j].hide();
} else {
cols[j].show();
cols[j].find('.cp-support-count').text(list.length);
}
});
};
var reorder = Util.throttle(_reorder, 150);
var to = Util.throttle(function () {
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+linkedId+'"]');
4 years ago
$ticket.addClass('cp-support-open');
$ticket[0].scrollIntoView();
linkedId = undefined;
}, 200);
// Register to the "support" mailbox
common.mailbox.subscribe(['supportadmin'], {
onMessage: function (data) {
/*
Get ID of the ticket
If we already have a div for this ID
Push the message to the end of the ticket
If it's a new ticket ID
Make a new div for this ID
*/
var msg = data.content.msg;
var hash = data.content.hash;
var content = msg.content;
var id = content.id;
var $ticket = $div.find('.cp-support-list-ticket[data-id="'+id+'"]');
hashesById[id] = hashesById[id] || [];
if (hashesById[id].indexOf(hash) === -1) {
hashesById[id].push(data);
}
if (msg.type === 'CLOSE') {
// A ticket has been closed by the admins...
if (!$ticket.length) { return; }
$ticket.addClass('cp-support-list-closed');
$ticket.append(APP.support.makeCloseMessage(content, hash));
reorder();
return;
}
if (msg.type !== 'TICKET') { return; }
$ticket.removeClass('cp-support-list-closed');
if (!$ticket.length) {
$ticket = APP.support.makeTicket($div, content, function (hideButton) {
// the ticket will still be displayed until the worker confirms its deletion
// so make it unclickable in the meantime
hideButton.setAttribute('disabled', true);
var error = false;
5 years ago
nThen(function (w) {
hashesById[id].forEach(function (d) {
common.mailbox.dismiss(d, w(function (err) {
if (err) {
error = true;
console.error(err);
}
}));
});
5 years ago
}).nThen(function () {
if (!error) {
$ticket.remove();
delete hashesById[id];
reorder();
return;
}
// if deletion failed then reactivate the button and warn
hideButton.removeAttribute('disabled');
// and show a generic error message
UI.alert(Messages.error);
});
});
4 years ago
makeOpenButton($ticket);
if (category !== 'all' && $ticket.attr('data-cat') !== category) {
$ticket.hide();
}
}
$ticket.append(APP.support.makeMessage(content, hash));
reorder();
if (linkedId) { to(); }
}
});
return $container;
};
create['support-priv'] = function () {
if (!supportKey || !APP.privateKey || !checkAdminKey(APP.privateKey)) { return; }
var $div = makeBlock('support-priv', true); // Msg.admin_supportPrivHint, .admin_supportPrivTitle, .admin_supportPrivButton
var $button = $div.find('button').click(function () {
$button.remove();
var $selectable = $(UI.dialog.selectable(APP.privateKey)).css({ 'max-width': '28em' });
$div.append($selectable);
});
return $div;
};
create['support-init'] = function () {
var $div = makeBlock('support-init'); // Msg.admin_supportInitHint, .admin_supportInitTitle
if (!supportKey) {
(function () {
$div.append(h('p', Messages.admin_supportInitHelp));
var button = h('button.btn.btn-primary', Messages.admin_supportInitGenerate);
var $button = $(button).appendTo($div);
$div.append($button);
var spinner = UI.makeSpinner($div);
$button.click(function () {
spinner.spin();
$button.attr('disabled', 'disabled');
var keyPair = Nacl.box.keyPair();
var pub = Nacl.util.encodeBase64(keyPair.publicKey);
var priv = Nacl.util.encodeBase64(keyPair.secretKey);
// Store the private key first. It won't be used until the decree is accepted.
sFrameChan.query("Q_ADMIN_MAILBOX", priv, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
UI.warn(Messages.error);
spinner.hide();
return;
}
// Then send the decree
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SUPPORT_MAILBOX', [pub]]
}, function (e, response) {
$button.removeAttr('disabled');
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
spinner.hide();
return;
}
spinner.done();
UI.log(Messages.saved);
supportKey = pub;
APP.privateKey = priv;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
});
})();
return $div;
}
if (!APP.privateKey || !checkAdminKey(APP.privateKey)) {
5 years ago
$div.append(h('p', Messages.admin_supportInitPrivate));
var error = h('div.cp-admin-support-error');
var input = h('input.cp-admin-add-private-key');
5 years ago
var button = h('button.btn.btn-primary', Messages.admin_supportAddKey);
if (APP.privateKey && !checkAdminKey(APP.privateKey)) {
5 years ago
$(error).text(Messages.admin_supportAddError);
}
$div.append(h('div', [
error,
input,
button
]));
$(button).click(function () {
var key = $(input).val();
if (!checkAdminKey(key)) {
$(input).val('');
5 years ago
return void $(error).text(Messages.admin_supportAddError);
}
sFrameChan.query("Q_ADMIN_MAILBOX", key, function () {
APP.privateKey = key;
$('.cp-admin-support-init').hide();
5 years ago
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
return $div;
}
return;
};
4 years ago
var getApi = function (cb) {
return function () {
require(['/api/broadcast?'+ (+new Date())], function (Broadcast) {
cb(Broadcast);
setTimeout(function () {
try {
var ctx = require.s.contexts._;
var defined = ctx.defined;
Object.keys(defined).forEach(function (href) {
if (/^\/api\/broadcast\?[0-9]{13}/.test(href)) {
delete defined[href];
return;
}
});
} catch (e) {}
});
});
};
4 years ago
};
4 years ago
// Update the lastBroadcastHash in /api/broadcast if we can do it.
// To do so, find the last "BROADCAST_CUSTOM" in the current history and use the previous
// message's hash.
// If the last BROADCAST_CUSTOM has been deleted by an admin, we can use the most recent
// message's hash.
var checkLastBroadcastHash = function () {
var deleted = [];
4 years ago
require(['/api/broadcast?'+ (+new Date())], function (BCast) {
var hash = BCast.lastBroadcastHash || '1'; // Truthy value if no lastKnownHash
common.mailbox.getNotificationsHistory('broadcast', null, hash, function (e, msgs) {
if (e) { return void console.error(e); }
4 years ago
// No history, nothing to change
if (!Array.isArray(msgs)) { return; }
if (!msgs.length) { return; }
4 years ago
var lastHash;
var next = false;
4 years ago
// Start from the most recent messages until you find a CUSTOM message and
// check if it has been deleted
msgs.reverse().some(function (data) {
var c = data.content;
4 years ago
// This is the hash we want to keep
if (next) {
if (!c || !c.hash) { return; }
lastHash = c.hash;
next = false;
return true;
}
4 years ago
// initialize with the most recent hash
if (!lastHash && c && c.hash) { lastHash = c.hash; }
4 years ago
var msg = c && c.msg;
if (!msg) { return; }
4 years ago
// Remember all deleted messages
if (msg.type === "BROADCAST_DELETE") {
deleted.push(Util.find(msg, ['content', 'uid']));
}
4 years ago
// Only check custom messages
if (msg.type !== "BROADCAST_CUSTOM") { return; }
4 years ago
// If the most recent CUSTOM message has been deleted, it means we don't
// need to keep any message and we can continue with lastHash as the most
// recent broadcast message.
if (deleted.indexOf(msg.uid) !== -1) { return true; }
4 years ago
// We just found the oldest message we want to keep, move one iteration
// further into the loop to get the next message's hash.
// If this is the end of the loop, don't bump lastBroadcastHash at all.
next = true;
});
4 years ago
// If we don't have to bump our lastBroadcastHash, abort
if (next) { return; }
4 years ago
// Otherwise, bump to lastHash
console.warn('Updating last broadcast hash to', lastHash);
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [lastHash]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
4 years ago
return;
}
console.log('lastBroadcastHash updated');
});
});
});
4 years ago
};
4 years ago
create['broadcast'] = function () {
var key = 'broadcast';
var $div = makeBlock(key); // Msg.admin_broadcastHint, admin_broadcastTitle
4 years ago
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
var refresh = getApi(function (Broadcast) {
4 years ago
var button = h('button.btn.btn-primary', Messages.admin_broadcastButton);
var $button = $(button);
var removeButton = h('button.btn.btn-danger', Messages.admin_broadcastCancel);
var active = h('div.cp-broadcast-active', h('p', Messages.admin_broadcastActive));
var $active = $(active);
var activeUid;
var deleted = [];
// Render active message (if there is one)
var hash = Broadcast.lastBroadcastHash || '1'; // Truthy value if no lastKnownHash
common.mailbox.getNotificationsHistory('broadcast', null, hash, function (e, msgs) {
if (e) { return void console.error(e); }
if (!Array.isArray(msgs)) { return; }
if (!msgs.length) {
$active.hide();
}
msgs.reverse().some(function (data) {
var c = data.content;
var msg = c && c.msg;
if (!msg) { return; }
if (msg.type === "BROADCAST_DELETE") {
deleted.push(Util.find(msg, ['content', 'uid']));
4 years ago
}
if (msg.type !== "BROADCAST_CUSTOM") { return; }
if (deleted.indexOf(msg.uid) !== -1) { return true; }
// We found an active custom message, show it
var el = common.mailbox.createElement(data);
var table = h('table.cp-broadcast-delete');
var $table = $(table);
var uid = Util.find(data, ['content', 'msg', 'uid']);
var time = Util.find(data, ['content', 'msg', 'content', 'time']);
var tr = h('tr', { 'data-uid': uid }, [
h('td', 'ID: '+uid),
h('td', new Date(time || 0).toLocaleString()),
h('td', el),
h('td.delete', removeButton),
]);
$table.append(tr);
$active.append(table);
activeUid = uid;
return true;
4 years ago
});
if (!activeUid) { $active.hide(); }
4 years ago
});
4 years ago
// Custom message
var container = h('div.cp-broadcast-container');
var $container = $(container);
var languages = Messages._languages;
var keys = Object.keys(languages).sort();
// Always keep the textarea ordered by language code
var reorder = function () {
$container.find('.cp-broadcast-lang').each(function (i, el) {
var $el = $(el);
var l = $el.attr('data-lang');
$el.css('order', keys.indexOf(l));
});
};
// Remove a textarea
var removeLang = function (l) {
$container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove();
var hasDefault = $container.find('.cp-broadcast-lang .cp-checkmark input:checked').length;
if (!hasDefault) {
$container.find('.cp-broadcast-lang').first().find('.cp-checkmark input').prop('checked', 'checked');
}
4 years ago
};
4 years ago
var getData = function () { return false; };
var onPreview = function (l) {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
4 years ago
var msg = {
uid: Util.uid(),
type: 'BROADCAST_CUSTOM',
content: data
};
common.mailbox.onMessage({
lang: l,
type: 'broadcast',
content: {
msg: msg,
hash: 'LOCAL|' + JSON.stringify(msg).slice(0,58)
}
4 years ago
}, function () {
UI.log(Messages.saved);
});
};
// Add a textarea
var addLang = function (l) {
if ($container.find('.cp-broadcast-lang[data-lang="'+l+'"]').length) { return; }
var preview = h('button.btn.btn-secondary', Messages.broadcast_preview);
$(preview).click(function () {
onPreview(l);
});
var bcastDefault = Messages.broadcast_defaultLanguage;
var first = !$container.find('.cp-broadcast-lang').length;
var radio = UI.createRadio('broadcastDefault', null, bcastDefault, first, {
'data-lang': l,
label: {class: 'noTitle'}
});
$container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [
h('h4', languages[l]),
h('label', Messages.kanban_body),
h('textarea'),
radio,
preview
]));
reorder();
};
// Checkboxes to select translations
var boxes = keys.map(function (l) {
var $cbox = $(UI.createCheckbox('cp-broadcast-custom-lang-'+l,
languages[l], false, { label: { class: 'noTitle' } }));
var $check = $cbox.find('input').on('change', function () {
var c = $check.is(':checked');
if (c) { return void addLang(l); }
removeLang(l);
});
if (l === 'en') {
setTimeout(function () {
$check.click();
});
}
4 years ago
return $cbox[0];
});
// Extract form data
getData = function () {
var map = {};
var defaultLanguage;
var error = false;
$container.find('.cp-broadcast-lang').each(function (i, el) {
var $el = $(el);
var l = $el.attr('data-lang');
if (!l) { error = true; return; }
var text = $el.find('textarea').val();
if (!text.trim()) { error = true; return; }
if ($el.find('.cp-checkmark input').is(':checked')) {
defaultLanguage = l;
}
map[l] = text;
});
if (!Object.keys(map).length) {
console.error('You must select at least one language');
return false;
}
if (error) {
console.error('One of the selected languages has no data');
return false;
}
4 years ago
return {
defaultLanguage: defaultLanguage,
content: map
};
};
4 years ago
var send = function (data) {
$button.prop('disabled', 'disabled');
//data.time = +new Date(); // FIXME not used anymore?
4 years ago
common.mailbox.sendTo('BROADCAST_CUSTOM', data, {}, function (err) {
4 years ago
if (err) {
$button.prop('disabled', '');
console.error(err);
return UI.warn(Messages.error);
}
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
});
4 years ago
};
4 years ago
$button.click(function () {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
send(data);
});
UI.confirmButton(removeButton, {
classes: 'btn-danger',
}, function () {
4 years ago
if (!activeUid) { return; }
common.mailbox.sendTo('BROADCAST_DELETE', {
uid: activeUid
4 years ago
}, {}, function (err) {
4 years ago
if (err) { return UI.warn(Messages.error); }
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
});
});
4 years ago
// Make the form
$form.empty().append([
active,
h('label', Messages.broadcast_translations),
h('div.cp-broadcast-languages', boxes),
container,
h('div.cp-broadcast-form-submit', [
h('br'),
button
])
]);
});
4 years ago
refresh();
4 years ago
return $div;
};
4 years ago
create['maintenance'] = function () {
var key = 'maintenance';
var $div = makeBlock(key); // Msg.admin_maintenanceHint, admin_maintenanceTitle
4 years ago
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
4 years ago
var refresh = getApi(function (Broadcast) {
var button = h('button.btn.btn-primary', Messages.admin_maintenanceButton);
var $button = $(button);
var removeButton = h('button.btn.btn-danger', Messages.admin_maintenanceCancel);
var active;
if (Broadcast && Broadcast.maintenance) {
var m = Broadcast.maintenance;
if (m.start && m.end && m.end >= (+new Date())) {
active = h('div.cp-broadcast-active', [
UI.setHTML(h('p'), Messages._getKey('broadcast_maintenance', [
new Date(m.start).toLocaleString(),
new Date(m.end).toLocaleString(),
])),
removeButton
]);
}
}
4 years ago
// Start and end date pickers
var start = h('input');
var end = h('input');
var $start = $(start);
var $end = $(end);
4 years ago
var is24h = false;
var dateFormat = "Y-m-d H:i";
try {
is24h = !new Intl.DateTimeFormat(navigator.language, { hour: 'numeric' }).format(0).match(/AM/);
} catch (e) {}
if (!is24h) { dateFormat = "Y-m-d h:i K"; }
var endPickr = Flatpickr(end, {
4 years ago
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
4 years ago
minDate: new Date()
});
Flatpickr(start, {
enableTime: true,
time_24hr: is24h,
4 years ago
minDate: new Date(),
dateFormat: dateFormat,
4 years ago
onChange: function () {
endPickr.set('minDate', new Date($start.val()));
}
4 years ago
});
4 years ago
// Extract form data
var getData = function () {
var start = +new Date($start.val());
var end = +new Date($end.val());
if (isNaN(start) || isNaN(end)) {
console.error('Invalid dates');
return false;
}
return {
start: start,
end: end
};
};
var send = function (data) {
$button.prop('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
4 years ago
data: ['SET_MAINTENANCE', [data]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
4 years ago
$button.prop('disabled', '');
return;
}
4 years ago
// Maintenance applied, send notification
4 years ago
common.mailbox.sendTo('BROADCAST_MAINTENANCE', {}, {}, function () {
4 years ago
refresh();
checkLastBroadcastHash();
});
});
};
4 years ago
$button.click(function () {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
send(data);
});
UI.confirmButton(removeButton, {
classes: 'btn-danger',
}, function () {
4 years ago
send("");
});
4 years ago
$form.empty().append([
active,
h('label', Messages.broadcast_start),
start,
h('label', Messages.broadcast_end),
end,
h('br'),
h('div.cp-broadcast-form-submit', [
button
])
]);
});
refresh();
4 years ago
common.makeUniversal('broadcast', {
4 years ago
onEvent: function (obj) {
var cmd = obj.ev;
if (cmd !== "MAINTENANCE") { return; }
refresh();
}
});
4 years ago
return $div;
};
4 years ago
create['survey'] = function () {
var key = 'survey';
var $div = makeBlock(key); // Msg.admin_surveyHint, admin_surveyTitle
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
4 years ago
var refresh = getApi(function (Broadcast) {
var button = h('button.btn.btn-primary', Messages.admin_surveyButton);
var $button = $(button);
var removeButton = h('button.btn.btn-danger', Messages.admin_surveyCancel);
var active;
if (Broadcast && Broadcast.surveyURL) {
var a = h('a', {href: Broadcast.surveyURL}, Messages.admin_surveyActive);
$(a).click(function (e) {
e.preventDefault();
common.openUnsafeURL(Broadcast.surveyURL);
});
active = h('div.cp-broadcast-active', [
4 years ago
h('p', a),
4 years ago
removeButton
]);
}
4 years ago
// Survey form
var label = h('label', Messages.broadcast_surveyURL);
var input = h('input');
var $input = $(input);
// Extract form data
var getData = function () {
var url = $input.val();
if (!Util.isValidURL(url)) {
console.error('Invalid URL');
return false;
}
4 years ago
return url;
};
4 years ago
var send = function (data) {
$button.prop('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_SURVEY_URL', [data]]
}, function (e, response) {
if (e || response.error) {
4 years ago
$button.prop('disabled', '');
UI.warn(Messages.error);
console.error(e, response);
4 years ago
return;
}
// Maintenance applied, send notification
4 years ago
common.mailbox.sendTo('BROADCAST_SURVEY', {
url: data
4 years ago
}, {}, function () {
4 years ago
refresh();
checkLastBroadcastHash();
});
});
4 years ago
};
$button.click(function () {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
send(data);
});
UI.confirmButton(removeButton, {
classes: 'btn-danger',
}, function () {
send("");
});
4 years ago
$form.empty().append([
active,
label,
input,
h('br'),
h('div.cp-broadcast-form-submit', [
button
])
]);
});
4 years ago
refresh();
4 years ago
common.makeUniversal('broadcast', {
4 years ago
onEvent: function (obj) {
var cmd = obj.ev;
if (cmd !== "SURVEY") { return; }
refresh();
}
});
return $div;
};
var onRefreshPerformance = Util.mkEvent();
create['refresh-performance'] = function () {
var key = 'refresh-performance';
var btn = h('button.btn.btn-primary', Messages.oo_refresh);
var div = h('div.cp-admin-' + key + '.cp-sidebarlayout-element', btn);
$(btn).click(function () {
onRefreshPerformance.fire();
});
return $(div);
};
create['performance-profiling'] = function () {
var $div = makeBlock('performance-profiling'); // Msg.admin_performanceProfilingHint, .admin_performanceProfilingTitle
var onRefresh = function () {
var body = h('tbody');
var table = h('table#cp-performance-table', [
h('thead', [
h('th', Messages.admin_performanceKeyHeading),
h('th', Messages.admin_performanceTimeHeading),
h('th', Messages.admin_performancePercentHeading),
]),
body,
]);
var appendRow = function (key, time, percent) {
console.log("[%s] %ss running time (%s%)", key, time, percent);
body.appendChild(h('tr', [ key, time, percent ].map(function (x) {
return h('td', x);
})));
};
var process = function (_o) {
var o = _o[0];
var sorted = Object.keys(o).sort(function (a, b) {
if (o[b] - o[a] <= 0) { return -1; }
return 1;
});
var total = 0;
sorted.forEach(function (k) { total += o[k]; });
sorted.forEach(function (k) {
var percent = Math.floor((o[k] / total) * 1000) / 10;
appendRow(k, o[k], percent);
});
};
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'GET_WORKER_PROFILES',
}, function (e, data) {
if (e || data.error) {
UI.warn(Messages.error);
return void console.error(e, data);
}
//console.info(data);
$div.find("table").remove();
process(data);
$div.append(table);
});
};
onRefresh();
onRefreshPerformance.reg(onRefresh);
return $div;
};
Messages.admin_cat_network = 'Network'; // XXX
Messages.admin_updateAvailableTitle = 'New versions'; // XXX
Messages.admin_updateAvailableHint = "A new version of CryptPad is available."; // XXX
Messages.admin_updateAvailableButton = 'See release notes'; // XXX
create['update-available'] = function () {
if (!APP.instanceStatus.updateAvailable) { return; }
var $div = makeBlock('update-available', true);
var updateURL = 'https://github.com/xwiki-labs/cryptpad/releases/latest';
if (typeof(APP.instanceStatus.updateAvailable) === 'string') {
updateURL = APP.instanceStatus.updateAvailable;
}
$div.find('button').click(function () {
common.openURL(updateURL);
});
return $div;
};
Messages.admin_checkupTitle = 'Diagnose configuration errors'; // XXX
Messages.admin_checkupHint = "CryptPad includes a page which automatically diagnoses common configuration issues and suggests how to correct them."; // XXX
Messages.admin_checkupButton = 'Run diagnostics'; // XXX
create['checkup'] = function () {
var $div = makeBlock('checkup', true);
$div.find('button').click(function () {
common.openURL('/checkup/');
});
return $div;
};
Messages.admin_consentToContactTitle = 'Consent to contact'; // XXX
Messages.admin_consentToContactHint = "Server telemetry includes the admin contact email so that the developers can notify you of vulnerabilities in the softare. This will never be shared, sold, or used for marketing purposes. Consent to contact if you'd like to be informed of critical issues in your server."; // XXX
Messages.admin_consentToContactLabel = 'I consent'; // XXX
create['consent-to-contact'] = makeAdminCheckbox({
key: 'consent-to-contact',
getState: function () {
return APP.instanceStatus.consentToContact;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['CONSENT_TO_CONTACT', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.consentToContact);
});
});
},
});
Messages.admin_listMyInstanceTitle = 'List my instance in public directories'; // XXX
Messages.admin_listMyInstanceHint = 'If your instance is suitable for public use you may consent to be listed in web directories.'; // XXX
Messages.admin_listMyInstanceLabel = 'List this instance'; // XXX
create['list-my-instance'] = makeAdminCheckbox({ // XXX uncheck if server telemetry is disabled?
key: 'list-my-instance',
getState: function () {
return APP.instanceStatus.listMyInstance;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['LIST_MY_INSTANCE', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.listMyInstance);
});
});
},
});
Messages.admin_provideAggregateStatisticsTitle = 'Provide aggregated statistics'; // XXX
Messages.admin_provideAggregateStatisticsHint = 'You may opt-in to providing additional usage metrics to the developers, such as the approximate number of registered and daily users.'; // XXX
Messages.admin_provideAggregateStatisticsLabel = 'Provide aggregated data'; // XXX
create['provide-aggregate-statistics'] = makeAdminCheckbox({ // XXX uncheck if server telemetry is disabled?
key: 'provide-aggregate-statistics',
getState: function () {
return APP.instanceStatus.provideAggregateStatistics;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['PROVIDE_AGGREGATE_STATISTICS', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.provideAggregateStatistics);
});
});
},
});
Messages.admin_removeDonateButtonTitle = "Crowdfunding participation"; // XXX
Messages.admin_removeDonateButtonHint = "CryptPad's development is partially funded by public grants and donations. Advertizing our crowdfunding efforts on your instance help the developers to continue improving the platform for everybody, but you may disable these notices if you find them inappropriate."; // XXX
Messages.admin_removeDonateButtonLabel = 'Do not advertize crowdfunding campaigns'; // XXX
create['remove-donate-button'] = makeAdminCheckbox({
key: 'remove-donate-button',
getState: function () {
return APP.instanceStatus.removeDonateButton;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['REMOVE_DONATE_BUTTON', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.removeDonateButton);
});
});
},
});
Messages.admin_blockDailyCheckTitle = 'Opt-out of server telemetry'; // XXX
Messages.admin_blockDailyCheckHint = "CryptPad instances send a message to the developers' server when launched and once per day thereafter. This lets them keep track of how many servers are running which versions of the software. You can opt-out of this measurement below."; // XXX
Messages.admin_blockDailyCheckLabel = 'Disable server telemetry'; // XXX
/* // XXX
* By default, CryptPad contacts one of our servers once a day.
* This check-in will also send some very basic information about your instance including its
* version and the adminEmail so we can reach you if we are aware of a serious problem.
* We will never sell it or send you marketing mail.
*
* If you want to block this check-in and remain set 'blockDailyCheck' to true.
*/
create['block-daily-check'] = makeAdminCheckbox({
key: 'block-daily-check',
getState: function () {
return APP.instanceStatus.blockDailyCheck;
},
query: function (val, setState) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['BLOCK_DAILY_CHECK', [val]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
}
APP.updateStatus(function () {
setState(APP.instanceStatus.blockDailyCheck);
});
});
},
});
6 years ago
var hideCategories = function () {
APP.$rightside.find('> div').hide();
};
var showCategories = function (cat) {
hideCategories();
cat.forEach(function (c) {
APP.$rightside.find('.'+c).show();
});
};
var SIDEBAR_ICONS = {
general: 'fa fa-user-o',
stats: 'fa fa-line-chart',
quota: 'fa fa-hdd-o',
support: 'fa fa-life-ring',
broadcast: 'fa fa-bullhorn',
performance: 'fa fa-heartbeat',
network: 'fa fa-sitemap', // or fa-university ?
};
6 years ago
var createLeftside = function () {
var $categories = $('<div>', {'class': 'cp-sidebarlayout-categories'})
.appendTo(APP.$leftside);
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var active = privateData.category || 'general';
if (active.indexOf('-') !== -1) {
active = active.split('-')[0];
}
if (!categories[active]) { active = 'general'; }
6 years ago
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('<div>', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
var iconClass = SIDEBAR_ICONS[key];
if (iconClass) {
$category.append($('<span>', {'class': iconClass}));
}
6 years ago
if (key === active) {
$category.addClass('cp-leftside-active');
}
$category.click(function () {
if (!Array.isArray(categories[key]) && categories[key].onClick) {
categories[key].onClick();
return;
}
active = key;
common.setHash(key);
$categories.find('.cp-leftside-active').removeClass('cp-leftside-active');
$category.addClass('cp-leftside-active');
showCategories(categories[key]);
});
$category.append(Messages['admin_cat_'+key] || key);
});
showCategories(categories[active]);
};
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
6 years ago
var configTb = {
displayed: displayed,
sfCommon: common,
$container: APP.$toolbar,
pageTitle: Messages.adminPage || 'Admin',
metadataMgr: common.getMetadataMgr(),
};
APP.toolbar = Toolbar.create(configTb);
APP.toolbar.$rightside.hide();
};
var updateStatus = APP.updateStatus = function (cb) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'INSTANCE_STATUS',
}, function (e, data) {
if (e) { console.error(e); return void cb(e); }
if (!Array.isArray(data)) { return void cb('EINVAL'); }
APP.instanceStatus = data[0];
console.log("Status", APP.instanceStatus);
cb();
});
};
6 years ago
nThen(function (waitFor) {
$(waitFor(UI.addLoadingScreen));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
APP.$container = $('#cp-sidebarlayout-container');
APP.$toolbar = $('#cp-toolbar');
APP.$leftside = $('<div>', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
APP.$rightside = $('<div>', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
sFrameChan = common.getSframeChannel();
sFrameChan.onReady(waitFor());
}).nThen(function (waitFor) {
updateStatus(waitFor());
6 years ago
}).nThen(function (/*waitFor*/) {
createToolbar();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
common.setTabTitle(Messages.adminPage || 'Administration');
if (!common.isAdmin()) {
6 years ago
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
}
APP.privateKey = privateData.supportPrivateKey;
6 years ago
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
APP.support = Support.create(common, true);
6 years ago
6 years ago
// Content
var $rightside = APP.$rightside;
var addItem = function (cssClass) {
var item = cssClass.slice(9); // remove 'cp-settings-'
if (typeof (create[item]) === "function") {
$rightside.append(create[item]());
}
};
for (var cat in categories) {
if (!Array.isArray(categories[cat])) { continue; }
categories[cat].forEach(addItem);
}
createLeftside();
UI.removeLoadingScreen();
});
});