Merge branch 'master' of github.com:ZoneMinder/zoneminder

This commit is contained in:
Isaac Connor 2020-10-20 11:52:25 -04:00
commit 5b27d77ea4
17 changed files with 418 additions and 638 deletions

View File

@ -1,28 +1,107 @@
<?php <?php
ini_set('display_errors', '0');
if ( empty($_REQUEST['eids']) ) { $message = '';
ajaxError('No event id(s) supplied'); $data = array();
//
// INITIALIZE AND CHECK SANITY
//
if ( !canEdit('Events') ) $message = 'Insufficient permissions for user '.$user['Username'];
if ( empty($_REQUEST['task']) ) {
$message = 'Must specify a task';
} else {
$task = $_REQUEST['task'];
} }
if ( canView('Events') ) { if ( empty($_REQUEST['eids']) ) {
} // end if canView('Events') if ( isset($_REQUEST['task']) && $_REQUEST['task'] != "query" ) $message = 'No event id(s) supplied';
} else {
$eids = $_REQUEST['eids'];
}
if ( canEdit('Events') ) { if ( $message ) {
$message = array(); ajaxError($message);
return;
}
foreach ( $_REQUEST['eids'] as $eid ) { // Search contains a user entered string to search on
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
switch ( $_REQUEST['action'] ) { // Advanced search contains an array of "column name" => "search text" pairs
// Bootstrap table sends json_ecoded array, which we must decode
$advsearch = isset($_REQUEST['filter']) ? json_decode($_REQUEST['filter'], JSON_OBJECT_AS_ARRAY) : array();
// Sort specifies the name of the column to sort on
$sort = 'StartTime';
if ( isset($_REQUEST['sort']) ) {
if ( !in_array($_REQUEST['sort'], array_merge($columns, $col_alt)) ) {
ZM\Error('Invalid sort field: ' . $_REQUEST['sort']);
} else {
$sort = $_REQUEST['sort'];
//if ( $sort == 'DateTime' ) $sort = 'TimeKey';
}
}
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
$limit = 100;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
}
}
//
// MAIN LOOP
//
switch ( $task ) {
case 'archive' : case 'archive' :
case 'unarchive' : case 'unarchive' :
$archiveVal = ($_REQUEST['action'] == 'archive') ? 1 : 0; foreach ( $eids as $eid ) archiveRequest($task, $eid);
break;
case 'delete' :
foreach ( $eids as $eid ) $data[] = deleteRequest($eid);
break;
case 'query' :
$data = queryRequest($search, $advsearch, $sort, $offset, $order, $limit);
break;
default :
ZM\Fatal("Unrecognised task '$task'");
} // end switch task
ajaxResponse($data);
//
// FUNCTION DEFINITIONS
//
function archiveRequest($task, $eid) {
$archiveVal = ($task == 'archive') ? 1 : 0;
dbQuery( dbQuery(
'UPDATE Events SET Archived = ? WHERE Id = ?', 'UPDATE Events SET Archived = ? WHERE Id = ?',
array($archiveVal, $eid) array($archiveVal, $eid)
); );
break; }
case 'delete' :
function deleteRequest($eid) {
$message = array();
$event = new ZM\Event($eid); $event = new ZM\Event($eid);
if ( !$event->Id() ) { if ( !$event->Id() ) {
$message[] = array($eid=>'Event not found.'); $message[] = array($eid=>'Event not found.');
@ -31,11 +110,98 @@ if ( canEdit('Events') ) {
} else { } else {
$event->delete(); $event->delete();
} }
break;
} // end switch action
} // end foreach
ajaxResponse($message);
} // end if canEdit('Events')
ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user '.$user['Username']); return $message;
}
function queryRequest($search, $advsearch, $sort, $offset, $order, $limit) {
// Put server pagination code here
// The table we want our data from
$table = 'Events';
// The names of the dB columns in the log table we are interested in
$columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartTime', 'EndTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace');
// The names of columns shown in the log view that are NOT dB columns in the database
$col_alt = array('Monitor', 'Storage');
$col_str = implode(', ', $columns);
$data = array();
$query = array();
$query['values'] = array();
$likes = array();
$where = '';
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
foreach ( $advsearch as $col=>$text ) {
if ( !in_array($col, array_merge($columns, $col_alt)) ) {
ZM\Error("'$col' is not a sortable column name");
continue;
}
$text = '%' .$text. '%';
array_push($likes, $col.' LIKE ?');
array_push($query['values'], $text);
}
$wherevalues = $query['values'];
$where = ' WHERE (' .implode(' OR ', $likes). ')';
} else if ( $search != '' ) {
$search = '%' .$search. '%';
foreach ( $columns as $col ) {
array_push($likes, $col.' LIKE ?');
array_push($query['values'], $search);
}
$wherevalues = $query['values'];
$where = ' WHERE (' .implode(' OR ', $likes). ')';
}
$query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?';
array_push($query['values'], $offset, $limit);
//ZM\Warning('Calling the following sql query: ' .$query['sql']);
$data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table, 'Total');
if ( $search != '' || count($advsearch) ) {
$data['total'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table.$where , 'Total', $wherevalues);
} else {
$data['total'] = $data['totalNotFiltered'];
}
$storage_areas = ZM\Storage::find();
$StorageById = array();
foreach ( $storage_areas as $S ) {
$StorageById[$S->Id()] = $S;
}
$monitor_names = ZM\Monitor::find();
$MonitorById = array();
foreach ( $monitor_names as $S ) {
$MonitorById[$S->Id()] = $S;
}
$rows = array();
foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $row ) {
// Modify the row data as needed
$row['Name'] = validHtmlStr($row['Name']);
$row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No');
$row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No');
$row['Monitor'] = ( $row['MonitorId'] and isset($MonitorById[$row['MonitorId']]) ) ? $MonitorById[$row['MonitorId']]->Name() : '';
$row['Cause'] = validHtmlStr($row['Cause']);
$row['StartTime'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['StartTime']));
$row['EndTime'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['StartTime']));
$row['Length'] = gmdate('H:i:s', $row['Length'] );
$row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default';
$row['Notes'] = htmlspecialchars($row['Notes']);
$row['DiskSpace'] = human_filesize($row['DiskSpace']);
$rows[] = $row;
}
$data['rows'] = $rows;
$data['updated'] = preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG);
return $data;
}
?> ?>

