You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by el...@apache.org on 2017/01/17 23:01:53 UTC

[1/4] incubator-trafficcontrol git commit: Move TM2 static GUI files to static/

Repository: incubator-trafficcontrol
Updated Branches:
  refs/heads/master 8833d3e2a -> 03610c8fb


Move TM2 static GUI files to static/


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/6a92f32f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/6a92f32f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/6a92f32f

Branch: refs/heads/master
Commit: 6a92f32f09a522b750e9889253750604a6d1716e
Parents: bb81c13
Author: Robert Butts <ro...@gmail.com>
Authored: Tue Jan 17 15:24:50 2017 -0700
Committer: Jeff Elsloo <je...@cable.comcast.com>
Committed: Tue Jan 17 16:00:43 2017 -0700

----------------------------------------------------------------------
 .../experimental/build/traffic_monitor.spec     |   4 +-
 .../experimental/traffic_monitor/index.html     | 521 -------------------
 .../experimental/traffic_monitor/sorttable.js   | 495 ------------------
 .../traffic_monitor/static/index.html           | 521 +++++++++++++++++++
 .../traffic_monitor/static/sorttable.js         | 495 ++++++++++++++++++
 5 files changed, 1018 insertions(+), 1018 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/6a92f32f/traffic_monitor/experimental/build/traffic_monitor.spec
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/build/traffic_monitor.spec b/traffic_monitor/experimental/build/traffic_monitor.spec
index dd185ce..55f02e4 100644
--- a/traffic_monitor/experimental/build/traffic_monitor.spec
+++ b/traffic_monitor/experimental/build/traffic_monitor.spec
@@ -81,8 +81,8 @@ mkdir -p "${RPM_BUILD_ROOT}"/etc/logrotate.d
 
 src=src/github.com/apache/incubator-trafficcontrol/traffic_monitor/experimental
 cp -p "$src"/traffic_monitor/traffic_monitor     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin/traffic_monitor
