monitor: Test page for monitoring
authorJosé Bollo <jose.bollo@iot.bzh>
Tue, 22 Aug 2017 07:21:43 +0000 (09:21 +0200)
committerJosé Bollo <jose.bollo@iot.bzh>
Thu, 24 Aug 2017 17:06:07 +0000 (19:06 +0200)
This is a test page for showing basic monitoring
use.

Signed-off-by: José Bollo <jose.bollo@iot.bzh>
13 files changed:
test/AFB.js
test/index.html
test/iot-bzh-logo-small.png [new file with mode: 0644]
test/monitoring/AFB.js [new file with mode: 0644]
test/monitoring/content-background-car-wide.png [new file with mode: 0644]
test/monitoring/cross.png [new file with mode: 0644]
test/monitoring/down.png [new file with mode: 0644]
test/monitoring/favicon.ico [new file with mode: 0644]
test/monitoring/monitor.css [new file with mode: 0644]
test/monitoring/monitor.html [new file with mode: 0644]
test/monitoring/monitor.js [new file with mode: 0644]
test/monitoring/underscore-min.js [new file with mode: 0644]
test/monitoring/up.png [new file with mode: 0644]

index c77e5e6..88da4d5 100644 (file)
@@ -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) {
index 014ae46..6a8e220 100644 (file)
@@ -7,6 +7,9 @@
      <li><a href="hello-world.html">Hello World!</a>
      <li><a href="client-ctx.html">client context</a>
      <li><a href="sample-post.html">Sample post</a>
-     <li><a href="websock.html">websockets</a>
      <li><a href="AFB.html">AFB.js</a>
+     <li><a href="monitoring/monitor.html" target="_blank">Monitoring</a>
+<!--
+     <li><a href="websock.html">websockets</a>
      <li><a href="angular.html">AfbAngular.js</a>
+-->
diff --git a/test/iot-bzh-logo-small.png b/test/iot-bzh-logo-small.png
new file mode 100644 (file)
index 0000000..2c3b2ae
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 (file)
index 0000000..88da4d5
--- /dev/null
@@ -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 (file)
index 0000000..073d1b9
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 (file)
index 0000000..24453e7
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 (file)
index 0000000..c399895
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 (file)
index 0000000..eeb7ab7
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 (file)
index 0000000..ff7fd30
--- /dev/null
@@ -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 (file)
index 0000000..1a1d49c
--- /dev/null
@@ -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 (file)
index 0000000..c4f24a0
--- /dev/null
@@ -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 (file)
index 0000000..3c3eec0
--- /dev/null
@@ -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 (file)
index 0000000..aeec342
Binary files /dev/null and b/test/monitoring/up.png differ