-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
cronlog-renderer.class.php 18.57 KiB
<?php
require_once 'cronlog.class.php';
/**
* central cronlog viewer
*
* cronlog-renderer contains visual methods to render html
* @see cronlog.class.php
*
* @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', '$?', 'Expired', 'Status' /*, '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']*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 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'
.($aEntry['SCRIPTEXECTIME']>$this->_iMinTime4Timeline ? ' class="message-warning"' : '' )
. '>'
.(int)$aEntry['SCRIPTEXECTIME'].'s'
.((int)$aEntry['SCRIPTEXECTIME']>100 ? ' ('.round((int)$aEntry['SCRIPTEXECTIME']/60).'min)' : '')
.'</td>'
. '<td>'.$aEntry['SCRIPTTTL'].'</td>'
. '<td class="'.($aEntry['SCRIPTRC']>0 ? 'message-error' : 'message-ok' ).'">'
.$aEntry['SCRIPTRC']
.'</td>'
. '<td class="'.($iNextRun < date("U")
? ($iNextRun < date("U") -600 ? 'message-error' : 'message-warning')
: '' ).'">'
. date("Y-m-d H:i", $iNextRun).'</td>'
. '<td class="'.(count($aErrors) ? 'message-error' : '' ).'">'
// .(count($aErrors) ? 'FEHLER:<br>*'.implode('<br>*', $aErrors) : 'OK').'</td>'
.(count($aErrors) ? 'FEHLER' : '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.'" class="table-striped">'
. '<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', '$?') 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>'
. '<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>'
. '</tr>'
;
}
$sIdTable='datatable2';
$sHtml='
<!-- START '.__METHOD__.' -->
'
. '<h3>History</h3>'
. '<p class="hint">'
. 'Von den gestarteten Cronjobs werden die Ausführungszeiten 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 work ... 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;
}
}