<style>
#minerList {
  background-color: whitesmoke;
  padding-left: 15px;
  padding-right: 15px;
}

td, th{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 140px;
}
.show-it {
  overflow: visible !important;
}

td {
   padding: 5px !important;
}

.temp-hot,.glyphicon-fire {
  color:red;
}

.repair-fan,.error {
  font-size: 16px; 
  color:navy;
}

.fa-power-off {
  color:maroon;
}

.fa-skull-crossbones {
  color:black;
}

.fa-triangle-exclamation {
  color:#FFBF00;
}

.bg-temp-hot {
  background-color:red;
  color: white;
  font-weight: bold;
}

.bg-temp-warm{
  background-color:orange;
  color: white;
  font-weight: bold;
}

.bg-temp-mild {
  background-color:limegreen;
  color: white;
  font-weight: bold;
}

.bg-temp-cool {
  background-color: deepskyblue;
  color: white;
  font-weight: bold;
}

.bg-temp-dead {
  background-color:rgb(19, 18, 18);
  color: white;
  font-weight: bold;
}

.bg-temp-empty {
  background-color:rgb(199, 199, 199);
  color: black;
  font-weight: bold;
}

td, th{
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  border:1px solid black;
  max-height: 20px;
}

td {
   padding: 5px !important;
}

.shelf-num{
  width: 30px;
  max-width: 60px;
  text-align: right;
}

.shelf-row{
  height: 48px;
}

.layout-table {
   margin-bottom: 20px;
}

.centered {
  text-align: center;
}

.text-right {
  text-align: right;
}

.pad-left {
  padding-left:20px;
}

.pad-right {
  padding-right:20px;
}

@media only screen and (max-width: 640px) {
  .large-screen {
    display: none;
  }
}

.fa-lock-open {
  color:goldenrod;
}

.fa-lock {
  color:#804A00;
}

.ssh-state {
  float:right;
  margin-right:20px;
}

.filter-instructions{
  background: white;
  position: absolute;
  border:2px black solid;
  padding:10px;
  width:660px;
  height:460px;
}

* {
  box-sizing: border-box;
}

.row {
  display: flex;
  margin-left:-5px;
  margin-right:-5px;
}

.column {
  flex: 50%;
  padding: 5px;
}

.rack-title {
  font-weight: bold;
  font-size: large;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
  width: 100%;
  border: 1px solid #ddd;
}

th, td {
  padding: 6px;
  height:30px;
}

tr:nth-child(even) {
  background-color: #f2f2f2;
}

.miner-open, .miner-state {
    cursor: pointer;
}

