<commit_before>{% extends "base.html" %}

{% block head %}
{% block head_more %}
<link href="/static/style.css" rel="stylesheet">
<link href="/static/darktheme.css" rel="stylesheet" id="dark">
<link href="/static/lighttheme.css" rel="stylesheet" id="light">
<script src="https://unpkg.com/react@15.3.2/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/babel-core@5.8.38/browser.min.js"></script>
<script src="https://unpkg.com/remarkable@1.6.2/dist/remarkable.min.js"></script>
<script type="text/javascript" src="/realtimewebui/js/realtimewebui.js"></script>
<script type="text/javascript" src="/realtimewebui/externals/lodash.min.js"></script>
<script type="text/javascript" src="/static/cookies.js"></script>
<script>
var globalModelWebsocketPort = {{globalModelWebsocketPort}};
var globalModelWebsocketSecret = "{{globalModelWebsocketSecret}}";

</script>
{% endblock %}
{% endblock %}

{% block content %}
<script type="text/babel">
var nrHostsPerRow = 2;
var NAT_ADDR = "rackattack-nat.dc1";

var rackattackInstances = [
{% for dashboardSource in dashboardSources %}
{name: "{{ dashboardSource.name }}", host: "{{ dashboardSource.host }}"},
{% endfor %}
]
var activeDashboardName = "";
var activeDashboardHost = "";
var ui = new RealTimeWebUI();
var lotsOfColors = ["#ab47bc", "#5c6bc0", "#8d6e63", "#f44336", "#e91e63", "#9c27b0", "#3f51b5", "#2196f3", "#00bcd4", "#009688", "#4caf50", "#cddc39", "#ff9800", "#795548", "#e53935", "#d81b60", "#8e24aa", "#3949ab", "#1e88e5", "#00acc1", "#00897b", "#43a047", "#fb8c00", "#6d4c41", "#d32f2f", "#c2185b", "#7b1fa2", "#303f9f", "#1976d2", "#0097a7", "#00796b", "#388e3c", "#afb42b", "#f57c00", "#5d4037", "#c62828", "#ad1457", "#6a1b9a", "#283593", "#1565c0", "#00838f", "#00695c", "#2e7d32", "#9e9d24", "#ef6c00", "#4e342e", "#b71c1c", "#880e4f", "#4a148c", "#1a237e", "#0d47a1", "#006064", "#004d40", "#1b5e20", "#827717", "#e65100", "#3e2723", 'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Black', 'BlanchedAlmond', 'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray', 'DarkGreen', 'DarkKhaki', 'DarkMagenta', 'DarkOliveGreen', 'DarkOrange', 'DarkOrchid', 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray', 'DarkTurquoise', 'DarkViolet', 'DeepPink', 'DeepSkyBlue', 'DimGray', 'DodgerBlue', 'FireBrick', 'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod', 'Gray', 'Green', 'GreenYellow', 'HoneyDew', 'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSteelBlue', 'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 'RebeccaPurple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', 'SlateGray', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen']

function pad(str) {
    str = str.toString();
    var pad = "00"
    var ans = pad.substring(0, pad.length - str.length) + str
    return ans;
}

function changeTheme(theme) {
  Cookies.set("theme", theme)
  document.body.className = theme;
}

function getAllocationIdxByHostID(hostID, hostsByIndices, allocations) {
 return _.findIndex(allocations, function(allocation) {
   for (var node in allocation.allocated) {
     var hostIdxInHosts = allocation.allocated[node];
     if (hostsByIndices[hostIdxInHosts].id == hostID) {
       return true;
     }
   }
   return false;
  });
}

var DashboardContent = React.createClass({
  getInitialState: function() {
    return {viewMode: "state"};
  },

  getPoolsNames: function() {
    var poolsNames = _.uniq(_.map(this.props.hosts, "pool"));
    poolsNames.sort();
    return poolsNames;
  },

  getHostsByIndices: function() {
    var result = {};
    _.each(this.props.hosts, function(host){
      result[host.index] = host;
    });
    return result;
  },

  render: function() {
    var poolsNames = this.getPoolsNames()
    var blinkingHostsIDs;
    if (this.blinkingHostsIDs === undefined) {
        blinkingHostsIDs = [];
    } else {
        blinkingHostsIDs = this.blinkingHostsIDs;
    }
    this.hostsByIndices = this.getHostsByIndices();
    return (
      <div className="row">
        <section className="col-md-6">
          <ViewModeSelection setViewModeCallback={this.setViewMode} />
          <RackList hosts={this.props.hosts}
                    viewMode={this.state.viewMode}
                    poolsNames={poolsNames}
                    allocations={this.props.allocations}
                    hostsByIndices={this.hostsByIndices}
                    blinkingHostsIDs={blinkingHostsIDs} />
        </section>
        <aside className="col-md-6">
            <DashboardPanelGroup
             allocations={this.props.allocations}
             hosts={this.props.hosts}
             poolsNames={poolsNames}
             startBlinkingCallback={this.startBlinking}
             stopBlinkingCallback={this.stopBlinking}
             hostsByIndices={this.hostsByIndices}
            />
        </aside>
      </div>
    );
  },
  
  setViewMode: function(viewMode) {
    this.state.viewMode = viewMode;
    this.forceUpdate();
  },

  startBlinking: function(hostsIDs) {
    this.blinkingHostsIDs = hostsIDs;
    this.forceUpdate();
  },

  stopBlinking: function() {
    this.blinkingHostsIDs = [];
    this.forceUpdate();
  },
});

var ViewModeOption = React.createClass({
  render: function() {
    var checked = "";
    if (this.props.viewMode == this.props.activeViewMode) {
      checked = "checked";
    }
    return (
      <div className="radio-inline">
        <label>
          <input name="hostViewOptions"
                 onChange={() => this.props.setViewModeCallback(this.props.viewMode)}
                 type="radio"
                 value="{this.props.viewMode}"
                 checked={checked} ></input>
          Show {this.props.viewMode}
        </label>
      </div>
    );
  },
});

var ViewModeSelection = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return false;
  },

  getInitialState: function() {
    return {activeViewMode: "state"};
  },

  render: function() {
    return (
      <div>
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="state" />
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="allocation" />
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="pool" />
      </div>
    );
  },
  
  setViewMode: function(viewMode) {
    this.state.activeViewMode = viewMode;
    this.forceUpdate();
    this.props.setViewModeCallback(viewMode);
  }
});

