From 86b2f6a12ed57a8e49b2427e9841c59bc6cd45f0 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Thu, 26 Apr 2018 16:18:36 -0500 Subject: [PATCH] New Monitor Type - Website (#2065) * implement website monitor * don't check certain fields when using website monitor * continue to fix javascript errors for website monitors * check $monitor, not $new_monitor here * add website monitor documentation was somehow left out of the initial commit * fix corruption of functions.php * add missing comma * remove errors by testing for existence of key. If it's a new monitor, then none of the keys will be valid * If the monitor type is WebSite, then default Status to Running. * put back start function that got lost in merge. Don't start StreamCmd's if it's a WebSite * Add midding comma * Hide unrelated tabs when type is WebSite. Put back input fields for Type=WebSite * Don't show control or any of the status fields for WebSite type monitors * add some parenthesis to ensure order of operations, seems to fix fps and status fields not being shown for regular monitors --- db/zm_create.sql.in | 2 +- db/zm_update-1.31.43.sql | 24 +++ distros/redhat/zoneminder.spec | 2 +- docs/userguide/definemonitor.rst | 17 +++ .../lib/ZoneMinder/ConfigData.pm.in | 17 +++ scripts/zmpkg.pl.in | 2 +- scripts/zmwatch.pl.in | 1 + version | 2 +- .../Console/Templates/skel/Console/cake.bat | 60 ++++---- web/api/lib/Cake/Console/cake.bat | 56 +++---- web/includes/actions.php | 28 ++-- web/includes/functions.php | 39 ++++- web/lang/en_gb.php | 3 + web/skins/classic/views/console.php | 9 +- web/skins/classic/views/js/monitor.js.php | 101 +++++++------ web/skins/classic/views/js/montage.js | 54 +++++-- web/skins/classic/views/js/montage.js.php | 4 +- web/skins/classic/views/js/watch.js | 142 ++++++++++-------- web/skins/classic/views/js/watch.js.php | 2 + web/skins/classic/views/monitor.php | 57 ++++--- web/skins/classic/views/montage.php | 8 +- web/skins/classic/views/montagereview.php | 2 +- web/skins/classic/views/watch.php | 8 +- 23 files changed, 406 insertions(+), 234 deletions(-) create mode 100644 db/zm_update-1.31.43.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 0dad55059..b7cfca3c5 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -63,7 +63,7 @@ DROP TABLE IF EXISTS `Controls`; CREATE TABLE `Controls` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', - `Type` enum('Local','Remote','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local', + `Type` enum('Local','Remote','Ffmpeg','Libvlc','cURL','WebSite') NOT NULL default 'Local', `Protocol` varchar(64) default NULL, `CanWake` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0', diff --git a/db/zm_update-1.31.43.sql b/db/zm_update-1.31.43.sql new file mode 100644 index 000000000..d8d6eaefd --- /dev/null +++ b/db/zm_update-1.31.43.sql @@ -0,0 +1,24 @@ +-- +-- This updates a 1.31.42 database to 1.31.43 +-- +-- Add WebSite enum to Monitor.Type +-- Add Refresh column to Monitors table +-- + +ALTER TABLE `zm`.`Monitors` +CHANGE COLUMN `Type` `Type` ENUM('Local', 'Remote', 'File', 'Ffmpeg', 'Libvlc', 'cURL', 'WebSite') NOT NULL DEFAULT 'Local' ; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'Refresh' + ) > 0, +"SELECT 'Column Refresh exists in Monitors'", +"ALTER TABLE Monitors ADD `Refresh` int(10) unsigned default NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 9ef5b2606..c52ccbad9 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -26,7 +26,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.31.42 +Version: 1.31.43 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/docs/userguide/definemonitor.rst b/docs/userguide/definemonitor.rst index 66e8928d3..8f85feb98 100644 --- a/docs/userguide/definemonitor.rst +++ b/docs/userguide/definemonitor.rst @@ -147,6 +147,23 @@ Keep aspect ratio Orientation As per local devices. +WebSite +^^^^^^^ + +This Source Type allows one to configure an arbitrary website as a non-reocrdable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue. + +Website URL + Enter the full http or https url to the desired website. + +Width (pixels) + Chose a desired width in pixels that gives an acceptable appearance. This may take some expirimentation. + +Height (pixels) + Chose a desired height in pixels that gives an acceptable appearance. This may take some expirimentation. + +Web Site Refresh + If the website in question has static content, optionally enter a time period in seconds for ZoneMinder to refresh the content. + Timestamp Tab ------------- diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index addd6239b..6eb41e57e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -2946,6 +2946,23 @@ our @options = ( type => $types{boolean}, category => 'web', }, + { + name => 'ZM_WEB_XFRAME_WARN', + default => 'yes', + description => 'Warn when website X-Frame-Options is set to sameorigin', + help => q` + When creating a Web Site monitor, if the target web site has + X-Frame-Options set to sameorigin in the header, the site will + not display in ZoneMinder. This is a design feature in most modern + browsers. When this condiction has occured, ZoneMinder will write a + warning to the log file. To get around this, one can install a browser + plugin or extension to ignore X-Frame headers, and then the page will + display properly. Once the plugin or extenstion has ben installed, + the end user may choose to turn this warning off. + `, + type => $types{boolean}, + category => 'web', + }, { name => 'ZM_WEB_H_REFRESH_MAIN', default => '60', diff --git a/scripts/zmpkg.pl.in b/scripts/zmpkg.pl.in index 6b6839bff..62771c7aa 100644 --- a/scripts/zmpkg.pl.in +++ b/scripts/zmpkg.pl.in @@ -211,7 +211,7 @@ if ( $command =~ /^(?:start|restart)$/ ) { my $res = $sth->execute( @values ) or Fatal( "Can't execute: ".$sth->errstr() ); while( my $monitor = $sth->fetchrow_hashref() ) { - if ( $monitor->{Function} ne 'None' ) { + if ( $monitor->{Function} ne 'None' && $monitor->{Type} ne 'WebSite' ) { if ( $monitor->{Type} eq 'Local' ) { runCommand( "zmdc.pl start zmc -d $monitor->{Device}" ); } else { diff --git a/scripts/zmwatch.pl.in b/scripts/zmwatch.pl.in index 7d21ecfee..28a8d4a9b 100644 --- a/scripts/zmwatch.pl.in +++ b/scripts/zmwatch.pl.in @@ -84,6 +84,7 @@ while( 1 ) { while( my $monitor = $sth->fetchrow_hashref() ) { my $now = time(); next if $monitor->{Function} eq 'None'; + next if $monitor->{Type} eq 'WebSite'; my $restart = 0; if ( zmMemVerify( $monitor ) ) { # Check we have got an image recently diff --git a/version b/version index dd8165476..92522fa6a 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.42 +1.31.43 diff --git a/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat b/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat index 722febf10..31bde01b1 100644 --- a/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat +++ b/web/api/lib/Cake/Console/Templates/skel/Console/cake.bat @@ -1,30 +1,30 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Bake is a shell script for running CakePHP bake script -:: -:: CakePHP(tm) : Rapid Development Framework (https://cakephp.org) -:: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) -:: -:: Licensed under The MIT License -:: Redistributions of files must retain the above copyright notice. -:: -:: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) -:: @link https://cakephp.org CakePHP(tm) Project -:: @package app.Console -:: @since CakePHP(tm) v 2.0 -:: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -:: In order for this script to work as intended, the cake\console\ folder must be in your PATH - -@echo. -@echo off - -SET app=%0 -SET lib=%~dp0 - -php -q "%lib%cake.php" -working "%CD% " %* - -echo. - -exit /B %ERRORLEVEL% +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: +:: CakePHP(tm) : Rapid Development Framework (https://cakephp.org) +:: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +:: @link https://cakephp.org CakePHP(tm) Project +:: @package app.Console +:: @since CakePHP(tm) v 2.0 +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +:: In order for this script to work as intended, the cake\console\ folder must be in your PATH + +@echo. +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD% " %* + +echo. + +exit /B %ERRORLEVEL% diff --git a/web/api/lib/Cake/Console/cake.bat b/web/api/lib/Cake/Console/cake.bat index 7aa9ad78f..8792173ef 100644 --- a/web/api/lib/Cake/Console/cake.bat +++ b/web/api/lib/Cake/Console/cake.bat @@ -1,28 +1,28 @@ -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -:: -:: Bake is a shell script for running CakePHP bake script -:: -:: CakePHP(tm) : Rapid Development Framework (https://cakephp.org) -:: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) -:: -:: Licensed under The MIT License -:: Redistributions of files must retain the above copyright notice. -:: -:: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) -:: @link https://cakephp.org CakePHP(tm) Project -:: @package Cake.Console -:: @since CakePHP(tm) v 1.2.0.5012 -:: @license https://opensource.org/licenses/mit-license.php MIT License -:: -:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -@echo off - -SET app=%0 -SET lib=%~dp0 - -php -q "%lib%cake.php" -working "%CD% " %* - -echo. - -exit /B %ERRORLEVEL% +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:: +:: Bake is a shell script for running CakePHP bake script +:: +:: CakePHP(tm) : Rapid Development Framework (https://cakephp.org) +:: Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +:: +:: Licensed under The MIT License +:: Redistributions of files must retain the above copyright notice. +:: +:: @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) +:: @link https://cakephp.org CakePHP(tm) Project +:: @package Cake.Console +:: @since CakePHP(tm) v 1.2.0.5012 +:: @license https://opensource.org/licenses/mit-license.php MIT License +:: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +@echo off + +SET app=%0 +SET lib=%~dp0 + +php -q "%lib%cake.php" -working "%CD% " %* + +echo. + +exit /B %ERRORLEVEL% diff --git a/web/includes/actions.php b/web/includes/actions.php index 423f63bcb..d0540b8dc 100644 --- a/web/includes/actions.php +++ b/web/includes/actions.php @@ -299,10 +299,12 @@ if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) { continue; } $Monitor = new Monitor( $mid ); - $Monitor->zmaControl('stop'); - $Monitor->zmcControl('stop'); + if ( $Monitor->Type() != 'WebSite' ) { + $Monitor->zmaControl('stop'); + $Monitor->zmcControl('stop'); + } $Monitor->save( $_REQUEST['newMonitor'] ); - if ($Monitor->Function() != 'None' ) { + if ($Monitor->Function() != 'None' && $Monitor->Type() != 'WebSite' ) { $Monitor->zmcControl('start'); if ( $Monitor->Enabled() ) { $Monitor->zmaControl('start'); @@ -330,7 +332,7 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { $monitor['Function'] = $newFunction; $monitor['Enabled'] = $newEnabled; - if ( daemonCheck() ) { + if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { $restart = ($oldFunction == 'None') || ($newFunction == 'None') || ($newEnabled != $oldEnabled); zmaControl( $monitor, 'stop' ); zmcControl( $monitor, $restart?'restart':'' ); @@ -371,7 +373,7 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { } else { dbQuery( 'INSERT INTO Zones SET MonitorId=?, '.implode( ', ', $changes ), array( $mid ) ); } - if ( daemonCheck() ) { + if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { if ( $_REQUEST['newZone']['Type'] == 'Privacy' ) { zmaControl( $monitor, 'stop' ); zmcControl( $monitor, 'restart' ); @@ -399,7 +401,7 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { } } if($changes>0) { - if ( daemonCheck() ) { + if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { zmaControl( $mid, 'restart' ); } $refreshParent = true; @@ -424,7 +426,7 @@ if ( !empty($_REQUEST['mid']) && canEdit( 'Monitors', $_REQUEST['mid'] ) ) { $deletedZid = 1; } if ( $deletedZid ) { - if ( daemonCheck() ) { + if ( daemonCheck() && $monitor['Type'] != 'WebSite' ) { if ( $zone['Type'] == 'Privacy' ) { zmaControl( $mid, 'stop' ); zmcControl( $mid, 'restart' ); @@ -492,8 +494,10 @@ if ( canEdit( 'Monitors' ) ) { if ( $mid ) { # If we change anything that changes the shared mem size, zma can complain. So let's stop first. - zmaControl( $monitor, 'stop' ); - zmcControl( $monitor, 'stop' ); + if ( $monitor['Type'] != 'WebSite' ) { + zmaControl( $monitor, 'stop' ); + zmcControl( $monitor, 'stop' ); + } dbQuery( 'UPDATE Monitors SET '.implode( ', ', $changes ).' WHERE Id=?', array($mid) ); // Groups will be added below if ( isset($changes['Name']) or isset($changes['StorageId']) ) { @@ -606,8 +610,10 @@ if ( canEdit( 'Monitors' ) ) { $new_monitor = new Monitor($mid); //fixDevices(); - $new_monitor->zmcControl('start'); - $new_monitor->zmaControl('start'); + if ( $monitor['Type'] != 'WebSite' ) { + $new_monitor->zmcControl('start'); + $new_monitor->zmaControl('start'); + } if ( $new_monitor->Controllable() ) { require_once( 'control_functions.php' ); diff --git a/web/includes/functions.php b/web/includes/functions.php index 0e396a02f..27974b222 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -280,6 +280,27 @@ function getImageStill( $id, $src, $width, $height, $title='' ) { return ''.$title.''; } +function getWebSiteUrl( $id, $src, $width, $height, $title='' ) { + # Prevent unsightly warnings when php cannot verify the ssl certificate + stream_context_set_default( [ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], + ]); + # The End User can turn off the following warning under Options -> Web + if ( ZM_WEB_XFRAME_WARN ) { + $header = get_headers($src, 1); + # If the target website has set X-Frame-Options, check it for "sameorigin" and warn the end user + if (array_key_exists('X-Frame-Options', $header)) { + $header = $header['X-Frame-Options']; + if ( stripos($header, 'sameorigin') === 0 ) + Warning("Web site $src has X-Frame-Options set to sameorigin. An X-Frame-Options browser plugin is required to display this site."); + } + } + return ''; +} + function outputControlStill( $src, $width, $height, $monitor, $scale, $target ) { ?>
@@ -486,7 +507,7 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) { $types = array(); foreach( $newValues as $key=>$value ) { - if ( $columns && !$columns[$key] ) + if ( $columns && !isset($columns[$key]) ) continue; if ( !isset($types[$key]) ) @@ -495,11 +516,11 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) { switch( $types[$key] ) { case 'set' : { - if ( is_array( $newValues[$key] ) ) { - if ( join(',',$newValues[$key]) != $values[$key] ) { + if ( is_array($newValues[$key]) ) { + if ( (!isset($values[$key])) or ( join(',',$newValues[$key]) != $values[$key] ) ) { $changes[$key] = "`$key` = ".dbEscape(join(',',$newValues[$key])); } - } elseif ( $values[$key] ) { + } else if ( (!isset($values[$key])) or $values[$key] ) { $changes[$key] = "`$key` = ''"; } break; @@ -548,7 +569,7 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) { } case 'raw' : { - if ( $values[$key] != $value ) { + if ( (!isset($values[$key])) or ($values[$key] != $value) ) { $changes[$key] = $key . ' = '.dbEscape($value); } break; @@ -2128,8 +2149,14 @@ function getStreamHTML( $monitor, $options = array() ) { $options['buffer'] = $monitor->StreamReplayBuffer(); //Warning("width: " . $options['width'] . ' height: ' . $options['height']. ' scale: ' . $options['scale'] ); + if ( $monitor->Type() == "WebSite" ) { + return getWebSiteUrl( 'liveStream'.$monitor->Id(), $monitor->Path(), + ( isset($options['width']) ? $options['width'] : NULL ), + ( isset($options['height']) ? $options['height'] : NULL ), + $monitor->Name() + ); //FIXME, the width and height of the image need to be scaled. - if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) { + } else if ( ZM_WEB_STREAM_METHOD == 'mpeg' && ZM_MPEG_LIVE_FORMAT ) { $streamSrc = $monitor->getStreamSrc( array( 'mode'=>'mpeg', 'scale'=>(isset($options['scale'])?$options['scale']:100), diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 6e97eaab8..262870d82 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -174,8 +174,10 @@ $SLANG = array( 'BadSectionLength' => 'Section length must be an integer of 30 or more', 'BadSignalCheckColour' => 'Signal check colour must be a valid RGB colour string', 'BadStreamReplayBuffer' => 'Stream replay buffer must be an integer of zero or more', + 'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', 'BadWarmupCount' => 'Warmup frames must be an integer of zero or more', 'BadWebColour' => 'Web colour must be a valid web colour string', + 'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', 'BadWidth' => 'Width must be set to a valid value', 'Bandwidth' => 'Bandwidth', 'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing @@ -768,6 +770,7 @@ $SLANG = array( 'Watch' => 'Watch', 'WebColour' => 'Web Colour', 'Web' => 'Web', + 'WebSiteUrl' => 'Website URL', 'Week' => 'Week', 'WhiteBalance' => 'White Balance', 'White' => 'White', diff --git a/web/skins/classic/views/console.php b/web/skins/classic/views/console.php index 7565335bb..e21cf5311 100644 --- a/web/skins/classic/views/console.php +++ b/web/skins/classic/views/console.php @@ -110,6 +110,9 @@ $status_counts = array(); for ( $i = 0; $i < count($displayMonitors); $i++ ) { $monitor = &$displayMonitors[$i]; if ( ! $monitor['Status'] ) { + if ( $monitor['Type'] == 'WebSite' ) + $monitor['Status'] = 'Running'; + else $monitor['Status'] = 'NotRunning'; } if ( !isset($status_counts[$monitor['Status']]) ) @@ -220,7 +223,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { ?> "; - if ( form.elements['newMonitor[AnalysisFPSLimit]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPSLimit]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( form.elements['newMonitor[MaxFPS]'].value && !(parseFloat(form.elements['newMonitor[MaxFPS]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( form.elements['newMonitor[AlarmMaxFPS]'].value && !(parseFloat(form.elements['newMonitor[AlarmMaxFPS]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[RefBlendPerc]'].value || (parseInt(form.elements['newMonitor[RefBlendPerc]'].value) > 100 ) || (parseInt(form.elements['newMonitor[RefBlendPerc]'].value) < 0 ) ) - errors[errors.length] = ""; if ( form.elements['newMonitor[Type]'].value == 'Local' ) { if ( !form.elements['newMonitor[Palette]'].value || !form.elements['newMonitor[Palette]'].value.match( /^\d+$/ ) ) errors[errors.length] = ""; @@ -81,44 +73,63 @@ function validateForm( form ) { } else if ( form.elements['newMonitor[Type]'].value == 'File' ) { if ( !form.elements['newMonitor[Path]'].value ) errors[errors.length] = ""; + } else if ( form.elements['newMonitor[Type]'].value == 'WebSite' ) { + if ( form.elements['newMonitor[Function]'].value != 'Monitor' && form.elements['newMonitor[Function]'].value != 'None') + errors[errors.length] = ""; + if ( form.elements['newMonitor[Path]'].value.search(/^https?:\/\//i) ) + errors[errors.length] = ""; + } + + if ( form.elements['newMonitor[Type]'].value != 'WebSite' ) { + + if ( form.elements['newMonitor[AnalysisFPSLimit]'].value && !(parseFloat(form.elements['newMonitor[AnalysisFPSLimit]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( form.elements['newMonitor[MaxFPS]'].value && !(parseFloat(form.elements['newMonitor[MaxFPS]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( form.elements['newMonitor[AlarmMaxFPS]'].value && !(parseFloat(form.elements['newMonitor[AlarmMaxFPS]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[RefBlendPerc]'].value || (parseInt(form.elements['newMonitor[RefBlendPerc]'].value) > 100 ) || (parseInt(form.elements['newMonitor[RefBlendPerc]'].value) < 0 ) ) + errors[errors.length] = ""; + + if ( !form.elements['newMonitor[Colours]'].value || (parseInt(form.elements['newMonitor[Colours]'].value) != 1 && parseInt(form.elements['newMonitor[Colours]'].value) != 3 && parseInt(form.elements['newMonitor[Colours]'].value) != 4 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[Width]'].value || !(parseInt(form.elements['newMonitor[Width]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[Height]'].value || !(parseInt(form.elements['newMonitor[Height]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[LabelX]'].value || !(parseInt(form.elements['newMonitor[LabelX]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[LabelY]'].value || !(parseInt(form.elements['newMonitor[LabelY]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[ImageBufferCount]'].value || !(parseInt(form.elements['newMonitor[ImageBufferCount]'].value) >= 10 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[WarmupCount]'].value || !(parseInt(form.elements['newMonitor[WarmupCount]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[PreEventCount]'].value || !(parseInt(form.elements['newMonitor[PreEventCount]'].value) >= 0 ) || (parseInt(form.elements['newMonitor[PreEventCount]'].value) > parseInt(form.elements['newMonitor[ImageBufferCount]'].value)) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[PostEventCount]'].value || !(parseInt(form.elements['newMonitor[PostEventCount]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[StreamReplayBuffer]'].value || !(parseInt(form.elements['newMonitor[StreamReplayBuffer]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[AlarmFrameCount]'].value || !(parseInt(form.elements['newMonitor[AlarmFrameCount]'].value) > 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[SectionLength]'].value || !(parseInt(form.elements['newMonitor[SectionLength]'].value) >= 30 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[AnalysisUpdateDelay]'].value || !(parseInt(form.elements['newMonitor[AnalysisUpdateDelay]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[FPSReportInterval]'].value || !(parseInt(form.elements['newMonitor[FPSReportInterval]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[FrameSkip]'].value || !(parseInt(form.elements['newMonitor[FrameSkip]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[MotionFrameSkip]'].value || !(parseInt(form.elements['newMonitor[MotionFrameSkip]'].value) >= 0 ) ) + errors[errors.length] = ""; + if ( form.elements['newMonitor[Type]'].value == 'Local' ) + if ( !form.elements['newMonitor[SignalCheckColour]'].value || !form.elements['newMonitor[SignalCheckColour]'].value.match( /^[#0-9a-zA-Z]+$/ ) ) + errors[errors.length] = ""; + if ( !form.elements['newMonitor[WebColour]'].value || !form.elements['newMonitor[WebColour]'].value.match( /^[#0-9a-zA-Z]+$/ ) ) + errors[errors.length] = ""; + } - if ( !form.elements['newMonitor[Colours]'].value || (parseInt(form.elements['newMonitor[Colours]'].value) != 1 && parseInt(form.elements['newMonitor[Colours]'].value) != 3 && parseInt(form.elements['newMonitor[Colours]'].value) != 4 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[Width]'].value || !(parseInt(form.elements['newMonitor[Width]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[Height]'].value || !(parseInt(form.elements['newMonitor[Height]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[LabelX]'].value || !(parseInt(form.elements['newMonitor[LabelX]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[LabelY]'].value || !(parseInt(form.elements['newMonitor[LabelY]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[ImageBufferCount]'].value || !(parseInt(form.elements['newMonitor[ImageBufferCount]'].value) >= 10 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[WarmupCount]'].value || !(parseInt(form.elements['newMonitor[WarmupCount]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[PreEventCount]'].value || !(parseInt(form.elements['newMonitor[PreEventCount]'].value) >= 0 ) || (parseInt(form.elements['newMonitor[PreEventCount]'].value) > parseInt(form.elements['newMonitor[ImageBufferCount]'].value)) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[PostEventCount]'].value || !(parseInt(form.elements['newMonitor[PostEventCount]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[StreamReplayBuffer]'].value || !(parseInt(form.elements['newMonitor[StreamReplayBuffer]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[AlarmFrameCount]'].value || !(parseInt(form.elements['newMonitor[AlarmFrameCount]'].value) > 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[SectionLength]'].value || !(parseInt(form.elements['newMonitor[SectionLength]'].value) >= 30 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[AnalysisUpdateDelay]'].value || !(parseInt(form.elements['newMonitor[AnalysisUpdateDelay]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[FPSReportInterval]'].value || !(parseInt(form.elements['newMonitor[FPSReportInterval]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[FrameSkip]'].value || !(parseInt(form.elements['newMonitor[FrameSkip]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[MotionFrameSkip]'].value || !(parseInt(form.elements['newMonitor[MotionFrameSkip]'].value) >= 0 ) ) - errors[errors.length] = ""; - if ( form.elements['newMonitor[Type]'].value == 'Local' ) - if ( !form.elements['newMonitor[SignalCheckColour]'].value || !form.elements['newMonitor[SignalCheckColour]'].value.match( /^[#0-9a-zA-Z]+$/ ) ) - errors[errors.length] = ""; - if ( !form.elements['newMonitor[WebColour]'].value || !form.elements['newMonitor[WebColour]'].value.match( /^[#0-9a-zA-Z]+$/ ) ) - errors[errors.length] = ""; if ( errors.length ) { alert( errors.join( "\n" ) ); diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 152fdcd9b..b9dc84f00 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -12,6 +12,15 @@ function Monitor( monitorData ) { if ( auth_hash ) this.streamCmdParms += '&auth='+auth_hash; this.streamCmdTimer = null; + this.type = monitorData.type; + this.refresh = monitorData.refresh; + this.start = function( delay ) { + if ( this.streamCmdQuery ) + this.streamCmdTimer = this.streamCmdQuery.delay( delay, this ); + else + console.log("No streamCmdQuery"); + }; + this.setStateClass = function( element, stateClass ) { if ( !element.hasClass( stateClass ) ) { @@ -68,7 +77,7 @@ function Monitor( monitorData ) { else stateClass = "idle"; - if ( !COMPACT_MONTAGE ) { + if ( (!COMPACT_MONTAGE) && (this.type != 'WebSite') ) { $('fpsValue'+this.id).set( 'text', this.status.fps ); $('stateValue'+this.id).set( 'text', stateStrings[this.alarmState] ); this.setStateClass( $('monitorState'+this.id), stateClass ); @@ -137,22 +146,26 @@ function Monitor( monitorData ) { this.streamCmdReq.cancel(); } //console.log("Starting CmdQuery for " + this.connKey ); - this.streamCmdReq.send( this.streamCmdParms+"&command="+CMD_QUERY ); + if ( this.type != 'WebSite' ) { + this.streamCmdReq.send( this.streamCmdParms+"&command="+CMD_QUERY ); + } }; - this.streamCmdReq = new Request.JSON( { - url: this.server_url, - method: 'get', - timeout: 1000+AJAX_TIMEOUT, - onSuccess: this.getStreamCmdResponse.bind( this ), - onTimeout: this.streamCmdQuery.bind( this, true ), - onError: this.onError.bind(this), - onFailure: this.onFailure.bind(this), - link: 'cancel' - } ); + if ( this.type != 'WebSite' ) { + this.streamCmdReq = new Request.JSON( { + url: this.server_url, + method: 'get', + timeout: 1000+AJAX_TIMEOUT, + onSuccess: this.getStreamCmdResponse.bind( this ), + onTimeout: this.streamCmdQuery.bind( this, true ), + onError: this.onError.bind(this), + onFailure: this.onFailure.bind(this), + link: 'cancel' + } ); + console.log("queueing for " + this.id + " " + this.connKey ); + requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq ); + } - console.log("queueing for " + this.id + " " + this.connKey ); - requestQueue.addRequest( "cmdReq"+this.id, this.streamCmdReq ); } function selectLayout( element ) { @@ -378,15 +391,26 @@ function cancel_layout(button) { selectLayout('#zmMontageLayout'); } +function reloadWebSite(ndx) { + document.getElementById('imageFeed'+ndx).innerHTML = document.getElementById('imageFeed'+ndx).innerHTML; +} + var monitors = new Array(); function initPage() { -console.log("initPage"); for ( var i = 0; i < monitorData.length; i++ ) { monitors[i] = new Monitor(monitorData[i]); + var delay = Math.round( (Math.random()+0.5)*statusRefreshTimeout ); + var interval = monitors[i].refresh; + monitors[i].start( delay ); + if ( monitors[i].type == 'WebSite' && interval > 0 ) { + setInterval(reloadWebSite, interval*1000, i); + } } selectLayout('#zmMontageLayout'); for ( var i = 0; i < monitorData.length; i++ ) { + if ( monitors[i].type == 'WebSite' ) + continue; var delay = Math.round( (Math.random()+0.75)*statusRefreshTimeout ); console.log("Delay for monitor " + monitorData[i].id + " is " + delay ); monitors[i].streamCmdQuery.delay( delay, monitors[i] ); diff --git a/web/skins/classic/views/js/montage.js.php b/web/skins/classic/views/js/montage.js.php index 7f749d47a..1b67d764b 100644 --- a/web/skins/classic/views/js/montage.js.php +++ b/web/skins/classic/views/js/montage.js.php @@ -36,7 +36,9 @@ monitorData[monitorData.length] = { 'width': Width() ?>, 'height':Height() ?>, 'server_url': 'Server()->Url().$_SERVER['PHP_SELF'] ?>', - 'onclick': function(){createPopup( '?view=watch&mid=Id() ?>', 'zmWatchId() ?>', 'watch', Width(), $monitor->PopupScale() ); ?>, Height(), $monitor->PopupScale() ); ?> );} + 'onclick': function(){createPopup( '?view=watch&mid=Id() ?>', 'zmWatchId() ?>', 'watch', Width(), $monitor->PopupScale() ); ?>, Height(), $monitor->PopupScale() ); ?> );}, + 'type': 'Type() ?>', + 'refresh': 'Refresh() ?>' }; 0 ) { + var myReload = setInterval(reloadWebSite, monitorRefresh*1000); } } diff --git a/web/skins/classic/views/js/watch.js.php b/web/skins/classic/views/js/watch.js.php index 79b48de94..c446703cf 100644 --- a/web/skins/classic/views/js/watch.js.php +++ b/web/skins/classic/views/js/watch.js.php @@ -49,6 +49,8 @@ var monitorId = Id() ?>; var monitorWidth = Width() ?>; var monitorHeight = Height() ?>; var monitorUrl = 'Server()->Url() . ( ZM_MIN_STREAMING_PORT ? ':'. (ZM_MIN_STREAMING_PORT+$monitor->Id()) : '' ) ) ?>'; +var monitorType = 'Type() ) ?>'; +var monitorRefresh = 'Refresh() ) ?>'; var scale = ''; diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 424748410..43998c174 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -26,23 +26,6 @@ if ( !canView( 'Monitors' ) ) { return; } -$tabs = array(); -$tabs['general'] = translate('General'); -$tabs['source'] = translate('Source'); -$tabs['storage'] = translate('Storage'); -$tabs['timestamp'] = translate('Timestamp'); -$tabs['buffers'] = translate('Buffers'); -if ( ZM_OPT_CONTROL && canView( 'Control' ) ) - $tabs['control'] = translate('Control'); -if ( ZM_OPT_X10 ) - $tabs['x10'] = translate('X10'); -$tabs['misc'] = translate('Misc'); - -if ( isset($_REQUEST['tab']) ) - $tab = validHtmlStr($_REQUEST['tab']); -else - $tab = 'general'; - $Server = null; if ( defined( 'ZM_SERVER_ID' ) ) { $Server = dbFetchOne( 'SELECT * FROM Servers WHERE Id=?', NULL, array( ZM_SERVER_ID ) ); @@ -142,6 +125,7 @@ if ( ! $monitor ) { 'V4LCapturesPerFrame' => 1, 'ServerId' => 'auto', 'StorageId' => '1', + 'Refresh' => '', ) ); } # end if $_REQUEST['dupID'] } # end if $_REQUEST['mid'] @@ -212,7 +196,8 @@ $sourceTypes = array( 'Ffmpeg' => translate('Ffmpeg'), 'Libvlc' => translate('Libvlc'), 'cURL' => 'cURL (HTTP(S) only)', - 'NVSocket' => translate('NVSocket') + 'WebSite'=> 'Web Site', + 'NVSocket' => translate('NVSocket') ); if ( !ZM_HAS_V4L ) unset($sourceTypes['Local']); @@ -507,6 +492,25 @@ if ( canEdit( 'Monitors' ) ) {
    Type() != 'WebSite' ) { + $tabs['storage'] = translate('Storage'); + $tabs['timestamp'] = translate('Timestamp'); + $tabs['buffers'] = translate('Buffers'); + if ( ZM_OPT_CONTROL && canView( 'Control' ) ) + $tabs['control'] = translate('Control'); + if ( ZM_OPT_X10 ) + $tabs['x10'] = translate('X10'); + $tabs['misc'] = translate('Misc'); +} + +if ( isset($_REQUEST['tab']) ) + $tab = validHtmlStr($_REQUEST['tab']); +else + $tab = 'general'; + foreach ( $tabs as $name=>$value ) { if ( $tab == $name ) { ?> @@ -578,7 +582,7 @@ if ( $tab != 'source' || ($monitor->Type()!= 'Ffmpeg' && $monitor->Type()!= 'Lib Type()!= 'Remote' && $monitor->Type()!= 'File' && $monitor->Type()!= 'Ffmpeg' && $monitor->Type()!= 'Libvlc' && $monitor->Type()!= 'cURL') ) { +if ( $tab != 'source' || ($monitor->Type()!= 'Remote' && $monitor->Type()!= 'File' && $monitor->Type()!= 'Ffmpeg' && $monitor->Type()!= 'Libvlc' && $monitor->Type()!= 'cURL' && $monitor->Type() != 'WebSite') ) { ?> @@ -708,6 +712,9 @@ switch ( $tab ) { ?> Enabled() ) { ?> checked="checked"/> +Type != 'WebSite' ) { +?> @@ -797,6 +804,7 @@ echo htmlOptions(Group::get_dropdown_options( ), $monitor->GroupIds() ); ?> +Type() == 'WebSite' ) { +?> + + () + () + Type() == 'Ffmpeg' || $monitor->Type() == 'Libvlc' ) { ?> @@ -871,7 +886,7 @@ include('_monitor_source_nvsocket.php');  (Type()), 'zmOptionHelp', 'optionhelp', '?' ) ?>) Type() != 'NVSocket' ) { +if ( $monitor->Type() != 'NVSocket' && $monitor->Type() != 'WebSite' ) { ?> Colours() ); ?> @@ -885,7 +900,7 @@ if ( $monitor->Type() == 'Local' ) { ?> Type() != 'WebSite' ) { ?> Type() == "WebSite" ) { + echo getWebSiteUrl( 'liveStream'.$monitor->Id(), $monitor->Path(), reScale( $monitor->Width(), $scale ), reScale( $monitor->Height(), $scale ), $monitor->Name() ); + } else { + echo getStreamHTML( $monitor, $monitor_options ); + } if ( $showZones ) { $height = null; $width = null; @@ -255,7 +259,7 @@ foreach ( $monitors as $monitor ) {
Type() != 'WebSite') ) { ?>
 -  fps
Controllable() && canView( 'Control' ) ); +$showPtzControls = ( ZM_OPT_CONTROL && $monitor->Controllable() && canView('Control') && $monitor->Type() != 'WebSite' ); if ( isset( $_REQUEST['scale'] ) ) { $scale = validInt($_REQUEST['scale']); @@ -80,6 +80,7 @@ if ( canView( 'Control' ) && $monitor->Type() == 'Local' ) {
$scale) ); ?>
+Type() != 'WebSite' ) { ?>
@@ -119,7 +120,7 @@ if ( $streamMode == 'jpeg' ) { ?>
+Type() != 'WebSite' ?> Type() != 'WebSite' ) { ?>