diff --git a/CMakeLists.txt b/CMakeLists.txt
index 99ea9ba8be0682978f026c7ce4b1eba3e170bb64..8efdb08041507f1d9382de158dfbe0eb93f6b30b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -748,7 +748,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 	#
 	# Helper function for adding a test app.
 	#
-	macro(create_test_app TEST_NAME MAIN_SRC S2 S3 S4 S5)
+	macro(create_test_app TEST_NAME MAIN_SRC S2 S3 S4 S5 S6)
 
 		set(TEST_SRCS ${MAIN_SRC})
 		set(TEST_HDR)
@@ -768,6 +768,10 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		else()
 			list(APPEND TEST_SRCS ${S5})
 		endif()
+		if ("${S6}" STREQUAL "")
+		else()
+			list(APPEND TEST_SRCS ${S6})
+		endif()
 		if (WIN32)
 			list(APPEND TEST_SRCS
 				${WIN32_HELPERS_PATH}/getopt.c
@@ -829,12 +833,14 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 				"test-server/test-server-http.c"
 				"test-server/test-server-dumb-increment.c"
 				"test-server/test-server-mirror.c"
+				"test-server/test-server-status.c"
 				"test-server/test-server-echogen.c")
 			if (UNIX)
 				create_test_app(test-fuzxy "test-server/fuzxy.c"
 					""
 					""
 					""
+					""
 					"")
 			endif()
 			if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")))
@@ -843,6 +849,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 					"test-server/test-server-http.c"
 					"test-server/test-server-dumb-increment.c"
 					"test-server/test-server-mirror.c"
+					"test-server/test-server-status.c"
 					"test-server/test-server-echogen.c")
 			endif()
 			if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
@@ -852,6 +859,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 					"test-server/test-server-http.c"
 					"test-server/test-server-dumb-increment.c"
 					"test-server/test-server-mirror.c"
+					"test-server/test-server-status.c"
 					"test-server/test-server-echogen.c")
 			endif()
 			if (UNIX AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
@@ -861,6 +869,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 					"test-server/test-server-http.c"
 					"test-server/test-server-dumb-increment.c"
 					"test-server/test-server-mirror.c"
+					"test-server/test-server-status.c"
 					"test-server/test-server-echogen.c")
 			endif()
 		endif()
@@ -873,6 +882,7 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 				"test-server/test-server-http.c"
 				"test-server/test-server-dumb-increment.c"
 				"test-server/test-server-mirror.c"
+				"test-server/test-server-status.c"
 				"test-server/test-server-echogen.c")
 			# Set defines for this executable only.
 			set_property(
@@ -978,27 +988,27 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		# test-client
 		#
 		if (NOT LWS_WITHOUT_TEST_CLIENT)
-			create_test_app(test-client "test-server/test-client.c" "" "" "" "")
+			create_test_app(test-client "test-server/test-client.c" "" "" "" "" "")
 		endif()
 
 		#
 		# test-fraggle
 		#
 		if (NOT LWS_WITHOUT_TEST_FRAGGLE)
-			create_test_app(test-fraggle "test-server/test-fraggle.c" "" "" "" "")
+			create_test_app(test-fraggle "test-server/test-fraggle.c" "" "" "" "" "")
 		endif()
 
 		#
 		# test-ping
 		#
 		if (NOT LWS_WITHOUT_TEST_PING)
-			create_test_app(test-ping "test-server/test-ping.c" "" "" "" "")
+			create_test_app(test-ping "test-server/test-ping.c" "" "" "" "" "")
 		endif()
 		#
 		# test-echo
 		#
 		if (NOT LWS_WITHOUT_TEST_ECHO)
-			create_test_app(test-echo "test-server/test-echo.c" "" "" "" "")
+			create_test_app(test-echo "test-server/test-echo.c" "" "" "" "" "")
 		endif()
 
 	endif(NOT LWS_WITHOUT_CLIENT)
diff --git a/changelog b/changelog
index f068cf2aa9595a1d60f9a448d0d425391dac5cde..9267d20ce3093f36072cd06cbcedd794561daca7 100644
--- a/changelog
+++ b/changelog
@@ -40,6 +40,9 @@ with systemd
 5) MINOR example systemd .service file now provided for test server
 (not installed by default)
 
+6) test server html is updated with tabs and a new live server monitoring
+feature.  Input sanitization added to the js.
+
 User API additions
 ------------------
 
