Merge branch 'storageareas' of github.com:ConnorTechnology/ZoneMinder into storageareas

This commit is contained in:
Isaac Connor 2019-03-19 10:50:05 -04:00
commit f89654f442
43 changed files with 640 additions and 215 deletions

View File

@ -68,6 +68,7 @@ CREATE TABLE `Controls` (
`CanWake` tinyint(3) unsigned NOT NULL default '0', `CanWake` tinyint(3) unsigned NOT NULL default '0',
`CanSleep` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0',
`CanReset` tinyint(3) unsigned NOT NULL default '0', `CanReset` tinyint(3) unsigned NOT NULL default '0',
`CanReboot` tinyint(3) unsigned NOT NULL default '0',
`CanZoom` tinyint(3) unsigned NOT NULL default '0', `CanZoom` tinyint(3) unsigned NOT NULL default '0',
`CanAutoZoom` tinyint(3) unsigned NOT NULL default '0', `CanAutoZoom` tinyint(3) unsigned NOT NULL default '0',
`CanZoomAbs` tinyint(3) unsigned NOT NULL default '0', `CanZoomAbs` tinyint(3) unsigned NOT NULL default '0',

View File

@ -39,6 +39,8 @@ sub AUTOLOAD
my $class = ref($self) || croak( "$self not object" ); my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD; my $name = $AUTOLOAD;
$name =~ s/.*://; $name =~ s/.*://;
## This seems odd... if the method existed would we even be here?
## https://perldoc.perl.org/perlsub.html#Autoloading
if ( exists($self->{$name}) ) if ( exists($self->{$name}) )
{ {
return( $self->{$name} ); return( $self->{$name} );
@ -46,9 +48,17 @@ sub AUTOLOAD
Fatal( "Can't access $name member of object of class $class" ); Fatal( "Can't access $name member of object of class $class" );
} }
# FIXME: Do we really have to open a new connection every time?
#Digest usernbme="bdmin", reblm="Login to 4K05DB3PAJE98BE", nonae="1720242756",
#uri="/agi-bin/ptz.agi?bation=getStbtus&ahbnnel=1", response="10dd925b26ebd559353734635b859b8b",
#opbque="1a99677524b4ae63bbe3a132b2e9b38e3b163ebd", qop=buth, na=00000001, anonae="ab1bb5d43aa5d542"
sub open sub open
{ {
#Debug("&open invoked by: " . (caller(1))[3]);
my $self = shift; my $self = shift;
my $cgi = shift || '/cgi-bin/configManager.cgi?action=getConfig&name=Ptz';
$self->loadMonitor(); $self->loadMonitor();
# The Dahua camera firmware API supports the concept of having multiple # The Dahua camera firmware API supports the concept of having multiple
@ -73,60 +83,55 @@ sub open
} }
use LWP::UserAgent; use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new; $self->{ua} = LWP::UserAgent->new(keep_alive => 1);
$self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION); $self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed'; $self->{state} = 'closed';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug("sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
# Detect REALM # Detect REALM
my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz"; my $url = $PROTOCOL . $ADDRESS . $cgi;
my $req = HTTP::Request->new(GET=>$get_config_url); my $req = HTTP::Request->new(GET=>$url);
my $res = $self->{ua}->request($req); my $res = $self->{ua}->request($req);
if ($res->is_success) { if ($res->is_success) {
$self->{state} = 'open'; $self->{state} = 'open';
return; return 1;
} }
if ( $res->status_line() eq '401 Unauthorized' ) { if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers(); my $headers = $res->headers();
foreach my $k (keys %$headers) {
Debug("Initial Header $k => $$headers{$k}");
}
if ($$headers{'www-authenticate'}) { if ($$headers{'www-authenticate'}) {
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
Debug("Tokens: " . $tokens);
## FIXME: This is necessary because the Dahua spec does not match reality
if ($tokens =~ /\w+="([^"]+)"/i) { if ($tokens =~ /\w+="([^"]+)"/i) {
if ($REALM ne $1) { if ($REALM ne $1) {
$REALM = $1; $REALM = $1;
Debug("Changing REALM to '" . $REALM . "'"); Debug("Changing REALM to '" . $REALM . "'");
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
my $req = HTTP::Request->new(GET=>$get_config_url); my $req = HTTP::Request->new(GET=>$url);
$res = $self->{ua}->request($req); $res = $self->{ua}->request($req);
if ($res->is_success()) { if ($res->is_success()) {
$self->{state} = 'open'; $self->{state} = 'open';
return; Debug('Authentication succeeded...');
return 1;
} }
Debug('Authentication still failed after updating REALM' . $res->status_line); Debug('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers(); $headers = $res->headers();
foreach my $k ( keys %$headers ) { foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}"); Debug("Initial Header $k => $$headers{$k}");
} # end foreach } # end foreach
} else { } else { ## NOTE: Each of these else conditions is fatal as the command will not be
Error('Authentication failed, not a REALM problem'); ## executed. No use going further.
Fatal('Authentication failed: Check username and password.');
} }
} else { } else {
Error('Failed to match realm in tokens'); Fatal('Authentication failed: Incorrect realm.');
} # end if } # end if
} else { } else {
Error('No WWW-Authenticate Header'); Fatal('Authentication failed: No www-authenticate header returned.');
} # end if headers } # end if headers
} # end if $res->status_line() eq '401 Unauthorized' } # end if $res->status_line() eq '401 Unauthorized'
} }
@ -146,45 +151,40 @@ sub printMsg
Debug( $msg."[".$msg_len."]" ); Debug( $msg."[".$msg_len."]" );
} }
sub sendGetRequest { sub _sendGetRequest {
my $self = shift; my $self = shift;
my $url_path = shift; my $url_path = shift;
my $result = undef; # Attempt to reuse the connection
# FIXME: I think we need some sort of keepalive/heartbeat sent to the camera
# in order to keep the session alive. As it is, it appears that the
# ua's authentication times out or some such.
#
# This might be of some use:
# {"method":"global.keepAlive","params":{"timeout":300,"active":false},"id":1518,"session":"dae233a51c0693519395209b271411b6"}[!http]
# The web browser interface POSTs commands as JSON using js
my $url = $PROTOCOL . $ADDRESS . $url_path; my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(GET=>$url); my $req = HTTP::Request->new(GET => $url);
my $res = $self->{ua}->request($req); my $res = $self->{ua}->request($req);
if ($res->is_success) { if ($res->is_success) {
$result = !undef; return 1;
} else { } else {
if ($res->status_line() eq '401 Unauthorized') { return($self->open($url_path)); # if we have to, open a new connection
Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD);
Debug("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error("Error check failed: '".$res->status_line());
}
} }
return($result);
} }
sub sendPtzCommand sub _sendPtzCommand
{ {
my $self = shift; my $self = shift;
my $action = shift; my $action = shift;
my $command_code = shift; my $command_code = shift;
my $arg1 = shift; my $arg1 = shift || 0;
my $arg2 = shift; my $arg2 = shift || 0;
my $arg3 = shift; my $arg3 = shift || 0;
my $arg4 = shift || 0;
my $channel = $self->{dahua_channel_number}; my $channel = $self->{dahua_channel_number};
@ -194,10 +194,12 @@ sub sendPtzCommand
$url_path .= "code=" . $command_code . "&"; $url_path .= "code=" . $command_code . "&";
$url_path .= "arg1=" . $arg1 . "&"; $url_path .= "arg1=" . $arg1 . "&";
$url_path .= "arg2=" . $arg2 . "&"; $url_path .= "arg2=" . $arg2 . "&";
$url_path .= "arg3=" . $arg3; $url_path .= "arg3=" . $arg3 . "&";
$self->sendGetRequest($url_path); $url_path .= "arg4=" . $arg4;
return $self->_sendGetRequest($url_path);
} }
sub sendMomentaryPtzCommand
sub _sendMomentaryPtzCommand
{ {
my $self = shift; my $self = shift;
my $command_code = shift; my $command_code = shift;
@ -206,92 +208,195 @@ sub sendMomentaryPtzCommand
my $arg3 = shift; my $arg3 = shift;
my $duration_ms = shift; my $duration_ms = shift;
$self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3); $self->_sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
my $duration_ns = $duration_ms * 1000; my $duration_ns = $duration_ms * 1000;
usleep($duration_ns); usleep($duration_ns);
$self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3); $self->_sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
}
sub _sendAbsolutePositionCommand
{
my $self = shift;
my $arg1 = shift;
my $arg2 = shift;
my $arg3 = shift;
my $arg4 = shift;
$self->_sendPtzCommand("start", "PositionABS", $arg1, $arg2, $arg3, $arg4);
}
sub moveConLeft
{
my $self = shift;
Debug("Move Up Left");
$self->_sendMomentaryPtzCommand("Left", 0, 1, 0, 0);
}
sub moveConRight
{
my $self = shift;
Debug( "Move Right" );
$self->_sendMomentaryPtzCommand("Right", 0, 1, 0, 0);
}
sub moveConUp
{
my $self = shift;
Debug( "Move Up" );
$self->_sendMomentaryPtzCommand("Up", 0, 1, 0, 0);
}
sub moveConDown
{
my $self = shift;
Debug( "Move Down" );
$self->_sendMomentaryPtzCommand("Down", 0, 1, 0, 0);
}
sub moveConUpRight
{
my $self = shift;
Debug( "Move Diagonally Up Right" );
$self->_sendMomentaryPtzCommand("RightUp", 1, 1, 0, 0);
}
sub moveConDownRight
{
my $self = shift;
Debug( "Move Diagonally Down Right" );
$self->_sendMomentaryPtzCommand("RightDown", 1, 1, 0, 0);
}
sub moveConUpLeft
{
my $self = shift;
Debug( "Move Diagonally Up Left" );
$self->_sendMomentaryPtzCommand("LeftUp", 1, 1, 0, 0);
}
sub moveConDownLeft
{
my $self = shift;
Debug( "Move Diagonally Up Right" );
$self->_sendMomentaryPtzCommand("LeftDown", 1, 1, 0, 0);
}
sub zoomConTele
{
my $self = shift;
Debug( "Zoom Tele" );
$self->_sendMomentaryPtzCommand("ZoomTele", 0, 1, 0, 0);
}
sub zoomConWide
{
my $self = shift;
Debug( "Zoom Wide" );
$self->_sendMomentaryPtzCommand("ZoomWide", 0, 1, 0, 0);
} }
sub moveRelUpLeft sub moveRelUpLeft
{ {
my $self = shift; my $self = shift;
Debug("Move Up Left"); Debug("Move Up Left");
$self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500); $self->_sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
} }
sub moveRelUp sub moveRelUp
{ {
my $self = shift; my $self = shift;
Debug("Move Up"); Debug("Move Up");
$self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500); $self->_sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
} }
sub moveRelUpRight sub moveRelUpRight
{ {
my $self = shift; my $self = shift;
Debug("Move Up Right"); Debug("Move Up Right");
$self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500); $self->_sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
} }
sub moveRelLeft sub moveRelLeft
{ {
my $self = shift; my $self = shift;
Debug("Move Left"); Debug("Move Left");
$self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500); $self->_sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
} }
sub moveRelRight sub moveRelRight
{ {
my $self = shift; my $self = shift;
Debug("Move Right"); Debug("Move Right");
$self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500); $self->_sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
} }
sub moveRelDownLeft sub moveRelDownLeft
{ {
my $self = shift; my $self = shift;
Debug("Move Down Left"); Debug("Move Down Left");
$self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500); $self->_sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
} }
sub moveRelDown sub moveRelDown
{ {
my $self = shift; my $self = shift;
Debug("Move Down"); Debug("Move Down");
$self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500); $self->_sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
} }
sub moveRelDownRight sub moveRelDownRight
{ {
my $self = shift; my $self = shift;
Debug("Move Down Right"); Debug("Move Down Right");
$self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500); $self->_sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
} }
sub zoomRelTele sub zoomRelTele
{ {
my $self = shift; my $self = shift;
Debug("Zoom Relative Tele"); Debug("Zoom Relative Tele");
$self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500); $self->_sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
} }
sub zoomRelWide sub zoomRelWide
{ {
my $self = shift; my $self = shift;
Debug("Zoom Relative Wide"); Debug("Zoom Relative Wide");
$self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500); $self->_sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
} }
sub focusRelNear
{
my $self = shift;
my $response = $self->_sendPtzCommand("start", "FocusNear", 0, 1, 0, 0);
Debug("focusRelNear response: " . $response);
}
sub focusRelFar
{
my $self = shift;
my $response = $self->_sendPtzCommand("start", "FocusFar", 0, 1, 0, 0);
Debug("focusRelFar response: " . $response);
}
sub moveStop
{
my $self = shift;
Debug( "Move Stop" );
# The command does not matter here, just the stop...
$self->_sendPtzCommand("stop", "Up", 0, 0, 1, 0);
}
sub presetClear sub presetClear
{ {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset_id = $self->getParam($params, 'preset'); my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0); $self->_sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
} }
sub presetSet sub presetSet
{ {
my $self = shift; my $self = shift;
@ -308,8 +413,8 @@ sub presetSet
my $control_preset_row = $sth->fetchrow_hashref(); my $control_preset_row = $sth->fetchrow_hashref();
my $new_label_name = $control_preset_row->{'Label'}; my $new_label_name = $control_preset_row->{'Label'};
$self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0); $self->_sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
$self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0); $self->_sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
} }
sub presetGoto sub presetGoto
@ -318,12 +423,39 @@ sub presetGoto
my $params = shift; my $params = shift;
my $preset_id = $self->getParam($params, 'preset'); my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0); $self->_sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
}
sub presetHome
{
my $self = shift;
$self->_sendAbsolutePositionCommand( 0, 0, 0, 1 );
}
sub reset
{
my $self = shift;
Debug( "Camera Reset" );
$self->_sendPtzCommand("Reset", 0, 0, 0, 0);
}
sub reboot
{
my $self = shift;
Debug( "Camera Reboot" );
my $cmd = "/cgi-bin/magicBox.cgi?action=reboot";
$self->_sendGetRequest($cmd);
} }
1; 1;
__END__ __END__
=pod
=encoding utf8
=head1 NAME =head1 NAME
ZoneMinder::Control::Dahua - Perl module for Dahua cameras ZoneMinder::Control::Dahua - Perl module for Dahua cameras
@ -337,10 +469,6 @@ place this in /usr/share/perl5/ZoneMinder/Control
This module is an implementation of the Dahua IP camera HTTP control API. This module is an implementation of the Dahua IP camera HTTP control API.
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE =head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC Copyright (C) 2018 ZoneMinder LLC
@ -359,4 +487,138 @@ You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=head1 Private Methods
Methods intended for use internally but documented here for future developers.
=head2 _sendAbsolutePositionCommand( $arg1, $arg2, $arg3, $arg4 )
Where:
$arg1 = Horizontal angle 0° to 360°
$arg2 = Vertical angle 0° to -90°
$arg3 = Zoom multiplier
$arg4 = Speed 1 to 8
This is an private method used to send an absolute position command to the
camera.
=head1 Public Methods
Methods made available to control.pl via ZoneMinder::Control
=head2 Notes:
=over 1
Which methods are invoked depends on which types of movement are selected in
the camera control type. For example: if the 'Can Move Continuous' option is
checked, then methods including 'Con' in their names are invoked. Likewise if
the 'Can Move Relative" option is checked, then methods including 'Rel' in
their names are invoked.
At present, these types of movement are prioritized and exclusive. This applies
to all types of movement, not just PTZ, but focus, iris, etc. as well. The options
are tested in the following order:
1. Continuous
2. Relative
3. Absolute
These types are exclusive meaning that the first one that matches is the one
ZoneMinder will use to control with. It would be nice to allow the user to
select the type used given that some cameras support all three types of
movement.
=back
=head2 new
This method instantiates a new control object based upon this control module
and sets the 'id' attribute to the value passed in.
=head2 open
This method opens an HTTP connection to the camera. It handles authentication,
etc. Upon success it sets the 'state' attribute to 'open.'
=head2 close
This method effectively closes the HTTP connection to the camera. It sets the
'state' attribute to 'close.'
=head2 printMsg
This method appears to be used for debugging.
=head2 moveCon<direction>
This set of methods invoke continuous movement in the direction indicated by
the <direction> portion of their name. They accept no arguments and move the
camera at a speed of 1 for 0ms. The speed index of 1 is the lowest of the
accepted range of 1-8.
NOTE:
This is not true continuous movmement as currently implemented.
=head2 focusCon<range>
This set of methods invoke continuous focus in the range direction indicated
by the <range> portion of their name. They accept no arguments.
NOTE:
This is not true continuous movmement as currently implemented.
=head2 moveRel<direction>
This set of methods invoke relatvie movement in the direction indicated by
the <direction> portion of their name. They accept no arguments and move the
camera at a speed of 4 for 500ms. The speed index of 4 is half-way between
the accepted range of 1-8.
=head2 focusRel<range>
This set of methods invoke realtive focus in the range direction indicated by
the <range> portion of their name. They accept no arguments.
NOTE:
This only just does work. The Dahua API specifies "multiples" as the input.
We pass in a 1 for that as it does not seem to matter what number (0-8) is
provided, the camera focus behaves the same.
=head2 moveStop
This method attempts to stop the camera. The problem is that if continuous
motion is occurring in multiple directions, this will only stop the motion
in the 'Up' direction. Dahua does not support an "all-stop" command.
=head2 presetHome
This method "homes" the camera to a preset position. It accepts no arguments.
When either continuous or relative movement is enabled, pressing the center
button on the movement controls invokes this method.
NOTE:
The Dahua protocol does not appear to support a preset Home feature. We could
allow the user to assign a preset slot as the "home" slot. Dahua does appear
to support naming presets which may lend itself to this sort of thing. At
this point, we'll just send the camera back to center and zoom wide. (0°,0°,0)
=head2 reset
This method will reset the PTZ controls to their "default." It is not clear
what that is.
=head2 reboot
This method performs a reboot of the camera. This will take the camera offline
for the time it takes to reboot.
=cut =cut

