Skip to content
Snippets Groups Projects
Select Git revision
  • 3e550af3ef43a144c310c8d21c9f4988e2e88849
  • master default protected
  • simple-task/7248-eol-check-add-node-22
  • 6877_check_iml_deployment
4 results

check_mysqlserver

Blame
  • cronlog-renderer.class.php 29.02 KiB
    <?php
    require_once 'cronlog.class.php';
    /**
     * ______________________________________________________________________
     * 
     *  _____                 _       _             _                        
     * /  __ \               (_)     | |           (_)                       
     * | /  \/_ __ ___  _ __  _  ___ | |__   __   ___  _____      _____ _ __ 
     * | |   | '__/ _ \| '_ \| |/ _ \| '_ \  \ \ / / |/ _ \ \ /\ / / _ \ '__|
     * | \__/\ | | (_) | | | | | (_) | |_) |  \ V /| |  __/\ V  V /  __/ |   
     *  \____/_|  \___/|_| |_| |\___/|_.__/    \_/ |_|\___| \_/\_/ \___|_|   
     *                      _/ |                                             
     *                     |__/                                              
     * ______________________________________________________________________
     * 
     * The cronjob viewer for centralized monitoring of cronjobs using
     * Axels cronrwapper (see <https://github.com/axelhahn/cronwrapper>).
     * 
     * You can browse all servers to see 
     * - the last status of all cronjobs
     * - results an execution times of running jonbs
     * - a timeline for all jobs running > 60s
     * 
     * 
     * Free software. Open Source. GNU GPL 3.
     * SOURCE: <https://git-repo.iml.unibe.ch/iml-open-source/cronlog-viewer>
     * 
     * ______________________________________________________________________
     * 
     * The class cronlog-renderer contains visual methods to render html
     * @see cronlog.class.php
     *
     * @license GNU GPL 3.0
     * @author Axel Hahn <axel.hahn@iml.unibe.ch>
     */
    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;
        
        /**
         * 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){
                return '';
            }
            $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("last-entry"), $iAge)
                .'</div>'
                ;
        }
        
        /**
         * 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;';
        }
    
        /**
         * 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(){
            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").'",
                    "oPaginate":{
                        "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
         * 
         * @param array  $aData   result of $this->getServerLogs()
         * @return string
         */
        public function renderCronlogs($aData=false){
            $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer;
            $sHtml=$this->_getCacheData($sTaskId);
            if($sHtml){
                return $sHtml;
            }
            $sHtml='';
            
            if(!$aData){
                $aData=array_merge($this->getRunningJobs(), $this->getServersLastLog());
            }
            $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"),
                        $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>';
                    }
                }
                // $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);
    
                // ticket #5850 - check hostname vs. servername in logfile
                $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']),
                    ];
                }
                
                if(!strstr($sServerFromLogfile, ".")){
                    $aErrors[]=[
                        $this->t('error-no-fqdn-label'),
                        sprintf($this->t('error-no-fqdn-description'),$sServerFromLogfile),
                    ];
                }
                
                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']),
                        $this->t('error-exitcode-description'),
                    ];
                }
                if(!$aEntry['SCRIPTLABEL']){
                    $aErrors[]=[
                        $this->t('error-no-label-label'),
                        $this->t('error-no-label-description'),
                    ];
                }
                
                
                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=$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>';
                    } else {
                        $sColStatus.=$this->t('status-ok');
                    }
                } else {
                    $bIsRunning=true;
                    $sColStatus.=$this->t('status-running');
                }
                // 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'
                            .($aEntry['SCRIPTEXECTIME']>$this->_iMinTime4Timeline ? ' class="message-warning"' : '' )
                        . '>'
                            .'<span style="display: none">'.str_pad((int)$aEntry['SCRIPTEXECTIME'], 6, '0', STR_PAD_LEFT).'</span>'
                            .(int)$aEntry['SCRIPTEXECTIME'].'s'
                            .((int)$aEntry['SCRIPTEXECTIME']>100 ? ' ('.round((int)$aEntry['SCRIPTEXECTIME']/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__.' -->
                '
            
                . '<h3>'.$this->t('logs-head').'</h3>'
                . '<p class="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> &nbsp; ' . $iErrors.'</a> ' : '')
                            . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> &nbsp; ' . $iOK.'</a>' : '') 
                            . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> &nbsp; ' . 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().'
                             }); 
                           });'
                        . '</script>'
                        : ''
                )
    
                // init datatable
    
                . '
                <!-- ENDE '.__METHOD__.' -->
                '
                ;
            $this->_writeCacheData($sTaskId, $sHtml);
            return $sHtml;
        }
       
        /**
         * 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){
                $this->setServer($sServer);
                $aData=array_merge($aData, $this->getRunningJobs(),$this->getServersLastLog());
            }
            $this->setServer('ALL');
            return $this->renderCronlogs($aData);
        }
        
        /**
         * get html code for a switcher of multiple instances
         * 
         * @param array  $aData   result of $oCL->getServerLogs()
         * @return string
         */
        public function renderInstances(){
            if(count($this->_aInstances) < 2){
                return false;
            }
            $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>';
            }
            return $sReturn;
        }
        /**
         * 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(
                        $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>';
                    }
                }
                $iLast=max(array($iLast, date("U", $aEntry['start'])));
                $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>'
                        . '<td>'.( $aEntry['rc'] ? $this->t('status-error') : $this->t('status-ok') ).'</td>'
                        . '</tr>'
                        ;
            }
            $sIdTable='datatable2';
            $sHtml='
                <!-- START '.__METHOD__.' -->
                '
                . '<h3>'.$this->t('history-head').'</h3>'
                . '<p class="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> &nbsp; ' . $iErrors.'</a> ' : '')
                    . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> &nbsp; ' . $iOK.'</a>' : '') 
                    . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> &nbsp; ' . count($aData).'</a>' : '' )
                . '</div>'
                . '<br>'
    
                . '<table id="'.$sIdTable.'">'
                . '<thead><tr>'.$sTblHead.'</tr></thead>'
                . '<tbody>'
                    .$sHtml
                .'</tbody>'
                . '</table>'
    
                // init datatable
                . (count($aData)
                    ? '<script>'
                        . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({
                                "retrieve": true, 
                                "aaSorting":[[0,"desc"]], 
                                "aLengthMenu":[[25,100,-1],[25,100,"---"]]
                                '.$this->_getDatatableLanguage().'
                            });} );'
                        . '</script>'
                    : ''
                    )
    
                . '
                <!-- 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){
                $this->setServer($sServer);
                $aData=array_merge($aData, $this->getServerJobHistory());
            }
            $this->setServer('ALL');
            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(false);
            }
            $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>'.$this->t('timeline-head').'</h3>'
            . '<p class="hint">'
                . sprintf($this->t('timeline-hint'), $this->_iMinTime4Timeline)
            . '</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>
    
                <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>
                ' 
                : $this->t('graph-no-data').'<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');
            return $this->renderJobGraph($aData);
        }
        
        /**
         * used in config/page-replacements.php
         * generate an array of javascript lang texts
         */
        public function renderJSLang(){
            $aReturn=[];
    
            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"
            ;
        }
        
       /**
        * 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){
                return $sHtml . $this->t('error-nologfile');
            }
            if(strstr($sLogfile, '..')){
                return $sHtml . $this->t('error-dots-not-allowed');
            }
            // $sMyFile=$this->_getServerlogDir().'/'.$sLogfile;
            $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>';
                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;
       }
        
        /**
         * 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>';
            }
            $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;
            return $sHtml;
        }
        
    }