var RackList = React.createClass({
  getRacksToHosts: function(hosts) {
    return _.groupBy(hosts, function(host) {
      var rackNumber = parseInt(host.id.replace("rack", "").replace("-server\d", ""));
      return rackNumber;
    });
  },

  getNrSlotsInASingleRack: function(racksToHosts) {
    var nrHostsPerRack = _.map(_.values(racksToHosts), function(hosts) {
      return hosts.length;
    });
    return _.max(nrHostsPerRack);
  },

  getOrderedRacks: function(hosts) {
    var self = this;
    var racksToHosts = this.getRacksToHosts(hosts);
    var racksNumbers = Object.keys(racksToHosts);
    var orderedRacksIDs = _.sortBy(racksNumbers, function(number) {
        return number;
    });
    return _.map(orderedRacksIDs, function(rackID) {
        return (
            <Rack id={rackID}
             key={rackID}
             hosts={racksToHosts[rackID]}
             nrSlots={self.getNrSlotsInASingleRack(racksToHosts)}
             viewMode={self.props.viewMode}
             poolsNames={self.props.poolsNames}
             allocations={self.props.allocations}
             hostsByIndices={self.props.hostsByIndices}
             blinkingHostsIDs={self.props.blinkingHostsIDs} />
        );
    });
  },

  render: function() {
	var racks = this.getOrderedRacks(this.props.hosts);
    return (
      <div className="rackList">
        {racks}
      </div>
    );
  }
});

var Rack = React.createClass({
  getRackRows: function(hosts) {
    var self = this;
    hosts = _.keyBy(hosts, "id");
    var nrRows = Math.ceil(this.props.nrSlots / nrHostsPerRow);
    var rackID = "rack" + pad(this.props.id);
    var rows = _.map(_.range(nrRows), function(rowNumber) {
        var idxOfFirstHostInRow = 1 + rowNumber * nrHostsPerRow;
        var rowHostsIndices = _.range(idxOfFirstHostInRow, idxOfFirstHostInRow + nrHostsPerRow);
        var rowHosts = _.map(rowHostsIndices, function(hostIdx) {
            var hostID = rackID + "-server" + pad(hostIdx);
            var blink = self.props.blinkingHostsIDs.indexOf(hostID) >= 0;
            if (hostID in hosts) {
                return <Host data={hosts[hostID]}
                        key={hostID}
                        hostIdx={hostIdx} 
                        viewMode={self.props.viewMode}
                        poolsNames={self.props.poolsNames}
                        allocations={self.props.allocations}
                        hostsByIndices={self.props.hostsByIndices}
                        blink={blink} />
            }
            return <EmptyHostSlot hostID={hostID} key={hostID} hostIdx={hostIdx} />
        });
        return <RackRow key={idxOfFirstHostInRow} hosts={rowHosts} />
    });
    return rows;
  },
  render: function() {
    var rackRows = this.getRackRows(this.props.hosts);
    return (
      <div className="rack col-md-4">
        <h4>
          Rack {this.props.id}
        </h4>
        <table className="rack-table">
          <tbody>
            {rackRows}
          </tbody>
        </table>
      </div>
    );
  }
});

var RackRow = React.createClass({
  render: function() {
    return (
      <tr className="rackRow">
       {this.props.hosts}
      </tr>
    );
  }
});

var stateMap = {
    "QUICK_RECLAIMATION_IN_PROGRESS":{ iconClass: "glyphicon-backward", labelClass: "label-info" },
    "SLOW_RECLAIMATION_IN_PROGRESS":{ iconClass: "glyphicon-fast-backward", labelClass: "label-warning" },
    "CHECKED_IN":{ iconClass: "glyphicon-pause", labelClass: "label-primary" },
    "INAUGURATION_LABEL_PROVIDED":{ iconClass: "glyphicon-play", labelClass: "label-info" },
    "INAUGURATION_DONE":{ iconClass: "glyphicon-ok", labelClass: "label-success" },
    "DESTROYED":{ iconClass: "glyphicon-remove", labelClass: "label-danger" },
    "OFFLINE":{ iconClass: "glyphicon-off", labelClass: "label-default-dark" },
    "DETACHED":{ iconClass: "glyphicon-eject", labelClass: "label-detached" }
};

var Host = React.createClass({
  getNatPortFromInnerIPAddress: function(ipAddress) {
        return 2000 + parseInt(ipAddress.split(".")[3]) + (parseInt(ipAddress.split(".")[2]) - 1) * 256;
  },

  generatePopoverContent: function() {
    var content = "Inner: " + this.props.data.ipAddress;
    content += "<br />Outer: " + NAT_ADDR + ":" + this.getNatPortFromInnerIPAddress(this.props.data.ipAddress);
    if (this.props.data.nodeName) {
        content += "<br />Node: " + this.props.data.nodeName;
    }
    content += "<br />Pool: " + this.props.data.pool;
    if (this.props.data.warnings) {
        content += "<br /><span class='text-danger'>Warnings:<ul>";
        for (var idx in this.props.data.warnings) {
            var warning = this.props.data.warnings[idx];
            content += "<li>" + warning + "</li>";
        }
        content += "</ul></span>";
    }
    if (this.props.data.reasonForDestruction) {
        content += "<div class='text-danger'>Reason for destruction: " + this.props.data.reasonForDestruction + "</span>";
    }
    return content;
  },

  componentDidMount: function() {
    var content = this.generatePopoverContent();
    $("#node-" + this.props.data.id).popover({
      title: this.props.data.id,
      container: "body",
      content: content,
      trigger: "hover",
      placement: "auto",
      html: true
    });
  },

  getAllocationIdx: function() {
    var hostID = this.props.data.id;
    return getAllocationIdxByHostID(hostID, this.props.hostsByIndices, this.props.allocations);
  },

  render: function() {
    var labelClassNames = "label machine-label";
    var outerSpanStyle = {};
    if (this.props.viewMode == "state") {
      labelClassNames += " " + stateMap[this.props.data.state]["labelClass"];
    } else if (this.props.viewMode == "pool") {
      var poolIdx = this.props.poolsNames.indexOf(this.props.data.pool);
      outerSpanStyle["backgroundColor"] = lotsOfColors[poolIdx];
    } else if (this.props.viewMode == "allocation") {
      var allocationIdx = this.getAllocationIdx();
      var color;
      if (allocationIdx >= 0) {
        color = lotsOfColors[allocationIdx];
      } else {
        color = "gray";
      }
      outerSpanStyle["backgroundColor"] = color;
    }
    var classes = "host machine";
    if (this.props.blink) {
		classes += " node-when-allocation-is-hovered";
    }
    var iconClassNames = "glyphicon " + stateMap[this.props.data.state]["iconClass"];
    var warningSign = "";
    if (this.props.data.warnings) {
      warningSign = (<span className="warningSignWrapper">
          <span className="glyphicon glyphicon-warning-sign">{this.props.data.warnings.length}</span>
      </span>)
    }
    var logURL = "http://" + activeDashboardHost + ":" + "6001" + "/seriallogs/" + this.props.data.id + "-serial.txt";
    return (
      <td className={classes} id={"node-"+this.props.data.id}>
        <a className="machine-link" href={logURL} target="_new">
          <span className={labelClassNames} style={outerSpanStyle} >
            <span className={iconClassNames}></span>
              &nbsp;
              {pad(this.props.hostIdx)}
              {warningSign}
          </span>
        </a>
      </td>
    );
  }
});

