Update copyright dates
[src/app-framework-binder.git] / test / monitoring / monitor.js
index c4f24a0..4bc04fd 100644 (file)
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2015-2020 "IoT.bzh"
+ * Author: José Bollo <jose.bollo@iot.bzh>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 var afb;
 var ws;
@@ -8,10 +24,13 @@ var t_logmsg;
 var t_traceevent;
 var t_verbosity;
 var t_trace;
+var t_separator;
+
 var apis = {};
 var events = [];
 var inhibit = false;
 var msgs = false;
+var autoscroll = false;
 
 var root_node;
 var connected_node;
@@ -20,6 +39,8 @@ var logmsgs_node;
 var apis_node;
 var all_node;
 
+var styles;
+
 /* flags */
 var show_perms = false;
 var show_monitor_events = false;
@@ -28,37 +49,48 @@ _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
 
 function untrace_all() {
        do_call("monitor/trace", {drop: true});
-       for_all_nodes(null, ".trace-item input[type=radio]", function(n){n.checked = n.value == "no";});
 }
 
-function disconnect() {
-       untrace_all();
-       apis = {};
-       apis_node.innerHTML = "";
-       root_node.className = "off";
+function disconnect(status) {
+       class_toggle(root_node, { on: "off" }, "off");
        connected_node.innerHTML = "Connection Closed";
-       connected_node.className = "ok";
-       ws && ws.close();
-       afb = null;
+       connected_node.className = status;
+       if (ws) {
+               untrace_all();
+               ws.onclose = ws.onabort = null;
+               ws.close();
+       }
        ws = null;
+       if (afb)
+               at("param-token").value = afb.context.token;
+       afb = null;
 }
 
-function connect(args) {
-       drop_all_trace_events();
-       drop_all_logmsgs();
-       ws && ws.close();
-       afb = new AFB(args);
-       ws = new afb.ws(onopen, onabort);
+function on_disconnect() {
+       disconnect("ok");
 }
 
-function on_connect(evt) {
-       connect({
+function connect() {
+       ws && ws.close();
+       afb = new AFB({
                host: at("param-host").value + ":" + at("param-port").value,
                token: at("param-token").value
        });
+       ws = new afb.ws(onopen, onabort);
+}
+
+function on_connect(evt) {
+       connect();
+}
+
+function next_style(evt) {
+       styles.next();
 }
 
 function init() {
+       styles = makecss();
+       at("style").onclick = next_style;
+
        /* prepare the DOM templates */
        t_api = at("t-api").content.firstElementChild;
        t_verb = at("t-verb").content.firstElementChild;
@@ -66,6 +98,7 @@ function init() {
        t_traceevent = at("t-traceevent").content.firstElementChild;
        t_verbosity = at("t-verbosity").content.firstElementChild;
        t_trace = at("t-trace").content.firstElementChild;
+       t_separator = at("t-separator").content.firstElementChild;
 
        root_node = at("root");
        connected_node = at("connected");
@@ -83,13 +116,24 @@ function init() {
        for_all_nodes(root_node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
        for_all_nodes(root_node, ".verbosity select", function(n){n.onchange = set_verbosity});
        for_all_nodes(root_node, ".trace-item input", function(n){n.onchange = on_trace_change});
-       at("disconnect").onclick = disconnect;
+       at("disconnect").onclick = on_disconnect;
        at("connect").onclick = on_connect;
        at("droptracevts").onclick = drop_all_trace_events;
        at("dropmsgs").onclick = drop_all_logmsgs;
        at("stopmsgs").onclick = toggle_logmsgs;
        start_logmsgs(false);
        trace_events_node.onclick = on_toggle_traceevent;
+       at("autoscroll").onclick = toggle_autoscroll;
+       start_autoscroll(true);
+       at("addsep").onclick = add_separator;
+       at("experts").onclick = toggle_experts;
+
+       at("param-host").value = document.location.hostname;
+       at("param-port").value = document.location.port;
+       var args = new URLSearchParams(document.location.search.substring(1));
+       at("param-token").value = args.get("x-afb-token") || args.get("token") || "HELLO";
+
+       document.onbeforeunload = on_disconnect;
 
        connect();
 }
@@ -114,17 +158,39 @@ function plug(target, sel, node) {
 }
 
 function onopen() {
-       root_node.className = "on";
+       class_toggle(root_node, { off: "on" }, "on");
        connected_node.innerHTML = "Connected " + ws.url;
        connected_node.className = "ok";
        ws.onevent("*", gotevent);
        ws.onclose = onabort;
+       untrace_all();
+       for_all_nodes(all_node, ".trace-box", update_trace_box);
        do_call("monitor/get", {apis:true,verbosity:true}, on_got_apis, on_error_apis);
 }
+
 function onabort() {
-       root_node.className = "off";
-       connected_node.innerHTML = "Connection Closed";
-       connected_node.className = "error";
+       disconnect("error");
+}
+
+function start_autoscroll(val) {
+       at("autoscroll").textContent = (autoscroll = val) ? "Stop scroll" : "Start scroll";
+}
+
+function toggle_autoscroll() {
+       start_autoscroll(!autoscroll);
+}
+
+function add_separator() {
+       var x = document.importNode(t_separator, true);
+       trace_events_node.append(x);
+       if (autoscroll)
+               x.scrollIntoView();
+       if (msgs) {
+               x = document.importNode(t_separator, true);
+               logmsgs_node.append(x);
+               if (autoscroll)
+                       x.scrollIntoView();
+       }
 }
 
 function start_logmsgs(val) {
@@ -151,7 +217,9 @@ function add_logmsg(tag, content, add) {
        get(".close", x).onclick = function(evt){x.remove();};
        if (add)
                x.className = x.className + " " + add;
-       logmsgs_node.prepend(x);
+       logmsgs_node.append(x);
+       if (autoscroll)
+               x.scrollIntoView();
 }
 
 function add_error(tag, obj) {
@@ -164,15 +232,15 @@ function on_error_apis(obj) {
 
 function do_call(api_verb, request, onsuccess, onerror) {
        var call = api_verb + "(" + JSON.stringify(request, null, 1) + ")";
-       add_logmsg(call, "", "call");
+       add_logmsg("send request", call, "call");
        ws.call(api_verb, request).then(
                function(obj){
-                       add_logmsg(call + " SUCCESS:", JSON.stringify(obj, null, 1), "retok");
+                       add_logmsg("receive success", call + " -> " + JSON.stringify(obj, null, 1), "retok");
                        if (onsuccess)
                                onsuccess(obj);
                },
                function(obj){
-                       add_logmsg(call + " ERROR:", JSON.stringify(obj, null, 1), "reterr");
+                       add_logmsg("receive error", call + " -> ", JSON.stringify(obj, null, 1), "reterr");
                        if (onerror)
                                onerror(obj);
                });
@@ -209,25 +277,38 @@ function set_verbosity(evt) {
 /* show all apis */
 function on_got_apis(obj) {
        inhibit = true;
+       var saved_apis = apis;
+       apis = {};
+       apis_node.innerHTML = "";
        _.each(obj.response.apis, function(api_desc, api_name){
                if (api_name == "monitor") return;
-               var api = apis[api_name];
+               var api = saved_apis[api_name];
                if (!api) {
+                       /* create the node */
                        api = {
                                node: document.importNode(t_api, true),
                                verbs: {},
                                name: api_name
                        };
                        api.node.API = api;
-                       api.node.dataset.api = api_name;
+                       api.node.dataset.apiname = api_name;
                        api.vnode = get(".verbs", api.node);
-                       apis[api_name] = api;
                        get(".name", api.node).textContent = api_name;
-                       get(".desc", api.node).textContent = api_desc.info.description || "";
+                       var s = get(".verbosity select", api.node);
+                       s.API = api;
+                       s.onchange = set_verbosity;
                        for_all_nodes(api.node, ".opclo", function(n){n.onclick = on_toggle_opclo});
                        for_all_nodes(api.node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
                        for_all_nodes(api.node, ".trace-item input", function(n){n.onchange = on_trace_change});
-                       apis_node.append(api.node);
+               } else {
+                       /* reactivate the expected traces */
+                       for_all_nodes(api.node, ".trace-box", update_trace_box);
+               }
+               apis[api_name] = api;
+               if (api_desc == null) {
+                       get(".desc", api.node).textContent = "?? unrecoverable ??";
+               } else {
+                       get(".desc", api.node).textContent = api_desc.info.description || "";
                        _.each(api_desc.paths, function(verb_desc, path_name){
                                var verb_name = path_name.substring(1);
                                var verb = api.verbs[verb_name];
@@ -253,10 +334,8 @@ function on_got_apis(obj) {
                                        api.vnode.append(verb.node);
                                }
                        });
-                       var s = get(".verbosity select", api.node);
-                       s.API = api;
-                       s.onchange = set_verbosity;
                }
+               apis_node.append(api.node);
        });
        inhibit = false;
        on_got_verbosities(obj);
@@ -266,37 +345,42 @@ function on_toggle_opclo(evt) {
        toggle_opened_closed(evt.target.parentElement);
 }
 
-function on_trace_change(evt) {
-       var obj = evt.target;
-       var tra = obj.parentElement;
-       while (tra && !tra.dataset.trace)
-               tra = tra.parentElement;
-       var api = tra;
-       while (api && !api.dataset.api)
+function toggle_experts(evt) {
+       toggle_opened_closed(evt.target);
+}
+
+function update_trace_box(node) {
+       set_trace_box(node, false);
+}
+
+function set_trace_box(node, clear) {
+       var api = node;
+       while (api && !api.dataset.apiname)
                api = api.parentElement;
-       var tag = api.dataset.api + "/" + tra.dataset.trace;
-       if (tra) {
-               var drop = false;
-               for_all_nodes(tra, "input", function(n){
-                       if (n.checked) {
-                               n.checked = false;
-                               if (n != obj && n.value != "no")
-                                       drop = true;
-                       }
-               });
-               if (drop)
-                       do_call("monitor/trace", {drop: {tag: tag}});
-               obj.checked = true;
-               if (obj.value != "no") {
-                       var spec = {tag: tag, name: "trace"};
-                       spec[tra.dataset.trace] = obj.value;
-                       if (api.dataset.api != "*")
-                               spec.api = api.dataset.api;
-                       do_call("monitor/trace", {add: spec});
-               }
+       var tag = api.dataset.apiname + "/" + node.dataset.trace;
+       var value = false;
+       for_all_nodes(node, "input", function(n){ if (n.checked) value = n.value; });
+       if (clear)
+               do_call("monitor/trace", {drop: {tag: tag}});
+       if (value != "no") {
+               var spec = {tag: tag, name: "trace"};
+               spec[node.dataset.trace] = value;
+               if (api.dataset.apiname != "*")
+                       spec.apiname = api.dataset.apiname;
+               do_call("monitor/trace", {add: spec});
        }
 }
 
+function on_trace_change(evt) {
+       var obj = evt.target;
+       var box = obj.parentElement;
+       while (box && !box.dataset.trace)
+               box = box.parentElement;
+       for_all_nodes(box, "input", function(n){n.checked = false;});
+       obj.checked = true;
+       set_trace_box(box, true);
+}
+
 function makecontent(node, deep, val) {
        if (--deep > 0) {
                if (_.isObject(val)) {
@@ -308,7 +392,7 @@ function makecontent(node, deep, val) {
                        return;
                }
        }
-       node.innerHTML = obj2html(val);
+       node.innerHTML = '<pre>' + obj2html(val) + '</pre>';
 }
 
 function makearritem(tbl, deep, val) {
@@ -358,7 +442,7 @@ function gotevent(obj) {
 
 function gottraceevent(obj) {
        var data = obj.data;
-       var type = _.find(["request", "service", "daemon", "event"],function(x){return x in data;});
+       var type = data.type;
        var desc = data[type];
        if (!show_monitor_events) {
                if (type == "event" ? desc.name.startsWith("monitor/") : desc.api == "monitor")
@@ -370,35 +454,40 @@ function gottraceevent(obj) {
        x.className = x.className + " " + type;
        get(".time", x).textContent = data.time;
        get(".tag", x).textContent = ({
-               request: function(r) { return r.api + "/" + r.verb + "  [" + r.index + "] " + r.action; },
+               request: function(r,d) { return r.api + "/" + r.verb + "  [" + r.index + "] " + r.action + (r.action == 'reply' ? ' '+d.data.error  : ''); },
                service: function(r) { return r.api + "@" + r.action; },
                daemon: function(r) { return r.api + ":" + r.action; },
                event: function(r) { return r.name + "!" + r.action; },
-               })[type](desc);
+               global: function(r) { return "$" + r.action; },
+               })[type](desc,data);
        var tab = makeobj(desc, 4);
        if ("data" in data)
-               makeobjitem(tab, 1, "data", data.data);
+               makeobjitem(tab, 2, "data", data.data);
        get(".content", x).append(tab);
        trace_events_node.append(x);
+       if (autoscroll)
+               x.scrollIntoView();
 }
 
-function toggle_opened_closed(node, defval) {
+function class_toggle(node, assoc, defval) {
        var matched = false;
        var cs = node.className.split(" ").map(
                function(x){
-                       if (!matched) {
-                               switch(x) {
-                               case "closed": matched = true; return "opened";
-                               case "opened": matched = true; return "closed";
-                               }
+                       if (!matched && (x in assoc)) {
+                               matched = true;
+                               return assoc[x];
                        }
-                       return x;
+                       return x == defval ? "" : x;
                }).join(" ");
-       if (!matched)
-               cs = cs + " " + (defval || "closed");
+       if (!matched && defval)
+               cs = cs + " " + defval;
        node.className = cs;
 }
 
+function toggle_opened_closed(node, defval) {
+       class_toggle(node, { closed: "opened", opened: "closed" }, defval);
+}
+
 function on_toggle_traceevent(evt) {
        if (getSelection() != "") return;
        var node = evt.target;
@@ -419,6 +508,7 @@ function obj2html(json) {
                                        cls = 'key';
                                } else {
                                        cls = 'string';
+                                       match = match.replace(/\\n/g, "\\n<br>");
                                }
                        } else if (/true|false/.test(match)) {
                                cls = 'boolean';
@@ -429,3 +519,44 @@ function obj2html(json) {
                });
 }
 
+function makecss()
+{
+       var i, l, a, links, x;
+
+       x = { idx: 0, byidx: [], byname: {}, names: [] };
+       links = document.getElementsByTagName("link");
+       for (i = 0 ; i < links.length ; i++) {
+               l = links[i];
+               if (l.title && l.rel.indexOf( "stylesheet" ) != -1) {
+                       if (!(l.title in x.byname)) {
+                               x.byname[l.title] = x.byidx.length;
+                               x.names.push(l.title);
+                               x.byidx.push([]);
+                       }
+                       x.byidx[x.byname[l.title]].push(l);
+               }
+       }
+
+       x.set = function(id) {
+               if (id in x.byname)
+                       id = x.byname[id];
+               if (id in x.byidx) {
+                       var i, j, a, b;
+                       x.idx = id;
+                       a = x.byidx;
+                       for (i = 0 ; i < a.length ; i++) {
+                               b = a[i];
+                               for (j = 0 ; j < b.length ; j++)
+                                       b[j].disabled = i != id;
+                       }
+               }
+       };
+
+       x.next = function() {
+               x.set((x.idx + 1) % x.byidx.length);
+       };
+
+       x.set(0);
+       return x;
+}
+