View File

@ -1005,17 +1005,17 @@ sub delete_empty_directories {
Error("delete_empty_directories: Can't open directory '/$_[0]': $!" ); Error("delete_empty_directories: Can't open directory '/$_[0]': $!" );
return; return;
} }
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR ); my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir($DIR);
#Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' )); #Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
if ( @dirs ) { if ( @dirs ) {
Debug("Have " . @dirs . " dirs in $_[0]"); Debug('Have ' . @dirs . " dirs in $_[0]");
foreach ( @dirs ) { foreach ( @dirs ) {
delete_empty_directories( $_[0].'/'.$_ ); delete_empty_directories($_[0].'/'.$_);
} }
#Reload, since we may now be empty #Reload, since we may now be empty
rewinddir $DIR; rewinddir $DIR;
@contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR ); @contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir($DIR);
} }
closedir($DIR); closedir($DIR);
if ( ! @contents ) { if ( ! @contents ) {

View File

@ -923,10 +923,10 @@ if ( $version ) {
die( "Can't find upgrade from version '$version'" ); die( "Can't find upgrade from version '$version'" );
} }
# Re-enable the privacy popup after each upgrade # Re-enable the privacy popup after each upgrade
my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'"; #my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish(); #$sth->finish();
print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" );
} }
zmDbDisconnect(); zmDbDisconnect();

