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

This commit is contained in:
Isaac Connor 2021-12-14 09:29:03 -05:00
commit cfcc39defb
22 changed files with 642 additions and 287 deletions

View File

@ -283,6 +283,7 @@ CREATE TABLE `Filters` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`UserId` int(10) unsigned, `UserId` int(10) unsigned,
`ExecuteInterval` int(10) unsigned NOT NULL default '60',
`Query_json` text NOT NULL, `Query_json` text NOT NULL,
`AutoArchive` tinyint(3) unsigned NOT NULL default '0', `AutoArchive` tinyint(3) unsigned NOT NULL default '0',
`AutoUnarchive` tinyint(3) unsigned NOT NULL default '0', `AutoUnarchive` tinyint(3) unsigned NOT NULL default '0',

View File

@ -56,7 +56,7 @@ EXECUTE stmt;
SET @s = (SELECT IF( SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Monitor_Status' AND table_name = 'Monitor_Status'
AND column_name = 'DayEvents' AND column_name = 'DayEventDiskSpace'
) > 0, ) > 0,
"ALTER TABLE `Monitor_Status` DROP `DayEventDiskSpace`", "ALTER TABLE `Monitor_Status` DROP `DayEventDiskSpace`",
"SELECT 'Column DayEventDiskSpace already removed from Monitor_Status'" "SELECT 'Column DayEventDiskSpace already removed from Monitor_Status'"

18
db/zm_update-1.37.6.sql Normal file
View File

@ -0,0 +1,18 @@
--
-- Update Filters table to have a ExecuteInterval Column
--
SELECT 'Checking for ExecuteInterval in Filters';
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Filters'
AND table_schema = DATABASE()
AND column_name = 'ExecuteInterval'
) > 0,
"SELECT 'Column ExecuteInterval already exists in Filters'",
"ALTER TABLE Filters ADD COLUMN `ExecuteInterval` int(10) unsigned NOT NULL default '60' AFTER `UserId`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

View File

@ -36,7 +36,7 @@
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.37.5 Version: 1.37.6
Release: 1%{?dist} Release: 1%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons Group: System Environment/Daemons

View File

