', {'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;
};
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);
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);
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;
};
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);
return $div;
};
create['disk-usage'] = function () {
var key = 'disk-usage';
var $div = makeBlock(key, true); // Msg.admin_diskUsageHint, .admin_diskUsageTitle, .admin_diskUsageButton
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)
]));
var $col1 = $(col1), $col2 = $(col2), $col3 = $(col3), $col4 = $(col4);
$div.append([
//catContainer
col1,
col2,
col3,
col4
]);
$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 = {};
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']);
});
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');
});
};
var makeOpenButton = function ($ticket) {
var button = h('button.btn.btn-primary.cp-support-expand', Messages.admin_support_open);
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) {
$ticket.toggleClass('cp-support-open', false);
e.stopPropagation();
setTimeout(function () {
addClickHandler($ticket);
});
});
$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)
])
]);
};
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 () {
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+'"]');
$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;
nThen(function (w) {
hashesById[id].forEach(function (d) {
common.mailbox.dismiss(d, w(function (err) {
if (err) {
error = true;
console.error(err);
}
}));
});
}).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);
});
});
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)) {
$div.append(h('p', Messages.admin_supportInitPrivate));
var error = h('div.cp-admin-support-error');
var input = h('input.cp-admin-add-private-key');
var button = h('button.btn.btn-primary', Messages.admin_supportAddKey);
if (APP.privateKey && !checkAdminKey(APP.privateKey)) {
$(error).text(Messages.admin_supportAddError);
}
$div.append(h('div', [
error,
input,
button
]));
$(button).click(function () {
var key = $(input).val();
if (!checkAdminKey(key)) {
$(input).val('');
return void $(error).text(Messages.admin_supportAddError);
}
sFrameChan.query("Q_ADMIN_MAILBOX", key, function () {
APP.privateKey = key;
$('.cp-admin-support-init').hide();
APP.$rightside.append(create['support-list']());
APP.$rightside.append(create['support-priv']());
});
});
return $div;
}
return;
};
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) {}
});
});
};
};
// 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 = [];
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); }
// No history, nothing to change
if (!Array.isArray(msgs)) { return; }
if (!msgs.length) { return; }
var lastHash;
var next = false;
// 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;
// This is the hash we want to keep
if (next) {
if (!c || !c.hash) { return; }
lastHash = c.hash;
next = false;
return true;
}
// initialize with the most recent hash
if (!lastHash && c && c.hash) { lastHash = c.hash; }
var msg = c && c.msg;
if (!msg) { return; }
// Remember all deleted messages
if (msg.type === "BROADCAST_DELETE") {
deleted.push(Util.find(msg, ['content', 'uid']));
}
// Only check custom messages
if (msg.type !== "BROADCAST_CUSTOM") { return; }
// 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; }
// 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;
});
// If we don't have to bump our lastBroadcastHash, abort
if (next) { return; }
// 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);
return;
}
console.log('lastBroadcastHash updated');
});
});
});
};
create['broadcast'] = function () {
var key = 'broadcast';
var $div = makeBlock(key); // Msg.admin_broadcastHint, admin_broadcastTitle
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
var refresh = getApi(function (Broadcast) {
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']));
}
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;
});
if (!activeUid) { $active.hide(); }
});
// 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');
}
};
var getData = function () { return false; };
var onPreview = function (l) {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
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)
}
}, 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();
});
}
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;
}
return {
defaultLanguage: defaultLanguage,
content: map
};
};
var send = function (data) {
$button.prop('disabled', 'disabled');
//data.time = +new Date(); // FIXME not used anymore?
common.mailbox.sendTo('BROADCAST_CUSTOM', data, {}, function (err) {
if (err) {
$button.prop('disabled', '');
console.error(err);
return UI.warn(Messages.error);
}
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
});
};
$button.click(function () {
var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
send(data);
});
UI.confirmButton(removeButton, {
classes: 'btn-danger',
}, function () {
if (!activeUid) { return; }
common.mailbox.sendTo('BROADCAST_DELETE', {
uid: activeUid
}, {}, function (err) {
if (err) { return UI.warn(Messages.error); }
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
});
});
// 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
])
]);
});
refresh();
return $div;
};
create['maintenance'] = function () {
var key = 'maintenance';
var $div = makeBlock(key); // Msg.admin_maintenanceHint, admin_maintenanceTitle
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
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
]);
}
}
// Start and end date pickers
var start = h('input');
var end = h('input');
var $start = $(start);
var $end = $(end);
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, {
enableTime: true,
time_24hr: is24h,
dateFormat: dateFormat,
minDate: new Date()
});
Flatpickr(start, {
enableTime: true,
time_24hr: is24h,
minDate: new Date(),
dateFormat: dateFormat,
onChange: function () {
endPickr.set('minDate', new Date($start.val()));
}
});
// 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',
data: ['SET_MAINTENANCE', [data]]
}, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
console.error(e, response);
$button.prop('disabled', '');
return;
}
// Maintenance applied, send notification
common.mailbox.sendTo('BROADCAST_MAINTENANCE', {}, {}, function () {
refresh();
checkLastBroadcastHash();
});
});
};
$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("");
});
$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();
common.makeUniversal('broadcast', {
onEvent: function (obj) {
var cmd = obj.ev;
if (cmd !== "MAINTENANCE") { return; }
refresh();
}
});
return $div;
};
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);
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', [
h('p', a),
removeButton
]);
}
// 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', url);
return false;
}
return url;
};
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) {
$button.prop('disabled', '');
UI.warn(Messages.error);
console.error(e, response);
return;
}
// Maintenance applied, send notification
common.mailbox.sendTo('BROADCAST_SURVEY', {
url: data
}, {}, function () {
refresh();
checkLastBroadcastHash();
});
});
};
$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("");
});
$form.empty().append([
active,
label,
input,
h('br'),
h('div.cp-broadcast-form-submit', [
button
])
]);
});
refresh();
common.makeUniversal('broadcast', {
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;
};
create['update-available'] = function () { // Messages.admin_updateAvailableTitle.admin_updateAvailableHint.admin_updateAvailableLabel.admin_updateAvailableButton
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;
};
create['checkup'] = function () {
var $div = makeBlock('checkup', true); // Messages.admin_checkupButton.admin_checkupHint.admin_checkupTitle
$div.find('button').click(function () {
common.openURL('/checkup/');
});
return $div;
};
create['consent-to-contact'] = makeAdminCheckbox({ // Messages.admin_consentToContactTitle.admin_consentToContactHint.admin_consentToContactLabel
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);
});
});
},
});
create['list-my-instance'] = makeAdminCheckbox({ // Messages.admin_listMyInstanceTitle.admin_listMyInstanceHint.admin_listMyInstanceLabel
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);
});
});
},
});
create['provide-aggregate-statistics'] = makeAdminCheckbox({ // Messages.admin_provideAggregateStatisticsTitle.admin_provideAggregateStatisticsHint.admin_provideAggregateStatisticsLabel
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);
});
});
},
});
create['remove-donate-button'] = makeAdminCheckbox({ // Messages.admin_removeDonateButtonTitle.admin_removeDonateButtonHint.admin_removeDonateButtonLabel
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);
});
});
},
});
create['block-daily-check'] = makeAdminCheckbox({ // Messages.admin_blockDailyCheckTitle.admin_blockDailyCheckHint.admin_blockDailyCheckLabel
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);
});
});
},
});
var sendDecree = function (data, cb) {
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: data,
}, cb);
};
create['instance-purpose'] = function () {
var key = 'instance-purpose';
var $div = makeBlock(key); // Messages.admin_instancePurposeTitle.admin_instancePurposeHint
var values = [
'noanswer', // Messages.admin_purpose_noanswer
'experiment', // Messages.admin_purpose_experiment
'personal', // Messages.admin_purpose_personal
'education', // Messages.admin_purpose_education
'org', // Messages.admin_purpose_org
'business', // Messages.admin_purpose_business
'public', // Messages.admin_purpose_public
];
var defaultPurpose = 'noanswer';
var purpose = APP.instanceStatus.instancePurpose || defaultPurpose;
var opts = h('div.cp-admin-radio-container', [
values.map(function (key) {
var full_key = 'admin_purpose_' + key;
return UI.createRadio('cp-instance-purpose-radio', 'cp-instance-purpose-radio-'+key,
Messages[full_key] || Messages._getKey(full_key, [defaultPurpose]),
key === purpose, {
input: { value: key },
label: { class: 'noTitle' }
});
})
]);
var $opts = $(opts);
//var $br = $(h('br',));
//$div.append($br);
$div.append(opts);
var setPurpose = function (value, cb) {
sendDecree([
'SET_INSTANCE_PURPOSE',
[ value]
], cb);
};
$opts.on('change', function () {
var val = $opts.find('input:radio:checked').val();
console.log(val);
//spinner.spin();
setPurpose(val, function (e, response) {
if (e || response.error) {
UI.warn(Messages.error);
//spinner.hide();
return;
}
//spinner.done();
UI.log(Messages.saved);
});
});
return $div;
};
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 ?
};
var createLeftside = function () {
var $categories = $('
', {'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'; }
common.setHash(active);
Object.keys(categories).forEach(function (key) {
var $category = $('
', {'class': 'cp-sidebarlayout-category'}).appendTo($categories);
var iconClass = SIDEBAR_ICONS[key];
if (iconClass) {
$category.append($('
', {'class': iconClass}));
}
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'];
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();
});
};
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 = $('', {id: 'cp-sidebarlayout-leftside'}).appendTo(APP.$container);
APP.$rightside = $('
', {id: 'cp-sidebarlayout-rightside'}).appendTo(APP.$container);
sFrameChan = common.getSframeChannel();
sFrameChan.onReady(waitFor());
}).nThen(function (waitFor) {
updateStatus(waitFor());
}).nThen(function (/*waitFor*/) {
createToolbar();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
common.setTabTitle(Messages.adminPage || 'Administration');
if (!common.isAdmin()) {
return void UI.errorLoadingScreen(Messages.admin_authError || '403 Forbidden');
}
APP.privateKey = privateData.supportPrivateKey;
APP.origin = privateData.origin;
APP.readOnly = privateData.readOnly;
APP.support = Support.create(common, true);
// 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();
});
});