View File

@ -248,15 +248,19 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) {
//Function to check Username length //Function to check Username length
bool checkUser ( const char *username) { bool checkUser ( const char *username) {
if ( strlen(username) > 32) { if ( ! username )
return false; return false;
} if ( strlen(username) > 32 )
return false;
return true; return true;
} }
//Function to check password length //Function to check password length
bool checkPass (const char *password) { bool checkPass (const char *password) {
if ( strlen(password) > 64) { if ( !password )
return false; return false;
} if ( strlen(password) > 64 )
return false;
return true; return true;
} }

View File

@ -394,7 +394,7 @@ int main(int argc, char *argv[]) {
//fprintf( stderr, "?? getopt returned character code 0%o ??\n", c ); //fprintf( stderr, "?? getopt returned character code 0%o ??\n", c );
break; break;
} }
} } // end getopt loop
if ( optind < argc ) { if ( optind < argc ) {
fprintf(stderr, "Extraneous options, "); fprintf(stderr, "Extraneous options, ");
@ -425,44 +425,38 @@ int main(int argc, char *argv[]) {
if ( config.opt_use_auth ) { if ( config.opt_use_auth ) {
if ( strcmp(config.auth_relay, "none") == 0 ) { if ( strcmp(config.auth_relay, "none") == 0 ) {
if ( !checkUser(username)) {
fprintf(stderr, "Error, username greater than allowed 32 characters\n");
exit_zmu(-1);
}
if ( !username ) { if ( !username ) {
fprintf(stderr, "Error, username must be supplied\n"); fprintf(stderr, "Error, username must be supplied\n");
exit_zmu(-1); exit_zmu(-1);
} }
if ( username ) {
user = zmLoadUser(username);
}
} else {
if ( !(username && password) && !auth ) {
fprintf(stderr, "Error, username and password or auth string must be supplied\n");
exit_zmu(-1);
}
if ( !checkUser(username)) { if ( !checkUser(username)) {
fprintf(stderr, "Error, username greater than allowed 32 characters\n"); fprintf(stderr, "Error, username greater than allowed 32 characters\n");
exit_zmu(-1); exit_zmu(-1);
} }
if ( !checkPass(password)) {
fprintf(stderr, "Error, password greater than allowed 64 characters\n"); user = zmLoadUser(username);
} else {
if ( !(username && password) && !auth ) {
fprintf(stderr, "Error, username and password or auth string must be supplied\n");
exit_zmu(-1); exit_zmu(-1);
} }
//if ( strcmp( config.auth_relay, "hashed" ) == 0 ) if ( auth ) {
{ user = zmLoadAuthUser(auth, false);
if ( auth ) {
user = zmLoadAuthUser(auth, false);
}
} }
//else if ( strcmp( config.auth_relay, "plain" ) == 0 ) if ( username && password ) {
{ if ( !checkUser(username)) {
if ( username && password ) { fprintf(stderr, "Error, username greater than allowed 32 characters\n");
user = zmLoadUser(username, password); exit_zmu(-1);
} }
} if ( !checkPass(password)) {
} fprintf(stderr, "Error, password greater than allowed 64 characters\n");
exit_zmu(-1);
}
user = zmLoadUser(username, password);
} // end if username && password
} // end if relay or not
if ( !user ) { if ( !user ) {
fprintf(stderr, "Error, unable to authenticate user\n"); fprintf(stderr, "Error, unable to authenticate user\n");
return exit_zmu(-1); return exit_zmu(-1);

View File

@ -1,14 +1,88 @@
<?php <?php
App::uses('Component', 'Controller'); App::uses('Component', 'Controller');
class FilterComponent extends Component { class FilterComponent extends Component {
/**
* Valid MySQL operands that can be used from namedParams with two operands
* where the right-hand side (RHS) is a literal.
* These came from https://dev.mysql.com/doc/refman/8.0/en/non-typed-operators.html
*/
public $twoOperandSQLOperands = array(
'AND',
'&&',
'=',
//':=',
//'BETWEEN ... AND ...',
//'BINARY',
'&',
'~',
//'|',
'^',
//'CASE',
'DIV',
'/',
'=',
'<=>',
'>',
'>=',
'IS',
'IS NOT',
//'IS NOT NULL',
//'IS NULL',
//'->',
//'->>',
'<<',
'<',
'<=',
'LIKE',
'-',
'%',
'MOD',
//'NOT',
//'!',
//'NOT BETWEEN ... AND ...',
'!=',
'<>',
'NOT LIKE',
'NOT REGEXP',
// `or` operators aren't safe as they can
// be used to skip an existing condition
// enforcing access to only certain
// monitors/events.
//'||',
//'OR',
'+',
'REGEXP',
'>>',
'RLIKE',
'SOUNDS LIKE',
//'*',
'-',
//'XOR',
);
// Build a CakePHP find() condition based on the named parameters // Build a CakePHP find() condition based on the named parameters
// that are passed in // that are passed in
public function buildFilter($namedParams) { public function buildFilter($namedParams) {
$conditions = array();
if ($namedParams) { if ($namedParams) {
$conditions = array();
foreach ($namedParams as $attribute => $value) { foreach ($namedParams as $attribute => $value) {
// We need to sanitize $attribute to avoid SQL injection.
$lhs = trim($attribute);
$matches = NULL;
if (preg_match('/^(?P<field>[a-z0-9]+)(?P<operator>.+)?$/i', $lhs, $matches) !== 1) {
throw new Exception('Invalid argument before `:`: ' . $lhs);
}
$operator = trim($matches['operator']);
// Only allow operators on our allow list. No operator
// specified defaults to `=` by cakePHP.
if ($operator != '' && !in_array($operator, $this->twoOperandSQLOperands)) {
throw new Exception('Invalid operator: ' . $operator);
}
$lhs = '`' . $matches['field'] . '` ' . $operator;
// If the named param contains an array, we want to turn it into an IN condition // If the named param contains an array, we want to turn it into an IN condition
// Otherwise, we add it right into the $conditions array // Otherwise, we add it right into the $conditions array
if (is_array($value)) { if (is_array($value)) {
@ -18,10 +92,10 @@ class FilterComponent extends Component {
array_push($array, $term); array_push($array, $term);
} }
$query = array($attribute => $array); $query = array($lhs => $array);
array_push($conditions, $query); array_push($conditions, $query);
} else { } else {
$conditions[$attribute] = $value; $conditions[$lhs] = $value;
} }
} }

View File

@ -44,9 +44,8 @@ class EventsController extends AppController {
} }
if ( $this->request->params['named'] ) { if ( $this->request->params['named'] ) {
//$this->FilterComponent = $this->Components->load('Filter'); $this->FilterComponent = $this->Components->load('Filter');
//$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); $conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
$conditions = $this->request->params['named'];
} else { } else {
$conditions = array(); $conditions = array();
} }
@ -234,18 +233,34 @@ class EventsController extends AppController {
public function search() { public function search() {
$this->Event->recursive = -1; $this->Event->recursive = -1;
// Unmodified conditions to pass to find()
$find_conditions = array();
// Conditions to be filtered by buildFilter
$conditions = array(); $conditions = array();
foreach ($this->params['named'] as $param_name => $value) { foreach ($this->params['named'] as $param_name => $value) {
// Transform params into mysql // Transform params into conditions
if ( preg_match('/interval/i', $value, $matches) ) { if ( preg_match('/^\s?interval\s?/i', $value) ) {
$condition = array("$param_name >= (date_sub(now(), $value))"); if (preg_match('/^[a-z0-9]+$/i', $param_name) !== 1) {
throw new Exception('Invalid field name: ' . $param_name);
}
$matches = NULL;
$value = preg_replace('/^\s?interval\s?/i', '', $value);
if (preg_match('/^(?P<expr>[ -.:0-9\']+)\s+(?P<unit>[_a-z]+)$/i', trim($value), $matches) !== 1) {
throw new Exception('Invalid interval: ' . $value);
}
$expr = trim($matches['expr']);
$unit = trim($matches['unit']);
array_push($find_conditions, "$param_name >= DATE_SUB(NOW(), INTERVAL $expr $unit)");
} else { } else {
$condition = array($param_name => $value); $conditions[$param_name] = $value;
} }
array_push($conditions, $condition);
} }
$this->FilterComponent = $this->Components->load('Filter');
$conditions = $this->FilterComponent->buildFilter($conditions);
array_push($conditions, $find_conditions);
$results = $this->Event->find('all', array( $results = $this->Event->find('all', array(
'conditions' => $conditions 'conditions' => $conditions
)); ));
@ -261,18 +276,32 @@ class EventsController extends AppController {
// consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json // consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json
public function consoleEvents($interval = null) { public function consoleEvents($interval = null) {
$matches = NULL;
// https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals
// Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND`
if (preg_match('/^(?P<expr>[ -.:0-9\']+)\s+(?P<unit>[_a-z]+)$/i', trim($interval), $matches) !== 1) {
throw new Exception('Invalid interval: ' . $interval);
}
$expr = trim($matches['expr']);
$unit = trim($matches['unit']);
$this->Event->recursive = -1; $this->Event->recursive = -1;
$results = array(); $results = array();
$this->FilterComponent = $this->Components->load('Filter');
$conditions = $this->FilterComponent->buildFilter($conditions);
array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)"));
$moreconditions = ''; $query = $this->Event->find('all', array(
foreach ($this->request->params['named'] as $name => $param) { 'fields' => array(
$moreconditions = $moreconditions . ' AND '.$name.$param; 'MonitorId',
} 'COUNT(*) AS Count',
),
$query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;"); 'conditions' => $conditions,
'group' => 'MonitorId',
));
foreach ($query as $result) { foreach ($query as $result) {
$results[$result['Events']['MonitorId']] = $result[0]['Count']; $results[$result['Event']['MonitorId']] = $result[0]['Count'];
} }
$this->set(array( $this->set(array(

View File

@ -40,8 +40,7 @@ class MonitorsController extends AppController {
if ( $this->request->params['named'] ) { if ( $this->request->params['named'] ) {
$this->FilterComponent = $this->Components->load('Filter'); $this->FilterComponent = $this->Components->load('Filter');
//$conditions = $this->FilterComponent->buildFilter($this->request->params['named']); $conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
$conditions = $this->request->params['named'];
} else { } else {
$conditions = array(); $conditions = array();
} }
@ -318,6 +317,10 @@ class MonitorsController extends AppController {
throw new NotFoundException(__('Invalid monitor')); throw new NotFoundException(__('Invalid monitor'));
} }
if (preg_match('/^[a-z]+$/i', $daemon) !== 1) {
throw new BadRequestException(__('Invalid command'));
}
$monitor = $this->Monitor->find('first', array( $monitor = $this->Monitor->find('first', array(
'fields' => array('Id', 'Type', 'Device'), 'fields' => array('Id', 'Type', 'Device'),
'conditions' => array('Id' => $id) 'conditions' => array('Id' => $id)

View File

@ -122,7 +122,12 @@ class Event {
public function Path() { public function Path() {
$Storage = $this->Storage(); $Storage = $this->Storage();
return $Storage->Path().'/'.$this->Relative_Path(); if ( $Storage->Path() and $this->Relative_Path() ) {
return $Storage->Path().'/'.$this->Relative_Path();
} else {
Error("Event Path not complete. Storage: " . $Storage->Path() . " relative: " . $this->Relative_Path());
return '';
}
} }
public function Relative_Path() { public function Relative_Path() {
@ -148,17 +153,19 @@ class Event {
} }
public function delete() { public function delete() {
# This wouldn't work with foreign keys if ( ! $this->{'Id'} ) {
dbQuery( 'DELETE FROM Events WHERE Id = ?', array($this->{'Id'}) ); Error('Event delete on event with empty Id');
return;
}
if ( !ZM_OPT_FAST_DELETE ) { if ( !ZM_OPT_FAST_DELETE ) {
dbQuery( 'DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}) ); dbQuery('DELETE FROM Stats WHERE EventId = ?', array($this->{'Id'}));
dbQuery( 'DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}) ); dbQuery('DELETE FROM Frames WHERE EventId = ?', array($this->{'Id'}));
if ( $this->{'Scheme'} == 'Deep' ) { if ( $this->{'Scheme'} == 'Deep' ) {
# Assumption: All events have a start time # Assumption: All events have a start time
$start_date = date_parse( $this->{'StartTime'} ); $start_date = date_parse($this->{'StartTime'});
if ( ! $start_date ) { if ( ! $start_date ) {
Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.' ); Error('Unable to parse start time for event ' . $this->{'Id'} . ' not deleting files.');
return; return;
} }
$start_date['year'] = $start_date['year'] % 100; $start_date['year'] = $start_date['year'] % 100;
@ -166,37 +173,42 @@ class Event {
# So this is because ZM creates a link under the day pointing to the time that the event happened. # So this is because ZM creates a link under the day pointing to the time that the event happened.
$link_path = $this->Link_Path(); $link_path = $this->Link_Path();
if ( ! $link_path ) { if ( ! $link_path ) {
Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.' ); Error('Unable to determine link path for event ' . $this->{'Id'} . ' not deleting files.');
return; return;
} }
$Storage = $this->Storage(); $Storage = $this->Storage();
$eventlink_path = $Storage->Path().'/'.$link_path; $eventlink_path = $Storage->Path().'/'.$link_path;
if ( $id_files = glob( $eventlink_path ) ) { if ( $id_files = glob($eventlink_path) ) {
if ( ! $eventPath = readlink($id_files[0]) ) { if ( ! $eventPath = readlink($id_files[0]) ) {
Error("Unable to read link at $id_files[0]"); Error("Unable to read link at $id_files[0]");
return; return;
} }
# I know we are using arrays here, but really there can only ever be 1 in the array # I know we are using arrays here, but really there can only ever be 1 in the array
$eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] ); $eventPath = preg_replace( '/\.'.$this->{'Id'}.'$/', $eventPath, $id_files[0] );
deletePath( $eventPath ); deletePath($eventPath);
deletePath( $id_files[0] ); deletePath($id_files[0]);
$pathParts = explode( '/', $eventPath ); $pathParts = explode('/', $eventPath);
for ( $i = count($pathParts)-1; $i >= 2; $i-- ) { for ( $i = count($pathParts)-1; $i >= 2; $i-- ) {
$deletePath = join( '/', array_slice( $pathParts, 0, $i ) ); $deletePath = join('/', array_slice($pathParts, 0, $i));
if ( !glob( $deletePath."/*" ) ) { if ( !glob($deletePath.'/*') ) {
deletePath( $deletePath ); deletePath($deletePath);
} }
} }
} else { } else {
Warning( "Found no event files under $eventlink_path" ); Warning("Found no event files under $eventlink_path");
} # end if found files } # end if found files
} else { } else {
$eventPath = $this->Path(); $eventPath = $this->Path();
deletePath( $eventPath ); if ( ! $eventPath ) {
Error("No event Path in Event delete. Not deleting");
return;
}
deletePath($eventPath);
} # USE_DEEP_STORAGE OR NOT } # USE_DEEP_STORAGE OR NOT
} # ! ZM_OPT_FAST_DELETE } # ! ZM_OPT_FAST_DELETE
dbQuery('DELETE FROM Events WHERE Id = ?', array($this->{'Id'}));
} # end Event->delete } # end Event->delete
public function getStreamSrc( $args=array(), $querySep='&' ) { public function getStreamSrc( $args=array(), $querySep='&' ) {

View File

@ -27,28 +27,32 @@ if ( $action == 'donate' && isset($_REQUEST['option']) ) {
$option = $_REQUEST['option']; $option = $_REQUEST['option'];
switch( $option ) { switch( $option ) {
case 'go' : case 'go' :
// Ignore this, the caller will open the page itself // Ignore this, the caller will open the page itself
break; break;
case 'hour' : case 'hour' :
case 'day' : case 'day' :
case 'week' : case 'week' :
case 'month' : case 'month' :
$nextReminder = time(); $nextReminder = time();
if ( $option == 'hour' ) { if ( $option == 'hour' ) {
$nextReminder += 60*60; $nextReminder += 60*60;
} elseif ( $option == 'day' ) { } elseif ( $option == 'day' ) {
$nextReminder += 24*60*60; $nextReminder += 24*60*60;
} elseif ( $option == 'week' ) { } elseif ( $option == 'week' ) {
$nextReminder += 7*24*60*60; $nextReminder += 7*24*60*60;
} elseif ( $option == 'month' ) { } elseif ( $option == 'month' ) {
$nextReminder += 30*24*60*60; $nextReminder += 30*24*60*60;
} }
dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'"); dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_DONATE_REMINDER_TIME'");
break; break;
case 'never' : case 'never' :
case 'already' : case 'already' :
dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'"); dbQuery("UPDATE Config SET Value = '0' WHERE Name = 'ZM_DYN_SHOW_DONATE_REMINDER'");
break; break;
default :
Warning("Unknown value for option in donate: $option");
break;
} // end switch option } // end switch option
$view = 'none';
} }
?> ?>

View File

@ -211,7 +211,7 @@ global $user;
if ( ZM_OPT_USE_AUTH ) { if ( ZM_OPT_USE_AUTH ) {
$close_session = 0; $close_session = 0;
if ( !is_session_started() ) { if ( !is_session_started() ) {
session_start(); zm_session_start();
$close_session = 1; $close_session = 1;
} }

View File

@ -17,7 +17,6 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// //
namespace ZM;
error_reporting(E_ALL); error_reporting(E_ALL);
@ -72,7 +71,7 @@ define('ZM_BASE_URL', '');
require_once('includes/functions.php'); require_once('includes/functions.php');
if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) { if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) {
Logger::Debug("OPTIONS Method, only doing CORS"); ZM\Logger::Debug("OPTIONS Method, only doing CORS");
# Add Cross domain access headers # Add Cross domain access headers
CORSHeaders(); CORSHeaders();
return; return;
@ -159,7 +158,7 @@ CORSHeaders();
// Check for valid content dirs // Check for valid content dirs
if ( !is_writable(ZM_DIR_EVENTS) ) { if ( !is_writable(ZM_DIR_EVENTS) ) {
Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user."); ZM\Warning("Cannot write to event folder ".ZM_DIR_EVENTS.". Check that it exists and is owned by the web account user.");
} }
# Globals # Globals
@ -181,7 +180,7 @@ if ( isset($_REQUEST['request']) )
require_once('includes/auth.php'); require_once('includes/auth.php');
foreach ( getSkinIncludes('skin.php') as $includeFile ) { foreach ( getSkinIncludes('skin.php') as $includeFile ) {
#Logger::Debug("including $includeFile"); #ZM\Logger::Debug("including $includeFile");
require_once $includeFile; require_once $includeFile;
} }
@ -194,7 +193,7 @@ isset($view) || $view = NULL;
isset($request) || $request = NULL; isset($request) || $request = NULL;
isset($action) || $action = NULL; isset($action) || $action = NULL;
Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' )); ZM\Logger::Debug("View: $view Request: $request Action: $action User: " . ( isset($user) ? $user['Username'] : 'none' ));
if ( if (
ZM_ENABLE_CSRF_MAGIC && ZM_ENABLE_CSRF_MAGIC &&
( $action != 'login' ) && ( $action != 'login' ) &&
@ -205,17 +204,17 @@ if (
( $view != 'archive' ) ( $view != 'archive' )
) { ) {
require_once( 'includes/csrf/csrf-magic.php' ); require_once( 'includes/csrf/csrf-magic.php' );
#Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\""); #ZM\Logger::Debug("Calling csrf_check with the following values: \$request = \"$request\", \$view = \"$view\", \$action = \"$action\"");
csrf_check(); csrf_check();
} }
# Need to include actions because it does auth # Need to include actions because it does auth
if ( $action ) { if ( $action ) {
if ( file_exists('includes/actions/'.$view.'.php') ) { if ( file_exists('includes/actions/'.$view.'.php') ) {
Logger::Debug("Including includes/actions/$view.php"); ZM\Logger::Debug("Including includes/actions/$view.php");
require_once('includes/actions/'.$view.'.php'); require_once('includes/actions/'.$view.'.php');
} else { } else {
Warning("No includes/actions/$view.php for action $action"); ZM\Warning("No includes/actions/$view.php for action $action");
} }
} }
@ -227,7 +226,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) {
header('HTTP/1.1 401 Unauthorized'); header('HTTP/1.1 401 Unauthorized');
exit; exit;
} }
Logger::Debug('Redirecting to login'); ZM\Logger::Debug('Redirecting to login');
$view = 'none'; $view = 'none';
$redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login';
$request = null; $request = null;
@ -240,7 +239,7 @@ if ( ZM_OPT_USE_AUTH and !isset($user) and ($view != 'login') ) {
CSPHeaders($view, $cspNonce); CSPHeaders($view, $cspNonce);
if ( $redirect ) { if ( $redirect ) {
Logger::Debug("Redirecting to $redirect"); ZM\Logger::Debug("Redirecting to $redirect");
header('Location: '.$redirect); header('Location: '.$redirect);
return; return;
} }
@ -248,7 +247,7 @@ if ( $redirect ) {
if ( $request ) { if ( $request ) {
foreach ( getSkinIncludes('ajax/'.$request.'.php', true, true) as $includeFile ) { foreach ( getSkinIncludes('ajax/'.$request.'.php', true, true) as $includeFile ) {
if ( !file_exists($includeFile) ) if ( !file_exists($includeFile) )
Fatal("Request '$request' does not exist"); ZM\Fatal("Request '$request' does not exist");
require_once $includeFile; require_once $includeFile;
} }
return; return;
@ -257,7 +256,7 @@ if ( $request ) {
if ( $includeFiles = getSkinIncludes('views/'.$view.'.php', true, true) ) { if ( $includeFiles = getSkinIncludes('views/'.$view.'.php', true, true) ) {
foreach ( $includeFiles as $includeFile ) { foreach ( $includeFiles as $includeFile ) {
if ( !file_exists($includeFile) ) if ( !file_exists($includeFile) )
Fatal("View '$view' does not exist"); ZM\Fatal("View '$view' does not exist");
require_once $includeFile; require_once $includeFile;
} }
// If the view overrides $view to 'error', and the user is not logged in, then the // If the view overrides $view to 'error', and the user is not logged in, then the

View File

@ -216,6 +216,7 @@ $SLANG = array(
'CanMoveRel' => 'Podržava Relativno kretanje', 'CanMoveRel' => 'Podržava Relativno kretanje',
'CanPan' => 'Podržava Pomak' , 'CanPan' => 'Podržava Pomak' ,
'CanReset' => 'PodržavaReset', 'CanReset' => 'PodržavaReset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Podržava presetove', 'CanSetPresets' => 'Podržava presetove',
'CanSleep' => 'Podržava Sleep', 'CanSleep' => 'Podržava Sleep',
'CanTilt' => 'Podržava nagib', 'CanTilt' => 'Podržava nagib',

View File

@ -209,6 +209,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -205,6 +205,7 @@ $SLANG = array(
'CanMoveRel' => '可以相对移动', 'CanMoveRel' => '可以相对移动',
'CanPan' => '可以平移' , 'CanPan' => '可以平移' ,
'CanReset' => '可以复位', 'CanReset' => '可以复位',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => '可以进行预设', 'CanSetPresets' => '可以进行预设',
'CanSleep' => '可以休眠', 'CanSleep' => '可以休眠',
'CanTilt' => '可以倾斜', 'CanTilt' => '可以倾斜',

View File

@ -205,6 +205,7 @@ $SLANG = array(
'CanMoveRel' => 'Umí relativní pohyb', 'CanMoveRel' => 'Umí relativní pohyb',
'CanPan' => 'Umí otáčení', 'CanPan' => 'Umí otáčení',
'CanReset' => 'Umí reset', 'CanReset' => 'Umí reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Umí navolit předvolby', 'CanSetPresets' => 'Umí navolit předvolby',
'CanSleep' => 'Může spát', 'CanSleep' => 'Může spát',
'CanTilt' => 'Umí náklon', 'CanTilt' => 'Umí náklon',

View File

@ -207,6 +207,7 @@ $SLANG = array(
'CanMoveRel' => 'Kann relative Bewegung', 'CanMoveRel' => 'Kann relative Bewegung',
'CanPan' => 'Kann Pan' , 'CanPan' => 'Kann Pan' ,
'CanReset' => 'Kann Reset', 'CanReset' => 'Kann Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Kann Voreinstellungen setzen', 'CanSetPresets' => 'Kann Voreinstellungen setzen',
'CanSleep' => 'Kann Sleep', 'CanSleep' => 'Kann Sleep',
'CanTilt' => 'Kann Neigung', 'CanTilt' => 'Kann Neigung',

View File

@ -206,6 +206,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -216,6 +216,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -156,6 +156,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -205,6 +205,7 @@ $SLANG = array(
'CanMoveRel' => 'Puede moverse de forma relativa', 'CanMoveRel' => 'Puede moverse de forma relativa',
'CanPan' => 'Puede desplazarse' , 'CanPan' => 'Puede desplazarse' ,
'CanReset' => 'Puede restablecerse', 'CanReset' => 'Puede restablecerse',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Puede fefinir programaciones', 'CanSetPresets' => 'Puede fefinir programaciones',
'CanSleep' => 'Puede dormirse', 'CanSleep' => 'Puede dormirse',
'CanTilt' => 'Puede inclinarse', 'CanTilt' => 'Puede inclinarse',

View File

@ -212,6 +212,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -211,6 +211,7 @@ $SLANG = array(
'CanMoveRel' => 'Relatif', 'CanMoveRel' => 'Relatif',
'CanPan' => 'Panoramique' , 'CanPan' => 'Panoramique' ,
'CanReset' => 'RàZ', 'CanReset' => 'RàZ',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Stockage prépos.', 'CanSetPresets' => 'Stockage prépos.',
'CanSleep' => 'Veille', 'CanSleep' => 'Veille',
'CanTilt' => 'Inclinaison', 'CanTilt' => 'Inclinaison',

View File

@ -205,6 +205,7 @@ $SLANG = array(
'CanMoveRel' => 'àôùø úæåæä éçñéú', 'CanMoveRel' => 'àôùø úæåæä éçñéú',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'àôùø àúçåì', 'CanReset' => 'àôùø àúçåì',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'àôùø îöá ùéðä', 'CanSleep' => 'àôùø îöá ùéðä',
'CanTilt' => 'àôùø æòæåò', 'CanTilt' => 'àôùø æòæåò',

View File

@ -248,6 +248,7 @@ $SLANG = array(
'CanMoveRel' => 'Relatíven tud mozogni', 'CanMoveRel' => 'Relatíven tud mozogni',
'CanPan' => 'Tud jobb-bal mozgást' , 'CanPan' => 'Tud jobb-bal mozgást' ,
'CanReset' => 'Tud alaphelyzetbe jönni', 'CanReset' => 'Tud alaphelyzetbe jönni',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Tud menteni profilokat', 'CanSetPresets' => 'Tud menteni profilokat',
'CanSleep' => 'Tud phihenő üzemmódot', 'CanSleep' => 'Tud phihenő üzemmódot',
'CanTilt' => 'Tud fel-le mozgást', 'CanTilt' => 'Tud fel-le mozgást',

View File

@ -210,6 +210,7 @@ $SLANG = array(
'CanMoveRel' => 'Puo\' Mov. Relativo', 'CanMoveRel' => 'Puo\' Mov. Relativo',
'CanPan' => 'Puo\' Pan' , 'CanPan' => 'Puo\' Pan' ,
'CanReset' => 'Puo\' Reset', 'CanReset' => 'Puo\' Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Puo\' impostare preset', 'CanSetPresets' => 'Puo\' impostare preset',
'CanSleep' => 'Puo\' andare in sleep', 'CanSleep' => 'Puo\' andare in sleep',
'CanTilt' => 'Puo\' Tilt', 'CanTilt' => 'Puo\' Tilt',

View File

@ -206,6 +206,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -206,6 +206,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relatief', 'CanMoveRel' => 'Can Move Relatief',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -220,6 +220,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -145,6 +145,7 @@ $SLANG = array(
'CanMoveRel' => 'Can Move Relative', 'CanMoveRel' => 'Can Move Relative',
'CanPan' => 'Can Pan' , 'CanPan' => 'Can Pan' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Can Tilt', 'CanTilt' => 'Can Tilt',

View File

@ -176,6 +176,7 @@ $SLANG = array(
'CanMoveRel' => 'Mi&#351;care relativ&#259;', 'CanMoveRel' => 'Mi&#351;care relativ&#259;',
'CanPan' => 'Rotativ' , 'CanPan' => 'Rotativ' ,
'CanReset' => 'Can Reset', 'CanReset' => 'Can Reset',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Can Set Presets', 'CanSetPresets' => 'Can Set Presets',
'CanSleep' => 'Can Sleep', 'CanSleep' => 'Can Sleep',
'CanTilt' => 'Se poate &#238;nclina', 'CanTilt' => 'Se poate &#238;nclina',

View File

@ -206,6 +206,7 @@ $SLANG = array(
'CanMoveRel' => 'Относительное перемещение', 'CanMoveRel' => 'Относительное перемещение',
'CanPan' => 'Панорама' , 'CanPan' => 'Панорама' ,
'CanReset' => 'Сброс', 'CanReset' => 'Сброс',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Создание предустановок', 'CanSetPresets' => 'Создание предустановок',
'CanSleep' => 'Сон', 'CanSleep' => 'Сон',
'CanTilt' => 'Наклон', 'CanTilt' => 'Наклон',

View File

@ -206,6 +206,7 @@ $SLANG = array(
'CanMoveRel' => 'Har relativ förflyttning', 'CanMoveRel' => 'Har relativ förflyttning',
'CanPan' => 'Har panorering', 'CanPan' => 'Har panorering',
'CanReset' => 'Har återställning', 'CanReset' => 'Har återställning',
'CanReboot' => 'Can Reboot',
'CanSetPresets' => 'Har förinställningar', 'CanSetPresets' => 'Har förinställningar',
'CanSleep' => 'Kan vila', 'CanSleep' => 'Kan vila',
'CanTilt' => 'Kan tilta', 'CanTilt' => 'Kan tilta',

View File

@ -24,6 +24,7 @@ function getControlCommands( $monitor ) {
$cmds['Wake'] = 'wake'; $cmds['Wake'] = 'wake';
$cmds['Sleep'] = 'sleep'; $cmds['Sleep'] = 'sleep';
$cmds['Reset'] = 'reset'; $cmds['Reset'] = 'reset';
$cmds['Reboot'] = 'reboot';
$cmds['PresetSet'] = 'presetSet'; $cmds['PresetSet'] = 'presetSet';
$cmds['PresetGoto'] = 'presetGoto'; $cmds['PresetGoto'] = 'presetGoto';
@ -319,6 +320,11 @@ function controlPower( $monitor, $cmds ) {
if ( $monitor->CanReset() ) { if ( $monitor->CanReset() ) {
?> ?>
<button type="button" class="ptzTextBtn" value="Reset" onclick="controlCmd('<?php echo $cmds['Reset'] ?>')"><?php echo translate('Reset') ?></button> <button type="button" class="ptzTextBtn" value="Reset" onclick="controlCmd('<?php echo $cmds['Reset'] ?>')"><?php echo translate('Reset') ?></button>
<?php
}
if ( $monitor->CanReboot() ) {
?>
<button type="button" class="ptzTextBtn" value="Reboot" onclick="controlCmd('<?php echo $cmds['Reboot'] ?>')"><?php echo translate('Reboot') ?></button>
<?php <?php
} }
?> ?>

View File

@ -33,7 +33,7 @@ var popupSizes = {
'cycle': {'addWidth': 32, 'minWidth': 384, 'addHeight': 62}, 'cycle': {'addWidth': 32, 'minWidth': 384, 'addHeight': 62},
'device': {'width': 260, 'height': 150}, 'device': {'width': 260, 'height': 150},
'devices': {'width': 400, 'height': 240}, 'devices': {'width': 400, 'height': 240},
'donate': {'width': 500, 'height': 280}, 'donate': {'width': 500, 'height': 480},
'download': {'width': 350, 'height': 215}, 'download': {'width': 350, 'height': 215},
'event': {'addWidth': 108, 'minWidth': 496, 'addHeight': 230, 'minHeight': 540}, 'event': {'addWidth': 108, 'minWidth': 496, 'addHeight': 230, 'minHeight': 540},
'eventdetail': {'width': 600, 'height': 420}, 'eventdetail': {'width': 600, 'height': 420},

View File

@ -59,6 +59,7 @@ else
'CanWake' => "", 'CanWake' => "",
'CanSleep' => "", 'CanSleep' => "",
'CanReset' => "", 'CanReset' => "",
'CanReboot' => "",
'CanMove' => "", 'CanMove' => "",
'CanMoveDiag' => "", 'CanMoveDiag' => "",
'CanMoveMap' => "", 'CanMoveMap' => "",
@ -352,6 +353,7 @@ switch ( $tab )
<tr><th scope="row"><?php echo translate('CanWake') ?></th><td><input type="checkbox" name="newControl[CanWake]" value="1"<?php if ( !empty($newControl['CanWake']) ) { ?> checked="checked"<?php } ?>/></td></tr> <tr><th scope="row"><?php echo translate('CanWake') ?></th><td><input type="checkbox" name="newControl[CanWake]" value="1"<?php if ( !empty($newControl['CanWake']) ) { ?> checked="checked"<?php } ?>/></td></tr>
<tr><th scope="row"><?php echo translate('CanSleep') ?></th><td><input type="checkbox" name="newControl[CanSleep]" value="1"<?php if ( !empty($newControl['CanSleep']) ) { ?> checked="checked"<?php } ?>/></td></tr> <tr><th scope="row"><?php echo translate('CanSleep') ?></th><td><input type="checkbox" name="newControl[CanSleep]" value="1"<?php if ( !empty($newControl['CanSleep']) ) { ?> checked="checked"<?php } ?>/></td></tr>
<tr><th scope="row"><?php echo translate('CanReset') ?></th><td><input type="checkbox" name="newControl[CanReset]" value="1"<?php if ( !empty($newControl['CanReset']) ) { ?> checked="checked"<?php } ?>/></td></tr> <tr><th scope="row"><?php echo translate('CanReset') ?></th><td><input type="checkbox" name="newControl[CanReset]" value="1"<?php if ( !empty($newControl['CanReset']) ) { ?> checked="checked"<?php } ?>/></td></tr>
<tr><th scope="row"><?php echo translate('CanReboot') ?></th><td><input type="checkbox" name="newControl[CanReboot]" value="1"<?php if ( !empty($newControl['CanReboot']) ) { ?> checked="checked"<?php } ?>/></td></tr>
<?php <?php
break; break;
} }

View File

@ -18,25 +18,24 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// //
if ( !canEdit( 'System' ) ) if ( !canEdit('System') ) {
{ $view = 'error';
$view = "error"; return;
return;
} }
$options = array( $options = array(
"go" => translate('DonateYes'), "go" => translate('DonateYes'),
"hour" => translate('DonateRemindHour'), "hour" => translate('DonateRemindHour'),
"day" => translate('DonateRemindDay'), "day" => translate('DonateRemindDay'),
"week" => translate('DonateRemindWeek'), "week" => translate('DonateRemindWeek'),
"month" => translate('DonateRemindMonth'), "month" => translate('DonateRemindMonth'),
"never" => translate('DonateRemindNever'), "never" => translate('DonateRemindNever'),
"already" => translate('DonateAlready'), "already" => translate('DonateAlready'),
); );
$focusWindow = true; $focusWindow = true;
xhtmlHeaders(__FILE__, translate('Donate') ); xhtmlHeaders(__FILE__, translate('Donate'));
?> ?>
<body> <body>
<div id="page"> <div id="page">
@ -46,17 +45,17 @@ xhtmlHeaders(__FILE__, translate('Donate') );
</div> </div>
<div id="content"> <div id="content">
<form name="contentForm" id="contentForm" method="post" action="?"> <form name="contentForm" id="contentForm" method="post" action="?">
<input type="hidden" name="view" value="none"/> <input type="hidden" name="view" value="donate"/>
<input type="hidden" name="action" value="donate"/> <input type="hidden" name="action" value="donate"/>
<p> <p>
<?php echo translate('DonateEnticement') ?> <?php echo translate('DonateEnticement') ?>
</p> </p>
<p> <p>
<?php echo buildSelect( "option", $options ); ?> <?php echo buildSelect('option', $options); ?>
</p> </p>
<div id="contentButtons"> <div id="contentButtons">
<input type="submit" value="<?php echo translate('Apply') ?>" data-on-click-this="submitForm"> <button type="submit"><?php echo translate('Apply') ?></button>
<input type="button" value="<?php echo translate('Close') ?>" data-on-click="closeWindow"> <button type="button" data-on-click="closeWindow"><?php echo translate('Close') ?></button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -26,10 +26,11 @@ function cycleNext() {
window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout);
} }
function cyclePrev() { function cyclePrev() {
if ( monIdx ) if (monIdx) {
monIdx -= 1; monIdx -= 1;
else } else {
monIdx = monitorData.length-1; monIdx = monitorData.length - 1;
}
window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout); window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout);
} }
@ -82,9 +83,9 @@ function changeScale() {
var scale = $('scale').get('value'); var scale = $('scale').get('value');
$('width').set('value', 'auto'); $('width').set('value', 'auto');
$('height').set('value', 'auto'); $('height').set('value', 'auto');
Cookie.write('zmCycleScale', scale, { duration: 10*365 }); Cookie.write('zmCycleScale', scale, {duration: 10*365});
Cookie.write('zmCycleWidth', 'auto', { duration: 10*365 }); Cookie.write('zmCycleWidth', 'auto', {duration: 10*365});
Cookie.write('zmCycleHeight', 'auto', { duration: 10*365 }); Cookie.write('zmCycleHeight', 'auto', {duration: 10*365});
var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE; var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE;
var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE; var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE;

View File

@ -1,13 +1,3 @@
function submitForm( element ) {
var form = element.form;
if ( form.option.selectedIndex == 0 ) {
form.view.value = currentView;
} else {
form.view.value = 'none';
}
form.submit();
}
if ( action == "donate" && option == "go" ) { if ( action == "donate" && option == "go" ) {
zmWindow(); zmWindow();
} }

View File

@ -11,7 +11,6 @@ function Monitor(monitorData) {
this.alarmState = STATE_IDLE; this.alarmState = STATE_IDLE;
this.lastAlarmState = STATE_IDLE; this.lastAlarmState = STATE_IDLE;
this.streamCmdParms = 'view=request&request=stream&connkey='+this.connKey; this.streamCmdParms = 'view=request&request=stream&connkey='+this.connKey;
this.onclick = monitorData.onclick;
if ( auth_hash ) { if ( auth_hash ) {
this.streamCmdParms += '&auth='+auth_hash; this.streamCmdParms += '&auth='+auth_hash;
} }
@ -26,8 +25,30 @@ function Monitor(monitorData) {
} }
}; };
this.onclick = function() {
var el = this;
var url = '?view=watch&mid='+this.id;
var name = 'zmWatch'+this.id;
var tag = 'watch';
var width = el.getAttribute("data-window-width");
var height = el.getAttribute("data-window-height");
evt.preventDefault();
createPopup(url, name, tag, width, height);
};
this.setup_onclick = function() {
document.querySelectorAll('#imageFeed'+this.id).forEach(function(el) {
el.addEventListener('click', this.onclick);
});
}
this.disable_onclick = function() {
document.querySelectorAll('#imageFeed'+this.id).forEach(function(el) {
el.removeEventListener('click',this.onclick);
});
}
this.setStateClass = function(element, stateClass) { this.setStateClass = function(element, stateClass) {
if ( !element.hasClass(stateClass) ) { if ( !element.hasClass( stateClass ) ) {
if ( stateClass != 'alarm' ) if ( stateClass != 'alarm' )
element.removeClass('alarm'); element.removeClass('alarm');
if ( stateClass != 'alert' ) if ( stateClass != 'alert' )
@ -359,8 +380,7 @@ function edit_layout(button) {
for ( var i = 0, length = monitors.length; i < length; i++ ) { for ( var i = 0, length = monitors.length; i < length; i++ ) {
var monitor = monitors[i]; var monitor = monitors[i];
monitor_feed = $j('#imageFeed'+monitor.id)[0]; monitor.disable_onclick();
monitor_feed.onclick = '';
}; };
$j('#monitors .monitorFrame').draggable({ $j('#monitors .monitorFrame').draggable({
@ -435,8 +455,9 @@ function initPage() {
if ( monitors[i].type == 'WebSite' && interval > 0 ) { if ( monitors[i].type == 'WebSite' && interval > 0 ) {
setInterval(reloadWebSite, interval*1000, i); setInterval(reloadWebSite, interval*1000, i);
} }
monitors[i].setup_onclick();
} }
selectLayout('#zmMontageLayout'); selectLayout('#zmMontageLayout');
} }
// Kick everything off // Kick everything off
window.addEventListener( 'DOMContentLoaded', initPage ); window.addEventListener('DOMContentLoaded', initPage);

View File

@ -448,10 +448,16 @@ function cmdEnableAlarms() {
function cmdForceAlarm() { function cmdForceAlarm() {
alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm"); alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm");
if (window.event) {
window.event.preventDefault();
}
} }
function cmdCancelForcedAlarm() { function cmdCancelForcedAlarm() {
alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm"); alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm");
if (window.event) {
window.event.preventDefault();
}
return false; return false;
} }

View File

@ -199,10 +199,7 @@ foreach ( $monitors as $monitor ) {
<div id="monitor<?php echo $monitor->Id() ?>" class="monitor idle"> <div id="monitor<?php echo $monitor->Id() ?>" class="monitor idle">
<div <div
id="imageFeed<?php echo $monitor->Id() ?>" id="imageFeed<?php echo $monitor->Id() ?>"
class="imageFeed popup-link" class="imageFeed"
data-url="?view=watch&amp;mid=<?php echo $monitor->Id() ?>"
data-name="zmWatch<?php echo $monitor->Id() ?>"
data-tag="watch"
data-width="<?php echo reScale( $monitor->Width(), $monitor->PopupScale() ); ?>" data-width="<?php echo reScale( $monitor->Width(), $monitor->PopupScale() ); ?>"
data-height="<?php echo reScale( $monitor->Height(), $monitor->PopupScale() ); ?>"> data-height="<?php echo reScale( $monitor->Height(), $monitor->PopupScale() ); ?>">
<?php <?php