diff --git a/lib/Vroom/Constants.pm b/lib/Vroom/Constants.pm
index 0fae109..5dbda81 100644
--- a/lib/Vroom/Constants.pm
+++ b/lib/Vroom/Constants.pm
@@ -97,6 +97,7 @@ use constant MOH => {
use constant API_ACTIONS => {
admin => {
get_room_list => 1,
+ get_event_list => 1,
set_persistent => 1
},
owner => {
diff --git a/public/js/vroom.js b/public/js/vroom.js
index f796ff8..8232bf7 100644
--- a/public/js/vroom.js
+++ b/public/js/vroom.js
@@ -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($('
')
+ .append($('').html(stringEscape(obj.id)).addClass('hidden-xs'))
+ .append($(' | ').html(stringEscape(date)))
+ .append($(' | ').html(stringEscape(obj.from_ip)))
+ .append($(' | ').html(stringEscape(obj.event)))
+ .append($(' | ').html(stringEscape(obj.user)).addClass('hidden-xs'))
+ .append($(' | ').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
diff --git a/vroom.pl b/vroom.pl
index a179243..ec9ac35 100755
--- a/vroom.pl
+++ b/vroom.pl
@@ -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();
|