mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	
							
								
								
									
										43
									
								
								public/src/admin/advanced/events.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								public/src/admin/advanced/events.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | "use strict"; | ||||||
|  | /* global define, socket, app, templates */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | define('admin/advanced/events', ['forum/infinitescroll'], function(infinitescroll) { | ||||||
|  | 	var	Events = {}; | ||||||
|  |  | ||||||
|  | 	Events.init = function() { | ||||||
|  |  | ||||||
|  | 		$('[data-action="clear"]').on('click', function() { | ||||||
|  | 			socket.emit('admin.deleteAllEvents', function(err) { | ||||||
|  | 				if (err) { | ||||||
|  | 					return app.alertError(err.message); | ||||||
|  | 				} | ||||||
|  | 				$('.events-list').empty(); | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		infinitescroll.init(function(direction) { | ||||||
|  | 			if (direction < 0 || !$('.events').length) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(data, done) { | ||||||
|  | 				console.log(data.events); | ||||||
|  | 				if (data.events && data.events.length) { | ||||||
|  | 					templates.parse('admin/advanced/events', 'events', {events: data.events}, function(html) { | ||||||
|  | 						console.log(html); | ||||||
|  | 						$('.events-list').append(html); | ||||||
|  | 						done(); | ||||||
|  | 					}); | ||||||
|  |  | ||||||
|  | 					$('[data-next]').attr('data-next', data.next); | ||||||
|  | 				} else { | ||||||
|  | 					done(); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	return Events; | ||||||
|  | }); | ||||||
| @@ -174,14 +174,14 @@ adminController.database.get = function(req, res, next) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| adminController.events.get = function(req, res, next) { | adminController.events.get = function(req, res, next) { | ||||||
| 	events.getLog(-1, 5000, function(err, data) { | 	events.getEvents(0, 19, function(err, events) { | ||||||
| 		if(err || !data) { | 		if(err || !events) { | ||||||
| 			return next(err); | 			return next(err); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		res.render('admin/advanced/events', { | 		res.render('admin/advanced/events', { | ||||||
| 			eventdata: data.data, | 			events: events, | ||||||
| 			next: data.next | 			next: 20 | ||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										232
									
								
								src/events.js
									
									
									
									
									
								
							
							
						
						
									
										232
									
								
								src/events.js
									
									
									
									
									
								
							| @@ -1,154 +1,128 @@ | |||||||
|  |  | ||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var fs = require('fs'), | var async = require('async'), | ||||||
| 	winston = require('winston'), |  | ||||||
| 	path = require('path'), | 	db =  require('./database'), | ||||||
| 	nconf = require('nconf'), | 	batch = require('./batch'), | ||||||
| 	user = require('./user'); | 	user = require('./user'), | ||||||
|  | 	utils = require('../public/src/utils'); | ||||||
|  |  | ||||||
|  |  | ||||||
| (function(events) { | (function(events) { | ||||||
| 	var logFileName = 'logs/events.log'; | 	events.log = function(data, callback) { | ||||||
|  | 		callback = callback || function() {}; | ||||||
|  |  | ||||||
| 	events.logPasswordChange = function(uid) { | 		async.waterfall([ | ||||||
| 		events.logWithUser(uid, 'changed password'); | 			function(next) { | ||||||
|  | 				db.incrObjectField('global', 'nextEid', next); | ||||||
|  | 			}, | ||||||
|  | 			function(eid, next) { | ||||||
|  | 				data.timestamp = Date.now(); | ||||||
|  | 				data.eid = eid; | ||||||
|  |  | ||||||
|  | 				async.parallel([ | ||||||
|  | 					function(next) { | ||||||
|  | 						db.sortedSetAdd('events:time', data.timestamp, eid, next); | ||||||
|  | 					}, | ||||||
|  | 					function(next) { | ||||||
|  | 						db.setObject('event:' + eid, data, next); | ||||||
|  | 					} | ||||||
|  | 				], next); | ||||||
|  | 			} | ||||||
|  | 		], function(err, result) { | ||||||
|  | 			callback(err); | ||||||
|  | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	events.logAdminChangeUserPassword = function(adminUid, theirUid, callback) { | 	events.getEvents = function(start, stop, callback) { | ||||||
| 		logAdminEvent(adminUid, theirUid, 'changed password of', callback); | 		async.waterfall([ | ||||||
|  | 			function(next) { | ||||||
|  | 				db.getSortedSetRevRange('events:time', start, stop, next); | ||||||
|  | 			}, | ||||||
|  | 			function(eids, next) { | ||||||
|  | 				var keys = eids.map(function(eid) { | ||||||
|  | 					return 'event:' + eid; | ||||||
|  | 				}); | ||||||
|  | 				db.getObjects(keys, next); | ||||||
|  | 			}, | ||||||
|  | 			function(eventsData, next) { | ||||||
|  | 				eventsData.forEach(function(event) { | ||||||
|  | 					var e = utils.merge(event); | ||||||
|  | 					e.eid = e.uid = e.type = e.ip = undefined; | ||||||
|  | 					event.jsonString = JSON.stringify(e, null, 4); | ||||||
|  | 					event.timestampISO = new Date(parseInt(event.timestamp, 10)).toUTCString(); | ||||||
|  | 				}); | ||||||
|  | 				addUserData(eventsData, 'uid', 'user', next); | ||||||
|  | 			}, | ||||||
|  | 			function(eventsData, next) { | ||||||
|  | 				addUserData(eventsData, 'targetUid', 'targetUser', next); | ||||||
|  | 			} | ||||||
|  | 		], callback); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	events.logAdminUserDelete = function(adminUid, theirUid, callback) { | 	function addUserData(eventsData, field, objectName, callback) { | ||||||
| 		logAdminEvent(adminUid, theirUid, 'deleted', callback); | 		var uids = eventsData.map(function(event) { | ||||||
| 	}; | 			return event && event[field]; | ||||||
|  | 		}).filter(function(uid, index, array) { | ||||||
|  | 			return uid && array.indexOf(uid) === index; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 	function logAdminEvent(adminUid, theirUid, message, callback) { | 		if (!uids.length) { | ||||||
| 		user.getMultipleUserFields([adminUid, theirUid], ['username'], function(err, userData) { | 			return callback(null, eventsData); | ||||||
| 			if(err) { | 		} | ||||||
| 				return winston.error('Error logging event. ' + err.message); |  | ||||||
|  | 		async.parallel({ | ||||||
|  | 			isAdmin: function(next) { | ||||||
|  | 				user.isAdministrator(uids, next); | ||||||
|  | 			}, | ||||||
|  | 			userData: function(next) { | ||||||
|  | 				user.getMultipleUserFields(uids, ['username', 'userslug', 'picture'], next); | ||||||
|  | 			} | ||||||
|  | 		}, function(err, results) { | ||||||
|  | 			if (err) { | ||||||
|  | 				return callback(err); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			var msg = userData[0].username + '(uid ' + adminUid + ') ' + message + ' ' +  userData[1].username + '(uid ' + theirUid + ')'; | 			var userData = results.userData; | ||||||
| 			events.log(msg, callback); |  | ||||||
|  | 			var map = {}; | ||||||
|  | 			userData.forEach(function(user, index) { | ||||||
|  | 				user.isAdmin = results.isAdmin[index]; | ||||||
|  | 				map[user.uid] = user; | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			eventsData.forEach(function(event) { | ||||||
|  | 				if (map[event[field]]) { | ||||||
|  | 					event[objectName] = map[event[field]]; | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 			callback(null, eventsData); | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	events.logPasswordReset = function(uid) { | 	events.deleteEvents = function(eids, callback) { | ||||||
| 		events.logWithUser(uid, 'reset password'); | 		callback = callback || function() {}; | ||||||
| 	}; | 		async.parallel([ | ||||||
|  | 			function(next) { | ||||||
| 	events.logEmailChange = function(uid, oldEmail, newEmail) { | 				var keys = eids.map(function(eid) { | ||||||
| 		events.logWithUser(uid,'changed email from "' + oldEmail + '" to "' + newEmail +'"'); | 					return 'event:' + eid; | ||||||
| 	}; | 				}); | ||||||
|  | 				db.deleteAll(keys, next); | ||||||
| 	events.logUsernameChange = function(uid, oldUsername, newUsername) { | 			}, | ||||||
| 		events.logWithUser(uid,'changed username from "' + oldUsername + '" to "' + newUsername +'"'); | 			function(next) { | ||||||
| 	}; | 				db.sortedSetRemove('events:time', eids, next); | ||||||
|  |  | ||||||
| 	events.logAdminLogin = function(uid) { |  | ||||||
| 		events.logWithUser(uid, 'logged into admin panel'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logPostEdit = function(uid, pid) { |  | ||||||
| 		events.logWithUser(uid, 'edited post (pid ' + pid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logPostDelete = function(uid, pid) { |  | ||||||
| 		events.logWithUser(uid, 'deleted post (pid ' + pid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logPostRestore = function(uid, pid) { |  | ||||||
| 		events.logWithUser(uid, 'restored post (pid ' + pid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logPostPurge = function(uid, pid) { |  | ||||||
| 		events.logWithUser(uid, 'purged post (pid ' + pid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logTopicMove = function(uid, tid) { |  | ||||||
| 		events.logWithUser(uid, 'moved topic (tid ' + tid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logTopicDelete = function(uid, tid) { |  | ||||||
| 		events.logWithUser(uid, 'deleted topic (tid ' + tid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logTopicRestore = function(uid, tid) { |  | ||||||
| 		events.logWithUser(uid, 'restored topic (tid ' + tid + ')'); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logAccountLock = function(uid, until) { |  | ||||||
| 		var date = new Date(); |  | ||||||
| 		date.setTime(date.getTime() + until); |  | ||||||
|  |  | ||||||
| 		events.logWithUser(uid, 'locked out until ' + date.toString()); |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	events.logWithUser = function(uid, string) { |  | ||||||
| 		user.getUserField(uid, 'username', function(err, username) { |  | ||||||
| 			if(err) { |  | ||||||
| 				return winston.error('Error logging event. ' + err.message); |  | ||||||
| 			} | 			} | ||||||
|  | 		], callback); | ||||||
| 			var msg = username + '(uid ' + uid + ') ' + string; |  | ||||||
| 			events.log(msg); |  | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	events.log = function(msg, callback) { | 	events.deleteAll = function(callback) { | ||||||
| 		var logFile = path.join(nconf.get('base_dir'), logFileName); | 		callback = callback || function() {}; | ||||||
|  |  | ||||||
| 		msg = '[' + new Date().toUTCString() + '] - ' + msg; | 		batch.processSortedSet('events:time', function(eids, next) { | ||||||
|  | 			events.deleteEvents(eids, callback); | ||||||
| 		fs.appendFile(logFile, msg + '\n', function(err) { | 		}, {alwaysStartAt: 0}, callback); | ||||||
| 			if(err) { |  | ||||||
| 				winston.error('Error logging event. ' + err.message); |  | ||||||
| 				if (typeof callback === 'function') { |  | ||||||
| 					callback(err); |  | ||||||
| 				} |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if (typeof callback === 'function') { |  | ||||||
| 				callback(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	events.getLog = function(end, len, callback) { |  | ||||||
| 		var logFile = path.join(nconf.get('base_dir'), logFileName); |  | ||||||
|  |  | ||||||
| 		fs.stat(logFile, function(err, stat) { |  | ||||||
| 			if (err) { |  | ||||||
| 				return callback(null, 'No logs found!'); |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var buffer = ''; |  | ||||||
| 			var size = stat.size; |  | ||||||
| 			if (end === -1) { |  | ||||||
| 				end = size; |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			end = parseInt(end, 10); |  | ||||||
| 			var start = Math.max(0, end - len); |  | ||||||
|  |  | ||||||
| 			var rs = fs.createReadStream(logFile, {start: start, end: end}); |  | ||||||
| 			rs.addListener('data', function(lines) { |  | ||||||
| 				buffer += lines.toString(); |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 			rs.addListener('end', function() { |  | ||||||
| 				var firstNewline = buffer.indexOf('\n'); |  | ||||||
| 				if (firstNewline !== -1) { |  | ||||||
| 					buffer = buffer.slice(firstNewline); |  | ||||||
| 					buffer = buffer.split('\n').reverse().join('\n'); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				callback(null, {data: buffer, next: end - buffer.length}); |  | ||||||
| 			}); |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| }(module.exports)); | }(module.exports)); | ||||||
|   | |||||||
| @@ -43,10 +43,6 @@ var async = require('async'), | |||||||
|  |  | ||||||
| 				db.sortedSetAdd('users:reputation', newreputation, postData.uid); | 				db.sortedSetAdd('users:reputation', newreputation, postData.uid); | ||||||
|  |  | ||||||
| 				if (type === 'downvote') { |  | ||||||
| 					banUserForLowReputation(postData.uid, newreputation); |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				adjustPostVotes(pid, uid, type, unvote, function(err, votes) { | 				adjustPostVotes(pid, uid, type, unvote, function(err, votes) { | ||||||
| 					postData.votes = votes; | 					postData.votes = votes; | ||||||
| 					callback(err, { | 					callback(err, { | ||||||
| @@ -62,23 +58,6 @@ var async = require('async'), | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	function banUserForLowReputation(uid, newreputation) { |  | ||||||
| 		if (parseInt(meta.config['autoban:downvote'], 10) === 1 && newreputation < parseInt(meta.config['autoban:downvote:threshold'], 10)) { |  | ||||||
| 			user.getUserField(uid, 'banned', function(err, banned) { |  | ||||||
| 				if (err || parseInt(banned, 10) === 1) { |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 				var adminUser = require('./socket.io/admin/user'); |  | ||||||
| 				adminUser.banUser(uid, function(err) { |  | ||||||
| 					if (err) { |  | ||||||
| 						return winston.error(err.message); |  | ||||||
| 					} |  | ||||||
| 					winston.info('uid ' + uid + ' was banned for reaching ' + newreputation + ' reputation'); |  | ||||||
| 				}); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	function adjustPostVotes(pid, uid, type, unvote, callback) { | 	function adjustPostVotes(pid, uid, type, unvote, callback) { | ||||||
| 		var notType = (type === 'upvote' ? 'downvote' : 'upvote'); | 		var notType = (type === 'upvote' ? 'downvote' : 'upvote'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -311,8 +311,6 @@ var async = require('async'), | |||||||
| 				if (process.env.NODE_ENV === 'development') { | 				if (process.env.NODE_ENV === 'development') { | ||||||
| 					winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.'); | 					winston.info('[notifications.prune] Notification pruning completed. ' + numPruned + ' expired notification' + (numPruned !== 1 ? 's' : '') + ' removed.'); | ||||||
| 				} | 				} | ||||||
| 				var diff = process.hrtime(start); |  | ||||||
| 				events.log('Pruning '+ numPruned + ' notifications took : ' + (diff[0] * 1e3 + diff[1] / 1e6) + ' ms'); |  | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ var winston = require('winston'), | |||||||
| 					return callback(err); | 					return callback(err); | ||||||
| 				} | 				} | ||||||
| 				results.content = results.postData.content; | 				results.content = results.postData.content; | ||||||
| 				//events.logPostEdit(uid, pid); |  | ||||||
| 				plugins.fireHook('action:post.edit', postData); | 				plugins.fireHook('action:post.edit', postData); | ||||||
| 				callback(null, results); | 				callback(null, results); | ||||||
| 			}); | 			}); | ||||||
| @@ -146,7 +146,6 @@ var winston = require('winston'), | |||||||
| 				return callback(err); | 				return callback(err); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			events[isDelete ? 'logPostDelete' : 'logPostRestore'](uid, pid); |  | ||||||
| 			if (isDelete) { | 			if (isDelete) { | ||||||
| 				posts.delete(pid, callback); | 				posts.delete(pid, callback); | ||||||
| 			} else { | 			} else { | ||||||
| @@ -165,7 +164,7 @@ var winston = require('winston'), | |||||||
| 			if (err || !canEdit) { | 			if (err || !canEdit) { | ||||||
| 				return callback(err || new Error('[[error:no-privileges]]')); | 				return callback(err || new Error('[[error:no-privileges]]')); | ||||||
| 			} | 			} | ||||||
| 			events.logPostPurge(uid, pid); |  | ||||||
| 			posts.purge(pid, callback); | 			posts.purge(pid, callback); | ||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ | |||||||
| 		}); | 		}); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Auth.login = function(username, password, next) { | 	Auth.login = function(req, username, password, next) { | ||||||
| 		if (!username || !password) { | 		if (!username || !password) { | ||||||
| 			return next(new Error('[[error:invalid-password]]')); | 			return next(new Error('[[error:invalid-password]]')); | ||||||
| 		} | 		} | ||||||
| @@ -85,7 +85,7 @@ | |||||||
| 					return next(new Error('[[error:no-user]]')); | 					return next(new Error('[[error:no-user]]')); | ||||||
| 				} | 				} | ||||||
| 				uid = _uid; | 				uid = _uid; | ||||||
| 				user.auth.logAttempt(uid, next); | 				user.auth.logAttempt(uid, req.ip, next); | ||||||
| 			}, | 			}, | ||||||
| 			function(next) { | 			function(next) { | ||||||
| 				db.getObjectFields('user:' + uid, ['password', 'banned'], next); | 				db.getObjectFields('user:' + uid, ['password', 'banned'], next); | ||||||
| @@ -109,7 +109,7 @@ | |||||||
| 		], next); | 		], next); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	passport.use(new passportLocal(Auth.login)); | 	passport.use(new passportLocal({passReqToCallback: true}, Auth.login)); | ||||||
|  |  | ||||||
| 	passport.serializeUser(function(user, done) { | 	passport.serializeUser(function(user, done) { | ||||||
| 		done(null, user.uid); | 		done(null, user.uid); | ||||||
|   | |||||||
| @@ -49,7 +49,11 @@ SocketAdmin.before = function(socket, method, next) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketAdmin.reload = function(socket, data, callback) { | SocketAdmin.reload = function(socket, data, callback) { | ||||||
| 	events.logWithUser(socket.uid, ' is reloading NodeBB'); | 	events.log({ | ||||||
|  | 		type: 'reload', | ||||||
|  | 		uid: socket.uid, | ||||||
|  | 		ip: socket.ip | ||||||
|  | 	}); | ||||||
| 	if (process.send) { | 	if (process.send) { | ||||||
| 		process.send({ | 		process.send({ | ||||||
| 			action: 'reload' | 			action: 'reload' | ||||||
| @@ -60,7 +64,11 @@ SocketAdmin.reload = function(socket, data, callback) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketAdmin.restart = function(socket, data, callback) { | SocketAdmin.restart = function(socket, data, callback) { | ||||||
| 	events.logWithUser(socket.uid, ' is restarting NodeBB'); | 	events.log({ | ||||||
|  | 		type: 'restart', | ||||||
|  | 		uid: socket.uid, | ||||||
|  | 		ip: socket.ip | ||||||
|  | 	}); | ||||||
| 	meta.restart(); | 	meta.restart(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -274,10 +282,21 @@ function getMonthlyPageViews(callback) { | |||||||
| } | } | ||||||
|  |  | ||||||
| SocketAdmin.getMoreEvents = function(socket, next, callback) { | SocketAdmin.getMoreEvents = function(socket, next, callback) { | ||||||
| 	if (parseInt(next, 10) < 0) { | 	var start = parseInt(next, 10); | ||||||
|  | 	if (start < 0) { | ||||||
| 		return callback(null, {data: [], next: next}); | 		return callback(null, {data: [], next: next}); | ||||||
| 	} | 	} | ||||||
| 	events.getLog(next, 5000, callback); | 	var end = next + 10; | ||||||
|  | 	events.getEvents(start, end, function(err, events) { | ||||||
|  | 		if (err) { | ||||||
|  | 			return callback(err); | ||||||
|  | 		} | ||||||
|  | 		callback(null, {events: events, next: end + 1}); | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | SocketAdmin.deleteAllEvents = function(socket, data, callback) { | ||||||
|  | 	events.deleteAll(callback); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketAdmin.dismissFlag = function(socket, pid, callback) { | SocketAdmin.dismissFlag = function(socket, pid, callback) { | ||||||
|   | |||||||
| @@ -157,7 +157,7 @@ User.deleteUsers = function(socket, uids, callback) { | |||||||
| 	async.each(uids, function(uid, next) { | 	async.each(uids, function(uid, next) { | ||||||
| 		user.isAdministrator(uid, function(err, isAdmin) { | 		user.isAdministrator(uid, function(err, isAdmin) { | ||||||
| 			if (err || isAdmin) { | 			if (err || isAdmin) { | ||||||
| 				return callback(err || new Error('[[error:cant-ban-other-admins]]')); | 				return callback(err || new Error('[[error:cant-delete-other-admins]]')); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			user.delete(uid, function(err) { | 			user.delete(uid, function(err) { | ||||||
| @@ -165,7 +165,12 @@ User.deleteUsers = function(socket, uids, callback) { | |||||||
| 					return next(err); | 					return next(err); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				events.logAdminUserDelete(socket.uid, uid); | 				events.log({ | ||||||
|  | 					type: 'user-delete', | ||||||
|  | 					uid: socket.uid, | ||||||
|  | 					targetUid: uid, | ||||||
|  | 					ip: socket.ip | ||||||
|  | 				}); | ||||||
|  |  | ||||||
| 				websockets.logoutUser(uid); | 				websockets.logoutUser(uid); | ||||||
| 				next(); | 				next(); | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ var	async = require('async'), | |||||||
| 	groups = require('../groups'), | 	groups = require('../groups'), | ||||||
| 	user = require('../user'), | 	user = require('../user'), | ||||||
| 	websockets = require('./index'), | 	websockets = require('./index'), | ||||||
|  | 	events = require('../events'), | ||||||
| 	utils = require('../../public/src/utils'), | 	utils = require('../../public/src/utils'), | ||||||
|  |  | ||||||
| 	SocketPosts = {}; | 	SocketPosts = {}; | ||||||
| @@ -138,7 +139,38 @@ SocketPosts.upvote = function(socket, data, callback) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketPosts.downvote = function(socket, data, callback) { | SocketPosts.downvote = function(socket, data, callback) { | ||||||
| 	favouriteCommand(socket, 'downvote', 'voted', '', data, callback); | 	function banUserForLowReputation(uid, callback) { | ||||||
|  | 		if (parseInt(meta.config['autoban:downvote'], 10) === 1) { | ||||||
|  | 			user.getUserFields(uid, ['reputation', 'banned'], function(err, userData) { | ||||||
|  | 				if (err || parseInt(userData.banned, 10) === 1 || parseInt(userData.reputation) >= parseInt(meta.config['autoban:downvote:threshold'], 10)) { | ||||||
|  | 					return callback(err); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				var adminUser = require('./admin/user'); | ||||||
|  | 				adminUser.banUser(uid, function(err) { | ||||||
|  | 					if (err) { | ||||||
|  | 						return callback(err); | ||||||
|  | 					} | ||||||
|  | 					events.log({ | ||||||
|  | 						type: 'banned', | ||||||
|  | 						reason: 'low-reputation', | ||||||
|  | 						uid: socket.uid, | ||||||
|  | 						ip: socket.ip, | ||||||
|  | 						targetUid: data.uid, | ||||||
|  | 						reputation: userData.reputation | ||||||
|  | 					}); | ||||||
|  | 					callback(); | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	favouriteCommand(socket, 'downvote', 'voted', '', data, function(err) { | ||||||
|  | 		if (err) { | ||||||
|  | 			return callback(err); | ||||||
|  | 		} | ||||||
|  | 		banUserForLowReputation(data.uid, callback); | ||||||
|  | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketPosts.unvote = function(socket, data, callback) { | SocketPosts.unvote = function(socket, data, callback) { | ||||||
| @@ -309,9 +341,16 @@ function deleteOrRestore(command, socket, data, callback) { | |||||||
| 			return callback(err); | 			return callback(err); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var eventName = command === 'restore' ? 'event:post_restored' : 'event:post_deleted'; | 		var eventName = command === 'delete' ? 'event:post_deleted' : 'event:post_restored'; | ||||||
| 		websockets.in('topic_' + data.tid).emit(eventName, postData); | 		websockets.in('topic_' + data.tid).emit(eventName, postData); | ||||||
|  |  | ||||||
|  | 		events.log({ | ||||||
|  | 			type: command === 'delete' ? 'post-delete' : 'post-restore', | ||||||
|  | 			uid: socket.uid, | ||||||
|  | 			pid: data.pid, | ||||||
|  | 			ip: socket.ip | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		callback(); | 		callback(); | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -327,6 +366,13 @@ SocketPosts.purge = function(socket, data, callback) { | |||||||
|  |  | ||||||
| 		websockets.in('topic_' + data.tid).emit('event:post_purged', data.pid); | 		websockets.in('topic_' + data.tid).emit('event:post_purged', data.pid); | ||||||
|  |  | ||||||
|  | 		events.log({ | ||||||
|  | 			type: 'post-purge', | ||||||
|  | 			uid: socket.uid, | ||||||
|  | 			pid: data.pid, | ||||||
|  | 			ip: socket.ip | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		callback(); | 		callback(); | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ var nconf = require('nconf'), | |||||||
| 	user = require('../user'), | 	user = require('../user'), | ||||||
| 	db = require('../database'), | 	db = require('../database'), | ||||||
| 	meta = require('../meta'), | 	meta = require('../meta'), | ||||||
|  | 	events = require('../events'), | ||||||
| 	utils = require('../../public/src/utils'), | 	utils = require('../../public/src/utils'), | ||||||
| 	SocketPosts = require('./posts'), | 	SocketPosts = require('./posts'), | ||||||
|  |  | ||||||
| @@ -259,7 +260,21 @@ function doTopicAction(action, socket, data, callback) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if(typeof threadTools[action] === 'function') { | 			if(typeof threadTools[action] === 'function') { | ||||||
| 				threadTools[action](tid, socket.uid, next); | 				threadTools[action](tid, socket.uid, function(err) { | ||||||
|  | 					if (err) { | ||||||
|  | 						return next(err); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if (action === 'delete' || action === 'restore' || action === 'purge') { | ||||||
|  | 						events.log({ | ||||||
|  | 							type: 'topic-' + action, | ||||||
|  | 							uid: socket.uid, | ||||||
|  | 							ip: socket.ip, | ||||||
|  | 							tid: tid | ||||||
|  | 						}); | ||||||
|  | 					} | ||||||
|  | 					next(); | ||||||
|  | 				}); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 	}, callback); | 	}, callback); | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ var	async = require('async'), | |||||||
| 	utils = require('../../public/src/utils'), | 	utils = require('../../public/src/utils'), | ||||||
| 	websockets = require('./index'), | 	websockets = require('./index'), | ||||||
| 	meta = require('../meta'), | 	meta = require('../meta'), | ||||||
|  | 	events = require('../events'), | ||||||
| 	SocketUser = {}; | 	SocketUser = {}; | ||||||
|  |  | ||||||
| SocketUser.exists = function(socket, data, callback) { | SocketUser.exists = function(socket, data, callback) { | ||||||
| @@ -56,7 +57,7 @@ SocketUser.emailConfirm = function(socket, data, callback) { | |||||||
|  |  | ||||||
| SocketUser.search = function(socket, data, callback) { | SocketUser.search = function(socket, data, callback) { | ||||||
| 	if (!data) { | 	if (!data) { | ||||||
| 		return callback(new Error('[[error:invalid-data]]')) | 		return callback(new Error('[[error:invalid-data]]')); | ||||||
| 	} | 	} | ||||||
| 	if (!socket.uid) { | 	if (!socket.uid) { | ||||||
| 		return callback(new Error('[[error:not-logged-in]]')); | 		return callback(new Error('[[error:not-logged-in]]')); | ||||||
| @@ -87,7 +88,16 @@ SocketUser.reset.valid = function(socket, code, callback) { | |||||||
|  |  | ||||||
| SocketUser.reset.commit = function(socket, data, callback) { | SocketUser.reset.commit = function(socket, data, callback) { | ||||||
| 	if(data && data.code && data.password) { | 	if(data && data.code && data.password) { | ||||||
| 		user.reset.commit(data.code, data.password, callback); | 		user.reset.commit(data.code, data.password, function(err) { | ||||||
|  | 			if (err) { | ||||||
|  | 				return callback(err); | ||||||
|  | 			} | ||||||
|  | 			events.log({ | ||||||
|  | 				type: 'password-reset', | ||||||
|  | 				uid: socket.uid, | ||||||
|  | 				ip: socket.ip | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -109,12 +119,74 @@ SocketUser.checkStatus = function(socket, uid, callback) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketUser.changePassword = function(socket, data, callback) { | SocketUser.changePassword = function(socket, data, callback) { | ||||||
| 	if (data && socket.uid) { | 	if (!data || !data.uid) { | ||||||
| 		user.changePassword(socket.uid, data, callback); | 		return callback(new Error('[[error:invalid-data]]')); | ||||||
| 	} | 	} | ||||||
|  | 	if (!socket.uid) { | ||||||
|  | 		return callback('[[error:invalid-uid]]'); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	user.changePassword(socket.uid, data, function(err) { | ||||||
|  | 		if (err) { | ||||||
|  | 			return callback(err); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		events.log({ | ||||||
|  | 			type: 'password-change', | ||||||
|  | 			uid: socket.uid, | ||||||
|  | 			targetUid: data.uid, | ||||||
|  | 			ip: socket.ip | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| SocketUser.updateProfile = function(socket, data, callback) { | SocketUser.updateProfile = function(socket, data, callback) { | ||||||
|  | 	function update(oldUserData) { | ||||||
|  | 		function done(err, userData) { | ||||||
|  | 			if (err) { | ||||||
|  | 				return callback(err); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (userData.email !== oldUserData.email) { | ||||||
|  | 				events.log({ | ||||||
|  | 					type: 'email-change', | ||||||
|  | 					uid: socket.uid, | ||||||
|  | 					targetUid: data.uid, | ||||||
|  | 					ip: socket.ip, | ||||||
|  | 					oldEmail: oldUserData.email, | ||||||
|  | 					newEmail: userData.email | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (userData.username !== oldUserData.username) { | ||||||
|  | 				events.log({ | ||||||
|  | 					type: 'username-change', | ||||||
|  | 					uid: socket.uid, | ||||||
|  | 					targetUid: data.uid, | ||||||
|  | 					ip: socket.ip, | ||||||
|  | 					oldUsername: oldUserData.username, | ||||||
|  | 					newUsername: userData.username | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (socket.uid === parseInt(data.uid, 10)) { | ||||||
|  | 			return user.updateProfile(socket.uid, data, done); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		user.isAdministrator(socket.uid, function(err, isAdmin) { | ||||||
|  | 			if (err) { | ||||||
|  | 				return callback(err); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (!isAdmin) { | ||||||
|  | 				return callback(new Error('[[error:no-privileges]]')); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			user.updateProfile(data.uid, data, done); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (!socket.uid) { | 	if (!socket.uid) { | ||||||
| 		return callback('[[error:invalid-uid]]'); | 		return callback('[[error:invalid-uid]]'); | ||||||
| 	} | 	} | ||||||
| @@ -123,20 +195,12 @@ SocketUser.updateProfile = function(socket, data, callback) { | |||||||
| 		return callback(new Error('[[error:invalid-data]]')); | 		return callback(new Error('[[error:invalid-data]]')); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (socket.uid === parseInt(data.uid, 10)) { | 	user.getUserFields(data.uid, ['email', 'username'], function(err, oldUserData) { | ||||||
| 		return user.updateProfile(socket.uid, data, callback); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	user.isAdministrator(socket.uid, function(err, isAdmin) { |  | ||||||
| 		if (err) { | 		if (err) { | ||||||
| 			return callback(err); | 			return callback(err); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!isAdmin) { | 		update(oldUserData, callback); | ||||||
| 			return callback(new Error('[[error:no-privileges]]')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		user.updateProfile(data.uid, data, callback); |  | ||||||
| 	}); | 	}); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -56,8 +56,6 @@ var winston = require('winston'), | |||||||
| 					plugins.fireHook('action:topic.restore', topicData); | 					plugins.fireHook('action:topic.restore', topicData); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				events[isDelete ? 'logTopicDelete' : 'logTopicRestore'](uid, tid); |  | ||||||
|  |  | ||||||
| 				emitTo('topic_' + tid); | 				emitTo('topic_' + tid); | ||||||
| 				emitTo('category_' + topicData.cid); | 				emitTo('category_' + topicData.cid); | ||||||
|  |  | ||||||
| @@ -214,8 +212,6 @@ var winston = require('winston'), | |||||||
|  |  | ||||||
| 			topics.setTopicField(tid, 'cid', cid, callback); | 			topics.setTopicField(tid, 'cid', cid, callback); | ||||||
|  |  | ||||||
| 			events.logTopicMove(uid, tid); |  | ||||||
|  |  | ||||||
| 			plugins.fireHook('action:topic.move', { | 			plugins.fireHook('action:topic.move', { | ||||||
| 				tid: tid, | 				tid: tid, | ||||||
| 				fromCid: oldCid, | 				fromCid: oldCid, | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ var async = require('async'), | |||||||
| module.exports = function(User) { | module.exports = function(User) { | ||||||
| 	User.auth = {}; | 	User.auth = {}; | ||||||
|  |  | ||||||
| 	User.auth.logAttempt = function(uid, callback) { | 	User.auth.logAttempt = function(uid, ip, callback) { | ||||||
| 		db.exists('lockout:' + uid, function(err, exists) { | 		db.exists('lockout:' + uid, function(err, exists) { | ||||||
| 			if (err) { | 			if (err) { | ||||||
| 				return callback(err); | 				return callback(err); | ||||||
| @@ -30,12 +30,15 @@ module.exports = function(User) { | |||||||
| 							return callback(err); | 							return callback(err); | ||||||
| 						} | 						} | ||||||
| 						var duration = 1000 * 60 * (meta.config.lockoutDuration || 60); | 						var duration = 1000 * 60 * (meta.config.lockoutDuration || 60); | ||||||
| 						 |  | ||||||
| 						db.delete('loginAttempts:' + uid); | 						db.delete('loginAttempts:' + uid); | ||||||
| 						db.pexpire('lockout:' + uid, duration); | 						db.pexpire('lockout:' + uid, duration); | ||||||
|  | 						events.log({ | ||||||
| 						events.logAccountLock(uid, duration); | 							type: 'account-locked', | ||||||
| 						callback(new Error('account-locked')); | 							uid: uid, | ||||||
|  | 							ip: ip | ||||||
|  | 						}); | ||||||
|  | 						callback(new Error('[[error:account-locked]]')); | ||||||
| 					}); | 					}); | ||||||
| 				} else { | 				} else { | ||||||
| 					db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60); | 					db.pexpire('loginAttempts:' + uid, 1000 * 60 * 60); | ||||||
|   | |||||||
| @@ -148,8 +148,6 @@ module.exports = function(User) { | |||||||
| 					return callback(err); | 					return callback(err); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				events.logEmailChange(uid, userData.email, newEmail); |  | ||||||
|  |  | ||||||
| 				var gravatarpicture = User.createGravatarURLFromEmail(newEmail); | 				var gravatarpicture = User.createGravatarURLFromEmail(newEmail); | ||||||
| 				async.parallel([ | 				async.parallel([ | ||||||
| 					function(next) { | 					function(next) { | ||||||
| @@ -183,9 +181,13 @@ module.exports = function(User) { | |||||||
| 		if (!newUsername) { | 		if (!newUsername) { | ||||||
| 			return callback(); | 			return callback(); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { | 		User.getUserFields(uid, ['username', 'userslug'], function(err, userData) { | ||||||
| 			function update(field, object, value, callback) { | 			function update(field, object, value, callback) { | ||||||
| 				async.parallel([ | 				async.series([ | ||||||
|  | 					function(next) { | ||||||
|  | 						db.deleteObjectField(field + ':uid', userData[field], next); | ||||||
|  | 					}, | ||||||
| 					function(next) { | 					function(next) { | ||||||
| 						User.setUserField(uid, field, value, next); | 						User.setUserField(uid, field, value, next); | ||||||
| 					}, | 					}, | ||||||
| @@ -198,7 +200,6 @@ module.exports = function(User) { | |||||||
| 			if (err) { | 			if (err) { | ||||||
| 				return callback(err); | 				return callback(err); | ||||||
| 			} | 			} | ||||||
| 			var userslug = utils.slugify(newUsername); |  | ||||||
|  |  | ||||||
| 			async.parallel([ | 			async.parallel([ | ||||||
| 				function(next) { | 				function(next) { | ||||||
| @@ -206,25 +207,15 @@ module.exports = function(User) { | |||||||
| 						return next(); | 						return next(); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					db.deleteObjectField('username:uid', userData.username, function(err) { | 					update('username', 'username:uid', newUsername, next); | ||||||
| 						if (err) { |  | ||||||
| 							return next(err); |  | ||||||
| 						} |  | ||||||
| 						events.logUsernameChange(uid, userData.username, newUsername); |  | ||||||
| 						update('username', 'username:uid', newUsername, next); |  | ||||||
| 					}); |  | ||||||
| 				}, | 				}, | ||||||
| 				function(next) { | 				function(next) { | ||||||
| 					if (userslug === userData.userslug) { | 					var newUserslug = utils.slugify(newUsername); | ||||||
|  | 					if (newUserslug === userData.userslug) { | ||||||
| 						return next(); | 						return next(); | ||||||
| 					} | 					} | ||||||
|  |  | ||||||
| 					db.deleteObjectField('userslug:uid', userData.userslug, function(err) { | 					update('userslug', 'userslug:uid', newUserslug, next); | ||||||
| 						if (err) { |  | ||||||
| 							return next(err); |  | ||||||
| 						} |  | ||||||
| 						update('userslug', 'userslug:uid', userslug, next); |  | ||||||
| 					}); |  | ||||||
| 				} | 				} | ||||||
| 			], callback); | 			], callback); | ||||||
| 		}); | 		}); | ||||||
| @@ -261,23 +252,11 @@ module.exports = function(User) { | |||||||
|  |  | ||||||
| 		function hashAndSetPassword(callback) { | 		function hashAndSetPassword(callback) { | ||||||
| 			User.hashPassword(data.newPassword, function(err, hash) { | 			User.hashPassword(data.newPassword, function(err, hash) { | ||||||
| 				if(err) { | 				if (err) { | ||||||
| 					return callback(err); | 					return callback(err); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				User.setUserField(data.uid, 'password', hash, function(err) { | 				User.setUserField(data.uid, 'password', hash, callback); | ||||||
| 					if(err) { |  | ||||||
| 						return callback(err); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					if(parseInt(uid, 10) === parseInt(data.uid, 10)) { |  | ||||||
| 						events.logPasswordChange(data.uid); |  | ||||||
| 					} else { |  | ||||||
| 						events.logAdminChangeUserPassword(uid, data.uid); |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					callback(); |  | ||||||
| 				}); |  | ||||||
| 			}); | 			}); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -83,7 +83,6 @@ var async = require('async'), | |||||||
| 						return callback(err); | 						return callback(err); | ||||||
| 					} | 					} | ||||||
| 					user.setUserField(uid, 'password', hash); | 					user.setUserField(uid, 'password', hash); | ||||||
| 					events.logPasswordReset(uid); |  | ||||||
|  |  | ||||||
| 					db.deleteObjectField('reset:uid', code); | 					db.deleteObjectField('reset:uid', code); | ||||||
| 					db.deleteObjectField('reset:expiry', code); | 					db.deleteObjectField('reset:expiry', code); | ||||||
|   | |||||||
| @@ -1,31 +1,32 @@ | |||||||
| <div class="events"> | <div class="events"> | ||||||
| 	<div class="col-sm-9"> | 	<div class="col-lg-9"> | ||||||
| 		<div class="panel panel-default"> | 		<div class="panel panel-default"> | ||||||
| 			<div class="panel-heading"><i class="fa fa-calendar-o"></i> Events</div> | 			<div class="panel-heading"><i class="fa fa-calendar-o"></i> Events</div> | ||||||
| 			<div class="panel-body" data-next="{next}"> | 			<div class="panel-body" data-next="{next}"> | ||||||
| 				<pre>{eventdata}</pre> | 				<!-- IF !events.length --> | ||||||
|  | 				<div class="alert alert-info">There are no events</div> | ||||||
|  | 				<!-- ENDIF !events.length --> | ||||||
|  | 				<div class="events-list"> | ||||||
|  | 				<!-- BEGIN events --> | ||||||
|  | 				<div> | ||||||
|  | 					<span>#{events.eid} </span><span class="label label-info">{events.type}</span> | ||||||
|  | 					<a href="{config.relative_path}/user/{events.user.userslug}"><img class="user-img" src="{events.user.picture}"/></a> <a href="{config.relative_path}/user/{events.user.userslug}">{events.user.username}</a> (uid {events.user.uid}) (IP {events.ip}) | ||||||
|  | 					<span class="pull-right">{events.timestampISO}</span> | ||||||
|  | 					<br/><br/> | ||||||
|  | 					<pre>{events.jsonString}</pre> | ||||||
|  | 				</div> | ||||||
|  | 				<!-- END events --> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="col-lg-3"> | ||||||
|  | 		<div class="panel panel-default affix"> | ||||||
|  | 			<div class="panel-heading">Events Control Panel</div> | ||||||
|  | 			<div class="panel-body"> | ||||||
|  | 				<button class="btn btn-warning" data-action="clear"><i class="fa fa-eraser"></i> Delete Events</button> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
|  |  | ||||||
|  |  | ||||||
| <script> |  | ||||||
| require(['forum/infinitescroll'], function(infinitescroll) { |  | ||||||
|  |  | ||||||
| 	infinitescroll.init(function(direction) { |  | ||||||
| 		if (direction < 0 || !$('.events').length) { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		infinitescroll.loadMore('admin.getMoreEvents', $('[data-next]').attr('data-next'), function(events, done) { |  | ||||||
| 			if (events.data && events.data.length) { |  | ||||||
| 				$('.panel-body pre').append(events.data); |  | ||||||
| 				$('[data-next]').attr('data-next', events.next); |  | ||||||
| 			} |  | ||||||
| 			done(); |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| </script> |  | ||||||
		Reference in New Issue
	
	Block a user