aboutsummaryrefslogtreecommitdiffstats
path: root/test/monitoring
diff options
context:
space:
mode:
authorJosé Bollo <jose.bollo@iot.bzh>2017-08-22 09:21:43 +0200
committerJosé Bollo <jose.bollo@iot.bzh>2017-08-24 19:06:07 +0200
commit99f9504a15ff302c4aa6842ad2f5095263c23c3f (patch)
treedd15323afa61004bcde4537d3a46e316820ef884 /test/monitoring
parentd7936bbe45900b7bb315e0504672d242b7100af5 (diff)
monitor: Test page for monitoring
This is a test page for showing basic monitoring use. Signed-off-by: José Bollo <jose.bollo@iot.bzh>
Diffstat (limited to 'test/monitoring')
-rw-r--r--test/monitoring/AFB.js188
-rw-r--r--test/monitoring/content-background-car-wide.pngbin0 -> 1215422 bytes
-rw-r--r--test/monitoring/cross.pngbin0 -> 695 bytes
-rw-r--r--test/monitoring/down.pngbin0 -> 449 bytes
-rw-r--r--test/monitoring/favicon.icobin0 -> 1150 bytes
-rw-r--r--test/monitoring/monitor.css366
-rw-r--r--test/monitoring/monitor.html162
-rw-r--r--test/monitoring/monitor.js431
-rw-r--r--test/monitoring/underscore-min.js5
-rw-r--r--test/monitoring/up.pngbin0 -> 447 bytes
10 files changed, 1152 insertions, 0 deletions
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
--- /dev/null
+++ b/test/monitoring/content-background-car-wide.png
Binary files differ
diff --git a/test/monitoring/cross.png b/test/monitoring/cross.png
new file mode 100644
index 00000000..24453e7b
--- /dev/null
+++ b/test/monitoring/cross.png
Binary files differ
diff --git a/test/monitoring/down.png b/test/monitoring/down.png
new file mode 100644
index 00000000..c399895c
--- /dev/null
+++ b/test/monitoring/down.png
Binary files differ
diff --git a/test/monitoring/favicon.ico b/test/monitoring/favicon.ico
new file mode 100644
index 00000000..eeb7ab7a
--- /dev/null
+++ b/test/monitoring/favicon.ico
Binary files 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 @@
+<html>
+<head>
+ <title>Monitoring</title>
+ <link href="monitor.css" rel="stylesheet">
+ <script type="text/javascript" src="underscore-min.js"></script>
+ <script type="text/javascript" src="AFB.js"></script>
+ <script type="text/javascript" src="monitor.js"></script>
+
+<body id="root" class="on" onload="init();">
+ <div id="head" class="clearfix">
+ <div id="logo"></div>
+ <div id="connected">Not Connected</div>
+ <div id="title">Monitoring</div>
+ </div>
+ <div id="work">
+ <div id="params" class="clearfix">
+ <div id="connect" class="x-button">connect</div>
+ <div>host: <input type="text" id="param-host" size="50" value="localhost"></input></div>
+ <div>port: <input type="text" id="param-port" size="10" value="1234"></input></div>
+ <div>token: <input type="text" id="param-token" size="33" value="hello"></input></div>
+ </div>
+ <div id="main">
+ <div class="fillfix"></div>
+ <div id="controls">
+ <div id="all" class="api opened" data-api="*">
+ <div class="opclo"></div>
+ <div class="name">{ALL}</div>
+ <div class="desc">Settings for all</div>
+ <hr>
+ <div class="verbosity">placeholder</div>
+ <hr class="closedoff">
+ <div class="closedon">traces...</div>
+ <div class="trace closedoff"></div>
+ </div>
+ <div id="common" class="api" data-api="">
+ <div class="name">{COMMON}</div>
+ <div class="desc">Settings without apis</div>
+ <hr>
+ <div class="verbosity">placeholder</div>
+ </div>
+ <div id="apis">
+ </div>
+ </div>
+ <div id="logmsg-box">
+ <div id="disconnect" class="x-button">Disconnect</div>
+ <div id="droptracevts" class="x-button">Clear traces</div>
+ <div id="expert-pane" class="closed">
+ <div class="opclo"></div>
+ <div class="expert">{EXPERT}</div>
+ <div class="closedoff">
+ <div id="stopmsgs" class="x-button">Stop logs</div>
+ <div id="dropmsgs" class="x-button">Clear logs</div>
+ <div id="logmsgs"></div>
+ </div>
+ </div>
+ </div>
+ <div id="trace-events">
+ </div>
+ </div>
+ </div>
+
+<!-- template for APIS -->
+ <template id="t-api">
+ <div class="api closed" data-api="">
+ <div class="opclo"></div>
+ <div class="name"></div>
+ <div class="desc"></div>
+ <div class="closedoff">
+ <hr>
+ <div class="verbosity">placeholder</div>
+ <hr>
+ <div class="closed">
+ <div class="opclo"></div>
+ <div class="closedon">verbs...</div>
+ <div class="verbs closedoff"></div>
+ </div>
+ <div class="closed">
+ <div class="opclo"></div>
+ <div class="closedon">traces...</div>
+ <div class="trace closedoff"></div>
+ </div>
+ </div>
+ </div>
+ </template>
+
+<!-- template for VERBS of APIS -->
+ <template id="t-verb">
+ <div class="verb" data-verb="">
+ <div class="vdsc">
+ <span class="name"></span>
+ <span class="colon">:</span>
+ <span class="desc"></span>
+ </div>
+ <div class="perm"></div>
+ </div>
+ </template>
+
+<!-- template for ERRORS -->
+ <template id="t-logmsg">
+ <div class="logmsg">
+ <div class="close"></div>
+ <div class="tag"></div>
+ <div class="content"></div>
+ </div>
+ </template>
+
+<!-- template for VERBOSITY -->
+ <template id="t-verbosity">
+ <div class="verbosity">
+ <span>Verbosity:</span>
+ <select class="select">
+ <option value="error">error</option>
+ <option value="notice">notice</option>
+ <option value="info">info</option>
+ <option value="debug">debug</option>
+ </select>
+ </div>
+ </template>
+
+<!-- template for TRACE -->
+ <template id="t-trace">
+ <div class="trace closedoff">
+ <div class="trace-box request" data-trace="request">
+ <div class="trace-title">trace requests: <a target="_blank" href="http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-binder/afb-binding-references.html#functions-of-class-afbreq">(doc)</a></div>
+ <div class="trace-item"><input type="radio" value="no" checked>no</input></div>
+ <div class="trace-item"><input type="radio" value="common">common</input></div>
+ <div class="trace-item"><input type="radio" value="extra">extra</input></div>
+ <div class="trace-item"><input type="radio" value="all">all</input></div>
+ </div>
+ <div class="trace-box service" data-trace="service">
+ <div class="trace-title">trace service call: <a target="_blank" href="http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-binder/afb-binding-references.html#functions-of-class-afbservice">(doc)</a></div>
+ <div class="trace-item"><input type="radio" value="no" checked>no</input></div>
+ <div class="trace-item"><input type="radio" value="all">all</input></div>
+ </div>
+ <div class="trace-box daemon" data-trace="daemon">
+ <div class="trace-title">trace daemon: <a target="_blank" href="http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-binder/afb-binding-references.html#functions-of-class-afbdaemon">(doc)</a></div>
+ <div class="trace-item"><input type="radio" value="no" checked>no</input></div>
+ <div class="trace-item"><input type="radio" value="common">common</input></div>
+ <div class="trace-item"><input type="radio" value="extra">extra</input></div>
+ <div class="trace-item"><input type="radio" value="all">all</input></div>
+ </div>
+ <div class="trace-box event" data-trace="event">
+ <div class="trace-title">trace events: <a target="_blank" href="http://docs.automotivelinux.org/docs/apis_services/en/dev/reference/af-binder/afb-binding-references.html#functions-of-class-afbevent">(doc)</a></div>
+ <div class="trace-item"><input type="radio" value="no" checked>no</input></div>
+ <div class="trace-item"><input type="radio" value="common">common</input></div>
+ <div class="trace-item"><input type="radio" value="extra">extra</input></div>
+ <div class="trace-item"><input type="radio" value="all">all</input></div>
+ </div>
+ </div>
+ </template>
+
+<!-- template for EVENTS -->
+ <template id="t-traceevent">
+ <div class="traceevent closed">
+ <div class="close"></div>
+ <div class="time"></div>
+ <div class="tag"></div>
+ <div class="content"></div>
+ </div>
+ </template>
+
+
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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ 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 '<span class="json ' + cls + '">' + match + '</span>';
+ });
+}
+
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])<u?i=a+1:o=a}return i},m.indexOf=r(1,m.findIndex,m.sortedIndex),m.lastIndexOf=r(-1,m.findLastIndex),m.range=function(n,t,r){null==t&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>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<arguments.length;)i.push(arguments[e++]);return E(n,r,this,this,i)};return r},m.bindAll=function(n){var t,r,e=arguments.length;if(1>=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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},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
--- /dev/null
+++ b/test/monitoring/up.png
Binary files differ