diff --git a/classes/cronlog-renderer.class.php b/classes/cronlog-renderer.class.php index 165b9d0935b89f9f1d1e9be689aa64eb6083aa65..dbaebe8edc457e3e5a46041e426191be3b6bf2ef 100644 --- a/classes/cronlog-renderer.class.php +++ b/classes/cronlog-renderer.class.php @@ -14,8 +14,9 @@ require_once 'cronlog.class.php'; * @author hahn */ class cronlogrenderer extends cronlog{ -//put your code here + + protected $_iMinTime4Timeline = 60; /** * get html code for a table with events of executed cronjobs @@ -23,18 +24,157 @@ class cronlogrenderer extends cronlog{ * @param array $aData result of $oCL->getServerLogs() * @return string */ - public function renderJoblist($aData){ + public function renderCronlogs($aData=false){ + $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; + $sHtml=$this->_getCacheData($sTaskId); + if($sHtml){ + return $sHtml; + } $sHtml=''; + if(!$aData){ + $aData=$this->getServersLastLog(); + } + // $sHtml='DEBUG: <pre>'.print_r($aData, 1).'</pre>'; + $sTblHead=''; + $iOK=0; + $iErrors=0; + $iLast=false; + // Array ( [SCRIPTNAME] => apt-get update [SCRIPTTTL] => 1440 [SCRIPTSTARTTIME] => 2016-06-21 06:00:02, 1466481602 [SCRIPTLABEL] => apt-get [SCRIPTENDTIME] => 2016-06-21 06:00:49, 1466481649 [SCRIPTEXECTIME] => 47 s [SCRIPTRC] => 0 ) + foreach($aData as $sDtakey=>$aEntry){ + if(!$sTblHead){ + foreach(array('SCRIPTSTARTTIME', 'SCRIPTLABEL', 'Server', 'SCRIPTEXECTIME', 'SCRIPTTTL', 'SCRIPTRC', 'Kommentar', 'Aktionen') as $sKey){ + $sTblHead.='<th>'.$sKey.'</th>'; + } + $iLast=date("U", $aEntry['SCRIPTSTARTTIME']); + } + // $sViewerUrl='viewer.php?host='.$aEntry['host'].'&job='.$aEntry['job']; + // $sClass='message-'.($aEntry['SCRIPTRC']?'error':'ok'); + + $aErrors=array(); + $iNextRun=$aEntry['SCRIPTSTARTTIME']+($aEntry['SCRIPTTTL']*60); + + if($iNextRun < date("U")){ + $aErrors[]='outdated'; + } + if($aEntry['SCRIPTRC']>0){ + $aErrors[]='exitcode is <>0'; + } + if(!$aEntry['SCRIPTLABEL']){ + $aErrors[]='no label?'; + } + + + if(count($aErrors)){ + $iErrors++; + $sClass='message-error'; + } else { + $iOK++; + $sClass='message-ok'; + } + + $sHtml.='<tr class="'.$sClass.'" onclick="showFile(\''.$aEntry['logfile'].'\');">' + . '<td>'.date("Y-m-d H:i:s", $aEntry['SCRIPTSTARTTIME']).'</td>' + // . '<td>'.$aEntry['SCRIPTNAME'].'</td>' + . '<td>'.$aEntry['SCRIPTLABEL'].'</td>' + . '<td>'.$aEntry['server'].'</td>' + . '<td>' + .$aEntry['SCRIPTEXECTIME'].'s' + .($aEntry['SCRIPTEXECTIME']>100 ? ' ('.round($aEntry['SCRIPTEXECTIME']/60).'min)' : '') + .'</td>' + . '<td>'.$aEntry['SCRIPTTTL'].'</td>' + . '<td>'.$aEntry['SCRIPTRC'].'</td>' + . '<td>'.(count($aErrors) ? 'FEHLER:<br>*'.implode('<br>*', $aErrors) : 'OK').'</td>' + . '<td><button onclick="showFile(\''.$aEntry['logfile'].'\');">Ansehen</button></td>' + . '</tr>' + ; + } + $iAge=round((date('U')-$iLast)/60); + $sIdTable='datatable1'; + $sHtml=' + <!-- START '.__METHOD__.' --> + ' + + . '<h3>Letztes Logfile pro Job</h3>' + . '<div>' + . 'Abruf: '.date("Y-m-d H:i:s").' min<br>' + . 'letzter Eintrag: vor '.$iAge.' min<br><br>' + . 'gesamt: <strong>' . count($aData).'</strong>' + . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '') + . '<br><br>' + . '</div>' + . '<table id="'.$sIdTable.'">' + . '<thead><tr>'.$sTblHead.'</tr></thead>' + . '<tbody>' + .$sHtml + .'</tbody>' + . '</table>' + + // init datatable + . '<script>' + . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({"bPaginate":false, "aaSorting":[[0,"desc"]]});} );' + . '</script>' + + . ' + <!-- ENDE '.__METHOD__.' --> + ' + ; + $this->_writeCacheData($sTaskId, $sHtml); + return $sHtml; + } + + public function renderCronlogsOfAllServers(){ + $aData=array(); + foreach (array_keys($this->getServers()) as $sServer){ + $this->setServer($sServer); + $aData=array_merge($aData, $this->getServersLastLog()); + } + $this->setServer('ALL'); + // echo '<pre>'.print_r($aData, 1).'</pre>'; + return $this->renderCronlogs($aData); + } + + /** + * get html code for a table with events of executed cronjobs + * + * @param array $aData result of $oCL->getServerLogs() + * @return string + */ + public function renderJoblist($aData=false){ + $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; + $sHtml=$this->_getCacheData($sTaskId); + if($sHtml){ + return $sHtml; + } + $sHtml=''; + + if(!$aData){ + $aData=$this->getServerJobHistory(); + } + + $sTblHead=''; + $iOK=0; + $iErrors=0; + $iLast=false; // job=dok-kvm-instances:host=kalium:start=1538358001:end=1538358001:exectime=0:ttl=60:rc=0 foreach($aData as $aEntry){ if(!$sTblHead){ foreach(array('start', 'end', 'job', 'host', 'exectime', 'ttl', 'rc') as $sKey){ $sTblHead.='<th>'.$sKey.'</th>'; } + $iLast=date("U", $aEntry['start']); + } + // $sViewerUrl='viewer.php?host='.$aEntry['host'].'&job='.$aEntry['job']; + $sClass='message-'.($aEntry['rc']?'error':'ok'); + + if($aEntry['rc']){ + $iErrors++; + } else { + $iOK++; } - $sHtml.='<tr>' + + $sHtml.='<tr class="'.$sClass.'">' . '<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>' @@ -48,12 +188,38 @@ class cronlogrenderer extends cronlog{ . '</tr>' ; } - return '<table>' + $iAge=round((date('U')-$iLast)/60); + $sIdTable='datatable2'; + $sHtml=' + <!-- START '.__METHOD__.' --> + ' + + . '<h3>History</h3>' + . '<div>' + . 'Abruf: '.date("Y-m-d H:i:s").' min<br>' + . 'letzter Eintrag: vor '.$iAge.' min<br><br>' + . 'gesamt: <strong>' . count($aData).'</strong>' + . ($iErrors ? ' (Fehler: <strong>' . $iErrors.'</strong>... OK: <strong>' . $iOK.'</strong>)' : '') + . '<br><br>' + . '</div>' + . '<table id="'.$sIdTable.'">' . '<thead><tr>'.$sTblHead.'</tr></thead>' . '<tbody>' .$sHtml .'</tbody>' - . '</table>'; + . '</table>' + + // init datatable + . '<script>' + . '$(document).ready( function () {$(\'#'.$sIdTable.'\').DataTable({"aaSorting":[[0,"desc"]]});} );' + . '</script>' + + . ' + <!-- ENDE '.__METHOD__.' --> + ' + ; + $this->_writeCacheData($sTaskId, $sHtml); + return $sHtml; } /** * get html code for a timeline with events of executed cronjobs @@ -65,42 +231,52 @@ class cronlogrenderer extends cronlog{ * @param array $aData result of $oCL->getServerLogs() * @return string */ - public function renderJobGraph($aData){ + public function renderJobGraph($aData=false){ $sHtml=''; static $iGraphCounter; if(!isset($iGraphCounter)){ $iGraphCounter=0; } $iGraphCounter++; - + if(!$aData){ + $aData=$this->getServerJobHistory(); + } $sDivId='vis-timeline-'.$iGraphCounter; $aDataset=array(); $iEntry=0; foreach($aData as $aEntry){ $iEntry++; - $aDataset[]=array( - 'id'=>$iEntry, - 'start'=>date("Y-m-d H:i:s", $aEntry['start']), - 'end'=>date("Y-m-d H:i:s", $aEntry['end']), - 'content'=>$aEntry['job'].'@'.$aEntry['host'], - 'className'=>'timeline-result-'.($aEntry['rc'] ? 'error' : 'ok'), - 'title'=>'<strong>'.$aEntry['job'].'@'.$aEntry['host'].'</strong><br>' - . 'start: ' . date("Y-m-d H:i:s", $aEntry['start']).'<br>' - . 'end: ' . date("Y-m-d H:i:s", $aEntry['end']).'<br>' - . 'exectime: ' - .$aEntry['exectime'].'s' - .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') - . '<br>' - . 'rc = ' . $aEntry['rc'].'<br>' - , - ); + if($aEntry['exectime']>$this->_iMinTime4Timeline){ + $aDataset[]=array( + 'id'=>$iEntry, + 'start'=>date("Y-m-d H:i:s", $aEntry['start']), + 'end'=>date("Y-m-d H:i:s", $aEntry['end']), + 'content'=>$aEntry['job'].'@'.$aEntry['host'], + 'className'=>'timeline-result-'.($aEntry['rc'] ? 'error' : 'ok'), + 'title'=>'<strong>'.$aEntry['job'].'@'.$aEntry['host'].'</strong><br>' + . 'start: ' . date("Y-m-d H:i:s", $aEntry['start']).'<br>' + . 'end: ' . date("Y-m-d H:i:s", $aEntry['end']).'<br>' + . 'exectime: ' + .$aEntry['exectime'].'s' + .($aEntry['exectime']>100 ? ' ('.round($aEntry['exectime']/60).'min)' : '') + . '<br>' + . 'rc = ' . $aEntry['rc'].'<br>' + , + ); + } } $sHtml.=' <!-- START '.__METHOD__.' --> + <h3>Graph mit Timeline</h3> + + <p> + Jobs ges.: <strong>'.count($aData).'</strong> ... davon mit mehr als '.$this->_iMinTime4Timeline.'s Laufzeit: <strong>'.count($aDataset).'</strong> + </p> + <div id="'.$sDivId.'"></div> <script type="text/javascript"> @@ -124,4 +300,75 @@ class cronlogrenderer extends cronlog{ return $sHtml; } -} + /** + * show a single log file + * + * @param string $sLogfile logfile; [server]/[filename.log] + * @return string + */ + public function renderLogfile($sLogfile){ + $sHtml='<link rel="stylesheet" type="text/css" href="main.css"><h3>Logfile '.basename($sLogfile).'</h3>' + . '<button onclick="showFileBack();">back</button><br><br>' + ; + if(!$sLogfile){ + return $sHtml . 'ERROR: empty filename for log file was given.'; + } + if(strstr($sLogfile, '..')){ + return $sHtml . 'ERROR: wrong log file chars [..] are not allowed.'; + } + $sMyFile=$this->_getServerlogDir().'/'.$sLogfile; + if(!file_exists($sMyFile)){ + return $sHtml . 'ERROR: The requested logfile ['.$sMyFile.'] does not exist (anymore).'; + } + + + if ($fileHandle = fopen($sMyFile, "r")) { + $sHtml.='<div style="float: left;" onclick="showFileBack();"><pre>'; + while (($line = fgets($fileHandle)) !== false) { + # do same stuff with the $line + $bIsComment=strstr($line, 'REM '); + if($bIsComment){ + $sHtml.='<div class="log-rem">'.$line.'</div>'; + } else { + $sKey=trim(preg_replace('/=.*/', '', $line)); + $sValue=preg_replace('/^([A-Z]*\=)/', '', $line); + $sDivClass=''; + switch($sKey){ + case 'SCRIPTRC': + $sDivClass=(int)$sValue===0 ? 'message-ok' : 'message-error'; + break; + case 'JOBEXPIRE': + $sDivClass=date('U') < (int)$sValue ? 'message-ok' : 'message-error'; + break; + } + $sValue=preg_replace('/(rc=0)/', '<span class="message-ok">$1</span>', $sValue); + $sValue=preg_replace('/(rc=[1-9][0-9]*)/', '<span class="message-error">$1</span>', $sValue); + $sValue=preg_replace('/(rc=[1-9])/', '<span class="message-error">$1</span>', $sValue); + // remove terminal color + $sValue=preg_replace('/(\[[0-9]{1,3}m)/', '', $sValue); + + $sHtml.='<div'.($sDivClass ? ' class="'.$sDivClass.'"' : '').'><span class="log-var">'.$sKey.'</span>=<span class="log-value">'.$sValue.'</span></div>'; + } + } + $sHtml.='</pre></div>'; + } + return $sHtml; + } + + public function renderServerlist($sSelectedItem=false){ + $sHtml=''; + $sHtml.='<option value="ALL">[ALLE]</option>'; + foreach($this->getServers() as $sServer=>$aData){ + $sHtml.='<option value="'.$sServer.'"' + .($sSelectedItem===$sServer ? ' selected="selected"' : '') + .'>'.$sServer.'</option>'; + } + $sHtml=$sHtml ? '<select' + . ' size="'.(count($this->getServers())+1).'"' + // . ' size="1"' + . ' onchange="setServer(this.value); return false;"' + . '>'.$sHtml.'</select>' : false; + return $sHtml; + } + +} \ No newline at end of file diff --git a/classes/cronlog.class.php b/classes/cronlog.class.php index fb0e1639a898ba039d5545eb9c6d4dc4fbf7d344..e1fc560867278f12d47a380619bf732e034b17fd 100644 --- a/classes/cronlog.class.php +++ b/classes/cronlog.class.php @@ -16,6 +16,7 @@ class cronlog { protected $_sDataDir = "__DIR__/data"; + protected $_iTtlCache = 60; // in sec protected $_aServers = false; @@ -58,7 +59,7 @@ class cronlog { * @return type */ protected function _getCacheFile($sTaskId){ - return _getCacheDir().'/'.$sTaskId; + return $this->_getCacheDir().'/'.$sTaskId; } /** @@ -69,6 +70,25 @@ class cronlog { return $this->_sDataDir.'/'.$this->_sActiveServer; } + protected function _getCacheData($sTaskId){ + $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 { + // echo "DELETE cache $sFile<br>"; + unlink($sFile); + } + } + return false; + } + protected function _writeCacheData($sTaskId, $data){ + $sFile=$this->_getCacheFile($sTaskId); + // echo "WRITE cache $sFile<br>"; + return file_put_contents($sFile, serialize($data)); + } + // ---------------------------------------------------------------------- // public getter // ---------------------------------------------------------------------- @@ -135,9 +155,17 @@ class cronlog { * get logs from jobfilea of the current or given server * @return array */ - public function getServerLogs(){ + public function getServerJobHistory(){ $aReturn=array(); + $sTaskId=__FUNCTION__.'-'.$this->_sActiveServer; + + $aData=$this->_getCacheData($sTaskId); + if($aData){ + return $aData; + } + $aData=array(); + foreach(glob($this->_getServerlogDir().'/'.$this->_sFileFilter_serverjoblog) as $sMyJobfile){ // echo "DEBUG: $sMyJobfile<br>"; $fileHandle = fopen($sMyJobfile, "r"); @@ -150,7 +178,59 @@ class cronlog { } fclose($fileHandle); } - ksort($aReturn); + krsort($aReturn); + $this->_writeCacheData($sTaskId, $aReturn); + return $aReturn; + } + + protected function _parseLogfile($sFile) { + $aReturn=array( + 'SCRIPTNAME'=>false, + 'SCRIPTTTL'=>false, + 'SCRIPTSTARTTIME'=>false, + 'SCRIPTLABEL'=>false, + 'SCRIPTENDTIME'=>false, + 'SCRIPTEXECTIME'=>false, + 'SCRIPTRC'=>false, + // 'SCRIPTOUT'=>array(), + ); + $fileHandle = fopen($sFile, "r"); + while (($line = fgets($fileHandle)) !== false) { + // get key ... the part before "=" + $sKey=trim(preg_replace('/=.*/', '', $line)); + if($sKey && isset($aReturn[$sKey])){ + // add value ... the part behind "=" + $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']); + + // remove " s" from exec time value + $aReturn['SCRIPTEXECTIME']=preg_replace('/\ s$/', '', $aReturn['SCRIPTEXECTIME']); + + return $aReturn; + + } + + /** + * 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_serverlog) 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; + } + rsort($aReturn); return $aReturn; } @@ -160,8 +240,11 @@ class cronlog { public function setServer($sServer){ $this->_sActiveServer=false; - if(!array_key_exists($sServer, $this->_aServers)){ - echo "WARNING: server does not exist<br>"; + if($sServer==='ALL'){ + return false; + } + if($sServer && !array_key_exists($sServer, $this->_aServers)){ + echo "WARNING: server [$sServer] does not exist<br>"; return false; } $this->_sActiveServer=$sServer; diff --git a/get.php b/get.php new file mode 100644 index 0000000000000000000000000000000000000000..4fc5e321e3e5589d1def5a58c971b743f4a1d196 --- /dev/null +++ b/get.php @@ -0,0 +1,47 @@ +<?php +/* ====================================================================== + * + * CRONJOB VIEWER :: AJAX HELPER + * + * ====================================================================== + */ + +require_once 'classes/cronlog-renderer.class.php'; + + +$sMode='html'; +$sItem=isset($_GET['item']) && $_GET['item'] ? $_GET['item'] : false; +$sServer=isset($_GET['server']) && $_GET['server'] ? $_GET['server'] : false; + + +$sHtml=''; +$oCL = new cronlogrenderer(); +if($sServer){ + $oCL->setServer($sServer); +} +switch ($sItem){ + case 'crontable': + $sHtml.=$oCL->renderJoblist(); + break; + case 'cronlogs': + if($sServer==='ALL'){ + $sHtml.=$oCL->renderCronlogsOfAllServers(); + } else { + $sHtml.=$oCL->renderCronlogs(); + } + break; + case 'graph': + $sHtml.=$oCL->renderJobGraph(); + break; + case 'selectserver': + $sHtml.=$oCL->renderServerlist($sServer); + break; + case 'showlog': + $sLogfile=isset($_GET['logfile']) && $_GET['logfile'] ? $_GET['logfile'] : false; + $sHtml.=$oCL->renderLogfile($sLogfile); + break; + default: + header('HTTP/1.0 400 Bad request'); + die('unknown item ['.$sItem.'] ... or it is not implemented yet.'); +} +echo $sHtml; diff --git a/index.php b/index.php index 551a83ed2ff2482e8fcf55d6d6633735337a726e..cf6b89c611b02a7dbc0fe1d6baa2e3ceced89356 100644 --- a/index.php +++ b/index.php @@ -1,39 +1,72 @@ <?php -# do spomething here ... -require_once 'classes/cronlog-renderer.class.php'; -$oCL = new cronlogrenderer(); - -$oCL->getServers(); -$oCL->setServer('kalium'); -$oCL->getServerLogs(); - -$sHtml='<h2>'.$oCL->getServer().'</h2>' - - . 'Tabelle der Cronjobs:<br>' - . $oCL->renderJoblist($oCL->getServerLogs()) - - . '<br>' - . 'Graph (Timeline):<br>' - . $oCL->renderJobGraph($oCL->getServerLogs()) - ; +// ----- supported query params +// server=[servername] ... default: ALL +// q=[filtervalue] ... default: nothing ?><!doctype html> <html><head> <title>Cronjob-Viewer</title> - <script type="text/javascript" src="vendor/vis/4.21.0/vis.min.js"></script> - <link href="vendor/vis/4.21.0/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css"/> - <link rel="stylesheet" type="text/css" href="main.css"> - <!-- - <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"> + <script type="text/javascript" src="js/functions.js"></script> + <script type="text/javascript" src="vendor/jquery/3.2.1/jquery.min.js"></script> + + <script type="text/javascript" src="vendor/vis/4.21.0/vis.min.js"></script> + <link href="vendor/vis/4.21.0/vis-timeline-graph2d.min.css" rel="stylesheet" type="text/css"/> + + <script type="text/javascript" src="vendor/datatables/1.10.15/js/jquery.dataTables.min.js"></script> + <link href="vendor/datatables/1.10.15/css/jquery.dataTables.min.css" rel="stylesheet" type="text/css"/> + + <link href="vendor/font-awesome/5.0.9/web-fonts-with-css/css/fontawesome-all.min.css" rel="stylesheet" type="text/css"/> <link rel="stylesheet" type="text/css" href="main.css"> - <script type="text/javascript" src="vendor/vis/4.21.0/vis.min.js"></script> - <link href="vendor/vis/4.21.0/vis-network.min.css" rel="stylesheet" type="text/css"/> - --> </head> <body> + <div id="errorlog"> + + </div> <h1><span class="imllogo"></span> CronjobViewer</h1> - <?php - echo $sHtml - ?> + <nav class="servers"> + <div id="selectserver" class="active">...</div> + </nav> + <div id="content"> + + <h2 id="lblServername">Server wählen...</h2> + + <nav class="tabs"> + <a href="#cronlogs" onclick="setTab(this); return false;" class="active"><i class="far fa-file-alt"></i> Logs</a> + <a href="#crontable" onclick="setTab(this); return false;"><i class="fas fa-history"></i> History</a> + <a href="#graph" onclick="setTab(this); return false;"><i class="far fa-chart-bar"></i> Timeline</a> + </nav><br><br><br> + + <div id="tabcontent"> + <div id="cronlogs" class="active"></div> + <div id="crontable"></div> + <div id="graph"></div> + </div> + + </div> + <script> + + // draw navigation with servers + getPageItem('selectserver'); + + // check query params and set filter + // ?server=neon&tab=crontable&q=123 + + var oQuery=getQueryParams(); + var sServer=oQuery.server ? oQuery.server : 'ALL'; + var q=oQuery.q ? oQuery.q : ''; + var sTab=oQuery.tab ? oQuery.tab : ''; + + setServer(sServer); + + if(sTab){ + var oLink=$('a[href="#'+sTab+'"]').first(); + console.log(oLink); + setTab(oLink); + } + if (q) { + window.setTimeout("$('#datatable1_filter label input').val(q); $('#datatable1').dataTable().fnFilter(q)" , 500); + window.setTimeout("$('#datatable2_filter label input').val(q); $('#datatable2').dataTable().fnFilter(q)" , 500); + } + </script> </body></html> \ No newline at end of file diff --git a/js/functions.js b/js/functions.js new file mode 100644 index 0000000000000000000000000000000000000000..83200e1823b73b4fe0f976b2a14a247dc5225114 --- /dev/null +++ b/js/functions.js @@ -0,0 +1,101 @@ + +var sSELECTEDSERVER=''; +var sACTIVESERVERTAB='cronlogs'; + + +/** + * get query parameters from url as object + * @returns {object} + */ +function getQueryParams() { + qs = document.location.search.split('+').join(' '); + + var params = {}, + tokens, + re = /[?&]?([^=]+)=([^&]*)/g; + + while (tokens = re.exec(qs)) { + params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]); + } + + return params; +} + +/** + * make an ajax-request and put response content into given div id + * @param {string} id id of the div to be filled + * @returns {undefined} + */ +function getPageItem(id, sData) { + // $('#'+id).html('reading ...'); + if(!$('#'+id).hasClass('active')){ + $('#errorlog').html('#'+id+' is not active') ; + return false; + } + var phpscript='get.php'; + $('#'+id).css('opacity', '0.2'); + + if(!sData){ + sData='item='+id+'&server='+sSELECTEDSERVER; + } + jQuery.ajax({ + url: phpscript, + data: sData, + type: "GET", + success:function(data){ + $('#'+id).css('opacity', '1'); + $('#'+id).html(data); + }, + error:function(){ + $('#'+id).css('opacity', false); + $('#'+id).html('Failed :-/'); + $('#errorlog').html( + $('#errorlog').html('AJAX error: <a href="'+phpscript+'?' + sData+'">'+phpscript+'?' + sData+'</a>') + ); + } + }); +} + +/** + * set server (show its navigation) + * tghis action is used after clicking a servername in the navigation + * + * @param {string} sServer + * @returns {undefined} + */ +function setServer(sServer){ + if(!sServer){ + return false; + } + sSELECTEDSERVER=sServer; + $('#lblServername').html('<i class="far fa-hdd"></i> '+sSELECTEDSERVER); + getPageItem(sACTIVESERVERTAB); +} + +/** + * onclick callback for tabs crontable, crontabs, graph + * @param {object} oLink clicked link + * @returns {undefined} + */ +function setTab(oLink){ + sTabid=$(oLink).attr('href').replace(/^#/, ''); + sACTIVESERVERTAB=sTabid; + + $('nav.tabs a').each(function(){ + $(this).removeClass('active'); + }); + $(oLink).addClass('active'); + $('#tabcontent div').each(function(){ + $(this).removeClass('active'); + }); + $('#'+sTabid).addClass('active'); + + setServer(sSELECTEDSERVER); +} + +function showFile(sLogfile){ + getPageItem(sACTIVESERVERTAB, 'item=showlog&logfile='+sLogfile); +} +function showFileBack(){ + getPageItem(sACTIVESERVERTAB); +} \ No newline at end of file diff --git a/js/functions.min.js b/js/functions.min.js new file mode 100644 index 0000000000000000000000000000000000000000..12bcd192b4abdb1dc6009a4195b1d5ce8d348b2d --- /dev/null +++ b/js/functions.min.js @@ -0,0 +1,2 @@ +/* https://www.axel-hahn.de/ */ +var sMyServer=false;var oMapHtml2Api={crontable:"crontable",graph:"graph",selectServer:"selectserver"};function getPageItem(c){var b="get.php";$("#"+c).css("opacity","0.2");var a="item="+oMapHtml2Api[c]+"&server="+sMyServer;jQuery.ajax({url:b,data:a,type:"GET",success:function(d){$("#"+c).css("opacity","1");$("#"+c).html(d)},error:function(){$("#"+c).css("opacity",false);$("#"+c).html("Failed :-/");$("#errorlog").html($("#errorlog").html()+'AJAX error: <a href="'+b+"?"+a+'">'+b+"?"+a+"</a><br><br>")}})}function setServer(a){sMyServer=a;$("#lblServername").html(sMyServer);getPageItem("crontable")}; \ No newline at end of file diff --git a/main.css b/main.css index 4ef9eb4832ed6eb4e07f747f674d9aa2ea1ef1d4..b0c9e58d679b11226de3aaf781c1bbdba8f43873 100644 --- a/main.css +++ b/main.css @@ -22,15 +22,42 @@ footer a{color:#678;} i.fa{font-size: 150%; } i.fa.lookup{font-size: 100%; opacity: 0.4;} +nav.servers{float: left; margin-right: 1em;} + +nav.tabs{} +nav.tabs a{display: block; float: left; background: rgba(0,60,60,0.05); padding: 0.5em 1em; color:#345; text-decoration: none; margin-right: 2px; border-top: 4px solid rgba(0,0,0,0.01);} +nav.tabs a.active{background: #cdd; border-color: rgb(255,0,51);; } + +option{font-family: verdana,arial; font-size:1.0em; } +select{border: 0;} + table{border: 1px solid #ccc;} +tr:hover{background:#eee;} +th{background:#cdd; padding:0.5em;} +td{padding:0.3em;} .imllogo:before {background: rgb(255,0,51);color: #fff;padding: 0.5em 0.3em;content: 'IML'; font-family: "arial"; text-shadow: none;} +/* ----- tabbed content */ +#crontable,#cronlogs,#graph{display: none;} +#tabcontent div.active{display: block;} + + +/* ----- override datatable defaukts */ +.dataTables_wrapper{clear: none;float: left; margin: auto 1px;} +table.dataTable tbody tr{background: none;} +table.dataTable{margin: 0; width: auto;} + + .message{border: 1px solid rgba(0,0,0,0.1); padding: 1em; float: right;} -.message-ok{background:#cfc; color:#080;} -.message-error{background:#fee; color:#800;} +.message-ok{background:#cfc !important; color:#080 !important;} +.message-error{background:#fee !important; color:#800 !important;} -/* +.log-rem{color:#aaa; font-style: italic;} +.log-var{color:#088;} +.log-value{color:#008;} + +/* timeline */ .vis-item.timeline-result-error{background:#fcc; border-color: #800} .vis-item.timeline-result-ok{background:#cfc;border-color: #080} diff --git a/main.min.css b/main.min.css new file mode 100644 index 0000000000000000000000000000000000000000..c5bbbd33b46d9397e7aab0f5e15d75154066978e --- /dev/null +++ b/main.min.css @@ -0,0 +1,2 @@ +/* https://www.axel-hahn.de/ */ +a{color:#38a;text-decoration:none}a:hover{text-decoration:underline}body{background:#fff;color:#456;font-family:verdana,arial;font-size:1.0em}button{background:#468;border:0;color:#fff;padding:.5em 1em;border-radius:.3em;border:1px solid rgba(0,0,0,0.1)}button.add{background:#8c8}button.del{background:#c88}button:hover{background:#cde;color:#000}footer{background:rgba(0,60,60,0.05);border-top:1px solid #ccc;padding:1em;margin-top:5em}footer a{color:#678}i.fa{font-size:150%}i.fa.lookup{font-size:100%;opacity:.4}nav{margin-right:1em}option{font-family:verdana,arial;font-size:1.0em}table{border:1px solid #ccc}tr:hover{background:#eee}th{background:#abc;padding:.5em}td{padding:.3em}.imllogo:before{background:#f03;color:#fff;padding:.5em .3em;content:'IML';font-family:"arial";text-shadow:none}.message{border:1px solid rgba(0,0,0,0.1);padding:1em;float:right}.message-ok{background:#cfc;color:#080}.message-error{background:#fee;color:#800}.vis-item.timeline-result-error{background:#fcc;border-color:#800}.vis-item.timeline-result-ok{background:#cfc;border-color:#080} \ No newline at end of file