diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index d41913ebc0..db2cd3fef3 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -2,12 +2,10 @@ /* global config, socket, define, templates, bootbox, app, ajaxify */ -define('admin/manage/users', ['admin/modules/selectable', 'translator'], function(selectable, translator) { +define('admin/manage/users', ['translator'], function(translator) { var Users = {}; Users.init = function() { - selectable.enable('#users-container', '.users-box'); - var navPills = $('.nav-pills li'); var pathname = window.location.pathname; if (!navPills.find('a[href="' + pathname + '"]').length) { @@ -17,25 +15,29 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio function getSelectedUids() { var uids = []; - $('#users-container .users-box.ui-selected').each(function() { - uids.push(this.getAttribute('data-uid')); + + $('.users-table [component="user/select/single"]').each(function() { + if ($(this).is(':checked')) { + uids.push($(this).attr('data-uid')); + } }); return uids; } function update(className, state) { - $('#users-container .users-box.ui-selected .labels').find(className).each(function() { - $(this).toggleClass('hide', !state); + $('.users-table [component="user/select/single"]:checked').parents('.user-row').find(className).each(function() { + $(this).toggleClass('hidden', !state); }); } function unselectAll() { - $('#users-container .users-box.ui-selected').removeClass('ui-selected'); + $('.users-table [component="user/select/single"]').prop('checked', false); + $('.users-table [component="user/select/all"]').prop('checked', false); } function removeSelected() { - $('#users-container .users-box.ui-selected').remove(); + $('.users-table [component="user/select/single"]:checked').parents('.user-row').remove(); } function done(successMessage, className, flag) { @@ -51,6 +53,14 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio }; } + $('[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); + } + }); + $('.ban-user').on('click', function() { var uids = getSelectedUids(); if (!uids.length) { @@ -164,9 +174,18 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio } bootbox.confirm('Do you want to validate email(s) of these user(s)?', function(confirm) { - if (confirm) { - socket.emit('admin.user.validateEmail', uids, done('Emails validated', '.notvalidated', false)); + if (!confirm) { + return; } + socket.emit('admin.user.validateEmail', uids, function(err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('Emails validated'); + update('.notvalidated', false); + update('.validated', true); + unselectAll(); + }); }); }); @@ -299,8 +318,6 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio var timeoutId = 0; - - $('#search-user-name, #search-user-email, #search-user-ip').on('keyup', function() { if (timeoutId !== 0) { clearTimeout(timeoutId); @@ -319,23 +336,25 @@ define('admin/manage/users', ['admin/modules/selectable', 'translator'], functio } templates.parse('admin/manage/users', 'users', data, function(html) { - $('#users-container').html(html).find('.timeago').timeago(); - + html = $(html); + $('.users-table tr').not(':first').remove(); + $('.users-table tr').first().after(html); + html.find('.timeago').timeago(); $('.fa-spinner').addClass('hidden'); if (data && data.users.length === 0) { $('#user-notfound-notify').html('User not found!') - .show() + .removeClass('hide') .addClass('label-danger') .removeClass('label-success'); } else { $('#user-notfound-notify').html(data.users.length + ' user' + (data.users.length > 1 ? 's' : '') + ' found! Search took ' + data.timing + ' ms.') - .show() + .removeClass('hide') .addClass('label-success') .removeClass('label-danger'); } - selectable.enable('#users-container', '.users-box'); + }); }); }, 250); diff --git a/src/controllers/admin/users.js b/src/controllers/admin/users.js index 6be5af1e73..c2e085ada1 100644 --- a/src/controllers/admin/users.js +++ b/src/controllers/admin/users.js @@ -1,6 +1,8 @@ "use strict"; var async = require('async'); +var validator = require('validator'); + var user = require('../../user'); var meta = require('../../meta'); var db = require('../../database'); @@ -10,6 +12,9 @@ var plugins = require('../../plugins'); var usersController = {}; +var userFields = ['uid', 'username', 'userslug', 'email', 'postcount', 'joindate', 'banned', + 'reputation', 'picture', 'flags', 'lastonline', 'email:confirmed']; + usersController.search = function(req, res, next) { res.render('admin/manage/users', { search_display: '', @@ -18,67 +23,29 @@ usersController.search = function(req, res, next) { }; usersController.sortByJoinDate = function(req, res, next) { - getUsers('users:joindate', 'latest', req, res, next); + getUsers('users:joindate', 'latest', undefined, undefined, req, res, next); }; usersController.notValidated = function(req, res, next) { - getUsers('users:notvalidated', 'notvalidated', req, res, next); + getUsers('users:notvalidated', 'notvalidated', undefined, undefined, req, res, next); }; usersController.noPosts = function(req, res, next) { - getUsersByScore('users:postcount', 'noposts', 0, 0, req, res, next); + getUsers('users:postcount', 'noposts', '-inf', 0, req, res, next); }; usersController.flagged = function(req, res, next) { - getUsersByScore('users:flags', 'mostflags', 1, '+inf', req, res, next); + getUsers('users:flags', 'mostflags', 1, '+inf', req, res, next); }; usersController.inactive = function(req, res, next) { var timeRange = 1000 * 60 * 60 * 24 * 30 * (parseInt(req.query.months, 10) || 3); var cutoff = Date.now() - timeRange; - getUsersByScore('users:online', 'inactive', '-inf', cutoff, req, res, next); + getUsers('users:online', 'inactive', '-inf', cutoff, req, res, next); }; -function getUsersByScore(set, section, min, max, req, res, callback) { - var page = parseInt(req.query.page, 10) || 1; - var resultsPerPage = 25; - var start = Math.max(0, page - 1) * resultsPerPage; - var count = 0; - - async.waterfall([ - function (next) { - async.parallel({ - count: function (next) { - db.sortedSetCount(set, min, max, next); - }, - uids: function (next) { - db.getSortedSetRevRangeByScore(set, start, resultsPerPage, max, min, next); - } - }, next); - }, - function (results, next) { - count = results.count; - user.getUsers(results.uids, req.uid, next); - } - ], function(err, users) { - if (err) { - return callback(err); - } - users = users.filter(function(user) { - return user && parseInt(user.uid, 10); - }); - var data = { - users: users, - page: page, - pageCount: Math.ceil(count / resultsPerPage) - }; - data[section] = true; - render(req, res, data); - }); -} - usersController.banned = function(req, res, next) { - getUsers('users:banned', 'banned', req, res, next); + getUsers('users:banned', 'banned', undefined, undefined, req, res, next); }; usersController.registrationQueue = function(req, res, next) { @@ -141,18 +108,34 @@ usersController.registrationQueue = function(req, res, next) { }); }; -function getUsers(set, section, req, res, next) { +function getUsers(set, section, min, max, req, res, next) { var page = parseInt(req.query.page, 10) || 1; - var resultsPerPage = 25; + var resultsPerPage = 50; var start = Math.max(0, page - 1) * resultsPerPage; var stop = start + resultsPerPage - 1; + var byScore = min !== undefined && max !== undefined; async.parallel({ count: function(next) { - db.sortedSetCard(set, next); + if (byScore) { + db.sortedSetCount(set, min, max, next); + } else { + db.sortedSetCard(set, next); + } }, users: function(next) { - user.getUsersFromSet(set, req.uid, start, stop, next); + async.waterfall([ + function(next) { + if (byScore) { + db.getSortedSetRevRangeByScore(set, start, resultsPerPage, max, min, next); + } else { + user.getUidsFromSet(set, start, stop, next); + } + }, + function(uids, next) { + user.getUsersWithFields(uids, userFields, req.uid, next); + } + ], next); } }, function(err, results) { if (err) { @@ -160,6 +143,7 @@ function getUsers(set, section, req, res, next) { } results.users = results.users.filter(function(user) { + user.email = validator.escape(String(user.email || '')); return user && parseInt(user.uid, 10); }); var data = { diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index 5ebe40665b..d8cbb74e34 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -198,7 +198,7 @@ User.search = function(socket, data, callback) { return user && user.uid; }); - user.getUsersFields(uids, ['email', 'flags'], function(err, userInfo) { + user.getUsersFields(uids, ['email', 'flags', 'lastonline', 'joindate'], function(err, userInfo) { if (err) { return callback(err); } @@ -207,6 +207,8 @@ User.search = function(socket, data, callback) { if (user && userInfo[index]) { user.email = validator.escape(String(userInfo[index].email || '')); user.flags = userInfo[index].flags || 0; + user.lastonlineISO = userInfo[index].lastonlineISO; + user.joindateISO = userInfo[index].joindateISO; } }); diff --git a/src/user.js b/src/user.js index f2060497ff..40aefeaaf2 100644 --- a/src/user.js +++ b/src/user.js @@ -8,7 +8,6 @@ var db = require('./database'); var topics = require('./topics'); var privileges = require('./privileges'); var meta = require('./meta'); -var utils = require('../public/src/utils'); (function(User) { @@ -90,10 +89,7 @@ var utils = require('../public/src/utils'); ], callback); }; - User.getUsers = function(uids, uid, callback) { - var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', - 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; - + User.getUsersWithFields = function(uids, fields, uid, callback) { async.waterfall([ function (next) { plugins.fireHook('filter:users.addFields', {fields: fields}, next); @@ -116,13 +112,11 @@ var utils = require('../public/src/utils'); results.userData.forEach(function(user, index) { if (user) { user.status = User.getStatus(user); - user.joindateISO = utils.toISOString(user.joindate); user.administrator = results.isAdmin[index]; user.banned = parseInt(user.banned, 10) === 1; user.banned_until = parseInt(user['banned:expire'], 10) || 0; user.banned_until_readable = user.banned_until ? new Date(user.banned_until).toString() : 'Not Banned'; user['email:confirmed'] = parseInt(user['email:confirmed'], 10) === 1; - user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; } }); plugins.fireHook('filter:userlist.get', {users: results.userData, uid: uid}, next); @@ -133,6 +127,13 @@ var utils = require('../public/src/utils'); ], callback); }; + User.getUsers = function(uids, uid, callback) { + var fields = ['uid', 'username', 'userslug', 'picture', 'status', 'flags', + 'banned', 'banned:expire', 'joindate', 'postcount', 'reputation', 'email:confirmed', 'lastonline']; + + User.getUsersWithFields(uids, fields, uid, callback); + }; + User.getStatus = function(userData) { var isOnline = (Date.now() - parseInt(userData.lastonline, 10)) < 300000; return isOnline ? (userData.status || 'online') : 'offline'; diff --git a/src/user/data.js b/src/user/data.js index d0c70ce80c..c8af4698a9 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -6,6 +6,7 @@ var winston = require('winston'); var db = require('../database'); var plugins = require('../plugins'); +var utils = require('../../public/src/utils'); module.exports = function(User) { @@ -136,6 +137,14 @@ module.exports = function(User) { return cur + next.charCodeAt(); }, 0) % iconBackgrounds.length]; } + + if (user.hasOwnProperty('joindate')) { + user.joindateISO = utils.toISOString(user.joindate); + } + + if (user.hasOwnProperty('lastonline')) { + user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; + } }); plugins.fireHook('filter:users.get', users, callback); diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index 02ba3ad45c..63da329784 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -1,5 +1,6 @@
-
+ +
Users
@@ -12,7 +13,6 @@
  • Banned
  • User Search
  • -
    + + Download CSV + + + + + + + +
    @@ -50,68 +60,51 @@ User not found!
    + 3 months 6 months 12 months - -
      + + + + + + + + + + + + + + -
      -
      - - - -
      {users.icon:text}
      - -
      - - - Not Validated - - - Admin - Banned -
      -
      - - {users.username} ({users.uid})
      - - {users.email}
      - - - joined
      - login
      - posts {users.postcount} - - - - -
      + + + + + + + + + + + + + - +
      uidusernameadminemailpostcountreputationflagsjoinedlast onlinebanned
      {users.uid}{users.username} + + + + + {users.email}{users.postcount}{users.reputation}{users.flags}0
    - -
    -
    -
    Users Control Panel
    -
    - - Download CSV - - - - - - - -
    -
    -