From 0a24f8550affc3fd591e656303ea585dfcf58712 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Mon, 23 Apr 2018 08:59:31 -0500 Subject: [PATCH 01/26] Update path to zmtriggers.sql ZM_PATH_DATA is not configured as a make macro. Use @PKGDATADIR@ instead. --- db/zm_create.sql.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 9c237b8a0..0dad55059 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -891,7 +891,7 @@ INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('4 Wide', '{ "default":{ INSERT INTO MontageLayouts (`Name`,`Positions`) VALUES ('5 Wide', '{ "default":{"float":"left", "width":"19%","left":"0px","right":"0px","top":"0px","bottom":"0px"} }' ); -- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts. -source @ZM_PATH_DATA@/db/triggers.sql +source @PKGDATADIR@/db/triggers.sql -- -- Apply the initial configuration -- From a68ed3a3051c826cc844cc07ab1e8e1078fff1ef Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 23 Apr 2018 11:38:25 -0400 Subject: [PATCH 02/26] watchfeed => watch. Fixes #2086 --- scripts/zmfilter.pl.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 3bde3e859..faa9814b9 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -646,8 +646,8 @@ sub substituteTags { $text =~ s/%MEM%/$Monitor->{MonthEvents}/g; $text =~ s/%MEA%/$Monitor->{ArchivedEvents}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; - $text =~ s/%MPS%/$url?view=watchfeed&mid=$Event->{MonitorId}&mode=stream/g; - $text =~ s/%MPI%/$url?view=watchfeed&mid=$Event->{MonitorId}&mode=still/g; + $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; + $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; $text =~ s/%EP%/$url?view=event&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; $text =~ s/%EPI%/$url?view=event&mode=still&mid=$Event->{MonitorId}&eid=$Event->{Id}/g; From de149c1328c78cfee57d11e6754fc883aeaf8ae0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 24 Apr 2018 08:48:35 -0700 Subject: [PATCH 03/26] spacing --- web/includes/Storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 352a2249d..57f518a65 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -13,7 +13,7 @@ class Storage { if ( ! $row ) { Error("Unable to load Storage record for Id=" . $IdOrRow ); } - } elseif ( is_array( $IdOrRow ) ) { + } else if ( is_array($IdOrRow) ) { $row = $IdOrRow; } } From 678503b992d296552e255bdcbd081850e0dc2d73 Mon Sep 17 00:00:00 2001 From: Andy Bauer Date: Tue, 24 Apr 2018 12:16:19 -0500 Subject: [PATCH 04/26] fix ftbs on el7 --- src/zm_eventstream.cpp | 1 + src/zm_ffmpeg.cpp | 1 + src/zm_monitor.cpp | 1 + src/zm_videostore.cpp | 2 +- src/zms.cpp | 1 + src/zmu.cpp | 1 + 6 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 989d2b14d..2578d91d4 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "zm.h" #include "zm_db.h" diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index b4d7e323a..b0d11948a 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -16,6 +16,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include "zm_ffmpeg.h" #include "zm_image.h" diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 601f9f424..b49609d1e 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "zm.h" #include "zm_db.h" diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 6ca8f3f96..3500d716a 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -20,7 +20,7 @@ #define __STDC_FORMAT_MACROS 1 -#include +#include #include #include diff --git a/src/zms.cpp b/src/zms.cpp index 63cbfff5f..e8d20f72f 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "zm.h" #include "zm_db.h" diff --git a/src/zmu.cpp b/src/zmu.cpp index 640b9a342..266f91704 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -87,6 +87,7 @@ Options for use with monitors: */ #include +#include #include "zm.h" #include "zm_db.h" From 426b3cf9d4fea7e65e2cc079524f86cde9984463 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Tue, 24 Apr 2018 12:43:45 -0500 Subject: [PATCH 05/26] Update startpackpack.sh --- utils/packpack/startpackpack.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 91e671f2e..3794e4645 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -105,10 +105,11 @@ commonprep () { fi # fix 32bit rpm builds - patch --dry-run --silent -f -p1 < utils/packpack/setarch.patch - if [ $? -eq 0 ]; then - patch -p1 < utils/packpack/setarch.patch - fi + # FIXME: breaks arm rpm builds + #patch --dry-run --silent -f -p1 < utils/packpack/setarch.patch + #if [ $? -eq 0 ]; then + # patch -p1 < utils/packpack/setarch.patch + #fi # The rpm specfile requires we download each submodule as a tarball then manually move it into place # Might as well do this for Debian as well, rather than git submodule init From 1907b18ac24c538ffa8d326fe3987dc941739781 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 25 Apr 2018 12:36:18 -0400 Subject: [PATCH 06/26] fix libnumber-bytes-human => libnumber-bytes-human-perl --- distros/ubuntu1204/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/ubuntu1204/control b/distros/ubuntu1204/control index 8c9bf2e61..febcb9435 100644 --- a/distros/ubuntu1204/control +++ b/distros/ubuntu1204/control @@ -54,7 +54,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,liburi-encode-perl ,libwww-perl ,libdata-uuid-perl - ,libnumber-bytes-human + ,libnumber-bytes-human-perl ,libfile-slurp-perl ,mysql-client | virtual-mysql-client ,perl-modules From 636d0caf30a4d64d3fb800f5db7a8e4d8da76951 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 25 Apr 2018 10:28:19 -0700 Subject: [PATCH 07/26] handle no swap --- web/skins/classic/views/options.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 697abd6e5..42da8ae70 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -234,7 +234,7 @@ foreach( array_map( 'basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI +"> From f2073c958f27716eeb266b2545b209848f6f6eac Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 25 Apr 2018 10:33:42 -0700 Subject: [PATCH 08/26] more debug --- scripts/zmdc.pl.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 00c796a53..ab6d4e4c6 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -530,7 +530,7 @@ sub check_for_processes_to_kill { foreach my $command ( %terminating_processes ) { my $process = $cmd_hash{$command}; Debug("Have process $command at pid $$process{pid} $$process{term_sent_at}"); - if ( $$process{term_sent_at} - time > KILL_DELAY ) { + if ( $$process{term_sent_at} and ( $$process{term_sent_at} - time > KILL_DELAY ) ) { dPrint(ZoneMinder::Logger::WARNING, "'$$process{command}' has not stopped at " .strftime('%y/%m/%d %H:%M:%S', localtime()) .' after ' . KILL_DELAY . ' seconds.' From 00e82fb75108981eb8fa637222c17ca304ec0321 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 25 Apr 2018 13:04:46 -0700 Subject: [PATCH 09/26] Implement MonitoServerId,StorageServerId,FilterServerID in Filters --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 11 +++++--- scripts/zmdc.pl.in | 6 ++--- web/includes/database.php | 2 +- web/includes/functions.php | 10 +++++++ web/lang/en_gb.php | 4 ++- web/skins/classic/views/filter.php | 7 +++-- web/skins/classic/views/js/filter.js | 29 +++++++++------------ 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 065a2339b..b78b801f1 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -160,9 +160,12 @@ sub Sql { if ( $term->{attr} =~ /^Monitor/ ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= 'M.'.$temp_attr_name; - } elsif ( $term->{attr} =~ /^Server/ ) { + } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { + $self->{Sql} .= 'M.'.$term->{attr}; + } elsif ( $term->{attr} eq 'StorageServerId' ) { $self->{Sql} .= 'S.'.$term->{attr}; - + } elsif ( $term->{attr} eq 'FilterServerId' ) { + $self->{Sql} .= $Config{ZM_SERVER_ID}; # StartTime options } elsif ( $term->{attr} eq 'DateTime' ) { $self->{Sql} .= 'E.StartTime'; @@ -208,7 +211,7 @@ sub Sql { foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { if ( $term->{attr} =~ /^MonitorName/ ) { $value = "'$temp_value'"; - } elsif ( $term->{attr} eq 'ServerId' ) { + } elsif ( $term->{attr} =~ /ServerId/) { Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); if ( $temp_value eq 'ZM_SERVER_ID' ) { $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; @@ -241,7 +244,7 @@ sub Sql { } $value = "'$value'"; } - } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { + } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { if ( $temp_value eq 'NULL' ) { $value = $temp_value; } else { diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index ab6d4e4c6..d7627ac42 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -346,14 +346,13 @@ sub run { } close(CLIENT); } else { - Fatal('Bogus descriptor'); + Error('Bogus descriptor'); } } elsif ( $nfound < 0 ) { if ( $! == EINTR ) { # Dead child, will be reaped #print( "Probable dead child\n" ); # See if it needs to start up again - restartPending(); } elsif ( $! == EPIPE ) { Error("Can't select: $!"); } else { @@ -361,12 +360,13 @@ sub run { } } else { #print( "Select timed out\n" ); - restartPending(); } + restartPending(); check_for_processes_to_kill(); } # end while + dPrint(ZoneMinder::Logger::INFO, 'Server exiting at ' .strftime( '%y/%m/%d %H:%M:%S', localtime() ) ."\n" diff --git a/web/includes/database.php b/web/includes/database.php index 82fc3c765..2c3a818c1 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -133,7 +133,7 @@ function dbQuery( $sql, $params=NULL ) { } } else { if ( defined('ZM_DB_DEBUG') ) { - Logger::Debug("SQL: $sql values:" . $params?implode(',',$params):'' ); + Logger::Debug("SQL: $sql values:" . ($params?implode(',',$params):'') ); } $result = $dbConn->query($sql); } diff --git a/web/includes/functions.php b/web/includes/functions.php index 2ae484784..1bca995b4 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -1004,8 +1004,15 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { $filter['sql'] .= 'M.'.preg_replace('/^Monitor/', '', $terms[$i]['attr']); break; case 'ServerId': + case 'MonitorServerId': $filter['sql'] .= 'M.ServerId'; break; + case 'StorageServerId': + $filter['sql'] .= 'S.ServerId'; + break; + case 'FilterServerId': + $filter['sql'] .= ZM_SERVER_ID; + break; # Unspecified start or end, so assume start, this is to support legacy filters case 'DateTime': $filter['sql'] .= 'E.StartTime'; @@ -1100,6 +1107,9 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { case 'Notes': $value = dbEscape($value); break; + case 'MonitorServerId': + case 'FilterServerId': + case 'StorageServerId': case 'ServerId': if ( $value == 'ZM_SERVER_ID' ) { $value = ZM_SERVER_ID; diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 6e97eaab8..786d38dff 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -130,7 +130,9 @@ $SLANG = array( 'AttrMonitorId' => 'Monitor Id', 'AttrMonitorName' => 'Monitor Name', 'AttrStorageArea' => 'Storage Area', - 'AttrServer' => 'Server', + 'AttrFilterServer' => 'Server Filter is Running On', + 'AttrMonitorServer' => 'Server Monitor is Running On', + 'AttrStorageServer' => 'Server Hosting Storage', 'AttrStateId' => 'Run State', 'AttrName' => 'Name', 'AttrNotes' => 'Notes', diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 5c639cecc..2f82fcd49 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -101,7 +101,10 @@ $attrTypes = array( 'DiskSpace' => translate('AttrDiskSpace'), 'SystemLoad' => translate('AttrSystemLoad'), 'StorageId' => translate('AttrStorageArea'), - 'ServerId' => translate('AttrServer'), + 'ServerId' => translate('AttrMonitorServer'), + 'FilterServerId' => translate('AttrFilterServer'), + 'MonitorServerId' => translate('AttrMonitorServer'), + 'StorageServerId' => translate('AttrStorageServer'), 'StateId' => translate('AttrStateId'), ); @@ -268,7 +271,7 @@ for ( $i=0; $i < count($terms); $i++ ) { diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index 4df73997e..f752dbff7 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -142,7 +142,9 @@ function parseRows (rows) { inputTds.eq(6).find(':input[value="-"]').prop('disabled', false); } - if (inputTds.eq(2).children().val() == "Archived") { //Archived types + var attr = inputTds.eq(2).children().val(); + + if ( attr == "Archived") { //Archived types inputTds.eq(3).html('equal to'); let archiveSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (let i = 0; i < archiveTypes.length; i++) { @@ -151,7 +153,7 @@ function parseRows (rows) { let archiveVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(archiveSelect).children().val(archiveVal).chosen({width: "101%"}); - } else if (inputTds.eq(2).children().val().indexOf('Weekday') >= 0) { //Weekday selection + } else if ( attr.indexOf('Weekday') >= 0 ) { //Weekday selection let weekdaySelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (let i = 0; i < weekdays.length; i++) { weekdaySelect.append(''); @@ -159,7 +161,7 @@ function parseRows (rows) { let weekdayVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(weekdaySelect).children().val(weekdayVal).chosen({width: "101%"}); - } else if (inputTds.eq(2).children().val() == 'StateId') { //Run state + } else if ( attr == 'StateId' ) { //Run state let stateSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (let key in states) { stateSelect.append(''); @@ -167,8 +169,7 @@ function parseRows (rows) { let stateVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(stateSelect).children().val(stateVal).chosen({width: "101%"}); - - } else if (inputTds.eq(2).children().val() == 'ServerId') { //Select Server + } else if ( attr == 'ServerId' || attr == 'MonitorServerId' || attr == 'StorageServerId' || attr == 'FilterServerId' ) { //Select Server let serverSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (let key in servers) { serverSelect.append(''); @@ -176,21 +177,15 @@ function parseRows (rows) { let serverVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(serverSelect).children().val(serverVal).chosen({width: "101%"}); - } else if (inputTds.eq(2).children().val() == 'StorageId') { //Choose by storagearea + } else if ( attr == 'StorageId' ) { //Choose by storagearea let storageSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for ( key in storageareas ) { -console.log(key + ' ' + storageareas[key]); storageSelect.append(''); -} -/* - for (let i=0; i < storageareas.length; i++) { - storageSelect.append(''); } -*/ let storageVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(storageSelect).children().val(storageVal).chosen({width: "101%"}); - } else if (inputTds.eq(2).children().val() == 'MonitorName') { //Monitor names + } else if ( attr == 'MonitorName' ) { //Monitor names let monitorSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (let key in monitors) { monitorSelect.append(''); @@ -208,15 +203,15 @@ console.log(key + ' ' + storageareas[key]); let textVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(textInput).children().val(textVal); } - if (inputTds.eq(2).children().val().endsWith('DateTime')) { //Start/End DateTime + if ( attr.endsWith('DateTime') ) { //Start/End DateTime inputTds.eq(4).children().datetimepicker({timeFormat: "HH:mm:ss", dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false}); - } else if (inputTds.eq(2).children().val().endsWith('Date')) { //Start/End Date + } else if ( attr.endsWith('Date') ) { //Start/End Date inputTds.eq(4).children().datepicker({dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false}); - } else if (inputTds.eq(2).children().val().endsWith('Time')) { //Start/End Time + } else if ( attr.endsWith('Time')) { //Start/End Time inputTds.eq(4).children().timepicker({timeFormat: "HH:mm:ss", constrainInput: false}); } - let attr = inputTds.find("[name$='attr\\]']") // Set attr list id and name + attr = inputTds.find("[name$='attr\\]']") // Set attr list id and name let term = attr.attr('name').split(/[[\]]{1,2}/); term.length--; term.shift(); From 86b2f6a12ed57a8e49b2427e9841c59bc6cd45f0 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Thu, 26 Apr 2018 16:18:36 -0500 Subject: [PATCH 10/26] 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' ) { ?>
From 7e88d2885735bc665193c4648c5afcc965319698 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 26 Apr 2018 21:58:19 -0400 Subject: [PATCH 11/26] bump version to .44 --- version | 2 +- web/api/app/Plugin/CakePHP-Enum-Behavior | 2 +- web/api/app/Plugin/Crud | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/version b/version index 92522fa6a..856307b22 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.31.43 +1.31.44 diff --git a/web/api/app/Plugin/CakePHP-Enum-Behavior b/web/api/app/Plugin/CakePHP-Enum-Behavior index ea90c0cd7..ca91b87fd 160000 --- a/web/api/app/Plugin/CakePHP-Enum-Behavior +++ b/web/api/app/Plugin/CakePHP-Enum-Behavior @@ -1 +1 @@ -Subproject commit ea90c0cd7f6e24333a90885e563b5d30b793db29 +Subproject commit ca91b87fda8e006e4fca2ed870f24f9a29c2905d diff --git a/web/api/app/Plugin/Crud b/web/api/app/Plugin/Crud index 0bd63fb46..1351dde6b 160000 --- a/web/api/app/Plugin/Crud +++ b/web/api/app/Plugin/Crud @@ -1 +1 @@ -Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef +Subproject commit 1351dde6b4c75b215099ae8bcf5a21d6c6e10298 From d5fedd54702d87b51c1639ae5b26c21e5375ef82 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 27 Apr 2018 11:25:01 -0400 Subject: [PATCH 12/26] Update to add cache dir config and api configs --- utils/generate_apache_config.pl | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/utils/generate_apache_config.pl b/utils/generate_apache_config.pl index 9d9e94e10..664b4c819 100755 --- a/utils/generate_apache_config.pl +++ b/utils/generate_apache_config.pl @@ -55,6 +55,22 @@ DocumentRoot /usr/share/zoneminder/www qq` ErrorLog $$opts{error_log} +Alias /zm/cache "/var/cache/zoneminder/cache" +Alias /cache "/var/cache/zoneminder/cache" + + Options -Indexes +FollowSymLinks + AllowOverride None + + # Apache 2.4 + Require all granted + + + # Apache 2.2 + Order deny,allow + Allow from all + + + ScriptAlias /zm/cgi-bin/ /usr/lib/zoneminder/cgi-bin/ ScriptAlias /cgi-bin/ /usr/lib/zoneminder/cgi-bin/ @@ -67,7 +83,6 @@ ScriptAlias /cgi-bin/ /usr/lib/zoneminder/cgi-bin/ Alias /zm /usr/share/zoneminder/www - php_flag register_globals off Options +Indexes +FollowSymLinks @@ -81,6 +96,32 @@ Alias /zm /usr/share/zoneminder/www Allow from all + +# For better visibility, the following directives have been migrated from the +# default .htaccess files included with the CakePHP project. +# Parameters not set here are inherited from the parent directive above. + + RewriteEngine on + RewriteRule ^$ app/webroot/ [L] + RewriteRule (.*) app/webroot/$1 [L] + RewriteBase /zm/api + + + + RewriteEngine on + RewriteRule ^$ webroot/ [L] + RewriteRule (.*) webroot/$1 [L] + RewriteBase /zm/api + + + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + RewriteBase /zm/api + + `; if ( $$opts{protocol} eq 'https' ) { $template .= qq` From a4da624f4c8ccf2156bd7680c4e117cd35761d82 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 27 Apr 2018 13:20:38 -0700 Subject: [PATCH 13/26] break out of loops when zm-terminate is set --- scripts/zmfilter.pl.in | 2 +- src/zm_event.cpp | 23 +++++++++++-------- src/zm_logger.cpp | 2 +- src/zm_monitor.cpp | 2 +- web/api/app/Plugin/Crud | 2 +- web/includes/Event.php | 5 ++++ .../classic/views/report_event_audit.php | 10 ++++---- 7 files changed, 28 insertions(+), 18 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index faa9814b9..97de3490f 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -94,7 +94,7 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) ; -logInit(); +logInit($filter_id?(id=>'zmfilter_'.$filter_id.'.log'):()); sub HupHandler { Info("Received HUP, reloading"); &ZoneMinder::Logger::logHupHandler(); diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 2008132ef..77a229409 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -257,7 +257,7 @@ Event::~Event() { "UPDATE Events SET Name='%s %" PRIu64 "', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' WHERE Id = %" PRIu64, monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id ); db_mutex.lock(); - while ( mysql_query(&dbconn, sql) ) { + while ( mysql_query(&dbconn, sql) && !zm_terminate ) { Error("Can't update event: %s reason: %s", sql, mysql_error(&dbconn)); db_mutex.unlock(); sleep(1); @@ -422,16 +422,16 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) { #else static char escapedNotes[ZM_SQL_MED_BUFSIZ]; - mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() ); + mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length()); - snprintf( sql, sizeof(sql), "UPDATE Events SET Notes = '%s' WHERE Id = %" PRIu64, escapedNotes, id ); + snprintf(sql, sizeof(sql), "UPDATE Events SET Notes = '%s' WHERE Id = %" PRIu64, escapedNotes, id); db_mutex.lock(); - if ( mysql_query( &dbconn, sql ) ) { - Error( "Can't insert event: %s", mysql_error( &dbconn ) ); + if ( mysql_query(&dbconn, sql) ) { + Error("Can't insert event: %s", mysql_error(&dbconn)); } db_mutex.unlock(); #endif - } + } // end if update } void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) { @@ -485,7 +485,7 @@ void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, st snprintf( sql+sql_len, sizeof(sql)-sql_len, "( %" PRIu64 ", %d, from_unixtime(%ld), %s%ld.%02ld ), ", id, frames, timestamps[i]->tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec ); frameCount++; - } + } // end foreach frame if ( frameCount ) { Debug( 1, "Adding %d/%d frames to DB", frameCount, n_frames ); @@ -543,7 +543,10 @@ Debug(3, "Writing video"); Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] ); static char sql[ZM_SQL_MED_BUFSIZ]; - snprintf(sql, sizeof(sql), "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score ) values ( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score); + snprintf(sql, sizeof(sql), + "INSERT INTO Frames ( EventId, FrameId, Type, TimeStamp, Delta, Score )" + " VALUES ( %" PRIu64 ", %d, '%s', from_unixtime( %ld ), %s%ld.%02ld, %d )", + id, frames, frame_type_names[frame_type], timestamp.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, score); db_mutex.lock(); if ( mysql_query(&dbconn, sql) ) { Error("Can't insert frame: %s", mysql_error(&dbconn)); @@ -556,7 +559,7 @@ Debug(3, "Writing video"); // We are writing a Bulk frame if ( frame_type == BULK ) { - snprintf( sql, sizeof(sql), + snprintf(sql, sizeof(sql), "UPDATE Events SET Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d where Id = %" PRIu64, ( delta_time.positive?"":"-" ), delta_time.sec, delta_time.fsec, @@ -568,7 +571,7 @@ Debug(3, "Writing video"); id ); db_mutex.lock(); - while ( mysql_query(&dbconn, sql) ) { + while ( mysql_query(&dbconn, sql) && !zm_terminate ) { Error("Can't update event: %s", mysql_error(&dbconn)); db_mutex.unlock(); sleep(1); diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 7c063f0f9..3e30d2182 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -543,7 +543,7 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co mysql_real_escape_string( &dbconn, escapedString, syslogStart, strlen(syslogStart) ); snprintf( sql, sizeof(sql), "insert into Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) values ( %ld.%06ld, '%s', %d, %d, %d, '%s', '%s', '%s', %d )", timeVal.tv_sec, timeVal.tv_usec, mId.c_str(), staticConfig.SERVER_ID, tid, level, classString, escapedString, file, line ); - if (mysql_query(&dbconn, sql)) { + if ( mysql_query(&dbconn, sql) ) { Level tempDatabaseLevel = mDatabaseLevel; databaseLevel(NOLOG); Error("Can't insert log entry: sql(%s) error(%s)", sql, mysql_error(&dbconn)); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 6c6208366..4b268c016 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1246,9 +1246,9 @@ bool Monitor::Analyse() { Info("%s: %d - Analysing at %.2f fps", name, image_count, new_fps); if ( fps != new_fps ) { fps = new_fps; + db_mutex.lock(); static char sql[ZM_SQL_SML_BUFSIZ]; snprintf(sql, sizeof(sql), "INSERT INTO Monitor_Status (MonitorId,AnalysisFPS) VALUES (%d, %.2lf) ON DUPLICATE KEY UPDATE AnalysisFPS = %.2lf", id, fps, fps); - db_mutex.lock(); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); } diff --git a/web/api/app/Plugin/Crud b/web/api/app/Plugin/Crud index 1351dde6b..0bd63fb46 160000 --- a/web/api/app/Plugin/Crud +++ b/web/api/app/Plugin/Crud @@ -1 +1 @@ -Subproject commit 1351dde6b4c75b215099ae8bcf5a21d6c6e10298 +Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef diff --git a/web/includes/Event.php b/web/includes/Event.php index 175163890..45662ef3b 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -451,6 +451,11 @@ class Event { $values[] = $this->{'Id'}; dbQuery( $sql, $values ); } + public function link_to($text=null) { + if ( !$text ) + $text = $this->{'Id'}; + return ''.$text.''; + } } # end class diff --git a/web/skins/classic/views/report_event_audit.php b/web/skins/classic/views/report_event_audit.php index 4da7b032c..85c8454b4 100644 --- a/web/skins/classic/views/report_event_audit.php +++ b/web/skins/classic/views/report_event_audit.php @@ -169,10 +169,12 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { ?> - - - - + + + + + + Date: Sat, 28 Apr 2018 12:32:21 -0400 Subject: [PATCH 14/26] remove extra spaces --- web/includes/Storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 38ba83bf9..5dc7016be 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -124,7 +124,7 @@ public static function find_all( $parameters = null, $options = null ) { return $usage; } public function disk_total_space() { - if ( ! array_key_exists( 'disk_total_space', $this ) ) { + if ( ! array_key_exists('disk_total_space', $this ) ) { $this->{'disk_total_space'} = disk_total_space( $this->Path() ); } return $this->{'disk_total_space'}; @@ -142,7 +142,7 @@ public static function find_all( $parameters = null, $options = null ) { } } else { $path = $this->Path(); - $used = disk_total_space( $path ) - disk_free_space( $path );; + $used = disk_total_space($path) - disk_free_space($path); } $this->{'DiskSpace'} = $used; } From abd52ebfb12d78f4a415fd661fa95f25668aec12 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 28 Apr 2018 12:38:10 -0400 Subject: [PATCH 15/26] Add DiskSpace column to storage listing --- web/skins/classic/views/options.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/web/skins/classic/views/options.php b/web/skins/classic/views/options.php index 697abd6e5..ef01003c5 100644 --- a/web/skins/classic/views/options.php +++ b/web/skins/classic/views/options.php @@ -265,21 +265,22 @@ foreach( array_map( 'basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI + - +'lower(Name)') ) as $Storage ) { ?> - - - - - + + + + + - + echo makePopupLink( '?view=storage&id='.$Storage->Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Name()), $canEdit ) ?> + From 5ebfae5f14b2eadde62fdeac485c6efc3ec32eb6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 09:51:26 -0400 Subject: [PATCH 16/26] fix warnings in building docs by using anonymous links (double underscore at the end) --- docs/faq.rst | 1 + docs/installationguide/dedicateddrive.rst | 2 +- docs/installationguide/redhat.rst | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index d6694b153..7735ff38f 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -71,6 +71,7 @@ The 1.2 at the start is basically adding 20% on top of the calculation to accoun The math breakdown for 4 cameras running at 1280x960 capture, 50 frame buffer, 24 bit color space: :: + 1280*960 = 1,228,800 (bytes) 1,228,800 * (3 bytes for 24 bit) = 3,686,400 (bytes) 3,686,400 * 50 = 184,320,000 (bytes) diff --git a/docs/installationguide/dedicateddrive.rst b/docs/installationguide/dedicateddrive.rst index 40db1ee93..f0dfadc28 100644 --- a/docs/installationguide/dedicateddrive.rst +++ b/docs/installationguide/dedicateddrive.rst @@ -14,7 +14,7 @@ If you are using an older version of ZoneMinder, please follow the legacy steps **Step 2:** Mount your dedicated drive, partition, or network share to the local filesystem in any folder of your choosing. We recommend you use systemd to manage the mount points. -Instructions on how to accomplish this can be found `here `_ and `here `_. +Instructions on how to accomplish this can be found `here `__ and `here `__. Note that bind mounting ZoneMinder's images folder is optional. Newer version of ZoneMinder write very little, if anything, to the images folder. Verify the dedicated drive, partition, or network share is successfully mounted before proceeding to the next step. diff --git a/docs/installationguide/redhat.rst b/docs/installationguide/redhat.rst index 327089380..12d9ed769 100644 --- a/docs/installationguide/redhat.rst +++ b/docs/installationguide/redhat.rst @@ -45,7 +45,7 @@ The following notes are based on real problems which have occurred by those who How to Install ZoneMinder ------------------------- -ZoneMinder releases are now being hosted at RPM Fusion. New users should navigate the `RPM Fusion site `_ then follow the instructions to enable that repo. RHEL/CentOS users must also navaigate to the `EPEL Site `_ and enable that repo as well. Once enabled, install ZoneMinder from the commandline: +ZoneMinder releases are now being hosted at RPM Fusion. New users should navigate the `RPM Fusion site `__ then follow the instructions to enable that repo. RHEL/CentOS users must also navaigate to the `EPEL Site `_ and enable that repo as well. Once enabled, install ZoneMinder from the commandline: :: @@ -65,9 +65,9 @@ The feedback we get from those who use these development packages is extremely h How to Change from Zmrepo to RPM Fusion --------------------------------------- -As mentioned above, the place to get the latest ZoneMinder release is now `RPM Fusion `_. If you are currently using ZoneMinder release packages from Zmrepo, then the following steps will change you over to RPM Fusion: +As mentioned above, the place to get the latest ZoneMinder release is now `RPM Fusion `__. If you are currently using ZoneMinder release packages from Zmrepo, then the following steps will change you over to RPM Fusion: -- Navigate to the `RPM Fusion site `_ and enable RPM Fusion on your system +- Navigate to the `RPM Fusion site `__ and enable RPM Fusion on your system - Now issue the following from the command line: :: @@ -123,7 +123,7 @@ Your build environment is now set up. Build from SRPM *************** -To continue, you need a ZoneMinder SRPM. If you wish to rebuild a ZoneMinder release, then browse the `RPM Fusion site `_. If instead you wish to rebuild the latest source rpm from our master branch then browse the `Zmrepo site `_. +To continue, you need a ZoneMinder SRPM. If you wish to rebuild a ZoneMinder release, then browse the `RPM Fusion site `__. If instead you wish to rebuild the latest source rpm from our master branch then browse the `Zmrepo site `_. For this example, I'll use one of the source rpms from zmrepo: From 3f05d46012fdb1f10b7858fa4d1eb0e70a5777c2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 07:09:23 -0700 Subject: [PATCH 17/26] Merge zmdc.pl fixes. We can now handle database disconnects gracefully. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 2 + scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 4 +- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 39 ++++++----- scripts/zmdc.pl.in | 70 +++++++++++++------ .../classic/views/report_event_audit.php | 24 ++++++- 5 files changed, 100 insertions(+), 39 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index e1f554483..7a7b8d146 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -529,6 +529,8 @@ sub MoveTo { my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint if ( ! $$NewStorage{Id} ) { return "New storage does not have an id. Moving will not happen."; + } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { + return "Event is already located at " . $NewPath; } elsif ( !$NewPath ) { return "New path ($NewPath) is empty."; } elsif ( ! -e $NewPath ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index b78b801f1..1b158b2ac 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -161,9 +161,9 @@ sub Sql { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= 'M.'.$temp_attr_name; } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { - $self->{Sql} .= 'M.'.$term->{attr}; + $self->{Sql} .= 'M.ServerId'; } elsif ( $term->{attr} eq 'StorageServerId' ) { - $self->{Sql} .= 'S.'.$term->{attr}; + $self->{Sql} .= 'S.ServerId'; } elsif ( $term->{attr} eq 'FilterServerId' ) { $self->{Sql} .= $Config{ZM_SERVER_ID}; # StartTime options diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index ef5432693..30cbc11a6 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -158,6 +158,7 @@ sub new { ( $this->{fileName} = $0 ) =~ s|^.*/||; $this->{logPath} = $Config{ZM_PATH_LOGS}; $this->{logFile} = $this->{logPath}.'/'.$this->{id}.'.log'; + ($this->{logFile}) = $this->{logFile} =~ /^([\w\.\/]+)$/; $this->{trace} = 0; @@ -207,6 +208,7 @@ sub initialise( @ ) { if ( my $logFile = $this->getTargettedEnv('LOG_FILE') ) { $tempLogFile = $logFile; } + ($tempLogFile) = $tempLogFile =~ /^([\w\.\/]+)$/; my $tempLevel = INFO; my $tempTermLevel = $this->{termLevel}; @@ -582,24 +584,29 @@ sub logPrint { } print($LOGFILE $message) if $level <= $this->{fileLevel}; if ( $level <= $this->{databaseLevel} ) { - my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; - $this->{sth} = $this->{dbh}->prepare_cached($sql); - if ( !$this->{sth} ) { - $this->{databaseLevel} = NOLOG; - Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); - } else { - my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0) - , $this->{id} - , $$ - , $level - , $code - , $string - , $this->{fileName} - ); - if ( !$res ) { + if ( ( $this->{dbh} and $this->{dbh}->ping() ) or ( $this->{dbh} = zmDbConnect() ) ) { + + my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; + $this->{sth} = $this->{dbh}->prepare_cached($sql); + if ( !$this->{sth} ) { $this->{databaseLevel} = NOLOG; - Error("Can't execute log entry '$sql': ".$this->{sth}->errstr()); + Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); + } else { + my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0) + , $this->{id} + , $$ + , $level + , $code + , $string + , $this->{fileName} + ); + if ( !$res ) { + $this->{databaseLevel} = NOLOG; + Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); + } } + } else { + print(STDERR "Can't log to database: "); } } # end if doing db logging print(STDERR $message) if $level <= $this->{termLevel}; diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index d7627ac42..8e254eed3 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -242,6 +242,7 @@ use constant KILL_DELAY => 60; # seconds to wait between sending TERM and sendin our %cmd_hash; our %pid_hash; our %terminating_processes; +our $zm_terminate = 0; sub run { my $fd = 0; @@ -276,9 +277,9 @@ sub run { listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); $SIG{CHLD} = \&reaper; - $SIG{INT} = \&shutdownAll; - $SIG{TERM} = \&shutdownAll; - $SIG{ABRT} = \&shutdownAll; + $SIG{INT} = \&shutdown_sig_handler; + $SIG{TERM} = \&shutdown_sig_handler; + $SIG{ABRT} = \&shutdown_sig_handler; $SIG{HUP} = \&logrot; my $rin = ''; @@ -295,12 +296,18 @@ sub run { dPrint(ZoneMinder::Logger::INFO, 'Loading Server record have ' . $$Server{Name}); } - while( 1 ) { + while( !$zm_terminate ) { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { - $dbh = zmDbConnect() if ! $dbh->ping(); + Debug("Connecting"); + while ( !$zm_terminate and ! ($dbh and $dbh->ping()) ) { + Warning("Not connected to db. Reconnecting"); + $dbh = zmDbConnect(); + Debug("Conneted? : $dbh"); + } my @cpuload = CpuLoad(); + Debug("UPdating Server record @cpuload"); if ( ! defined $dbh->do(q{UPDATE Servers SET Status=?,CpuLoad=?,TotalMem=?,FreeMem=?,TotalSwap=?,FreeSwap=? WHERE Id=?}, undef, 'Running', $cpuload[0], &totalmem, &freemem, &totalswap, &freeswap, $Config{ZM_SERVER_ID} ) ) { Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); @@ -308,7 +315,9 @@ sub run { } $secs_count += 1; } +Debug("Before select"); my $nfound = select(my $rout = $rin, undef, undef, $timeout); +Debug("Aftere select $nfound"); if ( $nfound > 0 ) { if ( vec($rout, fileno(SERVER), 1) ) { my $paddr = accept(CLIENT, SERVER); @@ -330,7 +339,8 @@ sub run { # Do nothing, this is all we're here for dPrint(ZoneMinder::Logger::WARNING, "Already running, ignoring command '$command'\n"); } elsif ( $command eq 'shutdown' ) { - shutdownAll(); + # Breka out of while loop + last; } elsif ( $command eq 'check' ) { check($daemon, @args); } elsif ( $command eq 'status' ) { @@ -362,7 +372,9 @@ sub run { #print( "Select timed out\n" ); } +Debug("restartPending"); restartPending(); +Debug("check_for_processes_to_kill"); check_for_processes_to_kill(); } # end while @@ -377,9 +389,7 @@ sub run { Error("Failed Updating status of Server record for Id=$Config{ZM_SERVER_ID}".$dbh->errstr()); } } - unlink(main::SOCK_FILE) or Error('Unable to unlink ' . main::SOCK_FILE .". Error message was: $!") if ( -e main::SOCK_FILE ); - unlink(ZM_PID) or Error('Unable to unlink ' . ZM_PID .". Error message was: $!") if ( -e ZM_PID ); - exit(); + shutdownAll(); } sub cPrint { @@ -426,10 +436,12 @@ sub start { } my $sigset = POSIX::SigSet->new; - my $blockset = POSIX::SigSet->new( SIGCHLD ); + my $blockset = POSIX::SigSet->new(SIGCHLD); + Debug("Blocking SIGCHLD"); sigprocmask(SIG_BLOCK, $blockset, $sigset) or Fatal("Can't block SIGCHLD: $!"); + Debug("forking"); if ( my $cpid = fork() ) { - logReinit(); + #logReinit(); $process->{pid} = $cpid; $process->{started} = time(); @@ -442,6 +454,7 @@ sub start { $cmd_hash{$process->{command}} = $pid_hash{$cpid} = $process; sigprocmask(SIG_SETMASK, $sigset) or Fatal("Can't restore SIGCHLD: $!"); + Debug("unblocko child"); } elsif ( defined($cpid) ) { # Force reconnection to the db. $dbh = zmDbConnect(1); @@ -527,16 +540,18 @@ sub check_for_processes_to_kill { my $sigset = POSIX::SigSet->new; my $blockset = POSIX::SigSet->new(SIGCHLD); sigprocmask(SIG_BLOCK, $blockset, $sigset) or die "dying at block...\n"; - foreach my $command ( %terminating_processes ) { + foreach my $command ( keys %terminating_processes ) { my $process = $cmd_hash{$command}; -Debug("Have process $command at pid $$process{pid} $$process{term_sent_at}"); - if ( $$process{term_sent_at} and ( $$process{term_sent_at} - time > KILL_DELAY ) ) { + my $now = time; + Debug("Have process $command at pid $$process{pid} $$process{term_sent_at} - $now = " . ( $$process{term_sent_at} - $now ) ); + if ( $$process{term_sent_at} and ( $$process{term_sent_at} - $now > KILL_DELAY ) ) { dPrint(ZoneMinder::Logger::WARNING, "'$$process{command}' has not stopped at " .strftime('%y/%m/%d %H:%M:%S', localtime()) .' after ' . KILL_DELAY . ' seconds.' ." Sending KILL to pid $$process{pid}\n" ); kill('KILL', $$process{pid}); + delete $terminating_processes{$command}; } } sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; @@ -595,8 +610,14 @@ sub logrot { } } +sub shutdown_sig_handler { + $zm_terminate = 1; +} + sub reaper { my $saved_status = $!; + + # Wait for a child to terminate while ( (my $cpid = waitpid(-1, WNOHANG)) > 0 ) { my $status = $?; @@ -620,8 +641,8 @@ sub reaper { my $out_str = "'$process->{command}' "; if ( $exit_signal ) { + # 15 == TERM, 14 == ALARM if ( $exit_signal == 15 || $exit_signal == 14 ) { -# TERM or ALRM $out_str .= 'exited'; } else { $out_str .= 'crashed'; @@ -659,22 +680,26 @@ sub reaper { $process->{delay} = $Config{ZM_MAX_RESTART_DELAY}; } } + Debug("Delay for $$process{command} is now $$process{delay}"); } else { delete $cmd_hash{$$process{command}}; } - } + } # end while waitpid $SIG{CHLD} = \&reaper; $! = $saved_status; +Debug("Leaving reaper"); } sub restartPending { -# Restart any pending processes - foreach my $process ( values( %cmd_hash ) ) { +# Restart any pending processes, we list them first because cmd_hash may change in foreach + my @processes = values %cmd_hash; + foreach my $process ( @processes ) { if ( $process->{pending} && $process->{pending} <= time() ) { dPrint(ZoneMinder::Logger::INFO, "Starting pending process, $process->{command}\n"); start($process->{daemon}, @{$process->{args}}); } } + dPrint(ZoneMinder::Logger::INFO, "done restartPending"); } sub shutdownAll { @@ -683,9 +708,14 @@ sub shutdownAll { next if ! $pid_hash{$pid}; send_stop(1, $pid_hash{$pid}); } - while ( %terminating_processes ) { + my $count= KILL_DELAY; + while ( (keys %terminating_processes) and $count) { check_for_processes_to_kill(); - sleep(1) if %terminating_processes; + if ( %terminating_processes ) { + Debug("Still " . %terminating_processes . ' to die. count is: ' . $count . ' sleeping'); + sleep(1); + $count --; + } } dPrint(ZoneMinder::Logger::INFO, "Server shutdown at " .strftime('%y/%m/%d %H:%M:%S', localtime()) diff --git a/web/skins/classic/views/report_event_audit.php b/web/skins/classic/views/report_event_audit.php index 85c8454b4..7ec79d3bb 100644 --- a/web/skins/classic/views/report_event_audit.php +++ b/web/skins/classic/views/report_event_audit.php @@ -102,6 +102,7 @@ while( $event = $result->fetch(PDO::FETCH_ASSOC) ) { if ( count($EventsByMonitor[$event['MonitorId']]['Events']) ) { $last_event = end($EventsByMonitor[$event['MonitorId']]['Events']); +#Logger::Debug(print_r($last_event,true)); $gap = $last_event->EndTimeSecs() - $event['StartTimeSecs']; if ( $gap < $EventsByMonitor[$event['MonitorId']]['MinGap'] ) @@ -140,7 +141,10 @@ while( $event = $result->fetch(PDO::FETCH_ASSOC) ) { + + + @@ -152,8 +156,25 @@ while( $event = $result->fetch(PDO::FETCH_ASSOC) ) { for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { $monitor = $displayMonitors[$monitor_i]; $Monitor = new Monitor($monitor); - $montagereview_link = "?view=montagereview&live=0&MonitorId=". $monitor['Id'] . '&minTime='.$minTime.'&maxTime='.$maxTime; + + if ( isset($EventsByMonitor[$Monitor->Id()]) ) { + $EventCounts = $EventsByMonitor[$Monitor->Id()]; + $MinGap = $EventCounts['MinGap']; + $MaxGap = $EventCounts['MaxGap']; + $FileMissing = $EventCounts['FileMissing']; + $ZeroSize = $EventCounts['ZeroSize']; + $FirstEvent = $EventCounts['Events'][0]; + $LastEvent = end($EventCounts['Events']); + } else { + $MinGap = 0; + $MaxGap = 0; + $FileMissing = 0; + $ZeroSize = 0; + $FirstEvent = 0; + $LastEvent = 0; + } + ?> @@ -168,6 +189,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) { }, $Monitor->GroupIds() ) ); ?> + From e55846e71628806bbe91e80da6ab150a6ec693a7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 10:12:34 -0400 Subject: [PATCH 18/26] Log start_time when its > Now --- src/zm_event.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 2008132ef..cd2f42caa 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -69,7 +69,9 @@ Event::Event( untimedEvent = true; start_time = now; } else if ( start_time.tv_sec > now.tv_sec ) { - Error("StartTime in the future"); + Error("StartTime in the future %d.%d > $d.%d", + start_time.tv_sec, start_time.tv_usec, now.tv_sec, now.tv_usec + ); start_time = now; } From d3a611ae49d200ab3f3db816fe0dd851146bdea2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 07:56:51 -0700 Subject: [PATCH 19/26] google code style. sth does not have a errstr() method. --- scripts/zmfilter.pl.in | 300 ++++++++++++++++++++--------------------- 1 file changed, 149 insertions(+), 151 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 97de3490f..fa59d91e0 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -94,7 +94,7 @@ use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) ; -logInit($filter_id?(id=>'zmfilter_'.$filter_id.'.log'):()); +logInit($filter_id?(id=>'zmfilter_'.$filter_id):()); sub HupHandler { Info("Received HUP, reloading"); &ZoneMinder::Logger::logHupHandler(); @@ -154,38 +154,38 @@ my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; if ( ! EVENT_PATH ) { - Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); + Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}\n"); die; } # In future, should not be neccessary wrt StorageAreas chdir( EVENT_PATH ); -# SHould not be neccessary... but nice to get a local var. What if it fails? +# Should not be neccessary... but nice to get a local var. What if it fails? my $dbh = zmDbConnect(); if ( $filter_name ) { - Info( "Scanning for events using filter '$filter_name'\n" ); + Info("Scanning for events using filter '$filter_name'\n"); } elsif ( $filter_id ) { - Info( "Scanning for events using filter id '$filter_id'\n" ); + Info("Scanning for events using filter id '$filter_id'\n"); } else { - Info( "Scanning for events using all filters\n" ); + Info("Scanning for events using all filters\n"); } if ( ! ( $filter_name or $filter_id ) ) { - Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...' ); - sleep( START_DELAY ); + Debug("Sleeping due to start delay: " . START_DELAY . ' seconds...'); + sleep(START_DELAY); } my @filters; my $last_action = 0; -while( ! $zm_terminate ) { +while( !$zm_terminate ) { my $now = time; if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { - Debug( "Reloading filters\n" ); + Debug("Reloading filters\n"); $last_action = $now; - @filters = getFilters( { Name=>$filter_name, Id=>$filter_id } ); + @filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); } foreach my $filter ( @filters ) { @@ -195,16 +195,16 @@ while( ! $zm_terminate ) { my ( $id ) = $$filter{Id} =~ /(\d+)/; Debug("Running concurrent filter process $proc --filter_id $$filter{Id} => $id for $$filter{Name}"); - system( qq`$proc --filter "$$filter{Name}" &` ); + system(qq`$proc --filter "$$filter{Name}" &`); } else { - checkFilter( $filter ); + checkFilter($filter); } } last if $filter_name or $filter_id or $zm_terminate; - Debug( "Sleeping for $delay seconds\n" ); - sleep( $delay ); + Debug("Sleeping for $delay seconds\n"); + sleep($delay); } sub getFilters { @@ -232,18 +232,18 @@ sub getFilters { or UpdateDiskSpace = 1 or AutoMove = 1 ) ORDER BY Name'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable to prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Unable to execute '$sql': ".$sth->errstr() ); + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute(@sql_values) + or Fatal("Unable to execute '$sql': ".$dbh->errstr()); FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - Debug( "Found filter '$db_filter->{Name}'\n" ); + my $filter = new ZoneMinder::Filter($$db_filter{Id}, $db_filter); + Debug("Found filter '$db_filter->{Name}'"); # The undef here is to make sure the Sql gets regenerated because the Filter object may be cached my $filter_sql = $filter->Sql(undef); if ( ! $filter_sql ) { - Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + Error("Error parsing Sql. skipping filter '$db_filter->{Name}'"); next FILTER; } push @filters, $filter; @@ -252,7 +252,7 @@ FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { if ( ! @filters ) { Warning("No filter found for $sql with values(@sql_values)"); } else { - Debug( "Got " . @filters . " filters" ); + Debug("Got " . @filters . " filters"); } return @filters; @@ -283,56 +283,56 @@ sub checkFilter { foreach my $event ( @Events ) { last if $zm_terminate; - my $Event = new ZoneMinder::Event( $$event{Id}, $event ); + my $Event = new ZoneMinder::Event($$event{Id}, $event); - Debug( "Checking event $event->{Id}" ); + Debug("Checking event $event->{Id}"); my $delete_ok = !undef; $dbh->ping(); if ( $filter->{AutoArchive} ) { - Info( "Archiving event $event->{Id}" ); + Info("Archiving event $event->{Id}"); # Do it individually to avoid locking up the table for new events my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute( $event->{Id} ) - or Error( "Unable toexecute '$sql': ".$sth->errstr() ); + or Error("Unable to execute '$sql': ".$dbh->errstr()); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { if ( !$event->{Videoed} ) { - $delete_ok = undef if ( !generateVideo( $filter, $event ) ); + $delete_ok = undef if !generateVideo($filter, $event); } } if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { if ( !$event->{Emailed} ) { - $delete_ok = undef if ( !sendEmail( $filter, $Event ) ); + $delete_ok = undef if !sendEmail($filter, $Event); } } if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { if ( !$event->{Messaged} ) { - $delete_ok = undef if ( !sendMessage( $filter, $event ) ); + $delete_ok = undef if !sendMessage($filter, $event); } } if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { if ( !$event->{Uploaded} ) { - $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); + $delete_ok = undef if !uploadArchFile($filter, $event); } } if ( $filter->{AutoExecute} ) { if ( !$event->{Executed} ) { - $delete_ok = undef if ( !executeCommand( $filter, $event ) ); + $delete_ok = undef if !executeCommand($filter, $event); } } if ( $filter->{AutoDelete} ) { if ( $delete_ok ) { $Event->delete(); } else { - Error( "Unable toto delete event $event->{Id} as previous operations failed\n" ); + Error("Unable toto delete event $event->{Id} as previous operations failed"); } } # end if AutoDelete if ( $filter->{AutoMove} ) { - my $NewStorage = new ZoneMinder::Storage( $filter->{AutoMoveTo} ); - $_ = $Event->MoveTo( $NewStorage ); + my $NewStorage = new ZoneMinder::Storage($filter->{AutoMoveTo}); + $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } @@ -364,7 +364,7 @@ sub generateVideo { my $scale = $event->{DefaultScale}/100; my $format; - my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); + my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS}); my $default_video_format; my $default_phone_format; foreach my $ffmpeg_format( @ffmpeg_formats ) { @@ -408,10 +408,10 @@ sub generateVideo { return 0; } else { my $sql = 'UPDATE Events SET Videoed = 1 WHERE Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $event->{Id} ) - or Fatal("Unable toexecute '$sql': ".$sth->errstr()); + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($event->{Id}) + or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); if ( wantarray() ) { return( $format, $output ); } @@ -463,12 +463,12 @@ sub uploadArchFile { my $event = shift; if ( ! $Config{ZM_UPLOAD_HOST} ) { - Error( 'Cannot upload archive as no upload host defined' ); + Error('Cannot upload archive as no upload host defined'); return( 0 ); } my $archFile = $event->{MonitorName}.'-'.$event->{Id}; - my $archImagePath = getEventPath( $event ) + my $archImagePath = getEventPath($event) .'/' .( ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) @@ -485,14 +485,14 @@ sub uploadArchFile { $archFile .= '.zip'; $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; my $zip = Archive::Zip->new(); - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n"); my $status = &AZ_OK; foreach my $imageFile ( @archImageFiles ) { - Debug( "Adding $imageFile\n" ); - my $member = $zip->addFile( $imageFile ); + Debug("Adding $imageFile\n"); + my $member = $zip->addFile($imageFile); if ( !$member ) { - Error( "Unable toto add image file $imageFile to zip archive $archLocPath" ); + Error("Unable toto add image file $imageFile to zip archive $archLocPath"); $archError = 1; last; } @@ -505,10 +505,10 @@ sub uploadArchFile { $status = $zip->writeToFileNamed( $archLocPath ); if ( $archError = ($status != &AZ_OK) ) { - Error( "Zip error: $status\n " ); + Error("Zip error: $status"); } } else { - Error( "Error adding images to zip archive $archLocPath, not writing" ); + Error("Error adding images to zip archive $archLocPath, not writing"); } } elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'tar' ) { if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) { @@ -517,7 +517,7 @@ sub uploadArchFile { $archFile .= '.tar'; } $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n"); if ( $archError = !Archive::Tar->create_archive( $archLocPath, @@ -525,7 +525,7 @@ sub uploadArchFile { @archImageFiles ) ) { - Error( 'Tar error: '.Archive::Tar->error()."\n " ); + Error('Tar error: '.Archive::Tar->error()); } } @@ -533,7 +533,7 @@ sub uploadArchFile { return( 0 ); } else { if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { - Info( 'Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP\n" ); + Info('Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP"); my $ftp = Net::FTP->new( $Config{ZM_UPLOAD_HOST}, Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, @@ -541,58 +541,56 @@ sub uploadArchFile { Debug=>$Config{ZM_UPLOAD_DEBUG} ); if ( !$ftp ) { - Error( "Unable tocreate FTP connection: $@" ); - return( 0 ); + Error("Unable tocreate FTP connection: $@"); + return 0; } - $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) - or Error( "FTP - Unable tologin" ); + $ftp->login($Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS}) + or Error("FTP - Unable tologin"); $ftp->binary() - or Error( "FTP - Unable togo binary" ); - $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "FTP - Unable tocwd" ) + or Error("FTP - Unable togo binary"); + $ftp->cwd($Config{ZM_UPLOAD_REM_DIR}) + or Error("FTP - Unable tocwd") if ( $Config{ZM_UPLOAD_REM_DIR} ); $ftp->put( $archLocPath ) - or Error( "FTP - Unable toupload '$archLocPath'" ); + or Error("FTP - Unable toupload '$archLocPath'"); $ftp->quit() - or Error( "FTP - Unable toquit" ); + or Error("FTP - Unable toquit"); } else { my $host = $Config{ZM_UPLOAD_HOST}; - $host .= ':'.$Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - Info( 'Uploading to '.$host." using SFTP\n" ); - my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); - $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} - if $Config{ZM_UPLOAD_PASS}; - $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} - if $Config{ZM_UPLOAD_TIMEOUT}; + $host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT}; + Info('Uploading to '.$host." using SFTP\n"); + my %sftpOptions = ( + host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} + ($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()), + ($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()), + ($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()), + ); my @more_ssh_args; push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' if ! $Config{ZM_UPLOAD_STRICT}; push @more_ssh_args, '-v' if $Config{ZM_UPLOAD_DEBUG}; $sftpOptions{more} = [@more_ssh_args]; - my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); + my $sftp = Net::SFTP::Foreign->new($Config{ZM_UPLOAD_HOST}, %sftpOptions); if ( $sftp->error ) { - Error( "Unable tocreate SFTP connection: ".$sftp->error ); - return( 0 ); + Error("Unable tocreate SFTP connection: ".$sftp->error); + return 0; } - $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "SFTP - Unable tosetcwd: ".$sftp->error ) + $sftp->setcwd($Config{ZM_UPLOAD_REM_DIR}) + or Error("SFTP - Unable to setcwd: ".$sftp->error) if $Config{ZM_UPLOAD_REM_DIR}; - $sftp->put( $archLocPath, $archFile ) - or Error( "SFTP - Unable toupload '$archLocPath': ".$sftp->error ); + $sftp->put($archLocPath, $archFile) + or Error("SFTP - Unable to upload '$archLocPath': ".$sftp->error); } - unlink( $archLocPath ); + unlink($archLocPath); my $sql = 'UPDATE Events SET Uploaded = 1 WHERE Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($event->{Id}) + or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); } - return( 1 ); -} + return 1; +} # end sub uploadArchFile sub substituteTags { my $text = shift; @@ -613,14 +611,13 @@ sub substituteTags { my $max_alarm_frame; my $max_alarm_score = 0; if ( $need_images ) { - my $sql = "SELECT * FROM Frames + my $sql = q`SELECT * FROM Frames WHERE EventId = ? AND Type = 'Alarm' - ORDER BY FrameId" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Event->{Id} ) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + ORDER BY FrameId`; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($Event->{Id}) + or Fatal( "Unable toexecute '$sql': ".$dbh->errstr()); my $rows = 0; while( my $frame = $sth->fetchrow_hashref() ) { if ( !$first_alarm_frame ) { @@ -697,13 +694,13 @@ sub substituteTags { if ( $attachments_ref && $text =~ s/%EIMA%//g ) { # Don't attach the same image twice if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId}) ) { my $path = generateImage($Event, $max_alarm_frame, 'analyse'); if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { - Warning("No image for EIMA"); + Warning('No image for EIMA'); } } } @@ -737,7 +734,7 @@ sub sendEmail { my $Event = shift; if ( ! $Config{ZM_FROM_EMAIL} ) { - Error("No 'from' email address defined, not sending email"); + Error('No from email address defined, not sending email'); return 0; } if ( ! $Config{ZM_EMAIL_ADDRESS} ) { @@ -745,7 +742,7 @@ sub sendEmail { return 0; } - Info("Creating notification email\n"); + Info('Creating notification email'); my $subject = substituteTags($Config{ZM_EMAIL_SUBJECT}, $filter, $Event); return 0 if !$subject; @@ -753,7 +750,7 @@ sub sendEmail { my $body = substituteTags($Config{ZM_EMAIL_BODY}, $filter, $Event, \@attachments); return 0 if !$body; - Info("Sending notification email '$subject'\n"); + Info("Sending notification email '$subject'"); eval { if ( $Config{ZM_NEW_MAIL_MODULES} ) { @@ -771,7 +768,7 @@ sub sendEmail { ); ### Add the attachments foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}\n" ); + Info( "Attaching '$attachment->{path}'" ); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, @@ -783,20 +780,20 @@ sub sendEmail { my $ssmtp_location = $Config{ZM_SSMTP_PATH}; if ( !$ssmtp_location ) { if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); } $ssmtp_location = qx('which ssmtp'); } if ( !$ssmtp_location ) { - Debug( "Unable tofind ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + Debug('Unable tofind ssmtp, trying MIME::Lite->send'); + MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } else { ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); + $mail->send('sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS}); } } else { - MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } } else { @@ -809,29 +806,29 @@ sub sendEmail { ); foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}\n" ); + Info("Attaching '$attachment->{path}'"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Encoding => 'base64' ); } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + $mail->smtpsend(Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL}); } }; if ( $@ ) { - Error( "Unable tosend email: $@" ); - return( 0 ); + Error("Unable tosend email: $@"); + return 0; } else { - Info( "Notification email sent\n" ); + Info('Notification email sent'); } - my $sql = 'update Events set Emailed = 1 where Id = ?'; + my $sql = 'UPDATE Events SET Emailed = 1 WHERE Id = ?'; my $sth = $dbh->prepare_cached($sql) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); + or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); my $res = $sth->execute($Event->{Id}) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); - return( 1 ); + return 1; } sub sendMessage { @@ -839,28 +836,28 @@ sub sendMessage { my $event = shift; if ( ! $Config{ZM_FROM_EMAIL} ) { - Error( "No 'from' email address defined, not sending message" ); - return( 0 ); + Error('No from email address defined, not sending message'); + return 0; } if ( ! $Config{ZM_MESSAGE_ADDRESS} ) { - Error( 'No message address defined, not sending message' ); - return( 0 ); + Error('No message address defined, not sending message'); + return 0; } - Info( "Creating notification message\n" ); + Info('Creating notification message'); - my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); + my $subject = substituteTags($Config{ZM_MESSAGE_SUBJECT}, $filter, $event); + return 0 if !$subject; my @attachments; - my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); + my $body = substituteTags($Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments); + return 0 if !$body; - Info( "Sending notification message '$subject'\n" ); + Info("Sending notification message '$subject'"); eval { if ( $Config{ZM_NEW_MAIL_MODULES} ) { ### Create the multipart container - my $mail = MIME::Lite->new ( + my $mail = MIME::Lite->new( From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_MESSAGE_ADDRESS}, Subject => $subject, @@ -873,7 +870,7 @@ sub sendMessage { ); ### Add the attachments foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}\n" ); + Info("Attaching '$attachment->{path}"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, @@ -885,20 +882,20 @@ sub sendMessage { my $ssmtp_location = $Config{ZM_SSMTP_PATH}; if ( !$ssmtp_location ) { if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message"); } $ssmtp_location = qx('which ssmtp'); } if ( !$ssmtp_location ) { - Debug( 'Unable tofind ssmtp, trying MIME::Lite->send' ); - MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + Debug('Unable tofind ssmtp, trying MIME::Lite->send'); + MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } else { ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); + $mail->send('sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS}); } } else { - MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60); $mail->send(); } } else { @@ -911,59 +908,60 @@ sub sendMessage { ); foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}\n" ); + Info("Attaching '$attachment->{path}'"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, Encoding => 'base64' ); } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, + $mail->smtpsend( + Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); } }; if ( $@ ) { - Error( "Unable tosend email: $@" ); - return( 0 ); + Error("Unable tosend email: $@"); + return 0; } else { - Info( "Notification message sent\n" ); + Info('Notification message sent'); } - my $sql = 'update Events set Messaged = 1 where Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($event->{Id}) + or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); - return( 1 ); + return 1; } sub executeCommand { my $filter = shift; my $event = shift; - my $event_path = getEventPath( $event ); + my $event_path = getEventPath($event); my $command = $filter->{AutoExecuteCmd}; $command .= " $event_path"; - $command = substituteTags( $command, $filter, $event ); + $command = substituteTags($command, $filter, $event); - Info( "Executing '$command'\n" ); + Info("Executing '$command'"); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { - chomp( $output ); - Debug( "Output: $output\n" ); + chomp($output); + Debug("Output: $output"); } if ( $status ) { - Error( "Command '$command' exited with status: $status\n" ); - return( 0 ); + Error("Command '$command' exited with status: $status"); + return 0; } else { - my $sql = 'update Events set Executed = 1 where Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Unable toprepare '$sql': ".$dbh->errstr() ); + my $sql = 'UPDATE Events SET Executed = 1 WHERE Id = ?'; + my $sth = $dbh->prepare_cached($sql) + or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); my $res = $sth->execute( $event->{Id} ) - or Fatal( "Unable toexecute '$sql': ".$sth->errstr() ); + or Fatal("Unable to execute '$sql': ".$dbh->errstr()); } return( 1 ); } From c3b6cd4bab8b30625535a10b20d5cf6891bb6e43 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 11:24:53 -0400 Subject: [PATCH 20/26] include auth.php if auth is on, and return '' for auth_hash is auth is disabled --- web/api/app/Controller/HostController.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index ff6410e15..3f1770f42 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -6,14 +6,14 @@ class HostController extends AppController { public $components = array('RequestHandler'); public function daemonCheck($daemon=false, $args=false) { - $string = Configure::read('ZM_PATH_BIN')."/zmdc.pl check"; + $string = Configure::read('ZM_PATH_BIN').'/zmdc.pl check'; if ( $daemon ) { $string .= " $daemon"; if ( $args ) $string .= " $args"; } - $result = exec( $string ); - $result = preg_match( '/running/', $result ); + $result = exec($string); + $result = preg_match('/running/', $result); $this->set(array( 'result' => $result, @@ -31,10 +31,18 @@ class HostController extends AppController { } function getAuthHash() { - $this->set(array( - 'auth_hash'=> generateAuthHash( ZM_AUTH_HASH_IPS ), - '_serialize' => array('auth_hash') - ) ); + if ( $zmOptAuth == '1' ) { + require_once '../../../includes/auth.php'; + $this->set(array( + 'auth_hash' => generateAuthHash(ZM_AUTH_HASH_IPS), + '_serialize' => array('auth_hash') + ) ); + } else { + $this->set(array( + 'auth_hash' => '', + '_serialize' => array('auth_hash') + ) ); + } } // If $mid is set, only return disk usage for that monitor From 8e5ee944209560499769cf48ad3f0e89764342e0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 13:02:13 -0400 Subject: [PATCH 21/26] If we fail to connect, the dbh->trace will crash --- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 19a5f8662..8be04ee99 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -77,39 +77,39 @@ sub zmDbConnect { } my $options = shift; - if ( ( ! defined( $dbh ) ) or ! $dbh->ping() ) { + if ( ( !defined($dbh) ) or ! $dbh->ping() ) { my ( $host, $portOrSocket ) = ( $ZoneMinder::Config::Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $socket; if ( defined($portOrSocket) ) { if ( $portOrSocket =~ /^\// ) { - $socket = ";mysql_socket=".$portOrSocket; + $socket = ';mysql_socket='.$portOrSocket; } else { - $socket = ";host=".$host.";port=".$portOrSocket; + $socket = ';host='.$host.';port='.$portOrSocket; } } else { - $socket = ";host=".$Config{ZM_DB_HOST}; + $socket = ';host='.$Config{ZM_DB_HOST}; } - my $sslOptions = ""; + my $sslOptions = ''; if ( $Config{ZM_DB_SSL_CA_CERT} ) { $sslOptions = ';'.join(';', - "mysql_ssl=1", - "mysql_ssl_ca_file=".$Config{ZM_DB_SSL_CA_CERT}, - "mysql_ssl_client_key=".$Config{ZM_DB_SSL_CLIENT_KEY}, - "mysql_ssl_client_cert=".$Config{ZM_DB_SSL_CLIENT_CERT} + 'mysql_ssl=1', + 'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT}, + 'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY}, + 'mysql_ssl_client_cert='.$Config{ZM_DB_SSL_CLIENT_CERT} ); } - $dbh = DBI->connect( "DBI:mysql:database=".$Config{ZM_DB_NAME} + $dbh = DBI->connect( 'DBI:mysql:database='.$Config{ZM_DB_NAME} .$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '' ) , $Config{ZM_DB_USER} , $Config{ZM_DB_PASS} ); - $dbh->trace( 0 ); + $dbh->trace(0) if $dbh; } - return( $dbh ); -} + return $dbh; +} # end sub zmDbConnect sub zmDbDisconnect { if ( defined( $dbh ) ) { From f4b6bde3c214aff4b7db2d56c5a6de7102eddbe2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 13:02:43 -0400 Subject: [PATCH 22/26] Put print to STDERR before logging to db because it is more reliable than db --- scripts/ZoneMinder/lib/ZoneMinder/Logger.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index 30cbc11a6..929a32b2e 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -583,6 +583,8 @@ sub logPrint { syslog($priorities{$level}, $code.' [%s]', $string); } print($LOGFILE $message) if $level <= $this->{fileLevel}; + print(STDERR $message) if $level <= $this->{termLevel}; + if ( $level <= $this->{databaseLevel} ) { if ( ( $this->{dbh} and $this->{dbh}->ping() ) or ( $this->{dbh} = zmDbConnect() ) ) { @@ -609,7 +611,6 @@ sub logPrint { print(STDERR "Can't log to database: "); } } # end if doing db logging - print(STDERR $message) if $level <= $this->{termLevel}; } # end if level < effectivelevel } From 3bb1a5b544e47e1178e5da367f4ce8f9f76db0e5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 13:02:53 -0400 Subject: [PATCH 23/26] Whitespace --- web/includes/auth.php | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/web/includes/auth.php b/web/includes/auth.php index 875785359..297dcaec9 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -18,7 +18,7 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -function userLogin( $username, $password='', $passwordHashed=false ) { +function userLogin($username, $password='', $passwordHashed=false) { global $user, $cookies; $sql = 'SELECT * FROM Users WHERE Enabled=1'; @@ -29,10 +29,10 @@ function userLogin( $username, $password='', $passwordHashed=false ) { } else { $sql .= ' AND Username=? AND Password=password(?)'; } - $sql_values = array( $username, $password ); + $sql_values = array($username, $password); } else { $sql .= ' AND Username=?'; - $sql_values = array( $username ); + $sql_values = array($username); } session_start(); $_SESSION['username'] = $username; @@ -41,8 +41,8 @@ function userLogin( $username, $password='', $passwordHashed=false ) { $_SESSION['password'] = $password; } $_SESSION['remoteAddr'] = $_SERVER['REMOTE_ADDR']; // To help prevent session hijacking - if ( $dbUser = dbFetchOne( $sql, NULL, $sql_values ) ) { - Info( "Login successful for user \"$username\"" ); + if ( $dbUser = dbFetchOne($sql, NULL, $sql_values) ) { + Info("Login successful for user \"$username\""); $_SESSION['user'] = $user = $dbUser; unset($_SESSION['loginFailed']); if ( ZM_AUTH_TYPE == 'builtin' ) { @@ -50,30 +50,30 @@ function userLogin( $username, $password='', $passwordHashed=false ) { } session_regenerate_id(); } else { - Warning( "Login denied for user \"$username\"" ); + Warning("Login denied for user \"$username\""); $_SESSION['loginFailed'] = true; - unset( $user ); + unset($user); } session_write_close(); -} +} # end function userLogin function userLogout() { global $user; - Info( 'User "'.$user['Username'].'" logged out' ); + Info('User "'.$user['Username'].'" logged out'); session_start(); - unset( $_SESSION['user'] ); - unset( $user ); + unset($_SESSION['user']); + unset($user); session_destroy(); } -function getAuthUser( $auth ) { +function getAuthUser($auth) { if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' && !empty($auth) ) { $remoteAddr = ''; if ( ZM_AUTH_HASH_IPS ) { $remoteAddr = $_SERVER['REMOTE_ADDR']; if ( !$remoteAddr ) { - Error( "Can't determine remote address for authentication, using empty string" ); + Error("Can't determine remote address for authentication, using empty string"); $remoteAddr = ''; } } @@ -103,7 +103,7 @@ function getAuthUser( $auth ) { return false; } // end getAuthUser($auth) -function generateAuthHash( $useRemoteAddr ) { +function generateAuthHash($useRemoteAddr) { if ( ZM_OPT_USE_AUTH and ZM_AUTH_RELAY == 'hashed' and isset($_SESSION['username']) and $_SESSION['passwordHash'] ) { # regenerate a hash at half the liftetime of a hash, an hour is 3600 so half is 1800 $time = time(); @@ -119,7 +119,7 @@ function generateAuthHash( $useRemoteAddr ) { $authKey = ZM_AUTH_HASH_SECRET.$_SESSION['username'].$_SESSION['passwordHash'].$local_time[2].$local_time[3].$local_time[4].$local_time[5]; } #Logger::Debug("Generated using hour:".$local_time[2] . ' mday:' . $local_time[3] . ' month:'.$local_time[4] . ' year: ' . $local_time[5] ); - $auth = md5( $authKey ); + $auth = md5($authKey); session_start(); $_SESSION['AuthHash'] = $auth; $_SESSION['AuthHashGeneratedAt'] = $time; @@ -135,22 +135,22 @@ function generateAuthHash( $useRemoteAddr ) { return $auth; } -function visibleMonitor( $mid ) { +function visibleMonitor($mid) { global $user; - return( empty($user['MonitorIds']) || in_array( $mid, explode( ',', $user['MonitorIds'] ) ) ); + return ( empty($user['MonitorIds']) || in_array($mid, explode(',', $user['MonitorIds'])) ); } -function canView( $area, $mid=false ) { +function canView($area, $mid=false) { global $user; - return( ($user[$area] == 'View' || $user[$area] == 'Edit') && ( !$mid || visibleMonitor( $mid ) ) ); + return ( ($user[$area] == 'View' || $user[$area] == 'Edit') && ( !$mid || visibleMonitor($mid) ) ); } -function canEdit( $area, $mid=false ) { +function canEdit($area, $mid=false) { global $user; - return( $user[$area] == 'Edit' && ( !$mid || visibleMonitor( $mid ) ) ); + return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); } ?> From acd6b5f4a993546b6cf615f5c69995532bf67161 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 14:33:41 -0400 Subject: [PATCH 24/26] limit the # of log lines deleted to 100 to limit the time spent there. --- web/skins/classic/includes/functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index b814e9a13..e1808410a 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -244,7 +244,7 @@ function getNavBarHTML($reload = null) { if ( logToDatabase() > Logger::NOLOG ) { if ( ! ZM_RUN_AUDIT ) { # zmaudit can clean the logs, but if we aren't running it, then we should clecan them regularly - dbQuery('DELETE FROM Logs WHERE TimeKey < unix_timestamp( NOW() - interval '.ZM_LOG_DATABASE_LIMIT.')'); + dbQuery('DELETE FROM Logs WHERE TimeKey < unix_timestamp( NOW() - interval '.ZM_LOG_DATABASE_LIMIT.') LIMIT 100'); } echo makePopupLink( '?view=log', 'zmLog', 'log', ''.translate('Log').'' ); } From 469a7347e824c3c84292b8d48590a49c757119c9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 15:09:00 -0400 Subject: [PATCH 25/26] include zm_terminate to make Capture break out of process is TERM'd --- src/zm_ffmpeg_camera.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 2216d723b..4661ce385 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -18,6 +18,7 @@ // #include "zm.h" +#include "zm_signal.h" #if HAVE_LIBAVFORMAT @@ -180,7 +181,7 @@ int FfmpegCamera::Capture( Image &image ) { // If the reopen thread has a value, but mCanCapture != 0, then we have just reopened the connection to the ffmpeg device, and we can clean up the thread. int frameComplete = false; - while ( !frameComplete ) { + while ( !frameComplete && !zm_terminate) { int avResult = av_read_frame(mFormatContext, &packet); char errbuf[AV_ERROR_MAX_STRING_SIZE]; if ( avResult < 0 ) { @@ -295,12 +296,12 @@ int FfmpegCamera::Capture( Image &image ) { bytes += packet.size; zm_av_packet_unref( &packet ); } // end while ! frameComplete - return 1; + return frameComplete ? 1 : 0; } // FfmpegCamera::Capture int FfmpegCamera::PostCapture() { // Nothing to do here - return( 0 ); + return 0; } int FfmpegCamera::OpenFfmpeg() { From 04198aa3816c4d8046f7e69ba2e85feeb507d79b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 30 Apr 2018 15:09:13 -0400 Subject: [PATCH 26/26] Log valu of dbh if not connected --- scripts/zmdc.pl.in | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 8e254eed3..fceb3b440 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -301,10 +301,9 @@ sub run { if ( $Config{ZM_SERVER_ID} ) { if ( ! ( $secs_count % 60 ) ) { Debug("Connecting"); - while ( !$zm_terminate and ! ($dbh and $dbh->ping()) ) { - Warning("Not connected to db. Reconnecting"); + while ( (!$zm_terminate) and !($dbh and $dbh->ping()) ) { + Warning("Not connected to db ($dbh). Reconnecting"); $dbh = zmDbConnect(); - Debug("Conneted? : $dbh"); } my @cpuload = CpuLoad(); Debug("UPdating Server record @cpuload");
Id()])?count($EventsByMonitor[$Monitor->Id()]['Events']):0 ?>Id()])?$EventsByMonitor[$Monitor->Id()]['MinGap']:0 ?>Id()])?$EventsByMonitor[$Monitor->Id()]['MaxGap']:0 ?>Id()])?$EventsByMonitor[$Monitor->Id()]['FileMissing']:0 ?>Id()])?$EventsByMonitor[$Monitor->Id()]['ZeroSize']:0 ?>link_to($FirstEvent->Id().' at ' . $FirstEvent->StartTime()) : 'none'?>link_to($LastEvent->Id().' at ' . $LastEvent->StartTime()) : 'none'?>
Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Id()), $canEdit ) ?>Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Name()), $canEdit ) ?>Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Path()), $canEdit ) ?>Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Type()), $canEdit ) ?>Id(), 'zmStorage', 'storage', validHtmlStr($Storage->Scheme()), $canEdit ) ?> Name()), $canEdit ) ?> disabled="disabled"/>disk_used_space()) . ' of ' . human_filesize($Storage->disk_total_space()) ?> + disabled="disabled"/>
videocam 
Server()->Name()?> Id()])?count($EventsByMonitor[$Monitor->Id()]['Events']):0 ?> link_to($FirstEvent->Id().' at ' . $FirstEvent->StartTime()) : 'none'?> link_to($LastEvent->Id().' at ' . $LastEvent->StartTime()) : 'none'?>