From a4da624f4c8ccf2156bd7680c4e117cd35761d82 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 27 Apr 2018 13:20:38 -0700 Subject: [PATCH 1/2] 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 ) { ?> 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'?> + + + + Date: Mon, 30 Apr 2018 07:09:23 -0700 Subject: [PATCH 2/2] 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) ) { videocam  + + + @@ -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() ) ); ?> + 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'?>