View File

@ -1,466 +1,132 @@
<?php <?php
ini_set('display_errors', '0'); global $Servers;
# Moved up here because it is used in several spots. if ( !canView('System') ) {
# These are the valid columns that you can filter on. ajaxError('Insufficient permissions to view log entries');
$filterFields = array('Component', 'ServerId', 'Pid', 'Level', 'File', 'Line'); return;
}
function buildLogQuery($action) { // Only the query task is supported at the moment
global $filterFields; if ( !isset($_REQUEST['task']) or $_REQUEST['task'] != 'query' ) {
ajaxError('Unrecognised action');
return;
}
// The table we want our data from
$table = 'Logs';
$minTime = isset($_REQUEST['minTime']) ? $_REQUEST['minTime'] : NULL; // The names of the dB columns in the log table we are interested in
$maxTime = isset($_REQUEST['maxTime']) ? $_REQUEST['maxTime'] : NULL; $columns = array('TimeKey', 'Component', 'ServerId', 'Pid', 'Code', 'Message', 'File', 'Line');
$limit = 100; // The names of columns shown in the log view that are NOT dB columns in the database
if ( isset($_REQUEST['limit']) ) { $col_alt = array('DateTime', 'Server');
if ( ( !is_integer($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit ' . $_REQUEST['limit']); // Search contains a user entered string to search on
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
// Advanced search contains an array of "column name" => "search text" pairs
// Bootstrap table sends json_ecoded array, which we must decode
$advsearch = isset($_REQUEST['filter']) ? json_decode($_REQUEST['filter'], JSON_OBJECT_AS_ARRAY) : array();
// Sort specifies the name of the column to sort on
$sort = 'TimeKey';
if ( isset($_REQUEST['sort']) ) {
if ( !in_array($_REQUEST['sort'], array_merge($columns, $col_alt)) ) {
ZM\Error('Invalid sort field: ' . $_REQUEST['sort']);
} else {
$sort = $_REQUEST['sort'];
if ( $sort == 'DateTime' ) $sort = 'TimeKey';
}
}
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
$limit = 100;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else { } else {
$limit = $_REQUEST['limit']; $limit = $_REQUEST['limit'];
} }
} }
$sortField = 'TimeKey';
if ( isset($_REQUEST['sortField']) ) {
if ( !in_array($_REQUEST['sortField'], $filterFields) and ( $_REQUEST['sortField'] != 'TimeKey' ) ) {
ZM\Error('Invalid sort field ' . $_REQUEST['sortField']);
} else {
$sortField = $_REQUEST['sortField'];
}
}
$sortOrder = (isset($_REQUEST['sortOrder']) and ($_REQUEST['sortOrder'] == 'asc')) ? 'asc' : 'desc';
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : array();
$sql = $action.' FROM Logs'; $col_str = implode(', ', $columns);
$where = array(); $data = array();
$values = array(); $query = array();
if ( $minTime ) { $query['values'] = array();
$where[] = 'TimeKey > ?'; $likes = array();
$values[] = $minTime; $where = '';
} elseif ( $maxTime ) { // There are two search bars in the log view, normal and advanced
$where[] = 'TimeKey < ?'; // Making an exuctive decision to ignore the normal search, when advanced search is in use
$values[] = $maxTime; // Alternatively we could try to do both
} if ( count($advsearch) ) {
foreach ( $filter as $field=>$value ) { foreach ( $advsearch as $col=>$text ) {
if ( !in_array($field, $filterFields) ) { if ( !in_array($col, array_merge($columns, $col_alt)) ) {
ZM\Error("'$field' is not in valid filter fields " . print_r($filterField, true)); ZM\Error("'$col' is not a sortable column name");
continue; continue;
} }
if ( $field == 'Level' ) { $text = '%' .$text. '%';
$where[] = $field.' <= ?'; array_push($likes, $col.' LIKE ?');
$values[] = $value; array_push($query['values'], $text);
} else {
$where[] = $field.' = ?';
$values[] = $value;
} }
$wherevalues = $query['values'];
$where = ' WHERE (' .implode(' OR ', $likes). ')';
} else if ( $search != '' ) {
$search = '%' .$search. '%';
foreach ( $columns as $col ) {
array_push($likes, $col.' LIKE ?');
array_push($query['values'], $search);
} }
if ( count($where) ) $wherevalues = $query['values'];
$sql.= ' WHERE '.join(' AND ', $where); $where = ' WHERE (' .implode(' OR ', $likes). ')';
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; }
return array('sql'=>$sql, 'values'=>$values); $query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?';
} # function buildLogQuery($action) array_push($query['values'], $offset, $limit);
switch ( $_REQUEST['task'] ) { //ZM\Warning('Calling the following sql query: ' .$query['sql']);
case 'create' :
{
// Silently ignore bogus requests
if ( !empty($_POST['level']) && !empty($_POST['message']) ) {
ZM\logInit(array('id'=>'web_js'));
$string = $_POST['message']; $data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table, 'Total');
if ( $search != '' || count($advsearch) ) {
$data['total'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table.$where , 'Total', $wherevalues);
} else {
$data['total'] = $data['totalNotFiltered'];
}
$file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; if ( !$Servers )
if ( !empty($_POST['line']) ) {
$line = validInt($_POST['line']);
} else {
$line = NULL;
}
$levels = array_flip(ZM\Logger::$codes);
if ( !isset($levels[$_POST['level']]) ) {
ZM\Panic('Unexpected logger level '.$_POST['level']);
}
$level = $levels[$_POST['level']];
ZM\Logger::fetch()->logPrint($level, $string, $file, $line);
} else {
ZM\Error('Invalid log create: '.print_r($_POST, true));
}
ajaxResponse();
break;
}
case 'delete' :
{
if ( !canEdit('System') )
ajaxError('Insufficient permissions to delete log entries');
$query = buildLogQuery('DELETE');
$result = dbQuery($query['sql'], $query['values']);
ajaxResponse(array('result'=>'Ok', 'deleted'=>$result->rowCount()));
}
case 'query' :
{
if ( !canView('System') )
ajaxError('Insufficient permissions to view log entries');
$total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total');
$query = buildLogQuery('SELECT *');
global $Servers;
if ( !$Servers )
$Servers = ZM\Server::find(); $Servers = ZM\Server::find();
$servers_by_Id = array(); $servers_by_Id = array();
# There is probably a better way to do this. # There is probably a better way to do this.
foreach ( $Servers as $server ) { foreach ( $Servers as $server ) {
$servers_by_Id[$server->Id()] = $server; $servers_by_Id[$server->Id()] = $server;
} }
$logs = array(); $rows = array();
$options = array(); foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $row ) {
$row['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($row['TimeKey']));
$row['Server'] = ( $row['ServerId'] and isset($servers_by_Id[$row['ServerId']]) ) ? $servers_by_Id[$row['ServerId']]->Name() : '';
// First strip out any html tags
// Second strip out all characters that are not ASCII 32-126 (yes, 126)
$row['Message'] = preg_replace('/[^\x20-\x7E]/', '', strip_tags($row['Message']));
$rows[] = $row;
}
$data['rows'] = $rows;
$data['logstate'] = logState();
$data['updated'] = preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG);
foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { ajaxResponse($data);
$log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey']));
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']);
foreach ( $filterFields as $field ) {
if ( !isset($options[$field]) )
$options[$field] = array();
$value = $log[$field];
if ( $field == 'Level' ) {
if ( $value <= ZM\Logger::INFO )
$options[$field][$value] = ZM\Logger::$codes[$value];
else
$options[$field][$value] = 'DB'.$value;
} else if ( $field == 'ServerId' ) {
$options['ServerId'][$value] = ( $value and isset($servers_by_Id[$value]) ) ? $servers_by_Id[$value]->Name() : '';
} else if ( isset($log[$field]) ) {
$options[$field][$log[$field]] = $value;
}
}
$logs[] = $log;
} # end foreach log db row
foreach ( $options as $field => $values ) {
asort($options[$field]);
}
$available = count($logs);
ajaxResponse(array(
'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG),
'total' => $total,
'available' => isset($available) ? $available : $total,
'logs' => $logs,
'state' => logState(),
'options' => $options,
));
break;
}
case 'export' :
{
if ( !canView('System') )
ajaxError('Insufficient permissions to export logs');
$minTime = isset($_POST['minTime']) ? $_POST['minTime'] : NULL;
$maxTime = isset($_POST['maxTime']) ? $_POST['maxTime'] : NULL;
if ( !is_null($minTime) && !is_null($maxTime) && ($minTime > $maxTime) ) {
$tempTime = $minTime;
$minTime = $maxTime;
$maxTime = $tempTime;
}
//$limit = isset($_POST['limit'])?$_POST['limit']:1000;
$filter = isset($_POST['filter'])?$_POST['filter']:array();
$sortField = 'TimeKey';
if ( isset($_POST['sortField']) ) {
if ( !in_array($_POST['sortField'], $filterFields) and ($_POST['sortField'] != 'TimeKey') ) {
ZM\Error('Invalid sort field '.$_POST['sortField']);
} else {
$sortField = $_POST['sortField'];
}
}
$sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc' : 'desc';
global $Servers;
if ( !$Servers )
$Servers = ZM\Server::find();
$servers_by_Id = array();
# There is probably a better way to do this.
foreach ( $Servers as $server ) {
$servers_by_Id[$server->Id()] = $server;
}
$sql = 'SELECT * FROM Logs';
$where = array();
$values = array();
if ( $minTime ) {
if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) {
# This handles sub second precision
$minTime = strtotime($matches[1]).$matches[2];
} else {
$minTime = strtotime($minTime);
}
$where[] = 'TimeKey >= ?';
$values[] = $minTime;
}
if ( $maxTime ) {
if ( preg_match('/(.+)(\.\d+)/', $maxTime, $matches) ) {
$maxTime = strtotime($matches[1]).$matches[2];
} else {
$maxTime = strtotime($maxTime);
}
$where[] = 'TimeKey <= ?';
$values[] = $maxTime;
}
foreach ( $filter as $field=>$value ) {
if ( $value != '' ) {
if ( $field == 'Level' ) {
$where[] = $field.' <= ?';
$values[] = $value;
} else {
$where[] = $field.' = ?';
$values[] = $value;
}
}
}
if ( count($where) )
$sql.= ' WHERE '.join(' AND ', $where);
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder;
//$sql .= " limit ".dbEscape($limit);
$format = isset($_POST['format']) ? $_POST['format'] : 'text';
switch ( $format ) {
case 'text' :
$exportExt = 'txt';
break;
case 'tsv' :
$exportExt = 'tsv';
break;
case 'html' :
$exportExt = 'html';
break;
case 'xml' :
$exportExt = 'xml';
break;
default :
ZM\Fatal("Unrecognised log export format '$format'");
}
$exportKey = substr(md5(rand()), 0, 8);
$exportFile = 'zm-log.'.$exportExt;
// mkdir will generate a warning if it exists, but that is ok
error_reporting(0);
if ( ! ( mkdir(ZM_DIR_EXPORTS) || file_exists(ZM_DIR_EXPORTS) ) ) {
ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\'');
}
$exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt;
ZM\Debug("Exporting to $exportPath");
if ( !($exportFP = fopen($exportPath, 'w')) )
ZM\Fatal("Unable to open log export file $exportPath");
$logs = array();
foreach ( dbFetchAll($sql, NULL, $values) as $log ) {
$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']);
$log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$logs[] = $log;
}
ZM\Debug(count($logs).' lines being exported by '.$sql.implode(',', $values));
switch( $format ) {
case 'text' :
{
foreach ( $logs as $log ) {
if ( $log['Line'] )
fprintf($exportFP, "%s %s[%d].%s-%s/%d [%s]\n",
$log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message']);
else
fprintf($exportFP, "%s %s[%d].%s-%s [%s]\n",
$log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message']);
}
break;
}
case 'tsv' :
{
# This line doesn't need fprintf, it could use fwrite
fprintf($exportFP, join("\t",
translate('DateTime'),
translate('Component'),
translate('Server'),
translate('Pid'),
translate('Level'),
translate('Message'),
translate('File'),
translate('Line')
)."\n");
foreach ( $logs as $log ) {
fprintf($exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n",
$log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']);
}
break;
}
case 'html' :
{
fwrite($exportFP,
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>'.translate('ZoneMinderLog').'</title>
<style type="text/css">
body, h3, p, table, td {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
}
table {
border-collapse: collapse;
width: 100%;
}
th {
font-weight: bold;
}
th, td {
border: 1px solid #888888;
padding: 1px 2px;
}
tr.log-fat td {
background-color:#ffcccc;
font-weight: bold;
font-style: italic;
}
tr.log-err td {
background-color:#ffcccc;
}
tr.log-war td {
background-color: #ffe4b5;
}
tr.log-dbg td {
color: #666666;
font-style: italic;
}
</style>
</head>
<body>
<h3>'.translate('ZoneMinderLog').'</h3>
<p>'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'</p>
<p>'.count($logs).' '.translate('Logs').'</p>
<table>
<tbody>
<tr><th>'.translate('DateTime').'</th><th>'.translate('Component').'</th><th>'.translate('Server').'</th><th>'.translate('Pid').'</th><th>'.translate('Level').'</th><th>'.translate('Message').'</th><th>'.translate('File').'</th><th>'.translate('Line').'</th></tr>
');
foreach ( $logs as $log ) {
$classLevel = $log['Level'];
if ( $classLevel < ZM\Logger::FATAL ) {
$classLevel = ZM\Logger::FATAL;
} else if ( $classLevel > ZM\Logger::DEBUG ) {
$classLevel = ZM\Logger::DEBUG;
}
$logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]);
fprintf($exportFP, ' <tr class="%s"><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>
', $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']);
}
fwrite($exportFP,
' </tbody>
</table>
</body>
</html>');
break;
}
case 'xml' :
{
fwrite($exportFP,
'<?xml version="1.0" encoding="utf-8"?>
<logexport title="'.translate('ZoneMinderLog').'" date="'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'">
<selector>'.$_POST['selector'].'</selector>');
foreach ( $filter as $field=>$value )
if ( $value != '' )
fwrite( $exportFP,
' <filter>
<'.strtolower($field).'>'.htmlspecialchars($value).'</'.strtolower($field).'>
</filter>' );
fwrite( $exportFP,
'
<columns>
<column field="datetime">'.translate('DateTime').'</column>
<column field="component">'.translate('Component').'</column>
<column field="server">'.translate('Server').'</column>
<column field="pid">'.translate('Pid').'</column>
<column field="level">'.translate('Level').'</column>
<column field="message">'.translate('Message').'</column>
<column field="file">'.translate('File').'</column>
<column field="line">'.translate('Line').'</column>
</columns>
<logs count="'.count($logs).'">
' );
foreach ( $logs as $log ) {
fprintf( $exportFP,
' <log>
<datetime>%s</datetime>
<component>%s</component>
<server>%s</server>
<pid>%d</pid>
<level>%s</level>
<message><![CDATA[%s]]></message>
<file>%s</file>
<line>%d</line>
</log>
', $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] );
}
fwrite( $exportFP,
' </logs>
</logexport>' );
break;
}
$exportExt = 'xml';
break;
}
fclose( $exportFP );
ajaxResponse( array(
'key' => $exportKey,
'format' => $format,
) );
break;
}
case 'download' :
{
if ( !canView('System') )
ajaxError('Insufficient permissions to download logs');
if ( empty($_REQUEST['key']) )
ZM\Fatal('No log export key given');
$exportKey = $_REQUEST['key'];
if ( empty($_REQUEST['format']) )
ZM\Fatal('No log export format given');
$format = $_REQUEST['format'];
switch ( $format ) {
case 'text' :
$exportExt = 'txt';
break;
case 'tsv' :
$exportExt = 'tsv';
break;
case 'html' :
$exportExt = 'html';
break;
case 'xml' :
$exportExt = 'xml';
break;
default :
ZM\Fatal("Unrecognised log export format '$format'");
}
$exportFile = 'zm-log.'.$exportExt;
$exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt;
header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Cache-Control: private', false); // required by certain browsers
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="'.$exportFile.'"');
header('Content-Transfer-Encoding: binary');
header('Content-Type: application/force-download');
header('Content-Length: '.filesize($exportPath));
readfile($exportPath);
exit(0);
break;
}
} // end switch ( $_REQUEST['task'] )
ajaxError('Unrecognised action or insufficient permissions');
?>

View File

@ -1,132 +0,0 @@
<?php
global $Servers;
if ( !canView('System') ) {
ajaxError('Insufficient permissions to view log entries');
return;
}
// Only the query task is supported at the moment
if ( !isset($_REQUEST['task']) or $_REQUEST['task'] != 'query' ) {
ajaxError('Unrecognised action');
return;
}
// The table we want our data from
$table = 'Logs';
// The names of the dB columns in the log table we are interested in
$columns = array('TimeKey', 'Component', 'ServerId', 'Pid', 'Code', 'Message', 'File', 'Line');
// The names of columns shown in the log view that are NOT dB columns in the database
$col_alt = array('DateTime', 'Server');
// Search contains a user entered string to search on
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
// Advanced search contains an array of "column name" => "search text" pairs
// Bootstrap table sends json_ecoded array, which we must decode
$advsearch = isset($_REQUEST['filter']) ? json_decode($_REQUEST['filter'], JSON_OBJECT_AS_ARRAY) : array();
// Sort specifies the name of the column to sort on
$sort = 'TimeKey';
if ( isset($_REQUEST['sort']) ) {
if ( !in_array($_REQUEST['sort'], array_merge($columns, $col_alt)) ) {
ZM\Error('Invalid sort field: ' . $_REQUEST['sort']);
} else {
$sort = $_REQUEST['sort'];
if ( $sort == 'DateTime' ) $sort = 'TimeKey';
}
}
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
$limit = 100;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
}
}
$col_str = implode(', ', $columns);
$data = array();
$query = array();
$query['values'] = array();
$likes = array();
$where = '';
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
foreach ( $advsearch as $col=>$text ) {
if ( !in_array($col, array_merge($columns, $col_alt)) ) {
ZM\Error("'$col' is not a sortable column name");
continue;
}
$text = '%' .$text. '%';
array_push($likes, $col.' LIKE ?');
array_push($query['values'], $text);
}
$wherevalues = $query['values'];
$where = ' WHERE (' .implode(' OR ', $likes). ')';
} else if ( $search != '' ) {
$search = '%' .$search. '%';
foreach ( $columns as $col ) {
array_push($likes, $col.' LIKE ?');
array_push($query['values'], $search);
}
$wherevalues = $query['values'];
$where = ' WHERE (' .implode(' OR ', $likes). ')';
}
$query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?';
array_push($query['values'], $offset, $limit);
//ZM\Warning('Calling the following sql query: ' .$query['sql']);
$data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table, 'Total');
if ( $search != '' || count($advsearch) ) {
$data['total'] = dbFetchOne('SELECT count(*) AS Total FROM ' .$table.$where , 'Total', $wherevalues);
} else {
$data['total'] = $data['totalNotFiltered'];
}
if ( !$Servers )
$Servers = ZM\Server::find();
$servers_by_Id = array();
# There is probably a better way to do this.
foreach ( $Servers as $server ) {
$servers_by_Id[$server->Id()] = $server;
}
$rows = array();
foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $row ) {
$row['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($row['TimeKey']));
$row['Server'] = ( $row['ServerId'] and isset($servers_by_Id[$row['ServerId']]) ) ? $servers_by_Id[$row['ServerId']]->Name() : '';
// First strip out any html tags
// Second strip out all characters that are not ASCII 32-126 (yes, 126)
$row['Message'] = preg_replace('/[^\x20-\x7E]/', '', strip_tags($row['Message']));
$rows[] = $row;
}
$data['rows'] = $rows;
$data['logstate'] = logState();
$data['updated'] = preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG);
ajaxResponse($data);

