You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by zm...@apache.org on 2015/08/26 22:59:54 UTC

[04/51] [partial] aurora git commit: Move packages from com.twitter.common to org.apache.aurora.common

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/dygraph-extra.js
----------------------------------------------------------------------
diff --git a/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/dygraph-extra.js b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/dygraph-extra.js
new file mode 100644
index 0000000..3a353a5
--- /dev/null
+++ b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/dygraph-extra.js
@@ -0,0 +1,367 @@
+/*
+ * Licensed 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.
+ */
+/*jslint vars: true, nomen: true, plusplus: true, maxerr: 500, indent: 4 */
+
+/**
+ * @license
+ * Copyright 2011 Juan Manuel Caicedo Carvajal (juan@cavorite.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview This file contains additional features for dygraphs, which
+ * are not required but can be useful for certain use cases. Examples include
+ * exporting a dygraph as a PNG image.
+ */
+
+/**
+ * Demo code for exporting a Dygraph object as an image.
+ *
+ * See: http://cavorite.com/labs/js/dygraphs-export/
+ */
+
+Dygraph.Export = {};
+
+Dygraph.Export.DEFAULT_ATTRS = {
+
+    backgroundColor: "transparent",
+
+    //Texts displayed below the chart's x-axis and to the left of the y-axis
+    titleFont: "bold 18px serif",
+    titleFontColor: "black",
+
+    //Texts displayed below the chart's x-axis and to the left of the y-axis
+    axisLabelFont: "bold 14px serif",
+    axisLabelFontColor: "black",
+
+    // Texts for the axis ticks
+    labelFont: "normal 12px serif",
+    labelFontColor: "black",
+
+    // Text for the chart legend
+    legendFont: "bold 12px serif",
+    legendFontColor: "black",
+
+    // Default position for vertical labels
+    vLabelLeft: 20,
+
+    legendHeight: 20,    // Height of the legend area
+	legendMargin: 20,
+	lineHeight: 30,
+	maxlabelsWidth: 0,
+	labelTopMargin: 35,
+	magicNumbertop: 8
+
+};
+
+/**
+ * Tests whether the browser supports the canvas API and its methods for
+ * drawing text and exporting it as a data URL.
+ */
+Dygraph.Export.isSupported = function () {
+    "use strict";
+    try {
+        var canvas = document.createElement("canvas");
+        var context = canvas.getContext("2d");
+        return (!!canvas.toDataURL && !!context.fillText);
+    } catch (e) {
+        // Silent exception.
+    }
+    return false;
+};
+
+/**
+ * Exports a dygraph object as a PNG image.
+ *
+ *  dygraph: A Dygraph object
+ *  img: An IMG DOM node
+ *  userOptions: An object with the user specified options.
+ *
+ */
+Dygraph.Export.asPNG = function (dygraph, img, userOptions) {
+    "use strict";
+    var canvas = Dygraph.Export.asCanvas(dygraph, userOptions);
+    img.src = canvas.toDataURL();
+};
+
+/**
+ * Exports a dygraph into a single canvas object.
+ *
+ * Returns a canvas object that can be exported as a PNG.
+ *
+ *  dygraph: A Dygraph object
+ *  userOptions: An object with the user specified options.
+ *
+ */
+Dygraph.Export.asCanvas = function (dygraph, userOptions) {
+    "use strict";
+    var options = {},
+        canvas = Dygraph.createCanvas();
+
+    Dygraph.update(options, Dygraph.Export.DEFAULT_ATTRS);
+    Dygraph.update(options, userOptions);
+
+    canvas.width = dygraph.width_;
+    canvas.height = dygraph.height_ + options.legendHeight;
+
+    Dygraph.Export.drawPlot(canvas, dygraph, options);
+    Dygraph.Export.drawLegend(canvas, dygraph, options);
+
+    return canvas;
+};
+
+/**
+ * Adds the plot and the axes to a canvas context.
+ */
+Dygraph.Export.drawPlot = function (canvas, dygraph, options) {
+    "use strict";
+    var ctx = canvas.getContext("2d");
+
+    // Add user defined background
+    ctx.fillStyle = options.backgroundColor;
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+    // Copy the plot canvas into the context of the new image.
+    var plotCanvas = dygraph.hidden_;
+
+    var i = 0;
+
+    ctx.drawImage(plotCanvas, 0, 0);
+
+
+    // Add the x and y axes
+    var axesPluginDict = Dygraph.Export.getPlugin(dygraph, 'Axes Plugin');
+    if (axesPluginDict) {
+        var axesPlugin = axesPluginDict.plugin;
+
+        for (i = 0; i < axesPlugin.ylabels_.length; i++) {
+            Dygraph.Export.putLabel(ctx, axesPlugin.ylabels_[i], options,
+                options.labelFont, options.labelFontColor);
+        }
+
+        for (i = 0; i < axesPlugin.xlabels_.length; i++) {
+            Dygraph.Export.putLabel(ctx, axesPlugin.xlabels_[i], options,
+                options.labelFont, options.labelFontColor);
+        }
+    }
+
+    // Title and axis labels
+
+    var labelsPluginDict = Dygraph.Export.getPlugin(dygraph, 'ChartLabels Plugin');
+    if (labelsPluginDict) {
+        var labelsPlugin = labelsPluginDict.plugin;
+
+        Dygraph.Export.putLabel(ctx, labelsPlugin.title_div_, options,
+            options.titleFont, options.titleFontColor);
+
+        Dygraph.Export.putLabel(ctx, labelsPlugin.xlabel_div_, options,
+            options.axisLabelFont, options.axisLabelFontColor);
+
+        Dygraph.Export.putVerticalLabelY1(ctx, labelsPlugin.ylabel_div_, options,
+            options.axisLabelFont, options.axisLabelFontColor, "center");
+
+        Dygraph.Export.putVerticalLabelY2(ctx, labelsPlugin.y2label_div_, options,
+            options.axisLabelFont, options.axisLabelFontColor, "center");
+    }
+
+
+	for (i = 0; i < dygraph.layout_.annotations.length; i++) {
+        Dygraph.Export.putLabelAnn(ctx, dygraph.layout_.annotations[i], options,
+                options.labelFont, options.labelColor);
+    }
+
+};
+
+/**
+ * Draws a label (axis label or graph title) at the same position
+ * where the div containing the text is located.
+ */
+Dygraph.Export.putLabel = function (ctx, divLabel, options, font, color) {
+    "use strict";
+
+    if (!divLabel || !divLabel.style) {
+        return;
+    }
+
+    var top = parseInt(divLabel.style.top, 10);
+    var left = parseInt(divLabel.style.left, 10);
+
+    if (!divLabel.style.top.length) {
+        var bottom = parseInt(divLabel.style.bottom, 10);
+        var height = parseInt(divLabel.style.height, 10);
+
+        top = ctx.canvas.height - options.legendHeight - bottom - height;
+    }
+
+    // FIXME: Remove this 'magic' number needed to get the line-height.
+    top = top + options.magicNumbertop;
+
+    var width = parseInt(divLabel.style.width, 10);
+
+    switch (divLabel.style.textAlign) {
+    case "center":
+        left = left + Math.ceil(width / 2);
+        break;
+    case "right":
+        left = left + width;
+        break;
+    }
+
+    Dygraph.Export.putText(ctx, left, top, divLabel, font, color);
+};
+
+/**
+ * Draws a label Y1 rotated 90 degrees counterclockwise.
+ */
+Dygraph.Export.putVerticalLabelY1 = function (ctx, divLabel, options, font, color, textAlign) {
+    "use strict";
+    if (!divLabel) {
+        return;
+    }
+
+    var top = parseInt(divLabel.style.top, 10);
+    var left = parseInt(divLabel.style.left, 10) + parseInt(divLabel.style.width, 10) / 2;
+    var text = divLabel.innerText || divLabel.textContent;
+
+
+    // FIXME: The value of the 'left' property is frequently 0, used the option.
+    if (!left)
+        left = options.vLabelLeft;
+
+    if (textAlign == "center") {
+        var textDim = ctx.measureText(text);
+        top = Math.ceil((ctx.canvas.height - textDim.width) / 2 + textDim.width);
+    }
+
+    ctx.save();
+    ctx.translate(0, ctx.canvas.height);
+    ctx.rotate(-Math.PI / 2);
+
+    ctx.fillStyle = color;
+    ctx.font = font;
+    ctx.textAlign = textAlign;
+    ctx.fillText(text, top, left);
+
+    ctx.restore();
+};
+
+/**
+ * Draws a label Y2 rotated 90 degrees clockwise.
+ */
+Dygraph.Export.putVerticalLabelY2 = function (ctx, divLabel, options, font, color, textAlign) {
+    "use strict";
+    if (!divLabel) {
+        return;
+    }
+
+    var top = parseInt(divLabel.style.top, 10);
+    var right = parseInt(divLabel.style.right, 10) + parseInt(divLabel.style.width, 10) * 2;
+    var text = divLabel.innerText || divLabel.textContent;
+
+    if (textAlign == "center") {
+        top = Math.ceil(ctx.canvas.height / 2);
+    }
+
+    ctx.save();
+    ctx.translate(parseInt(divLabel.style.width, 10), 0);
+    ctx.rotate(Math.PI / 2);
+
+    ctx.fillStyle = color;
+    ctx.font = font;
+    ctx.textAlign = textAlign;
+    ctx.fillText(text, top, right - ctx.canvas.width);
+
+    ctx.restore();
+};
+
+/**
+ * Draws the text contained in 'divLabel' at the specified position.
+ */
+Dygraph.Export.putText = function (ctx, left, top, divLabel, font, color) {
+    "use strict";
+    var textAlign = divLabel.style.textAlign || "left";
+    var text = divLabel.innerText || divLabel.textContent;
+
+    ctx.fillStyle = color;
+    ctx.font = font;
+    ctx.textAlign = textAlign;
+    ctx.textBaseline = "middle";
+    ctx.fillText(text, left, top);
+};
+
+/**
+ * Draws the legend of a dygraph
+ *
+ */
+Dygraph.Export.drawLegend = function (canvas, dygraph, options) {
+    "use strict";
+    var ctx = canvas.getContext("2d");
+
+    // Margin from the plot
+    var labelTopMargin = 10;
+
+    // Margin between labels
+    var labelMargin = 5;
+
+    var colors = dygraph.getColors();
+    // Drop the first element, which is the label for the time dimension
+    var labels = dygraph.attr_("labels").slice(1);
+
+    // 1. Compute the width of the labels:
+    var labelsWidth = 0;
+
+    var i;
+    for (i = 0; i < labels.length; i++) {
+        labelsWidth = labelsWidth + ctx.measureText("- " + labels[i]).width + labelMargin;
+    }
+
+    var labelsX = Math.floor((canvas.width - labelsWidth) / 2);
+    var labelsY = canvas.height - options.legendHeight + labelTopMargin;
+
+
+    var labelVisibility=dygraph.attr_("visibility");
+
+    ctx.font = options.legendFont;
+    ctx.textAlign = "left";
+    ctx.textBaseline = "middle";
+
+    var usedColorCount=0;
+    for (i = 0; i < labels.length; i++) {
+        if (labelVisibility[i]) {
+            //TODO Replace the minus sign by a proper dash, although there is a
+            //     problem when the page encoding is different than the encoding
+            //     of this file (UTF-8).
+            var txt = "- " + labels[i];
+            ctx.fillStyle = colors[usedColorCount];
+            usedColorCount++
+            ctx.fillText(txt, labelsX, labelsY);
+            labelsX = labelsX + ctx.measureText(txt).width + labelMargin;
+        }
+    }
+};
+
+/**
+ * Finds a plugin by the value returned by its toString method..
+ *
+ * Returns the the dictionary corresponding to the plugin for the argument.
+ * If the plugin is not found, it returns null.
+ */
+Dygraph.Export.getPlugin = function(dygraph, name) {
+    for (i = 0; i < dygraph.plugins_.length; i++) {
+        if (dygraph.plugins_[i].plugin.toString() == name) {
+            return dygraph.plugins_[i];
+        }
+    }
+    return null;
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/grapher.js
----------------------------------------------------------------------
diff --git a/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/grapher.js b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/grapher.js
new file mode 100644
index 0000000..247e6d9
--- /dev/null
+++ b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/grapher.js
@@ -0,0 +1,365 @@
+/*
+ * Licensed 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.
+ */
+// Whether there is currently an outbound request.
+var awaitingResponse = false;
+
+var allMetrics = [];     // Names of all available metrics.
+var metrics = {};        // Time series names, mapped to their index in rawData.
+var rawData;             // 2-d array of metric data from the server.
+var evaluatedData = [];  // 2-d array of evaluated metric data.
+
+// Expressions being plotted.
+var expressions = {};
+
+// Timestamp of the most-recent row.
+var lastTimestamp = -1;
+
+// Whether to continuously update the graph with new data..
+var realTime = true;
+
+// Store at most 3600 points per plot.
+var maxPoints = 3600;
+
+// Actual dygraph object.
+var dygraph;
+
+// Formatter for the x axis.
+var xAxisFormatter = function(date, self) {
+  return Dygraph.hmsString_(date);
+}
+
+// Dygraph graph options.
+var options = {
+  axes: { x: { valueFormatter: xAxisFormatter,
+               axisLabelFormatter: xAxisFormatter
+             }
+        },
+  labelsSeparateLines: true,
+  hideOverlayOnMouseOut: false,
+  showRangeSelector: true,
+  labelsDiv: 'legend',
+  legend: 'always',
+};
+
+/**
+ * Issues a query to fetch graph data from the server.
+ */
+function fetchData() {
+  var metrics = {};
+  $.each(Object.keys(expressions), function(i, name) {
+    $.each(expressions[name].getVars(), function(j, metric) {
+      metrics[metric] = true;
+    });
+  });
+  if ($('#errors').is(':empty') && Object.keys(metrics).length > 0) {
+    sendQuery('?metrics=' + Object.keys(metrics).join(',') + '&since=' + lastTimestamp,
+              handleDataResponse);
+  }
+}
+
+/**
+ * Clears error messages from the page.
+ */
+function clearErrors() {
+  $('#errors').hide();
+  $('#errors').empty();
+}
+
+function addErrorText(text) {
+  $('#errors').append(text).show();
+}
+
+function addError(expr, error, pos) {
+  throw expr + '\n' + Array(pos + 1).join(' ') + '^ ' + error;
+}
+
+/**
+ * Issues a query to fetch time series data from the server.
+ * If the request is successful, the provided response handler will be called.
+ */
+function sendQuery(query, responseHandler) {
+  if (awaitingResponse) return;
+  if (!realTime) return;
+  awaitingResponse = true;
+
+  var wrappedHandler = function(data) {
+    awaitingResponse = false;
+    responseHandler(data);
+  };
+
+  $.getJSON('/graphdata/' + query, wrappedHandler).error(
+    function(xhr) {
+      addErrorText('Query failed: ' + $.parseJSON(xhr.responseText).error);
+      awaitingResponse = false;
+    });
+}
+
+/**
+ * Clears stored data and fetches all plot data.
+ */
+function refreshGraph() {
+  append = false;
+  rawData = null;
+  evaluatedData = [];
+  metrics = {};
+  lastTimestamp = -1;
+  fetchData();
+}
+
+/**
+ * Redraws the graph with the current data and options.
+ */
+function redraw() {
+  options.file = evaluatedData;
+  options.labels = ['t'].concat(Object.keys(expressions));
+  dygraph.updateOptions(options);
+}
+
+/**
+ * Handles a data query response from the server.
+ */
+function handleDataResponse(resp) {
+  var newData = resp.data;
+
+  if (newData.length == 0) {
+    return;
+  }
+
+  var append = false;
+  if (!rawData) {
+    rawData = newData;
+    $.each(resp.names, function(i, name) {
+      metrics[name] = i;
+    });
+  } else {
+    // Append the new table to the old table.
+    // TODO(William Farner): Make sure metricNames indices match up.
+    rawData = rawData.concat(newData);
+    append = true;
+  }
+
+  // Feed data into expressions.
+  $.each(newData, function(j, point) {
+    evaluatedData.push([point[metrics["time"]]].concat(
+      $.map(Object.keys(expressions), function(name) {
+        return expressions[name].evaluate(point);
+      })));
+  });
+
+  // Figure out what the last timestamp is.
+  lastTimestamp = rawData[rawData.length - 1][metrics["time"]];
+
+  // Evict the oldest rows.
+  if (rawData.length > maxPoints) {
+    rawData.splice(0, rawData.length - maxPoints);
+  }
+  if (evaluatedData.length > maxPoints) {
+    evaluatedData.splice(0, evaluatedData.length - maxPoints);
+  }
+
+  if (append) {
+    redraw();
+  } else {
+    options.labels = ['t'].concat(Object.keys(expressions));
+    dygraph = new Dygraph($('#dygraph')[0], evaluatedData, options);
+  }
+}
+
+/**
+ * Calls the apply function with the parsed value extracted from a text field.
+ * If the value of the text field is not a valid number, the apply function will
+ * not be called.
+ */
+function tryApply(textField, numberParser, applyFunction) {
+  var number = numberParser(textField.value);
+  if (!isNaN(number)) {
+    applyFunction(number);
+  }
+
+  return false;
+}
+
+/**
+ * Convenience function to call tryApply() if the provided key press event
+ * was for the enter key.
+ */
+function applyOnEnter(event, textField, numberParser, applyFunction) {
+  if (event.keyCode == 13) tryApply(textField, numberParser, applyFunction);
+}
+
+function applyQuery() {
+  var query = $('#query').val();
+  clearErrors();
+  $('#links').empty();
+  expressions = {};
+  $.each($('#query').val().replace(/[ \t]/g, '').split('\n'), function(i, query) {
+    if (query) {
+      try {
+        expressions[query] = parser.parse(query);
+      } catch (err) {
+        addErrorText(err);
+      }
+    }
+  });
+  refreshGraph();
+  $('#links')
+    .append($('<a>', {
+      text: 'link',
+      href: '?query=' + encodeURIComponent($('#query').val()),
+      target: 'none'}))
+    .append($('<br />'))
+    .append($('<a>', {
+      text: 'img',
+      href: '#',
+      id: 'download'}));
+  $('#download').click(function() {
+    window.location = Dygraph.Export.asCanvas(dygraph).toDataURL('image/png');
+  });
+}
+
+$(document).ready(function() {
+  $('#submit').click(applyQuery);
+  var fieldApplier = function(selector, verify, apply) {
+    $(selector).keypress(function(e) {
+      applyOnEnter(e, this, verify, apply);
+    });
+    $(selector).blur(function() {
+      tryApply(this, verify, apply);
+    });
+  }
+  fieldApplier('#yMin', parseFloat, function(ymin) {
+    dygraph.updateOptions({'valueRange': [ymin, dygraph.yAxisRange(0)[1]]});
+  });
+  fieldApplier('#yMax', parseFloat, function(ymax) {
+    dygraph.updateOptions({'valueRange': [dygraph.yAxisRange(0)[0], ymax]});
+  });
+  fieldApplier('#smoothing', parseInt, function(value) {
+    options.rollPeriod = value;
+    redraw();
+  });
+  $('#realTime').click(function() {
+    realTime = !realTime;
+    redraw();
+  });
+
+  sendQuery('', function(metrics) {
+    metrics.sort();
+    allMetrics = metrics;
+    $.each(metrics, function(i, metric) {
+      $('#availableMetrics')
+          .append($('<option></option>')
+          .attr('value', metric)
+          .text(metric));
+    });
+    $('#availableMetrics').change(function() {
+      var q = $('#query');
+      var pos = q[0].selectionStart;
+      var adjustedPos;
+      var val = q.val();
+      var metric = $('#availableMetrics').val();
+      if (pos == val.length) {
+        // The cursor is at the end.  For convenience, append the selection
+        // and insert a newline.  This simplifies selection of multiple plots.
+        q.val(val + metric + '\n');
+        adjustedPos = pos + metric.length + 1;
+      } else {
+        q.val(val.substring(0, pos) + metric + val.substring(pos));
+        adjustedPos = val.substring(0, pos).length + metric.length;
+      }
+      q[0].selectionStart = adjustedPos;
+      q[0].selectionEnd = adjustedPos;
+    });
+
+    var termBounds = function() {
+      var isIdChar = function(c) {
+        return /[\w\-]/g.exec(c);
+      };
+      var pos = $('#query')[0].selectionStart;
+      var fullQuery = $('#query').val();
+      if (pos > 0 && isIdChar(fullQuery.charAt(pos - 1))) {
+        if (fullQuery.length > pos && isIdChar(fullQuery.charAt(pos))) {
+          // Break out if this is editing within a word.
+          return;
+        }
+
+        // Walk backwards to the beginning of the word.
+        var wordStart = pos;
+        while (wordStart > 0 && isIdChar(fullQuery.charAt(wordStart - 1))) {
+          wordStart--;
+        }
+        return {
+          wordStart: wordStart,
+          pos: pos
+        };
+      }
+    };
+    $('#query').autocomplete({
+      delay: 0,
+      source: function(request, response) {
+        var results = [];
+        var term = termBounds();
+        if (term) {
+          var corpus = $.map(allMetrics, function(metric) {
+            return {
+              label: metric,
+              value: metric
+            };
+          });
+          corpus = corpus.concat($.map(Object.keys(parser.functions), function(f) {
+            return {
+              label: parser.functions[f].help, value: f
+            };
+          }));
+          var text = $('#query').val().substring(term.wordStart, term.pos);
+          response($.ui.autocomplete.filter(corpus, text));
+        } else {
+          response([]);
+        }
+      },
+      select: function(event, ui) {
+        var query = $('#query').val()
+        var bounds = termBounds();
+        if (bounds) {
+          this.value = query.substring(0, bounds.wordStart)
+              + ui.item.value
+              + query.substring(bounds.pos);
+          var adjustedPos = bounds.wordStart + ui.item.value.length;
+          // Note: This is not likely to work in IE.
+          $('#query')[0].selectionStart = adjustedPos;
+          $('#query')[0].selectionEnd = adjustedPos;
+        }
+        return false;
+      },
+      focus: function() { return false; }
+    });
+  });
+  // Submit on alt/option-enter.
+  $('#query').keypress(function(e) {
+    if (e.which == 13 && e.altKey) {
+      applyQuery();
+      return false;
+    }
+  });
+  var getUrlParam = function(name) {
+    var match = RegExp(name + '=' + '(.+?)(&|$)').exec(location.search);
+    return match ? decodeURIComponent(match[1]) : null;
+  };
+  var urlQuery = getUrlParam('query');
+  if (urlQuery != null) {
+    $('#query').val(urlQuery);
+    applyQuery();
+  }
+
+  setInterval(fetchData, 1000);
+});

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/graphview.html
----------------------------------------------------------------------
diff --git a/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/graphview.html b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/graphview.html
new file mode 100755
index 0000000..dd3fd5d
--- /dev/null
+++ b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/graphview.html
@@ -0,0 +1,84 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!--
+
+    Licensed 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.
+
+-->
+<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
+    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
+
+<html>
+  <head>
+    <title>Server Graphing</title>
+    <script src='//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'></script>
+    <script src='//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/jquery-ui.min.js'></script>
+    <link href='//ajax.googleapis.com/ajax/libs/jqueryui/1.9.2/themes/base/jquery.ui.all.css'
+          rel='stylesheet'>
+    <link href='//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-combined.min.css'
+          rel='stylesheet'>
+    <script src='//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js'></script>
+    <script src='graphview/dygraph-combined.js'></script>
+    <script src='graphview/dygraph-extra.js'></script>
+    <script src='graphview/grapher.js'></script>
+    <script src='graphview/parser.js'></script>
+  </head>
+  <body>
+    <h2>Metrics</h2>
+    <div style='width: 50%; margin:20px; float:left;'>
+      <select id='availableMetrics'></select>
+      <br />
+      <div>
+        <textarea id='query'
+                  rows='4'
+                  style='width:100%; box-sizing:border-box;'
+                  placeholder='Type arithmetic expressions using metrics (lines plot separately).'></textarea>
+        <button class='btn' id='submit' type='button' style='float:right;'>Plot</button>
+      </div>
+      <pre id='errors' style='display: none; clear: both'></pre>
+      <br />
+      <br />
+      <table>
+        <tr>
+          <td>
+            <label for='yMin'>Y min</label>
+            <input class='input-mini' type='text' size='4' id='yMin' />
+          </td>
+          <td>
+            <label for='yMax'>Y max</label>
+            <input class='input-mini' type='text' size='4' id='yMax' />
+          </td>
+        </tr>
+        <tr>
+          <td>
+            <label for='smoothing'>Smooth</label>
+            <div class='input-append'>
+              <input class='input-mini' type='text' size='4' id='smoothing' />
+              <span class='add-on'>pt</span>
+            </div>
+          </td>
+          <td align='right' valign='bottom'>
+            <div>
+              <label for='realTime'>Pause</label>
+              <input type='checkbox' id='realTime' />
+            </div>
+          </td>
+        </tr>
+      </table>
+    </div>
+    <div style='width:40%; float:left; margin:20px;'>
+      <div id='links'></div>
+      <div id='dygraph' style='width:100%;'></div>
+      <div id='legend'></div>
+    </div>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/parser.js
----------------------------------------------------------------------
diff --git a/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/parser.js b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/parser.js
new file mode 100644
index 0000000..17acf84
--- /dev/null
+++ b/commons/src/main/resources/org/apache/aurora/common/application/http/graphview/parser.js
@@ -0,0 +1,337 @@
+/*
+ * Licensed 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.
+ */
+// TODO(William Farner): A unit test is sorely needed here.  Add a unit test for jsirois to use
+// as a starter for pants support for jsunit.
+
+// Declare namespace.
+parser = {};
+
+parser.Abs = function(args) {
+  if (args.length != 1) {
+    throw "abs() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Abs.help = "abs(num)";
+parser.Abs.prototype.evaluate = function(vars) {
+  return Math.abs(this.arg.evaluate(vars));
+};
+
+
+parser.Ceil = function(args) {
+  if (args.length != 1) {
+    throw "ceil() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Ceil.help = "ceil(num)";
+parser.Ceil.prototype.evaluate = function(vars) {
+  return Math.ceil(this.arg.evaluate(vars));
+};
+
+
+parser.Floor = function(args) {
+  if (args.length != 1) {
+    throw "floor() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Floor.help = "floor(num)";
+parser.Floor.prototype.evaluate = function(vars) {
+  return Math.floor(this.arg.evaluate(vars));
+};
+
+
+parser.Log = function(args) {
+  if (args.length != 1) {
+    throw "log() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Log.help = "log(num)";
+parser.Log.prototype.evaluate = function(vars) {
+  return Math.log(this.args.evaluate(vars));
+};
+
+
+parser.Rate = function(args) {
+  if (args.length == 1) {
+    this.winLen = 1;
+    this.arg = args[0];
+  } else if (args.length == 2) {
+    if (!(args[0] instanceof parser.Constant)) {
+      throw "the first argument to rate() must be a constant.";
+    }
+    this.winLen = args[0].c;
+    this.arg = args[1];
+  } else {
+    throw "rate() accepts one or two arguments.";
+  }
+
+  this.samples = [];
+  this.timeInput = new parser.Var(-1, "time");
+};
+parser.Rate.help = "rate([window size,] var)";
+parser.Rate.prototype.evaluate = function(vars) {
+  var newY = this.arg.evaluate(vars);
+  var newT = this.timeInput.evaluate(vars);
+  this.samples.push([newY, newT]);
+  var oldest = this.samples[0];
+  if (this.samples.length > this.winLen) {
+    this.samples.splice(0, this.samples.length - this.winLen);
+  }
+  var denom = newT - oldest[1];
+  // Assumes time unit is milliseconds.
+  return (denom == 0) ? 0 : ((1000 * (newY - oldest[0])) / denom);
+};
+
+
+parser.Round = function(args) {
+  if (args.length != 1) {
+    throw "round() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Round.help = "round(num)";
+parser.Round.prototype.evaluate = function(vars) {
+  return Math.round(this.arg.evaluate(vars));
+};
+
+
+parser.Sqrt = function(args) {
+  if (args.length != 1) {
+    throw "sqrt() accepts exactly one argument.";
+  }
+  this.arg = args[0];
+};
+parser.Sqrt.help = "sqrt(num)";
+parser.Sqrt.prototype.evaluate = function(vars) {
+  return Math.sqrt(this.arg.evaluate(vars));
+};
+
+
+parser.functions = {
+  'abs':    parser.Abs,
+  'ceil':   parser.Ceil,
+  'floor':  parser.Floor,
+  'log':    parser.Log,
+  'rate':   parser.Rate,
+  'round':  parser.Round,
+  'sqrt':   parser.Sqrt
+};
+
+
+parser.Operator = function(evaluator) {
+  this.evaluator = evaluator;
+};
+parser._operators = {
+    '+': new parser.Operator(function(a, b) { return a + b; }),
+    '-': new parser.Operator(function(a, b) { return a - b; }),
+    '*': new parser.Operator(function(a, b) { return a * b; }),
+    '/': new parser.Operator(function(a, b) { return a / b; })
+};
+
+
+parser.Token = function(start, text) {
+  this.start = start;
+  this.text = text;
+};
+
+
+parser.Part = function(start) {
+  this.start = start;
+};
+parser.Part.prototype.getVars = function() {
+  return [];
+};
+
+
+parser.MetaPart = function(start, args) {
+  this.Part = parser.Part;
+  this.Part(start);
+  this.args = args || [];
+};
+parser.MetaPart.prototype.getVars = function() {
+  var all = [];
+  $.each(this.args, function(i, arg) {
+    all = all.concat(arg.getVars());
+  });
+  return all;
+};
+
+
+parser.Function = function(start, evaluator, args) {
+  this.MetaPart = parser.MetaPart;
+  this.MetaPart(start, args);
+  this.evaluator = evaluator;
+};
+parser.Function.prototype = new parser.MetaPart();
+parser.Function.prototype.evaluate = function(vars) {
+  return this.evaluator.evaluate(vars);
+};
+
+
+parser.Operation = function(start, op) {
+  this.MetaPart = parser.MetaPart;
+  this.MetaPart(start, []);
+  this.op = op;
+};
+parser.Operation.prototype = new parser.MetaPart();
+parser.Operation.prototype.evaluate = function(vars) {
+  var result = this.args[0].evaluate(vars);
+  for (var i = 1; i < this.args.length; i++) {
+    result = this.op.evaluator(result, this.args[i].evaluate(vars));
+  }
+  return result;
+};
+
+
+parser.Constant = function(start, c) {
+  this.Part = parser.Part;
+  this.Part(start);
+  this.c = parseFloat(c);
+};
+parser.Constant.prototype = new parser.Part();
+parser.Constant.prototype.evaluate = function() {
+  return this.c;
+};
+
+
+parser.Var = function(start, name) {
+  this.Part = parser.Part;
+  this.Part(start);
+  this.name = name;
+};
+parser.Var.prototype.evaluate = function(vars) {
+  // TODO(William Farner): Clean this up - currently it's reaching out
+  // to state within grapher.js.
+  return vars[metrics[this.name]];
+};
+parser.Var.prototype.getVars = function() {
+  return [this.name];
+};
+
+
+parser.tokenize = function(str, offset, isDelimiter) {
+  if (offset === undefined) {
+    offset = 0;
+  }
+
+  var level = 0;
+  var start = 0;
+  var tokens = [];
+  for (var i = 0; i < str.length; i++) {
+    var c = str.charAt(i);
+    if (c == '(') {
+      level += 1;
+      continue;
+    } else if (c == ')') {
+      level -= 1;
+      continue;
+    }
+
+    if (level == 0) {
+      if (isDelimiter(c)) {
+        var token = str.substring(start, i);
+        if (token.length == 0) {
+          addError(str, 'Missing operand', i + offset);
+        }
+        tokens.push(new parser.Token(start + offset, token));
+        tokens.push(new parser.Token(start, c));
+        start = i + 1;
+      }
+    }
+  }
+
+  var token = str.substring(start);
+  if (token.length == 0) {
+    addError(str, 'Expected expression but found operator', start + offset);
+  }
+  tokens.push(new parser.Token(start + offset, str.substring(start)));
+
+  return tokens;
+};
+
+var _FUNCTION_CALL_RE = /^(\w+)\((.*)\)$/g;
+var _SUB_EXPRESSION_RE = /^\((.*)\)$/g;
+var _PAREN_RE = /([\(\)])/;
+
+parser.parse = function(query, offset) {
+  // Split the expression at operator boundaries in the top-level scope.
+  var tokens = parser.tokenize(query, offset, function(c) {
+    return parser._operators[c];
+  });
+  tokens = $.map(tokens, function(token) {
+    var op = parser._operators[token.text];
+    return op ? new parser.Operation(token.start, op) : token;
+  });
+
+  var result = [];
+  $.each(tokens, function(i, token) {
+    if (token instanceof parser.Operation) {
+      token.args.push(result.splice(result.length - 1, 1)[0]);
+      result.push(token);
+      return;
+    }
+
+    // Match a function call.
+    var parsed;
+    var match = _FUNCTION_CALL_RE.exec(token.text);
+    if (match) {
+      var f = match[1];
+      var arg = match[2];
+      if (!parser.functions[f]) {
+        addError(query, 'Unrecognized function ' + f, token.start);
+      }
+      var parsedArg = parser.parse(arg, token.start + 1);
+      // Split and parse function args.
+      var argTokens = parser.tokenize(arg, token.start + 1, function(c) { return c == ','; });
+      argTokens = $.grep(argTokens, function(argToken) { return argToken.text != ','; });
+      var parsedArgs = $.map(argTokens, function(argToken) {
+        return parser.parse(argToken.text, argToken.start);
+      });
+      parsed = new parser.Function(
+          token.start,
+          new parser.functions[f](parsedArgs), parsedArgs);
+    } else {
+      // Match a sub expression.
+      match = _SUB_EXPRESSION_RE.exec(token.text);
+      if (match) {
+        parsed = parser.parse(match[1], token.start + 1);
+      } else {
+        match = _PAREN_RE.exec(token.text);
+        if (match) {
+          addError(query, 'Unmatched paren', token.start + token.text.indexOf(match[1]));
+        }
+        if (isNaN(token.text)) {
+          parsed = new parser.Var(token.start, token.text);
+        } else {
+          parsed = new parser.Constant(token.start, token.text);
+        }
+      }
+    }
+
+    var lastResult = result.length == 0 ? null : result[result.length - 1];
+    if (lastResult instanceof parser.Operation) {
+      lastResult.args.push(parsed);
+    } else {
+      result.push(parsed);
+    }
+  });
+
+  if (result.length != 1) {
+    throw 'Unexpected state.';
+  }
+  return result[0];
+};

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/resources/org/apache/aurora/common/common/webassets/bootstrap/2.1.1/css/bootstrap-responsive.min.css
----------------------------------------------------------------------
diff --git a/commons/src/main/resources/org/apache/aurora/common/common/webassets/bootstrap/2.1.1/css/bootstrap-responsive.min.css b/commons/src/main/resources/org/apache/aurora/common/common/webassets/bootstrap/2.1.1/css/bootstrap-responsive.min.css
new file mode 100644
index 0000000..c39fd97
--- /dev/null
+++ b/commons/src/main/resources/org/apache/aurora/common/common/webassets/bootstrap/2.1.1/css/bootstrap-responsive.min.css
@@ -0,0 +1,14 @@
+/**
+ * Licensed 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.
+ */
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zo
 om:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block
 ;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-flu
 id .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.3
 93162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017
 094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.spa
 n2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{marg
 in-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:
 40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.0718232
 0441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:
 28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,
 .uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-
 box-sizing:border-box;box-sizing:border-box}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade.in{top:auto}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-rig
 ht:10px;padding-left:10px}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-bo
 rder-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px
  0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}