diff --git a/test-server/test-server-status.c b/test-server/test-server-status.c
new file mode 100644
index 0000000000000000000000000000000000000000..5fb599a27fe23b240af5e66f2cc8d4177d29c47d
--- /dev/null
+++ b/test-server/test-server-status.c
@@ -0,0 +1,164 @@
+/*
+ * libwebsockets-test-server - libwebsockets test implementation
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * The test apps are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "test-server.h"
+#include <time.h>
+
+static unsigned char server_info[1024];
+static int server_info_len;
+static int current;
+static char cache[16384];
+static int cache_len;
+static struct per_session_data__lws_status *list;
+static int live_wsi;
+
+
+static void
+update_status(struct lws *wsi, struct per_session_data__lws_status *pss)
+{
+	struct per_session_data__lws_status **pp = &list;
+	int subsequent = 0;
+	char *p = cache + LWS_PRE, *start = p;
+	char date[128];
+	time_t t;
+	struct tm *ptm;
+#ifndef WIN32
+	struct tm tm;
+#endif
+
+	p += snprintf(p, 512, " { %s, \"wsi\":\"%d\", \"conns\":[",
+		     server_info, live_wsi);
+
+	/* render the list */
+	while (*pp) {
+		t = (*pp)->tv_established.tv_sec;
+#ifdef WIN32
+		ptm = localtime(&t);
+		if (!ptm)
+#else
+		ptm = &tm;
+		if (!localtime_r(&t, &tm))
+#endif
+			strcpy(date, "unknown");
+		else
+			strftime(date, sizeof(date), "%F %H:%M %Z", ptm);
+		if ((p - start) > (sizeof(cache) - 512))
+			break;
+		if (subsequent)
+			*p++ = ',';
+		subsequent = 1;
+		p += snprintf(p, sizeof(cache) - (p - start) - 1,
+				"{\"peer\":\"%s\",\"time\":\"%s\","
+				"\"ua\":\"%s\"}",
+			     (*pp)->ip, date, (*pp)->user_agent);
+		pp = &((*pp)->list);
+	}
+
+	p += sprintf(p, "]}");
+	cache_len = p - start;
+	lwsl_err("cache_len %d\n", cache_len);
+	*p = '\0';
+
+	/* since we changed the list, increment the 'version' */
+	current++;
+	/* update everyone */
+	lws_callback_on_writable_all_protocol(lws_get_context(wsi),
+					      lws_get_protocol(wsi));
+}
+
+
+/* lws-status protocol */
+
+int
+callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
+		    void *user, void *in, size_t len)
+{
+	struct per_session_data__lws_status *pss =
+			(struct per_session_data__lws_status *)user,
+			**pp;
+	char name[128], rip[128];
+	int m;
+
+	switch (reason) {
+
+	case LWS_CALLBACK_PROTOCOL_INIT:
+		/*
+		 * Prepare the static server info
+		 */
+		server_info_len = sprintf((char *)server_info,
+					  "\"version\":\"%s\","
+					  " \"hostname\":\"%s\"",
+					  lws_get_library_version(),
+					  lws_canonical_hostname(
+							lws_get_context(wsi)));
+		break;
+
+	case LWS_CALLBACK_ESTABLISHED:
+		/*
+		 * we keep a linked list of live pss, so we can walk it
+		 */
+		pss->last = 0;
+		pss->list = list;
+		list = pss;
+		live_wsi++;
+		lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name,
+				       sizeof(name), rip, sizeof(rip));
+		sprintf(pss->ip, "%s (%s)", name, rip);
+		gettimeofday(&pss->tv_established, NULL);
+		strcpy(pss->user_agent, "unknown");
+		lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent),
+			     WSI_TOKEN_HTTP_USER_AGENT);
+		update_status(wsi, pss);
+		break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+		m = lws_write(wsi, (unsigned char *)cache + LWS_PRE, cache_len,
+			      LWS_WRITE_TEXT);
+		if (m < server_info_len) {
+			lwsl_err("ERROR %d writing to di socket\n", m);
+			return -1;
+		}
+		break;
+
+	case LWS_CALLBACK_CLOSED:
+		/*
+		 * remove ourselves from live pss list
+		 */
+		lwsl_err("CLOSING pss %p ********\n", pss);
+
+		pp = &list;
+		while (*pp) {
+			if (*pp == pss) {
+				*pp = pss->list;
+				pss->list = NULL;
+				live_wsi--;
+				break;
+			}
+			pp = &((*pp)->list);
+		}
+
+		update_status(wsi, pss);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
diff --git a/test-server/test-server.c b/test-server/test-server.c
index 6062383e7acdf4ab5e287130f2706cabb8fe496c..09f7046472fc8ce8dfa6936cab054f652d1f5c23 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -68,6 +68,7 @@ enum demo_protocols {
 	PROTOCOL_DUMB_INCREMENT,
 	PROTOCOL_LWS_MIRROR,
 	PROTOCOL_LWS_ECHOGEN,
+	PROTOCOL_LWS_STATUS,
 
 	/* always last */
 	DEMO_PROTOCOL_COUNT
@@ -102,6 +103,12 @@ static struct lws_protocols protocols[] = {
 		sizeof(struct per_session_data__echogen),
 		128,
 	},
+	{
+		"lws-status",
+		callback_lws_status,
+		sizeof(struct per_session_data__lws_status),
+		128,
+	},
 	{ NULL, NULL, 0, 0 } /* terminator */
 };
 
