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.
478 lines
17 KiB
JavaScript
478 lines
17 KiB
JavaScript
'use strict';
|
|
|
|
define('admin/manage/privileges', [
|
|
'api',
|
|
'autocomplete',
|
|
'bootbox',
|
|
'translator',
|
|
'categorySelector',
|
|
'mousetrap',
|
|
'admin/modules/checkboxRowSelector',
|
|
], function (api, autocomplete, bootbox, translator, categorySelector, mousetrap, checkboxRowSelector) {
|
|
const Privileges = {};
|
|
|
|
let cid;
|
|
// number of columns to skip in category privilege tables
|
|
const SKIP_PRIV_COLS = 3;
|
|
|
|
Privileges.init = function () {
|
|
cid = isNaN(parseInt(ajaxify.data.selectedCategory.cid, 10)) ? 'admin' : ajaxify.data.selectedCategory.cid;
|
|
|
|
checkboxRowSelector.init('.privilege-table-container');
|
|
|
|
categorySelector.init($('[component="category-selector"]'), {
|
|
onSelect: function (category) {
|
|
cid = parseInt(category.cid, 10);
|
|
cid = isNaN(cid) ? 'admin' : cid;
|
|
Privileges.refreshPrivilegeTable();
|
|
ajaxify.updateHistory('admin/manage/privileges/' + (cid || ''));
|
|
},
|
|
localCategories: ajaxify.data.categories,
|
|
privilege: 'find',
|
|
showLinks: true,
|
|
});
|
|
|
|
Privileges.setupPrivilegeTable();
|
|
|
|
highlightRow();
|
|
$('.privilege-filters button:last-child').click();
|
|
};
|
|
|
|
Privileges.setupPrivilegeTable = function () {
|
|
$('.privilege-table-container').on('change', 'input[type="checkbox"]:not(.checkbox-helper)', function () {
|
|
const checkboxEl = $(this);
|
|
const wrapperEl = checkboxEl.parent();
|
|
const privilege = wrapperEl.attr('data-privilege');
|
|
const state = checkboxEl.prop('checked');
|
|
const rowEl = checkboxEl.parents('tr');
|
|
const member = rowEl.attr('data-group-name') || rowEl.attr('data-uid');
|
|
const isPrivate = parseInt(rowEl.attr('data-private') || 0, 10);
|
|
const isGroup = rowEl.attr('data-group-name') !== undefined;
|
|
const isBanned = (isGroup && rowEl.attr('data-group-name') === 'banned-users') || rowEl.attr('data-banned') !== undefined;
|
|
const delta = checkboxEl.prop('checked') === (wrapperEl.attr('data-value') === 'true') ? null : state;
|
|
|
|
if (member) {
|
|
if (isGroup && privilege === 'groups:moderate' && !isPrivate && state) {
|
|
bootbox.confirm('[[admin/manage/privileges:alert.confirm-moderate]]', function (confirm) {
|
|
if (confirm) {
|
|
wrapperEl.attr('data-delta', delta);
|
|
Privileges.exposeAssumedPrivileges(isBanned);
|
|
} else {
|
|
checkboxEl.prop('checked', !checkboxEl.prop('checked'));
|
|
}
|
|
});
|
|
} else if (privilege.endsWith('admin:admins-mods') && state) {
|
|
bootbox.confirm('[[admin/manage/privileges:alert.confirm-admins-mods]]', function (confirm) {
|
|
if (confirm) {
|
|
wrapperEl.attr('data-delta', delta);
|
|
Privileges.exposeAssumedPrivileges();
|
|
} else {
|
|
checkboxEl.prop('checked', !checkboxEl.prop('checked'));
|
|
}
|
|
});
|
|
} else {
|
|
wrapperEl.attr('data-delta', delta);
|
|
Privileges.exposeAssumedPrivileges(isBanned);
|
|
}
|
|
checkboxRowSelector.updateState(checkboxEl);
|
|
} else {
|
|
app.alertError('[[error:invalid-data]]');
|
|
}
|
|
});
|
|
|
|
Privileges.exposeAssumedPrivileges();
|
|
checkboxRowSelector.updateAll();
|
|
Privileges.addEvents(); // events with confirmation modals
|
|
};
|
|
|
|
Privileges.addEvents = function () {
|
|
document.getElementById('save').addEventListener('click', function () {
|
|
throwConfirmModal('save', Privileges.commit);
|
|
});
|
|
|
|
document.getElementById('discard').addEventListener('click', function () {
|
|
throwConfirmModal('discard', Privileges.discard);
|
|
});
|
|
|
|
// Expose discard button as necessary
|
|
const containerEl = document.querySelector('.privilege-table-container');
|
|
containerEl.addEventListener('change', (e) => {
|
|
const subselector = e.target.closest('td[data-privilege] input');
|
|
if (subselector) {
|
|
document.getElementById('discard').style.display = containerEl.querySelectorAll('td[data-delta]').length ? 'unset' : 'none';
|
|
}
|
|
});
|
|
|
|
const $privTableCon = $('.privilege-table-container');
|
|
$privTableCon.on('click', '[data-action="search.user"]', Privileges.addUserToPrivilegeTable);
|
|
$privTableCon.on('click', '[data-action="search.group"]', Privileges.addGroupToPrivilegeTable);
|
|
$privTableCon.on('click', '[data-action="copyToChildren"]', function () {
|
|
throwConfirmModal('copyToChildren', Privileges.copyPrivilegesToChildren.bind(null, cid, ''));
|
|
});
|
|
$privTableCon.on('click', '[data-action="copyToChildrenGroup"]', function () {
|
|
const groupName = $(this).parents('[data-group-name]').attr('data-group-name');
|
|
throwConfirmModal('copyToChildrenGroup', Privileges.copyPrivilegesToChildren.bind(null, cid, groupName));
|
|
});
|
|
|
|
$privTableCon.on('click', '[data-action="copyPrivilegesFrom"]', function () {
|
|
Privileges.copyPrivilegesFromCategory(cid, '');
|
|
});
|
|
$privTableCon.on('click', '[data-action="copyPrivilegesFromGroup"]', function () {
|
|
const groupName = $(this).parents('[data-group-name]').attr('data-group-name');
|
|
Privileges.copyPrivilegesFromCategory(cid, groupName);
|
|
});
|
|
|
|
$privTableCon.on('click', '[data-action="copyToAll"]', function () {
|
|
throwConfirmModal('copyToAll', Privileges.copyPrivilegesToAllCategories.bind(null, cid, ''));
|
|
});
|
|
$privTableCon.on('click', '[data-action="copyToAllGroup"]', function () {
|
|
const groupName = $(this).parents('[data-group-name]').attr('data-group-name');
|
|
throwConfirmModal('copyToAllGroup', Privileges.copyPrivilegesToAllCategories.bind(null, cid, groupName));
|
|
});
|
|
|
|
$privTableCon.on('click', '.privilege-filters > button', filterPrivileges);
|
|
|
|
mousetrap.bind('ctrl+s', function (ev) {
|
|
throwConfirmModal('save', Privileges.commit);
|
|
ev.preventDefault();
|
|
});
|
|
|
|
function throwConfirmModal(method, onConfirm) {
|
|
const privilegeSubset = getPrivilegeSubset();
|
|
bootbox.confirm(`[[admin/manage/privileges:alert.confirm-${method}, ${privilegeSubset}]]<br /><br />[[admin/manage/privileges:alert.no-undo]]`, function (ok) {
|
|
if (ok) {
|
|
onConfirm.call();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
Privileges.commit = function () {
|
|
const tableEl = document.querySelector('.privilege-table-container');
|
|
const requests = $.map(tableEl.querySelectorAll('td[data-delta]'), function (el) {
|
|
const privilege = el.getAttribute('data-privilege');
|
|
const rowEl = el.parentNode;
|
|
const member = rowEl.getAttribute('data-group-name') || rowEl.getAttribute('data-uid');
|
|
const state = el.getAttribute('data-delta') === 'true' ? 1 : 0;
|
|
|
|
return Privileges.setPrivilege(member, privilege, state);
|
|
});
|
|
|
|
Promise.allSettled(requests).then((results) => {
|
|
Privileges.refreshPrivilegeTable();
|
|
|
|
const rejects = results.filter(r => r.status === 'rejected');
|
|
if (rejects.length) {
|
|
rejects.forEach((result) => {
|
|
app.alertError(result.reason);
|
|
});
|
|
} else {
|
|
app.alertSuccess('[[admin/manage/privileges:alert.saved]]');
|
|
}
|
|
});
|
|
};
|
|
|
|
Privileges.discard = function () {
|
|
Privileges.refreshPrivilegeTable();
|
|
app.alertSuccess('[[admin/manage/privileges:alert.discarded]]');
|
|
};
|
|
|
|
Privileges.refreshPrivilegeTable = function (groupToHighlight) {
|
|
api.get(`/categories/${cid}/privileges`, {}).then((privileges) => {
|
|
ajaxify.data.privileges = { ...ajaxify.data.privileges, ...privileges };
|
|
const tpl = parseInt(cid, 10) ? 'admin/partials/privileges/category' : 'admin/partials/privileges/global';
|
|
app.parseAndTranslate(tpl, { privileges }).then((html) => {
|
|
// Get currently selected filters
|
|
const btnIndices = $('.privilege-filters button.btn-warning').map((idx, el) => $(el).index()).get();
|
|
$('.privilege-table-container').html(html);
|
|
Privileges.exposeAssumedPrivileges();
|
|
document.querySelectorAll('.privilege-filters').forEach((con, i) => {
|
|
// Three buttons, placed in reverse order
|
|
const lastIdx = $('.privilege-filters').first().find('button').length - 1;
|
|
const idx = btnIndices[i] === undefined ? lastIdx : btnIndices[i];
|
|
con.querySelectorAll('button')[idx].click();
|
|
});
|
|
|
|
hightlightRowByDataAttr('data-group-name', groupToHighlight);
|
|
});
|
|
}).catch(app.alertError);
|
|
};
|
|
|
|
Privileges.exposeAssumedPrivileges = function (isBanned) {
|
|
/*
|
|
If registered-users has a privilege enabled, then all users and groups of that privilege
|
|
should be assumed to have that privilege as well, even if not set in the db, so reflect
|
|
this arrangement in the table
|
|
*/
|
|
|
|
// As such, individual banned users inherits privileges from banned-users group
|
|
// Running this block only when needed
|
|
if (isBanned === undefined || isBanned === true) {
|
|
const getBannedUsersInputSelector = (privs, i) => `.privilege-table tr[data-banned] td[data-privilege="${privs[i]}"] input`;
|
|
const bannedUsersPrivs = getPrivilegesFromRow('banned-users');
|
|
applyPrivileges(bannedUsersPrivs, getBannedUsersInputSelector);
|
|
if (isBanned === true) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
const getRegisteredUsersInputSelector = (privs, i) => `.privilege-table tr[data-group-name]:not([data-group-name="registered-users"],[data-group-name="banned-users"],[data-group-name="guests"],[data-group-name="spiders"]) td[data-privilege="${privs[i]}"] input, .privilege-table tr[data-uid]:not([data-banned]) td[data-privilege="${privs[i]}"] input`;
|
|
const registeredUsersPrivs = getPrivilegesFromRow('registered-users');
|
|
applyPrivileges(registeredUsersPrivs, getRegisteredUsersInputSelector);
|
|
};
|
|
|
|
Privileges.setPrivilege = (member, privilege, state) => api[state ? 'put' : 'delete'](`/categories/${isNaN(cid) ? 0 : cid}/privileges/${privilege}`, { member });
|
|
|
|
Privileges.addUserToPrivilegeTable = function () {
|
|
const modal = bootbox.dialog({
|
|
title: '[[admin/manage/categories:alert.find-user]]',
|
|
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.user-search]]" />',
|
|
show: true,
|
|
});
|
|
|
|
modal.on('shown.bs.modal', function () {
|
|
const inputEl = modal.find('input');
|
|
inputEl.focus();
|
|
|
|
autocomplete.user(inputEl, function (ev, ui) {
|
|
addUserToCategory(ui.item.user, function () {
|
|
modal.modal('hide');
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Privileges.addGroupToPrivilegeTable = function () {
|
|
const modal = bootbox.dialog({
|
|
title: '[[admin/manage/categories:alert.find-group]]',
|
|
message: '<input class="form-control input-lg" placeholder="[[admin/manage/categories:alert.group-search]]" />',
|
|
show: true,
|
|
});
|
|
|
|
modal.on('shown.bs.modal', function () {
|
|
const inputEl = modal.find('input');
|
|
inputEl.focus();
|
|
|
|
autocomplete.group(inputEl, function (ev, ui) {
|
|
if (ui.item.group.name === 'administrators') {
|
|
return app.alert({
|
|
type: 'warning',
|
|
message: '[[admin/manage/privileges:alert.admin-warning]]',
|
|
});
|
|
}
|
|
addGroupToCategory(ui.item.group.name, function () {
|
|
modal.modal('hide');
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Privileges.copyPrivilegesToChildren = function (cid, group) {
|
|
const filter = getPrivilegeFilter();
|
|
socket.emit('admin.categories.copyPrivilegesToChildren', { cid, group, filter }, function (err) {
|
|
if (err) {
|
|
return app.alertError(err.message);
|
|
}
|
|
app.alertSuccess('[[admin/manage/categories:privileges.copy-success]]');
|
|
});
|
|
};
|
|
|
|
Privileges.copyPrivilegesFromCategory = function (cid, group) {
|
|
const privilegeSubset = getPrivilegeSubset();
|
|
const message = '<br>' +
|
|
(group ? `[[admin/manage/privileges:alert.copyPrivilegesFromGroup-warning, ${privilegeSubset}]]` :
|
|
`[[admin/manage/privileges:alert.copyPrivilegesFrom-warning, ${privilegeSubset}]]`) +
|
|
'<br><br>[[admin/manage/privileges:alert.no-undo]]';
|
|
categorySelector.modal({
|
|
title: '[[admin/manage/privileges:alert.copyPrivilegesFrom-title]]',
|
|
message,
|
|
localCategories: [],
|
|
showLinks: true,
|
|
onSubmit: function (selectedCategory) {
|
|
socket.emit('admin.categories.copyPrivilegesFrom', {
|
|
toCid: cid,
|
|
filter: getPrivilegeFilter(),
|
|
fromCid: selectedCategory.cid,
|
|
group: group,
|
|
}, function (err) {
|
|
if (err) {
|
|
return app.alertError(err.message);
|
|
}
|
|
ajaxify.refresh();
|
|
});
|
|
},
|
|
});
|
|
};
|
|
|
|
Privileges.copyPrivilegesToAllCategories = function (cid, group) {
|
|
const filter = getPrivilegeFilter();
|
|
socket.emit('admin.categories.copyPrivilegesToAllCategories', { cid, group, filter }, function (err) {
|
|
if (err) {
|
|
return app.alertError(err.message);
|
|
}
|
|
app.alertSuccess('[[admin/manage/categories:privileges.copy-success]]');
|
|
});
|
|
};
|
|
|
|
function getPrivilegesFromRow(sourceGroupName) {
|
|
const privs = [];
|
|
$(`.privilege-table tr[data-group-name="${sourceGroupName}"] td input[type="checkbox"]:not(.checkbox-helper)`)
|
|
.parent()
|
|
.each(function (idx, el) {
|
|
if ($(el).find('input').prop('checked')) {
|
|
privs.push(el.getAttribute('data-privilege'));
|
|
}
|
|
});
|
|
|
|
// Also apply to non-group privileges
|
|
return privs.concat(privs.map(function (priv) {
|
|
if (priv.startsWith('groups:')) {
|
|
return priv.slice(7);
|
|
}
|
|
|
|
return false;
|
|
})).filter(Boolean);
|
|
}
|
|
|
|
function applyPrivileges(privs, inputSelectorFn) {
|
|
for (let x = 0, numPrivs = privs.length; x < numPrivs; x += 1) {
|
|
const inputs = $(inputSelectorFn(privs, x));
|
|
inputs.each(function (idx, el) {
|
|
if (!el.checked) {
|
|
el.indeterminate = true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function hightlightRowByDataAttr(attrName, attrValue) {
|
|
if (attrValue) {
|
|
const el = $('[' + attrName + ']').filter(function () {
|
|
return $(this).attr(attrName) === String(attrValue);
|
|
});
|
|
|
|
if (el.length) {
|
|
el.addClass('selected');
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function highlightRow() {
|
|
if (ajaxify.data.group) {
|
|
if (hightlightRowByDataAttr('data-group-name', ajaxify.data.group)) {
|
|
return;
|
|
}
|
|
addGroupToCategory(ajaxify.data.group);
|
|
}
|
|
}
|
|
|
|
function addGroupToCategory(group, cb) {
|
|
cb = cb || function () {};
|
|
const groupRow = document.querySelector('.privilege-table [data-group-name="' + group + '"]');
|
|
if (groupRow) {
|
|
hightlightRowByDataAttr('data-group-name', group);
|
|
return cb();
|
|
}
|
|
// Generate data for new row
|
|
const privilegeSet = ajaxify.data.privileges.keys.groups.reduce(function (memo, cur) {
|
|
memo[cur] = false;
|
|
return memo;
|
|
}, {});
|
|
|
|
app.parseAndTranslate('admin/partials/privileges/' + ((isNaN(cid) || cid === 0) ? 'global' : 'category'), 'privileges.groups', {
|
|
privileges: {
|
|
groups: [
|
|
{
|
|
name: group,
|
|
nameEscaped: translator.escape(group),
|
|
privileges: privilegeSet,
|
|
},
|
|
],
|
|
},
|
|
}, function (html) {
|
|
const tbodyEl = document.querySelector('.privilege-table tbody');
|
|
const btnIdx = $('.privilege-filters').first().find('button.btn-warning').index();
|
|
tbodyEl.append(html.get(0));
|
|
Privileges.exposeAssumedPrivileges();
|
|
hightlightRowByDataAttr('data-group-name', group);
|
|
document.querySelector('.privilege-filters').querySelectorAll('button')[btnIdx].click();
|
|
cb();
|
|
});
|
|
}
|
|
|
|
async function addUserToCategory(user, cb) {
|
|
cb = cb || function () {};
|
|
const userRow = document.querySelector('.privilege-table [data-uid="' + user.uid + '"]');
|
|
if (userRow) {
|
|
hightlightRowByDataAttr('data-uid', user.uid);
|
|
return cb();
|
|
}
|
|
// Generate data for new row
|
|
const privilegeSet = ajaxify.data.privileges.keys.users.reduce(function (memo, cur) {
|
|
memo[cur] = false;
|
|
return memo;
|
|
}, {});
|
|
|
|
const html = await app.parseAndTranslate('admin/partials/privileges/' + (isNaN(cid) ? 'global' : 'category'), 'privileges.users', {
|
|
privileges: {
|
|
users: [
|
|
{
|
|
picture: user.picture,
|
|
username: user.username,
|
|
banned: user.banned,
|
|
uid: user.uid,
|
|
'icon:text': user['icon:text'],
|
|
'icon:bgColor': user['icon:bgColor'],
|
|
privileges: privilegeSet,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const tbodyEl = document.querySelectorAll('.privilege-table tbody');
|
|
const btnIdx = $('.privilege-filters').last().find('button.btn-warning').index();
|
|
tbodyEl[1].append(html.get(0));
|
|
Privileges.exposeAssumedPrivileges();
|
|
hightlightRowByDataAttr('data-uid', user.uid);
|
|
document.querySelectorAll('.privilege-filters')[1].querySelectorAll('button')[btnIdx].click();
|
|
cb();
|
|
}
|
|
|
|
function filterPrivileges(ev) {
|
|
const [startIdx, endIdx] = ev.target.getAttribute('data-filter').split(',').map(i => parseInt(i, 10));
|
|
const rows = $(ev.target).closest('table')[0].querySelectorAll('thead tr:last-child, tbody tr ');
|
|
rows.forEach((tr) => {
|
|
tr.querySelectorAll('td, th').forEach((el, idx) => {
|
|
const offset = el.tagName.toUpperCase() === 'TH' ? 1 : 0;
|
|
if (idx < (SKIP_PRIV_COLS - offset)) {
|
|
return;
|
|
}
|
|
el.classList.toggle('hidden', !(idx >= (startIdx - offset) && idx <= (endIdx - offset)));
|
|
});
|
|
});
|
|
checkboxRowSelector.updateAll();
|
|
$(ev.target).siblings('button').toArray().forEach(btn => btn.classList.remove('btn-warning'));
|
|
ev.target.classList.add('btn-warning');
|
|
}
|
|
|
|
function getPrivilegeFilter() {
|
|
const indices = document.querySelector('.privilege-filters .btn-warning')
|
|
.getAttribute('data-filter')
|
|
.split(',')
|
|
.map(i => parseInt(i, 10));
|
|
indices[0] -= SKIP_PRIV_COLS;
|
|
indices[1] = indices[1] - SKIP_PRIV_COLS + 1;
|
|
return indices;
|
|
}
|
|
|
|
function getPrivilegeSubset() {
|
|
const currentPrivFilter = document.querySelector('.privilege-filters .btn-warning');
|
|
const filterText = currentPrivFilter ? currentPrivFilter.textContent.toLocaleLowerCase() : '';
|
|
return filterText.indexOf('privileges') > -1 ? filterText : `${filterText} privileges`.trim();
|
|
}
|
|
|
|
return Privileges;
|
|
});
|