monitoring: add copyrights
[src/app-framework-binder.git] / test / monitoring / monitor.js
1 /*
2  * Copyright (C) 2017 "IoT.bzh"
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 var afb;
19 var ws;
20
21 var t_api;
22 var t_verb;
23 var t_logmsg;
24 var t_traceevent;
25 var t_verbosity;
26 var t_trace;
27 var t_separator;
28
29 var apis = {};
30 var events = [];
31 var inhibit = false;
32 var msgs = false;
33 var autoscroll = false;
34
35 var root_node;
36 var connected_node;
37 var trace_events_node;
38 var logmsgs_node;
39 var apis_node;
40 var all_node;
41
42 /* flags */
43 var show_perms = false;
44 var show_monitor_events = false;
45
46 _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
47
48 function untrace_all() {
49         do_call("monitor/trace", {drop: true});
50         for_all_nodes(null, ".trace-item input[type=radio]", function(n){n.checked = n.value == "no";});
51 }
52
53 function disconnect() {
54         untrace_all();
55         apis = {};
56         apis_node.innerHTML = "";
57         root_node.className = "off";
58         connected_node.innerHTML = "Connection Closed";
59         connected_node.className = "ok";
60         ws && ws.close();
61         afb = null;
62         ws = null;
63 }
64
65 function connect(args) {
66         drop_all_trace_events();
67         drop_all_logmsgs();
68         ws && ws.close();
69         afb = new AFB(args);
70         ws = new afb.ws(onopen, onabort);
71 }
72
73 function on_connect(evt) {
74         connect({
75                 host: at("param-host").value + ":" + at("param-port").value,
76                 token: at("param-token").value
77         });
78 }
79
80 function init() {
81         /* prepare the DOM templates */
82         t_api = at("t-api").content.firstElementChild;
83         t_verb = at("t-verb").content.firstElementChild;
84         t_logmsg = at("t-logmsg").content.firstElementChild;
85         t_traceevent = at("t-traceevent").content.firstElementChild;
86         t_verbosity = at("t-verbosity").content.firstElementChild;
87         t_trace = at("t-trace").content.firstElementChild;
88         t_separator = at("t-separator").content.firstElementChild;
89
90         root_node = at("root");
91         connected_node = at("connected");
92         trace_events_node = at("trace-events");
93         logmsgs_node = at("logmsgs");
94         apis_node = at("apis");
95         all_node = at("all");
96
97         plug(t_api, ".verbosity", t_verbosity);
98         plug(t_api, ".trace", t_trace);
99         plug(all_node, ".trace", t_trace);
100         plug(all_node, ".verbosity", t_verbosity);
101         plug(at("common"), ".verbosity", t_verbosity);
102         for_all_nodes(root_node, ".opclo", function(n){n.onclick = on_toggle_opclo});
103         for_all_nodes(root_node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
104         for_all_nodes(root_node, ".verbosity select", function(n){n.onchange = set_verbosity});
105         for_all_nodes(root_node, ".trace-item input", function(n){n.onchange = on_trace_change});
106         at("disconnect").onclick = disconnect;
107         at("connect").onclick = on_connect;
108         at("droptracevts").onclick = drop_all_trace_events;
109         at("dropmsgs").onclick = drop_all_logmsgs;
110         at("stopmsgs").onclick = toggle_logmsgs;
111         start_logmsgs(false);
112         trace_events_node.onclick = on_toggle_traceevent;
113         at("autoscroll").onclick = toggle_autoscroll;
114         start_autoscroll(true);
115         at("addsep").onclick = add_separator;
116
117         connect();
118 }
119
120 function for_all_nodes(root, sel, fun) {
121         (root ? root : document).querySelectorAll(sel).forEach(fun);
122 }
123
124 function get(sel,x) {
125         if (!x)
126                 x = document;
127         var r = x.querySelector(sel);
128         return r;
129 }
130 function at(id) { return document.getElementById(id); }
131
132 function plug(target, sel, node) {
133         var x = get(sel, target);
134         var n = target.ownerDocument.importNode(node, true);
135         x.parentNode.insertBefore(n, x);
136         x.parentNode.removeChild(x);
137 }
138
139 function onopen() {
140         root_node.className = "on";
141         connected_node.innerHTML = "Connected " + ws.url;
142         connected_node.className = "ok";
143         ws.onevent("*", gotevent);
144         ws.onclose = onabort;
145         do_call("monitor/get", {apis:true,verbosity:true}, on_got_apis, on_error_apis);
146 }
147 function onabort() {
148         root_node.className = "off";
149         connected_node.innerHTML = "Connection Closed";
150         connected_node.className = "error";
151 }
152
153 function start_autoscroll(val) {
154         at("autoscroll").textContent = (autoscroll = val) ? "Stop scroll" : "Start scroll";
155 }
156
157 function toggle_autoscroll() {
158         start_autoscroll(!autoscroll);
159 }
160
161 function add_separator() {
162         var x = document.importNode(t_separator, true);
163         trace_events_node.append(x);
164         if (autoscroll)
165                 x.scrollIntoView();
166 }
167
168 function start_logmsgs(val) {
169         at("stopmsgs").textContent = (msgs = val) ? "Stop logs" : "Get logs";
170 }
171
172 function toggle_logmsgs() {
173         start_logmsgs(!msgs);
174 }
175
176 function drop_all_logmsgs() {
177         logmsgs_node.innerHTML = "";
178 }
179
180 function drop_all_trace_events() {
181         trace_events_node.innerHTML = "";
182 }
183
184 function add_logmsg(tag, content, add) {
185         if (!msgs) return;
186         var x = document.importNode(t_logmsg, true);
187         get(".tag", x).textContent = tag;
188         get(".content", x).textContent = content;
189         get(".close", x).onclick = function(evt){x.remove();};
190         if (add)
191                 x.className = x.className + " " + add;
192         logmsgs_node.append(x);
193         if (autoscroll)
194                 x.scrollIntoView();
195 }
196
197 function add_error(tag, obj) {
198         add_logmsg(tag, JSON.stringify(obj, null, 1), "error");
199 }
200
201 function on_error_apis(obj) {
202         add_error("can't get apis", obj);
203 }
204
205 function do_call(api_verb, request, onsuccess, onerror) {
206         var call = api_verb + "(" + JSON.stringify(request, null, 1) + ")";
207         add_logmsg("send request", call, "call");
208         ws.call(api_verb, request).then(
209                 function(obj){
210                         add_logmsg("receive success", call + " -> " + JSON.stringify(obj, null, 1), "retok");
211                         if (onsuccess)
212                                 onsuccess(obj);
213                 },
214                 function(obj){
215                         add_logmsg("receive error", call + " -> ", JSON.stringify(obj, null, 1), "reterr");
216                         if (onerror)
217                                 onerror(obj);
218                 });
219 }
220
221 /* show all verbosities */
222 function on_got_verbosities(obj) {
223         inhibit = true;
224         _.each(obj.response.verbosity, function(verbosity, api_name){
225                 if (api_name == "monitor") return;
226                 var node = api_name ? apis[api_name].node : at("common");
227                 if (node)
228                         get(".verbosity option[value='"+verbosity+"']", node).selected = true;
229         });
230         inhibit = false;
231 }
232
233 function set_verbosity(evt) {
234         if (inhibit) return;
235         inhibit = true;
236         var obj = evt.target;
237         var req = {verbosity:{}};
238         var name = obj.API ? obj.API.name : obj === get(".select", all_node) ? "*" : "";
239         if (name != "*") {
240                 req.verbosity[name] = obj.value;
241         } else {
242                 req.verbosity = obj.value;
243         }
244         inhibit = false;
245         do_call("monitor/set", req);
246         do_call("monitor/get", {verbosity:true}, on_got_verbosities);
247 }
248
249 /* show all apis */
250 function on_got_apis(obj) {
251         inhibit = true;
252         _.each(obj.response.apis, function(api_desc, api_name){
253                 if (api_name == "monitor") return;
254                 var api = apis[api_name];
255                 if (!api) {
256                         api = {
257                                 node: document.importNode(t_api, true),
258                                 verbs: {},
259                                 name: api_name
260                         };
261                         api.node.API = api;
262                         api.node.dataset.api = api_name;
263                         api.vnode = get(".verbs", api.node);
264                         apis[api_name] = api;
265                         get(".name", api.node).textContent = api_name;
266                         get(".desc", api.node).textContent = api_desc.info.description || "";
267                         for_all_nodes(api.node, ".opclo", function(n){n.onclick = on_toggle_opclo});
268                         for_all_nodes(api.node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
269                         for_all_nodes(api.node, ".trace-item input", function(n){n.onchange = on_trace_change});
270                         apis_node.append(api.node);
271                         _.each(api_desc.paths, function(verb_desc, path_name){
272                                 var verb_name = path_name.substring(1);
273                                 var verb = api.verbs[verb_name];
274                                 if (!verb) {
275                                         verb = {
276                                                 node: document.importNode(t_verb, true),
277                                                 name: verb_name,
278                                                 api: api
279                                         };
280                                         verb.node.VERB = verb;
281                                         verb.node.dataset.verb = verb_name;
282                                         api.verbs[verb_name] = verb;
283                                         get(".name", verb.node).textContent = verb_name;
284                                         var g = verb_desc.get ||{};
285                                         var r = g["responses"] || {};
286                                         var t = r["200"] || {};
287                                         var d = t.description || "";
288                                         get(".desc", verb.node).textContent = d;
289                                         if (show_perms) {
290                                                 var p = g["x-permissions"] || "";
291                                                 get(".perm", verb.node).textContent = p ? JSON.stringify(p, null, 1) : "";
292                                         }
293                                         api.vnode.append(verb.node);
294                                 }
295                         });
296                         var s = get(".verbosity select", api.node);
297                         s.API = api;
298                         s.onchange = set_verbosity;
299                 }
300         });
301         inhibit = false;
302         on_got_verbosities(obj);
303 }
304
305 function on_toggle_opclo(evt) {
306         toggle_opened_closed(evt.target.parentElement);
307 }
308
309 function on_trace_change(evt) {
310         var obj = evt.target;
311         var tra = obj.parentElement;
312         while (tra && !tra.dataset.trace)
313                 tra = tra.parentElement;
314         var api = tra;
315         while (api && !api.dataset.api)
316                 api = api.parentElement;
317         var tag = api.dataset.api + "/" + tra.dataset.trace;
318         if (tra) {
319                 var drop = false;
320                 for_all_nodes(tra, "input", function(n){
321                         if (n.checked) {
322                                 n.checked = false;
323                                 if (n != obj && n.value != "no")
324                                         drop = true;
325                         }
326                 });
327                 if (drop)
328                         do_call("monitor/trace", {drop: {tag: tag}});
329                 obj.checked = true;
330                 if (obj.value != "no") {
331                         var spec = {tag: tag, name: "trace"};
332                         spec[tra.dataset.trace] = obj.value;
333                         if (api.dataset.api != "*")
334                                 spec.api = api.dataset.api;
335                         do_call("monitor/trace", {add: spec});
336                 }
337         }
338 }
339
340 function makecontent(node, deep, val) {
341         if (--deep > 0) {
342                 if (_.isObject(val)) {
343                         node.append(makeobj(val, deep));
344                         return;
345                 }
346                 if (_.isArray(val)) {
347                         node.append(makearr(val, deep));
348                         return;
349                 }
350         }
351         node.innerHTML = obj2html(val);
352 }
353
354 function makearritem(tbl, deep, val) {
355         var tr = document.createElement("tr");
356         var td = document.createElement("td");
357         tr.append(td);
358         tbl.append(tr);
359         makecontent(td, deep, val);
360 }
361
362 function makearr(arr, deep) {
363         var node = document.createElement("table");
364         node.className = "array";
365         _.each(arr, function(v) { makearritem(node, deep, v);});
366         return node;
367 }
368
369 function makeobjitem(tbl, deep, key, val) {
370         var tr = document.createElement("tr");
371         var td1 = document.createElement("td");
372         var td2 = document.createElement("td");
373         tr.className = key;
374         tr.append(td1);
375         td1.textContent = key;
376         tr.append(td2);
377         tbl.append(tr);
378         makecontent(td2, deep, val);
379 }
380
381 function makeobj(obj, deep, ekey, eobj) {
382         var node = document.createElement("table");
383         node.className = "object";
384         _.each(_.keys(obj).sort(), function(k) { makeobjitem(node, deep, k, obj[k]);});
385         if (ekey)
386                 makeobjitem(node, deep, ekey, eobj);
387         return node;
388 }
389
390 function gotevent(obj) {
391         if (obj.event != "monitor/trace")
392                 add_logmsg("unexpected event!", JSON.stringify(obj, null, 1), "event");
393         else {
394                 add_logmsg("trace event", JSON.stringify(obj, null, 1), "trace");
395                 gottraceevent(obj);
396         }
397 }
398
399 function gottraceevent(obj) {
400         var data = obj.data;
401         var type = _.find(["request", "service", "daemon", "event"],function(x){return x in data;});
402         var desc = data[type];
403         if (!show_monitor_events) {
404                 if (type == "event" ? desc.name.startsWith("monitor/") : desc.api == "monitor")
405                         return;
406         }
407         var x = document.importNode(t_traceevent, true);
408         x.dataset.event = obj;
409         get(".close", x).onclick = function(evt){x.remove();};
410         x.className = x.className + " " + type;
411         get(".time", x).textContent = data.time;
412         get(".tag", x).textContent = ({
413                 request: function(r) { return r.api + "/" + r.verb + "  [" + r.index + "] " + r.action; },
414                 service: function(r) { return r.api + "@" + r.action; },
415                 daemon: function(r) { return r.api + ":" + r.action; },
416                 event: function(r) { return r.name + "!" + r.action; },
417                 })[type](desc);
418         var tab = makeobj(desc, 4);
419         if ("data" in data)
420                 makeobjitem(tab, 2, "data", data.data);
421         get(".content", x).append(tab);
422         trace_events_node.append(x);
423         if (autoscroll)
424                 x.scrollIntoView();
425 }
426
427 function toggle_opened_closed(node, defval) {
428         var matched = false;
429         var cs = node.className.split(" ").map(
430                 function(x){
431                         if (!matched) {
432                                 switch(x) {
433                                 case "closed": matched = true; return "opened";
434                                 case "opened": matched = true; return "closed";
435                                 }
436                         }
437                         return x;
438                 }).join(" ");
439         if (!matched)
440                 cs = cs + " " + (defval || "closed");
441         node.className = cs;
442 }
443
444 function on_toggle_traceevent(evt) {
445         if (getSelection() != "") return;
446         var node = evt.target;
447         while(node && node.parentElement != trace_events_node)
448                 node = node.parentElement;
449         node && toggle_opened_closed(node);
450 }
451
452 function obj2html(json) {
453         json = JSON.stringify(json, undefined, 2);
454         json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
455         return json.replace(
456                 /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
457                 function (match) {
458                         var cls = 'number';
459                         if (/^"/.test(match)) {
460                                 if (/:$/.test(match)) {
461                                         cls = 'key';
462                                 } else {
463                                         cls = 'string';
464                                 }
465                         } else if (/true|false/.test(match)) {
466                                 cls = 'boolean';
467                         } else if (/null/.test(match)) {
468                                 cls = 'null';
469                         }
470                         return '<span class="json ' + cls + '">' + match + '</span>';
471                 });
472 }
473