diff --git a/test-server/test-server.h b/test-server/test-server.h
index 00556514378db8fe45c228c78003ea512eeb79a3..cb69d500e4b4c2c4e4bcf007c83c70687799d33b 100644
--- a/test-server/test-server.h
+++ b/test-server/test-server.h
@@ -94,6 +94,16 @@ struct per_session_data__echogen {
 	int wr;
 };
 
+struct per_session_data__lws_status {
+	struct per_session_data__lws_status *list;
+	struct timeval tv_established;
+	int last;
+	char ip[270];
+	char user_agent[512];
+	const char *pos;
+	int len;
+};
+
 extern int
 callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 	      void *in, size_t len);
@@ -106,6 +116,10 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 extern int
 callback_lws_echogen(struct lws *wsi, enum lws_callback_reasons reason,
 			void *user, void *in, size_t len);
+extern int
+callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
+		    void *user, void *in, size_t len);
+
 
 extern void
 dump_handshake_info(struct lws *wsi);
diff --git a/test-server/test.html b/test-server/test.html
index afd5bc8497bd545d8e3457845b9aa9b17becd3b7..eff2ba19d15a7c9075b1600e3ca6a1b89779607c 100644
--- a/test-server/test.html
+++ b/test-server/test.html
@@ -4,7 +4,7 @@
  <meta charset=utf-8 http-equiv="Content-Language" content="en"/>
  <title>Minimal Websocket test app</title>
 <style type="text/css">
