diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php index 388820040cb3430809a3cb10e2d8cced08d55c1c..cf5a377dacbd693b002ce92e24bd18a6aa2f7713 100644 --- a/classes/cronlog-renderer.class.php +++ b/classes/cronlog-renderer.class.php @@ -32,434 +32,446 @@ require_once 'cronlog.class.php'; * * @license GNU GPL 3.0 * @author Axel Hahn <axel.hahn@iml.unibe.ch> + * + * 2024-09-20 <axel.hahn@unibe.ch> added type declarations; update php docs */ -class cronlogrenderer extends cronlog{ +class cronlogrenderer extends cronlog +{ /** * minimal length for execution time of a job to be rendered in the timeline; value is in seconds * @var integer */ - protected $_iMinTime4Timeline = 60; - + protected int $_iMinTime4Timeline = 60; + /** - * show date of last data and last access; used in rendering methods to display it on top + * Show date of last data and last access; used in rendering methods to display it on top * @param integer $iLast unix timestamp of last log entry * @return string */ - protected function _renderAccessAndAge($iLast){ - if(!$iLast){ + protected function _renderAccessAndAge(int $iLast): string + { + if (!$iLast) { return ''; } - $iAge=round((date('U')-$iLast)/60); + $iAge = round((date('U') - $iLast) / 60); return '<div class="accessandage">' - . sprintf($this->t("request-time"), date("Y-m-d H:i:s")).'<br>' + . sprintf($this->t("request-time"), date("Y-m-d H:i:s")) . '<br>' . sprintf($this->t("last-entry"), $iAge) - .'</div>' - ; + . '</div>' + ; } - + /** - * get onclick value to filter a datatable + * Get onclick value to filter a datatable * @param string $sDatatable id of table * @param string $sFiltertext text to filter * @return string * */ - protected function _filterDatatable($sDatatable, $sFiltertext){ - return '$(\'#'.$sDatatable.'\').dataTable().fnFilter(\''.$sFiltertext.'\'); return false;'; + protected function _filterDatatable($sDatatable, $sFiltertext) + { + return '$(\'#' . $sDatatable . '\').dataTable().fnFilter(\'' . $sFiltertext . '\'); return false;'; } /** - * helper function to be used in methods that render datatable tables - * get javascript code to be added in init options and set language specifi texts + * Helper function to be used in methods that render datatable tables + * Get javascript code to be added in init options and set language specifi texts * @return string */ - protected function _getDatatableLanguage(){ + protected function _getDatatableLanguage(): string + { return $this->t("dt-USE") ? ', "oLanguage":{ - "sProcessing":"'.$this->t("dt-sProcessing").'", - "sLengthMenu":"'.$this->t("dt-sLengthMenu").'", - "sZeroRecords":"'.$this->t("dt-sZeroRecords").'", - "sInfo":"'.$this->t("dt-sInfo").'", - "sInfoEmpty":"'.$this->t("dt-sInfoEmpty").'", - "sInfoFiltered":"'.$this->t("dt-sInfoFiltered").'", - "sInfoPostFix":"'.$this->t("dt-sInfoPostFix").'", - "sSearch":"'.$this->t("dt-sSearch").'", - "sUrl":"'.$this->t("dt-sUrl").'", + "sProcessing":"' . $this->t("dt-sProcessing") . '", + "sLengthMenu":"' . $this->t("dt-sLengthMenu") . '", + "sZeroRecords":"' . $this->t("dt-sZeroRecords") . '", + "sInfo":"' . $this->t("dt-sInfo") . '", + "sInfoEmpty":"' . $this->t("dt-sInfoEmpty") . '", + "sInfoFiltered":"' . $this->t("dt-sInfoFiltered") . '", + "sInfoPostFix":"' . $this->t("dt-sInfoPostFix") . '", + "sSearch":"' . $this->t("dt-sSearch") . '", + "sUrl":"' . $this->t("dt-sUrl") . '", "oPaginate":{ - "sFirst":"'.$this->t("dt-sFirst").'", - "sPrevious":"'.$this->t("dt-sPrevious").'", - "sNext":"'.$this->t("dt-sNext").'", - "sLast":"'.$this->t("dt-sLast").'" + "sFirst":"' . $this->t("dt-sFirst") . '", + "sPrevious":"' . $this->t("dt-sPrevious") . '", + "sNext":"' . $this->t("dt-sNext") . '", + "sLast":"' . $this->t("dt-sLast") . '" } }' : '' ; } /** - * get html code for a table with events of executed cronjobs + * Get html code for a table with events of executed cronjobs * * @param array $aData result of $this->getServerLogs() * @return string */ - public function renderCronlogs($aData=false){ - $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; - $sHtml=$this->_getCacheData($sTaskId); - if($sHtml){ + public function renderCronlogs(array $aData = []): string + { + $sTaskId = __FUNCTION__ . '-' . $this->_sActiveServer; + $sHtml = $this->_getCacheData($sTaskId); + if ($sHtml) { return $sHtml; } - $sHtml=''; - - if(!$aData){ - $aData=array_merge($this->getRunningJobs(), $this->getServersLastLog()); + $sHtml = ''; + + if (!count($aData)) { + $aData = array_merge($this->getRunningJobs(), $this->getServersLastLog()); } - $sTblHead=''; - $iOK=0; - $iErrors=0; - $iLast=false; + $sTblHead = ''; + $iOK = 0; + $iErrors = 0; + $iLast = false; // Array ( [SCRIPTNAME] => apt-get update [SCRIPTTTL] => 1440 [SCRIPTSTARTTIME] => 2016-06-21 06:00:02, 1466481602 [SCRIPTLABEL] => apt-get [SCRIPTENDTIME] => 2016-06-21 06:00:49, 1466481649 [SCRIPTEXECTIME] => 47 s [SCRIPTRC] => 0 ) - foreach($aData as $sDtakey=>$aEntry){ - if(!$sTblHead){ - foreach(array( - $this->t("col-starting-time"), - $this->t("col-label"), + foreach ($aData as $sDtakey => $aEntry) { + if (!$sTblHead) { + foreach ([ + $this->t("col-starting-time"), + $this->t("col-label"), $this->t("col-server"), $this->t("col-duration"), $this->t("col-ttl"), $this->t("col-rc"), $this->t("col-expired"), $this->t("col-status"), - ) as $sKey){ - $sTblHead.='<th>'.$sKey.'</th>'; + ] as $sKey) { + $sTblHead .= "<th>$sKey</th>"; } } // $sViewerUrl='viewer.php?host='.$aEntry['host'].'&job='.$aEntry['job']; // $sClass='message-'.($aEntry['SCRIPTRC']?'error':'ok'); - $iLast=max(array($iLast, date("U", $aEntry['SCRIPTSTARTTIME']))); - - $aErrors=array(); - $iTtlUsed=max($aEntry['SCRIPTTTL'], $this->_iMinTtl); - $iNextRun=$aEntry['SCRIPTSTARTTIME']+((int)$aEntry['SCRIPTTTL']*60); - $iNextRunWarn=$aEntry['SCRIPTSTARTTIME']+((int)$iTtlUsed*60); - $iNextRunErr=$aEntry['SCRIPTSTARTTIME']+(((int)$aEntry['SCRIPTTTL']+(int)$this->_iExpiredJobsFailAfter)*60); + $iLast = max([$iLast, date("U", $aEntry['SCRIPTSTARTTIME'])]); + + $aErrors = []; + $iTtlUsed = max($aEntry['SCRIPTTTL'], $this->_iMinTtl); + $iNextRun = $aEntry['SCRIPTSTARTTIME'] + ((int) $aEntry['SCRIPTTTL'] * 60); + $iNextRunWarn = $aEntry['SCRIPTSTARTTIME'] + ((int) $iTtlUsed * 60); + $iNextRunErr = $aEntry['SCRIPTSTARTTIME'] + (((int) $aEntry['SCRIPTTTL'] + (int) $this->_iExpiredJobsFailAfter) * 60); // ticket #5850 - check hostname vs. servername in logfile - $sServerFromLogfile=preg_replace('/_.*/', '', basename($aEntry['logfile'])); - if($sServerFromLogfile!=$aEntry['server']){ - $aErrors[]=[ + $sServerFromLogfile = preg_replace('/_.*/', '', basename($aEntry['logfile'])); + if ($sServerFromLogfile != $aEntry['server']) { + $aErrors[] = [ $this->t('error-host-in-log-differs-servername-label'), - sprintf($this->t('error-host-in-log-differs-servername-description'),$sServerFromLogfile, $aEntry['server']), + sprintf($this->t('error-host-in-log-differs-servername-description'), $sServerFromLogfile, $aEntry['server']), ]; } - - if(!strstr($sServerFromLogfile, ".")){ - $aErrors[]=[ + + if (!strstr($sServerFromLogfile, ".")) { + $aErrors[] = [ $this->t('error-no-fqdn-label'), - sprintf($this->t('error-no-fqdn-description'),$sServerFromLogfile), + sprintf($this->t('error-no-fqdn-description'), $sServerFromLogfile), ]; } - - if($iNextRunErr < date("U")){ - $aErrors[]=[ + + if ($iNextRunErr < date("U")) { + $aErrors[] = [ $this->t('error-expired-label'), $this->t('error-expired-description'), ]; } - if($aEntry['SCRIPTRC']>0){ - $aErrors[]=[ - sprintf($this->t('error-exitcode-label'), (int)$aEntry['SCRIPTRC']), + if ($aEntry['SCRIPTRC'] > 0) { + $aErrors[] = [ + sprintf($this->t('error-exitcode-label'), (int) $aEntry['SCRIPTRC']), $this->t('error-exitcode-description'), ]; } - if(!$aEntry['SCRIPTLABEL']){ - $aErrors[]=[ + if (!$aEntry['SCRIPTLABEL']) { + $aErrors[] = [ $this->t('error-no-label-label'), $this->t('error-no-label-description'), ]; } - - - if(count($aErrors)){ + + + if (count($aErrors)) { $iErrors++; } else { $iOK++; } - + // human readable TTL value ... SCRIPTTTL is in [min] - $sTtlHr=''; - if((int)$aEntry['SCRIPTTTL']>60*24*3){ - $sTtlHr.='='.round((int)$aEntry['SCRIPTTTL']/60/24).'d '; - } else if((int)$aEntry['SCRIPTTTL']>60*3){ - $sTtlHr.='='.round((int)$aEntry['SCRIPTTTL']/60).'h'; + $sTtlHr = ''; + if ((int) $aEntry['SCRIPTTTL'] > 60 * 24 * 3) { + $sTtlHr .= '=' . round((int) $aEntry['SCRIPTTTL'] / 60 / 24) . 'd '; + } else if ((int) $aEntry['SCRIPTTTL'] > 60 * 3) { + $sTtlHr .= '=' . round((int) $aEntry['SCRIPTTTL'] / 60) . 'h'; } - $sTtlHr=$sTtlHr ? '('.$sTtlHr.')' : ''; - - $sColStatus=''; - $bIsRunning=false; - if($aEntry['SCRIPTRC']){ - if (count($aErrors)){ - foreach($aErrors as $aErr){ - $sColStatus.='<li><abbr title="'.$aErr[1].'">'.$this->t('status-error').': '.$aErr[0].'</abbr></li>'; + $sTtlHr = $sTtlHr ? '(' . $sTtlHr . ')' : ''; + + $sColStatus = ''; + $bIsRunning = false; + if ($aEntry['SCRIPTRC']) { + if (count($aErrors)) { + foreach ($aErrors as $aErr) { + $sColStatus .= '<li><abbr title="' . $aErr[1] . '">' . $this->t('status-error') . ': ' . $aErr[0] . '</abbr></li>'; } - $sColStatus='<ul>'.$sColStatus.'</ul>'; + $sColStatus = '<ul>' . $sColStatus . '</ul>'; } else { - $sColStatus.=$this->t('status-ok'); + $sColStatus .= $this->t('status-ok'); } } else { - $bIsRunning=true; - $sColStatus.=$this->t('status-running'); + $bIsRunning = true; + $sColStatus .= $this->t('status-running'); } // execution time of time of currently running job - $iExectime=$aEntry['SCRIPTEXECTIME'] - ? (int)$aEntry['SCRIPTEXECTIME'] - : date('U')-$aEntry['SCRIPTSTARTTIME'] - ; + $iExectime = $aEntry['SCRIPTEXECTIME'] + ? (int) $aEntry['SCRIPTEXECTIME'] + : date('U') - $aEntry['SCRIPTSTARTTIME'] + ; // render table of last logfile per cron job - $sHtml.='<tr onclick="showFile(\''.$aEntry['logfile'].'\');" title="'.sprintf($this->t('row-click-show-logfile'), $aEntry['logfile']).'"' - .($bIsRunning ? ' class="message-running"' : '') - .'>' - . '<td>'.date("Y-m-d H:i:s", $aEntry['SCRIPTSTARTTIME']).'</td>' - // . '<td>'.$aEntry['SCRIPTNAME'].'</td>' - . '<td>'.$aEntry['SCRIPTLABEL'].'</td>' - . '<td>'.$aEntry['server'].'</td>' - . '<td' - .((int)$aEntry['SCRIPTEXECTIME']>$this->_iMinTime4Timeline ? ' class="message-warning"' : '' ) - . '>' - .'<span style="display: none">'.str_pad((int)$aEntry['SCRIPTEXECTIME'], 6, '0', STR_PAD_LEFT).'</span>' - - .$iExectime.'s' - .($iExectime>100 ? ' ('.round($iExectime/60).'min)' : '') - .'</td>' - . '<td' - .($aEntry['SCRIPTTTL']<$this->_iMinTtl ? ' class="message-warning" title="(using minimal TTL = '.$this->_iMinTtl.' min)"' : '') - . '>' - .'<span style="display: none">'.str_pad((int)$aEntry['SCRIPTTTL'], 6, '0', STR_PAD_LEFT).'</span>' - .$aEntry['SCRIPTTTL'] - .$sTtlHr - .'</td>' - . '<td class="'.($aEntry['SCRIPTRC'] ? ($aEntry['SCRIPTRC']>0 ? 'message-error' : 'message-ok') : '' ).'">' - .($aEntry['SCRIPTRC'] ? $aEntry['SCRIPTRC'] : 'â³') - .'</td>' - . '<td class="'.($iNextRunWarn < date("U") - ? ($iNextRunErr < date("U") ? 'message-error' : 'message-warning') - : '' ).'">' - . date("Y-m-d H:i", $iNextRun).'</td>' - . '<td'.(count($aErrors) ? ' class="message-error" title=""' : '' ).'>' - .$sColStatus.'</td>' - // .(count($aErrors) ? 'FEHLER' : 'OK').'</td>' - // . '<td><button onclick="showFile(\''.$aEntry['logfile'].'\');">Ansehen</button></td>' - . '</tr>' - ; + $sHtml .= '<tr onclick="showFile(\'' . $aEntry['logfile'] . '\');" title="' . sprintf($this->t('row-click-show-logfile'), $aEntry['logfile']) . '"' + . ($bIsRunning ? ' class="message-running"' : '') + . '>' + . '<td>' . date("Y-m-d H:i:s", $aEntry['SCRIPTSTARTTIME']) . '</td>' + // . '<td>'.$aEntry['SCRIPTNAME'].'</td>' + . '<td>' . $aEntry['SCRIPTLABEL'] . '</td>' + . '<td>' . $aEntry['server'] . '</td>' + . '<td' + . ((int) $aEntry['SCRIPTEXECTIME'] > $this->_iMinTime4Timeline ? ' class="message-warning"' : '') + . '>' + . '<span style="display: none">' . str_pad((int) $aEntry['SCRIPTEXECTIME'], 6, '0', STR_PAD_LEFT) . '</span>' + + . $iExectime . 's' + . ($iExectime > 100 ? ' (' . round($iExectime / 60) . 'min)' : '') + . '</td>' + . '<td' + . ($aEntry['SCRIPTTTL'] < $this->_iMinTtl ? ' class="message-warning" title="(using minimal TTL = ' . $this->_iMinTtl . ' min)"' : '') + . '>' + . '<span style="display: none">' . str_pad((int) $aEntry['SCRIPTTTL'], 6, '0', STR_PAD_LEFT) . '</span>' + . $aEntry['SCRIPTTTL'] + . $sTtlHr + . '</td>' + . '<td class="' . ($aEntry['SCRIPTRC'] ? ($aEntry['SCRIPTRC'] > 0 ? 'message-error' : 'message-ok') : '') . '">' + . ($aEntry['SCRIPTRC'] ? $aEntry['SCRIPTRC'] : 'â³') + . '</td>' + . '<td class="' . ($iNextRunWarn < date("U") + ? ($iNextRunErr < date("U") ? 'message-error' : 'message-warning') + : '') . '">' + . date("Y-m-d H:i", $iNextRun) . '</td>' + . '<td' . (count($aErrors) ? ' class="message-error" title=""' : '') . '>' + . $sColStatus . '</td>' + // .(count($aErrors) ? 'FEHLER' : 'OK').'</td>' + // . '<td><button onclick="showFile(\''.$aEntry['logfile'].'\');">Ansehen</button></td>' + . '</tr>' + ; } - $sIdTable='datatable1'; - $sHtml=' - <!-- START '.__METHOD__.' --> + $sIdTable = 'datatable1'; + $sHtml = ' + <!-- START ' . __METHOD__ . ' --> ' - - . '<h3>'.$this->t('logs-head').'</h3>' + + . '<h3>' . $this->t('logs-head') . '</h3>' . '<p class="hint">' - . $this->t('logs-hint') + . $this->t('logs-hint') . '</p>' . (count($aData) - ? '<div>' - . $this->_renderAccessAndAge($iLast) - . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-error')).'"><i class="fas fa-exclamation-circle"></i> ' . $iErrors.'</a> ' : '') - . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> ' . $iOK.'</a>' : '') - . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> ' . count($aData).'</a>' : '' ) - . '</div><br>' - . '<table id="'.$sIdTable.'" class="table-striped">' - . '<thead><tr>'.$sTblHead.'</tr></thead>' - . '<tbody>' - .$sHtml - .'</tbody>' - . '</table>' - . '<script>' - . '$(document).ready( function () { $(\'#'.$sIdTable.'\').DataTable({ + ? '<div>' + . $this->_renderAccessAndAge($iLast) + . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="' . $this->_filterDatatable($sIdTable, $this->t('status-error')) . '"><i class="fas fa-exclamation-circle"></i> ' . $iErrors . '</a> ' : '') + . ($iOK ? '<a href="#" class="btn bg-success" onclick="' . $this->_filterDatatable($sIdTable, $this->t('status-ok')) . '"><i class="fas fa-check"></i> ' . $iOK . '</a>' : '') + . ($iErrors && $iOK ? ' ... ' . $this->t('total') . ': <a href="#" class="btn bg-gray" onclick="' . $this->_filterDatatable($sIdTable, "") . '"><i class="fas fa-th-large"></i> ' . count($aData) . '</a>' : '') + . '</div><br>' + . '<table id="' . $sIdTable . '" class="table-striped">' + . '<thead><tr>' . $sTblHead . '</tr></thead>' + . '<tbody>' + . $sHtml + . '</tbody>' + . '</table>' + . '<script>' + . '$(document).ready( function () { $(\'#' . $sIdTable . '\').DataTable({ "retrieve": true, "bPaginate":false, "aaSorting":[[0,"desc"]] - '.$this->_getDatatableLanguage().' + ' . $this->_getDatatableLanguage() . ' }); });' - . '</script>' - : '' + . '</script>' + : '' ) // init datatable . ' - <!-- ENDE '.__METHOD__.' --> + <!-- ENDE ' . __METHOD__ . ' --> ' - ; + ; $this->_writeCacheData($sTaskId, $sHtml); return $sHtml; } - + /** - * get html code for a table with events of executed cronjobs for ALL servers + * Get html code for a table with events of executed cronjobs for ALL servers * * @param array $aData result of $this->getServerLogs() * @return string */ - public function renderCronlogsOfAllServers(){ - $aData=array(); - foreach (array_keys($this->getServers()) as $sServer){ + public function renderCronlogsOfAllServers(): string + { + $aData = []; + foreach (array_keys($this->getServers()) as $sServer) { $this->setServer($sServer); - $aData=array_merge($aData, $this->getRunningJobs(),$this->getServersLastLog()); + $aData = array_merge($aData, $this->getRunningJobs(), $this->getServersLastLog()); } $this->setServer('ALL'); return $this->renderCronlogs($aData); } - + /** - * get html code for a switcher of multiple instances + * Get html code for a switcher of multiple instances. + * It returns an empty string if there is no / only one instance * * @param array $aData result of $oCL->getServerLogs() * @return string */ - public function renderInstances(){ - if(count($this->_aInstances) < 2){ - return false; + public function renderInstances(): string + { + if (count($this->_aInstances) < 2) { + return ''; } - $sReturn=''; - $sServer=isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : false; - $sReturn.='<li class="nav-item"><a class="nav-link nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a></li>' - . '<li class="nav-item d-none d-sm-inline-block"><a href="#" class="nav-link">'.$this->t('instances').':</a></li>'; - - foreach($this->_aInstances as $sInstance => $sUrl){ - $sHost=parse_url($sUrl, PHP_URL_HOST); - $sClass=($sServer && $sServer==$sHost) ? 'active bg-gray' : ''; + $sReturn = ''; + $sServer = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : false; + $sReturn .= '<li class="nav-item"><a class="nav-link nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a></li>' + . '<li class="nav-item d-none d-sm-inline-block"><a href="#" class="nav-link">' . $this->t('instances') . ':</a></li>'; + + foreach ($this->_aInstances as $sInstance => $sUrl) { + $sHost = parse_url($sUrl, PHP_URL_HOST); + $sClass = ($sServer && $sServer == $sHost) ? 'active bg-gray' : ''; // $sReturn.='<a class="'.$sClass.'" href="'.$sUrl.'" title="'.$sUrl.'">'.$sInstance.'</a> '; - $sReturn.='<li class="nav-item d-none d-sm-inline-block '.$sClass.'"><a href="'.$sUrl.'" class="nav-link">'.$sInstance.'</a></li>'; + $sReturn .= '<li class="nav-item d-none d-sm-inline-block ' . $sClass . '"><a href="' . $sUrl . '" class="nav-link">' . $sInstance . '</a></li>'; } return $sReturn; } /** - * get html code for a table with history of executed cronjobs + * Get html code for a table with history of executed cronjobs * * @param array $aData result of $oCL->getServerLogs() * @return string */ - public function renderJoblist($aData=false){ - $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; - $sHtml=$this->_getCacheData($sTaskId); - if($sHtml){ + public function renderJoblist(array $aData = []): string + { + $sTaskId = __FUNCTION__ . '-' . $this->_sActiveServer; + $sHtml = $this->_getCacheData($sTaskId); + if ($sHtml) { return $sHtml; } - $sHtml=''; - - if(!$aData){ - $aData=$this->getServerJobHistory(); + $sHtml = ''; + + if (!$aData) { + $aData = $this->getServerJobHistory(); } - $sTblHead=''; - $iOK=0; - $iErrors=0; - $iLast=false; + $sTblHead = ''; + $iOK = 0; + $iErrors = 0; + $iLast = false; // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0 - foreach($aData as $aEntry){ - if(!$sTblHead){ - foreach(array( - $this->t("col-starting-time"), - $this->t("col-label"), + foreach ($aData as $aEntry) { + if (!$sTblHead) { + foreach ([ + $this->t("col-starting-time"), + $this->t("col-label"), $this->t("col-server"), $this->t("col-duration"), $this->t("col-ttl"), $this->t("col-rc"), $this->t("col-status"), - ) as $sKey){ - $sTblHead.='<th>'.$sKey.'</th>'; + ] as $sKey) { + $sTblHead .= '<th>' . $sKey . '</th>'; } } - $iLast=max(array($iLast, date("U", $aEntry['start']))); - $sClass='message-'.($aEntry['rc']?'error':'ok'); - - if($aEntry['rc']){ + $iLast = max([$iLast, date("U", $aEntry['start'])]); + + if ($aEntry['rc']) { $iErrors++; } else { $iOK++; } - - $sHtml.='<tr>' - . '<td>'.date("Y-m-d H:i:s", $aEntry['start']).'</td>' - // . '<td>'.date("Y-m-d H:i:s", $aEntry['end']).'</td>' - . '<td>'.$aEntry['job'].'</td>' - . '<td>'.$aEntry['host'].'</td>' - . '<td' - .($aEntry['exectime']>$this->_iMinTime4Timeline ? ' class="message-warning"' : '' ) - . '>' - .$aEntry['exectime'].'s' - .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') - . '</td>' - . '<td>'.$aEntry['ttl'].'</td>' - . '<td class="' - .($aEntry['rc']>0 ? 'message-error': 'message-ok') - . '">'.$aEntry['rc'].'</td>' - . '<td>'.( $aEntry['rc'] ? $this->t('status-error') : $this->t('status-ok') ).'</td>' - . '</tr>' - ; + + $sHtml .= '<tr>' + . '<td>' . date("Y-m-d H:i:s", $aEntry['start']) . '</td>' + // . '<td>'.date("Y-m-d H:i:s", $aEntry['end']).'</td>' + . '<td>' . $aEntry['job'] . '</td>' + . '<td>' . $aEntry['host'] . '</td>' + . '<td' + . ($aEntry['exectime'] > $this->_iMinTime4Timeline ? ' class="message-warning"' : '') + . '>' + . $aEntry['exectime'] . 's' + . ($aEntry['exectime'] > 100 ? ' (' . round($aEntry['exectime'] / 60) . 'min)' : '') + . '</td>' + . '<td>' . $aEntry['ttl'] . '</td>' + . '<td class="' + . ($aEntry['rc'] > 0 ? 'message-error' : 'message-ok') + . '">' . $aEntry['rc'] . '</td>' + . '<td>' . ($aEntry['rc'] ? $this->t('status-error') : $this->t('status-ok')) . '</td>' + . '</tr>' + ; } - $sIdTable='datatable2'; - $sHtml=' - <!-- START '.__METHOD__.' --> + $sIdTable = 'datatable2'; + $sHtml = ' + <!-- START ' . __METHOD__ . ' --> ' - . '<h3>'.$this->t('history-head').'</h3>' + . '<h3>' . $this->t('history-head') . '</h3>' . '<p class="hint">' - . $this->t('history-hint') + . $this->t('history-hint') . '</p>' . '<div>' - . $this->_renderAccessAndAge($iLast) - . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-error')).'"><i class="fas fa-exclamation-circle"></i> ' . $iErrors.'</a> ' : '') - . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> ' . $iOK.'</a>' : '') - . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> ' . count($aData).'</a>' : '' ) + . $this->_renderAccessAndAge($iLast) + . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="' . $this->_filterDatatable($sIdTable, $this->t('status-error')) . '"><i class="fas fa-exclamation-circle"></i> ' . $iErrors . '</a> ' : '') + . ($iOK ? '<a href="#" class="btn bg-success" onclick="' . $this->_filterDatatable($sIdTable, $this->t('status-ok')) . '"><i class="fas fa-check"></i> ' . $iOK . '</a>' : '') + . ($iErrors && $iOK ? ' ... ' . $this->t('total') . ': <a href="#" class="btn bg-gray" onclick="' . $this->_filterDatatable($sIdTable, "") . '"><i class="fas fa-th-large"></i> ' . count($aData) . '</a>' : '') . '</div>' . '<br>' - . '<table id="'.$sIdTable.'">' - . '<thead><tr>'.$sTblHead.'</tr></thead>' + . '<table id="' . $sIdTable . '">' + . '<thead><tr>' . $sTblHead . '</tr></thead>' . '<tbody>' - .$sHtml - .'</tbody>' + . $sHtml + . '</tbody>' . '</table>' // init datatable . (count($aData) ? '<script>' - . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({ + . '$(document).ready( function () {$(\'#' . $sIdTable . '\').DataTable({ "retrieve": true, "aaSorting":[[0,"desc"]], "aLengthMenu":[[25,100,-1],[25,100,"---"]] - '.$this->_getDatatableLanguage().' + ' . $this->_getDatatableLanguage() . ' });} );' - . '</script>' + . '</script>' : '' - ) + ) . ' - <!-- ENDE '.__METHOD__.' --> + <!-- ENDE ' . __METHOD__ . ' --> ' - ; + ; $this->_writeCacheData($sTaskId, $sHtml); return $sHtml; } - + /** * get html code for a joblist of the selected server * it uses the filter for hidden joblog entries (aHidelogs in config) + * * @return string */ - public function renderJoblistOfAllServers(){ - $aData=array(); - foreach (array_keys($this->getServers()) as $sServer){ + public function renderJoblistOfAllServers(): string + { + $aData = []; + foreach (array_keys($this->getServers()) as $sServer) { $this->setServer($sServer); - $aData=array_merge($aData, $this->getServerJobHistory()); + $aData = array_merge($aData, $this->getServerJobHistory()); } $this->setServer('ALL'); return $this->renderJoblist($aData); } - + /** * get html code for a timeline with events of executed cronjobs * @@ -470,32 +482,33 @@ class cronlogrenderer extends cronlog{ * @param array $aData result of $oCL->getServerLogs() * @return string */ - public function renderJobGraph($aData=false){ - $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; - $sHtml=$this->_getCacheData($sTaskId); - if($sHtml){ + public function renderJobGraph(array $aData = []): string + { + $sTaskId = __FUNCTION__ . '-' . $this->_sActiveServer; + $sHtml = $this->_getCacheData($sTaskId); + if ($sHtml) { return $sHtml; } - $sHtml=''; + $sHtml = ''; static $iGraphCounter; - if(!isset($iGraphCounter)){ - $iGraphCounter=0; + if (!isset($iGraphCounter)) { + $iGraphCounter = 0; } $iGraphCounter++; - if(!$aData){ - $aData=$this->getServerJobHistory(false); + if (!count($aData)) { + $aData = $this->getServerJobHistory(false); } - $sDivId='vis-timeline-'.$iGraphCounter; - - $aDataset=array(); - $iLast=false; - $iEntry=0; - foreach($aData as $aEntry){ - if($aEntry['exectime']>$this->_iMinTime4Timeline){ + $sDivId = 'vis-timeline-' . $iGraphCounter; + + $aDataset = []; + $iLast = false; + $iEntry = 0; + foreach ($aData as $aEntry) { + if ($aEntry['exectime'] > $this->_iMinTime4Timeline) { $iEntry++; - $iLast=max(array($iLast, date("U", $aEntry['start']))); - $aDataset[]=array( - 'id'=>$iEntry, + $iLast = max([$iLast, date("U", $aEntry['start'])]); + $aDataset[] = [ + 'id' => $iEntry, /* 'start'=>(int)date("U", $aEntry['start']), 'end'=>(int)date("U", $aEntry['end']), @@ -507,47 +520,47 @@ class cronlogrenderer extends cronlog{ 'end'=>date('Y-m-d\TH:i:s.000\Z', $aEntry['end']), * */ - 'start'=>(int)date("U", $aEntry['start'])*1000, - 'end'=>(int)date("U", $aEntry['end'])*1000, - 'content'=>$aEntry['job'].'@'.$aEntry['host'], - 'className'=>'timeline-result-'.($aEntry['rc'] ? 'error' : 'ok'), - 'title'=>'<strong>'.$aEntry['job'].'@'.$aEntry['host'].'</strong><br>' - . 'start: ' . date("Y-m-d H:i:s", $aEntry['start']).'<br>' - . 'end: ' . date("Y-m-d H:i:s", $aEntry['end']).'<br>' + 'start' => (int) date("U", $aEntry['start']) * 1000, + 'end' => (int) date("U", $aEntry['end']) * 1000, + 'content' => $aEntry['job'] . '@' . $aEntry['host'], + 'className' => 'timeline-result-' . ($aEntry['rc'] ? 'error' : 'ok'), + 'title' => '<strong>' . $aEntry['job'] . '@' . $aEntry['host'] . '</strong><br>' + . 'start: ' . date("Y-m-d H:i:s", $aEntry['start']) . '<br>' + . 'end: ' . date("Y-m-d H:i:s", $aEntry['end']) . '<br>' . 'exectime: ' - .$aEntry['exectime'].'s' - .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') + . $aEntry['exectime'] . 's' + . ($aEntry['exectime'] > 100 ? ' (' . round($aEntry['exectime'] / 60) . 'min)' : '') . '<br>' - . 'rc = ' . $aEntry['rc'].'<br>' - , - ); + . 'rc = ' . $aEntry['rc'] . '<br>' + , + ]; // if($iEntry>=265){break;} } } + + $sHtml .= ' - $sHtml.=' - - <!-- START '.__METHOD__.' --> + <!-- START ' . __METHOD__ . ' --> ' - . '<h3>'.$this->t('timeline-head').'</h3>' - . '<p class="hint">' + . '<h3>' . $this->t('timeline-head') . '</h3>' + . '<p class="hint">' . sprintf($this->t('timeline-hint'), $this->_iMinTime4Timeline) - . '</p> + . '</p> <p>' - .sprintf($this->t('graph-rendered-jobs'), $this->_iMinTime4Timeline).': <strong>'.count($aDataset).'</strong> ' - .'('.$this->t('total').': '.count($aData).')<br><br>' - .(count($aDataset) ? $this->_renderAccessAndAge($iLast) : '' ) - .'</p>' - .(count($aDataset) ? - '<div id="'.$sDivId.'"></div> + . sprintf($this->t('graph-rendered-jobs'), $this->_iMinTime4Timeline) . ': <strong>' . count($aDataset) . '</strong> ' + . '(' . $this->t('total') . ': ' . count($aData) . ')<br><br>' + . (count($aDataset) ? $this->_renderAccessAndAge($iLast) : '') + . '</p>' + . (count($aDataset) ? + '<div id="' . $sDivId . '"></div> <script type="text/javascript"> // DOM element where the Timeline will be attached - var container = document.getElementById("'.$sDivId.'"); + var container = document.getElementById("' . $sDivId . '"); // Create a DataSet (allows two way data-binding) - var items = new vis.DataSet('.json_encode($aDataset).'); + var items = new vis.DataSet(' . json_encode($aDataset) . '); // Configuration for the Timeline var options = { @@ -559,142 +572,150 @@ class cronlogrenderer extends cronlog{ var timeline = new vis.Timeline(container, items, options); // fix: some timelines do not properly work ... but I make them visible - $(\'#'.$sDivId.' .vis-timeline\').css(\'visibility\', \'visible\'); + $(\'#' . $sDivId . ' .vis-timeline\').css(\'visibility\', \'visible\'); </script> - ' - : $this->t('graph-no-data').'<br>' - ) - .' + ' + : $this->t('graph-no-data') . '<br>' + ) + . ' - <!-- ENDE '.__METHOD__.'--> + <!-- ENDE ' . __METHOD__ . '--> '; $this->_writeCacheData($sTaskId, $sHtml); - + return '' - .$sHtml + . $sHtml // . strlen($sHtml).' byte - ' . '<pre>'.htmlentities($sHtml).'</pre><br>' - ; + ; } - - public function renderJobGraphOfAllServers(){ - $aData=array(); - foreach (array_keys($this->getServers()) as $sServer){ + + /** + * Get html code for the job graph of all servers + * @return string + */ + public function renderJobGraphOfAllServers(): string + { + $aData = []; + foreach (array_keys($this->getServers()) as $sServer) { $this->setServer($sServer); - $aData=array_merge($aData, $this->getServerJobHistory()); + $aData = array_merge($aData, $this->getServerJobHistory()); } $this->setServer('ALL'); return $this->renderJobGraph($aData); } - + /** - * used in config/page-replacements.php * generate an array of javascript lang texts + * used in config/page-replacements.php + * @return string */ - public function renderJSLang(){ - $aReturn=[]; + public function renderJSLang(): string + { + $aReturn = []; - foreach($this->_aLang as $sKey=>$sText){ - if(preg_match('/^JS_/', $sKey)){ - $aReturn[preg_replace('/^JS_/', '', $sKey)]=$sText; + foreach ($this->_aLang as $sKey => $sText) { + if (preg_match('/^JS_/', $sKey)) { + $aReturn[preg_replace('/^JS_/', '', $sKey)] = $sText; } } return "\n" - .'// generated by '.__METHOD__."\n" - .'var aLang='.json_encode($aReturn, JSON_PRETTY_PRINT).'; '."\n" + . '// generated by ' . __METHOD__ . "\n" + . 'var aLang=' . json_encode($aReturn, JSON_PRETTY_PRINT) . '; ' . "\n" ; } - - /** - * show a single log file - * - * @param string $sLogfile logfile; [server]/[filename.log] - * @return string - */ - public function renderLogfile($sLogfile){ - $sHtml='' - . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> '.$this->t('back').'</button><br><br>' - . '<h3>'. $this->t('logfile').' '.basename($sLogfile).'</h3>' - ; - if(!$sLogfile){ + + /** + * Get html code to show a single log file with syntax highligt an marking the reeturn code + * + * @param string $sLogfile logfile; [server]/[filename.log] + * @return string + */ + public function renderLogfile(string $sLogfile): string + { + $sHtml = '' + . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> ' . $this->t('back') . '</button><br><br>' + . '<h3>' . $this->t('logfile') . ' ' . basename($sLogfile) . '</h3>' + ; + if (!$sLogfile) { return $sHtml . $this->t('error-nologfile'); } - if(strstr($sLogfile, '..')){ + if (strstr($sLogfile, '..')) { return $sHtml . $this->t('error-dots-not-allowed'); } // $sMyFile=$this->_getServerlogDir().'/'.$sLogfile; - $sMyFile=$this->_sDataDir.'/'.$sLogfile; - if(!file_exists($sMyFile)){ + $sMyFile = $this->_sDataDir . '/' . $sLogfile; + if (!file_exists($sMyFile)) { return $sHtml . sprintf($this->t('error-logfile-not-found'), $sMyFile); } - if ($fileHandle = fopen($sMyFile, "r")) { - $sHtml.='<div style="float: left;"><pre>'; + $sHtml .= '<div style="float: left;"><pre>'; while (($line = fgets($fileHandle)) !== false) { # do same stuff with the $line - $bIsComment=strstr($line, 'REM '); - if($bIsComment){ - $sHtml.='<div class="log-rem">'.$line.'</div>'; + $bIsComment = strstr($line, 'REM '); + if ($bIsComment) { + $sHtml .= '<div class="log-rem">' . $line . '</div>'; } else { - $sKey=trim(preg_replace('/=.*/', '', $line)); - $sValue=preg_replace('/^([A-Z]*\=)/', '', $line); - $sDivClass=''; - switch($sKey){ + $sKey = trim(preg_replace('/=.*/', '', $line)); + $sValue = preg_replace('/^([A-Z]*\=)/', '', $line); + $sDivClass = ''; + switch ($sKey) { case 'SCRIPTRC': - $sDivClass=(int)$sValue===0 ? 'message-ok' : 'message-error'; + $sDivClass = (int) $sValue === 0 ? 'message-ok' : 'message-error'; break; case 'JOBEXPIRE': - $sDivClass=date('U') < (int)$sValue ? 'message-ok' : 'message-error'; + $sDivClass = date('U') < (int) $sValue ? 'message-ok' : 'message-error'; break; } - $sValue=preg_replace('/(rc=0)/', '<span class="message-ok">$1</span>', $sValue); - $sValue=preg_replace('/(rc=[1-9][0-9]*)/', '<span class="message-error">$1</span>', $sValue); - $sValue=preg_replace('/(rc=[1-9])/', '<span class="message-error">$1</span>', $sValue); + $sValue = preg_replace('/(rc=0)/', '<span class="message-ok">$1</span>', $sValue); + $sValue = preg_replace('/(rc=[1-9][0-9]*)/', '<span class="message-error">$1</span>', $sValue); + $sValue = preg_replace('/(rc=[1-9])/', '<span class="message-error">$1</span>', $sValue); // remove terminal color - $sValue=preg_replace('/(\[[0-9]{1,3}m)/', '', $sValue); - - $sHtml.='<div'.($sDivClass ? ' class="'.$sDivClass.'"' : '') - // . ' title="'.$sKey.'='.$sValue.'" ' - . '><span class="log-var">'.$sKey.'</span>=<span class="log-value">'.$sValue.'</span></div>'; + $sValue = preg_replace('/(\[[0-9]{1,3}m)/', '', $sValue); + + $sHtml .= '<div' . ($sDivClass ? ' class="' . $sDivClass . '"' : '') + // . ' title="'.$sKey.'='.$sValue.'" ' + . '><span class="log-var">' . $sKey . '</span>=<span class="log-value">' . $sValue . '</span></div>'; } } - $sHtml.='</pre></div>'; - } - return $sHtml; - } - + $sHtml .= '</pre></div>'; + } + return $sHtml; + } + /** - * get html code for a select box with all servers + * Get html code for a select box with all servers * @return string */ - public function renderServerlist($sSelectedItem=false){ - $sHtml=''; - $iMaxItemsToShow=30; - $sHtml.='<option value="ALL"' - .($sSelectedItem===false || $sSelectedItem==='ALL' ? ' selected="selected"' : '') - . '>['.$this->t('ALL').' ('.count($this->getServers()).')]</option>'; - foreach($this->getServers() as $sServer=>$aData){ - $sHtml.='<option value="'.$sServer.'"' - .($sSelectedItem===$sServer ? ' selected="selected"' : '') - .'>'.$sServer.'</option>'; + public function renderServerlist(string $sSelectedItem = ''): string + { + $sHtml = ''; + $iMaxItemsToShow = 30; + $sHtml .= '<option value="ALL"' + . ($sSelectedItem === '' || $sSelectedItem === 'ALL' ? ' selected="selected"' : '') + . '>[' . $this->t('ALL') . ' (' . count($this->getServers()) . ')]</option>'; + foreach ($this->getServers() as $sServer => $aData) { + $sHtml .= '<option value="' . $sServer . '"' + . ($sSelectedItem === $sServer ? ' selected="selected"' : '') + . '>' . $sServer . '</option>'; } - $sHtml=$sHtml - ? '' - /* - .'<input id="serverfiltertext" type="text" placeholder="filter server" value="" - onchange="filterServers();" - onkeypress="filterServers();" - onkeyup="filterServers();" - ><button onclick="$(\'#serverfiltertext\').val(\'\'); filterServers();">X</button><br><br>' - */ - .'<select' - . ' size="'.( min(array(count($this->getServers())+1 , $iMaxItemsToShow)) ).'"' - // . ' size="1"' - . ' onchange="setServer(this.value); return false;"' - . '>'.$sHtml.'</select>' - : false; + $sHtml = $sHtml + ? '' + /* + .'<input id="serverfiltertext" type="text" placeholder="filter server" value="" + onchange="filterServers();" + onkeypress="filterServers();" + onkeyup="filterServers();" + ><button onclick="$(\'#serverfiltertext\').val(\'\'); filterServers();">X</button><br><br>' + */ + . '<select' + . ' size="' . (min([count($this->getServers()) + 1, $iMaxItemsToShow])) . '"' + // . ' size="1"' + . ' onchange="setServer(this.value); return false;"' + . '>' . $sHtml . '</select>' + : false; return $sHtml; } - + } \ No newline at end of file diff --git a/classes/cronlog.class.php b/classes/cronlog.class.php index de0ed21f8c2a3165bfc952c1f64ed99d8436d3e3..e3c596d7ea9fe0e4f45cb071ae43ca3888703b40 100644 --- a/classes/cronlog.class.php +++ b/classes/cronlog.class.php @@ -31,110 +31,114 @@ * * @license GNU GPL 3.0 * @author Axel Hahn <axel.hahn@iml.unibe.ch> + * + * 2024-09-20 <axel.hahn@unibe.ch> added type declarations; update php docs */ -class cronlog { - - - protected $_sDataDir = "__APPDIR__/data"; - protected $_iTtlCache = 60; // in sec +class cronlog +{ + + + protected string $_sDataDir = "__APPDIR__/data"; + protected int $_iTtlCache = 60; // in sec /** * when show an error for expired jobs (latency to execute job and sync logs) * @var integer */ - protected $_iExpiredJobsFailAfter = 60*30; // in sec - protected $_iMinTtl = 0; // in sec - protected $_aSkipJoblogs = array(); - - protected $_aInstances = array(); - - protected $_aServers = array(); - protected $_sActiveServer = false; - - protected $_sFileFilter_serverjoblog = '*joblog*.done'; - protected $_sFileFilter_joblog = '*.log'; - protected $_sFileFilter_jobrunning = '*.log.running*'; - - protected $_sLang = ''; // language ... read from config file - protected $_aLang = []; // language data + protected int $_iExpiredJobsFailAfter = 60 * 30; // in sec + protected int $_iMinTtl = 0; // in sec + protected array $_aSkipJoblogs = []; + + protected array $_aInstances = []; + + protected array $_aServers = []; + protected string $_sActiveServer = ''; + + protected string $_sFileFilter_serverjoblog = '*joblog*.done'; + protected string $_sFileFilter_joblog = '*.log'; + protected string $_sFileFilter_jobrunning = '*.log.running*'; + + protected string $_sLang = ''; // language ... read from config file + protected array $_aLang = []; // language data // ---------------------------------------------------------------------- // MAIN // ---------------------------------------------------------------------- /** - * init - * @return boolean + * Constructor */ - public function __construct() { - + public function __construct() + { // read config - if (file_exists(__DIR__.'/../config/inc_cronlog.php')){ - $aCfgTemp=include(__DIR__.'/../config/inc_cronlog.php'); + if (file_exists(__DIR__ . '/../config/inc_cronlog.php')) { + $aCfgTemp = include(__DIR__ . '/../config/inc_cronlog.php'); $this->_sDataDir = isset($aCfgTemp['sDatadir']) ? $aCfgTemp['sDatadir'] : $this->_sDataDir; - $this->_iTtlCache = isset($aCfgTemp['iTtlCache']) ? (int)$aCfgTemp['iTtlCache'] : $this->_iTtlCache; - $this->_iMinTtl = isset($aCfgTemp['iMinTtl']) ? (int)$aCfgTemp['iMinTtl'] : $this->_iMinTtl; - $this->_iExpiredJobsFailAfter = isset($aCfgTemp['iExpiredJobsFailAfter']) ? (int)$aCfgTemp['iExpiredJobsFailAfter'] : $this->_iExpiredJobsFailAfter; - $this->_aSkipJoblogs = isset($aCfgTemp['aHidelogs']) && is_array($aCfgTemp['aHidelogs']) ? $aCfgTemp['aHidelogs'] : $this->_aSkipJoblogs; + $this->_iTtlCache = isset($aCfgTemp['iTtlCache']) ? (int) $aCfgTemp['iTtlCache'] : $this->_iTtlCache; + $this->_iMinTtl = isset($aCfgTemp['iMinTtl']) ? (int) $aCfgTemp['iMinTtl'] : $this->_iMinTtl; + $this->_iExpiredJobsFailAfter = isset($aCfgTemp['iExpiredJobsFailAfter']) ? (int) $aCfgTemp['iExpiredJobsFailAfter'] : $this->_iExpiredJobsFailAfter; + $this->_aSkipJoblogs = isset($aCfgTemp['aHidelogs']) && is_array($aCfgTemp['aHidelogs']) ? $aCfgTemp['aHidelogs'] : $this->_aSkipJoblogs; $this->_aInstances = isset($aCfgTemp['instances']) ? $aCfgTemp['instances'] : []; - $this->_sLang=isset($aCfgTemp['lang']) && $aCfgTemp['lang'] ? $aCfgTemp['lang'] : 'en-en'; - if(!file_exists(__DIR__.'/../config/lang_'.$this->_sLang.'.php')){ + $this->_sLang = isset($aCfgTemp['lang']) && $aCfgTemp['lang'] ? $aCfgTemp['lang'] : 'en-en'; + if (!file_exists(__DIR__ . '/../config/lang_' . $this->_sLang . '.php')) { header('HTTP/1.1 503 Service Temporarily Unavailable'); header('Status: 503 Service Temporarily Unavailable'); - die('ERROR: lang file for lang => "'.$this->_sLang.'" not found.<br>config/lang_'.$this->_sLang.'.php<br>does not exist.'); + die('ERROR: lang file for lang => "' . $this->_sLang . '" not found.<br>config/lang_' . $this->_sLang . '.php<br>does not exist.'); } - $this->_aLang=$aCfgTemp=include(__DIR__.'/../config/lang_'.$this->_sLang.'.php'); + $this->_aLang = $aCfgTemp = include(__DIR__ . '/../config/lang_' . $this->_sLang . '.php'); } $this->_sDataDir = str_replace("__APPDIR__", dirname(dirname(__FILE__)), $this->_sDataDir); $this->_sDataDir = str_replace('\\', '/', $this->_sDataDir); - + $this->getServers(); - - return true; } - + // ---------------------------------------------------------------------- // private // ---------------------------------------------------------------------- - + /** * chaching: get the full path of directory for caching * @return string */ - protected function _getCacheDir(){ - return $this->_sDataDir.'/__cache'; + protected function _getCacheDir(): string + { + return $this->_sDataDir . '/__cache'; } /** - * caching: get full path of a caching item - * @param string $sTaskId + * Caching: get full path of a caching item + * @param string $sTaskId Name of a task * @return string */ - protected function _getCacheFile($sTaskId){ - return $this->_getCacheDir().'/'.$sTaskId; + protected function _getCacheFile(string $sTaskId): string + { + return $this->_getCacheDir() . '/' . $sTaskId; } /** - * read logs: get full path to a servers cronjob logdata + * Read logs: get full path to a servers cronjob logdata * @return string */ - protected function _getServerlogDir(){ - return $this->_sDataDir.'/'.$this->_sActiveServer; + protected function _getServerlogDir(): string + { + return $this->_sDataDir . '/' . $this->_sActiveServer; } /** - * caching: get cached data if they exist and aren't expired - * @param string $sTaskId + * Caching: get cached data if they exist and aren't expired + * @param string $sTaskId Name of the task * @return mixed boolean|array */ - protected function _getCacheData($sTaskId){ + protected function _getCacheData(string $sTaskId): string + { // DISABLE CACHE return false; - $sFile=$this->_getCacheFile($sTaskId); - if(file_exists($sFile)){ - if (filemtime($sFile)>(date('U')-$this->_iTtlCache)){ + $sFile = $this->_getCacheFile($sTaskId); + if (file_exists($sFile)) { + if (filemtime($sFile) > (date('U') - $this->_iTtlCache)) { // echo "USE cache $sFile<br>"; return unserialize(file_get_contents($sFile)); } else { @@ -142,24 +146,25 @@ class cronlog { unlink($sFile); } } - return false; + return ''; } /** - * read logs: parse a single line in the joblog and return has with all key value items + * Read logs: parse a single line in the joblog and return has with all key value items * @param string $sLine single line in the log * @return array */ - protected function _parseJoblogLine($sLine){ - $aReturn=array(); + protected function _parseJoblogLine(string $sLine): array + { + $aReturn = []; // echo "DEBUG $sLine<br>"; // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0 - $sLine=str_replace("\n", '', $sLine); - $sLine=str_replace(':', '", "', $sLine); - $sLine=str_replace('=', '": "', $sLine); - $sLine='{"'.$sLine.'"}'; + $sLine = str_replace("\n", '', $sLine); + $sLine = str_replace(':', '", "', $sLine); + $sLine = str_replace('=', '": "', $sLine); + $sLine = '{"' . $sLine . '"}'; // echo "DEBUG $sLine<br><br>"; - $aReturn=json_decode($sLine, 1); - if(!is_array($aReturn)){ + $aReturn = json_decode($sLine, 1); + if (!is_array($aReturn)) { echo "not a JSON string<br>"; echo "DEBUG $sLine<br><br>"; die(); @@ -168,50 +173,53 @@ class cronlog { } /** - * read logs: parse the whole cronwrapper logfile and return a hash + * Read logs: parse the whole cronwrapper logfile and return a hash * @param string $sFile filename with full path + * @return array */ - protected function _parseLogfile($sFile) { - $aReturn=array( - 'SCRIPTNAME'=>false, - 'SCRIPTTTL'=>false, - 'SCRIPTSTARTTIME'=>false, - 'SCRIPTLABEL'=>false, - 'SCRIPTENDTIME'=>false, - 'SCRIPTEXECTIME'=>false, - 'SCRIPTRC'=>false, - // 'SCRIPTOUT'=>array(), - ); + protected function _parseLogfile(string $sFile): array + { + $aReturn = [ + 'SCRIPTNAME' => false, + 'SCRIPTTTL' => false, + 'SCRIPTSTARTTIME' => false, + 'SCRIPTLABEL' => false, + 'SCRIPTENDTIME' => false, + 'SCRIPTEXECTIME' => false, + 'SCRIPTRC' => false, + // 'SCRIPTOUT'=>[], + ]; $fileHandle = fopen($sFile, "r"); while (($line = fgets($fileHandle)) !== false) { // get key ... the part before "=" - $sKey=trim(preg_replace('/=.*/', '', $line)); - if($sKey && isset($aReturn[$sKey])){ + $sKey = trim(preg_replace('/=.*/', '', $line)); + if ($sKey && isset($aReturn[$sKey])) { // add value ... the part behind "=" - $aReturn[$sKey]=preg_replace('/^([A-Z]*\=)/', '', $line); + $aReturn[$sKey] = preg_replace('/^([A-Z]*\=)/', '', $line); } } fclose($fileHandle); - + // fetch unit timestamp from date values (they are like "2018-09-30 03:40:05, 1538278805") - $aReturn['SCRIPTSTARTTIME']=(int)preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']); - $aReturn['SCRIPTENDTIME']=(int)preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']); - + $aReturn['SCRIPTSTARTTIME'] = (int) preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']); + $aReturn['SCRIPTENDTIME'] = (int) preg_replace('/.*,\ /', '', $aReturn['SCRIPTSTARTTIME']); + // remove " s" from exec time value - $aReturn['SCRIPTEXECTIME']=preg_replace('/\ s$/', '', $aReturn['SCRIPTEXECTIME']); - + $aReturn['SCRIPTEXECTIME'] = preg_replace('/\ s$/', '', $aReturn['SCRIPTEXECTIME']); + return $aReturn; - + } - + /** * caching: write new data; it returns the success of write operation as bool * @param string $sTaskId - * @param [any] $data data to store; can be any serializable value - * @return boolean + * @param mixed $data data to store; can be any serializable value + * @return boolean|integer */ - protected function _writeCacheData($sTaskId, $data){ - $sFile=$this->_getCacheFile($sTaskId); + protected function _writeCacheData(string $sTaskId, mixed $data): bool|int + { + $sFile = $this->_getCacheFile($sTaskId); // echo "WRITE cache $sFile<br>"; return file_put_contents($sFile, serialize($data)); } @@ -219,37 +227,39 @@ class cronlog { // ---------------------------------------------------------------------- // public getter // ---------------------------------------------------------------------- - + /** - * get currently selected server + * Get currently selected server * @return string */ - public function getServer(){ + public function getServer(): string + { return $this->_sActiveServer; } - + /** - * get array with existing servers in data dir + * Get array with existing servers in data dir * @return array */ - public function getServers(){ - - if(is_array($this->_aServers) && count($this->_aServers)){ + public function getServers(): array + { + + if (is_array($this->_aServers) && count($this->_aServers)) { return $this->_aServers; } - - $this->_aServers=array(); + + $this->_aServers = []; // echo "DEBUG DATADIR: " . $this->_sDataDir."<br>"; - if (!is_dir($this->_sDataDir)){ + if (!is_dir($this->_sDataDir)) { echo "WARNING: no data. Check sDatadir in the config and set it to an existing directory.<br>"; die(); } if ($handle = opendir($this->_sDataDir)) { while (false !== ($entry = readdir($handle))) { - if ($entry != "." && $entry != ".." && $entry != "__cache" && is_dir($this->_sDataDir.'/'.$entry)) { + if ($entry != "." && $entry != ".." && $entry != "__cache" && is_dir($this->_sDataDir . '/' . $entry)) { // echo "DEBUG $entry<br>\n"; - $this->_aServers[$entry]=array(); + $this->_aServers[$entry] = []; } } closedir($handle); @@ -257,31 +267,32 @@ class cronlog { ksort($this->_aServers); return $this->_aServers; } - + /** - * get logs from jobfilea of the current or given server + * Get logs from jobfilea of the current or given server * @param boolean $bUseSkip hide jobs if their label matches the skip list; default: true * @return array */ - public function getServerJobHistory($bUseSkip=true){ - $aReturn=array(); - $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; + public function getServerJobHistory(bool $bUseSkip = true): bool|array + { + $aReturn = []; + $sTaskId = __FUNCTION__ . '-' . $this->_sActiveServer; - $aData=$this->_getCacheData($sTaskId); - if($aData){ + $aData = $this->_getCacheData($sTaskId); + if ($aData) { return $aData; } - $aData=array(); - - foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_serverjoblog) as $sMyJobfile){ + $aData = []; + + foreach (glob($this->_getServerlogDir() . '/' . $this->_sFileFilter_serverjoblog) as $sMyJobfile) { // echo "DEBUG: $sMyJobfile<br>"; $fileHandle = fopen($sMyJobfile, "r"); while (($line = fgets($fileHandle)) !== false) { // send the current file part to the browser - $aData=$this->_parseJoblogLine($line); - if(!$bUseSkip || array_search($aData['job'], $this->_aSkipJoblogs)===false){ - $aReturn[$aData['start']]=$aData; + $aData = $this->_parseJoblogLine($line); + if (!$bUseSkip || array_search($aData['job'], $this->_aSkipJoblogs) === false) { + $aReturn[$aData['start']] = $aData; } } fclose($fileHandle); @@ -290,75 +301,79 @@ class cronlog { $this->_writeCacheData($sTaskId, $aReturn); return $aReturn; } - + /** - * get logs from jobfilea of the current or given server + * Get logs from jobfilea of the current or given server * @return array */ - public function getServersLastLog(){ - $aReturn=array(); - $aData=array(); - foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_joblog) as $sMyJobfile){ + public function getServersLastLog(): array + { + $aReturn = []; + $aData = []; + foreach (glob($this->_getServerlogDir() . '/' . $this->_sFileFilter_joblog) as $sMyJobfile) { // echo "DEBUG: log file $sMyJobfile<br>"; - $aData=$this->_parseLogfile($sMyJobfile); - $aData['server']=$this->_sActiveServer; - $aData['logfile']= $this->_sActiveServer.'/'.basename($sMyJobfile); - $aReturn[$aData['SCRIPTSTARTTIME'].$sMyJobfile]=$aData; + $aData = $this->_parseLogfile($sMyJobfile); + $aData['server'] = $this->_sActiveServer; + $aData['logfile'] = $this->_sActiveServer . '/' . basename($sMyJobfile); + $aReturn[$aData['SCRIPTSTARTTIME'] . $sMyJobfile] = $aData; } rsort($aReturn); return $aReturn; } + /** * get logs from jobfilea of the current or given server * @return array */ - public function getRunningJobs(){ - $aReturn=array(); - $aData=array(); - foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_jobrunning) as $sMyJobfile){ + public function getRunningJobs(): array + { + $aReturn = []; + $aData = []; + foreach (glob($this->_getServerlogDir() . '/' . $this->_sFileFilter_jobrunning) as $sMyJobfile) { // echo "DEBUG: log file $sMyJobfile<br>"; - $aData=$this->_parseLogfile($sMyJobfile); - $aData['server']=$this->_sActiveServer; - $aData['logfile']= $this->_sActiveServer.'/'.basename($sMyJobfile); - $aReturn[$aData['SCRIPTSTARTTIME'].$sMyJobfile]=$aData; + $aData = $this->_parseLogfile($sMyJobfile); + $aData['server'] = $this->_sActiveServer; + $aData['logfile'] = $this->_sActiveServer . '/' . basename($sMyJobfile); + $aReturn[$aData['SCRIPTSTARTTIME'] . $sMyJobfile] = $aData; } rsort($aReturn); return $aReturn; } - + /** - * translate ... get a language specific text of a given key + * Translate: get a language specific text of a given key * @param string $id id of language text * @return string */ - public function t($id){ - return '' - .(isset($this->_aLang[$id]) ? $this->_aLang[$id] : '['.$id.'] ???') - ; + public function t($id): string + { + return $this->_aLang[$id] ?? '[' . $id . '] ???'; } // ---------------------------------------------------------------------- // public setter // ---------------------------------------------------------------------- - + /** - * set which server is selected + * Set which server is selected * The given server must exist as directory (that contains its logs) + * It returns false if the given server does not exist or has value 'ALL' * @param string $sServer server name - * @return string + * @return bool|string */ - public function setServer($sServer){ - $this->_sActiveServer=false; - if($sServer==='ALL'){ + public function setServer(string $sServer): bool|string + { + $this->_sActiveServer = false; + if ($sServer === 'ALL') { return false; } - if($sServer && !array_key_exists($sServer, $this->_aServers)){ + if ($sServer && !array_key_exists($sServer, $this->_aServers)) { echo "WARNING: server [$sServer] does not exist<br>"; return false; } - $this->_sActiveServer=$sServer; - + $this->_sActiveServer = $sServer; + return $this->_sActiveServer; } - + } \ No newline at end of file