mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-05-16 02:24:34 -04:00
Merge remote-tracking branch 'upstream/copilot/refactor-console-view-table'
This commit is contained in:
@@ -1,4 +1,23 @@
|
||||
<?php
|
||||
$message = '';
|
||||
$data = array();
|
||||
|
||||
// Handle query task for bootstrap-table AJAX requests
|
||||
if (!empty($_REQUEST['task'])) {
|
||||
$task = $_REQUEST['task'];
|
||||
|
||||
if ($task == 'query') {
|
||||
if (!canView('Monitors')) {
|
||||
ajaxError('Insufficient permissions for user '.$user->Username());
|
||||
return;
|
||||
}
|
||||
$data = queryRequest();
|
||||
ajaxResponse($data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle legacy action-based requests
|
||||
if ( canEdit('Monitors') ) {
|
||||
switch ( $_REQUEST['action'] ) {
|
||||
case 'sort' :
|
||||
@@ -30,4 +49,364 @@ if ( canEdit('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 session
|
||||
zm_session_start();
|
||||
$conditions = array();
|
||||
$values = array();
|
||||
|
||||
// Store session filters for later use
|
||||
$session_filters = array(
|
||||
'GroupId' => isset($_SESSION['GroupId']) ? $_SESSION['GroupId'] : null,
|
||||
'ServerId' => isset($_SESSION['ServerId']) ? $_SESSION['ServerId'] : null,
|
||||
'StorageId' => isset($_SESSION['StorageId']) ? $_SESSION['StorageId'] : null,
|
||||
'Capturing' => isset($_SESSION['Capturing']) ? $_SESSION['Capturing'] : null,
|
||||
'Analysing' => isset($_SESSION['Analysing']) ? $_SESSION['Analysing'] : null,
|
||||
'Recording' => isset($_SESSION['Recording']) ? $_SESSION['Recording'] : null,
|
||||
'Status' => isset($_SESSION['Status']) ? $_SESSION['Status'] : null,
|
||||
'MonitorName' => isset($_SESSION['MonitorName']) ? $_SESSION['MonitorName'] : null,
|
||||
'Source' => isset($_SESSION['Source']) ? $_SESSION['Source'] : null
|
||||
);
|
||||
|
||||
session_write_close();
|
||||
|
||||
// Apply session filters to SQL
|
||||
if ($session_filters['GroupId']) {
|
||||
$GroupIds = is_array($session_filters['GroupId']) ? $session_filters['GroupId'] : array($session_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 ($session_filters[$filter]) {
|
||||
$filter_values = is_array($session_filters[$filter]) ? $session_filters[$filter] : array($session_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 ($session_filters[$filter]) {
|
||||
$filter_values = is_array($session_filters[$filter]) ? $session_filters[$filter] : array($session_filters[$filter]);
|
||||
if (count($filter_values)) {
|
||||
$conditions[] = 'M.'.$filter.' IN (' . implode(',', array_fill(0, count($filter_values), '?')) . ')';
|
||||
$values = array_merge($values, $filter_values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($session_filters['Status']) {
|
||||
$status_values = is_array($session_filters['Status']) ? $session_filters['Status'] : array($session_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.*
|
||||
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 session filters
|
||||
if ($session_filters['MonitorName']) {
|
||||
$regexp = $session_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 ($session_filters['Source']) {
|
||||
$regexp = $session_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']));
|
||||
});
|
||||
}
|
||||
|
||||
$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'];
|
||||
|
||||
// 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
|
||||
$eventCounts = array('Total', 'Hour', 'Day', 'Week', 'Month', 'Archived');
|
||||
foreach ($eventCounts as $period) {
|
||||
$row[$period.'Events'] = (int)$monitor[$period.'Events'];
|
||||
$row[$period.'EventDiskSpace'] = human_filesize($monitor[$period.'EventDiskSpace']);
|
||||
}
|
||||
|
||||
// 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';
|
||||
$row['CaptureBandwidth'] = isset($monitor['CaptureBandwidth']) ? $monitor['CaptureBandwidth'] : 0;
|
||||
$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(' > ', array_map(function($Group) {
|
||||
if (canView('Stream')) {
|
||||
return '<a href="?view=montagereview&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')) {
|
||||
$options = array();
|
||||
$ratio_factor = $Monitor->ViewWidth() ? $Monitor->ViewHeight() / $Monitor->ViewWidth() : 1;
|
||||
$options['width'] = ZM_WEB_LIST_THUMB_WIDTH;
|
||||
$options['height'] = ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor;
|
||||
$options['scale'] = $Monitor->ViewWidth() ? intval(100*ZM_WEB_LIST_THUMB_WIDTH / $Monitor->ViewWidth()) : 100;
|
||||
$options['mode'] = 'jpeg';
|
||||
$options['frames'] = 1;
|
||||
|
||||
$stillSrc = $Monitor->getStreamSrc($options);
|
||||
$streamSrc = $Monitor->getStreamSrc(array('scale'=>($options['scale'] > 20 ? 100 : $options['scale']*5)));
|
||||
|
||||
$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&mid='.$monitor['Id'].'">'.
|
||||
'<img id="thumbnail'.$Monitor->Id().'" src="'.$stillSrc.'" style="'.$thmbWidth.$thmbHeight.
|
||||
'" stream_src="'.$streamSrc.'" still_src="'.$stillSrc.'"'.
|
||||
($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']
|
||||
);
|
||||
|
||||
// Add formatted event totals to footer
|
||||
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']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -90,6 +90,11 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.consoleTable .colId {
|
||||
width: 70px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.consoleTable .colLeftButtons {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -161,8 +166,20 @@ body.sticky #monitorList thead {
|
||||
}
|
||||
}
|
||||
|
||||
.colThumbnail img:hover {
|
||||
/*
|
||||
position: absolute;
|
||||
*/
|
||||
.colThumbnail {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.colThumbnail img:hover {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* Allow thumbnails to expand beyond table boundaries */
|
||||
.consoleTable:has(.colThumbnail img:hover) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
#monitorList:has(.colThumbnail img:hover) {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
@@ -19,12 +19,16 @@
|
||||
//
|
||||
|
||||
function addFilterSelect($name, $options) {
|
||||
global $view;
|
||||
// Use monitorFilterOnChange on console view for AJAX refresh, submitThisForm elsewhere
|
||||
$onChangeFunction = ($view == 'console') ? 'monitorFilterOnChange' : 'submitThisForm';
|
||||
|
||||
$html = '<span class="term '.$name.'Filter"><label>'.translate($name).'</label>';
|
||||
$html .= '<span class="term-value-wrapper">';
|
||||
$html .= htmlSelect($name.'[]', $options,
|
||||
(isset($_SESSION[$name])?$_SESSION[$name]:''),
|
||||
array(
|
||||
'data-on-change'=>'submitThisForm',
|
||||
'data-on-change'=>$onChangeFunction,
|
||||
'class'=>'chosen',
|
||||
'multiple'=>'multiple',
|
||||
'data-placeholder'=>'All',
|
||||
@@ -52,8 +56,11 @@ function addButtonResetForFilterSelect($nameSelect) {
|
||||
}
|
||||
|
||||
function buildMonitorsFilters() {
|
||||
global $user, $Servers;
|
||||
global $user, $Servers, $view;
|
||||
require_once('includes/Monitor.php');
|
||||
|
||||
// Use monitorFilterOnChange on console view for AJAX refresh, submitThisForm elsewhere
|
||||
$onChangeFunction = ($view == 'console') ? 'monitorFilterOnChange' : 'submitThisForm';
|
||||
|
||||
zm_session_start();
|
||||
foreach (array('GroupId','Capturing','Analysing','Recording','ServerId','StorageId','Status','MonitorId','MonitorName','Source') as $var) {
|
||||
@@ -151,7 +158,7 @@ function buildMonitorsFilters() {
|
||||
$html .= htmlSelect('ServerId[]', $ServersById,
|
||||
(isset($_SESSION['ServerId'])?$_SESSION['ServerId']:''),
|
||||
array(
|
||||
'data-on-change'=>'submitThisForm',
|
||||
'data-on-change'=>$onChangeFunction,
|
||||
'class'=>'chosen',
|
||||
'multiple'=>'multiple',
|
||||
'data-placeholder'=>'All',
|
||||
@@ -168,7 +175,7 @@ function buildMonitorsFilters() {
|
||||
$html .= htmlSelect('StorageId[]', $StorageById,
|
||||
(isset($_SESSION['StorageId'])?$_SESSION['StorageId']:''),
|
||||
array(
|
||||
'data-on-change'=>'submitThisForm',
|
||||
'data-on-change'=>$onChangeFunction,
|
||||
'class'=>'chosen',
|
||||
'multiple'=>'multiple',
|
||||
'data-placeholder'=>'All',
|
||||
@@ -189,7 +196,7 @@ function buildMonitorsFilters() {
|
||||
$html .= htmlSelect( 'Status[]', $status_options,
|
||||
( isset($_SESSION['Status']) ? $_SESSION['Status'] : '' ),
|
||||
array(
|
||||
'data-on-change'=>'submitThisForm',
|
||||
'data-on-change'=>$onChangeFunction,
|
||||
'class'=>'chosen',
|
||||
'multiple'=>'multiple',
|
||||
'data-placeholder'=>'All'
|
||||
@@ -296,7 +303,7 @@ function buildMonitorsFilters() {
|
||||
$html .= '<span class="term-value-wrapper">';
|
||||
$html .= htmlSelect('MonitorId[]', $monitors_dropdown, $selected_monitor_ids,
|
||||
array(
|
||||
'data-on-change'=>'submitThisForm',
|
||||
'data-on-change'=>$onChangeFunction,
|
||||
'class'=>'chosen',
|
||||
'multiple'=>'multiple',
|
||||
'data-placeholder'=>'All',
|
||||
|
||||
@@ -220,232 +220,81 @@ echo $navbar ?>
|
||||
<a href="#" data-flip-control-object="#fbpanel"><i id="fbflip" class="material-icons" data-icon-visible="filter_alt_off" data-icon-hidden="filter_alt"></i></a>
|
||||
|
||||
</div><!-- contentButtons -->
|
||||
<?php
|
||||
ob_start();
|
||||
?>
|
||||
|
||||
<div id="monitorList" class="container-fluid table-responsive-sm">
|
||||
<table class="table table-striped table-hover table-condensed consoleTable">
|
||||
<table
|
||||
id="consoleTable"
|
||||
data-locale="<?php echo i18n() ?>"
|
||||
data-side-pagination="server"
|
||||
data-ajax="ajaxRequest"
|
||||
data-pagination="true"
|
||||
data-page-size="<?php echo ZM_WEB_EVENTS_PER_PAGE ?>"
|
||||
data-page-list="[10, 25, 50, 100, 200, All]"
|
||||
data-search="true"
|
||||
data-cookie="true"
|
||||
data-cookie-same-site="Strict"
|
||||
data-cookie-id-table="zmConsoleTable"
|
||||
data-cookie-expire="2y"
|
||||
data-remember-order="true"
|
||||
data-show-columns="true"
|
||||
data-show-export="true"
|
||||
data-show-footer="true"
|
||||
data-toolbar="#toolbar"
|
||||
data-sort-name="Sequence"
|
||||
data-sort-order="asc"
|
||||
data-show-refresh="true"
|
||||
data-click-to-select="true"
|
||||
data-maintain-meta-data="true"
|
||||
data-buttons-class="btn btn-normal"
|
||||
data-mobile-responsive="true"
|
||||
class="table table-striped table-hover table-condensed consoleTable"
|
||||
style="display:none;"
|
||||
>
|
||||
<thead class="thead-highlight">
|
||||
<tr>
|
||||
<?php if ($canEditMonitors) { ?>
|
||||
<th class="colMark"><input type="checkbox" name="toggleCheck" value="1" data-checkbox-name="markMids[]" data-on-click-this="updateFormCheckboxesByName"/></th>
|
||||
<th data-sortable="false" data-field="toggleCheck" data-checkbox="true"></th>
|
||||
<?php } ?>
|
||||
<?php if ( ZM_WEB_ID_ON_CONSOLE ) { ?>
|
||||
<th class="colId"><?php echo translate('Id') ?></th>
|
||||
<th data-sortable="true" data-field="Id" class="colId"><?php echo translate('Id') ?></th>
|
||||
<?php } ?>
|
||||
<th class="colName"><i class="material-icons">videocam</i> <?php echo translate('Name') ?></th>
|
||||
<th class="colFunction"><?php echo translate('Function') ?></th>
|
||||
<?php if ( ZM_WEB_LIST_THUMBS ) { ?>
|
||||
<th data-sortable="false" data-field="Thumbnail" class="colThumbnail"><?php echo translate('Thumbnail') ?></th>
|
||||
<?php } ?>
|
||||
<th data-sortable="true" data-field="Name" class="colName"><i class="material-icons">videocam</i> <?php echo translate('Name') ?></th>
|
||||
<th data-sortable="true" data-field="Function" class="colFunction"><?php echo translate('Function') ?></th>
|
||||
<?php if ( count($Servers) ) { ?>
|
||||
<th class="colServer"><?php echo translate('Server') ?></th>
|
||||
<th data-sortable="true" data-field="Server" class="colServer"><?php echo translate('Server') ?></th>
|
||||
<?php } ?>
|
||||
<th class="colSource"><i class="material-icons">settings</i> <?php echo translate('Source') ?></th>
|
||||
<th data-sortable="true" data-field="Source" class="colSource"><i class="material-icons">settings</i> <?php echo translate('Source') ?></th>
|
||||
<?php if ( $show_storage_areas ) { ?>
|
||||
<th class="colStorage"><?php echo translate('Storage') ?></th>
|
||||
<th data-sortable="true" data-field="Storage" class="colStorage"><?php echo translate('Storage') ?></th>
|
||||
<?php }
|
||||
|
||||
foreach ( array_keys($eventCounts) as $i ) {
|
||||
$filter = addFilterTerm(
|
||||
$eventCounts[$i]['filter'],
|
||||
count($eventCounts[$i]['filter']['Query']['terms']),
|
||||
count($displayMonitorIds) != $colAllAvailableMonitors #Add monitors to the filter only if the filter limit is set
|
||||
count($displayMonitorIds) != $colAllAvailableMonitors // Add monitors to the filter only if the filter limit is set
|
||||
? array(
|
||||
'cnj'=>'and',
|
||||
'attr'=>'Monitor',
|
||||
'op'=>'IN',
|
||||
'val'=>implode(',', $displayMonitorIds)
|
||||
)
|
||||
: ['cnj'=>'and', 'attr'=>'Monitor']
|
||||
: array('cnj'=>'and', 'attr'=>'Monitor')
|
||||
);
|
||||
parseFilter($filter);
|
||||
echo '<th class="colEvents"><a '
|
||||
echo '<th data-sortable="true" data-field="'.$i.'Events" class="colEvents"><a '
|
||||
.(canView('Events') ? 'href="?view='.ZM_WEB_EVENTS_VIEW.'&page=1'.$filter['querystring'].'">' : '')
|
||||
.$eventCounts[$i]['title']
|
||||
.'</a></th>'.PHP_EOL;
|
||||
} // end foreach eventCounts
|
||||
?>
|
||||
<th class="colZones"><a href="?view=zones"><?php echo translate('Zones') ?></a></th>
|
||||
<th data-sortable="true" data-field="ZoneCount" class="colZones"><a href="?view=zones"><?php echo translate('Zones') ?></a></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="consoleTableBody">
|
||||
<?php
|
||||
$table_head = ob_get_contents();
|
||||
ob_end_clean();
|
||||
echo $table_head;
|
||||
|
||||
$group_ids_by_monitor_id = array();
|
||||
foreach (ZM\Group_Monitor::find(array('MonitorId'=>$displayMonitorIds)) 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();
|
||||
}
|
||||
$monitors = array();
|
||||
for ($monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1) {
|
||||
$monitor = $displayMonitors[$monitor_i];
|
||||
$Monitor = new ZM\Monitor($monitor);
|
||||
$monitors[] = $Monitor;
|
||||
$Monitor->GroupIds(isset($group_ids_by_monitor_id[$Monitor->Id()]) ? $group_ids_by_monitor_id[$Monitor->Id()] : array());
|
||||
if ( $monitor_i and ( $monitor_i % 200 == 0 ) ) {
|
||||
echo '</table>';
|
||||
echo $table_head;
|
||||
} # monitor_i % 200
|
||||
?>
|
||||
<tr id="<?php echo 'monitor_id-'.$monitor['Id'] ?>" title="<?php echo $monitor['Id'] ?>">
|
||||
<?php
|
||||
$source_class = 'infoText';
|
||||
$source_class_reason = '';
|
||||
|
||||
# 1 minute + fps_report_interval should be plenty.
|
||||
$fps_report_seconds = 60+($monitor['FPSReportInterval'] * $monitor['CaptureFPS']);
|
||||
if ( (!$monitor['Status'] || ($monitor['Status'] == 'NotRunning')) && ($monitor['Type'] != 'WebSite')) {
|
||||
$source_class = 'errorText';
|
||||
$source_class_reason = translate('Not Running');
|
||||
} else if ((!$monitor['UpdatedOn']) or (strtotime($monitor['UpdatedOn']) < time()-$fps_report_seconds)) {
|
||||
$source_class = 'errorText';
|
||||
$source_class_reason = translate('Offline');
|
||||
} else {
|
||||
if ( $monitor['CaptureFPS'] == '0.00' ) {
|
||||
$source_class = 'errorText';
|
||||
$source_class_reason = translate('No capture FPS');
|
||||
} else if ( (!$monitor['AnalysisFPS']) && ($monitor['Analysing'] != 'None') ) {
|
||||
$source_class = 'warnText';
|
||||
$source_class_reason = translate('No analysis FPS');
|
||||
}
|
||||
}
|
||||
|
||||
$function_class = 'infoText';
|
||||
|
||||
$dot_class = $source_class;
|
||||
$dot_class_reason = $source_class_reason;
|
||||
if ( $function_class != 'infoText' ) {
|
||||
$dot_class = $function_class;
|
||||
#} else if (($monitor['Analysing'] == 'Always') and !$monitor['Enabled']) {
|
||||
#$dot_class .= ' warnText';
|
||||
#$dot_class_reason .= ' '.translate('Analysis is disabled');
|
||||
#FIXME replace this with a check for runstate vs dbstate
|
||||
}
|
||||
|
||||
$stream_available = canView('Stream') and $monitor['Type']=='WebSite' or ($monitor['CaptureFPS'] && $monitor['Capturing'] != 'None');
|
||||
|
||||
if ($canEditMonitors) {
|
||||
?>
|
||||
<td class="colMark">
|
||||
<input type="checkbox" name="markMids[]" value="<?php echo $monitor['Id'] ?>" data-on-click-this="setButtonStates"/>
|
||||
</td>
|
||||
<?php
|
||||
}
|
||||
if (ZM_WEB_ID_ON_CONSOLE) {
|
||||
?>
|
||||
<td class="colId"><a <?php echo ($stream_available ? 'href="?view=watch&mid='.$monitor['Id'].'">' : '>') . $monitor['Id'] ?></a></td>
|
||||
<?php
|
||||
}
|
||||
$imgHTML = '';
|
||||
if (ZM_WEB_LIST_THUMBS && ($monitor['Capturing'] != 'None') && canView('Stream')) {
|
||||
$options = array();
|
||||
|
||||
$ratio_factor = $Monitor->ViewWidth() ? $Monitor->ViewHeight() / $Monitor->ViewWidth() : 1;
|
||||
$options['width'] = ZM_WEB_LIST_THUMB_WIDTH;
|
||||
$options['height'] = ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor;
|
||||
$options['scale'] = $Monitor->ViewWidth() ? intval(100*ZM_WEB_LIST_THUMB_WIDTH / $Monitor->ViewWidth()) : 100;
|
||||
$options['mode'] = 'jpeg';
|
||||
$options['frames'] = 1;
|
||||
|
||||
$stillSrc = $Monitor->getStreamSrc($options);
|
||||
$streamSrc = $Monitor->getStreamSrc(array('scale'=>($options['scale'] > 20 ? 100 : $options['scale']*5)));
|
||||
|
||||
$thmbWidth = ( $options['width'] ) ? 'width:'.$options['width'].'px;' : '';
|
||||
$thmbHeight = ( $options['height'] ) ? 'height:'.$options['height'].'px;' : '';
|
||||
|
||||
$imgHTML = '<div class="colThumbnail" style="'.$thmbHeight.'"><a';
|
||||
$imgHTML .= $stream_available ? ' href="?view=watch&mid='.$monitor['Id'].'">' : '>';
|
||||
$imgHTML .= '<img id="thumbnail' .$Monitor->Id(). '" src="' .$stillSrc. '" style="'
|
||||
.$thmbWidth.$thmbHeight. '" stream_src="' .$streamSrc. '" still_src="' .$stillSrc. '"'.
|
||||
($options['width'] ? ' width="'.$options['width'].'"' : '' ).
|
||||
($options['height'] ? ' height="'.$options['height'].'"' : '' ).
|
||||
' loading="lazy" /></a></div>';
|
||||
}
|
||||
?>
|
||||
<td class="colName">
|
||||
<i class="material-icons <?php echo $dot_class ?>" title="<?php echo $dot_class_reason ?>">lens</i>
|
||||
<a <?php echo ($stream_available ? 'href="?view=watch&mid='.$monitor['Id'].'">' : '>') . validHtmlStr($monitor['Name']) ?></a><br/>
|
||||
<?php echo $imgHTML ?>
|
||||
<div class="small text-nowrap text-muted">
|
||||
|
||||
<?php
|
||||
if (canView('Groups')) {
|
||||
echo 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 );
|
||||
}
|
||||
return implode(' > ', array_map(function($Group){
|
||||
if (canView('Stream')) {
|
||||
return '<a href="?view=montagereview&GroupId='.$Group->Id().'">'.validHtmlStr($Group->Name()).'</a>';
|
||||
} else {
|
||||
return validHtmlStr($Group->Name());
|
||||
}
|
||||
}, $Groups ));
|
||||
}, $Monitor->GroupIds()));
|
||||
}
|
||||
?>
|
||||
</div></td>
|
||||
<td class="colFunction">
|
||||
<!--<a class="functionLnk <?php echo $function_class ?>" data-mid="<?php echo $monitor['Id'] ?>" id="functionLnk-<?php echo $monitor['Id'] ?>" href="#"><?php echo translate('Fn'.$monitor['Function']) ?></a>-->
|
||||
<?php
|
||||
if ((!$monitor['UpdatedOn']) or (strtotime($monitor['UpdatedOn']) < time()-$fps_report_seconds)) {
|
||||
echo translate('Offline').'<br/>';
|
||||
} else {
|
||||
echo translate('Status'.$monitor['Status']).'<br/>';
|
||||
if ($monitor['Analysing'] != 'None') {
|
||||
echo translate('Analysing') . ': '.translate($monitor['Analysing']).'<br/>';
|
||||
}
|
||||
if ($monitor['Recording'] != 'None') {
|
||||
echo translate('Recording') . ': '.translate($monitor['Recording']) . ($monitor['ONVIF_Event_Listener'] ? ' Use ONVIF' : "") . '<br/>';
|
||||
}
|
||||
?><br/>
|
||||
<div class="small text-nowrap text-muted">
|
||||
<?php
|
||||
$fps_string = '';
|
||||
if (isset($monitor['CaptureFPS'])) {
|
||||
$fps_string .= $monitor['CaptureFPS'];
|
||||
}
|
||||
|
||||
if ( isset($monitor['AnalysisFPS']) and ($monitor['Analysing'] != 'None')) {
|
||||
$fps_string .= '/' . $monitor['AnalysisFPS'];
|
||||
$total_analysis_fps += $monitor['AnalysisFPS'];
|
||||
}
|
||||
if ($fps_string) $fps_string .= ' fps';
|
||||
if (!empty($monitor['CaptureBandwidth']))
|
||||
$fps_string .= ' ' . human_filesize($monitor['CaptureBandwidth']).'/s';
|
||||
$total_capturing_bandwidth += $monitor['CaptureBandwidth'];
|
||||
$total_fps += $monitor['CaptureFPS'];
|
||||
echo $fps_string;
|
||||
echo '</div>';
|
||||
} # end if offline
|
||||
echo '</td>'.PHP_EOL;
|
||||
if (count($Servers)) {
|
||||
$Server = isset($ServersById[$monitor['ServerId']]) ? $ServersById[$monitor['ServerId']] : new ZM\Server($monitor['ServerId']);
|
||||
echo '<td class="colServer">'.validHtmlStr($Server->Name()).'</td>'.PHP_EOL;
|
||||
}
|
||||
echo '<td class="colSource">'. makeLink( '?view=monitor&mid='.$monitor['Id'], '<span class="'.$source_class.'">'.validHtmlStr($Monitor->Source()).'</span>', $Monitor->canEdit());
|
||||
echo '<br/>'.$Monitor->Width().'x'.$Monitor->Height();
|
||||
echo '</td>';
|
||||
if ($show_storage_areas) {
|
||||
echo '<td class="colStorage">'.
|
||||
(isset($StorageById[$monitor['StorageId']]) ? validHtmlStr($StorageById[$monitor['StorageId']]->Name()) : ($monitor['StorageId']?'<span class="error">Deleted '.$monitor['StorageId'].'</span>' : '')).'</td>'.PHP_EOL;
|
||||
}
|
||||
|
||||
foreach (array_keys($eventCounts) as $i) {
|
||||
echo '<td class="colEvents"><a '. (canView('Events') ? 'href="?view='.ZM_WEB_EVENTS_VIEW.'&page=1'.$monitor['eventCounts'][$i]['filter']['querystring'].'">' : '') .
|
||||
(int)$monitor[$i.'Events'] . '<br/></a><div class="small text-nowrap text-muted">' . human_filesize($monitor[$i.'EventDiskSpace']).'</div></td>'.PHP_EOL;
|
||||
}
|
||||
echo '<td class="colZones">'. makeLink('?view=zones&mid='.$monitor['Id'], $monitor['ZoneCount'], canView('Monitors')) .'</td>'.PHP_EOL;
|
||||
?>
|
||||
</tr>
|
||||
<?php
|
||||
} # end for each monitor
|
||||
?>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
@@ -454,6 +303,9 @@ for ($monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1) {
|
||||
<?php } ?>
|
||||
<?php if ( ZM_WEB_ID_ON_CONSOLE ) { ?>
|
||||
<td class="colId"><?php echo translate('Total').":".count($displayMonitors) ?></td>
|
||||
<?php } ?>
|
||||
<?php if ( ZM_WEB_LIST_THUMBS ) { ?>
|
||||
<td class="colThumbnail"></td>
|
||||
<?php } ?>
|
||||
<td class="colName"></td>
|
||||
<td class="colFunction"><?php echo human_filesize($total_capturing_bandwidth ).'/s '.
|
||||
|
||||
@@ -1,21 +1,228 @@
|
||||
function setButtonStates(element) {
|
||||
const form = element.form;
|
||||
var checked = 0;
|
||||
for ( var i=0, len = form.elements.length; i < len; i++ ) {
|
||||
if (
|
||||
form.elements[i].type == "checkbox" &&
|
||||
form.elements[i].name == "markMids[]"
|
||||
) {
|
||||
var tr = $j(form.elements[i]).closest("tr");
|
||||
if ( form.elements[i].checked ) {
|
||||
checked ++;
|
||||
tr.addClass("danger");
|
||||
"use strict";
|
||||
const table = $j('#consoleTable');
|
||||
var ajax = null;
|
||||
var monitors = {}; // Store monitors by ID for function modal
|
||||
|
||||
// Update footer with dynamic totals
|
||||
function updateFooter(footer) {
|
||||
// Target the footer within the bootstrap-table wrapper
|
||||
// Bootstrap-table may transform td to th and wrap content in divs
|
||||
var footerRow = $j('#consoleTable').closest('.bootstrap-table').find('tfoot tr');
|
||||
if (!footerRow.length) {
|
||||
footerRow = $j('#consoleTable tfoot tr');
|
||||
}
|
||||
|
||||
// Helper function to update cell content (only updates th-inner div)
|
||||
function updateCell(selector, content) {
|
||||
var cell = footerRow.find(selector);
|
||||
if (cell.length) {
|
||||
// Only update the th-inner div if it exists
|
||||
var innerDiv = cell.find('.th-inner');
|
||||
if (innerDiv.length) {
|
||||
innerDiv.html(content);
|
||||
} else {
|
||||
tr.removeClass("danger");
|
||||
cell.html(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( checked ) {
|
||||
|
||||
// Update monitor count (in Id column if shown)
|
||||
updateCell('td.colId, th.colId', 'Total: ' + footer.monitor_count);
|
||||
|
||||
// Update bandwidth/FPS (in Function column)
|
||||
updateCell('td.colFunction, th.colFunction', footer.bandwidth_fps);
|
||||
|
||||
// Update event totals
|
||||
var eventPeriods = ['Total', 'Hour', 'Day', 'Week', 'Month', 'Archived'];
|
||||
var eventCells = footerRow.find('td.colEvents, th.colEvents');
|
||||
eventPeriods.forEach(function(period, index) {
|
||||
if (eventCells.length > index) {
|
||||
var cell = $j(eventCells[index]);
|
||||
// Only update the th-inner div if it exists
|
||||
var innerDiv = cell.find('.th-inner');
|
||||
var target = innerDiv.length ? innerDiv : cell;
|
||||
|
||||
var link = target.find('a');
|
||||
if (link.length) {
|
||||
// Preserve the link but update the count
|
||||
var newHtml = footer[period + 'Events'] + '<br/><div class="small text-nowrap text-muted">' +
|
||||
footer[period + 'EventDiskSpace'] + '</div>';
|
||||
link.html(newHtml);
|
||||
} else {
|
||||
target.html(footer[period + 'Events'] + '<br/><div class="small text-nowrap text-muted">' +
|
||||
footer[period + 'EventDiskSpace'] + '</div>');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update zone count
|
||||
updateCell('td.colZones, th.colZones', footer.total_zones);
|
||||
}
|
||||
|
||||
// Called by bootstrap-table to retrieve monitor data
|
||||
function ajaxRequest(params) {
|
||||
if (ajax) ajax.abort();
|
||||
ajax = $j.ajax({
|
||||
method: 'POST',
|
||||
url: thisUrl + '?view=request&request=console&task=query',
|
||||
data: params.data,
|
||||
timeout: 0,
|
||||
success: function(data) {
|
||||
if (data.result == 'Error') {
|
||||
alert(data.message);
|
||||
return;
|
||||
}
|
||||
var rows = processRows(data.rows);
|
||||
// Store monitors for function modal using original ID
|
||||
rows.forEach(function(row) {
|
||||
monitors[row._id] = row;
|
||||
});
|
||||
|
||||
// rearrange the result into what bootstrap-table expects
|
||||
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});
|
||||
|
||||
// Update footer with totals from response after table is rendered
|
||||
if (data.footer) {
|
||||
updateFooter(data.footer);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR) {
|
||||
if (jqXHR.statusText != 'abort') {
|
||||
console.log("error", jqXHR);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processRows(rows) {
|
||||
$j.each(rows, function(ndx, row) {
|
||||
var mid = row.Id;
|
||||
|
||||
// Store original ID for later use
|
||||
row._id = mid;
|
||||
|
||||
var stream_available = canView.Stream && (row.Type == 'WebSite' || (row.CaptureFPS && row.Capturing != 'None'));
|
||||
|
||||
// Determine status classes
|
||||
var source_class = 'infoText';
|
||||
var source_class_reason = '';
|
||||
// FPS report interval: 60 seconds base + 30 seconds buffer for FPSReportInterval
|
||||
var fps_report_seconds = 90;
|
||||
|
||||
if ((!row.Status || row.Status == 'NotRunning') && row.Type != 'WebSite') {
|
||||
source_class = 'errorText';
|
||||
source_class_reason = 'Not Running';
|
||||
} else if (!row.UpdatedOn || (new Date(row.UpdatedOn).getTime() < Date.now() - fps_report_seconds * 1000)) {
|
||||
source_class = 'errorText';
|
||||
source_class_reason = 'Offline';
|
||||
} else {
|
||||
if (row.CaptureFPS == '0.00') {
|
||||
source_class = 'errorText';
|
||||
source_class_reason = 'No capture FPS';
|
||||
} else if (!row.AnalysisFPS && row.Analysing != 'None') {
|
||||
source_class = 'warnText';
|
||||
source_class_reason = 'No analysis FPS';
|
||||
}
|
||||
}
|
||||
|
||||
var dot_class = source_class;
|
||||
var dot_class_reason = source_class_reason;
|
||||
|
||||
// Format Id column
|
||||
if (stream_available) {
|
||||
row.Id = '<a href="?view=watch&mid=' + mid + '">' + mid + '</a>';
|
||||
} else {
|
||||
row.Id = mid;
|
||||
}
|
||||
|
||||
// Thumbnail goes in its own column if enabled
|
||||
// (row.Thumbnail is already set from AJAX response)
|
||||
|
||||
// Format Name column with status indicator, link, and groups (no thumbnail)
|
||||
var nameHtml = '<i class="material-icons ' + dot_class + '" title="' + dot_class_reason + '">lens</i> ';
|
||||
if (stream_available) {
|
||||
nameHtml += '<a href="?view=watch&mid=' + mid + '">' + row.Name + '</a>';
|
||||
} else {
|
||||
nameHtml += row.Name;
|
||||
}
|
||||
|
||||
// Add groups
|
||||
if (row.Groups) {
|
||||
nameHtml += '<br/><div class="small text-nowrap text-muted">' + row.Groups + '</div>';
|
||||
}
|
||||
|
||||
row.Name = nameHtml;
|
||||
|
||||
// Format Function column with status and FPS info
|
||||
var functionHtml = '';
|
||||
if (!row.UpdatedOn || (new Date(row.UpdatedOn).getTime() < Date.now() - fps_report_seconds * 1000)) {
|
||||
functionHtml = 'Offline<br/>';
|
||||
} else {
|
||||
functionHtml = '';
|
||||
if (row.Analysing && row.Analysing != 'None') {
|
||||
functionHtml += 'Analysing: ' + row.Analysing + '<br/>';
|
||||
}
|
||||
if (row.Recording && row.Recording != 'None') {
|
||||
functionHtml += 'Recording: ' + row.Recording;
|
||||
if (row.ONVIF_Event_Listener) {
|
||||
functionHtml += ' Use ONVIF';
|
||||
}
|
||||
functionHtml += '<br/>';
|
||||
}
|
||||
functionHtml += '<br/><div class="small text-nowrap text-muted">';
|
||||
|
||||
var fps_string = '';
|
||||
if (row.CaptureFPS) {
|
||||
fps_string = row.CaptureFPS;
|
||||
}
|
||||
if (row.AnalysisFPS && row.Analysing != 'None') {
|
||||
fps_string += '/' + row.AnalysisFPS;
|
||||
}
|
||||
if (fps_string) fps_string += ' fps';
|
||||
if (row.CaptureBandwidth) {
|
||||
fps_string += ' ' + row.CaptureBandwidth;
|
||||
}
|
||||
functionHtml += fps_string + '</div>';
|
||||
}
|
||||
row.Function = functionHtml;
|
||||
|
||||
// Format Source column with link and dimensions
|
||||
var sourceHtml = '';
|
||||
if (canEdit.Monitors) {
|
||||
sourceHtml = '<a href="?view=monitor&mid=' + mid + '"><span class="' + source_class + '">' + row.Source + '</span></a>';
|
||||
} else {
|
||||
sourceHtml = '<span class="' + source_class + '">' + row.Source + '</span>';
|
||||
}
|
||||
sourceHtml += '<br/>' + row.Width + 'x' + row.Height;
|
||||
row.Source = sourceHtml;
|
||||
|
||||
// Format event count columns
|
||||
var eventPeriods = ['Total', 'Hour', 'Day', 'Week', 'Month', 'Archived'];
|
||||
eventPeriods.forEach(function(period) {
|
||||
if (canView.Events) {
|
||||
row[period + 'Events'] = '<a href="?view=' + ZM_WEB_EVENTS_VIEW + '&MonitorId=' + mid + '">' +
|
||||
row[period + 'Events'] + '</a><br/><div class="small text-nowrap text-muted">' +
|
||||
row[period + 'EventDiskSpace'] + '</div>';
|
||||
} else {
|
||||
row[period + 'Events'] = row[period + 'Events'] + '<br/><div class="small text-nowrap text-muted">' +
|
||||
row[period + 'EventDiskSpace'] + '</div>';
|
||||
}
|
||||
});
|
||||
|
||||
// Format Zones column
|
||||
if (canView.Monitors) {
|
||||
row.ZoneCount = '<a href="?view=zones&mid=' + mid + '">' + row.ZoneCount + '</a>';
|
||||
}
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function setButtonStates() {
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
const form = document.forms['monitorForm'];
|
||||
|
||||
if (selections && selections.length > 0) {
|
||||
form.editBtn.disabled = false;
|
||||
form.deleteBtn.disabled = false;
|
||||
form.selectBtn.disabled = false;
|
||||
@@ -44,73 +251,69 @@ function cloneMonitor(element) {
|
||||
alert('Need create monitors privilege');
|
||||
return;
|
||||
}
|
||||
var form = element.form;
|
||||
var monitorId = -1;
|
||||
// get the value of the first checkbox
|
||||
for ( var i=0, len=form.elements.length; i < len; i++ ) {
|
||||
if (
|
||||
form.elements[i].type == "checkbox" &&
|
||||
form.elements[i].name == "markMids[]" &&
|
||||
form.elements[i].checked
|
||||
) {
|
||||
monitorId = form.elements[i].value;
|
||||
break;
|
||||
}
|
||||
} // end foreach element
|
||||
if ( monitorId != -1 ) {
|
||||
window.location.assign('?view=monitor&dupId='+monitorId);
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
if (selections.length > 0) {
|
||||
var monitorId = selections[0]._id;
|
||||
window.location.assign('?view=monitor&dupId=' + monitorId);
|
||||
} else {
|
||||
alert('Please select a monitor to clone');
|
||||
}
|
||||
}
|
||||
|
||||
function editMonitor( element ) {
|
||||
var form = element.form;
|
||||
var monitorIds = Array();
|
||||
|
||||
for ( var i = 0; i < form.elements.length; i++ ) {
|
||||
if (
|
||||
form.elements[i].type == "checkbox" &&
|
||||
form.elements[i].name == "markMids[]" &&
|
||||
form.elements[i].checked
|
||||
) {
|
||||
monitorIds.push( form.elements[i].value );
|
||||
}
|
||||
} // end foreach checkboxes
|
||||
if ( monitorIds.length == 1 ) {
|
||||
window.location.assign('?view=monitor&mid='+monitorIds[0]);
|
||||
} else if ( monitorIds.length > 1 ) {
|
||||
window.location.assign( '?view=monitors&'+(monitorIds.map(function(mid) {
|
||||
return 'mids[]='+mid;
|
||||
function editMonitor(element) {
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
if (selections.length == 0) return;
|
||||
|
||||
var monitorIds = selections.map(function(sel) {
|
||||
return sel._id;
|
||||
});
|
||||
|
||||
if (monitorIds.length == 1) {
|
||||
window.location.assign('?view=monitor&mid=' + monitorIds[0]);
|
||||
} else if (monitorIds.length > 1) {
|
||||
window.location.assign('?view=monitors&' + (monitorIds.map(function(mid) {
|
||||
return 'mids[]=' + mid;
|
||||
}).join('&')));
|
||||
}
|
||||
}
|
||||
|
||||
function deleteMonitor( element ) {
|
||||
function deleteMonitor(element) {
|
||||
if (confirm('Deleting a monitor only marks it as deleted. Events will age out. If you want them to be immediately removed, please delete them first.\nAre you sure you wish to delete?')) {
|
||||
const form = element.form;
|
||||
form.elements['action'].value = 'delete';
|
||||
|
||||
// Get selected monitor IDs and add them to the form
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
selections.forEach(function(sel) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = 'markMids[]';
|
||||
input.value = sel._id;
|
||||
form.appendChild(input);
|
||||
});
|
||||
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
|
||||
function selectMonitor(element) {
|
||||
var form = element.form;
|
||||
var url = thisUrl+'?view=console';
|
||||
for ( var i = 0; i < form.elements.length; i++ ) {
|
||||
if (
|
||||
form.elements[i].type == 'checkbox' &&
|
||||
form.elements[i].name == 'markMids[]' &&
|
||||
form.elements[i].checked
|
||||
) {
|
||||
url += '&MonitorId[]='+form.elements[i].value;
|
||||
}
|
||||
}
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
var url = thisUrl + '?view=console';
|
||||
|
||||
selections.forEach(function(sel) {
|
||||
url += '&MonitorId[]=' + sel._id;
|
||||
});
|
||||
|
||||
window.location.replace(url);
|
||||
}
|
||||
|
||||
function reloadWindow() {
|
||||
window.location.replace( thisUrl );
|
||||
// Use table refresh instead of full page reload
|
||||
if (table && table.length) {
|
||||
table.bootstrapTable('refresh');
|
||||
} else {
|
||||
window.location.replace(thisUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// Manage the the Function modal and its buttons
|
||||
@@ -181,10 +384,46 @@ function manageFunctionModal(evt) {
|
||||
$j('#modalFunction').modal('show');
|
||||
} // end function manageFunctionModal
|
||||
|
||||
// Called when monitor filters change - refreshes table via AJAX instead of full page reload
|
||||
function monitorFilterOnChange() {
|
||||
// On console view with bootstrap-table, just refresh the table
|
||||
if (typeof table !== 'undefined' && table.length) {
|
||||
table.bootstrapTable('refresh');
|
||||
} else {
|
||||
// Fall back to full page reload on other views
|
||||
submitThisForm();
|
||||
}
|
||||
}
|
||||
|
||||
function initPage() {
|
||||
// Init the bootstrap-table
|
||||
table.bootstrapTable({icons: icons});
|
||||
|
||||
// Enable or disable buttons based on current selection and user rights
|
||||
table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table',
|
||||
function() {
|
||||
const selections = table.bootstrapTable('getSelections');
|
||||
const form = document.forms['monitorForm'];
|
||||
|
||||
if (selections.length > 0) {
|
||||
form.editBtn.disabled = false;
|
||||
form.deleteBtn.disabled = false;
|
||||
form.selectBtn.disabled = false;
|
||||
form.cloneBtn.disabled = false;
|
||||
} else {
|
||||
form.editBtn.disabled = true;
|
||||
form.deleteBtn.disabled = true;
|
||||
form.selectBtn.disabled = true;
|
||||
form.cloneBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Setup automatic refresh with table refresh instead of page reload
|
||||
if (consoleRefreshTimeout > 0) {
|
||||
setInterval(reloadWindow, consoleRefreshTimeout);
|
||||
}
|
||||
|
||||
if ( showDonatePopup ) {
|
||||
$j.getJSON(thisUrl + '?request=modal&modal=donate')
|
||||
.done(function(data) {
|
||||
@@ -199,17 +438,21 @@ function initPage() {
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
// Setup the thumbnail video animation after table loads
|
||||
table.on('post-body.bs.table', function() {
|
||||
if (!isMobile()) initThumbAnimation();
|
||||
$j('.functionLnk').click(manageFunctionModal);
|
||||
});
|
||||
|
||||
// Setup the thumbnail video animation
|
||||
if (!isMobile()) initThumbAnimation();
|
||||
|
||||
$j('.functionLnk').click(manageFunctionModal);
|
||||
|
||||
// Makes table sortable
|
||||
// Makes table sortable - disabled by default, enabled by Sort button
|
||||
// Note: This may need adjustment for bootstrap-table compatibility
|
||||
$j('#consoleTableBody').sortable({
|
||||
disabled: true,
|
||||
update: applySort,
|
||||
axis: 'Y'} );
|
||||
|
||||
// Make the table visible after initialization
|
||||
table.show();
|
||||
} // end function initPage
|
||||
|
||||
function sortMonitors(button) {
|
||||
|
||||
@@ -11,22 +11,6 @@ if ( canEdit('System') && ZM_DYN_SHOW_DONATE_REMINDER ) {
|
||||
}
|
||||
?>
|
||||
var showDonatePopup = <?php echo isset($showDonatePopup )?'true':'false' ?>;
|
||||
var monitors = new Array();
|
||||
<?php
|
||||
global $monitors;
|
||||
foreach ( $monitors as $monitor ) {
|
||||
?>
|
||||
monitors[<?php echo $monitor->Id() ?>] = {
|
||||
'Id': <?php echo $monitor->Id() ?>,
|
||||
'Name': '<?php echo $monitor->Name() ?>',
|
||||
'ViewWidth': <?php echo $monitor->ViewWidth() ?>,
|
||||
'ViewHeight':<?php echo $monitor->ViewHeight() ?>,
|
||||
'Url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
|
||||
'Type': '<?php echo $monitor->Type() ?>',
|
||||
'Function': '<?php echo $monitor->Function() ?>',
|
||||
'Enabled': '<?php echo $monitor->Enabled() ?>',
|
||||
'DecodingEnabled': '<?php echo $monitor->DecodingEnabled() ?>'
|
||||
};
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
||||
var ZM_WEB_EVENTS_VIEW = '<?php echo ZM_WEB_EVENTS_VIEW ?>';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user