-cp  "$src"/traffic_monitor/index.html     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
-cp  "$src"/traffic_monitor/sorttable.js     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/sorttable.js
+cp  "$src"/traffic_monitor/static/index.html     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
+cp  "$src"/traffic_monitor/static/sorttable.js     "${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/sorttable.js
 cp "$src"/conf/traffic_ops.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_ops.cfg
 cp "$src"/conf/traffic_monitor.cfg        "${RPM_BUILD_ROOT}"/opt/traffic_monitor/conf/traffic_monitor.cfg
 cp "$src"/build/traffic_monitor.init       "${RPM_BUILD_ROOT}"/etc/init.d/traffic_monitor

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/6a92f32f/traffic_monitor/experimental/traffic_monitor/index.html
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/index.html b/traffic_monitor/experimental/traffic_monitor/index.html
deleted file mode 100644
index 5c02902..0000000
--- a/traffic_monitor/experimental/traffic_monitor/index.html
+++ /dev/null
@@ -1,521 +0,0 @@
-<!DOCTYPE html>
-
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-  http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-
-<html>
-	<head>
-		<!-- <script src="sorttable.js"></script> -->
-		<meta charset="UTF-8">
-		<title>Traffic Monitor</title>
-		<style>
-		 body {font-family: "Lato", sans-serif;}
-
-		 ul.tab {
-			 list-style-type: none;
-			 margin: 0;
-			 padding: 0;
-			 overflow: hidden;
-			 border: 1px solid #ccc;
-			 background-color: #f1f1f1;
-		 }
-
-		 ul.tab li {float: left;}
-
-		 ul.tab li a {
-			 display: inline-block;
-			 color: black;
-			 text-align: center;
-			 padding: 14px 16px;
-			 text-decoration: none;
-			 transition: 0.3s;
-			 font-size: 17px;
-		 }
-
-		 ul.tab li a:hover {
-			 background-color: #cfd;
-		 }
-
-		 .tab-header-selected {
-			 background-color: #adb;
-		 }
-
-		 .tabcontent {
-			 display: none;
-			 padding: 6px 12px;
-			 border: 1px solid #ccc;
-			 border-top: none;
-		 }
-
-		 table, th, td {
-			 border: 0px solid black;
-		 }
-
-		 table {
-			 border-collapse: separate;
-			 border-spacing: 0px 0;
-		 }
-
-		 th, td {
-			 padding:5px 50px 5px 5px
-		 }
-
-		 tr.stripes:nth-child(even) {
-			 background: #dfe
-		 }
-		 tr.stripes:nth-child(odd) {
-			 background: #fff
-		 }
-
-		 li.endpoint {
-			 margin: 4px 0;
-		 }
-
-		 ul {
-			 list-style: none;
-		 }
-
-		 #top-bar {
-			 border-collapse: collapse;
-			 border-color: #adb;;
-			 border-width: 0px 0px 1px 0px;
-			 padding-bottom: 10px;
-		 }
-
-		 .links {
-			 display: table;
-		 }
-		 .links ul {
-			 display: table-cell;
-			 vertical-align: top;
-		 }
-
-		 #bottom-bar {
-			 position: relative;
-			 z-index: 100;
-			 bottom: 0;
-			 left: 0;
-			 width: 100%;
-			 height: 14px;
-			 padding: 6px 20px 4px 0px;
-			 text-align: right;
-			 font-size: 14px;
-		 }
-
-		 #bottom-bar-vanillajs {
-			 padding-top: 10px;
-		 }
-
-		 .error {
-			 background-color: #f00;
-		 }
-		 .warning {
-			 background-color: #f80;
-		 }
-		</style>
-		<script>
-		 function init() {
-			 openTab('cache-states-content');
-			 getTopBar();
-			 setInterval(getCacheCount, 4755);
-			 setInterval(getCacheAvailableCount, 4800);
-			 setInterval(getBandwidth, 4621);
-			 setInterval(getBandwidthCapacity, 4591);
-			 setInterval(getCacheDownCount, 4832);
-			 setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
-			 setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
-			 setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
-			 setInterval(getCacheStatuses, 5009);
-			 setInterval(getDsStats, 4003);
-		 }
-
-		 // source: http://stackoverflow.com/a/2901298/292623
-		 function numberStrWithCommas(x) {
-			 return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
-		 }
-
-		 function openTab(tabName) {
-			 var i, x, tablinks;
-			 x = document.getElementsByClassName("tabcontent");
-			 for (i = 0; i < x.length; i++) {
-				 x[i].style.display = "none";
-			 }
-			 tablinks = document.getElementsByClassName("tablinks");
-			 for (i = 0; i < x.length; i++) {
-				 tablinks[i].className = tablinks[i].className.replace(" tab-selected", "");
-			 }
-
-			 tabheaders = document.getElementsByClassName("tab-header");
-			 for (i = 0; i < tabheaders.length; i++) {
-				 tabheaders[i].className = tabheaders[i].className.replace(" tab-header-selected", "");
-			 }
-
-			 document.getElementById(tabName).style.display = "block";
-			 document.getElementById(tabName).className += " tab-selected";
-			 document.getElementById(tabName + "-tab").className += " tab-header-selected";
-		 }
-
-		 function getCacheCount() {
-			 ajax("/api/cache-count", function(r) {
-				 document.getElementById("cache-count").innerHTML = r;
-			 });
-		 }
-
-		 function getCacheAvailableCount() {
-			 ajax("/api/cache-available-count", function(r) {
-				 document.getElementById("cache-available").innerHTML = r;
-			 });
-		 }
-
-		 function getBandwidth() {
-			 ajax("/api/bandwidth-kbps", function(r) {
-				 document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
-			 });
-		 }
-
-		 function getBandwidthCapacity() {
-			 ajax("/api/bandwidth-capacity-kbps", function(r) {
-				 document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
-			 });
-		 }
-
-		 function getCacheDownCount() {
-			 ajax("/api/cache-down-count", function(r) {
-				 document.getElementById("cache-down").innerHTML = r;
-			 });
-		 }
-
-		 function getVersion() {
-			 ajax("/api/version", function(r) {
-				 document.getElementById("version").innerHTML = r;
-				 document.title = r;
-			 });
-		 }
-
-		 function getTrafficOpsUri() {
-			 ajax("/api/traffic-ops-uri", function(r) {
-				 document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
-				 ajax("/publish/ConfigDoc", function(r) {
-					 var j = JSON.parse(r);
-					 document.getElementById("source-uri").innerHTML += "/" + j.cdnName;
-				 });
-			 });
-		 }
-
-		 var lastEvent = 0;
-		 function getEvents() {
-			 /// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest 
-			 ajax("/publish/EventLog", function(r) {
-				 var jdata = JSON.parse(r);
-				 for (i = jdata.events.length - 1; i >= 0; i--) {
-					 var event = jdata.events[i];
-					 if (event.index <= lastEvent) {
-						 continue;
-					 }
-					 lastEvent = event.index
-					 var row = document.getElementById("event-log").insertRow(1); //document.createElement("tr");
-					 row.classList.add("stripes");
-					 row.insertCell(0).textContent = event.name;
-					 row.insertCell(1).textContent = event.type;
-					 row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
-					 if(event.isAvailable) {
-						 row.classList.add("stripes");
-						 row.classList.remove("error");
-					 } else {
-						 row.classList.add("error");
-						 row.classList.remove("stripes");
-					 }
-					 row.insertCell(3).textContent = event.description;
-					 row.insertCell(4).textContent = new Date(event.time * 1000).toISOString();
-				 }
-			 });
-		 }
-
-		 function getCacheStates() {
-			 ajax("/api/cache-statuses", function(r) {
-				 var jdata = JSON.parse(r);
-				 var servers = Object.keys(jdata); //debug
-				 for (i = 0; i < servers.length; i++) {
-					 var server = servers[i];
-					 if (!document.getElementById("cache-states-" + server)) {
-						 var row = document.getElementById("cache-states").insertRow(1); //document.createElement("tr");
-						 row.classList.add("stripes");
-						 row.id = "cache-states-" + server
-						 row.insertCell(0).id = row.id + "-server";
-						 row.insertCell(1).id = row.id + "-type";
-						 row.insertCell(2).id = row.id + "-status";
-						 row.insertCell(3).id = row.id + "-load-average";
-						 row.insertCell(4).id = row.id + "-query-time";
-						 row.insertCell(5).id = row.id + "-health-time";
-						 row.insertCell(6).id = row.id + "-stat-time";
-						 row.insertCell(7).id = row.id + "-health-span";
-						 row.insertCell(8).id = row.id + "-stat-span";
-						 row.insertCell(9).id = row.id + "-bandwidth";
-						 row.insertCell(10).id = row.id + "-connection-count";
-						 document.getElementById(row.id + "-server").textContent = server;
-					 }
-
-					 /* \todo change to iterate over members, and make cells id constructed from these*/
-					 if (jdata[server].hasOwnProperty("type")) {
-						 document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
-					 }
-					 if (jdata[server].hasOwnProperty("status")) {
-						 document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
-						 var row = document.getElementById("cache-states-" + server);
-						 if (jdata[server].status.indexOf("ADMIN_DOWN") != -1) {
-							 row.classList.add("warning");
-							 row.classList.remove("error");
-							 row.classList.remove("stripes");
-						 } else if (jdata[server].status.indexOf(" available") == -1) {
-							 row.classList.add("error");
-							 row.classList.remove("warning");
-							 row.classList.remove("stripes");
-						 } else {
-							 row.classList.add("stripe");
-							 row.classList.remove("warning");
-							 row.classList.remove("error");
-						 }
-					 }
-					 if (jdata[server].hasOwnProperty("load_average")) {
-						 document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
-					 }
-					 if (jdata[server].hasOwnProperty("query_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("health_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("stat_time_ms")) {
-						 document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("health_span_ms")) {
-						 document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("stat_span_ms")) {
-						 document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
-					 }
-					 if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
-						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
-					 }
-					 if (jdata[server].hasOwnProperty("connection_count")) {
-						 document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
-					 }
-				 }
-			 })
-		 }
-
-		 var millisecondsInSecond = 1000;
-		 var kilobitsInGigabit = 1000000;
-		 var kilobitsInMegabit = 1000;
-
-		 // dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
-		 function dsDisplayFloat(f) {
-			 var s = "";
-			 if (f != 0.0) {
-				 s = f.toFixed(2);
-			 }
-			 return s
-		 }
-
-		 function getDsStats() {
-			 var now = Date.now();
-
-			 /// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
-			 ajax("/publish/DsStats", function(r) {
-				 var j = JSON.parse(r);
-				 var jds = j.deliveryService
-				 var deliveryServiceNames = Object.keys(jds); //debug
-				 for (i = 0; i < deliveryServiceNames.length; i++) {
-					 var deliveryService = deliveryServiceNames[i];
-
-					 if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
-						 var row = document.getElementById("deliveryservice-stats").insertRow(1); //document.createElement("tr");
-						 row.id = "deliveryservice-stats-" + deliveryService
-						 row.insertCell(0).id = row.id + "-delivery-service";
-						 row.insertCell(1).id = row.id + "-status";
-						 row.insertCell(2).id = row.id + "-caches-reporting";
-						 row.insertCell(3).id = row.id + "-bandwidth";
-						 row.insertCell(4).id = row.id + "-2xx";
-						 row.insertCell(5).id = row.id + "-3xx";
-						 row.insertCell(6).id = row.id + "-4xx";
-						 row.insertCell(7).id = row.id + "-5xx";
-						 row.insertCell(8).id = row.id + "-disabled-locations";
-						 document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
-					 }
-
-					 // \todo check that array has a member before dereferencing [0]
-					 if (jds[deliveryService].hasOwnProperty("isAvailable")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value ? "available" : "unavailable";
-					 }
-					 if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.kbps")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
-					 }
-					 if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
-						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
-					 }
-
-					 // \todo implement disabled locations
-
-					 var row = document.getElementById("deliveryservice-stats-" + deliveryService);
-					 if (jds[deliveryService]["isAvailable"][0].value) {
-						 row.classList.add("stripes");
-						 row.classList.remove("unavailable");
-					 } else {
-						 row.classList.add("unavailable");
-						 row.classList.remove("stripes");
-					 }
-				 }
-			 })
-		 }
-
-		 function getCacheStatuses() {
-			 getCacheCount();
-			 getCacheAvailableCount();
-			 getCacheDownCount();
-			 getCacheStates();
-		 }
-
-		 function getTopBar() {
-			 getVersion();
-			 getTrafficOpsUri();
-			 getCacheStatuses();
-		 }
-
-		 function ajax(endpoint, f) {
-			 var xhttp = new XMLHttpRequest();
-			 xhttp.onreadystatechange = function() {
-				 if (xhttp.readyState == 4 && xhttp.status == 200) {
-					 f(xhttp.responseText);
-				 }
-			 };
-			 xhttp.open("GET", endpoint, true);
-			 xhttp.send();
-		 }
-		</script>
-	</head>
-	<body onload="init()">
-
-		<table id="top-bar" width="100%">
-			<tr>
-				<td> Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </td>
-				<td> Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity">\u221e</span> gbps</td>
-				<td>Source: <span id="source-uri"></span></td>
-				<td><span id="version">traffic_monitor</span></td>
-			</tr>
-		</table>
-
-		<div class="links">
-			<ul>
-				<li class="endpoint"><a href="/publish/EventLog">EventLog</a></li>
-				<li class="endpoint"><a href="/publish/CacheStats">CacheStats</a></li>
-				<li class="endpoint"><a href="/publish/DsStats">DsStats</a></li>
-				<li class="endpoint"><a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a></li>
-				<li class="endpoint"><a href="/publish/CrConfig">CrConfig (json)</a></li>
-				<li class="endpoint"><a href="/publish/PeerStates">PeerStates</a></li>
-				<li class="endpoint"><a href="/publish/Stats">Stats</a></li>
-				<li class="endpoint"><a href="/publish/StatSummary">StatSummary</a></li>
-				<li class="endpoint"><a href="/publish/ConfigDoc">ConfigDoc</a></li>
-			</ul>
-
-			<ul>
-				<li class="endpoint"><a href="/api/cache-count">/api/cache-count</a></li>
-				<li class="endpoint"><a href="/api/cache-available-count">/api/cache-available-count</a></li>
-				<li class="endpoint"><a href="/api/cache-down-count">/api/cache-down-count</a></li>
-				<li class="endpoint"><a href="/api/version">/api/version</a></li>
-				<li class="endpoint"><a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a></li>
-				<li class="endpoint"><a href="/api/cache-statuses">/api/cache-statuses</a></li>
-				<li class="endpoint"><a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a></li>
-				<li class="endpoint"><a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a></li>
-			</ul>
-		</div>
-
-		<ul class="tab">
-			<li id="cache-states-content-tab" class="tab-header"><a href="#" onclick="openTab('cache-states-content')" class="tablinks">Cache States</a></li>
-			<li id="deliveryservice-stats-content-tab" class="tab-header"><a href="#" onclick="openTab('deliveryservice-stats-content')" class="tablinks">Deliveryservice States</a></li>
-			<li id="event-log-content-tab" class="tab-header"><a href="#" onclick="openTab('event-log-content')" class="tablinks">Event Log</a></li>
-		</ul>
-
-		<div id="cache-states-content" class="tabcontent">
-			<table id="cache-states" class="tab-grid sortable">
-				<tr>
-					<th>Server</th>
-					<th>Type</th>
-					<th>Status</th>
-					<th>Load Average</th>
-					<th>Query Time (ms)</th>
-					<th>Health Time (ms)</th>
-					<th>Stat Time (ms)</th>
-					<th>Health Span (ms)</th>
-					<th>Stat Span (ms)</th>
-					<th>Bandwidth (mbps)</th>
-					<th>Connection Count</th>
-				</tr>
-			</table>
-		</div>
-		<div id="deliveryservice-stats-content" class="tabcontent">
-			<table id="deliveryservice-stats" class="tab-grid sortable">
-				<tr>
-					<th>Delivery Service</th>
-					<th>Status</th>
-					<th>Caches Reporting/Available/Configured</th>
-					<th>Bandwidth (mbps)</th>
-					<th>2xx/sec</th>
-					<th>3xx/sec</th>
-					<th>4xx/sec</th>
-					<th>5xx/sec</th>
-					<th>Disabled Locations</th>
-				</tr>
-			</table>
-		</div>
-
-		<div id="event-log-content" class="tabcontent">
-			<table id="event-log" class="tab-grid sortable">
-				<tr>
-					<th>Name</th>
-					<th>Type</th>
-					<th>Status</th>
-					<th>Description</th>
-					<th id="event-log-last-header">Event Time</th>
-				</tr>
-			</table>
-		</div>
-
-		<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
-		<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
-		<a href="/">Refresh Server List</a>
-
-		<div id="bottom-bar">
-			Powered by <a id="bottom-bar-vanillajs" href="http://vanilla-js.com"><img border="0" alt="VanillaJS" src="http://vanilla-js.com/assets/button.png"></a>
-		</div>
-
-	</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/6a92f32f/traffic_monitor/experimental/traffic_monitor/sorttable.js
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/sorttable.js b/traffic_monitor/experimental/traffic_monitor/sorttable.js
deleted file mode 100644
index 38b0fc6..0000000
--- a/traffic_monitor/experimental/traffic_monitor/sorttable.js
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
-  SortTable
-  version 2
-  7th April 2007
-  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
-
-  Instructions:
-  Download this file
-  Add <script src="sorttable.js"></script> to your HTML
-  Add class="sortable" to any table you'd like to make sortable
-  Click on the headers to sort
-
-  Thanks to many, many people for contributions and suggestions.
-  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
-  This basically means: do what you want with it.
-*/
-
-
-var stIsIE = /*@cc_on!@*/false;
-
-sorttable = {
-  init: function() {
-    // quit if this function has already been called
-    if (arguments.callee.done) return;
-    // flag this function so we don't do the same thing twice
-    arguments.callee.done = true;
-    // kill the timer
-    if (_timer) clearInterval(_timer);
-
-    if (!document.createElement || !document.getElementsByTagName) return;
-
-    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
-
-    forEach(document.getElementsByTagName('table'), function(table) {
-      if (table.className.search(/\bsortable\b/) != -1) {
-        sorttable.makeSortable(table);
-      }
-    });
-
-  },
-
-  makeSortable: function(table) {
-    if (table.getElementsByTagName('thead').length == 0) {
-      // table doesn't have a tHead. Since it should have, create one and
-      // put the first table row in it.
-      the = document.createElement('thead');
-      the.appendChild(table.rows[0]);
-      table.insertBefore(the,table.firstChild);
-    }
-    // Safari doesn't support table.tHead, sigh
-    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
-
-    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
-
-    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
-    // "total" rows, for example). This is B&R, since what you're supposed
-    // to do is put them in a tfoot. So, if there are sortbottom rows,
-    // for backwards compatibility, move them to tfoot (creating it if needed).
-    sortbottomrows = [];
-    for (var i=0; i<table.rows.length; i++) {
-      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
-        sortbottomrows[sortbottomrows.length] = table.rows[i];
-      }
-    }
-    if (sortbottomrows) {
-      if (table.tFoot == null) {
-        // table doesn't have a tfoot. Create one.
-        tfo = document.createElement('tfoot');
-        table.appendChild(tfo);
-      }
-      for (var i=0; i<sortbottomrows.length; i++) {
-        tfo.appendChild(sortbottomrows[i]);
-      }
-      delete sortbottomrows;
-    }
-
-    // work through each column and calculate its type
-    headrow = table.tHead.rows[0].cells;
-    for (var i=0; i<headrow.length; i++) {
-      // manually override the type with a sorttable_type attribute
-      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
-        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
-        if (mtch) { override = mtch[1]; }
-	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
-	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
-	      } else {
-	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
-	      }
-	      // make it clickable to sort
-	      headrow[i].sorttable_columnindex = i;
-	      headrow[i].sorttable_tbody = table.tBodies[0];
-	      dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
-
-          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
-            // if we're already sorted by this column, just
-            // reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted',
-                                                    'sorttable_sorted_reverse');
-            this.removeChild(document.getElementById('sorttable_sortfwdind'));
-            sortrevind = document.createElement('span');
-            sortrevind.id = "sorttable_sortrevind";
-            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
-            this.appendChild(sortrevind);
-            return;
-          }
-          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
-            // if we're already sorted by this column in reverse, just
-            // re-reverse the table, which is quicker
-            sorttable.reverse(this.sorttable_tbody);
-            this.className = this.className.replace('sorttable_sorted_reverse',
-                                                    'sorttable_sorted');
-            this.removeChild(document.getElementById('sorttable_sortrevind'));
-            sortfwdind = document.createElement('span');
-            sortfwdind.id = "sorttable_sortfwdind";
-            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-            this.appendChild(sortfwdind);
-            return;
-          }
-
-          // remove sorttable_sorted classes
-          theadrow = this.parentNode;
-          forEach(theadrow.childNodes, function(cell) {
-            if (cell.nodeType == 1) { // an element
-              cell.className = cell.className.replace('sorttable_sorted_reverse','');
-              cell.className = cell.className.replace('sorttable_sorted','');
-            }
-          });
-          sortfwdind = document.getElementById('sorttable_sortfwdind');
-          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
-          sortrevind = document.getElementById('sorttable_sortrevind');
-          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
-
-          this.className += ' sorttable_sorted';
-          sortfwdind = document.createElement('span');
-          sortfwdind.id = "sorttable_sortfwdind";
-          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
-          this.appendChild(sortfwdind);
-
-	        // build an array to sort. This is a Schwartzian transform thing,
-	        // i.e., we "decorate" each row with the actual sort key,
-	        // sort based on the sort keys, and then put the rows back in order
-	        // which is a lot faster because you only do getInnerText once per row
-	        row_array = [];
-	        col = this.sorttable_columnindex;
-	        rows = this.sorttable_tbody.rows;
-	        for (var j=0; j<rows.length; j++) {
-	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
-	        }
-	        /* If you want a stable sort, uncomment the following line */
-	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
-	        /* and comment out this one */
-	        row_array.sort(this.sorttable_sortfunction);
-
-	        tb = this.sorttable_tbody;
-	        for (var j=0; j<row_array.length; j++) {
-	          tb.appendChild(row_array[j][1]);
-	        }
-
-	        delete row_array;
-	      });
-	    }
-    }
-  },
-
-  guessType: function(table, column) {
-    // guess the type of a column based on its first non-blank row
-    sortfn = sorttable.sort_alpha;
-    for (var i=0; i<table.tBodies[0].rows.length; i++) {
-      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
-      if (text != '') {
-        if (text.match(/^-?[\ufffd$\ufffd]?[\d,.]+%?$/)) {
-          return sorttable.sort_numeric;
-        }
-        // check for a date: dd/mm/yyyy or dd/mm/yy
-        // can have / or . or - as separator
-        // can be mm/dd as well
-        possdate = text.match(sorttable.DATE_RE)
-        if (possdate) {
-          // looks like a date
-          first = parseInt(possdate[1]);
-          second = parseInt(possdate[2]);
-          if (first > 12) {
-            // definitely dd/mm
-            return sorttable.sort_ddmm;
-          } else if (second > 12) {
-            return sorttable.sort_mmdd;
-          } else {
-            // looks like a date, but we can't tell which, so assume
-            // that it's dd/mm (English imperialism!) and keep looking
-            sortfn = sorttable.sort_ddmm;
-          }
-        }
-      }
-    }
-    return sortfn;
-  },
-
-  getInnerText: function(node) {
-    // gets the text we want to use for sorting for a cell.
-    // strips leading and trailing whitespace.
-    // this is *not* a generic getInnerText function; it's special to sorttable.
-    // for example, you can override the cell text with a customkey attribute.
-    // it also gets .value for <input> fields.
-
-    if (!node) return "";
-
-    hasInputs = (typeof node.getElementsByTagName == 'function') &&
-                 node.getElementsByTagName('input').length;
-
-    if (node.getAttribute("sorttable_customkey") != null) {
-      return node.getAttribute("sorttable_customkey");
-    }
-    else if (typeof node.textContent != 'undefined' && !hasInputs) {
-      return node.textContent.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.innerText != 'undefined' && !hasInputs) {
-      return node.innerText.replace(/^\s+|\s+$/g, '');
-    }
-    else if (typeof node.text != 'undefined' && !hasInputs) {
-      return node.text.replace(/^\s+|\s+$/g, '');
-    }
-    else {
-      switch (node.nodeType) {
-        case 3:
-          if (node.nodeName.toLowerCase() == 'input') {
-            return node.value.replace(/^\s+|\s+$/g, '');
-          }
-        case 4:
-          return node.nodeValue.replace(/^\s+|\s+$/g, '');
-          break;
-        case 1:
-        case 11:
-          var innerText = '';
-          for (var i = 0; i < node.childNodes.length; i++) {
-            innerText += sorttable.getInnerText(node.childNodes[i]);
-          }
-          return innerText.replace(/^\s+|\s+$/g, '');
-          break;
-        default:
-          return '';
-      }
-    }
-  },
-
-  reverse: function(tbody) {
-    // reverse the rows in a tbody
-    newrows = [];
-    for (var i=0; i<tbody.rows.length; i++) {
-      newrows[newrows.length] = tbody.rows[i];
-    }
-    for (var i=newrows.length-1; i>=0; i--) {
-       tbody.appendChild(newrows[i]);
-    }
-    delete newrows;
-  },
-
-  /* sort functions
-     each sort function takes two parameters, a and b
-     you are comparing a[0] and b[0] */
-  sort_numeric: function(a,b) {
-    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(aa)) aa = 0;
-    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
-    if (isNaN(bb)) bb = 0;
-    return aa-bb;
-  },
-  sort_alpha: function(a,b) {
-    if (a[0]==b[0]) return 0;
-    if (a[0]<b[0]) return -1;
-    return 1;
-  },
-  sort_ddmm: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; m = mtch[2]; d = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-  sort_mmdd: function(a,b) {
-    mtch = a[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt1 = y+m+d;
-    mtch = b[0].match(sorttable.DATE_RE);
-    y = mtch[3]; d = mtch[2]; m = mtch[1];
-    if (m.length == 1) m = '0'+m;
-    if (d.length == 1) d = '0'+d;
-    dt2 = y+m+d;
-    if (dt1==dt2) return 0;
-    if (dt1<dt2) return -1;
-    return 1;
-  },
-
-  shaker_sort: function(list, comp_func) {
-    // A stable sort function to allow multi-level sorting of data
-    // see: http://en.wikipedia.org/wiki/Cocktail_sort
-    // thanks to Joseph Nahmias
-    var b = 0;
-    var t = list.length - 1;
-    var swap = true;
-
-    while(swap) {
-        swap = false;
-        for(var i = b; i < t; ++i) {
-            if ( comp_func(list[i], list[i+1]) > 0 ) {
-                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
-                swap = true;
-            }
-        } // for
-        t--;
-
-        if (!swap) break;
-
-        for(var i = t; i > b; --i) {
-            if ( comp_func(list[i], list[i-1]) < 0 ) {
-                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
-                swap = true;
-            }
-        } // for
-        b++;
-
-    } // while(swap)
-  }
-}
-
-/* ******************************************************************
-   Supporting functions: bundled here to avoid depending on a library
-   ****************************************************************** */
-
-// Dean Edwards/Matthias Miller/John Resig
-
-/* for Mozilla/Opera9 */
-if (document.addEventListener) {
-    document.addEventListener("DOMContentLoaded", sorttable.init, false);
-}
-
-/* for Internet Explorer */
-/*@cc_on @*/
-/*@if (@_win32)
-    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
-    var script = document.getElementById("__ie_onload");
-    script.onreadystatechange = function() {
-        if (this.readyState == "complete") {
-            sorttable.init(); // call the onload handler
-        }
-    };
-/*@end @*/
-
-/* for Safari */
-if (/WebKit/i.test(navigator.userAgent)) { // sniff
-    var _timer = setInterval(function() {
-        if (/loaded|complete/.test(document.readyState)) {
-            sorttable.init(); // call the onload handler
-        }
-    }, 10);
-}
-
-/* for other browsers */
-window.onload = sorttable.init;
-
-// written by Dean Edwards, 2005
-// with input from Tino Zijdel, Matthias Miller, Diego Perini
-
-// http://dean.edwards.name/weblog/2005/10/add-event/
-
-function dean_addEvent(element, type, handler) {
-	if (element.addEventListener) {
-		element.addEventListener(type, handler, false);
-	} else {
-		// assign each event handler a unique ID
-		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
-		// create a hash table of event types for the element
-		if (!element.events) element.events = {};
-		// create a hash table of event handlers for each element/event pair
-		var handlers = element.events[type];
-		if (!handlers) {
-			handlers = element.events[type] = {};
-			// store the existing event handler (if there is one)
-			if (element["on" + type]) {
-				handlers[0] = element["on" + type];
-			}
-		}
-		// store the event handler in the hash table
-		handlers[handler.$$guid] = handler;
-		// assign a global event handler to do all the work
-		element["on" + type] = handleEvent;
-	}
-};
-// a counter used to create unique IDs
-dean_addEvent.guid = 1;
-
-function removeEvent(element, type, handler) {
-	if (element.removeEventListener) {
-		element.removeEventListener(type, handler, false);
-	} else {
-		// delete the event handler from the hash table
-		if (element.events && element.events[type]) {
-			delete element.events[type][handler.$$guid];
-		}
-	}
-};
-
-function handleEvent(event) {
-	var returnValue = true;
-	// grab the event object (IE uses a global event object)
-	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
-	// get a reference to the hash table of event handlers
-	var handlers = this.events[event.type];
-	// execute each event handler
-	for (var i in handlers) {
-		this.$$handleEvent = handlers[i];
-		if (this.$$handleEvent(event) === false) {
-			returnValue = false;
-		}
-	}
-	return returnValue;
-};
-
-function fixEvent(event) {
-	// add W3C standard event methods
-	event.preventDefault = fixEvent.preventDefault;
-	event.stopPropagation = fixEvent.stopPropagation;
-	return event;
-};
-fixEvent.preventDefault = function() {
-	this.returnValue = false;
-};
-fixEvent.stopPropagation = function() {
-  this.cancelBubble = true;
-}
-
-// Dean's forEach: http://dean.edwards.name/base/forEach.js
-/*
-	forEach, version 1.0
-	Copyright 2006, Dean Edwards
-	License: http://www.opensource.org/licenses/mit-license.php
-*/
-
-// array-like enumeration
-if (!Array.forEach) { // mozilla already supports this
-	Array.forEach = function(array, block, context) {
-		for (var i = 0; i < array.length; i++) {
-			block.call(context, array[i], i, array);
-		}
-	};
-}
-
-// generic enumeration
-Function.prototype.forEach = function(object, block, context) {
-	for (var key in object) {
-		if (typeof this.prototype[key] == "undefined") {
-			block.call(context, object[key], key, object);
-		}
-	}
-};
-
-// character enumeration
-String.forEach = function(string, block, context) {
-	Array.forEach(string.split(""), function(chr, index) {
-		block.call(context, chr, index, string);
-	});
-};
-
-// globally resolve forEach enumeration
-var forEach = function(object, block, context) {
-	if (object) {
-		var resolve = Object; // default
-		if (object instanceof Function) {
-			// functions have a "length" property
-			resolve = Function;
-		} else if (object.forEach instanceof Function) {
-			// the object implements a custom forEach method so use that
-			object.forEach(block, context);
-			return;
-		} else if (typeof object == "string") {
-			// the object is a string
-			resolve = String;
-		} else if (typeof object.length == "number") {
-			// the object is array-like
-			resolve = Array;
-		}
-		resolve.forEach(object, block, context);
-	}
-};
-

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/6a92f32f/traffic_monitor/experimental/traffic_monitor/static/index.html
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/static/index.html b/traffic_monitor/experimental/traffic_monitor/static/index.html
new file mode 100644
index 0000000..5c02902
--- /dev/null
+++ b/traffic_monitor/experimental/traffic_monitor/static/index.html
@@ -0,0 +1,521 @@
+<!DOCTYPE html>
+
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+
+<html>
+	<head>
+		<!-- <script src="sorttable.js"></script> -->
+		<meta charset="UTF-8">
+		<title>Traffic Monitor</title>
+		<style>
+		 body {font-family: "Lato", sans-serif;}
+
+		 ul.tab {
+			 list-style-type: none;
+			 margin: 0;
+			 padding: 0;
+			 overflow: hidden;
+			 border: 1px solid #ccc;
+			 background-color: #f1f1f1;
+		 }
+
+		 ul.tab li {float: left;}
+
+		 ul.tab li a {
+			 display: inline-block;
+			 color: black;
+			 text-align: center;
+			 padding: 14px 16px;
+			 text-decoration: none;
+			 transition: 0.3s;
+			 font-size: 17px;
+		 }
+
+		 ul.tab li a:hover {
+			 background-color: #cfd;
+		 }
+
+		 .tab-header-selected {
+			 background-color: #adb;
+		 }
+
+		 .tabcontent {
+			 display: none;
+			 padding: 6px 12px;
+			 border: 1px solid #ccc;
+			 border-top: none;
+		 }
+
+		 table, th, td {
+			 border: 0px solid black;
+		 }
+
+		 table {
+			 border-collapse: separate;
+			 border-spacing: 0px 0;
+		 }
+
+		 th, td {
+			 padding:5px 50px 5px 5px
+		 }
+
+		 tr.stripes:nth-child(even) {
+			 background: #dfe
+		 }
+		 tr.stripes:nth-child(odd) {
+			 background: #fff
+		 }
+
+		 li.endpoint {
+			 margin: 4px 0;
+		 }
+
+		 ul {
+			 list-style: none;
+		 }
+
+		 #top-bar {
+			 border-collapse: collapse;
+			 border-color: #adb;;
+			 border-width: 0px 0px 1px 0px;
+			 padding-bottom: 10px;
+		 }
+
+		 .links {
+			 display: table;
+		 }
+		 .links ul {
+			 display: table-cell;
+			 vertical-align: top;
+		 }
+
+		 #bottom-bar {
+			 position: relative;
+			 z-index: 100;
+			 bottom: 0;
+			 left: 0;
+			 width: 100%;
+			 height: 14px;
+			 padding: 6px 20px 4px 0px;
+			 text-align: right;
+			 font-size: 14px;
+		 }
+
+		 #bottom-bar-vanillajs {
+			 padding-top: 10px;
+		 }
+
+		 .error {
+			 background-color: #f00;
+		 }
+		 .warning {
+			 background-color: #f80;
+		 }
+		</style>
+		<script>
+		 function init() {
+			 openTab('cache-states-content');
+			 getTopBar();
+			 setInterval(getCacheCount, 4755);
+			 setInterval(getCacheAvailableCount, 4800);
+			 setInterval(getBandwidth, 4621);
+			 setInterval(getBandwidthCapacity, 4591);
+			 setInterval(getCacheDownCount, 4832);
+			 setInterval(getVersion, 10007); // change to retry on failure, and only do on startup
+			 setInterval(getTrafficOpsUri, 10019); // change to retry on failure, and only do on startup
+			 setInterval(getEvents, 2004); // change to retry on failure, and only do on startup
+			 setInterval(getCacheStatuses, 5009);
+			 setInterval(getDsStats, 4003);
+		 }
+
+		 // source: http://stackoverflow.com/a/2901298/292623
+		 function numberStrWithCommas(x) {
+			 return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+		 }
+
+		 function openTab(tabName) {
+			 var i, x, tablinks;
+			 x = document.getElementsByClassName("tabcontent");
+			 for (i = 0; i < x.length; i++) {
+				 x[i].style.display = "none";
+			 }
+			 tablinks = document.getElementsByClassName("tablinks");
+			 for (i = 0; i < x.length; i++) {
+				 tablinks[i].className = tablinks[i].className.replace(" tab-selected", "");
+			 }
+
+			 tabheaders = document.getElementsByClassName("tab-header");
+			 for (i = 0; i < tabheaders.length; i++) {
+				 tabheaders[i].className = tabheaders[i].className.replace(" tab-header-selected", "");
+			 }
+
+			 document.getElementById(tabName).style.display = "block";
+			 document.getElementById(tabName).className += " tab-selected";
+			 document.getElementById(tabName + "-tab").className += " tab-header-selected";
+		 }
+
+		 function getCacheCount() {
+			 ajax("/api/cache-count", function(r) {
+				 document.getElementById("cache-count").innerHTML = r;
+			 });
+		 }
+
+		 function getCacheAvailableCount() {
+			 ajax("/api/cache-available-count", function(r) {
+				 document.getElementById("cache-available").innerHTML = r;
+			 });
+		 }
+
+		 function getBandwidth() {
+			 ajax("/api/bandwidth-kbps", function(r) {
+				 document.getElementById("bandwidth").innerHTML = numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
+			 });
+		 }
+
+		 function getBandwidthCapacity() {
+			 ajax("/api/bandwidth-capacity-kbps", function(r) {
+				 document.getElementById("bandwidth-capacity").innerHTML = numberStrWithCommas((r / kilobitsInGigabit).toString());
+			 });
+		 }
+
+		 function getCacheDownCount() {
+			 ajax("/api/cache-down-count", function(r) {
+				 document.getElementById("cache-down").innerHTML = r;
+			 });
+		 }
+
+		 function getVersion() {
+			 ajax("/api/version", function(r) {
+				 document.getElementById("version").innerHTML = r;
+				 document.title = r;
+			 });
+		 }
+
+		 function getTrafficOpsUri() {
+			 ajax("/api/traffic-ops-uri", function(r) {
+				 document.getElementById("source-uri").innerHTML = "<a href='" + r + "'>" + r + "</a>";
+				 ajax("/publish/ConfigDoc", function(r) {
+					 var j = JSON.parse(r);
+					 document.getElementById("source-uri").innerHTML += "/" + j.cdnName;
+				 });
+			 });
+		 }
+
+		 var lastEvent = 0;
+		 function getEvents() {
+			 /// \todo add /api/events-since/{index} (and change Traffic Monitor to keep latest 
+			 ajax("/publish/EventLog", function(r) {
+				 var jdata = JSON.parse(r);
+				 for (i = jdata.events.length - 1; i >= 0; i--) {
+					 var event = jdata.events[i];
+					 if (event.index <= lastEvent) {
+						 continue;
+					 }
+					 lastEvent = event.index
+					 var row = document.getElementById("event-log").insertRow(1); //document.createElement("tr");
+					 row.classList.add("stripes");
+					 row.insertCell(0).textContent = event.name;
+					 row.insertCell(1).textContent = event.type;
+					 row.insertCell(2).textContent = event.isAvailable ? "available" : "offline";
+					 if(event.isAvailable) {
+						 row.classList.add("stripes");
+						 row.classList.remove("error");
+					 } else {
+						 row.classList.add("error");
+						 row.classList.remove("stripes");
+					 }
+					 row.insertCell(3).textContent = event.description;
+					 row.insertCell(4).textContent = new Date(event.time * 1000).toISOString();
+				 }
+			 });
+		 }
+
+		 function getCacheStates() {
+			 ajax("/api/cache-statuses", function(r) {
+				 var jdata = JSON.parse(r);
+				 var servers = Object.keys(jdata); //debug
+				 for (i = 0; i < servers.length; i++) {
+					 var server = servers[i];
+					 if (!document.getElementById("cache-states-" + server)) {
+						 var row = document.getElementById("cache-states").insertRow(1); //document.createElement("tr");
+						 row.classList.add("stripes");
+						 row.id = "cache-states-" + server
+						 row.insertCell(0).id = row.id + "-server";
+						 row.insertCell(1).id = row.id + "-type";
+						 row.insertCell(2).id = row.id + "-status";
+						 row.insertCell(3).id = row.id + "-load-average";
+						 row.insertCell(4).id = row.id + "-query-time";
+						 row.insertCell(5).id = row.id + "-health-time";
+						 row.insertCell(6).id = row.id + "-stat-time";
+						 row.insertCell(7).id = row.id + "-health-span";
+						 row.insertCell(8).id = row.id + "-stat-span";
+						 row.insertCell(9).id = row.id + "-bandwidth";
+						 row.insertCell(10).id = row.id + "-connection-count";
+						 document.getElementById(row.id + "-server").textContent = server;
+					 }
+
+					 /* \todo change to iterate over members, and make cells id constructed from these*/
+					 if (jdata[server].hasOwnProperty("type")) {
+						 document.getElementById("cache-states-" + server + "-type").textContent = jdata[server].type;
+					 }
+					 if (jdata[server].hasOwnProperty("status")) {
+						 document.getElementById("cache-states-" + server + "-status").textContent = jdata[server].status;
+						 var row = document.getElementById("cache-states-" + server);
+						 if (jdata[server].status.indexOf("ADMIN_DOWN") != -1) {
+							 row.classList.add("warning");
+							 row.classList.remove("error");
+							 row.classList.remove("stripes");
+						 } else if (jdata[server].status.indexOf(" available") == -1) {
+							 row.classList.add("error");
+							 row.classList.remove("warning");
+							 row.classList.remove("stripes");
+						 } else {
+							 row.classList.add("stripe");
+							 row.classList.remove("warning");
+							 row.classList.remove("error");
+						 }
+					 }
+					 if (jdata[server].hasOwnProperty("load_average")) {
+						 document.getElementById("cache-states-" + server + "-load-average").textContent = jdata[server].load_average;
+					 }
+					 if (jdata[server].hasOwnProperty("query_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-query-time").textContent = jdata[server].query_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("health_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-health-time").textContent = jdata[server].health_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("stat_time_ms")) {
+						 document.getElementById("cache-states-" + server + "-stat-time").textContent = jdata[server].stat_time_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("health_span_ms")) {
+						 document.getElementById("cache-states-" + server + "-health-span").textContent = jdata[server].health_span_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("stat_span_ms")) {
+						 document.getElementById("cache-states-" + server + "-stat-span").textContent = jdata[server].stat_span_ms;
+					 }
+					 if (jdata[server].hasOwnProperty("bandwidth_kbps")) {
+						 document.getElementById("cache-states-" + server + "-bandwidth").textContent = (jdata[server].bandwidth_kbps / kilobitsInMegabit).toFixed(2);
+					 }
+					 if (jdata[server].hasOwnProperty("connection_count")) {
+						 document.getElementById("cache-states-" + server + "-connection-count").textContent = jdata[server].connection_count;
+					 }
+				 }
+			 })
+		 }
+
+		 var millisecondsInSecond = 1000;
+		 var kilobitsInGigabit = 1000000;
+		 var kilobitsInMegabit = 1000;
+
+		 // dsDisplayFloat takes a float, and returns the string to display. For nonzero values, it returns two decimal places. For zero values, it returns an empty string, to make nonzero values more visible.
+		 function dsDisplayFloat(f) {
+			 var s = "";
+			 if (f != 0.0) {
+				 s = f.toFixed(2);
+			 }
+			 return s
+		 }
+
+		 function getDsStats() {
+			 var now = Date.now();
+
+			 /// \todo add /api/delivery-service-stats which only returns the data needed by the UI, for efficiency
+			 ajax("/publish/DsStats", function(r) {
+				 var j = JSON.parse(r);
+				 var jds = j.deliveryService
+				 var deliveryServiceNames = Object.keys(jds); //debug
+				 for (i = 0; i < deliveryServiceNames.length; i++) {
+					 var deliveryService = deliveryServiceNames[i];
+
+					 if (!document.getElementById("deliveryservice-stats-" + deliveryService)) {
+						 var row = document.getElementById("deliveryservice-stats").insertRow(1); //document.createElement("tr");
+						 row.id = "deliveryservice-stats-" + deliveryService
+						 row.insertCell(0).id = row.id + "-delivery-service";
+						 row.insertCell(1).id = row.id + "-status";
+						 row.insertCell(2).id = row.id + "-caches-reporting";
+						 row.insertCell(3).id = row.id + "-bandwidth";
+						 row.insertCell(4).id = row.id + "-2xx";
+						 row.insertCell(5).id = row.id + "-3xx";
+						 row.insertCell(6).id = row.id + "-4xx";
+						 row.insertCell(7).id = row.id + "-5xx";
+						 row.insertCell(8).id = row.id + "-disabled-locations";
+						 document.getElementById(row.id + "-delivery-service").textContent = deliveryService;
+					 }
+
+					 // \todo check that array has a member before dereferencing [0]
+					 if (jds[deliveryService].hasOwnProperty("isAvailable")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-status").textContent = jds[deliveryService]["isAvailable"][0].value ? "available" : "unavailable";
+					 }
+					 if (jds[deliveryService].hasOwnProperty("caches-reporting") && jds[deliveryService].hasOwnProperty("caches-available") && jds[deliveryService].hasOwnProperty("caches-configured")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-caches-reporting").textContent = jds[deliveryService]['caches-reporting'][0].value + " / " + jds[deliveryService]['caches-available'][0].value + " / " + jds[deliveryService]['caches-configured'][0].value;
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.kbps")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-bandwidth").textContent = (jds[deliveryService]['total.kbps'][0].value / kilobitsInMegabit).toFixed(2);
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-2xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-3xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-4xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
+					 }
+					 if (jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
+						 document.getElementById("deliveryservice-stats-" + deliveryService + "-5xx").textContent = dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
+					 }
+
+					 // \todo implement disabled locations
+
+					 var row = document.getElementById("deliveryservice-stats-" + deliveryService);
+					 if (jds[deliveryService]["isAvailable"][0].value) {
+						 row.classList.add("stripes");
+						 row.classList.remove("unavailable");
+					 } else {
+						 row.classList.add("unavailable");
+						 row.classList.remove("stripes");
+					 }
+				 }
+			 })
+		 }
+
+		 function getCacheStatuses() {
+			 getCacheCount();
+			 getCacheAvailableCount();
+			 getCacheDownCount();
+			 getCacheStates();
+		 }
+
+		 function getTopBar() {
+			 getVersion();
+			 getTrafficOpsUri();
+			 getCacheStatuses();
+		 }
+
+		 function ajax(endpoint, f) {
+			 var xhttp = new XMLHttpRequest();
+			 xhttp.onreadystatechange = function() {
+				 if (xhttp.readyState == 4 && xhttp.status == 200) {
+					 f(xhttp.responseText);
+				 }
+			 };
+			 xhttp.open("GET", endpoint, true);
+			 xhttp.send();
+		 }
+		</script>
+	</head>
+	<body onload="init()">
+
+		<table id="top-bar" width="100%">
+			<tr>
+				<td> Caches: count=<span id="cache-count">0</span> available=<span id="cache-available">0</span> down=<span id="cache-down">0</span> </td>
+				<td> Bandwidth: <span id="bandwidth">0</span> / <span id="bandwidth-capacity">\u221e</span> gbps</td>
+				<td>Source: <span id="source-uri"></span></td>
+				<td><span id="version">traffic_monitor</span></td>
+			</tr>
+		</table>
+
+		<div class="links">
+			<ul>
+				<li class="endpoint"><a href="/publish/EventLog">EventLog</a></li>
+				<li class="endpoint"><a href="/publish/CacheStats">CacheStats</a></li>
+				<li class="endpoint"><a href="/publish/DsStats">DsStats</a></li>
+				<li class="endpoint"><a href="/publish/CrStates">CrStates (as published to Traffic Routers)</a></li>
+				<li class="endpoint"><a href="/publish/CrConfig">CrConfig (json)</a></li>
+				<li class="endpoint"><a href="/publish/PeerStates">PeerStates</a></li>
+				<li class="endpoint"><a href="/publish/Stats">Stats</a></li>
+				<li class="endpoint"><a href="/publish/StatSummary">StatSummary</a></li>
+				<li class="endpoint"><a href="/publish/ConfigDoc">ConfigDoc</a></li>
+			</ul>
+
+			<ul>
+				<li class="endpoint"><a href="/api/cache-count">/api/cache-count</a></li>
+				<li class="endpoint"><a href="/api/cache-available-count">/api/cache-available-count</a></li>
+				<li class="endpoint"><a href="/api/cache-down-count">/api/cache-down-count</a></li>
+				<li class="endpoint"><a href="/api/version">/api/version</a></li>
+				<li class="endpoint"><a href="/api/traffic-ops-uri">/api/traffic-ops-uri</a></li>
+				<li class="endpoint"><a href="/api/cache-statuses">/api/cache-statuses</a></li>
+				<li class="endpoint"><a href="/api/bandwidth-kbps">/api/bandwidth-kbps</a></li>
+				<li class="endpoint"><a href="/api/bandwidth-capacity-kbps">/api/bandwidth-capacity-kbps</a></li>
+			</ul>
+		</div>
+
+		<ul class="tab">
+			<li id="cache-states-content-tab" class="tab-header"><a href="#" onclick="openTab('cache-states-content')" class="tablinks">Cache States</a></li>
+			<li id="deliveryservice-stats-content-tab" class="tab-header"><a href="#" onclick="openTab('deliveryservice-stats-content')" class="tablinks">Deliveryservice States</a></li>
+			<li id="event-log-content-tab" class="tab-header"><a href="#" onclick="openTab('event-log-content')" class="tablinks">Event Log</a></li>
+		</ul>
+
+		<div id="cache-states-content" class="tabcontent">
+			<table id="cache-states" class="tab-grid sortable">
+				<tr>
+					<th>Server</th>
+					<th>Type</th>
+					<th>Status</th>
+					<th>Load Average</th>
+					<th>Query Time (ms)</th>
+					<th>Health Time (ms)</th>
+					<th>Stat Time (ms)</th>
+					<th>Health Span (ms)</th>
+					<th>Stat Span (ms)</th>
+					<th>Bandwidth (mbps)</th>
+					<th>Connection Count</th>
+				</tr>
+			</table>
+		</div>
+		<div id="deliveryservice-stats-content" class="tabcontent">
+			<table id="deliveryservice-stats" class="tab-grid sortable">
+				<tr>
+					<th>Delivery Service</th>
+					<th>Status</th>
+					<th>Caches Reporting/Available/Configured</th>
+					<th>Bandwidth (mbps)</th>
+					<th>2xx/sec</th>
+					<th>3xx/sec</th>
+					<th>4xx/sec</th>
+					<th>5xx/sec</th>
+					<th>Disabled Locations</th>
+				</tr>
+			</table>
+		</div>
+
+		<div id="event-log-content" class="tabcontent">
+			<table id="event-log" class="tab-grid sortable">
+				<tr>
+					<th>Name</th>
+					<th>Type</th>
+					<th>Status</th>
+					<th>Description</th>
+					<th id="event-log-last-header">Event Time</th>
+				</tr>
+			</table>
+		</div>
+
+		<div id="update-num-text">Number of updates: <span id="update-num">0</span></div>
+		<div id="last-val-text">Last Val: <span id="last-val">0</span></div>
+		<a href="/">Refresh Server List</a>
+
+		<div id="bottom-bar">
+			Powered by <a id="bottom-bar-vanillajs" href="http://vanilla-js.com"><img border="0" alt="VanillaJS" src="http://vanilla-js.com/assets/button.png"></a>
+		</div>
+
+	</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/6a92f32f/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/static/sorttable.js b/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
new file mode 100644
index 0000000..38b0fc6
--- /dev/null
+++ b/traffic_monitor/experimental/traffic_monitor/static/sorttable.js
@@ -0,0 +1,495 @@
+/*
+  SortTable
+  version 2
+  7th April 2007
+  Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
+
+  Instructions:
+  Download this file
+  Add <script src="sorttable.js"></script> to your HTML
+  Add class="sortable" to any table you'd like to make sortable
+  Click on the headers to sort
+
+  Thanks to many, many people for contributions and suggestions.
+  Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
+  This basically means: do what you want with it.
+*/
+
+
+var stIsIE = /*@cc_on!@*/false;
+
+sorttable = {
+  init: function() {
+    // quit if this function has already been called
+    if (arguments.callee.done) return;
+    // flag this function so we don't do the same thing twice
+    arguments.callee.done = true;
+    // kill the timer
+    if (_timer) clearInterval(_timer);
+
+    if (!document.createElement || !document.getElementsByTagName) return;
+
+    sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
+
+    forEach(document.getElementsByTagName('table'), function(table) {
+      if (table.className.search(/\bsortable\b/) != -1) {
+        sorttable.makeSortable(table);
+      }
+    });
+
+  },
+
+  makeSortable: function(table) {
+    if (table.getElementsByTagName('thead').length == 0) {
+      // table doesn't have a tHead. Since it should have, create one and
+      // put the first table row in it.
+      the = document.createElement('thead');
+      the.appendChild(table.rows[0]);
+      table.insertBefore(the,table.firstChild);
+    }
+    // Safari doesn't support table.tHead, sigh
+    if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
+
+    if (table.tHead.rows.length != 1) return; // can't cope with two header rows
+
+    // Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
+    // "total" rows, for example). This is B&R, since what you're supposed
+    // to do is put them in a tfoot. So, if there are sortbottom rows,
+    // for backwards compatibility, move them to tfoot (creating it if needed).
+    sortbottomrows = [];
+    for (var i=0; i<table.rows.length; i++) {
+      if (table.rows[i].className.search(/\bsortbottom\b/) != -1) {
+        sortbottomrows[sortbottomrows.length] = table.rows[i];
+      }
+    }
+    if (sortbottomrows) {
+      if (table.tFoot == null) {
+        // table doesn't have a tfoot. Create one.
+        tfo = document.createElement('tfoot');
+        table.appendChild(tfo);
+      }
+      for (var i=0; i<sortbottomrows.length; i++) {
+        tfo.appendChild(sortbottomrows[i]);
+      }
+      delete sortbottomrows;
+    }
+
+    // work through each column and calculate its type
+    headrow = table.tHead.rows[0].cells;
+    for (var i=0; i<headrow.length; i++) {
+      // manually override the type with a sorttable_type attribute
+      if (!headrow[i].className.match(/\bsorttable_nosort\b/)) { // skip this col
+        mtch = headrow[i].className.match(/\bsorttable_([a-z0-9]+)\b/);
+        if (mtch) { override = mtch[1]; }
+	      if (mtch && typeof sorttable["sort_"+override] == 'function') {
+	        headrow[i].sorttable_sortfunction = sorttable["sort_"+override];
+	      } else {
+	        headrow[i].sorttable_sortfunction = sorttable.guessType(table,i);
+	      }
+	      // make it clickable to sort
+	      headrow[i].sorttable_columnindex = i;
+	      headrow[i].sorttable_tbody = table.tBodies[0];
+	      dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
+
+          if (this.className.search(/\bsorttable_sorted\b/) != -1) {
+            // if we're already sorted by this column, just
+            // reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted',
+                                                    'sorttable_sorted_reverse');
+            this.removeChild(document.getElementById('sorttable_sortfwdind'));
+            sortrevind = document.createElement('span');
+            sortrevind.id = "sorttable_sortrevind";
+            sortrevind.innerHTML = stIsIE ? '&nbsp<font face="webdings">5</font>' : '&nbsp;&#x25B4;';
+            this.appendChild(sortrevind);
+            return;
+          }
+          if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
+            // if we're already sorted by this column in reverse, just
+            // re-reverse the table, which is quicker
+            sorttable.reverse(this.sorttable_tbody);
+            this.className = this.className.replace('sorttable_sorted_reverse',
+                                                    'sorttable_sorted');
+            this.removeChild(document.getElementById('sorttable_sortrevind'));
+            sortfwdind = document.createElement('span');
+            sortfwdind.id = "sorttable_sortfwdind";
+            sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+            this.appendChild(sortfwdind);
+            return;
+          }
+
+          // remove sorttable_sorted classes
+          theadrow = this.parentNode;
+          forEach(theadrow.childNodes, function(cell) {
+            if (cell.nodeType == 1) { // an element
+              cell.className = cell.className.replace('sorttable_sorted_reverse','');
+              cell.className = cell.className.replace('sorttable_sorted','');
+            }
+          });
+          sortfwdind = document.getElementById('sorttable_sortfwdind');
+          if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
+          sortrevind = document.getElementById('sorttable_sortrevind');
+          if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
+
+          this.className += ' sorttable_sorted';
+          sortfwdind = document.createElement('span');
+          sortfwdind.id = "sorttable_sortfwdind";
+          sortfwdind.innerHTML = stIsIE ? '&nbsp<font face="webdings">6</font>' : '&nbsp;&#x25BE;';
+          this.appendChild(sortfwdind);
+
+	        // build an array to sort. This is a Schwartzian transform thing,
+	        // i.e., we "decorate" each row with the actual sort key,
+	        // sort based on the sort keys, and then put the rows back in order
+	        // which is a lot faster because you only do getInnerText once per row
+	        row_array = [];
+	        col = this.sorttable_columnindex;
+	        rows = this.sorttable_tbody.rows;
+	        for (var j=0; j<rows.length; j++) {
+	          row_array[row_array.length] = [sorttable.getInnerText(rows[j].cells[col]), rows[j]];
+	        }
+	        /* If you want a stable sort, uncomment the following line */
+	        //sorttable.shaker_sort(row_array, this.sorttable_sortfunction);
+	        /* and comment out this one */
+	        row_array.sort(this.sorttable_sortfunction);
+
+	        tb = this.sorttable_tbody;
+	        for (var j=0; j<row_array.length; j++) {
+	          tb.appendChild(row_array[j][1]);
+	        }
+
+	        delete row_array;
+	      });
+	    }
+    }
+  },
+
+  guessType: function(table, column) {
+    // guess the type of a column based on its first non-blank row
+    sortfn = sorttable.sort_alpha;
+    for (var i=0; i<table.tBodies[0].rows.length; i++) {
+      text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
+      if (text != '') {
+        if (text.match(/^-?[\ufffd$\ufffd]?[\d,.]+%?$/)) {
+          return sorttable.sort_numeric;
+        }
+        // check for a date: dd/mm/yyyy or dd/mm/yy
+        // can have / or . or - as separator
+        // can be mm/dd as well
+        possdate = text.match(sorttable.DATE_RE)
+        if (possdate) {
+          // looks like a date
+          first = parseInt(possdate[1]);
+          second = parseInt(possdate[2]);
+          if (first > 12) {
+            // definitely dd/mm
+            return sorttable.sort_ddmm;
+          } else if (second > 12) {
+            return sorttable.sort_mmdd;
+          } else {
+            // looks like a date, but we can't tell which, so assume
+            // that it's dd/mm (English imperialism!) and keep looking
+            sortfn = sorttable.sort_ddmm;
+          }
+        }
+      }
+    }
+    return sortfn;
+  },
+
+  getInnerText: function(node) {
+    // gets the text we want to use for sorting for a cell.
+    // strips leading and trailing whitespace.
+    // this is *not* a generic getInnerText function; it's special to sorttable.
+    // for example, you can override the cell text with a customkey attribute.
+    // it also gets .value for <input> fields.
+
+    if (!node) return "";
+
+    hasInputs = (typeof node.getElementsByTagName == 'function') &&
+                 node.getElementsByTagName('input').length;
+
+    if (node.getAttribute("sorttable_customkey") != null) {
+      return node.getAttribute("sorttable_customkey");
+    }
+    else if (typeof node.textContent != 'undefined' && !hasInputs) {
+      return node.textContent.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.innerText != 'undefined' && !hasInputs) {
+      return node.innerText.replace(/^\s+|\s+$/g, '');
+    }
+    else if (typeof node.text != 'undefined' && !hasInputs) {
+      return node.text.replace(/^\s+|\s+$/g, '');
+    }
+    else {
+      switch (node.nodeType) {
+        case 3:
+          if (node.nodeName.toLowerCase() == 'input') {
+            return node.value.replace(/^\s+|\s+$/g, '');
+          }
+        case 4:
+          return node.nodeValue.replace(/^\s+|\s+$/g, '');
+          break;
+        case 1:
+        case 11:
+          var innerText = '';
+          for (var i = 0; i < node.childNodes.length; i++) {
+            innerText += sorttable.getInnerText(node.childNodes[i]);
+          }
+          return innerText.replace(/^\s+|\s+$/g, '');
+          break;
+        default:
+          return '';
+      }
+    }
+  },
+
+  reverse: function(tbody) {
+    // reverse the rows in a tbody
+    newrows = [];
+    for (var i=0; i<tbody.rows.length; i++) {
+      newrows[newrows.length] = tbody.rows[i];
+    }
+    for (var i=newrows.length-1; i>=0; i--) {
+       tbody.appendChild(newrows[i]);
+    }
+    delete newrows;
+  },
+
+  /* sort functions
+     each sort function takes two parameters, a and b
+     you are comparing a[0] and b[0] */
+  sort_numeric: function(a,b) {
+    aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(aa)) aa = 0;
+    bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
+    if (isNaN(bb)) bb = 0;
+    return aa-bb;
+  },
+  sort_alpha: function(a,b) {
+    if (a[0]==b[0]) return 0;
+    if (a[0]<b[0]) return -1;
+    return 1;
+  },
+  sort_ddmm: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; m = mtch[2]; d = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+  sort_mmdd: function(a,b) {
+    mtch = a[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt1 = y+m+d;
+    mtch = b[0].match(sorttable.DATE_RE);
+    y = mtch[3]; d = mtch[2]; m = mtch[1];
+    if (m.length == 1) m = '0'+m;
+    if (d.length == 1) d = '0'+d;
+    dt2 = y+m+d;
+    if (dt1==dt2) return 0;
+    if (dt1<dt2) return -1;
+    return 1;
+  },
+
+  shaker_sort: function(list, comp_func) {
+    // A stable sort function to allow multi-level sorting of data
+    // see: http://en.wikipedia.org/wiki/Cocktail_sort
+    // thanks to Joseph Nahmias
+    var b = 0;
+    var t = list.length - 1;
+    var swap = true;
+
+    while(swap) {
+        swap = false;
+        for(var i = b; i < t; ++i) {
+            if ( comp_func(list[i], list[i+1]) > 0 ) {
+                var q = list[i]; list[i] = list[i+1]; list[i+1] = q;
+                swap = true;
+            }
+        } // for
+        t--;
+
+        if (!swap) break;
+
+        for(var i = t; i > b; --i) {
+            if ( comp_func(list[i], list[i-1]) < 0 ) {
+                var q = list[i]; list[i] = list[i-1]; list[i-1] = q;
+                swap = true;
+            }
+        } // for
+        b++;
+
+    } // while(swap)
+  }
+}
+
+/* ******************************************************************
+   Supporting functions: bundled here to avoid depending on a library
+   ****************************************************************** */
+
+// Dean Edwards/Matthias Miller/John Resig
+
+/* for Mozilla/Opera9 */
+if (document.addEventListener) {
+    document.addEventListener("DOMContentLoaded", sorttable.init, false);
+}
+
+/* for Internet Explorer */
+/*@cc_on @*/
+/*@if (@_win32)
+    document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
+    var script = document.getElementById("__ie_onload");
+    script.onreadystatechange = function() {
+        if (this.readyState == "complete") {
+            sorttable.init(); // call the onload handler
+        }
+    };
+/*@end @*/
+
+/* for Safari */
+if (/WebKit/i.test(navigator.userAgent)) { // sniff
+    var _timer = setInterval(function() {
+        if (/loaded|complete/.test(document.readyState)) {
+            sorttable.init(); // call the onload handler
+        }
+    }, 10);
+}
+
+/* for other browsers */
+window.onload = sorttable.init;
+
+// written by Dean Edwards, 2005
+// with input from Tino Zijdel, Matthias Miller, Diego Perini
+
+// http://dean.edwards.name/weblog/2005/10/add-event/
+
+function dean_addEvent(element, type, handler) {
+	if (element.addEventListener) {
+		element.addEventListener(type, handler, false);
+	} else {
+		// assign each event handler a unique ID
+		if (!handler.$$guid) handler.$$guid = dean_addEvent.guid++;
+		// create a hash table of event types for the element
+		if (!element.events) element.events = {};
+		// create a hash table of event handlers for each element/event pair
+		var handlers = element.events[type];
+		if (!handlers) {
+			handlers = element.events[type] = {};
+			// store the existing event handler (if there is one)
+			if (element["on" + type]) {
+				handlers[0] = element["on" + type];
+			}
+		}
+		// store the event handler in the hash table
+		handlers[handler.$$guid] = handler;
+		// assign a global event handler to do all the work
+		element["on" + type] = handleEvent;
+	}
+};
+// a counter used to create unique IDs
+dean_addEvent.guid = 1;
+
+function removeEvent(element, type, handler) {
+	if (element.removeEventListener) {
+		element.removeEventListener(type, handler, false);
+	} else {
+		// delete the event handler from the hash table
+		if (element.events && element.events[type]) {
+			delete element.events[type][handler.$$guid];
+		}
+	}
+};
+
+function handleEvent(event) {
+	var returnValue = true;
+	// grab the event object (IE uses a global event object)
+	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
+	// get a reference to the hash table of event handlers
+	var handlers = this.events[event.type];
+	// execute each event handler
+	for (var i in handlers) {
+		this.$$handleEvent = handlers[i];
+		if (this.$$handleEvent(event) === false) {
+			returnValue = false;
+		}
+	}
+	return returnValue;
+};
+
+function fixEvent(event) {
+	// add W3C standard event methods
+	event.preventDefault = fixEvent.preventDefault;
+	event.stopPropagation = fixEvent.stopPropagation;
+	return event;
+};
+fixEvent.preventDefault = function() {
+	this.returnValue = false;
+};
+fixEvent.stopPropagation = function() {
+  this.cancelBubble = true;
+}
+
+// Dean's forEach: http://dean.edwards.name/base/forEach.js
+/*
+	forEach, version 1.0
+	Copyright 2006, Dean Edwards
+	License: http://www.opensource.org/licenses/mit-license.php
+*/
+
+// array-like enumeration
+if (!Array.forEach) { // mozilla already supports this
+	Array.forEach = function(array, block, context) {
+		for (var i = 0; i < array.length; i++) {
+			block.call(context, array[i], i, array);
+		}
+	};
+}
+
+// generic enumeration
+Function.prototype.forEach = function(object, block, context) {
+	for (var key in object) {
+		if (typeof this.prototype[key] == "undefined") {
+			block.call(context, object[key], key, object);
+		}
+	}
+};
+
+// character enumeration
+String.forEach = function(string, block, context) {
+	Array.forEach(string.split(""), function(chr, index) {
+		block.call(context, chr, index, string);
+	});
+};
+
+// globally resolve forEach enumeration
+var forEach = function(object, block, context) {
+	if (object) {
+		var resolve = Object; // default
+		if (object instanceof Function) {
+			// functions have a "length" property
+			resolve = Function;
+		} else if (object.forEach instanceof Function) {
+			// the object implements a custom forEach method so use that
+			object.forEach(block, context);
+			return;
+		} else if (typeof object == "string") {
+			// the object is a string
+			resolve = String;
+		} else if (typeof object.length == "number") {
+			// the object is array-like
+			resolve = Array;
+		}
+		resolve.forEach(object, block, context);
+	}
+};
+


