Skip to content
Snippets Groups Projects
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&ouml;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&uuml;hrungszeiten und deren Exitcode f&uuml;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&auml;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;
    }
    
}