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