From c698f2f24328937da474e0eb0c1e0be5b0558622 Mon Sep 17 00:00:00 2001 From: hahn <axel.hahn@iml.unibe.ch> Date: Wed, 29 May 2019 11:20:36 +0200 Subject: [PATCH] Update components font-awesome (change icon syntax from fontawesome 4 --> 5), jquery action button (build, deploy, ...) have the color of the target phase Update appmonitor class files --- .../classes/appmonitor-checks.class.php | 414 +++++++++++++++--- .../classes/appmonitor-client.class.php | 180 +++++++- 2 files changed, 517 insertions(+), 77 deletions(-) diff --git a/public_html/appmonitor/classes/appmonitor-checks.class.php b/public_html/appmonitor/classes/appmonitor-checks.class.php index 82df3271..66d2375e 100644 --- a/public_html/appmonitor/classes/appmonitor-checks.class.php +++ b/public_html/appmonitor/classes/appmonitor-checks.class.php @@ -1,12 +1,28 @@ <?php -define("RESULT_OK", 0); -define("RESULT_UNKNOWN", 1); -define("RESULT_WARNING", 2); -define("RESULT_ERROR", 3); +if(!defined('RESULT_OK')){ + define("RESULT_OK", 0); + define("RESULT_UNKNOWN", 1); + define("RESULT_WARNING", 2); + define("RESULT_ERROR", 3); +} /** - * APPMONITOR CLIENT CHECKS<br> + * ____________________________________________________________________________ + * + * _____ _____ __ _____ _ _ + * | | | | ___ ___ ___| |___ ___|_| |_ ___ ___ + * |- -| | | | |__ | .'| . | . | | | | . | | | _| . | _| + * |_____|_|_|_|_____| |__,| _| _|_|_|_|___|_|_|_|_| |___|_| + * |_| |_| + * _ _ _ + * ___| |_|___ ___| |_ + * | _| | | -_| | _| + * |___|_|_|___|_|_|_| + * + * ____________________________________________________________________________ + * + * APPMONITOR :: CLASS FOR CLIENT TEST FUNCTIONS<br> * <br> * THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE <br> * LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR <br> @@ -24,8 +40,14 @@ define("RESULT_ERROR", 3); * 2015-04-08 0.9 axel.hahn@iml.unibe.ch added sochket test: checkPortTcp<br> * 2018-06-29 0.24 axel.hahn@iml.unibe.ch add file and directory checks<br> * 2018-07-17 0.42 axel.hahn@iml.unibe.ch add port on mysqli check<br> + * 2018-07-26 0.46 axel.hahn@iml.unibe.ch fix mysql connection check with empty port param<br> + * 2018-08-14 0.47 axel.hahn@iml.unibe.ch appmonitor client: use timeout of 5 sec for tcp socket connections<br> + * 2018-08-15 0.49 axel.hahn@iml.unibe.ch cert check: added flag to skip verification<br> + * 2018-08-23 0.50 axel.hahn@iml.unibe.ch replace mysqli connect with mysqli real connect (to use a timeout)<br> + * 2018-08-27 0.52 axel.hahn@iml.unibe.ch add pdo connect (starting with mysql)<br> + * 2018-11-05 0.58 axel.hahn@iml.unibe.ch additional flag in http check to show content<br> * --------------------------------------------------------------------------------<br> - * @version 0.9 + * @version 0.77 * @author Axel Hahn * @link TODO * @license GPL @@ -36,22 +58,31 @@ class appmonitorcheck { // ---------------------------------------------------------------------- // CONFIG // ---------------------------------------------------------------------- - + /** * config container * @var array */ - private $_aConfig = array(); + protected $_aConfig = array(); /** * data of all checks * @var array */ - private $_aData = array(); + protected $_aData = array(); - - // protected $_units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'); + + /** + * flat array with units for sizes + * @var array + */ protected $_units = array( 'B', 'KB', 'MB', 'GB', 'TB'); + + /** + * timeout in sec for tcp socket connections + * @var type + */ + protected $_iTimeoutTcp=5; // ---------------------------------------------------------------------- // CONSTRUCTOR @@ -72,13 +103,15 @@ class appmonitorcheck { * create basic array values for metadata * @return boolean */ - private function _createDefaultMetadata() { + protected function _createDefaultMetadata() { $this->_aData = array( "name" => $this->_aConfig["name"], "description" => $this->_aConfig["description"], "result" => RESULT_UNKNOWN, "value" => false, + "type" => false, + "time" => false, ); return true; } @@ -88,7 +121,7 @@ class appmonitorcheck { * @param type $iResult * @return type */ - private function _setResult($iResult) { + protected function _setResult($iResult) { return $this->_aData["result"] = (int) $iResult; } @@ -97,7 +130,7 @@ class appmonitorcheck { * @param type $iResult * @return type */ - private function _setOutput($s) { + protected function _setOutput($s) { return $this->_aData["value"] = (string) $s; } @@ -107,13 +140,13 @@ class appmonitorcheck { * @param type $s * @return boolean */ - private function _setReturn($iResult, $s) { + protected function _setReturn($iResult, $s) { $this->_setResult($iResult); $this->_setOutput($s); return true; } - private function _checkArrayKeys($aConfig, $sKeyList) { + protected function _checkArrayKeys($aConfig, $sKeyList) { foreach (explode(",", $sKeyList) as $sKey) { if (!array_key_exists($sKey, $aConfig)) { header('HTTP/1.0 503 Service Unavailable'); @@ -203,17 +236,22 @@ class appmonitorcheck { /** * helper function: read certificate data * called in checkCert() - * @param string $sUrl url to connect + * @param string $sUrl url to connect + * @param boolean $bVerifyCert flag: verify certificate; default: no check * @return array */ - protected function _certGetInfos($sUrl) { + protected function _certGetInfos($sUrl, $bVerifyCert) { $iTimeout=10; $aUrldata=parse_url($sUrl); $sHost = isset($aUrldata['host']) ? $aUrldata['host'] : false; $iPort = isset($aUrldata['port']) ? $aUrldata['port'] : ((isset($aUrldata['scheme']) && $aUrldata['scheme'] === 'https') ? 443 : false); - - $get = stream_context_create(array("ssl" => array("capture_peer_cert" => TRUE))); + $aSsl=array('capture_peer_cert' => true); + if($bVerifyCert){ + $aSsl['verify_peer']=false; + $aSsl['verify_peer_name']=false; + }; + $get = stream_context_create(array('ssl' => $aSsl)); if(!$get){ return array('_error' => 'Error: Cannot create stream_context'); } @@ -221,7 +259,7 @@ class appmonitorcheck { $errstr="stream_socket_client failed."; $read = stream_socket_client("ssl://$sHost:$iPort", $errno, $errstr, $iTimeout, STREAM_CLIENT_CONNECT, $get); if(!$read){ - return array('_error' => "Error $errno: $errstr; cannot create stream_context to ssl://$sHost:$iPort"); + return array('_error' => "Error $errno: $errstr; cannot create stream_socket_client with given stream_context to ssl://$sHost:$iPort; you can try to set the flag [verify] to false to check expiration date only."); } $cert = stream_context_get_params($read); if(!$cert){ @@ -238,26 +276,28 @@ class appmonitorcheck { * @param array $aParams * array( * "url" optional: url to connect check; default: own protocol + server + * "verify" optional: flag for verification of certificate or check for expiration only; default=true (=verification is on) * "warning" optional: count of days to warn; default=30 * ) * @return boolean */ - private function checkCert($aParams) { + protected function checkCert($aParams) { $sUrl = isset($aParams["url"]) ? $aParams["url"] : 'http'. ($_SERVER['HTTPS'] ? 's' : '') . '://' . $_SERVER['SERVER_NAME'] .':' . $_SERVER['SERVER_PORT'] ; - $iWarn = isset($aParams["warning"]) ? (int)($aParams["warning"]) : 30; + $bVerify = isset($aParams["verify"]) ? !!$aParams["verify"] : true; + $iWarn = isset($aParams["warning"]) ? (int)($aParams["warning"]) : 30; $sMessage="Checked url: $sUrl ... "; - $certinfo=$this->_certGetInfos($sUrl); + $certinfo=$this->_certGetInfos($sUrl, $bVerify); if(isset($certinfo['_error'])){ $this->_setReturn(RESULT_ERROR, $certinfo['_error'] . $sMessage); return true; } $sDNS=isset($certinfo['extensions']['subjectAltName']) ? $certinfo['extensions']['subjectAltName'] : false; - $sHost=parse_url($url,PHP_URL_HOST); + $sHost=parse_url($sUrl,PHP_URL_HOST); if(strstr($sDNS, 'DNS:'.$sHost)===false){ $this->_setReturn(RESULT_ERROR, 'Wrong certificate: '.$sHost.' is not listed as DNS alias in ['.$sDNS.'] ' . $sMessage); return true; @@ -278,7 +318,9 @@ class appmonitorcheck { return true; } // echo '<pre>'; - $this->_setReturn(RESULT_OK, 'OK, is valid. ' . $sMessage); + $this->_setReturn(RESULT_OK, 'OK. ' + .($bVerify ? 'Certificate is valid. ' : '(Verification is disabled; Check for expiration only.) ' ) + . $sMessage); return true; } @@ -302,7 +344,7 @@ class appmonitorcheck { } $power=0; foreach($this->_units as $sUnit){ - if (preg_match('/^[0-9\.]*'.$sUnit.'/', $sValue)){ + if (preg_match('/^[0-9\.\ ]*'.$sUnit.'/', $sValue)){ $i=preg_replace('/([0-9\.]*).*/', '$1', $sValue); $iReal=$i*pow(1024, $power); // die("FOUND: $sValue with unit ${sUnit} - 1024^$power * $i = $iReal"); @@ -325,7 +367,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkDiskfree($aParams) { + protected function checkDiskfree($aParams) { $this->_checkArrayKeys($aParams, "directory", "critical"); $sDirectory = $aParams["directory"]; @@ -378,7 +420,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkFile($aParams) { + protected function checkFile($aParams) { $aOK = array(); $aErrors = array(); $this->_checkArrayKeys($aParams, "filename"); @@ -413,42 +455,235 @@ class appmonitorcheck { return true; } + + /** + * compare function for 2 values + * @param any $verifyValue search value + * @param string $sCompare compare function; it is one of + * IS - + * GE - greater or equal + * GT - greater + * @param any $value existing value + * @return boolean + */ + protected function _compare($value, $sCompare, $verifyValue){ + switch ($sCompare){ + case "IS": + return $value===$verifyValue; + break; + case "GE": + return $value>=$verifyValue; + break; + case "GT": + return $value>$verifyValue; + break; + case "HAS": + return !!(strstr($value, $verifyValue)!==false); + break; + default: + die("FATAL ERROR: a compare function [$sCompare] is not implemented (yet)."); + break; + } + return false; + } + + /** * make http request and test response body * @param array $aParams * array( - * "url" url to fetch - * "contains" string that must exist in response body + * url string url to fetch + * headeronly boolean optional flag to fetch http response herader only; default: false = returns header and body + * follow boolean optional flag to follow a location; default: false = do not follow + * status integer test for an expected http status code; if none is given then test fails on status 400 and greater + * headercontains string test for a string in the http response header; it returns OK if the text was found + * headernotcontains string test for a string in the http response header; it returns OK if the text was not found + * headerregex string test for a regex in the http response header; it returns OK if the regex matches; example: "headerregex"=>"/lowercasematch/i" + * bodycontains string test for a string in the http response body; it returns OK if the text was found + * bodynotcontains string test for a string in the http response body; it returns OK if the text was not found + * bodyregex string test for a regex in the http response body; it returns OK if the regex matches; example: "headerregex"=>"/lowercasematch/i" * ) * @param integer $iTimeout value in sec; default: 5sec */ - private function checkHttpContent($aParams, $iTimeout = 5) { - $this->_checkArrayKeys($aParams, "url,contains"); + protected function checkHttpContent($aParams, $iTimeout = 5) { + $this->_checkArrayKeys($aParams, "url"); if (!function_exists("curl_init")) { header('HTTP/1.0 503 Service Unavailable'); die("ERROR: PHP CURL module is not installed."); } + $bShowContent = (isset($aParams["content"]) && $aParams["content"]) ? true : false; $ch = curl_init($aParams["url"]); + + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_NOBODY, isset($aParams["headeronly"]) && $aParams["headeronly"]); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, isset($aParams["follow"]) && $aParams["follow"]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_TIMEOUT, $iTimeout); $res = curl_exec($ch); - curl_close($ch); if (!$res) { $this->_setReturn(RESULT_ERROR, 'ERROR: failed to fetch ' . $aParams["url"] . '.'); + return false; + } + $sOut=''; + $bError=false; + + $aInfos = curl_getinfo($ch); + /* + Array + ( + [url] => https://www.iml.unibe.ch/ + [content_type] => text/html; charset=utf-8 + [http_code] => 200 + [header_size] => 926 + [request_size] => 55 + [filetime] => -1 + [ssl_verify_result] => 20 + [redirect_count] => 0 + [total_time] => 1.812 + [namelookup_time] => 0 + [connect_time] => 0 + [pretransfer_time] => 0.015 + [size_upload] => 0 + [size_download] => 94654 + [speed_download] => 52237 + [speed_upload] => 0 + [download_content_length] => -1 + [upload_content_length] => -1 + [starttransfer_time] => 1.812 + [redirect_time] => 0 + [redirect_url] => + [primary_ip] => 130.92.30.80 + [certinfo] => Array + ( + ) + + [primary_port] => 443 + [local_ip] => 130.92.79.49 + [local_port] => 63597 + ) + */ + + curl_close($ch); + + $aTmp=explode("\r\n\r\n", $res, 2); + $sHttpHeader=$aTmp[0]; + $sHttpBody=isset($aTmp[1]) ? $aTmp[1] : false; + + // ---------- check functions + + // --- http status code + $sOut.="Http status: ".$aInfos['http_code']." - "; + if(isset($aParams["status"])){ + if($aInfos['http_code'] === $aParams["status"]){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } } else { - if (!strpos($res, $aParams["contains"]) === false) { - $this->_setReturn(RESULT_OK, 'OK: The text "' . $aParams["contains"] . '" was found in response of ' . $aParams["url"] . '.'); + if($aInfos['http_code'] >= 400){ + $sOut.="Error page detected<br>"; + $bError=true; } else { - $this->_setReturn(RESULT_ERROR, 'ERROR: The text ' . $aParams["contains"] . ' was NOT found in response of ' . $aParams["url"] . '.'); + $sOut.="request successful<br>"; + } + } + // --- http header + if(isset($aParams["headercontains"]) && $aParams["headercontains"]){ + $sOut.="Http header contains "".$aParams["headercontains"]."" - "; + if(!strstr($sHttpHeader, $aParams["headercontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["headernotcontains"]) && $aParams["headernotcontains"]){ + $sOut.="Http header does not contain "".$aParams["headernotcontains"]."" - "; + if(strstr($sHttpHeader, $aParams["headernotcontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["headerregex"]) && $aParams["headerregex"]){ + $sOut.="Http header regex test "".$aParams["headerregex"]."" - "; + try{ + $bRegex=preg_match($aParams["headerregex"], $sHttpHeader); + if($bRegex){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + catch(Exception $e){ + $sOut.="Wrong REGEX<br>" . print_r($e, 1).'<br>'; + $bError=true; } } - return $res; + // --- http body + if(isset($aParams["bodycontains"]) && $aParams["bodycontains"]){ + $sOut.="Http body contains "".$aParams["bodycontains"]."" - "; + if(!strstr($sHttpBody, $aParams["bodycontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["bodynotcontains"]) && $aParams["bodynotcontains"]){ + $sOut.="Http body does not contain "".$aParams["bodynotcontains"]."" - "; + if(strstr($sHttpBody, $aParams["bodynotcontains"])===false){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + if(isset($aParams["bodyregex"]) && $aParams["bodyregex"]){ + $sOut.="Http body regex test "".$aParams["bodyregex"]."" - "; + try{ + $bRegex=preg_match($aParams["bodyregex"], $sHttpBody); + if($bRegex){ + $sOut.="compare OK<br>"; + } else { + $sOut.="compare failed<br>"; + $bError=true; + } + } + catch(Exception $e){ + $sOut.="Wrong REGEX<br>" . print_r($e, 1).'<br>'; + $bError=true; + } + } + + if (!$bError) { + $this->_setReturn(RESULT_OK, + 'OK: http check "' . $aParams["url"] . '".<br>'.$sOut + ); + } else { + $this->_setReturn(RESULT_ERROR, + 'ERROR: http check "' . $aParams["url"] . '".<br>'.$sOut + ); + } + + /* + echo '<pre>'; + echo $sOut."<hr>"; + echo "<hr>HEADER: ".htmlentities($sHttpHeader)."<hr>"; + print_r($aParams); print_r($aInfos); + // echo htmlentities($sHttpBody); + die(); + */ + return true; } /** - * check mysql connection to a database using mysqli + * check mysql connection to a database using mysqli realconnect * @param array $aParams * array( * "server" @@ -458,18 +693,65 @@ class appmonitorcheck { * "port" <<< optional * ) */ - private function checkMysqlConnect($aParams) { + protected function checkMysqlConnect($aParams) { $this->_checkArrayKeys($aParams, "server,user,password,db"); + $mysqli=mysqli_init(); + if(!$mysqli){ + $this->_setReturn(RESULT_ERROR, 'ERROR: mysqli_init failed.'); + return false; + } + if (!$mysqli->options(MYSQLI_OPT_CONNECT_TIMEOUT, $this->_iTimeoutTcp)) { + $this->_setReturn(RESULT_ERROR, 'ERROR: setting mysqli_init failed.'); + return false; + } + $db = (isset($aParams["port"]) && $aParams["port"]) - ? mysqli_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"], $aParams["port"]) - : mysqli_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"]) + ? $mysqli->real_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"], $aParams["port"]) + : $mysqli->real_connect($aParams["server"], $aParams["user"], $aParams["password"], $aParams["db"]) ; if ($db) { $this->_setReturn(RESULT_OK, "OK: Mysql database " . $aParams["db"] . " was connected"); - mysqli_close($db); + $mysqli->close(); return true; } else { - $this->_setReturn(RESULT_ERROR, "ERROR: Mysql database " . $aParams["db"] . " was not connected. " . mysqli_connect_error()); + $this->_setReturn(RESULT_ERROR, "ERROR: Mysql database " . $aParams["db"] . " was not connected. Error ".mysqli_connect_errno() .": ". mysqli_connect_error()); + return false; + } + } + /** + * check connection to a database using pdo + * see http://php.net/manual/en/pdo.drivers.php + * + * @param array $aParams + * array( + * "connect" + * "password" + * "user" + * ) + */ + protected function checkPdoConnect($aParams) { + $this->_checkArrayKeys($aParams, "connect,user,password"); + + try{ + $db = new PDO( + $aParams['connect'], + $aParams['user'], + $aParams['password'], + array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + + // timeout + // Not all drivers support this option; mysqli does + PDO::ATTR_TIMEOUT => $this->_iTimeoutTcp, + // mssql + // PDO::SQLSRV_ATTR_QUERY_TIMEOUT => $this->_iTimeoutTcp, + ) + ); + $this->_setReturn(RESULT_OK, "OK: Database was connected with PDO " . $aParams['connect']); + $db=null; + return true; + } catch(PDOException $e) { + $this->_setReturn(RESULT_ERROR, "ERROR: Database was not connected " . $aParams['connect'] . " was not connected. Error ".$e->getMessage()); return false; } } @@ -483,7 +765,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkPortTcp($aParams) { + protected function checkPortTcp($aParams) { $this->_checkArrayKeys($aParams, "port"); $sHost = array_key_exists('host', $aParams) ? $aParams['host'] : '127.0.0.1'; @@ -496,6 +778,16 @@ class appmonitorcheck { $this->_setReturn(RESULT_UNKNOWN, "ERROR: $sHost:$iPort was not checked. socket_create() failed: " . socket_strerror(socket_last_error())); return false; } + // set socket timeout + socket_set_option( + $socket, + SOL_SOCKET, // socket level + SO_SNDTIMEO, // timeout option + array( + "sec"=>$this->_iTimeoutTcp, // timeout in seconds + "usec"=>0 + ) + ); $result = socket_connect($socket, $sHost, $iPort); if ($result === false) { @@ -510,16 +802,32 @@ class appmonitorcheck { } /** - * most simple check: set values + * most simple check: set given values + * Use this function to add a counter + * * @param array $aParams - * array( - * "result" integer; RESUL_nn - * "value" description text - * ) + * array keys: + * "result" integer; RESULT_nn + * "value" description text + * + * brainstorming for a future release + * + * "counter" optioal: array of counter values + * - "label" string: a label + * - "value" a number + * - "unit" string: unit, i.e. ms, MB/s + * - "type" one of counter | bars | bars-stacked | lines | ... ?? + * */ - private function checkSimple($aParams) { + protected function checkSimple($aParams) { $this->_checkArrayKeys($aParams, "result,value"); - return $this->_setReturn((int) $aParams["result"], $aParams["value"]); + $this->_setReturn((int) $aParams["result"], $aParams["value"]); + foreach(array('type', 'count', 'visual') as $sMyKey){ + if(isset($aParams[$sMyKey])){ + $this->_aData[$sMyKey]=$aParams[$sMyKey]; + } + } + return true; } /** @@ -530,7 +838,7 @@ class appmonitorcheck { * ) * @return boolean */ - private function checkSqliteConnect($aParams) { + protected function checkSqliteConnect($aParams) { $this->_checkArrayKeys($aParams, "db"); if (!file_exists($aParams["db"])) { $this->_setReturn(RESULT_ERROR, "ERROR: Sqlite database file " . $aParams["db"] . " does not exist."); diff --git a/public_html/appmonitor/classes/appmonitor-client.class.php b/public_html/appmonitor/classes/appmonitor-client.class.php index 235b2217..a73a52a3 100644 --- a/public_html/appmonitor/classes/appmonitor-client.class.php +++ b/public_html/appmonitor/classes/appmonitor-client.class.php @@ -1,7 +1,21 @@ <?php /** - * APPMONITOR CLIENT<br> + * ____________________________________________________________________________ + * + * _____ _____ __ _____ _ _ + * | | | | ___ ___ ___| |___ ___|_| |_ ___ ___ + * |- -| | | | |__ | .'| . | . | | | | . | | | _| . | _| + * |_____|_|_|_|_____| |__,| _| _|_|_|_|___|_|_|_|_| |___|_| + * |_| |_| + * _ _ _ + * ___| |_|___ ___| |_ + * | _| | | -_| | _| + * |___|_|_|___|_|_|_| + * + * ____________________________________________________________________________ + * + * APPMONITOR :: CLASS FOR CLIENT CHECKS<br> * <br> * THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE <br> * LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR <br> @@ -15,10 +29,14 @@ * --------------------------------------------------------------------------------<br> * <br> * --- HISTORY:<br> - * 2014-10-24 0.5 axel.hahn@iml.unibe.ch<br> - * 2014-11-21 0.6 axel.hahn@iml.unibe.ch removed meta::ts <br> + * 2014-10-24 0.5 axel.hahn@iml.unibe.ch<br> + * 2014-11-21 0.6 axel.hahn@iml.unibe.ch removed meta::ts <br> + * 2018-08-23 0.50 axel.hahn@iml.unibe.ch show version<br> + * 2018-08-24 0.51 axel.hahn@iml.unibe.ch method to show local status page<br> + * 2018-08-27 0.52 axel.hahn@iml.unibe.ch add pdo connect (starting with mysql)<br> + * 2018-11-05 0.58 axel.hahn@iml.unibe.ch additional flag in http check to show content<br> * --------------------------------------------------------------------------------<br> - * @version 0.6 + * @version 0.85 * @author Axel Hahn * @link TODO * @license GPL @@ -32,29 +50,41 @@ class appmonitor { * value is in seconds * @var int */ - private $_iDefaultTtl = 300; + protected $_sVersion = 'php-client-v0.85'; + + /** + * config: default ttl for server before requesting the client check again + * value is in seconds + * @var int + */ + protected $_iDefaultTtl = 300; /** * internal counter: greatest return value of all checks * @var type */ - private $_iMaxResult = false; + protected $_iMaxResult = false; /** * responded metadata of a website * @see _createDefaultMetadata() * @var array */ - private $_aMeta = array(); + protected $_aMeta = array(); /** * repended array of all checks * @see addCheck() * @var array */ - private $_aChecks = array(); + protected $_aChecks = array(); + + /** + * for time measurements: start time + * @var type + */ protected $_iStart = false; - + /** * constructor: init data */ @@ -63,21 +93,22 @@ class appmonitor { } // ---------------------------------------------------------------------- - // private function + // protected function // ---------------------------------------------------------------------- /** * create basic array values for metadata * @return boolean */ - private function _createDefaultMetadata() { + protected function _createDefaultMetadata() { $this->_iStart = microtime(true); $this->_aMeta = array( "host" => false, "website" => false, "ttl" => false, "result" => false, - "time" => false + "time" => false, + "version" => $this->_sVersion, ); // fill with default values @@ -92,7 +123,9 @@ class appmonitor { // ---------------------------------------------------------------------- /** - * set a host for metadata + * set the physical hostname for metadata; if no host is given then + * the php_uname("n") will be used to set one. + * * @param string $s hostname * @return bool */ @@ -104,7 +137,14 @@ class appmonitor { } /** - * set a vhost for metadata + * set a name for this website or application and its environment + * (dev, test, prod); + * + * If you have several application in subdirectories, i.e. /blog, /shop... + * then you should the path or any description to identify them too + * + * if no argument is given the name of HTTP_HOST will be used + * * @param string $sNewHost hostname * @return bool */ @@ -116,7 +156,9 @@ class appmonitor { } /** - * set a ttl value for + * set a ttl value in seconds to define how long a server should not + * ask again for a new status of this instance + * * @param int $iTTl TTL value in sec * @return boolean */ @@ -151,9 +193,16 @@ class appmonitor { $oCheck = new appmonitorcheck(); // print_r($aJob); die(); $aCheck = $oCheck->makecheck($aJob); - - if (!$this->_iMaxResult || $aCheck["result"] > $this->_iMaxResult) { - $this->_iMaxResult = $aCheck["result"]; + + // limit error code + // echo "DEBUG ".$aCheck["name"].": ".$aCheck["result"]."\n"; + $iMyResult=isset($aJob['worstresult']) + ? min($aCheck["result"], $aJob['worstresult']) + : $aCheck["result"] + ; + + if (!$this->_iMaxResult || $iMyResult > $this->_iMaxResult) { + $this->_iMaxResult = $iMyResult; } return $this->_aChecks[] = $aCheck; } @@ -282,7 +331,7 @@ class appmonitor { /** * verify array values and abort with all found errors */ - private function _checkData() { + protected function _checkData() { $aErrors = array(); if (!count($this->_aChecks)) { @@ -293,19 +342,28 @@ class appmonitor { $aErrors[] = "method setResult was not used to set a final result for all checks."; } - if (count($aErrors)) { - header('HTTP/1.0 503 Service Unavailable'); - echo "<h1>Errors detected</h1><ol><li>" . implode("<li>", $aErrors) . "</ol><hr>"; - echo "<pre>" . print_r($this->getResults(), true) . "</pre><hr>"; - die("ABORT"); + $this->abort( + '<h2>Error: client check is not complete</h2><p>Found errors:</p><ol><li>' . implode('<li>', $aErrors) . '</ol><br><br>' + // .'Dump of your data so far:<pre>' . json_encode($this->getResults(), JSON_PRETTY_PRINT) . '</pre><hr>' + ); } + return true; } // ---------------------------------------------------------------------- // output // ---------------------------------------------------------------------- + /** + * stop processing the client checks and abort with an error + * @param string $sMessage + */ + public function abort($sMessage){ + header('HTTP/1.0 503 Service Unavailable'); + die('<h1>503 Service Unavailable</h1>'.$sMessage); + } + /** * get full array for response with metadata and Checks * @return type @@ -384,5 +442,79 @@ class appmonitor { echo $sOut; return $sOut; } + /** + * output appmonitor client status as single html page + * + * @example <code> + * ob_start();<br> + * require __DIR__ . '/appmonitor/client/index.php'; + * $sJson=ob_get_contents(); + * ob_end_clean(); + * $oMonitor->renderHtmloutput($sJson); + * </code> + * + * @param string $sJson JSON of client output + */ + public function renderHtmloutput($sJson) { + + + header('Content-type: text/html'); + header('Cache-Control: cache'); + header('max-age: ' . $this->_aMeta["ttl"]); + $aMsg = array( + 0 => "OK", + 1 => "UNKNOWN", + 2 => "WARNING", + 3 => "ERROR" + ); + + // $sOut = print_r($sJson, 1); + $aData= json_decode($sJson, 1); + + // ----- Ausgabe human readable + $sOut=''; + $sOut.='<h2>Metadata</h2>' + . '<div class="meta'.(isset($aData['meta']['result']) ? ' result'.$aData['meta']['result'] : '' ) .'">' + . 'Status: ' . (isset($aData['meta']['result']) ? $aMsg[$aData['meta']['result']] : '?').'<br>' + . '</div>' + . 'Host: ' . (isset($aData['meta']['host']) ? '<span class="string">' . $aData['meta']['host'] .'</span>' : '?').'<br>' + . 'Website: ' . (isset($aData['meta']['website']) ? '<span class="string">' . $aData['meta']['website'].'</span>' : '?').'<br>' + // . 'Status: ' . (isset($aData['meta']['result']) ? '<span class="result'.$aData['meta']['result'].'">'. $aMsg[$aData['meta']['result']].'</span>' : '?').'<br>' + . 'Execution time: ' . (isset($aData['meta']['time']) ? '<span class="float">' . $aData['meta']['time'] .'</span>' : '?').'<br>' + + .'<h2>Checks</h2>' + ; + if (isset($aData['checks'][0]) && count($aData['checks'])){ + foreach($aData['checks'] as $aCheck){ + $sOut.= '<span class="result'.$aCheck['result'].'"> <strong>'.$aCheck['name'].'</strong></span> <br>' + . $aCheck['description'].'<br>' + // . '<span class="result'.$aCheck['result'].'">'.$aCheck['value'].'</span><br>' + . $aCheck['value'].'<br>' + . 'Execution time: ' . $aCheck['time'].'<br>' + . 'Status: ' . $aMsg[$aCheck['result']].'<br>' + . '<br>' + ; + } + } + $sOut.= '<hr>List of farbcodes: '; + foreach ($aMsg as $i=>$sText){ + $sOut.= '<span class="result'.$i.'">'. $sText.'</span> '; + } + $sOut = '<!DOCTYPE html><html><head>' + . '<style>' + . 'body{background:#fff; color:#444; font-family: verdana,arial; margin: 3em;}' + . '.result0{background:#aca; border-left: 1em solid #080; padding: 0 0.5em; }' + . '.result1{background:#ccc; border-left: 1em solid #aaa; padding: 0 0.5em; }' + . '.result2{background:#fc9; border-left: 1em solid #860; padding: 0 0.5em; }' + . '.result3{background:#f88; border-left: 1em solid #f00; padding: 0 0.5em; }' + . '</style>' + . '<title>' . __CLASS__ . '</title>' + . '</head><body>' + . '<h1>' . __CLASS__ . ' :: client status</h1>' + . $sOut + . '</body></html>'; + echo $sOut; + return $sOut; + } } -- GitLab