diff --git a/public/language/en-GB/admin/manage/users.json b/public/language/en-GB/admin/manage/users.json index 01088037f2..ae225d5b6f 100644 --- a/public/language/en-GB/admin/manage/users.json +++ b/public/language/en-GB/admin/manage/users.json @@ -15,6 +15,8 @@ "delete": "Delete User(s)", "purge": "Delete User(s) and Content", "download-csv": "Download CSV", + "manage-groups": "Manage Groups", + "add-group": "Add Group", "invite": "Invite", "new": "New User", diff --git a/public/less/admin/manage/users.less b/public/less/admin/manage/users.less index 0ab74c7544..097c13d4ac 100644 --- a/public/less/admin/manage/users.less +++ b/public/less/admin/manage/users.less @@ -1,57 +1,19 @@ .manage-users { min-height: 500px; - - #users-container { - border: 1px solid #eee; - padding: 0px 20px 20px; - - .users-box { - display: inline-block; - margin-top: 20px; - text-align: center; - vertical-align: top; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - height: auto; - max-width: 145px; - min-width: 145px; - padding: 1rem; - - img, .user-icon { - .user-icon-style(80px, 4rem); - } - - a { - margin-bottom:5px; - font-size: 12px; - } - - .user-image { - position: relative; - - .labels { - position: absolute; - bottom: 0px; - width: 100%; - - span { - width: 100%; - opacity: 0.9; - font-size: 10px; - display: block; - border-radius: 0; - } - } - } + .search { + .form-control { + width: 100%; } + } +} - .ui-selected { - background: lighten(@brand-success, 25%); - } +.page-admin-users { + .group-card { + margin: 2px; + padding: 2px; + } - .ui-selecting { - background: lighten(@brand-success, 40%); - } + .remove-group-icon { + margin-left: 5px; } } \ No newline at end of file diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index d70808a8b7..dd3554d11b 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -1,13 +1,12 @@ 'use strict'; - -define('admin/manage/users', ['translator', 'benchpress'], function (translator, Benchpress) { +define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], function (translator, Benchpress, autocomplete) { var Users = {}; Users.init = function () { var navPills = $('.nav-pills li'); var pathname = window.location.pathname; - if (!navPills.find('a[href^="' + pathname + '"]').length) { + if (!navPills.find('a[href^="' + pathname + '"]').length || pathname === config.relative_path + '/admin/manage/users') { pathname = config.relative_path + '/admin/manage/users/latest'; } navPills.removeClass('active').find('a[href^="' + pathname + '"]').parent().addClass('active'); @@ -59,11 +58,56 @@ define('admin/manage/users', ['translator', 'benchpress'], function (translator, } $('[component="user/select/all"]').on('click', function () { - if ($(this).is(':checked')) { - $('.users-table [component="user/select/single"]').prop('checked', true); - } else { - $('.users-table [component="user/select/single"]').prop('checked', false); + $('.users-table [component="user/select/single"]').prop('checked', $(this).is(':checked')); + }); + + $('.manage-groups').on('click', function () { + var uids = getSelectedUids(); + if (!uids.length) { + app.alertError('[[error:no-users-selected]]'); + return false; } + socket.emit('admin.user.loadGroups', uids, function (err, data) { + if (err) { + return app.alertError(err); + } + Benchpress.parse('admin/partials/manage_user_groups', data, function (html) { + var modal = bootbox.dialog({ + message: utils.escapeHTML(html), + title: '[[admin/manage/users:manage-groups]]', + onEscape: true, + }); + modal.on('shown.bs.modal', function () { + autocomplete.group(modal.find('.group-search'), function (ev, ui) { + var uid = $(ev.target).attr('data-uid'); + socket.emit('admin.groups.join', { uid: uid, groupName: ui.item.value }, function (err) { + if (err) { + return app.alertError(err); + } + ui.item.group.nameEscaped = translator.escape(ui.item.group.displayName); + app.parseAndTranslate('admin/partials/manage_user_groups', { users: [{ groups: [ui.item.group] }] }, function (html) { + $('[data-uid=' + uid + '] .group-area').append(html.find('.group-area').html()); + }); + }); + }); + }); + modal.on('click', '.group-area a', function () { + modal.modal('hide'); + }); + modal.on('click', '.remove-group-icon', function () { + var groupCard = $(this).parents('[data-group-name]'); + var groupName = groupCard.attr('data-group-name'); + var uid = $(this).parents('[data-uid]').attr('data-uid'); + socket.emit('admin.groups.leave', { uid: uid, groupName: groupName }, function (err) { + if (err) { + return app.alertError(err); + } + groupCard.remove(); + }); + return false; + }); + }); + }); }); $('.ban-user').on('click', function () { @@ -321,8 +365,8 @@ define('admin/manage/users', ['translator', 'benchpress'], function (translator, Benchpress.parse('admin/manage/users', 'users', data, function (html) { translator.translate(html, function (html) { html = $(html); - $('.users-table tr').not(':first').remove(); - $('.users-table tr').first().after(html); + $('.users-table tbody tr').remove(); + $('.users-table tbody').append(html); html.find('.timeago').timeago(); $('.fa-spinner').addClass('hidden'); diff --git a/public/src/modules/autocomplete.js b/public/src/modules/autocomplete.js index 92f01e25b9..9baf4329f5 100644 --- a/public/src/modules/autocomplete.js +++ b/public/src/modules/autocomplete.js @@ -55,7 +55,12 @@ define('autocomplete', function () { app.loadJQueryUI(function () { input.autocomplete({ delay: 200, - select: onselect, + open: function () { + $(this).autocomplete('widget').css('z-index', 100005); + }, + select: function (event, ui) { + handleOnSelect(input, onselect, event, ui); + }, source: function (request, response) { socket.emit('groups.search', { query: request.term, @@ -63,16 +68,12 @@ define('autocomplete', function () { if (err) { return app.alertError(err.message); } - if (results && results.length) { var names = results.map(function (group) { return group && { label: group.name, value: group.name, - group: { - name: group.name, - slug: group.slug, - }, + group: group, }; }); response(names); diff --git a/src/groups/search.js b/src/groups/search.js index 45dbdbbc61..a19c6ea253 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -13,7 +13,7 @@ module.exports = function (Groups) { if (!options.hideEphemeralGroups) { groupNames = Groups.ephemeralGroups.concat(groupNames); } - groupNames = groupNames.filter(name => name.toLowerCase().includes(query) && name !== 'administrators' && !Groups.isPrivilegeGroup(name)); + groupNames = groupNames.filter(name => name.toLowerCase().includes(query) && !Groups.isPrivilegeGroup(name)); groupNames = groupNames.slice(0, 100); let groupsData; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 3313bd8d19..7899f2b61b 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -9,6 +9,7 @@ const user = require('../../user'); const events = require('../../events'); const meta = require('../../meta'); const plugins = require('../../plugins'); +const translator = require('../../translator'); const User = module.exports; @@ -190,3 +191,17 @@ User.search = async function (socket, data) { User.restartJobs = async function () { user.startJobs(); }; + +User.loadGroups = async function (socket, uids) { + const [userData, groupData] = await Promise.all([ + user.getUsersData(uids), + groups.getUserGroupsFromSet('groups:createtime', uids), + ]); + userData.forEach((data, index) => { + data.groups = groupData[index].filter(group => !groups.isPrivilegeGroup(group.name)); + data.groups.forEach((group) => { + group.nameEscaped = translator.escape(group.displayName); + }); + }); + return { users: userData }; +}; diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index ef250aa980..fb9061a700 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -1,127 +1,127 @@
-
-
[[admin/manage/users:users]]
-
- -
- - - [[admin/manage/users:download-csv]] - - - - - - +
+
+ + + + + [[admin/manage/users:download-csv]] + - -
- - -
- - +
+ + +
+ + - - [[admin/manage/users:inactive.3-months]] - [[admin/manage/users:inactive.6-months]] - [[admin/manage/users:inactive.12-months]] - + + [[admin/manage/users:inactive.3-months]] + [[admin/manage/users:inactive.6-months]] + [[admin/manage/users:inactive.12-months]] + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[[admin/manage/users:users.uid]][[admin/manage/users:users.username]][[admin/manage/users:users.email]][[admin/manage/users:users.postcount]][[admin/manage/users:users.reputation]][[admin/manage/users:users.flags]][[admin/manage/users:users.joined]][[admin/manage/users:users.last-online]][[admin/manage/users:users.banned]]
{users.uid} {users.username} + + + + {users.email}{users.postcount}{users.reputation}{users.flags}0
+
-
- - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - -
[[admin/manage/users:users.uid]][[admin/manage/users:users.username]][[admin/manage/users:users.email]][[admin/manage/users:users.postcount]][[admin/manage/users:users.reputation]][[admin/manage/users:users.flags]][[admin/manage/users:users.joined]][[admin/manage/users:users.last-online]][[admin/manage/users:users.banned]]
{users.uid} {users.username} - - - - {users.email}{users.postcount}{users.reputation}{users.flags}0
-
- - -
-
diff --git a/src/views/admin/partials/manage_user_groups.tpl b/src/views/admin/partials/manage_user_groups.tpl new file mode 100644 index 0000000000..6136f8c44c --- /dev/null +++ b/src/views/admin/partials/manage_user_groups.tpl @@ -0,0 +1,13 @@ + +
+
{users.username}
+ + +
+ \ No newline at end of file