From 99f9504a15ff302c4aa6842ad2f5095263c23c3f Mon Sep 17 00:00:00 2001 From: José Bollo Date: Tue, 22 Aug 2017 09:21:43 +0200 Subject: monitor: Test page for monitoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a test page for showing basic monitoring use. Signed-off-by: José Bollo --- test/AFB.js | 22 +- test/index.html | 5 +- test/iot-bzh-logo-small.png | Bin 0 -> 14449 bytes test/monitoring/AFB.js | 188 +++++++++++ test/monitoring/content-background-car-wide.png | Bin 0 -> 1215422 bytes test/monitoring/cross.png | Bin 0 -> 695 bytes test/monitoring/down.png | Bin 0 -> 449 bytes test/monitoring/favicon.ico | Bin 0 -> 1150 bytes test/monitoring/monitor.css | 366 ++++++++++++++++++++ test/monitoring/monitor.html | 162 +++++++++ test/monitoring/monitor.js | 431 ++++++++++++++++++++++++ test/monitoring/underscore-min.js | 5 + test/monitoring/up.png | Bin 0 -> 447 bytes 13 files changed, 1175 insertions(+), 4 deletions(-) create mode 100644 test/iot-bzh-logo-small.png create mode 100644 test/monitoring/AFB.js create mode 100644 test/monitoring/content-background-car-wide.png create mode 100644 test/monitoring/cross.png create mode 100644 test/monitoring/down.png create mode 100644 test/monitoring/favicon.ico create mode 100644 test/monitoring/monitor.css create mode 100644 test/monitoring/monitor.html create mode 100644 test/monitoring/monitor.js create mode 100644 test/monitoring/underscore-min.js create mode 100644 test/monitoring/up.png (limited to 'test') diff --git a/test/AFB.js b/test/AFB.js index c77e5e60..88da4d55 100644 --- a/test/AFB.js +++ b/test/AFB.js @@ -1,7 +1,16 @@ AFB = function(base, initialtoken){ -var urlws = "ws://"+window.location.host+"/"+base; -var urlhttp = "http://"+window.location.host+"/"+base; +if (typeof base != "object") + base = { base: base, token: initialtoken }; + +var initial = { + base: base.base || "api", + token: base.token || "hello", + host: base.host || window.location.host, + url: base.url || undefined +}; + +var urlws = initial.url || "ws://"+initial.host+"/"+initial.base; /*********************************************/ /**** ****/ @@ -11,7 +20,7 @@ var urlhttp = "http://"+window.location.host+"/"+base; var AFB_context; { var UUID = undefined; - var TOKEN = initialtoken; + var TOKEN = initial.token; var context = function(token, uuid) { this.token = token; @@ -49,6 +58,7 @@ var AFB_websocket; u = u + '&x-afb-uuid=' + AFB_context.uuid; } this.ws = new WebSocket(u, [ PROTO1 ]); + this.url = u; this.pendings = {}; this.awaitens = {}; this.counter = 0; @@ -132,6 +142,12 @@ var AFB_websocket; function close() { this.ws.close(); + this.ws.onopen = + this.ws.onerror = + this.ws.onclose = + this.ws.onmessage = + this.onopen = + this.onabort = function(){}; } function call(method, request) { diff --git a/test/index.html b/test/index.html index 014ae461..6a8e220e 100644 --- a/test/index.html +++ b/test/index.html @@ -7,6 +7,9 @@
  • Hello World!
  • client context
  • Sample post -
  • websockets
  • AFB.js +
  • Monitoring + diff --git a/test/iot-bzh-logo-small.png b/test/iot-bzh-logo-small.png new file mode 100644 index 00000000..2c3b2aef Binary files /dev/null and b/test/iot-bzh-logo-small.png differ diff --git a/test/monitoring/AFB.js b/test/monitoring/AFB.js new file mode 100644 index 00000000..88da4d55 --- /dev/null +++ b/test/monitoring/AFB.js @@ -0,0 +1,188 @@ +AFB = function(base, initialtoken){ + +if (typeof base != "object") + base = { base: base, token: initialtoken }; + +var initial = { + base: base.base || "api", + token: base.token || "hello", + host: base.host || window.location.host, + url: base.url || undefined +}; + +var urlws = initial.url || "ws://"+initial.host+"/"+initial.base; + +/*********************************************/ +/**** ****/ +/**** AFB_context ****/ +/**** ****/ +/*********************************************/ +var AFB_context; +{ + var UUID = undefined; + var TOKEN = initial.token; + + var context = function(token, uuid) { + this.token = token; + this.uuid = uuid; + } + + context.prototype = { + get token() {return TOKEN;}, + set token(tok) {if(tok) TOKEN=tok;}, + get uuid() {return UUID;}, + set uuid(id) {if(id) UUID=id;} + }; + + AFB_context = new context(); +} +/*********************************************/ +/**** ****/ +/**** AFB_websocket ****/ +/**** ****/ +/*********************************************/ +var AFB_websocket; +{ + var CALL = 2; + var RETOK = 3; + var RETERR = 4; + var EVENT = 5; + + var PROTO1 = "x-afb-ws-json1"; + + AFB_websocket = function(onopen, onabort) { + var u = urlws; + if (AFB_context.token) { + u = u + '?x-afb-token=' + AFB_context.token; + if (AFB_context.uuid) + u = u + '&x-afb-uuid=' + AFB_context.uuid; + } + this.ws = new WebSocket(u, [ PROTO1 ]); + this.url = u; + this.pendings = {}; + this.awaitens = {}; + this.counter = 0; + this.ws.onopen = onopen.bind(this); + this.ws.onerror = onerror.bind(this); + this.ws.onclose = onclose.bind(this); + this.ws.onmessage = onmessage.bind(this); + this.onopen = onopen; + this.onabort = onabort; + } + + function onerror(event) { + var f = this.onabort; + if (f) { + delete this.onopen; + delete this.onabort; + f && f(this); + } + this.onerror && this.onerror(this); + } + + function onopen(event) { + var f = this.onopen; + delete this.onopen; + delete this.onabort; + f && f(this); + } + + function onclose(event) { + for (var id in this.pendings) { + var ferr = this.pendings[id].onerror; + ferr && ferr(null, this); + } + this.pendings = {}; + this.onclose && this.onclose(); + } + + function fire(awaitens, name, data) { + var a = awaitens[name]; + if (a) + a.forEach(function(handler){handler(data);}); + var i = name.indexOf("/"); + if (i >= 0) { + a = awaitens[name.substring(0,i)]; + if (a) + a.forEach(function(handler){handler(data);}); + } + a = awaitens["*"]; + if (a) + a.forEach(function(handler){handler(data);}); + } + + function reply(pendings, id, ans, offset) { + if (id in pendings) { + var p = pendings[id]; + delete pendings[id]; + var f = p[offset]; + f(ans); + } + } + + function onmessage(event) { + var obj = JSON.parse(event.data); + var code = obj[0]; + var id = obj[1]; + var ans = obj[2]; + AFB_context.token = obj[3]; + switch (code) { + case RETOK: + reply(this.pendings, id, ans, 0); + break; + case RETERR: + reply(this.pendings, id, ans, 1); + break; + case EVENT: + default: + fire(this.awaitens, id, ans); + break; + } + } + + function close() { + this.ws.close(); + this.ws.onopen = + this.ws.onerror = + this.ws.onclose = + this.ws.onmessage = + this.onopen = + this.onabort = function(){}; + } + + function call(method, request) { + return new Promise((function(resolve, reject){ + var id, arr; + do { + id = String(this.counter = 4095 & (this.counter + 1)); + } while (id in this.pendings); + this.pendings[id] = [ resolve, reject ]; + arr = [CALL, id, method, request ]; + if (AFB_context.token) arr.push(AFB_context.token); + this.ws.send(JSON.stringify(arr)); + }).bind(this)); + } + + function onevent(name, handler) { + var id = name; + var list = this.awaitens[id] || (this.awaitens[id] = []); + list.push(handler); + } + + AFB_websocket.prototype = { + close: close, + call: call, + onevent: onevent + }; +} +/*********************************************/ +/**** ****/ +/**** ****/ +/**** ****/ +/*********************************************/ +return { + context: AFB_context, + ws: AFB_websocket +}; +}; + diff --git a/test/monitoring/content-background-car-wide.png b/test/monitoring/content-background-car-wide.png new file mode 100644 index 00000000..073d1b9d Binary files /dev/null and b/test/monitoring/content-background-car-wide.png differ diff --git a/test/monitoring/cross.png b/test/monitoring/cross.png new file mode 100644 index 00000000..24453e7b Binary files /dev/null and b/test/monitoring/cross.png differ diff --git a/test/monitoring/down.png b/test/monitoring/down.png new file mode 100644 index 00000000..c399895c Binary files /dev/null and b/test/monitoring/down.png differ diff --git a/test/monitoring/favicon.ico b/test/monitoring/favicon.ico new file mode 100644 index 00000000..eeb7ab7a Binary files /dev/null and b/test/monitoring/favicon.ico differ diff --git a/test/monitoring/monitor.css b/test/monitoring/monitor.css new file mode 100644 index 00000000..ff7fd305 --- /dev/null +++ b/test/monitoring/monitor.css @@ -0,0 +1,366 @@ +/*******************************************************************/ +/* top */ +body { + margin: 0px; + position: fixed; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +body.on #params, +body.off #controls, +body.off #logmsg-box { display: none; } + +/*******************************************************************/ +/* head */ +#head { + position: relative; +} + +#logo { + float: left; +} + +#title { + font-weight: bolder; + font-size: larger; + padding: 10px; + margin: 5px; +} + +#connected { + float: right; + margin: 5px; + padding: 10px; + border: solid 2px black; + border-radius: 7px; +} + +#connected.ok { + background: #afa; +} + +#connected.error { + background: #f88; +} + +/*******************************************************************/ +/* connection area */ + + +#params { + border: dashed 4px red; + background: #fde; + padding: 10px; + margin: 10px; + border-radius: 0px 50px; +} + +#connect { + float: right; + margin: 20px; +} + +/*******************************************************************/ +/* main area */ + +#work { + position: relative; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +#main { + position: relative; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; +} + +.fillfix { + width: 100%; + height: 100%; +} + +#controls { + position: absolute; + width: 250px; + left: 0px; + top: 0px; + bottom: 0px; + overflow: auto; + margin-bottom: 75px; +} + +#logmsg-box { + position: absolute; + width: 250px; + right: 0px; + top: 0px; + bottom: 0px; + font-size: smaller; + overflow: auto; + margin-bottom: 75px; +} + +#global { + background: #ff5; +} + +#trace-events { + overflow: auto; + position: absolute; + right: 250px; + left: 250px; + top: 0px; + bottom: 0px; + margin-bottom: 75px; +} + +.expert { + text-align: center; + font-weight: bolder; + font-size: larger; + text-decoration: underline; +} + +/*******************************************************************/ +/* setting for apis */ + +#controls .api { + margin: 2px; + padding: 5px; + border: solid 1px black; + background: #ff5; + border-radius: 7px; + overflow: auto; +} + +#controls #apis .api { + background: #fa5; +} + +#controls .api .name { + text-align: center; + font-weight: bolder; + font-size: larger; + text-decoration: underline; +} + +#controls .api .desc { + text-align: center; +} + +#controls .api .verb { + margin-left: 5px; +} + +/*******************************************************************/ +/* setting of verbs */ +.verb .name { + font-weight: bolder; + text-decoration: underline; +} + +.verb .desc { + font-size: smaller; +} + +.verb .perm { + font-size: 8px; + text-align: right; + color: blue; +} + +/*******************************************************************/ +/* setting for traces */ + +.trace-box { + margin: 1px; + padding: 1px 1px 1px 10px; + border: solid 1px black; + border-radius: 10px 0px; + font-size: smaller; +} + +.trace-title { + font-weight: bolder; +} +.trace-item { + margin-left: 10px; +} + +#apis .trace-evt { + visibility: hidden; + display: none; +} + +/*******************************************************************/ +/* tiny button */ +.x-button { + font-size: larger; + text-align: center; + margin: 5px; + padding: 10px; + border: solid 2px grey; + border-radius: 7px; + background: #ffc; + font-weight: bolder; +} + +.x-button:hover { + background: #fcc; + border: solid 2px black; +} + +.x-button:active { + background: #fcc; + border: solid 3px black; + margin: 4px; +} + +/*******************************************************************/ +/* display of logmsg */ +.logmsg { + background: #f44; /* red by default */ + margin: 1px; + padding: 2px; + font-size: smaller; + border-radius: 3px; + min-height: 20px; +} + +.logmsg.call { background: #ee3; } +.logmsg.retok { background: #8e8; } +.logmsg.event { background: #d6f; } +.logmsg.error { background: #f66; } +.logmsg.trace { background: #aaa; } + +/*******************************************************************/ +/* close box */ +.close { + float: right; + width: 16; + height: 16; + background-image: url(cross.png); + background-size: contain; +} + +/*******************************************************************/ +/* open / close */ +.opclo { float: right; width: 16; height: 16; } +.api > .opclo { float: left; width: 16; height: 16; background: #feb; border: solid 1px black; border-radius: 4px; } + +.closed > .opclo { background-image: url(down.png); background-size: contain; } +.opened > .opclo { background-image: url(up.png); background-size: contain; } + +.closed > .closedoff { visibility: hidden; display: none; } + +.opened > .closedon { visibility: hidden; display: none; } + +/*******************************************************************/ +/* setting for traceevents */ + +.traceevent { + position: relative; + margin: 1px; + padding: 2px; + min-height: 16px; + border: solid 1px black; + border-radius: 0px 5px 5px 5px; +} + +.traceevent.request, .trace-box.request { background: #ffd; } +.traceevent.daemon, .trace-box.daemon { background: #fdf; } +.traceevent.service, .trace-box.service { background: #ddf; } +.traceevent.event, .trace-box.event { background: #dfd; } + +.traceevent.closed { + max-height: 16px; + overflow: hidden; +} + +.traceevent .time { + height: 16px; + margin: -2px 8px 2px -2px; + padding: 1px 3px; + float: left; + background: black; + color: white; + font-weight: bolder; +} + +.traceevent.closed:hover { + overflow: visible; + z-index: 100; + position: relative; +} + +.traceevent.closed:not(:hover) .content { + display: none; +} + +.traceevent.closed:hover .content { + display: block; + background: inherit; + margin: 5px; + padding: 5px; + border: solid 1px black; + border-radius: 0px 5px 5px 5px; + left: 50%; + position: absolute; + box-shadow: 10px 10px grey; +} + +.traceevent table.object tr td:nth-child(1) { + text-align: right; +} + +.traceevent table.object tr td:nth-child(2) { + border: solid 1px black; + font-weight: bolder; + padding: 0px 4px; +} + +.traceevent { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.traceevent table.object tr td:nth-child(2) { + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +/*******************************************************************/ +/* aesthetic clue */ +.select { + padding: 0px; +} + +/*******************************************************************/ +/* json format */ + +.json.string { color: lightskyblue; } +.json.number { color: darkorange; } +.json.boolean { color: deepskyblue; } +.json.null { color: magenta; } +.json.key { color: red; } + +/*******************************************************************/ +/* clear fix */ + +.clearfix::after { + content: ""; + clear: both; + display: table; +} diff --git a/test/monitoring/monitor.html b/test/monitoring/monitor.html new file mode 100644 index 00000000..1a1d49cb --- /dev/null +++ b/test/monitoring/monitor.html @@ -0,0 +1,162 @@ + + + Monitoring + + + + + + + +
    +
    +
    connect
    +
    host:
    +
    port:
    +
    token:
    +
    +
    +
    +
    +
    +
    +
    {ALL}
    +
    Settings for all
    +
    +
    placeholder
    +
    +
    traces...
    +
    +
    +
    +
    {COMMON}
    +
    Settings without apis
    +
    +
    placeholder
    +
    +
    +
    +
    +
    +
    Disconnect
    +
    Clear traces
    +
    +
    +
    {EXPERT}
    +
    +
    Stop logs
    +
    Clear logs
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + diff --git a/test/monitoring/monitor.js b/test/monitoring/monitor.js new file mode 100644 index 00000000..c4f24a00 --- /dev/null +++ b/test/monitoring/monitor.js @@ -0,0 +1,431 @@ + +var afb; +var ws; + +var t_api; +var t_verb; +var t_logmsg; +var t_traceevent; +var t_verbosity; +var t_trace; +var apis = {}; +var events = []; +var inhibit = false; +var msgs = false; + +var root_node; +var connected_node; +var trace_events_node; +var logmsgs_node; +var apis_node; +var all_node; + +/* flags */ +var show_perms = false; +var show_monitor_events = false; + +_.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"; + connected_node.innerHTML = "Connection Closed"; + connected_node.className = "ok"; + ws && ws.close(); + afb = null; + ws = 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_connect(evt) { + connect({ + host: at("param-host").value + ":" + at("param-port").value, + token: at("param-token").value + }); +} + +function init() { + /* prepare the DOM templates */ + t_api = at("t-api").content.firstElementChild; + t_verb = at("t-verb").content.firstElementChild; + t_logmsg = at("t-logmsg").content.firstElementChild; + t_traceevent = at("t-traceevent").content.firstElementChild; + t_verbosity = at("t-verbosity").content.firstElementChild; + t_trace = at("t-trace").content.firstElementChild; + + root_node = at("root"); + connected_node = at("connected"); + trace_events_node = at("trace-events"); + logmsgs_node = at("logmsgs"); + apis_node = at("apis"); + all_node = at("all"); + + plug(t_api, ".verbosity", t_verbosity); + plug(t_api, ".trace", t_trace); + plug(all_node, ".trace", t_trace); + plug(all_node, ".verbosity", t_verbosity); + plug(at("common"), ".verbosity", t_verbosity); + for_all_nodes(root_node, ".opclo", function(n){n.onclick = on_toggle_opclo}); + 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("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; + + connect(); +} + +function for_all_nodes(root, sel, fun) { + (root ? root : document).querySelectorAll(sel).forEach(fun); +} + +function get(sel,x) { + if (!x) + x = document; + var r = x.querySelector(sel); + return r; +} +function at(id) { return document.getElementById(id); } + +function plug(target, sel, node) { + var x = get(sel, target); + var n = target.ownerDocument.importNode(node, true); + x.parentNode.insertBefore(n, x); + x.parentNode.removeChild(x); +} + +function onopen() { + root_node.className = "on"; + connected_node.innerHTML = "Connected " + ws.url; + connected_node.className = "ok"; + ws.onevent("*", gotevent); + ws.onclose = onabort; + 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"; +} + +function start_logmsgs(val) { + at("stopmsgs").textContent = (msgs = val) ? "Stop logs" : "Get logs"; +} + +function toggle_logmsgs() { + start_logmsgs(!msgs); +} + +function drop_all_logmsgs() { + logmsgs_node.innerHTML = ""; +} + +function drop_all_trace_events() { + trace_events_node.innerHTML = ""; +} + +function add_logmsg(tag, content, add) { + if (!msgs) return; + var x = document.importNode(t_logmsg, true); + get(".tag", x).textContent = tag; + get(".content", x).textContent = content; + get(".close", x).onclick = function(evt){x.remove();}; + if (add) + x.className = x.className + " " + add; + logmsgs_node.prepend(x); +} + +function add_error(tag, obj) { + add_logmsg(tag, JSON.stringify(obj, null, 1), "error"); +} + +function on_error_apis(obj) { + add_error("can't get apis", obj); +} + +function do_call(api_verb, request, onsuccess, onerror) { + var call = api_verb + "(" + JSON.stringify(request, null, 1) + ")"; + add_logmsg(call, "", "call"); + ws.call(api_verb, request).then( + function(obj){ + add_logmsg(call + " SUCCESS:", JSON.stringify(obj, null, 1), "retok"); + if (onsuccess) + onsuccess(obj); + }, + function(obj){ + add_logmsg(call + " ERROR:", JSON.stringify(obj, null, 1), "reterr"); + if (onerror) + onerror(obj); + }); +} + +/* show all verbosities */ +function on_got_verbosities(obj) { + inhibit = true; + _.each(obj.response.verbosity, function(verbosity, api_name){ + if (api_name == "monitor") return; + var node = api_name ? apis[api_name].node : at("common"); + if (node) + get(".verbosity option[value='"+verbosity+"']", node).selected = true; + }); + inhibit = false; +} + +function set_verbosity(evt) { + if (inhibit) return; + inhibit = true; + var obj = evt.target; + var req = {verbosity:{}}; + var name = obj.API ? obj.API.name : obj === get(".select", all_node) ? "*" : ""; + if (name != "*") { + req.verbosity[name] = obj.value; + } else { + req.verbosity = obj.value; + } + inhibit = false; + do_call("monitor/set", req); + do_call("monitor/get", {verbosity:true}, on_got_verbosities); +} + +/* show all apis */ +function on_got_apis(obj) { + inhibit = true; + _.each(obj.response.apis, function(api_desc, api_name){ + if (api_name == "monitor") return; + var api = apis[api_name]; + if (!api) { + api = { + node: document.importNode(t_api, true), + verbs: {}, + name: api_name + }; + api.node.API = api; + api.node.dataset.api = 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 || ""; + 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); + _.each(api_desc.paths, function(verb_desc, path_name){ + var verb_name = path_name.substring(1); + var verb = api.verbs[verb_name]; + if (!verb) { + verb = { + node: document.importNode(t_verb, true), + name: verb_name, + api: api + }; + verb.node.VERB = verb; + verb.node.dataset.verb = verb_name; + api.verbs[verb_name] = verb; + get(".name", verb.node).textContent = verb_name; + var g = verb_desc.get ||{}; + var r = g["responses"] || {}; + var t = r["200"] || {}; + var d = t.description || ""; + get(".desc", verb.node).textContent = d; + if (show_perms) { + var p = g["x-permissions"] || ""; + get(".perm", verb.node).textContent = p ? JSON.stringify(p, null, 1) : ""; + } + api.vnode.append(verb.node); + } + }); + var s = get(".verbosity select", api.node); + s.API = api; + s.onchange = set_verbosity; + } + }); + inhibit = false; + on_got_verbosities(obj); +} + +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) + 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}); + } + } +} + +function makecontent(node, deep, val) { + if (--deep > 0) { + if (_.isObject(val)) { + node.append(makeobj(val, deep)); + return; + } + if (_.isArray(val)) { + node.append(makearr(val, deep)); + return; + } + } + node.innerHTML = obj2html(val); +} + +function makearritem(tbl, deep, val) { + var tr = document.createElement("tr"); + var td = document.createElement("td"); + tr.append(td); + tbl.append(tr); + makecontent(td, deep, val); +} + +function makearr(arr, deep) { + var node = document.createElement("table"); + node.className = "array"; + _.each(arr, function(v) { makearritem(node, deep, v);}); + return node; +} + +function makeobjitem(tbl, deep, key, val) { + var tr = document.createElement("tr"); + var td1 = document.createElement("td"); + var td2 = document.createElement("td"); + tr.className = key; + tr.append(td1); + td1.textContent = key; + tr.append(td2); + tbl.append(tr); + makecontent(td2, deep, val); +} + +function makeobj(obj, deep, ekey, eobj) { + var node = document.createElement("table"); + node.className = "object"; + _.each(_.keys(obj).sort(), function(k) { makeobjitem(node, deep, k, obj[k]);}); + if (ekey) + makeobjitem(node, deep, ekey, eobj); + return node; +} + +function gotevent(obj) { + if (obj.event != "monitor/trace") + add_logmsg("unexpected event!", JSON.stringify(obj, null, 1), "event"); + else { + add_logmsg("trace event", JSON.stringify(obj, null, 1), "trace"); + gottraceevent(obj); + } +} + +function gottraceevent(obj) { + var data = obj.data; + var type = _.find(["request", "service", "daemon", "event"],function(x){return x in data;}); + var desc = data[type]; + if (!show_monitor_events) { + if (type == "event" ? desc.name.startsWith("monitor/") : desc.api == "monitor") + return; + } + var x = document.importNode(t_traceevent, true); + x.dataset.event = obj; + get(".close", x).onclick = function(evt){x.remove();}; + 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; }, + 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); + var tab = makeobj(desc, 4); + if ("data" in data) + makeobjitem(tab, 1, "data", data.data); + get(".content", x).append(tab); + trace_events_node.append(x); +} + +function toggle_opened_closed(node, 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"; + } + } + return x; + }).join(" "); + if (!matched) + cs = cs + " " + (defval || "closed"); + node.className = cs; +} + +function on_toggle_traceevent(evt) { + if (getSelection() != "") return; + var node = evt.target; + while(node && node.parentElement != trace_events_node) + node = node.parentElement; + node && toggle_opened_closed(node); +} + +function obj2html(json) { + json = JSON.stringify(json, undefined, 2); + json = json.replace(/&/g, '&').replace(//g, '>'); + return json.replace( + /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, + function (match) { + var cls = 'number'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'key'; + } else { + cls = 'string'; + } + } else if (/true|false/.test(match)) { + cls = 'boolean'; + } else if (/null/.test(match)) { + cls = 'null'; + } + return '' + match + ''; + }); +} + diff --git a/test/monitoring/underscore-min.js b/test/monitoring/underscore-min.js new file mode 100644 index 00000000..3c3eec02 --- /dev/null +++ b/test/monitoring/underscore-min.js @@ -0,0 +1,5 @@ +// Underscore.js 1.8.3 +// http://underscorejs.org +// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors +// Underscore may be freely distributed under the MIT license. +(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); diff --git a/test/monitoring/up.png b/test/monitoring/up.png new file mode 100644 index 00000000..aeec342c Binary files /dev/null and b/test/monitoring/up.png differ -- cgit 1.2.3-korg