mcp-agent mode for mcp-checker with web-info and REST API

New:
 - agent index page serving on 0.0.0.0:8765
 - REST API with modular approach to modules
 - 'fio' module working via thread-safe Thread able to return
   real-time info on its status
 - 'fio' module scheduled run option
 - ability to preserve multiple testrun results while active
 - dockerfile for agent image

Fixed:
 - Network report fixes to work on Kube envs
 - Fixed function for running commands inside daemonset pods

 Related-PROD: PROD-36669

Change-Id: I57e73001247af9187680bfc5744590eef219d93c
diff --git a/templates/agent_index_html.j2 b/templates/agent_index_html.j2
new file mode 100644
index 0000000..ff8f613
--- /dev/null
+++ b/templates/agent_index_html.j2
@@ -0,0 +1,305 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>cfg-checker agent</title>
+    {% include 'common_styles.j2' %}
+    {% include 'common_scripts.j2' %}
+    <script language="JavaScript">
+        const getJSON = async url => {
+            const response = await fetch(url);
+            if(!response.ok) // check if response worked (no 404 errors etc...)
+              throw new Error(response.statusText);
+          
+            const data = response.json(); // get JSON from the response
+            return data; // returns a promise, which resolves to this data value
+        }
+
+        function qStatus(uri, tagId) {
+            var dt, el;
+   
+            el = document.getElementById(tagId);
+            el.innerHTML = "calling " + uri;
+
+            getJSON(uri).then(data => {
+                el.innerHTML = "<pre>" + JSON.stringify(data, null, '  ') + "</pre>";
+            }).catch(error => {
+                el.innerHTML = JSON.stringify(error, null, '  ');
+            });
+        };
+
+        function updateModules() {
+            var _n, _v, _hc, _s, _phead, _p;
+
+            let mods = [];
+            // Jinja populated list
+            {% for mod in modules %}
+            mods.push("{{ mod }}");
+            {% endfor %}
+    
+            mods.forEach((mod) => {
+                // get all ids needed to fill
+                _n = document.getElementById(mod + "_name");
+                _v = document.getElementById(mod + "_version");
+                _hc = document.getElementById(mod + "_healthcheck");
+                _s = document.getElementById(mod + "_status");
+                _phead = document.getElementById(mod + "_progress_head");
+                _p = document.getElementById(mod + "_progress");
+                
+                // Fill
+                _n.innerHTML = mod;
+                // get status
+                getJSON("api/" + mod).then(data => {
+                    _v.innerHTML = data['healthcheck']['version'];
+                    _hc.innerHTML = data['healthcheck']['ready'];
+                    _s.innerHTML = data['status'];
+                    if ((data['status'] == "idle") || (data['status'] == "finished")) {
+                        if (_phead.className.indexOf("idle") < 0) {
+                            _phead.className += " idle";
+                        }
+                    }
+                    else if (data['status'] == "running") {
+                        _phead.className = _phead.className.replace(" idle", "");   
+                    }
+                    _p.style.width = data['progress'] + "%";
+                }).catch(error => {
+                    _v.innerHTML = "?";
+                    _hc.innerHTML = "No response";
+                    _s.innerHTML = "Unknown";
+                    _phead.className = _phead.className.replace(" idle", "");   
+                    // set bar status to red later
+                    //_p.style.width = "100%";
+                });
+            })
+        };
+        var interval = 500;
+        setInterval(updateModules, interval);
+    </script>
+    <style>
+        .barcontent {
+            margin: auto;
+            width: 1350px;
+            padding: 10px;
+        }
+        .bar-centered {
+            float: none;
+            transform: translate(25%);
+        }
+        .agent_labels {
+            display: inline-block;
+            margin: 0px;
+            margin-right: 5px;
+
+        }
+        .info_label {
+            font-family: "LaoSangamMN", Monaco, monospace;
+            display: block;
+            float: left;
+            box-sizing: content-box;
+            background: #262;
+            color: white;
+            border-radius: 5px;
+            text-align: center;
+            padding: 0px 5px 5px 5px;
+            height: 14px;
+            margin: 5px 2px 5px 2px;
+        }
+        .gray_bg { background: #444; }
+        .section_head { font-size: 0.8em; color: Navy; padding-left: 2px; }
+        .row_button {
+            background-color: #468;
+            color: #fff;
+            cursor: pointer;
+            padding: 5px;
+            width: 100%;
+            border: none;
+            text-align: left;
+            outline: none;
+            font-size: 13px;
+        }
+        .row_button:after {
+            content: '\02795'; /* Unicode character for "plus" sign (+) */
+            font-size: 13px;
+            color: white;
+            float: left;
+            margin-left: 5px;
+        }
+          
+        .row_active:after {
+            content: "\2796"; /* Unicode character for "minus" sign (-) */
+            color: white
+        }        
+
+        .row_active, .row_button:hover {
+            background-color: #68a;
+            color: white
+        }
+
+        .cell_button {
+            color: darkgreen;
+            cursor: pointer;
+            padding: 5px;
+            width: 100%;
+            border: none;
+            text-align: center;
+            outline: none;
+        }
+        .cell_button:hover {
+            background-color: gray;
+        }
+        
+        .tooltiptext {
+            transform: translate(100px);
+        }
+
+        .console {
+            background-color: black;
+            font-family: "Lucida Console", Monaco, monospace;
+            font-size: 0.5em;
+            width: auto;
+            color: #fff;
+            border-radius: 6px;
+            padding: 5px 5px;
+        }
+    
+        .progress {
+            box-sizing: content-box;
+            height: 10px; /* Can be anything */
+            position: relative;
+            background: #aaa;
+            border-radius: 5px;
+            padding: 5px;
+            box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3);
+          }
+          .progress > span {
+            display: block;
+            height: 100%;
+            border-top-right-radius: 3px;
+            border-bottom-right-radius: 3px;
+            border-top-left-radius: 5px;
+            border-bottom-left-radius: 5px;
+            background-color: rgb(43, 194, 83);
+            background-image: linear-gradient(
+              center bottom,
+              rgb(43, 194, 83) 37%,
+              rgb(84, 240, 84) 69%
+            );
+            position: relative;
+            overflow: hidden;
+          }
+          .progress > span:after,
+          .animate > span > span {
+            content: "";
+            position: absolute;
+            top: 0;
+            left: 0;
+            bottom: 0;
+            right: 0;
+            background-image: linear-gradient(
+              -45deg,
+              rgba(255, 255, 255, 0.1) 25%,
+              transparent 25%,
+              transparent 50%,
+              rgba(255, 255, 255, 0.1) 50%,
+              rgba(255, 255, 255, 0.1) 75%,
+              transparent 75%,
+              transparent
+            );
+            z-index: 1;
+            background-size: 50px 50px;
+            animation: move 2s linear infinite;
+            border-top-right-radius: 8px;
+            border-bottom-right-radius: 8px;
+            border-top-left-radius: 20px;
+            border-bottom-left-radius: 20px;
+            overflow: hidden;
+          }
+          
+          .animate > span:after {
+            display: none;
+          }
+          
+          @keyframes move {
+            0% {
+              background-position: 0 0;
+            }
+            100% {
+              background-position: 50px 50px;
+            }
+          }
+          
+          .bluewish > span {
+            background-image: linear-gradient(#3d52b1, #3052b1);
+          }
+          .idle > span > span,
+          .idle > span::after {
+            background-image: none;
+          }          
+    </style>
+</head>
+<body onload="init()">
+
+<div class="header">
+    <div class="label date">generated on: {{ gen_date }}</div>
+</div>
+
+<div class="bar">
+    <div class="bar-centered">
+        <button class="bar-item" onclick="openBar(event, 'status')">Status</button>
+    </div>
+</div>
+
+{% macro status_page(info, id_label) %}
+<div id="{{ id_label }}" class="barcontent">
+    <h5>{{ caller() }}</h5>
+    <hr>
+    <div class="agent_labels">
+        <div class="info_label" onclick="updateModules()">{{ hostname }}</div>
+        <div class="info_label">{{ system }}</div>
+        <div class="info_label">{{ release }}</div>
+        <div class="info_label gray_bg">Started: {{ agent['started'] }}</div>
+    </div>
+    <hr>
+    {% for mod in modules %}
+    <div class="agent_labels">
+        <div class="info_label" id="{{ mod }}_name">{{ mod }}</div>
+        <div class="info_label" id="{{ mod }}_version">unknown</div>
+        <div class="info_label" id="{{ mod }}_healthcheck">unknown</div>
+        <div class="info_label" id="{{ mod }}_status">unknown</div>
+    </div>
+    <div class="progress bluewish idle" id="{{ mod }}_progress_head">
+        <span style="width: 0%" id="{{ mod }}_progress"></span>
+    </div>
+    <hr>
+    {% endfor %}
+    <div class="section_head">REST Api help:</div>
+    {% for uri, ops in help.items() %}
+    <div class="console">
+        <div class="">{{ uri | escape }}</div>
+        {% for op, op_help in ops.items() %}
+        <div class="">{{ op }}: {{ op_help | escape }}</div>
+        {% endfor %}
+    </div>
+    <br>
+    {% endfor %}
+    <div class="section_head">Available modules: {{ modules | join(", ")}}</div>
+    <div class="section_head">Agent:</div>
+    <div class="console" onclick="qStatus('api/','agent')">
+        <div class="" id="agent"></div>
+    </div>
+
+    <div class="section_head">Status json:</div>
+    <div class="console" onclick="qStatus('api/{{ modules[0] }}','fio')">
+        <div class="" id="fio"></div>
+    </div>
+
+    
+</div>
+{% endmacro %}
+
+
+{% call status_page(info, "status") %}
+    Agent status
+{% endcall %}
+</body>
+</html>
\ No newline at end of file