[2/4] incubator-trafficcontrol git commit: Fix TM2 health poll to only get system stats

Posted by el...@apache.org.
Fix TM2 health poll to only get system stats


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/bb81c133
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/bb81c133
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/bb81c133

Branch: refs/heads/master
Commit: bb81c133c20f7f39290d9e046ec193d5f50bad71
Parents: 7e80e88
Author: Robert Butts <ro...@gmail.com>
Authored: Tue Jan 17 14:59:18 2017 -0700
Committer: Jeff Elsloo <je...@cable.comcast.com>
Committed: Tue Jan 17 16:00:43 2017 -0700

----------------------------------------------------------------------
 .../experimental/traffic_monitor/manager/monitorconfig.go      | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/bb81c133/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go b/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
index b199b55..f524a3c 100644
--- a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
+++ b/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
@@ -224,14 +224,14 @@ func monitorConfigListen(
 			r := strings.NewReplacer(
 				"${hostname}", srv.IP,
 				"${interface_name}", srv.InterfaceName,
-				"application=system", "application=plugin.remap",
-				"application=", "application=plugin.remap",
+				"application=plugin.remap", "application=system",
+				"application=", "application=system",
 			)
 			url = r.Replace(url)
 
 			connTimeout := trafficOpsHealthConnectionTimeoutToDuration(monitorConfig.Profile[srv.Profile].Parameters.HealthConnectionTimeout)
 			healthURLs[srv.HostName] = poller.PollConfig{URL: url, Timeout: connTimeout}
-			r = strings.NewReplacer("application=plugin.remap", "application=")
+			r = strings.NewReplacer("application=system", "application=")
 			statURL := r.Replace(url)
 			statURLs[srv.HostName] = poller.PollConfig{URL: statURL, Timeout: connTimeout}
 		}


