You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by ha...@apache.org on 2015/11/30 03:51:10 UTC

[36/44] incubator-eagle git commit: update pom using npm to install web deps

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/app.time.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.time.js b/eagle-webservice/src/main/webapp/app/public/js/app.time.js
new file mode 100644
index 0000000..c6fb03c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.time.js
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+"use strict";
+
+// Time Zone
+(function() {
+	app.time = {
+		UTC_OFFSET: 0,
+		now: function() {
+			return app.time.offset();
+		},
+		offset: function(time) {
+			// Parse string number
+			if(typeof time === "string" && !isNaN(+time)) {
+				time = +time;
+			}
+
+			var _mom = new moment(time);
+			_mom.utcOffset(app.time.UTC_OFFSET);
+			return _mom;
+		},
+		/*
+		 * Return the moment object which use server time zone and keep the time.
+		 */
+		srvZone: function(time) {
+			var _timezone = time._isAMomentObject ? time.utcOffset() : new moment().utcOffset();
+			var _mom = app.time.offset(time);
+			_mom.subtract(app.time.UTC_OFFSET, "m").add(_timezone, "m");
+			return _mom;
+		},
+
+		refreshInterval: 1000 * 10
+	};
+
+	// Moment update
+	moment.fn.toISO = function() {
+		return this.format("YYYY-MM-DDTHH:mm:ss.000Z");
+	};
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.ui.js b/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
new file mode 100644
index 0000000..4e0ee6e
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+(function() {
+	// ================== AdminLTE Update ==================
+	var _boxSelect = $.AdminLTE.options.boxWidgetOptions.boxWidgetSelectors;
+
+	// Box collapse
+	$(document).on("click", _boxSelect.collapse, function(e) {
+		if(common.getValueByPath($._data(this), "events.click")) return;
+
+		e.preventDefault();
+		$.AdminLTE.boxWidget.collapse($(this));
+	});
+
+	// Box remove
+	$(document).on("click", _boxSelect.remove, function(e) {
+		if(common.getValueByPath($._data(this), "events.click")) return;
+
+		e.preventDefault();
+		$.AdminLTE.boxWidget.remove($(this));
+	});
+
+	// =================== jQuery Update ===================
+	// Slide Toggle
+	var _slideToggle = $.fn.slideToggle;
+	$.fn.slideToggle = function(showOrHide) {
+		if(arguments.length === 1 && typeof showOrHide === "boolean") {
+			if(showOrHide) {
+				this.slideDown();
+			} else {
+				this.slideUp();
+			}
+		} else {
+			_slideToggle.apply(this, arguments);
+		}
+	};
+
+	// Fade Toggle
+	var _fadeToggle = $.fn.fadeToggle;
+	$.fn.fadeToggle = function(showOrHide) {
+		if(arguments.length === 1 && typeof showOrHide === "boolean") {
+			if(showOrHide) {
+				this.fadeIn();
+			} else {
+				this.fadeOut();
+			}
+		} else {
+			_fadeToggle.apply(this, arguments);
+		}
+	};
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/common.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/common.js b/eagle-webservice/src/main/webapp/app/public/js/common.js
new file mode 100644
index 0000000..f64f051
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/common.js
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+var common = {};
+
+common.template = function (str, list) {
+	$.each(list, function(key, value) {
+		var _regex = new RegExp("\\$\\{" + key + "\\}", "g");
+		str = str.replace(_regex, value);
+	});
+	return str;
+};
+
+common.getValueByPath = function (unit, path, defaultValue) {
+	if(unit === null || unit === undefined) throw "Unit or path can't be empty!";
+	if(path === "" || path === null || path === undefined) return unit;
+
+	path = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "").split(/\./);
+	$.each(path, function(i, path) {
+		unit = unit[path];
+		if(unit === null || unit === undefined) {
+			unit = null;
+			return false;
+		}
+	});
+	if(unit === null && defaultValue !== undefined) {
+		unit = defaultValue;
+	}
+	return unit;
+};
+
+common.setValueByPath = function(unit, path, value) {
+	if(!unit || path == null || path === "") throw "Unit or path can't be empty!";
+
+	var _inArray = false;
+	var _end = 0;
+	var _start = 0;
+	var _unit = unit;
+
+	function _nextPath(array) {
+		var _key = path.slice(_start, _end);
+		if(_inArray) {
+			_key = _key.slice(0, -1);
+		}
+		if(!_unit[_key]) {
+			if(array) {
+				_unit[_key] = [];
+			} else {
+				_unit[_key] = {};
+			}
+		}
+		_unit = _unit[_key];
+	}
+
+	for(; _end < path.length ; _end += 1) {
+		if(path[_end] === ".") {
+			_nextPath(false);
+			_start = _end + 1;
+			_inArray = false;
+		} else if(path[_end] === "[") {
+			_nextPath(true);
+			_start = _end + 1;
+			_inArray = true;
+		}
+	}
+
+	_unit[path.slice(_start, _inArray ? -1 : _end)] = value;
+
+	return unit;
+};
+
+common.parseJSON = function (str, defaultVal) {
+	try {
+		return JSON.parse(str);
+	} catch(err) {
+		if(defaultVal === undefined) {
+			console.warn("Can't parse JSON: " + str);
+		}
+	}
+	return defaultVal === undefined ? null : defaultVal;
+};
+
+common.isEmpty = function(val) {
+	if($.isArray(val)) {
+		return val.length === 0;
+	} else {
+		return val === null || val === undefined;
+	}
+};
+
+// ====================== Format ======================
+common.format = {};
+
+/*
+ * Format date to string. Support number, string, Date instance. Will auto convert time zone offset(Moment instance will keep time zone).
+ */
+common.format.date = function(val, type) {
+	if(val === undefined || val === null) return "";
+
+	if(typeof val === "number" || typeof val === "string" || val instanceof Date) {
+		val = app.time.offset(val);
+	}
+	switch(type) {
+	default:
+		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
+	}
+};
+
+// ====================== Array =======================
+common.array = {};
+
+common.array.sum = function(list, path) {
+	var _sum = 0;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number") {
+				_sum += _val;
+			}
+		});
+	}
+	return _sum;
+};
+
+common.array.max = function(list, path) {
+	var _max = null;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number" && (_max === null || _max < _val)) {
+				_max = _val;
+			}
+		});
+	}
+	return _max;
+};
+
+common.array.bottom = function(list, path, count) {
+	var _list = list.slice();
+
+	_list.sort(function(a, b) {
+		var _a = common.getValueByPath(a, path, null);
+		var _b = common.getValueByPath(b, path, null);
+
+		if(_a === _b) return 0;
+		if(_a < _b || _a === null) {
+			return -1;
+		} else {
+			return 1;
+		}
+	});
+	return !count ? _list : _list.slice(0, count);
+};
+common.array.top = function(list, path, count) {
+	var _list = common.array.bottom(list, path);
+	_list.reverse();
+	return !count ? _list : _list.slice(0, count);
+};
+
+common.array.find = function(val, list, path, findAll) {
+	path = path || "";
+	var _list = $.grep(list, function(unit) {
+		return val === common.getValueByPath(unit, path);
+	});
+	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
+};
+
+common.array.filter = function(val, list, path) {
+	return common.array.find(val, list, path, true);
+};
+
+common.array.count = function(list, val, path) {
+	if(arguments.length === 1) {
+		return list.length;
+	} else {
+		return common.array.find(val, list, path, true).length;
+	}
+};
+
+common.array.remove = function(val, list) {
+	for(var i = 0 ; i < list.length ; i += 1) {
+		if(list[i] === val) {
+			list.splice(i, 1);
+			i -= 1;
+		}
+	}
+};
+
+// ======================= Map ========================
+common.map = {};
+
+common.map.toArray = function(map) {
+	return $.map(map, function(unit) {
+		return unit;
+	});
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/charts.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/charts.js b/eagle-webservice/src/main/webapp/app/public/js/components/charts.js
new file mode 100644
index 0000000..8b3d8d2
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/charts.js
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+eagleComponents.directive('chart', function($compile) {
+	return {
+		restrict : 'E',
+		scope: {
+			title: "@",
+			data: "=",
+		},
+		controller: function(charts, $scope, $element) {
+			var _charts = charts($scope);
+
+			_charts.gen($element.find(".chart-body"), [{
+				data: "data",
+			}]);
+		},
+		template :	'<div class="chart">' +
+						'<div class="chart-header">' +
+							'<h3>{{title}}</h3>' +
+						'</div>' +
+						'<div class="chart-body">' +
+						'</div>' +
+					'</div>',
+		replace: true
+	};
+});
+
+/*
+ * config:
+ * 		type:	"line"(default), "area". Chart type is the default type of all series. Will be replaced by each series configure.
+ */
+eagleComponents.service('charts', function() {
+	/*
+	 * Destroy chart
+	 */
+	function _destroy(ele) {
+		$(ele).each(function() {
+			var _plot = $(this).data("plot");
+			_plot.shutdown();
+			_plot.destroy();
+		});
+	};
+
+	var charts = function($scope) {
+		var _id = 0;
+
+		return {
+			/*
+			 * Generate chart
+			 */
+			gen: function(ele, series, config) {
+				// Initialization
+				ele = $(ele);
+
+				series = series || [];
+				config = config || {};
+
+				var _listenList = [];
+
+				var _config = {
+					grid: {
+						hoverable: true,
+						clickable: true,
+						borderWidth: 0,
+					},
+					type: "line",
+					colors: [
+						"#7cb5ec", "#f7a35c", "#90ee7e", "#7798BF", "#aaeeee",
+					],
+					series: {
+						shadowSize: 0,
+					},
+					crosshair: {
+						color: "#3c8dbc"
+					},
+					tooltip: {
+						id: "chartTooltip",
+					},
+					xaxis: {
+						mode: "time",
+					},
+					yaxis: {
+						min: 0,
+					},
+					legend: {},
+				};
+
+				$.extend(_config, config);
+
+				// Series process
+				// > Series type
+				$.each(series, function(i, _series) {
+					// Data source
+					if(typeof _series.data === "string") {
+						_listenList.push(_series.data);
+						_series._key = _series.data;
+						_series.data = [];
+					}
+
+					// Chart type
+					switch((_series.type || _config.type || "").toLowerCase()) {
+					case "area":
+						common.setValueByPath(_series, "lines.show", true);
+						common.setValueByPath(_series, "lines.fill", true);
+					default:
+						common.setValueByPath(_series, "lines.show", true);
+					}
+
+					if(_config.xaxis.mode === "time" && $.isArray(_series.data)) {
+						$.each(_series.data, function(i, unit) {
+							unit[0] += app.time.UTC_OFFSET * 1000 * 60;
+						});
+					}
+				});
+
+				// > Data source
+				function _updateSeriesSource() {
+					$.each(series, function(i, _series) {
+						if(typeof _series._key === "string" && $scope[_series._key]) {
+							_series.data = $scope[_series._key];
+							delete _series._key;
+						}
+					});
+				}
+
+				// Chart process
+				_config.type = _config.type.toLowerCase();
+				if(_config.type === "line" || _config.type === "area") {
+					common.setValueByPath(_config, "crosshair.mode", "x");
+				}
+
+				// Draw charts
+				function _drawChart() {
+					_updateSeriesSource();
+
+					ele.each(function() {
+						var _my = $(this);
+						var _plot = _my.data("plot");
+						if(!_plot) {
+							var _plot = $.plot(this, series, _config);
+							_plot._id = ++_id;
+							_my.data("plot", _plot);
+						} else {
+							_plot.setData(series);
+							_plot.setupGrid();
+							_plot.draw();
+						}
+					});
+				}
+
+				// Watch
+				$.each(_listenList, function(i, item) {
+					$scope.$watchCollection(item, function() {
+						_drawChart();
+					});
+				});
+
+				// Destroy
+				$scope.$on('$destroy', function() {
+					_destroy(ele);
+				});
+			},
+
+			destroy: _destroy,
+		};
+	};
+
+	charts.destroy = _destroy;
+
+	return charts;
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/charts/bar.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/charts/bar.js b/eagle-webservice/src/main/webapp/app/public/js/components/charts/bar.js
new file mode 100644
index 0000000..8090774
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/charts/bar.js
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+eagleComponents.directive('barChart', function($compile) {
+	return {
+		restrict : 'AE',
+		scope : {
+			title : "@",
+			data : "=",
+
+			height : "=?height"
+		},
+		controller : function(barCharts, $scope, $element, $attrs) {
+			$scope.height = $scope.height || 200;
+
+			var _chart = barCharts($scope);
+
+			var _chartBody = $element.find(".chart-body");
+			_chartBody.height($scope.height);
+
+			_chart.gen($element.find(".chart-body"), $attrs.data, {
+				height : $scope.height,
+			});
+		},
+		template : '<div class="chart">' + '<div class="chart-header">' + '<h3>{{title}}</h3>' + '</div>' + '<div class="chart-body">' + '</div>' + '</div>',
+		replace : true
+	};
+});
+
+eagleComponents.service('barCharts', function() {
+	/*$(window).resize(function() {
+	 });
+	 $("body").on("collapsed.pushMenu", function() {
+	 });
+	 $("body").on("expanded.pushMenu", function() {
+	 });*/
+
+	var charts = function($scope) {
+		return {
+			gen : function(ele, series, config) {
+				// ======================= Initialization =======================
+				ele = $(ele);
+
+				series = series || [];
+				config = config || {};
+
+				// ========================== ToolTips ==========================
+				var $tooltip = $("<div>").css({
+					display: "inline-block",
+					background: "rgba(0,0,0,0.7)",
+					padding: "3px 5px",
+					color: "#FFFFFF",
+					position: "fixed",
+					"z-index": 3,
+					"border-radius": "3px",
+					"font-size": "12px",
+				}).appendTo("body");
+
+				// ======================= Set Up D3 View =======================
+				var margin = {
+					top : 20,
+					right : 20,
+					bottom : 50,
+					left : 40
+				}, width = ele.innerWidth() - margin.left - margin.right, height = config.height - margin.top - margin.bottom;
+
+				var x0 = d3.scale.ordinal().rangeRoundBands([0, width], .1);
+				var x1 = d3.scale.ordinal();
+				var y = d3.scale.linear().range([height, 0]);
+
+				var color = d3.scale.ordinal().range(["#7cb5ec", "#f7a35c", "#90ee7e", "#7798BF", "#aaeeee"]);
+
+				var xAxis = d3.svg.axis().scale(x0).orient("bottom");
+				var yAxis = d3.svg.axis().scale(y).orient("left").tickFormat(d3.format("0.2f"));
+
+				var cntr = d3.select(ele[0]).append("svg").attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom);
+				var svg = cntr.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
+
+				// =========================== Render ===========================
+				function _render() {
+					// ======== Parse Data ========
+					var _series = typeof series === "string" ? $scope.data : series;
+					if(!_series) return;
+
+					var _data = [];
+					// > Detect category
+					var _categoryList = [];
+					_series = $.grep(_series, function(unit) {
+						if(unit.type === "category") _categoryList = unit.data;
+						return !unit.type;
+					});
+
+					// > Keys
+					var _keys = $.map(_series, function(unit) {
+						return unit.name;
+					});
+
+					// > Merge values
+					var _maxLen = 0;
+					$.each(_series, function(i, unit) {
+						_maxLen = Math.max(_maxLen, unit.data.length);
+					});
+					for(var i = 0 ; i < _maxLen ; i += 1) {
+						_data[i] = {};
+
+						// Category
+						if(_categoryList[i]) {
+							_data[i]._category = _categoryList[i];
+						} else {
+							_data[i]._category = "CAT" + i;
+						}
+
+						// Value
+						$.each(_keys, function(j, key) {
+							_data[i][key] = _series[j].data[i] || 0;
+						});
+					}
+
+					// ====== Convert Format ======
+					var ageNames = d3.keys(_data[0]).filter(function(key) {
+						return key !== "_category";
+					});
+
+					_data.forEach(function(d) {
+						d.items = ageNames.map(function(name) {
+							return {
+								name : name,
+								value : +d[name]
+							};
+						});
+					});
+
+					x0.domain(_data.map(function(d) {
+						return d._category;
+					}));
+					x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
+					y.domain([0, d3.max(_data, function(d) {
+						return d3.max(d.items, function(d) {
+							return d.value;
+						});
+					})]);
+
+					// Axis
+					svg.append("g").attr("class", "y axis").call(yAxis).append("text").attr("transform", "rotate(-90)").attr("y", 6).attr("dy", ".71em").style("text-anchor", "end");
+					svg.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis)
+					.selectAll("text")
+						.attr("transform", "rotate(15)")
+						.attr("x", -x1.rangeBand() * 0.3)
+						.style("text-anchor", "start");
+
+					// Bar
+					var barGroup = svg.selectAll(".barGroup").data(_data).enter().append("g").attr("class", "g").attr("transform", function(d) {
+						return "translate(" + x0(d._category) + ",0)";
+					});
+
+					barGroup.selectAll("rect").data(function(d) {
+						return d.items;
+					}).enter().append("rect").attr("width", x1.rangeBand()).attr("x", function(d) {
+						return x1(d.name);
+					}).attr("y", function(d) {
+						return y(d.value);
+					}).attr("height", function(d) {
+						return height - y(d.value);
+					}).style("fill", function(d) {
+						return color(d.name);
+					});
+
+					// Legend
+					var legend = svg.selectAll(".legend").data(ageNames.slice().reverse()).enter().append("g").attr("class", "legend").attr("transform", function(d, i) {
+						return "translate(0," + (30 + i * 20) + ")";
+					});
+
+					legend.append("rect").attr("x", width - 18).attr("width", 18).attr("height", 18).style("fill", color);
+					legend.append("text").attr("x", width - 24).attr("y", 9).attr("dy", ".35em").style("text-anchor", "end").text(function(d) {
+						return d;
+					});
+
+					// Tool tip
+					var OFFSET_X = 15;
+					var OFFSET_Y = 20;
+					var OFFSET_DES = 20;
+
+					var _tooltipId;
+					var _tooltipPrev;
+					var _xCells = $.map(_data, function(d) {
+						return [[x0(d._category), x0(d._category) + x0.rangeBand(), d]];
+					});
+
+					cntr.on("mousemove", function () {
+						var mouseX = d3.mouse(this.parentNode)[0] - margin.left;
+						var d;
+						for(var i = 0 ; i < _xCells.length ; i += 1) {
+							if(_xCells[i][0] <= mouseX && mouseX <= _xCells[i][1]) {
+								d= _xCells[i][2];
+								break;
+							}
+						}
+
+						if(!d && _tooltipPrev) {
+							_tooltipId = setTimeout(function() {
+								$tooltip.fadeOut('fast');
+							}, _xCells[0] && mouseX < _xCells[0][0] ? 100 : 500);
+						} else if(d) {
+							if(_tooltipPrev !== d) {
+								clearTimeout(_tooltipId);
+
+								$tooltip.empty()
+								.stop().fadeIn('fast');
+
+								var $cntr = $("<div>").appendTo($tooltip);
+								$("<span>").css("display", "block").text(d._category)
+								.appendTo($cntr);
+								$.each(d.items, function(i, item) {
+									$("<span>").css("display", "block")
+									.append($("<span>").html("\u25CF").css("color", color(item.name))).append(" ")
+									.append(item.name)
+									.append(": ")
+									.append($("<b>").text(item.value))
+									.appendTo($cntr);
+								});
+							}
+
+							// Position
+							var _x = event.pageX + OFFSET_X;
+							var _y = event.pageY + OFFSET_Y;
+							var _width = $tooltip.outerWidth();
+							var _height = $tooltip.outerHeight();
+							var _winWidth = $(window).width();
+							var _winHeight = $(window).height();
+							var _winLeft = $(window).scrollLeft();
+							var _winTop = $(window).scrollTop();
+
+							if(_x + _width + OFFSET_DES - _winLeft > _winWidth) {
+								_x = event.pageX - _width - OFFSET_X;
+							}
+							if(_y + _height + OFFSET_DES - _winTop > _winHeight) {
+								_y = event.pageY - _height - OFFSET_Y;
+							}
+
+							$tooltip.offset({
+								left: _x,
+								top: _y
+							});
+						}
+						_tooltipPrev = d;
+					});
+					cntr.on("mouseleave", function () {
+						_tooltipId = setTimeout(function() {
+							$tooltip.fadeOut('fast');
+						}, 100);
+					});
+				}
+
+				// ======================= Dynamic Detect =======================
+				if(typeof series === "string") {
+					$scope.$parent.$watch(series, function() {
+						_render();
+					}, true);
+				} else {
+					_render();
+				}
+
+
+				// ========================== Clean Up ==========================
+				$scope.$on('$destroy', function() {
+					cntr.remove();
+					$tooltip.remove();
+				});
+			},
+		};
+	};
+
+	return charts;
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/charts/line3d.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/charts/line3d.js b/eagle-webservice/src/main/webapp/app/public/js/components/charts/line3d.js
new file mode 100644
index 0000000..8d89ba2
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/charts/line3d.js
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+'use strict';
+
+eagleComponents.directive('line3dChart', function($compile) {
+	return {
+		restrict : 'AE',
+		scope : {
+			title : "@",
+			data : "=",
+
+			height : "=?height"
+		},
+		controller : function(line3dCharts, $scope, $element, $attrs) {
+			$scope.height = $scope.height || 200;
+
+			var _chart = line3dCharts($scope);
+
+			var _chartBody = $element.find(".chart-body");
+			_chartBody.height($scope.height);
+
+			_chart.gen($element.find(".chart-body"), $attrs.data, {
+				height : $scope.height,
+			});
+		},
+		template : '<div class="chart">' + '<div class="chart-header">' + '<h3>{{title}}</h3>' + '</div>' + '<div class="chart-body">' + '</div>' + '</div>',
+		replace : true
+	};
+});
+
+eagleComponents.service('line3dCharts', function() {
+	var charts = function($scope) {
+		return {
+			gen : function(ele, series, config) {
+				// ======================= Initialization =======================
+				ele = $(ele);
+
+				series = series || [];
+				config = config || {};
+
+				var _bounds = [{min:-10, max: 10},{min:-10, max: 10},{min:-10, max: 10}];
+				var _scale = 1;
+
+				// ======================= Set Up D3 View =======================
+				var width = ele.innerWidth(), height = config.height;
+
+				var color = ["#7cb5ec", "#f7a35c", "#90ee7e", "#7798BF", "#aaeeee"];
+
+				var svg = d3.select(ele[0]).append("svg").attr("width", width).attr("height", height);
+
+				// ========================== Function ==========================
+				var yaw=0.5,pitch=0.5,drag;
+				var transformPrecalc = [];
+
+				var offsetPoint = function(point) {
+					var _point = [
+						+point[0] - (_bounds[0].max + _bounds[0].min) / 2,
+						-point[1] + (_bounds[1].max + _bounds[1].min) / 2,
+						-point[2] + (_bounds[2].max + _bounds[2].min) / 2
+					];
+					return [_point[0] * _scale, _point[1] * _scale, _point[2] * _scale];
+				};
+
+				var transfromPointX = function(point) {
+					point = offsetPoint(point);
+					return transformPrecalc[0] * point[0] + transformPrecalc[1] * point[1] + transformPrecalc[2] * point[2] + width / 2;
+				};
+				var transfromPointY = function(point) {
+					point = offsetPoint(point);
+					return transformPrecalc[3] * point[0] + transformPrecalc[4] * point[1] + transformPrecalc[5] * point[2] + height / 2;
+				};
+				var transfromPointZ = function(point) {
+					point = offsetPoint(point);
+					return transformPrecalc[6] * point[0] + transformPrecalc[7] * point[1] + transformPrecalc[8] * point[2];
+				};
+				var transformPoint2D = function(point) {
+					var _point = [point[0], point[1], point[2]];
+					return transfromPointX(_point).toFixed(10) + "," + transfromPointY(_point).toFixed(10);
+				};
+
+				var setTurtable = function(yaw, pitch, update) {
+					var cosA = Math.cos(pitch);
+					var sinA = Math.sin(pitch);
+					var cosB = Math.cos(yaw);
+					var sinB = Math.sin(yaw);
+					transformPrecalc[0] = cosB;
+					transformPrecalc[1] = 0;
+					transformPrecalc[2] = sinB;
+					transformPrecalc[3] = sinA * sinB;
+					transformPrecalc[4] = cosA;
+					transformPrecalc[5] = -sinA * cosB;
+					transformPrecalc[6] = -sinB * cosA;
+					transformPrecalc[7] = sinA;
+					transformPrecalc[8] = cosA * cosB;
+
+					if(update) _update();
+				};
+				setTurtable(0.4,0.4);
+
+				// =========================== Redraw ===========================
+				var _coordinateList = [];
+				var _axisText = [];
+				var coordinate = svg.selectAll(".axis");
+				var axisText = svg.selectAll(".axis-text");
+				var lineList = svg.selectAll(".line");
+
+				function _redraw(series) {
+					var _desX, _desY, _desZ, _step;
+
+					// Bounds
+					if(series) {
+						_bounds = [{},{},{}];
+						$.each(series, function(j, series) {
+							// Points
+							$.each(series.data, function(k, point) {
+								for(var i = 0 ; i < 3 ; i += 1) {
+									// Minimum
+									if(_bounds[i].min === undefined || point[i] < _bounds[i].min) {
+										_bounds[i].min = point[i];
+									}
+									// Maximum
+									if(_bounds[i].max === undefined || _bounds[i].max < point[i]) {
+										_bounds[i].max = point[i];
+									}
+								}
+							});
+						});
+					}
+
+					_desX = _bounds[0].max - _bounds[0].min;
+					_desY = _bounds[1].max - _bounds[1].min;
+					_desZ = _bounds[2].max - _bounds[2].min;
+
+					// Step
+					(function() {
+						var _stepX = _desX / 10;
+						var _stepY = _desY / 10;
+						var _stepZ = _desZ / 10;
+
+						_step = Math.min(_stepX, _stepY, _stepZ);
+						_step = Math.max(_step, 0.5);
+						_step = 0.5;
+					})();
+
+					// Scale
+					(function() {
+						var _scaleX = width / _desX;
+						var _scaleY = height / _desY / 2;
+						var _scaleZ = width / _desZ;
+						_scale = Math.min(_scaleX, _scaleY, _scaleZ) / 2;
+					})();
+
+					// Coordinate
+					// > Basic
+					_coordinateList = [
+						{color: "rgba(0,0,0,0.1)", data: [[0,0,-100],[0,0,100]]},
+						{color: "rgba(0,0,0,0.1)", data: [[0,-100,0],[0,100,0]]},
+						{color: "rgba(0,0,0,0.1)", data: [[-100,0,0],[100,0,0]]},
+
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].min,_bounds[2].min]]},
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].min,_bounds[1].min,_bounds[2].max]]},
+
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].min,_bounds[2].max],[_bounds[0].max,_bounds[1].min,_bounds[2].max]]},
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].max,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].min,_bounds[2].max]]},
+
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].max,_bounds[2].min],[_bounds[0].max,_bounds[1].max,_bounds[2].min]]},
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].max,_bounds[2].min],[_bounds[0].min,_bounds[1].max,_bounds[2].max]]},
+
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].min,_bounds[2].min],[_bounds[0].min,_bounds[1].max,_bounds[2].min]]},
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].max,_bounds[1].min,_bounds[2].min],[_bounds[0].max,_bounds[1].max,_bounds[2].min]]},
+						{color: "rgba(0,0,255,0.3)", data: [[_bounds[0].min,_bounds[1].min,_bounds[2].max],[_bounds[0].min,_bounds[1].max,_bounds[2].max]]},
+					];
+
+					_axisText = [];
+					function _axisPoint(point, dimension, number) {
+						// Coordinate
+						if(dimension === 1) {
+							_coordinateList.push({
+								color: "rgba(0,0,0,0.2)",
+								data:[[_step/5,point[1],0],[0,point[1],0],[0,point[1],_step/5]]
+							});
+						} else {
+							_coordinateList.push({
+								color: "rgba(0,0,0,0.2)",
+								data:[[point[0],-_step/8,point[2]], [point[0],_step/8,point[2]]]
+							});
+						}
+
+						// Axis Text
+						if(number.toFixed(0) == number + "") {
+							point.text = number;
+							point.dimension = dimension;
+							_axisText.push(point);
+						}
+					}
+					function _axisPoints(dimension, bound) {
+						for(var i = _step ; i < bound.max + _step ; i += _step) {
+							var _unit = [0,0,0];
+							_unit[dimension] = i;
+							_axisPoint(_unit, dimension, i);
+						}
+						for(var i = -_step ; i > bound.min - _step ; i -= _step) {
+							var _unit = [0,0,0];
+							_unit[dimension] = i;
+							_axisPoint(_unit, dimension, i);
+						}
+					}
+					// > Steps
+					_axisPoint([0,0,0],1,0);
+					_axisPoints(0, _bounds[0]);
+					_axisPoints(1, _bounds[1]);
+					_axisPoints(2, _bounds[2]);
+
+					// > Draw
+					coordinate = coordinate.data(_coordinateList);
+					coordinate.enter()
+					.append("path")
+						.attr("fill", "none")
+						.attr("stroke", function(d) {return d.color;});
+					coordinate.exit().remove();
+
+					// Axis Text
+					axisText = axisText.data(_axisText);
+					axisText.enter()
+						.append("text")
+						.classed("noSelect", true)
+						.attr("fill", "rgba(0,0,0,0.5)")
+						.attr("text-anchor", function(d) {return d.dimension === 1 ? "start" : "middle";})
+						.text(function(d) {return d.text;});
+					axisText.transition()
+						.attr("text-anchor", function(d) {return d.dimension === 1 ? "end" : "middle";})
+						.text(function(d) {return d.text;});
+					axisText.exit().remove();
+
+					// Lines
+					lineList = lineList.data(series || []);
+					lineList.enter()
+						.append("path")
+							.attr("fill", "none")
+							.attr("stroke", function(d) {return d.color;});
+					lineList.exit().remove();
+
+					_update();
+				}
+
+				function _update() {
+					coordinate
+						.attr("d", function(d) {
+						var path = "";
+						$.each(d.data, function(i, point) {
+							path += (i === 0 ? "M" : "L") + transformPoint2D(point);
+						});
+						return path;
+						});
+
+					axisText
+						.attr("x", function(d) {return transfromPointX(d) + (d.dimension === 1 ? -3 : 0);})
+						.attr("y", function(d) {return transfromPointY(d) + (d.dimension === 1 ? 0 : -5);});
+
+					lineList
+						.attr("d", function(d, index) {
+							var path = "";
+							$.each(d.data, function(i, point) {
+								path += (i === 0 ? "M" : "L") + transformPoint2D(point);
+							});
+							return path;
+						});
+				}
+
+
+				svg.on("mousedown", function() {
+					drag = [d3.mouse(this), yaw, pitch];
+				}).on("mouseup", function() {
+					drag = false;
+				}).on("mousemove", function() {
+					if (drag) {
+						var mouse = d3.mouse(this);
+						yaw = drag[1] - (mouse[0] - drag[0][0]) / 50;
+						pitch = drag[2] + (mouse[1] - drag[0][1]) / 50;
+						pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch));
+						setTurtable(yaw, pitch, true);
+					}
+				});
+
+				// =========================== Render ===========================
+				_redraw();
+
+				function _render() {
+					// ======== Parse Data ========
+					var _series = typeof series === "string" ? $scope.data : series;
+					if(!_series) return;
+
+					// Clone
+					_series = $.map(_series, function(series) {
+						return {
+							name: series.name,
+							color: series.color,
+							data: series.data
+						};
+					});
+
+					// Colors
+					$.each(_series, function(i, series) {
+						series.color = series.color || color[i % color.length];
+					});
+
+					// Render
+					_redraw(_series);
+				}
+
+				// ======================= Dynamic Detect =======================
+				if(typeof series === "string") {
+					$scope.$parent.$watch(series, function() {
+						_render();
+					}, true);
+				} else {
+					_render();
+				}
+
+
+				// ========================== Clean Up ==========================
+				$scope.$on('$destroy', function() {
+					svg.remove();
+				});
+			},
+		};
+	};
+	return charts;
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/file.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/file.js b/eagle-webservice/src/main/webapp/app/public/js/components/file.js
new file mode 100644
index 0000000..b63c320
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/file.js
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+eagleComponents.directive('file', function($compile) {
+	return {
+		restrict : 'A',
+		scope: {
+			filepath: "=?filepath",
+		},
+		controller: function($scope, $element, $attrs) {
+			// Watch change(Only support clean the data)
+			if($attrs.filepath) {
+				$scope.$parent.$watch($attrs.filepath, function(value) {
+					if(!value) $element.val(value);
+				});
+			}
+
+			// Bind changed value
+			$element.on("change", function() {
+				var _path = $(this).val();
+				if($attrs.filepath) {
+					common.setValueByPath($scope.$parent, $attrs.filepath, _path);
+					$scope.$parent.$apply();
+				}
+			});
+
+			$scope.$on('$destroy',function(){
+				$element.off("change");
+			});
+		},
+		replace: false
+	};
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/main.js b/eagle-webservice/src/main/webapp/app/public/js/components/main.js
new file mode 100644
index 0000000..b3528a1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/main.js
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+var eagleComponents = angular.module('eagle.components', []);
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/sortTable.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/sortTable.js b/eagle-webservice/src/main/webapp/app/public/js/components/sortTable.js
new file mode 100644
index 0000000..6f4fb9c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/sortTable.js
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+eagleComponents.directive('sorttable', function($compile) {
+	return {
+		restrict : 'AE',
+		scope: {
+			source: '=',
+			search: '=?search',
+			searchfunc: "=?searchfunc",
+
+			orderKey: "@?sort",
+
+			maxSize: "=?maxSize",
+		},
+		controller: function($scope, $element, $attrs) {
+			// Initialization
+			$scope.app = app;
+			$scope.common = common;
+			$scope._parent = $scope.$parent;
+
+			$scope.pageNumber = $scope.pageNumber || 1;
+			$scope.pageSize = $scope.pageSize || 10;
+
+			$scope.maxSize = $scope.maxSize || 10;
+
+			// Search box
+			if($scope.search !== false) {
+				var $search = $(
+					'<div style="overflow:hidden;">' +
+						'<div class="row">' +
+							'<div class="col-xs-4">' +
+								'<div class="search-box">' +
+									'<input type="search" class="form-control input-sm" placeholder="Search" ng-model="search" />' +
+									'<span class="fa fa-search"></span>' +
+								'</div>' +
+							'</div>' +
+						'</div>' +
+					'</div>'
+				).prependTo($element);
+				$compile($search)($scope);
+			}
+
+			// List head
+			$scope.doSort = function(path) {
+				if($scope.orderKey === path) {
+					$scope.orderKey = "-" + path;
+				} else {
+					$scope.orderKey = path;
+				}
+			};
+			$scope.checkSortClass = function(key) {
+				if($scope.orderKey === key) {
+					return "fa fa-sort-asc";
+				} else if($scope.orderKey === "-" + key) {
+					return "fa fa-sort-desc";
+				}
+				return "fa fa-sort";
+			};
+
+			var $listHead = $element.find("thead > tr");
+			$listHead.find("> th").each(function() {
+				var $th = $(this);
+				$th.addClass("noSelect");
+
+				var _sortpath = $th.attr("sortpath");
+				if(_sortpath) {
+					$th.attr("ng-click", "doSort('" + _sortpath + "')");
+					$th.prepend('<span ng-class="checkSortClass(\'' + _sortpath + '\')"></span>');
+				}
+			});
+			$compile($listHead)($scope);
+
+			// List body
+			var $listBody = $element.find("tbody > tr");
+			$listBody.attr("ng-repeat", 'item in (filteredList = (source | filter: ' + ($scope.searchfunc ? 'searchfunc' : 'search') + ' | orderBy: orderKey)).slice((pageNumber - 1) * pageSize, pageNumber * pageSize)');
+			$compile($listBody)($scope);
+
+			// Navigation
+			var $navigation = $(
+				'<div style="overflow:hidden;">' +
+					'<div class="row">' +
+						'<div class="col-xs-5">' +
+							'show {{(pageNumber - 1) * pageSize + 1}} to {{pageNumber * pageSize}} of {{filteredList.length}} items' +
+						'</div>' +
+						'<div class="col-xs-7 text-right">' +
+							'<uib-pagination total-items="filteredList.length" ng-model="pageNumber" boundary-links="true" items-per-page="pageSize" num-pages="numPages" max-size="maxSize"></uib-pagination>' +
+						'</div>' +
+					'</div>' +
+				'</div>'
+			).appendTo($element);
+			$compile($navigation)($scope);
+		},
+		replace: false
+	};
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/components/tabs.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/tabs.js b/eagle-webservice/src/main/webapp/app/public/js/components/tabs.js
new file mode 100644
index 0000000..47d0f71
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/components/tabs.js
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+'use strict';
+
+eagleComponents.directive('tabs', function() {
+	return {
+		restrict : 'AE',
+		transclude : true,
+		scope : {
+			title: "@",
+			icon: "@",
+			selected: "@?selected",
+
+			inner: "=?inner"
+		},
+		controller: function($scope, $element, $attrs, $timeout) {
+			var _selected = null;
+
+			var panes = $scope.panes = [];
+
+			$scope.getList = function() {
+				if($scope.inner) {
+					return $scope.panes;
+				} else {
+					return $scope.panes.slice().reverse();
+				}
+			};
+
+			$scope.select = function(pane, updateBind) {
+				angular.forEach(panes, function(pane) {
+					pane.selected = false;
+				});
+				pane.selected = true;
+				_selected = pane;
+
+				if(updateBind !== false && $attrs.selected) {
+					$scope.$parent[$attrs.selected] = _selected.title;
+				}
+			};
+
+			this.addPane = function(pane) {
+				if (panes.length == 0 || ($attrs.selected && $scope.$parent[$attrs.selected] === pane.title)) {
+					$scope.select(pane, false);
+				}
+				panes.push(pane);
+			};
+
+			// Listen tab selected change
+			if($attrs.selected) {
+				$scope.$parent.$watch($attrs.selected, function(value) {
+					$.each(panes, function(i, pane) {
+						if(value === pane.title) {
+							$scope.select(pane, false);
+							return false;
+						}
+					});
+				});
+			}
+		},
+		template : '<div ng-class="inner ? \'\' : \'nav-tabs-custom\'">' +
+			'<ul class="nav nav-tabs ui-sortable-handle" ng-class="inner ? \'\' : \'pull-right\'">' +
+				'<li ng-repeat="pane in getList()" ng-class="{active:pane.selected}">' +
+					'<a href="" ng-click="select(pane)">{{pane.title}}</a>' +
+				'</li>' +
+				'<li class="pull-left header"><i class="fa fa-{{icon}}"></i> {{title}}</li>' +
+			'</ul>' +
+			'<div class="tab-content" ng-transclude></div>' +
+		'</div>',
+		replace : true
+	};
+}).directive('pane', function() {
+	return {
+		require : '^tabs',
+		restrict : 'AE',
+		transclude : true,
+		scope : {
+			title : '@'
+		},
+		controller: function($scope, $element, $timeout) {
+			// Initialization
+			var $innerScope = angular.element($element).scope();
+			$innerScope.app = app;
+			$innerScope.common = common;
+			$innerScope._parent = $scope.$parent.$parent.$parent;
+		},
+		link : function(scope, element, attrs, tabsController) {
+			tabsController.addPane(scope);
+		},
+		template : '<div class="tab-pane" ng-class="{active: selected}" ng-transclude="parent">' + '</div>',
+		replace : true
+	};
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
new file mode 100644
index 0000000..075c309
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/alertController.js
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+'use strict';
+
+// =============================================================
+// =                        Alert List                         =
+// =============================================================
+damControllers.controller('alertListCtrl', function(globalContent, Site, damContent, $scope, $routeParams, $interval, $timeout, Entities) {
+	globalContent.setConfig(damContent.config);
+	globalContent.pageSubTitle = Site.current().name;
+
+	var MAX_PAGESIZE = 10000;
+
+	// Initial load
+	$scope.dataSource = $routeParams.dataSource;
+
+	$scope.alertList = [];
+	$scope.alertList.ready = false;
+
+	// Load data
+	function _loadAlerts() {
+		if($scope.alertList._promise) {
+			$scope.alertList._promise.abort();
+		}
+
+		var _list = Entities.queryEntities("AlertService", {
+			site: Site.current().name,
+			dataSource: $scope.dataSource,
+			hostname: null,
+			_pageSize: MAX_PAGESIZE,
+			_duration: 1000 * 60 * 60 * 24 * 30,
+			__ETD: 1000 * 60 * 60 * 24
+		});
+		$scope.alertList._promise = _list._promise;
+		_list._promise.then(function() {
+			var index;
+
+			if($scope.alertList[0]) {
+				// List new alerts
+				for(index = 0 ; index < _list.length ; index += 1) {
+					var _alert = _list[index];
+					_alert.__new = true;
+					if(_alert.encodedRowkey === $scope.alertList[0].encodedRowkey) {
+						break;
+					}
+				}
+
+				if(index > 0) {
+					$scope.alertList.unshift.apply($scope.alertList, _list.slice(0, index));
+
+					// Clean up UI highlight
+					$timeout(function() {
+						$.each(_list, function(i, alert) {
+							delete alert.__new;
+						});
+					}, 100);
+				}
+			} else {
+				// List all alerts
+				$scope.alertList.push.apply($scope.alertList, _list);
+			}
+
+			$scope.alertList.ready = true;
+		});
+	}
+
+	_loadAlerts();
+	var _loadInterval = $interval(_loadAlerts, app.time.refreshInterval);
+	$scope.$on('$destroy',function(){
+		$interval.cancel(_loadInterval);
+	});
+});
+
+// =============================================================
+// =                       Alert Detail                        =
+// =============================================================
+damControllers.controller('alertDetailCtrl', function(globalContent, Site, damContent, $scope, $routeParams, Entities) {
+	globalContent.setConfig(damContent.config);
+	globalContent.pageTitle = "Alert Detail";
+	globalContent.navPath = ["Alert List", "Alert Detail"];
+	globalContent.lockSite = true;
+
+	$scope.common = common;
+
+	// Query policy
+	$scope.alertList = Entities.queryEntity("AlertService", $routeParams.encodedRowkey);
+	$scope.alertList._promise.then(function() {
+		if($scope.alertList.length === 0) {
+			$.dialog({
+				title: "OPS!",
+				content: "Alert not found!",
+			}, function() {
+				location.href = "#/dam/alertList";
+			});
+			return;
+		} else {
+			var alert = $scope.alertList[0];
+
+			$scope.alert = alert;
+			Site.current(Site.find($scope.alert.tags.site));
+			console.log($scope.alert);
+		}
+	});
+
+	// UI
+	$scope.getMessageTime = function(alert) {
+		var _time = common.getValueByPath(alert, "alertContext.properties.timestamp");
+		return Number(_time);
+	};
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
new file mode 100644
index 0000000..bed94fb
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+'use strict';
+
+// =============================================================
+// =                     User Profile List                     =
+// =============================================================
+damControllers.controller('authLoginCtrl', function(globalContent, Site, Authorization, $scope) {
+	globalContent.hideSidebar = true;
+	globalContent.hideSite = true;
+	globalContent.hideUser = true;
+
+	$scope.username = "";
+	$scope.password = "";
+	$scope.lock = false;
+
+	// UI
+	setTimeout(function() {
+		$("#username").focus();
+	});
+
+	// Login
+	$scope.login = function(event, forceSubmit) {
+		if($scope.lock) return;
+
+		if(event.which === 13 || forceSubmit) {
+			$scope.lock = true;
+
+			Authorization.login($scope.username, $scope.password).then(function(success) {
+				if(success) {
+					Site.refresh();
+					Authorization.refresh();
+					Authorization.path(true);
+				} else {
+					$.dialog({
+						title: "OPS",
+						content: "User name or password not correct."
+					}).on("hidden.bs.modal", function() {
+						$("#username").focus();
+					});
+				}
+			}).finally(function() {
+				$scope.lock = false;
+			});
+		}
+	};
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/2c3005c9/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
new file mode 100644
index 0000000..8ef2c95
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/damController.js
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+'use strict';
+
+/* Controllers */
+var damControllers = angular.module('damControllers', ['ui.bootstrap', 'eagle.components']);
+
+damControllers.service('damContent', function(Entities) {
+	var content = {
+		config: {
+			pageList: [
+				{icon: "list", title: "Policies", url: "#/dam/summary"},
+				{icon: "exclamation-triangle", title: "Alerts", url: "#/dam/alertList"},
+				{icon: "user-secret", title: "Classification", url: "#/dam/sensitivitySummary"},
+				{icon: "graduation-cap", title: "User Profiles", url: "#/dam/userProfileList"},
+				{icon: "bullseye", title: "Metadata", url: "#/dam/streamList"},
+				{icon: "server", title: "Setup", url: "#/dam/siteList", roles: ["ROLE_ADMIN"]},
+			],
+			navMapping: {
+				"Policy View": "#/dam/summary",
+				"Polict List": "#/dam/policyList",
+				"Alert List": "#/dam/alertList",
+				"User Profile": "#/dam/userProfileList",
+			},
+		},
+		updatePolicyStatus: function(policy, status) {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to " + (status ? "enable" : "disable") + " policy[" + policy.tags.policyId + "]?",
+				confirm: true,
+			}, function(ret) {
+				if(ret) {
+					policy.enabled = status;
+					Entities.updateEntity("AlertDefinitionService", policy);
+				}
+			});
+		},
+		deletePolicy: function(policy, callback) {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to delete policy[" + policy.tags.policyId + "]?",
+				confirm: true,
+			}, function(ret) {
+				if(ret) {
+					policy.enabled = status;
+					Entities.deleteEntity("AlertDefinitionService", policy)._promise.finally(function() {
+						if(callback) {
+							callback(policy);
+						}
+					});
+				}
+			});
+		},
+	};
+	return content;
+});
+
+// =============================================================
+// =                          Summary                          =
+// =============================================================
+damControllers.controller('summaryCtrl', function(globalContent, Site, damContent, $scope, $q, Entities, $route) {
+	globalContent.setConfig(damContent.config);
+	globalContent.pageSubTitle = Site.current().name;
+
+	$scope.dataSources = {};
+	$scope.dataReady = false;
+
+	var _policyList = Entities.queryGroup("AlertDefinitionService", {dataSource:null, site: Site.current().name}, "@dataSource", "count");
+
+	_policyList._promise.then(function() {
+		// List programs
+		$.each(_policyList, function(i, unit) {
+			var _dataSrc = Site.current().dataSrcList.find(unit.key[0]);
+			if(_dataSrc) {
+				_dataSrc.count = unit.value[0];
+			} else {
+				var _siteHref = $("<a href='#/dam/siteList'>").text("Setup");
+				var _dlg = $.dialog({
+					title: "Data Source Not Found",
+					content: $("<div>")
+						.append("Data Source [" + unit.key[0] + "] not found. Please check your configuration in ")
+						.append(_siteHref)
+						.append(" page.")
+				});
+				_siteHref.click(function() {
+					_dlg.modal('hide');
+				});
+			}
+		});
+
+		$scope.dataReady = true;
+	});
+});