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 (j...@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}}