Basic audit viewer

This commit is contained in:
Daniel Berteaud 2015-07-08 23:07:29 +02:00
parent 90df86724f
commit 6662c0a9e8
3 changed files with 208 additions and 0 deletions

View File

@ -97,6 +97,7 @@ use constant MOH => {
use constant API_ACTIONS => {
admin => {
get_room_list => 1,
get_event_list => 1,
set_persistent => 1
},
owner => {

View File

@ -720,6 +720,136 @@ function initAdminRooms(){
});
}
// Audit page
function initAdminAudit(){
var eventList = {};
var matches = 0;
// Update display of event list
function updateEventList(filter, min, max){
$('#loading-icon').show();
$('#eventList').html('');
var filterRe = new RegExp(filter, "gi");
var i = 0;
matches = 0;
$.each(eventList, function (index, obj){
if (filter === '' ||
( obj.event.match(filterRe) ||
obj.from_ip.match(filterRe) ||
obj.user.match(filterRe) ||
obj.message.match(filterRe))
){
matches ++;
if (i >= min && i < max){
var t = obj.date.split(/[- :]/);
var date = utc2Local(new Date(t[0], t[1]-1, t[2], t[3], t[4], t[5])).toLocaleString();
$('#eventList').append($('<tr>')
.append($('<td>').html(stringEscape(obj.id)).addClass('hidden-xs'))
.append($('<td>').html(stringEscape(date)))
.append($('<td>').html(stringEscape(obj.from_ip)))
.append($('<td>').html(stringEscape(obj.event)))
.append($('<td>').html(stringEscape(obj.user)).addClass('hidden-xs'))
.append($('<td>').html(stringEscape(obj.message)))
);
}
i++;
}
});
$('#loadingIcon').hide();
$('.tablesorter').trigger('update');
}
function updatePagination(){
if (matches <= itemPerPage){
$('#pagination').hide(200);
return;
}
var total = Math.ceil(matches / itemPerPage);
if (total === 0){
total = 1;
}
$('#pagination').bootpag({
total: total,
maxVisible: 10,
page: 1
}).on('page', function(e, page){
var min = itemPerPage * (page - 1);
var max = min + itemPerPage;
updateEventList($('#searchEvent').val(), min, max);
});
$('#pagination').show(200);
}
function reloadEvents(start,end){
$.ajax({
data: {
req: JSON.stringify({
action: 'get_event_list',
param: {
start: start,
end: end
}
})
},
error: function(data) {
showApiError(data);
},
success: function(data){
eventList = data.events;
matches = Object.keys(eventList).length;
updateEventList($('#eventSearch').val(), 0, itemPerPage);
updatePagination();
//$('.tablesorter').tablesorter({textSorter: $.tablesorter.sortText});
}
});
}
// Intercept form submission
$('#eventSearch').submit(function(e){
e.preventDefault();
var startObj = new Date($('#dateStart').val());
var endObj = new Date($('#dateEnd').val());
if (!$('#dateStart').val().match(dateRe)){
$('#dateStart').notify(localize('ERROR_DATE_INVALID'), {
class: 'error',
position: 'bottom center'
});
return false;
}
else if (!$('#dateEnd').val().match(dateRe)){
$('#dateEnd').notify(localize('ERROR_DATE_INVALID'), {
class: 'error',
position: 'bottom center'
});
return false;
}
else if (startObj > endObj){
$('#dateEnd').notify(localize('ERROR_END_MUST_BE_AFTER_START'), {
class: 'error',
position: 'bottom center'
});
return false;
}
else{
reloadEvents($('#dateStart').val(),$('#dateEnd').val());
return;
}
});
$('#searchEvent').on('input', function(){
var lastInput = +new Date;
setTimeout(function(){
if (lastInput + 500 < +new Date){
$('#loading-icon').show();
$('#pagination').html('');
updateEventList($('#searchEvent').val(), 0, itemPerPage);
updatePagination();
}
}, 600);
});
reloadEvents($('#dateStart').val(),$('#dateEnd').val());
}
// Started when entering a room
function initJoin(room){
// Auth input if access is protected

View File

@ -125,6 +125,18 @@ helper valid_email => sub {
return Email::Valid->address($email);
};
# Validate a date in YYYY-MM-DD format
# Also accept YYYY-MM-DD hh:mm:ss
helper valid_date => sub {
my $self = shift;
my ($date) = @_;
if ($date =~ m/^\d{4}\-\d{1,2}\-\d{1,2}(\s+\d{1,2}:\d{1,2}:\d{1,2})?$/){
return 1;
}
$self->app->log->debug("$date is not a valid date");
return 0;
};
##########################
# Various helpers #
##########################
@ -167,6 +179,36 @@ helper log_event => sub {
return 1;
};
# Return a list of event between 2 dates
helper get_event_list => sub {
my $self = shift;
my ($start,$end) = @_;
# Check both start and end dates seems valid
if (!$self->valid_date($start) || !$self->valid_date($end)){
$self->app->log->debug("Invalid date submitted while looking for events");
return 0;
}
my $sth;
$sth = eval {
$self->db->prepare('SELECT * FROM `audit`
WHERE `date`>=?
AND `date`<=?');
};
if ($@){
$self->app->log->debug("DB error: $@");
return 0;
}
# We want both dates to be inclusive, as the default time is 00:00:00
# if not given, append 23:59:59 to the end date
$sth->execute($start,$end . ' 23:59:59');
if ($sth->err){
$self->app->log->debug("DB error: " . $sth->errstr . " (code " . $sth->err . ")");
return 0;
}
# Everything went fine, return the list of event as a hashref
return $sth->fetchall_hashref('id');
};
# Generate and manage rotation of session keys
# used to sign cookies
helper update_session_keys => sub {
@ -1570,6 +1612,41 @@ any '/api' => sub {
}
);
}
elsif ($req->{action} eq 'get_event_list'){
my $start = $req->{param}->{start};
my $end = $req->{param}->{end};
if ($start eq ''){
$start = DateTime->now->ymd;
}
if ($end eq ''){
$end = DateTime->now->ymd;
}
# Validate input
if (!$self->valid_date($start) || !$self->valid_date($end)){
return $self->render(
json => {
err => 'ERROR_INPUT_INVALID',
msg => $self->l('ERROR_INPUT_INVALID'),
status => 'error'
},
);
}
my $events = $self->get_event_list($start,$end);
foreach my $event (keys %{$events}){
# Init NULL values to empty strings
foreach (qw(date from_ip event user message)){
if (!$events->{$event}->{$_}){
$events->{$event}->{$_} = '';
}
}
}
# And send the list of event as a json object
return $self->render(
json => {
events => $events
}
);
}
# And here anonymous method, which do not require an API Key
elsif ($req->{action} eq 'create_room'){
$req->{param}->{room} ||= $self->get_random_name();