View File

@ -1,7 +1,7 @@
/** /**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
* *
* @version v1.18.0 * @version v1.17.1
* @homepage https://bootstrap-table.com * @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/) * @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT * @license MIT

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/** /**
* bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation)
* *
* @version v1.18.0 * @version v1.17.1
* @homepage https://bootstrap-table.com * @homepage https://bootstrap-table.com
* @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/) * @author wenzhixin <wenzhixin2010@gmail.com> (http://wenzhixin.net.cn/)
* @license MIT * @license MIT

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -215,7 +215,7 @@ if ( ($codec == 'MP4' || $codec == 'auto' ) && $Event->DefaultVideo() ) {
array_map(function($r){return $r/100;}, array_map(function($r){return $r/100;},
array_filter( array_filter(
array_keys($rates), array_keys($rates),
function($r){return $r >= 0 ? true : false;}, function($r){return $r >= 0 ? true : false;}
))) ?>], "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}' ))) ?>], "plugins": { "zoomrotate": { "zoom": "<?php echo $Zoom ?>"}}}'
> >
<source src="<?php echo $Event->getStreamSrc(array('mode'=>'mpeg','format'=>'h264'),'&amp;'); ?>" type="video/mp4"> <source src="<?php echo $Event->getStreamSrc(array('mode'=>'mpeg','format'=>'h264'),'&amp;'); ?>" type="video/mp4">

View File

@ -1,3 +1,77 @@
var backBtn = $j('#backBtn');
var viewBtn = $j('#viewBtn');
var archiveBtn = $j('#archiveBtn');
var unarchiveBtn = $j('#unarchiveBtn');
var editBtn = $j('#editBtn');
var exportBtn = $j('#exportBtn');
var downloadBtn = $j('#downloadBtn');
var deleteBtn = $j('#deleteBtn');
var table = $j('#eventTable');
/*
This is the format of the json object sent by bootstrap-table
var params =
{
"type":"get",
"data":
{
"search":"some search text",
"sort":"StartTime",
"order":"asc",
"offset":0,
"limit":25
"filter":
{
"Name":"some advanced search text"
"StartTime":"some more advanced search text"
}
},
"cache":true,
"contentType":"application/json",
"dataType":"json"
};
*/
// Called by bootstrap-table to retrieve zm event data
function ajaxRequest(params) {
$j.getJSON(thisUrl + '?view=request&request=events&task=query', params.data)
.done(function(data) {
//console.log('Ajax parameters: ' + JSON.stringify(params));
// rearrange the result into what bootstrap-table expects
var rows = processRows(data.rows);
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});
})
.fail(logAjaxFail);
}
function processRows(rows) {
// WIP: Inject desired html and formatting for the cells in each row
// REMINDER: Make these lines dependent on user permissions e.g. canEditEvents
$j.each(rows, function(ndx, row) {
var eid = row.Id;
var mid = row.MonitorId;
var archived = row.Archived == yesString ? archivedString : '';
var emailed = row.Emailed == yesString ? emailedString : '';
row.Id = '<a href="?view=event&amp;eid=' + eid + filterQuery + sortQuery + '&amp;page=1">' + eid + '</a>';
row.Name = '<a href="?view=event&amp;eid=' + eid + filterQuery + sortQuery + '&amp;page=1">' + row.Name + '</a>'
+ '<br/><div class="small text-nowrap text-muted">' + archived + emailed + '</div>';
row.Monitor = '<a href="?view=monitor&amp;mid=' + mid + '">' + row.Monitor + '</a>';
row.Cause = '<a href="#" title="' + row.Notes + '" class="eDetailLink" data-eid="' + eid + '">' + row.Cause + '</a>';
if ( row.Notes.indexOf('detected:') >= 0 ) {
row.Cause = row.Cause + '<a href="#?view=image&amp;eid=' + eid + '&amp;fid=objdetect"><div class="small text-nowrap text-muted"><u>' + row.Notes + '</u></div></a>';
} else if ( row.Notes != 'Forced Web: ' ) {
row.Cause = row.Cause + '<br/><div class="small text-nowrap text-muted">' + row.Notes + '</div>';
}
row.Frames = '<a href="?view=frames&amp;eid=' + eid + '">' + row.Frames + '</a>';
row.AlarmFrames = '<a href="?view=frames&amp;eid=' + eid + '">' + row.AlarmFrames + '</a>';
row.MaxScore = '<a href="?view=frame&amp;eid=' + eid + '&amp;fid=0">' + row.MaxScore + '</a>';
});
return rows;
}
function thumbnail_onmouseover(event) { function thumbnail_onmouseover(event) {
var img = event.target; var img = event.target;
img.src = ''; img.src = '';
@ -56,7 +130,7 @@ function manageDelConfirmModalBtns() {
var selections = getIdSelections(); var selections = getIdSelections();
evt.preventDefault(); evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&action=delete&eids[]='+selections.join('&eids[]=')) $j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+selections.join('&eids[]='))
.done( function(data) { .done( function(data) {
$j('#eventTable').bootstrapTable('refresh'); $j('#eventTable').bootstrapTable('refresh');
window.location.reload(true); window.location.reload(true);
@ -85,16 +159,6 @@ function getEventDetailModal(eid) {
} }
function initPage() { function initPage() {
var backBtn = $j('#backBtn');
var viewBtn = $j('#viewBtn');
var archiveBtn = $j('#archiveBtn');
var unarchiveBtn = $j('#unarchiveBtn');
var editBtn = $j('#editBtn');
var exportBtn = $j('#exportBtn');
var downloadBtn = $j('#downloadBtn');
var deleteBtn = $j('#deleteBtn');
var table = $j('#eventTable');
// Load the delete confirmation modal into the DOM // Load the delete confirmation modal into the DOM
getDelConfirmModal(); getDelConfirmModal();
@ -169,7 +233,7 @@ function initPage() {
var selections = getIdSelections(); var selections = getIdSelections();
evt.preventDefault(); evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&action=archive&eids[]='+selections.join('&eids[]=')) $j.getJSON(thisUrl + '?request=events&task=archive&eids[]='+selections.join('&eids[]='))
.done( function(data) { .done( function(data) {
$j('#eventTable').bootstrapTable('refresh'); $j('#eventTable').bootstrapTable('refresh');
window.location.reload(true); window.location.reload(true);
@ -188,7 +252,7 @@ function initPage() {
console.log(selections); console.log(selections);
evt.preventDefault(); evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&action=unarchive&eids[]='+selections.join('&eids[]=')) $j.getJSON(thisUrl + '?request=events&task=unarchive&eids[]='+selections.join('&eids[]='))
.done( function(data) { .done( function(data) {
$j('#eventTable').bootstrapTable('refresh'); $j('#eventTable').bootstrapTable('refresh');
window.location.reload(true); window.location.reload(true);
@ -262,6 +326,16 @@ function initPage() {
getEventDetailModal(eid); getEventDetailModal(eid);
}); });
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
// Manage the eventdetail links in the events list
$j(".eDetailLink").click(function(evt) {
evt.preventDefault();
var eid = $j(this).data('eid');
getEventDetailModal(eid);
});
});
// The table is initially given a hidden style, so now that we are done rendering, show it // The table is initially given a hidden style, so now that we are done rendering, show it
table.show(); table.show();
} }

View File

@ -7,3 +7,7 @@ var filterQuery = '<?php echo isset($filterQuery)?validJsStr(htmlspecialchars_de
var sortQuery = '<?php echo isset($sortQuery)?validJsStr(htmlspecialchars_decode($sortQuery)):'' ?>'; var sortQuery = '<?php echo isset($sortQuery)?validJsStr(htmlspecialchars_decode($sortQuery)):'' ?>';
var confirmDeleteEventsString = "<?php echo addslashes(translate('ConfirmDeleteEvents')) ?>"; var confirmDeleteEventsString = "<?php echo addslashes(translate('ConfirmDeleteEvents')) ?>";
var archivedString = "<?php echo translate('Archived') ?>";
var emailedString = "<?php echo translate('Emailed') ?>";
var yesString = "<?php echo translate('Yes') ?>";
var noString = "<?php echo translate('No') ?>";

View File

@ -27,7 +27,7 @@ var params =
// Called by bootstrap-table to retrieve zm log data // Called by bootstrap-table to retrieve zm log data
function ajaxRequest(params) { function ajaxRequest(params) {
$j.getJSON(thisUrl + '?view=request&request=newlog&task=query', params.data) $j.getJSON(thisUrl + '?view=request&request=log&task=query', params.data)
.done(function(data) { .done(function(data) {
//console.log('Ajax parameters: ' + JSON.stringify(params)); //console.log('Ajax parameters: ' + JSON.stringify(params));
// rearrange the result into what bootstrap-table expects // rearrange the result into what bootstrap-table expects

View File

@ -182,7 +182,9 @@ function applyPreset() {
function toPixels(field, maxValue) { function toPixels(field, maxValue) {
if ( field.value != '' ) { if ( field.value != '' ) {
field.value = Math.round((field.value*maxValue)/100); field.value = Math.round((field.value*maxValue)/100);
if ( field.value > maxValue ) field.value = maxValue; if ( field.value > maxValue ) {
field.value = maxValue;
}
} }
field.setAttribute('step', 1); field.setAttribute('step', 1);
field.setAttribute('max', maxValue); field.setAttribute('max', maxValue);