.sizing-cell {
  min-width:70px;
}
</style>
<script> 

  const getTempStateName = function (temperature, tempBoundaries) {
    tempBoundaries = tempBoundaries ||  {"hot":90,"warm":80,"mild":70};
    if (temperature >= tempBoundaries.hot) {
      return 'hot';
    } 
    else if (temperature >= tempBoundaries.warm) {
      return 'warm';
    } 
    else if (temperature >= tempBoundaries.mild) {
      return 'mild';
    } 
    else if (temperature == 0) {
      return 'dead';
    } 
    return 'cool';  
  }   
  
  function RackLayout(rack, hall, minerTypeMap) {
  
    rack = rack;
    hall = hall;
    minerTypeMap = minerTypeMap;
    
    cell = [];
    shelfUnitsCount = 3; //default
    shelvingUnits = []; //default
    shelvesCount = 1; //default
  
    this.addCell = function(shelf = 0, position = 0, minerInfo) {
      shelf = parseInt(shelf);
      position = parseInt(position);

      if (!cell[shelf]) {
        cell[shelf] = []
      }
      
      if (cell[shelf][position]) {
        console.warn("Duplicate position within the rack", minerInfo);
        return;
      }
  
      const {placement = {}, minerType} = minerInfo;
      const {upright} = placement;
      const {uprightUnits = 1, onsideUnits = 1, tempBoundaries} = minerTypeMap[minerType] || {};
      minerInfo.shelvingUnits = upright ? uprightUnits : onsideUnits;
      cell[shelf][position] = minerInfo;
      
      if (!shelvingUnits[shelf]) {
        shelvingUnits[shelf] = 0;
      }
      shelvingUnits[shelf] += minerInfo.shelvingUnits;
  
      if (shelvingUnits[shelf] > shelfUnitsCount) {
        shelfUnitsCount = shelvingUnits[shelf];
      }
  
      if (shelf > shelvesCount) {
        shelvesCount = shelf;
      }
    }       
  
    this.getTableHtml = function() {
      let tableHtml = [];
      if (!hall && !rack) {
        return "";
      }
      tableHtml.push(`<span class="rack-layout"><table class="layout-table" hall="${hall}" rack="${rack}"><tr class="rack-title-row"><th class="rack-title" colspan="${shelfUnitsCount+1}">&nbsp;&nbsp;${hall}, Rack: ${rack}</th></tr>`);
      tableHtml.push('<tr class="rack-header"><th></th>');
      for (let position=1; position <= shelfUnitsCount; position++){
        tableHtml.push(`<th class="sizing-cell">${position}</th>`);
      }
      tableHtml.push('</tr>');
      
      for(let shelf=1; shelf <= shelvesCount; shelf++) {
        tableHtml.push(`<tr class="shelf-row" shelf="${shelf}"><td class="shelf-num">${shelf}</td>`);
        let shelfUnitsConsumed = 0;  
        for (let position=1; position <= shelfUnitsCount && shelfUnitsConsumed < shelfUnitsCount; position++){
          const minerInfo = !cell[shelf] ? null : cell[shelf][position];
          
          if (minerInfo) {
            let  {
              placement, workerName, accountName, model, modelType, minerType, ipAddress, macAddress, logStates = {}
              , condition = {}, conditionPrime = {}, serialNumber, runtimeState
              , psuSerialNumber, psuModel, breakerPanelId, powerBarId,powerPhase, transformerLow, transformerMed,             
            } = minerInfo;
            const {shelf, position} = placement;
            let {temperatures=[0], timestamp, hashRate, hashRateAvg = 0, exhaustSpeed, intakeSpeed:fanSpeeds} = condition;
            let intakeSpeed =  Array.isArray(fanSpeeds) ? Math.min(...fanSpeeds) : fanSpeeds;
            let errors = logStates && Array.isArray(logStates.errors) ? minerInfo.logStates.errors : [];
            
            if (runtimeState === "hashing") {
              runtimeState = '';
            }
            
            const { hashRate:hashRatePrime, hashRateAvg:hashRateAvgPrime = 0} = conditionPrime || {};
            const temp = Math.max(...temperatures) || 0;
            const minerTyped = minerTypeMap[minerType] || {};
            const {tempBoundaries} = minerTyped;
            const tempClass = getTempStateName(temp, tempBoundaries);
  
            const colspan = minerInfo.shelvingUnits;
            shelfUnitsConsumed += colspan;
  
            hashRateAvg =  parseInt(hashRate || hashRateAvg); 
            let hashRateBestAvg = parseInt(hashRatePrime || hashRateAvgPrime);
            let hashRatePerf = Math.round(hashRateAvg / (hashRateBestAvg || hashRateAvg || 1) * 20) * 5;
            let coldBoot = hashRateAvg < hashRateBestAvg*0.8;
  
            let lastScanTitle = "never";
            let lastScan = "never";
            
            if (condition && timestamp) {
              const priorScanHoursDiff =  (Date.now() - timestamp)/(60*60*1000);
              lastScanTitle = !timestamp.toUTCString ? "n/a" : timestamp.toUTCString();
              lastScan = "recent";
              
              if (priorScanHoursDiff > 24 ) {
                lastScan = parseInt(priorScanHoursDiff/24, 10) + " days";
              } else if (priorScanHoursDiff > 1 ){
                lastScan = parseInt(priorScanHoursDiff, 10) + " hours";
              }  
  
              if(/^1\s/.test(lastScan)) {
                lastScan = lastScan.replace(/s$/,'');
              }
            }
  
            let glyphHtml = ""
            let glyphNode = ""
            let glyphClass = "";
            let glyphTitle = "";
            if (lastScan === "recent") {
              // Network connection is not really an issue if the miner is still mining
              if (errors.length === 1 && /network connection lost/i.test(errors[0]) && hashRatePerf > 10) {
                errors = [];
              }
  
              // Show fan repair icon when miner currently underperforming and poor fan speed
              let brokenHashBoards = !temperatures ? 0 : temperatures.filter(i => !i).length;
              if (hashRatePerf < 10 && (intakeSpeed < 2000 || exhaustSpeed < 2000)) {
                glyphTitle += "Exhaust=" + exhaustSpeed + ", All fans=" + fanSpeeds ;
                glyphClass="material-icons repair-fan";   
                glyphNode="mode_fan_off";   
              } else if (coldBoot) {
                glyphClass="fas fa-power-off";
                glyphTitle="Power off miner till PSU fan stops! (" + new Intl.NumberFormat('en-US', {maximumSignificantDigits: 5}).format(hashRatePrime) + ")";
              } else if (brokenHashBoards) {
                glyphClass="fas fa-triangle-exclamation";
                glyphTitle= brokenHashBoards + " inactive hash boards.";
              } else if (errors.length) {
                glyphClass="fas fa-skull-crossbones";
              }
            } else {
              glyphClass="fas fa-link-slash";   
            }
  
            if (glyphClass) {
              glyphTitle += (glyphTitle ? '\n' :'') + errors.join('');
              glyphHtml = `<i class="miner-state ${glyphClass}" title="${glyphTitle}">${glyphNode}</i>`;
            }
  
            const titleText = `${ipAddress}, ${macAddress}  \n${accountName}.${workerName} \nHashrate: ${parseInt(hashRate||0)} \nLast scan: ${lastScan} \nTemperatures: ${temperatures} \nFan speeds: ${fanSpeeds}`;
            tableHtml.push(`<td class="miner-open bg-temp-${tempClass}" colspan="${colspan}"  placement='${JSON.stringify(placement)}'><div align="center" class="miner-tag"  mac="${macAddress}" id="cell_${macAddress}" title="${titleText}"> ${glyphHtml} &nbsp;<span>${model}</span></div></td>`);            
          } else if (shelfUnitsConsumed < shelfUnitsCount) {
            shelfUnitsConsumed += 1;
            tableHtml.push(`<td class="miner-open bg-temp-empty" placement='${JSON.stringify({hall, shelf, rack, position})}'><div align="center" class="bg-temp-empty">n/a</div></td>`);
          }
        }
        tableHtml.push('</tr>');
      }
      tableHtml.push('</table></span>');
      return tableHtml.join('');
    }
  
  }
  
  
  
</script>
<div class="chart-container" style="display: none;">
  <div class="chart-title text-center" >Daily hashrate chart</div>
  <div class="ct-chart ct-perfect-fourth" ></div>
</div>

<div class="row" style="overflow:scroll; height:780px">
  <div id="minerList" style="width:100%">
  </div>
</div>
