Files
zoneminder/web/ajax/console.php
Isaac Connor eae89025ee refactor: rename RTSP2WebStream to StreamChannel
Rename applies to Go2RTC, Janus, and RTSP2Web streaming options.
Update enum values from Primary/Secondary to Restream/CameraDirectPrimary/CameraDirectSecondary.

- Add db migration zm_update-1.37.79.sql to rename column and migrate data
- Update C++ enum StreamChannelOption and member stream_channel
- Update PHP getStreamChannelOptions() method
- Update all JavaScript references
- Auto-select CameraDirectPrimary when Restream option becomes disabled

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:37:08 -05:00

591 lines
24 KiB
PHP

<?php
$message = '';
$data = array();
// Handle query task for bootstrap-table AJAX requests
if (!empty($_REQUEST['task'])) {
$task = $_REQUEST['task'];
if ($task == 'query') {
$data = queryRequest();
ajaxResponse($data);
return;
}
}
// Handle legacy action-based requests
if ( canEdit('Monitors') ) {
switch ( $_REQUEST['action'] ) {
case 'sort' :
{
$monitor_ids = $_POST['monitor_ids'];
# Two concurrent sorts could generate odd sorting... so lock the table.
global $dbConn;
$dbConn->beginTransaction();
$dbConn->exec('LOCK TABLES Monitors WRITE');
for ( $i = 0; $i < count($monitor_ids); $i += 1 ) {
$monitor_id = $monitor_ids[$i];
$monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id);
if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) {
Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]);
continue;
}
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id));
} // end for each monitor_id
$dbConn->commit();
$dbConn->exec('UNLOCK TABLES');
return;
} // end case sort
default:
ZM\Warning('unknown action '.$_REQUEST['action']);
}
} else {
ZM\Warning('Cannot edit monitors');
}
ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user->Username());
//
// FUNCTION DEFINITIONS
//
function queryRequest() {
global $user, $Servers;
require_once('includes/Monitor.php');
require_once('includes/Group_Monitor.php');
$data = array(
'total' => 0,
'totalNotFiltered' => 0,
'rows' => array()
);
// Get pagination parameters
$offset = 0;
if (isset($_REQUEST['offset']) and ($_REQUEST['offset'] != 'NaN')) {
if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
$limit = 0;
if (isset($_REQUEST['limit']) and ($_REQUEST['limit'] != 'NaN')) {
if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
}
}
// Get search parameter
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
// Get sort parameters
$sort = isset($_REQUEST['sort']) ? $_REQUEST['sort'] : 'Sequence';
$order = isset($_REQUEST['order']) ? strtoupper($_REQUEST['order']) : 'ASC';
// Build monitor query with filters from request parameters (stateless)
$conditions = array();
$values = array();
// Get filter values directly from request
$request_filters = array(
'GroupId' => isset($_REQUEST['GroupId']) ? $_REQUEST['GroupId'] : null,
'ServerId' => isset($_REQUEST['ServerId']) ? $_REQUEST['ServerId'] : null,
'StorageId' => isset($_REQUEST['StorageId']) ? $_REQUEST['StorageId'] : null,
'Capturing' => isset($_REQUEST['Capturing']) ? $_REQUEST['Capturing'] : null,
'Analysing' => isset($_REQUEST['Analysing']) ? $_REQUEST['Analysing'] : null,
'Recording' => isset($_REQUEST['Recording']) ? $_REQUEST['Recording'] : null,
'Status' => isset($_REQUEST['Status']) ? $_REQUEST['Status'] : null,
'MonitorId' => isset($_REQUEST['MonitorId']) ? $_REQUEST['MonitorId'] : null,
'MonitorName' => isset($_REQUEST['MonitorName']) ? $_REQUEST['MonitorName'] : null,
'Source' => isset($_REQUEST['Source']) ? $_REQUEST['Source'] : null
);
// Apply request filters to SQL
if ($request_filters['GroupId']) {
$GroupIds = is_array($request_filters['GroupId']) ? $request_filters['GroupId'] : array($request_filters['GroupId']);
$conditions[] = 'M.Id IN (SELECT MonitorId FROM Groups_Monitors WHERE GroupId IN (' . implode(',', array_fill(0, count($GroupIds), '?')) . '))';
$values = array_merge($values, $GroupIds);
}
foreach (array('ServerId','StorageId') as $filter) {
if ($request_filters[$filter]) {
$filter_values = is_array($request_filters[$filter]) ? $request_filters[$filter] : array($request_filters[$filter]);
if (count($filter_values)) {
$conditions[] = 'M.'.$filter.' IN (' . implode(',', array_fill(0, count($filter_values), '?')) . ')';
$values = array_merge($values, $filter_values);
}
}
}
foreach (array('Capturing','Analysing','Recording') as $filter) {
if ($request_filters[$filter]) {
$filter_values = is_array($request_filters[$filter]) ? $request_filters[$filter] : array($request_filters[$filter]);
if (count($filter_values)) {
$conditions[] = 'M.'.$filter.' IN (' . implode(',', array_fill(0, count($filter_values), '?')) . ')';
$values = array_merge($values, $filter_values);
}
}
}
if ($request_filters['Status']) {
$status_values = is_array($request_filters['Status']) ? $request_filters['Status'] : array($request_filters['Status']);
if (count($status_values)) {
$conditions[] = 'COALESCE(S.Status, IF(M.Type="WebSite","Running","NotRunning")) IN (' . implode(',', array_fill(0, count($status_values), '?')) . ')';
$values = array_merge($values, $status_values);
}
}
// Build SQL query
$sql = 'SELECT M.*, S.*, E.*, (SELECT Name FROM Manufacturers WHERE Manufacturers.Id=M.ManufacturerId) AS Manufacturer, (SELECT Name FROM Models where Models.Id=M.ModelId) AS Model
FROM Monitors AS M
LEFT JOIN Monitor_Status AS S ON S.MonitorId=M.Id
LEFT JOIN Event_Summaries AS E ON E.MonitorId=M.Id
WHERE M.`Deleted`=false';
if (count($conditions)) {
$sql .= ' AND ' . implode(' AND ', $conditions);
}
// Get total count before filtering
$monitors = dbFetchAll($sql, null, $values);
$unfiltered_monitors = array();
foreach ($monitors as $monitor) {
if (visibleMonitor($monitor['Id'])) {
$unfiltered_monitors[] = $monitor;
}
}
$data['totalNotFiltered'] = count($unfiltered_monitors);
// Apply search filter
$filtered_monitors = $unfiltered_monitors;
if ($search != '') {
$search_lower = strtolower($search);
$filtered_monitors = array_filter($unfiltered_monitors, function($monitor) use ($search_lower) {
// Search across common fields without creating Monitor object
return (
stripos($monitor['Name'], $search_lower) !== false ||
stripos($monitor['Function'], $search_lower) !== false ||
stripos($monitor['Path'], $search_lower) !== false ||
stripos($monitor['Device'], $search_lower) !== false ||
stripos($monitor['Host'], $search_lower) !== false ||
stripos($monitor['Id'], $search_lower) !== false ||
(isset($monitor['Status']) && stripos($monitor['Status'], $search_lower) !== false)
);
});
}
// Apply MonitorName and Source request filters
if ($request_filters['MonitorName']) {
$regexp = $request_filters['MonitorName'];
if (!strpos($regexp, '/')) $regexp = '/'.$regexp.'/i';
$filtered_monitors = array_filter($filtered_monitors, function($monitor) use ($regexp) {
return @preg_match($regexp, $monitor['Name']);
});
}
if ($request_filters['Source']) {
$regexp = $request_filters['Source'];
if (!preg_match("/^\/.+\/[a-z]*$/i", $regexp))
$regexp = '/'.$regexp.'/i';
$filtered_monitors = array_filter($filtered_monitors, function($monitor) use ($regexp) {
// Match against Path field directly instead of creating Monitor object
return (preg_match($regexp, $monitor['Path']) || preg_match($regexp, $monitor['Device']) || preg_match($regexp, $monitor['Host']));
});
}
// Apply MonitorId filter
if ($request_filters['MonitorId']) {
$monitor_ids = is_array($request_filters['MonitorId']) ? $request_filters['MonitorId'] : array($request_filters['MonitorId']);
$filtered_monitors = array_filter($filtered_monitors, function($monitor) use ($monitor_ids) {
return in_array($monitor['Id'], $monitor_ids);
});
}
$data['total'] = count($filtered_monitors);
// Sort monitors
usort($filtered_monitors, function($a, $b) use ($sort, $order) {
$aVal = isset($a[$sort]) ? $a[$sort] : '';
$bVal = isset($b[$sort]) ? $b[$sort] : '';
if (is_numeric($aVal) && is_numeric($bVal)) {
$result = $aVal - $bVal;
} else {
$result = strcasecmp($aVal, $bVal);
}
return $order == 'ASC' ? $result : -$result;
});
// Apply pagination
if ($limit > 0) {
$filtered_monitors = array_slice($filtered_monitors, $offset, $limit);
} else {
$filtered_monitors = array_slice($filtered_monitors, $offset);
}
// Get storage areas and servers
$storage_areas = ZM\Storage::find();
$StorageById = array();
foreach ($storage_areas as $S) {
$StorageById[$S->Id()] = $S;
}
$ServersById = array();
foreach ($Servers as $s) {
$ServersById[$s->Id()] = $s;
}
// Get group IDs for each monitor
$monitor_ids = array_map(function($m) { return $m['Id']; }, $filtered_monitors);
$group_ids_by_monitor_id = array();
if (count($monitor_ids)) {
foreach (ZM\Group_Monitor::find(array('MonitorId'=>$monitor_ids)) as $GM) {
if (!isset($group_ids_by_monitor_id[$GM->MonitorId()]))
$group_ids_by_monitor_id[$GM->MonitorId()] = array();
$group_ids_by_monitor_id[$GM->MonitorId()][] = $GM->GroupId();
}
}
// Process each monitor and build row data
$footer_totals = array(
'monitor_count' => count($filtered_monitors),
'total_bandwidth' => 0,
'total_fps' => 0,
'total_analysis_fps' => 0,
'total_zones' => 0,
'event_totals' => array(
'Total' => array('events' => 0, 'diskspace' => 0),
'Hour' => array('events' => 0, 'diskspace' => 0),
'Day' => array('events' => 0, 'diskspace' => 0),
'Week' => array('events' => 0, 'diskspace' => 0),
'Month' => array('events' => 0, 'diskspace' => 0),
'Archived' => array('events' => 0, 'diskspace' => 0)
)
);
foreach ($filtered_monitors as $monitor) {
$Monitor = new ZM\Monitor($monitor);
$Monitor->GroupIds(isset($group_ids_by_monitor_id[$Monitor->Id()]) ? $group_ids_by_monitor_id[$Monitor->Id()] : array());
// Accumulate footer totals
$footer_totals['total_bandwidth'] += isset($monitor['CaptureBandwidth']) ? $monitor['CaptureBandwidth'] : 0;
$footer_totals['total_fps'] += isset($monitor['CaptureFPS']) ? floatval($monitor['CaptureFPS']) : 0;
$footer_totals['total_analysis_fps'] += isset($monitor['AnalysisFPS']) ? floatval($monitor['AnalysisFPS']) : 0;
$footer_totals['total_zones'] += isset($monitor['ZoneCount']) ? intval($monitor['ZoneCount']) : 0;
foreach (array('Total', 'Hour', 'Day', 'Week', 'Month', 'Archived') as $period) {
$footer_totals['event_totals'][$period]['events'] += isset($monitor[$period.'Events']) ? intval($monitor[$period.'Events']) : 0;
$footer_totals['event_totals'][$period]['diskspace'] += isset($monitor[$period.'EventDiskSpace']) ? intval($monitor[$period.'EventDiskSpace']) : 0;
}
$row = array();
$row['Id'] = $monitor['Id'];
$row['Name'] = validHtmlStr($monitor['Name']);
$row['Function'] = $monitor['Function'];
$row['Enabled'] = $monitor['Enabled'];
$row['Manufacturer'] = $monitor['Manufacturer'];
$row['Model'] = $monitor['Model'];
$row['Sequence'] = isset($monitor['Sequence']) ? $monitor['Sequence'] : 0;
// Status
if (!$monitor['Status']) {
if ($monitor['Type'] == 'WebSite')
$monitor['Status'] = 'Running';
else
$monitor['Status'] = 'NotRunning';
}
$row['Status'] = $monitor['Status'];
// Server
if (count($Servers)) {
$Server = isset($ServersById[$monitor['ServerId']]) ? $ServersById[$monitor['ServerId']] : new ZM\Server($monitor['ServerId']);
$row['Server'] = validHtmlStr($Server->Name());
$row['ServerId'] = $monitor['ServerId'];
}
// Source
$row['Source'] = validHtmlStr($Monitor->Source());
$row['Width'] = $Monitor->Width();
$row['Height'] = $Monitor->Height();
// Storage
if (isset($StorageById[$monitor['StorageId']])) {
$row['Storage'] = validHtmlStr($StorageById[$monitor['StorageId']]->Name());
} else if ($monitor['StorageId']) {
$row['Storage'] = '<span class="error">Deleted '.$monitor['StorageId'].'</span>';
} else {
$row['Storage'] = '';
}
// Event counts with filter querystrings
$eventCounts = array(
'Total' => array(
'filter' => array('Query' => array('terms' => array()))
),
'Hour' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 hour')
)))
),
'Day' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 day')
)))
),
'Week' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-7 day')
)))
),
'Month' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 month')
)))
),
'Archived' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'Archived', 'op' => '=', 'val' => '1')
)))
)
);
foreach ($eventCounts as $period => $eventCount) {
$row[$period.'Events'] = (int)$monitor[$period.'Events'];
$row[$period.'EventDiskSpace'] = human_filesize($monitor[$period.'EventDiskSpace']);
// Generate filter querystring for this period and monitor
$filter = addFilterTerm(
$eventCount['filter'],
count($eventCount['filter']['Query']['terms']),
array('cnj' => 'and', 'attr' => 'Monitor', 'op' => '=', 'val' => $monitor['Id'])
);
parseFilter($filter);
$row[$period.'FilterQuery'] = $filter['querystring'];
}
// Zone count
$row['ZoneCount'] = $monitor['ZoneCount'];
// FPS and bandwidth
$row['CaptureFPS'] = isset($monitor['CaptureFPS']) ? $monitor['CaptureFPS'] : '0.00';
$row['AnalysisFPS'] = isset($monitor['AnalysisFPS']) ? $monitor['AnalysisFPS'] : '0.00';
// Format bandwidth with units (bytes per second) - use human_filesize for consistency
$bandwidth = isset($monitor['CaptureBandwidth']) ? $monitor['CaptureBandwidth'] : 0;
if ($bandwidth > 0) {
$row['CaptureBandwidth'] = human_filesize($bandwidth).'/s';
} else {
$row['CaptureBandwidth'] = '';
}
$row['Analysing'] = isset($monitor['Analysing']) ? $monitor['Analysing'] : 'None';
$row['Recording'] = isset($monitor['Recording']) ? $monitor['Recording'] : 'None';
$row['ONVIF_Event_Listener'] = isset($monitor['ONVIF_Event_Listener']) ? $monitor['ONVIF_Event_Listener'] : 0;
$row['UpdatedOn'] = isset($monitor['UpdatedOn']) ? $monitor['UpdatedOn'] : '';
$row['Type'] = $monitor['Type'];
$row['Capturing'] = isset($monitor['Capturing']) ? $monitor['Capturing'] : 'None';
// Groups
if (canView('Groups')) {
$groups_html = implode('<br/>',
array_map(function($group_id) {
$Group = ZM\Group::find_one(array('Id'=>$group_id));
if ($Group) {
$Groups = $Group->Parents();
array_push($Groups, $Group);
} else {
$Groups = array();
}
return implode(' &gt; ', array_map(function($Group) {
if (canView('Stream')) {
return '<a href="?view=montagereview&amp;GroupId='.$Group->Id().'">'.validHtmlStr($Group->Name()).'</a>';
} else {
return validHtmlStr($Group->Name());
}
}, $Groups));
}, $Monitor->GroupIds())
);
$row['Groups'] = $groups_html;
} else {
$row['Groups'] = '';
}
// Thumbnail
$row['Thumbnail'] = '';
if (ZM_WEB_LIST_THUMBS && ($monitor['Capturing'] != 'None') && canView('Stream')) {
$ratio_factor = $Monitor->ViewWidth() ? $Monitor->ViewHeight() / $Monitor->ViewWidth() : 1;
$options = array(
'width' => ZM_WEB_LIST_THUMB_WIDTH,
'height' => ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH * $ratio_factor,
'scale' => $Monitor->ViewWidth() ? intval(100 * ZM_WEB_LIST_THUMB_WIDTH / $Monitor->ViewWidth()) : 100,
'mode' => 'jpeg',
'frames' => 1,
);
$stillSrc = $Monitor->getStreamSrc($options);
// Calculate optimal scale for the popup stream based on browser width
// The JS overlay uses 60% of window.innerWidth (see calculateOverlayDimensions in skin.js)
$browser_width = 1920; // Default fallback
if (isset($_COOKIE['zmBrowserSizes'])) {
$zmBrowserSizes = jsonDecode($_COOKIE['zmBrowserSizes']);
if (!empty($zmBrowserSizes['innerWidth'])) {
$browser_width = validInt($zmBrowserSizes['innerWidth']);
}
}
$target_width = $browser_width * 0.6; // Match JS overlay sizing (60% of viewport)
$options['scale'] = $Monitor->ViewWidth() ? intval(100 * $target_width / $Monitor->ViewWidth()) : 100;
if ($options['scale'] > 100) $options['scale'] = 100;
else if ($options['scale'] < 10) $options['scale'] = 10;
unset($options['frames']);
$streamSrc = $Monitor->getStreamSrc($options);
$videoAttr = '';
$go2rtcAttr = '';
$liveStreamAttr = '';
// Debug info for troubleshooting
$debugInfo = 'Analysing='.$monitor['Analysing'].',Decoding='.$monitor['Decoding'].
',Go2RTC='.$Monitor->Go2RTCEnabled().
',ZM_GO2RTC_PATH='.(defined('ZM_GO2RTC_PATH') ? ZM_GO2RTC_PATH : 'undefined');
$debugAttr = ' data-debug="'.htmlspecialchars($debugInfo).'"';
// STILL THUMBNAIL: When not analysing/decoding, use the most recent event thumbnail
// (ZM's nph-zms can't produce frames when not decoding)
if ($monitor['Analysing'] == 'None' && $monitor['Decoding'] == 'None') {
$debugAttr .= ' data-debug-still="event"';
$event = ZM\Event::find_one(
array(
'MonitorId' => $monitor['Id'],
'EndDateTime' => 'NOT NULL'
),
array('order' => 'Id DESC')
);
if ($event) {
$stillSrc = $event->getThumbnailSrc(array(), '&amp;');
// Default stream/video sources from event (may be overridden by live streaming below)
// Use browser width for optimal scale (same as live stream calculation above)
$event_scale = $event->Width() ? intval(100 * $target_width / $event->Width()) : 100;
if ($event_scale > 100) $event_scale = 100;
else if ($event_scale < 10) $event_scale = 10;
$streamSrc = $event->getStreamSrc(array(
'mode' => 'jpeg', 'scale' => $event_scale, 'maxfps' => ZM_WEB_VIDEO_MAXFPS,
'replay' => 'single', 'rate' => '400'), '&amp;');
if ($event->DefaultVideo()) {
$videoSrc = $event->getStreamSrc(array('mode' => 'mp4'), '&amp;');
$videoAttr = ' video_src="'.$videoSrc.'" data-event-start="'.htmlspecialchars($event->StartDateTime()).'"';
}
}
} else {
$debugAttr .= ' data-debug-still="monitor"';
}
// HOVER OVERLAY: Check for external streaming methods (these work independently of ZM's decode/analyze)
// Priority: go2rtc > rtsp2web > janus > event replay > ZM native stream
if ($Monitor->Go2RTCEnabled() && defined('ZM_GO2RTC_PATH') && ZM_GO2RTC_PATH) {
$liveStreamAttr = ' data-stream-type="go2rtc"'.
' data-go2rtc-src="'.htmlspecialchars(ZM_GO2RTC_PATH).'"'.
' data-monitor-id="'.$monitor['Id'].'"';
$go2rtcAttr = ' go2rtc_src="'.htmlspecialchars(ZM_GO2RTC_PATH).'" go2rtc_mid="'.$monitor['Id'].'"';
$debugAttr .= ' data-debug-overlay="go2rtc"';
} else if ($Monitor->RTSP2WebEnabled() && defined('ZM_RTSP2WEB_PATH') && ZM_RTSP2WEB_PATH) {
$liveStreamAttr = ' data-stream-type="rtsp2web"'.
' data-rtsp2web-src="'.htmlspecialchars(ZM_RTSP2WEB_PATH).'"'.
' data-stream-channel="'.($Monitor->StreamChannel() ?: 'Restream').'"'.
' data-monitor-id="'.$monitor['Id'].'"';
$debugAttr .= ' data-debug-overlay="rtsp2web"';
} else if ($Monitor->JanusEnabled()) {
$janusPath = defined('ZM_JANUS_PATH') && ZM_JANUS_PATH ? ZM_JANUS_PATH : '';
$liveStreamAttr = ' data-stream-type="janus"'.
' data-janus-src="'.htmlspecialchars($janusPath).'"'.
' data-monitor-id="'.$monitor['Id'].'"';
$debugAttr .= ' data-debug-overlay="janus"';
} else if ($monitor['Analysing'] == 'None' && $monitor['Decoding'] == 'None') {
// No external streaming and ZM not running - overlay will use event replay (already set above)
$debugAttr .= ' data-debug-overlay="event-replay"';
} else {
// Use native ZM streaming (zms) - Analysing or Decoding is running
$debugAttr .= ' data-debug-overlay="zm-native"';
}
$thmbWidth = ($options['width']) ? 'width:'.$options['width'].'px;' : '';
$thmbHeight = ($options['height']) ? 'height:'.$options['height'].'px;' : '';
$row['Thumbnail'] = '<div class="colThumbnail" style="'.$thmbHeight.'"><a href="?view=watch&amp;mid='.$monitor['Id'].'">'.
'<img id="thumbnail'.$Monitor->Id().'" src="'.$stillSrc.'" style="'.$thmbWidth.$thmbHeight.
'" stream_src="'.$streamSrc.'" still_src="'.$stillSrc.'"'.$videoAttr.$go2rtcAttr.$liveStreamAttr.$debugAttr.
($options['width'] ? ' width="'.$options['width'].'"' : '').
($options['height'] ? ' height="'.$options['height'].'"' : '').
' loading="lazy" /></a></div>';
}
$data['rows'][] = $row;
}
// Add footer totals to response
$data['footer'] = array(
'monitor_count' => $footer_totals['monitor_count'],
'bandwidth_fps' => human_filesize($footer_totals['total_bandwidth']).'/s '.
round($footer_totals['total_fps'], 2).' fps / '.
round($footer_totals['total_analysis_fps'], 2).' fps',
'total_zones' => $footer_totals['total_zones']
);
// Build filter querystrings for footer event totals (matching header behavior)
$eventCountsFooter = array(
'Total' => array(
'filter' => array('Query' => array('terms' => array()))
),
'Hour' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 hour')
)))
),
'Day' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 day')
)))
),
'Week' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-7 day')
)))
),
'Month' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'StartDateTime', 'op' => '>=', 'val' => '-1 month')
)))
),
'Archived' => array(
'filter' => array('Query' => array('terms' => array(
array('cnj' => 'and', 'attr' => 'Archived', 'op' => '=', 'val' => '1')
)))
)
);
// Add formatted event totals to footer with filter querystrings
foreach (array('Total', 'Hour', 'Day', 'Week', 'Month', 'Archived') as $period) {
$data['footer'][$period.'Events'] = $footer_totals['event_totals'][$period]['events'];
$data['footer'][$period.'EventDiskSpace'] = human_filesize($footer_totals['event_totals'][$period]['diskspace']);
// Generate filter querystring for footer (include monitor filter if monitors are filtered)
$filter = $eventCountsFooter[$period]['filter'];
if (count($monitor_ids) > 0) {
$filter = addFilterTerm(
$filter,
count($filter['Query']['terms']),
array('cnj' => 'and', 'attr' => 'Monitor', 'op' => 'IN', 'val' => implode(',', $monitor_ids))
);
}
parseFilter($filter);
$data['footer'][$period.'FilterQuery'] = $filter['querystring'];
}
return $data;
}
?>