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