@ -56,6 +56,7 @@ $primary_key = 'Id';
%fields = map { $_ => $_ } qw( %fields = map { $_ => $_ } qw(
Id Id
Name Name
ExecuteInterval
Query_json Query_json
AutoArchive AutoArchive
AutoUnarchive AutoUnarchive
@ -106,7 +107,6 @@ sub Execute {
$sql =~ s/zmSystemLoad/$load/g; $sql =~ s/zmSystemLoad/$load/g;
} }
$sql .= ' FOR UPDATE' if $$self{LockRows};
Debug("Filter::Execute SQL ($sql)"); Debug("Filter::Execute SQL ($sql)");
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
@ -371,10 +371,7 @@ sub Sql {
if ( @auto_terms ) { if ( @auto_terms ) {
$sql .= ' AND ( '.join(' or ', @auto_terms).' )'; $sql .= ' AND ( '.join(' or ', @auto_terms).' )';
} }
if ( !$filter_expr->{sort_field} ) {
$filter_expr->{sort_field} = 'StartDateTime';
$filter_expr->{sort_asc} = 0;
}
my $sort_column = ''; my $sort_column = '';
if ( $filter_expr->{sort_field} eq 'Id' ) { if ( $filter_expr->{sort_field} eq 'Id' ) {
$sort_column = 'E.Id'; $sort_column = 'E.Id';
@ -406,14 +403,23 @@ sub Sql {
$sort_column = 'E.MaxScore'; $sort_column = 'E.MaxScore';
} elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) { } elsif ( $filter_expr->{sort_field} eq 'DiskSpace' ) {
$sort_column = 'E.DiskSpace'; $sort_column = 'E.DiskSpace';
} else { } elsif ( $filter_expr->{sort_field} ne '' ) {
$sort_column = 'E.StartDateTime'; $sort_column = 'E.'.$filter_expr->{sort_field};
}
if ( $sort_column ne '' ) {
$sql .= ' ORDER BY '.$sort_column.' '.($filter_expr->{sort_asc} ? 'ASC' : 'DESC');
} }
my $sort_order = $filter_expr->{sort_asc} ? 'ASC' : 'DESC';
$sql .= ' ORDER BY '.$sort_column.' '.$sort_order;
if ($filter_expr->{limit}) { if ($filter_expr->{limit}) {
$sql .= ' LIMIT 0,'.$filter_expr->{limit}; $sql .= ' LIMIT 0,'.$filter_expr->{limit};
} }
if ($$self{LockRows}) {
$sql .= ' FOR UPDATE OF E'
} else {
$sql .= ' FOR SHARE OF E'
}
if ($filter_expr->{skip_locked}) {
$sql .= ' SKIP LOCKED';
}
$self->{Sql} = $sql; $self->{Sql} = $sql;
} # end if has Sql } # end if has Sql
return $self->{Sql}; return $self->{Sql};

View File

@ -42,6 +42,7 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# will save memory. # will save memory.
our %EXPORT_TAGS = ( our %EXPORT_TAGS = (
constants => [ qw( constants => [ qw(
STATE_UNKNOWN
STATE_IDLE STATE_IDLE
STATE_PREALARM STATE_PREALARM
STATE_ALARM STATE_ALARM
@ -98,11 +99,12 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Config qw(:all); use ZoneMinder::Config qw(:all);
use ZoneMinder::Logger qw(:all); use ZoneMinder::Logger qw(:all);
use constant STATE_IDLE => 0; use constant STATE_UNKNOWN => 0;
use constant STATE_PREALARM => 1; use constant STATE_IDLE => 1;
use constant STATE_ALARM => 2; use constant STATE_PREALARM => 2;
use constant STATE_ALERT => 3; use constant STATE_ALARM => 3;
use constant STATE_TAPE => 4; use constant STATE_ALERT => 4;
use constant STATE_TAPE => 5;
use constant ACTION_GET => 1; use constant ACTION_GET => 1;
use constant ACTION_SET => 2; use constant ACTION_SET => 2;

View File

@ -138,7 +138,6 @@ $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $event_id = 0; my $event_id = 0;
if (!EVENT_PATH) { if (!EVENT_PATH) {
@ -174,6 +173,7 @@ my @filters;
my $last_action = 0; my $last_action = 0;
while (!$zm_terminate) { while (!$zm_terminate) {
my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $now = time; my $now = time;
if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) { if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) {
Debug('Reloading filters'); Debug('Reloading filters');
@ -183,6 +183,15 @@ while (!$zm_terminate) {
foreach my $filter (@filters) { foreach my $filter (@filters) {
last if $zm_terminate; last if $zm_terminate;
my $elapsed = ($now - $$filter{last_ran});
if ($$filter{last_ran} and ($elapsed < $$filter{ExecuteInterval})) {
my $filter_delay = $$filter{ExecuteInterval} - ($now - $$filter{last_ran});
$delay = $filter_delay if $filter_delay < $delay;
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed");
next;
}
if ($$filter{Concurrent} and !($filter_id or $filter_name)) { if ($$filter{Concurrent} and !($filter_id or $filter_name)) {
my ( $proc ) = $0 =~ /(\S+)/; my ( $proc ) = $0 =~ /(\S+)/;
my ( $id ) = $$filter{Id} =~ /(\d+)/; my ( $id ) = $$filter{Id} =~ /(\d+)/;
@ -191,8 +200,9 @@ while (!$zm_terminate) {
system(qq`$proc --filter_id $id &`); system(qq`$proc --filter_id $id &`);
} else { } else {
checkFilter($filter); checkFilter($filter);
$$filter{last_ran} = $now;
} }
} } # end foreach filter
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate; last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
@ -362,11 +372,6 @@ sub checkFilter {
} # end if AutoCopy } # end if AutoCopy
if ( $filter->{UpdateDiskSpace} ) { if ( $filter->{UpdateDiskSpace} ) {
if ( $$filter{LockRows} ) {
$ZoneMinder::Database::dbh->begin_work();
$Event->lock_and_load();
}
my $old_diskspace = $$Event{DiskSpace}; my $old_diskspace = $$Event{DiskSpace};
my $new_diskspace = $Event->DiskSpace(undef); my $new_diskspace = $Event->DiskSpace(undef);

View File

@ -166,11 +166,7 @@ while (!$zm_terminate) {
foreach my $connection ( values(%spawned_connections) ) { foreach my $connection ( values(%spawned_connections) ) {
if ( vec($rout, $connection->fileno(), 1) ) { if ( vec($rout, $connection->fileno(), 1) ) {
Debug('Got input from spawned connection ' Debug('Got input from spawned connection '
.$connection->name() .$connection->name().' ('.$connection->fileno().')');
.' ('
.$connection->fileno()
.')'
);
my $messages = $connection->getMessages(); my $messages = $connection->getMessages();
if (defined($messages)) { if (defined($messages)) {
foreach my $message ( @$messages ) { foreach my $message ( @$messages ) {
@ -200,15 +196,17 @@ while (!$zm_terminate) {
foreach my $connection ( @in_poll_connections ) { foreach my $connection ( @in_poll_connections ) {
my $messages = $connection->getMessages(); my $messages = $connection->getMessages();
if (defined($messages)) { if (defined($messages)) {
foreach my $message ( @$messages ) { foreach my $message (@$messages) { handleMessage($connection, $message) };
handleMessage($connection, $message);
}
} }
} }
# Check for alarms that might have happened # Check for alarms that might have happened
my @out_messages; my @out_messages;
foreach my $monitor ( values %monitors ) { foreach my $monitor ( values %monitors ) {
if ($$monitor{Function} eq 'None') {
$monitor_reload_time = 0;
next;
}
if (!zmMemVerify($monitor)) { if (!zmMemVerify($monitor)) {
# Our attempt to verify the memory handle failed. We should reload the monitors. # Our attempt to verify the memory handle failed. We should reload the monitors.
@ -217,15 +215,11 @@ while (!$zm_terminate) {
next; next;
} }
my ( $state, $last_event ) = zmMemRead( $monitor, my ($state, $last_event) = zmMemRead($monitor, [
[
'shared_data:state', 'shared_data:state',
'shared_data:last_event' 'shared_data:last_event'
] ]);
);
#print( "$monitor->{Id}: S:$state, LE:$last_event" );
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" );
if ($state == STATE_ALARM or $state == STATE_ALERT) { if ($state == STATE_ALARM or $state == STATE_ALERT) {
# In alarm state # In alarm state
if ( !defined($monitor->{LastEvent}) if ( !defined($monitor->{LastEvent})

View File

@ -65,7 +65,7 @@ unsigned int Buffer::expand(unsigned int count) {
int Buffer::read_into(int sd, unsigned int bytes) { int Buffer::read_into(int sd, unsigned int bytes) {
// Make sure there is enough space // Make sure there is enough space
this->expand(bytes); this->expand(bytes);
Debug(3, "Reading %u btes", bytes); Debug(3, "Reading %u bytes", bytes);
int bytes_read = ::read(sd, mTail, bytes); int bytes_read = ::read(sd, mTail, bytes);
if (bytes_read > 0) { if (bytes_read > 0) {
mTail += bytes_read; mTail += bytes_read;

View File

@ -118,38 +118,31 @@ constexpr Rgb kRGBTransparent = 0x01000000;
/* Convert RGB colour value into BGR\ARGB\ABGR */ /* Convert RGB colour value into BGR\ARGB\ABGR */
inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) { inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) {
Rgb result; Rgb result = 0;
switch (p_subpixorder) { switch (p_subpixorder) {
case ZM_SUBPIX_ORDER_BGR: case ZM_SUBPIX_ORDER_BGR:
case ZM_SUBPIX_ORDER_BGRA: case ZM_SUBPIX_ORDER_BGRA:
{
BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col); BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col); GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col); RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col);
}
break; break;
case ZM_SUBPIX_ORDER_ARGB: case ZM_SUBPIX_ORDER_ARGB:
{
BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col); BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col); GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col); RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col);
}
break; break;
case ZM_SUBPIX_ORDER_ABGR: case ZM_SUBPIX_ORDER_ABGR:
{
BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col); BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col);
GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col); GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col);
RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col); RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col);
}
break; break;
/* Grayscale */ /* Grayscale */
case ZM_SUBPIX_ORDER_NONE: case ZM_SUBPIX_ORDER_NONE:
result = p_col & 0xff; result = p_col & 0xff;
break; break;
default: default:
return p_col; result = p_col;
break; break;
} }

View File

@ -365,6 +365,7 @@ int main(int argc, char *argv[]) {
monitor->Id()); monitor->Id());
zmDbDo(sql); zmDbDo(sql);
} }
monitors.clear();
Image::Deinitialise(); Image::Deinitialise();
Debug(1, "terminating"); Debug(1, "terminating");

View File

@ -218,8 +218,8 @@ rm .gitignore
cd ../ cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then if [ -e "$DIRECTORY.orig.tar.gz" ]; then
read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]" read -p "$DIRECTORY.orig.tar.gz exists, overwrite it? [Y/n]"
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi; fi;

View File

@ -1 +1 @@
1.37.5 1.37.6

View File

@ -48,7 +48,7 @@ if (isset($_REQUEST['order'])) {
} else if (strtolower($_REQUEST['order']) == 'desc') { } else if (strtolower($_REQUEST['order']) == 'desc') {
$order = 'DESC'; $order = 'DESC';
} else { } else {
Warning("Invalid value for order " . $_REQUEST['order']); Warning('Invalid value for order ' . $_REQUEST['order']);
} }
} }
@ -170,18 +170,23 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
// The names of columns shown in the event view that are NOT dB columns in the database // The names of columns shown in the event view that are NOT dB columns in the database
$col_alt = array('Monitor', 'Storage'); $col_alt = array('Monitor', 'Storage');
if ( $sort != '' ) {
if (!in_array($sort, array_merge($columns, $col_alt))) { if (!in_array($sort, array_merge($columns, $col_alt))) {
ZM\Error('Invalid sort field: ' . $sort); ZM\Error('Invalid sort field: ' . $sort);
$sort = 'Id'; $sort = '';
} else if ( $sort == 'Monitor' ) {
$sort = 'M.Name';
} else {
$sort = 'E.'.$sort;
}
} }
$values = array(); $values = array();
$likes = array(); $likes = array();
$where = $filter->sql()?' WHERE ('.$filter->sql().')' : ''; $where = $filter->sql()?' WHERE ('.$filter->sql().')' : '';
$sort = $sort == 'Monitor' ? 'M.Name' : 'E.'.$sort;
$col_str = 'E.*, M.Name AS Monitor'; $col_str = 'E.*, M.Name AS Monitor';
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.' ORDER BY '.$sort.' '.$order; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:'');
$storage_areas = ZM\Storage::find(); $storage_areas = ZM\Storage::find();
$StorageById = array(); $StorageById = array();

View File

@ -9,7 +9,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<?php <?php
//require_once('includes/Filter.php'); require_once('includes/Filter.php');
$fid = validInt($_REQUEST['fid']); $fid = validInt($_REQUEST['fid']);
if (!$fid) { if (!$fid) {
echo '<div class="error">No filter id specified.</div>'; echo '<div class="error">No filter id specified.</div>';
@ -25,7 +25,16 @@
// We have to manually insert the csrf key into the form when using a modal generated via ajax call // We have to manually insert the csrf key into the form when using a modal generated via ajax call
echo getCSRFinputHTML(); echo getCSRFinputHTML();
?> ?>
<p><label>SQL</label><?php echo $filter->sql() ?></p> <p><label>SQL</label>
<?php
$sql = 'SELECT E.*,M.Name AS MonitorName,M.DefaultScale<br/>FROM Monitors AS M INNER JOIN Events AS E ON (M.Id = E.MonitorId)<br/>WHERE<br/>';
$sql .= $filter->sql();
$sql .= $filter->sort_field() ? ' ORDER BY '.$filter->sort_field(). ' ' .($filter->sort_asc() ? 'ASC' : 'DESC') : '';
$sql .= $filter->limit() ? ' LIMIT '.$filter->limit() : '';
$sql .= $filter->skip_locked() ? ' SKIP LOCKED' : '';
echo $sql;
?></p>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php echo translate('Cancel')?> </button> <button type="button" class="btn btn-secondary" data-dismiss="modal"><?php echo translate('Cancel')?> </button>
</div> </div>

View File

@ -321,12 +321,20 @@ class MonitorsController extends AppController {
} }
$monitor = $this->Monitor->find('first', array( $monitor = $this->Monitor->find('first', array(
'fields' => array('Id', 'Type', 'Device'), 'fields' => array('Id', 'Type', 'Device', 'Function'),
'conditions' => array('Id' => $id) 'conditions' => array('Id' => $id)
)); ));
// Clean up the returned array // Clean up the returned array
$monitor = Set::extract('/Monitor/.', $monitor); $monitor = Set::extract('/Monitor/.', $monitor);
if ($monitor[0]['Function'] == 'None') {
$this->set(array(
'status' => false,
'statustext' => 'Monitor function is set to None',
'_serialize' => array('status','statustext'),
));
return;
}
// Pass -d for local, otherwise -m // Pass -d for local, otherwise -m
if ( $monitor[0]['Type'] == 'Local' ) { if ( $monitor[0]['Type'] == 'Local' ) {

View File

@ -9,6 +9,8 @@ class Filter extends ZM_Object {
protected $defaults = array( protected $defaults = array(
'Id' => null, 'Id' => null,
'Name' => '', 'Name' => '',
'UserId' => 0,
'ExecuteInterval' => 60,
'AutoExecute' => 0, 'AutoExecute' => 0,
'AutoExecuteCmd' => '', 'AutoExecuteCmd' => '',
'AutoEmail' => 0, 'AutoEmail' => 0,
@ -26,7 +28,6 @@ class Filter extends ZM_Object {
'AutoCopy' => 0, 'AutoCopy' => 0,
'AutoCopyTo' => 0, 'AutoCopyTo' => 0,
'UpdateDiskSpace' => 0, 'UpdateDiskSpace' => 0,
'UserId' => 0,
'Background' => 0, 'Background' => 0,
'Concurrent' => 0, 'Concurrent' => 0,
'Query_json' => '', 'Query_json' => '',
@ -62,6 +63,7 @@ class Filter extends ZM_Object {
} # end foreach term } # end foreach term
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc(); $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc();
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field(); $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field();
$this->_querystring .= $separator.urlencode($objectname.'[Query][skip_locked]').'='.$this->skip_locked();
$this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit(); $this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit();
if ( $this->Id() ) { if ( $this->Id() ) {
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
@ -219,6 +221,17 @@ class Filter extends ZM_Object {
#return $this->defaults{'sort_asc'}; #return $this->defaults{'sort_asc'};
} }
public function skip_locked() {
if (func_num_args()) {
$Query = $this->Query();
$Query['skip_locked'] = func_get_arg(0);
$this->Query($Query);
}
if (isset($this->Query()['skip_locked']))
return $this->{'Query'}['skip_locked'];
return false;
}
public function limit( ) { public function limit( ) {
if ( func_num_args( ) ) { if ( func_num_args( ) ) {
$Query = $this->Query(); $Query = $this->Query();

View File

@ -1,6 +1,8 @@
#header { #header {
display: flex; }
justify-content: space-between;
#sidebar {
min-width: 140px;
} }
#menuControls { #menuControls {
@ -19,19 +21,22 @@
#monitorStatus { #monitorStatus {
margin: 4px auto; margin: 4px auto;
text-align: center; text-align: center;
} display: inline-block;
#monitorStatus #enableDisableAlarms {
float: left;
}
#monitorStatus #forceCancelAlarm {
float: right;
} }
#monitorStatus #monitorState { #monitorStatus #monitorState {
} }
#replayStatus {
margin: 3px 0 2px;
text-align: center;
display: inline-block;
}
#replayStatus > span {
padding: 0 4px;
}
#dvrControls { #dvrControls {
margin-top: 3px; margin-top: 3px;
margin-bottom: 2px; margin-bottom: 2px;
@ -67,15 +72,6 @@
cursor: default; cursor: default;
} }
#replayStatus {
margin: 3px 0 2px;
text-align: center;
clear: both;
}
#replayStatus > span {
padding: 0 4px;
}
#events { #events {
margin: 0 auto; margin: 0 auto;
@ -116,3 +112,22 @@ span.alert {
background-color: #DCDCDC; background-color: #DCDCDC;
} }
.controlHeader {
width: 100%;
}
#viewingFPS,
#captureFPS,
#analysisFPS
{
display: inline-block;
}
#stateValue,
#viewingFPSValue,
#captureFPSValue,
#analysisFPSValue
{
display: inline-block;
min-width: 40px;
text-align: right;
}

View File

@ -367,6 +367,7 @@ for ( $i=0; $i < count($terms); $i++ ) {
<?php <?php
# Note: The keys need to be actual column names # Note: The keys need to be actual column names
$sort_fields = array( $sort_fields = array(
'' => translate('None'),
'Id' => translate('AttrId'), 'Id' => translate('AttrId'),
'Name' => translate('AttrName'), 'Name' => translate('AttrName'),
'Cause' => translate('AttrCause'), 'Cause' => translate('AttrCause'),
@ -387,6 +388,14 @@ $sort_dirns = array(
'0' => translate('SortDesc') '0' => translate('SortDesc')
); );
echo htmlSelect('filter[Query][sort_asc]', $sort_dirns, $filter->sort_asc()); echo htmlSelect('filter[Query][sort_asc]', $sort_dirns, $filter->sort_asc());
?>
</td>
<td>
<label for="filter[Query][skip_locked]"><?php echo translate('Skip Locked') ?></label>
<?php
echo htmlSelect('filter[Query][skip_locked]',
array('0'=>translate('No'), '1'=>translate('Yes')),
$filter->skip_locked());
?> ?>
</td> </td>
<td> <td>
@ -468,9 +477,13 @@ if ( ZM_OPT_MESSAGE ) {
<div id="optionsTable" class="filterTable"> <div id="optionsTable" class="filterTable">
<fieldset><legend><?php echo translate('Options') ?></legend> <fieldset><legend><?php echo translate('Options') ?></legend>
<p> <p>
<label for="background"><?php echo translate('BackgroundFilter') ?></label> <label for="filter[Background]"><?php echo translate('BackgroundFilter') ?></label>
<input type="checkbox" id="filter[Background]" name="filter[Background]" value="1"<?php if ( $filter->Background() ) { ?> checked="checked"<?php } ?> data-on-click-this="updateButtons"/> <input type="checkbox" id="filter[Background]" name="filter[Background]" value="1"<?php if ( $filter->Background() ) { ?> checked="checked"<?php } ?> data-on-click-this="updateButtons"/>
</p> </p>
<p>
<label for="ExecuteInterval"><?php echo translate('Execute Interval') ?></label>
<input type="number" id="filter[ExecuteInterval]" name="filter[ExecuteInterval]" min="0" step="1" value="<?php echo $filter->ExecuteInterval() ?>" />
</p>
<p> <p>
<label for="Concurrent"><?php echo translate('ConcurrentFilter') ?></label> <label for="Concurrent"><?php echo translate('ConcurrentFilter') ?></label>
<input type="checkbox" id="filter[Concurrent]" name="filter[Concurrent]" value="1"<?php if ( $filter->Concurrent() ) { ?> checked="checked"<?php } ?> data-on-click-this="updateButtons"/> <input type="checkbox" id="filter[Concurrent]" name="filter[Concurrent]" value="1"<?php if ( $filter->Concurrent() ) { ?> checked="checked"<?php } ?> data-on-click-this="updateButtons"/>

View File

@ -94,8 +94,48 @@ function showPtzControls() {
showMode = 'control'; showMode = 'control';
} }
function changeSize() {
var width = $j('#width').val();
var height = $j('#height').val();
// Scale the frame
monitor_frame = $j('#imageFeed');
if (!monitor_frame) {
console.log('Error finding frame');
return;
}
if (width) monitor_frame.css('width', width);
if (height) monitor_frame.css('height', height);
var streamImg = document.getElementById('liveStream'+monitorData[monIdx].id);
if (streamImg) {
if (streamImg.nodeName == 'IMG') {
let src = streamImg.src;
streamImg.src = '';
src = src.replace(/width=[\.\d]+/i, 'width='+parseInt(width));
src = src.replace(/height=[\.\d]+/i, 'height='+parseInt(height));
src = src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
streamImg.src = src;
}
streamImg.style.width = width ? width : null;
streamImg.style.height = height ? height : null;
} else {
console.log('Did not find liveStream'+monitorData[monIdx].id);
}
$j('#scale').val('');
setCookie('zmCycleScale', '', 3600);
setCookie('zmCycleWidth', width, 3600);
setCookie('zmCycleHeight', height, 3600);
} // end function changeSize()
function changeScale() { function changeScale() {
var scale = $j('#scale').val(); var scale = $j('#scale').val();
$j('#width').val('auto');
$j('#height').val('auto');
setCookie('zmCycleScale', scale, 3600);
setCookie('zmCycleWidth', 'auto', 3600);
setCookie('zmCycleHeight', 'auto', 3600);
var newWidth; var newWidth;
var newHeight; var newHeight;
var autoScale; var autoScale;
@ -104,7 +144,7 @@ function changeScale() {
// times and what the consequences would be // times and what the consequences would be
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active $j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
if (scale == '0' || scale == 'auto') { if (scale == '0' || scale == 'auto') {
var newSize = scaleToFit(monitorWidth, monitorHeight, $j('#liveStream'+monitorId), $j('#replayStatus')); const newSize = scaleToFit(monitorWidth, monitorHeight, $j('#liveStream'+monitorId), $j('#replayStatus'));
newWidth = newSize.width; newWidth = newSize.width;
newHeight = newSize.height; newHeight = newSize.height;
autoScale = newSize.autoScale; autoScale = newSize.autoScale;
@ -118,16 +158,18 @@ function changeScale() {
var streamImg = $j('#liveStream'+monitorId); var streamImg = $j('#liveStream'+monitorId);
if (streamImg) { if (streamImg) {
var oldSrc = streamImg.attr('src'); if (streamImg.nodeName == 'IMG') {
const oldSrc = streamImg.attr('src');
streamImg.attr('src', ''); streamImg.attr('src', '');
// This is so that we don't waste bandwidth and let the browser do all the scaling. // This is so that we don't waste bandwidth and let the browser do all the scaling.
if (autoScale > 100) autoScale = 100; if (autoScale > 100) autoScale = 100;
if (scale > 100) scale = 100; if (scale > 100) scale = 100;
var newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+((scale == 'auto' || scale == '0') ? autoScale : scale)); const newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+((scale == 'auto' || scale == '0') ? autoScale : scale));
streamImg.width(newWidth); streamImg.width(newWidth);
streamImg.height(newHeight); streamImg.height(newHeight);
streamImg.attr('src', newSrc); streamImg.attr('src', newSrc);
}
} else { } else {
console.error('No element found for liveStream'+monitorId); console.error('No element found for liveStream'+monitorId);
} }
@ -200,9 +242,15 @@ function getStreamCmdResponse(respObj, respText) {
// The get status command can get backed up, in which case we won't be able to get the semaphore and will exit. // The get status command can get backed up, in which case we won't be able to get the semaphore and will exit.
if (respObj.status) { if (respObj.status) {
streamStatus = respObj.status; streamStatus = respObj.status;
$j('#fpsValue').text(streamStatus.fps); if ($j('#viewingFPSValue').text() != streamStatus.fps) {
$j('#capturefpsValue').text(streamStatus.capturefps); $j('#viewingFPSValue').text(streamStatus.fps);
$j('#analysisfpsValue').text(streamStatus.analysisfps); }
if ($j('#captureFPSValue').text() != streamStatus.capturefps) {
$j('#captureFPSValue').text(streamStatus.capturefps);
}
if ($j('#analysisFPSValue').text() != streamStatus.analysisfps) {
$j('#analysisFPSValue').text(streamStatus.analysisfps);
}
setAlarmState(streamStatus.state); setAlarmState(streamStatus.state);
@ -287,31 +335,19 @@ function getStreamCmdResponse(respObj, respText) {
// Try to reload the image stream. // Try to reload the image stream.
var streamImg = $j('#liveStream'+monitorId); var streamImg = $j('#liveStream'+monitorId);
if (streamImg) { if (streamImg) {
var oldSrc = streamImg.attr('src'); const oldSrc = streamImg.attr('src');
var newSrc = oldSrc.replace(/auth=\w+/i, 'auth='+streamStatus.auth); if (oldSrc) {
const newSrc = oldSrc.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
if (oldSrc != newSrc) { if (oldSrc != newSrc) {
streamImg.attr('src', newSrc); streamImg.attr('src', newSrc);
table.bootstrapTable('refresh'); table.bootstrapTable('refresh');
} }
} }
}
} // end if have a new auth hash } // end if have a new auth hash
} // end if respObj.status } // end if respObj.status
} else { } else {
checkStreamForErrors('getStreamCmdResponse', respObj);//log them checkStreamForErrors('getStreamCmdResponse', respObj);//log them
// Try to reload the image stream.
// If it's an auth error, we should reload the whole page.
console.log("have error");
//window.location.reload();
var streamImg = $j('#liveStream'+monitorId);
if (streamImg) {
var oldSrc = streamImg.attr('src');
var newSrc = oldSrc.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
streamImg.attr('src', newSrc);
console.log('Changing livestream src to ' + newSrc);
} else {
console.log('Unable to find streamImg liveStream');
}
} }
var streamCmdTimeout = statusRefreshTimeout; var streamCmdTimeout = statusRefreshTimeout;
@ -368,6 +404,7 @@ function streamCmdPlay(action) {
} }
function streamCmdReq(data) { function streamCmdReq(data) {
if (auth_hash) data.auth = auth_hash;
$j.getJSON(monitorUrl + '?view=request&request=stream&connkey='+connKey, data) $j.getJSON(monitorUrl + '?view=request&request=stream&connkey='+connKey, data)
.done(getStreamCmdResponse) .done(getStreamCmdResponse)
.fail(getStreamCmdError); .fail(getStreamCmdError);
@ -386,10 +423,7 @@ function streamCmdStop(action) {
setButtonState('fastRevBtn', 'unavail'); setButtonState('fastRevBtn', 'unavail');
} }
if (action) { if (action) {
var data = {}; streamCmdReq({command: CMD_STOP});
if (auth_hash) data.auth = auth_hash;
data.command = CMD_STOP;
streamCmdReq(data);
} }
setButtonState('stopBtn', 'unavail'); setButtonState('stopBtn', 'unavail');
setButtonState('playBtn', 'active'); setButtonState('playBtn', 'active');
@ -407,7 +441,6 @@ function streamCmdFastFwd(action) {
} }
if (action) { if (action) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_FASTFWD; data.command = CMD_FASTFWD;
streamCmdReq(data); streamCmdReq(data);
} }
@ -425,7 +458,6 @@ function streamCmdSlowFwd(action) {
} }
if (action) { if (action) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_SLOWFWD; data.command = CMD_SLOWFWD;
streamCmdReq(data); streamCmdReq(data);
} }
@ -447,7 +479,6 @@ function streamCmdSlowRev(action) {
} }
if (action) { if (action) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_SLOWREV; data.command = CMD_SLOWREV;
streamCmdReq(data); streamCmdReq(data);
} }
@ -469,7 +500,6 @@ function streamCmdFastRev(action) {
} }
if (action) { if (action) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_FASTREV; data.command = CMD_FASTREV;
streamCmdReq(data); streamCmdReq(data);
} }
@ -477,7 +507,6 @@ function streamCmdFastRev(action) {
function streamCmdZoomIn(x, y) { function streamCmdZoomIn(x, y) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.x = x; data.x = x;
data.y = y; data.y = y;
data.command = CMD_ZOOMIN; data.command = CMD_ZOOMIN;
@ -486,14 +515,12 @@ function streamCmdZoomIn(x, y) {
function streamCmdZoomOut() { function streamCmdZoomOut() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_ZOOMOUT; data.command = CMD_ZOOMOUT;
streamCmdReq(data); streamCmdReq(data);
} }
function streamCmdScale(scale) { function streamCmdScale(scale) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_SCALE; data.command = CMD_SCALE;
data.scale = scale; data.scale = scale;
streamCmdReq(data); streamCmdReq(data);
@ -501,7 +528,6 @@ function streamCmdScale(scale) {
function streamCmdPan(x, y) { function streamCmdPan(x, y) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.x = x; data.x = x;
data.y = y; data.y = y;
data.command = CMD_PAN; data.command = CMD_PAN;
@ -510,11 +536,11 @@ function streamCmdPan(x, y) {
function streamCmdQuery() { function streamCmdQuery() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = CMD_QUERY; data.command = CMD_QUERY;
streamCmdReq(data); streamCmdReq(data);
} }
/* getStatusCmd is used when not streaming, since there is no persistent zms */
function getStatusCmdResponse(respObj, respText) { function getStatusCmdResponse(respObj, respText) {
watchdogOk('status'); watchdogOk('status');
if (statusCmdTimer) { if (statusCmdTimer) {
@ -522,7 +548,7 @@ function getStatusCmdResponse(respObj, respText) {
} }
if (respObj.result == 'Ok') { if (respObj.result == 'Ok') {
$j('#fpsValue').text(respObj.monitor.FrameRate); $j('#captureFPSValue').text(respObj.monitor.FrameRate);
setAlarmState(respObj.monitor.Status); setAlarmState(respObj.monitor.Status);
} else { } else {
checkStreamForErrors('getStatusCmdResponse', respObj); checkStreamForErrors('getStatusCmdResponse', respObj);
@ -536,18 +562,19 @@ function getStatusCmdResponse(respObj, respText) {
} }
function statusCmdQuery() { function statusCmdQuery() {
$j.getJSON(monitorUrl + '?view=request&request=status&entity=monitor&element[]=Status&element[]=FrameRate&id='+monitorId) $j.getJSON(monitorUrl + '?view=request&request=status&entity=monitor&element[]=Status&element[]=FrameRate&id='+monitorId+'&'+auth_relay)
.done(getStatusCmdResponse) .done(getStatusCmdResponse)
.fail(logAjaxFail); .fail(logAjaxFail);
streamCmdTimer = null; statusCmdTimer = null;
} }
function alarmCmdReq(data) { function alarmCmdReq(data) {
if (auth_hash) data.auth = auth_hash;
$j.getJSON(monitorUrl + '?view=request&request=alarm&id='+monitorId, data) $j.getJSON(monitorUrl + '?view=request&request=alarm&id='+monitorId, data)
.done(getAlarmCmdResponse) .done(getAlarmCmdResponse)
.fail(function(jqxhr, textStatus, error) { .fail(function(jqxhr, textStatus, error) {
if ( textStatus === "timeout" ) { if (textStatus === 'timeout') {
streamCmdQuery(); streamCmdQuery();
} else { } else {
logAjaxFail(jqxhr, textStatus, error); logAjaxFail(jqxhr, textStatus, error);
@ -561,14 +588,12 @@ function getAlarmCmdResponse(respObj, respText) {
function cmdDisableAlarms() { function cmdDisableAlarms() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = 'disableAlarms'; data.command = 'disableAlarms';
alarmCmdReq(data); alarmCmdReq(data);
} }
function cmdEnableAlarms() { function cmdEnableAlarms() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = 'enableAlarms'; data.command = 'enableAlarms';
alarmCmdReq(data); alarmCmdReq(data);
} }
@ -583,7 +608,6 @@ function cmdAlarm() {
function cmdForceAlarm() { function cmdForceAlarm() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = 'forceAlarm'; data.command = 'forceAlarm';
alarmCmdReq(data); alarmCmdReq(data);
if (window.event) window.event.preventDefault(); if (window.event) window.event.preventDefault();
@ -591,7 +615,6 @@ function cmdForceAlarm() {
function cmdCancelForcedAlarm() { function cmdCancelForcedAlarm() {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.command = 'cancelForcedAlarm'; data.command = 'cancelForcedAlarm';
alarmCmdReq(data); alarmCmdReq(data);
if (window.event) window.event.preventDefault(); if (window.event) window.event.preventDefault();
@ -607,6 +630,7 @@ function cmdForce() {
} }
function controlReq(data) { function controlReq(data) {
if (auth_hash) data.auth = auth_hash;
$j.getJSON(monitorUrl + '?view=request&request=control&id='+monitorId, data) $j.getJSON(monitorUrl + '?view=request&request=control&id='+monitorId, data)
.done(getControlResponse) .done(getControlResponse)
.fail(logAjaxFail); .fail(logAjaxFail);
@ -639,16 +663,16 @@ function controlCmd(event) {
var data = {}; var data = {};
if (event && (xtell || ytell)) { if (event && (xtell || ytell)) {
var target = event.target; const target = event.target;
var offset = $j(target).offset(); const offset = $j(target).offset();
var width = $j(target).width(); const width = $j(target).width();
var height = $j(target).height(); const height = $j(target).height();
var x = event.pageX - offset.left; const x = event.pageX - offset.left;
var y = event.pageY - offset.top; const y = event.pageY - offset.top;
if (xtell) { if (xtell) {
var xge = parseInt((x*100)/width); let xge = parseInt((x*100)/width);
if (xtell == -1) { if (xtell == -1) {
xge = 100 - xge; xge = 100 - xge;
} else if (xtell == 2) { } else if (xtell == 2) {
@ -657,7 +681,7 @@ function controlCmd(event) {
data.xge = xge; data.xge = xge;
} }
if (ytell) { if (ytell) {
var yge = parseInt((y*100)/height); let yge = parseInt((y*100)/height);
if (ytell == -1) { if (ytell == -1) {
yge = 100 - yge; yge = 100 - yge;
} else if (ytell == 2) { } else if (ytell == 2) {
@ -667,7 +691,6 @@ function controlCmd(event) {
} }
} }
if (auth_hash) data.auth = auth_hash;
data.control = control; data.control = control;
controlReq(data); controlReq(data);
@ -678,7 +701,6 @@ function controlCmd(event) {
function controlCmdImage(x, y) { function controlCmdImage(x, y) {
var data = {}; var data = {};
if (auth_hash) data.auth = auth_hash;
data.scale = scale; data.scale = scale;
data.control = imageControlMode; data.control = imageControlMode;
data.x = x; data.x = x;
@ -768,7 +790,6 @@ function reloadWebSite() {
function updatePresetLabels() { function updatePresetLabels() {
var lblNdx = $j('#ctrlPresetForm option:selected').val(); var lblNdx = $j('#ctrlPresetForm option:selected').val();
$j('#newLabel').val(labels[lblNdx]); $j('#newLabel').val(labels[lblNdx]);
} }
@ -878,7 +899,7 @@ function initPage() {
// Load the PTZ Preset modal into the DOM // Load the PTZ Preset modal into the DOM
if (monitorControllable) getCtrlPresetModal(); if (monitorControllable) getCtrlPresetModal();
// Load the settings modal into the DOM // Load the settings modal into the DOM
if (monitorType == "Local") getSettingsModal(); if (monitorType == 'Local') getSettingsModal();
} }
if (monitorType != 'WebSite') { if (monitorType != 'WebSite') {
@ -886,15 +907,12 @@ function initPage() {
statusCmdTimer = setTimeout(statusCmdQuery, (Math.random()+0.1)*statusRefreshTimeout); statusCmdTimer = setTimeout(statusCmdQuery, (Math.random()+0.1)*statusRefreshTimeout);
setInterval(watchdogCheck, statusRefreshTimeout*2, 'status'); setInterval(watchdogCheck, statusRefreshTimeout*2, 'status');
} else { } else {
streamCmdTimer = setTimeout(streamCmdQuery, (Math.random()+0.1)*statusRefreshTimeout);
setInterval(watchdogCheck, statusRefreshTimeout*2, 'stream'); setInterval(watchdogCheck, statusRefreshTimeout*2, 'stream');
} }
if (canStreamNative || (streamMode == 'single')) { if (canStreamNative || (streamMode == 'single')) {
var streamImg = $j('#imageFeed img'); var streamImg = $j('#imageFeed img');
if (!streamImg) { if (!streamImg) streamImg = $j('#imageFeed object');
streamImg = $j('#imageFeed object');
}
if (!streamImg) { if (!streamImg) {
console.error('No streamImg found for imageFeed'); console.error('No streamImg found for imageFeed');
} else { } else {
@ -905,6 +923,10 @@ function initPage() {
streamImg.click(function(event) { streamImg.click(function(event) {
handleClick(event); handleClick(event);
}); });
streamImg.on("error", function(thing) {
console.log("Error loading image");
console.log(thing);
});
} }
} // end if have streamImg } // end if have streamImg
} // streamMode native or single } // streamMode native or single
@ -944,6 +966,18 @@ function initPage() {
$j('#settingsModal').modal('show'); $j('#settingsModal').modal('show');
}); });
bindButton('#cyclePlayBtn', 'click', null, cycleStart);
bindButton('#cyclePauseBtn', 'click', null, cyclePause);
bindButton('#cycleNextBtn', 'click', null, cycleNext);
bindButton('#cyclePrevBtn', 'click', null, cyclePrev);
bindButton('#cycleToggle', 'click', null, cycleToggle);
bindButton('#cyclePeriod', 'change', null, cyclePeriodChange);
if (cycle) {
cycleStart();
} else {
cyclePause();
}
// Only enable the settings button for local cameras // Only enable the settings button for local cameras
settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local'))); settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local')));
@ -972,7 +1006,6 @@ function initPage() {
function watchFullscreen() { function watchFullscreen() {
const btn = document.getElementById('fullscreenBtn'); const btn = document.getElementById('fullscreenBtn');
console.log(btn);
if (btn.firstElementChild.innerHTML=='fullscreen') { if (btn.firstElementChild.innerHTML=='fullscreen') {
const content = document.getElementById('content'); const content = document.getElementById('content');
openFullscreen(content); openFullscreen(content);
@ -985,5 +1018,70 @@ function watchFullscreen() {
} }
} }
var intervalId;
var secondsToCycle = 0;
function nextCycleView() {
secondsToCycle --;
if (secondsToCycle<=0) {
window.location.replace('?view=watch&mid='+nextMid+'&mode='+mode+'&cycle=true');
}
$j('#secondsToCycle').text(secondsToCycle);
}
function cyclePause() {
clearInterval(intervalId);
$j('#cyclePauseBtn').hide();
$j('#cyclePlayBtn').show();
}
function cycleStart() {
secondsToCycle = $j('#cyclePeriod').val();
intervalId = setInterval(nextCycleView, 1000);
$j('#cyclePauseBtn').show();
$j('#cyclePlayBtn').hide();
}
function cycleNext() {
monIdx ++;
if (monIdx >= monitorData.length) {
monIdx = 0;
}
if (!monitorData[monIdx]) {
console.log('No monitorData for ' + monIdx);
}
window.location.replace('?view=watch&cycle=true&mid='+monitorData[monIdx].id+'&mode='+mode);
}
function cyclePrev() {
monIdx --;
if (monIdx < 0) {
monIdx = monitorData.length - 1;
}
if (!monitorData[monIdx]) {
console.log('No monitorData for ' + monIdx);
}
window.location.replace('?view=watch&cycle=true&mid='+monitorData[monIdx].id+'&mode='+mode);
}
function cyclePeriodChange() {
const cyclePeriodSelect = $j('#cyclePeriod');
secondsToCycle = cyclePeriodSelect.val();
setCookie('zmCyclePeriod', secondsToCycle, 3600);
}
function cycleToggle(e) {
sidebar = $j('#sidebar');
button = $j('#cycleToggle');
if (sidebar.is(":visible")) {
sidebar.hide();
setCookie('zmCycleShow', false, 3600);
} else {
sidebar.show();
setCookie('zmCycleShow', true, 3600);
}
button.toggleClass('btn-secondary');
button.toggleClass('btn-primary');
}
// Kick everything off // Kick everything off
$j(document).ready(initPage); $j(document).ready(initPage);

View File

@ -1,21 +1,20 @@
<?php <?php
global $monIdx;
global $nextMid;
global $options;
global $monitors;
global $streamMode; global $streamMode;
global $showPtzControls; global $showPtzControls;
global $connkey; global $connkey;
global $monitor; global $monitor;
global $scale; global $scale;
global $labels; global $labels;
global $cycle;
?> ?>
// //
// Import constants // Import constants
// //
var deleteString = "<?php echo translate('Delete') ?>";
var enableAlarmsStr = "<?php echo translate('EnableAlarms') ?>";
var disableAlarmsStr = "<?php echo translate('DisableAlarms') ?>";
var forceAlarmStr = "<?php echo translate('ForceAlarm') ?>";
var cancelForcedAlarmStr = "<?php echo translate('CancelForcedAlarm') ?>";
var CMD_NONE = <?php echo CMD_NONE ?>; var CMD_NONE = <?php echo CMD_NONE ?>;
var CMD_PAUSE = <?php echo CMD_PAUSE ?>; var CMD_PAUSE = <?php echo CMD_PAUSE ?>;
@ -34,14 +33,13 @@ var CMD_NEXT = <?php echo CMD_NEXT ?>;
var CMD_SEEK = <?php echo CMD_SEEK ?>; var CMD_SEEK = <?php echo CMD_SEEK ?>;
var CMD_QUERY = <?php echo CMD_QUERY ?>; var CMD_QUERY = <?php echo CMD_QUERY ?>;
var SCALE_BASE = <?php echo SCALE_BASE ?>;
var SOUND_ON_ALARM = <?php echo ZM_WEB_SOUND_ON_ALARM ?>; var SOUND_ON_ALARM = <?php echo ZM_WEB_SOUND_ON_ALARM ?>;
var POPUP_ON_ALARM = <?php echo ZM_WEB_POPUP_ON_ALARM ?>; var POPUP_ON_ALARM = <?php echo ZM_WEB_POPUP_ON_ALARM ?>;
var LIST_THUMBS = <?php echo ZM_WEB_LIST_THUMBS?'true':'false' ?>; var LIST_THUMBS = <?php echo ZM_WEB_LIST_THUMBS?'true':'false' ?>;
var streamMode = "<?php echo $streamMode ?>"; var streamMode = "<?php echo $streamMode ?>";
var showMode = "<?php echo ($showPtzControls && !empty($control))?"control":"events" ?>"; var showMode = "<?php echo ($showPtzControls && !empty($control))?"control":"events" ?>";
var cycle = <?php echo $cycle ? 'true' : 'false' ?>;
var connKey = '<?php echo $connkey ?>'; var connKey = '<?php echo $connkey ?>';
var maxDisplayEvents = <?php echo 2 * MAX_EVENTS ?>; var maxDisplayEvents = <?php echo 2 * MAX_EVENTS ?>;
@ -55,6 +53,28 @@ var monitorRefresh = '<?php echo $monitor->Refresh() ?>';
var monitorStreamReplayBuffer = <?php echo $monitor->StreamReplayBuffer() ?>; var monitorStreamReplayBuffer = <?php echo $monitor->StreamReplayBuffer() ?>;
var monitorControllable = <?php echo $monitor->Controllable()?'true':'false' ?>; var monitorControllable = <?php echo $monitor->Controllable()?'true':'false' ?>;
var monIdx = '<?php echo $monIdx; ?>';
var nextMid = "<?php echo isset($nextMid)?$nextMid:'' ?>";
var mode = "<?php echo $options['mode'] ?>";
var monitorData = new Array();
<?php
foreach ($monitors as $monitor) {
?>
monitorData[monitorData.length] = {
'id': <?php echo $monitor->Id() ?>,
'width': <?php echo $monitor->ViewWidth() ?>,
'height':<?php echo $monitor->ViewHeight() ?>,
'url': '<?php echo $monitor->UrlToIndex() ?>',
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $monitor->Id() ?>' );},
'type': '<?php echo $monitor->Type() ?>',
'refresh': '<?php echo $monitor->Refresh() ?>'
};
<?php
} // end foreach monitor
?>
var SCALE_BASE = <?php echo SCALE_BASE ?>;
var scale = '<?php echo $scale ?>'; var scale = '<?php echo $scale ?>';
var statusRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>; var statusRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>;
@ -63,17 +83,16 @@ var imageRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_IMAGE ?>;
var canStreamNative = <?php echo canStreamNative()?'true':'false' ?>; var canStreamNative = <?php echo canStreamNative()?'true':'false' ?>;
<?php var imageControlMode = '<?php
$control = $monitor->Control(); $control = $monitor->Control();
if ( $control->CanMoveMap() ) { ?> if ($control->CanMoveMap()) {
var imageControlMode = "moveMap"; echo 'moveMap';
<?php } elseif ( $control->CanMoveRel() ) { ?> } else if ($control->CanMoveRel()) {
var imageControlMode = "movePseudoMap"; echo 'movePseudoMap';
<?php } elseif ( $control->CanMoveCon() ) { ?> } else if ($control->CanMoveCon()) {
var imageControlMode = "moveConMap"; echo 'moveConMap';
<?php } else { ?> }
var imageControlMode = null; ?>';
<?php } ?>
var refreshApplet = <?php echo (canStreamApplet() && $streamMode == "jpeg")?'true':'false' ?>; var refreshApplet = <?php echo (canStreamApplet() && $streamMode == "jpeg")?'true':'false' ?>;
var appletRefreshTime = <?php echo ZM_RELOAD_CAMBOZOLA ?>; var appletRefreshTime = <?php echo ZM_RELOAD_CAMBOZOLA ?>;
@ -82,16 +101,17 @@ var labels = new Array();
<?php <?php
$labels = array(); $labels = array();
foreach (dbFetchAll('SELECT * FROM ControlPresets WHERE MonitorId = ?', NULL, array($monitor->Id())) as $row) { foreach (dbFetchAll('SELECT * FROM ControlPresets WHERE MonitorId = ?', NULL, array($monitor->Id())) as $row) {
$labels[$row['Preset']] = $row['Label']; $label = $labels[$row['Preset']] = $row['Label'];
} echo 'labels['. validInt($index) .'] = '.validJsStr($label).'\'';
foreach ($labels as $index=>$label) {
?>
labels[<?php echo validInt($index) ?>] = '<?php echo validJsStr($label) ?>';
<?php
} }
?> ?>
var deleteString = "<?php echo translate('Delete') ?>";
var enableAlarmsStr = "<?php echo translate('EnableAlarms') ?>";
var disableAlarmsStr = "<?php echo translate('DisableAlarms') ?>";
var forceAlarmStr = "<?php echo translate('ForceAlarm') ?>";
var cancelForcedAlarmStr = "<?php echo translate('CancelForcedAlarm') ?>";
var translate = { var translate = {
"seconds": "<?php echo translate('seconds') ?>",
"Fullscreen": "<?php echo translate('Fullscreen') ?>", "Fullscreen": "<?php echo translate('Fullscreen') ?>",
"Exit Fullscreen": "<?php echo translate('Exit Fullscreen') ?>", "Exit Fullscreen": "<?php echo translate('Exit Fullscreen') ?>",
}; };

View File

@ -22,25 +22,90 @@ if ( !canView('Stream') ) {
$view = 'error'; $view = 'error';
return; return;
} }
require_once('includes/Monitor.php');
ob_start();
include('_monitor_filters.php');
$filterbar = ob_get_contents();
ob_end_clean();
if ( !isset($_REQUEST['mid']) ) {
$view = 'error';
return;
}
// This is for input sanitation // This is for input sanitation
$mid = intval($_REQUEST['mid']); $mid = isset($_REQUEST['mid']) ? intval($_REQUEST['mid']) : 0;
$widths = array(
'auto' => translate('auto'),
'100%' => '100%',
'160px' => '160px',
'320px' => '320px',
'352px' => '352px',
'640px' => '640px',
'1280px' => '1280px',
'1920px' => '1920px'
);
$heights = array(
'auto' => translate('auto'),
'240px' => '240px',
'480px' => '480px',
'720px' => '720px',
'1080px' => '1080px',
);
$monitors = array();
$monitor_index = 0;
foreach ($displayMonitors as &$row) {
if ($row['Function'] == 'None') continue;
if ($mid and ($row['Id'] == $mid)) $monitor_index = count($monitors);
$monitors[] = new ZM\Monitor($row);
if (!isset($widths[$row['Width'].'px'])) {
$widths[$row['Width'].'px'] = $row['Width'].'px';
}
if (!isset($heights[$row['Height'].'px'])) {
$heights[$row['Height'].'px'] = $row['Height'].'px';
}
unset($row);
} # end foreach Monitor
if (!$mid) {
$mid = $monitors[0]->Id();
$monitor_index = 0;
}
if (!visibleMonitor($mid)) { if (!visibleMonitor($mid)) {
$view = 'error'; $view = 'error';
return; return;
} }
require_once('includes/Monitor.php');
$monitor = new ZM\Monitor($mid); $monitor = new ZM\Monitor($mid);
$nextMid = ($monitor_index == count($monitors)-1) ? $monitors[0]->Id() : $monitors[$monitor_index+1]->Id();
$cycle = isset($_REQUEST['cycle']) and ($_REQUEST['cycle'] == 'true');
$showCycle = $cycle;
ZM\Error("Show cycle: $showCycle");
if (isset($_COOKIE['zmCycleShow'])) {
$showCycle = $_COOKIE['zmCycleShow'] == 'true';
ZM\Error("Show cycle: $showCycle");
} else {
ZM\Error("Show cycle: not set");
}
#Whether to show the controls button #Whether to show the controls button
$showPtzControls = ( ZM_OPT_CONTROL && $monitor->Controllable() && canView('Control') && $monitor->Type() != 'WebSite' ); $showPtzControls = ( ZM_OPT_CONTROL && $monitor->Controllable() && canView('Control') && $monitor->Type() != 'WebSite' );
$options = array();
if (empty($_REQUEST['mode'])) {
$options['mode'] = canStream() ? 'stream' : 'still';
} else {
$options['mode'] = validHtmlStr($_REQUEST['mode']);
}
zm_session_start();
$period = ZM_WEB_REFRESH_CYCLE;
if (isset($_REQUEST['period'])) {
$period = validInt($_REQUEST['period']);
} else if (isset($_COOKIE['zmCyclePeriod'])) {
$period = validInt($_COOKIE['zmCyclePeriod']);
}
if (isset($_REQUEST['scale'])) { if (isset($_REQUEST['scale'])) {
$scale = validInt($_REQUEST['scale']); $scale = validInt($_REQUEST['scale']);
} else if ( isset($_COOKIE['zmWatchScale'.$mid]) ) { } else if ( isset($_COOKIE['zmWatchScale'.$mid]) ) {
@ -48,62 +113,156 @@ if ( isset($_REQUEST['scale']) ) {
} else { } else {
$scale = $monitor->DefaultScale(); $scale = $monitor->DefaultScale();
} }
$options['scale'] = $scale;
if (isset($_REQUEST['width'])) {
$options['width'] = validInt($_REQUEST['width']);
} else if ( isset($_COOKIE['zmCycleWidth']) and $_COOKIE['zmCycleWidth'] ) {
$_SESSION['zmCycleWidth'] = $options['width'] = $_COOKIE['zmCycleWidth'];
#} elseif ( isset($_SESSION['zmCycleWidth']) and $_SESSION['zmCycleWidth'] ) {
#$options['width'] = $_SESSION['zmCycleWidth'];
} else {
$options['width'] = '';
}
if (isset($_REQUEST['height'])) {
$options['height'] =validInt($_REQUEST['height']);
} else if (isset($_COOKIE['zmCycleHeight']) and $_COOKIE['zmCycleHeight']) {
$_SESSION['zmCycleHeight'] = $options['height'] = $_COOKIE['zmCycleHeight'];
#else if ( isset($_SESSION['zmCycleHeight']) and $_SESSION['zmCycleHeight'] )
#$options['height'] = $_SESSION['zmCycleHeight'];
} else {
$options['height'] = '';
}
session_write_close();
$connkey = generateConnKey(); $connkey = generateConnKey();
$streamMode = getStreamMode(); $streamMode = getStreamMode();
$popup = ((isset($_REQUEST['popup'])) && ($_REQUEST['popup'] == 1));
noCacheHeaders(); noCacheHeaders();
xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed')); xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed'));
?> ?>
<body> <body>
<?php echo getNavBarHTML() ?> <?php echo getNavBarHTML() ?>
<div id="header">
<div class="controlHeader">
<form method="get">
<input type="hidden" name="view" value="watch"/>
<?php echo $filterbar ?>
</form>
</div>
<div class="d-flex flex-row justify-content-between px-3 py-1"> <div class="d-flex flex-row justify-content-between px-3 py-1">
<div> <div id="navButtons">
<button type="button" id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button> <button type="button" id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button type="button" id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button> <button type="button" id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<button type="button" id="settingsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Settings') ?>" disabled><i class="fa fa-sliders"></i></button> <button type="button" id="settingsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Settings') ?>" disabled><i class="fa fa-sliders"></i></button>
<button type="button" id="enableAlmBtn" class="btn btn-normal" data-on-click="cmdAlarm" data-toggle="tooltip" data-placement="top" title="<?php echo translate('DisableAlarms') ?>" disabled><i class="fa fa-bell"></i></button> <button type="button" id="enableAlmBtn" class="btn btn-normal" data-on-click="cmdAlarm" data-toggle="tooltip" data-placement="top" title="<?php echo translate('DisableAlarms') ?>" disabled><i class="fa fa-bell"></i></button>
<button type="button" id="forceAlmBtn" class="btn btn-danger" data-on-click="cmdForce" data-toggle="tooltip" data-placement="top" title="<?php echo translate('ForceAlarm') ?>" disabled><i class="fa fa-exclamation-circle"></i></button> <button type="button" id="forceAlmBtn" class="btn btn-danger" data-on-click="cmdForce" data-toggle="tooltip" data-placement="top" title="<?php echo translate('ForceAlarm') ?>" disabled><i class="fa fa-exclamation-circle"></i></button>
</div> </div>
<div id="headerButtons">
<div> <!--
<h2><?php echo makeLink('?view=monitor&amp;mid='.$monitor->Id(), validHtmlStr($monitor->Name()), canEdit('Monitors')) ?></h2> <?php if ( $options['mode'] == 'stream' ) { ?>
</div> <a href="?view=<?php echo $view ?>&amp;mode=still&amp;mid=<?php echo $monitor ? $monitor->Id() : '' ?>"><?php echo translate('Stills') ?></a>
<?php } else { ?>
<div> <a href="?view=<?php echo $view ?>&amp;mode=stream&amp;mid=<?php echo $monitor ? $monitor->Id() : '' ?>"><?php echo translate('Stream') ?></a>
<?php echo translate('Scale').': '.htmlSelect('scale', $scales, $scale, array('id'=>'scale')); ?> <?php } ?>
</div> -->
<button type="button" id="cycleToggle" class="btn <?php echo $showCycle ? 'btn-primary':'btn-secondary'?>" title="<?php echo translate('Toggle cycle sidebar')?>">
<span class="material-icons md-18">view_carousel</span>
</button>
</div> </div>
<div id="sizeControl">
<span id="widthControl">
<label><?php echo translate('Width') ?>:</label>
<?php echo htmlSelect('width', $widths, $options['width'], array('id'=>'width', 'data-on-change-this'=>'changeSize') ); ?>
</span>
<span id="heightControl">
<label><?php echo translate('Height') ?>:</label>
<?php echo htmlSelect('height', $heights, $options['height'], array('id'=>'height', 'data-on-change-this'=>'changeSize') ); ?>
</span>
<span id="scaleControl">
<label><?php echo translate('Scale') ?>:</label>
<?php echo htmlSelect('scale', $scales, $options['scale'], array('id'=>'scale', 'data-on-change-this'=>'changeScale') ); ?>
</span>
</div><!--sizeControl-->
</div><!--control header-->
</div><!--header-->
<?php <?php
if ( $monitor->Status() != 'Connected' and $monitor->Type() != 'WebSite' ) { if ( $monitor->Status() != 'Connected' and $monitor->Type() != 'WebSite' ) {
echo '<div class="warning">Monitor is not capturing. We will be unable to provide an image</div>'; echo '<div class="warning">Monitor is not capturing. We will be unable to provide an image</div>';
} }
?> ?>
<div id="content"> <div class="container-fluid h-100">
<div class="row flex-nowrap h-100" id="content">
<nav id="sidebar" class="h-100"<?php echo $showCycle?'':' style="display:none;"'?>>
<div id="cycleButtons" class="buttons">
<?php
$seconds = translate('seconds');
$minute = translate('minute');
$minutes = translate('minutes');
$cyclePeriodOptions = array(
10 => '10 '.$seconds,
30 => '30 '.$seconds,
60 => '1 '.$minute,
120 => '2 '.$minutes,
300 => '5 '.$minutes,
);
if (!isset($cyclePeriodOptions[ZM_WEB_REFRESH_CYCLE])) {
$cyclePeriodOptions[ZM_WEB_REFRESH_CYCLE] = ZM_WEB_REFRESH_CYCLE.' '.$seconds;
}
echo htmlSelect('cyclePeriod', $cyclePeriodOptions, $period, array('id'=>'cyclePeriod'));
?>
<span id="secondsToCycle"></span><br/>
<button type="button" id="cyclePrevBtn" title="<?php echo translate('PreviousMonitor') ?>">
<i class="material-icons md-18">skip_previous</i>
</button>
<button type="button" id="cyclePauseBtn" title="<?php echo translate('PauseCycle') ?>">
<i class="material-icons md-18">pause</i>
</button>
<button type="button" id="cyclePlayBtn" title="<?php echo translate('PlayCycle') ?>">
<i class="material-icons md-18">play_arrow</i>
</button>
<button type="button" id="cycleNextBtn" title="<?php echo translate('NextMonitor') ?>">
<i class="material-icons md-18">skip_next</i>
</button>
</div>
<ul class="nav nav-pills flex-column h-100">
<?php
foreach ($monitors as $m) {
echo '<li class="nav-item"><a class="nav-link'.( $m->Id() == $monitor->Id() ? ' active' : '' ).'" href="?view=watch&amp;mid='.$m->Id().'">'.$m->Name().'</a></li>';
}
?>
</ul>
</nav>
<div class="container-fluid col-sm-offset-2 h-100 pr-0">
<div id="imageFeed" <div id="imageFeed"
<?php <?php
if ($streamMode == 'jpeg') { if ($streamMode == 'jpeg') {
echo 'title="Click to zoom, shift click to pan, ctrl click to zoom out"'; echo 'title="Click to zoom, shift click to pan, ctrl click to zoom out"';
} }
?> ?>
><?php echo getStreamHTML($monitor, array('scale'=>$scale)); ?></div> ><?php echo getStreamHTML($monitor, array('scale'=>$scale)); ?>
</div>
<?php if ($monitor->Type() != 'WebSite') { ?> <?php if ($monitor->Type() != 'WebSite') { ?>
<div id="monitorStatus"> <div id="monitorStatus">
<div id="monitorState"> <div id="monitorState">
<?php echo translate('State') ?>: <span><?php echo translate('State') ?>:<span id="stateValue"></span></span>
<span id="stateValue"></span> - <span id="viewingFPS" title="<?php echo translate('Viewing FPS')?>"><span id="viewingFPSValue"></span> fps</span>
<span title="<?php echo translate('Viewing FPS')?>"><span id="fpsValue"></span> fps</span> <span id="captureFPS" title="<?php echo translate('Capturing FPS')?>"><span id="captureFPSValue"></span> fps</span>
<span title="<?php echo translate('Capturing FPS')?>"><span id="capturefpsValue"></span> fps</span>
<?php if ( $monitor->Function() == 'Modect' or $monitor->Function() == 'Mocord' ) { ?> <?php if ( $monitor->Function() == 'Modect' or $monitor->Function() == 'Mocord' ) { ?>
<span title="<?php echo translate('Analysis FPS')?>"><span id="analysisfpsValue"></span> fps</span> <span id="analysisFPS" title="<?php echo translate('Analysis FPS')?>"><span id="analysisFPSValue"></span> fps</span>
<?php } ?> <?php } ?>
</div> </div>
</div> </div>
<div id="replayStatus"<?php echo $streamMode=="single" ? ' class="hidden"' : '' ?>>
<span id="mode"><?php echo translate('Mode') ?>: <span id="modeValue"></span></span>
<span id="rate"><?php echo translate('Rate') ?>: <span id="rateValue"></span>x</span>
<span id="delay"><?php echo translate('Delay') ?>: <span id="delayValue"></span>s</span>
<span id="level"><?php echo translate('Buffer') ?>: <span id="levelValue"></span>%</span>
<span id="zoom"><?php echo translate('Zoom') ?>: <span id="zoomValue"></span>x</span>
</div>
<div class="buttons">
<div id="dvrControls"> <div id="dvrControls">
<?php <?php
if ($streamMode == 'jpeg') { if ($streamMode == 'jpeg') {
@ -148,13 +307,7 @@ if ( $streamMode == 'jpeg' ) {
<?php <?php
} // end if streamMode==jpeg } // end if streamMode==jpeg
?> ?>
</div> </div><!--dvrButtons-->
<div id="replayStatus"<?php echo $streamMode=="single" ? ' class="hidden"' : '' ?>>
<span id="mode"><?php echo translate('Mode') ?>: <span id="modeValue"></span></span>
<span id="rate"><?php echo translate('Rate') ?>: <span id="rateValue"></span>x</span>
<span id="delay"><?php echo translate('Delay') ?>: <span id="delayValue"></span>s</span>
<span id="level"><?php echo translate('Buffer') ?>: <span id="levelValue"></span>%</span>
<span id="zoom"><?php echo translate('Zoom') ?>: <span id="zoomValue"></span>x</span>
</div> </div>
<?php } // end if $monitor->Type() != 'WebSite' ?> <?php } // end if $monitor->Type() != 'WebSite' ?>
<?php <?php
@ -208,6 +361,7 @@ if ( canView('Events') && ($monitor->Type() != 'WebSite') ) {
</table> </table>
</div> </div>
</div>
<?php <?php
} }
if ( ZM_WEB_SOUND_ON_ALARM ) { if ( ZM_WEB_SOUND_ON_ALARM ) {