From 1a9da6819ce1820d178e3cb72fdc35fe9d37c611 Mon Sep 17 00:00:00 2001
From: "Hahn Axel (hahn)" <axel.hahn@unibe.ch>
Date: Fri, 20 Sep 2024 12:30:17 +0200
Subject: [PATCH] added type declarations; update php docs

---
 classes/cronlog-renderer.class.php | 783 +++++++++++++++--------------
 classes/cronlog.class.php          | 317 ++++++------
 2 files changed, 568 insertions(+), 532 deletions(-)

diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php
index 3888200..cf5a377 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> &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({
+                ? '<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().'
+                            ' . $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> &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>' : '' )
+            . $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>'
+            . '<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 de0ed21..e3c596d 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
-- 
GitLab