This is an automated email from the ASF dual-hosted git repository.
rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 4c1e6ea Tm ui improvements (#4403)
4c1e6ea is described below
commit 4c1e6eaaaf20f8f2e2413780530bf6fd298a04df
Author: ocket8888 <[email protected]>
AuthorDate: Thu Apr 2 10:15:35 2020 -0600
Tm ui improvements (#4403)
* Fixed unavailable cache servers not being highlighted in red
* Reverted unrelated change
* Removed unused library
* Split styling out into a separate file
Also improved the 'getEvents' function and removed a bunch of unused ids
and classes
* Removed a bunch of unused ids and classes from the cache-states table
* Moved TM UI script into its own file
* Added some JSDoc comments, switched 'var' to 'const' on some constants
* Greatly simplified 'getDSStats'
* Added JSDoc comment to 'ajax'
* Fixed missing license header
* Rearranged things so that nothing is used before it is defined
* Stop modifying text nodes with '.innerHTML'
* Formatted some long lines
* Remove obsolete attributes on TH elements
* removed some unused IDs
* removed element names from rules that don't need them, and one unused rule
* removed disallowed aria roles
* removed unused classes
* Use addEventListener on Window instead of onload on body
* Sectioned off the styling
* Fixed reference error and syntax error in script.js
* Added strict mode declaration
* Removed licensing for removed file
* fixed specfile
* Fixed broken references in srvhttp.go, added new ones
* Added TM binary to gitignore
* Added MIME Types for CSS and JS data
* Properly setting MIME Type for static files
* Fixed typo
* Fixed DSStats insertion
---
LICENSE | 4 -
lib/go-rfc/mimetype.go | 12 +
licenses/MIT-sorttable | 30 --
traffic_monitor/.gitignore | 2 +
traffic_monitor/build/traffic_monitor.spec | 5 +-
traffic_monitor/srvhttp/srvhttp.go | 36 +-
traffic_monitor/static/index.html | 519 ++---------------------------
traffic_monitor/static/script.js | 302 +++++++++++++++++
traffic_monitor/static/sorttable.js | 495 ---------------------------
traffic_monitor/static/style.css | 149 +++++++++
10 files changed, 532 insertions(+), 1022 deletions(-)
diff --git a/LICENSE b/LICENSE
index dce4fac..544a666 100644
--- a/LICENSE
+++ b/LICENSE
@@ -229,10 +229,6 @@ For the bootstrap component:
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
./licenses/MIT-normalize
-For the sorttable component:
-@traffic_monitor/static/sorttable.js
-./licenses/MIT-sorttable
-
For the jQuery component:
./licenses/MIT-jquery
diff --git a/lib/go-rfc/mimetype.go b/lib/go-rfc/mimetype.go
index aa769a5..ff7eeb0 100644
--- a/lib/go-rfc/mimetype.go
+++ b/lib/go-rfc/mimetype.go
@@ -257,3 +257,15 @@ var MIME_HTML = MimeType{
Name: "text/html",
Parameters: map[string]string{"charset": "utf-8"},
}
+
+// MIME_CSS is a pre-defined MimeType for CSS data.
+var MIME_CSS = MimeType{
+ Name: "text/css",
+ Parameters: map[string]string{"charset": "utf-8"},
+}
+
+// MIME_JS is a pre-defined MimeType for JavaScript data.
+var MIME_JS = MimeType{
+ Name: "text/javascript",
+ Parameters: map[string]string{"charset": "utf-8"},
+}
diff --git a/licenses/MIT-sorttable b/licenses/MIT-sorttable
deleted file mode 100644
index 36dcf0d..0000000
--- a/licenses/MIT-sorttable
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- SortTable
- version 2
- 7th April 2007
- Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
-
- Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
- This basically means: do what you want with it.
-*/
-
-The MIT Licence, for code from kryogenix.org
-
-Code downloaded from the Browser Experiments section of kryogenix.org is
-licenced under the so-called MIT licence. The licence is below.
-
-Copyright (c) 1997-date Stuart Langridge
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/traffic_monitor/.gitignore b/traffic_monitor/.gitignore
new file mode 100644
index 0000000..5c82061
--- /dev/null
+++ b/traffic_monitor/.gitignore
@@ -0,0 +1,2 @@
+#TM binary
+traffic_monitor
diff --git a/traffic_monitor/build/traffic_monitor.spec
b/traffic_monitor/build/traffic_monitor.spec
index c848401..abf8cfe 100644
--- a/traffic_monitor/build/traffic_monitor.spec
+++ b/traffic_monitor/build/traffic_monitor.spec
@@ -55,8 +55,9 @@ mkdir -p "${RPM_BUILD_ROOT}"/etc/logrotate.d
src=src/github.com/apache/trafficcontrol/traffic_monitor
cp -p "$src"/traffic_monitor
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/bin/traffic_monitor
-cp "$src"/static/index.html
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
-cp "$src"/static/sorttable.js
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/sorttable.js
+cp "$src"/static/index.html
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/index.html
+cp "$src"/static/script.js
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/script.js
+cp "$src"/static/style.css
"${RPM_BUILD_ROOT}"/opt/traffic_monitor/static/style.css
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
diff --git a/traffic_monitor/srvhttp/srvhttp.go
b/traffic_monitor/srvhttp/srvhttp.go
index f6992f1..c1fdff1 100644
--- a/traffic_monitor/srvhttp/srvhttp.go
+++ b/traffic_monitor/srvhttp/srvhttp.go
@@ -30,6 +30,7 @@ import (
"time"
"github.com/apache/trafficcontrol/lib/go-log"
+ "github.com/apache/trafficcontrol/lib/go-rfc"
"github.com/hydrogen18/stoppableListener"
)
@@ -61,9 +62,13 @@ func (s *Server) registerEndpoints(sm *http.ServeMux,
endpoints map[string]http.
if err != nil {
return fmt.Errorf("Error getting root endpoint: %v", err)
}
- handleSortableJs, err := s.handleSortableFunc(staticFileDir)
+ handleScript, err := s.handleScriptFunc(staticFileDir)
if err != nil {
- return fmt.Errorf("Error getting sortable endpoint: %v", err)
+ return fmt.Errorf("Error getting script endpoint: %v", err)
+ }
+ handleStyle, err := s.handleStyleFunc(staticFileDir)
+ if err != nil {
+ return fmt.Errorf("Error getting style endpoint: %v", err)
}
for path, f := range endpoints {
@@ -71,7 +76,8 @@ func (s *Server) registerEndpoints(sm *http.ServeMux,
endpoints map[string]http.
}
sm.HandleFunc("/", handleRoot)
- sm.HandleFunc("/sorttable.js", handleSortableJs)
+ sm.HandleFunc("/script.js", handleScript)
+ sm.HandleFunc("/style.css", handleStyle)
return nil
}
@@ -223,11 +229,29 @@ func DateStr(t time.Time) string {
}
func (s *Server) handleRootFunc(staticFileDir string) (http.HandlerFunc,
error) {
- return s.handleFile(staticFileDir + "/index.html")
+ return s.handleFile(staticFileDir + "index.html")
+}
+
+func (s *Server) handleScriptFunc(staticFileDir string) (http.HandlerFunc,
error) {
+ bytes, err := ioutil.ReadFile(staticFileDir + "script.js")
+ if err != nil {
+ return nil, err
+ }
+ return func(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set(rfc.ContentType, rfc.MIME_JS.String())
+ w.Write(bytes)
+ }, nil
}
-func (s *Server) handleSortableFunc(staticFileDir string) (http.HandlerFunc,
error) {
- return s.handleFile(staticFileDir + "/sorttable.js")
+func (s *Server) handleStyleFunc(staticFileDir string) (http.HandlerFunc,
error) {
+ bytes, err := ioutil.ReadFile(staticFileDir + "style.css")
+ if err != nil {
+ return nil, err
+ }
+ return func(w http.ResponseWriter, req *http.Request) {
+ w.Header().Set(rfc.ContentType, rfc.MIME_CSS.String())
+ w.Write(bytes)
+ }, nil
}
func (s *Server) handleFile(name string) (http.HandlerFunc, error) {
diff --git a/traffic_monitor/static/index.html
b/traffic_monitor/static/index.html
index 809c993..ef1b851 100644
--- a/traffic_monitor/static/index.html
+++ b/traffic_monitor/static/index.html
@@ -19,467 +19,14 @@ specific language governing permissions and limitations
under the License.
-->
-
-<html>
+<html lang="en">
<head>
- <!-- <script src="sorttable.js"></script> -->
<meta charset="UTF-8">
<title>Traffic Monitor</title>
- <style>
- body {
- font-family: "Lato", sans-serif;
- font-size: 14px;
- margin: 0;
- max-width: 100vw;
- }
-
- table {
- border-collapse: separate;
- border-spacing: 0px 0;
- width: 100%;
- }
-
- th, td {
- padding:5px 20px 5px 5px;
- }
-
- th {
- white-space: nowrap;
- }
-
- tbody tr:nth-child(even) {
- background: #ced;
- }
- tbody tr:nth-child(odd) {
- background: #fff;
- }
- #cache-states td:nth-child(n+4) {
- text-align: right;
- }
- #cache-states td:first-child {
- white-space: nowrap;
- }
-
-
- li.endpoint {
- margin: 4px 0;
- }
-
- div#top-bar {
- display: inline-flex;
- justify-content: space-around;
- align-items: center;
- width: 100%;
- margin: 15px 0;
- }
-
- div#links {
- display: grid;
- grid-template-columns: 1fr 1fr;
- max-width: 100ch;
- }
- div#links div {
- margin-left: 4px;
- }
- div#links a {
- display: block;
- }
-
- tbody tr.error {
- background-color: #f00;
- }
- tbody tr.warning {
- background-color: #f80;
- }
-
- input[type=radio] {
- visibility: hidden;
- display: none;
- }
- label {
- display: block;
- padding: 14px 21px;
- border-radius: 2px 2px 0 0;
- cursor: pointer;
- position: relative;
- top: 4px;
- transition: background-color ease-in-out 0.3s;
- text-align: center;
- border: 1px solid green;
- }
- label:hover {
- background-color: #cfd;
- }
- ul.tabs {
- list-style: none;
- max-width: 100%;
- border: 1px solid #ccc;
- background-color: #f1f1f1;
- position: relative;
- }
- div.tabcontent {
- z-index: 2;
- display: none;
- visibility: hidden;
- overflow: hidden;
- width: 100%;
- position: absolute;
- top: 53px;
- left: 0;
- padding: 6px 0;
- border-top: none;
- }
- input.tab:checked ~ div.tabcontent {
- display: block;
- visibility: visible;
- }
- input.tab:checked ~ label{
- background-color: #adb;
- border-bottom-width: 0;
- }
- ul.tabs li {
- float: left;
- display: block;
- }
- </style>
- <script>
- function init() {
- 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(getTrafficOpsCdn, 10500); // 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 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;
- });
- }
-
- function getTrafficOpsUri() {
- ajax("/api/traffic-ops-uri", function(r) {
- document.getElementById("source-uri").innerHTML
= "<a href='" + r + "'>" + r + "</a>";
- });
- }
-
-
- function getTrafficOpsCdn() {
- ajax("/publish/ConfigDoc", function(r) {
- var j = JSON.parse(r);
- document.getElementById("cdn-name").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(0);
//document.createElement("tr");
- row.classList.add("stripes");
- row.insertCell(0).id = row.id + "-name";
- document.getElementById(row.id +
"-name").textContent = event.name;
- document.getElementById(row.id +
"-name").style.whiteSpace = "nowrap";
- 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).id = row.id + "-last";
- document.getElementById(row.id +
"-last").textContent = new Date(event.time * 1000).toISOString();
- document.getElementById(row.id +
"-last").style.whiteSpace = "nowrap";
- document.getElementById(row.id +
"-last").style.textAlign = "right";
- }
- });
- }
-
- function getCacheStates() {
- ajax("/api/cache-statuses", function(r) {
- const servers = new
Map(Object.entries(JSON.parse(r)));
- const table = document.createElement('TBODY');
- table.id = "cache-states"
-
- // TODO: I'm not sure all these IDs are
actually necessary anymore
- for (const [serverName, server] of servers) {
- const row = table.insertRow(0);
- row.classList.add("stripes");
- row.id = "cache-states-" + serverName;
-
- let cell = row.insertCell(0);
- cell.id = row.id + "-server";
- cell.textContent = serverName;
-
- cell = row.insertCell(1);
- cell.id = row.id + "-type";
- cell.textContent = server.type ||
"UNKNOWN";
-
- cell = row.insertCell(2);
- cell.id = row.id + "-ipv4";
- if (server.status.indexOf("ONLINE") !== 0) {
- cell.textContent = server.ipv4_available;
- } else {
- cell.textContent = "N/A";
- }
-
- cell = row.insertCell(3);
- cell.id = row.id + "-ipv6";
- if (server.status.indexOf("ONLINE") !== 0) {
- cell.textContent = server.ipv6_available;
- } else {
- cell.textContent = "N/A";
- }
- cell = row.insertCell(4);
- cell.id = row.id + "-status";
- if
(Object.prototype.hasOwnProperty.call(server, "status")) {
- cell.textContent =
server.status;
- if
(server.status.indexOf("ADMIN_DOWN") !== -1 || server.status.indexOf("OFFLINE")
!== -1) {
-
row.classList.add("warning");
-
row.classList.remove("error");
-
row.classList.remove("stripes");
- } else if
(!server.combined_available && server.status.indexOf("ONLINE") !== 0) {
-
row.classList.add("error");
-
row.classList.remove("warning");
-
row.classList.remove("stripes");
- } else if
(server.status.indexOf(" availableBandwidth") !== -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");
- }
- }
-
- cell = row.insertCell(5);
- cell.id = row.id + "-load-average";
- cell.textContent = server.load_average
|| "";
-
- cell = row.insertCell(6);
- cell.id = row.id + "-query-time";
- cell.textContent = server.query_time_ms
|| "";
-
- cell = row.insertCell(7);
- cell.id = row.id + "-health-time";
- cell.textContent =
server.health_time_ms || "";
-
- cell = row.insertCell(8);
- cell.id = row.id + "-stat-time";
- cell.textContent = server.stat_time_ms
|| "";
-
- cell = row.insertCell(9);
- cell.id = row.id + "-health-span";
- cell.textContent =
server.health_span_ms || "";
-
- cell = row.insertCell(10);
- cell.id = row.id + "-stat-span";
- cell.textContent = server.stat_span_ms
|| "";
-
- cell = row.insertCell(11);
- cell.id = row.id + "-bandwidth";
- if
(Object.prototype.hasOwnProperty.call(server, "bandwidth_kbps")) {
- const kbps =
(server.bandwidth_kbps / kilobitsInMegabit).toFixed(2);
- const max =
numberStrWithCommas((server.bandwidth_capacity_kbps /
kilobitsInMegabit).toFixed(0));
- cell.textContent = `${kbps} /
${max}`;
- } else {
- cell.textContent = "N/A";
- }
-
- cell = row.insertCell(12);
- cell.id = row.id + "-connection-count";
- cell.textContent =
server.connection_count || "N/A";
- }
-
-
- const oldtable =
document.getElementById("cache-states");
- oldtable.parentNode.replaceChild(table,
oldtable);
-
- })
- }
-
- 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 = f
- 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
- //decrementing for loop so DsNames are
alphabetical A-Z
- //TODO allow for filtering of columns so this
isn't necessary
- for (var i =
deliveryServiceNames.length - 1; i >= 0; i--) {
- var deliveryService =
deliveryServiceNames[i];
-
- if
(!document.getElementById("deliveryservice-stats-" + deliveryService)) {
- var row =
document.getElementById("deliveryservice-stats").insertRow(0);
//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 +
"-tps";
- row.insertCell(5).id = row.id +
"-2xx";
- row.insertCell(6).id = row.id +
"-3xx";
- row.insertCell(7).id = row.id +
"-4xx";
- row.insertCell(8).id = row.id +
"-5xx";
- row.insertCell(9).id = row.id +
"-disabled-locations";
- document.getElementById(row.id
+ "-delivery-service").textContent = deliveryService;
- document.getElementById(row.id
+ "-delivery-service").style.whiteSpace = "nowrap";
- document.getElementById(row.id
+ "-caches-reporting").style.textAlign = "right";
- document.getElementById(row.id
+ "-bandwidth").style.textAlign = "right";
- document.getElementById(row.id
+ "-tps").style.textAlign = "right";
- document.getElementById(row.id
+ "-2xx").style.textAlign = "right";
- document.getElementById(row.id
+ "-3xx").style.textAlign = "right";
- document.getElementById(row.id
+ "-4xx").style.textAlign = "right";
- document.getElementById(row.id
+ "-5xx").style.textAlign = "right";
- }
-
- // \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 == "true"
? "available" : "unavailable - " +
jds[deliveryService]["error-string"][0].value;
- }
- 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);
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-bandwidth").textContent = "N/A";
- }
- if
(jds[deliveryService].hasOwnProperty("total.tps_total")) {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-tps").textContent =
dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_total'][0].value));
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-tps").textContent = "N/A";
- }
- if
(jds[deliveryService].hasOwnProperty("total.tps_2xx")) {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-2xx").textContent =
dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_2xx'][0].value));
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-2xx").textContent = "N/A";
- }
- if
(jds[deliveryService].hasOwnProperty("total.tps_3xx")) {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-3xx").textContent =
dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_3xx'][0].value));
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-3xx").textContent = "N/A";
- }
- if
(jds[deliveryService].hasOwnProperty("total.tps_4xx")) {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-4xx").textContent =
dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_4xx'][0].value));
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-4xx").textContent = "N/A";
- }
- if
(jds[deliveryService].hasOwnProperty("total.tps_5xx")) {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-5xx").textContent =
dsDisplayFloat(parseFloat(jds[deliveryService]['total.tps_5xx'][0].value));
- } else {
-
document.getElementById("deliveryservice-stats-" + deliveryService +
"-5xx").textContent = "N/A";
- }
-
- // \todo implement disabled locations
-
- var row =
document.getElementById("deliveryservice-stats-" + deliveryService);
- if
(jds[deliveryService]["isAvailable"][0].value == "true") {
- row.classList.add("stripes");
- row.classList.remove("error");
- } else {
- row.classList.add("error");
- row.classList.remove("stripes");
- }
- }
- })
- }
-
- function getCacheStatuses() {
- getCacheCount();
- getCacheAvailableCount();
- getCacheDownCount();
- getCacheStates();
- }
-
- function getTopBar() {
- getVersion();
- getTrafficOpsUri();
- getTrafficOpsCdn();
- 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>
+ <link rel="stylesheet" type="text/css" href="./style.css"/>
+ <script src="./script.js"></script>
</head>
-<body onload="init()">
+<body>
<div id="top-bar">
<div>Caches: count=<span id="cache-count">0</span>
available=<span id="cache-available">0</span> down=<span
id="cache-down">0</span> </div>
<div>Bandwidth: <span id="bandwidth">0</span> / <span
id="bandwidth-capacity">∞</span> gbps</div>
@@ -515,14 +62,16 @@ under the License.
</div>
</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>
+ <!-- TODO: There's no reference to these in the script nor styling;
safe to remove? -->
+ <div>Number of updates: <span>0</span></div>
+ <div>Last Val: <span>0</span></div>
+
<ul class="tabs" role="tablist">
- <li class="tab tab-header" id="cache-states-content-tab">
- <input type="radio" name="tabs" class="tab"
id="cache-states-input" checked />
- <label for="cache-states-input"
aria-selected="true">Cache States</label>
+ <li class="tab" id="cache-states-content-tab">
+ <input type="radio" name="tabs" class="tab"
id="cache-states-input" checked/>
+ <label for="cache-states-input">Cache States</label>
<div id="cache-states-content" class="tabcontent">
- <table class="tab-grid sortable">
+ <table>
<thead>
<tr>
<th>Server</th>
@@ -530,14 +79,14 @@ under the License.
<th>IPv4</th>
<th>IPv6</th>
<th>Status</th>
- <th align="right">Load
Average</th>
- <th align="right">Query
Time (ms)</th>
- <th
align="right">Health Time (ms)</th>
- <th align="right">Stat
Time (ms)</th>
- <th
align="right">Health Span (ms)</th>
- <th align="right">Stat
Span (ms)</th>
- <th
align="right">Bandwidth (mbps)</th>
- <th
align="right">Connection Count</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>
</thead>
<tbody id="cache-states"></tbody>
@@ -545,22 +94,22 @@ under the License.
</div>
</li>
- <li class="tab tab-header"
id="deliveryservice-stats-content-tab">
+ <li class="tab" id="deliveryservice-stats-content-tab">
<input type="radio" name="tabs" class="tab"
id="deliveryservice-stats-input"/>
- <label for="deliveryservice-stats-input"
aria-selected="false">Delivery Service States</label>
+ <label for="deliveryservice-stats-input">Delivery
Service States</label>
<div id="deliveryservice-stats-content"
class="tabcontent">
- <table class="tab-grid sortable">
+ <table>
<thead>
<tr>
<th>Delivery
Service</th>
<th>Status</th>
- <th
align="right">Caches Reporting/Available/Configured</th>
- <th
align="right">Bandwidth (mbps)</th>
- <th
align="right">t/sec</th>
- <th
align="right">2xx/sec</th>
- <th
align="right">3xx/sec</th>
- <th
align="right">4xx/sec</th>
- <th
align="right">5xx/sec</th>
+ <th>Caches
Reporting/Available/Configured</th>
+ <th>Bandwidth
(mbps)</th>
+ <th>t/sec</th>
+ <th>2xx/sec</th>
+ <th>3xx/sec</th>
+ <th>4xx/sec</th>
+ <th>5xx/sec</th>
<th>Disabled
Locations</th>
</tr>
</thead>
@@ -569,18 +118,18 @@ under the License.
</div>
</li>
- <li id="event-log-content-tab" class="tab tab-header">
+ <li id="event-log-content-tab" class="tab">
<input type="radio" name="tabs" class="tab"
id="event-log-input"/>
- <label for="event-log-input"
aria-selected="false">Event Log</label>
+ <label for="event-log-input">Event Log</label>
<div id="event-log-content" class="tabcontent">
- <table class="tab-grid sortable">
+ <table>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Status</th>
<th>Description</th>
- <th align="center"
id="event-log-last-header">Event Time</th>
+ <th>Event Time</th>
</tr>
</thead>
<tbody id="event-log"></tbody>
diff --git a/traffic_monitor/static/script.js b/traffic_monitor/static/script.js
new file mode 100644
index 0000000..8a3698a
--- /dev/null
+++ b/traffic_monitor/static/script.js
@@ -0,0 +1,302 @@
+/*
+ * 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.
+ */
+
+"use strict";
+
+const millisecondsInSecond = 1000;
+const kilobitsInGigabit = 1000000;
+const kilobitsInMegabit = 1000;
+
+/**
+ * This is the index of the latest event already catalogued by the UI. TM
doesn't
+ * provide a way to fetch event logs "older than x" etc., so this is how we
keep
+ * track of what we've seen.
+*/
+var lastEvent = 0;
+
+/**
+ * Adds a comma for every 3rd power of ten in a number represented as a string.
+ * e.g. numberStrWithCommas("100000") outputs "100,000".
+ * (source: http://stackoverflow.com/a/2901298/292623)
+ * @param x A string containing a number which will be formatted.
+*/
+function numberStrWithCommas(x) {
+ return x.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+}
+
+/**
+ * ajax performs an XMLHttpRequest and passes the result to a function - *only
if the response code is EXACTLY 200*.
+ * @param endpoint An API endpoint relative to the root of the TM webserver
e.g. /publish/DsStats
+ * @param f A function that takes a single argument which will be the entire
response body as a string.
+ */
+function ajax(endpoint, f) {
+ const xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = () => {
+ if (xhttp.readyState === 4 && xhttp.status === 200) {
+ f(xhttp.responseText);
+ }
+ };
+ xhttp.open("GET", endpoint, true);
+ xhttp.send();
+}
+
+function getCacheCount() {
+ ajax("/api/cache-count", function(r) {
+ document.getElementById("cache-count").textContent = r;
+ });
+}
+
+function getCacheAvailableCount() {
+ ajax("/api/cache-available-count", function(r) {
+ document.getElementById("cache-available").textContent = r;
+ });
+}
+
+function getBandwidth() {
+ ajax("/api/bandwidth-kbps", function(r) {
+ document.getElementById("bandwidth").textContent =
numberStrWithCommas((parseFloat(r) / kilobitsInGigabit).toFixed(2));
+ });
+}
+
+function getBandwidthCapacity() {
+ ajax("/api/bandwidth-capacity-kbps", function(r) {
+ document.getElementById("bandwidth-capacity").textContent =
numberStrWithCommas((r / kilobitsInGigabit).toString());
+ });
+}
+
+function getCacheDownCount() {
+ ajax("/api/cache-down-count", function(r) {
+ document.getElementById("cache-down").textContent = r;
+ });
+}
+
+function getVersion() {
+ ajax("/api/version", function(r) {
+ document.getElementById("version").textContent = r;
+ });
+}
+
+function getTrafficOpsUri() {
+ ajax("/api/traffic-ops-uri", function(r) {
+ // This used to be done by setting the element's `innerHTML`,
but that doesn't remove
+ // the child nodes. They're orphaned, but continue to take up
memory.
+ const link = document.createElement('A');
+ link.href = r;
+ link.textContent = r;
+ const sourceURISpan = document.getElementById('source-uri');
+ while (sourceURISpan.lastChild) {
+ sourceURISpan.removeChild(sourceURISpan.lastChild);
+ }
+ sourceURISpan.appendChild(link);
+ });
+}
+
+function getTrafficOpsCdn() {
+ ajax("/publish/ConfigDoc", function(r) {
+ document.getElementById("cdn-name").textContent =
JSON.parse(r).cdnName || "unknown";
+ });
+}
+
+/**
+ * Fetches the event log from TM and updates the "Event Log" table with the new
+ * results.
+*/
+function getEvents() {
+ /// \todo add /api/events-since/{index} (and change Traffic Monitor to
keep latest
+ ajax("/publish/EventLog", function(r) {
+ const events = JSON.parse(r).events || [];
+ for (const event of events.slice(lastEvent+1)) {
+ lastEvent = event.index
+ const row =
document.getElementById("event-log").insertRow(0);
+
+ row.insertCell(0).textContent = event.name;
+ row.insertCell(1).textContent = event.type;
+
+ const cell = row.insertCell(2);
+ if(event.isAvailable) {
+ cell.textContent = "available";
+ } else {
+ cell.textContent = "offline";
+ row.classList.add("error");
+ }
+
+ row.insertCell(3).textContent = event.description;
+ row.insertCell(4).textContent = new Date(event.time *
1000).toISOString();
+ }
+ });
+}
+
+/**
+ * Fetches the current cache server states and statistics from TM and updates
+ * the "Cache States" table with the results - replacing the current content.
+*/
+function getCacheStates() {
+ ajax("/api/cache-statuses", function(r) {
+ const servers = new Map(Object.entries(JSON.parse(r)));
+ const table = document.createElement('TBODY');
+ table.id = "cache-states"
+
+ for (const [serverName, server] of servers) {
+ const row = table.insertRow(0);
+
+ row.insertCell(0).textContent = serverName;
+ row.insertCell(1).textContent = server.type ||
"UNKNOWN";
+ row.insertCell(2).textContent =
server.status.indexOf("ONLINE") !== 0 ? server.ipv4_available : "N/A";
+ row.insertCell(3).textContent =
server.status.indexOf("ONLINE") !== 0 ? server.ipv6_available : "N/A";
+ row.insertCell(4).textContent = server.status || "";
+ if (Object.prototype.hasOwnProperty.call(server,
"status")) {
+ if (server.status.indexOf("ADMIN_DOWN") !== -1
|| server.status.indexOf("OFFLINE") !== -1) {
+ row.classList.add("warning");
+ } else if (!server.combined_available &&
server.status.indexOf("ONLINE") !== 0) {
+ row.classList.add("error");
+ } else if (server.status.indexOf("
availableBandwidth") !== -1) {
+ row.classList.add("error");
+ }
+ }
+
+ row.insertCell(5).textContent = server.load_average ||
"";
+ row.insertCell(6).textContent = server.query_time_ms ||
"";
+ row.insertCell(7).textContent = server.health_time_ms
|| "";
+ row.insertCell(8).textContent = server.stat_time_ms ||
"";
+ row.insertCell(9).textContent = server.health_span_ms
|| "";
+ row.insertCell(10).textContent = server.stat_span_ms ||
"";
+
+ if (Object.prototype.hasOwnProperty.call(server,
"bandwidth_kbps")) {
+ const kbps = (server.bandwidth_kbps /
kilobitsInMegabit).toFixed(2);
+ const max =
numberStrWithCommas((server.bandwidth_capacity_kbps /
kilobitsInMegabit).toFixed(0));
+ row.insertCell(11).textContent = `${kbps} /
${max}`;
+ } else {
+ row.insertCell(11).textContent = "N/A";
+ }
+
+ row.insertCell(12).textContent =
server.connection_count || "N/A";
+ }
+
+
+ const oldtable = document.getElementById("cache-states");
+ oldtable.parentNode.replaceChild(table, oldtable);
+
+ });
+}
+
+/**
+ * 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.
+ * @param f The floating point number to format
+*/
+const dsDisplayFloat = (f) => { return f === 0 ? "" : f.toFixed(2); }
+
+/**
+ * Attempts to extract data from a deliveryService object, but falls back on
"N/A" if it doesn't have that
+ * property.
+ * @param ds The Delivery Service from which to extract data
+ * @param prop The property being extracted. Technically, the extracted
property is dsDisplayFloat(parseFloat(ds[prop][0].value)).
+*/
+function getDSProperty(ds, prop) {
+ try {
+ return dsDisplayFloat(parseFloat(ds[prop][0].value));
+ } catch (e) {
+ console.error(e);
+ }
+ return "N/A";
+}
+
+/**
+ * Fetches the current Delivery Service stats from TM and updates the
"Delivery Service States"
+ * table with the results - replacing the current content.
+*/
+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) {
+ const deliveryServices = new
Map(Object.entries(JSON.parse(r).deliveryService));
+ const table = document.createElement('TBODY');
+ table.id = "deliveryservice-stats";
+
+ for (const [dsName, deliveryService] of deliveryServices) {
+ const row = table.insertRow(0);
+ const available = !deliveryService.isAvailable ||
!deliveryService.isAvailable[0] || !deliveryService.isAvailable[0].value ===
"true";
+ if (available) {
+ row.classList.add("error");
+ }
+
+ row.insertCell(0).textContent = dsName;
+ row.insertCell(1).textContent = available ? "available"
: `unavailable - ${deliveryService["error-string"][0].value}`;
+ row.insertCell(2).textContent =
(Object.prototype.hasOwnProperty.call(deliveryService, "caches-reporting") &&
+
Object.prototype.hasOwnProperty.call(deliveryService, "caches-available") &&
+
Object.prototype.hasOwnProperty.call(deliveryService, "caches-configured")) ?
+
`${deliveryService['caches-reporting'][0].value} /
${deliveryService['caches-available'][0].value} /
${deliveryService['caches-configured'][0].value}` : "N/A";
+
+ row.insertCell(3).textContent =
Object.prototype.hasOwnProperty.call(deliveryService, "total.kbps") ?
(deliveryService['total.kbps'][0].value / kilobitsInMegabit).toFixed(2) : "N/A";
+ row.insertCell(4).textContent =
getDSProperty(deliveryService, "total.tps_total");
+ row.insertCell(5).textContent =
getDSProperty(deliveryService, "total.tps_2xx");
+ row.insertCell(6).textContent =
getDSProperty(deliveryService, "total.tps_3xx");
+ row.insertCell(7).textContent =
getDSProperty(deliveryService, "total.tps_4xx");
+ row.insertCell(8).textContent =
getDSProperty(deliveryService, "total.tps_5xx");
+ row.insertCell(9);
+ // \todo implement disabled locations
+ }
+
+ const oldtable =
document.getElementById("deliveryservice-stats");
+ oldtable.parentNode.replaceChild(table, oldtable);
+ });
+}
+
+/**
+ * Fetches not only the "Cache States" but also the aggregate cache server
statistics used in the
+ * informational section at the top of the page.
+ */
+function getCacheStatuses() {
+ getCacheCount();
+ getCacheAvailableCount();
+ getCacheDownCount();
+ getCacheStates();
+}
+
+/**
+ * Fetches the metadata information used at the very top of the page.
+ */
+function getTopBar() {
+ getVersion();
+ getTrafficOpsUri();
+ getTrafficOpsCdn();
+ getCacheStatuses();
+}
+
+/**
+ * Runs immediately after content is loaded, fetching initial information and
setting intervals for
+ * for gathering other data.
+ */
+function init() {
+ 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(getTrafficOpsCdn, 10500); // 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);
+}
+
+window.addEventListener('load', init);
diff --git a/traffic_monitor/static/sorttable.js
b/traffic_monitor/static/sorttable.js
deleted file mode 100644
index 38b0fc6..0000000
--- a/traffic_monitor/static/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 ? ' <font
face="webdings">5</font>' : ' ▴';
- 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 ? ' <font
face="webdings">6</font>' : ' ▾';
- 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 ? ' <font
face="webdings">6</font>' : ' ▾';
- 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(/^-?[�$�]?[\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);
- }
-};
-
diff --git a/traffic_monitor/static/style.css b/traffic_monitor/static/style.css
new file mode 100644
index 0000000..87de588
--- /dev/null
+++ b/traffic_monitor/static/style.css
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+body {
+ font-family: "Lato", sans-serif;
+ font-size: 14px;
+ margin: 0;
+ max-width: 100vw;
+}
+
+/*****************/
+/* Table Styling */
+/*****************/
+table {
+ border-collapse: separate;
+ border-spacing: 0px 0;
+ width: 100%;
+}
+
+th, td {
+ padding:5px 20px 5px 5px;
+}
+
+tbody tr:nth-child(even) {
+ background: #ced;
+}
+tbody tr:nth-child(odd) {
+ background: #fff;
+}
+.error {
+ background-color: #f00;
+}
+
+.warning {
+ background-color: #f80;
+}
+
+#cache-states td:nth-child(n+4),
+#cache-states-content td:nth-child(n+4),
+#event-log td:last-child,
+#deliveryservice-stats td:nth-child(n+3),
+#deliveryservice-stats-content th:nth-child(n+3) {
+ text-align: right;
+}
+#cache-states td:first-child,
+#event-log td:first-child,
+#event-log td:last-child,
+#deliveryservice-stats td:first-child,
+th {
+ white-space: nowrap;
+}
+#event-log-content th:last-child {
+ text-align: center;
+}
+
+/*****************/
+/* Top Bar */
+/*****************/
+#top-bar {
+ display: inline-flex;
+ justify-content: space-around;
+ align-items: center;
+ width: 100%;
+ margin: 15px 0;
+}
+
+/*****************/
+/* Links */
+/*****************/
+#links {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ max-width: 100ch;
+}
+#links div {
+ margin-left: 4px;
+}
+#links a {
+ display: block;
+}
+
+/*****************/
+/* Tabs */
+/*****************/
+input[type=radio] {
+ visibility: hidden;
+ display: none;
+}
+label {
+ display: block;
+ padding: 14px 21px;
+ border-radius: 2px 2px 0 0;
+ cursor: pointer;
+ position: relative;
+ top: 4px;
+ transition: background-color ease-in-out 0.3s;
+ text-align: center;
+ border: 1px solid green;
+}
+label:hover {
+ background-color: #cfd;
+}
+.tabs {
+ list-style: none;
+ max-width: 100%;
+ border: 1px solid #ccc;
+ background-color: #f1f1f1;
+ position: relative;
+}
+.tabcontent {
+ z-index: 2;
+ display: none;
+ visibility: hidden;
+ overflow: hidden;
+ width: 100%;
+ position: absolute;
+ top: 53px;
+ left: 0;
+ padding: 6px 0;
+ border-top: none;
+}
+input.tab:checked ~ div.tabcontent {
+ display: block;
+ visibility: visible;
+}
+input.tab:checked ~ label{
+ background-color: #adb;
+ border-bottom-width: 0;
+}
+.tabs li {
+ float: left;
+ display: block;
+}