mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
feat: design revamp of ACP events page (#7664)
* feat: design revamp of ACP events page - minor UX changes - new filter controls, filters moved to sidebar - form usable without js - start/end filters NOT programmed yet * feat: events ACP limit by date and perPage * feat: pre-fill applied filters on template render * fix: missing translation for ACP events perPage filter * fix: added some comments to explain setHours behaviour * fix: stop - start + 1 * fix: socket tests * fix: +inf, not just inf * fix: tests, because I am an idiot
This commit is contained in:
@@ -2,5 +2,11 @@
|
||||
"events": "Events",
|
||||
"no-events": "There are no events",
|
||||
"control-panel": "Events Control Panel",
|
||||
"delete-events": "Delete Events"
|
||||
"delete-events": "Delete Events",
|
||||
"filters": "Filters",
|
||||
"filters-apply": "Apply Filters",
|
||||
"filter-type": "Event Type",
|
||||
"filter-start": "Start Date",
|
||||
"filter-end": "End Date",
|
||||
"filter-perPage": "Per Page"
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
.delete-event {
|
||||
i {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
.events-list {
|
||||
.delete-event {
|
||||
i {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,14 @@ define('admin/advanced/events', function () {
|
||||
});
|
||||
});
|
||||
|
||||
$('#filter').on('change', function () {
|
||||
var filter = $(this).val();
|
||||
ajaxify.go('admin/advanced/events' + (filter ? '?filter=' + filter : ''));
|
||||
});
|
||||
$('#apply').on('click', Events.refresh);
|
||||
};
|
||||
|
||||
Events.refresh = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
var formEl = $('#filters');
|
||||
ajaxify.go('admin/advanced/events?' + formEl.serialize());
|
||||
};
|
||||
|
||||
return Events;
|
||||
|
||||
@@ -10,26 +10,31 @@ var eventsController = module.exports;
|
||||
|
||||
eventsController.get = function (req, res, next) {
|
||||
var page = parseInt(req.query.page, 10) || 1;
|
||||
var itemsPerPage = 20;
|
||||
var itemsPerPage = parseInt(req.query.perPage, 10) || 20;
|
||||
var start = (page - 1) * itemsPerPage;
|
||||
var stop = start + itemsPerPage - 1;
|
||||
|
||||
var currentFilter = req.query.filter || '';
|
||||
// Limit by date
|
||||
var from = req.query.start ? new Date(req.query.start) || undefined : undefined;
|
||||
var to = req.query.end ? new Date(req.query.end) || undefined : new Date();
|
||||
from = from && from.setHours(0, 0, 0, 0); // setHours returns a unix timestamp (Number, not Date)
|
||||
to = to && to.setHours(23, 59, 59, 999); // setHours returns a unix timestamp (Number, not Date)
|
||||
|
||||
var currentFilter = req.query.type || '';
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
eventCount: function (next) {
|
||||
db.sortedSetCard('events:time' + (currentFilter ? ':' + currentFilter : ''), next);
|
||||
db.sortedSetCount('events:time' + (currentFilter ? ':' + currentFilter : ''), from || '-inf', to, next);
|
||||
},
|
||||
events: function (next) {
|
||||
events.getEvents(currentFilter, start, stop, next);
|
||||
events.getEvents(currentFilter, start, stop, from || '-inf', to, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results) {
|
||||
var types = [''].concat(events.types);
|
||||
var filters = types.map(function (type) {
|
||||
var types = [''].concat(events.types).map(function (type) {
|
||||
return {
|
||||
value: type,
|
||||
name: type || 'all',
|
||||
@@ -42,7 +47,8 @@ eventsController.get = function (req, res, next) {
|
||||
res.render('admin/advanced/events', {
|
||||
events: results.events,
|
||||
pagination: pagination.create(page, pageCount, req.query),
|
||||
filters: filters,
|
||||
types: types,
|
||||
query: req.query,
|
||||
});
|
||||
},
|
||||
], next);
|
||||
|
||||
@@ -48,6 +48,7 @@ events.types = [
|
||||
'export:uploads',
|
||||
'account-locked',
|
||||
'getUsersCSV',
|
||||
// To add new types from plugins, just Array.push() to this array
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -82,10 +83,17 @@ events.log = function (data, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
events.getEvents = function (filter, start, stop, callback) {
|
||||
events.getEvents = function (filter, start, stop, from, to, callback) {
|
||||
// from/to optional
|
||||
if (typeof from === 'function' && !to && !callback) {
|
||||
callback = from;
|
||||
from = 0;
|
||||
to = Date.now();
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRevRange('events:time' + (filter ? ':' + filter : ''), start, stop, next);
|
||||
db.getSortedSetRevRangeByScore('events:time' + (filter ? ':' + filter : ''), start, stop - start + 1, to, from, next);
|
||||
},
|
||||
function (eids, next) {
|
||||
db.getObjects(eids.map(eid => 'event:' + eid), next);
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<div class="row events">
|
||||
<div class="col-lg-9">
|
||||
<select id="filter" class="form-control">
|
||||
<!-- BEGIN filters -->
|
||||
<option value="{filters.value}" <!-- IF filters.selected -->selected<!-- ENDIF filters.selected -->>{filters.name}</option>
|
||||
<!-- END filters -->
|
||||
</select>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><i class="fa fa-calendar-o"></i> [[admin/advanced/events:events]]</div>
|
||||
<div class="panel-body">
|
||||
@@ -14,19 +9,21 @@
|
||||
<div class="events-list">
|
||||
<!-- BEGIN events -->
|
||||
<div data-eid="{events.eid}">
|
||||
<span>#{events.eid} </span><span class="label label-info">{events.type}</span>
|
||||
<span class="label label-default">#{events.eid}</span>
|
||||
<span class="label label-info">{events.type}</span>
|
||||
<span class="label label-default">uid {events.uid}</span>
|
||||
<!-- IF events.ip --><span class="label label-default">{events.ip}</span><!-- END -->
|
||||
<a href="{config.relative_path}/user/{events.user.userslug}" target="_blank">
|
||||
<!-- IF events.user.picture -->
|
||||
<img class="avatar avatar-sm" src="{events.user.picture}" />
|
||||
<img class="avatar avatar-xs" src="{events.user.picture}" />
|
||||
<!-- ELSE -->
|
||||
<div class="avatar avatar-sm" style="background-color: {events.user.icon:bgColor};">{events.user.icon:text}</div>
|
||||
<div class="avatar avatar-xs" style="background-color: {events.user.icon:bgColor};">{events.user.icon:text}</div>
|
||||
<!-- ENDIF events.user.picture -->
|
||||
</a>
|
||||
<a href="{config.relative_path}/user/{events.user.userslug}" target="_blank">{events.user.username}</a> (uid {events.uid}) (IP {events.ip})
|
||||
<a href="{config.relative_path}/user/{events.user.userslug}" target="_blank">{events.user.username}</a>
|
||||
<span class="pull-right delete-event"><i class="fa fa-trash-o"></i></span>
|
||||
<span class="pull-right">{events.timestampISO}</span>
|
||||
<br /><br />
|
||||
<pre>{events.jsonString}</pre>
|
||||
<pre class="well">{events.jsonString}</pre>
|
||||
</div>
|
||||
<!-- END events -->
|
||||
<!-- IMPORT partials/paginator.tpl -->
|
||||
@@ -36,9 +33,36 @@
|
||||
</div>
|
||||
<div class="col-lg-3 acp-sidebar">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"></div>
|
||||
<div class="panel-heading">[[admin/advanced/events:filters]]</div>
|
||||
<div class="panel-body">
|
||||
<button class="btn btn-warning" data-action="clear">
|
||||
<form role="form" id="filters">
|
||||
<div class="form-group">
|
||||
<label for="type">[[admin/advanced/events:filter-type]]</label>
|
||||
<select class="form-control" id="type" name="type" class="form-control">
|
||||
<!-- BEGIN types -->
|
||||
<option value="{types.value}" <!-- IF types.selected -->selected<!-- ENDIF types.selected -->>{types.name}</option>
|
||||
<!-- END types -->
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="start">[[admin/advanced/events:filter-start]]</label>
|
||||
<input type="date" id="start" name="start" value="{query.start}" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="end">[[admin/advanced/events:filter-end]]</label>
|
||||
<input type="date" id="end" name="end" value="{query.end}" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="perPage">[[admin/advanced/events:filter-perPage]]</label>
|
||||
<input type="text" id="perPage" name="perPage" value="{query.perPage}" class="form-control" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block" id="apply">[[admin/advanced/events:filters-apply]]</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<button class="btn btn-block btn-danger" data-action="clear">
|
||||
<i class="fa fa-eraser"></i> [[admin/advanced/events:delete-events]]
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user