diff --git a/.gitignore b/.gitignore
index e7719900d590f77637a881ba53800d59425caa25..c1ff376a9e1cdcc8d0aea89a7e13a1cac5464794 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@
 /cronjobs.html
 /winscp.rnd
 config/inc_cronlog.php
-/data__/
\ No newline at end of file
+/data__/
+allcronlogs/*
diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php
index 388820040cb3430809a3cb10e2d8cced08d55c1c..b7b9fb33ba38fca846024756f16aabc202d72aac 100644
--- a/classes/cronlog-renderer.class.php
+++ b/classes/cronlog-renderer.class.php
@@ -32,434 +32,472 @@ require_once 'cronlog.class.php';
  *
  * @license GNU GPL 3.0
  * @author Axel Hahn <axel.hahn@iml.unibe.ch>
+ * 
+ * 2024-10-01  <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;
+
+    /**
+     * Max count of entries in history table to prevent freezing of the screen
+     * when displaying a lot of entries, eg all logs of 100+ servers
+     * @var int
+     */
+    protected int $_iHistoryLimit = 1000;
+
     /**
-     * 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);
+        $sAge = ($iAge > 60*24 ? '<span class="message-error">'.$iAge.'</span>' : $iAge);
         return '<div class="accessandage">'
-            . sprintf($this->t("request-time"), date("Y-m-d H:i:s")).'<br>'
-            . sprintf($this->t("last-entry"), $iAge)
-            .'</div>'
-            ;
+            . sprintf($this->t("request-time"), date("Y-m-d H:i:s")) . '<br>'
+            . sprintf($this->t("last-entry"), $sAge)
+            .($iAge > 60*24
+                ? " <a href=\"?deleteserverlogs=$this->_sActiveServer\" class=\"btn btn-danger\">Delete server</a>"
+                : ''
+            )
+            . '</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 align="right"'
+                . ((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 align="right"'
+                . ($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 align="right" 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']) ?? 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 renderHistoryTable(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();
+        }
+
+        // sort by starting time - especially before cutting the list
+        $aTmp=[];
+        foreach($aData as $sKey => $aEntry) {
+            $aTmp[$aEntry['start'].'__'.$aEntry['host'].'__'.$aEntry['job']]=$aEntry;
         }
+        krsort($aTmp);
+        $aData = array_values($aTmp);
 
-        $sTblHead='';
-        $iOK=0;
-        $iErrors=0;
-        $iLast=false;
+        // render table
+        $sTblHead = '';
+        $iOK = 0;
+        $iErrors = 0;
+        $iLast = false;
+        $iCounter = 0;
         // 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) {
+            $iCounter++;
+            if($iCounter>$this->_iHistoryLimit) {
+                break;
+            }
+            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 align="right"'
+                . ($aEntry['exectime'] > $this->_iMinTime4Timeline ? ' class="message-warning"' : '')
+                . '>'
+                . $aEntry['exectime'] . 's'
+                . ($aEntry['exectime'] > 100 ? ' (' . round($aEntry['exectime'] / 60) . 'min)' : '')
+                . '</td>'
+                . '<td align="right">' . $aEntry['ttl'] . '</td>'
+                . '<td  align="right" 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 renderHistoryOfAllServers(): 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);
+        return $this->renderHistoryTable($aData);
     }
-    
+
     /**
      * get html code for a timeline with events of executed cronjobs
      * 
@@ -470,32 +508,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 +546,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 = {
@@ -557,144 +596,155 @@ class cronlogrenderer extends cronlog{
 
               // Create a Timeline
               var timeline = new vis.Timeline(container, items, options);
+
+              // set focus to newest jobs
+              // timeline.moveTo("'.date("Y-m-d H:i:s", time()-60*60*24*4).'");
               
               // 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 de0ed21f8c2a3165bfc952c1f64ed99d8436d3e3..2340d1fd37af0b58a70d24feb66586b1e7e744e8 100644
--- a/classes/cronlog.class.php
+++ b/classes/cronlog.class.php
@@ -31,110 +31,205 @@
  *
  * @license GNU GPL 3.0
  * @author Axel Hahn <axel.hahn@iml.unibe.ch>
+ * 
+ * 2024-10-01  <axel.hahn@unibe.ch>  added type declarations; update php docs
  */
 
-class cronlog {
-    
-    
-    protected $_sDataDir = "__APPDIR__/data";
-    protected $_iTtlCache = 60; // in sec
+class cronlog
+{
+
 
     /**
-     * when show an error for expired jobs (latency to execute job and sync logs)
+     * Data dir with subdirs per host and its cronjob logs
+     * @var string
+     */
+    protected string $_sDataDir = "__APPDIR__/data";
+
+    /**
+     * TTL for cached rendered html output of logs/ history/ timeline
+     * @var int
+     */
+    protected int $_iTtlCache = 60; // in sec
+
+    /**
+     * When show an error for expired jobs (keep in mind the 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 int $_iExpiredJobsFailAfter = 60 * 30; // in sec
 
-    protected $_aServers = array();
-    protected $_sActiveServer = false;
+    /**
+     * Minimal TTL for a cronjob
+     * @var int
+     */
+    protected int $_iMinTtl = 0; // in sec
+
+    /**
+     * List of cronjobs to hide in the web view
+     * @var array
+     */
+    protected array $_aSkipJoblogs = [];
+
+    /**
+     * Array of cronlog viewer instances to show a navigation to switch between them
+     * @var array
+     */
+    protected array $_aInstances = [];
+
+    /**
+     * Array of all servers with cronjobs
+     * @var array
+     */
+    protected array $_aServers = [];
+
+    /**
+     * Current server
+     * @var string
+     */
+    protected string $_sActiveServer = '';
+
+    /**
+     * Filefilter for finished joblogs
+     * @var string
+     */
+    protected string $_sFileFilter_serverjoblog = '*joblog*.done';
+
+    /**
+     * Filefilter for history logs
+     * @var string
+     */
+    protected string $_sFileFilter_joblog = '*.log';
     
-    protected $_sFileFilter_serverjoblog = '*joblog*.done';
-    protected $_sFileFilter_joblog = '*.log';
-    protected $_sFileFilter_jobrunning = '*.log.running*';
+    /**
+     * Filefilter for running jobs
+     * @var string
+     */
+    protected string $_sFileFilter_jobrunning = '*.log.running*';
 
-    protected $_sLang = ''; // language ... read from config file
-    protected $_aLang = []; // language data
+    /**
+     * Current language
+     * @var string
+     */
+    protected string $_sLang = ''; // language ... read from config file
+
+    /**
+     * Array of language texts
+     * @var array
+     */
+    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);
-        
+
+        if (isset($_GET['deleteserverlogs']) && $_GET['deleteserverlogs']){
+            $this->_deleteServerlogs($_GET['deleteserverlogs']);
+        }
+
         $this->getServers();
-                
-        return true;
     }
-    
+
     // ----------------------------------------------------------------------
     // private
     // ----------------------------------------------------------------------
 
-    
+
+    protected function _deleteServerlogs(string $sServer): bool
+    {
+        $dirCronlog="$this->_sDataDir/$sServer";
+
+        if (! is_dir($dirCronlog)) {
+            // throw new InvalidArgumentException("$dirCronlog must be a directory");
+            return false;
+        }
+        if (substr($dirCronlog, strlen($dirCronlog) - 1, 1) != '/') {
+            $dirCronlog .= '/';
+        }
+        $files = array_merge(
+            glob($dirCronlog . '*', GLOB_MARK),
+            glob($this->_getCacheDir() . "/*$sServer", GLOB_MARK)
+        );
+        foreach ($files as $file) {
+            /*
+            if (is_dir($file)) {
+                deleteDir($file);
+            } else {
+            */
+            @unlink($file);
+            //}
+        }
+        return @rmdir($dirCronlog);
+    }
     /**
      * chaching: get the full path of directory for caching
      * @return string
      */