[4/4] incubator-trafficcontrol git commit: This closes #194.

Posted by el...@apache.org.
This closes #194.


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/03610c8f
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/03610c8f
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/03610c8f

Branch: refs/heads/master
Commit: 03610c8fb6e00a721f366a7806d2998b6c0a014e
Parents: 6a92f32
Author: Jeff Elsloo <je...@cable.comcast.com>
Authored: Tue Jan 17 16:01:32 2017 -0700
Committer: Jeff Elsloo <je...@cable.comcast.com>
Committed: Tue Jan 17 16:01:32 2017 -0700

----------------------------------------------------------------------

----------------------------------------------------------------------



[3/4] incubator-trafficcontrol git commit: Change TM2 poll params to match TM1

Posted by el...@apache.org.
Change TM2 poll params to match TM1

Changes Traffic Monitor 2.0 'stat' poll, which corresponds to the 1.0
'health' poll, to use the 'health.polling.interval' parameter, and
changes the 'health' poll to use a 'heartbeat.polling.interval' param.


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/7e80e88c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/7e80e88c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/7e80e88c

Branch: refs/heads/master
Commit: 7e80e88c5458167d6778cf11b8fb7bf56c58b4d1
Parents: 8833d3e
Author: Robert Butts <ro...@gmail.com>
Authored: Tue Jan 17 14:03:21 2017 -0700
Committer: Jeff Elsloo <je...@cable.comcast.com>
Committed: Tue Jan 17 16:00:43 2017 -0700