-	div.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; }
+	span.title { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#000000; }
 	.browser { font-size:18pt; font: Arial; font-weight:normal; text-align:center; color:#ffff00; vertical-align:middle; text-align:center; background:#d0b070; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px;}
 	.group2 { vertical-align:middle;
 		text-align:center;
@@ -20,6 +20,20 @@
 		-moz-border-radius:10px;
 		border-radius:10px;
 		color:#404000; }
+	td.wsstatus { vertical-align:middle; width:200px; height:50px;
+		text-align:center;
+		background:#f0f0c0; padding:6px;
+		-webkit-border-radius:8px;
+		-moz-border-radius:8px;
+		border-radius:8px;
+		color:#404000; }
+	td.l { vertical-align:middle;
+		text-align:center;
+		background:#d0d0b0; 
+		padding:3px; 
+		-webkit-border-radius:3px; 
+		-moz-border-radius:3px;
+		border-radius:3px; }
 	.content { vertical-align:top; text-align:center; background:#fffff0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
 	.canvas { vertical-align:top; text-align:center; background:#efefd0; padding:12px; -webkit-border-radius:10px; -moz-border-radius:10px; border-radius:10px; }
 .tabs {
@@ -94,19 +108,14 @@ have the test server send a big picture by http.
        
        <div class="content">
         <div id="dumb" class="group2">
-	  <table>
-	   <tr>
-	   	    <td width=200px id=wsdi_statustd align=center class="explain">
-	     <div id=wsdi_status>Not initialized</div>
-	    </td>
-
-	    <td align=center><div id=number> </div></td>
-	    <td align=center>
-	     <input type=button id=offset value="Reset counter" onclick="reset();" >
-	    </td>
-	    </tr>
+         <table>
+          <tr>
+	   <td id=wsdi_statustd align=center class="wsstatus">
+	     <span id=wsdi_status>Websocket connection not initialized</span></td>
+           <td><span class="title">dumb increment-protocol</span></td>
+	   </tr>
 	   <tr>
-	   <td class="explain" colspan=3>
+	   <td class="explain" colspan=2>
 The incrementing number is coming from the server at 20Hz and is individual for
 each connection to the server... try opening a second browser window.
 <br/><br/>
@@ -114,6 +123,12 @@ The button sends a message over the websocket link to ask the server
 to zero just this connection's number.
 	   </td>
 	  </tr>
+	   <tr>
+	    <td align=center><div id=number style="font-size:120%;"> </div></td>
+	    <td align=center>
+	     <input type=button id=offset value="Reset counter" onclick="reset();" >
+	    </td>
+	    </tr>
 	 </table>
 	</div>
        </div> 
@@ -127,8 +142,15 @@ to zero just this connection's number.
      <div id="mirror" class="group2">
       <table>
        <tr>
-        <td colspan="3">
-         <div class="title">libwebsockets "lws-mirror-protocol"</div>
+	 <td colspan=1 id=wslm_statustd align=center class="wsstatus">
+	 <span id=wslm_status>Websocket connection not initialized</span>
+	</td>
+        <td>
+         <span class="title">lws-mirror-protocol</span>
+        </td>
+       </tr>
+       <tr>
+       <td colspan=2>
          <div class="explain">
 Use the mouse to draw on the canvas below -- all other browser windows open
 on this page see your drawing in realtime and you can see any of theirs as
@@ -144,20 +166,16 @@ run.
         </td>
        </tr>
        <tr>
-	<td>Drawing color:
+	<td colspan=2>Drawing color:
 	  <select id="color" onchange="update_color();">
 		<option value=#000000>Black</option>
 		<option value=#0000ff>Blue</option>
 		<option value=#20ff20>Green</option>
 		<option value=#802020>Dark Red</option>
 	  </select>
-	</td>
-	 <td colspan=2 id=wslm_statustd align=center class="explain">
-	 <div id=wslm_status>Not initialized</div>
-	</td>
        </tr>
        <tr>
-	 <td colspan=3 width=500 height=320>
+	 <td colspan=2 width=500 height=320>
 		<div id="wslm_drawing" style="background:white"></div>
 	</td>
        </tr>
@@ -174,26 +192,61 @@ run.
 <div id="ot" class="group2">
       <table>
        <tr>
-        <td colspan=3>
-<div class="title">libwebsockets "open and close testing"</div>
+        <td>
+
 		</td></tr>
-		<tr>
-		<td align=center><input type=button id=ot_open_btn value="Open" onclick="ot_open();" ></td>
-		<td align=center><input type=button id=ot_close_btn disabled value="Close" onclick="ot_close();" ></td>
-		<td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" onclick="ot_req_close();" ></td>
+		<tr><td id=ot_statustd align=center class="wsstatus">
+		 <span id=ot_status>Websocket connection not initialized</span>
+		</td>
+		<td colspan=2><span class="title">Open and close testing</span></td>
 		</tr>
-		<tr><td id=ot_statustd align=center class="explain">
-		 <div id=ot_status>Not initialized</div>
-		</td>	
-<td class="explain" colspan=2>
+		<tr>	
+<td class="explain" colspan=3 style="padding:3">
 To help with open and close testing, you can open and close a connection by hand using
  the buttons.<br>
  "<b>Close</b>" closes the connection from the browser with code 3000
   and reason 'Bye!".<br>
  "<b>Request Server Close</b>" sends a message asking the server to
 initiate the close, which it does with code 1001 and reason "Seeya".
-</td></tr></table>
+</td></tr>
+		<tr>
+		<td align=center><input type=button id=ot_open_btn value="Open" onclick="ot_open();" ></td>
+		<td align=center><input type=button id=ot_close_btn disabled value="Close" onclick="ot_close();" ></td>
+		<td align=center><input type=button id=ot_req_close_btn disabled value="Request Server Close" onclick="ot_req_close();" ></td>
+		</tr>
+
+</table>
 
+</div>
+       </div> 
+   </div>
+   
+    <div class="tab">
+       <input type="radio" id="tab-4" name="tab-group-1">
+       <label for="tab-4">Server info</label>
+
+       <div class="content">
+<div id="ot" class="group2">
+      <table>
+       <tr>
+	<td id=s_statustd align=center class="wsstatus">
+	 <div id=s_status>Websocket connection not initialized</div>
+	</td>
+	        <td colspan=1>
+<span class="title">Server Info</span>
+	</td>
+	</tr><tr>
+<td class="explain" colspan=2>
+This information is sent by the server over a ws[s] link and updated live
+whenever the information changes server-side.
+</td></tr>
+	<tr>
+	<td align=center colspan=2><div id=servinfo></</div></td>
+	</tr>
+	<tr>
+	<td align=center colspan=2><div id=conninfo style="border : solid 2px #e0d040; padding : 4px; width : 500px; height : 350px; overflow : auto; "></</div></td>
+	</tr>
+</table>
 </div>
        </div> 
    </div>
@@ -208,6 +261,19 @@ Join the mailing list: <a href="https://libwebsockets.org/mailman/listinfo/libwe
 
 <script>
 
+/*
+ * We display untrusted stuff in html context... reject anything
+ * that has HTML stuff in it
+ */
+
+function san(s)
+{
+	if (s.search("<") != -1)
+		return "invalid string";
+	
+	return s;
+}
+
 /* BrowserDetect came from http://www.quirksmode.org/js/detect.html */
 
 var BrowserDetect = {
@@ -378,7 +444,9 @@ document.getElementById("number").textContent = get_appropriate_ws_url();
 	try {
 		socket_di.onopen = function() {
 			document.getElementById("wsdi_statustd").style.backgroundColor = "#40ff40";
-			document.getElementById("wsdi_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions;
+			document.getElementById("wsdi_status").innerHTML =
+				" <b>websocket connection opened</b><br>" +
+				san(socket_di.extensions);
 		} 
 
 		socket_di.onmessage =function got_packet(msg) {
@@ -392,6 +460,55 @@ document.getElementById("number").textContent = get_appropriate_ws_url();
 	} catch(exception) {
 		alert('<p>Error' + exception);  
 	}
+	
+	var socket_status, jso, s;
+
+	if (typeof MozWebSocket != "undefined") {
+		socket_status = new MozWebSocket(get_appropriate_ws_url(),
+				   "lws-status");
+	} else {
+		socket_status = new WebSocket(get_appropriate_ws_url(),
+				   "lws-status");
+	}
+
+
+	try {
+		socket_status.onopen = function() {
+			document.getElementById("s_statustd").style.backgroundColor = "#40ff40";
+			document.getElementById("s_status").innerHTML =
+				" <b>websocket connection opened</b><br>" +
+				san(socket_di.extensions);
+		} 
+
+		socket_status.onmessage =function got_packet(msg) {
+			jso = JSON.parse(msg.data);
+			
+			document.getElementById("servinfo").innerHTML = 
+				"<table><tr><td class=l>Build info</td><td>"+
+					san(jso.version) + "</td></tr>" +
+					"<tr><td class=l>Server info</td><td>" +
+					san(jso.hostname) + "</td></tr>" +
+					"</table>";
+			s="<table>";
+			var n;
+			for (n = 0; n < jso.conns.length; n++)
+				s = s + "<tr><td class=l>client " + (n + 1) +
+				"</td><td><b>" + san(jso.conns[n].peer) +
+				"</b><br>" + san(jso.conns[n].time) +
+				"<br>" + san(jso.conns[n].ua) +
+				"</td></tr>";
+			s = s + "</table>";
+			
+			document.getElementById("conninfo").innerHTML = s;
+		} 
+
+		socket_status.onclose = function(){
+			document.getElementById("s_statustd").style.backgroundColor = "#ff4040";
+			document.getElementById("s_status").textContent = " websocket connection CLOSED ";
+		}
+	} catch(exception) {
+		alert('<p>Error' + exception);  
+	}
 
 function reset() {
 	socket_di.send("reset\n");
@@ -410,7 +527,7 @@ function ot_open() {
 	try {
 		socket_ot.onopen = function() {
 			document.getElementById("ot_statustd").style.backgroundColor = "#40ff40";
-			document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions;
+			document.getElementById("ot_status").innerHTML = " <b>websocket connection opened</b><br>" + san(socket_di.extensions);
 			document.getElementById("ot_open_btn").disabled = true;
 			document.getElementById("ot_close_btn").disabled = false;
 			document.getElementById("ot_req_close_btn").disabled = false;
@@ -460,7 +577,9 @@ function ot_req_close() {
 	try {
 		socket_lm.onopen = function() {
 			document.getElementById("wslm_statustd").style.backgroundColor = "#40ff40";
-			document.getElementById("wslm_status").innerHTML = " <b>websocket connection opened</b><br>" + socket_di.extensions;
+			document.getElementById("wslm_status").innerHTML =
+				" <b>websocket connection opened</b><br>" +
+				san(socket_di.extensions);
 		} 
 
 		socket_lm.onmessage =function got_packet(msg) {