var Stats = React.createClass({
  render: function() {
    return (
            <ul className="list-group">
              <li className="list-group-item">
                <span className="label label-primary">
                  <span className="glyphicon glyphicon-pause">
                  </span>
                  &nbsp;
                  Available
                </span>
                &nbsp;(checked in) hosts in default pool:
                <span className="badge badge-primary">
                  {this.nrHosts('default', 'CHECKED_IN')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-primary">
                  <span className="glyphicon glyphicon-pause">
                  </span>
                  &nbsp;
                  Available
                </span>
                &nbsp;(checked in) in non-default pools:
                <span className="badge badge-primary">
                  {this.nrHosts('', 'CHECKED_IN') - this.nrHosts('default', 'CHECKED_IN')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-danger">
                  <span className="glyphicon glyphicon-remove">
                  </span>
                  &nbsp;
                  Destroyed
                </span>
                &nbsp;hosts:
                <span className="badge badge-danger">
                  {this.nrHosts('', 'DESTROYED')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-default-dark">
                  <span className="glyphicon glyphicon-off">
                  </span>
                  &nbsp;
                  Offline
                </span>
                &nbsp;hosts:
                <span className="badge badge-dark">
                  {this.nrHosts('', 'OFFLINE')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-success">
                  <span className="glyphicon glyphicon-ok">
                  </span>
                  &nbsp;
                  In-use
                </span>
                &nbsp;(Inauguration Done) hosts:
                <span className="badge badge-success">
                  {this.nrHosts('', 'INAUGURATION_DONE')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-detached">
                  <span className="glyphicon glyphicon-eject">
                  </span>
                  &nbsp;
                  Detached
                </span>
               &nbsp;hosts:
                <span className="badge badge-detached">
                  {this.nrHosts('', 'DETACHED')}
                </span>
              </li>
              <li className="list-group-item">
                Total Nr. of hosts:
                <span className="badge badge">
                  {this.nrHosts('', '')}
                </span>
              </li>
              <li className="list-group-item">
                Current hosts usage:
                <span className="badge badge">
                  {(((this.nrHosts('', 'INAUGURATION_DONE') + this.nrHosts('', 'DETACHED')) / this.nrHosts('', '')) * 100).toFixed(0).toString() + '%'}
                </span>
              </li>
            </ul>
    );
  },

  nrHosts: function(pool, state) {
    var hosts = this.props.hosts;
    if (hosts === undefined) {
        return 0;
    }
    if (!pool && !state) {
        return hosts.length;
    }
    hosts = _.countBy(hosts, function(host) {
        var condition = host.state == state;
        if (pool) {
            condition = condition && (host.pool == pool);
        }
        return condition;
    })[true];
    if (hosts === undefined) {
        return 0;
    }
    return hosts;
  }
});


var StatesLegend = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return false;
  },

  render: function() {
    return (
      <div>
      <p><span className="label label-info"><span className="glyphicon glyphicon-backward"></span> QUICK_RECLAIMATION_IN_PROGRESS</span></p>
      <p>Quick reclaim works using kexecing the inaugurator on the host
      </p>
      <p><span className="label label-warning"><span className="glyphicon glyphicon-fast-backward"></span> SLOW_RECLAIMATION_IN_PROGRESS</span></p>
      <p>
          Slow reclaim works by rebooting the server and using PXE to load the inaugurator.
          This is slow since a server takes minutes to reboot, plus, more than one reboot
          might be required.
      </p>
      <p><span className="label label-primary"><span className="glyphicon glyphicon-pause"></span> CHECKED_IN</span></p>
      <p>
          The inaugurator has booted successfully, and is waiting for the command to switch
          a rootfs. The current rootfs is being hashed in the background.
      </p>
      <p><span className="label label-info"><span className="glyphicon glyphicon-play"></span> INAUGURATION_LABEL_PROVIDED</span></p>
      <p>
          The inaugurator is replacing the rootfs in this host.
      </p>
      <p><span className="label label-success"><span className="glyphicon glyphicon-ok"></span> INAUGURATION_DONE</span></p>
      <p>
          The inaugurator has finished replacing the rootfs. This host was kexeced into
          its image, which if not hung, means the test itself is running.
      </p>
      <p><span className="label label-danger"><span className="glyphicon glyphicon-remove"></span> DESTROYED</span></p>
      <p>
          A temporary stage, on the road for the host to drop out of the cluster. This
          happens if 5 slow reclamations failed on this host
      </p>
      <p><span className="label label-default-dark"><span className="glyphicon glyphicon-off"></span> OFFLINE</span></p>
      <p>
          The server is off.
      </p>
      <p><span className="label label-detached"><span className="glyphicon glyphicon-eject"></span> DETACHED</span></p>
      <p>
          A temporary state which "freezes" the server at the moment it was detached; Rackattack will not shut it off or reclaim it in this state, nor will it be available for allocation. Meant for debugging or one-time-inaugurations.
      </p>
      </div>
    );
  }
});

function objectSize(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

var Collection = React.createClass({
  render: function() {
    var style = {backgroundColor: lotsOfColors[this.props.idx]};
    return (
      <div className="media">
        <a className="media-left pull-left" href="#">
          <div style={style} className="color-swatch">
            {this.props.swatch}
          </div>
        </a>
        <div className="media-body">
          <div className="media-heading">
            {this.props.title}
          </div>
          {this.props.children}
        </div>
      </div>
    );
  }
});

var AllocatedHostList = React.createClass({
  componentWillMount: function() {
    this.isExtendedDataVisible = false;
  },

  render: function() {
    var tableRowClasses = "host-collection-row";
    if (this.isExtendedDataVisible) {
      tableRowClasses += " hover";
    }
    var state;
    var stateClass;
    if (this.props.allocation.dead) {
	  state = "DEAD";
      stateClass = "label-danger";
    } else if (this.props.allocation.done) {
      state = "DONE";
      stateClass = "label-success";
    } else {
      state = "WAIT";
      stateClass = "label-primary";
    }
    stateClass = "label " + stateClass;
    var extraHeaderCells = [<td className="state-column">
                             <div>
                               <span className={stateClass}>{state}</span>
                             </div>
                           </td>,
                           <td>
                             {this.prettyDuration(this.props.allocation.duration)}
                           </td>];
    return (
          <CollapsableCollectionRow key={this.props.pool}
                                    name={this.props.allocation.allocationInfo.user}
	    							showContentCallback={this.showContent}
	    							hideContentCallback={this.hideContent}
                                    swatch={objectSize(this.props.allocation.allocated)}
                                    idx={this.props.idx}
                                    extraHeaderCells={extraHeaderCells}
                                    >
            <AllocationExtendedData
             hosts={this.props.hosts}
             allocatedHosts={this.props.allocation.allocated}
             allocationInfo={this.props.allocation.allocationInfo}
             hostsByIndices={this.props.hostsByIndices}
             isVisible={this.isExtendedDataVisible}
             />
          </CollapsableCollectionRow>
    );
  },

  prettyDuration: function(duration) {
    var nrSeconds = parseInt(duration);
    var nrMinutes = Math.floor(nrSeconds / 60);
    if (nrMinutes == 0) {
        nrMinutes = 1;
    }
    if (nrMinutes < 60) {
        return nrMinutes + "m";
    }
    var nrHours = Math.floor(nrMinutes / 60);
    if (nrHours < 24) {
        result = nrHours + "h";
        var nrMinutesResidue = nrMinutes % 60;
        if (nrMinutesResidue > 0) {
            result += " " + nrMinutesResidue + "m";
        }
        return result;
    }
    var nrHoursResidue = nrHours % 24;
    var nrDays = Math.floor(nrHours / 24);
    if (nrDays < 7) {
      var result = nrDays + "d";
      if (nrHoursResidue > 0) {
          result += " " + nrHoursResidue + "h";
      }
      return result;
    }
    var nrWeeks = Math.floor(nrDays / 7);
    var nrDaysResidue = nrDays % 7;
    var result = nrWeeks + "w";
    if (nrDaysResidue > 0) {
        result += " " + nrDaysResidue + "d";
    }
    return result;
  },

  showContent: function() {
    this.isExtendedDataVisible = true;
    var indices = _.values(this.props.allocation.allocated)
    var blinkingHostsIDs = this.hostsIndicesToIDs(indices);
    this.props.startBlinkingCallback(blinkingHostsIDs)
  },

  hideContent: function() {
    this.isExtendedDataVisible = false;
    this.props.stopBlinkingCallback()
  },

  hostsIndicesToIDs: function(hostsIndices) {
	var hostsByIndices = this.props.hostsByIndices;
    return hostsIndices.map(function(hostIndex) {
        return hostsByIndices[hostIndex].id;
    });
  }
});

var AllocationExtendedData = React.createClass({
  render: function() {
    return (
      <div className="hosts-list">
        <small>Nice: {this.props.allocationInfo.nice}, Purpose: {this.props.allocationInfo.purpose}</small>
        <AllocatedHostsList
         hosts={this.props.allocatedHosts}
         hostsByIndices={this.props.hostsByIndices} />
      </div>
    );
  },

  shouldComponentUpdate: function(nextProps, nextState) {
    return nextProps.isVisible;
  }
});

var Allocations = React.createClass({
  componentDidUpdate: function() {
    ReactDOM.findDOMNode(this.refs.filter).focus();
  },

  filter: function() {
    var elem = ReactDOM.findDOMNode(this.refs.filter);
    this.allocationFilter = elem.value;
    this.forceUpdate();
  },

  render: function() {
    var self = this;
    var hosts = this.props.hosts;
    var startBlinkingCallback = this.props.startBlinkingCallback;
    var stopBlinkingCallback = this.props.stopBlinkingCallback;
    var hostsByIndices = this.props.hostsByIndices;
    var allocationFilter = "";
    var allocations = this.props.allocations;
    if (this.allocationFilter !== undefined && this.allocationFilter) {
      allocations = _.filter(allocations, function(allocation) {
          return (allocation.allocationInfo.user.toLowerCase().indexOf(
            self.allocationFilter.toLowerCase()) >= 0);
      });
    }
    return (<commit_msg>Don't focus on the allocations filter<commit_after>{% extends "base.html" %}

{% block head %}
{% block head_more %}
<link href="/static/style.css" rel="stylesheet">
<link href="/static/darktheme.css" rel="stylesheet" id="dark">
<link href="/static/lighttheme.css" rel="stylesheet" id="light">
<script src="https://unpkg.com/react@15.3.2/dist/react.min.js"></script>
<script src="https://unpkg.com/react-dom@15.3.2/dist/react-dom.min.js"></script>
<script src="https://unpkg.com/babel-core@5.8.38/browser.min.js"></script>
<script src="https://unpkg.com/remarkable@1.6.2/dist/remarkable.min.js"></script>
<script type="text/javascript" src="/realtimewebui/js/realtimewebui.js"></script>
<script type="text/javascript" src="/realtimewebui/externals/lodash.min.js"></script>
<script type="text/javascript" src="/static/cookies.js"></script>
<script>
var globalModelWebsocketPort = {{globalModelWebsocketPort}};
var globalModelWebsocketSecret = "{{globalModelWebsocketSecret}}";

</script>
{% endblock %}
{% endblock %}

{% block content %}
<script type="text/babel">
var nrHostsPerRow = 2;
var NAT_ADDR = "rackattack-nat.dc1";

var rackattackInstances = [
{% for dashboardSource in dashboardSources %}
{name: "{{ dashboardSource.name }}", host: "{{ dashboardSource.host }}"},
{% endfor %}
]
var activeDashboardName = "";
var activeDashboardHost = "";
var ui = new RealTimeWebUI();
var lotsOfColors = ["#ab47bc", "#5c6bc0", "#8d6e63", "#f44336", "#e91e63", "#9c27b0", "#3f51b5", "#2196f3", "#00bcd4", "#009688", "#4caf50", "#cddc39", "#ff9800", "#795548", "#e53935", "#d81b60", "#8e24aa", "#3949ab", "#1e88e5", "#00acc1", "#00897b", "#43a047", "#fb8c00", "#6d4c41", "#d32f2f", "#c2185b", "#7b1fa2", "#303f9f", "#1976d2", "#0097a7", "#00796b", "#388e3c", "#afb42b", "#f57c00", "#5d4037", "#c62828", "#ad1457", "#6a1b9a", "#283593", "#1565c0", "#00838f", "#00695c", "#2e7d32", "#9e9d24", "#ef6c00", "#4e342e", "#b71c1c", "#880e4f", "#4a148c", "#1a237e", "#0d47a1", "#006064", "#004d40", "#1b5e20", "#827717", "#e65100", "#3e2723", 'AliceBlue', 'AntiqueWhite', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 'Black', 'BlanchedAlmond', 'Blue', 'BlueViolet', 'Brown', 'BurlyWood', 'CadetBlue', 'Chartreuse', 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan', 'DarkBlue', 'DarkCyan', 'DarkGoldenRod', 'DarkGray', 'DarkGreen', 'DarkKhaki', 'DarkMagenta', 'DarkOliveGreen', 'DarkOrange', 'DarkOrchid', 'DarkRed', 'DarkSalmon', 'DarkSeaGreen', 'DarkSlateBlue', 'DarkSlateGray', 'DarkTurquoise', 'DarkViolet', 'DeepPink', 'DeepSkyBlue', 'DimGray', 'DodgerBlue', 'FireBrick', 'FloralWhite', 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod', 'Gray', 'Green', 'GreenYellow', 'HoneyDew', 'HotPink', 'IndianRed', 'Indigo', 'Ivory', 'Khaki', 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 'LightSlateGray', 'LightSteelBlue', 'LightYellow', 'Lime', 'LimeGreen', 'Linen', 'Magenta', 'Maroon', 'MediumAquaMarine', 'MediumBlue', 'MediumOrchid', 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 'MediumTurquoise', 'MediumVioletRed', 'MidnightBlue', 'MintCream', 'MistyRose', 'Moccasin', 'NavajoWhite', 'Navy', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 'RebeccaPurple', 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Salmon', 'SandyBrown', 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', 'SlateGray', 'Snow', 'SpringGreen', 'SteelBlue', 'Tan', 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 'WhiteSmoke', 'Yellow', 'YellowGreen']

function pad(str) {
    str = str.toString();
    var pad = "00"
    var ans = pad.substring(0, pad.length - str.length) + str
    return ans;
}

function changeTheme(theme) {
  Cookies.set("theme", theme)
  document.body.className = theme;
}

function getAllocationIdxByHostID(hostID, hostsByIndices, allocations) {
 return _.findIndex(allocations, function(allocation) {
   for (var node in allocation.allocated) {
     var hostIdxInHosts = allocation.allocated[node];
     if (hostsByIndices[hostIdxInHosts].id == hostID) {
       return true;
     }
   }
   return false;
  });
}

var DashboardContent = React.createClass({
  getInitialState: function() {
    return {viewMode: "state"};
  },

  getPoolsNames: function() {
    var poolsNames = _.uniq(_.map(this.props.hosts, "pool"));
    poolsNames.sort();
    return poolsNames;
  },

  getHostsByIndices: function() {
    var result = {};
    _.each(this.props.hosts, function(host){
      result[host.index] = host;
    });
    return result;
  },

  render: function() {
    var poolsNames = this.getPoolsNames()
    var blinkingHostsIDs;
    if (this.blinkingHostsIDs === undefined) {
        blinkingHostsIDs = [];
    } else {
        blinkingHostsIDs = this.blinkingHostsIDs;
    }
    this.hostsByIndices = this.getHostsByIndices();
    return (
      <div className="row">
        <section className="col-md-6">
          <ViewModeSelection setViewModeCallback={this.setViewMode} />
          <RackList hosts={this.props.hosts}
                    viewMode={this.state.viewMode}
                    poolsNames={poolsNames}
                    allocations={this.props.allocations}
                    hostsByIndices={this.hostsByIndices}
                    blinkingHostsIDs={blinkingHostsIDs} />
        </section>
        <aside className="col-md-6">
            <DashboardPanelGroup
             allocations={this.props.allocations}
             hosts={this.props.hosts}
             poolsNames={poolsNames}
             startBlinkingCallback={this.startBlinking}
             stopBlinkingCallback={this.stopBlinking}
             hostsByIndices={this.hostsByIndices}
            />
        </aside>
      </div>
    );
  },
  
  setViewMode: function(viewMode) {
    this.state.viewMode = viewMode;
    this.forceUpdate();
  },

  startBlinking: function(hostsIDs) {
    this.blinkingHostsIDs = hostsIDs;
    this.forceUpdate();
  },

  stopBlinking: function() {
    this.blinkingHostsIDs = [];
    this.forceUpdate();
  },
});

var ViewModeOption = React.createClass({
  render: function() {
    var checked = "";
    if (this.props.viewMode == this.props.activeViewMode) {
      checked = "checked";
    }
    return (
      <div className="radio-inline">
        <label>
          <input name="hostViewOptions"
                 onChange={() => this.props.setViewModeCallback(this.props.viewMode)}
                 type="radio"
                 value="{this.props.viewMode}"
                 checked={checked} ></input>
          Show {this.props.viewMode}
        </label>
      </div>
    );
  },
});

var ViewModeSelection = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return false;
  },

  getInitialState: function() {
    return {activeViewMode: "state"};
  },

  render: function() {
    return (
      <div>
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="state" />
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="allocation" />
        <ViewModeOption setViewModeCallback={this.setViewMode} activeViewMode={this.state.activeViewMode} viewMode="pool" />
      </div>
    );
  },
  
  setViewMode: function(viewMode) {
    this.state.activeViewMode = viewMode;
    this.forceUpdate();
    this.props.setViewModeCallback(viewMode);
  }
});

var RackList = React.createClass({
  getRacksToHosts: function(hosts) {
    return _.groupBy(hosts, function(host) {
      var rackNumber = parseInt(host.id.replace("rack", "").replace("-server\d", ""));
      return rackNumber;
    });
  },

  getNrSlotsInASingleRack: function(racksToHosts) {
    var nrHostsPerRack = _.map(_.values(racksToHosts), function(hosts) {
      return hosts.length;
    });
    return _.max(nrHostsPerRack);
  },

  getOrderedRacks: function(hosts) {
    var self = this;
    var racksToHosts = this.getRacksToHosts(hosts);
    var racksNumbers = Object.keys(racksToHosts);
    var orderedRacksIDs = _.sortBy(racksNumbers, function(number) {
        return number;
    });
    return _.map(orderedRacksIDs, function(rackID) {
        return (
            <Rack id={rackID}
             key={rackID}
             hosts={racksToHosts[rackID]}
             nrSlots={self.getNrSlotsInASingleRack(racksToHosts)}
             viewMode={self.props.viewMode}
             poolsNames={self.props.poolsNames}
             allocations={self.props.allocations}
             hostsByIndices={self.props.hostsByIndices}
             blinkingHostsIDs={self.props.blinkingHostsIDs} />
        );
    });
  },

  render: function() {
	var racks = this.getOrderedRacks(this.props.hosts);
    return (
      <div className="rackList">
        {racks}
      </div>
    );
  }
});

var Rack = React.createClass({
  getRackRows: function(hosts) {
    var self = this;
    hosts = _.keyBy(hosts, "id");
    var nrRows = Math.ceil(this.props.nrSlots / nrHostsPerRow);
    var rackID = "rack" + pad(this.props.id);
    var rows = _.map(_.range(nrRows), function(rowNumber) {
        var idxOfFirstHostInRow = 1 + rowNumber * nrHostsPerRow;
        var rowHostsIndices = _.range(idxOfFirstHostInRow, idxOfFirstHostInRow + nrHostsPerRow);
        var rowHosts = _.map(rowHostsIndices, function(hostIdx) {
            var hostID = rackID + "-server" + pad(hostIdx);
            var blink = self.props.blinkingHostsIDs.indexOf(hostID) >= 0;
            if (hostID in hosts) {
                return <Host data={hosts[hostID]}
                        key={hostID}
                        hostIdx={hostIdx} 
                        viewMode={self.props.viewMode}
                        poolsNames={self.props.poolsNames}
                        allocations={self.props.allocations}
                        hostsByIndices={self.props.hostsByIndices}
                        blink={blink} />
            }
            return <EmptyHostSlot hostID={hostID} key={hostID} hostIdx={hostIdx} />
        });
        return <RackRow key={idxOfFirstHostInRow} hosts={rowHosts} />
    });
    return rows;
  },
  render: function() {
    var rackRows = this.getRackRows(this.props.hosts);
    return (
      <div className="rack col-md-4">
        <h4>
          Rack {this.props.id}
        </h4>
        <table className="rack-table">
          <tbody>
            {rackRows}
          </tbody>
        </table>
      </div>
    );
  }
});

var RackRow = React.createClass({
  render: function() {
    return (
      <tr className="rackRow">
       {this.props.hosts}
      </tr>
    );
  }
});

var stateMap = {
    "QUICK_RECLAIMATION_IN_PROGRESS":{ iconClass: "glyphicon-backward", labelClass: "label-info" },
    "SLOW_RECLAIMATION_IN_PROGRESS":{ iconClass: "glyphicon-fast-backward", labelClass: "label-warning" },
    "CHECKED_IN":{ iconClass: "glyphicon-pause", labelClass: "label-primary" },
    "INAUGURATION_LABEL_PROVIDED":{ iconClass: "glyphicon-play", labelClass: "label-info" },
    "INAUGURATION_DONE":{ iconClass: "glyphicon-ok", labelClass: "label-success" },
    "DESTROYED":{ iconClass: "glyphicon-remove", labelClass: "label-danger" },
    "OFFLINE":{ iconClass: "glyphicon-off", labelClass: "label-default-dark" },
    "DETACHED":{ iconClass: "glyphicon-eject", labelClass: "label-detached" }
};

var Host = React.createClass({
  getNatPortFromInnerIPAddress: function(ipAddress) {
        return 2000 + parseInt(ipAddress.split(".")[3]) + (parseInt(ipAddress.split(".")[2]) - 1) * 256;
  },

  generatePopoverContent: function() {
    var content = "Inner: " + this.props.data.ipAddress;
    content += "<br />Outer: " + NAT_ADDR + ":" + this.getNatPortFromInnerIPAddress(this.props.data.ipAddress);
    if (this.props.data.nodeName) {
        content += "<br />Node: " + this.props.data.nodeName;
    }
    content += "<br />Pool: " + this.props.data.pool;
    if (this.props.data.warnings) {
        content += "<br /><span class='text-danger'>Warnings:<ul>";
        for (var idx in this.props.data.warnings) {
            var warning = this.props.data.warnings[idx];
            content += "<li>" + warning + "</li>";
        }
        content += "</ul></span>";
    }
    if (this.props.data.reasonForDestruction) {
        content += "<div class='text-danger'>Reason for destruction: " + this.props.data.reasonForDestruction + "</span>";
    }
    return content;
  },

  componentDidMount: function() {
    var content = this.generatePopoverContent();
    $("#node-" + this.props.data.id).popover({
      title: this.props.data.id,
      container: "body",
      content: content,
      trigger: "hover",
      placement: "auto",
      html: true
    });
  },

  getAllocationIdx: function() {
    var hostID = this.props.data.id;
    return getAllocationIdxByHostID(hostID, this.props.hostsByIndices, this.props.allocations);
  },

  render: function() {
    var labelClassNames = "label machine-label";
    var outerSpanStyle = {};
    if (this.props.viewMode == "state") {
      labelClassNames += " " + stateMap[this.props.data.state]["labelClass"];
    } else if (this.props.viewMode == "pool") {
      var poolIdx = this.props.poolsNames.indexOf(this.props.data.pool);
      outerSpanStyle["backgroundColor"] = lotsOfColors[poolIdx];
    } else if (this.props.viewMode == "allocation") {
      var allocationIdx = this.getAllocationIdx();
      var color;
      if (allocationIdx >= 0) {
        color = lotsOfColors[allocationIdx];
      } else {
        color = "gray";
      }
      outerSpanStyle["backgroundColor"] = color;
    }
    var classes = "host machine";
    if (this.props.blink) {
		classes += " node-when-allocation-is-hovered";
    }
    var iconClassNames = "glyphicon " + stateMap[this.props.data.state]["iconClass"];
    var warningSign = "";
    if (this.props.data.warnings) {
      warningSign = (<span className="warningSignWrapper">
          <span className="glyphicon glyphicon-warning-sign">{this.props.data.warnings.length}</span>
      </span>)
    }
    var logURL = "http://" + activeDashboardHost + ":" + "6001" + "/seriallogs/" + this.props.data.id + "-serial.txt";
    return (
      <td className={classes} id={"node-"+this.props.data.id}>
        <a className="machine-link" href={logURL} target="_new">
          <span className={labelClassNames} style={outerSpanStyle} >
            <span className={iconClassNames}></span>
              &nbsp;
              {pad(this.props.hostIdx)}
              {warningSign}
          </span>
        </a>
      </td>
    );
  }
});

var Stats = React.createClass({
  render: function() {
    return (
            <ul className="list-group">
              <li className="list-group-item">
                <span className="label label-primary">
                  <span className="glyphicon glyphicon-pause">
                  </span>
                  &nbsp;
                  Available
                </span>
                &nbsp;(checked in) hosts in default pool:
                <span className="badge badge-primary">
                  {this.nrHosts('default', 'CHECKED_IN')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-primary">
                  <span className="glyphicon glyphicon-pause">
                  </span>
                  &nbsp;
                  Available
                </span>
                &nbsp;(checked in) in non-default pools:
                <span className="badge badge-primary">
                  {this.nrHosts('', 'CHECKED_IN') - this.nrHosts('default', 'CHECKED_IN')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-danger">
                  <span className="glyphicon glyphicon-remove">
                  </span>
                  &nbsp;
                  Destroyed
                </span>
                &nbsp;hosts:
                <span className="badge badge-danger">
                  {this.nrHosts('', 'DESTROYED')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-default-dark">
                  <span className="glyphicon glyphicon-off">
                  </span>
                  &nbsp;
                  Offline
                </span>
                &nbsp;hosts:
                <span className="badge badge-dark">
                  {this.nrHosts('', 'OFFLINE')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-success">
                  <span className="glyphicon glyphicon-ok">
                  </span>
                  &nbsp;
                  In-use
                </span>
                &nbsp;(Inauguration Done) hosts:
                <span className="badge badge-success">
                  {this.nrHosts('', 'INAUGURATION_DONE')}
                </span>
              </li>
              <li className="list-group-item">
                <span className="label label-detached">
                  <span className="glyphicon glyphicon-eject">
                  </span>
                  &nbsp;
                  Detached
                </span>
               &nbsp;hosts:
                <span className="badge badge-detached">
                  {this.nrHosts('', 'DETACHED')}
                </span>
              </li>
              <li className="list-group-item">
                Total Nr. of hosts:
                <span className="badge badge">
                  {this.nrHosts('', '')}
                </span>
              </li>
              <li className="list-group-item">
                Current hosts usage:
                <span className="badge badge">
                  {(((this.nrHosts('', 'INAUGURATION_DONE') + this.nrHosts('', 'DETACHED')) / this.nrHosts('', '')) * 100).toFixed(0).toString() + '%'}
                </span>
              </li>
            </ul>
    );
  },

  nrHosts: function(pool, state) {
    var hosts = this.props.hosts;
    if (hosts === undefined) {
        return 0;
    }
    if (!pool && !state) {
        return hosts.length;
    }
    hosts = _.countBy(hosts, function(host) {
        var condition = host.state == state;
        if (pool) {
            condition = condition && (host.pool == pool);
        }
        return condition;
    })[true];
    if (hosts === undefined) {
        return 0;
    }
    return hosts;
  }
});


var StatesLegend = React.createClass({
  shouldComponentUpdate: function(nextProps, nextState) {
    return false;
  },

  render: function() {
    return (
      <div>
      <p><span className="label label-info"><span className="glyphicon glyphicon-backward"></span> QUICK_RECLAIMATION_IN_PROGRESS</span></p>
      <p>Quick reclaim works using kexecing the inaugurator on the host
      </p>
      <p><span className="label label-warning"><span className="glyphicon glyphicon-fast-backward"></span> SLOW_RECLAIMATION_IN_PROGRESS</span></p>
      <p>
          Slow reclaim works by rebooting the server and using PXE to load the inaugurator.
          This is slow since a server takes minutes to reboot, plus, more than one reboot
          might be required.
      </p>
      <p><span className="label label-primary"><span className="glyphicon glyphicon-pause"></span> CHECKED_IN</span></p>
      <p>
          The inaugurator has booted successfully, and is waiting for the command to switch
          a rootfs. The current rootfs is being hashed in the background.
      </p>
      <p><span className="label label-info"><span className="glyphicon glyphicon-play"></span> INAUGURATION_LABEL_PROVIDED</span></p>
      <p>
          The inaugurator is replacing the rootfs in this host.
      </p>
      <p><span className="label label-success"><span className="glyphicon glyphicon-ok"></span> INAUGURATION_DONE</span></p>
      <p>
          The inaugurator has finished replacing the rootfs. This host was kexeced into
          its image, which if not hung, means the test itself is running.
      </p>
      <p><span className="label label-danger"><span className="glyphicon glyphicon-remove"></span> DESTROYED</span></p>
      <p>
          A temporary stage, on the road for the host to drop out of the cluster. This
          happens if 5 slow reclamations failed on this host
      </p>
      <p><span className="label label-default-dark"><span className="glyphicon glyphicon-off"></span> OFFLINE</span></p>
      <p>
          The server is off.
      </p>
      <p><span className="label label-detached"><span className="glyphicon glyphicon-eject"></span> DETACHED</span></p>
      <p>
          A temporary state which "freezes" the server at the moment it was detached; Rackattack will not shut it off or reclaim it in this state, nor will it be available for allocation. Meant for debugging or one-time-inaugurations.
      </p>
      </div>
    );
  }
});

function objectSize(obj) {
    var size = 0, key;
    for (key in obj) {
        if (obj.hasOwnProperty(key)) size++;
    }
    return size;
};

var Collection = React.createClass({
  render: function() {
    var style = {backgroundColor: lotsOfColors[this.props.idx]};
    return (
      <div className="media">
        <a className="media-left pull-left" href="#">
          <div style={style} className="color-swatch">
            {this.props.swatch}
          </div>
        </a>
        <div className="media-body">
          <div className="media-heading">
            {this.props.title}
          </div>
          {this.props.children}
        </div>
      </div>
    );
  }
});

var AllocatedHostList = React.createClass({
  componentWillMount: function() {
    this.isExtendedDataVisible = false;
  },

  render: function() {
    var tableRowClasses = "host-collection-row";
    if (this.isExtendedDataVisible) {
      tableRowClasses += " hover";
    }
    var state;
    var stateClass;
    if (this.props.allocation.dead) {
	  state = "DEAD";
      stateClass = "label-danger";
    } else if (this.props.allocation.done) {
      state = "DONE";
      stateClass = "label-success";
    } else {
      state = "WAIT";
      stateClass = "label-primary";
    }
    stateClass = "label " + stateClass;
    var extraHeaderCells = [<td className="state-column">
                             <div>
                               <span className={stateClass}>{state}</span>
                             </div>
                           </td>,
                           <td>
                             {this.prettyDuration(this.props.allocation.duration)}
                           </td>];
    return (
          <CollapsableCollectionRow key={this.props.pool}
                                    name={this.props.allocation.allocationInfo.user}
	    							showContentCallback={this.showContent}
	    							hideContentCallback={this.hideContent}
                                    swatch={objectSize(this.props.allocation.allocated)}
                                    idx={this.props.idx}
                                    extraHeaderCells={extraHeaderCells}
                                    >
            <AllocationExtendedData
             hosts={this.props.hosts}
             allocatedHosts={this.props.allocation.allocated}
             allocationInfo={this.props.allocation.allocationInfo}
             hostsByIndices={this.props.hostsByIndices}
             isVisible={this.isExtendedDataVisible}
             />
          </CollapsableCollectionRow>
    );
  },

  prettyDuration: function(duration) {
    var nrSeconds = parseInt(duration);
    var nrMinutes = Math.floor(nrSeconds / 60);
    if (nrMinutes == 0) {
        nrMinutes = 1;
    }
    if (nrMinutes < 60) {
        return nrMinutes + "m";
    }
    var nrHours = Math.floor(nrMinutes / 60);
    if (nrHours < 24) {
        result = nrHours + "h";
        var nrMinutesResidue = nrMinutes % 60;
        if (nrMinutesResidue > 0) {
            result += " " + nrMinutesResidue + "m";
        }
        return result;
    }
    var nrHoursResidue = nrHours % 24;
    var nrDays = Math.floor(nrHours / 24);
    if (nrDays < 7) {
      var result = nrDays + "d";
      if (nrHoursResidue > 0) {
          result += " " + nrHoursResidue + "h";
      }
      return result;
    }
    var nrWeeks = Math.floor(nrDays / 7);
    var nrDaysResidue = nrDays % 7;
    var result = nrWeeks + "w";
    if (nrDaysResidue > 0) {
        result += " " + nrDaysResidue + "d";
    }
    return result;
  },

  showContent: function() {
    this.isExtendedDataVisible = true;
    var indices = _.values(this.props.allocation.allocated)
    var blinkingHostsIDs = this.hostsIndicesToIDs(indices);
    this.props.startBlinkingCallback(blinkingHostsIDs)
  },

  hideContent: function() {
    this.isExtendedDataVisible = false;
    this.props.stopBlinkingCallback()
  },

  hostsIndicesToIDs: function(hostsIndices) {
	var hostsByIndices = this.props.hostsByIndices;
    return hostsIndices.map(function(hostIndex) {
        return hostsByIndices[hostIndex].id;
    });
  }
});

var AllocationExtendedData = React.createClass({
  render: function() {
    return (
      <div className="hosts-list">
        <small>Nice: {this.props.allocationInfo.nice}, Purpose: {this.props.allocationInfo.purpose}</small>
        <AllocatedHostsList
         hosts={this.props.allocatedHosts}
         hostsByIndices={this.props.hostsByIndices} />
      </div>
    );
  },

  shouldComponentUpdate: function(nextProps, nextState) {
    return nextProps.isVisible;
  }
});

var Allocations = React.createClass({
  filter: function() {
    var elem = ReactDOM.findDOMNode(this.refs.filter);
    this.allocationFilter = elem.value;
    this.forceUpdate();
  },

  render: function() {
    var self = this;
    var hosts = this.props.hosts;
    var startBlinkingCallback = this.props.startBlinkingCallback;
    var stopBlinkingCallback = this.props.stopBlinkingCallback;
    var hostsByIndices = this.props.hostsByIndices;
    var allocationFilter = "";
    var allocations = this.props.allocations;
    if (this.allocationFilter !== undefined && this.allocationFilter) {
      allocations = _.filter(allocations, function(allocation) {
          return (allocation.allocationInfo.user.toLowerCase().indexOf(
            self.allocationFilter.toLowerCase()) >= 0);
      });
    }
    return (
{'fraction_non_alphanumeric': 0.14817026517625542, 'fraction_numerical': 0.01156997032862448, 'mean_word_length': 2.478385044787745, 'pattern_counts': {'":': 20, '<': 469, '<?xml version=': 0, '>': 477, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2829896', 'n_tokens_mistral': 17430, 'n_tokens_neox': 16424, 'n_words': 3198}