----------------------------------------------------------------------
 .../traffic_monitor/manager/monitorconfig.go    | 30 ++++++++------------
 1 file changed, 12 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7e80e88c/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
----------------------------------------------------------------------
diff --git a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go b/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
index c9d9005..b199b55 100644
--- a/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
+++ b/traffic_monitor/experimental/traffic_monitor/manager/monitorconfig.go
@@ -139,16 +139,6 @@ func trafficOpsHealthPollIntervalToDuration(t int) time.Duration {
 
 // getPollIntervals reads the Traffic Ops Client monitorConfig structure, and parses and returns the health, peer, and stat poll intervals
 func getHealthPeerStatPollIntervals(monitorConfig to.TrafficMonitorConfigMap, cfg config.Config) (time.Duration, time.Duration, time.Duration, error) {
-	healthPollIntervalI, healthPollIntervalExists := monitorConfig.Config["health.polling.interval"]
-	if !healthPollIntervalExists {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'health.polling.interval', not setting config changes.\n")
-	}
-	healthPollIntervalInt, healthPollIntervalIsInt := healthPollIntervalI.(float64)
-	if !healthPollIntervalIsInt {
-		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'health.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", healthPollIntervalI, healthPollIntervalI)
-	}
-	healthPollInterval := trafficOpsHealthPollIntervalToDuration(int(healthPollIntervalInt))
-
 	peerPollIntervalI, peerPollIntervalExists := monitorConfig.Config["peers.polling.interval"]
 	if !peerPollIntervalExists {
 		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'peers.polling.interval', not setting config changes.\n")
@@ -159,22 +149,26 @@ func getHealthPeerStatPollIntervals(monitorConfig to.TrafficMonitorConfigMap, cf
 	}
 	peerPollInterval := trafficOpsPeerPollIntervalToDuration(int(peerPollIntervalInt))
 
-	statPollIntervalI, statPollIntervalExists := monitorConfig.Config["stat.polling.interval"]
+	statPollIntervalI, statPollIntervalExists := monitorConfig.Config["health.polling.interval"]
 	if !statPollIntervalExists {
-		log.Warnf("Traffic Ops Monitor config missing 'stat.polling.interval', using health for stat.\n")
-		statPollIntervalI = healthPollIntervalI
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config missing 'health.polling.interval', not setting config changes.\n")
 	}
 	statPollIntervalInt, statPollIntervalIsInt := statPollIntervalI.(float64)
 	if !statPollIntervalIsInt {
-		log.Warnf("Traffic Ops Monitor config 'stat.polling.interval' value '%v' type %T is not an integer, using health for stat\n", statPollIntervalI, statPollIntervalI)
-		statPollIntervalInt = healthPollIntervalInt
+		return 0, 0, 0, fmt.Errorf("Traffic Ops Monitor config 'health.polling.interval' value '%v' type %T is not an integer, not setting config changes.\n", statPollIntervalI, statPollIntervalI)
 	}
 	statPollInterval := trafficOpsStatPollIntervalToDuration(int(statPollIntervalInt))
 
-	// Formerly, only 'health' polling existed. If TO still has old configuration and doesn't have a 'stat' parameter, this allows us to assume the 'health' poll is slow, and sets it to the stat poll (which used to be the only poll, getting all astats data) to the given presumed-slow health poll, and set the now-fast-and-small health poll to a short fraction of that.
-	if healthPollIntervalExists && !statPollIntervalExists {
-		healthPollInterval = time.Duration(float64(healthPollInterval) / float64(cfg.HealthToStatRatio))
+	healthPollIntervalI, healthPollIntervalExists := monitorConfig.Config["heartbeat.polling.interval"]
+	healthPollIntervalInt, healthPollIntervalIsInt := healthPollIntervalI.(float64)
+	if !healthPollIntervalExists {
+		log.Warnf("Traffic Ops Monitor config missing 'heartbeat.polling.interval', using health for heartbeat.\n")
+		healthPollIntervalInt = statPollIntervalInt
+	} else if !healthPollIntervalIsInt {
+		log.Warnf("Traffic Ops Monitor config 'heartbeat.polling.interval' value '%v' type %T is not an integer, using health for heartbeat\n", statPollIntervalI, statPollIntervalI)
+		healthPollIntervalInt = statPollIntervalInt
 	}
+	healthPollInterval := trafficOpsHealthPollIntervalToDuration(int(healthPollIntervalInt))
 
 	return healthPollInterval, peerPollInterval, statPollInterval, nil
 }