diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php index 0a730ca0260da8bf15d093f1ac265e446ce3faca..e2cf5e2f45f46ff42de2dbdd28bf8617d28de83a 100644 --- a/classes/cronlog-renderer.class.php +++ b/classes/cronlog-renderer.class.php @@ -51,9 +51,10 @@ class cronlogrenderer extends cronlog{ return ''; } $iAge=round((date('U')-$iLast)/60); - return '' - . 'Abruf: '.date("Y-m-d H:i:s").' min<br>' - . 'letzter Eintrag: vor '.$iAge.' min<br><br>' + return '<div class="accessandage">' + . sprintf($this->t("request-time"), date("Y-m-d H:i:s")).'<br>' + . sprintf($this->t("last-entry"), $iAge) + .'</div>' ; } @@ -65,14 +66,36 @@ class cronlogrenderer extends cronlog{ * */ protected function _filterDatatable($sDatatable, $sFiltertext){ - /* - <a href="#" title="Filtere nach VHost «crawler.ascii.iml.unibe.ch:443»" - onclick="$('#tableRenderedallrequestsphp1_filter>INPUT').val('crawler.ascii.iml.unibe.ch:443'); - $('#tableRenderedallrequestsphp1').dataTable().fnFilter('crawler.ascii.iml.unibe.ch:443'); return false;">crawler.ascii.iml.unibe.ch:443</a> - */ return '$(\'#'.$sDatatable.'\').dataTable().fnFilter(\''.$sFiltertext.'\'); return false;'; } + /** + * helper function to be used in methods that render datatable tables + * get javascript code to be added in init options and set language specifi texts + * @return string + */ + protected function _getDatatableLanguage(){ + return $this->t("dt-USE") + ? ', "oLanguage":{ + "sProcessing":"'.$this->t("dt-sProcessing").'", + "sLengthMenu":"'.$this->t("dt-sLengthMenu").'", + "sZeroRecords":"'.$this->t("dt-sZeroRecords").'", + "sInfo":"'.$this->t("dt-sInfo").'", + "sInfoEmpty":"'.$this->t("dt-sInfoEmpty").'", + "sInfoFiltered":"'.$this->t("dt-sInfoFiltered").'", + "sInfoPostFix":"'.$this->t("dt-sInfoPostFix").'", + "sSearch":"'.$this->t("dt-sSearch").'", + "sUrl":"'.$this->t("dt-sUrl").'", + "oPaginate":{ + "sFirst":"'.$this->t("dt-sFirst").'", + "sPrevious":"'.$this->t("dt-sPrevious").'", + "sNext":"'.$this->t("dt-sNext").'", + "sLast":"'.$this->t("dt-sLast").'" + } + }' + : '' + ; + } /** * get html code for a table with events of executed cronjobs * @@ -97,7 +120,16 @@ class cronlogrenderer extends cronlog{ // Array ( [SCRIPTNAME] => apt-get update [SCRIPTTTL] => 1440 [SCRIPTSTARTTIME] => 2016-06-21 06:00:02, 1466481602 [SCRIPTLABEL] => apt-get [SCRIPTENDTIME] => 2016-06-21 06:00:49, 1466481649 [SCRIPTEXECTIME] => 47 s [SCRIPTRC] => 0 ) foreach($aData as $sDtakey=>$aEntry){ if(!$sTblHead){ - foreach(array('Startzeit', 'Label', 'Server', 'Dauer', 'TTL', '$?', 'Expired', 'Status' /*, 'Aktionen'*/) as $sKey){ + foreach(array( + $this->t("col-starting-time"), + $this->t("col-label"), + $this->t("col-server"), + $this->t("col-duration"), + $this->t("col-ttl"), + $this->t("col-rc"), + $this->t("col-expired"), + $this->t("col-status"), + ) as $sKey){ $sTblHead.='<th>'.$sKey.'</th>'; } } @@ -115,25 +147,29 @@ class cronlogrenderer extends cronlog{ $sServerFromLogfile=preg_replace('/_.*/', '', basename($aEntry['logfile'])); if($sServerFromLogfile!=$aEntry['server']){ $aErrors[]=[ - 'Hostname?', - 'Der Hostname im Log ['.$sServerFromLogfile.'] stimmt nicht mit Servernamen ['.$aEntry['server'].'] überein.', + $this->t('error-host-in-log-differs-servername-label'), + sprintf($this->t('error-host-in-log-differs-servername-description'),$sServerFromLogfile, $aEntry['server']), ]; } if(!strstr($sServerFromLogfile, ".")){ $aErrors[]=[ - 'No FQDN', - 'Der Hostname im Log ['.$sServerFromLogfile.'] ist kein KFQDN.', + $this->t('error-no-fqdn-label'), + sprintf($this->t('error-no-fqdn-description'),$sServerFromLogfile), ]; } if($iNextRunErr < date("U")){ $aErrors[]=[ - 'Abgelaufen', - 'Job wurde nicht mehr gestartet oder kein Sync zum Logserver', + $this->t('error-expired-label'), + $this->t('error-expired-description'), ]; } if($aEntry['SCRIPTRC']>0){ + $aErrors[]=[ + sprintf($this->t('error-exitcode-label'), (int)$aEntry['SCRIPTRC']), + $this->t('error-exitcode-description'), + ]; $aErrors[]=[ 'Exitcode '.(int)$aEntry['SCRIPTRC'].' (<>0)', 'The command finished with a non error status' @@ -141,8 +177,8 @@ class cronlogrenderer extends cronlog{ } if(!$aEntry['SCRIPTLABEL']){ $aErrors[]=[ - 'Kein Label?', - 'No label was detected for this job. Check the scheduled cronjob on server.' + $this->t('error-no-label-label'), + $this->t('error-no-label-description'), ]; } @@ -165,14 +201,14 @@ class cronlogrenderer extends cronlog{ $sColStatus=''; if (count($aErrors)){ foreach($aErrors as $aErr){ - $sColStatus.='<li><abbr title="'.$aErr[1].'">FEHLER: '.$aErr[0].'</abbr></li>'; + $sColStatus.='<li><abbr title="'.$aErr[1].'">'.$this->t('status-error').': '.$aErr[0].'</abbr></li>'; } $sColStatus='<ul>'.$sColStatus.'</ul>'; } else { - $sColStatus.='OK'; + $sColStatus.=$this->t('status-ok'); } // render table of last logfile per cron job - $sHtml.='<tr onclick="showFile(\''.$aEntry['logfile'].'\');" title="Klick=['.$aEntry['logfile'].'] anzeigen ">' + $sHtml.='<tr onclick="showFile(\''.$aEntry['logfile'].'\');" title="'.sprintf($this->t('row-click-show-logfile'), $aEntry['logfile']).'">' . '<td>'.date("Y-m-d H:i:s", $aEntry['SCRIPTSTARTTIME']).'</td>' // . '<td>'.$aEntry['SCRIPTNAME'].'</td>' . '<td>'.$aEntry['SCRIPTLABEL'].'</td>' @@ -208,17 +244,16 @@ class cronlogrenderer extends cronlog{ <!-- START '.__METHOD__.' --> ' - . '<h3>Letztes Logfile pro Job</h3>' - . '<p class="hint">' - . 'Von jedem Cronjob kann man das jeweils letzte Log im Detail ansehen. Mit Klick in der Tabelle wird die Logdatei geöffnet.' - . '</p>' - . '<div>' + . '<h3>'.$this->t('logs-head').'</h3>' + . '<p class="hint">' + . $this->t('logs-hint') + . '</p>' + . '<div>' . $this->_renderAccessAndAge($iLast) - . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, "FEHLER").'">' . $iErrors.'</a> ' : '') - . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, "OK").'">' . $iOK.'</a>' : '') - . ($iErrors && $iOK ? ' ... gesamt: <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'">' . count($aData).'</a>' : '' ) - . '<br>' - . '</div>' + . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-error')).'"><i class="fas fa-exclamation-circle"></i> ' . $iErrors.'</a> ' : '') + . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> ' . $iOK.'</a>' : '') + . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> ' . count($aData).'</a>' : '' ) + . '</div><br>' . '<table id="'.$sIdTable.'" class="table-striped">' . '<thead><tr>'.$sTblHead.'</tr></thead>' . '<tbody>' @@ -228,7 +263,13 @@ class cronlogrenderer extends cronlog{ // init datatable . '<script>' - . '$(document).ready( function () { $(\'#'.$sIdTable.'\').DataTable({"retrieve": true, "bPaginate":false, "aaSorting":[[0,"desc"]]}); } );' + . '$(document).ready( function () { $(\'#'.$sIdTable.'\').DataTable({ + "retrieve": true, + "bPaginate":false, + "aaSorting":[[0,"desc"]] + '.$this->_getDatatableLanguage().' + }); + });' . '</script>' . ' @@ -262,7 +303,7 @@ class cronlogrenderer extends cronlog{ $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">Instances:</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); @@ -297,7 +338,15 @@ class cronlogrenderer extends cronlog{ // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0 foreach($aData as $aEntry){ if(!$sTblHead){ - foreach(array('Startzeit', /*'Ende',*/ 'Label', 'Server', 'Dauer', 'TTL', '$?') as $sKey){ + foreach(array( + $this->t("col-starting-time"), + $this->t("col-label"), + $this->t("col-server"), + $this->t("col-duration"), + $this->t("col-ttl"), + $this->t("col-rc"), + $this->t("col-status"), + ) as $sKey){ $sTblHead.='<th>'.$sKey.'</th>'; } } @@ -324,7 +373,8 @@ class cronlogrenderer extends cronlog{ . '<td>'.$aEntry['ttl'].'</td>' . '<td class="' .($aEntry['rc']>0 ? 'message-error': 'message-ok') - . '">'.$aEntry['rc'].'</td>' + . '">'.$aEntry['rc'].'</td>' + . '<td>'.( $aEntry['rc'] ? $this->t('status-error') : $this->t('status-ok') ).'</td>' . '</tr>' ; } @@ -332,17 +382,18 @@ class cronlogrenderer extends cronlog{ $sHtml=' <!-- START '.__METHOD__.' --> ' - - . '<h3>History</h3>' - . '<p class="hint">' - . 'Von den gestarteten Cronjobs werden die Ausführungszeiten und deren Exitcode für 6 Tage aufgehoben.' - . '</p>' - . '<div>' + . '<h3>'.$this->t('history-head').'</h3>' + . '<p class="hint">' + . $this->t('history-hint') + . '</p>' + . '<div>' . $this->_renderAccessAndAge($iLast) - . 'gesamt: <strong>' . count($aData).'</strong>' - . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '') - . '<br><br>' - . '</div>' + . ($iErrors ? '<a href="#" class="btn bg-danger" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-error')).'"><i class="fas fa-exclamation-circle"></i> ' . $iErrors.'</a> ' : '') + . ( $iOK ? '<a href="#" class="btn bg-success" onclick="'.$this->_filterDatatable($sIdTable, $this->t('status-ok')).'"><i class="fas fa-check"></i> ' . $iOK.'</a>' : '') + . ($iErrors && $iOK ? ' ... '.$this->t('total').': <a href="#" class="btn bg-gray" onclick="'.$this->_filterDatatable($sIdTable, "").'"><i class="fas fa-th-large"></i> ' . count($aData).'</a>' : '' ) + . '</div>' + . '<br>' + . '<table id="'.$sIdTable.'">' . '<thead><tr>'.$sTblHead.'</tr></thead>' . '<tbody>' @@ -352,7 +403,12 @@ class cronlogrenderer extends cronlog{ // init datatable . '<script>' - . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({"retrieve": true, "aaSorting":[[0,"desc"]],"aaSorting":[[0,"desc"]], "aLengthMenu":[[25,100,-1],[25,100,"---"]]});} );' + . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({ + "retrieve": true, + "aaSorting":[[0,"desc"]], + "aLengthMenu":[[25,100,-1],[25,100,"---"]] + '.$this->_getDatatableLanguage().' + });} );' . '</script>' . ' @@ -447,18 +503,16 @@ class cronlogrenderer extends cronlog{ <!-- START '.__METHOD__.' --> - <h3>Graph mit Timeline</h3> - <p class="hint"> - Aus der History der letzten 6 Tage werden die Cronjobs mit mehr als '.$this->_iMinTime4Timeline.' Sekunden Laufzeit dargestellt.<br> - So kann man ggf. Konflikte und Ungereimtheiten finden.<br> - Innerhalb der Timeline kann man mit dem Mausrad den Zoom verändern. - </p> - - <p> - Jobs mit mehr als '.$this->_iMinTime4Timeline.'s Laufzeit: <strong>'.count($aDataset).'</strong> (ges.: '.count($aData).')<br><br> - '. (count($aDataset) ? $this->_renderAccessAndAge($iLast) : '' ).' - </p> ' + . '<h3>'.$this->t('timeline-head').'</h3>' + . '<p class="hint">' + . sprintf($this->t('timeline-hint'), $this->_iMinTime4Timeline) + . '</p> + <p>' + .sprintf($this->t('graph-rendered-jobs'), $this->_iMinTime4Timeline).': <strong>'.count($aDataset).'</strong> ' + .'('.$this->t('total').': '.count($aData).')<br><br>' + .(count($aDataset) ? $this->_renderAccessAndAge($iLast) : '' ) + .'</p>' .(count($aDataset) ? '<div id="'.$sDivId.'"></div> @@ -481,7 +535,9 @@ class cronlogrenderer extends cronlog{ // fix: some timelines do not properly work ... but I make them visible $(\'#'.$sDivId.' .vis-timeline\').css(\'visibility\', \'visible\'); </script> - ' : '(kein Graph)<br>') + ' + : $this->t('graph-no-data').'<br>' + ) .' <!-- ENDE '.__METHOD__.'--> @@ -505,6 +561,23 @@ class cronlogrenderer extends cronlog{ return $this->renderJobGraph($aData); } + /** + * used in config/page-replacements.php + * generate an array of javascript lang texts + */ + public function renderJSLang(){ + $aReturn=[]; + + foreach($this->_aLang as $sKey=>$sText){ + if(preg_match('/^JS_/', $sKey)){ + $aReturn[preg_replace('/^JS_/', '', $sKey)]=$sText; + } + } + return "\n" + .'// generated by '.__METHOD__."\n" + .'var aLang='.json_encode($aReturn, JSON_PRETTY_PRINT).'; '."\n" + ; + } /** * show a single log file @@ -514,19 +587,19 @@ class cronlogrenderer extends cronlog{ */ public function renderLogfile($sLogfile){ $sHtml='' - . '<button style="position: fixed;" onclick="closeOverlay();" class="btn btn-default"><i class="fas fa-chevron-left"></i> back</button><br><br>' - . '<h3>Logfile '.basename($sLogfile).'</h3>' + . '<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 . 'ERROR: empty filename for log file was given.'; + return $sHtml . $this->t('error-nologfile'); } if(strstr($sLogfile, '..')){ - return $sHtml . 'ERROR: wrong log file chars [..] are not allowed.'; + return $sHtml . $this->t('error-dots-not-allowed'); } // $sMyFile=$this->_getServerlogDir().'/'.$sLogfile; $sMyFile=$this->_sDataDir.'/'.$sLogfile; if(!file_exists($sMyFile)){ - return $sHtml . 'ERROR: The requested logfile<br>['.$sMyFile.']<br>does not exist (anymore).'; + return $sHtml . sprintf($this->t('error-logfile-not-found'), $sMyFile); } @@ -574,7 +647,7 @@ class cronlogrenderer extends cronlog{ $iMaxItemsToShow=30; $sHtml.='<option value="ALL"' .($sSelectedItem===false || $sSelectedItem==='ALL' ? ' selected="selected"' : '') - . '>[ALLE ('.count($this->getServers()).')]</option>'; + . '>['.$this->t('ALL').' ('.count($this->getServers()).')]</option>'; foreach($this->getServers() as $sServer=>$aData){ $sHtml.='<option value="'.$sServer.'"' .($sSelectedItem===$sServer ? ' selected="selected"' : '') diff --git a/classes/cronlog.class.php b/classes/cronlog.class.php index d0a0afee369572da935bf05b512af27b9697c586..fb7516741b821eef40cbbb9a56a5dc0a1b250666 100644 --- a/classes/cronlog.class.php +++ b/classes/cronlog.class.php @@ -55,6 +55,9 @@ class cronlog { protected $_sFileFilter_serverjoblog = '*joblog*.done'; protected $_sFileFilter_serverlog = '*.log'; + protected $_sLang = ''; // language ... read from config file + protected $_aLang = []; // language data + // ---------------------------------------------------------------------- // MAIN // ---------------------------------------------------------------------- @@ -75,6 +78,13 @@ class cronlog { $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')){ + 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.'); + } + $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); @@ -299,6 +309,14 @@ class cronlog { return $aReturn; } + /** + * translate ... get a language specific text of a given key + */ + public function t($id){ + return '' + .(isset($this->_aLang[$id]) ? $this->_aLang[$id] : '['.$id.'] ???') + ; + } // ---------------------------------------------------------------------- // public setter // ---------------------------------------------------------------------- @@ -323,5 +341,4 @@ class cronlog { return $this->_sActiveServer; } - } \ No newline at end of file diff --git a/config/inc_cronlog.php.dist b/config/inc_cronlog.php.dist index bcacdd6fbc86f475d29c8deecddcfffdba1bfa93..cc03cc295884066f75aef6be906a05875356f178 100644 --- a/config/inc_cronlog.php.dist +++ b/config/inc_cronlog.php.dist @@ -20,6 +20,9 @@ return array( // starting directory with all servers cronwrapper logs 'sDatadir'=>'__APPDIR__/data', + // language - see config/lang_*.php files + 'lang'=>'en-en', + // caching time for serverinfos 'iTtlCache'=>60, diff --git a/config/lang_de-de.php b/config/lang_de-de.php new file mode 100644 index 0000000000000000000000000000000000000000..4506a22ee3318c582982828f03bd5b5a42c65f25 --- /dev/null +++ b/config/lang_de-de.php @@ -0,0 +1,100 @@ +<?php +/* + CronLog viewer :: language file +*/ +return [ + + "id" => "deutsch", + + // ---------- left bar + "search" => "Suche", + + // ---------- top bar + "instances" => "Instanzen", + + // ---------- content + "ALL" => "alle Server", + + "request-time" => "Abruf: %s", + "last-entry" => "Letzer Eintrag: vor <strong>%s</strong> min", + + "total" => "Gesamt", + + "logs" => "Logdateien", + "logs-head" => "Aktueller Status aller Cronjobs", + "logs-hint" => "Von jedem Cronjob kann man das jeweils letzte Log im Detail ansehen. Mit Klick in der Tabelle wird die Logdatei geöffnet.", + + "history" => "History", + "history-head" => "History", + "history-hint" => "Von den gestarteten Cronjobs werden die Ausführungszeiten und deren Exitcode für 6 Tage aufgehoben.", + + "timeline" => "Timeline", + "timeline-head" => "Graph mit Timeline", + "timeline-hint" => "Aus der History der letzten 6 Tage werden die Cronjobs mit mehr als %s Sekunden Laufzeit dargestellt.<br>" + ."So kann man ggf. Konflikte und Ungereimtheiten finden.<br>" + ."Innerhalb der Timeline kann man mit dem Mausrad den Zoom verändern. Mit gedrückter Maustaste kann man nach links und rechts schieben, um das Zeitabschnitt zu verschieben.", + + + // ---------- table + "col-starting-time" => "Startzeit", + "col-label" => "Label", + "col-server" => "Server", + "col-duration" => "Dauer", + "col-ttl" => "TTL", + "col-rc" => "$?", + "col-expired" => "veraltet?", + "col-status" => "Status", + "status-ok" => "OK", + "status-error" => "FEHLER", + + + "row-click-show-logfile" => "Klick=[%s] anzeigen", + + // ---------- errors + "error-host-in-log-differs-servername-label" => "Hostname?", + "error-host-in-log-differs-servername-description" => "Der Hostname im Log [%s] stimmt nicht mit Servernamen [%s] überein.", + "error-no-fqdn-label" => "Kein FQDN", + "error-no-fqdn-description" => "Der Hostname im Log [%s] ist kein FQDN.", + "error-expired-label" => "Abgelaufen", + "error-expired-description" => "Job wurde nicht mehr gestartet oder kein Sync zum Logserver.", + "error-exitcode-label" => "Exitcode %s (<> 0)", + "error-exitcode-description" => "Job wurde nicht mit exitcode 0 beendet.", + "error-no-label-label" => "Kein Label?", + "error-no-label-description" => "Es wurde kein Label für den Cronjob erkannt. Bitte Kommandozeile des Jobs prüfen.", + + "graph-rendered-jobs" => "Jobs mit mehr als %s s Laufzeit", + "graph-no-data" => "(Keine Daten zum Rendern eines Graphen)", + + // ---------- popup with log file + "back" => "zurück", + "logfile" => "Logdatei", + + "error-no-logfile" => "ERROR: empty filename for log file was given.", + "error-dots-not-allowed" => "ERROR: wrong log file chars [..] are not allowed.", + "error-logfile-not-found" => "ERROR: The requested logfile<br>[%s]<br>does not exist (anymore).", + + // ---------- javascript texts + "JS_Refresh" => "Aktualisierung in <span>__TIME__</span> s", + + // ---------- datatable texts + // flag: set to true to all languages except English (it is the default of datatables) + "dt-USE" => true, + + // these will be used if dt-USE is set to true: + "dt-sProcessing" => "Bitte warten...", + "dt-sLengthMenu" => "_MENU_ Einträge anzeigen", + "dt-sZeroRecords" => "Keine Einträge vorhanden.", + "dt-sInfo" => "_START_ bis _END_ von _TOTAL_ Einträgen", + "dt-sInfoEmpty" => "0 bis 0 von 0 Einträgen", + "dt-sInfoFiltered" => "(gefiltert von _MAX_ Einträgen)", + "dt-sInfoPostFix" => "", + "dt-sSearch" => "Suchen: ", + "dt-sUrl" => "", + "dt-sFirst" => "Erster", + "dt-sPrevious" => "Zurück", + "dt-sNext" => "Nächster", + "dt-sLast" => "Letzter", + + "" => "", + +]; \ No newline at end of file diff --git a/config/lang_en-en.php b/config/lang_en-en.php new file mode 100644 index 0000000000000000000000000000000000000000..eef52a056c0ff6fe3b011de51ac5f6d831896388 --- /dev/null +++ b/config/lang_en-en.php @@ -0,0 +1,100 @@ +<?php +/* + CronLog viewer :: language file +*/ +return [ + + "id" => "english", + + // ---------- left bar + "search" => "Search", + + // ---------- top bar + "instances" => "Instances", + + // ---------- content + "ALL" => "all servers", + + "request-time" => "Request time: %s", + "last-entry" => "Last entry: <strong>%s</strong> min ago", + + "total" => "Total", + + "logs" => "Logfiles", + "logs-head" => "Current status of each cronjob", + "logs-hint" => "View the last logfile of each cronjob. Click into the table row to open it.", + + "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.", + + "timeline" => "Timeline", + "timeline-head" => "Graph with timeline", + "timeline-hint" => "The a graph of the history of the last 6 days - draw cronjobs running longer than %s seconds.<br>" + ."It helps to find timing conflicts and errors.<br>" + ."Inside the timeline you can zoom with the mouse wheel or drag to the left and right to change the time window.", + + + // ---------- table + "col-starting-time" => "Starting time", + "col-label" => "Label", + "col-server" => "Server", + "col-duration" => "Duration", + "col-ttl" => "TTL", + "col-rc" => "$?", + "col-expired" => "Expired?", + "col-status" => "Status", + "status-ok" => "OK", + "status-error" => "ERROR", + + + "row-click-show-logfile" => "Click=show [%s]", + + // ---------- errors + "error-host-in-log-differs-servername-label" => "Hostname?", + "error-host-in-log-differs-servername-description" => "The hostname in the logfile [%s] does not match with server name [%s].", + "error-no-fqdn-label" => "No fqdn", + "error-no-fqdn-description" => "The hostname in the logfile [%s] is no fqdn.", + "error-expired-label" => "Expired", + "error-expired-description" => "The Job was not executed anymore (or no sync to the logserver).", + "error-exitcode-label" => "Exitcode %s (<> 0)", + "error-exitcode-description" => "The job did not finish with exitcode 0.", + "error-no-label-label" => "No label?", + "error-no-label-description" => "No label detected for the cronjob. Check the command line of the job.", + + "graph-rendered-jobs" => "Jobs with an execution time longer %s s", + "graph-no-data" => "(No data to render a graph)", + + // ---------- popup with log file + "back" => "back", + "logfile" => "Logfile", + + "error-no-logfile" => "ERROR: empty filename for log file was given.", + "error-dots-not-allowed" => "ERROR: wrong log file chars [..] are not allowed.", + "error-logfile-not-found" => "ERROR: The requested logfile<br>[%s]<br>does not exist (anymore).", + + // ---------- javascript texts + "JS_Refresh" => "Refresh in <span>__TIME__</span> sec", + + // ---------- datatable texts + // flag: set to true to all languages except English (it is the default of datatables) + "dt-USE" => false, + + // these will be used if dt-USE is set to true: + "dt-sProcessing" => "Bitte warten...", + "dt-sLengthMenu" => "_MENU_ Einträge anzeigen", + "dt-sZeroRecords" => "Keine Einträge vorhanden.", + "dt-sInfo" => "_START_ bis _END_ von _TOTAL_ Einträgen", + "dt-sInfoEmpty" => "0 bis 0 von 0 Einträgen", + "dt-sInfoFiltered" => "(gefiltert von _MAX_ Einträgen)", + "dt-sInfoPostFix" => "", + "dt-sSearch" => "Suchen: ", + "dt-sUrl" => "", + "dt-sFirst" => "Erster", + "dt-sPrevious" => "Zurück", + "dt-sNext" => "Nächster", + "dt-sLast" => "Letzter", + + "" => "", + +]; \ No newline at end of file diff --git a/config/page.tpl.php b/config/page.tpl.php index 3659b4545b783f4ff1f1201e17d01e6230c1ca9b..d429d459a1f726aa95a63efdc18d410fd838d289 100644 --- a/config/page.tpl.php +++ b/config/page.tpl.php @@ -22,10 +22,6 @@ <!-- fontawesome --> <link href="vendor/font-awesome/5.15.4/css/all.min.css" rel="stylesheet" type="text/css"/> - - <!-- - <link rel="stylesheet" href="{{DIR_ADMINLTEPLUGINS}}/fontawesome-free/css/all.min.css"> - --> <!-- Adminlte --> <link rel="stylesheet" href="{{DIR_ADMINLTE}}/css/adminlte.min.css?v=3.2.0"> @@ -102,225 +98,13 @@ <div class="wrapper"> {{NAVI_TOP}} - <!-- - <nav class="main-header navbar navbar-expand navbar-white navbar-light"> - - <ul class="navbar-nav"> - <li class="nav-item"> - <a class="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="index3.html" class="nav-link">Home</a> - </li> - <li class="nav-item d-none d-sm-inline-block"> - <a href="#" class="nav-link">Contact</a> - </li> - </ul> - - <ul class="navbar-nav ml-auto"> - - <li class="nav-item"> - <a class="nav-link" data-widget="navbar-search" href="#" role="button"> - <i class="fas fa-search"></i> - </a> - <div class="navbar-search-block"> - <form class="form-inline"> - <div class="input-group input-group-sm"> - <input class="form-control form-control-navbar" type="search" placeholder="Search" - aria-label="Search"> - <div class="input-group-append"> - <button class="btn btn-navbar" type="submit"> - <i class="fas fa-search"></i> - </button> - <button class="btn btn-navbar" type="button" data-widget="navbar-search"> - <i class="fas fa-times"></i> - </button> - </div> - </div> - </form> - </div> - </li> - - <li class="nav-item dropdown"> - <a class="nav-link" data-toggle="dropdown" href="#"> - <i class="far fa-comments"></i> - <span class="badge badge-danger navbar-badge">3</span> - </a> - <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> - <a href="#" class="dropdown-item"> - - <div class="media"> - <img src="dist/img/user1-128x128.jpg" alt="User Avatar" - class="img-size-50 mr-3 img-circle"> - <div class="media-body"> - <h3 class="dropdown-item-title"> - Brad Diesel - <span class="float-right text-sm text-danger"><i class="fas fa-star"></i></span> - </h3> - <p class="text-sm">Call me whenever you can...</p> - <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> - </div> - </div> - - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item"> - - <div class="media"> - <img src="dist/img/user8-128x128.jpg" alt="User Avatar" - class="img-size-50 img-circle mr-3"> - <div class="media-body"> - <h3 class="dropdown-item-title"> - John Pierce - <span class="float-right text-sm text-muted"><i class="fas fa-star"></i></span> - </h3> - <p class="text-sm">I got your message bro</p> - <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> - </div> - </div> - - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item"> - - <div class="media"> - <img src="dist/img/user3-128x128.jpg" alt="User Avatar" - class="img-size-50 img-circle mr-3"> - <div class="media-body"> - <h3 class="dropdown-item-title"> - Nora Silvester - <span class="float-right text-sm text-warning"><i - class="fas fa-star"></i></span> - </h3> - <p class="text-sm">The subject goes here</p> - <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p> - </div> - </div> - - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item dropdown-footer">See All Messages</a> - </div> - </li> - - <li class="nav-item dropdown"> - <a class="nav-link" data-toggle="dropdown" href="#"> - <i class="far fa-bell"></i> - <span class="badge badge-warning navbar-badge">15</span> - </a> - <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right"> - <span class="dropdown-header">15 Notifications</span> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item"> - <i class="fas fa-envelope mr-2"></i> 4 new messages - <span class="float-right text-muted text-sm">3 mins</span> - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item"> - <i class="fas fa-users mr-2"></i> 8 friend requests - <span class="float-right text-muted text-sm">12 hours</span> - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item"> - <i class="fas fa-file mr-2"></i> 3 new reports - <span class="float-right text-muted text-sm">2 days</span> - </a> - <div class="dropdown-divider"></div> - <a href="#" class="dropdown-item dropdown-footer">See All Notifications</a> - </div> - </li> - <li class="nav-item"> - <a class="nav-link" data-widget="fullscreen" href="#" role="button"> - <i class="fas fa-expand-arrows-alt"></i> - </a> - </li> - <li class="nav-item"> - <a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#" role="button"> - <i class="fas fa-th-large"></i> - </a> - </li> - </ul> - </nav> - --> <aside class="main-sidebar sidebar-dark-primary elevation-4"> {{BRAND}} - <!-- - <a href="index3.html" class="brand-link"> - <img src="dist/img/AdminLTELogo.png" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" - style="opacity: .8"> - <span class="brand-text font-weight-light">AdminLTE 3</span> - </a> - --> <div class="sidebar"> - - <!-- - <div class="user-panel mt-3 pb-3 mb-3 d-flex"> - <div class="image"> - <img src="dist/img/user2-160x160.jpg" class="img-circle elevation-2" alt="User Image"> - </div> - <div class="info"> - <a href="#" class="d-block">Alexander Pierce</a> - </div> - </div> - - <div class="form-inline"> - <div class="input-group" data-widget="sidebar-search"> - <input class="form-control form-control-sidebar" type="search" placeholder="Search" - aria-label="Search"> - <div class="input-group-append"> - <button class="btn btn-sidebar"> - <i class="fas fa-search fa-fw"></i> - </button> - </div> - </div> - </div> - --> {{NAVI_LEFT}} - <br><br> - - <!-- - <nav class="mt-2"> - <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false"> - - <li class="nav-item menu-open"> - <a href="#" class="nav-link active"> - <i class="nav-icon fas fa-tachometer-alt"></i> - <p> - Starter Pages - <i class="right fas fa-angle-left"></i> - </p> - </a> - <ul class="nav nav-treeview"> - <li class="nav-item"> - <a href="#" class="nav-link active"> - <i class="far fa-circle nav-icon"></i> - <p>Active Page</p> - </a> - </li> - <li class="nav-item"> - <a href="#" class="nav-link"> - <i class="far fa-circle nav-icon"></i> - <p>Inactive Page</p> - </a> - </li> - </ul> - </li> - <li class="nav-item"> - <a href="#" class="nav-link"> - <i class="nav-icon fas fa-th"></i> - <p> - Simple Link - <span class="right badge badge-danger">New</span> - </p> - </a> - </li> - </ul> - </nav> - --> - </div> </aside> @@ -338,8 +122,7 @@ </div> <div class="col-sm-6"> <ol class="breadcrumb float-sm-right"> - {{PAGE_HEADER_RIGHT}} - + {{PAGE_HEADER_RIGHT}} <!-- <li class="breadcrumb-item"><a href="#">Home</a></li> <li class="breadcrumb-item active">Starter Page</li> @@ -353,68 +136,8 @@ <div class="content"> - - <div class="container-fluid"> - {{PAGE_BODY}} - - <!-- - <div class="row"> - <div class="col-lg-6"> - <div class="card"> - <div class="card-body"> - <h5 class="card-title">Card title</h5> - <p class="card-text"> - Some quick example text to build on the card title and make up the bulk of the - card's - content. - </p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - <div class="card card-primary card-outline"> - <div class="card-body"> - <h5 class="card-title">Card title</h5> - <p class="card-text"> - Some quick example text to build on the card title and make up the bulk of the - card's - content. - </p> - <a href="#" class="card-link">Card link</a> - <a href="#" class="card-link">Another link</a> - </div> - </div> - </div> - - <div class="col-lg-6"> - <div class="card"> - <div class="card-header"> - <h5 class="m-0">Featured</h5> - </div> - <div class="card-body"> - <h6 class="card-title">Special title treatment</h6> - <p class="card-text">With supporting text below as a natural lead-in to additional - content.</p> - <a href="#" class="btn btn-primary">Go somewhere</a> - </div> - </div> - <div class="card card-primary card-outline"> - <div class="card-header"> - <h5 class="m-0">Featured</h5> - </div> - <div class="card-body"> - <h6 class="card-title">Special title treatment</h6> - <p class="card-text">With supporting text below as a natural lead-in to additional - content.</p> - <a href="#" class="btn btn-primary">Go somewhere</a> - </div> - </div> - </div> - - </div> - --> </div> </div> @@ -434,19 +157,13 @@ <div class="float-right d-none d-sm-inline"> {{PAGE_FOOTER_RIGHT}} - <!-- - Anything you want - --> </div> {{PAGE_FOOTER_LEFT}} - <!-- - <strong>Copyright © 2014-2021 <a href="https://adminlte.io">AdminLTE.io</a>.</strong> All rights - reserved. - --> </footer> </div> + <script>{{INJECT_JS}}</script> <script src="{{DIR_ADMINLTEPLUGINS}}/bootstrap/js/bootstrap.bundle.min.js"></script> <script src="{{DIR_ADMINLTE}}/js/adminlte.min.js?v=3.2.0"></script> <script type="text/javascript" src="js/functions.js"></script> diff --git a/config/page_replacements.php b/config/page_replacements.php index c063d2fb8091c7d3e88f3f63cdd0f16a40fd7573..f9551de9b256554921b639facdbbca64e498bab5 100644 --- a/config/page_replacements.php +++ b/config/page_replacements.php @@ -1,5 +1,10 @@ <?php +/* + this file is included by ../index.php + $cr=new cronlogrenderer(); + +*/ return [ '{{DIR_ADMINLTE}}' =>'/vendor/admin-lte/3.2.0', '{{DIR_ADMINLTEPLUGINS}}' =>'/vendor/admin-lte-plugins', @@ -60,13 +65,13 @@ return [ <ul class="navbar-nav"> <li class="nav-item"> <a href="#cronlogs" onclick="setTab(this); return false;" class="nav-link active"> - <i class="far fa-file-alt"></i> Logs</a></li> + <i class="far fa-file-alt"></i> '.$cr->t('logs').'</a></li> <li class="nav-item"> <a href="#cronhistory" onclick="setTab(this); return false;" class="nav-link"> - <i class="fas fa-history"></i> History</a></li> + <i class="fas fa-history"></i> '.$cr->t('history').'</a></li> <li class="nav-item"> <a href="#graph" onclick="setTab(this); return false;" class="nav-link"> - <i class="far fa-chart-bar"></i> Timeline</a></li> + <i class="far fa-chart-bar"></i> '.$cr->t('timeline').'</a></li> </ul> </nav>', '{{PAGE_HEADER_RIGHT}}'=>'<span id="counter" style="float: right;"></span>', @@ -92,6 +97,7 @@ return [ </div> ', - '{{PAGE_FOOTER_LEFT}}'=>'2018 -2022 // University of Bern * Institute for Medical education', + '{{INJECT_JS}}' => $cr->renderJSLang(), + '{{PAGE_FOOTER_LEFT}}'=>'2018 - '.date('Y').' // University of Bern * Institute for Medical education', '{{PAGE_FOOTER_RIGHT}}'=>'Source: <a href="https://git-repo.iml.unibe.ch/iml-open-source/cronlog-viewer/" target="_blank">git-repo.iml.unibe.ch</a>', ]; \ No newline at end of file diff --git a/index.php b/index.php index f27f56482ce2b42db4cdac40fcc819bcda69c2a0..595fc612ae062c5004bf866188d7a279d4e43593 100644 --- a/index.php +++ b/index.php @@ -1,9 +1,11 @@ <?php -define("APP_VERSION", '2.0.3'); +define("APP_VERSION", '2.1.0'); require_once('classes/render-adminlte.class.php'); +require_once('classes/cronlog-renderer.class.php'); $renderAdminLTE=new renderadminlte(); +$cr=new cronlogrenderer(); $aReplace=include("./config/page_replacements.php"); $sTemplate=file_get_contents('config/page.tpl.php'); diff --git a/js/asimax.class.js b/js/asimax.class.js index ea49a4b78c796c09f300bc2c167da3ba25cc5e60..70a2bedac425e2d25a9597f31fb7d8934bef4224 100644 --- a/js/asimax.class.js +++ b/js/asimax.class.js @@ -79,7 +79,7 @@ var asimax = function () { } catch(e){ // nop } - console.log('ERROR in getVar() - argument "'+sName+'" does not exist'); + // console.log('ERROR in getVar() - argument "'+sName+'" does not exist'); return false; }; /** diff --git a/js/functions.js b/js/functions.js index b6ff8a9265bb36efedace9bca7f8508c15f6d786..de8b1e9f705cb3310762bb6fce930c0f4f6f477d 100644 --- a/js/functions.js +++ b/js/functions.js @@ -83,7 +83,7 @@ function getPageItem(id, sMoreData, bNoAdressbarUpdate) { */ function refreshTimer() { if(iRefreshCounter==0){ - $('#counter').html('Refresh in <span>'+(iRefreshTime)+'</span> sec'+'<div style="width:100%;"> </div>'); + $('#counter').html(aLang['Refresh'].replace('__TIME__', iRefreshTime)+'<div style="width:100%;"> </div>'); } else { $('#counter span').html(iRefreshTime-iRefreshCounter); $('#counter div').css('width', (100 - (iRefreshCounter/iRefreshTime*100))+'%'); diff --git a/main.css b/main.css index dff21044ecf9a3575c72b6710f1d4e326710844b..09ee0ecfa9a9469c2f033be273e86684eda4a121 100644 --- a/main.css +++ b/main.css @@ -10,7 +10,7 @@ /* ----- head area */ #counter{border-bottom: 1px solid #ccc;} -#counter div{background:#bcd; background:#aaa; height: 5px; transition: width 0.3s ;} +#counter div{background:#17a2b8; height: 5px; transition: width 0.3s ;} #subnav .nav-link.active{ @@ -26,10 +26,11 @@ #errorlog {background:#fcc; color:#800;} p.hint{margin-bottom: 1em; background:#f8f8f8; padding: 1em; } - +div.accessandage{float: left; margin-right: 2em;} /* ----- override datatable defaults */ .dataTables_wrapper{clear: none;float: left; margin: auto 1px;} +.dataTables_wrapper input[type="search"]{ border: 1px solid rgba(0,0,0,0.3); border-radius: 0.2em;;} /* table.dataTable tbody tr.even_{background: #f0f4f8;} table.dataTable tbody tr.odd{}