-    protected function _getCacheDir(){
-        return $this->_sDataDir.'/__cache';
+    protected function _getCacheDir(): string
+    {
+        if(!is_dir("$this->_sDataDir/__cache")) {
+            mkdir("$this->_sDataDir/__cache", 0750, true);
+        }
+        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
-     * @return mixed boolean|array
+     * Caching: get cached data for rendered html output 
+     * if they exist and isn't expired
+     * 
+     * @param  string  $sTaskId  Name of the task
+     * @return mixed string|array
      */
-    protected function _getCacheData($sTaskId){
-        // DISABLE CACHE return false;
-        $sFile=$this->_getCacheFile($sTaskId);
-        if(file_exists($sFile)){
-            if (filemtime($sFile)>(date('U')-$this->_iTtlCache)){
+    protected function _getCacheData(string $sTaskId): string|array
+    {
+        // DISABLE CACHE 
+        // return false;
+        $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 +237,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 +264,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 +318,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 +358,32 @@ class cronlog {
         ksort($this->_aServers);
         return $this->_aServers;
     }
-    
+
     /**
-     * get logs from jobfilea of the current or given server
+     * Get logs from jobfiles 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['host'].'__'.$aData['job']] = $aData;
                 }
             }
             fclose($fileHandle);
@@ -290,75 +392,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
diff --git a/classes/htmlelements.class.php b/classes/htmlelements.class.php
index 91943c7c97d4a3226b6a2c457496ed85b08ef447..b890fda4dd77302de87fa59e603fb353ce763543 100755
--- a/classes/htmlelements.class.php
+++ b/classes/htmlelements.class.php
@@ -15,27 +15,33 @@
  *    - icon  - will be added as <i class="[icon value]"></i> to the label
  * 
  * @author Axel
+ * 
+ * 2024-07-04  <axel.hahn@unibe.ch>  added type declarations; update php docs
+ * 2024-08-26  <axel.hahn@unibe.ch>  remove unneeded methods; simplify icon methods; update phpdocs
  */
-class htmlelements {
+class htmlelements
+{
 
     /**
-     * set of auto generated icon prefixes
-     * @var type 
+     * Extracted label from array with attributes
+     * @var string
      */
-    var $_aIcons=array(
-            // 'fa-'=>'fa ',
-        );
-    
     var $_sLabel = '';
-    var $_aAttributes = array();
-    
+
+    /**
+     * Array of attributes for a html tag
+     * @var array
+     */
+    var $_aAttributes = [];
+
 
     // ----------------------------------------------------------------------
     // CONSTRUCTOR
     // ----------------------------------------------------------------------
-    
-    public function __construct() {
-        return true;
+
+    public function __construct()
+    {
+        // nothiung here
     }
 
     // ----------------------------------------------------------------------
@@ -43,27 +49,28 @@ class htmlelements {
     // PRIVATE FUNCTIONS 
     // 
     // ----------------------------------------------------------------------
-    
-    
+
+
     /**
      * generate html attibutes with all internal attributes key -> values
+     * to be added in opening tag
      * @return string
      */
-    protected function _addAttributes() {
+    protected function _addAttributes(): string
+    {
         $sReturn = '';
         foreach ($this->_aAttributes as $sAttr => $sValue) {
-            if(is_array($sValue)){
-                echo "ERROR: an html tag was defined with array in attribute [$sAttr]:<br><pre>".print_r($this->_aAttributes, 1)."</pre>";
+            if (is_array($sValue)) {
+                echo "ERROR: an html tag was defined with array in attribute [$sAttr]:<br><pre>" . print_r($this->_aAttributes, 1) . "</pre>";
             }
-            $sReturn .= ' '.$sAttr . '="' . $sValue . '"';
-            
+            $sReturn .= " $sAttr=\"$sValue\"";
         }
         return $sReturn;
     }
-    
-    
+
+
     /**
-     * internal helper: fetch all attributes from key-value hash; 
+     * Internal helper: fetch all attributes from key-value hash; 
      * Specialties here:
      * - label will be extracted from key 'label' 
      * - and optional existing key 'icon' will be added at beginning of a label
@@ -71,17 +78,18 @@ class htmlelements {
      * @param array $aAttributes
      * @return boolean
      */
-    protected function _setAttributes($aAttributes){
-        $this->_sLabel='';
-        if(isset($aAttributes['icon']) && $aAttributes['icon']){
-            $this->_sLabel.=$this->getIcon($aAttributes['icon']);
+    protected function _setAttributes(array $aAttributes): bool
+    {
+        $this->_sLabel = '';
+        if (isset($aAttributes['icon']) && $aAttributes['icon']) {
+            $this->_sLabel .= $this->getIcon($aAttributes['icon']);
             unset($aAttributes['icon']);
         }
-        if(isset($aAttributes['label']) && $aAttributes['label']){
+        if (isset($aAttributes['label']) && $aAttributes['label']) {
             $this->_sLabel .= $aAttributes['label'];
             unset($aAttributes['label']);
         }
-        $this->_aAttributes=$aAttributes;
+        $this->_aAttributes = $aAttributes;
         return true;
     }
 
@@ -91,21 +99,22 @@ class htmlelements {
     // HTML GENERIC
     // 
     // ----------------------------------------------------------------------
-    
+
     /**
-     * generic function to get html code for a single tag 
+     * Generic function to get html code for a single tag 
      * 
      * @param string   $sTag          tag name
      * @param array    $aAttributes   array with attributes (optional including 'icon' and 'label')
      * @param boolean  $bCloseTag     optional: set false if tag has no closing tag (= ending with "/>")
-     * @return type
+     * @return string html code
      */
-    public function getTag($sTag, $aAttributes, $bCloseTag=true){
+    public function getTag(string $sTag, array $aAttributes, bool $bCloseTag = true): string
+    {
         $sTpl = $bCloseTag ? "<$sTag%s>%s</$sTag>" : "<$sTag %s/>%s";
         $this->_setAttributes($aAttributes);
         return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel);
     }
-    
+
     // ----------------------------------------------------------------------
     // 
     // PUBLIC FUNCTIONS
@@ -114,109 +123,22 @@ class htmlelements {
     // ----------------------------------------------------------------------
 
     /**
-     * helper detect prefix of a string add prefix of a framework
+     * Helper detect prefix of a string add prefix of a framework
      * i.e. value "fa-close" detects font awesome and adds "fa " as prefix
      * 
      * @param string $sIconclass
-     * @return boolean
+     * @return string HTML code
      */
-    public function getIcon($sIconclass=false){
-        if(!$sIconclass){
+    public function getIcon(string $sIconclass = ''): string
+    {
+        if (!$sIconclass) {
             return '';
         }
-        $sPrefix='';
-        foreach ($this->_aIcons as $sPrefix =>$add) {
-            if (strpos($sIconclass, $sPrefix)===0){
-                $sPrefix=$add;
-                continue;
-            }
-        }
-        // do not use this .. it overrides internal attribute vars
-        // return $this->getTag('i', array('class'=>$sPrefix.$sIconclass));
-        return '<i class="'.$sPrefix.$sIconclass.'"></i> ';
-    }
-   
-
-    // ----------------------------------------------------------------------
-    // 
-    // PUBLIC FUNCTIONS
-    // HTML COMPONENTS
-    // 
-    // ----------------------------------------------------------------------
 
-    /**
-     * get html code for an input field
-     * 
-     * @param array $aAttributes  attributes of the select tag
-     * @return string
-     */
-    public function getFormInput($aAttributes){
-        $sTpl = '<input %s/>';
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes());
-    }
-    /**
-     * get html code for an option field in a select drop down
-     * 
-     * @param array $aAttributes  attributes of the option tag
-     * @return string
-     */
-    public function getFormOption($aAttributes){
-        $sTpl = '<option %s>%s</option>';
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes(), $this->_sLabel);
-    }
-    /**
-     * get html code for a select drop down
-     * 
-     * @param array $aAttributes  attributes of the select tag
-     * @param array $aOptions     array for all option fields
-     * @return string
-     */
-    public function getFormSelect($aAttributes, $aOptions=array()){
-        // $sTpl = '<select %s>%s</select>';
+        // do not use this .. it overrides internal attribute vars
+        // return $this->getTag('i', ['class'=>$sIconclass]);
 
-        if(!count($aOptions)){
-            return false;
-        }
-        $sOptions='';
-        foreach($aOptions as $aOptionAttributes){
-            // $sOptions.=$this->getFormOption($aOptionAttributes);
-            $sOptions.=$this->getTag('option', $aOptionAttributes);
-        }
-        $aAttributes['label']=$sOptions;
-        return $this->getTag('select', $aAttributes);
-        /*
-        $this->_setAttributes($aAttributes);
-        return sprintf($sTpl, $this->_addAttributes(), $sOptions);
-         * 
-         */
+        return "<i class=\"$sIconclass\"></i>&nbsp;&nbsp;";
     }
 
-    public function getTable($aHead, $aBody, $aTableAttributes=array()){
-        $sReturn='';
-        $sTdata='';
-        $sThead='';
-        $sTpl = '<table %s>'
-                . '<thead><tr>%s</tr></thead>'
-                . '<tbody>%s</tbody>'
-                . '</table>';
-        
-        foreach($aHead as $sTh){
-            $sThead.='<th>'.$sTh.'</th>';
-        }
-        foreach($aBody as $aTr){
-            $sTdata.='<tr>';
-            foreach($aTr as $sTd){
-                $sTdata.='<td>'.$sTd.'</td>';
-            }
-            $sTdata.='</tr>';
-        }
-        $this->_setAttributes($aTableAttributes);
-        return sprintf($sTpl, 
-                $this->_addAttributes(), 
-                $sThead,
-                $sTdata
-                );
-    }
 }
diff --git a/classes/render-adminlte.class.php b/classes/render-adminlte.class.php
index 0f25163c89698484a71b800007d0633e8fbf0991..83cf626c624afc23bc6237422c52be5c2b01d7b4 100755
--- a/classes/render-adminlte.class.php
+++ b/classes/render-adminlte.class.php
@@ -1,151 +1,171 @@
 <?php
 require_once 'htmlelements.class.php';
 /**
- * ======================================================================
+ * ______________________________________________________________________
+ * 
+ *     _  __  __  _    
+ *    | ||  \/  || |__     Institute for Medical Education
+ *    |_||_|\/|_||____|    University of Bern
+ * 
+ * ______________________________________________________________________
  * 
  * RENDERER FOR ADNINLTE template https://adminlte.io
- * DOCS: https://adminlte.io/docs/3.2/
- *       https://adminlte.io/themes/v3/index3.html
+ * its docs: https://adminlte.io/docs/3.2/
+ *           https://adminlte.io/themes/v3/index3.html
+ * 
+ * This is a php class to render
+ * - grid layout
+ * - navigation
+ * - widgets, components and forms
  * 
+ * DOCS: https://os-docs.iml.unibe.ch/adminlte-renderer/
+ * ----------------------------------------------------------------------
+ * 2023-09-11  <axel.hahn@unibe.ch>  add shadows on card + callout
+ * 2023-09-27  <axel.hahn@unibe.ch>  add form input fields
+ * 2023-11-17  <axel.hahn@unibe.ch>  add tabbed content; "=" renders hamburger item
+ * 2024-05-03  <axel.hahn@unibe.ch>  add line in sidebar menu; add getFormSelect
+ * 2024-05-10  <axel.hahn@unibe.ch>  add support for bootstrap-select in getFormSelect
+ * 2024-05-18  <axel.hahn@unibe.ch>  add variable types
+ * 2024-07-04  <axel.hahn@unibe.ch>  added type declarations
  * ======================================================================
- *
- * @author Axel
  */
-class renderadminlte {
+class renderadminlte
+{
 
-    var $aPresets=[
+    protected array $aPresets = [
 
-        'bgcolor'=>[
-            'description'=>'background colors',
-            'group'=>'styling',
-            'values'=>[
+        'bgcolor' => [
+            'description' => 'background colors',
+            'group' => 'styling',
+            'values' => [
                 // https://adminlte.io/themes/v3/pages/UI/general.html
-                ''=>'no value',
-                'indigo'=>'indigo',
-                'lightblue'=>'',
-                'navy'=>'',
-                'purple'=>'',
-                'fuchsia'=>'',
-                'pink'=>'',
-                'maroon'=>'',
-                'orange'=>'',
-                'lime'=>'',
-                'teal'=>'',
-                'olive'=>'',
-        
-                'black'=>'black',
-                'dark'=>'dark gray',
-                'gray'=>'gray', 
-                'light'=>'light gray', 
+                '' => 'no value',
+                'indigo' => 'indigo',
+                'lightblue' => '',
+                'navy' => '',
+                'purple' => '',
+                'fuchsia' => '',
+                'pink' => '',
+                'maroon' => '',
+                'orange' => '',
+                'lime' => '',
+                'teal' => '',
+                'olive' => '',
+
+                'black' => 'black',
+                'dark' => 'dark gray',
+                'gray' => 'gray',
+                'light' => 'light gray',
             ]
         ],
-    
-        'type'=>[
-            'description'=>'type or status like info/ warning/ danger to define a color',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'danger'=>'red',
-                'info'=>'aqua',
-                'primary'=>'blue',
-                'secondary'=>'gray',
-                'success'=>'green',
-                'warning'=>'yellow',
-                'dark'=>'dark gray',
-                'gray'=>'gray', 
+
+        'type' => [
+            'description' => 'type or status like info/ warning/ danger to define a color',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'danger' => 'red',
+                'info' => 'aqua',
+                'primary' => 'blue',
+                'secondary' => 'gray',
+                'success' => 'green',
+                'warning' => 'yellow',
+                'dark' => 'dark gray',
+                'gray' => 'gray',
             ]
         ],
-        'shadow'=>[
-            'description'=>'use a shadow',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value', 
-                'none'=>'none',
-                'small'=>'small', 
-                'regular'=>'regular',
-                'large'=>'large'
+        'shadow' => [
+            'description' => 'use a shadow',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'none' => 'none',
+                'small' => 'small',
+                'regular' => 'regular',
+                'large' => 'large'
             ]
         ],
-        'size'=>[
-            'description'=>'set a size',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'lg'=>'',
-                'sm'=>'',
-                'xs'=>'',
-                'flat'=>'',
+        'size' => [
+            'description' => 'set a size',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'lg' => '',
+                'sm' => '',
+                'xs' => '',
+                'flat' => '',
             ]
         ],
-        'variant'=>[
-            'description'=>'coloring style',
-            'group'=>'styling',
-            'values'=>[
-                ''=>'no value',
-                'outline'=>'small stripe on top',
-                'solid'=>'full filled widget',
-                'gradient'=>'full filled with gradient',
+        'variant' => [
+            'description' => 'coloring style',
+            'group' => 'styling',
+            'values' => [
+                '' => 'no value',
+                'outline' => 'small stripe on top',
+                'solid' => 'full filled widget',
+                'gradient' => 'full filled with gradient',
             ]
         ],
-        'visibility'=>[
-            'description'=>'',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                '0'=>'hide', 
-                '1'=>'show',
+        'visibility' => [
+            'description' => '',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                '0' => 'hide',
+                '1' => 'show',
             ]
         ],
         // for keys: state
-        'windowstate'=>[
-            'description'=>'state of a resizable widget',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                'collapsed'=>'header only', 
-                'maximized'=>'full window',
+        'windowstate' => [
+            'description' => 'state of a resizable widget',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                'collapsed' => 'header only',
+                'maximized' => 'full window',
             ]
         ],
         // for keys: dismissable
-        'yesno'=>[
-            'description'=>'',
-            'group'=>'customizing',
-            'values'=>[
-                ''=>'no value', 
-                '0'=>'no', 
-                '1'=>'yes',
+        'yesno' => [
+            'description' => '',
+            'group' => 'customizing',
+            'values' => [
+                '' => 'no value',
+                '0' => 'no',
+                '1' => 'yes',
             ]
         ],
     ];
 
-    var $_aValueMappings=[
-        'shadow'=>[
-            'default'  => '',
-            'none'     => 'shadow-none',
-            'small'    => 'shadow-small',
-            'regular'  => 'shadow',
-            'large'    => 'shadow-lg',
+    protected array $_aValueMappings = [
+        'shadow' => [
+            'default' => '',
+            'none' => 'shadow-none',
+            'small' => 'shadow-small',
+            'regular' => 'shadow',
+            'large' => 'shadow-lg',
         ]
     ];
 
-    var $_aElements=[];
-    
+    protected array $_aElements = [];
+
     /**
-     * instance of htmlelements
+     * instance of htmlelements object
      * @var object
      */
-    var $_oHtml=false;
-    
-    
+    protected object $_oHtml;
+
+
     // ----------------------------------------------------------------------
     // 
     // CONSTRUCTOR
     // 
     // ----------------------------------------------------------------------
-    public function __construct() {
-        $this->_oHtml=new htmlelements();
+    public function __construct()
+    {
+        $this->_oHtml = new htmlelements();
         $this->_initElements();
-        return true;
+        // return true;
     }
 
     // ----------------------------------------------------------------------
@@ -153,233 +173,378 @@ class renderadminlte {
     // PRIVATE FUNCTIONS 
     // 
     // ----------------------------------------------------------------------
-    
-    /**
-     * verify if an item has a correct value
-     * it returns false if a key is not defined to be checked
-     * it returns true if it was validated successfully
-     * it dies with an errror, if a value check failed
-     * 
-     * @param string  $sType      type; key in $_aValidItems; one of bgcolor|color|type|size
-     * @param string  $sValue     value to check
-     * @param string  $sReferrer  optional: method that called this function
-     */
-    protected function _DELETE_ME___checkValue($sType, $sValue, $sReferrer=false){
-        if (!$sValue || !array_key_exists($sType, $this->_aValidItems)){
-            return false;
-        }
-        if(array_search($sValue, $this->_aValidItems[$sType])===false){
-            echo "ERROR: ".($sReferrer ? $sReferrer.' - ' : '')."value [$sValue] is not a valid for type [$sType]; it must be one of ".implode("|", $this->_aValidItems[$sType]).'<br>';
-        }
-        return true;
-    }
 
     /**
      * used in cosntructor
      * initialize all element definitions
+     * @return void
      */
-    protected function _initElements(){
-        $this->_aElements=[
+    protected function _initElements(): void
+    {
+        $this->_aElements = [
 
             // ------------------------------------------------------------
-            'alert'=>[
-                'label'=>'Alert',
-                'description'=>'Colored box with title and a text',
-                'method'=>'getAlert',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'warning'],
-                    'dismissible' => ['select'=>$this->aPresets['yesno'],    'example_value'=>''],
-                    'title'       => [
-                        'description'=>'Title in a bit bigger font', 
-                        'group'=>'content',
-                        'example_value'=>'Alert title'
+            'alert' => [
+                'label' => 'Alert',
+                'description' => 'Colored box with title and a text',
+                'method' => 'getAlert',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'warning'],
+                    'dismissible' => ['select' => $this->aPresets['yesno'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'description'=>'Message text', 
-                        'group'=>'content',
-                        'example_value'=>'I am a message. Read me, please.'
+                    'title' => [
+                        'description' => 'Title in a bit bigger font',
+                        'group' => 'content',
+                        'example_value' => 'Alert title'
+                    ],
+                    'text' => [
+                        'description' => 'Message text',
+                        'group' => 'content',
+                        'example_value' => 'I am a message. Read me, please.'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'badge'=>[
-                'label'=>'Badge',
-                'description'=>'Tiny additional info; mostly as counter',
-                'method'=>'getBadge',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
-                    'bgcolor'     => ['select'=>$this->aPresets['bgcolor'],  'example_value'=>''],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'badge' => [
+                'label' => 'Badge',
+                'description' => 'Tiny additional info; mostly as counter',
+                'method' => 'getBadge',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'danger'],
+                    'bgcolor' => ['select' => $this->aPresets['bgcolor'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'id'          => [
-                        'group'=>'customizing', 
-                        'description'=>'optional: id attribute', 
-                        'example_value'=>''
+                    'id' => [
+                        'group' => 'customizing',
+                        'description' => 'optional: id attribute',
+                        'example_value' => ''
                     ],
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'optional: title attribute for mouseover', 
-                        'example_value'=>'Errors: 5'
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'optional: title attribute for mouseover',
+                        'example_value' => 'Errors: 5'
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Text or value in the badge', 
-                        'example_value'=>'5'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Text or value in the badge',
+                        'example_value' => '5'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'button'=>[
-                'label'=>'Button',
-                'description'=>'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.',
-                'method'=>'getButton',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
-                    'size'        => ['select'=>$this->aPresets['size'],     'example_value'=>''],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'button' => [
+                'label' => 'Button',
+                'description' => 'Buttons<br>In this component you can add other parmeter keys too - these will be added as attributes in the button tag.',
+                'method' => 'getButton',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'primary'],
+                    'size' => ['select' => $this->aPresets['size'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Text/ html code on the button', 
-                        'example_value'=>'Click me'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Text/ html code on the button',
+                        'example_value' => 'Click me'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'callout'=>[
-                'label'=>'Callout',
-                'description'=>'Kind of infobox',
-                'method'=>'getCallout',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'danger'],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'callout' => [
+                'label' => 'Callout',
+                'description' => 'Kind of infobox',
+                'method' => 'getCallout',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'danger'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'Title in a bit bigger font', 
-                        'example_value'=>'I am a callout'
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'Title in a bit bigger font',
+                        'example_value' => 'I am a callout'
                     ],
-                    'text'        => [
-                        'group'=>'content',
-                        'description'=>'Message text', 
-                        'example_value'=>'Here is some description to whatever.'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Message text',
+                        'example_value' => 'Here is some description to whatever.'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'card'=>[
-                'label'=>'Card',
-                'description'=>'Content box with header, text, footer',
-                'method'=>'getCard',
-        
-                'params'=>[
-                    'type'        => ['select'=>$this->aPresets['type'],     'example_value'=>'primary'],
-                    'variant'     => ['select'=>$this->aPresets['variant'],  'example_value'=>'outline'],
-                    'class'       => [
-                        'group'=>'styling', 
-                        'description'=>'optional: css classes', 
-                        'example_value'=>''
+            'card' => [
+                'label' => 'Card',
+                'description' => 'Content box with header, text, footer',
+                'method' => 'getCard',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'primary'],
+                    'variant' => ['select' => $this->aPresets['variant'], 'example_value' => 'outline'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'state'       => [
-                        'group'=>'customizing', 
-                        'select'=>$this->aPresets['windowstate'], 
-                        'example_value'=>''
+                    'state' => [
+                        'group' => 'customizing',
+                        'select' => $this->aPresets['windowstate'],
+                        'example_value' => ''
                     ],
-        
-                    'tb-collapse' => ['description'=>'show minus symbol as collapse button', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-expand'   => ['description'=>'show plus symbol to expand card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-maximize' => ['description'=>'show maximize button for fullscreen', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-minimize' => ['description'=>'show minimize button to minimize', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-                    'tb-remove'   => ['description'=>'show cross symbol to remove card', 'select'=>$this->aPresets['visibility'], 'example_value'=>''],
-        
-                    'title'       => [
-                        'group'=>'content', 
-                        'description'=>'Title in the top row', 
-                        'example_value'=>'I am a card'
+
+                    'tb-collapse' => ['description' => 'show minus symbol as collapse button', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-expand' => ['description' => 'show plus symbol to expand card', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-maximize' => ['description' => 'show maximize button for fullscreen', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-minimize' => ['description' => 'show minimize button to minimize', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+                    'tb-remove' => ['description' => 'show cross symbol to remove card', 'select' => $this->aPresets['visibility'], 'example_value' => ''],
+
+                    'title' => [
+                        'group' => 'content',
+                        'description' => 'Title in the top row',
+                        'example_value' => 'I am a card'
                     ],
-                    'tools'       => [
-                        'group'=>'content', 
-                        'description'=>'Html code for the top right', 
-                        'example_value'=>''
+                    'tools' => [
+                        'group' => 'content',
+                        'description' => 'Html code for the top right',
+                        'example_value' => ''
                     ],
-                    'text'        => [
-                        'group'=>'content', 
-                        'description'=>'Main content', 
-                        'example_value'=>'Here is some beautiful content.'
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'Main content',
+                        'example_value' => 'Here is some beautiful content.'
                     ],
-                    'footer'      => [
-                        'group'=>'content', 
-                        'description'=>'optional: footer content', 
-                        'example_value'=>'Footer'
+                    'footer' => [
+                        'group' => 'content',
+                        'description' => 'optional: footer content',
+                        'example_value' => 'Footer'
                     ],
                 ]
             ],
             // ------------------------------------------------------------
-            'infobox'=>[
-                'label'=>'Info box',
-                'description'=>'Box with icon to highlight a single value; optional with a progress bar',
-                'method'=>'getInfobox',
-        
-                'params'=>[
-                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>''],
-                    'iconbg'=>['select'=>$this->aPresets['type'],   'example_value'=>'info'],
-                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
-                    'icon'=>[
-                        'group'=>'content', 
-                        'description'=>'css class for an icon', 
-                        'example_value'=>'far fa-thumbs-up'
+            'infobox' => [
+                'label' => 'Info box',
+                'description' => 'Box with icon to highlight a single value; optional with a progress bar',
+                'method' => 'getInfobox',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => ''],
+                    'iconbg' => ['select' => $this->aPresets['type'], 'example_value' => 'info'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
                     ],
-                    'text'=>[
-                        'group'=>'content', 
-                        'description'=>'short information text', 
-                        'example_value'=>'Likes'
+                    'icon' => [
+                        'group' => 'content',
+                        'description' => 'css class for an icon',
+                        'example_value' => 'fa-regular fa-thumbs-up'
                     ],
-                    'number'=>[
-                        'group'=>'content', 
-                        'description'=>'a number to highlight', 
-                        'example_value'=>"41,410"
+                    'text' => [
+                        'group' => 'content',
+                        'description' => 'short information text',
+                        'example_value' => 'Likes'
                     ],
-                    'progressvalue'=>[
-                        'group'=>'content', 
-                        'description'=>'optional: progress value 0..100 to draw a progress bar', 
-                        'example_value'=>70
+                    'number' => [
+                        'group' => 'content',
+                        'description' => 'a number to highlight',
+                        'example_value' => "41,410"
                     ],
-                    'progresstext'=>[
-                        'group'=>'content', 
-                        'description'=>'optional: text below progress bar', 
-                        'example_value'=>'70% Increase in 30 Days'
+                    'progressvalue' => [
+                        'group' => 'content',
+                        'description' => 'optional: progress value 0..100 to draw a progress bar',
+                        'example_value' => 70
+                    ],
+                    'progresstext' => [
+                        'group' => 'content',
+                        'description' => 'optional: text below progress bar',
+                        'example_value' => '70% Increase in 30 Days'
                     ]
                 ]
             ],
             // ------------------------------------------------------------
-            'smallbox'=>[
-                'label'=>'Small box',
-                'description'=>'Solid colored box to highlight a single value; optional with a link',
-                'method'=>'getSmallbox',
-        
-                'params'=>[
-                    'type'=>['select'=>$this->aPresets['type'],     'example_value'=>'info'],
-                    'shadow'=>['select'=>$this->aPresets['shadow'], 'example_value'=>''],
-        
-                    'icon'=>['group'=>'content', 'description'=>'css class for an icon', 'example_value'=>'fas fa-shopping-cart'],
-        
-                    'text'=>['group'=>'content', 'description'=>'short information text', 'example_value'=>'New orders'],
-                    'number'=>['group'=>'content', 'description'=>'a number to highlight', 'example_value'=>"150"],
-                    'url'=>['group'=>'content', 'description'=>'optional: url to set a link on the bottom', 'example_value'=>'#'],
-                    'linktext'=>['group'=>'content', 'optional: description'=>'linktext', 'example_value'=>'More info']
+            'smallbox' => [
+                'label' => 'Small box',
+                'description' => 'Solid colored box to highlight a single value; optional with a link',
+                'method' => 'getSmallbox',
+
+                'params' => [
+                    'type' => ['select' => $this->aPresets['type'], 'example_value' => 'info'],
+                    'shadow' => ['select' => $this->aPresets['shadow'], 'example_value' => ''],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => ''
+                    ],
+                    'icon' => ['group' => 'content', 'description' => 'css class for an icon', 'example_value' => 'fa-solid fa-shopping-cart'],
+
+                    'text' => ['group' => 'content', 'description' => 'short information text', 'example_value' => 'New orders'],
+                    'number' => ['group' => 'content', 'description' => 'a number to highlight', 'example_value' => "150"],
+                    'url' => ['group' => 'content', 'description' => 'optional: url to set a link on the bottom', 'example_value' => '#'],
+                    'linktext' => ['group' => 'content', 'description' => 'used if a url was given: linked text', 'example_value' => 'More info']
+                ]
+            ],
+            // ------------------------------------------------------------
+            'input' => [
+                'label' => 'Form: input',
+                'description' => 'Input form fiels',
+                'method' => 'getFormInput',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the input field',
+                        'example_value' => 'Enter something'
+                    ],
+                    'type' => [
+                        'select' => [
+                            'description' => 'type or input field',
+                            'group' => 'styling',
+                            'values' => [
+
+                                'button' => 'button',
+                                'checkbox' => 'checkbox',
+                                'color' => 'color',
+                                'date' => 'date',
+                                'datetime-local' => 'datetime-local',
+                                'email' => 'email',
+                                'file' => 'file',
+                                'hidden' => 'hidden',
+                                'image' => 'image',
+                                'month' => 'month',
+                                'number' => 'number',
+                                'password' => 'password',
+                                'radio' => 'radio',
+                                'range' => 'range',
+                                'reset' => 'reset',
+                                'search' => 'search',
+                                'submit' => 'submit',
+                                'tel' => 'tel',
+                                'text' => 'text',
+                                'time' => 'time',
+                                'url' => 'url',
+                                'week' => 'week',
+                            ]
+                        ],
+                        'example_value' => 'text'
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'prepend' => [
+                        'group' => 'styling',
+                        'description' => 'optional: content on input start',
+                        'example_value' => ''
+                    ],
+                    'append' => [
+                        'group' => 'styling',
+                        'description' => 'optional: content on input end',
+                        'example_value' => ''
+                    ],
+                    'name' => [
+                        'group' => 'content',
+                        'description' => 'name attribute',
+                        'example_value' => 'firstname'
+                    ],
+                    'value' => [
+                        'group' => 'content',
+                        'description' => 'Value',
+                        'example_value' => 'Jack'
+                    ],
+                ]
+            ],
+            // ------------------------------------------------------------
+            // WIP
+            'select' => [
+                'label' => 'Form: select',
+                'description' => 'Select box',
+                'method' => 'getFormSelect',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the select field',
+                        'example_value' => 'Enter text'
+                    ],
+                    'bootstrap-select' => [
+                        'select' => [
+                            'description' => 'Enable bootstrap-select plugin',
+                            'group' => 'styling',
+                            'values' => [
+                                '0' => 'no',
+                                '1' => 'yes',
+                            ]
+                        ]
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'options' => [
+                        'example_value' => [
+                            ["value" => "1", "label" => "one"],
+                            ["value" => "2", "label" => "two"],
+                            ["value" => "3", "label" => "three"],
+                        ],
+                    ]
+                ],
+            ],
+            // ------------------------------------------------------------
+            'textarea' => [
+                'label' => 'Form: textarea',
+                'description' => 'textarea or html editor',
+                'method' => 'getFormTextarea',
+
+                'params' => [
+                    'label' => [
+                        'group' => 'styling',
+                        'description' => 'label for the input field',
+                        'example_value' => 'Enter text'
+                    ],
+                    'type' => [
+                        'select' => [
+                            'description' => 'type or input field',
+                            'group' => 'styling',
+                            'values' => [
+                                '' => 'text',
+                                'html' => 'html editor',
+                            ]
+                        ],
+                    ],
+                    'class' => [
+                        'group' => 'styling',
+                        'description' => 'optional: css classes',
+                        'example_value' => 'myclass'
+                    ],
+                    'name' => [
+                        'group' => 'content',
+                        'description' => 'name attribute',
+                        'example_value' => 'textdata'
+                    ],
+                    'value' => [
+                        'group' => 'content',
+                        'description' => 'Value',
+                        'example_value' => 'Here is some text...'
+                    ],
                 ]
             ],
         ];
@@ -388,12 +553,16 @@ class renderadminlte {
      * helper function: a shortcut for $this->_oHtml->getTag
      * @param  string  $sTag         name of html tag
      * @param  array   $aAttributes  array of its attributes
+     * @param  string  $sContent     content between opening and closing tag
+     * @param  bool    $bClosetag    flag: write a closing tag or not? default: true
+     * @return string
      */
-    protected function _tag($sTag, $aAttributes, $sContent=false){
-        if ($sContent){
-            $aAttributes['label']=(isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent;
+    protected function _tag(string $sTag, array $aAttributes, string $sContent = '', bool $bClosetag = true): string
+    {
+        if ($sContent) {
+            $aAttributes['label'] = (isset($aAttributes['label']) ? $aAttributes['label'] : '') . $sContent;
         }
-        return $this->_oHtml->getTag($sTag, $aAttributes);
+        return $this->_oHtml->getTag($sTag, $aAttributes, $bClosetag);
     }
     // ----------------------------------------------------------------------
     // 
@@ -405,25 +574,29 @@ class renderadminlte {
      * render a page by using template 
      * @param  string  $stemplate  html template with placeholders
      * @param  array   $aReplace   key = what to replace .. value = new value
+     * @return string
      */
-    public function render($sTemplate, $aReplace){
+    public function render(string $sTemplate, array $aReplace): string
+    {
         return str_replace(
             array_keys($aReplace),
             array_values($aReplace),
             $sTemplate
         );
     }
-    
 
     /**
      * add a wrapper: wrap some content into a tag
      * 
+     * @param  string  $sTag      name of html tag
+     * @param  array   $aOptions  array of its attributes
      * @param  string  $sContent  html content inside
      * @return string
      */
-    public function addWrapper($sTag, $aOptions, $sContent){
-        $aOptions['label']=$sContent;
-        return $this->_tag($sTag, $aOptions)."\n";
+    public function addWrapper(string $sTag, array $aOptions, string $sContent): string
+    {
+        $aOptions['label'] = $sContent;
+        return $this->_tag($sTag, $aOptions) . PHP_EOL;
     }
 
     // ----------------------------------------------------------------------
@@ -432,7 +605,6 @@ class renderadminlte {
     // 
     // ----------------------------------------------------------------------
 
-
     /** 
      * get a single navigation item on top bar
      * 
@@ -440,73 +612,93 @@ class renderadminlte {
      * @param  integer  $iLevel  Menu level; 1=top bar; 2=pupup menu with subitems
      * @return string
      */
-    public function getNavItem($aLink, $iLevel=1){
+    public function getNavItem(array $aLink, int $iLevel = 1): string
+    {
         static $iCounter;
-        if(!isset($iCounter)){
-            $iCounter=0;
+        if (!isset($iCounter)) {
+            $iCounter = 0;
         }
-        
-        // special menu entry: horizontal bar (label is "-")
-        if($aLink['label']=='-'){
-            return '<div class="dropdown-divider"></div>';
+
+        switch ($aLink['label']) {
+            // special menu entry: horizontal bar (label is "-")
+            case '-':
+                return '<div class="dropdown-divider"></div>';
+                break;
+
+            // special menu entry: hamburger menu item (label is "=")
+            case '=':
+                return '<li class="nav-item"><a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a></li>';
+                break;
+
+            // special menu entry: hamburger menu item (label is "|")
+            // requires css: .navbar-nav li.divider{border-left: 1px solid rgba(0,0,0,0.2);}
+            case '|':
+                return '<li class="divider"></li>';
+                break;
         }
 
-        $aChildren=isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false;
+        $aChildren = isset($aLink['children']) && is_array($aLink['children']) && count($aLink['children']) ? $aLink['children'] : false;
 
-        $aLink['class']='nav-link'
-            . (isset($aLink['class']) ? ' '.$aLink['class'] : '')
+        $aLink['class'] = 'nav-link'
+            . (isset($aLink['class']) ? ' ' . $aLink['class'] : '')
             . ($aChildren ? ' dropdown-toggle' : '')
-            ;
-        if($aChildren){
+        ;
+        if ($aChildren) {
             $iCounter++;
-            $sNavId="navbarDropdown".$iCounter;
-            $aLink=array_merge($aLink,[
-                'id'=>$sNavId,
-                'role'=>"button",
-                'data-toggle'=>"dropdown",
-                'aria-haspopup'=>"true",
-                'aria-expanded'=>"false",
+            $sNavId = "navbarDropdown" . $iCounter;
+            $aLink = array_merge($aLink, [
+                'id' => $sNavId,
+                'role' => "button",
+                'data-toggle' => "dropdown",
+                'aria-haspopup' => "true",
+                'aria-expanded' => "false",
             ]);
             unset($aLink['children']); // remove from html attributes to draw
         }
-        $sReturn=$this->_tag('a', $aLink)."\n";
-        if($aChildren){ 
+        $sReturn = $this->_tag('a', $aLink) . "\n";
+        if ($aChildren) {
             $iLevel++;
-            $sReturn.=$this->addWrapper(
-                    'div', 
-                    [ 
-                        'aria-labelledby'=> $sNavId,
-                        'class'=>'dropdown-menu'
-                    ], 
-                    $this->getNavItems($aChildren, $iLevel)
-                )."\n";
+            $sReturn .= $this->addWrapper(
+                'div',
+                [
+                    'aria-labelledby' => $sNavId,
+                    'class' => 'dropdown-menu'
+                ],
+                $this->getNavItems($aChildren, $iLevel)
+            ) . "\n";
             $iLevel--;
         }
-   
-        if($iLevel==1){
-            $sLiClass='nav-item'.($aChildren ? ' dropdown' : '');
-            $sReturn=$this->addWrapper(
-                'li', 
-                ['class'=>$sLiClass],
-                $sReturn 
+
+        if ($iLevel == 1) {
+            $sLiClass = 'nav-item' . ($aChildren ? ' dropdown' : '');
+            $sReturn = $this->addWrapper(
+                'li',
+                ['class' => $sLiClass],
+                $sReturn
             );
         }
         return $sReturn;
     }
+
     /** 
-     * get a navigation item for top bar
+     * get html code for navigation on top bar
      * 
+     * @param array $aLinks  array of navigation items
+     * @param int   $iLevel  current navigation level; default: 1
+     * @return string|bool
      */
-    public function getNavItems($aLinks, $iLevel=1){
-        $sReturn='';
-        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
+    public function getNavItems(array $aLinks, int $iLevel = 1): string|bool
+    {
+        $sReturn = '';
+        if (!$aLinks || !is_array($aLinks) || !count($aLinks)) {
             return false;
         }
-        foreach($aLinks as $aLink){
-            $sReturn.=$this->getNavItem($aLink, $iLevel);
+        foreach ($aLinks as $aLink) {
+            $sReturn .= $this->getNavItem($aLink, $iLevel);
         }
         return $sReturn;
     }
+
     /**
      * get a top left navigation for a top navigation bar
      * 
@@ -528,62 +720,94 @@ class renderadminlte {
      *     .'</nav>'
      * </code>
      * 
-     * @param  array  $aNavitems   array of navigation items/ tree
-     * @param  array  $aUlOptions  array of html attrubutes for wrapping UL tag
+     * @param  array  $aNavItems        array of navigation items/ tree
+     * @param  array  $aUlOptions       array of html attrubutes for wrapping UL tag
+     * @param  array  $aNavItemsRight   array of html attrubutes for wrapping UL tag
+     * @param  array  $aUlOptionsRight  array of html attrubutes for wrapping UL tag
      * @return string
      */
-    public function getTopNavigation($aNavItems, $aUlOptions=['class'=>'navbar-nav']){
-        array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fas fa-bars"></i>']);
-        return $this->addWrapper('ul', $aUlOptions, $this->getNavItems($aNavItems));
+    public function getTopNavigation(array $aNavItems, array $aUlOptions = [], array $aNavItemsRight = [], array $aUlOptionsRight = []): string
+    {
+        // array_unshift($aNavItems, ['class'=>'nav-link', 'data-widget'=>'pushmenu', 'href'=>'#', 'role'=>'button', 'label'=>'<i class="fa-solid fa-bars"></i>']);
+        $aUlOptLeft = count($aUlOptions) ? $aUlOptions : ['class' => 'navbar-nav'];
+        $aUlOptRight = count($aUlOptionsRight) ? $aUlOptionsRight : ['class' => 'navbar-nav ml-auto'];
+        return $this->addWrapper('ul', $aUlOptLeft, $this->getNavItems($aNavItems))
+            . (count($aNavItemsRight)
+                ? $this->addWrapper('ul', $aUlOptRight, $this->getNavItems($aNavItemsRight))
+                : ''
+            )
+        ;
     }
 
     // ----------------------------------------------------------------------
 
 
     /** 
-     * get a navigation items for sidebar
+     * Get a navigation items for sidebar
+     * Links can be nested with the key "children".
+     * 
+     * Remark: for a horizontal line ($aLink['label']='-') this css is required
+     *   .nav-item hr{color: #505860; border-top: 1px solid; height: 1px; padding: 0; margin: 0; }
+     * 
      * @param  array  $aLinks  list of link items
+     * @return string|bool
      */
-    public function getNavi2Items($aLinks){
-        $sReturn='';
-        if (!$aLinks || !is_array($aLinks) || !count($aLinks)){
+    public function getNavi2Items(array $aLinks): string|bool
+    {
+        $sReturn = '';
+        if (!$aLinks || !is_array($aLinks) || !count($aLinks)) {
             return false;
         }
-        foreach($aLinks as $aLink){
+
+        foreach ($aLinks as $aLink) {
+
+            if ($aLink['label'] == '-') {
+                // TODO: draw a nice line
+                $sReturn .= '<li class="nav-item"><hr></li>';
+                continue;
+            }
             // to render active or open links: 
-            $aLink['class']='nav-link' . (isset($aLink['class']) ? ' '.$aLink['class'] : '');
-
-            $aChildren=isset($aLink['children']) ? $aLink['children'] : false;
-            $aLiClass='nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : '');
-            $sSubmenu='';
-            if($aChildren){
-                unset($aLink['children']);                
-                $aLink['label'].='<i class="right fas fa-angle-left"></i>';
-                $sSubmenu.=$this->getSidebarNavigation($aChildren, ['class'=>'nav nav-treeview']);
+            $aLink['class'] = 'nav-link' . (isset($aLink['class']) ? ' ' . $aLink['class'] : '');
+
+            $aChildren = isset($aLink['children']) ? $aLink['children'] : false;
+            $aLiClass = 'nav-item' . ($aChildren && strstr($aLink['class'], 'active') ? ' menu-open' : '');
+            $sSubmenu = '';
+            if ($aChildren) {
+                unset($aLink['children']);
+                $aLink['label'] .= '<i class="right fa-solid fa-angle-left"></i>';
+                $sSubmenu .= $this->getSidebarNavigation($aChildren, ['class' => 'nav nav-treeview']);
             }
-            $aLink['label']=$this->addWrapper('p', [], $aLink['label']);
-            $sReturn.=$this->addWrapper(
-                'li', ['class'=>$aLiClass],
-                $this->_tag('a', $aLink).$sSubmenu
-            )."\n";
+            $aLink['label'] = $this->addWrapper('p', [], $aLink['label']);
+            $sReturn .= $this->addWrapper(
+                'li',
+                ['class' => $aLiClass],
+                $this->_tag('a', $aLink) . $sSubmenu
+            ) . "\n";
         }
         return $sReturn;
     }
 
     /**
+     * get html code for sidebar navigation
      * 
+     * @param  array  $aNavItems   navigation item
+     * @param  array  $aUlOptions  aatributes for UL tag
+     * @param string
      */
-    public function getSidebarNavigation($aNavItems, $aUlOptions=[
-            'class'=>'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent',
-            'data-widget'=>'treeview',
-            'role'=>'menu',
-            'data-accordion'=>'false'
-            ])
-    {
+    public function getSidebarNavigation(
+        array $aNavItems,
+        array $aUlOptions = [
+            'class' => 'nav nav-pills nav-sidebar flex-column nav-flat_ nav-child-indent',
+            'data-widget' => 'treeview',
+            'role' => 'menu',
+            'data-accordion' => 'false'
+        ]
+    ): string {
         return $this->addWrapper(
-                'ul', $aUlOptions, 
-                $this->getNavi2Items($aNavItems)
-            )."\n";
+            'ul',
+            $aUlOptions,
+            $this->getNavi2Items($aNavItems)
+        ) . "\n";
     }
 
     // ----------------------------------------------------------------------
@@ -591,15 +815,16 @@ class renderadminlte {
     // PUBLIC FUNCTIONS :: CONTENT - BASIC FUNCTIONS
     // 
     // ----------------------------------------------------------------------
-    
+
     /**
      * add page row
      * 
      * @param  string  $sContent  html content inside
      * @return string
      */
-    public function addRow($sContent){
-        return $this->addWrapper('div', ['class'=>'row'], $sContent);
+    public function addRow(string $sContent): string
+    {
+        return $this->addWrapper('div', ['class' => 'row'], $sContent);
     }
 
     /**
@@ -610,8 +835,9 @@ class renderadminlte {
      * @param  string   $sFloat    css value for float attribute; default=false
      * @return string
      */
-    public function addCol($sContent, $iCols=6, $sFloat=false){
-        return $this->addWrapper('div', ['class'=>'col-md-'.$iCols, 'style'=>'float:'.$sFloat], $sContent);
+    public function addCol(string $sContent, int $iCols = 6, string $sFloat = ''): string
+    {
+        return $this->addWrapper('div', ['class' => 'col-md-' . $iCols, 'style' => 'float:' . $sFloat], $sContent);
     }
 
     // ----------------------------------------------------------------------
@@ -620,49 +846,52 @@ class renderadminlte {
     // 
     // ----------------------------------------------------------------------
 
-
     /**
      * get a list of all defined components that can be rendered
-     * @param  {bool}  $bSendData  flag: send including subkeys of the hash; default: false (keys only)
-     * @return {array}
+     * @param   bool  $bSendData  flag: send including subkeys of the hash; default: false (keys only)
+     * @return  array
      */
-    public function getComponents($bSendData=false){
+    public function getComponents(bool $bSendData = false): array
+    {
         return $bSendData
             ? $this->_aElements
             : array_keys($this->_aElements)
         ;
     }
+
     /**
      * get data of a component
-     * @param  {string}  $sComponent  id of the component
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @return array|bool
      */
-    public function getComponent($sComponent){
-        if(!isset($this->_aElements[$sComponent])){
+    public function getComponent(string $sComponent): array|bool
+    {
+        if (!isset($this->_aElements[$sComponent])) {
             return false;
         }
-        $aReturn=array_merge(['id'=>$sComponent], $this->_aElements[$sComponent]);
+        $aReturn = array_merge(['id' => $sComponent], $this->_aElements[$sComponent]);
         unset($aReturn['params']);
         return $aReturn;
     }
 
     /**
      * get parameter keys of a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {bool}    $bSendData  flag: send including subkeys of the hash; default: false (keys only)
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  bool    $bSendData   flag: send including subkeys of the hash; default: false (keys only)
+     * @return array|bool
      */
-    public function getComponentParamkeys($sComponent, $bSendData=false){
-        if(!isset($this->_aElements[$sComponent])){
+    public function getComponentParamkeys(string $sComponent, bool $bSendData = false)
+    {
+        if (!isset($this->_aElements[$sComponent])) {
             return false;
         }
-        $aKeys=array_keys($this->_aElements[$sComponent]['params']);
-        if(!$bSendData){
+        $aKeys = array_keys($this->_aElements[$sComponent]['params']);
+        if (!$bSendData) {
             return $aKeys;
         }
-        $aReturn=[];
-        foreach($aKeys as $sKey){
-            $aReturn[$sKey]=$this->getComponentParamkey($sComponent, $sKey);
+        $aReturn = [];
+        foreach ($aKeys as $sKey) {
+            $aReturn[$sKey] = $this->getComponentParamkey($sComponent, $sKey);
         }
         // $aReturn=$this->_aElements[$sComponent]['params'];
         return $aReturn;
@@ -670,34 +899,36 @@ class renderadminlte {
 
     /**
      * get information a parameter keys of a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {string}  $sKey        key in the options array
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  string  $sKey        key in the options array
+     * @return array|bool
      */
-    public function getComponentParamkey($sComponent, $sKey){
-        if(!isset($this->_aElements[$sComponent]['params'][$sKey])){
+    public function getComponentParamkey(string $sComponent, string $sKey): array|bool
+    {
+        if (!isset($this->_aElements[$sComponent]['params'][$sKey])) {
             return false;
         }
-        $aReturn=$this->_aElements[$sComponent]['params'][$sKey];
+        $aReturn = $this->_aElements[$sComponent]['params'][$sKey];
         // get description from a preset
-        if(!isset($aReturn['description']) && isset($aReturn['select']['description'])){
-            $aReturn['description']=$aReturn['select']['description'];
+        if (!isset($aReturn['description']) && isset($aReturn['select']['description'])) {
+            $aReturn['description'] = $aReturn['select']['description'];
         }
-        if(!isset($aReturn['group']) && isset($aReturn['select']['group'])){
-            $aReturn['group']=$aReturn['select']['group'];
+        if (!isset($aReturn['group']) && isset($aReturn['select']['group'])) {
+            $aReturn['group'] = $aReturn['select']['group'];
         }
         return $aReturn;
     }
 
     /**
      * get a flat list of valid parameters for a key in a component
-     * @param  {string}  $sComponent  id of the component
-     * @param  {string}  $sKey        key in the options array
-     * @return {array}
+     * @param  string  $sComponent  id of the component
+     * @param  string  $sKey        key in the options array
+     * @return array|bool
      */
-    public function getValidParamValues($sComponent, $sKey){
-        $aOptionkey=$this->getComponentParamkey($sComponent, $sKey);
-        if(!$aOptionkey || !isset($aOptionkey['select']['values'])){
+    public function getValidParamValues(string $sComponent, string $sKey): array|bool
+    {
+        $aOptionkey = $this->getComponentParamkey($sComponent, $sKey);
+        if (!$aOptionkey || !isset($aOptionkey['select']['values'])) {
             return false;
         }
         return array_keys($aOptionkey['select']['values']);
@@ -710,43 +941,49 @@ class renderadminlte {
      * helper: add a css value with prefix
      * this handles option keys in get[COMPONENT] methods
      * if a value is set then this function returns a space + prefix (param 2) + value
-     * @param  {string}  $sValue   option value
-     * @param  {string}  $sPrefix  prefix in front of css value
-     * @return {string}
+     * @param  string  $sValue   option value
+     * @param  string  $sPrefix  prefix in front of css value
+     * @return string
      */
-    protected function _addClassValue($sValue, $sPrefix=''){
-        return $sValue ? ' '.$sPrefix.$sValue : '';
+    protected function _addClassValue(string $sValue, string $sPrefix = ''): string
+    {
+        return $sValue ? ' ' . $sPrefix . $sValue : '';
     }
 
     /**
      * helper function for get[COMPONENTNAME] methods:
-     * ensure that all wanted keys exist in an array. Non existing keys will be added with value false
+     * ensure that all wanted keys exist in an array. Non existing keys will 
+     * be added with value false
+     * 
+     * @param string $sComponent    id of the component
+     * @param array  $aOptions      hash with keys for all options
      * @return array
      */
-    protected function _ensureOptions($sComponent, $aOptions=[]){
+    protected function _ensureOptions(string $sComponent, array $aOptions = []): array
+    {
 
-        $aParams=$this->getComponentParamkeys($sComponent, 0);
-        if(!$aParams){
-            $aOptions['_infos'][]="Warning: no definition was found for component $sComponent.";
+        $aParams = $this->getComponentParamkeys($sComponent, 0);
+        if (!$aParams) {
+            $aOptions['_infos'][] = "Warning: no definition was found for component $sComponent.";
             return $aOptions;
         }
-        foreach ($aParams as $sKey){
-            if(!isset($aOptions) || !isset($aOptions[$sKey])){
-                $aOptions[$sKey]=false;
-                if(!isset($aOptions['_infos'])){
-                    $aOptions['_infos']=[];
+        foreach ($aParams as $sKey) {
+            if (!isset($aOptions) || !isset($aOptions[$sKey])) {
+                $aOptions[$sKey] = false;
+                if (!isset($aOptions['_infos'])) {
+                    $aOptions['_infos'] = [];
                 }
-                $aOptions['_infos'][]="added missing key: $sKey";
+                $aOptions['_infos'][] = "added missing key: $sKey";
             }
 
             // $aParamdata
-            $aValidvalues=$this->getValidParamValues($sComponent, $sKey);
-            if($aValidvalues){
-                if(array_search($aOptions[$sKey], $aValidvalues)===false){
-                    echo "ERROR: [".$sComponent."] value &quot;".$aOptions[$sKey]."&quot; is not a valid for param key [".$sKey."]; it must be one of ".implode("|", $aValidvalues).'<br>';
+            $aValidvalues = $this->getValidParamValues($sComponent, $sKey);
+            if ($aValidvalues) {
+                if (array_search($aOptions[$sKey], $aValidvalues) === false) {
+                    echo "ERROR: [" . $sComponent . "] value &quot;" . $aOptions[$sKey] . "&quot; is not a valid for param key [" . $sKey . "]; it must be one of " . implode("|", $aValidvalues) . '<br>';
                 }
             }
-    
+
             // $this->_checkValue($sKey, $aOptions[$sKey], __METHOD__);
         }
         // echo '<pre>' . print_r($aOptions, 1) . '</pre>';
@@ -762,35 +999,36 @@ class renderadminlte {
      * return a alert box      
      * https://adminlte.io/themes/v3/pages/UI/general.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                          - type - one of [none]|danger|info|primary|success|warning
      *                          - dismissible - if dismissible - one of true|false; default: false
      *                          - title
      *                          - text
      * @return string
      */
-    public function getAlert($aOptions){
-        $aOptions=$this->_ensureOptions('alert', $aOptions);
-        $aAlertIcons=[
-            'danger'=>'icon fas fa-ban',
-            'info'=>'icon fas fa-info',
-            'warning'=>'icon fas fa-exclamation-triangle',
-            'success'=>'icon fas fa-check',
+    public function getAlert(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('alert', $aOptions);
+        $aAlertIcons = [
+            'danger' => 'icon fa-solid fa-ban',
+            'info' => 'icon fa-solid fa-info',
+            'warning' => 'icon fa-solid fa-exclamation-triangle',
+            'success' => 'icon fa-solid fa-check',
         ];
 
-        $aElement=[
-            'class'=>'alert'
+        $aElement = [
+            'class' => 'alert'
                 . $this->_addClassValue($aOptions['type'], 'alert-')
                 . $this->_addClassValue($aOptions['dismissible'], 'alert-')
-                ,
-            'label'=>''
+            ,
+            'label' => ''
                 . ($aOptions['dismissible'] ? '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' : '')
                 . $this->_tag('h5', [
-                    'label'=> ''
-                        .(isset($aAlertIcons[$aOptions['type']]) ? '<i class="'.$aAlertIcons[$aOptions['type']].'"></i> ' : '')
-                        .$aOptions['title']
-                    ])
-                .$aOptions['text']
+                    'label' => ''
+                        . (isset($aAlertIcons[$aOptions['type']]) ? '<i class="' . $aAlertIcons[$aOptions['type']] . '"></i> ' : '')
+                        . $aOptions['title']
+                ])
+                . $aOptions['text']
         ];
 
         return $this->_tag('div', $aElement);
@@ -812,20 +1050,22 @@ class renderadminlte {
      *                          - text    - visible text
      *                          - title   - optional: title attribute
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
+     * @return string
      */
-    public function getBadge($aOptions){
-        $aOptions=$this->_ensureOptions('badge', $aOptions);
-        $aElement=[];
-        $aElement['class']='badge'
-            . $this->_addClassValue($aOptions['class'],   '')
-            . $this->_addClassValue($aOptions['type'],    'badge-')
+    public function getBadge(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('badge', $aOptions);
+        $aElement = [];
+        $aElement['class'] = 'badge'
+            . $this->_addClassValue($aOptions['class'], '')
+            . $this->_addClassValue($aOptions['type'], 'badge-')
             . $this->_addClassValue($aOptions['bgcolor'], 'bg-')
-            ;
-        if ($aOptions['id']){
-            $aElement['id']=$aOptions['id'];
+        ;
+        if ($aOptions['id']) {
+            $aElement['id'] = $aOptions['id'];
         }
-        $aElement['title']=$aOptions['title'];
-        $aElement['label']=$aOptions['text'];
+        $aElement['title'] = $aOptions['title'];
+        $aElement['label'] = $aOptions['text'];
 
         return $this->_tag('span', $aElement);
     }
@@ -837,7 +1077,7 @@ class renderadminlte {
      * https://adminlte.io/themes/v3/pages/UI/buttons.html
      * 
      * <button type="button" class="btn btn-block btn-default">Default</button>
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                          - type  - one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - size  - one of [none]|lg|sm|xs|flat
      *                          - class - any css class for customizing, eg. "disabled"
@@ -845,16 +1085,17 @@ class renderadminlte {
      *                          - text  - text on button
      * @return string
      */
-    public function getButton($aOptions){
-        $aOptions=$this->_ensureOptions('button', $aOptions);
-        $aElement=$aOptions;
-        $aElement['class']='btn'
-            . $this->_addClassValue($aOptions['type'],    'btn-')
-            . $this->_addClassValue($aOptions['size'],    'btn-')
-            . $this->_addClassValue($aOptions['class'],   '')
-            ;
-        $aElement['label']=$aOptions['text'] ? $aOptions['text'] : '&nbsp;';
-        foreach(['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey){
+    public function getButton(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('button', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = 'btn'
+            . $this->_addClassValue($aOptions['type'], 'btn-')
+            . $this->_addClassValue($aOptions['size'], 'btn-')
+            . $this->_addClassValue($aOptions['class'], '')
+        ;
+        $aElement['label'] = $aOptions['text'] ? $aOptions['text'] : '&nbsp;';
+        foreach (['_infos', 'type', 'size', 'icon', 'text'] as $sDeleteKey) {
             unset($aElement[$sDeleteKey]);
         }
         return $this->_tag('button', $aElement);
@@ -864,27 +1105,31 @@ class renderadminlte {
      * get a callout (box with coloered left border; has type, title + text)
      * https://adminlte.io/themes/v3/pages/UI/general.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        >> styling
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - class   - optional css class
-     * 
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large     * 
      *                        >> texts/ html content
      *                          - title   - text: title of the card
      *                          - text    - text: content of the card
      * @return string
      */
-    public function getCallout($aOptions){
-        $aOptions=$this->_ensureOptions('callout', $aOptions);
-        $sClass='callout'
-            . $this->_addClassValue($aOptions['type'],    'callout-')
-            . $this->_addClassValue($aOptions['class'],   '')
-            ;
+    public function getCallout(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('callout', $aOptions);
+        $sClass = 'callout'
+            . $this->_addClassValue($aOptions['type'], 'callout-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
 
         return $this->addWrapper(
-            'div', ['class'=>$sClass],
-            ($aOptions['title'] ? $this->_tag('h5', ['label'=>$aOptions['title']]) : '')
-            .($aOptions['text'] ? $this->_tag('p', ['label'=>$aOptions['text']]) : '')
+            'div',
+            ['class' => $sClass],
+            ($aOptions['title'] ? $this->_tag('h5', ['label' => $aOptions['title']]) : '')
+            . ($aOptions['text'] ? $this->_tag('p', ['label' => $aOptions['text']]) : '')
         );
     }
 
@@ -893,13 +1138,14 @@ class renderadminlte {
      * https://adminlte.io/docs/3.2/components/cards.html
      * https://adminlte.io/docs/3.2/javascript/card-widget.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        >> styling
      *                          - variant: "default"  - titlebar is colored
      *                                     "outline"  - a small stripe on top border is colored
      *                                     "solid"    - whole card is colored
      *                                     "gradient" - whole card is colored with a gradient
      *                          - type    - one of [none]|danger|dark|info|primary|secondary|success|warning
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large 
      *                          - class   - any css class for customizing, eg. "disabled"
      *                          - state   - one of [none]|collapsed|maximized
      * 
@@ -917,62 +1163,70 @@ class renderadminlte {
      *                          - footer  - text: footer of the card
      * @return string
      */
-    public function getCard($aOptions){
-        $aOptions=$this->_ensureOptions('card', $aOptions);
+    public function getCard(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('card', $aOptions);
         // css class prefixes based on "variant" value
-        $aVariants=[
-            'default'  => 'card-',
-            'outline'  => 'card-outline card-',
-            'solid'    => 'bg-',
+        $aVariants = [
+            'default' => 'card-',
+            'outline' => 'card-outline card-',
+            'solid' => 'bg-',
             'gradient' => 'bg-gradient-',
         ];
 
         // window states: css class and toolbar buttons to add
-        $aStates=[
-            'collapsed'=>[ 'class'=>'collapsed-card', 'tool'=>'tb-expand'],
-            'maximized'=>[ 'class'=>'maximized-card', 'tool'=>'tb-minimize'],
+        $aStates = [
+            'collapsed' => ['class' => 'collapsed-card', 'tool' => 'tb-expand'],
+            'maximized' => ['class' => 'maximized-card', 'tool' => 'tb-minimize'],
         ];
-        $aTools=[
-            'tb-collapse'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-minus"></i></button>',
-            'tb-expand'=>'<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fas fa-plus"></i></button>',
+        $aTools = [
+            'tb-collapse' => '<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fa-solid fa-minus"></i></button>',
+            'tb-expand' => '<button type="button" class="btn btn-tool" data-card-widget="collapse"><i class="fa-solid fa-plus"></i></button>',
 
-            'tb-maximize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-expand"></i></button>',
-            'tb-minimize'=>'<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fas fa-compress"></i></button>',
+            'tb-maximize' => '<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fa-solid fa-expand"></i></button>',
+            'tb-minimize' => '<button type="button" class="btn btn-tool" data-card-widget="maximize"><i class="fa-solid fa-compress"></i></button>',
 
-            'tb-remove'=>'<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fas fa-times"></i></button>',
+            'tb-remove' => '<button type="button" class="btn btn-tool" data-card-widget="remove"><i class="fa-solid fa-times"></i></button>',
         ];
 
         // print_r($aOptions);
 
-        $sVariantPrefix=$aVariants[$aOptions['variant']] ? $aVariants[$aOptions['variant']] : $aVariants['default'];
-        $sClass='card'
-                . $this->_addClassValue($aOptions['type'],    $sVariantPrefix)
-                . $this->_addClassValue($aOptions['class'],   '')
-                ;
+        $sVariantPrefix = isset($aVariants[$aOptions['variant']]) ? $aVariants[$aOptions['variant']] : $aVariants['default'];
+        $sClass = 'card'
+            . $this->_addClassValue($aOptions['type'], $sVariantPrefix)
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+            . $this->_addClassValue($aOptions['class'], '')
+        ;
 
         // check window state
-        foreach($aStates as $sStatus=>$aStatus){
-            if($aOptions['state']===$sStatus){
-                $sClass.=' '.$aStatus['class'];
-                $aOptions[$aStatus['tool']]=1;
+        foreach ($aStates as $sStatus => $aStatus) {
+            if ($aOptions['state'] === $sStatus) {
+                $sClass .= ' ' . $aStatus['class'];
+                $aOptions[$aStatus['tool']] = 1;
             }
         }
 
         // add toolbar buttons - from given options or by window state
-        foreach($aTools as $sTool=>$sHtml){
-            $aOptions['tools'].=($aOptions[$sTool] ? $sHtml : '');
+        foreach ($aTools as $sTool => $sHtml) {
+            $aOptions['tools'] .= ($aOptions[$sTool] ? $sHtml : '');
         }
         // build parts of the card
-        $sCardHeader=$this->addWrapper('div', ['class'=>'card-header'],
-            $this->_tag('h3', ['class'=>'card-title', 'label'=>$aOptions['title']])
-            . ($aOptions['tools'] ? $this->_tag('div', ['class'=>'card-tools', 'label'=>$aOptions['tools']]) : '')
-        );
+        $sCardHeader = $aOptions['title']
+            ? $this->addWrapper(
+                'div',
+                ['class' => 'card-header'],
+                $this->_tag('h3', ['class' => 'card-title', 'label' => $aOptions['title']])
+                . ($aOptions['tools'] ? $this->_tag('div', ['class' => 'card-tools', 'label' => $aOptions['tools']]) : '')
+            )
+            : ''
+        ;
 
-        $sCardBody=$this->_tag('div', ['class'=>'card-body', 'label'=>$aOptions['text']]);
-        $sCardFooter=$aOptions['footer'] ? $this->_tag('div', ['class'=>'card-footer', 'label'=>$aOptions['footer']]) : '';
+        $sCardBody = $this->_tag('div', ['class' => 'card-body', 'label' => $aOptions['text']]);
+        $sCardFooter = $aOptions['footer'] ? $this->_tag('div', ['class' => 'card-footer', 'label' => $aOptions['footer']]) : '';
 
         // merge all
-        return $this->addWrapper('div', ['class'=>$sClass], $sCardHeader.$sCardBody.$sCardFooter);
+        return $this->addWrapper('div', ['class' => $sClass], $sCardHeader . $sCardBody . $sCardFooter);
     }
 
 
@@ -981,7 +1235,7 @@ class renderadminlte {
      * A colored box with large icon, text and a value.
      * https://adminlte.io/docs/3.2/components/boxes.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        styling:
      *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning
      *                          - iconbg  - background color or type of icon; use it for default type (type="")
@@ -995,52 +1249,57 @@ class renderadminlte {
      *                          - progresstext  - text below progress bar
      * @return string
      */
-    public function getInfobox($aOptions){
-        $aOptions=$this->_ensureOptions('infobox', $aOptions);
+    public function getInfobox(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('infobox', $aOptions);
 
         // print_r($aOptions);
-        $sClass='info-box'
-                . $this->_addClassValue($aOptions['type'],    'bg-')
-                . $this->_addClassValue($aOptions['class'],   '')
-                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
-                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
-                ;
-        
+        $sClass = 'info-box'
+            . $this->_addClassValue($aOptions['type'], 'bg-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
+
         // build parts
-        $sIcon=$aOptions['icon'] 
+        $sIcon = $aOptions['icon']
             ? $this->addWrapper("span", [
-                    'class'=>'info-box-icon'.($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : '')
-                ], $this->_tag('i',['class'=>$aOptions['icon']])) 
+                'class' => 'info-box-icon' . ($aOptions['iconbg'] ? ' bg-' . $aOptions['iconbg'] : '')
+            ], $this->_tag('i', ['class' => $aOptions['icon']]))
             : ''
-            ;
-        $sContent=$this->addWrapper("div", ['class'=>'info-box-content'],
+        ;
+        $sContent = $this->addWrapper(
+            "div",
+            ['class' => 'info-box-content'],
             ''
-            . ($aOptions['text']   ? $this->_tag('span', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
-            . ($aOptions['number'] ? $this->_tag('span', ['class'=>'info-box-number', 'label'=>$aOptions['number']]) : '')
-            . ($aOptions['progressvalue']!==false && $aOptions['progressvalue']!=='' 
-                ? $this->addWrapper('div', ['class'=>'progress'],
-                        $this->_tag('div', ['class'=>'progress-bar'. ($aOptions['iconbg'] ? ' bg-'.$aOptions['iconbg'] : ''), 'style'=>'width: '.(int)$aOptions['progressvalue'].'%' ]) 
-                    )
-                    . ($aOptions['progresstext'] ? $this->_tag('span', ['class'=>'progress-description', 'label'=>$aOptions['progresstext']]) : '' )
-                : ''    
-                ) 
+            . ($aOptions['text'] ? $this->_tag('span', ['class' => 'info-box-text', 'label' => $aOptions['text']]) : '')
+            . ($aOptions['number'] ? $this->_tag('span', ['class' => 'info-box-number', 'label' => $aOptions['number']]) : '')
+            . ($aOptions['progressvalue'] !== false && $aOptions['progressvalue'] !== ''
+                ? $this->addWrapper(
+                    'div',
+                    ['class' => 'progress'],
+                    $this->_tag('div', ['class' => 'progress-bar' . ($aOptions['iconbg'] ? ' bg-' . $aOptions['iconbg'] : ''), 'style' => 'width: ' . (int) $aOptions['progressvalue'] . '%'])
+                )
+                . ($aOptions['progresstext'] ? $this->_tag('span', ['class' => 'progress-description', 'label' => $aOptions['progresstext']]) : '')
+                : ''
+            )
         );
 
         // merge all
-        return $this->_tag('div', ['class'=>$sClass], $sIcon.$sContent);
+        return $this->_tag('div', ['class' => $sClass], $sIcon . $sContent);
     }
 
 
     /**
-     * return an info-box:
+     * return a small box:
      * A colored box with large icon, text and a value.
      * https://adminlte.io/docs/3.2/components/boxes.html
      * https://adminlte.io/themes/v3/pages/widgets.html
      * 
-     * @param type $aOptions  hash with keys for all options
+     * @param array $aOptions  hash with keys for all options
      *                        styling:
      *                          - type    - color of the box; one of [none]|danger|dark|info|primary|secondary|success|warning
-
+     *                          - shadow  - size of shadow; one of [none] (=default: between small and regular)|none|small|regular|large 
      *                        content
      *                          - icon    - icon class for icon on the right
      *                          - text    - information text
@@ -1049,90 +1308,352 @@ class renderadminlte {
      *                          - linktext- text below progress bar
      * @return string
      */
-    public function getSmallbox($aOptions){
-        $aOptions=$this->_ensureOptions('smallbox', $aOptions);
-        $aShadows=[
-            'default'  => '',
-            'none'     => 'shadow-none',
-            'small'    => 'shadow-small',
-            'regular'  => 'shadow',
-            'large'    => 'shadow-lg',
-        ];
+    public function getSmallbox(array $aOptions): string
+    {
+        $aOptions = $this->_ensureOptions('smallbox', $aOptions);
         // print_r($aOptions);
-        $sClass='small-box'
-                . $this->_addClassValue($aOptions['type'],    'bg-')
-                .($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']]) 
-                    ? ' '.$this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
-                ;
-        
+        $sClass = 'small-box'
+            . $this->_addClassValue($aOptions['type'], 'bg-')
+            . $this->_addClassValue($aOptions['class'], '')
+            . ($aOptions['shadow'] && isset($this->_aValueMappings['shadow'][$aOptions['shadow']])
+                ? ' ' . $this->_aValueMappings['shadow'][$aOptions['shadow']] : '')
+        ;
+
         // build parts
-        $sContent=$this->addWrapper("div", ['class'=>'inner'],
+        $sContent = $this->addWrapper(
+            "div",
+            ['class' => 'inner'],
             ''
-            . ($aOptions['number'] ? $this->_tag('h3', ['label'=>$aOptions['number']]) : '')
-            . ($aOptions['text']   ? $this->_tag('p', ['class'=>'info-box-text',   'label'=>$aOptions['text']])   : '')
+            . ($aOptions['number'] ? $this->_tag('h3', ['label' => $aOptions['number']]) : '')
+            . ($aOptions['text'] ? $this->_tag('p', ['class' => 'info-box-text', 'label' => $aOptions['text']]) : '')
         );
-        $sIcon=$aOptions['icon'] 
-            ? $this->addWrapper("div", ['class'=>'icon'], 
-                $this->_tag('i',['class'=>$aOptions['icon']])) 
+        $sIcon = $aOptions['icon']
+            ? $this->addWrapper(
+                "div",
+                ['class' => 'icon'],
+                $this->_tag('i', ['class' => $aOptions['icon']])
+            )
             : ''
-            ;
-        $sFooter=($aOptions['url']
-            ? $this->addWrapper("a", [
-                'class'=>'small-box-footer',
-                'href'=>$aOptions['url'],
+        ;
+        $sFooter = ($aOptions['url']
+            ? $this->addWrapper(
+                "a",
+                [
+                    'class' => 'small-box-footer',
+                    'href' => $aOptions['url'],
                 ],
                 ''
                 . ($aOptions['linktext'] ? $aOptions['linktext'] : $aOptions['url'])
                 . ' '
-                . $this->_tag('i',['class'=>'fas fa-arrow-circle-right'])
+                . $this->_tag('i', ['class' => 'fa-solid fa-arrow-circle-right'])
             )
             : ''
         );
 
         // merge all
-        return $this->_tag('div', ['class'=>$sClass], $sContent.$sIcon.$sFooter);
+        return $this->_tag('div', ['class' => $sClass], $sContent . $sIcon . $sFooter);
     }
 
+    // ----------------------------------------------------------------------
+    // 
+    // PUBLIC FUNCTIONS :: CONTENT - FORM
+    // 
+    // ----------------------------------------------------------------------
 
-/*
 
-<div class="info-box">
-  <span class="info-box-icon bg-info"><i class="far fa-bookmark"></i></span>
-  <div class="info-box-content">
-    <span class="info-box-text">Bookmarks</span>
-    <span class="info-box-number">41,410</span>
-    <div class="progress">
-      <div class="progress-bar bg-info" style="width: 70%"></div>
-    </div>
-    <span class="progress-description">
-      70% Increase in 30 Days
-    </span>
-  </div>
-</div>
+    /**
+     * Generates a horizontal form element with a label, input, and optional hint.
+     *
+     * @param string $sInput The HTML input element to be rendered.
+     * @param string $sLabel The label for the input element.
+     * @param string $sId The ID attribute for the label and input elements.
+     * @param string $sHint An optional hint to be displayed below the input element.
+     * @return string The generated HTML for the horizontal form element.
+     */
+    public function getHorizontalFormElement(string $sInput, string $sLabel = '', string $sId = '', string $sHint=''): string
+    {
+        return '<div class="form-group row">'
+            . '<label for="' . $sId . '" class="col-sm-2 col-form-label">' . $sLabel . '</label>'
+            . '<div class="col-sm-10">'
+            . ($sHint 
+                ? '<div class="text-navy hint">' . $sHint . '</div>' 
+                : '')
+            . $sInput
+            . '</div>'
+            . '</div>'
+        ;
+    }
 
+    /**
+     * return a text input field:
+     * https://adminlte.io/themes/v3/pages/forms/general.html
+     * 
+     * @param array $aOptions  hash with keys for all options
+     *                        styling:
+     *                          - type    - field type: text, email, password, hidden and all other html 5 input types
+     *                        content
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          - value   - value in field
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function GetFormInput(array $aOptions): string
+    {
+        // $aOptions=$this->_ensureOptions('input', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . (isset($aOptions['class']) ? $aOptions['class'] : '')
+        ;
+        $sFormid = (isset($aOptions['id'])
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $aElement['id'] = $sFormid;
 
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+        $sPrepend = '';
+        $sAppend = '';
 
-  <div class="info-box bg-success">
-    <span class="info-box-icon"><i class="far fa-thumbs-up"></i></span>
-    <div class="info-box-content">
-      <span class="info-box-text">Likes</span>
-      <span class="info-box-number">41,410</span>
-      <div class="progress">
-        <div class="progress-bar" style="width: 70%"></div>
-      </div>
-      <span class="progress-description">
-        70% Increase in 30 Days
-      </span>
-    </div>
-  </div>
-*/
-  
 
+        if (isset($aOptions['prepend']) && $aOptions['prepend']) {
+            $sWrapperclass = 'input-group';
+            $sPrepend = $this->_tag(
+                'div',
+                ['class' => 'input-group-prepend'],
+                $this->_tag('span', ['class' => 'input-group-text'], $aOptions['prepend'])
+            );
+        }
+        if (isset($aOptions['append']) && $aOptions['append']) {
+            $sWrapperclass = 'input-group';
+            $sAppend = $this->_tag(
+                'div',
+                ['class' => 'input-group-append'],
+                $this->_tag('span', ['class' => 'input-group-text'], $aOptions['append'])
+            );
+        }
 
+        foreach (['_infos', 'label', 'append', 'prepend', 'debug'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
 
+        // return data
+
+        switch ($aElement['type']) {
+            case 'checkbox':
+            case 'radio':
+                $aElement['class'] = str_replace('form-control ', 'form-check-input', $aElement['class']);
+                $aElement['title'] = $aElement['title'] ?? $sHint;
+                return $this->_tag(
+                    'div',
+                    ['class' => 'form-check'],
+                    $this->_tag('input', $aElement, '', false) . $this->_tag('label', ['for' => $sFormid, 'label' => $sLabel], '')
+                );
+                break;
+            case 'hidden':
+                $aElement['title'] = $aElement['title'] ?? $sHint;
+                return $this->_tag('input', $aElement, '', false);
+                break;
+            default:
+                return $this->getHorizontalFormElement(
+                    $sPrepend . $this->_tag('input', $aElement, '', false) . $sAppend,
+                    $sLabel,
+                    $sFormid,
+                    $sHint
+                );
+        }
+    }
 
+    /**
+     * return a textarea field .. or html editor using summernote
+     * @param array $aOptions  hash with keys for all options
+     *                        styling:
+     *                          - type    - field type: [none]|html
+     *                        content
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          - value   - value in 
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function getFormTextarea(array $aOptions): string
+    {
+        // $aOptions=$this->_ensureOptions('textarea', $aOptions);
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . ((isset($aOptions['type']) && $aOptions['type'] == 'html') ? 'summernote ' : '')
+            . (isset($aOptions['class']) ? $aOptions['class'] : '')
+        ;
+        $sFormid = (isset($aOptions['id'])
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+        $aElement['id'] = $sFormid;
+
+        $value = isset($aOptions['value']) ? $aOptions['value'] : '';
+
+        foreach (['_infos', 'label', 'debug', 'type', 'value'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
+        return $this->getHorizontalFormElement(
+            $this->_tag('textarea', $aElement, $value),
+            $sLabel,
+            $sFormid,
+            $sHint
+        );
+
+    }
+
+    /**
+     * return a select box field
+     * @param array $aOptions  hash with keys for all options
+     *                        option fields
+     *                          - options - array of options with keys per item:
+     *                              - value   - value in the option
+     *                              - label   - visible text in the option
+     *                              other keys are attributes in the option
+     *                        styling:
+     *                          - bootstrap-select  - set true to enable select
+     *                                      box with bootstrap-select and
+     *                                      live search
+     *                          - class   - css class
+     *                        select tag
+     *                          - label   - label tag
+     *                          - name    - name attribute for sending form
+     *                          other keys are attributes in the select
+     *                        more:
+     *                          - hint    - hint to be displayed above the field
+     *                                      If not set, no hint is displayed.
+     *                                      css for ".row .hint" to customize look and feel
+     * @return string
+     */
+    public function getFormSelect(array $aOptions): string
+    {
+        $aElement = $aOptions;
+        $aElement['class'] = ''
+            . 'form-control '
+            . (isset($aOptions['class']) ? $aOptions['class'] . ' ' : '')
+            . (isset($aOptions['bootstrap-select']) ? 'selectpicker ' : '') //$aOptions
+        ;
+        if (isset($aOptions['bootstrap-select']) && $aOptions['bootstrap-select']) {
+            $aElement['data-live-search'] = "true";
+        }
 
+        $sFormid = (isset($aOptions['id'])
 
+            ? $aOptions['id']
+            : (isset($aOptions['name']) ? $aOptions['name'] : 'field') . '-' . md5(microtime(true))
+        );
+        $aElement['id'] = $sFormid;
+        $sLabel = isset($aOptions['label']) ? $aOptions['label'] : '';
+        $sHint = isset($aOptions['hint']) ? $aOptions['hint'] : '';
+
+        $sOptionTags = '';
+        foreach ($aOptions['options'] as $aField) {
+            $optionText = $aField['label'];
+            unset($aField['label']);
+            $sOptionTags .= $this->_tag('option', $aField, $optionText) . "\n";
+        }
 
+        foreach (['_infos', 'label', 'debug', 'type', 'value', 'options'] as $sDeleteKey) {
+            if (isset($aElement[$sDeleteKey])) {
+                unset($aElement[$sDeleteKey]);
+            }
+        }
+        return $this->getHorizontalFormElement(
+            $this->_tag(
+                'div',
+                ['class' => 'form-group'],
+                $this->_tag('select', $aElement, $sOptionTags)
+            ),
+            $sLabel,
+            $sFormid,
+            $sHint
+        );
+
+    }
+
+    // ----------------------------------------------------------------------
+    // 
+    // PUBLIC FUNCTIONS :: CONTENT - TABBED CONTENT
+    // 
+    // ----------------------------------------------------------------------
+
+    /**
+     * return a box with tabbed content
+     * @param array $aOptions  hash with keys for all options
+     *                           - tabs {array} key=tab label; value=content
+     * @param bool  $asArray   optional flag: return hash with keys or as string
+     * @retunr bool|string|array
+     */
+    public function getTabbedContent(array $aOptions, bool $asArray = false): bool|string|array
+    {
+        static $iTabCounter;
+        if (!isset($aOptions['tabs']) || !is_array($aOptions['tabs'])) {
+            return false;
+        }
+        if (!isset($iTabCounter)) {
+            $iTabCounter = 1;
+        } else {
+            $iTabCounter++;
+        }
+
+        $id = 'tab-content-' . $iTabCounter;
+        $iCounter = 0;
+
+        $sTabs = '';
+        $sContent = '';
+        foreach ($aOptions['tabs'] as $sLabel => $sTabContent) {
+            $iCounter++;
+            $sTabId = $id . '-tabitem-' . $iCounter . '-tab';
+            $sContentId = $id . '-tabitem-' . $iCounter . '-content';
+
+            $sTabs .= $this->_tag(
+                'li',
+                ['class' => 'nav-item'],
+                $this->_tag(
+                    'a',
+                    [
+                        'class' => 'nav-link' . ($iCounter == 1 ? ' active' : ''),
+                        'id' => $sTabId,
+                        'data-toggle' => 'tab',
+                        'href' => '#' . $sContentId,
+                        'role' => 'tab',
+                        'aria-controls' => 'custom-tabs-one-profile',
+                        'aria-selected' => ($iCounter == 1 ? true : false),
+                    ],
+                    $sLabel
+                )
+            );
+            $sContent .= $this->_tag('div', [
+                'class' => 'tab-pane fade' . ($iCounter == 1 ? ' active show' : ''),
+                'id' => $sContentId,
+                'role' => 'tabpanel',
+                'aria-labelledby' => $sTabId,
+            ], $sTabContent);
+        }
+        $sTabs = $this->_tag('ul', ['class' => 'nav nav-tabs', 'role' => 'tablist'], $sTabs);
+        $sContent = $this->_tag('div', ['class' => 'tab-content'], $sContent);
+
+        return $asArray
+            ? ['tabs' => $sTabs, 'content' => $sContent]
+            : $sTabs . $sContent
+        ;
+    }
 
 }
diff --git a/config/inc_cronlog.php.dist b/config/inc_cronlog.php.dist
index cc03cc295884066f75aef6be906a05875356f178..5f914266d32b0f32dfa76cfd7b2cf13b556c8ecb 100644
--- a/config/inc_cronlog.php.dist
+++ b/config/inc_cronlog.php.dist
@@ -16,7 +16,7 @@
  * 
  * it is included by classes/cronlog.class.php
  */
-return array(
+return [
     // starting directory with all servers cronwrapper logs
     'sDatadir'=>'__APPDIR__/data',
 
@@ -46,4 +46,4 @@ return array(
         'Extern'=>'https://cronlogs.extern.example.com/',
     ],
     */    
-);
+];
diff --git a/config/lang_de-de.php b/config/lang_de-de.php
index 70ddac699066b668bc260bb2ec726b06c223b6d2..ebb1aa87bdf96bbc9451c53249ac7ad042f6505d 100644
--- a/config/lang_de-de.php
+++ b/config/lang_de-de.php
@@ -26,7 +26,7 @@ return [
 
     "history"           => "History",
     "history-head"      => "History",
-    "history-hint"      => "Von den gestarteten Cronjobs werden die Ausf&uuml;hrungszeiten und deren Exitcode f&uuml;r 6 Tage aufgehoben.",
+    "history-hint"      => "Von den gestarteten Cronjobs werden die Ausf&uuml;hrungszeiten und deren Exitcode f&uuml;r mehrere Tage aufgehoben.",
 
     "timeline"          => "Timeline",
     "timeline-head"     => "Graph mit Timeline",
diff --git a/config/lang_en-en.php b/config/lang_en-en.php
index 305b112f372ca802b6829752f6415a63fcb6b4aa..abfaabf5327df4769833c1ea2149ba90f949614c 100644
--- a/config/lang_en-en.php
+++ b/config/lang_en-en.php
@@ -26,7 +26,7 @@ return [
 
     "history"           => "History",
     "history-head"      => "History",
-    "history-hint"      => "The cronwrapper keeps data for 6 days. For each executed cronjob you can see execution times and exitcode.",
+    "history-hint"      => "The cronwrapper keeps execution history data for multiples days. For each executed cronjob you can see execution times and exitcode.",
 
     "timeline"          => "Timeline",
     "timeline-head"     => "Graph with timeline",
diff --git a/config/page.tpl.php b/config/page.tpl.php
index d429d459a1f726aa95a63efdc18d410fd838d289..9a4720f4c7d4a7bdde2c656146a698e4e9bf4a3a 100644
--- a/config/page.tpl.php
+++ b/config/page.tpl.php
@@ -26,72 +26,7 @@
     <link rel="stylesheet" href="{{DIR_ADMINLTE}}/css/adminlte.min.css?v=3.2.0">
 
     <link rel="stylesheet" href="/main.css">
-    
-    <!--
-    <script nonce="b14481d0-1a31-4cec-b6e8-b5ad6020a5a9">
-        (function(w, d) {
-            ! function(cM, cN, cO, cP) {
-                cM.zarazData = cM.zarazData || {};
-                cM.zarazData.executed = [];
-                cM.zaraz = {
-                    deferred: [],
-                    listeners: []
-                };
-                cM.zaraz.q = [];
-                cM.zaraz._f = function(cQ) {
-                    return function() {
-                        var cR = Array.prototype.slice.call(arguments);
-                        cM.zaraz.q.push({
-                            m: cQ,
-                            a: cR
-                        })
-                    }
-                };
-                for (const cS of ["track", "set", "debug"]) cM.zaraz[cS] = cM.zaraz._f(cS);
-                cM.zaraz.init = () => {
-                    var cT = cN.getElementsByTagName(cP)[0],
-                        cU = cN.createElement(cP),
-                        cV = cN.getElementsByTagName("title")[0];
-                    cV && (cM.zarazData.t = cN.getElementsByTagName("title")[0].text);
-                    cM.zarazData.x = Math.random();
-                    cM.zarazData.w = cM.screen.width;
-                    cM.zarazData.h = cM.screen.height;
-                    cM.zarazData.j = cM.innerHeight;
-                    cM.zarazData.e = cM.innerWidth;
-                    cM.zarazData.l = cM.location.href;
-                    cM.zarazData.r = cN.referrer;
-                    cM.zarazData.k = cM.screen.colorDepth;
-                    cM.zarazData.n = cN.characterSet;
-                    cM.zarazData.o = (new Date).getTimezoneOffset();
-                    if (cM.dataLayer)
-                        for (const cZ of Object.entries(Object.entries(dataLayer).reduce(((c_, da) => ({
-                                ...c_[1],
-                                ...da[1]
-                            }))))) zaraz.set(cZ[0], cZ[1], {
-                            scope: "page"
-                        });
-                    cM.zarazData.q = [];
-                    for (; cM.zaraz.q.length;) {
-                        const db = cM.zaraz.q.shift();
-                        cM.zarazData.q.push(db)
-                    }
-                    cU.defer = !0;
-                    for (const dc of [localStorage, sessionStorage]) Object.keys(dc || {}).filter((de => de.startsWith("_zaraz_"))).forEach((dd => {
-                        try {
-                            cM.zarazData["z_" + dd.slice(7)] = JSON.parse(dc.getItem(dd))
-                        } catch {
-                            cM.zarazData["z_" + dd.slice(7)] = dc.getItem(dd)
-                        }
-                    }));
-                    cU.referrerPolicy = "origin";
-                    cU.src = "/cdn-cgi/zaraz/s.js?z=" + btoa(encodeURIComponent(JSON.stringify(cM.zarazData)));
-                    cT.parentNode.insertBefore(cU, cT)
-                };
-                ["complete", "interactive"].includes(cN.readyState) ? zaraz.init() : cM.addEventListener("DOMContentLoaded", zaraz.init)
-            }(w, d, 0, "script");
-        })(window, document);
-    </script>
-    -->
+
 </head>
 
 <body class="hold-transition {{PAGE_SKIN}} {{PAGE_LAYOUT}}">
diff --git a/get.php b/get.php
index 6c44582165e46292c98bbf15c52ffe308cc67d0c..6a00219c180e02c00c503d68114272f14bf2991c 100644
--- a/get.php
+++ b/get.php
@@ -48,9 +48,9 @@ if($sServer){
 switch ($sItem){
     case 'cronhistory':
         if($sServer==='ALL'){
-            $sHtml.=$oCL->renderJoblistOfAllServers();
+            $sHtml.=$oCL->renderHistoryOfAllServers();
         } else {
-            $sHtml.=$oCL->renderJoblist();
+            $sHtml.=$oCL->renderHistoryTable();
         }
         break;
     case 'cronlogs':
@@ -68,7 +68,7 @@ switch ($sItem){
         }
         break;
     case 'instances':
-        $sHtml.=$oCL->renderInstances($sServer);
+        $sHtml.=$oCL->renderInstances();
         break;
     case 'selectserver':
         $sHtml.=$oCL->renderServerlist($sServer);
diff --git a/index.php b/index.php
index e924b3d252fadc4c9850dee78503bd36a3ea7108..2faac085160b6fc53a101bce3d758d95e157b775 100644
--- a/index.php
+++ b/index.php
@@ -1,6 +1,6 @@
 <?php
 
-define("APP_VERSION", '2.1.4');
+define("APP_VERSION", '2.2.0');
 
 require_once('classes/render-adminlte.class.php');
 require_once('classes/cronlog-renderer.class.php');