diff --git a/public_html/appmonitor/classes/appmonitor-checks.class.php b/public_html/appmonitor/classes/appmonitor-checks.class.php
index 82df3271a5e0d3172771b368b20a3050caa2341a..a31e07f8c81b3c7b4880e50dfce8228c6bb550a6 100644
--- a/public_html/appmonitor/classes/appmonitor-checks.class.php
+++ b/public_html/appmonitor/classes/appmonitor-checks.class.php
@@ -24,8 +24,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 +42,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 +87,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 +105,7 @@ class appmonitorcheck {
      * @param type $iResult
      * @return type
      */
-    private function _setResult($iResult) {
+    protected function _setResult($iResult) {
         return $this->_aData["result"] = (int) $iResult;
     }
 
@@ -97,7 +114,7 @@ class appmonitorcheck {
      * @param type $iResult
      * @return type
      */
-    private function _setOutput($s) {
+    protected function _setOutput($s) {
         return $this->_aData["value"] = (string) $s;
     }
 
@@ -107,13 +124,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 +220,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 +243,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 +260,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 +302,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 +328,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 +351,7 @@ class appmonitorcheck {
      * )
      * @return boolean
      */
-    private function checkDiskfree($aParams) {
+    protected function checkDiskfree($aParams) {
         $this->_checkArrayKeys($aParams, "directory", "critical");
         
         $sDirectory = $aParams["directory"];
@@ -378,7 +404,7 @@ class appmonitorcheck {
      * )
      * @return boolean
      */
-    private function checkFile($aParams) {
+    protected function checkFile($aParams) {
         $aOK = array();
         $aErrors = array();
         $this->_checkArrayKeys($aParams, "filename");
@@ -413,42 +439,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 {
+                $sOut.="request successful<br>";
+            }
+        }
+        // --- http header
+        if(isset($aParams["headercontains"]) && $aParams["headercontains"]){
+            $sOut.="Http header contains &quot;".$aParams["headercontains"]."&quot; - ";
+            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 &quot;".$aParams["headernotcontains"]."&quot; - ";
+            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 &quot;".$aParams["headerregex"]."&quot; - ";
+            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;
+            }
+        }
+        // --- http body
+        if(isset($aParams["bodycontains"]) && $aParams["bodycontains"]){
+            $sOut.="Http body contains &quot;".$aParams["bodycontains"]."&quot; - ";
+            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 &quot;".$aParams["bodynotcontains"]."&quot; - ";
+            if(strstr($sHttpBody, $aParams["bodynotcontains"])===false){
+                $sOut.="compare OK<br>";
             } else {
-                $this->_setReturn(RESULT_ERROR, 'ERROR: The text ' . $aParams["contains"] . ' was NOT found in response of ' . $aParams["url"] . '.');
+                $sOut.="compare failed<br>";
+                $bError=true;
             }
         }
-        return $res;
+        if(isset($aParams["bodyregex"]) && $aParams["bodyregex"]){
+            $sOut.="Http body regex test &quot;".$aParams["bodyregex"]."&quot; - ";
+            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 +677,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 +749,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 +762,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 +786,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 +822,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 235b22171b2bf2adda0a746c4577049c70d60a99..297174ce14baba39e4ad26892d463d94f309de96 100644
--- a/public_html/appmonitor/classes/appmonitor-client.class.php
+++ b/public_html/appmonitor/classes/appmonitor-client.class.php
@@ -15,10 +15,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.77
  * @author Axel Hahn
  * @link TODO
  * @license GPL
@@ -32,27 +36,35 @@ class appmonitor {
      * value is in seconds
      * @var int
      */
-    private $_iDefaultTtl = 300;
+    protected $_sVersion = 'php-client-v0.77';
+
+    /**
+     * 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();
+    
     protected $_iStart = false;
 
     /**
@@ -63,21 +75,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 +105,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 +119,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 +138,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
      */
@@ -282,7 +306,7 @@ class appmonitor {
     /**
      * verify array values and abort with all found errors
      */
-    private function _checkData() {
+    protected function _checkData() {
         $aErrors = array();
 
         if (!count($this->_aChecks)) {
@@ -384,5 +408,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;
+    }
 
 }
diff --git a/public_html/appmonitor/index.php b/public_html/appmonitor/index.php
index 52cf4eb70efdc43255059f6ae98ddb4093a7b641..05cdadd98933faf24a4520311539b9e18a65e068 100644
--- a/public_html/appmonitor/index.php
+++ b/public_html/appmonitor/index.php
@@ -2,9 +2,6 @@
 
 require_once('classes/appmonitor-client.class.php');
 $oMonitor = new appmonitor();
-@include 'general_include.php';
-
-$oMonitor->addTag('deployment');
 
 $oMonitor->addCheck(
         array(
@@ -20,90 +17,172 @@ $oMonitor->addCheck(
         )
 );
 
+$sCfgfile='../../config/inc_projects_config.php';
+
 
-require_once '../../config/inc_projects_config.php';
 // ----------------------------------------------------------------------
-// needed directories
+// config file
 // ----------------------------------------------------------------------
 
-
 $oMonitor->addCheck(
     array(
-        "name" => "tmp subdir",
-        "description" => "Check storage for temp directories, git checkouts for logmessages exists and is writable",
+        "name" => "read Cfg file",
+        "description" => "Check if config file is readable",
         "check" => array(
             "function" => "File",
             "params" => array(
-                "filename" => $aConfig['tmpDir'],
-                "dir"      => true,
-                "writable" => true,
+                "filename" => $sCfgfile,
+                "file"     => true,
+                "readable" => true,
             ),
         ),
     )
 );
-$oMonitor->addCheck(
-    array(
-        "name" => "workdir",
-        "description" => "Check if base workdir exists and is writable",
-        "check" => array(
-            "function" => "File",
-            "params" => array(
-                "filename" => $aConfig['workDir'],
-                "dir"      => true,
-                "writable" => true,
-            ),
-        ),
-    )
-);
-  
 
-foreach(array('dataDir', 'buildDir', 'packageDir', 'archiveDir') as $sDirKey){
+require_once $sCfgfile;
+
+// echo '<pre>' . print_r($aConfig, 1) . '</pre>';die();
+
+// ----------------------------------------------------------------------
+// directories
+// ----------------------------------------------------------------------
+
+foreach (array(
+    
+    'tmpDir'=>array('dir'=>$aConfig['tmpDir'], 'descr'=>'Temp Dir mit git Daten'),
+    'configDir'=>array('dir'=>$aConfig['configDir'], 'descr'=>'Ablage der Programm-Config'),
+    'dataDir'=>array('dir'=>$aConfig['dataDir'], 'descr'=>'Basisverzeichnis fue DB, Projekt-Configs, SSH-Keys'),
+        'dataDir/database'=>array('dir'=>$aConfig['dataDir'].'/database', 'descr'=>'DB-Ablage (Sqlite)'),
+        'dataDir/projects'=>array('dir'=>$aConfig['dataDir'].'/projects', 'descr'=>'Projekt-Configdateien'),
+        'dataDir/sshkeys'=>array('dir'=>$aConfig['dataDir'].'/sshkeys', 'descr'=>'SSH Keys'),
+    
+    'buildDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Basisverzeichnis fuer Builds'),
+    'packageDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Basisverzeichnis der Pakete und Versionen'),
+    'archiveDir'=>array('dir'=>$aConfig['buildDir'], 'descr'=>'Ablage der gebuildeten Archive'),
+    
+) as $sKey=>$aItem) {
     $oMonitor->addCheck(
         array(
-            "name" => "dir [$sDirKey]",
-            "description" => "Check if workdir $sDirKey exists and is writable",
+            "name" => "dir $sKey",
+            "description" => $aItem['descr'],
             "check" => array(
                 "function" => "File",
                 "params" => array(
-                    "filename" => $aConfig[$sDirKey],
+                    "filename" => $aItem['dir'],
                     "dir"      => true,
                     "writable" => true,
                 ),
             ),
         )
     );
-        
 }
-foreach(array('dataDir', 'buildDir', 'packageDir', 'archiveDir') as $sDirKey){
-    $oMonitor->addCheck(
-        array(
-            "name" => "Disk space in dir [$sDirKey]",
-            "description" => "Check if workdir $sDirKey has enough space",
-            "check" => array(
-                "function" => "Diskfree",
-                "params" => array(
-                    "directory" => $aConfig[$sDirKey],
-                    "warning"   => "500MB",
-                    "critical"  => "100MB",
-                ),
+
+// ----------------------------------------------------------------------
+// foreman
+// ----------------------------------------------------------------------
+
+if(isset($aConfig['foreman']['api'])){
+    
+    require_once(__DIR__.'/../deployment/classes/foremanapi.class.php');
+    $oForeman = new ForemanApi($aConfig['foreman']);
+
+    foreach (array('hostgroups', 'hosts') as $sForemanKey){
+        $aFData=$oForeman->read(array(
+            'request' => array(
+                array($sForemanKey),
             ),
-        )
-    );
+            'response' => array(
+                'id', 'title'
+            ),
+        ));
         
+        $oMonitor->addCheck(
+            array(
+                "name" => "Foreman $sForemanKey",
+                "description" => "Count of $sForemanKey",
+                "check" => array(
+                    "function" => "Simple",
+                    "params" => array(
+                        "result" => count($aFData) ? RESULT_OK : RESULT_ERROR,
+                        "value" => "Count of found $sForemanKey: " . count($aFData)."; response status: ".$oForeman->getResponseStatus() . "; Http-Code ".$oForeman->getResponseInfo('http_code'),
+                        "count" => count($aFData),
+                        "visual" => "simple",
+                    ),
+                ),
+            )
+        );
+    }
 }
-    $oMonitor->addCheck(
-        array(
-            "name" => "Certificate check",
-            "description" => "Check if SSL cert is valid and does not expire soon",
-            "check" => array(
-                "function" => "Cert",
-            ),
+
+// ----------------------------------------------------------------------
+// ssh targets
+// ----------------------------------------------------------------------
+
+if(count($aConfig['mirrorPackages'])){
+    foreach($aConfig['mirrorPackages'] as $sHostKey=>$aData){
+        /*
+        [puppet.one] => Array
+        (
+            [type] => rsync
+            [runas] => 
+            [target] => copy-deployment@puppet.one.iml.unibe.ch:/var/shared/imldeployment
         )
-    );
+         */
+        $sCmd='ssh '.$aData['target']. ' ls -1';
+        exec($sCmd, $sOut, $iRc);
+        $oMonitor->addCheck(
+            array(
+                "name" => "mirror target $sHostKey",
+                "description" => "Sync target of generated packages",
+                "check" => array(
+                    "function" => "Simple",
+                    "params" => array(
+                        "result" => $iRc ? RESULT_ERROR : RESULT_OK,
+                        "value" => "$sCmd returns $sOut",
+                    ),
+                ),
+            )
+        );
+    }
     
+}
+foreach(array(
+    array(
+        'host'=>'gitlab.iml.unibe.ch',
+        'port'=>22,
+        'descr'=>'SSH port to Gitlab on gitlab.iml.unibe.ch'
+    ),
+    array(
+        'host'=>'git-repo.iml.unibe.ch',
+        'port'=>22,
+        'descr'=>'SSH port to Gitlab on git-repo.iml.unibe.ch'
+    ),
+    array(
+        'host'=>'github.com',
+        'port'=>22,
+        'descr'=>'SSH port to Github'
+    ),
+) as $aDescr){
+    $oMonitor->addCheck(
+            array(
+                    "name" => 'port check '.$aDescr['host'].':'.$aDescr['port'],
+                    "description" => $aDescr['descr'],
+                    "check" => array(
+                            "function" => "PortTcp",
+                            "params" => array(
+                                    "port"=>$aDescr['port'],
+                                    "host"=>$aDescr['host'],
+                            ),
+                    ),
+            )
+    );
+}
+
+
 // ----------------------------------------------------------------------
-// databases
+// database
 // ----------------------------------------------------------------------
+
 $sSqlitefile=$aConfig['dataDir'].'/database/logs.db';
 $oMonitor->addCheck(
     array(
@@ -117,34 +196,7 @@ $oMonitor->addCheck(
         ),
     )
 );
-$sSqlitefile2=$_SERVER['DOCUMENT_ROOT'].'/valuestore/data/versioncache.db';
-$oMonitor->addCheck(
-    array(
-        "name" => "Sqlite DB version cache",
-        "description" => "Connect sqlite db ". basename($sSqlitefile),
-        "check" => array(
-            "function" => "SqliteConnect",
-            "params" => array(
-                "db"=>$sSqlitefile2
-            ),
-        ),
-    )
-);
 
-// ----------------------------------------------------------------------
-// ssl cert
-// ----------------------------------------------------------------------
-if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']){
-    $oMonitor->addCheck(
-        array(
-            "name" => "Certificate check",
-            "description" => "Check if SSL cert is valid and does not expire soon",
-            "check" => array(
-                "function" => "Cert",
-            ),
-        )
-    );
-}
 
 // Gesamt-Ergebnis - ohne Param=aut. max. Wert nehmen
 $oMonitor->setResult();
diff --git a/public_html/deployment/classes/foremanapi.class.php b/public_html/deployment/classes/foremanapi.class.php
index 31fd9ddc8204005a40258e695b49ba405409dd24..e0e9851efb1eeb08f65c5b5dbabc01ee053ed24c 100644
--- a/public_html/deployment/classes/foremanapi.class.php
+++ b/public_html/deployment/classes/foremanapi.class.php
@@ -114,8 +114,18 @@ class ForemanApi {
     );
     
     
+    /**
+     * last request
+     * @var type 
+     */
     protected $_aRequest=array();
     
+    /**
+     * last response
+     * @var type 
+     */
+    protected $_aResponse=array();
+    
     
     // ----------------------------------------------------------------------
     // constructor
@@ -144,7 +154,9 @@ class ForemanApi {
      */
     protected function log($sMessage, $sLevel = "info") {
         global $oCLog;
-        return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel);
+        if($oCLog && method_exists($oLog, 'add')){
+            return $oCLog->add(basename(__FILE__) . " class " . __CLASS__ . " - " . $sMessage, $sLevel);
+        }
     }
     
     /**
@@ -228,6 +240,7 @@ class ForemanApi {
         if ($aRequest){
             $this->_aRequest=$aRequest;
         }
+        $this->_aResponse=array();
         $this->log(__FUNCTION__ . " start <pre>".print_r($this->_aRequest,1)."</pre>");
         if (!function_exists("curl_init")) {
             die("ERROR: PHP CURL module is not installed.");
@@ -262,11 +275,10 @@ class ForemanApi {
         $aReturn=array('info'=>curl_getinfo($ch), 'error'=>curl_error($ch));
         curl_close($ch);
         $this->log(__FUNCTION__ . " status ".$aReturn['info']['http_code'].' '.$this->_aRequest['method']." $sFullUrl");
-
         $sHeader=substr($res, 0, $aReturn['info']['header_size']);
         $aReturn['header']=explode("\n", $sHeader);
         $aReturn['body']=str_replace($sHeader, "", $res);
-                
+
         return $aReturn;
     }
 
@@ -333,7 +345,7 @@ class ForemanApi {
         // check status
         $iStatuscode=$aReturn['info']['http_code'];
         if ($iStatuscode===0){
-            $sStatus='wrong host';
+            $sStatus='wrong host or no connect';
         }
         if ($iStatuscode>=200 && $iStatuscode<400){
             $sStatus='OK';
@@ -363,6 +375,7 @@ class ForemanApi {
         $aReturn['_OK']=$bOk;
         $aReturn['_status']=$sStatus;
         $this->_writeDebug(__FUNCTION__ . ' result of request <pre>'.print_r($aReturn,1).'</pre>');
+        $this->_aResponse=$aReturn;
 
         return $aReturn;
     }
@@ -512,5 +525,91 @@ class ForemanApi {
         return $this->makeRequest();
          */
     }
+
+    // ----------------------------------------------------------------------
+    // get response infos 
+    // ----------------------------------------------------------------------
+    
+    /**
+     * get curl info data from last response
+     * Array
+        (
+            [url] => https://foreman/api/hostgroups/?&per_page=1000
+            [content_type] => application/json; charset=utf-8
+            [http_code] => 200
+            [header_size] => 1034
+            [request_size] => 218
+            [filetime] => -1
+            [ssl_verify_result] => 0
+            [redirect_count] => 0
+            [total_time] => 1.644417
+            [namelookup_time] => 0.007198
+            [connect_time] => 0.009012
+            [pretransfer_time] => 0.0332
+            [size_upload] => 0
+            [size_download] => 119602
+            [speed_download] => 72732
+            [speed_upload] => 0
+            [download_content_length] => -1
+            [upload_content_length] => 0
+            [starttransfer_time] => 1.642775
+            [redirect_time] => 0
+            [redirect_url] => 
+            [primary_ip] => 10.0.2.10
+            [certinfo] => Array
+                (
+                )
+
+            [primary_port] => 443
+            [local_ip] => 10.0.2.15
+            [local_port] => 33906
+        )
+     * @param string $sKey  get value of given key only
+     * @return any
+     */
+    public function getResponseInfo($sKey=false){
+        
+        if($sKey){
+            return isset($this->_aResponse['info'][$sKey]) 
+                ? $this->_aResponse['info'][$sKey] 
+                : false
+            ;
+        }
+        return isset($this->_aResponse['info']) 
+                ? $this->_aResponse['info']
+                : false
+            ;
+        /*
+        return isset($sKey) && $sKey
+            ? isset($this->_aResponse['info'][$sKey]) 
+                ? $this->_aResponse['info'][$sKey] 
+                : false
+            : isset($this->_aResponse['info']) 
+                ? $this->_aResponse['info']
+                : false
+        ;
+         */
+    }
     
+    /**
+     * get array of last http response header
+     * @return array
+     */
+    public function getResponseHeader(){
+        return isset($this->_aResponse['header']) 
+                ? $this->_aResponse['header']
+                : false
+            ;
+    }
+    /**
+     * get status of last request
+     * @return string
+     */
+    public function getResponseStatus(){
+        // print_r($this->_aResponse); die();
+        return isset($this->_aResponse['_status']) 
+                ? $this->_aResponse['_status']
+                : false
+            ;
+    }
 }