diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java
index 3537346b..abf27bd7 100644
--- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/annotations/EnableHystrixDashboard.java
@@ -6,7 +6,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.springframework.cloud.netflix.hystrix.HystrixDashboardConfiguration;
+import org.springframework.cloud.netflix.hystrix.dashboard.HystrixDashboardConfiguration;
import org.springframework.context.annotation.Import;
/**
diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java
similarity index 99%
rename from spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java
rename to spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java
index 3ff80319..a584dc14 100644
--- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/HystrixDashboardConfiguration.java
+++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/dashboard/HystrixDashboardConfiguration.java
@@ -1,4 +1,4 @@
-package org.springframework.cloud.netflix.hystrix;
+package org.springframework.cloud.netflix.hystrix.dashboard;
import java.io.IOException;
import java.io.InputStream;
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css
new file mode 100644
index 00000000..71d5ee39
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.css
@@ -0,0 +1,196 @@
+.dependencies .spacer {
+ width: 100%;
+ margin: 0 auto;
+ padding-top:4px;
+ clear:both;
+}
+
+
+.dependencies .last {
+ margin-right: 0px;
+}
+
+.dependencies span.loading {
+ display: block;
+ padding-top: 6%;
+ padding-bottom: 6%;
+ color: gray;
+ text-align: center;
+}
+
+.dependencies span.loading.failed {
+ color: red;
+}
+
+
+.dependencies div.monitor {
+ float: left;
+ margin-right:5px;
+ margin-top:5px;
+}
+
+.dependencies div.monitor p.name {
+ font-weight:bold;
+ font-size: 10pt;
+ text-align: right;
+ padding-bottom: 5px;
+}
+
+.dependencies div.monitor_data {
+ margin: 0 auto;
+}
+
+/* override the HREF when we have specified it as a tooltip to not act like a link */
+.dependencies div.monitor_data a.tooltip {
+ text-decoration: none;
+ cursor: default;
+}
+
+.dependencies div.monitor_data div.counters {
+ text-align: right;
+ padding-bottom: 10px;
+ font-size: 10pt;
+ clear: both;
+
+}
+
+.dependencies div.monitor_data div.counters div.cell {
+ display: inline;
+ float: right;
+}
+
+.dependencies .borderRight {
+ border-right: 1px solid grey;
+ padding-right: 6px;
+ padding-left: 8px;
+}
+
+.dependencies div.cell .line {
+ display: block;
+}
+
+.dependencies div.monitor_data a,
+.dependencies span.rate_value {
+ font-weight:bold;
+}
+
+
+.dependencies span.smaller {
+ font-size: 8pt;
+ color: grey;
+}
+
+
+
+.dependencies div.tableRow {
+ width:100%;
+ white-space: nowrap;
+ font-size: 8pt;
+ margin: 0 auto;
+ clear:both;
+ padding-left:26%;
+}
+
+.dependencies div.tableRow .cell {
+ float:left;
+}
+
+.dependencies div.tableRow .header {
+ width:18%;
+ text-align:right;
+ padding-right:2%;
+}
+
+.dependencies div.tableRow .data {
+ width:17%;
+ font-weight: bold;
+ text-align:right;
+}
+
+
+.dependencies div.monitor {
+ width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
+ height: 150px;
+}
+
+.dependencies .success {
+ color: green;
+}
+.dependencies .shortCircuited {
+ color: blue;
+}
+.dependencies .timeout {
+ color: #FF9900; /* shade of orange */
+}
+.dependencies .failure {
+ color: red;
+}
+
+.dependencies .rejected {
+ color: purple;
+}
+
+.dependencies .exceptionsThrown {
+ color: brown;
+}
+
+.dependencies div.monitor_data a.rate {
+ color: black;
+ font-size: 11pt;
+}
+
+.dependencies div.rate {
+ padding-top: 1px;
+ clear:both;
+ text-align:right;
+}
+
+.dependencies .errorPercentage {
+ color: grey;
+}
+
+.dependencies div.cell .errorPercentage {
+ padding-left:5px;
+ font-size: 12pt !important;
+}
+
+
+.dependencies div.monitor div.chart {
+}
+
+.dependencies div.monitor div.chart svg {
+}
+
+.dependencies div.monitor div.chart svg text {
+ fill: white;
+}
+
+
+.dependencies div.circuitStatus {
+ width:100%;
+ white-space: nowrap;
+ font-size: 9pt;
+ margin: 0 auto;
+ clear:both;
+ text-align:right;
+ padding-top: 4px;
+}
+
+.dependencies #hidden {
+ width:1px;
+ height:1px;
+ background: lightgrey;
+ display: none;
+}
+
+
+
+/* sparkline */
+.dependencies path {
+ stroke: steelblue;
+ stroke-width: 1;
+ fill: none;
+}
+
+
+}
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js
new file mode 100644
index 00000000..dc375759
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/hystrixCommand.js
@@ -0,0 +1,536 @@
+
+(function(window) {
+
+ // cache the templates we use on this page as global variables (asynchronously)
+ jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuit.html"), function(data) {
+ hystrixTemplateCircuit = data;
+ });
+ jQuery.get(getRelativePath("../components/hystrixCommand/templates/hystrixCircuitContainer.html"), function(data) {
+ hystrixTemplateCircuitContainer = data;
+ });
+
+ function getRelativePath(path) {
+ var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
+ return p + path;
+ }
+
+ /**
+ * Object containing functions for displaying and updating the UI with streaming data.
+ *
+ * Publish this externally as "HystrixCommandMonitor"
+ */
+ window.HystrixCommandMonitor = function(containerId, args) {
+
+ var self = this; // keep scope under control
+ self.args = args;
+ if(self.args == undefined) {
+ self.args = {};
+ }
+
+ this.containerId = containerId;
+
+ /**
+ * Initialization on construction
+ */
+ // intialize various variables we use for visualization
+ var maxXaxisForCircle="40%";
+ var maxYaxisForCircle="40%";
+ var maxRadiusForCircle="125";
+
+ // CIRCUIT_BREAKER circle visualization settings
+ self.circuitCircleRadius = d3.scale.pow().exponent(0.5).domain([0, 400]).range(["5", maxRadiusForCircle]); // requests per second per host
+ self.circuitCircleYaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxXaxisForCircle]);
+ self.circuitCircleXaxis = d3.scale.linear().domain([0, 400]).range(["30%", maxYaxisForCircle]);
+ self.circuitColorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
+ self.circuitErrorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
+
+ /**
+ * We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
+ * to maintain whatever sort the user (or default) has chosen.
+ *
+ * In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
+ */
+ setInterval(function() {
+ // sort since we have added a new one
+ self.sortSameAsLast();
+ }, 10000);
+
+
+ /**
+ * END of Initialization on construction
+ */
+
+ /**
+ * Event listener to handle new messages from EventSource as streamed from the server.
+ */
+ /* public */ self.eventSourceMessageListener = function(e) {
+ var data = JSON.parse(e.data);
+ if(data) {
+ // check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
+ if(!data.reportingHosts) {
+ data.reportingHosts = 1;
+ }
+
+ if(data && data.type == 'HystrixCommand') {
+ if (data.deleteData == 'true') {
+ deleteCircuit(data.escapedName);
+ } else {
+ displayCircuit(data);
+ }
+ }
+ }
+ };
+
+ /**
+ * Pre process the data before displying in the UI.
+ * e.g Get Averages from sums, do rate calculation etc.
+ */
+ function preProcessData(data) {
+ validateData(data);
+ // escape string used in jQuery & d3 selectors
+ data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
+ // do math
+ converAllAvg(data);
+ calcRatePerSecond(data);
+ }
+
+ /**
+ * Since the stream of data can be aggregated from multiple hosts in a tiered manner
+ * the aggregation just sums everything together and provides us the denominator (reportingHosts)
+ * so we must divide by it to get an average per instance value.
+ *
+ * We want to do this on any numerical values where we want per instance rather than cluster-wide sum.
+ */
+ function converAllAvg(data) {
+ convertAvg(data, "errorPercentage", true);
+ convertAvg(data, "latencyExecute_mean", false);
+ convertAvg(data, "latencyTotal_mean", false);
+
+ // the following will break when it becomes a compound string if the property is dynamically changed
+ convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
+ }
+
+ function convertAvg(data, key, decimal) {
+ if (decimal) {
+ data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
+ } else {
+ data[key] = getInstanceAverage(data[key], data["reportingHosts"], decimal);
+ }
+ }
+
+ function getInstanceAverage(value, reportingHosts, decimal) {
+ if (decimal) {
+ return roundNumber(value/reportingHosts);
+ } else {
+ return Math.floor(value/reportingHosts);
+ }
+ }
+
+ function calcRatePerSecond(data) {
+ var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
+
+ var totalRequests = data["requestCount"];
+ if (totalRequests < 0) {
+ totalRequests = 0;
+ }
+ data["ratePerSecond"] = roundNumber(totalRequests / numberSeconds);
+ data["ratePerSecondPerHost"] = roundNumber(totalRequests / numberSeconds / data["reportingHosts"]) ;
+ }
+
+ function validateData(data) {
+ assertNotNull(data,"reportingHosts");
+ assertNotNull(data,"type");
+ assertNotNull(data,"name");
+ assertNotNull(data,"group");
+ // assertNotNull(data,"currentTime");
+ assertNotNull(data,"isCircuitBreakerOpen");
+ assertNotNull(data,"errorPercentage");
+ assertNotNull(data,"errorCount");
+ assertNotNull(data,"requestCount");
+ assertNotNull(data,"rollingCountCollapsedRequests");
+ assertNotNull(data,"rollingCountExceptionsThrown");
+ assertNotNull(data,"rollingCountFailure");
+ assertNotNull(data,"rollingCountFallbackFailure");
+ assertNotNull(data,"rollingCountFallbackRejection");
+ assertNotNull(data,"rollingCountFallbackSuccess");
+ assertNotNull(data,"rollingCountResponsesFromCache");
+ assertNotNull(data,"rollingCountSemaphoreRejected");
+ assertNotNull(data,"rollingCountShortCircuited");
+ assertNotNull(data,"rollingCountSuccess");
+ assertNotNull(data,"rollingCountThreadPoolRejected");
+ assertNotNull(data,"rollingCountTimeout");
+ assertNotNull(data,"currentConcurrentExecutionCount");
+ assertNotNull(data,"latencyExecute_mean");
+ assertNotNull(data,"latencyExecute");
+ assertNotNull(data,"latencyTotal_mean");
+ assertNotNull(data,"latencyTotal");
+ assertNotNull(data,"propertyValue_circuitBreakerRequestVolumeThreshold");
+ assertNotNull(data,"propertyValue_circuitBreakerSleepWindowInMilliseconds");
+ assertNotNull(data,"propertyValue_circuitBreakerErrorThresholdPercentage");
+ assertNotNull(data,"propertyValue_circuitBreakerForceOpen");
+ assertNotNull(data,"propertyValue_executionIsolationStrategy");
+ assertNotNull(data,"propertyValue_executionIsolationThreadTimeoutInMilliseconds");
+ assertNotNull(data,"propertyValue_executionIsolationThreadInterruptOnTimeout");
+ // assertNotNull(data,"propertyValue_executionIsolationThreadPoolKeyOverride");
+ assertNotNull(data,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests");
+ assertNotNull(data,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests");
+ assertNotNull(data,"propertyValue_requestCacheEnabled");
+ assertNotNull(data,"propertyValue_requestLogEnabled");
+ assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
+ }
+
+ function assertNotNull(data, key) {
+ if(data[key] == undefined) {
+ throw new Error("Key Missing: " + key + " for " + data.name);
+ }
+ }
+
+ /**
+ * Method to display the CIRCUIT data
+ *
+ * @param data
+ */
+ /* private */ function displayCircuit(data) {
+
+ try {
+ preProcessData(data);
+ } catch (err) {
+ log("Failed preProcessData: " + err.message);
+ return;
+ }
+
+ // add the 'addCommas' function to the 'data' object so the HTML templates can use it
+ data.addCommas = addCommas;
+ // add the 'roundNumber' function to the 'data' object so the HTML templates can use it
+ data.roundNumber = roundNumber;
+ // add the 'getInstanceAverage' function to the 'data' object so the HTML templates can use it
+ data.getInstanceAverage = getInstanceAverage;
+
+ var addNew = false;
+ // check if we need to create the container
+ if(!$('#CIRCUIT_' + data.escapedName).length) {
+ // args for display
+ if(self.args.includeDetailIcon != undefined && self.args.includeDetailIcon) {
+ data.includeDetailIcon = true;
+ }else {
+ data.includeDetailIcon = false;
+ }
+
+ // it doesn't exist so add it
+ var html = tmpl(hystrixTemplateCircuitContainer, data);
+ // remove the loading thing first
+ $('#' + containerId + ' span.loading').remove();
+ // now create the new data and add it
+ $('#' + containerId + '').append(html);
+
+ // add the default sparkline graph
+ d3.selectAll('#graph_CIRCUIT_' + data.escapedName + ' svg').append("svg:path");
+
+ // remember this is new so we can trigger a sort after setting data
+ addNew = true;
+ }
+
+
+ // now update/insert the data
+ $('#CIRCUIT_' + data.escapedName + ' div.monitor_data').html(tmpl(hystrixTemplateCircuit, data));
+
+ var ratePerSecond = data.ratePerSecond;
+ var ratePerSecondPerHost = data.ratePerSecondPerHost;
+ var ratePerSecondPerHostDisplay = ratePerSecondPerHost;
+ var errorThenVolume = (data.errorPercentage * 100000000) + ratePerSecond;
+
+ // set the rates on the div element so it's available for sorting
+ $('#CIRCUIT_' + data.escapedName).attr('rate_value', ratePerSecond);
+ $('#CIRCUIT_' + data.escapedName).attr('error_then_volume', errorThenVolume);
+
+ // update errorPercentage color on page
+ $('#CIRCUIT_' + data.escapedName + ' a.errorPercentage').css('color', self.circuitErrorPercentageColorRange(data.errorPercentage));
+
+ updateCircle('circuit', '#CIRCUIT_' + data.escapedName + ' circle', ratePerSecondPerHostDisplay, data.errorPercentage);
+
+ if(data.graphValues) {
+ // we have a set of values to initialize with
+ updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', data.graphValues);
+ } else {
+ updateSparkline('circuit', '#CIRCUIT_' + data.escapedName + ' path', ratePerSecond);
+ }
+
+ if(addNew) {
+ // sort since we added a new circuit
+ self.sortSameAsLast();
+ }
+ }
+
+ /* round a number to X digits: num => the number to round, dec => the number of decimals */
+ /* private */ function roundNumber(num) {
+ var dec=1;
+ var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
+ var resultAsString = result.toString();
+ if(resultAsString.indexOf('.') == -1) {
+ resultAsString = resultAsString + '.0';
+ }
+ return resultAsString;
+ };
+
+
+
+
+ /* private */ function updateCircle(variablePrefix, cssTarget, rate, errorPercentage) {
+ var newXaxisForCircle = self[variablePrefix + 'CircleXaxis'](rate);
+ if(parseInt(newXaxisForCircle) > parseInt(maxXaxisForCircle)) {
+ newXaxisForCircle = maxXaxisForCircle;
+ }
+ var newYaxisForCircle = self[variablePrefix + 'CircleYaxis'](rate);
+ if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
+ newYaxisForCircle = maxYaxisForCircle;
+ }
+ var newRadiusForCircle = self[variablePrefix + 'CircleRadius'](rate);
+ if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
+ newRadiusForCircle = maxRadiusForCircle;
+ }
+
+ d3.selectAll(cssTarget)
+ .transition()
+ .duration(400)
+ .attr("cy", newYaxisForCircle)
+ .attr("cx", newXaxisForCircle)
+ .attr("r", newRadiusForCircle)
+ .style("fill", self[variablePrefix + 'ColorRange'](errorPercentage));
+ }
+
+ /* private */ function updateSparkline(variablePrefix, cssTarget, newDataPoint) {
+ var currentTimeMilliseconds = new Date().getTime();
+ var data = self[variablePrefix + cssTarget + '_data'];
+ if(typeof data == 'undefined') {
+ // else it's new
+ if(typeof newDataPoint == 'object') {
+ // we received an array of values, so initialize with it
+ data = newDataPoint;
+ } else {
+ // v: VALUE, t: TIME_IN_MILLISECONDS
+ data = [{"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds}];
+ }
+ self[variablePrefix + cssTarget + '_data'] = data;
+ } else {
+ if(typeof newDataPoint == 'object') {
+ /* if an array is passed in we'll replace the cached one */
+ data = newDataPoint;
+ } else {
+ // else we just add to the existing one
+ data.push({"v":parseFloat(newDataPoint),"t":currentTimeMilliseconds});
+ }
+ }
+
+ while(data.length > 200) { // 400 should be plenty for the 2 minutes we have the scale set to below even with a very low update latency
+ // remove data so we don't keep increasing forever
+ data.shift();
+ }
+
+ if(data.length == 1 && data[0].v == 0) {
+ //console.log("we have a single 0 so skipping");
+ // don't show if we have a single 0
+ return;
+ }
+
+ if(data.length > 1 && data[0].v == 0 && data[1].v != 0) {
+ //console.log("we have a leading 0 so removing it");
+ // get rid of a leading 0 if the following number is not a 0
+ data.shift();
+ }
+
+ var xScale = d3.time.scale().domain([new Date(currentTimeMilliseconds-(60*1000*2)), new Date(currentTimeMilliseconds)]).range([0, 140]);
+
+ var yMin = d3.min(data, function(d) { return d.v; });
+ var yMax = d3.max(data, function(d) { return d.v; });
+ var yScale = d3.scale.linear().domain([yMin, yMax]).nice().range([60, 0]); // y goes DOWN, so 60 is the "lowest"
+
+ sparkline = d3.svg.line()
+ // assign the X function to plot our line as we wish
+ .x(function(d,i) {
+ // return the X coordinate where we want to plot this datapoint based on the time
+ return xScale(new Date(d.t));
+ })
+ .y(function(d) {
+ return yScale(d.v);
+ })
+ .interpolate("basis");
+
+ d3.selectAll(cssTarget).attr("d", sparkline(data));
+ }
+
+ /* private */ function deleteCircuit(circuitName) {
+ $('#CIRCUIT_' + circuitName).remove();
+ }
+
+ };
+
+ // public methods for sorting
+ HystrixCommandMonitor.prototype.sortByVolume = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'rate_desc') {
+ direction = 'asc';
+ }
+ this.sortByVolumeInDirection(direction);
+ };
+
+ HystrixCommandMonitor.prototype.sortByVolumeInDirection = function(direction) {
+ this.sortedBy = 'rate_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
+ };
+
+ HystrixCommandMonitor.prototype.sortAlphabetically = function() {
+ var direction = "asc";
+ if(this.sortedBy == 'alph_asc') {
+ direction = 'desc';
+ }
+ this.sortAlphabeticalInDirection(direction);
+ };
+
+ HystrixCommandMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
+ this.sortedBy = 'alph_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
+ };
+
+
+ HystrixCommandMonitor.prototype.sortByError = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'error_desc') {
+ direction = 'asc';
+ }
+ this.sortByErrorInDirection(direction);
+ };
+
+ HystrixCommandMonitor.prototype.sortByErrorInDirection = function(direction) {
+ this.sortedBy = 'error_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort(".errorPercentage .value", {order: direction});
+ };
+
+ HystrixCommandMonitor.prototype.sortByErrorThenVolume = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'error_then_volume_desc') {
+ direction = 'asc';
+ }
+ this.sortByErrorThenVolumeInDirection(direction);
+ };
+
+ HystrixCommandMonitor.prototype.sortByErrorThenVolumeInDirection = function(direction) {
+ this.sortedBy = 'error_then_volume_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'error_then_volume'});
+ };
+
+ HystrixCommandMonitor.prototype.sortByLatency90 = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'lat90_desc') {
+ direction = 'asc';
+ }
+ this.sortedBy = 'lat90_' + direction;
+ this.sortByMetricInDirection(direction, ".latency90 .value");
+ };
+
+ HystrixCommandMonitor.prototype.sortByLatency99 = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'lat99_desc') {
+ direction = 'asc';
+ }
+ this.sortedBy = 'lat99_' + direction;
+ this.sortByMetricInDirection(direction, ".latency99 .value");
+ };
+
+ HystrixCommandMonitor.prototype.sortByLatency995 = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'lat995_desc') {
+ direction = 'asc';
+ }
+ this.sortedBy = 'lat995_' + direction;
+ this.sortByMetricInDirection(direction, ".latency995 .value");
+ };
+
+ HystrixCommandMonitor.prototype.sortByLatencyMean = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'latMean_desc') {
+ direction = 'asc';
+ }
+ this.sortedBy = 'latMean_' + direction;
+ this.sortByMetricInDirection(direction, ".latencyMean .value");
+ };
+
+ HystrixCommandMonitor.prototype.sortByLatencyMedian = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'latMedian_desc') {
+ direction = 'asc';
+ }
+ this.sortedBy = 'latMedian_' + direction;
+ this.sortByMetricInDirection(direction, ".latencyMedian .value");
+ };
+
+ HystrixCommandMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
+ $('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
+ };
+
+ // this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
+ HystrixCommandMonitor.prototype.sortSameAsLast = function() {
+ if(this.sortedBy == 'alph_asc') {
+ this.sortAlphabeticalInDirection('asc');
+ } else if(this.sortedBy == 'alph_desc') {
+ this.sortAlphabeticalInDirection('desc');
+ } else if(this.sortedBy == 'rate_asc') {
+ this.sortByVolumeInDirection('asc');
+ } else if(this.sortedBy == 'rate_desc') {
+ this.sortByVolumeInDirection('desc');
+ } else if(this.sortedBy == 'error_asc') {
+ this.sortByErrorInDirection('asc');
+ } else if(this.sortedBy == 'error_desc') {
+ this.sortByErrorInDirection('desc');
+ } else if(this.sortedBy == 'error_then_volume_asc') {
+ this.sortByErrorThenVolumeInDirection('asc');
+ } else if(this.sortedBy == 'error_then_volume_desc') {
+ this.sortByErrorThenVolumeInDirection('desc');
+ } else if(this.sortedBy == 'lat90_asc') {
+ this.sortByMetricInDirection('asc', '.latency90 .value');
+ } else if(this.sortedBy == 'lat90_desc') {
+ this.sortByMetricInDirection('desc', '.latency90 .value');
+ } else if(this.sortedBy == 'lat99_asc') {
+ this.sortByMetricInDirection('asc', '.latency99 .value');
+ } else if(this.sortedBy == 'lat99_desc') {
+ this.sortByMetricInDirection('desc', '.latency99 .value');
+ } else if(this.sortedBy == 'lat995_asc') {
+ this.sortByMetricInDirection('asc', '.latency995 .value');
+ } else if(this.sortedBy == 'lat995_desc') {
+ this.sortByMetricInDirection('desc', '.latency995 .value');
+ } else if(this.sortedBy == 'latMean_asc') {
+ this.sortByMetricInDirection('asc', '.latencyMean .value');
+ } else if(this.sortedBy == 'latMean_desc') {
+ this.sortByMetricInDirection('desc', '.latencyMean .value');
+ } else if(this.sortedBy == 'latMedian_asc') {
+ this.sortByMetricInDirection('asc', '.latencyMedian .value');
+ } else if(this.sortedBy == 'latMedian_desc') {
+ this.sortByMetricInDirection('desc', '.latencyMedian .value');
+ }
+ };
+
+ // default sort type and direction
+ this.sortedBy = 'alph_asc';
+
+
+ // a temporary home for the logger until we become more sophisticated
+ function log(message) {
+ console.log(message);
+ };
+
+ function addCommas(nStr){
+ nStr += '';
+ if(nStr.length <=3) {
+ return nStr; //shortcut if we don't need commas
+ }
+ x = nStr.split('.');
+ x1 = x[0];
+ x2 = x.length > 1 ? '.' + x[1] : '';
+ var rgx = /(\d+)(\d{3})/;
+ while (rgx.test(x1)) {
+ x1 = x1.replace(rgx, '$1' + ',' + '$2');
+ }
+ return x1 + x2;
+ }
+})(window);
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png
new file mode 100644
index 00000000..2833fe2c
Binary files /dev/null and b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon-20.png differ
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon.png b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon.png
new file mode 100644
index 00000000..e007dd39
Binary files /dev/null and b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/magnifying-glass-icon.png differ
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html
new file mode 100644
index 00000000..e50a3524
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuit.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ <% if(propertyValue_circuitBreakerForceClosed) { %>
+ [ Forced Closed ]
+ <% } %>
+ <% if(propertyValue_circuitBreakerForceOpen) { %>
+ Circuit Forced Open
+ <% } else { %>
+ <% if(isCircuitBreakerOpen == reportingHosts) { %>
+ Circuit Open
+ <% } else if(isCircuitBreakerOpen == 0) { %>
+ Circuit Closed
+ <% } else {
+ /* We have some circuits that are open */
+ %>
+ Circuit <%= isCircuitBreakerOpen.replace("true", "Open").replace("false", "Closed") %>)
+ <% } %>
+ <% } %>
+
+
+
+
+
+ <% if(typeof reportingHosts != 'undefined') { %>
+
+
<%= reportingHosts %>
+ <% } else { %>
+
+
Single
+ <% } %>
+
+
<%= getInstanceAverage(latencyExecute['90'], reportingHosts, false) %> ms
+
+
+
+
<%= getInstanceAverage(latencyExecute['50'], reportingHosts, false) %> ms
+
+
<%= getInstanceAverage(latencyExecute['99'], reportingHosts, false) %> ms
+
+
+
+
<%= latencyExecute_mean %> ms
+
+
<%= getInstanceAverage(latencyExecute['99.5'], reportingHosts, false) %> ms
+
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html
new file mode 100644
index 00000000..4d19dbe0
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitContainer.html
@@ -0,0 +1,40 @@
+
+ <%
+ var displayName = name;
+ var toolTip = "";
+ if(displayName.length > 32) {
+ displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
+ toolTip = "title=\"" + name + "\"";
+ }
+ %>
+
+
+
+ <% if(includeDetailIcon) { %>
+
style="padding-right:16px">
+ <%= displayName %>
+
+
+ <% } else { %>
+
><%= displayName %>
+ <% } %>
+
+
+
+
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html
new file mode 100644
index 00000000..5b8c0fae
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixCommand/templates/hystrixCircuitProperties.html
@@ -0,0 +1,6 @@
+
+
+
<%= sla_medianLastMinute %> ms
+
+
<%= sla_percentile99LastMinute %> ms
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css
new file mode 100644
index 00000000..e82ea5cc
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.css
@@ -0,0 +1,141 @@
+.dependencyThreadPools .spacer {
+ width: 100%;
+ margin: 0 auto;
+ padding-top:4px;
+ clear:both;
+}
+
+
+.dependencyThreadPools .last {
+ margin-right: 0px;
+}
+
+.dependencyThreadPools span.loading {
+ display: block;
+ padding-top: 6%;
+ padding-bottom: 6%;
+ color: gray;
+ text-align: center;
+}
+
+.dependencyThreadPools span.loading.failed {
+ color: red;
+}
+
+
+.dependencyThreadPools div.monitor {
+ float: left;
+ margin-right:5px; /* these are tweaked to look good on desktop and iPad portrait, and fit things densely */
+ margin-top:5px;
+}
+
+.dependencyThreadPools div.monitor p.name {
+ font-weight:bold;
+ font-size: 10pt;
+ text-align: right;
+ padding-bottom: 5px;
+}
+
+.dependencyThreadPools div.monitor_data {
+ margin: 0 auto;
+}
+
+.dependencyThreadPools span.smaller {
+ font-size: 8pt;
+ color: grey;
+}
+
+
+.dependencyThreadPools div.tableRow {
+ width:100%;
+ white-space: nowrap;
+ font-size: 8pt;
+ margin: 0 auto;
+ clear:both;
+}
+
+.dependencyThreadPools div.tableRow .cell {
+ float:left;
+}
+
+.dependencyThreadPools div.tableRow .header {
+ text-align:right;
+ padding-right:5px;
+}
+
+.dependencyThreadPools div.tableRow .header.left {
+ width:85px;
+}
+
+.dependencyThreadPools div.tableRow .header.right {
+ width:75px;
+}
+
+.dependencyThreadPools div.tableRow .data {
+ font-weight: bold;
+ text-align:right;
+}
+
+.dependencyThreadPools div.tableRow .data.left {
+ width:30px;
+}
+
+.dependencyThreadPools div.tableRow .data.right {
+ width:45px;
+}
+
+.dependencyThreadPools div.monitor {
+ width: 245px; /* we want a fixed width instead of percentage as I want the boxes to be a set size and then fill in as many as can fit in each row ... this allows 3 columns on an iPad */
+ height: 110px;
+}
+
+
+
+
+
+/* override the HREF when we have specified it as a tooltip to not act like a link */
+.dependencyThreadPools div.monitor_data a.tooltip {
+ text-decoration: none;
+ cursor: default;
+}
+
+.dependencyThreadPools div.monitor_data a.rate {
+ font-weight:bold;
+ color: black;
+ font-size: 11pt;
+}
+
+.dependencyThreadPools div.rate {
+ padding-top: 1px;
+ clear:both;
+ text-align:right;
+}
+
+.dependencyThreadPools span.rate_value {
+ font-weight:bold;
+}
+
+
+
+
+
+
+
+.dependencyThreadPools div.monitor div.chart {
+}
+
+.dependencyThreadPools div.monitor div.chart svg {
+}
+
+.dependencyThreadPools div.monitor div.chart svg text {
+ fill: white;
+}
+
+.dependencyThreadPools #hidden {
+ width:1px;
+ height:1px;
+ background: lightgrey;
+ display: none;
+}
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js
new file mode 100644
index 00000000..a5003bc4
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/hystrixThreadPool.js
@@ -0,0 +1,346 @@
+
+(function(window) {
+
+ // cache the templates we use on this page as global variables (asynchronously)
+ jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPool.html"), function(data) {
+ htmlTemplate = data;
+ });
+ jQuery.get(getRelativePath("../components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html"), function(data) {
+ htmlTemplateContainer = data;
+ });
+
+ function getRelativePath(path) {
+ var p = location.pathname.slice(0, location.pathname.lastIndexOf("/")+1);
+ return p + path;
+ }
+
+ /**
+ * Object containing functions for displaying and updating the UI with streaming data.
+ *
+ * Publish this externally as "HystrixThreadPoolMonitor"
+ */
+ window.HystrixThreadPoolMonitor = function(containerId) {
+
+ var self = this; // keep scope under control
+
+ this.containerId = containerId;
+
+ /**
+ * Initialization on construction
+ */
+ // intialize various variables we use for visualization
+ var maxXaxisForCircle="40%";
+ var maxYaxisForCircle="40%";
+ var maxRadiusForCircle="125";
+ var maxDomain = 2000;
+
+ self.circleRadius = d3.scale.pow().exponent(0.5).domain([0, maxDomain]).range(["5", maxRadiusForCircle]); // requests per second per host
+ self.circleYaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxXaxisForCircle]);
+ self.circleXaxis = d3.scale.linear().domain([0, maxDomain]).range(["30%", maxYaxisForCircle]);
+ self.colorRange = d3.scale.linear().domain([10, 25, 40, 50]).range(["green", "#FFCC00", "#FF9900", "red"]);
+ self.errorPercentageColorRange = d3.scale.linear().domain([0, 10, 35, 50]).range(["grey", "black", "#FF9900", "red"]);
+
+ /**
+ * We want to keep sorting in the background since data values are always changing, so this will re-sort every X milliseconds
+ * to maintain whatever sort the user (or default) has chosen.
+ *
+ * In other words, sorting only for adds/deletes is not sufficient as all but alphabetical sort are dynamically changing.
+ */
+ setInterval(function() {
+ // sort since we have added a new one
+ self.sortSameAsLast();
+ }, 1000)
+
+ /**
+ * END of Initialization on construction
+ */
+
+ /**
+ * Event listener to handle new messages from EventSource as streamed from the server.
+ */
+ /* public */ self.eventSourceMessageListener = function(e) {
+ var data = JSON.parse(e.data);
+ if(data) {
+ // check for reportingHosts (if not there, set it to 1 for singleHost vs cluster)
+ if(!data.reportingHosts) {
+ data.reportingHosts = 1;
+ }
+
+ if(data && data.type == 'HystrixThreadPool') {
+ if (data.deleteData == 'true') {
+ deleteThreadPool(data.escapedName);
+ } else {
+ displayThreadPool(data);
+ }
+ }
+ }
+ }
+
+ /**
+ * Pre process the data before displying in the UI.
+ * e.g Get Averages from sums, do rate calculation etc.
+ */
+ function preProcessData(data) {
+ validateData(data);
+ // escape string used in jQuery & d3 selectors
+ data.escapedName = data.name.replace(/([ !"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g,'\\$1');
+ // do math
+ converAllAvg(data);
+ calcRatePerSecond(data);
+ }
+
+ function converAllAvg(data) {
+ convertAvg(data, "propertyValue_queueSizeRejectionThreshold", false);
+
+ // the following will break when it becomes a compound string if the property is dynamically changed
+ convertAvg(data, "propertyValue_metricsRollingStatisticalWindowInMilliseconds", false);
+ }
+
+ function convertAvg(data, key, decimal) {
+ if (decimal) {
+ data[key] = roundNumber(data[key]/data["reportingHosts"]);
+ } else {
+ data[key] = Math.floor(data[key]/data["reportingHosts"]);
+ }
+ }
+
+ function calcRatePerSecond(data) {
+ var numberSeconds = data["propertyValue_metricsRollingStatisticalWindowInMilliseconds"] / 1000;
+
+ var totalThreadsExecuted = data["rollingCountThreadsExecuted"];
+ if (totalThreadsExecuted < 0) {
+ totalThreadsExecuted = 0;
+ }
+ data["ratePerSecond"] = roundNumber(totalThreadsExecuted / numberSeconds);
+ data["ratePerSecondPerHost"] = roundNumber(totalThreadsExecuted / numberSeconds / data["reportingHosts"]);
+ }
+
+ function validateData(data) {
+
+ assertNotNull(data,"type");
+ assertNotNull(data,"name");
+ // assertNotNull(data,"currentTime");
+ assertNotNull(data,"currentActiveCount");
+ assertNotNull(data,"currentCompletedTaskCount");
+ assertNotNull(data,"currentCorePoolSize");
+ assertNotNull(data,"currentLargestPoolSize");
+ assertNotNull(data,"currentMaximumPoolSize");
+ assertNotNull(data,"currentPoolSize");
+ assertNotNull(data,"currentQueueSize");
+ assertNotNull(data,"currentTaskCount");
+ assertNotNull(data,"rollingCountThreadsExecuted");
+ assertNotNull(data,"rollingMaxActiveThreads");
+ assertNotNull(data,"reportingHosts");
+
+ assertNotNull(data,"propertyValue_queueSizeRejectionThreshold");
+ assertNotNull(data,"propertyValue_metricsRollingStatisticalWindowInMilliseconds");
+ }
+
+ function assertNotNull(data, key) {
+ if(data[key] == undefined) {
+ if (key == "dependencyOwner") {
+ data["dependencyOwner"] = data.name;
+ } else {
+ throw new Error("Key Missing: " + key + " for " + data.name)
+ }
+ }
+ }
+
+ /**
+ * Method to display the THREAD_POOL data
+ *
+ * @param data
+ */
+ /* private */ function displayThreadPool(data) {
+
+ try {
+ preProcessData(data);
+ } catch (err) {
+ log("Failed preProcessData: " + err.message);
+ return;
+ }
+
+ // add the 'addCommas' function to the 'data' object so the HTML templates can use it
+ data.addCommas = addCommas;
+ // add the 'roundNumber' function to the 'data' object so the HTML templates can use it
+ data.roundNumber = roundNumber;
+
+ var addNew = false;
+ // check if we need to create the container
+ if(!$('#THREAD_POOL_' + data.escapedName).length) {
+ // it doesn't exist so add it
+ var html = tmpl(htmlTemplateContainer, data);
+ // remove the loading thing first
+ $('#' + containerId + ' span.loading').remove();
+ // get the current last column and remove the 'last' class from it
+ $('#' + containerId + ' div.last').removeClass('last');
+ // now create the new data and add it
+ $('#' + containerId + '').append(html);
+ // add the 'last' class to the column we just added
+ $('#' + containerId + ' div.monitor').last().addClass('last');
+
+ // add the default sparkline graph
+ d3.selectAll('#graph_THREAD_POOL_' + data.escapedName + ' svg').append("svg:path");
+
+ // remember this is new so we can trigger a sort after setting data
+ addNew = true;
+ }
+
+ // set the rate on the div element so it's available for sorting
+ $('#THREAD_POOL_' + data.escapedName).attr('rate_value', data.ratePerSecondPerHost);
+
+ // now update/insert the data
+ $('#THREAD_POOL_' + data.escapedName + ' div.monitor_data').html(tmpl(htmlTemplate, data));
+
+ // set variables for circle visualization
+ var rate = data.ratePerSecondPerHost;
+ // we will treat each item in queue as 1% of an error visualization
+ // ie. 5 threads in queue per instance == 5% error percentage
+ var errorPercentage = data.currentQueueSize / data.reportingHosts;
+
+ updateCircle('#THREAD_POOL_' + data.escapedName + ' circle', rate, errorPercentage);
+
+ if(addNew) {
+ // sort since we added a new circuit
+ self.sortSameAsLast();
+ }
+ }
+
+ /* round a number to X digits: num => the number to round, dec => the number of decimals */
+ /* private */ function roundNumber(num) {
+ var dec=1; // we are hardcoding to support only 1 decimal so that our padding logic at the end is simple
+ var result = Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
+ var resultAsString = result.toString();
+ if(resultAsString.indexOf('.') == -1) {
+ resultAsString = resultAsString + '.';
+ for(var i=0; i parseInt(maxXaxisForCircle)) {
+ newXaxisForCircle = maxXaxisForCircle;
+ }
+ var newYaxisForCircle = self.circleYaxis(rate);
+ if(parseInt(newYaxisForCircle) > parseInt(maxYaxisForCircle)) {
+ newYaxisForCircle = maxYaxisForCircle;
+ }
+ var newRadiusForCircle = self.circleRadius(rate);
+ if(parseInt(newRadiusForCircle) > parseInt(maxRadiusForCircle)) {
+ newRadiusForCircle = maxRadiusForCircle;
+ }
+
+ d3.selectAll(cssTarget)
+ .transition()
+ .duration(400)
+ .attr("cy", newYaxisForCircle)
+ .attr("cx", newXaxisForCircle)
+ .attr("r", newRadiusForCircle)
+ .style("fill", self.colorRange(errorPercentage));
+ }
+
+ /* private */ function deleteThreadPool(poolName) {
+ $('#THREAD_POOL_' + poolName).remove();
+ }
+
+ }
+
+ // public methods for sorting
+ HystrixThreadPoolMonitor.prototype.sortByVolume = function() {
+ var direction = "desc";
+ if(this.sortedBy == 'rate_desc') {
+ direction = 'asc';
+ }
+ this.sortByVolumeInDirection(direction);
+ }
+
+ HystrixThreadPoolMonitor.prototype.sortByVolumeInDirection = function(direction) {
+ this.sortedBy = 'rate_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort({order: direction, attr: 'rate_value'});
+ }
+
+ HystrixThreadPoolMonitor.prototype.sortAlphabetically = function() {
+ var direction = "asc";
+ if(this.sortedBy == 'alph_asc') {
+ direction = 'desc';
+ }
+ this.sortAlphabeticalInDirection(direction);
+ }
+
+ HystrixThreadPoolMonitor.prototype.sortAlphabeticalInDirection = function(direction) {
+ this.sortedBy = 'alph_' + direction;
+ $('#' + this.containerId + ' div.monitor').tsort("p.name", {order: direction});
+ }
+
+ HystrixThreadPoolMonitor.prototype.sortByMetricInDirection = function(direction, metric) {
+ $('#' + this.containerId + ' div.monitor').tsort(metric, {order: direction});
+ }
+
+ // this method is for when new divs are added to cause the elements to be sorted to whatever the user last chose
+ HystrixThreadPoolMonitor.prototype.sortSameAsLast = function() {
+ if(this.sortedBy == 'alph_asc') {
+ this.sortAlphabeticalInDirection('asc');
+ } else if(this.sortedBy == 'alph_desc') {
+ this.sortAlphabeticalInDirection('desc');
+ } else if(this.sortedBy == 'rate_asc') {
+ this.sortByVolumeInDirection('asc');
+ } else if(this.sortedBy == 'rate_desc') {
+ this.sortByVolumeInDirection('desc');
+ } else if(this.sortedBy == 'error_asc') {
+ this.sortByErrorInDirection('asc');
+ } else if(this.sortedBy == 'error_desc') {
+ this.sortByErrorInDirection('desc');
+ } else if(this.sortedBy == 'lat90_asc') {
+ this.sortByMetricInDirection('asc', 'p90');
+ } else if(this.sortedBy == 'lat90_desc') {
+ this.sortByMetricInDirection('desc', 'p90');
+ } else if(this.sortedBy == 'lat99_asc') {
+ this.sortByMetricInDirection('asc', 'p99');
+ } else if(this.sortedBy == 'lat99_desc') {
+ this.sortByMetricInDirection('desc', 'p99');
+ } else if(this.sortedBy == 'lat995_asc') {
+ this.sortByMetricInDirection('asc', 'p995');
+ } else if(this.sortedBy == 'lat995_desc') {
+ this.sortByMetricInDirection('desc', 'p995');
+ } else if(this.sortedBy == 'latMean_asc') {
+ this.sortByMetricInDirection('asc', 'pMean');
+ } else if(this.sortedBy == 'latMean_desc') {
+ this.sortByMetricInDirection('desc', 'pMean');
+ } else if(this.sortedBy == 'latMedian_asc') {
+ this.sortByMetricInDirection('asc', 'pMedian');
+ } else if(this.sortedBy == 'latMedian_desc') {
+ this.sortByMetricInDirection('desc', 'pMedian');
+ }
+ }
+
+ // default sort type and direction
+ this.sortedBy = 'alph_asc';
+
+
+ // a temporary home for the logger until we become more sophisticated
+ function log(message) {
+ console.log(message);
+ };
+
+ function addCommas(nStr){
+ nStr += '';
+ if(nStr.length <=3) {
+ return nStr; //shortcut if we don't need commas
+ }
+ x = nStr.split('.');
+ x1 = x[0];
+ x2 = x.length > 1 ? '.' + x[1] : '';
+ var rgx = /(\d+)(\d{3})/;
+ while (rgx.test(x1)) {
+ x1 = x1.replace(rgx, '$1' + ',' + '$2');
+ }
+ return x1 + x2;
+ }
+})(window)
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html
new file mode 100644
index 00000000..1e653ccb
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPool.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
<%= currentActiveCount%>
+
+
+
<%= addCommas(rollingMaxActiveThreads)%>
+
+
+
+
+
<%= currentQueueSize %>
+
+
<%= addCommas(rollingCountThreadsExecuted)%>
+
+
+
+
<%= currentPoolSize %>
+
+
<%= propertyValue_queueSizeRejectionThreshold %>
+
+
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html
new file mode 100644
index 00000000..6af0ef2c
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/components/hystrixThreadPool/templates/hystrixThreadPoolContainer.html
@@ -0,0 +1,34 @@
+
+
+ <%
+ var displayName = name;
+ var toolTip = "";
+ if(displayName.length > 32) {
+ displayName = displayName.substring(0,4) + "..." + displayName.substring(displayName.length-20, displayName.length);
+ toolTip = "title=\"" + name + "\"";
+ }
+ %>
+
+
+
+
+
+
+
+
+
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css
new file mode 100644
index 00000000..46898b11
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/global.css
@@ -0,0 +1,71 @@
+@IMPORT url("resets.css");
+
+body {
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+
+img, object, embed {
+ max-width: 100%;
+}
+
+img {
+ height: auto;
+}
+
+
+#header {
+ background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 0%;
+ height: 65px;
+ margin-bottom: 5px;
+}
+
+#header h2 {
+ float:left;
+ color: black;
+ position:relative;
+ padding-left: 20px;
+ top: 26px;
+ font-size: 20px;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+
+#header .header_nav {
+ position:absolute;
+ top:48px;
+ right:15px;
+}
+
+#header .header_links {
+ float:left;
+ color: lightgray;
+ font-size: 18px;
+ top: 3px;
+ padding-left: 10px;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+
+#header .header_links a {
+ color: white;
+}
+
+#header .header_clusters {
+ float:left;
+ position:relative;
+ padding-left: 10px;
+ top: -1px;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+
+
+@media screen and (min-width: 1500px) {
+
+ #header .header_nav {
+ top:13px;
+ right:130px;
+ }
+
+ #header {
+ background: #FFFFFF url(http://raw.github.com/wiki/Netflix/Hystrix/images/hystrix-logo-tagline-tiny.png) no-repeat scroll 99% 50%;
+ height: 65px;
+ }
+}
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css
new file mode 100644
index 00000000..4d137c7e
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/resets.css
@@ -0,0 +1,102 @@
+/*
+html5doctor.com Reset Stylesheet
+v1.6.1
+Last Updated: 2010-09-17
+Author: Richard Clark - http://richclarkdesign.com
+Twitter: @rich_clark
+*/
+
+html, body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+abbr, address, cite, code,
+del, dfn, em, img, ins, kbd, q, samp,
+small, strong, sub, sup, var,
+b, i,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section, summary,
+time, mark, audio, video {
+ margin:0;
+ padding:0;
+ border:0;
+ outline:0;
+ font-size:100%;
+ vertical-align:baseline;
+ background:transparent;
+}
+
+body {
+ line-height:1;
+}
+
+article,aside,details,figcaption,figure,
+footer,header,hgroup,menu,nav,section {
+ display:block;
+}
+
+nav ul {
+ list-style:none;
+}
+
+blockquote, q {
+ quotes:none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content:'';
+ content:none;
+}
+
+a {
+ margin:0;
+ padding:0;
+ font-size:100%;
+ vertical-align:baseline;
+ background:transparent;
+}
+
+/* change colours to suit your needs */
+ins {
+ background-color:#ff9;
+ color:#000;
+ text-decoration:none;
+}
+
+/* change colours to suit your needs */
+mark {
+ background-color:#ff9;
+ color:#000;
+ font-style:italic;
+ font-weight:bold;
+}
+
+del {
+ text-decoration: line-through;
+}
+
+abbr[title], dfn[title] {
+ border-bottom:1px dotted;
+ cursor:help;
+}
+
+table {
+ border-collapse:collapse;
+ border-spacing:0;
+}
+
+/* change border colour to suit your needs */
+hr {
+ display:block;
+ height:1px;
+ border:0;
+ border-top:1px solid #cccccc;
+ margin:1em 0;
+ padding:0;
+}
+
+input, select {
+ vertical-align:middle;
+}
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css
new file mode 100644
index 00000000..052ee488
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/1236_grid.css
@@ -0,0 +1,21 @@
+/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
+ * http://simplegrid.info
+ * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
+ * License: http://creativecommons.org/licenses/MIT/ */
+
+/* Containers */
+body { font-size: 1.125em; }
+.grid{ width:1206px; }
+
+/* 6-Col Grid Sizes */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:176px; } /* Sixths */
+.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:382px; } /* Thirds */
+.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:794px; } /* Two-Thirds */
+.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:1000px; } /* Five-Sixths */
+
+/* 4-Col Grid Sizes */
+.slot-6,.slot-7,.slot-8,.slot-9{ width:279px; } /* Quarters */
+.slot-6-7-8,.slot-7-8-9{ width:897px; } /* Three-Quarters */
+
+/* 6-Col/4-Col Shared Grid Sizes */
+.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:588px; } /* Halves */
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css
new file mode 100644
index 00000000..0ef2432c
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/720_grid.css
@@ -0,0 +1,33 @@
+/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
+ * http://simplegrid.info
+ * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
+ * License: http://creativecommons.org/licenses/MIT/ */
+
+/* Containers */
+body { font-size: 0.875em; padding: 0; }
+.grid{ margin:0 auto; padding: 0 10px; width:700px; }
+.row{ clear:left; }
+
+/* Slots Setup */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:20px; }
+
+/* 6-Col Grid Sizes */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:100px; } /* Sixths */
+.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:220px; } /* Thirds */
+.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:460px; } /* Two-Thirds */
+.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:580px; } /* Five-Sixths */
+
+/* 4-Col Grid Sizes */
+.slot-6,.slot-7,.slot-8,.slot-9{ width:160px; } /* Quarters */
+.slot-6-7-8,.slot-7-8-9{ width:520px; } /* Three-Quarters */
+
+/* 6-Col/4-Col Shared Grid Sizes */
+.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:340px; } /* Halves */
+.slot-0-1-2-3-4-5, .slot-6-7-8-9{ width: 100%; } /* Full-Width */
+
+/* Zeroing Out Leftmost Slot Margins */
+.slot-0,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-6,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-1 .slot-1,.slot-1-2 .slot-1,.slot-1-2 .slot-1-2,.slot-1-2-3 .slot-1,.slot-1-2-3 .slot-1-2,.slot-1-2-3 .slot-1-2-3,.slot-1-2-3-4 .slot-1,.slot-1-2-3-4 .slot-1-2,.slot-1-2-3-4 .slot-1-2-3,.slot-1-2-3-4 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1,.slot-1-2-3-4-5 .slot-1-2,.slot-1-2-3-4-5 .slot-1-2-3,.slot-1-2-3-4-5 .slot-1-2-3-4,.slot-1-2-3-4-5 .slot-1-2-3-4-5,.slot-2 .slot-2,.slot-2-3 .slot-2,.slot-2-3 .slot-2-3,.slot-2-3-4 .slot-2,.slot-2-3-4 .slot-2-3,.slot-2-3-4 .slot-2-3-4,.slot-2-3-4-5 .slot-2,.slot-2-3-4-5 .slot-2-3,.slot-2-3-4-5 .slot-2-3-4,.slot-2-3-4-5 .slot-2-3-4-5,.slot-3 .slot-3,.slot-3-4 .slot-3,.slot-3-4 .slot-3-4,.slot-3-4-5 .slot-3,.slot-3-4-5 .slot-3-4,.slot-3-4-5 .slot-3-4-5,.slot-4 .slot-4,.slot-4-5 .slot-4,.slot-4-5 .slot-4-5,.slot-5 .slot-5,.slot-7 .slot-7,.slot-7-8 .slot-7,.slot-7-8 .slot-7-8,.slot-7-8-9 .slot-7,.slot-7-8-9 .slot-7-8,.slot-7-8-9 .slot-7-8-9,.slot-8 .slot-8,.slot-8-9 .slot-8,.slot-8-9 .slot-8-9{ margin-left:0 !important; } /* Important is to avoid repeating this in larger screen css files */
+
+/* Row Clearfix */
+.row:after{ visibility:hidden; display:block; font-size:0; content:" "; clear:both; height:0; }
+.row{ zoom:1; }
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css
new file mode 100644
index 00000000..83a59788
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/986_grid.css
@@ -0,0 +1,24 @@
+/* SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
+ * http://simplegrid.info
+ * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
+ * License: http://creativecommons.org/licenses/MIT/ */
+
+/* Containers */
+body { font-size: 100%; }
+.grid{ width:966px; }
+
+/* Slots Setup */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:30px; }
+
+/* 6-Col Grid Sizes */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:136px; } /* Sixths */
+.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:302px; } /* Thirds */
+.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:634px; } /* Two-Thirds */
+.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:800px; } /* Five-Sixths */
+
+/* 4-Col Grid Sizes */
+.slot-6,.slot-7,.slot-8,.slot-9{ width:219px; } /* Quarters */
+.slot-6-7-8,.slot-7-8-9{ width:717px; } /* Three-Quarters */
+
+/* 6-Col/4-Col Shared Grid Sizes */
+.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:468px; } /* Halves */
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt
new file mode 100644
index 00000000..e942914a
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Crowd Favorite, Ltd.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt
new file mode 100644
index 00000000..15cfc8c3
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/README.txt
@@ -0,0 +1 @@
+http://simplegrid.info/
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css
new file mode 100644
index 00000000..13cb4b2d
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/css/simplegrid/percentage_grid.css
@@ -0,0 +1,27 @@
+/* Extension of SimpleGrid by benjchristensen to allow percentage based sizing on very large displays
+ *
+ * SimpleGrid - a fork of CSSGrid by Crowd Favorite (https://github.com/crowdfavorite/css-grid)
+ * http://simplegrid.info
+ * by Conor Muirhead (http://conor.cc) of Early LLC (http://earlymade.com)
+ * License: http://creativecommons.org/licenses/MIT/ */
+
+/* Containers */
+body { font-size: 1.125em; }
+.grid{ width:100%; }
+
+/* Slots Setup */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5,.slot-0-1,.slot-0-1-2,.slot-0-1-2-3,.slot-0-1-2-3-4,.slot-0-1-2-3-4-5,.slot-1-2,.slot-1-2-3,.slot-1-2-3-4,.slot-1-2-3-4-5,.slot-2-3,.slot-2-3-4,.slot-2-3-4-5,.slot-3-4,.slot-3-4-5,.slot-4-5,.slot-6,.slot-7,.slot-8,.slot-9,.slot-6-7,.slot-6-7-8,.slot-6-7-8-9,.slot-7-8,.slot-7-8-9,.slot-8-9{ display:inline; float:left; margin-left:0px; }
+
+
+/* 6-Col Grid Sizes */
+.slot-0,.slot-1,.slot-2,.slot-3,.slot-4,.slot-5{ width:16.6%; } /* Sixths */
+.slot-0-1,.slot-1-2,.slot-2-3,.slot-3-4,.slot-4-5{ width:33.3%; } /* Thirds */
+.slot-0-1-2-3,.slot-1-2-3-4,.slot-2-3-4-5{ width:66.6%; } /* Two-Thirds */
+.slot-0-1-2-3-4,.slot-1-2-3-4-5{ width:83.3%; } /* Five-Sixths */
+
+/* 4-Col Grid Sizes */
+.slot-6,.slot-7,.slot-8,.slot-9{ width:25%; } /* Quarters */
+.slot-6-7-8,.slot-7-8-9{ width:75%; } /* Three-Quarters */
+
+/* 6-Col/4-Col Shared Grid Sizes */
+.slot-0-1-2,.slot-1-2-3,.slot-2-3-4,.slot-3-4-5, .slot-6-7,.slot-7-8,.slot-8-9{ width:50%; } /* Halves */
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html
new file mode 100644
index 00000000..1c0bc56b
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/index.html
@@ -0,0 +1,56 @@
+
+
+
+
+Hystrix Dashboard
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js
new file mode 100644
index 00000000..40d62eb5
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/jquery.tinysort.min.js
@@ -0,0 +1,12 @@
+/*
+* jQuery TinySort - A plugin to sort child nodes by (sub) contents or attributes.
+*
+* Version: 1.0.5
+*
+* Copyright (c) 2008-2011 Ron Valstar http://www.sjeiti.com/
+*
+* Dual licensed under the MIT and GPL licenses:
+* http://www.opensource.org/licenses/mit-license.php
+* http://www.gnu.org/licenses/gpl.html
+*/
+(function(b){b.tinysort={id:"TinySort",version:"1.0.5",copyright:"Copyright (c) 2008-2011 Ron Valstar",uri:"http://tinysort.sjeiti.com/",defaults:{order:"asc",attr:"",place:"start",returns:false,useVal:false}};b.fn.extend({tinysort:function(h,j){if(h&&typeof(h)!="string"){j=h;h=null}var e=b.extend({},b.tinysort.defaults,j);var p={};this.each(function(t){var v=(!h||h=="")?b(this):b(this).find(h);var u=e.order=="rand"?""+Math.random():(e.attr==""?(e.useVal?v.val():v.text()):v.attr(e.attr));var s=b(this).parent();if(!p[s]){p[s]={s:[],n:[]}}if(v.length>0){p[s].s.push({s:u,e:b(this),n:t})}else{p[s].n.push({e:b(this),n:t})}});for(var g in p){var d=p[g];d.s.sort(function k(t,s){var i=t.s.toLowerCase?t.s.toLowerCase():t.s;var u=s.s.toLowerCase?s.s.toLowerCase():s.s;if(c(t.s)&&c(s.s)){i=parseFloat(t.s);u=parseFloat(s.s)}return(e.order=="asc"?1:-1)*(iu?1:0))})}var m=[];for(var g in p){var d=p[g];var n=[];var f=b(this).length;switch(e.place){case"first":b.each(d.s,function(s,t){f=Math.min(f,t.n)});break;case"org":b.each(d.s,function(s,t){n.push(t.n)});break;case"end":f=d.n.length;break;default:f=0}var q=[0,0];for(var l=0;l=f&&l0?d[1]:false}function a(e,f){var d=false;b.each(e,function(h,g){if(!d){d=g==f}});return d}b.fn.TinySort=b.fn.Tinysort=b.fn.tsort=b.fn.tinysort})(jQuery);
\ No newline at end of file
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js
new file mode 100644
index 00000000..d7098f78
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/js/tmpl.js
@@ -0,0 +1,43 @@
+
+//Simple JavaScript Templating
+//John Resig - http://ejohn.org/ - MIT Licensed
+// http://ejohn.org/blog/javascript-micro-templating/
+(function(window, undefined) {
+ var cache = {};
+
+ window.tmpl = function tmpl(str, data) {
+ try {
+ // Figure out if we're getting a template, or if we need to
+ // load the template - and be sure to cache the result.
+ var fn = !/\W/.test(str) ?
+ cache[str] = cache[str] ||
+ tmpl(document.getElementById(str).innerHTML) :
+
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ new Function("obj",
+ "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){p.push('" +
+
+ // Convert the template into pure JavaScript
+ str
+ .replace(/[\r\t\n]/g, " ")
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, "',$1,'")
+ .split("\t").join("');")
+ .split("%>").join("p.push('")
+ .split("\r").join("\\'")
+ + "');}return p.join('');");
+
+ //console.log(fn);
+
+ // Provide some basic currying to the user
+ return data ? fn(data) : fn;
+ }catch(e) {
+ console.log(e);
+ }
+ };
+})(window);
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css
new file mode 100644
index 00000000..a38647f0
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.css
@@ -0,0 +1,101 @@
+.container {
+ padding-left: 20px;
+ padding-right: 20px;
+}
+
+.row {
+ width: 100%;
+ margin: 0 auto;
+ overflow: hidden;
+}
+
+.spacer {
+ width: 100%;
+ margin: 0 auto;
+ padding-top:4px;
+ clear:both;
+}
+
+
+.last {
+ margin-right: 0px;
+}
+
+.menubar {
+ overflow: hidden;
+ border-bottom: 1px solid black;
+}
+
+.menubar div {
+ padding-bottom:5px;
+
+ margin: 0 auto;
+ overflow: hidden;
+
+ font-size: 80%;
+ font-family:'Bookman Old Style',Bookman,'URW Bookman L','Palatino Linotype',serif;
+
+ float:left;
+}
+
+.menubar .title {
+ float: left;
+ padding-right: 20px;
+
+ font-size: 110%;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+ font-weight: bold;
+
+ vertical-align: bottom;
+}
+
+.menubar .menu_actions {
+ float: left;
+ position:relative;
+ top: 4px;
+}
+
+.menubar .menu_legend {
+ float: right;
+ position:relative;
+ top: 4px;
+
+}
+
+h3.sectionHeader {
+ color: black;
+ font-size: 110%;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
+ background: lightgrey;
+}
+
+.success {
+ color: green;
+}
+.shortCircuited {
+ color: blue;
+}
+.timeout {
+ color: #FF9900; /* shade of orange */
+}
+.failure {
+ color: red;
+}
+
+.rejected {
+ color: purple;
+}
+
+.exceptionsThrown {
+ color: brown;
+}
+
+@media screen and (max-width: 1100px) {
+ .container {
+ padding-left: 5px;
+ padding-right: 5px;
+ }
+}
diff --git a/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html
new file mode 100644
index 00000000..2444f4ee
--- /dev/null
+++ b/spring-cloud-netflix-core/src/main/resources/static/hystrix/monitor/monitor.html
@@ -0,0 +1,197 @@
+
+
+
+
+ Hystrix Monitor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading ...
+
+
+
+
+
+
+
+
Loading ...
+
+
+
+
+
+
+
+
+