This is an automated email from the ASF dual-hosted git repository.
ocket8888 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 5153889 Update TM UI for multiple interfaces (#4746)
5153889 is described below
commit 515388937fe665e7359c3265291132f894166d54
Author: Steve Hamrick <[email protected]>
AuthorDate: Tue Sep 15 14:51:14 2020 -0600
Update TM UI for multiple interfaces (#4746)
* Update UI to reflect multiple interfaces
* Fix some issues with styling and typo
* Use tabs and fix merge conflic
* Add CHANGELOG entry
* Cleanup
* Only sort objects once, use set for constant time lookup of rows.
* Fix issues with interface rows being marked as down
---
CHANGELOG.md | 1 +
traffic_monitor/static/index.html | 70 ++++++++++++++----
traffic_monitor/static/script.js | 145 +++++++++++++++++++++++++++++---------
traffic_monitor/static/style.css | 70 ++++++++++++++++--
4 files changed, 233 insertions(+), 53 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6d28ac1..a68d158 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -80,6 +80,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- The `/servers` and `/servers/{{ID}}}` TO API endpoints have been updated
to use and reflect multi-interface servers.
- Updated `/cdns/{{name}}/configs/monitoring` TO API endpoint to return
multi-interface data.
- CDN Snapshots now use a server's "service addresses" to provide its IP
addresses.
+ - Changed the `Cache States` tab of the Traffic Monitor UI to properly
handle multiple interfaces.
- Changed the `/publish/CacheStats` in Traffic Monitor to support multiple
interfaces.
- Changed the CDN-in-a-Box server enrollment template to support multiple
interfaces.
diff --git a/traffic_monitor/static/index.html
b/traffic_monitor/static/index.html
index 66b38f9..5203525 100644
--- a/traffic_monitor/static/index.html
+++ b/traffic_monitor/static/index.html
@@ -78,23 +78,67 @@ under the License.
<table>
<thead>
<tr>
- <th>Server</th>
- <th>Type</th>
- <th>IPv4</th>
- <th>IPv6</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>
+ <th></th>
+ <th
class="text-entry">Server</th>
+ <th
class="text-entry">Type</th>
+ <th
class="text-entry">IPv4</th>
+ <th
class="text-entry">IPv6</th>
+ <th
class="text-entry">Status</th>
+ <th
class="number-entry">Load Average</th>
+ <th
class="number-entry">Query Time (ms)</th>
+ <th
class="number-entry">Health Time (ms)</th>
+ <th
class="number-entry">Stat Time (ms)</th>
+ <th
class="number-entry">Health Span (ms)</th>
+ <th
class="number-entry">Stat Span (ms)</th>
+ <th
class="number-entry">Bandwidth (mbps)</th>
+ <th
class="number-entry">Connection Count</th>
</tr>
</thead>
<tbody id="cache-states"></tbody>
</table>
+ <template id="interface-template" >
+ <table class="sub-table">
+ <thead>
+ <tr>
+ <th
class="text-entry">Interface</th>
+ <th
class="text-entry">IPv4</th>
+ <th
class="text-entry">IPv6</th>
+ <th
class="text-entry">Status</th>
+ <th
class="number-entry">Bandwidth (mbps)</th>
+ <th
class="number-entry">Connection Count</th>
+ </tr>
+ </thead>
+ <tbody
class="interface-content"></tbody>
+ </table>
+ </template>
+ <template id="cache-status-row-template">
+ <tr class="server-status">
+ <td> <div
class="sub-table-indicator right"></div> </td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ </tr>
+ </template>
+ <template id="interface-row-template">
+ <tr>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="text-entry"></td>
+ <td class="number-entry"></td>
+ <td class="number-entry"></td>
+ </tr>
+ </template>
</div>
</li>
diff --git a/traffic_monitor/static/script.js b/traffic_monitor/static/script.js
index 7aabb31..f2511c4 100644
--- a/traffic_monitor/static/script.js
+++ b/traffic_monitor/static/script.js
@@ -130,7 +130,7 @@ function getEvents() {
ajax("/publish/EventLog", function(r) {
const events = JSON.parse(r).events || [];
for (const event of events.slice(lastEvent+1)) {
- lastEvent = event.index
+ lastEvent = event.index;
const row =
document.getElementById("event-log").insertRow(0);
row.insertCell(0).textContent = event.name;
@@ -155,20 +155,50 @@ function getEvents() {
* the "Cache States" table with the results - replacing the current content.
*/
function getCacheStates() {
+ function parseIPAvailable(server, ipField) {
+ return server.status.indexOf("ONLINE") !== 0 ? server[ipField]
: "N/A";
+ }
+
+ function parseBandwidth(server) {
+ if (Object.prototype.hasOwnProperty.call(server,
"bandwidth_kbps") &&
+ Object.prototype.hasOwnProperty.call(server,
"bandwidth_capacity_kbps")) {
+ const kbps = (server.bandwidth_kbps /
kilobitsInMegabit).toFixed(2);
+ const max =
numberStrWithCommas((server.bandwidth_capacity_kbps /
kilobitsInMegabit).toFixed(0));
+ return `${kbps} / ${max}`;
+ } else {
+ return "N/A";
+ }
+ }
+
ajax("/api/cache-statuses", function(r) {
- const servers = new Map(Object.entries(JSON.parse(r)));
+ let serversArray =
Object.entries(JSON.parse(r)).sort((serverTupleA, serverTupleB) => {
+ return
-1*serverTupleA[0].localeCompare(serverTupleB[0]);
+ });
+ const servers = new Map(serversArray);
+
+ const oldtable = document.getElementById("cache-states");
const table = document.createElement('TBODY');
- table.id = "cache-states"
+ const interfaceTableTemplate =
document.getElementById("interface-template").content.children[0];
+ const interfaceRowTemplate =
document.getElementById("interface-row-template").content.children[0];
+ const cacheStatusRowTemplate =
document.getElementById("cache-status-row-template").content.children[0];
+ table.id = oldtable.id;
+
+ // Match visibility of interface tables based on previous table
+ const interfaceRows =
oldtable.querySelectorAll(".encompassing-row");
+ let openCachesByName = new Set();
+ for(const row of interfaceRows) {
+ if(row.classList.contains("visible")){
+
openCachesByName.add(row.querySelector(".sub-table").getAttribute("server-name"));
+ }
+ }
for (const [serverName, server] of servers) {
- const row = table.insertRow(0);
+ const row = cacheStatusRowTemplate.cloneNode(true);
+ const cacheRowChildren = row.children;
+ const indicatorDiv = cacheRowChildren[0].children[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 (Object.prototype.hasOwnProperty.call(server,
"status") &&
+
Object.prototype.hasOwnProperty.call(server, "combined_available")) {
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) {
@@ -178,28 +208,79 @@ function getCacheStates() {
}
}
- 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";
+ cacheRowChildren[1].textContent = serverName;
+ cacheRowChildren[2].textContent = server.type ||
"UNKNOWN";
+ cacheRowChildren[3].textContent =
parseIPAvailable(server, "ipv4_available");
+ cacheRowChildren[4].textContent =
parseIPAvailable(server, "ipv6_available");
+ cacheRowChildren[5].textContent = server.status || "";
+ cacheRowChildren[6].textContent = server.load_average
|| "";
+ cacheRowChildren[7].textContent = server.query_time_ms
|| "";
+ cacheRowChildren[8].textContent = server.health_time_ms
|| "";
+ cacheRowChildren[9].textContent = server.stat_time_ms
|| "";
+ cacheRowChildren[10].textContent =
server.health_span_ms || "";
+ cacheRowChildren[11].textContent = server.stat_span_ms
|| "";
+ cacheRowChildren[12].textContent =
parseBandwidth(server);
+ cacheRowChildren[13].textContent =
server.connection_count || "N/A";
+ table.prepend(row);
+
+ const encompassingRow = table.insertRow(1);
+ encompassingRow.classList.add("encompassing-row");
+ const encompassingCell = encompassingRow.insertCell(0);
+ const interfaceTable =
interfaceTableTemplate.cloneNode(true);
+ encompassingCell.colSpan = 14;
+ // Add interfaces
+ if (Object.prototype.hasOwnProperty.call(server,
"interfaces")) {
+ let interfacesArray =
Object.entries(server.interfaces).sort((interfaceTupleA, interfaceTupleB) => {
+ return -1 *
interfaceTupleA[0].localeCompare(interfaceTupleB[0]);
+ });
+ server.interfaces = new Map(interfacesArray);
+ interfaceTable.removeAttribute("id");
+ // To determine what cache this interface table
belongs to
+ // used to ensure servers that were expanded
remain expanded when refreshing the data.
+ interfaceTable.setAttribute("server-name",
serverName);
+ const interfaceBody =
interfaceTable.querySelector(".interface-content");
+ for (const [interfaceName, stat] of
server.interfaces) {
+ const interfaceRow =
interfaceRowTemplate.cloneNode(true);
+ const cells = interfaceRow.children;
+
+ cells[0].textContent = interfaceName;
+ cells[1].textContent =
parseIPAvailable(stat, "ipv4_available");
+ cells[2].textContent =
parseIPAvailable(stat, "ipv6_available");
+ cells[3].textContent = stat.status ||
"";
+ cells[4].textContent =
parseBandwidth(stat);
+ cells[5].textContent =
stat.connection_count || "N/A";
+
+ if
(Object.prototype.hasOwnProperty.call(stat, "available")) {
+ if (stat.available === false) {
+
interfaceRow.classList.add("error");
+ }
+ }
+
+ interfaceBody.prepend(interfaceRow);
+ }
+ row.onclick = function() {
+
if(encompassingRow.classList.contains("visible")) {
+
encompassingRow.classList.remove("visible");
+
indicatorDiv.classList.remove("down");
+ }
+ else {
+
encompassingRow.classList.add("visible");
+
indicatorDiv.classList.add("down");
+ }
+ };
+ }
+ else {
+ indicatorDiv.classList.add("hidden");
}
- row.insertCell(12).textContent =
server.connection_count || "N/A";
+ encompassingCell.appendChild(interfaceTable);
+ // Row was unhidden previously
+ if(openCachesByName.has(serverName)) {
+ row.click();
+ }
}
-
- const oldtable = document.getElementById("cache-states");
oldtable.parentNode.replaceChild(table, oldtable);
-
});
}
@@ -208,7 +289,7 @@ function getCacheStates() {
* 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); }
+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
@@ -230,8 +311,6 @@ function getDSProperty(ds, prop) {
* 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));
@@ -248,9 +327,9 @@ function getDsStats() {
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";
+
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");
diff --git a/traffic_monitor/static/style.css b/traffic_monitor/static/style.css
index e7c7e52..a12eabc 100644
--- a/traffic_monitor/static/style.css
+++ b/traffic_monitor/static/style.css
@@ -29,7 +29,7 @@ body {
/*****************/
table {
border-collapse: separate;
- border-spacing: 0px 0;
+ border-spacing: 0 0;
width: 100%;
}
@@ -37,12 +37,51 @@ th, td {
padding:5px 20px 5px 5px;
}
-tbody tr:nth-child(even) {
- background: #ced;
+tbody#cache-states tr.server-status:nth-child(4n+3) {
+ background: #adb;
}
-tbody tr:nth-child(odd) {
- background: #fff;
+
+tbody#cache-states tr.server-status:nth-child(odd) td {
+ border-top: 1px ridge black;
+}
+
+tbody.interface-content tr {
+ background: white;
+}
+
+tbody.interface-content tr:nth-child(even) {
+ background: #acd;
}
+
+table.sub-table {
+ border: 1px solid black;
+ border-radius: 3px;
+
+ width: 80%;
+ min-width: 300px;
+ margin: auto;
+ text-align: center
+}
+
+div.sub-table-indicator {
+ border: solid black;
+ border-width: 0 4px 4px 0;
+ display: inline-block;
+ padding: 5px;
+ cursor: pointer;
+
+ transition-duration: 0.4s;
+ transform: rotate(-45deg);
+}
+
+div.sub-table-indicator.hidden {
+ display: none;
+}
+
+div.sub-table-indicator.down {
+ transform: rotate(45deg);
+}
+
.error {
background-color: #f00!important;
}
@@ -51,8 +90,25 @@ tbody tr:nth-child(odd) {
background-color: #f80!important;
}
-#cache-states td:nth-child(n+4),
-#cache-states-content td:nth-child(n+4),
+#cache-states .text-entry {
+ text-align: left;
+}
+#cache-states .number-entry {
+ text-align: right;
+}
+
+tr.encompassing-row {
+ visibility: collapse;
+ opacity: 0;
+
+ transition: opacity 0.30s ease-in;
+}
+
+tr.encompassing-row.visible {
+ visibility: visible;
+ opacity: 1;
+}
+
#event-log td:last-child,
#deliveryservice-stats td:nth-child(n+3),
#deliveryservice-stats-content th:nth-child(n+3) {