2 * Copyright (C) 2017-2019 "IoT.bzh"
3 * Author: José Bollo <jose.bollo@iot.bzh>
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
33 var autoscroll = false;
37 var trace_events_node;
45 var show_perms = false;
46 var show_monitor_events = false;
48 _.templateSettings = { interpolate: /\{\{(.+?)\}\}/g };
50 function untrace_all() {
51 do_call("monitor/trace", {drop: true});
54 function disconnect(status) {
55 class_toggle(root_node, { on: "off" }, "off");
56 connected_node.innerHTML = "Connection Closed";
57 connected_node.className = status;
60 ws.onclose = ws.onabort = null;
65 at("param-token").value = afb.context.token;
69 function on_disconnect() {
76 host: at("param-host").value + ":" + at("param-port").value,
77 token: at("param-token").value
79 ws = new afb.ws(onopen, onabort);
82 function on_connect(evt) {
86 function next_style(evt) {
92 at("style").onclick = next_style;
94 /* prepare the DOM templates */
95 t_api = at("t-api").content.firstElementChild;
96 t_verb = at("t-verb").content.firstElementChild;
97 t_logmsg = at("t-logmsg").content.firstElementChild;
98 t_traceevent = at("t-traceevent").content.firstElementChild;
99 t_verbosity = at("t-verbosity").content.firstElementChild;
100 t_trace = at("t-trace").content.firstElementChild;
101 t_separator = at("t-separator").content.firstElementChild;
103 root_node = at("root");
104 connected_node = at("connected");
105 trace_events_node = at("trace-events");
106 logmsgs_node = at("logmsgs");
107 apis_node = at("apis");
108 all_node = at("all");
110 plug(t_api, ".verbosity", t_verbosity);
111 plug(t_api, ".trace", t_trace);
112 plug(all_node, ".trace", t_trace);
113 plug(all_node, ".verbosity", t_verbosity);
114 plug(at("common"), ".verbosity", t_verbosity);
115 for_all_nodes(root_node, ".opclo", function(n){n.onclick = on_toggle_opclo});
116 for_all_nodes(root_node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
117 for_all_nodes(root_node, ".verbosity select", function(n){n.onchange = set_verbosity});
118 for_all_nodes(root_node, ".trace-item input", function(n){n.onchange = on_trace_change});
119 at("disconnect").onclick = on_disconnect;
120 at("connect").onclick = on_connect;
121 at("droptracevts").onclick = drop_all_trace_events;
122 at("dropmsgs").onclick = drop_all_logmsgs;
123 at("stopmsgs").onclick = toggle_logmsgs;
124 start_logmsgs(false);
125 trace_events_node.onclick = on_toggle_traceevent;
126 at("autoscroll").onclick = toggle_autoscroll;
127 start_autoscroll(true);
128 at("addsep").onclick = add_separator;
129 at("experts").onclick = toggle_experts;
131 at("param-host").value = document.location.hostname;
132 at("param-port").value = document.location.port;
133 var args = new URLSearchParams(document.location.search.substring(1));
134 at("param-token").value = args.get("x-afb-token") || args.get("token") || "HELLO";
136 document.onbeforeunload = on_disconnect;
141 function for_all_nodes(root, sel, fun) {
142 (root ? root : document).querySelectorAll(sel).forEach(fun);
145 function get(sel,x) {
148 var r = x.querySelector(sel);
151 function at(id) { return document.getElementById(id); }
153 function plug(target, sel, node) {
154 var x = get(sel, target);
155 var n = target.ownerDocument.importNode(node, true);
156 x.parentNode.insertBefore(n, x);
157 x.parentNode.removeChild(x);
161 class_toggle(root_node, { off: "on" }, "on");
162 connected_node.innerHTML = "Connected " + ws.url;
163 connected_node.className = "ok";
164 ws.onevent("*", gotevent);
165 ws.onclose = onabort;
167 for_all_nodes(all_node, ".trace-box", update_trace_box);
168 do_call("monitor/get", {apis:true,verbosity:true}, on_got_apis, on_error_apis);
175 function start_autoscroll(val) {
176 at("autoscroll").textContent = (autoscroll = val) ? "Stop scroll" : "Start scroll";
179 function toggle_autoscroll() {
180 start_autoscroll(!autoscroll);
183 function add_separator() {
184 var x = document.importNode(t_separator, true);
185 trace_events_node.append(x);
189 x = document.importNode(t_separator, true);
190 logmsgs_node.append(x);
196 function start_logmsgs(val) {
197 at("stopmsgs").textContent = (msgs = val) ? "Stop logs" : "Get logs";
200 function toggle_logmsgs() {
201 start_logmsgs(!msgs);
204 function drop_all_logmsgs() {
205 logmsgs_node.innerHTML = "";
208 function drop_all_trace_events() {
209 trace_events_node.innerHTML = "";
212 function add_logmsg(tag, content, add) {
214 var x = document.importNode(t_logmsg, true);
215 get(".tag", x).textContent = tag;
216 get(".content", x).textContent = content;
217 get(".close", x).onclick = function(evt){x.remove();};
219 x.className = x.className + " " + add;
220 logmsgs_node.append(x);
225 function add_error(tag, obj) {
226 add_logmsg(tag, JSON.stringify(obj, null, 1), "error");
229 function on_error_apis(obj) {
230 add_error("can't get apis", obj);
233 function do_call(api_verb, request, onsuccess, onerror) {
234 var call = api_verb + "(" + JSON.stringify(request, null, 1) + ")";
235 add_logmsg("send request", call, "call");
236 ws.call(api_verb, request).then(
238 add_logmsg("receive success", call + " -> " + JSON.stringify(obj, null, 1), "retok");
243 add_logmsg("receive error", call + " -> ", JSON.stringify(obj, null, 1), "reterr");
249 /* show all verbosities */
250 function on_got_verbosities(obj) {
252 _.each(obj.response.verbosity, function(verbosity, api_name){
253 if (api_name == "monitor") return;
254 var node = api_name ? apis[api_name].node : at("common");
256 get(".verbosity option[value='"+verbosity+"']", node).selected = true;
261 function set_verbosity(evt) {
264 var obj = evt.target;
265 var req = {verbosity:{}};
266 var name = obj.API ? obj.API.name : obj === get(".select", all_node) ? "*" : "";
268 req.verbosity[name] = obj.value;
270 req.verbosity = obj.value;
273 do_call("monitor/set", req);
274 do_call("monitor/get", {verbosity:true}, on_got_verbosities);
278 function on_got_apis(obj) {
280 var saved_apis = apis;
282 apis_node.innerHTML = "";
283 _.each(obj.response.apis, function(api_desc, api_name){
284 if (api_name == "monitor") return;
285 var api = saved_apis[api_name];
287 /* create the node */
289 node: document.importNode(t_api, true),
294 api.node.dataset.apiname = api_name;
295 api.vnode = get(".verbs", api.node);
296 get(".name", api.node).textContent = api_name;
297 var s = get(".verbosity select", api.node);
299 s.onchange = set_verbosity;
300 for_all_nodes(api.node, ".opclo", function(n){n.onclick = on_toggle_opclo});
301 for_all_nodes(api.node, ".opclo ~ :not(.closedoff)", function(n){n.onclick = on_toggle_opclo});
302 for_all_nodes(api.node, ".trace-item input", function(n){n.onchange = on_trace_change});
304 /* reactivate the expected traces */
305 for_all_nodes(api.node, ".trace-box", update_trace_box);
307 apis[api_name] = api;
308 if (api_desc == null) {
309 get(".desc", api.node).textContent = "?? unrecoverable ??";
311 get(".desc", api.node).textContent = api_desc.info.description || "";
312 _.each(api_desc.paths, function(verb_desc, path_name){
313 var verb_name = path_name.substring(1);
314 var verb = api.verbs[verb_name];
317 node: document.importNode(t_verb, true),
321 verb.node.VERB = verb;
322 verb.node.dataset.verb = verb_name;
323 api.verbs[verb_name] = verb;
324 get(".name", verb.node).textContent = verb_name;
325 var g = verb_desc.get ||{};
326 var r = g["responses"] || {};
327 var t = r["200"] || {};
328 var d = t.description || "";
329 get(".desc", verb.node).textContent = d;
331 var p = g["x-permissions"] || "";
332 get(".perm", verb.node).textContent = p ? JSON.stringify(p, null, 1) : "";
334 api.vnode.append(verb.node);
338 apis_node.append(api.node);
341 on_got_verbosities(obj);
344 function on_toggle_opclo(evt) {
345 toggle_opened_closed(evt.target.parentElement);
348 function toggle_experts(evt) {
349 toggle_opened_closed(evt.target);
352 function update_trace_box(node) {
353 set_trace_box(node, false);
356 function set_trace_box(node, clear) {
358 while (api && !api.dataset.apiname)
359 api = api.parentElement;
360 var tag = api.dataset.apiname + "/" + node.dataset.trace;
362 for_all_nodes(node, "input", function(n){ if (n.checked) value = n.value; });
364 do_call("monitor/trace", {drop: {tag: tag}});
366 var spec = {tag: tag, name: "trace"};
367 spec[node.dataset.trace] = value;
368 if (api.dataset.apiname != "*")
369 spec.apiname = api.dataset.apiname;
370 do_call("monitor/trace", {add: spec});
374 function on_trace_change(evt) {
375 var obj = evt.target;
376 var box = obj.parentElement;
377 while (box && !box.dataset.trace)
378 box = box.parentElement;
379 for_all_nodes(box, "input", function(n){n.checked = false;});
381 set_trace_box(box, true);
384 function makecontent(node, deep, val) {
386 if (_.isObject(val)) {
387 node.append(makeobj(val, deep));
390 if (_.isArray(val)) {
391 node.append(makearr(val, deep));
395 node.innerHTML = '<pre>' + obj2html(val) + '</pre>';
398 function makearritem(tbl, deep, val) {
399 var tr = document.createElement("tr");
400 var td = document.createElement("td");
403 makecontent(td, deep, val);
406 function makearr(arr, deep) {
407 var node = document.createElement("table");
408 node.className = "array";
409 _.each(arr, function(v) { makearritem(node, deep, v);});
413 function makeobjitem(tbl, deep, key, val) {
414 var tr = document.createElement("tr");
415 var td1 = document.createElement("td");
416 var td2 = document.createElement("td");
419 td1.textContent = key;
422 makecontent(td2, deep, val);
425 function makeobj(obj, deep, ekey, eobj) {
426 var node = document.createElement("table");
427 node.className = "object";
428 _.each(_.keys(obj).sort(), function(k) { makeobjitem(node, deep, k, obj[k]);});
430 makeobjitem(node, deep, ekey, eobj);
434 function gotevent(obj) {
435 if (obj.event != "monitor/trace")
436 add_logmsg("unexpected event!", JSON.stringify(obj, null, 1), "event");
438 add_logmsg("trace event", JSON.stringify(obj, null, 1), "trace");
443 function gottraceevent(obj) {
445 var type = data.type;
446 var desc = data[type];
447 if (!show_monitor_events) {
448 if (type == "event" ? desc.name.startsWith("monitor/") : desc.api == "monitor")
451 var x = document.importNode(t_traceevent, true);
452 x.dataset.event = obj;
453 get(".close", x).onclick = function(evt){x.remove();};
454 x.className = x.className + " " + type;
455 get(".time", x).textContent = data.time;
456 get(".tag", x).textContent = ({
457 request: function(r,d) { return r.api + "/" + r.verb + " [" + r.index + "] " + r.action + (r.action == 'reply' ? ' '+d.data.error : ''); },
458 service: function(r) { return r.api + "@" + r.action; },
459 daemon: function(r) { return r.api + ":" + r.action; },
460 event: function(r) { return r.name + "!" + r.action; },
461 global: function(r) { return "$" + r.action; },
463 var tab = makeobj(desc, 4);
465 makeobjitem(tab, 2, "data", data.data);
466 get(".content", x).append(tab);
467 trace_events_node.append(x);
472 function class_toggle(node, assoc, defval) {
474 var cs = node.className.split(" ").map(
476 if (!matched && (x in assoc)) {
480 return x == defval ? "" : x;
482 if (!matched && defval)
483 cs = cs + " " + defval;
487 function toggle_opened_closed(node, defval) {
488 class_toggle(node, { closed: "opened", opened: "closed" }, defval);
491 function on_toggle_traceevent(evt) {
492 if (getSelection() != "") return;
493 var node = evt.target;
494 while(node && node.parentElement != trace_events_node)
495 node = node.parentElement;
496 node && toggle_opened_closed(node);
499 function obj2html(json) {
500 json = JSON.stringify(json, undefined, 2);
501 json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
503 /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
506 if (/^"/.test(match)) {
507 if (/:$/.test(match)) {
511 match = match.replace(/\\n/g, "\\n<br>");
513 } else if (/true|false/.test(match)) {
515 } else if (/null/.test(match)) {
518 return '<span class="json ' + cls + '">' + match + '</span>';
524 var i, l, a, links, x;
526 x = { idx: 0, byidx: [], byname: {}, names: [] };
527 links = document.getElementsByTagName("link");
528 for (i = 0 ; i < links.length ; i++) {
530 if (l.title && l.rel.indexOf( "stylesheet" ) != -1) {
531 if (!(l.title in x.byname)) {
532 x.byname[l.title] = x.byidx.length;
533 x.names.push(l.title);
536 x.byidx[x.byname[l.title]].push(l);
540 x.set = function(id) {
547 for (i = 0 ; i < a.length ; i++) {
549 for (j = 0 ; j < b.length ; j++)
550 b[j].disabled = i != id;
555 x.next = function() {
556 x.set((x.idx + 1) % x.byidx.length);