<#--

    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.

-->
<#include "*/generic.ftl">

<#-- Format comma-delimited string-->
<#macro format_string str>
  <#if str?has_content>
    ${(str?split(","))?join(", ")}
  <#else>
    ${"<empty>"}
  </#if>
</#macro>

<#macro page_head>
<style>
.list-value {
    text-align: right !important;
}
</style>
</#macro>

<#macro page_body>
  <div class="page-header">
  </div>

  <#if (model.getMismatchedVersions()?size > 0)>
    <div id="message" class="alert alert-danger alert-dismissable">
      <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
      <strong>Drill does not support clusters containing a mix of Drillbit versions.
          Current drillbit version is ${model.getCurrentVersion()}.
          One or more drillbits in cluster have different version:
          ${model.getMismatchedVersions()?join(", ")}.
      </strong>
    </div>
  </#if>

  <div class="row">
    <div class="col-md-12">
      <h3><span id="sizeLabel">Drillbits <span class="label label-primary" id="size">${model.getDrillbits()?size}</span>
          <button type="button" class="btn btn-warning" id='reloadBtn' style="display:none;font-size:50%" title='New Drillbits detected! Click to Refresh' onclick='location.reload();'>
          <span class="glyphicon glyphicon-refresh"></span></button></h3>
      <div class="table-responsive">
        <table class="table table-hover" id="bitTable">
          <thead>
            <tr>
              <th>#</th>
              <th title="Drillbits in Cluster" style="cursor: help;">Address <span class="glyphicon glyphicon-info-sign" style="font-size:100%"/></th>
              <th title="Heap Memory in use (as percent of Total Heap)" style="cursor: help;">Heap Memory Usage <span class="glyphicon glyphicon-info-sign" style="font-size:100%"/></th>
              <th title="Estimated Direct Memory ACTIVELY in use (as percent of Peak Usage)" style="cursor: help;">Direct Memory Usage <span class="glyphicon glyphicon-info-sign" style="font-size:100%"/></th>
              <th title="Current CPU usage by Drill" style="cursor: help;">CPU Usage<span class="glyphicon glyphicon-info-sign" style="font-size:100%"/></th>
              <th title="Average load on the system in the last 1 minute" style="cursor: help;">Avg Sys Load <span class="glyphicon glyphicon-info-sign" style="font-size:100%"/></th>
              <th>User Port</th>
              <th>Control Port</th>
              <th>Data Port</th>
              <th>Version</th>
              <th>Status</th>
              <th>Uptime</th>
              <th style="display:${(model.isAuthEnabled() && !model.shouldShowAdminInfo())?then("none","table-cell")}">Shutdown</th>
            </tr>
          </thead>
          <tbody>
            <#assign i = 1>
            <#list model.getDrillbits() as drillbit>
              <tr id="row-${i}">
                <td>${i}</td>
                <td id="address" >${drillbit.getAddress()}<#if drillbit.isCurrent()>
                    <span class="label label-info" id="current">Current</span>
                    <#else>
                    <a onclick="popOutRemoteDbitUI('${drillbit.getAddress()}','${drillbit.getHttpPort()}');" style="cursor:pointer;color:blue" title="Open in new window"><span class="glyphicon glyphicon-new-window"/></a>
                  </#if>
                </td>
                <td id="httpPort" style="display:none">${drillbit.getHttpPort()}</td>
                <td class="heap">Not Available</td>
                <td class="direct">Not Available</td>
                <td class="bitload"><span class="label label-info" id="NA">Not Available</span></td>
                <td class="avgload"><span class="label label-info" id="NA">Not Available</span></td>
                <td id="port">${drillbit.getUserPort()}</td>
                <td>${drillbit.getControlPort()}</td>
                <td>${drillbit.getDataPort()}</td>
                <td>
                  <span class="label
                    <#if drillbit.isVersionMatch()>label-success<#else>label-danger</#if>">
                    ${drillbit.getVersion()}
                  </span>
                </td>
                <td id="status" >${drillbit.getState()}</td>
                <td class="uptime" >Not Available</td>
                <td>
                <#if (model.isAuthEnabled() && !model.shouldShowAdminInfo())>
                      <button class='shutdownCtrl' type="button" id="shutdown" disabled="true" style="opacity:0.5;cursor:not-allowed;display:none" onClick="" comment="Placeholder. Hidden by default">
                <#else>
                      <button class='shutdownCtrl' type="button" id="shutdown" disabled="true" style="opacity:0.5;cursor:not-allowed;display:inline" onClick="shutdown($(this));" title="Not available for remote Drillbits">
                </#if>
                      <span class="glyphicon glyphicon-off"></span></button>
                </td>
                <td id="queriesCount">  </td>
              </tr>
              <#assign i = i + 1>
            </#list>
          </tbody>
        </table>
      </div>
    </div>
  </div>

  <div class="row">
      <div class="col-md-12">
        <h3>Encryption</h3>
        <div class="table-responsive">
          <table class="table table-hover" style="width: auto;">
            <tbody>
                <tr>
                  <td>Client to Bit Encryption</td>
                  <td class="list-value">${model.isUserEncryptionEnabled()?string("Enabled", "Disabled")}
                    <#if model.isBitEncryptionEnabled()><span class="glyphicon glyphicon-lock"/></#if>
                  </td>
                </tr>
                <tr>
                  <td>Bit to Bit Encryption</td>
                  <td class="list-value">${model.isBitEncryptionEnabled()?string("Enabled", "Disabled")}
                    <#if model.isBitEncryptionEnabled()><span class="glyphicon glyphicon-lock"/></#if>
                  </td>
                </tr>
            </tbody>
          </table>
        </div>
      </div>
  </div>

   <#if model.shouldShowAdminInfo()>
       <div class="row">
            <div class="col-md-12">
              <h3>User Info </h3>
              <div class="table-responsive">
                <table class="table table-hover" style="width: auto;">
                  <tbody>
                      <tr>
                        <td>Admin Users</td>
                        <td class="list-value"><@format_string str=model.getAdminUsers()/></td>
                      </tr>
                      <tr>
                        <td>Admin User Groups</td>
                        <td class="list-value"><@format_string str=model.getAdminUserGroups()/></td>
                      </tr>
                      <tr>
                        <td>Process User</td>
                        <td class="list-value">${model.getProcessUser()}</td>
                      </tr>
                      <tr>
                        <td>Process User Groups</td>
                        <td class="list-value">${model.getProcessUserGroups()}</td>
                      </tr>
                  </tbody>
                </table>
              </div>
            </div>
        </div>
   </#if>
  <#assign queueInfo = model.queueInfo() />
  <div class="row">
      <div class="col-md-12">
        <h3>Query Throttling</h3>
        <div class="table-responsive">
          <table class="table table-hover" style="width: auto;">
            <tbody>
               <tr>
                  <td>Queue Status</td>
                  <td class="list-value">${queueInfo.isEnabled()?string("Enabled", "Disabled")}</td>
                </tr>
  <#if queueInfo.isEnabled() >
                <tr>
                  <td>Maximum Concurrent "Small" Queries</td>
                  <td class="list-value">${queueInfo.smallQueueSize()}</td>
                 </tr>
                 <tr>
                  <td>Maximum Concurrent "Large" Queries</td>
                  <td class="list-value">${queueInfo.largeQueueSize()}</td>
                </tr>
                  <tr>
                  <td>Cost Threshhold for Large vs. Small Queries</td>
                  <td class="list-value">${queueInfo.threshold()}</td>
                </tr>
                <tr>
                  <td>Total Memory</td>
                  <td class="list-value">${queueInfo.totalMemory()}</td>
                </tr>
                <tr>
                  <td>Memory per Small Query</td>
                  <td class="list-value">${queueInfo.smallQueueMemory()}</td>
                </tr>
                <tr>
                  <td>Memory per Large Query</td>
                  <td class="list-value">${queueInfo.largeQueueMemory()}</td>
                </tr>
  </#if>
            </tbody>
          </table>
        </div>
      </div>
  </div>
   <script charset="utf-8">
      var refreshTime = 10000;
      var nAText = "Not Available";
      var refresh = getRefreshTime();
      var timeout;
      var size = $("#size").html();
      reloadMetrics();
      setInterval(reloadMetrics, refreshTime);
      var hideShutdownBtns = setShutdownCtrl();

      //Hide Shutdown buttons for remote HTTPS entries
      function setShutdownCtrl() {
        for (i = 1; i <= size; i++) {
          let currentRow = $("#row-"+i);
          if ( location.protocol == "https:" && (currentRow.find("#current").html() != "Current") ) {
            //Hide Shutdown Button for remote nodes with HTTPS enabled
            currentRow.find(".shutdownCtrl").css('display','none');
          }
        }
      }

      //Gets a refresh time for graceful shutdown
      function getRefreshTime() {
          $.ajax({
              type: 'GET',
              url: '/gracePeriod',
              dataType: "json",
              complete: function (data) {
                  let gracePeriod = data.responseJSON["gracePeriod"];
                  if (gracePeriod > 0) {
                      refreshTime = gracePeriod / 3;
                  }
                  timeout = setTimeout(reloadStatus, refreshTime);
              }
          });
      }

      //Periodic reload of status of Drillbits
      function reloadStatus () {
          let result = $.ajax({
                      type: 'GET',
                      url: '/state',
                      dataType: "json",
                      complete: function(data) {
                            fillStatus(data,size);
                            }
                      });
          timeout = setTimeout(reloadStatus, refreshTime);
      }

      //Fill the Status table for all the listed drillbits
      function fillStatus(dataResponse,size) {
          let status_map = (dataResponse.responseJSON);
          //In case localhost has gone down (i.e. we don't know status from ZK)
          if (typeof status_map == 'undefined') {
            //Query a couple of other nodes for state details
            //Pick 3 Random bits
            let randomAltBitList = getRandomIndexList(size, 3);
            let numAltBits = randomAltBitList.length;
            for (j = 0; j < numAltBits; j++) {
              let currentRow = $("#row-"+ randomAltBitList[j]);
              if (currentRow.find("#current").html() == "Current") {
                continue; //Skip LocalHost
              }
              let address = currentRow.find("#address").contents().get(0).nodeValue.trim();
              let restPort = currentRow.find("#httpPort").contents().get(0).nodeValue.trim();
              let altStateUrl = location.protocol + "//" + address+":"+restPort + "/state";
              let altResponse = $.ajax({
               url:altStateUrl, 
               dataType:'json', 
               timeout: 3000,
                    success : function(stateDataJson) {
                        //Update Status & Buttons for alternate stateData
                        if (typeof status_map == 'undefined') {
                          //Note: Race-condition would not exist due to JScript's single threaded model, only one thread can update the variable
                          //Ref: https://stackoverflow.com/questions/7238586/do-i-need-to-be-concerned-with-race-conditions-with-asynchronous-javascript
                          status_map = stateDataJson; //Marking map as 'defined'
                          updateStatusAndShutdown(stateDataJson);
                        }
                    }
                });
              //Don't loop any more (for small #bits, status_map will never update fast enough)
              if (typeof status_map != 'undefined') {
                break;
              }
            }
          } else {
            updateStatusAndShutdown(status_map);
          }
      }

      //Generates a list of indices
      function getRandomIndexList(inputSize, outputSize) {
        //Generate inputList
        let inputList = [];
        let outputList = [];
        //Skip 'row-1'
        for (p = 2; p <= inputSize; p++) {
          let currentRow = $("#row-"+ p);
          if (currentRow.find("#status").text() == 'ONLINE' ) {
              inputList.push(p);
          }
        }
        //Define how many to pick (pick min if numDBits is too small)
        let actualOPSize = (inputList.length > outputSize ? outputSize : inputList.length);
        for (q = 0; q < actualOPSize; q++) {
          let randomIndex = Math.floor(Math.random() * inputList.length);
          outputList.push(inputList[randomIndex]); //Capture random pick
          inputList.splice(randomIndex, 1); //Remove from list
        }
        return outputList;
      }

      //Updates the status map
      function updateStatusAndShutdown(status_map) {
        let bitMap = {};
        if (typeof status_map != 'undefined') {
            for (let k in status_map) {
              bitMap[k] = status_map[k];
            }
        }
        for (i = 1; i <= size; i++) {
            let currentRow = $("#row-"+i);
            let address = currentRow.find("#address").contents().get(0).nodeValue.trim();
            let port = currentRow.find("#httpPort").html();
            let key = address+"-"+port;
            if (typeof status_map == 'undefined') {
                currentRow.find("#status").text(nAText).css('font-style','').prop('title','');
                currentRow.find("#shutdown").prop('disabled',true).css('opacity',0.5).css('cursor','not-allowed');
                currentRow.find("#queriesCount").text("");
            } else if (status_map[key] == null) {
                currentRow.find("#status").text("OFFLINE*").css('font-style','italic').prop('title','Drillbit is De-Registered from ZooKeeper');
                currentRow.find("#shutdown").prop('disabled',true).css('opacity',0.5).css('cursor','not-allowed');
                currentRow.find("#queriesCount").text("");
            } else {
                if (status_map[key] == "ONLINE") {
                    currentRow.find("#status").text(status_map[key]).css('font-style','').prop('title','');
                    //EnableShutdown IFF => !isAuthEnabled-&&-!HTTPS OR isAuthEnabled-&&-current
                    if ( ( !${model.isAuthEnabled()?c} && location.protocol != "https:" ) || ( ${model.shouldShowAdminInfo()?c} && currentRow.find("#current").html() == "Current" ) ) {
                      currentRow.find("#shutdown").prop('disabled',false).css('opacity',1.0).css('cursor','pointer').attr('title','');
                    }
                } else {
                    if (currentRow.find("#current").html() == "Current") {
                        fillQueryCount(i);
                    }
                    currentRow.find("#status").text(status_map[key]).css('font-style','').prop('title','');;
                }
                //Removing accounted key
                delete bitMap[key];
            }
        }
        //If bitMap is not empty, then new bits have been discovered!
        listNewDrillbits(bitMap);
      }

      //Add new Bits for listing
      function listNewDrillbits(newBits) {
        let newBitList = Object.keys(newBits);
        let tableRef = document.getElementById('bitTable').getElementsByTagName('tbody')[0];
        let bitId = size;
        for (i = 0; i < newBitList.length; i++) {
           let splitPt = newBitList[i].lastIndexOf("-");
           let displayNodeName = newBitList[i].substring(0, splitPt);
           let newBitHttpPort = newBitList[i].substring(splitPt+1);
           let newBitElemId = "neo-"+newBitList[i];
           let newBitElem = document.getElementsByName(newBitElemId);
           bitId++;
           let bitState = newBits[newBitList[i]];
           //Injecting new row for previously unseen Drillbit
           $('#bitTable').find('tbody')
               .append("<tr id='row-" + bitId + "' class='newbit' title='Recommend page refresh for more info'>"
                 + "<td>"+bitId+"</td>"
                 + "<td id='address' name='"+newBitElemId+"'>"+displayNodeName+" <span class='label label-primary' id='size' >new</span></td>"
                 + "<td id='httpPort' style='display:none'>"+newBitHttpPort+"</td>"
                 + "<td class='heap'>"+nAText+"</td>"
                 + "<td class='direct'>"+nAText+"</td>"
                 + "<td class='bitload'>"+nAText+"</td>"
                 + "<td  class='avgload'>"+nAText+"</td>"
                 + "<td uiElem='userPort'>--</td>"
                 + "<td uiElem='ctrlPort'>--</td>"
                 + "<td uiElem='dataPort'>--</td>"
                 + "<td uiElem='version'>"+nAText+"</td>"
                 + "<td id='status'>"+bitState+"</td>"
                 + "<td class='uptime'>"+nAText+"</td>"
                 + "<td>"
                   + "<button type='button' id='shutdown' onclick='shutdown($(this));' disabled='true' style='opacity:0.5;cursor:not-allowed;${(model.isAuthEnabled() && !model.shouldShowAdminInfo())?then('display:none','')}'>"
                   + "<span class='glyphicon glyphicon-off'></span></button></td>"
                 + "<td uiElem='queriesCount' />"
                 + "</tr>"
            );
        }
        //Update Drillbits count on top
        if (newBitList.length > 0) {
          //Setting Drillbit Count & Display Refresh Icon
          $('#size').text(bitId);
          $('#reloadBtn').css('display', 'inline');
          size = bitId;
        }
      }

      //Updates the outstanding queries in flight before shutdown
      function fillQueryCount(row_id) {
          let requestPath = "/queriesCount";
          let url = getRequestUrl(requestPath);
          let result = $.ajax({
                        type: 'GET',
                        url: url,
                        complete: function(data) {
                              queries = data.responseJSON["queriesCount"];
                              fragments = data.responseJSON["fragmentsCount"];
                              $("#row-"+row_id).find("#queriesCount").text(queries+" queries and "+fragments+" fragments remaining before shutting down");
                              }
                        });
      }
       <#if (model.shouldShowAdminInfo() || !model.isAuthEnabled()) >
        function shutdown(shutdownBtn) {
            let rowElem = $(shutdownBtn).parent().parent();
            let hostAddr = $(rowElem).find('#address').contents().get(0).nodeValue.trim();
            let hostPort = $(rowElem).find('#httpPort').html();
            // Always use the host address from the url for the current Drillbit. For details refer DRILL-6663
            if ((rowElem.find("#current").html() == "Current")) {
              hostAddr = location.hostname;
            }
            let host = hostAddr+":"+hostPort

            if (confirm("Are you sure you want to shutdown Drillbit running on " + host + " node?")) {
              let url = location.protocol + "//" + host + "/gracefulShutdown";
              let result = $.ajax({
                    type: 'POST',
                    url: url,
                    contentType : 'text/plain',
                    error: function (request, textStatus, errorThrown) {
                        alert(errorThrown);
                    },
                    success: function(data) {
                        alert(data.responseJSON["response"]);
                        shutdownBtn.prop('disabled',true).css('opacity',0.5);
                    }
              });
            }
        }
        </#if>

      //Pops out the WebUI for a remote Drillbit
      function popOutRemoteDbitUI(dbitHost, dbitPort) {
            let dbitWebUIUrl = location.protocol+'//'+ dbitHost+':'+dbitPort;
            let tgtWindow = 'dbit_'+dbitHost;
            window.open(dbitWebUIUrl, tgtWindow);
      }

      //Construct the URL with the appropriate protocol and host
      function getRequestUrl(requestPath) {
            let protocol = location.protocol;
            let host = location.host;
            var url = protocol + "//" + host + requestPath;
            return url;
      }

      //Iterates through all the nodes for update
      function reloadMetrics() {
          for (i = 1; i <= size; i++) {
            //Skip metrics update for remote bits in HTTPS mode
            if (i > 1 && location.protocol == "https:") {
                break;
            }
            let currentRow = $("#row-"+i);
            let address = "";
            //For 'current' bit, address is referred to by location.hostname instead of FQDN ()
            if (i == 1) {
              address = location.hostname;
            } else {
              address = currentRow.find("#address").contents().get(0).nodeValue.trim();
            }
            let httpPort = currentRow.find("#httpPort").contents().get(0).nodeValue.trim();
            updateMetricsHtml(address, httpPort, i);
          }
      }

      //Update memory
      function updateMetricsHtml(drillbit,webport,idx) {
        /* NOTE: For remote drillbits:
           If Authentication or SSL is enabled; we'll assume we don't have valid certificates
        */
        let remoteHost = location.protocol+"//"+drillbit+":"+webport;
        //
        let result = $.ajax({
          type: 'GET',
          url: location.protocol+"//"+drillbit+":"+webport+"/status/metrics",
          dataType: "json",
          error: function(data) {
            resetMetricsHtml(idx);
          },
          complete: function(data) {
            if (typeof data.responseJSON == 'undefined') {
                resetMetricsHtml(idx);
                return;
            }
            let metrics = data.responseJSON['gauges'];
            //Memory
            let usedHeap = metrics['heap.used'].value;
            let maxHeap  = metrics['heap.max'].value;
            let usedDirect = metrics['drill.allocator.root.used'].value;
            let peakDirect = metrics['drill.allocator.root.peak'].value;
            let heapUsage    = computeMemUsage(usedHeap,maxHeap);
            let directUsage  = computeMemUsage(usedDirect,peakDirect);
            let rowElem = document.getElementById("row-"+idx);
            let heapElem = rowElem.getElementsByClassName("heap")[0];
            heapElem.innerHTML = heapUsage;
            let directElem = rowElem.getElementsByClassName("direct")[0];
            directElem.innerHTML = directUsage;
            //DrillbitLoad
            let dbitLoad = metrics['drillbit.load.avg'].value;
            let dbitLoadElem = rowElem.getElementsByClassName("bitload")[0];
            if (dbitLoad >= 0) {
              dbitLoadElem.innerHTML = parseFloat(Math.round(dbitLoad * 10000)/100).toFixed(2) + "%";
            } else {
              dbitLoadElem.innerHTML = nAText;
            }
            //AvgSysLoad
            let avgSysLoad = metrics['os.load.avg'].value;
            let sysLoadElem = rowElem.getElementsByClassName("avgload")[0];
            sysLoadElem.innerHTML = avgSysLoad;
            //Uptime
            let uptimeValue = metrics['drillbit.uptime'].value;
            let uptimeElem = rowElem.getElementsByClassName("uptime")[0];
            uptimeElem.innerHTML = elapsedTime(uptimeValue);
          }
        });
      }

      //Reset the metrics of a Drillbit in the table
      function resetMetricsHtml(idx) {
        $.each(["heap","direct","bitload","avgload","uptime"], function(i, key) {
          let resetElem = document.getElementById("row-"+idx).getElementsByClassName(key)[0];
          resetElem.innerHTML = nAText;
        });
    };

      //Compute Usage
      function computeMemUsage(used, max) {
        let percent = 0;
        if ( max > 0) {
          percent = Math.round((100 * used / max), 2);
        }
        let usage   =  bytesInGB(used, 2) + "GB (" + Math.max(0, percent) + "% of "+ bytesInGB(max, 2) +"GB)";
        return usage;
      }

      //Translate bytes into GB with decimal places
      function bytesInGB(byteVal, decimal) {
        let scale = Math.pow(10,decimal);
        return Math.round(scale * byteVal / 1073741824)/scale;
      }

      //Calculate Uptime
      function elapsedTime(valueInMsec) {
        let elapsedTime = "";
        let d, h, m, s;
        s = Math.floor(valueInMsec / 1000);
        m = Math.floor(s / 60);
        s = s % 60;
        h = Math.floor(m / 60);
        m = m % 60;
        d = Math.floor(h / 24);
        h = h % 24;
        let detailCount = 0;
        if (!isNaN(d) && d > 0) {
          elapsedTime = d+"d ";
          detailCount += 1;
        }
        if (!isNaN(h) && h > 0) {
          elapsedTime = elapsedTime + h+"h ";
          detailCount += 1;
        }
        if (!isNaN(m) && m > 0 && detailCount < 2) {
          elapsedTime = elapsedTime + m+"m ";
          detailCount += 1;
        }
        if (s > 0 && detailCount < 2) {
          elapsedTime = elapsedTime + s+"s";
        }
        return elapsedTime;
     };
  </script>
</#macro>

<@page_html/>
