<?php require_once 'cronlog.class.php'; /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ /** * Description of cronlog-renderer * * @author hahn */ class cronlogrenderer extends cronlog{ protected $_iMinTime4Timeline = 60; /** * show date of last data and last access * @param type $iLast * @return string */ protected function _renderAccessAndAge($iLast){ if(!$iLast){ return ''; } $iAge=round((date('U')-$iLast)/60); return '' . 'Abruf: '.date("Y-m-d H:i:s").' min<br>' . 'letzter Eintrag: vor '.$iAge.' min<br><br>' ; } /** * get html code for a table with events of executed cronjobs * * @param array $aData result of $oCL->getServerLogs() * @return string */ public function renderCronlogs($aData=false){ $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; $sHtml=$this->_getCacheData($sTaskId); if($sHtml){ return $sHtml; } $sHtml=''; if(!$aData){ $aData=$this->getServersLastLog(); } // $sHtml='DEBUG: <pre>'.print_r($aData, 1).'</pre>'; $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('Startzeit', 'Label', 'Server', 'Dauer', 'TTL', 'Exitcode', 'Kommentar' /*, 'Aktionen'*/) 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(); $iNextRun=$aEntry['SCRIPTSTARTTIME']+((int)$aEntry['SCRIPTTTL']*1.5*60); if($iNextRun < date("U")){ $aErrors[]='outdated'; } if($aEntry['SCRIPTRC']>0){ $aErrors[]='exitcode is <>0'; } if(!$aEntry['SCRIPTLABEL']){ $aErrors[]='no label?'; } if(count($aErrors)){ $iErrors++; $sClass='message-error'; } else { $iOK++; $sClass='message-ok'; } $sHtml.='<tr class="'.$sClass.'" onclick="showFile(\''.$aEntry['logfile'].'\');" title="Klick=['.$aEntry['logfile'].'] anzeigen ">' . '<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'].'s' .((int)$aEntry['SCRIPTEXECTIME']>100 ? ' ('.round((int)$aEntry['SCRIPTEXECTIME']/60).'min)' : '') .'</td>' . '<td>'.$aEntry['SCRIPTTTL'].'</td>' . '<td>'.$aEntry['SCRIPTRC'].'</td>' . '<td>'.(count($aErrors) ? 'FEHLER:<br>*'.implode('<br>*', $aErrors) : 'OK').'</td>' // . '<td><button onclick="showFile(\''.$aEntry['logfile'].'\');">Ansehen</button></td>' . '</tr>' ; } $sIdTable='datatable1'; $sHtml=' <!-- START '.__METHOD__.' --> ' . '<h3>Letztes Logfile pro Job</h3>' . '<p class="hint">' . 'Von jedem Cronjob kann man das jeweils letzte Log im Detail ansehen. Mit Klick in der Tabelle wird die Logdatei geöffnet.' . '</p>' . '<div>' . $this->_renderAccessAndAge($iLast) . 'gesamt: <strong>' . count($aData).'</strong>' . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '') . '<br>' . '</div>' . '<table id="'.$sIdTable.'">' . '<thead><tr>'.$sTblHead.'</tr></thead>' . '<tbody>' .$sHtml .'</tbody>' . '</table>' // init datatable . '<script>' . '$(document).ready( function () { $(\'#'.$sIdTable.'\').DataTable({"retrieve": true, "bPaginate":false, "aaSorting":[[0,"desc"]]}); } );' . '</script>' . ' <!-- ENDE '.__METHOD__.' --> ' ; $this->_writeCacheData($sTaskId, $sHtml); return $sHtml; } public function renderCronlogsOfAllServers(){ $aData=array(); foreach (array_keys($this->getServers()) as $sServer){ $this->setServer($sServer); $aData=array_merge($aData, $this->getServersLastLog()); } $this->setServer('ALL'); // echo '<pre>'.print_r($aData, 1).'</pre>'; return $this->renderCronlogs($aData); } /** * 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){ return $sHtml; } $sHtml=''; if(!$aData){ $aData=$this->getServerJobHistory(); } $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('Startzeit', /*'Ende',*/ 'Label', 'Server', 'Dauer', 'TTL', 'Exitcode') as $sKey){ $sTblHead.='<th>'.$sKey.'</th>'; } } $iLast=max(array($iLast, date("U", $aEntry['start']))); // $sViewerUrl='viewer.php?host='.$aEntry['host'].'&job='.$aEntry['job']; $sClass='message-'.($aEntry['rc']?'error':'ok'); if($aEntry['rc']){ $iErrors++; } else { $iOK++; } $sHtml.='<tr class="'.$sClass.'">' . '<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'].'s' .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') . '</td>' . '<td>'.$aEntry['ttl'].'</td>' . '<td>'.$aEntry['rc'].'</td>' . '</tr>' ; } $sIdTable='datatable2'; $sHtml=' <!-- START '.__METHOD__.' --> ' . '<h3>History</h3>' . '<p class="hint">' . 'Von den gestarteten Cronjobs werden die Ausführungen und deren Exitcode für 6 Tage aufgehoben.' . '</p>' . '<div>' . $this->_renderAccessAndAge($iLast) . 'gesamt: <strong>' . count($aData).'</strong>' . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '') . '<br><br>' . '</div>' . '<table id="'.$sIdTable.'">' . '<thead><tr>'.$sTblHead.'</tr></thead>' . '<tbody>' .$sHtml .'</tbody>' . '</table>' // init datatable . '<script>' . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({"retrieve": true, "aaSorting":[[0,"desc"]],"aaSorting":[[0,"desc"]], "aLengthMenu":[[25,100,-1],[25,100,"---"]]});} );' . '</script>' . ' <!-- ENDE '.__METHOD__.' --> ' ; $this->_writeCacheData($sTaskId, $sHtml); return $sHtml; } public function renderJoblistOfAllServers(){ $aData=array(); foreach (array_keys($this->getServers()) as $sServer){ $this->setServer($sServer); $aData=array_merge($aData, $this->getServerJobHistory()); } $this->setServer('ALL'); // echo '<pre>'.print_r($aData, 1).'</pre>'; return $this->renderJoblist($aData); } /** * get html code for a timeline with events of executed cronjobs * * TODO: * for rendering of several hosts ... see grouping in * http://visjs.org/examples/timeline/groups/groupsOrdering.html * * @param array $aData result of $oCL->getServerLogs() * @return string */ public function renderJobGraph($aData=false){ $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; $sHtml=$this->_getCacheData($sTaskId); if($sHtml){ return $sHtml; } $sHtml=''; static $iGraphCounter; if(!isset($iGraphCounter)){ $iGraphCounter=0; } $iGraphCounter++; if(!$aData){ $aData=$this->getServerJobHistory(); } $sDivId='vis-timeline-'.$iGraphCounter; $aDataset=array(); $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, /* 'start'=>(int)date("U", $aEntry['start']), 'end'=>(int)date("U", $aEntry['end']), * 'start'=>'Date'.date('Y-m-d\TH:i:s.000\Z', $aEntry['start']), 'end'=>'Date'.date('Y-m-d\TH:i:s.000\Z', $aEntry['end']), * 'start'=>date('Y-m-d\TH:i:s.000\Z', $aEntry['start']), '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>' . 'exectime: ' .$aEntry['exectime'].'s' .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') . '<br>' . 'rc = ' . $aEntry['rc'].'<br>' , ); // if($iEntry>=265){break;} } } $sHtml.=' <!-- START '.__METHOD__.' --> <h3>Graph mit Timeline</h3> <p class="hint"> Aus der History der letzten 6 Tage werden die Cronjobs mit mehr als '.$this->_iMinTime4Timeline.' Sekunden Laufzeit dargestellt.<br> So kann man ggf. Konflikte und Ungereimtheiten finden.<br> Innerhalb der Timeline kann man mit dem Mausrad den Zoom verändern. </p> <p> Jobs mit mehr als '.$this->_iMinTime4Timeline.'s Laufzeit: <strong>'.count($aDataset).'</strong> (ges.: '.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.'"); // Create a DataSet (allows two way data-binding) var items = new vis.DataSet('.json_encode($aDataset).'); // Configuration for the Timeline var options = { zoomMin: 1000 * 60 * 60, // an hour zoomMax: 1000 * 60 * 60 * 24 * 14 // 2 weeks }; // Create a Timeline var timeline = new vis.Timeline(container, items, options); // fix: some timelines do not properly wirk ... but I make them visible $(\'#'.$sDivId.' .vis-timeline\').css(\'visibility\', \'visible\'); </script> ' : '(kein Graph)<br>') .' <!-- ENDE '.__METHOD__.'--> '; $this->_writeCacheData($sTaskId, $sHtml); return '' .$sHtml // . strlen($sHtml).' byte - ' . '<pre>'.htmlentities($sHtml).'</pre><br>' ; } public function renderJobGraphOfAllServers(){ $aData=array(); foreach (array_keys($this->getServers()) as $sServer){ $this->setServer($sServer); $aData=array_merge($aData, $this->getServerJobHistory()); } $this->setServer('ALL'); // echo '<pre>'.print_r($aData, 1).'</pre>'; return $this->renderJobGraph($aData); } /** * 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();"><i class="fas fa-chevron-left"></i> back</button><br><br>' . '<h3>Logfile '.basename($sLogfile).'</h3>' ; if(!$sLogfile){ return $sHtml . 'ERROR: empty filename for log file was given.'; } if(strstr($sLogfile, '..')){ return $sHtml . 'ERROR: wrong log file chars [..] are not allowed.'; } // $sMyFile=$this->_getServerlogDir().'/'.$sLogfile; $sMyFile=$this->_sDataDir.'/'.$sLogfile; if(!file_exists($sMyFile)){ return $sHtml . 'ERROR: The requested logfile<br>['.$sMyFile.']<br>does not exist (anymore).'; } if ($fileHandle = fopen($sMyFile, "r")) { $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>'; } else { $sKey=trim(preg_replace('/=.*/', '', $line)); $sValue=preg_replace('/^([A-Z]*\=)/', '', $line); $sDivClass=''; switch($sKey){ case 'SCRIPTRC': $sDivClass=(int)$sValue===0 ? 'message-ok' : 'message-error'; break; case 'JOBEXPIRE': $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); // 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>'; } } $sHtml.='</pre></div>'; } return $sHtml; } public function renderServerlist($sSelectedItem=false){ $sHtml=''; $sHtml.='<option value="ALL"' .($sSelectedItem===false || $sSelectedItem==='ALL' ? ' selected="selected"' : '') . '>[ALLE]</option>'; foreach($this->getServers() as $sServer=>$aData){ $sHtml.='<option value="'.$sServer.'"' .($sSelectedItem===$sServer ? ' selected="selected"' : '') .'>'.$sServer.'</option>'; } $sHtml=$sHtml ? '<select' . ' size="'.(count($this->getServers())+1).'"' // . ' size="1"' . ' onchange="setServer(this.value); return false;"' . '>'.$sHtml.'</select>' : false; return $sHtml; } }