diff --git a/web/ajax/events.php b/web/ajax/events.php index 16c777ebf..f9a4fc14b 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -1,41 +1,214 @@ "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 'unarchive' : + 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( + 'UPDATE Events SET Archived = ? WHERE Id = ?', + array($archiveVal, $eid) + ); +} + +function deleteRequest($eid) { $message = array(); + $event = new ZM\Event($eid); + if ( !$event->Id() ) { + $message[] = array($eid=>'Event not found.'); + } else if ( $event->Archived() ) { + $message[] = array($eid=>'Event is archived, cannot delete it.'); + } else { + $event->delete(); + } + + return $message; +} - foreach ( $_REQUEST['eids'] as $eid ) { +function queryRequest($search, $advsearch, $sort, $offset, $order, $limit) { + // Put server pagination code here + // The table we want our data from + $table = 'Events'; - switch ( $_REQUEST['action'] ) { - case 'archive' : - case 'unarchive' : - $archiveVal = ($_REQUEST['action'] == 'archive') ? 1 : 0; - dbQuery( - 'UPDATE Events SET Archived = ? WHERE Id = ?', - array($archiveVal, $eid) - ); - break; - case 'delete' : - $event = new ZM\Event($eid); - if ( !$event->Id() ) { - $message[] = array($eid=>'Event not found.'); - } else if ( $event->Archived() ) { - $message[] = array($eid=>'Event is archived, cannot delete it.'); - } else { - $event->delete(); + // 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; } - break; - } // end switch action - } // end foreach - ajaxResponse($message); -} // end if canEdit('Events') + $text = '%' .$text. '%'; + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $text); + } + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; -ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user '.$user['Username']); + } 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 ) { + $event = new ZM\Event($row['Id']); + $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); + $imgSrc = $event->getThumbnailSrc(array(),'&'); + $streamSrc = $event->getStreamSrc(array( + 'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&'); + + // Modify the row data as needed + $row['imgHtml'] = ''; + $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; +} ?> diff --git a/web/ajax/log.php b/web/ajax/log.php index 32be2667a..9e6bd9585 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,466 +1,132 @@ "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'; } - $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'; - $where = array(); - $values = array(); - if ( $minTime ) { - $where[] = 'TimeKey > ?'; - $values[] = $minTime; - } elseif ( $maxTime ) { - $where[] = 'TimeKey < ?'; - $values[] = $maxTime; +// 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']; } +} - foreach ( $filter as $field=>$value ) { - if ( !in_array($field, $filterFields) ) { - ZM\Error("'$field' is not in valid filter fields " . print_r($filterField, true)); +// 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; } - if ( $field == 'Level' ) { - $where[] = $field.' <= ?'; - $values[] = $value; - } else { - $where[] = $field.' = ?'; - $values[] = $value; - } + $text = '%' .$text. '%'; + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $text); } - if ( count($where) ) - $sql.= ' WHERE '.join(' AND ', $where); - $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; - return array('sql'=>$sql, 'values'=>$values); -} # function buildLogQuery($action) +} else if ( $search != '' ) { -switch ( $_REQUEST['task'] ) { - case 'create' : - { - // Silently ignore bogus requests - if ( !empty($_POST['level']) && !empty($_POST['message']) ) { - ZM\logInit(array('id'=>'web_js')); - - $string = $_POST['message']; - - $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; - 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; + $search = '%' .$search. '%'; + foreach ( $columns as $col ) { + array_push($likes, $col.' LIKE ?'); + array_push($query['values'], $search); } - case 'delete' : - { - if ( !canEdit('System') ) - ajaxError('Insufficient permissions to delete log entries'); + $wherevalues = $query['values']; + $where = ' WHERE (' .implode(' OR ', $likes). ')'; +} - $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 *'); +$query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` ' .$where. ' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?'; +array_push($query['values'], $offset, $limit); - 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; - } +//ZM\Warning('Calling the following sql query: ' .$query['sql']); - $logs = array(); - $options = array(); +$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']; +} - foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { +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; +} - $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]; +$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); - 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 +ajaxResponse($data); - 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, -' - -
-'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'
-'.count($logs).' '.translate('Logs').'
-'.translate('DateTime').' | '.translate('Component').' | '.translate('Server').' | '.translate('Pid').' | '.translate('Level').' | '.translate('Message').' | '.translate('File').' | '.translate('Line').' |
---|---|---|---|---|---|---|---|
%s | %s | %s | %d | %s | %s | %s | %s |