Broadcast update

pull/1/head
yflory 4 years ago
parent 08073f4853
commit 8f679c141c

@ -400,7 +400,7 @@
button { button {
.toolbar_button; .toolbar_button;
&.cp-notifications-bell { &.cp-notifications-bell, &.cp-maintenance-wrench {
color: @cryptpad_text_col; color: @cryptpad_text_col;
} }
} }
@ -506,7 +506,7 @@
} }
.cp-toolbar-user { .cp-toolbar-user {
height: @toolbar_line-height; height: @toolbar_line-height;
.cp-toolbar-notifications { .cp-toolbar-notifications, .cp-toolbar-maintenance {
height: @toolbar_line-height; height: @toolbar_line-height;
width: @toolbar_line-height; width: @toolbar_line-height;
margin-left: 0; margin-left: 0;
@ -709,7 +709,7 @@
height: 43px; height: 43px;
} }
} }
.cp-toolbar-link, .cp-toolbar-notifications { .cp-toolbar-link, .cp-toolbar-notifications, .cp-toolbar-maintenance {
line-height: @toolbar_top-height; line-height: @toolbar_top-height;
width: @toolbar_top-height; width: @toolbar_top-height;
height: @toolbar_top-height; height: @toolbar_top-height;
@ -717,7 +717,7 @@
box-sizing: border-box; box-sizing: border-box;
display: inline-block; display: inline-block;
} }
.cp-toolbar-notifications { .cp-toolbar-notifications, .cp-toolbar-maintenance {
text-align: center; text-align: center;
font-size: 32px; font-size: 32px;
margin-left: 10px; margin-left: 10px;

@ -128,10 +128,13 @@ commands.SET_ACCOUNT_RETENTION_TIME = makeIntegerSetter('accountRetentionTime');
var args_isString = function (args) { var args_isString = function (args) {
return Array.isArray(args) && typeof(args[0]) === "string"; return Array.isArray(args) && typeof(args[0]) === "string";
}; };
var args_isMaintenance = function (args) {
return Array.isArray(args) && args[0] && args[0].end && args[0].start;
};
var makeBroadcastSetter = function (attr) { var makeBroadcastSetter = function (attr) {
return function (Env, args) { return function (Env, args) {
if (!args_isString(args)) { if (!args_isString(args) && !args_isMaintenance(args)) {
throw new Error('INVALID_ARGS'); throw new Error('INVALID_ARGS');
} }
var str = args[0]; var str = args[0];
@ -148,6 +151,10 @@ commands.SET_LAST_BROADCAST_HASH = makeBroadcastSetter('lastBroadcastHash');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SURVEY_URL', [url]]], console.log) // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SURVEY_URL', [url]]], console.log)
commands.SET_SURVEY_URL = makeBroadcastSetter('surveyURL'); commands.SET_SURVEY_URL = makeBroadcastSetter('surveyURL');
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAINTENANCE', [{start: +Date, end: +Date}]]], console.log)
// CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAINTENANCE', [""]]], console.log)
commands.SET_MAINTENANCE = makeBroadcastSetter('maintenance');
var Quota = require("./commands/quota"); var Quota = require("./commands/quota");
var Keys = require("./keys"); var Keys = require("./keys");
var Util = require("./common-util"); var Util = require("./common-util");

@ -70,6 +70,7 @@ module.exports.create = function (config) {
// /api/broadcast // /api/broadcast
lastBroadcastHash: '', lastBroadcastHash: '',
surveyURL: undefined, surveyURL: undefined,
maintenance: undefined,
netfluxUsers: {}, netfluxUsers: {},

@ -279,11 +279,16 @@ var serveBroadcast = (function () {
}; };
var template = function (host) { var template = function (host) {
var maintenance = Env.maintenance;
if (maintenance && maintenance.end && maintenance.end < (+new Date())) {
maintenance = undefined;
}
return [ return [
'define(function(){', 'define(function(){',
'return ' + JSON.stringify({ 'return ' + JSON.stringify({
lastBroadcastHash: Env.lastBroadcastHash, lastBroadcastHash: Env.lastBroadcastHash,
surveyURL: Env.surveyURL surveyURL: Env.surveyURL,
maintenance: maintenance
}, null, '\t'), }, null, '\t'),
'});' '});'
].join(';\n') ].join(';\n')

@ -200,10 +200,20 @@
} }
.cp-admin-broadcast-form { .cp-admin-broadcast-form {
margin-top: 30px;
input.flatpickr-input { input.flatpickr-input {
width: 307.875px !important; // same width as flatpickr calendar width: 307.875px !important; // same width as flatpickr calendar
} }
.cp-broadcast-active {
display: flex;
flex-flow: column;
align-items: start;
padding: 10px;
background-color: @cp_sidebar-left-bg;
color: @cp_sidebar-left-fg;
p {
margin: 0;
}
}
.cp-broadcast-form-submit { .cp-broadcast-form-submit {
margin-top: 30px; margin-top: 30px;
button { button {

@ -73,8 +73,9 @@ define([
'cp-admin-support-list', 'cp-admin-support-list',
'cp-admin-support-init' 'cp-admin-support-init'
], ],
'broadcast': [ // Msg.admin_cat_support 'broadcast': [ // Msg.admin_cat_broadcast
'cp-admin-broadcast-delete', 'cp-admin-maintenance',
'cp-admin-survey',
'cp-admin-broadcast', 'cp-admin-broadcast',
], ],
'performance': [ // Msg.admin_cat_performance 'performance': [ // Msg.admin_cat_performance
@ -941,38 +942,174 @@ define([
}; };
Messages.admin_cat_broadcast = "Broadcast"; // XXX Messages.admin_cat_broadcast = "Broadcast"; // XXX
// Messages.admin_broadcastHint // XXX
// Messages.admin_broadcastTitle // XXX Messages.admin_maintenanceTitle = "Maintenance"; // XXX
Messages.admin_maintenanceHint = "Plan, remove or update a maintenance. You can only have one active maintenance at a time."; // XXX
//Messages.admin_broadcastDeleteHint // XXX Messages.admin_maintenanceButton = "Plan maintenance"; // XXX
//Messages.admin_broadcastDeleteTitle // XXX Messages.admin_maintenanceCancel = "Cancel planned maintenance"; // XXX
Messages.broadcast_new = "New message";
Messages.broadcast_maintenance = 'maintenance';// XXX
Messages.broadcast_survey = 'survey'; // XXX
Messages.broadcast_version = 'version'; // XXX
Messages.broadcast_custom = 'custom'; // XXX
Messages.broadcast_delete = 'delete'; // XXX
Messages.broadcast_newVersionReload = 'Force a worker reload on all clients'; // XXX
Messages.broadcast_surveyURL = 'Survey URL';
Messages.broadcast_translations = 'Translations';
Messages.broadcast_defaultLanguage = 'Fallback to this language (optional)';
Messages.broadcast_start = 'Start time'; Messages.broadcast_start = 'Start time';
Messages.broadcast_end = 'End time'; Messages.broadcast_end = 'End time';
Messages.admin_surveyTitle = "Survey"; // XXX
Messages.admin_surveyHint = "Add, update or remove the active survey accessible from the user menu"; // XXX
Messages.admin_surveyButton = "Apply survey"; // XXX
Messages.admin_surveyCancel = "Cancel active survey"; // XXX
Messages.admin_surveyActive = "View the active survey"; // XXX
Messages.broadcast_surveyURL = 'Survey URL';
Messages.admin_broadcastTitle = "Broadcast a message"; // XXX
Messages.admin_broadcastHint = "Send a message to all the existing and future users as a notification"; // XXX
Messages.admin_broadcastButton = "Send"; // XXX
Messages.admin_broadcastActive = "Active message"; // XXX
Messages.admin_broadcastCancel = "Delete active message"; // XXX
Messages.broadcast_translations = 'Translations';
Messages.broadcast_defaultLanguage = 'Fallback to this language';
Messages.broadcast_preview = "Preview in a fake notification"; Messages.broadcast_preview = "Preview in a fake notification";
Messages.broadcast_deleteBtn = "Delete for all";
Messages.broadcast_clear = "Clear all for everybody"; var getApi = function (cb) {
Messages.expired = "Expired"; return function () {
Messages.broadcast_empty = "No active message"; require(['/api/broadcast?'+ (+new Date())], function (Broadcast) {
Messages.broadcast_noFallback = "Don't fallback to a default language"; cb(Broadcast);
});
var onRefreshBroadcast = Util.mkEvent(); };
var broadcast = { };
getData: function () { return false; },
reset: function () {}, // Update the lastBroadcastHash in /api/broadcast if we can do it.
handlers: {} // To do so, find the last "BROADCAST_CUSTOM" in the current history and use the previous
}; // message's hash.
broadcast.handlers.custom = function ($form, button, send, preview, onPreview) { // 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) {
if (e) {
console.error(e);
return;
}
console.log('lastBroadcastHash updated');
});
});
});
};
create['broadcast'] = function () {
var key = 'broadcast';
var $div = makeBlock(key);
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)
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); }
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 // Custom message
var container = h('div.cp-broadcast-container'); var container = h('div.cp-broadcast-container');
var $container = $(container); var $container = $(container);
@ -988,22 +1125,36 @@ define([
}); });
}; };
var noFallbackBtn = h('button.btn.btn-secondary.cp-broadcast-preview', // Remove a textarea
Messages.broadcast_noFallback); var removeLang = function (l) {
var $noFallbackBtn = $(noFallbackBtn); $container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove();
var checkFallbackBtn = function () {
var hasDefault = $container.find('.cp-broadcast-lang .cp-checkmark input:checked').length; var hasDefault = $container.find('.cp-broadcast-lang .cp-checkmark input:checked').length;
if (hasDefault) { if (!hasDefault) {
$noFallbackBtn.css('visibility', ''); $container.find('.cp-broadcast-lang').first().find('.cp-checkmark input').prop('checked', 'checked');
} else {
$noFallbackBtn.css('visibility', 'hidden');
} }
}; };
// Remove a textarea var getData = function () { return false; };
var removeLang = function (l) { var onPreview = function (l) {
$container.find('.cp-broadcast-lang[data-lang="'+l+'"]').remove(); var data = getData();
checkFallbackBtn(); 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 // Add a textarea
@ -1019,9 +1170,6 @@ define([
'data-lang': l, 'data-lang': l,
label: {class: 'noTitle'} label: {class: 'noTitle'}
}); });
$(radio).find('input').on('change', function () {
checkFallbackBtn();
});
$container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [ $container.append(h('div.cp-broadcast-lang', { 'data-lang': l }, [
h('h4', languages[l]), h('h4', languages[l]),
h('label', Messages.kanban_body), h('label', Messages.kanban_body),
@ -1029,7 +1177,6 @@ define([
radio, radio,
preview preview
])); ]));
checkFallbackBtn();
reorder(); reorder();
}; };
@ -1051,7 +1198,7 @@ define([
}); });
// Extract form data // Extract form data
broadcast.getData = function () { getData = function () {
var map = {}; var map = {};
var defaultLanguage; var defaultLanguage;
var error = false; var error = false;
@ -1079,33 +1226,85 @@ define([
content: map content: map
}; };
}; };
// Clear all the textarea when sent
broadcast.reset = function () { var send = function (data) {
$container.find('.cp-broadcast-lang textarea').each(function (i, el) { $button.prop('disabled', 'disabled');
$(el).val(''); data.time = +new Date();
common.mailbox.sendTo('BROADCAST_CUSTOM', data, {}, function (err, data) {
if (err) {
$button.prop('disabled', '');
console.error(err);
return UI.warn(Messages.error);
}
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
}); });
}; };
// "Don't fallback to a default language" button $button.click(function () {
$noFallbackBtn.click(function () { var data = getData();
$container.find('.cp-checkmark input').prop('checked', false); if (data === false) { return void UI.warn(Messages.error); }
$noFallbackBtn.css('visibility', 'hidden'); send(data);
});
UI.confirmButton(removeButton, {
classes: 'btn-danger',
}, function () {
if (!activeUid) { return; }
common.mailbox.sendTo('BROADCAST_DELETE', {
uid: activeUid
}, {}, function (err, data) {
if (err) { return UI.warn(Messages.error); }
UI.log(Messages.saved);
refresh();
checkLastBroadcastHash();
});
}); });
// Make the form // Make the form
$form.append([ $form.empty().append([
active,
h('label', Messages.broadcast_translations), h('label', Messages.broadcast_translations),
h('div.cp-broadcast-languages', boxes), h('div.cp-broadcast-languages', boxes),
container, container,
h('div.cp-broadcast-form-submit', [ h('div.cp-broadcast-form-submit', [
noFallbackBtn,
h('br'), h('br'),
button button
]) ])
]); ]);
});
refresh();
return $div;
}; };
broadcast.handlers.maintenance = function ($form, button, send, preview) {
// Maintenance message create['maintenance'] = function () {
var key = 'maintenance';
var $div = makeBlock(key);
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 // Start and end date pickers
var start = h('input'); var start = h('input');
@ -1125,7 +1324,7 @@ define([
}); });
// Extract form data // Extract form data
broadcast.getData = function () { var getData = function () {
var start = +new Date($start.val()); var start = +new Date($start.val());
var end = +new Date($end.val()); var end = +new Date($end.val());
if (isNaN(start) || isNaN(end)) { if (isNaN(start) || isNaN(end)) {
@ -1138,349 +1337,137 @@ define([
}; };
}; };
// Clear when sent var send = function (data) {
broadcast.reset = function () { $button.prop('disabled', 'disabled');
$start.val('');
$end.val('');
};
$form.append([
h('label', Messages.broadcast_start),
start,
h('label', Messages.broadcast_end),
end,
h('br'),
h('div.cp-broadcast-form-submit', [
button,
preview
])
]);
};
broadcast.handlers.version = function ($form, button, send, preview) {
// New version available message
// This checkbox can be used to trigger a fake "reconnect" event on the clients
// so that they can check api/config and reload the worker in case of a new version
var $cbox = $(UI.createCheckbox('cp-admin-version-reload',
Messages.broadcast_newVersionReload,
false, { label: { class: 'noTitle' } }));
var $checkbox = $cbox.find('input');
// Extract the data and make the form
broadcast.getData = function () {
return {
reload: $checkbox.is(':checked')
};
};
broadcast.reset = function () {
$checkbox[0].checked = false;
};
$form.append([
$cbox[0],
h('br'),
h('div.cp-broadcast-form-submit', [
button,
preview
])
]);
};
broadcast.handlers.survey = function ($form, button, send, preview) {
// New survey message
// TODO send different URLs for other languages?
var label = h('label', Messages.broadcast_surveyURL);
var input = h('input');
var $input = $(input);
broadcast.getData = function () {
var url = $input.val();
if (!Util.isValidURL(url)) {
console.error('Invalid URL');
return false;
}
return {
url: url
};
};
broadcast.reset = function () {
$input.val('');
};
$(button).off('click').click(function () {
var data = broadcast.getData();
var val = $input.val() || '';
// Invalid url: abort
// NOTE: empty strings are allowed to remove a surveyURL from the decrees
// XXX usability...
if (!data && val) { return void UI.warn(Messages.error); }
var url = data ? data.url : val;
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_SURVEY_URL', [url]] data: ['SET_MAINTENANCE', [data]]
}, function (e) { }, function (e) {
if (e) { if (e) {
UI.warn(Messages.error); console.error(e); UI.warn(Messages.error); console.error(e);
$button.prop('disabled', '');
return; return;
} }
if (!url) { return; } // Maintenance applied, send notification
send(); common.mailbox.sendTo('BROADCAST_MAINTENANCE', {}, {}, function (err, data) {
refresh();
checkLastBroadcastHash();
}); });
}); });
$form.append([
label,
input,
h('br'),
h('div.cp-broadcast-form-submit', [
button,
preview
])
]);
};
broadcast.handlers.delete = function ($form, button, send) {
require(['/api/broadcast?'+ (+new Date())], function (BCast) {
// Always display the messages from the instance "lastBroadcastHash"
var hash = BCast.lastBroadcastHash || '1'; // Truthy value if no lastKnownHash
common.mailbox.getNotificationsHistory('broadcast', null, hash, function (e, msgs) {
var table = h('table.cp-broadcast-delete');
var $table = $(table);
// Empty history
if (!msgs.length) {
$table.append(h('tr.empty', h('td', Messages.broadcast_empty)));
}
// Build the table
msgs.forEach(function (data) {
var el = common.mailbox.createElement(data);
var t = Util.find(data, ['content', 'msg', 'type']);
// A "DELETE" message is here to disable a previous line
if (t === 'BROADCAST_DELETE') {
var _uid = Util.find(data, ['content', 'msg', 'content', 'uid']);
var $button = $table.find('[data-uid="'+_uid+'"] td.delete button');
$button.prop('disabled', 'disabled').text(Messages.deleted);
return;
}
// Make the line
var uid = Util.find(data, ['content', 'msg', 'uid']);
var time = Util.find(data, ['content', 'msg', 'content', 'time']);
var deleteBtn = h('button.btn.btn-danger', Messages.broadcast_deleteBtn);
var tr = h('tr', { 'data-uid': uid }, [
h('td', 'ID: '+uid),
h('td', new Date(time || 0).toLocaleString()),
h('td', el),
h('td.delete', deleteBtn),
]);
// Auto-expire maintenance and survey messages
if (t === 'BROADCAST_MAINTENANCE') {
var end = Util.find(data, ['content', 'msg', 'content', 'end']);
if (end < +new Date()) {
$(deleteBtn).prop('disabled', 'disabled').text(Messages.expired);
}
}
if (t === 'BROADCAST_VERSION') {
$(deleteBtn).prop('disabled', 'disabled').text(Messages.expired);
}
// "Delete this message" button
UI.confirmButton(deleteBtn, {
classes: 'btn-danger',
multiple: true
}, function () {
broadcast.getData = function () {
if (!uid) { return false; }
return { uid: uid };
};
broadcast.reset = function () {
$(deleteBtn).prop('disabled', 'disabled').text(Messages.deleted);
}; };
send(); $button.click(function () {
}); var data = getData();
if (data === false) { return void UI.warn(Messages.error); }
$table.append(tr); send(data);
}); });
UI.confirmButton(removeButton, {
// Clear all button: remove all the messages and bump lastBroadcastHash
var clearAll = h('button.btn.btn-danger', Messages.broadcast_clear);
UI.confirmButton(clearAll, {
classes: 'btn-danger', classes: 'btn-danger',
multiple: true
}, function () { }, function () {
broadcast.getData = function () { send("");
return { all: true };
};
broadcast.reset = function () {};
// Send a message to all users telling them to wipe the broadcast mailbox
// and on success, send an admin decree to update /api/broadcast
send(function (err, obj) {
if (err) { return; }
if (!obj || !obj.hash) { return; }
/*
sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [obj.hash]]
}, function (e) {
if (e) {
UI.warn(Messages.error); console.error(e);
return;
}
// On success, reload the "delete" tab
onRefreshBroadcast.fire();
});
*/
});
}); });
$form.append([ $form.empty().append([
table, active,
msgs.length ? clearAll : undefined h('label', Messages.broadcast_start),
start,
h('label', Messages.broadcast_end),
end,
h('br'),
h('div.cp-broadcast-form-submit', [
button
])
]); ]);
}); });
}); refresh();
return $div;
}; };
create['survey'] = function () {
var key = 'survey';
var $div = makeBlock(key);
var getBroadcastForm = function ($form, key) { var form = h('div.cp-admin-broadcast-form');
$form.empty(); var $form = $(form).appendTo($div);
var button = h('button.btn.btn-primary', Messages.support_formButton); var refresh = getApi(function (Broadcast) {
var button = h('button.btn.btn-primary', Messages.admin_surveyButton);
var $button = $(button); var $button = $(button);
var removeButton = h('button.btn.btn-danger', Messages.admin_surveyCancel);
var send = function (_cb) { var active;
var cb = Util.once(_cb || function () {});
var data = broadcast.getData(); if (Broadcast && Broadcast.surveyURL) {
if (data === false) { var a = h('a', {href: Broadcast.surveyURL}, Messages.admin_surveyActive);
cb('NODATA'); $(a).click(function (e) {
return void UI.warn(Messages.error); e.preventDefault();
common.openUnsafeURL(Broadcast.surveyURL);
});
active = h('div.cp-broadcast-active', [
h('p', a),
removeButton
]);
} }
$button.prop('disabled', 'disabled');
data.time = +new Date(); // Survey form
common.mailbox.sendTo('BROADCAST_'+key.toUpperCase(), data, {}, function (err, data) { var label = h('label', Messages.broadcast_surveyURL);
$button.prop('disabled', ''); var input = h('input');
cb(err, data); var $input = $(input);
if (err) {
console.error(err); // Extract form data
return UI.warn(Messages.error); var getData = function () {
var url = $input.val();
if (!Util.isValidURL(url)) {
console.error('Invalid URL');
return false;
} }
return url;
};
var send = function (data) {
$button.prop('disabled', 'disabled');
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
cmd: 'ADMIN_DECREE', cmd: 'ADMIN_DECREE',
data: ['SET_LAST_BROADCAST_HASH', [data.hash]] data: ['SET_SURVEY_URL', [data]]
}, function (e) { }, function (e) {
if (e) { if (e) {
$button.prop('disabled', '');
UI.warn(Messages.error); console.error(e); UI.warn(Messages.error); console.error(e);
return; return;
} }
// On success, reload the "delete" tab // Maintenance applied, send notification
onRefreshBroadcast.fire(); common.mailbox.sendTo('BROADCAST_SURVEY', {}, {}, function (err, data) {
refresh();
checkLastBroadcastHash();
}); });
// Only print success if there is no callback
if (!_cb) {
UI.log(Messages.saved);
// Clear the UI
broadcast.reset();
onRefreshBroadcast.fire();
}
}); });
};
};
$button.click(function () { $button.click(function () {
send(); var data = getData();
});
var onPreview = function (l) {
var data = broadcast.getData();
if (data === false) { return void UI.warn(Messages.error); } if (data === false) { return void UI.warn(Messages.error); }
var msg = { send(data);
uid: Util.uid(),
type: 'BROADCAST_'+key.toUpperCase(),
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);
}); });
}; UI.confirmButton(removeButton, {
var preview = h('button.cp-broadcast-preview.btn.btn-secondary', Messages.broadcast_preview); classes: 'btn-danger',
$(preview).click(function () { }, function () {
onPreview(); send("");
}); });
$form.empty().append([
var handler = broadcast.handlers[key]; active,
if (!handler) { return; } // XXX label,
handler($form, button, send, preview, onPreview); input,
}; h('br'),
create['broadcast'] = function () { h('div.cp-broadcast-form-submit', [
var key = 'broadcast'; button
var $div = makeBlock(key); ])
]);
var form = h('div.cp-admin-broadcast-form');
var $select = $(h('div.cp-dropdown-container')).appendTo($div);
var $form = $(form).appendTo($div);
var categories = [
'maintenance',
'survey',
'version',
'custom',
];
// The "version" message only works if the instance is using a manual /api/config
// This is a custom setup for which our team won't provide support and is NOT
// recommended unless you know exactly what you're doing.
if (!AppConfig.customApiConfig) { categories.splice(2,1); }
categories = categories.map(function (key) {
return {
tag: 'a',
content: h('span', Messages['broadcast_'+key]),
action: function () {
getBroadcastForm($form, key);
}
};
}); });
var dropdownCfg = { refresh();
text: Messages.broadcast_new,
angleDown: 1,
options: categories,
container: $select,
isSelect: true,
buttonCls: 'btn btn-default'
};
UIElements.createDropdown(dropdownCfg);
return $div; return $div;
}; };
create['broadcast-delete'] = function () {
var key = 'broadcast-delete';
var $div = makeBlock(key);
var form = h('div.cp-admin-broadcast-form');
var $form = $(form).appendTo($div);
getBroadcastForm($form, 'delete');
onRefreshBroadcast.reg(function () {
getBroadcastForm($form, 'delete');
});
return $div;
};
var onRefreshPerformance = Util.mkEvent(); var onRefreshPerformance = Util.mkEvent();
create['refresh-performance'] = function () { create['refresh-performance'] = function () {

@ -1,6 +1,7 @@
define([ define([
'jquery', 'jquery',
'/api/config', '/api/config',
'/api/broadcast',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-language.js', '/common/common-language.js',
@ -17,7 +18,7 @@ define([
'/common/visible.js', '/common/visible.js',
'css!/customize/fonts/cptools/style.css', 'css!/customize/fonts/cptools/style.css',
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, ], function ($, Config, Broadcast, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard,
Messages, AppConfig, Pages, NThen, InviteInner, Visible) { Messages, AppConfig, Pages, NThen, InviteInner, Visible) {
var UIElements = {}; var UIElements = {};
var urlArgs = Config.requireConf.urlArgs; var urlArgs = Config.requireConf.urlArgs;
@ -1762,11 +1763,9 @@ define([
}); });
} }
// XXX Admin panel overrides AppConfig
// If you set "" in the admin panel, it will remove the AppConfig survey // If you set "" in the admin panel, it will remove the AppConfig survey
var surveyURL = typeof(Config.surveyURL) !== "undefined" ? Config.surveyURL var surveyURL = typeof(Broadcast.surveyURL) !== "undefined" ? Broadcast.surveyURL
: AppConfig.surveyURL; : AppConfig.surveyURL;
if (surveyURL) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
@ -1778,7 +1777,6 @@ define([
Feedback.send('SURVEY_CLICKED'); Feedback.send('SURVEY_CLICKED');
}, },
}); });
}
options.push({ tag: 'hr' }); options.push({ tag: 'hr' });
// Add login or logout button depending on the current status // Add login or logout button depending on the current status
@ -1845,6 +1843,24 @@ define([
}; };
var $userAdmin = UIElements.createDropdown(dropdownConfigUser); var $userAdmin = UIElements.createDropdown(dropdownConfigUser);
var $survey = $userAdmin.find('.cp-toolbar-survey');
if (!surveyURL) { $survey.hide(); }
Common.makeUniversal('broadcast', {
onEvent: function (obj) {
var cmd = obj.ev;
if (cmd !== "SURVEY") { return; }
var url = obj.data;
if (url === surveyURL) { return; }
if (url && !Util.isValidURL(url)) { return; }
surveyURL = url;
if (!url) {
$survey.hide();
return;
}
$survey.show();
}
});
/* /*
// Uncomment these lines to have a language selector in the admin menu // Uncomment these lines to have a language selector in the admin menu
// FIXME clicking on the inner menu hides the outer one // FIXME clicking on the inner menu hides the outer one

@ -422,32 +422,6 @@ define([
} }
}; };
Messages.broadcast_newMaintenance = "A maintenance is planned between <b>{0}</b> and <b>{1}</b>"; // XXX
handlers['BROADCAST_MAINTENANCE'] = function (common, data) {
var content = data.content;
var msg = content.msg.content;
content.getFormatText = function () {
return Messages._getKey('broadcast_newMaintenance', [
new Date(msg.start).toLocaleString(),
new Date(msg.end).toLocaleString(),
]);
};
if (!content.archived) {
content.dismissHandler = defaultDismiss(common, data);
}
};
Messages.broadcast_newVersion = "A new version is available. Reload the page to discover the new features!"; // XXX
handlers['BROADCAST_VERSION'] = function (common, data) {
var content = data.content;
content.getFormatText = function () {
return Messages.broadcast_newVersion;
};
if (!content.archived) {
content.dismissHandler = defaultDismiss(common, data);
}
};
Messages.broadcast_newCustom = "Message from the administrators"; // XXX Messages.broadcast_newCustom = "Message from the administrators"; // XXX
handlers['BROADCAST_CUSTOM'] = function (common, data) { handlers['BROADCAST_CUSTOM'] = function (common, data) {
var content = data.content; var content = data.content;

@ -626,6 +626,33 @@ define([
cb(JSON.parse(JSON.stringify(metadata))); cb(JSON.parse(JSON.stringify(metadata)));
}; };
Store.onMaintenanceUpdate = function (uid) {
// use uid in /api/broadcast so that all connected users will use the same cached
// version on the server
require(['/api/broadcast?'+uid], function (Broadcast) {
broadcast([], 'UNIVERSAL_EVENT', {
type: 'broadcast',
data: {
ev: 'MAINTENANCE',
data: Broadcast.maintenance
}
});
});
};
Store.onSurveyUpdate = function (uid) {
// use uid in /api/broadcast so that all connected users will use the same cached
// version on the server
require(['/api/broadcast?'+uid], function (Broadcast) {
broadcast([], 'UNIVERSAL_EVENT', {
type: 'broadcast',
data: {
ev: 'SURVEY',
data: Broadcast.surveyURL
}
});
});
};
var makePad = function (href, roHref, title) { var makePad = function (href, roHref, title) {
var now = +new Date(); var now = +new Date();
return { return {

@ -690,74 +690,47 @@ define([
var broadcasts = {}; var broadcasts = {};
handlers['BROADCAST_MAINTENANCE'] = function (ctx, box, data, cb) { handlers['BROADCAST_MAINTENANCE'] = function (ctx, box, data, cb) {
var msg = data.msg; var msg = data.msg;
var content = msg.content;
if (content.end < (+new Date())) {
// Expired maintenance: dismiss
return void cb(true);
}
var uid = msg.uid;
broadcasts[uid] = {
type: box.type,
hash: data.hash
};
cb(false);
};
handlers['BROADCAST_VERSION'] = function (ctx, box, data, cb) {
var msg = data.msg;
var content = msg.content;
if (!box.ready) {
// This is an old version message: dismiss
return void cb(true);
}
if (content.reload) {
// We're going to force a disconnect, dismiss
ctx.Store.newVersionReload();
return; // This message will be removed when reloading the worker
}
var uid = msg.uid; var uid = msg.uid;
broadcasts[uid] = { ctx.Store.onMaintenanceUpdate(uid);
type: box.type, cb(true);
hash: data.hash
};
cb(false);
}; };
var activeSurvey;
handlers['BROADCAST_SURVEY'] = function (ctx, box, data, cb) { handlers['BROADCAST_SURVEY'] = function (ctx, box, data, cb) {
var msg = data.msg; var msg = data.msg;
var uid = msg.uid; var uid = msg.uid;
broadcasts[uid] = { var old = activeSurvey;
activeSurvey = {
type: box.type, type: box.type,
hash: data.hash hash: data.hash
}; };
cb(false); ctx.Store.onSurveyUpdate(uid);
cb(false, old);
}; };
var activeCustom
handlers['BROADCAST_CUSTOM'] = function (ctx, box, data, cb) { handlers['BROADCAST_CUSTOM'] = function (ctx, box, data, cb) {
var msg = data.msg; var msg = data.msg;
var uid = msg.uid; var uid = msg.uid;
broadcasts[uid] = { var old = activeCustom;
activeCustom = {
uid: uid,
type: box.type, type: box.type,
hash: data.hash hash: data.hash
}; };
cb(false); cb(false, old);
}; };
handlers['BROADCAST_DELETE'] = function (ctx, box, data, cb) { handlers['BROADCAST_DELETE'] = function (ctx, box, data, cb) {
var msg = data.msg; var msg = data.msg;
var content = msg.content; var content = msg.content;
// If this is a "clear all" message, empty the box and update lkh
if (content.all) {
// 3rd argument of callback: clear the mailbox
return void cb(null, null, true);
}
var uid = content.uid; // uid of the message to delete var uid = content.uid; // uid of the message to delete
if (!broadcasts[uid]) { if (activeCustom && activeCustom.uid === uid) {
// We don't have this message in memory, nothing to delete
return void cb(true);
}
// We have the message in memory, remove it and don't keep the DELETE msg // We have the message in memory, remove it and don't keep the DELETE msg
cb(true, broadcasts[uid]); cb(true, activeCustom);
delete broadcasts[uid]; activeCustom = undefined;
return;
}
// We don't have this message in memory, nothing to delete
cb(true);
}; };
return { return {

@ -220,7 +220,7 @@ define([
var historyState = false; var historyState = false;
var onHistory = function () {}; var onHistory = function () {};
mailbox.getMoreHistory = function (type, count, lastKnownHash, cb) { mailbox.getMoreHistory = function (type, count, lastKnownHash, cb) {
if (historyState) { return void cb("ALREADY_CALLED"); } if (type !== "broadcast" && historyState) { return void cb("ALREADY_CALLED"); }
historyState = true; historyState = true;
var txid = Util.uid(); var txid = Util.uid();
execCommand('LOAD_HISTORY', { execCommand('LOAD_HISTORY', {

@ -2,6 +2,7 @@ define([
'jquery', 'jquery',
'/customize/application_config.js', '/customize/application_config.js',
'/api/config', '/api/config',
'/api/broadcast',
'/common/common-ui-elements.js', '/common/common-ui-elements.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-hash.js', '/common/common-hash.js',
@ -11,7 +12,7 @@ define([
'/common/hyperscript.js', '/common/hyperscript.js',
'/common/messenger-ui.js', '/common/messenger-ui.js',
'/customize/messages.js', '/customize/messages.js',
], function ($, Config, ApiConfig, UIElements, UI, Hash, Util, Feedback, MT, h, ], function ($, Config, ApiConfig, Broadcast, UIElements, UI, Hash, Util, Feedback, MT, h,
MessengerUI, Messages) { MessengerUI, Messages) {
var Common; var Common;
@ -45,6 +46,7 @@ MessengerUI, Messages) {
var TITLE_CLS = Bar.constants.title = "cp-toolbar-title"; var TITLE_CLS = Bar.constants.title = "cp-toolbar-title";
var LINK_CLS = Bar.constants.link = "cp-toolbar-link"; var LINK_CLS = Bar.constants.link = "cp-toolbar-link";
var NOTIFICATIONS_CLS = Bar.constants.user = 'cp-toolbar-notifications'; var NOTIFICATIONS_CLS = Bar.constants.user = 'cp-toolbar-notifications';
var MAINTENANCE_CLS = Bar.constants.user = 'cp-toolbar-maintenance';
// User admin menu // User admin menu
var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown'; var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown';
@ -78,6 +80,7 @@ MessengerUI, Messages) {
'class': USER_CLS 'class': USER_CLS
}).appendTo($topContainer); }).appendTo($topContainer);
$('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer); $('<span>', {'class': LIMIT_CLS}).hide().appendTo($userContainer);
$('<span>', {'class': MAINTENANCE_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
$('<span>', {'class': NOTIFICATIONS_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('<span>', {'class': NOTIFICATIONS_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
$('<span>', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer); $('<span>', {'class': USERADMIN_CLS + ' cp-dropdown-container'}).hide().appendTo($userContainer);
@ -1026,6 +1029,42 @@ MessengerUI, Messages) {
return $userAdmin; return $userAdmin;
}; };
Messages.broadcast_maintenance = "A maintenance is planned between <b>{0}</b> and <b>{1}</b>"; // XXX
var createMaintenance = function (toolbar, config) {
var $notif = toolbar.$top.find('.'+MAINTENANCE_CLS);
var button = h('button.cp-maintenance-wrench.fa.fa-wrench');
$notif.append(button);
var m = Broadcast.maintenance;
$(button).click(function () {
if (!m || !m.start || !m.end) { return; }
UI.alert(Messages._getKey('broadcast_maintenance', [
new Date(m.start).toLocaleString(),
new Date(m.end).toLocaleString(),
]), null, true);
});
Common.makeUniversal('broadcast', {
onEvent: function (obj) {
var cmd = obj.ev;
if (cmd !== "MAINTENANCE") { return; }
var data = obj.data;
if (!data) {
return void $notif.hide();
}
m = data;
$notif.css('display', '');
}
});
if (m && m.start && m.end) {
$notif.css('display', '');
} else {
$notif.hide();
}
};
var createNotifications = function (toolbar, config) { var createNotifications = function (toolbar, config) {
var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show(); var $notif = toolbar.$top.find('.'+NOTIFICATIONS_CLS).show();
var openNotifsApp = h('div.cp-notifications-gotoapp', h('p', Messages.openNotificationsApp || "Open notifications App")); var openNotifsApp = h('div.cp-notifications-gotoapp', h('p', Messages.openNotificationsApp || "Open notifications App"));
@ -1287,6 +1326,7 @@ MessengerUI, Messages) {
tb['useradmin'] = createUserAdmin; tb['useradmin'] = createUserAdmin;
tb['unpinnedWarning'] = createUnpinnedWarning; tb['unpinnedWarning'] = createUnpinnedWarning;
tb['notifications'] = createNotifications; tb['notifications'] = createNotifications;
tb['maintenance'] = createMaintenance;
tb['pad'] = function () { tb['pad'] = function () {
toolbar.$file.show(); toolbar.$file.show();
@ -1323,6 +1363,7 @@ MessengerUI, Messages) {
}; };
addElement(config.displayed, {}, true); addElement(config.displayed, {}, true);
addElement(['maintenance'], {}, true);
toolbar['linkToMain'] = createLinkToMain(toolbar, config); toolbar['linkToMain'] = createLinkToMain(toolbar, config);

Loading…
Cancel
Save