Skip to content
Snippets Groups Projects

OP#7365 PHP-8.1 - Upgrade - cronlog Viewer (monitors.ascii)https://projects.iml.unibe.ch/work_packages/7365

Merged Hahn Axel (hahn) requested to merge freshup-php into master
4 files
+ 47
7
Compare changes
  • Side-by-side
  • Inline
Files
4
@@ -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
Loading