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) {

Reply via email to