You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@eagle.apache.org by ji...@apache.org on 2016/09/28 05:38:42 UTC

[01/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Repository: incubator-eagle
Updated Branches:
  refs/heads/master 1fa490e0c -> afb897940


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
deleted file mode 100644
index dad9f6d..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/authorizationSrv.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-	serviceModule.service('Authorization', function ($rootScope, $http, $wrapState, $q) {
-		$http.defaults.withCredentials = true;
-
-		var _promise;
-		var _path = "";
-
-		var content = {
-			isLogin: true,	// Status mark. Work for UI status check, changed when eagle api return 403 authorization failure.
-			needLogin: function () {
-				console.log("[Authorization] Need Login!");
-				if(content.isLogin) {
-					_path = _path || $wrapState.path();
-					content.isLogin = false;
-					console.log("[Authorization] Call need login. Redirect...");
-					$wrapState.go("login", 99);
-				} else {
-					console.log("[Authorization] Already login state...");
-				}
-			},
-			login: function (username, password) {
-				var _hash = btoa(username + ':' + password);
-				return $http({
-					url: app.getURL('userProfile'),
-					method: "GET",
-					headers: {
-						'Authorization': "Basic " + _hash
-					}
-				}).then(function () {
-					content.isLogin = true;
-					return true;
-				}, function () {
-					return false;
-				});
-			},
-			logout: function () {
-				$http({
-					url: app.getURL('logout'),
-					method: "GET"
-				});
-			},
-			path: function (path) {
-				if (typeof path === "string") {
-					_path = path;
-				} else if (path === true) {
-					$wrapState.path(_path || "");
-					_path = "";
-				}
-			}
-		};
-
-		content.userProfile = {};
-		content.isRole = function (role) {
-			if (!content.userProfile.roles) return null;
-
-			return content.userProfile.roles[role] === true;
-		};
-
-		content.reload = function () {
-			_promise = $http({
-				url: app.getURL('userProfile'),
-				method: "GET"
-			}).then(function (data) {
-				content.userProfile = data.data;
-
-				// Role
-				content.userProfile.roles = {};
-				$.each(content.userProfile.authorities, function (i, role) {
-					content.userProfile.roles[role.authority] = true;
-				});
-
-				return content;
-			}, function(data) {
-				if(data.status === 403) {
-					content.needLogin();
-				}
-			});
-			return _promise;
-		};
-
-		content._promise = function () {
-			if (!_promise) {
-				content.reload();
-			}
-			return _promise;
-		};
-
-		content.rolePromise = function(role, rejectState) {
-			var _deferred = $q.defer();
-			var _oriPromise = content._promise();
-			_oriPromise.then(function() {
-				if(content.isRole(role)) {
-					_deferred.resolve(content);
-				} else if(content.isLogin) {
-					_deferred.resolve(content);
-					console.log("[Authorization] go landing...");
-					$wrapState.go(rejectState || "landing");
-				} else {
-					_deferred.reject(content);
-				}
-
-				return content;
-			});
-
-			return _deferred.promise;
-		};
-
-		// Call web service to keep session
-		setInterval(function() {
-			if(!content.isLogin) return;
-
-			$http.get(app.getURL('userProfile')).then(null, function (response) {
-				if(response.status === 403) {
-					console.log("[Session] Out of date...", response);
-					content.needLogin();
-				}
-			});
-		}, 1000 * 60 * 5);
-
-		return content;
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
deleted file mode 100644
index b725054..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/entitiesSrv.js
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-	serviceModule.service('Entities', function($http, $q, $rootScope, $location, Authorization) {
-		var pkg;
-
-		// Query
-		function _query(name, kvs) {
-			kvs = kvs || {};
-			var _list = [];
-			var _condition = kvs._condition || {};
-			var _additionalCondition = _condition.additionalCondition || {};
-			var _startTime, _endTime;
-			var _startTimeStr, _endTimeStr;
-
-			// Initial
-			// > Condition
-			delete kvs._condition;
-			if(_condition) {
-				kvs.condition = _condition.condition;
-			}
-
-			// > Values
-			if(!kvs.values) {
-				kvs.values = "*";
-			} else if($.isArray(kvs.values)) {
-				kvs.values = $.map(kvs.values, function(field) {
-					return (field[0] === "@" ? '' : '@') + field;
-				}).join(",");
-			}
-
-			var _url = app.getURL(name, kvs);
-
-			// Fill special parameters
-			// > Query by time duration
-			if(_additionalCondition._duration) {
-				_endTime = app.time.now();
-				_startTime = _endTime.clone().subtract(_additionalCondition._duration, "ms");
-
-				// Debug usage. Extend more time duration for end time
-				if(_additionalCondition.__ETD) {
-					_endTime.add(_additionalCondition.__ETD, "ms");
-				}
-
-				_additionalCondition._startTime = _startTime;
-				_additionalCondition._endTime = _endTime;
-
-				_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
-				_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
-
-				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-			} else if(_additionalCondition._startTime && _additionalCondition._endTime) {
-				_startTimeStr = _additionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
-				_endTimeStr = _additionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
-
-				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-			}
-
-			// > Query contains metric name
-			if(_additionalCondition._metricName) {
-				_url += "&metricName=" + _additionalCondition._metricName;
-			}
-
-			// > Customize page size
-			if(_additionalCondition._pageSize) {
-				_url = _url.replace(/pageSize=\d+/, "pageSize=" + _additionalCondition._pageSize);
-			}
-
-			// AJAX
-			var canceler = $q.defer();
-			_list._promise = $http.get(_url, {timeout: canceler.promise}).then(function(status) {
-				_list.push.apply(_list, status.data.obj);
-				return _list;
-			});
-			_list._promise.abort = function() {
-				canceler.resolve();
-			};
-
-			_list._promise.then(function() {}, function(data) {
-				if(data.status === 403) {
-					Authorization.needLogin();
-				}
-			});
-
-			return _list;
-		}
-		function _post(url, entities) {
-			var _list = [];
-			_list._promise = $http({
-				method: 'POST',
-				url: url,
-				headers: {
-					"Content-Type": "application/json"
-				},
-				data: entities
-			}).success(function(data) {
-				_list.push.apply(_list, data.obj);
-			});
-			return _list;
-		}
-		function _delete(url) {
-			var _list = [];
-			_list._promise = $http({
-				method: 'DELETE',
-				url: url,
-				headers: {
-					"Content-Type": "application/json"
-				}
-			}).success(function(data) {
-				_list.push.apply(_list, data.obj);
-			});
-			return _list;
-		}
-		function _get(url) {
-			var _list = [];
-			_list._promise = $http({
-				method: 'GET',
-				url: url,
-				headers: {
-					"Content-Type": "text/plain"
-				}
-			}).success(function(data) {
-				// console.log(data);
-				_list.push.apply(_list, data.obj);
-			});
-			return _list;
-		}
-		function ParseCondition(condition) {
-			var _this = this;
-			_this.condition = "";
-			_this.additionalCondition = {};
-
-			if(typeof condition === "string") {
-				_this.condition = condition;
-			} else {
-				_this.condition = $.map(condition, function(value, key) {
-					if(!key.match(/^_/)) {
-						if(value === undefined || value === null) {
-							return '@' + key + '=~".*"';
-						} else {
-							return '@' + key + '="' + value + '"';
-						}
-					} else {
-						_this.additionalCondition[key] = value;
-						return null;
-					}
-				}).join(" AND ");
-			}
-			return _this;
-		}
-
-		pkg = {
-			_query: _query,
-			_post: _post,
-
-			maprfsNameToID: function(serviceName, value, site) {
-				//var _url = "../rest/maprIDResolver/fNameResolver?fName="+name;
-				var _url = app.getMapRNameResolverURL(serviceName, value, site);
-				return _get(_url);
-			},
-
-			updateEntity: function(serviceName, entities, config) {
-				var _url;
-				config = config || {};
-				if(!$.isArray(entities)) entities = [entities];
-
-				// Post clone entities
-				var _entities = $.map(entities, function(entity) {
-					var _entity = {};
-
-					// Clone variables
-					$.each(entity, function(key) {
-						// Skip inner variables
-						if(!key.match(/^__/)) {
-							_entity[key] = entity[key];
-						}
-					});
-
-					// Add timestamp
-					if(config.timestamp !== false) {
-						if(config.createTime !== false && !_entity.createdTime) {
-							_entity.createdTime = new moment().valueOf();
-						}
-						if(config.lastModifiedDate !== false) {
-							_entity.lastModifiedDate = new moment().valueOf();
-						}
-					}
-
-					return _entity;
-				});
-
-				// Check for url hook
-				if(config.hook) {
-					_url = app.getUpdateURL(serviceName) || app.packageURL(serviceName);
-				} else {
-					_url = app.getURL("updateEntity", {serviceName: serviceName});
-				}
-
-				return _post(_url, _entities);
-			},
-
-			deleteEntity: function(serviceName, entities) {
-				if (!$.isArray(entities)) entities = [entities];
-
-				var _entities = $.map(entities, function (entity) {
-					return typeof entity === "object" ? entity.encodedRowkey : entity;
-				});
-				return _post(app.getURL("deleteEntity", {serviceName: serviceName}), _entities);
-			},
-			deleteEntities: function(serviceName, condition) {
-				return _delete(app.getURL("deleteEntities", {serviceName: serviceName, condition: new ParseCondition(condition).condition}));
-			},
-			delete: function(serviceName, kvs) {
-				var _deleteURL = app.getDeleteURL(serviceName);
-				return _delete(common.template(_deleteURL, kvs));
-			},
-
-			queryEntity: function(serviceName, encodedRowkey) {
-				return _query("queryEntity", {serviceName: serviceName, encodedRowkey: encodedRowkey});
-			},
-			queryEntities: function(serviceName, condition, fields) {
-				return _query("queryEntities", {serviceName: serviceName, _condition: new ParseCondition(condition), values: fields});
-			},
-			queryGroup: function(serviceName, condition, groupBy, fields) {
-				return _query("queryGroup", {serviceName: serviceName, _condition: new ParseCondition(condition), groupBy: groupBy, values: fields});
-			},
-			querySeries: function(serviceName, condition, groupBy, fields, intervalmin) {
-				var _cond = new ParseCondition(condition);
-				var _list = _query("querySeries", {serviceName: serviceName, _condition: _cond, groupBy: groupBy, values: fields, intervalmin: intervalmin});
-				_list._promise.then(function() {
-					if(_list.length === 0) {
-						_list._empty = true;
-						_list._convert = true;
-
-						for(var i = 0; i <= (_cond.additionalCondition._endTime.valueOf() - _cond.additionalCondition._startTime.valueOf()) / (1000 * 60 * intervalmin); i += 1) {
-							_list.push(0);
-						}
-					} else if(_list.length === 1) {
-						_list._convert = true;
-						var _unit = _list.pop();
-						_list.push.apply(_list, _unit.value[0]);
-					}
-
-					if(_list._convert) {
-						var _current = _cond.additionalCondition._startTime.clone();
-						$.each(_list, function(i, value) {
-							_list[i] = {
-								x: _current.valueOf(),
-								y: value
-							};
-							_current.add(intervalmin, "m");
-						});
-					}
-				});
-				return _list;
-			},
-
-			query: function(path, params) {
-				var _list = [];
-				_list._promise = $http({
-					method: 'GET',
-					url: app.getURL("query") + path,
-					params: params
-				}).success(function(data) {
-					_list.push.apply(_list, data.obj);
-				});
-				return _list;
-			},
-
-			dialog: function(data, callback) {
-				if(data.success === false || (data.exception || "").trim()) {
-					return $.dialog({
-						title: "OPS",
-						content: $("<pre>").html(data.exception)
-					}, callback);
-				}
-				return false;
-			}
-		};
-		return pkg;
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/main.js b/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
deleted file mode 100644
index 4f5a72a..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/main.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var eagleSrv = angular.module('eagle.service', []);
-
-	eagleSrv.provider('ServiceError', function() {
-		var errorContainer = {
-			list: [],
-			newError: function(err) {
-				err._read = false;
-				errorContainer.list.unshift(err);
-			},
-			showError: function(err) {
-				err._read = true;
-				$.dialog({
-					size: "large",
-					title: err.title,
-					content: $("<pre>").html(err.description)
-				});
-			},
-			clearAll: function() {
-				errorContainer.list = [];
-			}
-		};
-
-		Object.defineProperty(errorContainer, 'hasUnread', {
-			get: function() {
-				return !!common.array.find(false, errorContainer.list, "_read");
-			}
-		});
-
-		this.$get = function() {
-			return errorContainer;
-		};
-	});
-
-	eagleSrv.config(function ($httpProvider, ServiceErrorProvider) {
-		$httpProvider.interceptors.push(function ($q, $timeout) {
-			return {
-				response: function (response) {
-					var data = response.data;
-					if(data.exception) {
-						console.log(response);
-						ServiceErrorProvider.$get().newError({
-							title: "Http Request Error",
-							description: "URL:\n" + response.config.url + "\n\nParams:\n" + JSON.stringify(response.config.params, null, "\t") + "\n\nException:\n" + data.exception
-						});
-					}
-					return response;
-				}
-			};
-		});
-	});
-})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
deleted file mode 100644
index e59d8a3..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/pageSrv.js
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-
-	// ===========================================================
-	// =                         Service                         =
-	// ===========================================================
-	// Feature page
-	serviceModule.service('PageConfig', function() {
-		var _tmplConfig = {
-			pageTitle: "",
-			pageSubTitle: "",
-
-			hideSite: false,
-			lockSite: false,
-			hideApplication: false,
-			hideSidebar: false,
-			hideUser: false,
-
-			// Current page navigation path
-			navPath: [],
-
-			navConfig: {}
-		};
-
-		var PageConfig = {};
-
-		// Reset
-		PageConfig.reset = function() {
-			$.extend(PageConfig, _tmplConfig);
-			PageConfig.navPath = [];
-		};
-		PageConfig.reset();
-
-		// Create navigation path
-		PageConfig.addNavPath = function(title, path) {
-			PageConfig.navPath.push({
-				title: title,
-				path: path
-			});
-			return PageConfig;
-		};
-
-		return PageConfig;
-	});
-
-	// Feature page
-	serviceModule.service('FeaturePageConfig', function(Application) {
-		var config = {
-			// Feature mapping pages
-			_navItemMapping: {}
-		};
-
-		// Register feature controller
-		config.addNavItem = function(feature, item) {
-			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
-			_navItemList.push(item);
-		};
-
-		// Page list
-		Object.defineProperty(config, "pageList", {
-			get: function() {
-				var _app = Application.current();
-				var _list = [];
-
-				if(_app && _app.features) {
-					$.each(_app.features, function(i, featureName) {
-						_list = _list.concat(config._navItemMapping[featureName] || []);
-					});
-				}
-
-				return _list;
-			}
-		});
-
-		return config;
-	});
-
-	// Configuration page
-	serviceModule.service('ConfigPageConfig', function(Application) {
-		var _originPageList = [
-			{icon: "server", title: "Sites", url: "#/config/site"},
-			{icon: "cubes", title: "Applications", url: "#/config/application"},
-			{icon: "leaf", title: "Features", url: "#/config/feature"}
-		];
-
-		var config = {
-			_navItemMapping: {}
-		};
-
-		// Register feature controller
-		config.addNavItem = function(feature, item) {
-			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
-			_navItemList.push(item);
-		};
-
-		// Page list
-		Object.defineProperty(config, "pageList", {
-			get: function() {
-				var _list = _originPageList;
-
-				$.each(Application.featureList, function(i, feature) {
-					_list = _list.concat(config._navItemMapping[feature.tags.feature] || []);
-				});
-
-				return _list;
-			}
-		});
-
-		return config;
-	});
-})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
deleted file mode 100644
index fce64c0..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/siteSrv.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-	serviceModule.service('Site', function($rootScope, $wrapState, $location, $q, Entities, Application) {
-		var _currentSite;
-		var Site = {};
-		var _promise;
-
-		Site.list = [];
-		Site.list.set = {};
-
-		Site.current = function(site) {
-			if(site) {
-				var _prev = _currentSite;
-				_currentSite = site;
-
-				// Keep current site and reload page
-				if(!_prev || _prev.tags.site !== _currentSite.tags.site) {
-					if(sessionStorage) {
-						sessionStorage.setItem("site", _currentSite.tags.site);
-					}
-
-					if(!$wrapState.current.abstract && $wrapState.current.name !== "login") {
-						console.log("[Site]", "Switch. Reload.");
-						$wrapState.reload();
-					}
-				}
-			}
-			return _currentSite;
-		};
-		Site.find = function(siteName) {
-			return common.array.find(siteName, Site.list, "tags.site");
-		};
-		Site.url = function(site, url) {
-			console.warn("[Site] Site.url is a deprecated function.");
-			if(arguments.length == 1) {
-				url = site;
-			} else {
-				Site.current(site);
-			}
-			$wrapState.url(url);
-
-			if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
-				$rootScope.$apply();
-			}
-		};
-
-		Site.currentSiteApplication = function() {
-			var _app = Application.current();
-			if(!_app) return null;
-
-			return _currentSite.applicationList.set[_app.tags.application];
-		};
-
-		Site.reload = function() {
-			var _applicationList;
-
-			if(Site.list && Site.list._promise) Site.list._promise.abort();
-
-			Site.list = Entities.queryEntities("SiteDescService", '');
-			Site.list.set = {};
-			_applicationList = Entities.queryEntities("SiteApplicationService", '');
-
-			_promise = $q.all([Site.list._promise, _applicationList._promise, Application._promise()]).then(function() {
-				// Fill site set
-				$.each(Site.list, function(i, site) {
-					var _list = [];
-					var _appGrp = {};
-					var _appGrpList = [];
-					_list.set = {};
-					Site.list.set[site.tags.site] = site;
-
-					// Find application
-					_list.find = function(applicationName) {
-						return common.array.find(applicationName, _list, "tags.application");
-					};
-
-					// Define properties
-					Object.defineProperties(site, {
-						applicationList: {
-							get: function() {
-								return _list;
-							}
-						},
-						applicationGroup: {
-							get: function() {
-								return _appGrp;
-							}
-						},
-						applicationGroupList: {
-							get: function() {
-								return _appGrpList;
-							}
-						}
-					});
-				});
-
-				// Fill site application mapping
-				$.each(_applicationList, function(i, siteApplication) {
-					var _site = Site.list.set[siteApplication.tags.site];
-					var _application = Application.find(siteApplication.tags.application);
-					var _appGroup, _configObj;
-
-					if(!_site) {
-						console.warn("[Site] Application not match site:", siteApplication.tags.site, "-", siteApplication.tags.application);
-					} else if(!_application) {
-						console.warn("[Site] Application not found:", siteApplication.tags.site, "-", siteApplication.tags.application);
-					} else {
-						_configObj = common.properties.parse(siteApplication.config, {});
-						Object.defineProperties(siteApplication, {
-							application: {
-								get: function () {
-									return _application;
-								}
-							},
-							configObj: {
-								get: function () {
-									return _configObj;
-								}
-							}
-						});
-
-						_site.applicationList.push(siteApplication);
-						_site.applicationList.set[siteApplication.tags.application] = siteApplication;
-
-						_appGroup = _site.applicationGroup[_application.group] = _site.applicationGroup[_application.group] || [];
-						_appGroup.push(_application);
-					}
-				});
-
-				// Fill site application group attributes
-				$.each(Site.list, function(i, site) {
-					$.each(site.applicationGroup, function(grpName, grpList) {
-						var grp = {
-							name: grpName,
-							list: grpList,
-							enabledList: $.grep(grpList, function(application) {return site.applicationList.set[application.tags.application].enabled;}),
-							disabledList: $.grep(grpList, function(application) {return !site.applicationList.set[application.tags.application].enabled;})
-						};
-
-						site.applicationGroupList.push(grp);
-					});
-
-					site.applicationGroupList.sort(function(a, b) {
-						if(a.name === b.name) return 0;
-						if(a.name === "Others") return 1;
-						if(b.name === "Others") return -1;
-						return a.name < b.name ? -1 : 1;
-					});
-				});
-
-				// Set current site
-				if(sessionStorage && Site.find(sessionStorage.getItem("site"))) {
-					Site.current(Site.find(sessionStorage.getItem("site")));
-				} else {
-					Site.current(Site.list[0]);
-				}
-
-				return Site;
-			});
-
-			return _promise;
-		};
-
-		Site._promise = function() {
-			if(!_promise) {
-				Site.reload();
-			}
-			return _promise;
-		};
-
-		return Site;
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
deleted file mode 100644
index 882e179..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/uiSrv.js
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-
-	// ===========================================================
-	// =                         Service                         =
-	// ===========================================================
-	// Feature page
-	serviceModule.service('UI', function($rootScope, $q, $compile) {
-		var UI = {};
-
-		function _bindShortcut($dialog) {
-			$dialog.on("keydown", function (event) {
-				if(event.which === 13) {
-					if(!$(":focus").is("textarea")) {
-						$dialog.find(".confirmBtn:enabled").click();
-					}
-				}
-			});
-		}
-
-		function _fieldDialog(create, name, entity, fieldList, checkFunc) {
-			var _deferred, $mdl, $scope;
-
-			_deferred = $q.defer();
-			$scope = $rootScope.$new(true);
-			$scope.name = name;
-			$scope.entity = entity;
-			$scope.fieldList = fieldList;
-			$scope.checkFunc = checkFunc;
-			$scope.lock = false;
-			$scope.create = create;
-
-			$scope.config = typeof name === "object" ? name : {};
-
-			// Modal
-			$mdl = $(TMPL_FIELDS).appendTo('body');
-			$compile($mdl)($scope);
-			$mdl.modal();
-
-			$mdl.on("hide.bs.modal", function() {
-				_deferred.reject();
-			});
-			$mdl.on("hidden.bs.modal", function() {
-				_deferred.resolve({
-					entity: entity
-				});
-				$mdl.remove();
-			});
-
-			// Function
-			$scope.getFieldDescription = function (field) {
-				if(typeof field.description === "function") {
-					return field.description($scope.entity);
-				}
-				return field.description || ((field.name || field.field) + '...');
-			};
-
-			$scope.emptyFieldList = function() {
-				return $.map(fieldList, function(field) {
-					if(!field.optional && !entity[field.field]) {
-						return field.field;
-					}
-				});
-			};
-
-			$scope.confirm = function() {
-				$scope.lock = true;
-				_deferred.notify({
-					entity: entity,
-					closeFunc: function() {
-						$mdl.modal('hide');
-					},
-					unlock: function() {
-						$scope.lock = false;
-					}
-				});
-			};
-
-			_bindShortcut($mdl);
-
-			return _deferred.promise;
-		}
-
-		/***
-		 * Create a creation confirm modal.
-		 * @param name			Name title
-		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional: number), description(optional), optional(optional), readonly(optional), valueList(optional)}
-		 * @param checkFunc	Check logic function. Return string will prevent access
-		 */
-		UI.createConfirm = function(name, entity, fieldList, checkFunc) {
-			return _fieldDialog(true, name, entity, fieldList, checkFunc);
-		};
-
-		/***
-		 * Create a update confirm modal.
-		 * @param name			Name title
-		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional: number), description(optional), optional(optional), readonly(optional), valueList(optional)}
-		 * @param checkFunc	Check logic function. Return string will prevent access
-		 */
-		UI.updateConfirm = function(name, entity, fieldList, checkFunc) {
-			return _fieldDialog(false, name, entity, fieldList, checkFunc);
-		};
-
-		/***
-		 * Create a customize field confirm modal.
-		 * @param config		Configuration object
-		 * 			@param config.title			Title of dialog box
-		 * 			@param config.size				"large". Set dialog size
-		 * 			@param config.confirm			Boolean. Display or not confirm button
-		 * 			@param config.confirmDesc		Confirm button display description
-		 * @param entity		bind entity
-		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional: number), description(optional), optional(optional), readonly(optional), valueList(optional)}
-		 * @param checkFunc	Check logic function. Return string will prevent access
-		 */
-		UI.fieldConfirm = function(config, entity, fieldList, checkFunc) {
-			return _fieldDialog("field", config, entity, fieldList, checkFunc);
-		};
-
-		UI.deleteConfirm = function(name) {
-			var _deferred, $mdl, $scope;
-
-			_deferred = $q.defer();
-			$scope = $rootScope.$new(true);
-			$scope.name = name;
-			$scope.lock = false;
-
-			// Modal
-			$mdl = $(TMPL_DELETE).appendTo('body');
-			$compile($mdl)($scope);
-			$mdl.modal();
-
-			$mdl.on("hide.bs.modal", function() {
-				_deferred.reject();
-			});
-			$mdl.on("hidden.bs.modal", function() {
-				_deferred.resolve({
-					name: name
-				});
-				$mdl.remove();
-			});
-
-			// Function
-			$scope.delete = function() {
-				$scope.lock = true;
-				_deferred.notify({
-					name: name,
-					closeFunc: function() {
-						$mdl.modal('hide');
-					},
-					unlock: function() {
-						$scope.lock = false;
-					}
-				});
-			};
-
-			return _deferred.promise;
-		};
-
-		return UI;
-	});
-
-	// ===========================================================
-	// =                         Template                        =
-	// ===========================================================
-	var TMPL_FIELDS =
-		'<div class="modal fade" tabindex="-1" role="dialog">' +
-			'<div class="modal-dialog" ng-class="{\'modal-lg\': config.size === \'large\'}" role="document">' +
-				'<div class="modal-content">' +
-					'<div class="modal-header">' +
-						'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
-							'<span aria-hidden="true">&times;</span>' +
-						'</button>' +
-						'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
-					'</div>' +
-					'<div class="modal-body">' +
-						'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
-							'<label for="featureName">' +
-								'<span ng-if="!field.optional">*</span> ' +
-								'{{field.name || field.field}}' +
-							'</label>' +
-							'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
-							'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field] = entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
-								'<option ng-repeat="value in field.valueList">{{value}}</option>' +
-							'</select>' +
-							'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>' +
-						'</div>' +
-					'</div>' +
-					'<div class="modal-footer">' +
-						'<p class="pull-left text-danger">{{checkFunc(entity)}}</p>' +
-						'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Close</button>' +
-						'<button type="button" class="btn btn-primary confirmBtn" ng-click="confirm()" ng-disabled="checkFunc(entity) || emptyFieldList().length || lock" ng-if="config.confirm !== false">' +
-							'{{config.confirmDesc || (create ? "Create" : "Update")}}' +
-						'</button>' +
-					'</div>' +
-				'</div>' +
-			'</div>' +
-		'</div>';
-
-	var TMPL_DELETE =
-		'<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">' +
-			'<div class="modal-dialog">' +
-				'<div class="modal-content">' +
-					'<div class="modal-header">' +
-						'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
-						'<h4 class="modal-title">Delete Confirm</h4></div>' +
-						'<div class="modal-body">' +
-							'<span class="text-red fa fa-exclamation-triangle pull-left" style="font-size: 50px;"></span>' +
-							'<p>You are <strong class="text-red">DELETING</strong> \'{{name}}\'!</p>' +
-							'<p>Proceed to delete?</p>' +
-						'</div>' +
-						'<div class="modal-footer">' +
-							'<button type="button" class="btn btn-danger" ng-click="delete()" ng-disabled="lock">Delete</button>' +
-							'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Cancel</button>' +
-						'</div>' +
-				'</div>' +
-			'</div>' +
-		'</div>';
-})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
deleted file mode 100644
index 57872b2..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/wrapStateSrv.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-	serviceModule.service('$wrapState', function($state, $location, $stateParams) {
-		var $wrapState = {};
-		var _targetState = null;
-		var _targetPriority = 0;
-
-		// Go
-		$wrapState.go = function(state, param, priority) {
-			setTimeout(function() {
-				_targetState = null;
-				_targetPriority = 0;
-			});
-
-			if(typeof param !== "object") {
-				param = {};
-				priority = param;
-			}
-
-			priority = priority === true ? 1 : (priority || 0);
-			if(_targetPriority > priority) {
-				console.log("[Wrap State] Go - low priority:", state, "(Skip)");
-				return false;
-			}
-
-			if(_targetState !== state || priority) {
-				if($state.current && $state.current.name === state && angular.equals($state.params, param)) {
-					console.log($state);
-					console.log("[Wrap State] Go reload.");
-					$state.reload();
-				} else {
-					console.log("[Wrap State] Go:", state, param, priority);
-					$state.go(state, param);
-				}
-				_targetState = state;
-				_targetPriority = priority;
-				return true;
-			} else {
-				console.log("[Wrap State] Go:", state, "(Ignored)");
-			}
-			return false;
-		};
-
-		// Reload
-		$wrapState.reload = function() {
-			console.log("[Wrap State] Do reload.");
-			$state.reload();
-		};
-
-		// Path
-		$wrapState.path = function(path) {
-			if(path !== undefined) {
-				console.log("[Wrap State][Deprecated] Switch path:", path);
-			}
-			return $location.path(path);
-		};
-
-		// URL
-		$wrapState.url = function(url) {
-			if(url !== undefined) console.log("[Wrap State] Switch url:", url);
-			return $location.url(url);
-		};
-
-		Object.defineProperties($wrapState, {
-			// Origin $state
-			origin: {
-				get: function() {
-					return $state;
-				}
-			},
-
-			// Current
-			current: {
-				get: function() {
-					return $state.current;
-				}
-			},
-
-			// Parameter
-			param: {
-				get: function() {
-					return $.extend({}, $location.search(), $stateParams);
-				}
-			}
-		});
-
-		return $wrapState;
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/grunt.json
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/grunt.json b/eagle-webservice/src/main/webapp/grunt.json
deleted file mode 100644
index 15f58d9..0000000
--- a/eagle-webservice/src/main/webapp/grunt.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
-	"concat": {
-		"js": {
-			"options": {
-				"separator": "\n"
-			},
-			"src": [
-				"node_modules/jquery/dist/jquery.min.js",
-				"node_modules/jquery-slimscroll/jquery.slimscroll.min.js",
-				"node_modules/bootstrap/dist/js/bootstrap.min.js",
-				"node_modules/zombiej-bootstrap-components/bootstrap-components/js/bootstrap-components.min.js",
-				"node_modules/moment/min/moment-with-locales.min.js",
-				"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
-				"node_modules/admin-lte/dist/js/app.min.js",
-				"node_modules/angular/angular.min.js",
-				"node_modules/angular-resource/angular-resource.min.js",
-				"node_modules/angular-route/angular-route.min.js",
-				"node_modules/angular-animate/angular-animate.min.js",
-				"node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js",
-				"node_modules/angular-ui-router/release/angular-ui-router.min.js",
-				"node_modules/d3/d3.min.js",
-				"node_modules/zombiej-nvd3/build/nv.d3.min.js",
-
-				"tmp/public/js/scripts.min.js"
-			],
-			"dest": "tmp/public/js/doc.js"
-		},
-		"css": {
-			"src": [
-				"node_modules/bootstrap/dist/css/bootstrap.min.css",
-				"node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.min.css",
-				"node_modules/zombiej-nvd3/build/nv.d3.min.css",
-				"node_modules/font-awesome/css/font-awesome.min.css",
-				"node_modules/admin-lte/dist/css/AdminLTE.min.css",
-				"node_modules/admin-lte/dist/css/skins/skin-blue.min.css",
-
-				"tmp/public/css/styles.css"
-			],
-			"dest": "tmp/public/css/styles.min.css"
-		}
-	}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/index.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/index.html b/eagle-webservice/src/main/webapp/index.html
deleted file mode 100755
index 831f3f0..0000000
--- a/eagle-webservice/src/main/webapp/index.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!DOCTYPE html>
-<!--
-  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.
-  -->
-
-<html>
-	<head>
-		<script>
-			window.location.href = "ui";
-		</script>
-	</head>
-	<body>
-	</body>
-</html>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/package.json
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/package.json b/eagle-webservice/src/main/webapp/package.json
deleted file mode 100644
index 1db0bbb..0000000
--- a/eagle-webservice/src/main/webapp/package.json
+++ /dev/null
@@ -1,48 +0,0 @@
-{
-	"name": "ApacheEagleWebApp",
-	"description": "Apache Eagle Web Application",
-	"author": "ApacheEagle",
-	"repository": {
-		"type:": "git",
-		"url": "https://github.com/apache/incubator-eagle.git"
-	},
-	"license": "Apache-2.0",
-	"dependencies": {
-		"jquery"				: "1.12.0",
-		"bootstrap"				: "3.3.6",
-		"moment"				: "2.11.2",
-		"moment-timezone"		: "0.5.0",
-		"font-awesome"			: "4.5.0",
-		"admin-lte"				: "2.3.2",
-		"angular"				: "1.5.0",
-		"angular-resource"		: "1.5.0",
-		"angular-route"			: "1.5.0",
-		"angular-cookies"		: "1.5.0",
-		"angular-animate"		: "1.5.0",
-		"angular-ui-bootstrap"	: "1.1.2",
-		"angular-ui-router"		: "~0.2.18",
-		"d3"					: "3.5.16",
-		"zombiej-nvd3"			: "1.8.2-3",
-		"jquery-slimscroll"		:"1.3.6",
-		"zombiej-bootstrap-components"		: "1.1.1"
-	},
-
-	"devDependencies": {
-		"grunt": "~0.4.5",
-		"grunt-cli": "~0.1.13",
-
-		"grunt-contrib-jshint": "~0.11.3",
-
-		"grunt-regex-replace": "~0.2.6",
-		"grunt-contrib-clean": "~0.7.0",
-		"grunt-contrib-uglify": "~0.5.0",
-		"grunt-contrib-concat": "~0.5.1",
-		"grunt-contrib-cssmin": "~0.14.0",
-		"grunt-contrib-copy": "~0.8.2",
-		"grunt-htmlrefs": "~0.5.0"
-	},
-
-	"scripts": {
-		"grunt": "grunt"
-	}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/ui-build.sh
----------------------------------------------------------------------
diff --git a/eagle-webservice/ui-build.sh b/eagle-webservice/ui-build.sh
index 9e4f75c..4ca13eb 100644
--- a/eagle-webservice/ui-build.sh
+++ b/eagle-webservice/ui-build.sh
@@ -16,25 +16,4 @@
 # limitations under the License.
 
 echo "=============== Web APP Building Start ==============="
-echo "Environment Check..."
-# Pre-build check
-if [ -z "$(command -v git)" ]
-then
-	echo "git not installed!"
-	exit 1
-fi
-if [ -z "$(command -v npm)" ]
-then
-	echo "npm not installed!"
-	exit 1
-fi
-echo "Environment Check...Pass"
-
-# npm install
-cd src/main/webapp
-echo "npm install..."
-npm install
-
-# grunt build
-echo "grunt building..."
-npm run grunt
+echo "Environment Check..."
\ No newline at end of file



[06/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/authorizationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/authorizationSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/authorizationSrv.js
new file mode 100644
index 0000000..dad9f6d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/authorizationSrv.js
@@ -0,0 +1,143 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Authorization', function ($rootScope, $http, $wrapState, $q) {
+		$http.defaults.withCredentials = true;
+
+		var _promise;
+		var _path = "";
+
+		var content = {
+			isLogin: true,	// Status mark. Work for UI status check, changed when eagle api return 403 authorization failure.
+			needLogin: function () {
+				console.log("[Authorization] Need Login!");
+				if(content.isLogin) {
+					_path = _path || $wrapState.path();
+					content.isLogin = false;
+					console.log("[Authorization] Call need login. Redirect...");
+					$wrapState.go("login", 99);
+				} else {
+					console.log("[Authorization] Already login state...");
+				}
+			},
+			login: function (username, password) {
+				var _hash = btoa(username + ':' + password);
+				return $http({
+					url: app.getURL('userProfile'),
+					method: "GET",
+					headers: {
+						'Authorization': "Basic " + _hash
+					}
+				}).then(function () {
+					content.isLogin = true;
+					return true;
+				}, function () {
+					return false;
+				});
+			},
+			logout: function () {
+				$http({
+					url: app.getURL('logout'),
+					method: "GET"
+				});
+			},
+			path: function (path) {
+				if (typeof path === "string") {
+					_path = path;
+				} else if (path === true) {
+					$wrapState.path(_path || "");
+					_path = "";
+				}
+			}
+		};
+
+		content.userProfile = {};
+		content.isRole = function (role) {
+			if (!content.userProfile.roles) return null;
+
+			return content.userProfile.roles[role] === true;
+		};
+
+		content.reload = function () {
+			_promise = $http({
+				url: app.getURL('userProfile'),
+				method: "GET"
+			}).then(function (data) {
+				content.userProfile = data.data;
+
+				// Role
+				content.userProfile.roles = {};
+				$.each(content.userProfile.authorities, function (i, role) {
+					content.userProfile.roles[role.authority] = true;
+				});
+
+				return content;
+			}, function(data) {
+				if(data.status === 403) {
+					content.needLogin();
+				}
+			});
+			return _promise;
+		};
+
+		content._promise = function () {
+			if (!_promise) {
+				content.reload();
+			}
+			return _promise;
+		};
+
+		content.rolePromise = function(role, rejectState) {
+			var _deferred = $q.defer();
+			var _oriPromise = content._promise();
+			_oriPromise.then(function() {
+				if(content.isRole(role)) {
+					_deferred.resolve(content);
+				} else if(content.isLogin) {
+					_deferred.resolve(content);
+					console.log("[Authorization] go landing...");
+					$wrapState.go(rejectState || "landing");
+				} else {
+					_deferred.reject(content);
+				}
+
+				return content;
+			});
+
+			return _deferred.promise;
+		};
+
+		// Call web service to keep session
+		setInterval(function() {
+			if(!content.isLogin) return;
+
+			$http.get(app.getURL('userProfile')).then(null, function (response) {
+				if(response.status === 403) {
+					console.log("[Session] Out of date...", response);
+					content.needLogin();
+				}
+			});
+		}, 1000 * 60 * 5);
+
+		return content;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/entitiesSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/entitiesSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/entitiesSrv.js
new file mode 100644
index 0000000..b725054
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/entitiesSrv.js
@@ -0,0 +1,301 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Entities', function($http, $q, $rootScope, $location, Authorization) {
+		var pkg;
+
+		// Query
+		function _query(name, kvs) {
+			kvs = kvs || {};
+			var _list = [];
+			var _condition = kvs._condition || {};
+			var _additionalCondition = _condition.additionalCondition || {};
+			var _startTime, _endTime;
+			var _startTimeStr, _endTimeStr;
+
+			// Initial
+			// > Condition
+			delete kvs._condition;
+			if(_condition) {
+				kvs.condition = _condition.condition;
+			}
+
+			// > Values
+			if(!kvs.values) {
+				kvs.values = "*";
+			} else if($.isArray(kvs.values)) {
+				kvs.values = $.map(kvs.values, function(field) {
+					return (field[0] === "@" ? '' : '@') + field;
+				}).join(",");
+			}
+
+			var _url = app.getURL(name, kvs);
+
+			// Fill special parameters
+			// > Query by time duration
+			if(_additionalCondition._duration) {
+				_endTime = app.time.now();
+				_startTime = _endTime.clone().subtract(_additionalCondition._duration, "ms");
+
+				// Debug usage. Extend more time duration for end time
+				if(_additionalCondition.__ETD) {
+					_endTime.add(_additionalCondition.__ETD, "ms");
+				}
+
+				_additionalCondition._startTime = _startTime;
+				_additionalCondition._endTime = _endTime;
+
+				_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
+				_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+
+				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
+			} else if(_additionalCondition._startTime && _additionalCondition._endTime) {
+				_startTimeStr = _additionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
+				_endTimeStr = _additionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+
+				_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
+			}
+
+			// > Query contains metric name
+			if(_additionalCondition._metricName) {
+				_url += "&metricName=" + _additionalCondition._metricName;
+			}
+
+			// > Customize page size
+			if(_additionalCondition._pageSize) {
+				_url = _url.replace(/pageSize=\d+/, "pageSize=" + _additionalCondition._pageSize);
+			}
+
+			// AJAX
+			var canceler = $q.defer();
+			_list._promise = $http.get(_url, {timeout: canceler.promise}).then(function(status) {
+				_list.push.apply(_list, status.data.obj);
+				return _list;
+			});
+			_list._promise.abort = function() {
+				canceler.resolve();
+			};
+
+			_list._promise.then(function() {}, function(data) {
+				if(data.status === 403) {
+					Authorization.needLogin();
+				}
+			});
+
+			return _list;
+		}
+		function _post(url, entities) {
+			var _list = [];
+			_list._promise = $http({
+				method: 'POST',
+				url: url,
+				headers: {
+					"Content-Type": "application/json"
+				},
+				data: entities
+			}).success(function(data) {
+				_list.push.apply(_list, data.obj);
+			});
+			return _list;
+		}
+		function _delete(url) {
+			var _list = [];
+			_list._promise = $http({
+				method: 'DELETE',
+				url: url,
+				headers: {
+					"Content-Type": "application/json"
+				}
+			}).success(function(data) {
+				_list.push.apply(_list, data.obj);
+			});
+			return _list;
+		}
+		function _get(url) {
+			var _list = [];
+			_list._promise = $http({
+				method: 'GET',
+				url: url,
+				headers: {
+					"Content-Type": "text/plain"
+				}
+			}).success(function(data) {
+				// console.log(data);
+				_list.push.apply(_list, data.obj);
+			});
+			return _list;
+		}
+		function ParseCondition(condition) {
+			var _this = this;
+			_this.condition = "";
+			_this.additionalCondition = {};
+
+			if(typeof condition === "string") {
+				_this.condition = condition;
+			} else {
+				_this.condition = $.map(condition, function(value, key) {
+					if(!key.match(/^_/)) {
+						if(value === undefined || value === null) {
+							return '@' + key + '=~".*"';
+						} else {
+							return '@' + key + '="' + value + '"';
+						}
+					} else {
+						_this.additionalCondition[key] = value;
+						return null;
+					}
+				}).join(" AND ");
+			}
+			return _this;
+		}
+
+		pkg = {
+			_query: _query,
+			_post: _post,
+
+			maprfsNameToID: function(serviceName, value, site) {
+				//var _url = "../rest/maprIDResolver/fNameResolver?fName="+name;
+				var _url = app.getMapRNameResolverURL(serviceName, value, site);
+				return _get(_url);
+			},
+
+			updateEntity: function(serviceName, entities, config) {
+				var _url;
+				config = config || {};
+				if(!$.isArray(entities)) entities = [entities];
+
+				// Post clone entities
+				var _entities = $.map(entities, function(entity) {
+					var _entity = {};
+
+					// Clone variables
+					$.each(entity, function(key) {
+						// Skip inner variables
+						if(!key.match(/^__/)) {
+							_entity[key] = entity[key];
+						}
+					});
+
+					// Add timestamp
+					if(config.timestamp !== false) {
+						if(config.createTime !== false && !_entity.createdTime) {
+							_entity.createdTime = new moment().valueOf();
+						}
+						if(config.lastModifiedDate !== false) {
+							_entity.lastModifiedDate = new moment().valueOf();
+						}
+					}
+
+					return _entity;
+				});
+
+				// Check for url hook
+				if(config.hook) {
+					_url = app.getUpdateURL(serviceName) || app.packageURL(serviceName);
+				} else {
+					_url = app.getURL("updateEntity", {serviceName: serviceName});
+				}
+
+				return _post(_url, _entities);
+			},
+
+			deleteEntity: function(serviceName, entities) {
+				if (!$.isArray(entities)) entities = [entities];
+
+				var _entities = $.map(entities, function (entity) {
+					return typeof entity === "object" ? entity.encodedRowkey : entity;
+				});
+				return _post(app.getURL("deleteEntity", {serviceName: serviceName}), _entities);
+			},
+			deleteEntities: function(serviceName, condition) {
+				return _delete(app.getURL("deleteEntities", {serviceName: serviceName, condition: new ParseCondition(condition).condition}));
+			},
+			delete: function(serviceName, kvs) {
+				var _deleteURL = app.getDeleteURL(serviceName);
+				return _delete(common.template(_deleteURL, kvs));
+			},
+
+			queryEntity: function(serviceName, encodedRowkey) {
+				return _query("queryEntity", {serviceName: serviceName, encodedRowkey: encodedRowkey});
+			},
+			queryEntities: function(serviceName, condition, fields) {
+				return _query("queryEntities", {serviceName: serviceName, _condition: new ParseCondition(condition), values: fields});
+			},
+			queryGroup: function(serviceName, condition, groupBy, fields) {
+				return _query("queryGroup", {serviceName: serviceName, _condition: new ParseCondition(condition), groupBy: groupBy, values: fields});
+			},
+			querySeries: function(serviceName, condition, groupBy, fields, intervalmin) {
+				var _cond = new ParseCondition(condition);
+				var _list = _query("querySeries", {serviceName: serviceName, _condition: _cond, groupBy: groupBy, values: fields, intervalmin: intervalmin});
+				_list._promise.then(function() {
+					if(_list.length === 0) {
+						_list._empty = true;
+						_list._convert = true;
+
+						for(var i = 0; i <= (_cond.additionalCondition._endTime.valueOf() - _cond.additionalCondition._startTime.valueOf()) / (1000 * 60 * intervalmin); i += 1) {
+							_list.push(0);
+						}
+					} else if(_list.length === 1) {
+						_list._convert = true;
+						var _unit = _list.pop();
+						_list.push.apply(_list, _unit.value[0]);
+					}
+
+					if(_list._convert) {
+						var _current = _cond.additionalCondition._startTime.clone();
+						$.each(_list, function(i, value) {
+							_list[i] = {
+								x: _current.valueOf(),
+								y: value
+							};
+							_current.add(intervalmin, "m");
+						});
+					}
+				});
+				return _list;
+			},
+
+			query: function(path, params) {
+				var _list = [];
+				_list._promise = $http({
+					method: 'GET',
+					url: app.getURL("query") + path,
+					params: params
+				}).success(function(data) {
+					_list.push.apply(_list, data.obj);
+				});
+				return _list;
+			},
+
+			dialog: function(data, callback) {
+				if(data.success === false || (data.exception || "").trim()) {
+					return $.dialog({
+						title: "OPS",
+						content: $("<pre>").html(data.exception)
+					}, callback);
+				}
+				return false;
+			}
+		};
+		return pkg;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/main.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/main.js
new file mode 100644
index 0000000..4f5a72a
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/main.js
@@ -0,0 +1,72 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleSrv = angular.module('eagle.service', []);
+
+	eagleSrv.provider('ServiceError', function() {
+		var errorContainer = {
+			list: [],
+			newError: function(err) {
+				err._read = false;
+				errorContainer.list.unshift(err);
+			},
+			showError: function(err) {
+				err._read = true;
+				$.dialog({
+					size: "large",
+					title: err.title,
+					content: $("<pre>").html(err.description)
+				});
+			},
+			clearAll: function() {
+				errorContainer.list = [];
+			}
+		};
+
+		Object.defineProperty(errorContainer, 'hasUnread', {
+			get: function() {
+				return !!common.array.find(false, errorContainer.list, "_read");
+			}
+		});
+
+		this.$get = function() {
+			return errorContainer;
+		};
+	});
+
+	eagleSrv.config(function ($httpProvider, ServiceErrorProvider) {
+		$httpProvider.interceptors.push(function ($q, $timeout) {
+			return {
+				response: function (response) {
+					var data = response.data;
+					if(data.exception) {
+						console.log(response);
+						ServiceErrorProvider.$get().newError({
+							title: "Http Request Error",
+							description: "URL:\n" + response.config.url + "\n\nParams:\n" + JSON.stringify(response.config.params, null, "\t") + "\n\nException:\n" + data.exception
+						});
+					}
+					return response;
+				}
+			};
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/pageSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/pageSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/pageSrv.js
new file mode 100644
index 0000000..e59d8a3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/pageSrv.js
@@ -0,0 +1,131 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	// ===========================================================
+	// =                         Service                         =
+	// ===========================================================
+	// Feature page
+	serviceModule.service('PageConfig', function() {
+		var _tmplConfig = {
+			pageTitle: "",
+			pageSubTitle: "",
+
+			hideSite: false,
+			lockSite: false,
+			hideApplication: false,
+			hideSidebar: false,
+			hideUser: false,
+
+			// Current page navigation path
+			navPath: [],
+
+			navConfig: {}
+		};
+
+		var PageConfig = {};
+
+		// Reset
+		PageConfig.reset = function() {
+			$.extend(PageConfig, _tmplConfig);
+			PageConfig.navPath = [];
+		};
+		PageConfig.reset();
+
+		// Create navigation path
+		PageConfig.addNavPath = function(title, path) {
+			PageConfig.navPath.push({
+				title: title,
+				path: path
+			});
+			return PageConfig;
+		};
+
+		return PageConfig;
+	});
+
+	// Feature page
+	serviceModule.service('FeaturePageConfig', function(Application) {
+		var config = {
+			// Feature mapping pages
+			_navItemMapping: {}
+		};
+
+		// Register feature controller
+		config.addNavItem = function(feature, item) {
+			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
+			_navItemList.push(item);
+		};
+
+		// Page list
+		Object.defineProperty(config, "pageList", {
+			get: function() {
+				var _app = Application.current();
+				var _list = [];
+
+				if(_app && _app.features) {
+					$.each(_app.features, function(i, featureName) {
+						_list = _list.concat(config._navItemMapping[featureName] || []);
+					});
+				}
+
+				return _list;
+			}
+		});
+
+		return config;
+	});
+
+	// Configuration page
+	serviceModule.service('ConfigPageConfig', function(Application) {
+		var _originPageList = [
+			{icon: "server", title: "Sites", url: "#/config/site"},
+			{icon: "cubes", title: "Applications", url: "#/config/application"},
+			{icon: "leaf", title: "Features", url: "#/config/feature"}
+		];
+
+		var config = {
+			_navItemMapping: {}
+		};
+
+		// Register feature controller
+		config.addNavItem = function(feature, item) {
+			var _navItemList = config._navItemMapping[feature] = config._navItemMapping[feature] || [];
+			_navItemList.push(item);
+		};
+
+		// Page list
+		Object.defineProperty(config, "pageList", {
+			get: function() {
+				var _list = _originPageList;
+
+				$.each(Application.featureList, function(i, feature) {
+					_list = _list.concat(config._navItemMapping[feature.tags.feature] || []);
+				});
+
+				return _list;
+			}
+		});
+
+		return config;
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/siteSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/siteSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/siteSrv.js
new file mode 100644
index 0000000..fce64c0
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/siteSrv.js
@@ -0,0 +1,193 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('Site', function($rootScope, $wrapState, $location, $q, Entities, Application) {
+		var _currentSite;
+		var Site = {};
+		var _promise;
+
+		Site.list = [];
+		Site.list.set = {};
+
+		Site.current = function(site) {
+			if(site) {
+				var _prev = _currentSite;
+				_currentSite = site;
+
+				// Keep current site and reload page
+				if(!_prev || _prev.tags.site !== _currentSite.tags.site) {
+					if(sessionStorage) {
+						sessionStorage.setItem("site", _currentSite.tags.site);
+					}
+
+					if(!$wrapState.current.abstract && $wrapState.current.name !== "login") {
+						console.log("[Site]", "Switch. Reload.");
+						$wrapState.reload();
+					}
+				}
+			}
+			return _currentSite;
+		};
+		Site.find = function(siteName) {
+			return common.array.find(siteName, Site.list, "tags.site");
+		};
+		Site.url = function(site, url) {
+			console.warn("[Site] Site.url is a deprecated function.");
+			if(arguments.length == 1) {
+				url = site;
+			} else {
+				Site.current(site);
+			}
+			$wrapState.url(url);
+
+			if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
+				$rootScope.$apply();
+			}
+		};
+
+		Site.currentSiteApplication = function() {
+			var _app = Application.current();
+			if(!_app) return null;
+
+			return _currentSite.applicationList.set[_app.tags.application];
+		};
+
+		Site.reload = function() {
+			var _applicationList;
+
+			if(Site.list && Site.list._promise) Site.list._promise.abort();
+
+			Site.list = Entities.queryEntities("SiteDescService", '');
+			Site.list.set = {};
+			_applicationList = Entities.queryEntities("SiteApplicationService", '');
+
+			_promise = $q.all([Site.list._promise, _applicationList._promise, Application._promise()]).then(function() {
+				// Fill site set
+				$.each(Site.list, function(i, site) {
+					var _list = [];
+					var _appGrp = {};
+					var _appGrpList = [];
+					_list.set = {};
+					Site.list.set[site.tags.site] = site;
+
+					// Find application
+					_list.find = function(applicationName) {
+						return common.array.find(applicationName, _list, "tags.application");
+					};
+
+					// Define properties
+					Object.defineProperties(site, {
+						applicationList: {
+							get: function() {
+								return _list;
+							}
+						},
+						applicationGroup: {
+							get: function() {
+								return _appGrp;
+							}
+						},
+						applicationGroupList: {
+							get: function() {
+								return _appGrpList;
+							}
+						}
+					});
+				});
+
+				// Fill site application mapping
+				$.each(_applicationList, function(i, siteApplication) {
+					var _site = Site.list.set[siteApplication.tags.site];
+					var _application = Application.find(siteApplication.tags.application);
+					var _appGroup, _configObj;
+
+					if(!_site) {
+						console.warn("[Site] Application not match site:", siteApplication.tags.site, "-", siteApplication.tags.application);
+					} else if(!_application) {
+						console.warn("[Site] Application not found:", siteApplication.tags.site, "-", siteApplication.tags.application);
+					} else {
+						_configObj = common.properties.parse(siteApplication.config, {});
+						Object.defineProperties(siteApplication, {
+							application: {
+								get: function () {
+									return _application;
+								}
+							},
+							configObj: {
+								get: function () {
+									return _configObj;
+								}
+							}
+						});
+
+						_site.applicationList.push(siteApplication);
+						_site.applicationList.set[siteApplication.tags.application] = siteApplication;
+
+						_appGroup = _site.applicationGroup[_application.group] = _site.applicationGroup[_application.group] || [];
+						_appGroup.push(_application);
+					}
+				});
+
+				// Fill site application group attributes
+				$.each(Site.list, function(i, site) {
+					$.each(site.applicationGroup, function(grpName, grpList) {
+						var grp = {
+							name: grpName,
+							list: grpList,
+							enabledList: $.grep(grpList, function(application) {return site.applicationList.set[application.tags.application].enabled;}),
+							disabledList: $.grep(grpList, function(application) {return !site.applicationList.set[application.tags.application].enabled;})
+						};
+
+						site.applicationGroupList.push(grp);
+					});
+
+					site.applicationGroupList.sort(function(a, b) {
+						if(a.name === b.name) return 0;
+						if(a.name === "Others") return 1;
+						if(b.name === "Others") return -1;
+						return a.name < b.name ? -1 : 1;
+					});
+				});
+
+				// Set current site
+				if(sessionStorage && Site.find(sessionStorage.getItem("site"))) {
+					Site.current(Site.find(sessionStorage.getItem("site")));
+				} else {
+					Site.current(Site.list[0]);
+				}
+
+				return Site;
+			});
+
+			return _promise;
+		};
+
+		Site._promise = function() {
+			if(!_promise) {
+				Site.reload();
+			}
+			return _promise;
+		};
+
+		return Site;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/uiSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/uiSrv.js
new file mode 100644
index 0000000..9955fac
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/uiSrv.js
@@ -0,0 +1,247 @@
+/*
+ * 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() {
+	'use strict';
+
+	/**
+	 * Check function to check fields pass or not
+	 * @callback checkFieldFunction
+	 * @param {{}} entity
+	 * @return {string}
+     */
+
+	var serviceModule = angular.module('eagle.service');
+
+	// ===========================================================
+	// =                         Service                         =
+	// ===========================================================
+	// Feature page
+	serviceModule.service('UI', function($rootScope, $q, $compile) {
+		var UI = {};
+
+		function _bindShortcut($dialog) {
+			$dialog.on("keydown", function (event) {
+				if(event.which === 13) {
+					if(!$(":focus").is("textarea")) {
+						$dialog.find(".confirmBtn:enabled").click();
+					}
+				}
+			});
+		}
+
+		function _fieldDialog(create, name, entity, fieldList, checkFunc) {
+			var _deferred, $mdl, $scope;
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.entity = entity;
+			$scope.fieldList = fieldList;
+			$scope.checkFunc = checkFunc;
+			$scope.lock = false;
+			$scope.create = create;
+
+			$scope.config = typeof name === "object" ? name : {};
+
+			// Modal
+			$mdl = $(TMPL_FIELDS).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					entity: entity
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.getFieldDescription = function (field) {
+				if(typeof field.description === "function") {
+					return field.description($scope.entity);
+				}
+				return field.description || ((field.name || field.field) + '...');
+			};
+
+			$scope.emptyFieldList = function() {
+				return $.map(fieldList, function(field) {
+					if(!field.optional && !entity[field.field]) {
+						return field.field;
+					}
+				});
+			};
+
+			$scope.confirm = function() {
+				$scope.lock = true;
+				_deferred.notify({
+					entity: entity,
+					closeFunc: function() {
+						$mdl.modal('hide');
+					},
+					unlock: function() {
+						$scope.lock = false;
+					}
+				});
+			};
+
+			_bindShortcut($mdl);
+
+			return _deferred.promise;
+		}
+
+		/***
+		 * Create a creation confirm modal.
+		 * @param name			Name title
+		 * @param entity		bind entity
+		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional: number), description(optional), optional(optional), readonly(optional), valueList(optional)}
+		 * @param checkFunc	Check logic function. Return string will prevent access
+		 */
+		UI.createConfirm = function(name, entity, fieldList, checkFunc) {
+			return _fieldDialog(true, name, entity, fieldList, checkFunc);
+		};
+
+		/***
+		 * Create a update confirm modal.
+		 * @param name			Name title
+		 * @param entity		bind entity
+		 * @param fieldList	Array. Format: {name, field, type(optional: select, blob), rows(optional: number), description(optional), optional(optional), readonly(optional), valueList(optional)}
+		 * @param checkFunc	Check logic function. Return string will prevent access
+		 */
+		UI.updateConfirm = function(name, entity, fieldList, checkFunc) {
+			return _fieldDialog(false, name, entity, fieldList, checkFunc);
+		};
+
+		/***
+		 * Create a customize field confirm modal.
+		 * @param {object} config					- Configuration object
+		 * @param {string=} config.title				- Title of dialog box
+		 * @param {string=} config.size				- "large". Set dialog size
+		 * @param {boolean=} config.confirm			- Display or not confirm button
+		 * @param {string=} config.confirmDesc		- Confirm button display description
+		 * @param {object} entity					- bind entity
+		 * @param {{name:string, field:string,type:('select'|'blob'),rows:number,description:string,optional:boolean,readonly:boolean,valueList:Array}[]} fieldList - Display fields
+		 * @param {checkFieldFunction=} checkFunc	- Check logic function. Return string will prevent access
+		 */
+		UI.fieldConfirm = function(config, entity, fieldList, checkFunc) {
+			return _fieldDialog("field", config, entity, fieldList, checkFunc);
+		};
+
+		UI.deleteConfirm = function(name) {
+			var _deferred, $mdl, $scope;
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.lock = false;
+
+			// Modal
+			$mdl = $(TMPL_DELETE).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					name: name
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.delete = function() {
+				$scope.lock = true;
+				_deferred.notify({
+					name: name,
+					closeFunc: function() {
+						$mdl.modal('hide');
+					},
+					unlock: function() {
+						$scope.lock = false;
+					}
+				});
+			};
+
+			return _deferred.promise;
+		};
+
+		return UI;
+	});
+
+	// ===========================================================
+	// =                         Template                        =
+	// ===========================================================
+	var TMPL_FIELDS =
+		'<div class="modal fade" tabindex="-1" role="dialog">' +
+			'<div class="modal-dialog" ng-class="{\'modal-lg\': config.size === \'large\'}" role="document">' +
+				'<div class="modal-content">' +
+					'<div class="modal-header">' +
+						'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
+							'<span aria-hidden="true">&times;</span>' +
+						'</button>' +
+						'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
+					'</div>' +
+					'<div class="modal-body">' +
+						'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
+							'<label for="featureName">' +
+								'<span ng-if="!field.optional">*</span> ' +
+								'{{field.name || field.field}}' +
+							'</label>' +
+							'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
+							'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field] = entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
+								'<option ng-repeat="value in field.valueList">{{value}}</option>' +
+							'</select>' +
+							'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>' +
+						'</div>' +
+					'</div>' +
+					'<div class="modal-footer">' +
+						'<p class="pull-left text-danger">{{checkFunc(entity)}}</p>' +
+						'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Close</button>' +
+						'<button type="button" class="btn btn-primary confirmBtn" ng-click="confirm()" ng-disabled="checkFunc(entity) || emptyFieldList().length || lock" ng-if="config.confirm !== false">' +
+							'{{config.confirmDesc || (create ? "Create" : "Update")}}' +
+						'</button>' +
+					'</div>' +
+				'</div>' +
+			'</div>' +
+		'</div>';
+
+	var TMPL_DELETE =
+		'<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">' +
+			'<div class="modal-dialog">' +
+				'<div class="modal-content">' +
+					'<div class="modal-header">' +
+						'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
+						'<h4 class="modal-title">Delete Confirm</h4></div>' +
+						'<div class="modal-body">' +
+							'<span class="text-red fa fa-exclamation-triangle pull-left" style="font-size: 50px;"></span>' +
+							'<p>You are <strong class="text-red">DELETING</strong> \'{{name}}\'!</p>' +
+							'<p>Proceed to delete?</p>' +
+						'</div>' +
+						'<div class="modal-footer">' +
+							'<button type="button" class="btn btn-danger" ng-click="delete()" ng-disabled="lock">Delete</button>' +
+							'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Cancel</button>' +
+						'</div>' +
+				'</div>' +
+			'</div>' +
+		'</div>';
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/wrapStateSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/wrapStateSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/wrapStateSrv.js
new file mode 100644
index 0000000..57872b2
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/wrapStateSrv.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.
+ */
+
+(function() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('$wrapState', function($state, $location, $stateParams) {
+		var $wrapState = {};
+		var _targetState = null;
+		var _targetPriority = 0;
+
+		// Go
+		$wrapState.go = function(state, param, priority) {
+			setTimeout(function() {
+				_targetState = null;
+				_targetPriority = 0;
+			});
+
+			if(typeof param !== "object") {
+				param = {};
+				priority = param;
+			}
+
+			priority = priority === true ? 1 : (priority || 0);
+			if(_targetPriority > priority) {
+				console.log("[Wrap State] Go - low priority:", state, "(Skip)");
+				return false;
+			}
+
+			if(_targetState !== state || priority) {
+				if($state.current && $state.current.name === state && angular.equals($state.params, param)) {
+					console.log($state);
+					console.log("[Wrap State] Go reload.");
+					$state.reload();
+				} else {
+					console.log("[Wrap State] Go:", state, param, priority);
+					$state.go(state, param);
+				}
+				_targetState = state;
+				_targetPriority = priority;
+				return true;
+			} else {
+				console.log("[Wrap State] Go:", state, "(Ignored)");
+			}
+			return false;
+		};
+
+		// Reload
+		$wrapState.reload = function() {
+			console.log("[Wrap State] Do reload.");
+			$state.reload();
+		};
+
+		// Path
+		$wrapState.path = function(path) {
+			if(path !== undefined) {
+				console.log("[Wrap State][Deprecated] Switch path:", path);
+			}
+			return $location.path(path);
+		};
+
+		// URL
+		$wrapState.url = function(url) {
+			if(url !== undefined) console.log("[Wrap State] Switch url:", url);
+			return $location.url(url);
+		};
+
+		Object.defineProperties($wrapState, {
+			// Origin $state
+			origin: {
+				get: function() {
+					return $state;
+				}
+			},
+
+			// Current
+			current: {
+				get: function() {
+					return $state.current;
+				}
+			},
+
+			// Parameter
+			param: {
+				get: function() {
+					return $.extend({}, $location.search(), $stateParams);
+				}
+			}
+		});
+
+		return $wrapState;
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/index.html b/eagle-webservice/src/main/webapp/app/index.html
deleted file mode 100644
index 7cd3e25..0000000
--- a/eagle-webservice/src/main/webapp/app/index.html
+++ /dev/null
@@ -1,281 +0,0 @@
-<!DOCTYPE html>
-<!--
-  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.
-  -->
-
-<html ng-app="eagleApp" ng-controller="MainCtrl">
-	<head>
-		<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
-		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-		<meta charset="UTF-8">
-		<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
-		<link rel="shortcut icon" href="public/images/favicon.png">
-
-		<title>Eagle</title>
-		<link rel="shortcut icon" type="image/png" href="public/images/favicon.png">
-
-		<!-- ref:css public/css/styles.min.css -->
-		<link href="public/css/main.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="public/css/animation.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.css" rel="stylesheet" type="text/css" media="screen">
-		<link href="../node_modules/zombiej-nvd3/build/nv.d3.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/admin-lte/dist/css/AdminLTE.css" rel="stylesheet" type="text/css" />
-		<link href="../node_modules/admin-lte/dist/css/skins/skin-blue.css" rel="stylesheet" type="text/css" />
-		<!-- endref -->
-	</head>
-	<body class="skin-blue sidebar-mini" ng-class="{'no-sidebar' : PageConfig.hideSidebar}">
-		<!-- Site wrapper -->
-		<div class="wrapper">
-			<header class="main-header">
-				<a href="#/" class="logo">
-					<span class="logo-mini"><img src="public/images/favicon_white.png" /></span>
-					<span class="logo-lg">Apache Eagle</span>
-				</a>
-				<!-- Header Navbar: style can be found in header.less -->
-				<nav class="navbar navbar-static-top" role="navigation">
-					<!-- Sidebar toggle button-->
-					<a ng-hide="PageConfig.hideSidebar" class="sidebar-toggle" data-toggle="offcanvas" role="button">
-						<span class="sr-only">Toggle navigation</span>
-						<span class="icon-bar"></span>
-						<span class="icon-bar"></span>
-						<span class="icon-bar"></span>
-					</a>
-
-					<div class="navbar-custom-menu">
-						<ul class="nav navbar-nav">
-							<!-- Admin error list -->
-							<li class="dropdown" ng-show="ServiceError.list.length">
-								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
-									<i class="fa fa-exclamation-triangle" ng-class="{blink: ServiceError.hasUnread}"></i>
-								</a>
-								<ul class="dropdown-menu">
-									<li ng-repeat="error in ServiceError.list">
-										<a ng-click="ServiceError.showError(error);">
-											<span class="fa" ng-class="{'fa-envelope': !error._read, 'fa-check': error._read}"></span>
-											{{error.title}}
-										</a>
-									</li>
-									<li role="separator" class="divider"></li>
-									<li>
-										<a ng-click="ServiceError.clearAll();">
-											<span class="fa fa-trash"></span>
-											Clear All
-										</a>
-									</li>
-								</ul>
-							</li>
-
-							<!-- Site -->
-							<li class="dropdown" ng-show="!PageConfig.hideSite && !PageConfig.lockSite">
-								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
-									<i class="fa fa-server"></i>
-									{{Site.current().tags.site}}
-									<i class="fa fa-caret-down"></i>
-								</a>
-								<ul class="dropdown-menu">
-									<li ng-repeat="_site in Site.list" ng-if="_site.enabled">
-										<a ng-click="Site.current(_site);">
-											<span class="fa fa-database"></span> {{_site.tags.site}}
-										</a>
-									</li>
-								</ul>
-							</li>
-							<li class="dropdown" ng-show="PageConfig.lockSite">
-								<a>
-									<i class="fa fa-server"></i>
-									{{Site.current().tags.site}}
-								</a>
-							</li>
-
-							<!-- User -->
-							<li class="dropdown user user-menu" ng-hide="PageConfig.hideUser">
-								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
-									<i class="fa fa-user"></i>
-									{{Auth.userProfile.username}}
-								</a>
-								<ul class="dropdown-menu">
-									<!-- User image -->
-									<li class="user-header">
-										<span class="img-circle">
-											<span class="fa fa-user" alt="User Image"></span>
-										</span>
-										<p>
-											{{Auth.userProfile.username}}
-											<small>
-												<span ng-repeat="role in Auth.userProfile.authorities">{{role.authority}} </span>
-											</small>
-										</p>
-									</li>
-									<!-- Menu Footer-->
-									<li class="user-footer">
-										<div class="pull-left" ng-if="Auth.isRole('ROLE_ADMIN')">
-											<a href="#/config/site" class="btn btn-default btn-flat">Management</a>
-										</div>
-										<div class="pull-right">
-											<a ng-click="logout();" class="btn btn-default btn-flat">Sign out</a>
-										</div>
-									</li>
-								</ul>
-							</li>
-						</ul>
-					</div>
-
-					<!-- Applications -->
-					<div ng-hide="PageConfig.hideApplication">
-						<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#moduleMenu">
-							<span class="sr-only">Toggle navigation</span>
-							<span class="fa fa-map"></span>
-						</button>
-						<div class="collapse navbar-collapse" id="moduleMenu">
-							<ul class="nav navbar-nav">
-								<li ng-repeat="_grp in Site.current().applicationGroupList" ng-if="_grp.enabledList.length"
-									class="dropdown" ng-class="{active: Application.current().group === _grp.name}">
-									<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
-										{{_grp.name}}
-									</a>
-									<ul class="dropdown-menu">
-										<li ng-repeat="_app in _grp.enabledList">
-											<a ng-click="Application.current(_app);">
-												<span class="fa fa-cubes"></span> {{_app.displayName}}
-											</a>
-										</li>
-									</ul>
-								</li>
-							</ul>
-						</div>
-					</div>
-				</nav>
-			</header>
-
-			<!-- =============================================== -->
-			<!-- Left side column. contains the side bar -->
-			<aside class="main-sidebar" ng-hide="PageConfig.hideSidebar">
-				<!-- side bar: style can be found in sidebar.less -->
-				<section class="sidebar">
-					<ul class="sidebar-menu">
-						<li class="header">
-							{{Application.current().group || 'Application'}} >
-							{{Application.current().displayName || 'Features'}}
-						</li>
-						<li ng-repeat="page in PageConfig.navConfig.pageList track by $index" ng-class="getNavClass(page)" ng-show="getNavVisible(page)">
-							<a href="{{page.url}}">
-								<i class="fa fa-{{page.icon}}"></i> <span>{{page.title}}</span> 
-							</a>
-						</li>
-					</ul>
-				</section>
-				<!-- /.sidebar -->
-			</aside>
-
-			<!-- =============================================== -->
-			<!-- Right side column. Contains the navbar and content of the page -->
-			<div class="content-wrapper">
-				<!-- Content Header (Page header) -->
-				<section class="content-header" ng-hide="PageConfig.hideSidebar">
-					<h1>
-						<span class="pageTitle">{{PageConfig.pageTitle}}</span>
-						<small class="pageSubTitle">{{PageConfig.pageSubTitle}}</small>
-					</h1>
-
-
-					<ol class="breadcrumb">
-						<li ng-repeat="navPath in PageConfig.navPath">
-							<a ng-href="#{{navPath.path}}">
-								<span class="fa fa-home" ng-if="$first"></span>
-								{{navPath.title || navPath.path}}
-							</a>
-						</li>
-					</ol>
-				</section>
-
-				<!-- Main content -->
-				<section class="content">
-					<div id="content">
-						<div ui-view></div>
-					</div>
-				</section><!-- /.content -->
-			</div><!-- /.content-wrapper -->
-
-			<footer class="main-footer">
-				<div class="pull-right hidden-xs">
-					<b>License</b>
-					<a href="http://www.apache.org/licenses/LICENSE-2.0" class="text-muted">Apache-2.0</a>
-				</div>
-				<strong>
-					Apache Eagle
-					<a target="_blank" href="https://eagle.incubator.apache.org/">Home</a> /
-					<a target="_blank" href="https://eagle.incubator.apache.org/docs/community.html">Community</a> /
-					<a target="_blank" href="https://cwiki.apache.org/confluence/display/EAG/FAQ">FAQ</a>
-				</strong>
-			</footer>
-		</div><!-- ./wrapper -->
-
-		<!-- ref:js public/js/doc.js -->
-		<script src="../node_modules/jquery/dist/jquery.js"></script>
-		<script src="../node_modules/jquery-slimscroll/jquery.slimscroll.min.js"></script>
-		<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
-		<script src="../node_modules/zombiej-bootstrap-components/bootstrap-components/js/bootstrap-components.min.js"></script>
-		<script src="../node_modules/moment/min/moment-with-locales.min.js"></script>
-		<script src="../node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
-		<script src="../node_modules/admin-lte/dist/js/app.min.js"></script>
-		<script src="../node_modules/angular/angular.js"></script>
-		<script src="../node_modules/angular-resource/angular-resource.js"></script>
-		<script src="../node_modules/angular-route/angular-route.js"></script>
-		<script src="../node_modules/angular-animate/angular-animate.js"></script>
-		<script src="../node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js"></script>
-		<script src="../node_modules/angular-ui-router/release/angular-ui-router.js"></script>
-		<script src="../node_modules/d3/d3.js"></script>
-		<script src="../node_modules/zombiej-nvd3/build/nv.d3.js"></script>
-
-		<!-- Application -->
-		<script src="public/js/app.js" type="text/javascript" charset="utf-8"></script>
-
-		<!-- Service -->
-		<script src="public/js/srv/main.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/applicationSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/authorizationSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/entitiesSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/siteSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/pageSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/wrapStateSrv.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/srv/uiSrv.js" type="text/javascript" charset="utf-8"></script>
-
-		<!-- Misc -->
-		<script src="public/js/app.ui.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/app.time.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/app.config.js" type="text/javascript" charset="utf-8"></script>
-
-		<script src="public/js/common.js" type="text/javascript" charset="utf-8"></script>
-
-		<!-- Components -->
-		<script src="public/js/components/main.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/sortTable.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/tabs.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/file.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/charts/line3d.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/nvd3.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/components/sortable.js" type="text/javascript" charset="utf-8"></script>
-
-		<!-- Controllers -->
-		<script src="public/js/ctrl/main.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/authController.js" type="text/javascript" charset="utf-8"></script>
-		<script src="public/js/ctrl/configurationController.js" type="text/javascript" charset="utf-8"></script>
-		<!-- endref -->
-	</body>
-</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/partials/config/application.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/config/application.html b/eagle-webservice/src/main/webapp/app/partials/config/application.html
deleted file mode 100644
index 0bf194c..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/config/application.html
+++ /dev/null
@@ -1,124 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title">
-			<span class="fa fa-cogs"></span>
-			Configuration
-			<small class="text-danger" ng-show="changed">
-				<span class="label label-warning label-sm">Unsaved</span>
-			</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<div class="row">
-			<div class="col-md-3">
-				<ul class="nav nav-pills nav-stacked">
-					<li class="disabled"><a>Application</a></li>
-					<li role="presentation" ng-repeat="_application in Application.list track by $index" ng-class="{active: application === _application}">
-						<a ng-click="setApplication(_application)">
-							<span class="fa fa-server"></span>
-							{{_application.tags.application}}
-							<span ng-if="_application.alias">({{_application.alias}})</span>
-						</a>
-					</li>
-
-					<li>
-						<a class="text-light-blue" ng-click="newApplication()" ng-disabled="_pageLock">
-							<span class="fa fa-plus-square"></span>
-							New Application
-						</a>
-					</li>
-				</ul>
-			</div>
-
-			<div class="col-md-9">
-				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteApplication(application)" ng-disabled="_pageLock">
-					<span class="fa fa-trash-o"></span>
-					Delete Application
-				</a>
-
-				<!-- Title -->
-				<h3 class="guideline">
-					Application
-					<small>{{application.tags.application}}</small>
-				</h3>
-				<hr/>
-
-				<!-- Config -->
-				<div class="form-group">
-					<label for="displayName">Display Name</label>
-					<input type="text" class="form-control" id="displayName" placeholder="(Optional) Display name." ng-model="applications[application.tags.application].alias">
-				</div>
-				<div class="form-group">
-					<label for="applicationGroup">Group</label>
-					<input type="text" class="form-control" id="applicationGroup" placeholder="(Optional) Group name" ng-model="applications[application.tags.application].groupName">
-				</div>
-				<div class="form-group">
-					<label for="applicationDescription">Description</label>
-					<textarea id="applicationDescription" class="form-control" placeholder="(Optional) Application description" rows="2" ng-model="applications[application.tags.application].description"></textarea>
-				</div>
-				<div class="form-group">
-					<label for="applicationConfiguration">Configuration</label>
-					<span class="text-danger">{{configCheck(applications[application.tags.application].config)}}</span>
-					<textarea id="applicationConfiguration" class="form-control" placeholder="Application configuration. Feature can read this " rows="5" ng-model="applications[application.tags.application].config"></textarea>
-				</div>
-
-				<!-- Feature -->
-				<label>* Feature</label>
-				<div class="row">
-					<div class="col-sm-6">
-						<h1 class="text-muted text-center" ng-show="applications[application.tags.application].features.length === 0">No feature in using</h1>
-						<ul class="products-list product-list-in-box fixed-height" ng-show="applications[application.tags.application].features.length !== 0">
-							<li class="item" ng-repeat="feature in applications[application.tags.application].features track by $index" ng-class="{active: _feature === feature}">
-								<div class="product-operation">
-									<a class="fa fa-chevron-up" ng-click="moveFeature(feature, applications[application.tags.application].features, -1)"></a>
-									<a class="fa fa-chevron-down" ng-click="moveFeature(feature, applications[application.tags.application].features, 1)"></a>
-								</div>
-								<div class="product-info">
-									<a class="fa fa-times pull-right" ng-click="removeFeature(feature, applications[application.tags.application])"></a>
-									<span class="product-title">{{feature}}</span>
-									<span class="product-description">{{Application.featureList.set[feature].description}}</span>
-								</div>
-							</li>
-						</ul>
-					</div>
-					<div class="col-sm-6">
-						<ul class="products-list product-list-in-box fixed-height">
-							<li class="item" ng-repeat="feature in applications[application.tags.application].optionalFeatures track by $index">
-								<button class="btn btn-lg btn-primary pull-left" ng-click="addFeature(feature, applications[application.tags.application])" ng-disabled="_pageLock">
-									<span class="fa fa-star-o"></span>
-								</button>
-								<div class="product-info">
-									<span class="product-title">{{feature}}</span>
-									<span class="product-description">{{Application.featureList.set[feature].description}}</span>
-								</div>
-							</li>
-						</ul>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="box-footer clearfix">
-		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/partials/config/feature.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/config/feature.html b/eagle-webservice/src/main/webapp/app/partials/config/feature.html
deleted file mode 100644
index 945d90b..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/config/feature.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title">
-			<span class="fa fa-cogs"></span>
-			Configuration
-			<small class="text-danger" ng-show="changed">
-				<span class="label label-warning label-sm">Unsaved</span>
-			</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<div class="row">
-			<div class="col-md-3">
-				<ul class="nav nav-pills nav-stacked">
-					<li class="disabled">
-						<a>Feature</a>
-					</li>
-					<li role="presentation" ng-repeat="_feature in Application.featureList" ng-class="{active: feature === _feature}">
-						<a ng-click="setFeature(_feature)">
-							<span class="fa fa-leaf" ng-class="{'text-danger': _feature._loaded === false}" uib-tooltip="Module load failed!" tooltip-enable="_feature._loaded === false"></span>
-							{{_feature.tags.feature}}
-						</a>
-					</li>
-					<li>
-						<a class="text-light-blue" ng-click="newFeature()" ng-disabled="_pageLock">
-							<span class="fa fa-plus-square"></span>
-							New Feature
-						</a>
-					</li>
-				</ul>
-			</div>
-
-			<div class="col-md-9">
-				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteFeature(feature)" ng-disabled="_pageLock">
-					<span class="fa fa-trash-o"></span>
-					Delete Feature
-				</a>
-
-				<h3 class="guideline">
-					<span class="fa fa-exclamation-triangle text-danger" uib-tooltip="Module load failed!" ng-show="feature._loaded === false"></span>
-					{{feature.tags.feature}}
-				</h3>
-				<hr/>
-
-				<p class="text text-muted">
-					Will load the start up file <code>controller.js</code> from <code>public/feature/{{feature.tags.feature}}</code>.
-					If you are developing customized feature, please reference provided feature.
-				</p>
-
-				<!-- Config -->
-				<div class="form-group">
-					<label for="featureVersion">Version</label>
-					<input id="featureVersion" type="text" class="form-control" placeholder="(Optional) Feature version." ng-model="features[feature.tags.feature].version">
-				</div>
-				<div class="form-group">
-					<label for="featureDescription">Description</label>
-					<textarea id="featureDescription" class="form-control" placeholder="(Optional) Feature description." rows="10" ng-model="features[feature.tags.feature].description"></textarea>
-				</div>
-			</div>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="box-footer clearfix">
-		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/partials/config/site.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/config/site.html b/eagle-webservice/src/main/webapp/app/partials/config/site.html
deleted file mode 100644
index f7d43eb..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/config/site.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title">
-			<span class="fa fa-cogs"></span>
-			Configuration
-			<small class="text-danger" ng-show="changed">
-				<span class="label label-warning label-sm">Unsaved</span>
-			</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<div class="row">
-			<div class="col-md-3">
-				<ul class="nav nav-pills nav-stacked">
-					<li class="disabled"><a>Site</a></li>
-					<li role="presentation" ng-repeat="_site in Site.list track by $index" ng-class="{active: site === _site}">
-						<a ng-click="setSite(_site)">
-							<span class="fa fa-server"></span>
-							{{_site.tags.site}}
-						</a>
-					</li>
-
-					<li>
-						<a class="text-light-blue" ng-click="newSite()" ng-disabled="_pageLock">
-							<span class="fa fa-plus-square"></span>
-							New Site
-						</a>
-					</li>
-				</ul>
-			</div>
-
-			<div class="col-md-9">
-				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteSite(site)" ng-disabled="_pageLock">
-					<span class="fa fa-trash-o"></span>
-					Delete Site
-				</a>
-
-				<!-- Title -->
-				<h3 class="guideline">
-					Site
-					<small>{{site.tags.site}}</small>
-				</h3>
-				<hr/>
-
-				<!-- Config -->
-				<div class="checkbox">
-					<label>
-						<input type="checkbox" ng-checked="sites[site.tags.site].enabled" ng-click="sites[site.tags.site].enabled = !sites[site.tags.site].enabled">
-						<strong>Enabled</strong>
-					</label>
-				</div>
-				<hr/>
-
-				<!-- Application -->
-				<label>* Application</label>
-				<div class="row">
-					<div class="col-sm-6">
-						<h1 class="text-muted text-center" ng-show="sites[site.tags.site].applications.length === 0">No application in using</h1>
-						<ul class="products-list product-list-in-box fixed-height" ng-show="sites[site.tags.site].applications.length !== 0">
-							<li class="item" ng-repeat="application in sites[site.tags.site].applications track by $index" ng-class="{active: _application === application}">
-								<div class="product-operation single">
-									<span class="fa fa-cubes"></span>
-								</div>
-								<div class="product-info">
-									<a class="fa fa-times pull-right" ng-click="removeApplication(application, sites[site.tags.site])"></a>
-									<span class="product-title">
-										<a class="fa fa-cog" ng-click="setApplication(application)"></a>
-										{{application.tags.application}}
-									</span>
-									<span class="product-description">{{Application.list.set[application.tags.application].description}}</span>
-								</div>
-							</li>
-						</ul>
-					</div>
-					<div class="col-sm-6">
-						<ul class="products-list product-list-in-box fixed-height">
-							<li class="item" ng-repeat="application in sites[site.tags.site].optionalApplications track by $index">
-								<button class="btn btn-lg btn-primary pull-left" ng-click="addApplication(application, sites[site.tags.site])" ng-disabled="_pageLock">
-									<span class="fa fa-star-o"></span>
-								</button>
-								<div class="product-info">
-									<span class="product-title">{{application.tags.application}}</span>
-									<span class="product-description">{{Application.list.set[application.tags.application].description}}</span>
-								</div>
-							</li>
-						</ul>
-					</div>
-				</div>
-			</div>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="box-footer clearfix">
-		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/partials/landing.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/landing.html b/eagle-webservice/src/main/webapp/app/partials/landing.html
deleted file mode 100644
index a2e0f47..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/landing.html
+++ /dev/null
@@ -1,30 +0,0 @@
-<!--
-  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.
-  -->
-
-<p class="lead">
-	<span ng-if="!Application.current()">Current site do not use any application.</span>
-	<span ng-if="Application.current()">Current application do not install any feature.</span>
-
-	<span ng-if="Auth.isRole('ROLE_ADMIN')">
-		Click
-		<a href="#/config/site" ng-if="!Application.current()">here</a>
-		<a href="#/config/application" ng-if="Application.current()">here</a>
-		to configure.
-	</span>
-	<span ng-if="!Auth.isRole('ROLE_ADMIN')">Please contact your admin.</span>
-</p>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/partials/login.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/partials/login.html b/eagle-webservice/src/main/webapp/app/partials/login.html
deleted file mode 100644
index 7faef42..0000000
--- a/eagle-webservice/src/main/webapp/app/partials/login.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="login-box">
-	<div class="login-logo">
-		<a href="#/">Apache Eagle</a>
-	</div>
-
-	<div class="login-box-body" ng-show="!loginSuccess">
-		<p class="login-box-msg">Sign in to start your session</p>
-		<div class="form-group has-feedback">
-			<input type="text" class="form-control" placeholder="User Name" ng-model="username" ng-keypress="login($event)" autocomplete="off" id="username">
-			<span class="glyphicon glyphicon-user form-control-feedback"></span>
-		</div>
-		<div class="form-group has-feedback">
-			<input type="password" class="form-control" placeholder="Password" ng-model="password" ng-keypress="login($event)">
-			<span class="glyphicon glyphicon-lock form-control-feedback"></span>
-		</div>
-		<div class="row">
-			<div class="col-xs-8">
-				<div class="checkbox">
-					<label> <input type="checkbox" ng-checked="rememberUser" ng-click="rememberUser = !rememberUser;" /> Remember Me
-					</label>
-				</div>
-			</div>
-			<div class="col-xs-4">
-				<button class="btn btn-primary btn-block btn-flat" ng-click="login($event, true)" ng-disabled="lock">Sign In</button>
-			</div>
-		</div>
-	</div>
-
-	<div class="login-box-body text-center" ng-show="loginSuccess">
-		<p class="login-box-msg">Login success</p>
-		<p>
-			<span class="fa fa-refresh fa-spin"></span>
-			Loading environment...
-		</p>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/css/animation.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/css/animation.css b/eagle-webservice/src/main/webapp/app/public/css/animation.css
deleted file mode 100644
index 954bd29..0000000
--- a/eagle-webservice/src/main/webapp/app/public/css/animation.css
+++ /dev/null
@@ -1,46 +0,0 @@
-@CHARSET "UTF-8";
-/*
- * 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.
- */
-
-[ui-view].ng-enter, [ui-view].ng-leave {
-	position: absolute;
-	left: 0;
-	right: 0;
-	-webkit-transition: all .5s ease-in-out;
-	-moz-transition: all .5s ease-in-out;
-	-o-transition: all .5s ease-in-out;
-	transition: all .3s ease-in-out;
-}
-
-[ui-view].ng-enter {
-	opacity: 0;
-}
-
-[ui-view].ng-enter-active {
-	opacity: 1;
-}
-
-[ui-view].ng-leave {
-	opacity: 1;
-	transform:translate3d(0, 0, 0);
-}
-
-[ui-view].ng-leave-active {
-	opacity: 0;
-	transform:translate3d(20%, 0, 0);
-}
\ No newline at end of file


[12/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/index.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/index.html b/eagle-server/src/main/webapp/app/dev/index.html
new file mode 100644
index 0000000..56850d7
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/index.html
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<!--
+  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.
+  -->
+
+<html ng-controller="MainCtrl">
+	<head>
+		<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+		<meta charset="UTF-8">
+		<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
+		<link rel="shortcut icon" href="public/images/favicon.png">
+
+		<title>Eagle</title>
+		<link rel="shortcut icon" type="image/png" href="public/images/favicon.png">
+
+		<!-- ref:css public/css/doc.css -->
+		<link href="../node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.css" rel="stylesheet" type="text/css" media="screen">
+
+		<link href="../node_modules/zombiej-nvd3/build/nv.d3.css" rel="stylesheet" type="text/css" />
+
+		<link href="../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
+
+		<link href="../node_modules/admin-lte/dist/css/AdminLTE.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/admin-lte/dist/css/skins/skin-blue.css" rel="stylesheet" type="text/css" />
+
+		<link href="public/css/animation.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="public/css/sortTable.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="public/css/main.css" rel="stylesheet" type="text/css" media="screen">
+		<!-- endref -->
+	</head>
+	<body class="skin-blue sidebar-mini" ng-class="{'no-sidebar' : PageConfig.hideSidebar}">
+		<!-- Site wrapper -->
+		<div class="wrapper">
+			<header class="main-header">
+				<a href="#/" class="logo">
+					<span class="logo-mini"><img src="public/images/favicon_white.png" /></span>
+					<span class="logo-lg">Apache Eagle</span>
+				</a>
+				<!-- Header Navbar: style can be found in header.less -->
+				<nav class="navbar navbar-static-top" role="navigation">
+					<!-- Sidebar toggle button-->
+					<a ng-hide="PageConfig.hideSidebar" class="sidebar-toggle" data-toggle="offcanvas" role="button">
+						<span class="sr-only">Toggle navigation</span>
+						<span class="icon-bar"></span>
+						<span class="icon-bar"></span>
+						<span class="icon-bar"></span>
+					</a>
+
+					<div class="navbar-custom-menu">
+						<ul class="nav navbar-nav">
+							<li class="dropdown time-picker" ng-if="Time.pickerType === Time.TIME_RANGE_PICKER">
+								<a data-toggle="dropdown" aria-expanded="false">
+									<i class="fa fa-calendar"></i>
+									{{Time.format("startTime", Time.SHORT_FORMAT)}} ~ {{Time.format("endTime", Time.SHORT_FORMAT)}}
+								</a>
+								<ul class="dropdown-menu">
+									<li><a ng-click="setLastDuration(2)"><i class="fa fa-clock-o"></i>Last 2 Hours</a></li>
+									<li><a ng-click="setLastDuration(6)"><i class="fa fa-clock-o"></i>Last 6 Hours</a></li>
+									<li><a ng-click="setLastDuration(12)"><i class="fa fa-clock-o"></i>Last 12 Hours</a></li>
+									<li><a ng-click="setLastDuration(24)"><i class="fa fa-clock-o"></i>Last 24 Hours</a></li>
+									<li><a ng-click="customizeTimeRange()"><i class="fa fa-clock-o"></i>Customize</a></li>
+								</ul>
+							</li>
+							<li>
+								<a data-toggle="dropdown" aria-expanded="false">
+									<i class="glyphicon glyphicon-question-sign"></i>
+								</a>
+
+								<ul class="dropdown-menu">
+									<li><a>How to start using eagle</a></li>
+									<li><a>How to register new site</a></li>
+									<li><a>How to install application</a></li>
+									<li><a>How to manage application</a></li>
+									<li><a>How to develop application</a></li>
+								</ul>
+							</li>
+						</ul>
+					</div>
+				</nav>
+			</header>
+
+			<!-- =============================================== -->
+			<!-- Left side column. contains the side bar -->
+			<aside class="main-sidebar" ng-hide="PageConfig.hideSidebar">
+				<!-- side bar: style can be found in sidebar.less -->
+				<section class="sidebar">
+					<ul class="sidebar-menu">
+						<li ng-repeat="portal in Portal.list track by $index" ng-class="{treeview: portal.list}">
+							<a ng-href="{{portal.path}}">
+								<i class="fa fa-{{portal.icon || 'circle-o'}}"></i>
+								<span>{{portal.name}}</span>
+								<i class="fa fa-angle-left pull-right" ng-if="portal.list"></i>
+							</a>
+							<ul class="treeview-menu" ng-if="portal.list">
+								<li ng-repeat="subPortal in portal.list track by $index" ng-class="{active: getNavClass(subPortal)}">
+									<a ng-href="{{subPortal.path}}">
+										<i class="fa fa-{{subPortal.icon || 'circle-o'}}"></i>
+										<span>{{subPortal.name}}</span>
+									</a>
+								</li>
+							</ul>
+						</li>
+					</ul>
+				</section>
+				<!-- /.sidebar -->
+			</aside>
+
+			<!-- =============================================== -->
+			<!-- Right side column. Contains the navbar and content of the page -->
+			<div class="content-wrapper">
+				<!-- Content Header (Page header) -->
+				<section class="content-header" ng-hide="PageConfig.hideTitle">
+					<h1>
+						<span class="pageTitle">{{PageConfig.title}}</span>
+						<small class="pageSubTitle">{{PageConfig.subTitle}}</small>
+					</h1>
+
+
+					<ol class="breadcrumb">
+						<li ng-repeat="navPath in PageConfig.navPath">
+							<a ng-href="#{{navPath.path}}">
+								<span class="fa fa-home" ng-if="$first"></span>
+								{{navPath.title || navPath.path}}
+							</a>
+						</li>
+					</ol>
+				</section>
+
+				<!-- Main content -->
+				<section class="content">
+					<div id="content">
+						<div ui-view></div>
+					</div>
+				</section><!-- /.content -->
+			</div><!-- /.content-wrapper -->
+
+			<footer class="main-footer">
+				<div class="pull-right hidden-xs">
+					<b>License</b>
+					<a href="http://www.apache.org/licenses/LICENSE-2.0" class="text-muted">Apache-2.0</a>
+				</div>
+				<strong>
+					Apache Eagle
+					<a target="_blank" href="https://eagle.incubator.apache.org/">Home</a> /
+					<a target="_blank" href="https://eagle.incubator.apache.org/docs/community.html">Community</a> /
+					<a target="_blank" href="https://cwiki.apache.org/confluence/display/EAG/FAQ">FAQ</a>
+				</strong>
+			</footer>
+		</div><!-- ./wrapper -->
+
+		<!-- Modal: Time Range Picker -->
+		<div class="modal fade" tabindex="-1" role="dialog" id="eagleTimeRangeMDL">
+			<div class="modal-dialog" role="document">
+				<div class="modal-content">
+					<div class="modal-header">
+						<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+						<h4 class="modal-title">Customize Time Range</h4>
+					</div>
+					<div class="modal-body">
+						<div class="row">
+							<div class="col-sm-6">
+								<div class="form-group">
+									<label for="eagleStartTime">Start Time</label>
+									<input type="text" class="form-control" data-container="body" data-toggle="datepicker" id="eagleStartTime">
+								</div>
+							</div>
+							<div class="col-sm-6">
+								<div class="form-group">
+									<label for="eagleEndTime">End Time</label>
+									<input type="text" class="form-control" data-container="body" data-toggle="datepicker" id="eagleEndTime" data-position="right">
+								</div>
+							</div>
+						</div>
+					</div>
+					<div class="modal-footer">
+						<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+						<button type="button" class="btn btn-primary" ng-click="updateTimeRange()">Change</button>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<!-- ref:js public/js/modules.js -->
+		<script src="../node_modules/jquery/dist/jquery.min.js"></script>
+		<script src="../node_modules/jquery-slimscroll/jquery.slimscroll.min.js"></script>
+		<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
+		<script src="../node_modules/zombiej-bootstrap-components/bootstrap-components/js/bootstrap-components.min.js"></script>
+		<script src="../node_modules/moment/min/moment-with-locales.min.js"></script>
+		<script src="../node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
+		<script src="../node_modules/echarts/dist/echarts.min.js"></script>
+		<script src="../node_modules/admin-lte/dist/js/app.min.js"></script>
+		<script src="../node_modules/angular/angular.min.js"></script>
+		<script src="../node_modules/angular-resource/angular-resource.min.js"></script>
+		<script src="../node_modules/angular-route/angular-route.min.js"></script>
+		<script src="../node_modules/angular-animate/angular-animate.min.js"></script>
+		<script src="../node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js"></script>
+		<script src="../node_modules/angular-ui-router/release/angular-ui-router.min.js"></script>
+		<!-- endref -->
+
+		<!-- ref:js public/js/doc.min.js -->
+		<!-- Worker -->
+		<script src="public/js/worker/sortTableFunc.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Application -->
+		<script src="public/js/common.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/index.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/app.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Service -->
+		<script src="public/js/services/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/timeSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/pageSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/widgetSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/wrapStateSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/entitySrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/siteSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/applicationSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/services/uiSrv.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Components -->
+		<script src="public/js/components/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/sortTable.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/chart.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/widget.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Controllers -->
+		<script src="public/js/ctrls/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrls/mainCtrl.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrls/alertCtrl.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrls/integrationCtrl.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrls/siteCtrl.js" type="text/javascript" charset="utf-8"></script>
+		<!-- endref -->
+	</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/alert/list.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/list.html b/eagle-server/src/main/webapp/app/dev/partials/alert/list.html
new file mode 100644
index 0000000..d493976
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/list.html
@@ -0,0 +1,21 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	Good!
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/main.html b/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
new file mode 100644
index 0000000..2e062a8
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/main.html
@@ -0,0 +1,29 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li ng-class="{active: getState() === 'alert.list'}"><a href="#/alert/">Explore Triggered Alerts</a></li>
+		<li ng-class="{active: getState() === 'alert.policyList'}"><a href="#/alert/policyList">Manage Policies</a></li>
+		<li ng-class="{active: ['alert.policyCreate', 'alert.policyEdit'].indexOf(getState()) >= 0}"><a href="#/alert/policyCreate">Define Alert Policy</a></li>
+	</ul>
+	<div class="tab-content no-padding">
+		<div ui-view></div>
+	</div>
+</div>
+

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
new file mode 100644
index 0000000..3c335f7
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.back.html
@@ -0,0 +1,108 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<ul class="timeline">
+		<!-- Base Info -->
+		<li class="time-label">
+			<span class="bg-blue">#1. Basic Information</span>
+		</li>
+		<li>
+			<span class="fa fa-file-text bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<div class="form-group">
+						<label>Policy Name</label>
+						<input type="text" class="form-control" ng-model="policy.name" />
+					</div>
+					<div class="form-group">
+						<label>Severity</label>
+						<select class="form-control" ng-model="policy.severity">
+							<option>WARNING</option>
+							<option>CRITICAL</option>
+							<option>DANGER</option>
+						</select>
+					</div>
+					<div class="form-group">
+						<label>Description</label>
+						<textarea class="form-control" ng-model="policy.description" rows="3"></textarea>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Alert Stream -->
+		<li class="time-label">
+			<span class="bg-blue">#2. Alert Stream</span>
+		</li>
+		<li>
+			<span class="fa fa-rocket bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<div class="form-group">
+						<label>App Integration</label>
+						<select class="form-control"></select>
+					</div>
+					<div class="form-group">
+						<label>Alert Stream</label>
+						<select class="form-control"></select>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Streaming Logic -->
+		<li class="time-label">
+			<span class="bg-blue">#3. Streaming Logic</span>
+		</li>
+		<li>
+			<span class="fa fa-trophy bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<div class="form-group">
+						<label>Policy Type</label>
+						<select class="form-control"></select>
+					</div>
+					<div class="form-group">
+						<label>Policy Logic</label>
+						<textarea class="form-control" rows="5"></textarea>
+					</div>
+				</div>
+			</div>
+		</li>
+
+		<!-- Publication Configuration -->
+		<li class="time-label">
+			<span class="bg-blue">#4. Publication Configuration</span>
+		</li>
+		<li>
+			<span class="fa fa-envelope bg-aqua"></span>
+			<div class="timeline-item">
+				<div class="timeline-body">
+					<div class="form-group">
+						<label>Publication Type</label>
+						<select class="form-control">
+							<option>NOTIFICATION</option>
+						</select>
+					</div>
+					<a>+ New Publication</a>
+				</div>
+			</div>
+		</li>
+	</ul>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
new file mode 100644
index 0000000..9a1cbe4
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyEdit.html
@@ -0,0 +1,29 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<ul class="stepGuide">
+		<li>
+			<span class="icon bg-green">1</span>
+			<span class="title">This is the title!!!</span>
+		</li>
+		<li>
+			<span class="icon">2</span>
+		</li>
+	</ul>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html b/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
new file mode 100644
index 0000000..2d4703f
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/alert/policyList.html
@@ -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.
+  -->
+
+<div class="box box-solid">
+	<div class="box-body">
+		<div sort-table="policyList" ng-show="policyList.length">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th sortpath="name" width="20%">Name</th>
+						<th sortpath="definition.type" width="70">Type</th>
+						<th>Description</th>
+						<th width="85">Action</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td>
+							<a ng-href="#/alert/policyEdit/{{item.name}}">{{item.name}}</a>
+						</td>
+						<td class="text-center"><span class="label label-primary">{{item.definition.type}}</span></td>
+						<td>{{item.description}}</td>
+						<td class="text-center">
+							<div class="btn-group btn-group-xs">
+								<button class="btn btn-default"><span class="fa fa-play"></span></button>
+								<button class="btn btn-default"><span class="fa fa-pencil"></span></button>
+								<button class="btn btn-danger" ng-click="deletePolicy(item)"><span class="fa fa-trash"></span></button>
+							</div>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+
+		<div class="callout callout-warning no-margin" ng-show="policyList._done && policyList.length === 0">
+			<h4>No Policy yet</h4>
+			<p>You have not create policy yet. Click <a href="#/alert/policyCreate">here</a> to create a new policy.</p>
+		</div>
+	</div>
+
+	<div class="overlay" ng-if="!policyList._done">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+
+	<div class="box-footer text-right">
+		<a href="#/alert/policyCreate" class="btn btn-primary">New Policy</a>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/home.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/home.html b/eagle-server/src/main/webapp/app/dev/partials/home.html
new file mode 100644
index 0000000..ab75b18
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/home.html
@@ -0,0 +1,60 @@
+<!--
+  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.
+  -->
+
+<!--div class="row" ng-repeat="banner in bannerList track by $index">
+	<div class="col-sm-12" ng-if="banner.title">
+		<h3>{{banner.title}}</h3>
+	</div>
+	<div class="col-sm-6 col-md-4 col-lg-3" ng-repeat="widget in banner.list track by $index">
+		<div class="small-box {{widget.color || 'bg-aqua'}}">
+			<div class="inner">
+				<h3>{{widget.title || "Untitled"}}</h3>
+				<p>{{widget.description || "-"}}</p>
+				<p>{{widget.additionalTips || "&nbsp;"}}</p>
+			</div>
+			<div class="icon">
+				<i class="fa fa-question-circle"></i>
+			</div>
+			<a class="small-box-footer">More info <i class="fa fa-arrow-circle-right"></i></a>
+		</div>
+	</div>
+</div-->
+<div class="row flex">
+	<div class="col-sm-6 col-md-4 col-lg-3" ng-repeat="widget in Widget.list track by $index">
+		<div widget="widget"></div>
+	</div>
+</div>
+
+<!--div class="row flex">
+	<div class="col-md-4">
+		<div style="background: red; height: 200px;">111</div>
+	</div>
+	<div class="col-md-4">
+		<div style="background: green;">222</div>
+		<div style="background: blue;">222</div>
+	</div>
+	<div class="col-md-4">
+		<div style="background: red;">333</div>
+	</div>
+	<div class="col-md-4">
+		<div style="background: green;">444</div>
+	</div>
+	<div class="col-md-4">
+		<div style="background: red;">555</div>
+	</div>
+</div-->

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/integration/applicationList.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/applicationList.html b/eagle-server/src/main/webapp/app/dev/partials/integration/applicationList.html
new file mode 100644
index 0000000..bc603a7
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/applicationList.html
@@ -0,0 +1,80 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<table class="table table-bordered table-striped">
+		<thead>
+		<tr>
+			<th>Application</th>
+			<th>Type</th>
+			<th>Version</th>
+			<th>Path</th>
+			<th>Streams</th>
+			<th>Description</th>
+		</tr>
+		</thead>
+		<tbody>
+			<tr ng-repeat="app in Application.providerList track by $index">
+				<td class="text-no-break"><a ng-click="showAppDetail(app)">{{app.name}}</a></td>
+				<td>{{app.type}}</td>
+				<td class="text-no-break">{{app.version}}</td>
+				<td>{{app.viewPath}}</td>
+				<td>
+					<ul>
+						<li ng-repeat="stream in app.streams track by $index">
+							{{stream.streamId}}
+						</li>
+					</ul>
+				</td>
+				<td>{{app.description}}</td>
+			</tr>
+		</tbody>
+	</table>
+</div>
+
+
+<!-- Modal: Application information -->
+<div class="modal fade" role="dialog" id="appMDL">
+	<div class="modal-dialog modal-lg">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">�</span>
+				</button>
+				<h4 class="modal-title" id="myModalLabel">{{application.name}}</h4>
+			</div>
+			<div class="modal-body">
+				<ul class="nav nav-tabs">
+					<li class="active"><a href="[data-id='install']" data-toggle="tab">Install</a></li>
+					<li><a href="[data-id='uninstall']" data-toggle="tab">Uninstall</a></li>
+				</ul>
+				<div class="tab-content">
+					<div class="tab-pane active" data-id="install">
+						<pre ng-bind-html="installHTML"></pre>
+					</div>
+					<div class="tab-pane" data-id="uninstall">
+						<pre ng-bind-html="uninstallHTML"></pre>
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/integration/main.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/main.html b/eagle-server/src/main/webapp/app/dev/partials/integration/main.html
new file mode 100644
index 0000000..daea22e
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/main.html
@@ -0,0 +1,29 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li ng-class="{active: getState().indexOf('integration.site') !== -1}"><a href="#/integration/siteList">Sites</a></li>
+		<li ng-class="{active: getState() === 'integration.applicationList'}"><a href="#/integration/applicationList">Applications</a></li>
+		<li ng-class="{active: getState() === 'integration.streamList'}"><a href="#/integration/streamList">Streams</a></li>
+	</ul>
+	<div class="tab-content no-padding">
+		<div ui-view></div>
+	</div>
+</div>
+

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/site.html b/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
new file mode 100644
index 0000000..b391b49
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/site.html
@@ -0,0 +1,95 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<p class="text-danger" ng-if="site.applicationList.length === 0">
+		<span class="fa fa-exclamation-triangle"></span> Site must install at least one application to start up.
+	</p>
+	<p class="text-warning" ng-if="site.applicationList.length !== 0 && getStartedAppCount() === 0">
+		<span class="fa fa-exclamation-triangle"></span> No application started.
+	</p>
+
+	<table class="table table-bordered table-hover">
+		<thead>
+			<tr>
+				<th>App</th>
+				<th width="10">Status</th>
+				<th>Version</th>
+				<th>Description</th>
+				<!--th>Quick Links</th-->
+				<th width="150">Actions</th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr ng-repeat="app in applicationList track by $index">
+				<td><a ng-click="showAppDetail(app)">{{app.origin.name}}</a></td>
+				<td class="text-center">
+					<span class="label label-{{getAppStatusClass(app)}}" ng-if="app.installed">{{app.status}}</span>
+					<span class="label label-default" ng-if="!app.installed">UNINSTALLED</span>
+				</td>
+				<td>{{app.origin.version}}</td>
+				<td>{{app.description}}</td>
+				<!--td>TODO: ui link</td-->
+				<td class="text-center">
+					<div class="btn-group btn-group-xs" ng-if="app.installed">
+						<!--button class="btn btn-default btn-sm">Monitor</button-->
+						<button class="btn btn-default btn-sm" ng-click="startApp(app)">Start</button>
+						<button class="btn btn-default btn-sm" ng-click="stopApp(app)">Stop</button>
+						<button class="btn btn-default btn-sm" ng-click="uninstallApp(app)">Uninstall</button>
+					</div>
+					<div class="btn-group btn-group-xs" ng-if="!app.installed">
+						<button class="btn btn-primary btn-sm" ng-click="installApp(app)">Install Application</button>
+					</div>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+</div>
+
+
+
+<!-- Modal: Application information -->
+<div class="modal fade" role="dialog" id="appMDL">
+	<div class="modal-dialog modal-lg">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">�</span>
+				</button>
+				<h4 class="modal-title" id="myModalLabel">{{application.name}}</h4>
+			</div>
+			<div class="modal-body">
+				<ul class="nav nav-tabs">
+					<li class="active"><a href="[data-id='install']" data-toggle="tab">Install</a></li>
+					<li><a href="[data-id='uninstall']" data-toggle="tab">Uninstall</a></li>
+				</ul>
+				<div class="tab-content">
+					<div class="tab-pane active" data-id="install">
+						<pre ng-bind-html="installHTML"></pre>
+					</div>
+					<div class="tab-pane" data-id="uninstall">
+						<pre ng-bind-html="uninstallHTML"></pre>
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/integration/siteList.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/siteList.html b/eagle-server/src/main/webapp/app/dev/partials/integration/siteList.html
new file mode 100644
index 0000000..491d902
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/siteList.html
@@ -0,0 +1,60 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<table class="table table-bordered table-hover">
+		<thead>
+		<tr>
+			<th>Site</th>
+			<th>Description</th>
+			<th>Enabled Apps</th>
+			<th width="95">Actions</th>
+		</tr>
+		</thead>
+		<tbody>
+			<tr ng-repeat="site in Site.list track by $index">
+				<td>
+					<a ui-sref="integration.site({id: site.siteId})">
+						{{site.siteId}}
+						<span ng-if="site.siteName">({{site.siteName}})</span>
+					</a>
+				</td>
+				<td>{{site.description}}</td>
+				<td>
+					<span class="text-muted" ng-if="site.applicationList.length === 0">(Nothing installed...)</span>
+					<ul class="list-inline no-margin">
+						<li ng-repeat="app in site.applicationList track by $index">
+							<span class="label label-primary">
+								{{app.descriptor.name}}
+							</span>
+						</li>
+					</ul>
+				</td>
+				<td class="text-center">
+					<div class="btn-group btn-group-xs">
+						<a class="btn btn-default btn-sm" ui-sref="integration.site({id: site.siteId})">Edit</a>
+						<button class="btn btn-default btn-sm" ng-click="deleteSite(site)">Delete</button>
+					</div>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+</div>
+<div class="box-footer text-right">
+	<button class="btn btn-primary" ng-click="newSite()">New Site</button>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/integration/streamList.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/integration/streamList.html b/eagle-server/src/main/webapp/app/dev/partials/integration/streamList.html
new file mode 100644
index 0000000..beaf743
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/integration/streamList.html
@@ -0,0 +1,52 @@
+<!--
+  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.
+  -->
+
+<div class="box-body">
+	<div sort-table="streamList">
+		<table class="table table-bordered table-hover">
+			<thead>
+				<tr>
+					<th>Stream</th>
+					<th>Provider (App)</th>
+					<th>Site</th>
+					<th>Schema</th>
+					<th width="10">Actions</th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr>
+					<td><span class="label label-primary">{{item.streamId}}</span></td>
+					<td>{{item.appType}}</td>
+					<td>{{item.siteId}}</td>
+					<td>
+						<ul class="no-margin">
+							<li ng-repeat="column in item.schema.columns track by $index">
+								<strong>{{column.name}}</strong>:
+								{{column.type}}
+							</li>
+						</ul>
+					</td>
+					<td>
+						<!-- TODO:link with alert -->
+						<button class="btn btn-primary btn-sm">New Alert</button>
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/partials/setup.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/partials/setup.html b/eagle-server/src/main/webapp/app/dev/partials/setup.html
new file mode 100644
index 0000000..64944fb
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/partials/setup.html
@@ -0,0 +1,46 @@
+<!--
+  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.
+  -->
+
+<h2>Welcome for using Apache Eagle!</h2>
+<p class="lead">
+	As the first installation, please create a <code>SITE</code>.
+	(A site is a cluster/service which you want to monitor, as a quick start, you could use <code>sandbox</code> by default)
+</p>
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<h3 class="box-title">Create new site</h3>
+	</div>
+	<div class="box-body">
+		<div class="form-group">
+			<label>* Site Id</label>
+			<input class="form-control" placeholder="Site id should be unique" ng-model="siteId">
+		</div>
+		<div class="form-group">
+			<label>Display Name</label>
+			<input class="form-control" placeholder="Site display name in UI" ng-model="siteName">
+		</div>
+		<div class="form-group">
+			<label>Description</label>
+			<textarea class="form-control" placeholder="Description about current site" ng-model="description" rows="5"></textarea>
+		</div>
+	</div>
+	<div class="box-footer text-right">
+		<button class="btn btn-primary" ng-click="createSite()" ng-disabled="site === '' || lock">Next</button>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/css/animation.css
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/css/animation.css b/eagle-server/src/main/webapp/app/dev/public/css/animation.css
new file mode 100644
index 0000000..cbf4973
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/css/animation.css
@@ -0,0 +1,47 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+#content > [ui-view].ng-enter,
+#content > [ui-view].ng-leave {
+	position: absolute;
+	left: 0;
+	right: 0;
+	-webkit-transition: all .5s ease-in-out;
+	-moz-transition: all .5s ease-in-out;
+	-o-transition: all .5s ease-in-out;
+	transition: all .3s ease-in-out;
+}
+
+#content > [ui-view].ng-enter {
+	opacity: 0;
+}
+
+#content > [ui-view].ng-enter-active {
+	opacity: 1;
+}
+
+#content > [ui-view].ng-leave {
+	opacity: 1;
+	transform:translate3d(0, 0, 0);
+}
+
+#content > [ui-view].ng-leave-active {
+	opacity: 0;
+	transform:translate3d(20%, 0, 0);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/css/main.css
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/css/main.css b/eagle-server/src/main/webapp/app/dev/public/css/main.css
new file mode 100644
index 0000000..83f9b14
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/css/main.css
@@ -0,0 +1,317 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+a {
+	cursor: pointer;
+}
+
+/* ========================================================================
+ * =                               Side Bar                               =
+ * ======================================================================== */
+.sidebar-mini.sidebar-collapse .main-header .logo > .logo-mini > img {
+	max-height: 30px;
+}
+
+.main-sidebar .customize-panel {
+	padding: 10px;
+}
+
+.sidebar-collapse .main-sidebar .customize-panel {
+	display: none;
+}
+
+.main-sidebar .customize-panel .btn-group,
+.main-sidebar .customize-panel .btn-group button,
+.main-sidebar .customize-panel .btn-group .dropdown-menu {
+	width: 100%;
+}
+
+.main-sidebar .customize-panel .btn-group button {
+	padding: 5px;
+	background: #374850;
+	border: none;
+	overflow-x: hidden;
+}
+.main-sidebar .customize-panel .btn-group.open button{
+	background: #455b63;
+}
+
+.main-sidebar .customize-panel .btn-group button .caret {
+	position: absolute;
+	right: 10px;
+	top: 13px;
+}
+
+/* ========================================================================
+ * =                                 Main                                 =
+ * ======================================================================== */
+#content {
+	position: relative;
+}
+
+/* ========================================================================
+ * =                                 Grid                                 =
+ * ======================================================================== */
+
+@media (min-width: 1200px) {
+	.row.flex {
+		display: -webkit-box;
+		display: -webkit-flex;
+		display: -ms-flexbox;
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.row.flex > [class*='col-'] {
+		display: -webkit-box;
+		display: -webkit-flex;
+		display: -ms-flexbox;
+		display: flex;
+		flex-direction: column;
+		flex-wrap: nowrap;
+	}
+
+	.row.flex > [class*='col-'] > * {
+		flex: auto;
+	}
+}
+
+.row.border-split > div {
+	border: 1px solid #f4f4f4;
+}
+
+.no-padding > .row.border-split {
+	margin-left: 0;
+	margin-right: 0;
+}
+
+/* ========================================================================
+ * =                                Table                                 =
+ * ======================================================================== */
+table .info-wrapper .info-detail {
+	display: none;
+}
+
+table .info-wrapper:hover .info-detail {
+	display: table-row;
+}
+
+table ul {
+	padding: 0 0 0 20px;
+}
+
+table.table pre {
+	white-space: pre-wrap;
+	margin: 0;
+}
+
+table.table pre.inline {
+	padding: 0;
+	border: 0;
+	border-radius: 0;
+	background: transparent;
+}
+
+table.table.table-sm th,
+table.table.table-sm td {
+	padding: 3px 5px;
+	line-height: 120%;
+}
+
+/* ========================================================================
+ * =                              Step Guide                              =
+ * ======================================================================== */
+ul.stepGuide {
+	padding: 0;
+	position: relative;
+	display: inline-block;
+}
+ul.stepGuide:before {
+	display: block;
+	height: 6px;
+	background: #f4f4f4;
+	content: "";
+	position: absolute;
+	top: 12px;
+	left: 5px;
+	right: 5px;
+}
+
+ul.stepGuide li {
+	position: relative;
+	display: inline-block;
+	vertical-align: top;
+	text-align: center;
+}
+ul.stepGuide li:not(:first-child) {
+	margin-left: 15px;
+}
+
+ul.stepGuide li > .icon {
+	display: inline-block;
+	width: 30px;
+	height: 30px;
+	line-height: 30px;
+	text-align: center;
+	background: #f4f4f4;
+	border-radius: 100%;
+}
+
+ul.stepGuide li > .title {
+	display: block;
+}
+
+/* ========================================================================
+ * =                                 Box                                  =
+ * ======================================================================== */
+.box .box-title .label {
+	font-size: 12px;
+	padding: 1px 7px;
+}
+
+.small-box {
+	position: relative;
+	padding-bottom: 30px;
+}
+
+.small-box > .inner a {
+	color: #FFFFFF;
+}
+
+.small-box > .inner a:hover {
+	text-decoration: underline;
+}
+
+.small-box > .small-box-footer {
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+}
+
+	/* ========================================================================
+     * =                                 Tab                                  =
+     * ======================================================================== */
+.tab-content.keepContent > .tab-pane {
+	display: block;
+}
+
+.tab-content.keepContent > .tab-pane:not(.active) {
+	max-height: 0;
+	overflow: hidden;
+}
+
+/* ========================================================================
+ * =                                Modal                                 =
+ * ======================================================================== */
+.modal .nav-tabs li {
+	border-top: 3px solid transparent;
+}
+
+.modal .nav-tabs li.active {
+	border-top-color: #3c8dbc;
+}
+
+.modal .nav-tabs li a {
+	padding: 8px 15px;
+	border-radius: 0;
+	margin: 0;
+}
+
+.modal .nav-tabs li a,
+.modal .nav-tabs li.active a,
+.modal .nav-tabs li:hover a {
+	border-top: 0;
+}
+
+.modal .tab-content {
+	padding-top: 10px;
+}
+
+/* ========================================================================
+ * =                               Call out                               =
+ * ======================================================================== */
+.callout p,
+.callout span {
+	color: #FFF;
+}
+
+/* ========================================================================
+ * =                                 list                                 =
+ * ======================================================================== */
+.list-inline > li > .label {
+	margin-bottom: 2px;
+	display: inline-block;
+}
+
+/* ========================================================================
+ * =                                 label                                =
+ * ======================================================================== */
+.label.label-sm {
+	padding: .1em .4em .2em;
+}
+
+/* ========================================================================
+ * =                               Timeline                               =
+ * ======================================================================== */
+.nav-tabs-custom .timeline li .timeline-item {
+	background: #f4f4f4;
+}
+
+/* ========================================================================
+ * =                                Widget                                =
+ * ======================================================================== */
+@media (min-width: 1200px) {
+	.row.flex div[widget] {
+		height: 100%;
+	}
+}
+
+/* ========================================================================
+ * =                                 Misc                                 =
+ * ======================================================================== */
+.ng-hide.ng-hide-animate.no-animate {
+	-webkit-transition: none !important;
+	transition: none !important;
+}
+
+.ng-hide.ng-hide-animate.no-animate {
+	display: none;
+}
+
+.text-break {
+	word-break:break-all;
+}
+
+.text-no-break {
+	white-space: nowrap;
+}
+
+.no-select {
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+}
+
+.bsc-datepicker {
+	z-index: 2000;
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/css/sortTable.css
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/css/sortTable.css b/eagle-server/src/main/webapp/app/dev/public/css/sortTable.css
new file mode 100644
index 0000000..529eb1a
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/css/sortTable.css
@@ -0,0 +1,61 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+[sort-table] .tool-container .search-box {
+	margin: 0 0 10px 0;
+	position: relative;
+	max-width: 250px;
+	float: left;
+}
+
+[sort-table] .tool-container .search-box input {
+	padding-left: 28px;
+}
+
+[sort-table] .tool-container .search-box .fa-search {
+	pointer-events:none;
+	position: absolute;
+	top: 8px;
+	left: 8px;
+	opacity: 0.5;
+}
+
+[sort-table] .tool-container .page-size {
+	float: right;
+}
+
+[sort-table] .tool-container .page-size select {
+	width: initial;
+	display: inline-block;
+	margin: 0 7px;
+	height: 26px;
+	padding: 0 5px;
+}
+
+[sort-table] .fa.sort-mark {
+	float: right;
+	margin-top: 3px;
+	pointer-events: none;
+	color: #AAA;
+}
+
+[sort-table] .navigation-bar .pagination {
+	float: right;
+	margin-top: 0;
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/images/favicon.png
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/images/favicon.png b/eagle-server/src/main/webapp/app/dev/public/images/favicon.png
new file mode 100644
index 0000000..3bede2a
Binary files /dev/null and b/eagle-server/src/main/webapp/app/dev/public/images/favicon.png differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/images/favicon_white.png
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/images/favicon_white.png b/eagle-server/src/main/webapp/app/dev/public/images/favicon_white.png
new file mode 100644
index 0000000..9879e92
Binary files /dev/null and b/eagle-server/src/main/webapp/app/dev/public/images/favicon_white.png differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/app.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/app.js b/eagle-server/src/main/webapp/app/dev/public/js/app.js
new file mode 100644
index 0000000..4ec77d9
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/app.js
@@ -0,0 +1,311 @@
+/*
+ * 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 app = {};
+
+(function() {
+	'use strict';
+
+	$(document).on("APPLICATION_READY", function (event, register) {
+		console.info("[Eagle] Angular bootstrap...");
+
+		var STATE_NAME_MATCH = /^[^.]*/;
+		var state_next;
+		var state_current;
+		var param_next;
+		var param_current;
+
+		// ======================================================================================
+		// =                                   Initialization                                   =
+		// ======================================================================================
+		var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngAnimate', 'ui.router', 'eagleControllers', 'eagle.service'].concat(register.appList));
+
+		// ======================================================================================
+		// =                                   Router config                                    =
+		// ======================================================================================
+		function routeResolve(config) {
+			var resolve = {};
+			if(config === false) return resolve;
+
+			config = $.extend({
+				auth: true,
+				site: true,
+				application: true
+			}, config);
+
+			if(config.auth) {
+				// TODO: need auth module
+			}
+
+			resolve.Site = function (Site) {
+				return Site.getPromise(config);
+			};
+
+			resolve.Application = function (Application) {
+				return Application.getPromise();
+			};
+
+			resolve.Time = function (Time) {
+				return Time.getPromise(config, state_next, param_next);
+			};
+
+			return resolve;
+		}
+
+		eagleApp.config(function ($stateProvider, $urlRouterProvider, $httpProvider, $animateProvider) {
+			$urlRouterProvider.otherwise("/");
+			$stateProvider
+			// ================================== Home ==================================
+				.state('home', {
+					url: "/",
+					templateUrl: "partials/home.html?_=" + window._TRS(),
+					controller: "homeCtrl",
+					resolve: routeResolve()
+				})
+				.state('setup', {
+					url: "/setup",
+					templateUrl: "partials/setup.html?_=" + window._TRS(),
+					controller: "setupCtrl",
+					resolve: routeResolve({ site: false, application: false })
+				})
+				// ================================= Alerts =================================
+				.state('alert', {
+					abstract: true,
+					url: "/alert/",
+					templateUrl: "partials/alert/main.html?_=" + window._TRS(),
+					controller: "alertCtrl",
+					resolve: routeResolve(false)
+				})
+				.state('alert.list', {
+					url: "",
+					templateUrl: "partials/alert/list.html?_=" + window._TRS(),
+					controller: "alertListCtrl",
+					resolve: routeResolve()
+				})
+				.state('alert.policyList', {
+					url: "policyList",
+					templateUrl: "partials/alert/policyList.html?_=" + window._TRS(),
+					controller: "policyListCtrl",
+					resolve: routeResolve()
+				})
+				.state('alert.policyCreate', {
+					url: "policyCreate",
+					templateUrl: "partials/alert/policyEdit.html?_=" + window._TRS(),
+					controller: "policyCreateCtrl",
+					resolve: routeResolve()
+				})
+				.state('alert.policyEdit', {
+					url: "policyEdit/{name}",
+					templateUrl: "partials/alert/policyEdit.html?_=" + window._TRS(),
+					controller: "policyEditCtrl",
+					resolve: routeResolve()
+				})
+				// =============================== Integration ==============================
+				.state('integration', {
+					abstract: true,
+					url: "/integration/",
+					templateUrl: "partials/integration/main.html?_=" + window._TRS(),
+					controller: "integrationCtrl",
+					resolve: routeResolve(false)
+				})
+				.state('integration.siteList', {
+					url: "siteList",
+					templateUrl: "partials/integration/siteList.html?_=" + window._TRS(),
+					controller: "integrationSiteListCtrl",
+					resolve: routeResolve({ application: false })
+				})
+				.state('integration.site', {
+					url: "site/:id",
+					templateUrl: "partials/integration/site.html?_=" + window._TRS(),
+					controller: "integrationSiteCtrl",
+					resolve: routeResolve({ application: false })
+				})
+				.state('integration.applicationList', {
+					url: "applicationList",
+					templateUrl: "partials/integration/applicationList.html?_=" + window._TRS(),
+					controller: "integrationApplicationListCtrl",
+					resolve: routeResolve({ application: false })
+				})
+				.state('integration.streamList', {
+					url: "streamList",
+					templateUrl: "partials/integration/streamList.html?_=" + window._TRS(),
+					controller: "integrationStreamListCtrl",
+					resolve: routeResolve()
+				})
+				// ================================== Site ==================================
+				.state('site', {
+					url: "/site/:siteId",
+					templateUrl: "partials/home.html?_=" + window._TRS(),
+					controller: "siteCtrl",
+					resolve: routeResolve()
+				})
+			;
+
+			// =========================== Application States ===========================
+			$.each(register.routeList, function (i, route) {
+				var config = $.extend({}, route.config);
+
+				var resolve = {};
+				var resolveConfig = {};
+				if(route.config.resolve) {
+					$.each(route.config.resolve, function (key, value) {
+						if (typeof value === "function") {
+							resolve[key] = value;
+						} else {
+							resolveConfig[key] = value;
+						}
+					});
+				}
+				config.resolve = $.extend(routeResolve(resolveConfig), resolve);
+
+				$stateProvider.state(route.state, config);
+			});
+
+			$httpProvider.interceptors.push(function($q) {
+				function eagleRequestHandle(res) {
+					var data = res.data || {
+						exception: "",
+						message: ""
+					};
+					if(res.status === -1) {
+						$.dialog({
+							title: "AJAX Failed",
+							content: $("<pre>")
+								.text("url:\n" + common.getValueByPath(res, ["config", "url"]))
+						});
+					} else if(data.success === false || res.status === 404) {
+						$.dialog({
+							title: "AJAX Error",
+							content: $("<pre>")
+								.text(
+									"url:\n" + common.getValueByPath(res, ["config", "url"]) + "\n\n" +
+									"status:\n" + res.status + "\n\n" +
+									"exception:\n" + data.exception + "\n\n" +
+									"message:\n" + data.message
+								)
+						});
+					}
+					return res;
+				}
+
+				return {
+					response: eagleRequestHandle,
+					responseError: function(res) {
+						return $q.reject(eagleRequestHandle(res));
+					}
+				};
+			});
+		});
+
+		// ======================================================================================
+		// =                                   Main Controller                                  =
+		// ======================================================================================
+		eagleApp.controller('MainCtrl', function ($scope, $wrapState, $urlRouter, PageConfig, Portal, Widget, Entity, Site, Application, UI, Time) {
+			window._WrapState = $scope.$wrapState = $wrapState;
+			window._PageConfig = $scope.PageConfig = PageConfig;
+			window._Portal = $scope.Portal = Portal;
+			window._Widget = $scope.Widget = Widget;
+			window._Entity = $scope.Entity = Entity;
+			window._Site = $scope.Site = Site;
+			window._Application = $scope.Application = Application;
+			window._UI = $scope.UI = UI;
+			window._Time = $scope.Time = Time;
+			$scope.common = common;
+
+			Object.defineProperty(window, "scope", {
+				get: function () {
+					return angular.element("#content .ng-scope").scope();
+				}
+			});
+
+			// ============================== Route Update ==============================
+			$scope.$on('$stateChangeStart', function (event, next, nextParam, current, currentParam) {
+				console.log("[Switch] current ->", current, currentParam);
+				console.log("[Switch] next ->", next, nextParam);
+
+				state_next = next || {};
+				state_current = current || {};
+				param_next = nextParam;
+				param_current = currentParam;
+
+				var currentName = (current || {}).name || "";
+				var nextName = (next || {}).name || "";
+
+				// Page initialization
+				if(currentName.match(STATE_NAME_MATCH)[0] !== nextName.match(STATE_NAME_MATCH)[0]) {
+					PageConfig.reset();
+				}
+			});
+
+			// ================================ Function ================================
+			// Get side bar navigation item class
+			$scope.getNavClass = function (portal) {
+				var path = (portal.path || "").replace(/^#/, '');
+
+				if ($wrapState.path() === path) {
+					return "active";
+				} else {
+					return "";
+				}
+			};
+
+			// Customize time range
+			$scope.customizeTimeRange = function () {
+				$("#eagleStartTime").val(Time.format("startTime"));
+				$("#eagleEndTime").val(Time.format("endTime"));
+				$("#eagleTimeRangeMDL").modal();
+			};
+
+			$scope.setLastDuration = function (hours) {
+				var endTime = new Time();
+				var startTime = endTime.clone().subtract(hours, "hours");
+				Time.timeRange(startTime, endTime);
+			};
+
+			$scope.updateTimeRange = function () {
+				var startTime = Time.verifyTime($("#eagleStartTime").val());
+				var endTime = Time.verifyTime($("#eagleEndTime").val());
+				if(startTime && endTime) {
+					Time.timeRange(startTime, endTime);
+					$("#eagleTimeRangeMDL").modal("hide");
+				} else {
+					alert("Time range not validate");
+				}
+			};
+
+			// ================================== Init ==================================
+			$.each(register.portalList, function (i, config) {
+				Portal.register(config.portal, config.isSite);
+			});
+
+			$.each(register.widgetList, function (i, config) {
+				Widget.register(config.widget, config.isSite);
+			});
+		});
+
+		// ======================================================================================
+		// =                                      Bootstrap                                     =
+		// ======================================================================================
+		//noinspection JSCheckFunctionSignatures
+		angular.element(document).ready(function() {
+			console.info("[Eagle] UI start...");
+			//noinspection JSCheckFunctionSignatures
+			angular.bootstrap(document, ['eagleApp']);
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/common.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/common.js b/eagle-server/src/main/webapp/app/dev/public/js/common.js
new file mode 100644
index 0000000..9f5c4b1
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/common.js
@@ -0,0 +1,387 @@
+/*
+ * 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 () {
+	'use strict';
+
+	var scope = {};
+	if(typeof window != 'undefined') {
+		scope = window;
+	} else if(typeof self != 'undefined') {
+		scope = self;
+	}
+	var common = scope.common = {};
+
+	// ============================ 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 can't be empty!";
+		if(path === "" || path === null || path === undefined) return unit;
+
+		if(typeof path === "string") {
+			path = path.replace(/\[(\d+)\]/g, ".$1").replace(/^\./, "").split(/\./);
+		}
+		for(var i = 0 ; i < path.length ; i += 1) {
+			unit = unit[path[i]];
+			if(unit === null || unit === undefined) {
+				unit = null;
+				break;
+			}
+		}
+		if(unit === null && defaultValue !== undefined) {
+			unit = defaultValue;
+		}
+		return unit;
+	};
+
+	common.setValueByPath = function(unit, path, value) {
+		if(!unit || typeof path !== "string" || 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 {
+			str = (str + "").trim();
+			if(Number(str).toString() === str) throw "Number format";
+			return JSON.parse(str);
+		} catch(err) {
+			if(defaultVal === undefined) {
+				console.warn("Can't parse JSON: " + str);
+			}
+		}
+		return defaultVal === undefined ? null : defaultVal;
+	};
+
+	common.stringify = function(json) {
+		return JSON.stringify(json, function(key, value) {
+			if(/^(_|\$)/.test(key)) return undefined;
+			return value;
+		});
+	};
+
+	common.isEmpty = function(val) {
+		if($.isArray(val)) {
+			return val.length === 0;
+		} else {
+			return val === null || val === undefined;
+		}
+	};
+
+	common.extend = function(target, origin) {
+		$.each(origin, function(key, value) {
+			if(/^(_|\$)/.test(key)) return;
+
+			target[key] = value;
+		});
+		return target;
+	};
+
+	function merge(obj1, obj2) {
+		$.each(obj2, function (key, value) {
+			var oriValue = obj1[key];
+
+			if(typeof oriValue === "object" && typeof value === "object" && !common.isEmpty(value)) {
+				merge(oriValue, value);
+			} else {
+				obj1[key] = value;
+			}
+		});
+	}
+
+	common.merge = function (mergedObj) {
+		for(var i = 1 ; i < arguments.length ; i += 1) {
+			var obj = arguments[i];
+			merge(mergedObj, obj);
+		}
+
+		return mergedObj;
+	};
+
+	// ============================ String ============================
+	common.string = {};
+	common.string.safeText = function (str) {
+		return str
+			.replace(/&/g, '&amp;')
+			.replace(/</g, '&lt;')
+			.replace(/>/g, '&gt;');
+	};
+
+	common.string.capitalize = function (str) {
+		return (str + "").replace(/\b\w/g, function(match) {
+			return match.toUpperCase();
+		});
+	};
+
+	common.string.preFill = function (str, key, len) {
+		str = str + "";
+		len = len || 2;
+		while(str.length < len) {
+			str = key + str;
+		}
+		return str;
+	};
+
+	// ============================ Array =============================
+	common.array = {};
+
+	common.array.findIndex = function(val, list, path, findAll, caseSensitive) {
+		var _list = [];
+		val = caseSensitive === false ? (val + "").toUpperCase() : val;
+
+		for(var i = 0 ; i < list.length ; i += 1) {
+			var unit = list[i];
+			var _val = common.getValueByPath(unit, path);
+			_val = caseSensitive === false ? (_val + "").toUpperCase() : _val;
+
+			if(_val === val) {
+				if(!findAll) return i;
+				_list.push(i);
+			}
+		}
+
+		return findAll ? _list: -1;
+	};
+
+	common.array.find = function(val, list, path, findAll, caseSensitive) {
+		var index = common.array.findIndex(val, list, path, findAll, caseSensitive);
+
+		if(findAll) {
+			return $.map(index, function (index) {
+				return list[index];
+			});
+		} else {
+			return index === -1 ? null : list[index];
+		}
+	};
+
+	common.array.minus = function (list1, list2, path1, path2) {
+		if(arguments.length === 3) path2 = path1;
+		var list = [];
+		$.each(list1, function (i, item) {
+			var val1 = common.getValueByPath(item, path1);
+			if(!common.array.find(val1, list2, path2)) {
+				list.push(item);
+			}
+		});
+		return list;
+	};
+
+	common.array.doSort = function (list, path, asc, sortList) {
+		var sortFunc;
+		sortList = sortList || [];
+
+		if(asc !== false) {
+			sortFunc = function (obj1, obj2) {
+				var val1 = common.getValueByPath(obj1, path);
+				var val2 = common.getValueByPath(obj2, path);
+
+				var index1 = common.array.findIndex(val1, sortList);
+				var index2 = common.array.findIndex(val2, sortList);
+
+				if(index1 !== -1 && index2 === -1) {
+					return -1;
+				} else if(index1 == -1 && index2 !== -1) {
+					return 1;
+				} else if(index1 !== -1 && index2 !== -1) {
+					return index1 - index2;
+				}
+
+				if (val1 === val2) {
+					return 0;
+				} else if (val1 === null || val1 === undefined || val1 < val2) {
+					return -1;
+				}
+				return 1;
+			};
+		} else {
+			sortFunc = function (obj1, obj2) {
+				var val1 = common.getValueByPath(obj1, path);
+				var val2 = common.getValueByPath(obj2, path);
+
+				var index1 = common.array.findIndex(val1, sortList);
+				var index2 = common.array.findIndex(val2, sortList);
+
+				if(index1 !== -1 && index2 === -1) {
+					return -1;
+				} else if(index1 == -1 && index2 !== -1) {
+					return 1;
+				} else if(index1 !== -1 && index2 !== -1) {
+					return index1 - index2;
+				}
+
+				if (val1 === val2) {
+					return 0;
+				} else if (val1 === null || val1 === undefined || val1 < val2) {
+					return 1;
+				}
+				return -1;
+			};
+		}
+
+		return list.sort(sortFunc);
+	};
+
+	// =========================== Deferred ===========================
+	common.deferred = {};
+
+
+	common.deferred.all = function (deferredList) {
+		var deferred = $.Deferred();
+		var successList = [];
+		var failureList = [];
+		var hasFailure = false;
+		var rest = deferredList.length;
+		function doCheck() {
+			rest -= 1;
+			if(rest === 0) {
+				if(hasFailure) {
+					deferred.reject(failureList);
+				} else {
+					deferred.resolve(successList);
+				}
+			}
+		}
+
+		$.each(deferredList, function (i, deferred) {
+			if(deferred && deferred.then) {
+				deferred.then(function (data) {
+					successList[i] = data;
+				}, function (data) {
+					failureList[i] = data;
+					hasFailure = true;
+				}).always(doCheck);
+			} else {
+				successList[i] = deferred;
+				doCheck();
+			}
+		});
+
+		return deferred;
+	};
+
+	// ============================ Number ============================
+	common.number = {};
+
+	common.number.isNumber = function (num) {
+		return typeof num === "number" && !isNaN(num);
+	};
+
+	common.number.toFixed = function (num, fixed) {
+		if(!common.number.isNumber(num)) return "-";
+		num = Number(num);
+		return num.toFixed(fixed || 0);
+	};
+
+	common.number.format = function (num, fixed) {
+		if(!common.number.isNumber(num)) return "-";
+		return common.number.toFixed(num, fixed).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+	};
+
+	common.number.abbr = function (number, isByte, digits) {
+		digits = digits || 2;
+		var decPlaces = Math.pow(10, digits);
+		var abbrev = isByte ? ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['K', 'M', 'B', 'T', 'Q'];
+		var base = isByte ? 1024 : 1000;
+		var sign = number < 0 ? -1 : 1;
+		var unit = '';
+		number = Math.abs(number);
+
+		for(var i = abbrev.length - 1; i >= 0; i--) {
+			var size = Math.pow(base, i + 1);
+			if(size <= number) {
+				number = Math.round(number * decPlaces / size) / decPlaces;
+				if((number === base) && (i < abbrev.length - 1)) {
+					number = 1;
+					i++;
+				}
+				unit = abbrev[i];
+				break;
+			}
+		}
+		unit = unit ? unit : "";
+		return (number * sign).toFixed(digits) + unit;
+	};
+
+	common.number.compare = function (num1, num2) {
+		if(!common.number.isNumber(num1) || !common.number.isNumber(num2)) return "-";
+		if(num1 === 0) return 'N/A';
+		return (num2 - num1) / num1;
+	};
+
+	common.number.inRange = function (rangList, num) {
+		for(var i = 0 ; i < rangList.length - 1 ; i += 1) {
+			var start = rangList[i];
+			var end = rangList[i + 1];
+			if(start <= num && num < end) return i;
+		}
+		return rangList.length - 1;
+	};
+
+	common.number.sum = function (list, path) {
+		var total = 0;
+		$.each(list, function (i, obj) {
+			var value = common.getValueByPath(obj, path);
+			if(typeof value === "number" && !isNaN(value)) {
+				total += value;
+			}
+		});
+		return total;
+	};
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/components/chart.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/chart.js b/eagle-server/src/main/webapp/app/dev/public/js/components/chart.js
new file mode 100644
index 0000000..99f74d5
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/chart.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.
+ */
+
+(function() {
+	'use strict';
+
+	var eagleComponents = angular.module('eagle.components');
+
+	eagleComponents.service('Chart', function () {
+		return {
+			color: [ "#0073b7", "#dd4b39", "#00a65a", "#f39c12", "#605ca8", "#001F3F", "#39CCCC", "#D81B60", "#3c8dbc", "#f56954", "#00c0ef", "#3D9970", "#FF851B"  , "#01FF70", "#F012BE"],
+			//color: ['#4285f4', '#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83',  '#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3'],
+			charts: {}
+		};
+	});
+
+	eagleComponents.directive('chart', function(Chart) {
+		var charts = Chart.charts;
+
+		function chartResize() {
+			setTimeout(function () {
+				$.each(charts, function (id, chart) {
+					chart.resize();
+				});
+			}, 310);
+		}
+
+		$(window).resize(chartResize);
+		$("body").on("expanded.pushMenu collapsed.pushMenu", chartResize);
+
+		return {
+			restrict: 'AE',
+			scope: {
+				title: "@?title",
+				series: "=",
+				category: "=?category",
+				categoryFunc: "=?categoryFunc",
+				xTitle: "@?xTitle",
+				yTitle: "@?yTitle",
+
+				option: "=?option",
+
+				click: "=?ngClick",
+
+				chart: "@?chart"
+			},
+			controller: function ($scope, $element, $attrs, Time) {
+				var i;
+				var lastTooltipEvent;
+				var chart = echarts.init($element[0]);
+				charts[chart.id] = chart;
+
+				function refreshChart() {
+					var maxYAxis = 0;
+					var legendList = [];
+					var categoryList = $scope.category ? $scope.category : [];
+
+					var seriesList = $.map($scope.series || [], function (series, id) {
+						if(id === 0 && !$scope.category) {
+							//var preDate = -1;
+							categoryList = $.map(series.data, function (point) {
+								/*ivar time = new Time(point.x);
+								f(preDate !== time.date()) {
+									preDate = time.date();
+									return Time.format(point.x, "MMM.D HH:mm");
+								}*/
+								if($scope.categoryFunc) {
+									return $scope.categoryFunc(point.x);
+								}
+								return Time.format(point.x, "HH:mm");
+							});
+						}
+
+						legendList.push(series.name);
+						if(series.yAxisIndex) maxYAxis = Math.max(series.yAxisIndex, maxYAxis);
+
+						return $.extend({}, series, {
+							data: $scope.category ? series.data : $.map(series.data, function (point) {
+								return point.y;
+							})
+						});
+					});
+
+					var yAxis = [];
+					for(i = 0 ; i <= maxYAxis ; i += 1) {
+						yAxis.push({
+							name: $scope.yTitle,
+							type: "value"
+						});
+					}
+
+					var option = {
+						color: Chart.color.concat(),
+						title: [{text: $scope.title}],
+						tooltip: {trigger: 'axis'},
+						legend: [{
+							data: legendList
+						}],
+						grid: {
+							top: '30',
+							left: '0',
+							right: '0',
+							bottom: '0',
+							containLabel: true
+						},
+						xAxis: {
+							name: $scope.xTitle,
+							type: 'category',
+							data: categoryList,
+							axisTick: { show: false }
+						},
+						yAxis: yAxis,
+						series: seriesList
+					};
+
+					if($scope.option) {
+						option = common.merge(option, $scope.option);
+					}
+
+					chart.setOption(option);
+				}
+
+				// Event handle
+				var chartClick = false;
+				chart.on("click", function (e) {
+					if($scope.click) {
+						if($scope.click(e)) {
+							refreshChart();
+						}
+					}
+					chartClick = true;
+				});
+
+				chart.getZr().on('click', function () {
+					if(!chartClick && $scope.click) {
+						if($scope.click($.extend({
+							componentType: "tooltip"
+						}, lastTooltipEvent))) {
+							refreshChart();
+						}
+					}
+					chartClick = false;
+				});
+
+				chart.on('showtip', function (e) {
+					lastTooltipEvent = e;
+				});
+
+				// Insert chart object to parent scope
+				if($attrs.chart) {
+					$scope.$parent.$parent[$attrs.chart] = chart;
+				}
+
+				chart.refresh = function () {
+					refreshChart();
+				};
+
+				// Render
+				refreshChart();
+				$scope.$watch("series", refreshChart);
+
+				$scope.$on('$destroy', function() {
+					delete charts[chart.id];
+					chart.dispose();
+
+					delete $scope.$parent.$parent[$attrs.chart];
+				});
+			},
+			template: '<div>Loading...</div>',
+			replace: true
+		};
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/components/main.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/main.js b/eagle-server/src/main/webapp/app/dev/public/js/components/main.js
new file mode 100644
index 0000000..0c8a54c
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/main.js
@@ -0,0 +1,24 @@
+/*
+ * 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() {
+	'use strict';
+
+	angular.module('eagle.components', []);
+})();


[08/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metrics/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metrics/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/metrics/controller.js
new file mode 100644
index 0000000..d717ad1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/metrics/controller.js
@@ -0,0 +1,571 @@
+/*
+ * 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() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("metrics");
+
+	// ==============================================================
+	// =                       Initialization                       =
+	// ==============================================================
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+	// Format dashboard unit. Will adjust format with old version and add miss attributes.
+	feature.service("DashboardFormatter", function() {
+		return {
+			parse: function(unit) {
+				unit = unit || {};
+				unit.groups = unit.groups || [];
+
+				$.each(unit.groups, function (i, group) {
+					group.charts = group.charts || [];
+					$.each(group.charts, function (i, chart) {
+						if (!chart.metrics && chart.metric) {
+							chart.metrics = [{
+								aggregations: chart.aggregations,
+								dataSource: chart.dataSource,
+								metric: chart.metric
+							}];
+
+							delete chart.aggregations;
+							delete chart.dataSource;
+							delete chart.metric;
+						} else if (!chart.metrics) {
+							chart.metrics = [];
+						}
+					});
+				});
+
+				return unit;
+			}
+		};
+	});
+
+	// ==============================================================
+	// =                         Controller                         =
+	// ==============================================================
+
+	// ========================= Dashboard ==========================
+	feature.navItem("dashboard", "Metrics", "line-chart");
+
+	feature.controller('dashboard', function(PageConfig, $scope, $http, $q, UI, Site, Authorization, Application, Entities, DashboardFormatter) {
+		var _siteApp = Site.currentSiteApplication();
+		var _druidConfig = _siteApp.configObj.getValueByPath("web.druid");
+		var _refreshInterval;
+
+		var _menu_newChart;
+
+		$scope.lock = false;
+
+		$scope.dataSourceListReady = false;
+		$scope.dataSourceList = [];
+		$scope.dashboard = {
+			groups: []
+		};
+		$scope.dashboardEntity = null;
+		$scope.dashboardReady = false;
+
+		$scope._newMetricFilter = "";
+		$scope._newMetricDataSrc = null;
+		$scope._newMetricDataMetric = null;
+
+		$scope.tabHolder = {};
+
+		$scope.endTime = app.time.now();
+		$scope.startTime = $scope.endTime.clone();
+
+		// =================== Initialization ===================
+		if(!_druidConfig || !_druidConfig.coordinator || !_druidConfig.broker) {
+			$.dialog({
+				title: "OPS",
+				content: "Druid configuration can't be empty!"
+			});
+			return;
+		}
+
+		$scope.autoRefreshList = [
+			{title: "Last 1 Month", timeDes: "day", getStartTime: function(endTime) {return endTime.clone().subtract(1, "month");}},
+			{title: "Last 1 Day", timeDes: "thirty_minute", getStartTime: function(endTime) {return endTime.clone().subtract(1, "day");}},
+			{title: "Last 6 Hour", timeDes: "fifteen_minute", getStartTime: function(endTime) {return endTime.clone().subtract(6, "hour");}},
+			{title: "Last 2 Hour", timeDes: "fifteen_minute", getStartTime: function(endTime) {return endTime.clone().subtract(2, "hour");}},
+			{title: "Last 1 Hour", timeDes: "minute", getStartTime: function(endTime) {return endTime.clone().subtract(1, "hour");}}
+		];
+		$scope.autoRefreshSelect = $scope.autoRefreshList[2];
+
+		// ====================== Function ======================
+		$scope.setAuthRefresh = function(item) {
+			$scope.autoRefreshSelect = item;
+			$scope.refreshAllChart(true);
+		};
+
+		$scope.refreshTimeDisplay = function() {
+			PageConfig.pageSubTitle = common.format.date($scope.startTime) + " ~ " + common.format.date($scope.endTime) + " [refresh interval: 30s]";
+		};
+		$scope.refreshTimeDisplay();
+
+		// ======================= Metric =======================
+		// Fetch metric data
+		$http.get(_druidConfig.coordinator + "/druid/coordinator/v1/metadata/datasources", {withCredentials: false}).then(function(data) {
+			var _endTime = new moment();
+			var _startTime = _endTime.clone().subtract(1, "day");
+			var _intervals = _startTime.toISOString() + "/" + _endTime.toISOString();
+
+			$scope.dataSourceList = $.map(data.data, function(dataSrc) {
+				return {
+					dataSource: dataSrc,
+					metricList: []
+				};
+			});
+
+			// List dataSource metrics
+			var _metrixList_promiseList = $.map($scope.dataSourceList, function(dataSrc) {
+				var _data = JSON.stringify({
+					"queryType": "groupBy",
+					"dataSource": dataSrc.dataSource,
+					"granularity": "all",
+					"dimensions": ["metric"],
+					"aggregations": [
+						{
+							"type":"count",
+							"name":"count"
+						}
+					],
+					"intervals": [_intervals]
+				});
+
+				return $http.post(_druidConfig.broker + "/druid/v2", _data, {withCredentials: false}).then(function(response) {
+					dataSrc.metricList = $.map(response.data, function(entity) {
+						return entity.event.metric;
+					});
+				});
+			});
+
+			$q.all(_metrixList_promiseList).finally(function() {
+				$scope.dataSourceListReady = true;
+
+				$scope._newMetricDataSrc = $scope.dataSourceList[0];
+				$scope._newMetricDataMetric = common.getValueByPath($scope._newMetricDataSrc, "metricList.0");
+			});
+		}, function() {
+			$.dialog({
+				title: "OPS",
+				content: "Fetch data source failed. Please check Site Application Metrics configuration."
+			});
+		});
+
+		// Filter data source
+		$scope.dataSourceMetricList = function(dataSrc, filter) {
+			filter = (filter || "").toLowerCase().trim().split(/\s+/);
+			return $.grep((dataSrc && dataSrc.metricList) || [], function(metric) {
+				for(var i = 0 ; i < filter.length ; i += 1) {
+					if(metric.toLowerCase().indexOf(filter[i]) === -1) return false;
+				}
+				return true;
+			});
+		};
+
+		// New metric select
+		$scope.newMetricSelectDataSource = function(dataSrc) {
+			if(dataSrc !== $scope._newMetricDataMetric) $scope._newMetricDataMetric = dataSrc.metricList[0];
+			$scope._newMetricDataSrc = dataSrc;
+		};
+		$scope.newMetricSelectMetric = function(metric) {
+			$scope._newMetricDataMetric = metric;
+		};
+
+		// Confirm new metric
+		$scope.confirmSelectMetric = function() {
+			var group = $scope.tabHolder.selectedPane.data;
+			var metric = {
+				dataSource: $scope._newMetricDataSrc.dataSource,
+				metric: $scope._newMetricDataMetric,
+				aggregations: ["max"]
+			};
+			$("#metricMDL").modal('hide');
+
+			if($scope.metricForConfigChart) {
+				$scope.configPreviewChart.metrics.push(metric);
+				$scope.refreshChart($scope.configPreviewChart, true, true);
+			} else {
+				group.charts.push({
+					chart: "line",
+					metrics: [metric]
+				});
+				$scope.refreshAllChart();
+			}
+		};
+
+		// ======================== Menu ========================
+		function _checkGroupName(entity) {
+			if(common.array.find(entity.name, $scope.dashboard.groups, "name")) {
+				return "Group name conflict";
+			}
+		}
+
+		$scope.newGroup = function() {
+			if($scope.lock) return;
+
+			UI.createConfirm("Group", {}, [{field: "name"}], _checkGroupName).then(null, null, function(holder) {
+				$scope.dashboard.groups.push({
+					name: holder.entity.name,
+					charts: []
+				});
+				holder.closeFunc();
+
+				setTimeout(function() {
+					$scope.tabHolder.setSelect(holder.entity.name);
+				}, 0);
+			});
+		};
+
+		function renameGroup() {
+			var group = $scope.tabHolder.selectedPane.data;
+			UI.updateConfirm("Group", {}, [{field: "name", name: "New Name"}], _checkGroupName).then(null, null, function(holder) {
+				group.name = holder.entity.name;
+				holder.closeFunc();
+			});
+		}
+
+		function deleteGroup() {
+			var group = $scope.tabHolder.selectedPane.data;
+			UI.deleteConfirm(group.name).then(null, null, function(holder) {
+				common.array.remove(group, $scope.dashboard.groups);
+				holder.closeFunc();
+			});
+		}
+
+		_menu_newChart = {title: "Add Metric", func: function() {$scope.newChart();}};
+		Object.defineProperties(_menu_newChart, {
+			icon: {
+				get: function() {return $scope.dataSourceListReady ? 'plus' : 'refresh fa-spin';}
+			},
+			disabled: {
+				get: function() {return !$scope.dataSourceListReady;}
+			}
+		});
+
+		$scope.menu = Authorization.isRole('ROLE_ADMIN') ? [
+			{icon: "cog", title: "Configuration", list: [
+				_menu_newChart,
+				{icon: "pencil", title: "Rename Group", func: renameGroup},
+				{icon: "trash", title: "Delete Group", danger: true, func: deleteGroup}
+			]},
+			{icon: "plus", title: "New Group", func: $scope.newGroup}
+		] : [];
+
+		// ===================== Dashboard ======================
+		$scope.dashboardList = Entities.queryEntities("GenericResourceService", {
+			site: Site.current().tags.site,
+			application: Application.current().tags.application
+		});
+		$scope.dashboardList._promise.then(function(list) {
+			$scope.dashboardEntity = list[0];
+			$scope.dashboard = DashboardFormatter.parse(common.parseJSON($scope.dashboardEntity.value));
+			$scope.refreshAllChart();
+		}).finally(function() {
+			$scope.dashboardReady = true;
+		});
+
+		$scope.saveDashboard = function() {
+			$scope.lock = true;
+
+			if(!$scope.dashboardEntity) {
+				$scope.dashboardEntity = {
+					tags: {
+						site: Site.current().tags.site,
+						application: Application.current().tags.application,
+						name: "/metric_dashboard/dashboard/default"
+					}
+				};
+			}
+			$scope.dashboardEntity.value = common.stringify($scope.dashboard);
+
+			Entities.updateEntity("GenericResourceService", $scope.dashboardEntity)._promise.then(function() {
+				$.dialog({
+					title: "Done",
+					content: "Save success!"
+				});
+			}, function() {
+				$.dialog({
+					title: "POS",
+					content: "Save failed. Please retry."
+				});
+			}).finally(function() {
+				$scope.lock = false;
+			});
+		};
+
+		// ======================= Chart ========================
+		$scope.configTargetChart = null;
+		$scope.configPreviewChart = null;
+		$scope.metricForConfigChart = false;
+		$scope.viewChart = null;
+
+		$scope.chartConfig = {
+			xType: "time"
+		};
+
+		$scope.chartTypeList = [
+			{icon: "line-chart", chart: "line"},
+			{icon: "area-chart", chart: "area"},
+			{icon: "bar-chart", chart: "column"},
+			{icon: "pie-chart", chart: "pie"}
+		];
+
+		$scope.chartSeriesList = [
+			{name: "Min", series: "min"},
+			{name: "Max", series: "max"},
+			{name: "Avg", series: "avg"},
+			{name: "Count", series: "count"},
+			{name: "Sum", series: "sum"}
+		];
+
+		$scope.newChart = function() {
+			$scope.metricForConfigChart = false;
+			$("#metricMDL").modal();
+		};
+
+		$scope.configPreviewChartMinimumCheck = function() {
+			$scope.configPreviewChart.min = $scope.configPreviewChart.min === 0 ? undefined : 0;
+		};
+
+		$scope.seriesChecked = function(metric, series) {
+			if(!metric) return false;
+			return $.inArray(series, metric.aggregations || []) !== -1;
+		};
+		$scope.seriesCheckClick = function(metric, series, chart) {
+			if(!metric || !chart) return;
+			if($scope.seriesChecked(metric, series)) {
+				common.array.remove(series, metric.aggregations);
+			} else {
+				metric.aggregations.push(series);
+			}
+			$scope.chartSeriesUpdate(chart);
+		};
+
+		$scope.chartSeriesUpdate = function(chart) {
+			chart._data = $.map(chart._oriData, function(groupData, i) {
+				var metric = chart.metrics[i];
+				return $.map(groupData, function(series) {
+					if($.inArray(series._key, metric.aggregations) !== -1) return series;
+				});
+			});
+		};
+
+		$scope.configAddMetric = function() {
+			$scope.metricForConfigChart = true;
+			$("#metricMDL").modal();
+		};
+
+		$scope.configRemoveMetric = function(metric) {
+			common.array.remove(metric, $scope.configPreviewChart.metrics);
+		};
+
+		$scope.getChartConfig = function(chart) {
+			if(!chart) return null;
+
+			var _config = chart._config = chart._config || $.extend({}, $scope.chartConfig);
+			_config.yMin = chart.min;
+
+			return _config;
+		};
+
+		$scope.configChart = function(chart) {
+			$scope.configTargetChart = chart;
+			$scope.configPreviewChart = $.extend({}, chart);
+			$scope.configPreviewChart.metrics = $.map(chart.metrics, function(metric) {
+				return $.extend({}, metric, {aggregations: (metric.aggregations || []).slice()});
+			});
+			delete $scope.configPreviewChart._config;
+			$("#chartMDL").modal();
+			setTimeout(function() {
+				$(window).resize();
+			}, 200);
+		};
+
+		$scope.confirmUpdateChart = function() {
+			$("#chartMDL").modal('hide');
+			$.extend($scope.configTargetChart, $scope.configPreviewChart);
+			$scope.chartSeriesUpdate($scope.configTargetChart);
+			if($scope.configTargetChart._holder) $scope.configTargetChart._holder.refreshAll();
+			$scope.configPreviewChart = null;
+		};
+
+		$scope.deleteChart = function(group, chart) {
+			UI.deleteConfirm(chart.metric).then(null, null, function(holder) {
+				common.array.remove(chart, group.charts);
+				holder.closeFunc();
+				$scope.refreshAllChart(false, true);
+			});
+		};
+
+		$scope.showChart = function(chart) {
+			$scope.viewChart = chart;
+			$("#chartViewMDL").modal();
+			setTimeout(function() {
+				$(window).resize();
+			}, 200);
+		};
+
+		$scope.refreshChart = function(chart, forceRefresh, refreshAll) {
+			var _intervals = $scope.startTime.toISOString() + "/" + $scope.endTime.toISOString();
+
+			function _refreshChart() {
+				if (chart._holder) {
+					if (refreshAll) {
+						chart._holder.refreshAll();
+					} else {
+						chart._holder.refresh();
+					}
+				}
+			}
+
+			var _tmpData, _metricPromiseList;
+
+			if (chart._data && !forceRefresh) {
+				// Refresh chart without reload
+				_refreshChart();
+			} else {
+				// Refresh chart with reload
+				_tmpData = [];
+				_metricPromiseList = $.map(chart.metrics, function (metric, k) {
+					// Each Metric
+					var _query = JSON.stringify({
+						"queryType": "groupBy",
+						"dataSource": metric.dataSource,
+						"granularity": $scope.autoRefreshSelect.timeDes,
+						"dimensions": ["metric"],
+						"filter": {"type": "selector", "dimension": "metric", "value": metric.metric},
+						"aggregations": [
+							{
+								"type": "max",
+								"name": "max",
+								"fieldName": "maxValue"
+							},
+							{
+								"type": "min",
+								"name": "min",
+								"fieldName": "maxValue"
+							},
+							{
+								"type": "count",
+								"name": "count",
+								"fieldName": "maxValue"
+							},
+							{
+								"type": "longSum",
+								"name": "sum",
+								"fieldName": "maxValue"
+							}
+						],
+						"postAggregations" : [
+							{
+								"type": "javascript",
+								"name": "avg",
+								"fieldNames": ["sum", "count"],
+								"function": "function(sum, cnt) { return sum / cnt;}"
+							}
+						],
+						"intervals": [_intervals]
+					});
+
+					return $http.post(_druidConfig.broker + "/druid/v2", _query, {withCredentials: false}).then(function (response) {
+						var _data = nvd3.convert.druid([response.data]);
+						_tmpData[k] = _data;
+
+						// Process series name
+						$.each(_data, function(i, series) {
+							series._key = series.key;
+							if(chart.metrics.length > 1) {
+								series.key = metric.metric.replace(/^.*\./, "") + "-" +series._key;
+							}
+						});
+					});
+				});
+
+				$q.all(_metricPromiseList).then(function() {
+					chart._oriData = _tmpData;
+					$scope.chartSeriesUpdate(chart);
+					_refreshChart();
+				});
+			}
+		};
+
+		$scope.refreshAllChart = function(forceRefresh, refreshAll) {
+			setTimeout(function() {
+				$scope.endTime = app.time.now();
+				$scope.startTime = $scope.autoRefreshSelect.getStartTime($scope.endTime);
+
+				$scope.refreshTimeDisplay();
+
+				$.each($scope.dashboard.groups, function (i, group) {
+					$.each(group.charts, function (j, chart) {
+						$scope.refreshChart(chart, forceRefresh, refreshAll);
+					});
+				});
+
+				$(window).resize();
+			}, 0);
+		};
+
+		$scope.chartSwitchRefresh = function(source, target) {
+			var _oriSize = source.size;
+			source.size = target.size;
+			target.size = _oriSize;
+
+			if(source._holder) source._holder.refreshAll();
+			if(target._holder) target._holder.refreshAll();
+
+		};
+
+		_refreshInterval = setInterval(function() {
+			if(!$scope.dashboardReady) return;
+			$scope.refreshAllChart(true);
+		}, 1000 * 30);
+
+		// > Chart UI
+		$scope.configChartSize = function(chart, sizeOffset) {
+			chart.size = (chart.size || 6) + sizeOffset;
+			if(chart.size <= 0) chart.size = 1;
+			if(chart.size > 12) chart.size = 12;
+			setTimeout(function() {
+				$(window).resize();
+			}, 1);
+		};
+
+		// ========================= UI =========================
+		$("#metricMDL").on('hidden.bs.modal', function () {
+			if($(".modal-backdrop").length) {
+				$("body").addClass("modal-open");
+			}
+		});
+
+		$("#chartViewMDL").on('hidden.bs.modal', function () {
+			$scope.viewChart = null;
+		});
+
+		// ====================== Clean Up ======================
+		$scope.$on('$destroy', function() {
+			clearInterval(_refreshInterval);
+		});
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metrics/page/dashboard.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metrics/page/dashboard.html b/eagle-webservice/src/main/webapp/_app/public/feature/metrics/page/dashboard.html
new file mode 100644
index 0000000..2acb954
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/metrics/page/dashboard.html
@@ -0,0 +1,250 @@
+<!--
+  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.
+  -->
+
+<div class="page-fixed">
+	<div class="dropdown inline">
+		<button data-toggle="dropdown" class="btn btn-primary">
+			<span class="fa fa-clock-o"></span> {{autoRefreshSelect.title}}
+		</button>
+		<ul class="dropdown-menu left">
+			<li ng-repeat="item in autoRefreshList track by $index">
+				<a ng-click="setAuthRefresh(item)">
+					<span class="fa fa-clock-o"></span>
+					<span ng-class="{'text-bold': item === autoRefreshSelect}">{{item.title}}</span>
+				</a>
+			</li>
+		</ul>
+	</div>
+
+	<button class="btn btn-primary" ng-if="Auth.isRole('ROLE_ADMIN')"
+			ng-click="saveDashboard()" ng-disabled="lock">
+		<span class="fa fa-floppy-o"></span> Save
+	</button>
+</div>
+
+<div class="box box-default" ng-if="!dashboard.groups.length">
+	<div class="box-header with-border">
+		<h3 class="box-title">Empty Dashboard</h3>
+	</div>
+	<div class="box-body">
+		<div ng-show="!dashboardReady">
+			Loading...
+		</div>
+		<div ng-show="dashboardReady">
+			Current dashboard is empty.
+			<span ng-if="Auth.isRole('ROLE_ADMIN')">Click <a ng-click="newGroup()">here</a> to create a new group.</span>
+		</div>
+	</div>
+	<div class="overlay" ng-show="!dashboardReady">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>
+
+<div tabs menu="menu" holder="tabHolder" ng-show="dashboard.groups.length" data-sortable-model="Auth.isRole('ROLE_ADMIN') ? dashboard.groups : null">
+	<pane ng-repeat="group in dashboard.groups" data-data="group" data-title="{{group.name}}">
+		<div uie-sortable ng-model="group.charts" class="row narrow" sortable-update-func="chartSwitchRefresh" ng-show="group.charts.length">
+			<div ng-repeat="chart in group.charts track by $index" class="col-md-{{chart.size || 6}}">
+				<div class="nvd3-chart-wrapper">
+					<div nvd3="chart._data" data-holder="chart._holder" data-title="{{chart.title || chart.metrics[0].metric}}" data-watching="false"
+						 data-chart="{{chart.chart || 'line'}}" data-config="getChartConfig(chart)" class="nvd3-chart-cntr"></div>
+					<div class="nvd3-chart-config">
+						<a class="fa fa-expand" ng-click="showChart(chart, -1)"></a>
+						<span ng-if="Auth.isRole('ROLE_ADMIN')">
+							<a class="fa fa-minus" ng-click="configChartSize(chart, -1)"></a>
+							<a class="fa fa-plus" ng-click="configChartSize(chart, 1)"></a>
+							<a class="fa fa-cog" ng-click="configChart(chart)"></a>
+							<a class="fa fa-trash" ng-click="deleteChart(group, chart)"></a>
+						</span>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<p ng-if="!group.charts.length">
+			Empty group.
+			<span ng-if="Auth.isRole('ROLE_ADMIN')">
+				Click
+				<span ng-hide="dataSourceListReady" class="fa fa-refresh fa-spin"></span>
+				<a ng-show="dataSourceListReady" ng-click="newChart()">here</a>
+				to add metric.
+			</span>
+		</p>
+	</pane>
+</div>
+
+
+
+<!-- Modal: Chart configuration -->
+<div class="modal fade" id="chartMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Chart Configuration</h4>
+			</div>
+			<div class="modal-body">
+				<div class="row">
+					<div class="col-md-6">
+						<div class="nvd3-chart-wrapper">
+							<div nvd3="configPreviewChart._data" data-title="{{configPreviewChart.title || configPreviewChart.metrics[0].metric}}"
+								 data-watching="true" data-chart="{{configPreviewChart.chart || 'line'}}" data-config="getChartConfig(configPreviewChart)" class="nvd3-chart-cntr"></div>
+						</div>
+					</div>
+					<div class="col-md-6">
+						<!-- Chart Configuration -->
+						<table class="table">
+							<tbody>
+							<tr>
+								<th width="100">Name</th>
+								<td><input type="text" class="form-control input-xs" ng-model="configPreviewChart.title" placeholder="Default: {{configPreviewChart.metrics[0].metric}}" /></td>
+							</tr>
+							<tr>
+								<th>Chart Type</th>
+								<td>
+									<div class="btn-group" data-toggle="buttons">
+										<label class="btn btn-default btn-xs" ng-class="{active: (configPreviewChart.chart || 'line') === type.chart}"
+											   ng-repeat="type in chartTypeList track by $index" ng-click="configPreviewChart.chart = type.chart;">
+											<input type="radio" name="chartType" autocomplete="off"
+												   ng-checked="(configPreviewChart.chart || 'line') === type.chart">
+											<span class="fa fa-{{type.icon}}"></span>
+										</label>
+									</div>
+								</td>
+							</tr>
+							<tr>
+								<th>Minimum</th>
+								<td><input type="checkbox" ng-checked="configPreviewChart.min === 0" ng-disabled="configPreviewChart.chart === 'area' || configPreviewChart.chart === 'pie'"
+										   ng-click="configPreviewChartMinimumCheck()" /></td>
+							</tr>
+							<tr>
+								<th>Metrics</th>
+								<td>
+									<div ng-repeat="metric in configPreviewChart.metrics" class="box inner-box">
+										<div class="box-tools">
+											<button class="btn btn-box-tool" ng-click="configRemoveMetric(metric)">
+												<span class="fa fa-times"></span>
+											</button>
+										</div>
+
+										<h3 class="box-title">{{metric.metric}}</h3>
+										<div class="checkbox noMargin" ng-repeat="series in chartSeriesList track by $index">
+											<label>
+												<input type="checkbox" ng-checked="seriesChecked(metric, series.series)"
+													   ng-click="seriesCheckClick(metric, series.series, configPreviewChart)" />
+												{{series.name}}
+											</label>
+										</div>
+									</div>
+									<a ng-click="configAddMetric()">+ Add Metric</a>
+								</td>
+							</tr>
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+				<button type="button" class="btn btn-primary" ng-click="confirmUpdateChart()">
+					Confirm
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
+
+
+
+<!-- Modal: Metric selector -->
+<div class="modal fade" id="metricMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Select Metric</h4>
+			</div>
+			<div class="modal-body">
+				<div class="input-group" style="margin-bottom: 10px;">
+					<input type="text" class="form-control" placeholder="Filter..." ng-model="_newMetricFilter" />
+					<span class="input-group-btn">
+						<button class="btn btn-default btn-flat" ng-click="_newMetricFilter = '';"><span class="fa fa-times"></span></button>
+					</span>
+				</div>
+
+				<div class="row">
+					<div class="col-md-4">
+						<ul class="nav nav-pills nav-stacked fixed-height">
+							<li class="disabled"><a>Data Source</a></li>
+							<li ng-repeat="dataSrc in dataSourceList track by $index" ng-class="{active: _newMetricDataSrc === dataSrc}">
+								<a ng-click="newMetricSelectDataSource(dataSrc)">{{dataSrc.dataSource}}</a>
+							</li>
+						</ul>
+					</div>
+					<div class="col-md-8">
+						<ul class="nav nav-pills nav-stacked fixed-height">
+							<li class="disabled"><a>Metric</a></li>
+							<li ng-repeat="metric in dataSourceMetricList(_newMetricDataSrc, _newMetricFilter) track by $index" ng-class="{active: _newMetricDataMetric === metric}">
+								<a ng-click="newMetricSelectMetric(metric)">{{metric}}</a>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<span class="text-primary pull-left">{{_newMetricDataSrc.dataSource}} <span class="fa fa-arrow-right"></span> {{_newMetricDataMetric}}</span>
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+				<button type="button" class="btn btn-primary" ng-click="confirmSelectMetric()">
+					Select
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
+
+
+
+<!-- Modal: Chart View -->
+<div class="modal fade" id="chartViewMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">{{viewChart.title || viewChart.metrics[0].metric}}</h4>
+			</div>
+			<div class="modal-body">
+				<div nvd3="viewChart._data" data-title="{{viewChart.title || viewChart.metrics[0].metric}}"
+					 data-watching="true" data-chart="{{viewChart.chart || 'line'}}" data-config="getChartConfig(viewChart)" class="nvd3-chart-cntr lg"></div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/topology/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/topology/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/topology/controller.js
new file mode 100644
index 0000000..94886c9
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/topology/controller.js
@@ -0,0 +1,257 @@
+/*
+ * 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() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("topology", {
+		global: true	// Global Feature needn't add to applications
+	});
+
+	// ==============================================================
+	// =                       Initialization                       =
+	// ==============================================================
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+	//feature.service("DashboardFormatter", function() {
+	//});
+
+	// ==============================================================
+	// =                         Controller                         =
+	// ==============================================================
+	feature.configNavItem("monitoring", "Topology", "usb");
+
+	// ========================= Monitoring =========================
+	feature.configController('monitoring', function(PageConfig, $scope, $interval, Entities, UI, Site, Application) {
+		var topologyRefreshInterval;
+
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		PageConfig.pageTitle = "Topology Execution";
+
+		$scope.topologyExecutionList = null;
+
+		$scope.currentTopologyExecution = null;
+		$scope.currentTopologyExecutionOptList = [];
+
+		// ======================= Function =======================
+		function refreshExecutionList() {
+			var _list = Entities.queryEntities("TopologyExecutionService");
+			_list._promise.then(function () {
+				$scope.topologyExecutionList = _list;
+			});
+		}
+
+		$scope.showTopologyDetail = function (topologyExecution) {
+			$scope.currentTopologyExecution = topologyExecution;
+			$("#topologyMDL").modal();
+
+			$scope.currentTopologyExecutionOptList = Entities.queryEntities("TopologyOperationService", {
+				site: topologyExecution.tags.site,
+				application: topologyExecution.tags.application,
+				topology: topologyExecution.tags.topology,
+				_pageSize: 10,
+				_duration: 1000 * 60 * 60 * 24 * 30
+			});
+		};
+
+		$scope.getStatusClass = function (status) {
+			switch (status) {
+				case "NEW":
+					return "info";
+				case "STARTING":
+				case "STOPPING":
+					return "warning";
+				case "STARTED":
+					return "success";
+				case "STOPPED":
+					return "danger";
+				default:
+					return "default";
+			}
+		};
+
+		// ==================== Initialization ====================
+		refreshExecutionList();
+		topologyRefreshInterval = $interval(refreshExecutionList, 10 * 1000);
+
+		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
+		$scope.topologyList._promise.then(function () {
+			$scope.topologyList = $.map($scope.topologyList, function (topology) {
+				return topology.tags.topology;
+			});
+		});
+
+		$scope.applicationList = $.map(Application.list, function (application) {
+			return application.tags.application;
+		});
+
+		$scope.siteList = $.map(Site.list, function (site) {
+			return site.tags.site;
+		});
+
+		// ================== Topology Execution ==================
+		$scope.newTopologyExecution = function () {
+			UI.createConfirm("Topology", {}, [
+				{field: "site", type: "select", valueList: $scope.siteList},
+				{field: "application", type: "select", valueList: $scope.applicationList},
+				{field: "topology", type: "select", valueList: $scope.topologyList}
+			], function (entity) {
+				for(var i = 0 ; i < $scope.topologyExecutionList.length; i += 1) {
+					var _entity = $scope.topologyExecutionList[i].tags;
+					if(_entity.site === entity.site && _entity.application === entity.application && _entity.topology === entity.topology) {
+						return "Topology already exist!";
+					}
+				}
+			}).then(null, null, function(holder) {
+				var _entity = {
+					tags: {
+						site: holder.entity.site,
+						application: holder.entity.application,
+						topology: holder.entity.topology
+					},
+					status: "NEW"
+				};
+				Entities.updateEntity("TopologyExecutionService", _entity)._promise.then(function() {
+					holder.closeFunc();
+					$scope.topologyExecutionList.push(_entity);
+					refreshExecutionList();
+				});
+			});
+		};
+
+		$scope.deleteTopologyExecution = function (topologyExecution) {
+			UI.deleteConfirm(topologyExecution.tags.topology).then(null, null, function(holder) {
+				Entities.deleteEntities("TopologyExecutionService", topologyExecution.tags)._promise.then(function() {
+					holder.closeFunc();
+					common.array.remove(topologyExecution, $scope.topologyExecutionList);
+				});
+			});
+		};
+
+		// ================== Topology Operation ==================
+		$scope.doTopologyOperation = function (topologyExecution, operation) {
+			$.dialog({
+				title: operation + " Confirm",
+				content: "Do you want to " + operation + " '" + topologyExecution.tags.topology + "'?",
+				confirm: true
+			}, function (ret) {
+				if(!ret) return;
+
+				var list = Entities.queryEntities("TopologyOperationService", {
+					site: topologyExecution.tags.site,
+					application: topologyExecution.tags.application,
+					topology: topologyExecution.tags.topology,
+					_pageSize: 20
+				});
+
+				list._promise.then(function () {
+					var lastOperation = common.array.find(operation, list, "tags.operation");
+					if(lastOperation && (lastOperation.status === "INITIALIZED" || lastOperation.status === "PENDING")) {
+						refreshExecutionList();
+						return;
+					}
+
+					Entities.updateEntity("rest/app/operation", {
+						tags: {
+							site: topologyExecution.tags.site,
+							application: topologyExecution.tags.application,
+							topology: topologyExecution.tags.topology,
+							operation: operation
+						},
+						status: "INITIALIZED"
+					}, {timestamp: false, hook: true});
+				});
+			});
+		};
+
+		$scope.startTopologyOperation = function (topologyExecution) {
+			$scope.doTopologyOperation(topologyExecution, "START");
+		};
+		$scope.stopTopologyOperation = function (topologyExecution) {
+			$scope.doTopologyOperation(topologyExecution, "STOP");
+		};
+
+		// ======================= Clean Up =======================
+		$scope.$on('$destroy', function() {
+			$interval.cancel(topologyRefreshInterval);
+		});
+	});
+
+	// ========================= Management =========================
+	feature.configController('management', function(PageConfig, $scope, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		PageConfig.pageTitle = "Topology";
+
+		var typeList = ["CLASS", "DYNAMIC"];
+		var topologyDefineAttrs = [
+			{field: "topology", name: "name"},
+			{field: "type", type: "select", valueList: typeList},
+			{field: "exeClass", name: "execution entry", description: function (entity) {
+				if(entity.type === "CLASS") return "Class implements interface TopologyExecutable";
+				if(entity.type === "DYNAMIC") return "DSL based topology definition";
+			}, type: "blob", rows: 5},
+			{field: "version", optional: true},
+			{field: "description", optional: true, type: "blob"}
+		];
+		var topologyUpdateAttrs = $.extend(topologyDefineAttrs.concat(), [{field: "topology", name: "name", readonly: true}]);
+
+		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
+
+		$scope.newTopology = function () {
+			UI.createConfirm("Topology", {}, topologyDefineAttrs, function (entity) {
+				if(common.array.find(entity.topology, $scope.topologyList, "tags.topology", false, false)) {
+					return "Topology name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				holder.entity.tags = {
+					topology: holder.entity.topology
+				};
+				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function() {
+					holder.closeFunc();
+					$scope.topologyList.push(holder.entity);
+				});
+			});
+		};
+
+		$scope.updateTopology = function (topology) {
+			UI.updateConfirm("Topology", $.extend({}, topology, {topology: topology.tags.topology}), topologyUpdateAttrs).then(null, null, function(holder) {
+				holder.entity.tags = {
+					topology: holder.entity.topology
+				};
+				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function() {
+					holder.closeFunc();
+					$.extend(topology, holder.entity);
+				});
+			});
+		};
+
+		$scope.deleteTopology = function (topology) {
+			UI.deleteConfirm(topology.tags.topology).then(null, null, function(holder) {
+				Entities.delete("TopologyDescriptionService", {topology: topology.tags.topology})._promise.then(function() {
+					holder.closeFunc();
+					common.array.remove(topology, $scope.topologyList);
+				});
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/management.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/management.html b/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/management.html
new file mode 100644
index 0000000..9e22c84
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/management.html
@@ -0,0 +1,52 @@
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-cog"></i>
+		<a class="pull-right" href="#/config/topology/monitoring"><span class="fa fa-angle-right"></span> Monitoring</a>
+		<h3 class="box-title">
+			Management
+		</h3>
+	</div>
+	<div class="box-body">
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th>Name</th>
+					<th width="20%">Execution Class</th>
+					<th>Type</th>
+					<th width="50%">Description</th>
+					<th>Version</th>
+					<th width="70"></th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr ng-repeat="item in topologyList">
+					<td>{{item.tags.topology}}</td>
+					<td><pre class="noWrap">{{item.exeClass}}</pre></td>
+					<td>{{item.type}}</td>
+					<td>{{item.description}}</td>
+					<td>{{item.version}}</td>
+					<td class="text-center">
+						<button class="rm fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" ng-click="updateTopology(item)"> </button>
+						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="deleteTopology(item)"> </button>
+					</td>
+				</tr>
+				<tr ng-if="topologyList.length === 0">
+					<td colspan="6">
+						<span class="text-muted">Empty list</span>
+					</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+
+	<div class="box-footer">
+		<button class="btn btn-primary pull-right" ng-click="newTopology()">
+			New Topology
+			<i class="fa fa-plus-circle"> </i>
+		</button>
+	</div>
+
+	<div class="overlay" ng-hide="topologyList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/monitoring.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/monitoring.html b/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/monitoring.html
new file mode 100644
index 0000000..0acb2c1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/topology/page/monitoring.html
@@ -0,0 +1,151 @@
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-eye"></i>
+		<a class="pull-right" href="#/config/topology/management"><span class="fa fa-angle-right"></span> Management</a>
+		<h3 class="box-title">
+			Monitoring
+		</h3>
+	</div>
+	<div class="box-body">
+		<div sorttable source="topologyExecutionList">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+				<tr>
+					<th width="70" sortpath="status">Status</th>
+					<th width="90" sortpath="tags.topology">Topology</th>
+					<th width="60" sortpath="tags.site">Site</th>
+					<th width="100" sortpath="tags.application">Application</th>
+					<th width="60" sortpath="mode">Mode</th>
+					<th sortpath="description">Description</th>
+					<th width="70" style="min-width: 70px;"></th>
+				</tr>
+				</thead>
+				<tbody>
+				<tr>
+					<td class="text-center">
+						<span class="label label-{{_parent.getStatusClass(item.status)}}">{{item.status}}</span>
+					</td>
+					<td><a ng-click="_parent.showTopologyDetail(item)">{{item.tags.topology}}</a></td>
+					<td>{{item.tags.site}}</td>
+					<td>{{item.tags.application}}</td>
+					<td>{{item.mode}}</td>
+					<td>{{item.description}}</td>
+					<td class="text-center">
+						<button ng-if="item.status === 'NEW' || item.status === 'STOPPED'" class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Start" tooltip-animation="false" ng-click="_parent.startTopologyOperation(item)"> </button>
+						<button ng-if="item.status === 'STARTED'" class="fa fa-stop sm btn btn-default btn-xs" uib-tooltip="Stop" tooltip-animation="false" ng-click="_parent.stopTopologyOperation(item)"> </button>
+						<button ng-if="item.status !== 'NEW' && item.status !== 'STARTED' && item.status !== 'STOPPED'" class="fa fa-ban sm btn btn-default btn-xs" disabled="disabled"> </button>
+						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deleteTopologyExecution(item)"> </button>
+					</td>
+				</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+
+	<div class="box-footer">
+		<button class="btn btn-primary pull-right" ng-click="newTopologyExecution()">
+			New Topology Execution
+			<i class="fa fa-plus-circle"> </i>
+		</button>
+	</div>
+
+	<div class="overlay" ng-hide="topologyExecutionList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+</div>
+
+
+
+
+<!-- Modal: Topology Detail -->
+<div class="modal fade" id="topologyMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Topology Detail</h4>
+			</div>
+			<div class="modal-body">
+				<h3>Detail</h3>
+				<table class="table">
+					<tbody>
+						<tr>
+							<th>Site</th>
+							<td>{{currentTopologyExecution.tags.site}}</td>
+						</tr>
+						<tr>
+							<th>Application</th>
+							<td>{{currentTopologyExecution.tags.application}}</td>
+						</tr>
+						<tr>
+							<th>Topology</th>
+							<td>{{currentTopologyExecution.tags.topology}}</td>
+						</tr>
+						<tr>
+							<th>Full Name</th>
+							<td>{{currentTopologyExecution.fullName || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Status</th>
+							<td>
+								<span class="label label-{{getStatusClass(currentTopologyExecution.status)}}">{{currentTopologyExecution.status}}</span>
+							</td>
+						</tr>
+						<tr>
+							<th>Mode</th>
+							<td>{{currentTopologyExecution.mode || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Environment</th>
+							<td>{{currentTopologyExecution.environment || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Url</th>
+							<td>
+								<a ng-if="currentTopologyExecution.url" href="{{currentTopologyExecution.url}}" target="_blank">{{currentTopologyExecution.url}}</a>
+								<span ng-if="!currentTopologyExecution.url">-</span>
+							</td>
+						</tr>
+						<tr>
+							<th>Description</th>
+							<td>{{currentTopologyExecution.description || "-"}}</td>
+						</tr>
+						<tr>
+							<th>Last Modified Date</th>
+							<td>{{common.format.date(currentTopologyExecution.lastModifiedDate) || "-"}}</td>
+						</tr>
+					</tbody>
+				</table>
+
+				<h3>Latest Operations</h3>
+				<div class="table-responsive">
+					<table class="table table-bordered table-sm margin-bottom-none">
+						<thead>
+							<tr>
+								<th>Date Time</th>
+								<th>Operation</th>
+								<th>Status</th>
+								<th>Message</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr ng-repeat="action in currentTopologyExecutionOptList track by $index">
+								<td>{{common.format.date(action.lastModifiedDate) || "-"}}</td>
+								<td>{{action.tags.operation}}</td>
+								<td>{{action.status}}</td>
+								<td><pre class="noWrap">{{action.message}}</pre></td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/controller.js
new file mode 100644
index 0000000..ed619d3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/controller.js
@@ -0,0 +1,268 @@
+/*
+ * 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() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("userProfile");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// ==============================================================
+	// =                        User Profile                        =
+	// ==============================================================
+
+	// ======================== Profile List ========================
+	//feature.navItem("list", "User Profiles", "graduation-cap");
+	feature.controller('list', function(PageConfig, Site, $scope, $interval, Entities) {
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		$scope.common = common;
+		$scope.algorithms = [];
+
+		// ======================================== Algorithms ========================================
+		$scope.algorithmEntity = {};
+		Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, application: "userProfile"})._promise.then(function(data) {
+			$scope.algorithmEntity = common.getValueByPath(data, "obj[0]");
+			$scope.algorithmEntity.policy = common.parseJSON($scope.algorithmEntity.policyDef);
+		});
+
+		// ======================================= User profile =======================================
+		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site}, ["user", "algorithm", "content", "version"]);
+		$scope.profileList._promise.then(function() {
+			var _algorithms = {};
+			var _users = {};
+
+			// Map user
+			$.each($scope.profileList, function(i, unit) {
+				_algorithms[unit.tags.algorithm] = unit.tags.algorithm;
+				var _user = _users[unit.tags.user] = _users[unit.tags.user] || {user: unit.tags.user};
+				_user[unit.tags.algorithm] = {
+					version: unit.version
+				};
+
+				// DE
+				if(unit.tags.algorithm === "DE") {
+					var _statistics = common.parseJSON(unit.content);
+					_statistics = common.getValueByPath(_statistics, "statistics", []);
+					_user[unit.tags.algorithm].topCommands = $.map(common.array.top(_statistics, "mean"), function(command) {
+						return command.commandName;
+					});
+				}
+			});
+
+			// Map algorithms
+			$scope.algorithms = $.map(_algorithms, function(algorithm) {
+				return algorithm;
+			}).sort();
+
+			$scope.profileList.splice(0);
+			$scope.profileList.push.apply($scope.profileList, common.map.toArray(_users));
+		});
+
+		// =========================================== Task ===========================================
+		$scope.tasks = [];
+		function _loadTasks() {
+			var _tasks = Entities.queryEntities("ScheduleTaskService", {
+				site: Site.current().tags.site,
+				_pageSize: 100,
+				_duration: 1000 * 60 * 60 * 24 * 14,
+				__ETD: 1000 * 60 * 60 * 24
+			});
+			_tasks._promise.then(function() {
+				$scope.tasks.splice(0);
+				$scope.tasks.push.apply($scope.tasks, _tasks);
+
+				// Duration
+				$.each($scope.tasks, function(i, data) {
+					if(data.timestamp && data.updateTime) {
+						var _ms = (new moment(data.updateTime)).diff(new moment(data.timestamp));
+						var _d = moment.duration(_ms);
+						data._duration = Math.floor(_d.asHours()) + moment.utc(_ms).format(":mm:ss");
+						data.duration = _ms;
+					} else {
+						data._duration = "--";
+					}
+				});
+			});
+		}
+
+		$scope.runningTaskCount = function () {
+			return common.array.count($scope.tasks, "INITIALIZED", "status") +
+				common.array.count($scope.tasks, "PENDING", "status") +
+				common.array.count($scope.tasks, "EXECUTING", "status");
+		};
+
+		// Create task
+		$scope.updateTask = function() {
+			$.dialog({
+				title: "Confirm",
+				content: "Do you want to update now?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _entity = {
+					status: "INITIALIZED",
+					detail: "Newly created command",
+					tags: {
+						site: Site.current().tags.site,
+						type: "USER_PROFILE_TRAINING"
+					},
+					timestamp: +new Date()
+				};
+				Entities.updateEntity("ScheduleTaskService", _entity, {timestamp: false})._promise.success(function(data) {
+					if(!Entities.dialog(data)) {
+						_loadTasks();
+					}
+				});
+			});
+		};
+
+		// Show detail
+		$scope.showTaskDetail = function(task) {
+			var _content = $("<pre>").text(task.detail);
+
+			var $mdl = $.dialog({
+				title: "Detail",
+				content: _content
+			});
+
+			_content.click(function(e) {
+				if(!e.ctrlKey) return;
+
+				$.dialog({
+					title: "Confirm",
+					content: "Remove this task?",
+					confirm: true
+				}, function(ret) {
+					if(!ret) return;
+
+					$mdl.modal('hide');
+					Entities.deleteEntity("ScheduleTaskService", task)._promise.then(function() {
+						_loadTasks();
+					});
+				});
+			});
+		};
+
+		_loadTasks();
+		var _loadInterval = $interval(_loadTasks, app.time.refreshInterval);
+		$scope.$on('$destroy',function(){
+			$interval.cancel(_loadInterval);
+		});
+	});
+
+	// ======================= Profile Detail =======================
+	feature.controller('detail', function(PageConfig, Site, $scope, $wrapState, Entities) {
+		PageConfig.pageTitle = "User Profile";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		PageConfig
+			.addNavPath("User Profile", "/userProfile/list")
+			.addNavPath("Detail");
+
+		$scope.user = $wrapState.param.filter;
+
+		// User profile
+		$scope.profiles = {};
+		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site, user: $scope.user});
+		$scope.profileList._promise.then(function() {
+			$.each($scope.profileList, function(i, unit) {
+				unit._content = common.parseJSON(unit.content);
+				$scope.profiles[unit.tags.algorithm] = unit;
+			});
+
+			// DE
+			if($scope.profiles.DE) {
+				console.log($scope.profiles.DE);
+
+				$scope.profiles.DE._chart = {};
+
+				$scope.profiles.DE.estimates = {};
+				$.each($scope.profiles.DE._content, function(key, value) {
+					if(key !== "statistics") {
+						$scope.profiles.DE.estimates[key] = value;
+					}
+				});
+
+				var _meanList = [];
+				var _stddevList = [];
+
+				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
+					_meanList[i] = {
+						x: unit.commandName,
+						y: unit.mean
+					};
+					_stddevList[i] = {
+						x: unit.commandName,
+						y: unit.stddev
+					};
+				});
+				$scope.profiles.DE._chart.series = [
+					{
+						key: "mean",
+						values: _meanList
+					},
+					{
+						key: "stddev",
+						values: _stddevList
+					}
+				];
+
+				// Percentage table list
+				$scope.profiles.DE.meanList = [];
+				var _total = common.array.sum($scope.profiles.DE._content.statistics, "mean");
+				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
+					$scope.profiles.DE.meanList.push({
+						command: unit.commandName,
+						percentage: unit.mean / _total
+					});
+				});
+			}
+
+			// EigenDecomposition
+			if($scope.profiles.EigenDecomposition && $scope.profiles.EigenDecomposition._content.principalComponents) {
+				$scope.profiles.EigenDecomposition._chart = {
+					series: [],
+				};
+
+				$.each($scope.profiles.EigenDecomposition._content.principalComponents, function(z, grp) {
+					var _line = [];
+					$.each(grp, function(x, y) {
+						_line.push([x,y,z]);
+					});
+
+					$scope.profiles.EigenDecomposition._chart.series.push({
+						data: _line
+					});
+				});
+			}
+		});
+
+		// UI
+		$scope.showRawData = function(content) {
+			$.dialog({
+				title: "Raw Data",
+				content: $("<pre>").text(content)
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/detail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/detail.html b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/detail.html
new file mode 100644
index 0000000..0f94e03
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/detail.html
@@ -0,0 +1,87 @@
+<!--
+  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.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-user"> </i>
+		<h3 class="box-title">
+			{{user}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<div>
+			<div class="inline-group">
+				<dl><dt>User</dt><dd>{{user}}</dd></dl>
+				<dl><dt>Site</dt><dd>{{Site.current().tags.site}}</dd></dl>
+			</div>
+			<div class="inline-group">
+				<dl><dt>Other Info</dt><dd class="text-muted">N/A</dd></dl>
+			</div>
+		</div>
+
+		<div class="overlay" ng-hide="profileList._promise.$$state.status === 1;">
+			<span class="fa fa-refresh fa-spin"></span>
+		</div>
+	</div>
+</div>
+
+<!-- Analysis -->
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active">
+			<a href="[data-id='DE']" data-toggle="tab" ng-click=" currentTab='DE'">DE</a>
+		</li>
+		<li>
+			<a href="[data-id='EigenDecomposition']" data-toggle="tab" ng-click=" currentTab='EigenDecomposition'">EigenDecomposition</a>
+		</li>
+		<li class="pull-right">
+			<button class="btn btn-primary" ng-click="showRawData(currentTab === 'EigenDecomposition' ? profiles.EigenDecomposition.content : profiles.DE.content)">Raw Data</button>
+		</li>
+	</ul>
+	<div class="tab-content">
+		<div class="tab-pane active" data-id="DE">
+			<div class="row">
+				<div class="col-md-9">
+					<div nvd3="profiles.DE._chart.series" data-config="{chart: 'column', xType: 'text', height: 400}" class="nvd3-chart-cntr" height="400"></div>
+					<div class="inline-group text-center">
+						<dl ng-repeat="(key, value) in profiles.DE.estimates"><dt>{{key}}</dt><dd>{{value}}</dd></dl>
+					</div>
+				</div>
+
+				<div class="col-md-3">
+					<table class="table table-bordered">
+						<thead>
+							<tr>
+								<th>Command</th>
+								<th>Percentage</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr ng-repeat="unit in profiles.DE.meanList">
+								<td>{{unit.command}}</td>
+								<td class="text-right">{{(unit.percentage*100).toFixed(2)}}%</td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div><!-- /.tab-pane -->
+		<div class="tab-pane" data-id="EigenDecomposition">
+			<div line3d-chart height="400" data="profiles.EigenDecomposition._chart.series"> </div>
+		</div><!-- /.tab-pane -->
+	</div><!-- /.tab-content -->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/list.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/list.html b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/list.html
new file mode 100644
index 0000000..2f14479
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/userProfile/page/list.html
@@ -0,0 +1,138 @@
+<!--
+  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.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			User Profiles
+			<small><a data-toggle="collapse" href="[data-id='algorithms']">Detail</a></small>
+		</h3>
+		<div class="pull-right">
+			<a class="label label-primary" ng-class="runningTaskCount() ? 'label-primary' : 'label-default'" data-toggle="modal" data-target="#taskMDL">Update</a>
+		</div>
+	</div>
+	<div class="box-body">
+		<!-- Algorithms -->
+		<div data-id="algorithms" class="collapse">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th>Name</th>
+						<td>Feature</td>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-repeat="algorithm in algorithmEntity.policy.algorithms">
+						<td>{{algorithm.name}}</td>
+						<td>{{algorithm.features}}</td>
+					</tr>
+				</tbody>
+			</table>
+			<hr/>
+		</div>
+
+		<!-- User Profile List -->
+		<p ng-show="profileList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="profileList" ng-show="profileList._promise.$$state.status === 1">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="10%" sortpath="user">User</th>
+						<th>Most Predominat Feature</th>
+						<th width="10"></th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td>
+							<a href="#/userProfile/detail/{{item.user}}">{{item.user}}</a>
+						</td>
+						<td>
+							{{item.DE.topCommands.slice(0,3).join(", ")}}
+						</td>
+						<td>
+							<a href="#/userProfile/detail/{{item.user}}">Detail</a>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<!-- Modal: User profile Schedule Task -->
+<div class="modal fade" id="taskMDL" tabindex="-1" role="dialog">
+	<div class="modal-dialog modal-lg" role="document">
+		<div class="modal-content">
+			<div class="modal-header">
+				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+					<span aria-hidden="true">&times;</span>
+				</button>
+				<h4 class="modal-title">Training History</h4>
+			</div>
+			<div class="modal-body">
+				<div sorttable source="tasks">
+					<table class="table table-bordered" ng-non-bindable>
+						<thead>
+							<tr>
+								<th sortpath="tags.type">Command</th>
+								<th sortpath="timestamp">Start Time</th>
+								<th sortpath="updateTime">Update Time</th>
+								<th sortpath="duration">Duration</th>
+								<th sortpath="status">Status</th>
+								<th width="10"> </th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr>
+								<td>{{item.tags.type}}</td>
+								<td>{{common.format.date(item.timestamp) || "--"}}</td>
+								<td>{{common.format.date(item.updateTime) || "--"}}</td>
+								<td>{{item._duration}}</td>
+								<td class="text-nowrap">
+									<span class="fa fa-hourglass-start text-muted" ng-show="item.status === 'INITIALIZED'"></span>
+									<span class="fa fa-hourglass-half text-info" ng-show="item.status === 'PENDING'"></span>
+									<span class="fa fa-circle-o-notch text-primary" ng-show="item.status === 'EXECUTING'"></span>
+									<span class="fa fa-check-circle text-success" ng-show="item.status === 'SUCCEEDED'"></span>
+									<span class="fa fa-exclamation-circle text-danger" ng-show="item.status === 'FAILED'"></span>
+									<span class="fa fa-ban text-muted" ng-show="item.status === 'CANCELED'"></span>
+									{{item.status}}
+								</td>
+								<td>
+									<a ng-click="_parent.showTaskDetail(item)">Detail</a>
+								</td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-primary pull-left" ng-click="updateTask()" ng-show="Auth.isRole('ROLE_ADMIN')">
+					Update Now
+				</button>
+				<button type="button" class="btn btn-default" data-dismiss="modal">
+					Close
+				</button>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/images/favicon.png
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/images/favicon.png b/eagle-webservice/src/main/webapp/_app/public/images/favicon.png
new file mode 100644
index 0000000..3bede2a
Binary files /dev/null and b/eagle-webservice/src/main/webapp/_app/public/images/favicon.png differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/images/favicon_white.png
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/images/favicon_white.png b/eagle-webservice/src/main/webapp/_app/public/images/favicon_white.png
new file mode 100644
index 0000000..9879e92
Binary files /dev/null and b/eagle-webservice/src/main/webapp/_app/public/images/favicon_white.png differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/app.config.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/app.config.js b/eagle-webservice/src/main/webapp/_app/public/js/app.config.js
new file mode 100644
index 0000000..d7c4be9
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/app.config.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.
+ */
+
+(function() {
+	'use strict';
+
+	app.config = {
+		// ============================================================================
+		// =                                   URLs                                   =
+		// ============================================================================
+		urls: {
+			HOST: '..',
+
+			updateEntity: 'rest/entities?serviceName=${serviceName}',
+			queryEntity: 'rest/entities/rowkey?serviceName=${serviceName}&value=${encodedRowkey}',
+			queryEntities: 'rest/entities?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
+			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
+			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
+
+			queryGroup: 'rest/entities?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
+			querySeries: 'rest/entities?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
+
+			query: 'rest/',
+
+			userProfile: 'rest/authentication',
+			logout: 'logout',
+
+			maprNameResolver: '../rest/maprNameResolver',
+
+			DELETE_HOOK: {
+				FeatureDescService: 'rest/module/feature?feature=${feature}',
+				ApplicationDescService: 'rest/module/application?application=${application}',
+				SiteDescService: 'rest/module/site?site=${site}',
+				TopologyDescriptionService: 'rest/app/topology?topology=${topology}'
+			},
+			UPDATE_HOOK: {
+				SiteDescService: 'rest/module/siteApplication'
+			}
+		},
+	};
+
+	// ============================================================================
+	// =                                   URLs                                   =
+	// ============================================================================
+	app.getURL = function(name, kvs) {
+		var _path = app.config.urls[name];
+		if(!_path) throw "URL:'" + name + "' not exist!";
+		var _url = app.packageURL(_path);
+		if(kvs !== undefined) {
+			_url = common.template(_url, kvs);
+		}
+		return _url;
+	};
+
+	app.getMapRNameResolverURL = function(name,value, site) {
+		var key = "maprNameResolver";
+		var _path = app.config.urls[key];
+		if(!_path) throw "URL:'" + name + "' not exist!";
+		var _url = _path;
+		if(name == "fNameResolver") {
+			_url +=  "/" + name + "?fName=" + value + "&site=" + site;
+		} else if(name == "sNameResolver") {
+			_url +=  "/" + name + "?sName=" + value + "&site=" + site;
+		} else if (name == "vNameResolver") {
+			_url += "/" + name + "?vName=" + value + "&site=" + site;
+		} else{
+			throw "resolver:'" + name + "' not exist!";
+		}
+		return _url;
+	};
+
+	function getHookURL(hookType, serviceName) {
+		var _path = app.config.urls[hookType][serviceName];
+		if(!_path) return null;
+
+		return app.packageURL(_path);
+	}
+
+	/***
+	 * Eagle support delete function to process special entity delete. Which will delete all the relative entity.
+	 * @param serviceName
+	 */
+	app.getDeleteURL = function(serviceName) {
+		return getHookURL('DELETE_HOOK', serviceName);
+	};
+
+	/***
+	 * Eagle support update function to process special entity update. Which will update all the relative entity.
+	 * @param serviceName
+	 */
+	app.getUpdateURL = function(serviceName) {
+		return getHookURL('UPDATE_HOOK', serviceName);
+	};
+
+	app.packageURL = function (path) {
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		return (_host ? _host + "/" : '') + path;
+	};
+
+	app._Host = function(host) {
+		if(host) {
+			localStorage.setItem("HOST", host);
+			return app;
+		}
+		return localStorage.getItem("HOST");
+	};
+	app._Host.clear = function() {
+		localStorage.removeItem("HOST");
+	};
+	app._Host.sample = "http://localhost:9099/eagle-service";
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/app.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/app.js b/eagle-webservice/src/main/webapp/_app/public/js/app.js
new file mode 100644
index 0000000..70b4afe
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/app.js
@@ -0,0 +1,499 @@
+/*
+ * 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 app = {};
+
+(function() {
+	'use strict';
+
+	/* App Module */
+	var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngAnimate', 'ui.router', 'eagleControllers', 'featureControllers', 'eagle.service']);
+
+	// GRUNT REPLACEMENT: eagleApp.buildTimestamp = TIMESTAMP
+	eagleApp._TRS = function() {
+		return eagleApp.buildTimestamp || Math.random();
+	};
+
+	// ======================================================================================
+	// =                                   Feature Module                                   =
+	// ======================================================================================
+	var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
+	var FN_ARG_SPLIT = /,/;
+	var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
+	var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
+
+	var featureControllers = angular.module('featureControllers', ['ui.bootstrap', 'eagle.components']);
+	var featureControllerCustomizeHtmlTemplate = {};
+	var featureControllerProvider;
+	var featureProvider;
+
+	featureControllers.config(function ($controllerProvider, $provide) {
+		featureControllerProvider = $controllerProvider;
+		featureProvider = $provide;
+	});
+
+	featureControllers.service("Feature", function($wrapState, PageConfig, ConfigPageConfig, FeaturePageConfig) {
+		var _features = {};
+		var _services = {};
+
+		var Feature = function(name, config) {
+			this.name = name;
+			this.config = config || {};
+			this.features = {};
+		};
+
+		/***
+		 * Inner function. Replace the dependency of constructor.
+		 * @param constructor
+		 * @private
+		 */
+		Feature.prototype._replaceDependencies = function(constructor) {
+			var i, srvName;
+			var _constructor, _$inject;
+			var fnText, argDecl;
+
+			if($.isArray(constructor)) {
+				_constructor = constructor[constructor.length - 1];
+				_$inject = constructor.slice(0, -1);
+			} else if(constructor.$inject) {
+				_constructor = constructor;
+				_$inject = constructor.$inject;
+			} else {
+				_$inject = [];
+				_constructor = constructor;
+				fnText = constructor.toString().replace(STRIP_COMMENTS, '');
+				argDecl = fnText.match(FN_ARGS);
+				$.each(argDecl[1].split(FN_ARG_SPLIT), function(i, arg) {
+					arg.replace(FN_ARG, function(all, underscore, name) {
+						_$inject.push(name);
+					});
+				});
+			}
+			_constructor.$inject = _$inject;
+
+			for(i = 0 ; i < _$inject.length ; i += 1) {
+				srvName = _$inject[i];
+				_$inject[i] = this.features[srvName] || _$inject[i];
+			}
+
+			return _constructor;
+		};
+
+		/***
+		 * Register a common service for feature usage. Common service will share between the feature. If you are coding customize feature, use 'Feature.service' is the better way.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.commonService = function(name, constructor) {
+			if(!_services[name]) {
+				featureProvider.service(name, constructor);
+				_services[name] = this.name;
+			} else {
+				throw "Service '" + name + "' has already be registered by feature '" + _services[name] + "'";
+			}
+		};
+
+		/***
+		 * Register a service for feature usage.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.service = function(name, constructor) {
+			var _serviceName;
+			if(!this.features[name]) {
+				_serviceName = "__FEATURE_" + this.name + "_" + name;
+				featureProvider.service(_serviceName, this._replaceDependencies(constructor));
+				this.features[name] = _serviceName;
+			} else {
+				console.warn("Service '" + name + "' has already be registered.");
+			}
+		};
+
+		/***
+		 * Create an navigation item in left navigation bar
+		 * @param path
+		 * @param title
+		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
+		 */
+		Feature.prototype.navItem = function(path, title, icon) {
+			title = title || path;
+			icon = icon || "question";
+
+			FeaturePageConfig.addNavItem(this.name, {
+				icon: icon,
+				title: title,
+				url: "#/" + this.name + "/" + path
+			});
+		};
+
+		/***
+		 * Register a controller.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.controller = function(name, constructor, htmlTemplatePath) {
+			var _name = this.name + "_" + name;
+
+			// Replace feature registered service
+			constructor = this._replaceDependencies(constructor);
+
+			// Register controller
+			featureControllerProvider.register(_name, constructor);
+			if(htmlTemplatePath) {
+				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
+			}
+
+			return _name;
+		};
+
+		/***
+		 * Register a configuration controller for admin usage.
+		 * @param name
+		 * @param constructor
+		 */
+		Feature.prototype.configController = function(name, constructor, htmlTemplatePath) {
+			var _name = "config_" + this.name + "_" + name;
+
+			// Replace feature registered service
+			constructor = this._replaceDependencies(constructor);
+
+			// Register controller
+			featureControllerProvider.register(_name, constructor);
+			if(htmlTemplatePath) {
+				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
+			}
+
+			return _name;
+		};
+
+		/***
+		 * Create an navigation item in left navigation bar for admin configuraion page
+		 * @param path
+		 * @param title
+		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
+		 */
+		Feature.prototype.configNavItem = function(path, title, icon) {
+			title = title || path;
+			icon = icon || "question";
+
+			ConfigPageConfig.addNavItem(this.name, {
+				icon: icon,
+				title: title,
+				url: "#/config/" + this.name + "/" + path
+			});
+		};
+
+		// Register
+		featureControllers.register = Feature.register = function(featureName, config) {
+			_features[featureName] = _features[featureName] || new Feature(featureName, config);
+			return _features[featureName];
+		};
+
+		// Page go
+		Feature.go = function(feature, page, filter) {
+			if(!filter) {
+				$wrapState.go("page", {
+					feature: feature,
+					page: page
+				}, 2);
+			} else {
+				$wrapState.go("pageFilter", {
+					feature: feature,
+					page: page,
+					filter: filter
+				}, 2);
+			}
+		};
+
+		// Get feature by name
+		Feature.get = function (featureName) {
+			return _features[featureName];
+		};
+
+		return Feature;
+	});
+
+	// ======================================================================================
+	// =                                   Router config                                    =
+	// ======================================================================================
+	eagleApp.config(function ($stateProvider, $urlRouterProvider, $animateProvider) {
+		// Resolve
+		function _resolve(config) {
+			config = config || {};
+
+			var resolve = {
+				Site: function (Site) {
+					return Site._promise();
+				},
+				Authorization: function (Authorization) {
+					if(!config.roleType) {
+						return Authorization._promise();
+					} else {
+						return Authorization.rolePromise(config.roleType);
+					}
+				},
+				Application: function (Application) {
+					return Application._promise();
+				}
+			};
+
+			if(config.featureCheck) {
+				resolve._navigationCheck = function($q, $wrapState, Site, Application) {
+					var _deferred = $q.defer();
+
+					$q.all(Site._promise(), Application._promise()).then(function() {
+						var _match, i, tmpApp;
+						var _site = Site.current();
+						var _app = Application.current();
+
+						// Check application
+						if(_site && (
+							!_app ||
+							!_site.applicationList.set[_app.tags.application] ||
+							!_site.applicationList.set[_app.tags.application].enabled
+							)
+						) {
+							_match = false;
+
+							for(i = 0 ; i < _site.applicationGroupList.length ; i += 1) {
+								tmpApp = _site.applicationGroupList[i].enabledList[0];
+								if(tmpApp) {
+									_app = Application.current(tmpApp);
+									_match = true;
+									break;
+								}
+							}
+
+							if(!_match) {
+								_app = null;
+								Application.current(null);
+							}
+						}
+					}).finally(function() {
+						_deferred.resolve();
+					});
+
+					return _deferred.promise;
+				};
+			}
+
+			return resolve;
+		}
+
+		// Router
+		var _featureBase = {
+			templateUrl: function ($stateParams) {
+				var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
+				return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + eagleApp._TRS();
+			},
+			controllerProvider: function ($stateParams) {
+				return $stateParams.feature + "_" + $stateParams.page;
+			},
+			resolve: _resolve({featureCheck: true}),
+			pageConfig: "FeaturePageConfig"
+		};
+
+		$urlRouterProvider.otherwise("/landing");
+		$stateProvider
+			// =================== Landing ===================
+			.state('landing', {
+				url: "/landing",
+				templateUrl: "partials/landing.html?_=" + eagleApp._TRS(),
+				controller: "landingCtrl",
+				resolve: _resolve({featureCheck: true})
+			})
+
+			// ================ Authorization ================
+			.state('login', {
+				url: "/login",
+				templateUrl: "partials/login.html?_=" + eagleApp._TRS(),
+				controller: "authLoginCtrl",
+				access: {skipCheck: true}
+			})
+
+			// ================ Configuration ================
+			// Site
+			.state('configSite', {
+				url: "/config/site",
+				templateUrl: "partials/config/site.html?_=" + eagleApp._TRS(),
+				controller: "configSiteCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Application
+			.state('configApplication', {
+				url: "/config/application",
+				templateUrl: "partials/config/application.html?_=" + eagleApp._TRS(),
+				controller: "configApplicationCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Feature
+			.state('configFeature', {
+				url: "/config/feature",
+				templateUrl: "partials/config/feature.html?_=" + eagleApp._TRS(),
+				controller: "configFeatureCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Feature configuration page
+			.state('configFeatureDetail', $.extend({url: "/config/:feature/:page"}, {
+				templateUrl: function ($stateParams) {
+					var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
+					return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + eagleApp._TRS();
+				},
+				controllerProvider: function ($stateParams) {
+					return "config_" + $stateParams.feature + "_" + $stateParams.page;
+				},
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			}))
+
+			// =================== Feature ===================
+			// Dynamic feature page
+			.state('page', $.extend({url: "/:feature/:page"}, _featureBase))
+			.state('pageFilter', $.extend({url: "/:feature/:page/:filter"}, _featureBase))
+		;
+
+		// Animation
+		$animateProvider.classNameFilter(/^((?!(fa-spin)).)*$/);
+		$animateProvider.classNameFilter(/^((?!(tab-pane)).)*$/);
+	});
+
+	eagleApp.filter('parseJSON', function () {
+		return function (input, defaultVal) {
+			return common.parseJSON(input, defaultVal);
+		};
+	});
+
+	eagleApp.filter('split', function () {
+		return function (input, regex) {
+			return input.split(regex);
+		};
+	});
+
+	eagleApp.filter('reverse', function () {
+		return function (items) {
+			return items.slice().reverse();
+		};
+	});
+
+	// ======================================================================================
+	// =                                   Main Controller                                  =
+	// ======================================================================================
+	eagleApp.controller('MainCtrl', function ($scope, $wrapState, $http, $injector, ServiceError, PageConfig, FeaturePageConfig, Site, Authorization, Entities, nvd3, Application, Feature, UI) {
+		window.serviceError = $scope.ServiceError = ServiceError;
+		window.site = $scope.Site = Site;
+		window.auth = $scope.Auth = Authorization;
+		window.entities = $scope.Entities = Entities;
+		window.application = $scope.Application = Application;
+		window.pageConfig = $scope.PageConfig = PageConfig;
+		window.featurePageConfig = $scope.FeaturePageConfig = FeaturePageConfig;
+		window.feature = $scope.Feature = Feature;
+		window.ui = $scope.UI = UI;
+		window.nvd3 = nvd3;
+		$scope.app = app;
+		$scope.common = common;
+
+		Object.defineProperty(window, "scope",{
+			get: function() {
+				return angular.element("[ui-view]").scope();
+			}
+		});
+
+		// Clean up
+		$scope.$on('$stateChangeStart', function (event, next, nextParam, current, currentParam) {
+			console.log("[Switch] current ->", current, currentParam);
+			console.log("[Switch] next ->", next, nextParam);
+			// Page initialization
+			PageConfig.reset();
+
+			// Dynamic navigation list
+			if(next.pageConfig) {
+				$scope.PageConfig.navConfig = $injector.get(next.pageConfig);
+			} else {
+				$scope.PageConfig.navConfig = {};
+			}
+
+			// Authorization
+			// > Login check
+			if (!common.getValueByPath(next, "access.skipCheck", false)) {
+				if (!Authorization.isLogin) {
+					console.log("[Authorization] Need access. Redirect...");
+					$wrapState.go("login");
+				}
+			}
+
+			// > Role control
+			/*var _roles = common.getValueByPath(next, "access.roles", []);
+			if (_roles.length && Authorization.userProfile.roles) {
+				var _roleMatch = false;
+				$.each(_roles, function (i, roleName) {
+					if (Authorization.isRole(roleName)) {
+						_roleMatch = true;
+						return false;
+					}
+				});
+
+				if (!_roleMatch) {
+					$wrapState.path("/dam");
+				}
+			}*/
+		});
+
+		$scope.$on('$stateChangeError', function (event, next, nextParam, current, currentParam, error) {
+			console.error("[Switch] Error", arguments);
+		});
+
+		// Get side bar navigation item class
+		$scope.getNavClass = function (page) {
+			var path = page.url.replace(/^#/, '');
+
+			if ($wrapState.path() === path) {
+				PageConfig.pageTitle = PageConfig.pageTitle || page.title;
+				return "active";
+			} else {
+				return "";
+			}
+		};
+
+		// Get side bar navigation item class visible
+		$scope.getNavVisible = function (page) {
+			if (!page.roles) return true;
+
+			for (var i = 0; i < page.roles.length; i += 1) {
+				var roleName = page.roles[i];
+				if (Authorization.isRole(roleName)) {
+					return true;
+				}
+			}
+
+			return false;
+		};
+
+		// Authorization
+		$scope.logout = function () {
+			console.log("[Authorization] Logout. Redirect...");
+			Authorization.logout();
+			$wrapState.go("login");
+		};
+	});
+})();
\ No newline at end of file


[11/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js b/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
new file mode 100644
index 0000000..4143491
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/sortTable.js
@@ -0,0 +1,231 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleComponents = angular.module('eagle.components');
+
+	eagleComponents.directive('sortTable', function($compile) {
+		return {
+			restrict: 'AE',
+			scope: true,
+			//terminal: true,
+			priority: 1001,
+
+			/**
+			 * @param $scope
+			 * @param $element
+			 * @param {{}} $attrs
+			 * @param {string} $attrs.sortTable			Data source
+			 * @param {string?} $attrs.isSorting		Will bind parent variable of sort state
+			 * @param {string?} $attrs.scope			Will bind parent variable of current scope
+			 * @param {string?} $attrs.sortpath			Default sort path
+			 * @param {[]?} $attrs.searchPathList		Filter search path list
+			 */
+			controller: function($scope, $element, $attrs) {
+				var sortmatch;
+				var worker;
+				var worker_id = 0;
+				if(typeof(Worker) !== "undefined") {
+					worker = new Worker("public/js/worker/sortTableWorker.js?_=" + window._TRS());
+				}
+
+				// Initialization
+				$scope.pageNumber = 1;
+				$scope.pageSize = 10;
+				$scope.maxSize = 10;
+				$scope.search = "";
+				$scope.orderKey = "";
+				$scope.orderAsc = true;
+
+				if($attrs.sortpath) {
+					sortmatch = $attrs.sortpath.match(/^(-)?(.*)$/);
+					if(sortmatch[1]) {
+						$scope.orderAsc = false;
+					}
+					$scope.orderKey = sortmatch[2];
+				}
+
+				// UI - Column sort
+				$scope.doSort = function(path) {
+					if($scope.orderKey === path) {
+						$scope.orderAsc = !$scope.orderAsc;
+					} else {
+						$scope.orderKey = path;
+						$scope.orderAsc = true;
+					}
+				};
+				$scope.checkSortClass = function(key) {
+					if($scope.orderKey === key) {
+						return "fa sort-mark " + ($scope.orderAsc ? "fa-sort-asc" : "fa-sort-desc");
+					}
+					return "fa fa-sort sort-mark";
+				};
+
+				// List filter & sort
+				function setState(bool) {
+					if(!$attrs.isSorting) return;
+
+					$scope.$parent[$attrs.isSorting] = bool;
+				}
+
+
+				var cacheSearch = "";
+				var cacheOrder = "";
+				var cacheOrderAsc = null;
+				var cacheFilteredList = null;
+				$scope.getFilteredList = function () {
+					if(
+						cacheSearch !== $scope.search ||
+						cacheOrder !== $scope.orderKey ||
+						cacheOrderAsc !== $scope.orderAsc ||
+						!cacheFilteredList
+					) {
+						cacheSearch = $scope.search;
+						cacheOrder = $scope.orderKey;
+						cacheOrderAsc = $scope.orderAsc;
+
+						var fullList = $scope.$parent[$attrs.sortTable] || [];
+						if(!cacheFilteredList) cacheFilteredList = fullList;
+
+						if(!worker) {
+							cacheFilteredList = __sortTable_generateFilteredList(fullList, cacheSearch, cacheOrder, cacheOrderAsc, $scope.$parent[$attrs.searchPathList]);
+							setState(false);
+						} else {
+							worker_id += 1;
+							setState(true);
+							var list = JSON.stringify(fullList);
+							worker.postMessage({
+								search: cacheSearch,
+								order: cacheOrder,
+								orderAsc: cacheOrderAsc,
+								searchPathList: $scope.$parent[$attrs.searchPathList],
+								list: list,
+								id: worker_id
+							});
+						}
+					}
+
+					return cacheFilteredList;
+				};
+
+				// Week watch. Will not track each element
+				$scope.$watch($attrs.sortTable + ".length", function () {
+					cacheFilteredList = null;
+				});
+
+				function workMessage(event) {
+					var data = event.data;
+					if(worker_id !== data.id) return;
+
+					setState(false);
+					cacheFilteredList = data.list;
+					$scope.$apply();
+				}
+				worker.addEventListener("message", workMessage);
+
+				$scope.$on('$destroy', function() {
+					worker.removeEventListener("message", workMessage);
+				});
+
+				// Scope bind
+				if($attrs.scope) {
+					$scope.$parent[$attrs.scope] = $scope;
+				}
+			},
+			compile: function ($element) {
+				var contents = $element.contents().remove();
+
+				return {
+					post: function preLink($scope, $element) {
+						$scope.defaultPageSizeList = [10, 25, 50, 100];
+
+						$element.append(contents);
+
+						// Tool Container
+						var $toolContainer = $(
+							'<div class="tool-container clearfix">' +
+							'</div>'
+						).insertBefore($element.find("table"));
+
+						// Search Box
+						var $search = $(
+							'<div class="search-box">' +
+							'<input type="search" class="form-control input-sm" placeholder="Search" ng-model="search" />' +
+							'<span class="fa fa-search" />' +
+							'</div>'
+						).appendTo($toolContainer);
+						$compile($search)($scope);
+
+						// Page Size
+						var $pageSize = $(
+							'<div class="page-size">' +
+							'Show' +
+							'<select class="form-control" ng-model="pageSize" convert-to-number>' +
+							'<option ng-repeat="size in pageSizeList || defaultPageSizeList track by $index">{{size}}</option>' +
+							'</select>' +
+							'Entities' +
+							'</div>'
+						).appendTo($toolContainer);
+						$compile($pageSize)($scope);
+
+						// Sort Column
+						$element.find("table [sortpath]").each(function () {
+							var $this = $(this);
+							var _sortpath = $this.attr("sortpath");
+							$this.attr("ng-click", "doSort('" + _sortpath + "')");
+							$this.prepend('<span ng-class="checkSortClass(\'' + _sortpath + '\')"></span>');
+							$compile($this)($scope);
+						});
+
+						// Repeat Items
+						var $tr = $element.find("table [ts-repeat], table > tbody > tr").filter(":first");
+						$tr.attr("ng-repeat", 'item in getFilteredList().slice((pageNumber - 1) * pageSize, pageNumber * pageSize) track by $index');
+						$compile($tr)($scope);
+
+						// Page Navigation
+						var $navigation = $(
+							'<div class="navigation-bar clearfix">' +
+							'<span>' +
+							'show {{(pageNumber - 1) * pageSize + 1}} to {{pageNumber * pageSize}} of {{getFilteredList().length}} items' +
+							'</span>' +
+							'<uib-pagination total-items="getFilteredList().length" ng-model="pageNumber" boundary-links="true" items-per-page="pageSize" max-size="maxSize"></uib-pagination>' +
+							'</div>'
+						).appendTo($element);
+						$compile($navigation)($scope);
+					}
+				};
+			}
+		};
+	});
+
+	eagleComponents.directive('convertToNumber', function() {
+		return {
+			require: 'ngModel',
+			link: function(scope, element, attrs, ngModel) {
+				ngModel.$parsers.push(function(val) {
+					return parseInt(val, 10);
+				});
+				ngModel.$formatters.push(function(val) {
+					return '' + val;
+				});
+			}
+		};
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/components/widget.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/components/widget.js b/eagle-server/src/main/webapp/app/dev/public/js/components/widget.js
new file mode 100644
index 0000000..bc59b3c
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/components/widget.js
@@ -0,0 +1,52 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleComponents = angular.module('eagle.components');
+
+	eagleComponents.directive('widget', function($compile, Site) {
+		return {
+			restrict: 'AE',
+			priority: 1001,
+
+			controller: function($scope, $element, $attrs) {
+			},
+			compile: function ($element) {
+				$element.contents().remove();
+
+				return {
+					post: function preLink($scope, $element) {
+						var widget = $scope.widget;
+						$scope.site = Site.current();
+
+						if(widget.renderFunc) {
+							// Prevent auto compile if return false
+							if(widget.renderFunc($element, $scope, $compile) !== false) {
+								$compile($element.contents())($scope);
+							}
+						} else {
+							$element.append("Widget don't provide render function:" + widget.application + " - " + widget.name);
+						}
+					}
+				};
+			}
+		};
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
new file mode 100644
index 0000000..17ce775
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/alertCtrl.js
@@ -0,0 +1,119 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+
+	// TODO: Mock data
+	var publishmentTypes = [
+		{
+			"type": "email",
+			"className": "org.apache.eagle.alert.engine.publisher.impl.AlertEmailPublisher",
+			"description": "send alert to email",
+			"enabled":true,
+			"fields": [{"name":"sender"},{"name":"recipients"},{"name":"subject"},{"name":"smtp.server", "value":"host1"},{"name":"connection", "value":"plaintext"},{"name":"smtp.port", "value": "25"}]
+		},
+		{
+			"type": "kafka",
+			"className": "org.apache.eagle.alert.engine.publisher.impl.AlertKafkaPublisher",
+			"description": "send alert to kafka bus",
+			"enabled":true,
+			"fields": [{"name":"kafka_broker","value":"sandbox.hortonworks.com:6667"},{"name":"topic"}]
+		}
+	];
+
+
+	// ======================================================================================
+	// =                                        Main                                        =
+	// ======================================================================================
+	eagleControllers.controller('alertCtrl', function ($scope, $wrapState, PageConfig) {
+		PageConfig.title = "Alert";
+		$scope.getState = function() {
+			return $wrapState.current.name;
+		};
+	});
+
+	// ======================================================================================
+	// =                                        List                                        =
+	// ======================================================================================
+	eagleControllers.controller('alertListCtrl', function ($scope, $wrapState, PageConfig) {
+		PageConfig.subTitle = "Explore Alerts";
+	});
+
+	// ======================================================================================
+	// =                                     Policy List                                    =
+	// ======================================================================================
+	eagleControllers.controller('policyListCtrl', function ($scope, $wrapState, PageConfig, Entity, UI) {
+		PageConfig.subTitle = "Manage Policies";
+
+		$scope.policyList = Entity.queryMetadata("policies");
+
+		$scope.deletePolicy = function (item) {
+			UI.deleteConfirm(item.name)(function (entity, closeFunc) {
+				Entity.deleteMetadata("policies/" + item.name)._promise.finally(function () {
+					closeFunc();
+					$scope.policyList._refresh();
+				});
+			});
+		};
+	});
+
+	// ======================================================================================
+	// =                                    Policy Create                                   =
+	// ======================================================================================
+	function connectPolicyEditController(entity, args) {
+		var newArgs = [entity];
+		Array.prototype.push.apply(newArgs, args);
+		/* jshint validthis: true */
+		policyEditController.apply(this, newArgs);
+	}
+	function policyEditController(policy, $scope, $wrapState, PageConfig, Entity) {
+		$scope.policy = policy;
+	}
+
+	eagleControllers.controller('policyCreateCtrl', function ($scope, $wrapState, PageConfig, Entity) {
+		PageConfig.subTitle = "Define Alert Policy";
+		connectPolicyEditController({}, arguments);
+	});
+	eagleControllers.controller('policyEditCtrl', function ($scope, $wrapState, PageConfig, Entity) {
+		PageConfig.subTitle = "Edit Alert Policy";
+		var args = arguments;
+
+		// TODO: Wait for backend data update
+		$scope.policyList = Entity.queryMetadata("policies");
+		$scope.policyList._promise.then(function () {
+			var policy = $scope.policyList.find(function (entity) {
+				return entity.name === $wrapState.param.name;
+			});
+
+			if(policy) {
+				connectPolicyEditController(policy, args);
+			} else {
+				$.dialog({
+					title: "OPS",
+					content: "Policy '" + $wrapState.param.name + "' not found!"
+				}, function () {
+					$wrapState.go("alert.policyList");
+				});
+			}
+		});
+
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
new file mode 100644
index 0000000..a807520
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/integrationCtrl.js
@@ -0,0 +1,226 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+
+	// ======================================================================================
+	// =                                        Main                                        =
+	// ======================================================================================
+	eagleControllers.controller('integrationCtrl', function ($scope, $wrapState, PageConfig) {
+		PageConfig.title = "Integration";
+		$scope.getState = function() {
+			return $wrapState.current.name;
+		};
+	});
+
+	// ======================================================================================
+	// =                                        Site                                        =
+	// ======================================================================================
+	eagleControllers.controller('integrationSiteListCtrl', function ($scope, $wrapState, PageConfig, UI, Entity, Site) {
+		PageConfig.title = "Integration";
+		PageConfig.subTitle = "Site";
+
+		$scope.deleteSite = function (site) {
+			UI.deleteConfirm(site.siteId)
+			(function (entity, closeFunc, unlock) {
+				Entity.delete("sites", site.uuid)._then(function () {
+					Site.reload();
+					closeFunc();
+				}, unlock);
+			});
+		};
+
+		$scope.newSite = function () {
+			UI.createConfirm("Site", {}, [
+				{field: "siteId", name: "Site Id"},
+				{field: "siteName", name: "Display Name", optional: true},
+				{field: "description", name: "Description", optional: true, type: "blob", rows: 5}
+			])(function (entity, closeFunc, unlock) {
+				Entity.create("sites", entity)._then(function () {
+					Site.reload();
+					closeFunc();
+				}, unlock);
+			});
+		};
+	});
+
+	eagleControllers.controller('integrationSiteCtrl', function ($sce, $scope, $wrapState, PageConfig, Entity, UI, Site, Application) {
+		PageConfig.title = "Site";
+		PageConfig.subTitle = $wrapState.param.id;
+
+		// Check site
+		$scope.site = Site.find($wrapState.param.id);
+		if(!$scope.site) {
+			$.dialog({
+				title: "OPS",
+				content: "Site not found!"
+			}, function () {
+				$wrapState.go("integration.siteList");
+			});
+			return;
+		}
+
+		// Map applications
+		function mapApplications() {
+			Site.getPromise().then(function () {
+				$scope.site = Site.find($wrapState.param.id);
+				var uninstalledApplicationList = common.array.minus(Application.providerList, $scope.site.applicationList, "type", "descriptor.type");
+				$scope.applicationList = $.map($scope.site.applicationList, function (app) {
+					app.installed = true;
+					return app;
+				}).concat($.map(uninstalledApplicationList, function (oriApp) {
+					return { origin: oriApp };
+				}));
+			});
+		}
+		mapApplications();
+
+		// Application refresh
+		function refreshApplications() {
+			Application.reload().getPromise().then(mapApplications);
+		}
+
+		// Application status class
+		$scope.getAppStatusClass = function (application) {
+			switch((application.status || "").toUpperCase()) {
+				case "INITIALIZED":
+					return "primary";
+				case "STARTING":
+					return "warning";
+				case "RUNNING":
+					return "success";
+				case "STOPPING":
+					return "warning";
+				case "STOPPED":
+					return "danger";
+			}
+			return "default";
+		};
+
+		// Get started application count
+		$scope.getStartedAppCount = function () {
+			return $.grep($scope.site.applicationList, function (app) {
+				return $.inArray((app.status || "").toUpperCase(), ["STARTING", "RUNNING"]) >= 0;
+			}).length;
+		};
+
+		// Application detail
+		$scope.showAppDetail = function (application) {
+			application = application.origin;
+			var docs = application.docs || {install: "", uninstall: ""};
+			$scope.application = application;
+			$scope.installHTML = $sce.trustAsHtml(docs.install);
+			$scope.uninstallHTML = $sce.trustAsHtml(docs.uninstall);
+			$("#appMDL").modal();
+		};
+
+		// Install application
+		$scope.installApp = function (application) {
+			application = application.origin;
+			var fields = common.getValueByPath(application, "configuration.properties", []);
+			fields = $.map(fields, function (prop) {
+				return {
+					field: prop.name,
+					name: prop.displayName,
+					description: prop.description,
+					defaultValue: prop.value,
+					optional: prop.required === false
+				};
+			});
+
+			UI.fieldConfirm({
+				title: "Install '" + application.type + "'"
+			}, null, fields)(function (entity, closeFunc, unlock) {
+				Entity.create("apps/install", {
+					siteId: $scope.site.siteId,
+					appType: application.type,
+					configuration: entity
+				})._then(function (res) {
+					refreshApplications();
+					closeFunc();
+				}, function (res) {
+					$.dialog({
+						title: "OPS",
+						content: res.data.message
+					});
+					unlock();
+				});
+			});
+		};
+
+		// Uninstall application
+		$scope.uninstallApp = function (application) {
+			UI.deleteConfirm(application.descriptor.name + " - " + application.site.siteId)
+			(function (entity, closeFunc, unlock) {
+				Entity.delete("apps/uninstall", application.uuid)._then(function () {
+					refreshApplications();
+					closeFunc();
+				}, unlock);
+			});
+		};
+
+		// Start application
+		$scope.startApp = function (application) {
+			Entity.post("apps/start", { uuid: application.uuid })._then(function () {
+				refreshApplications();
+			});
+		};
+
+		// Stop application
+		$scope.stopApp = function (application) {
+			Entity.post("apps/stop", { uuid: application.uuid })._then(function () {
+				refreshApplications();
+			});
+		};
+	});
+
+	// ======================================================================================
+	// =                                     Application                                    =
+	// ======================================================================================
+	eagleControllers.controller('integrationApplicationListCtrl', function ($sce, $scope, $wrapState, PageConfig, Application) {
+		$scope.showAppDetail = function(application) {
+			var docs = application.docs || {install: "", uninstall: ""};
+			$scope.application = application;
+			$scope.installHTML = $sce.trustAsHtml(docs.install);
+			$scope.uninstallHTML = $sce.trustAsHtml(docs.uninstall);
+			$("#appMDL").modal();
+		};
+	});
+
+	// ======================================================================================
+	// =                                       Stream                                       =
+	// ======================================================================================
+	eagleControllers.controller('integrationStreamListCtrl', function ($scope, $wrapState, PageConfig, Application) {
+		PageConfig.title = "Integration";
+		PageConfig.subTitle = "Streams";
+
+		$scope.streamList = $.map(Application.list, function (app) {
+			return (app.streams || []).map(function (stream) {
+				return {
+					streamId: stream.streamId,
+					appType: app.descriptor.type,
+					siteId: app.site.siteId,
+					schema: stream.schema
+				};
+			});
+		});
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/ctrls/main.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/main.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/main.js
new file mode 100644
index 0000000..e4a0075
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/main.js
@@ -0,0 +1,27 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers', ['ui.bootstrap', 'eagle.components', 'eagle.service']);
+
+	// ===========================================================
+	// =                        Controller                       =
+	// ===========================================================
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/ctrls/mainCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/mainCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/mainCtrl.js
new file mode 100644
index 0000000..ddd3314
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/mainCtrl.js
@@ -0,0 +1,62 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+
+	// ======================================================================================
+	// =                                        Home                                        =
+	// ======================================================================================
+	eagleControllers.controller('homeCtrl', function ($scope, $wrapState, PageConfig) {
+		PageConfig.title = "Home";
+	});
+
+	// ======================================================================================
+	// =                                       Set Up                                       =
+	// ======================================================================================
+	eagleControllers.controller('setupCtrl', function ($wrapState, $scope, PageConfig, Entity, Site) {
+		PageConfig.hideTitle = true;
+
+		$scope.lock = false;
+		$scope.siteId = "sandbox";
+		$scope.siteName = "Sandbox";
+		$scope.description = "";
+
+		$scope.createSite = function () {
+			$scope.lock = true;
+
+			Entity.create("sites", {
+				siteId: $scope.siteId,
+				siteName: $scope.siteName,
+				description: $scope.description
+			})._then(function () {
+				Site.reload();
+				$wrapState.go('home');
+			}, function (res) {
+				$.dialog({
+					title: "OPS!",
+					content: res.message
+				});
+			}).finally(function () {
+				$scope.lock = false;
+			});
+		};
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/ctrls/siteCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/ctrls/siteCtrl.js b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/siteCtrl.js
new file mode 100644
index 0000000..94c2ed7
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/ctrls/siteCtrl.js
@@ -0,0 +1,33 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+
+
+	// ======================================================================================
+	// =                                        Main                                        =
+	// ======================================================================================
+	eagleControllers.controller('siteCtrl', function ($scope, PageConfig, Site) {
+		var site = Site.current();
+		PageConfig.title = site.siteName || site.siteId;
+		PageConfig.subTitle = "home";
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/index.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/index.js b/eagle-server/src/main/webapp/app/dev/public/js/index.js
new file mode 100644
index 0000000..906479f
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/index.js
@@ -0,0 +1,326 @@
+/*
+ * 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 () {
+	'use strict';
+
+	// ======================================================================================
+	// =                                        Host                                        =
+	// ======================================================================================
+	var _host = "";
+	var _app = {};
+	if(localStorage) {
+		_host = localStorage.getItem("host") || "";
+		_app = common.parseJSON(localStorage.getItem("app") || "") || {};
+	}
+
+	window._host = function (host) {
+		if(host) {
+			_host = host.replace(/[\\\/]+$/, "");
+			if(localStorage) {
+				localStorage.setItem("host", _host);
+			}
+		}
+		return _host;
+	};
+
+	window._app = function (appName, viewPath) {
+		if(arguments.length) {
+			_app[appName] = {
+				viewPath: viewPath
+			};
+			if(localStorage) {
+				localStorage.setItem("app", JSON.stringify(_app));
+			}
+		}
+		return _app;
+	};
+
+	// ======================================================================================
+	// =                                      Register                                      =
+	// ======================================================================================
+	var _moduleStateId = 0;
+	var _registerAppList = [];
+	var _lastRegisterApp = null;
+	var _hookRequireFunc = null;
+
+	function Module(dependencies) {
+		this.dependencies = dependencies;
+		this.queueList = [];
+		this.routeList = [];
+		this.portalList = [];
+		this.widgetList = [];
+
+		this.requireRest = 0;
+		this.requireDeferred = null;
+
+		return this;
+	}
+
+	// GRUNT REPLACEMENT: Module.buildTimestamp = TIMESTAMP
+	window._TRS = function() {
+		return Module.buildTimestamp || Math.random();
+	};
+
+	Module.prototype.service = function () {
+		this.queueList.push({type: "service", args: arguments});
+		return this;
+	};
+	Module.prototype.directive = function () {
+		this.queueList.push({type: "directive", args: arguments});
+		return this;
+	};
+	Module.prototype.controller = function () {
+		this.queueList.push({type: "controller", args: arguments});
+		return this;
+	};
+
+	/**
+	 * Add portal into side navigation bar.
+	 * @param {{}} portal				Config portal content
+	 * @param {string} portal.name		Display name
+	 * @param {string} portal.icon		Display icon. Use 'FontAwesome'
+	 * @param {string=} portal.path		Route path
+	 * @param {[]=} portal.list			Sub portal
+	 * @param {boolean} isSite			true will show in site page or will shown in main page
+	 */
+	Module.prototype.portal = function (portal, isSite) {
+		this.portalList.push({portal: portal, isSite: isSite});
+		return this;
+	};
+
+	/**
+	 * Set application route
+	 * @param {{}|string=} state				Config state. More info please check angular ui router
+	 * @param {{}} config						Route config
+	 * @param {string} config.url				Root url. start with '/'
+	 * @param {string} config.templateUrl		Template url. Relative path of application `viewPath`
+	 * @param {string} config.controller		Set page controller
+	 */
+	Module.prototype.route = function (state, config) {
+		if(arguments.length === 1) {
+			config = state;
+			state = "_APPLICATION_STATE_" + _moduleStateId++;
+		}
+
+		if(!config.url) throw "Url not defined!";
+
+		this.routeList.push({
+			state: state,
+			config: config
+		});
+		return this;
+	};
+
+	/**
+	 * Register home page widget
+	 * @param {string} name				Widget name
+	 * @param {Function} renderFunc		Register function
+	 * @param {boolean} isSite			true will show in site page or will shown in main page
+	 */
+	Module.prototype.widget = function (name, renderFunc, isSite) {
+		this.widgetList.push({
+			widget: {
+				name: name,
+				renderFunc: renderFunc
+			},
+			isSite: isSite
+		});
+		return this;
+	};
+
+	Module.prototype.require = function (scriptURL) {
+		var _this = this;
+
+		_this.requireRest += 1;
+		if(!_this.requireDeferred) {
+			_this.requireDeferred = $.Deferred();
+		}
+
+		setTimeout(function () {
+			$.getScript(_this.baseURL + "/" + scriptURL).then(function () {
+				if(_hookRequireFunc) {
+					_hookRequireFunc(_this);
+				} else {
+					console.error("Hook function not set!", _this);
+				}
+			}).always(function () {
+				_hookRequireFunc = null;
+				_this.requireRest -= 1;
+				_this.requireCheck();
+			});
+		}, 0);
+	};
+
+	Module.prototype.requireCSS = function (styleURL) {
+		var _this = this;
+		setTimeout(function () {
+			$("<link/>", {
+				rel: "stylesheet",
+				type: "text/css",
+				href: _this.baseURL + "/" + styleURL + "?_=" + _TRS()
+			}).appendTo("head");
+		}, 0);
+	};
+
+	Module.prototype.requireCheck = function () {
+		if(this.requireRest === 0) {
+			this.requireDeferred.resolve();
+		}
+	};
+
+	/**
+	 * Get module instance. Will init angular module.
+	 * @param {string} moduleName	angular module name
+	 */
+	Module.prototype.getInstance = function (moduleName) {
+		var _this = this;
+		var deferred = $.Deferred();
+		var module = angular.module(moduleName, this.dependencies);
+
+		// Required list
+		$.when(this.requireDeferred).always(function () {
+			// Fill module props
+			$.each(_this.queueList, function (i, item) {
+				var type = item.type;
+				var args = Array.prototype.slice.apply(item.args);
+				if (type === "controller") {
+					args[0] = moduleName + "_" + args[0];
+				}
+				module[type].apply(module, args);
+			});
+
+			// Render routes
+			var routeList = $.map(_this.routeList, function (route) {
+				var config = route.config = $.extend({}, route.config);
+
+				// Parse url
+				if(config.site) {
+					config.url = "/site/:siteId/" + config.url.replace(/^[\\\/]/, "");
+				}
+
+				// Parse template url
+				var parser = document.createElement('a');
+				parser.href = _this.baseURL + "/" + config.templateUrl;
+				parser.search = parser.search ? parser.search + "&_=" + window._TRS() : "?_=" + window._TRS();
+				config.templateUrl = parser.href;
+
+				if (typeof config.controller === "string") {
+					config.controller = moduleName + "_" + config.controller;
+				}
+
+				return route;
+			});
+
+			// Portal update
+			$.each(_this.portalList, function (i, config) {
+				config.portal.application = moduleName;
+			});
+
+			// Widget update
+			$.each(_this.widgetList, function (i, config) {
+				config.widget.application = moduleName;
+			});
+
+			deferred.resolve({
+				application: moduleName,
+				portalList: _this.portalList,
+				routeList: routeList,
+				widgetList: _this.widgetList
+			});
+		});
+
+		return deferred;
+	};
+
+	window.register = function (dependencies) {
+		if($.isArray(dependencies)) {
+			_lastRegisterApp = new Module(dependencies);
+		} else if(typeof dependencies === "function") {
+			_hookRequireFunc = function (module) {
+				dependencies(module);
+			};
+		}
+		return _lastRegisterApp;
+	};
+
+	// ======================================================================================
+	// =                                        Main                                        =
+	// ======================================================================================
+	$(function () {
+		console.info("[Eagle] Application initialization...");
+
+		// Load providers
+		$.get(_host + "/rest/apps/providers").then(function (res) {
+			/**
+			 * @param {{}} oriApp					application provider
+			 * @param {string} oriApp.viewPath		path of application interface
+			 */
+			var promiseList = $.map(res.data || [], function (oriApp) {
+				var deferred = $.Deferred();
+				var viewPath = common.getValueByPath(_app, [oriApp.type, "viewPath"], oriApp.viewPath);
+
+				if(viewPath) {
+					var url = viewPath;
+					url = url.replace(/^[\\\/]/, "").replace(/[\\\/]$/, "");
+
+					$.getScript(url + "/index.js").then(function () {
+						if(_lastRegisterApp) {
+							_registerAppList.push(oriApp.type);
+							_lastRegisterApp.baseURL = url;
+							_lastRegisterApp.getInstance(oriApp.type).then(function (module) {
+								deferred.resolve(module);
+							});
+						} else {
+							console.error("Application not register:", oriApp.type);
+							deferred.resolve();
+						}
+					}, function () {
+						console.error("Load application failed:", oriApp.type, viewPath);
+						deferred.resolve();
+					}).always(function () {
+						_lastRegisterApp = null;
+					});
+				} else {
+					deferred.resolve();
+				}
+
+				return deferred;
+			});
+
+			common.deferred.all(promiseList).then(function (moduleList) {
+				var routeList = $.map(moduleList, function (module) {
+					return module && module.routeList;
+				});
+				var portalList = $.map(moduleList, function (module) {
+					return module && module.portalList;
+				});
+				var widgetList = $.map(moduleList, function (module) {
+					return module && module.widgetList;
+				});
+
+				$(document).trigger("APPLICATION_READY", {
+					appList: _registerAppList,
+					routeList: routeList,
+					portalList: portalList,
+					widgetList: widgetList
+				});
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/applicationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/applicationSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/applicationSrv.js
new file mode 100644
index 0000000..fac44f9
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/applicationSrv.js
@@ -0,0 +1,71 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	serviceModule.service('Application', function($q, $wrapState, Entity) {
+		var Application = {};
+		var reloadListenerList = [];
+
+		Application.list = [];
+
+		Application.find = function (type, site) {
+			return $.grep(Application.list, function (app) {
+				return app.descriptor.type === type && (site ? app.site.siteId === site : true);
+			});
+		};
+
+		// Load applications
+		Application.reload = function () {
+			Application.list = Entity.query('apps');
+			Application.list._then(function () {
+				$.each(reloadListenerList, function (i, listener) {
+					listener(Application);
+				});
+			});
+			return Application;
+		};
+
+		Application.onReload = function (func) {
+			reloadListenerList.push(func);
+		};
+
+		// Load providers
+		Application.providers = {};
+		Application.providerList = Entity.query('apps/providers');
+		Application.providerList._promise.then(function () {
+			$.each(Application.providerList, function (i, oriApp) {
+				Application.providers[oriApp.type] = oriApp;
+			});
+		});
+
+		Application.getPromise = function () {
+			return Application.list._promise.then(function() {
+				return Application;
+			});
+		};
+
+		// Initialization
+		Application.reload();
+
+		return Application;
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
new file mode 100644
index 0000000..61c244d
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/entitySrv.js
@@ -0,0 +1,135 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	var _host = "";
+	if(localStorage) {
+		_host = localStorage.getItem("host") || "";
+	}
+
+	serviceModule.service('Entity', function($http, $q) {
+		function Entity() {}
+
+		function wrapList(list, promise) {
+			list._done = false;
+			list._promise = promise.then(function (res) {
+				var data = res.data;
+				list.splice(0);
+				Array.prototype.push.apply(list, data.data);
+				list._done = true;
+
+				return res;
+			});
+			return withThen(list);
+		}
+
+		function withThen(list) {
+			list._then = list._promise.then.bind(list._promise);
+			return list;
+		}
+
+		// Dev usage. Set rest api source
+		Entity._host = function (host) {
+			console.warn("This function only used for development usage.");
+			if(host) {
+				_host = host.replace(/[\\\/]+$/, "");
+				if(localStorage) {
+					localStorage.setItem("host", _host);
+				}
+			}
+			return _host;
+		};
+
+		Entity.query = function (url) {
+			var list = [];
+			list._refresh = function () {
+				return wrapList(list, $http.get(_host + "/rest/" + url));
+			};
+
+			return list._refresh();
+		};
+
+		Entity.create = Entity.post = function (url, entity) {
+			var list = [];
+			return wrapList(list, $http({
+				method: 'POST',
+				url: _host + "/rest/" + url,
+				headers: {
+					"Content-Type": "application/json"
+				},
+				data: entity
+			}));
+		};
+
+		Entity.delete = function (url, uuid) {
+			var list = [];
+			return wrapList(list, $http({
+				method: 'DELETE',
+				url: _host + "/rest/" + url,
+				headers: {
+					"Content-Type": "application/json"
+				},
+				data: {uuid: uuid}
+			}));
+		};
+
+		/**
+		 * Merge 2 array into one. Will return origin list before target list is ready. Then fill with target list.
+		 * @param oriList
+		 * @param tgtList
+		 * @return {[]}
+		 */
+		Entity.merge = function (oriList, tgtList) {
+			oriList = oriList || [];
+
+			var list = [].concat(oriList);
+			list._done = tgtList._done;
+			list._refresh = tgtList._refresh;
+			list._promise = tgtList._promise;
+
+			list._promise.then(function () {
+				list.splice(0);
+				Array.prototype.push.apply(list, tgtList);
+				list._done = true;
+			});
+
+			list = withThen(list);
+
+			return list;
+		};
+
+		// TODO: metadata will be removed
+		Entity.queryMetadata = function (url) {
+			return Entity.query('metadata/' +  url);
+		};
+
+		Entity.deleteMetadata = function (url) {
+			return {
+				_promise: $http.delete(_host + "/rest/metadata/" + url).then(function (res) {
+					console.log(res);
+				})
+			};
+		};
+
+		return Entity;
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/main.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/main.js b/eagle-server/src/main/webapp/app/dev/public/js/services/main.js
new file mode 100644
index 0000000..c060de8
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/main.js
@@ -0,0 +1,23 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleSrv = angular.module('eagle.service', []);
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
new file mode 100644
index 0000000..cd0e8b4
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/pageSrv.js
@@ -0,0 +1,137 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	// ============================================================
+	// =                           Page                           =
+	// ============================================================
+	serviceModule.service('PageConfig', function() {
+		function PageConfig() {
+		}
+
+		PageConfig.reset = function () {
+			PageConfig.title = "";
+			PageConfig.subTitle = "";
+			PageConfig.navPath = [];
+			PageConfig.hideTitle = false;
+		};
+
+		return PageConfig;
+	});
+
+	// ============================================================
+	// =                          Portal                          =
+	// ============================================================
+	var defaultPortalList = [
+		{name: "Home", icon: "home", path: "#/"},
+		{name: "Insight", icon: "heartbeat", list: [
+			{name: "Dashboards"},
+			{name: "Metrics"}
+		]},
+		{name: "Alert", icon: "bell", list: [
+			{name: "Explore Alerts", path: "#/alert/"},
+			{name: "Manage Policies", path: "#/alert/policyList"},
+			{name: "Define Policy", path: "#/alert/policyCreate"}
+		]}
+	];
+	var adminPortalList = [
+		{name: "Integration", icon: "puzzle-piece", list: [
+			{name: "Sites", path: "#/integration/siteList"},
+			{name: "Applications", path: "#/integration/applicationList"},
+			{name: "Streams", path: "#/integration/streamList"}
+		]}
+	];
+
+	serviceModule.service('Portal', function($wrapState, Site) {
+		var Portal = {};
+
+		var mainPortalList = [];
+		var sitePortalList = [];
+		var connectedMainPortalList = [];
+		var sitePortals = {};
+
+		var backHome = {name: "Back home", icon: "arrow-left", path: "#/"};
+
+		Portal.register = function (portal, isSite) {
+			(isSite ? sitePortalList : mainPortalList).push(portal);
+		};
+
+		function convertSitePortal(site, portal) {
+			portal = $.extend({}, portal, {
+				path: portal.path ? "#/site/" + site.siteId + "/" + portal.path.replace(/^[\\\/]/, "") : null
+			});
+
+			if(portal.list) {
+				portal.list = $.map(portal.list, function (portal) {
+					return convertSitePortal(site, portal);
+				});
+			}
+
+			return portal;
+		}
+
+		Portal.refresh = function () {
+			// TODO: check admin
+
+			// Main level
+			connectedMainPortalList = defaultPortalList.concat(adminPortalList);
+			var siteList = $.map(Site.list, function (site) {
+				return {
+					name: site.siteName || site.siteId,
+					path: "#/site/" + site.siteId
+				};
+			});
+			connectedMainPortalList.push({name: "Sites", icon: "server", list: siteList});
+
+			// Site level
+			sitePortals = {};
+			$.each(Site.list, function (i, site) {
+				var siteHome = {name: "Home", icon: "home", path: "#/site/" + site.siteId};
+				sitePortals[site.siteId] = [backHome, siteHome].concat($.map(sitePortalList, function (portal) {
+					var hasApp = !!common.array.find(portal.application, site.applicationList, "descriptor.type");
+					if(hasApp) {
+						return convertSitePortal(site, portal);
+					}
+				}));
+			});
+		};
+
+		Object.defineProperty(Portal, 'list', {
+			get: function () {
+				var match = $wrapState.path().match(/^\/site\/([^\/]*)/);
+				if(match && match[1]) {
+					return sitePortals[match[1]];
+				} else {
+					return connectedMainPortalList;
+				}
+			}
+		});
+
+
+		// Initialization
+		Site.onReload(Portal.refresh);
+
+		Portal.refresh();
+
+		return Portal;
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/siteSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/siteSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/siteSrv.js
new file mode 100644
index 0000000..399456d
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/siteSrv.js
@@ -0,0 +1,111 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	serviceModule.service('Site', function($q, $wrapState, Entity, Application) {
+		var Site = {};
+		var reloadListenerList = [];
+
+		Site.list = [];
+
+		// Link with application
+		function linkApplications(siteList, ApplicationList) {
+			$.each(siteList, function (i, site) {
+				var applications = common.array.find(site.siteId, ApplicationList, 'site.siteId', true);
+
+				$.each(applications, function (i, app) {
+					app.descriptor = app.descriptor || {};
+					var oriApp = Application.providers[app.descriptor.type];
+					Object.defineProperty(app, 'origin', {
+						configurable: true,
+						get: function () {
+							return oriApp;
+						}
+					});
+				});
+
+				Object.defineProperties(site, {
+					applicationList: {
+						configurable: true,
+						get: function () {
+							return applications;
+						}
+					}
+				});
+			});
+		}
+
+		// Load sites
+		Site.reload = function () {
+			var list = Site.list = Entity.query('sites');
+			list._promise.then(function () {
+				linkApplications(list, Application.list);
+				$.each(reloadListenerList, function (i, listener) {
+					listener(Site);
+				});
+			});
+			return Site;
+		};
+
+		Site.onReload = function (func) {
+			reloadListenerList.push(func);
+		};
+
+		// Find Site
+		Site.find = function (siteId) {
+			return common.array.find(siteId, Site.list, 'siteId');
+		};
+
+		Site.current = function () {
+			return Site.find($wrapState.param.siteId);
+		};
+
+		Site.getPromise = function (config) {
+			var siteList = Site.list;
+
+			return $q.all([siteList._promise, Application.getPromise()]).then(function() {
+				// Site check
+				if(config && config.site !== false && siteList.length === 0) {
+					$wrapState.go('setup', 1);
+					return $q.reject(Site);
+				}
+
+				// Application check
+				if(config && config.application !== false && Application.list.length === 0) {
+					$wrapState.go('integration.site', {id: siteList[0].siteId}, 1);
+					return $q.reject(Site);
+				}
+
+				return Site;
+			});
+		};
+
+		// Initialization
+		Application.onReload(function () {
+			Site.reload();
+		});
+
+		Site.reload();
+
+		return Site;
+	});
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/timeSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/timeSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/timeSrv.js
new file mode 100644
index 0000000..9d1f85c
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/timeSrv.js
@@ -0,0 +1,277 @@
+/*
+ * 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() {
+	'use strict';
+
+	var UNITS = [
+		["days", "day", "day"],
+		["hours", "hr", "hr"],
+		["minutes", "min", "min"],
+		["seconds", "s", "s"]
+	];
+
+	var keepTime = false;
+	var serviceModule = angular.module('eagle.service');
+
+	serviceModule.service('Time', function($q, $wrapState) {
+		var startTime, endTime;
+		var reloadListenerList = [];
+
+		var $Time = function (time) {
+			var _mom;
+
+			if(arguments.length === 1 && time === undefined) {
+				return null;
+			}
+
+			switch (time) {
+				case "startTime":
+					return startTime;
+				case "endTime":
+					return endTime;
+				case "month":
+					_mom = new moment();
+					_mom.utcOffset($Time.UTC_OFFSET);
+					_mom.date(1).hours(0).minutes(0).seconds(0).millisecond(0);
+					break;
+				case "monthEnd":
+					_mom = $Time("month").add(1, "month").subtract(1, "s");
+					break;
+				case "week":
+					_mom = new moment();
+					_mom.utcOffset($Time.UTC_OFFSET);
+					_mom.weekday(0).hours(0).minutes(0).seconds(0).millisecond(0);
+					break;
+				case "weekEnd":
+					_mom = $Time("week").add(7, "d").subtract(1, "s");
+					break;
+				case "day":
+					_mom = new moment();
+					_mom.utcOffset($Time.UTC_OFFSET);
+					_mom.hours(0).minutes(0).seconds(0).millisecond(0);
+					break;
+				case "dayEnd":
+					_mom = $Time("day").add(1, "d").subtract(1, "s");
+					break;
+				case "hour":
+					_mom = new moment();
+					_mom.utcOffset($Time.UTC_OFFSET);
+					_mom.minutes(0).seconds(0).millisecond(0);
+					break;
+				case "hourEnd":
+					_mom = $Time("hour").add(1, "h").subtract(1, "s");
+					break;
+				default:
+					// Parse string number
+					if(typeof time === "string") {
+						if(!isNaN(+time)) {
+							time = +time;
+						} else {
+							time = new moment(time);
+							time.add(time.utcOffset(), "minutes");
+						}
+					}
+
+					_mom = new moment(time);
+					_mom.utcOffset($Time.UTC_OFFSET);
+			}
+			return _mom;
+		};
+
+		$Time.TIME_RANGE_PICKER = "timeRange";
+		$Time.pickerType = null;
+		$Time._reloadListenerList = reloadListenerList;
+
+		// TODO: time zone
+		$Time.UTC_OFFSET = 0;
+
+		$Time.FORMAT = "YYYY-MM-DD HH:mm:ss";
+		$Time.SHORT_FORMAT = "MM-DD HH:mm";
+
+		$Time.format = function (time, format) {
+			time = $Time(time);
+			return time ? time.format(format || $Time.FORMAT) : "-";
+		};
+
+		$Time.startTime = function () {
+			return startTime;
+		};
+
+		$Time.endTime = function () {
+			return endTime;
+		};
+
+		$Time.timeRange = function (startTimeValue, endTimeValue) {
+			startTime = $Time(startTimeValue);
+			endTime = $Time(endTimeValue);
+
+			keepTime = true;
+			$wrapState.go(".", $.extend({}, $wrapState.param, {
+				startTime: $Time.format(startTime),
+				endTime: $Time.format(endTime)
+			}), {notify: false});
+
+			$.each(reloadListenerList, function (i, listener) {
+				listener($Time);
+			});
+		};
+
+		$Time.onReload = function (func, $scope) {
+			reloadListenerList.push(func);
+
+			// Clean up
+			if($scope) {
+				$scope.$on('$destroy', function() {
+					$Time.offReload(func);
+				});
+			}
+		};
+
+		$Time.offReload = function (func) {
+			reloadListenerList = $.grep(reloadListenerList, function(_func) {
+				return _func !== func;
+			});
+		};
+
+		$Time.verifyTime = function(str, format) {
+			format = format || $Time.FORMAT;
+			var date = $Time(str);
+			if(str === $Time.format(date, format)) {
+				return date;
+			}
+			return null;
+		};
+
+		$Time.diff = function (from, to) {
+			from = $Time(from);
+			to = $Time(to);
+			if (!from || !to) return null;
+			return to.diff(from);
+		};
+
+		$Time.diffStr = function (from, to) {
+			var diff = from;
+			if(arguments.length === 2) {
+				diff = $Time.diff(from, to);
+			}
+			if(diff === null) return "-";
+			if(diff === 0) return "0s";
+
+			var match = false;
+			var rows = [];
+			var duration = moment.duration(diff);
+			var rest = 3;
+
+			$.each(UNITS, function (i, unit) {
+				var interval = Math.floor(duration[unit[0]]());
+				if(interval > 0) match = true;
+
+				if(match) {
+					if(interval !== 0) {
+						rows.push(interval + (interval > 1 ? unit[1] : unit[2]));
+					}
+
+					rest -=1;
+					if(rest === 0) return false;
+				}
+			});
+
+			return rows.join(", ");
+		};
+
+		$Time.diffInterval = function (from, to) {
+			var timeDiff = $Time.diff(from, to);
+			if(timeDiff <= 1000 * 60 * 60 * 6) {
+				return 1000 * 60 * 5;
+			} else if(timeDiff <= 1000 * 60 * 60 * 24) {
+				return 1000 * 60 * 15;
+			} else if(timeDiff <= 1000 * 60 * 60 * 24 * 7) {
+				return 1000 * 60 * 30;
+			} else if(timeDiff <= 1000 * 60 * 60 * 24 * 14) {
+				return 1000 * 60 * 60;
+			} else {
+				return 1000 * 60 * 60 * 24;
+			}
+		};
+
+		$Time.align = function (time, interval, ceil) {
+			time = $Time(time);
+			if(!time) return null;
+
+			var func = ceil ? Math.ceil : Math.floor;
+
+			var timestamp = time.valueOf();
+			return $Time(func(timestamp / interval) * interval);
+		};
+
+		$Time.millionFormat = function (num) {
+			if(!num) return "-";
+			num = Math.floor(num / 1000);
+			var s = num % 60;
+			num = Math.floor(num / 60);
+			var m = num % 60;
+			num = Math.floor(num / 60);
+			var h = num % 60;
+			return common.string.preFill(h, "0") + ":" +
+				common.string.preFill(m, "0") + ":" +
+				common.string.preFill(s, "0");
+		};
+
+		var promiseLock = false;
+		$Time.getPromise = function (config, state, param) {
+			if(keepTime) {
+				keepTime = false;
+				return $q.when($Time);
+			}
+
+			if(config.time === true) {
+				$Time.pickerType = $Time.TIME_RANGE_PICKER;
+
+				if(!promiseLock) {
+					startTime = $Time.verifyTime(param.startTime);
+					endTime = $Time.verifyTime(param.endTime);
+
+					if (!startTime || !endTime) {
+						endTime = $Time();
+						startTime = endTime.clone().subtract(2, "hour");
+
+						setTimeout(function () {
+							promiseLock = true;
+							keepTime = true;
+							$wrapState.go(state.name, $.extend({}, param, {
+								startTime: $Time.format(startTime),
+								endTime: $Time.format(endTime)
+							}), {location: "replace", notify: false});
+
+							setTimeout(function () {
+								promiseLock = false;
+							}, 150);
+						}, 100);
+					}
+				}
+			} else {
+				$Time.pickerType = null;
+			}
+
+			return $q.when($Time);
+		};
+
+		return $Time;
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
new file mode 100644
index 0000000..b4a1a42
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/uiSrv.js
@@ -0,0 +1,276 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	function wrapPromise(promise) {
+		var retFunc = function (notifyFunc, resolveFunc, rejectFunc) {
+			promise.then(resolveFunc, rejectFunc, function (holder) {
+				notifyFunc(holder.entity, holder.closeFunc, holder.unlock);
+			});
+		};
+
+		retFunc.then = promise.then;
+
+		return retFunc;
+	}
+
+	/**
+	 * Check function to check fields pass or not
+	 * @callback checkFieldFunction
+	 * @param {{}} entity
+	 * @return {string}
+	 */
+
+	serviceModule.service('UI', function($rootScope, $q, $compile) {
+		function UI() {}
+
+		function _bindShortcut($dialog) {
+			$dialog.on("keydown", function (event) {
+				if(event.which === 13) {
+					if(!$(":focus").is("textarea")) {
+						$dialog.find(".confirmBtn:enabled").click();
+					}
+				}
+			});
+		}
+
+		function _fieldDialog(create, name, entity, fieldList, checkFunc) {
+			var _deferred, $mdl, $scope;
+
+			var _entity = entity || {};
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.entity = _entity;
+			$scope.fieldList = fieldList;
+			$scope.checkFunc = checkFunc;
+			$scope.lock = false;
+			$scope.create = create;
+
+			$scope.config = typeof name === "object" ? name : {};
+
+			// Init
+			if(!entity) {
+				$.each(fieldList, function (i, field) {
+					if(field.defaultValue) {
+						_entity[field.field] = field.defaultValue;
+					}
+				});
+			}
+
+			// Modal
+			$mdl = $(TMPL_FIELDS).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+			setTimeout(function () {
+				$mdl.find("input, select").filter(':visible:first:enabled').focus();
+			}, 500);
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					entity: _entity
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.getFieldDescription = function (field) {
+				if(typeof field.description === "function") {
+					return field.description($scope.entity);
+				}
+				return field.description || ((field.name || field.field) + '...');
+			};
+
+			$scope.emptyFieldList = function() {
+				return $.map(fieldList, function(field) {
+					if(!field.optional && !_entity[field.field]) {
+						return field.field;
+					}
+				});
+			};
+
+			$scope.confirm = function() {
+				$scope.lock = true;
+				_deferred.notify({
+					entity: _entity,
+					closeFunc: function() {
+						$mdl.modal('hide');
+					},
+					unlock: function() {
+						$scope.lock = false;
+					}
+				});
+			};
+
+			_bindShortcut($mdl);
+
+			return _deferred.promise;
+		}
+
+		/***
+		 * Create a customize field confirm modal.
+		 * @param {string} name							- Create entity name
+		 * @param {object} entity						- Bind entity
+		 * @param {Object[]} fieldList					- Display fields
+		 * @param {string} fieldList[].field				- Mapping entity field
+		 * @param {string=} fieldList[].name				- Field display name
+		 * @param {*=} fieldList[].defaultValue				- Field default value. Only will be set if entity object is undefined
+		 * @param {string=} fieldList[].type				- Field types: 'select', 'blob'
+		 * @param {number=} fieldList[].rows				- Display as textarea if rows is set
+		 * @param {string=} fieldList[].description			- Display as placeholder
+		 * @param {boolean=} fieldList[].optional			- Optional field will not block the confirm
+		 * @param {boolean=} fieldList[].readonly			- Read Only can not be updated
+		 * @param {string[]=} fieldList[].valueList			- For select type usage
+		 * @param {checkFieldFunction=} checkFunc	- Check logic function. Return string will prevent access
+		 */
+		UI.createConfirm = function(name, entity, fieldList, checkFunc) {
+			return wrapPromise(_fieldDialog(true, name, entity, fieldList, checkFunc));
+		};
+
+		/***
+		 * Create a customize field confirm modal.
+		 * @param {object} config						- Configuration object
+		 * @param {string} config.title						- Title of dialog box
+		 * @param {string=} config.size						- "large". Set dialog size
+		 * @param {boolean=} config.confirm					- Display or not confirm button
+		 * @param {string=} config.confirmDesc				- Confirm button display description
+		 * @param {object} entity						- bind entity
+		 * @param {Object[]} fieldList					- Display fields
+		 * @param {string} fieldList[].field				- Mapping entity field
+		 * @param {string=} fieldList[].name				- Field display name
+		 * @param {*=} fieldList[].defaultValue				- Field default value. Only will be set if entity object is undefined
+		 * @param {string=} fieldList[].type				- Field types: 'select', 'blob'
+		 * @param {number=} fieldList[].rows				- Display as textarea if rows is set
+		 * @param {string=} fieldList[].description			- Display as placeholder
+		 * @param {boolean=} fieldList[].optional			- Optional field will not block the confirm
+		 * @param {boolean=} fieldList[].readonly			- Read Only can not be updated
+		 * @param {string[]=} fieldList[].valueList			- For select type usage
+		 * @param {checkFieldFunction=} checkFunc	- Check logic function. Return string will prevent access
+		 */
+		UI.fieldConfirm = function(config, entity, fieldList, checkFunc) {
+			return wrapPromise(_fieldDialog("field", config, entity, fieldList, checkFunc));
+		};
+
+		UI.deleteConfirm = function (name) {
+			var _deferred, $mdl, $scope;
+
+			_deferred = $q.defer();
+			$scope = $rootScope.$new(true);
+			$scope.name = name;
+			$scope.lock = false;
+
+			// Modal
+			$mdl = $(TMPL_DELETE).appendTo('body');
+			$compile($mdl)($scope);
+			$mdl.modal();
+
+			$mdl.on("hide.bs.modal", function() {
+				_deferred.reject();
+			});
+			$mdl.on("hidden.bs.modal", function() {
+				_deferred.resolve({
+					name: name
+				});
+				$mdl.remove();
+			});
+
+			// Function
+			$scope.delete = function() {
+				$scope.lock = true;
+				_deferred.notify({
+					name: name,
+					closeFunc: function() {
+						$mdl.modal('hide');
+					},
+					unlock: function() {
+						$scope.lock = false;
+					}
+				});
+			};
+
+			return wrapPromise(_deferred.promise);
+		};
+
+		return UI;
+	});
+
+	// ===========================================================
+	// =                         Template                        =
+	// ===========================================================
+	var TMPL_FIELDS =
+		'<div class="modal fade" tabindex="-1" role="dialog">' +
+		'<div class="modal-dialog" ng-class="{\'modal-lg\': config.size === \'large\'}" role="document">' +
+		'<div class="modal-content">' +
+		'<div class="modal-header">' +
+		'<button type="button" class="close" data-dismiss="modal" aria-label="Close">' +
+		'<span aria-hidden="true">&times;</span>' +
+		'</button>' +
+		'<h4 class="modal-title">{{config.title || (create ? "New" : "Update") + " " + name}}</h4>' +
+		'</div>' +
+		'<div class="modal-body">' +
+		'<div class="form-group" ng-repeat="field in fieldList" ng-switch="field.type">' +
+		'<label for="featureName">' +
+		'<span ng-if="!field.optional">*</span> ' +
+		'{{field.name || field.field}}' +
+		'</label>' +
+		'<textarea class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" rows="{{ field.rows || 10 }}" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-when="blob"></textarea>' +
+		'<select class="form-control" ng-model="entity[field.field]" ng-init="entity[field.field] = entity[field.field] || field.valueList[0]" ng-switch-when="select">' +
+		'<option ng-repeat="value in field.valueList">{{value}}</option>' +
+		'</select>' +
+		'<input type="text" class="form-control" placeholder="{{getFieldDescription(field)}}" ng-model="entity[field.field]" ng-readonly="field.readonly" ng-disabled="lock" ng-switch-default>' +
+		'</div>' +
+		'</div>' +
+		'<div class="modal-footer">' +
+		'<p class="pull-left text-danger">{{checkFunc(entity)}}</p>' +
+		'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Close</button>' +
+		'<button type="button" class="btn btn-primary confirmBtn" ng-click="confirm()" ng-disabled="checkFunc(entity) || emptyFieldList().length || lock" ng-if="config.confirm !== false">' +
+		'{{config.confirmDesc || (create ? "Create" : "Update")}}' +
+		'</button>' +
+		'</div>' +
+		'</div>' +
+		'</div>' +
+		'</div>';
+
+	var TMPL_DELETE =
+		'<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">' +
+		'<div class="modal-dialog">' +
+		'<div class="modal-content">' +
+		'<div class="modal-header">' +
+		'<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>' +
+		'<h4 class="modal-title">Delete Confirm</h4></div>' +
+		'<div class="modal-body">' +
+		'<span class="text-red fa fa-exclamation-triangle pull-left" style="font-size: 50px;"></span>' +
+		'<p>You are <strong class="text-red">DELETING</strong> \'{{name}}\'!</p>' +
+		'<p>Proceed to delete?</p>' +
+		'</div>' +
+		'<div class="modal-footer">' +
+		'<button type="button" class="btn btn-danger" ng-click="delete()" ng-disabled="lock">Delete</button>' +
+		'<button type="button" class="btn btn-default" data-dismiss="modal" ng-disabled="lock">Cancel</button>' +
+		'</div>' +
+		'</div>' +
+		'</div>' +
+		'</div>';
+}());

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/widgetSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/widgetSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/widgetSrv.js
new file mode 100644
index 0000000..2d6093a
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/widgetSrv.js
@@ -0,0 +1,79 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+
+	serviceModule.service('Widget', function($wrapState, Site, Application) {
+		var Widget = {};
+
+		var mainWidgetList = [];
+		var siteWidgetList = [];
+
+		var displayWidgetList = [];
+		var siteWidgets = {};
+
+		Widget.register = function (widget, isSite) {
+			(isSite ? siteWidgetList : mainWidgetList).push(widget);
+		};
+
+		Widget.refresh = function () {
+			// Common widget
+			displayWidgetList = $.map(mainWidgetList, function (widget) {
+				var hasApp = !!common.array.find(widget.application, Application.list, "descriptor.type");
+				if(hasApp) {
+					return widget;
+				}
+			});
+
+			// Site widget
+			siteWidgets = {};
+			$.each(Site.list, function (i, site) {
+				siteWidgets[site.siteId] = $.map(siteWidgetList, function (widget) {
+					var hasApp = !!common.array.find(widget.application, site.applicationList, "descriptor.type");
+					if(hasApp) {
+						return widget;
+					}
+				});
+			});
+		};
+
+		Object.defineProperty(Widget, 'list', {
+			get: function () {
+				var site = Site.current();
+				if(!site) {
+					return displayWidgetList;
+				} else if(site.siteId) {
+					return siteWidgets[site.siteId];
+				} else {
+					console.warn("Can't find current site id.");
+					return [];
+				}
+			}
+		});
+
+		// Initialization
+		Site.onReload(Widget.refresh);
+
+		Widget.refresh();
+
+		return Widget;
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/services/wrapStateSrv.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/services/wrapStateSrv.js b/eagle-server/src/main/webapp/app/dev/public/js/services/wrapStateSrv.js
new file mode 100644
index 0000000..43e8cc2
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/services/wrapStateSrv.js
@@ -0,0 +1,119 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	serviceModule.service('$wrapState', function($state, $location, $stateParams) {
+		var $wrapState = {};
+		var _targetState = null;
+		var _targetPriority = 0;
+
+		// Go
+		$wrapState.go = function(state, param, option, priority) {
+			setTimeout(function() {
+				_targetState = null;
+				_targetPriority = 0;
+			});
+
+			if(typeof param !== "object") {
+				priority = param;
+				param = {};
+				option = {};
+			} else if(typeof option !== "object") {
+				priority = option;
+				option = {};
+			}
+
+			priority = priority === true ? 1 : (priority || 0);
+			if(_targetPriority > priority) {
+				console.log("[Wrap State] Go - low priority:", state, "(Skip)");
+				return false;
+			}
+
+			if(_targetState !== state || priority) {
+				if($state.current && $state.current.name === state && angular.equals($state.params, param)) {
+					console.log("[Wrap State] Go reload.", $state);
+					$state.reload();
+				} else {
+					console.log("[Wrap State] Go:", state, param, priority);
+					$state.go(state, param, option);
+				}
+				_targetState = state;
+				_targetPriority = priority;
+				return true;
+			} else {
+				console.log("[Wrap State] Go:", state, "(Ignored)");
+			}
+			return false;
+		};
+
+		// Reload
+		$wrapState.reload = function() {
+			console.log("[Wrap State] Do reload.");
+			$state.reload();
+		};
+
+		// Path
+		$wrapState.path = function(path) {
+			if(path !== undefined) {
+				console.log("[Wrap State][Deprecated] Switch path:", path);
+			}
+			return $location.path(path);
+		};
+
+		// URL
+		$wrapState.url = function(url) {
+			if(url !== undefined) console.log("[Wrap State] Switch url:", url);
+			return $location.url(url);
+		};
+
+		Object.defineProperties($wrapState, {
+			// Origin $state
+			origin: {
+				get: function() {
+					return $state;
+				}
+			},
+
+			// Current
+			current: {
+				get: function() {
+					return $state.current;
+				}
+			},
+
+			// Parameter
+			param: {
+				get: function() {
+					return $.extend({}, $location.search(), $stateParams);
+				}
+			},
+
+			// Parameter
+			state: {
+				get: function() {
+					return $state;
+				}
+			}
+		});
+
+		return $wrapState;
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableFunc.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableFunc.js b/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableFunc.js
new file mode 100644
index 0000000..eb0629b
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableFunc.js
@@ -0,0 +1,93 @@
+/*
+ * 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 __sortTable_generateFilteredList;
+
+(function () {
+	'use strict';
+
+	var isArray;
+	if(typeof $ !== "undefined") {
+		isArray = $.isArray;
+	} else {
+		isArray = Array.isArray;
+	}
+
+	function hasContentByPathList(object, content, pathList) {
+		for(var i = 0 ; i < pathList.length ; i += 1) {
+			var path = pathList[i];
+			var value = common.getValueByPath(object, path);
+			if((value + "").toUpperCase().indexOf(content) >= 0) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	function hasContent(object, content, depth) {
+		var i, keys;
+
+		depth = depth || 1;
+		if(!content) return true;
+		if(depth > 10) return false;
+
+		if(object === undefined || object === null) {
+			return false;
+		} else if(isArray(object)) {
+			for(i = 0 ; i < object.length ; i += 1) {
+				if(hasContent(object[i], content, depth + 1)) return true;
+			}
+		} else if(typeof object === "object") {
+			keys = Object.keys(object);
+			for(i = 0 ; i < keys.length ; i += 1) {
+				var value = object[keys[i]];
+				if(hasContent(value, content, depth + 1)) return true;
+			}
+		} else {
+			return (object + "").toUpperCase().indexOf(content) >= 0;
+		}
+
+		return false;
+	}
+
+	__sortTable_generateFilteredList = function(list, search, order, orderAsc, searchPathList) {
+		var i, _list;
+		var _search = (search + "").toUpperCase();
+
+		if (search) {
+			_list = [];
+			if(searchPathList) {
+				for(i = 0 ; i < list.length ; i += 1) {
+					if(hasContentByPathList(list[i], _search, searchPathList)) _list.push(list[i]);
+				}
+			} else {
+				for(i = 0 ; i < list.length ; i += 1) {
+					if(hasContent(list[i], _search)) _list.push(list[i]);
+				}
+			}
+		} else {
+			_list = list;
+		}
+
+		if (order) {
+			common.array.doSort(_list, order, orderAsc);
+		}
+
+		return _list;
+	};
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableWorker.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableWorker.js b/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableWorker.js
new file mode 100644
index 0000000..669ab51
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/dev/public/js/worker/sortTableWorker.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+self.importScripts("../common.js");
+self.importScripts("sortTableFunc.js");
+
+self.addEventListener("message", function (event) {
+	var data = event.data;
+	var list = JSON.parse(data.list);
+
+	list = __sortTable_generateFilteredList(list, data.search, data.order, data.orderAsc, data.searchPathList);
+
+	self.postMessage({
+		list: list,
+		id: data.id
+	});
+});

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/index.html b/eagle-server/src/main/webapp/app/index.html
index f3d3c80..831f3f0 100644
--- a/eagle-server/src/main/webapp/app/index.html
+++ b/eagle-server/src/main/webapp/app/index.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
@@ -16,4 +17,12 @@
   limitations under the License.
   -->
 
-Eagle Server has started!
\ No newline at end of file
+<html>
+	<head>
+		<script>
+			window.location.href = "ui";
+		</script>
+	</head>
+	<body>
+	</body>
+</html>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/package.json
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/package.json b/eagle-server/src/main/webapp/app/package.json
new file mode 100644
index 0000000..f5b43bb
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "ApacheEagleWebApp",
+  "description": "Apache Eagle Web Application",
+  "author": "ApacheEagle",
+  "repository": {
+    "type:": "git",
+    "url": "https://github.com/apache/incubator-eagle.git"
+  },
+  "scripts": {
+    "build": "node build/index.js",
+    "grunt": "grunt"
+  },
+  "license": "Apache-2.0",
+  "dependencies": {
+    "admin-lte": "2.3.2",
+    "angular": "1.5.0",
+    "angular-animate": "1.5.0",
+    "angular-cookies": "1.5.0",
+    "angular-resource": "1.5.0",
+    "angular-route": "1.5.0",
+    "angular-ui-bootstrap": "1.1.2",
+    "angular-ui-router": "~0.2.18",
+    "bootstrap": "3.3.6",
+    "d3": "3.5.16",
+    "echarts": "^3.2.3",
+    "font-awesome": "4.5.0",
+    "jquery": "2.2.4",
+    "jquery-slimscroll": "1.3.6",
+    "jsdom": "^9.5.0",
+    "moment": "2.11.2",
+    "moment-timezone": "0.5.0",
+    "zombiej-bootstrap-components": "1.1.6",
+    "zombiej-nvd3": "1.8.2-3"
+  },
+  "devDependencies": {
+    "grunt": "~0.4.5",
+    "grunt-cli": "~0.1.13",
+    "grunt-contrib-jshint": "~0.11.3",
+    "grunt-regex-replace": "~0.2.6",
+    "grunt-contrib-clean": "~0.7.0",
+    "grunt-contrib-uglify": "~0.5.0",
+    "grunt-contrib-concat": "~0.5.1",
+    "grunt-contrib-cssmin": "~0.14.0",
+    "grunt-contrib-copy": "~0.8.2",
+    "grunt-htmlrefs": "~0.5.0"
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/package.json
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/package.json b/eagle-server/src/main/webapp/package.json
deleted file mode 100644
index e69de29..0000000



[10/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/ui-build.sh
----------------------------------------------------------------------
diff --git a/eagle-server/ui-build.sh b/eagle-server/ui-build.sh
new file mode 100644
index 0000000..9f17ba6
--- /dev/null
+++ b/eagle-server/ui-build.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# 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.
+
+echo "=============== Web APP Building Start ==============="
+echo "Environment Check..."
+# Pre-build check
+if [ -z "$(command -v git)" ]
+then
+	echo "git not installed!"
+	exit 1
+fi
+if [ -z "$(command -v npm)" ]
+then
+	echo "npm not installed!"
+	exit 1
+fi
+echo "Environment Check...Pass"
+
+# npm install
+cd src/main/webapp/app
+echo "npm install..."
+npm install
+
+# grunt build
+echo "building..."
+npm run build

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/Gruntfile.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/Gruntfile.js b/eagle-webservice/src/main/webapp/Gruntfile.js
deleted file mode 100644
index d2a3a42..0000000
--- a/eagle-webservice/src/main/webapp/Gruntfile.js
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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';
-
-module.exports = function (grunt) {
-	// Project configuration.
-	grunt.initConfig({
-		pkg: grunt.file.readJSON('package.json'),
-		config: grunt.file.readJSON('grunt.json'),
-
-		jshint: {
-			options: {
-				browser: true,
-				globals: {
-					$: true,
-					jQuery: true,
-					moment: true
-				}
-			},
-			all: [
-				'app/**/*.js'
-			]
-		},
-
-		clean: {
-			build: ['ui/', 'tmp/'],
-			tmp: ['tmp/'],
-			ui: ['ui/']
-		},
-		concat: {
-			app: {
-				src: [
-					'app/public/js/app.js',
-
-					'app/public/js/srv/main.js',
-					'app/public/js/srv/**.js',
-
-					'app/public/js/app.*.js',
-
-					'app/public/js/common.js',
-
-					'app/public/js/components/main.js',
-					'app/public/js/components/**.js',
-					'app/public/js/components/**/**.js',
-
-					'app/public/js/ctrl/main.js',
-					'app/public/js/ctrl/*.js'
-				],
-				dest: 'tmp/public/js/scripts.js'
-			},
-			js: '<%= config.concat.js %>',
-			css: {
-				options: {
-					process: function(src, filepath) {
-						return "@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);" +
-						src.replace('@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);', '');
-					}
-				},
-				src: '<%= config.concat.css.src %>',
-				dest: '<%= config.concat.css.dest %>'
-			}
-		},
-		'regex-replace': {
-			strict: {
-				src: ['tmp/public/js/scripts.js'],
-				actions: [
-					{
-						name: 'use strict',
-						search: '\\\'use strict\\\';?',
-						replace: '',
-						flags: 'gmi'
-					},
-					{
-						name: 'build timestamp',
-						search: '\\/\\/ GRUNT REPLACEMENT\\: eagleApp\\.buildTimestamp \\= TIMESTAMP',
-						replace: 'eagleApp.buildTimestamp = ' + (+new Date()) + ';',
-						flags: 'gmi'
-					}
-				]
-			}
-		},
-		uglify: {
-			ui: {
-				options: {
-					mangle: false
-				},
-				files: [
-					{
-						src: 'tmp/public/js/scripts.js',
-						dest: 'tmp/public/js/scripts.min.js'
-					},
-					{
-						expand: true,
-						src: '**/*.js',
-						dest: 'tmp/feature',
-						cwd: 'app/public/feature'
-					}
-				]
-			}
-		},
-		cssmin: {
-			ui: {
-				files: {
-					'tmp/public/css/styles.css': ['app/public/css/main.css', 'app/public/css/animation.css']
-				}
-			}
-		},
-		htmlrefs: {
-			ui: {
-				src: 'app/index.html',
-				dest: "tmp/index.html"
-			}
-		},
-		copy: {
-			feature: {
-				files: [
-					{expand: true, cwd: 'app/', src: ['public/feature/**'], dest: 'tmp'}
-				]
-			},
-			ui: {
-				files: [
-					{expand: true, cwd: 'tmp/', src: ['**'], dest: 'ui'},
-					{expand: true, cwd: 'app/', src: ['public/images/**', 'partials/**'], dest: 'ui'},
-					{expand: true, cwd: 'node_modules/font-awesome/', src: ['fonts/**'], dest: 'ui/public'},
-					{expand: true, cwd: 'node_modules/bootstrap/', src: ['fonts/**'], dest: 'ui/public'}
-				]
-			}
-		}
-	});
-
-	grunt.loadNpmTasks('grunt-contrib-jshint');
-	grunt.loadNpmTasks('grunt-contrib-clean');
-	grunt.loadNpmTasks('grunt-contrib-concat');
-	grunt.loadNpmTasks('grunt-contrib-uglify');
-	grunt.loadNpmTasks('grunt-contrib-cssmin');
-	grunt.loadNpmTasks('grunt-htmlrefs');
-	grunt.loadNpmTasks('grunt-regex-replace');
-	grunt.loadNpmTasks('grunt-contrib-copy');
-
-	grunt.registerTask('default', [
-		// jshint
-		'jshint:all',
-		// Clean Env
-		'clean:build',
-		// Compress JS
-		'copy:feature',
-		'concat:app',
-		'regex-replace:strict',
-		'uglify',
-		'concat:js',
-		// Compress CSS
-		'cssmin',
-		'concat:css',
-		// Pass HTML Resources
-		'htmlrefs',
-		'copy:ui',
-		// Clean Env
-		'clean:tmp'
-	]);
-};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/README.md
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/README.md b/eagle-webservice/src/main/webapp/README.md
deleted file mode 100644
index b4168d5..0000000
--- a/eagle-webservice/src/main/webapp/README.md
+++ /dev/null
@@ -1,4 +0,0 @@
-Apache Eagle Web APP
-==
-
-Web client for Apache Eagle
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/index.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/index.html b/eagle-webservice/src/main/webapp/_app/index.html
new file mode 100644
index 0000000..7cd3e25
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/index.html
@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<!--
+  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.
+  -->
+
+<html ng-app="eagleApp" ng-controller="MainCtrl">
+	<head>
+		<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
+		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+		<meta charset="UTF-8">
+		<meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'>
+		<link rel="shortcut icon" href="public/images/favicon.png">
+
+		<title>Eagle</title>
+		<link rel="shortcut icon" type="image/png" href="public/images/favicon.png">
+
+		<!-- ref:css public/css/styles.min.css -->
+		<link href="public/css/main.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="public/css/animation.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/bootstrap/dist/css/bootstrap.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/zombiej-bootstrap-components/bootstrap-components/css/bootstrap-components.css" rel="stylesheet" type="text/css" media="screen">
+		<link href="../node_modules/zombiej-nvd3/build/nv.d3.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/font-awesome/css/font-awesome.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/admin-lte/dist/css/AdminLTE.css" rel="stylesheet" type="text/css" />
+		<link href="../node_modules/admin-lte/dist/css/skins/skin-blue.css" rel="stylesheet" type="text/css" />
+		<!-- endref -->
+	</head>
+	<body class="skin-blue sidebar-mini" ng-class="{'no-sidebar' : PageConfig.hideSidebar}">
+		<!-- Site wrapper -->
+		<div class="wrapper">
+			<header class="main-header">
+				<a href="#/" class="logo">
+					<span class="logo-mini"><img src="public/images/favicon_white.png" /></span>
+					<span class="logo-lg">Apache Eagle</span>
+				</a>
+				<!-- Header Navbar: style can be found in header.less -->
+				<nav class="navbar navbar-static-top" role="navigation">
+					<!-- Sidebar toggle button-->
+					<a ng-hide="PageConfig.hideSidebar" class="sidebar-toggle" data-toggle="offcanvas" role="button">
+						<span class="sr-only">Toggle navigation</span>
+						<span class="icon-bar"></span>
+						<span class="icon-bar"></span>
+						<span class="icon-bar"></span>
+					</a>
+
+					<div class="navbar-custom-menu">
+						<ul class="nav navbar-nav">
+							<!-- Admin error list -->
+							<li class="dropdown" ng-show="ServiceError.list.length">
+								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+									<i class="fa fa-exclamation-triangle" ng-class="{blink: ServiceError.hasUnread}"></i>
+								</a>
+								<ul class="dropdown-menu">
+									<li ng-repeat="error in ServiceError.list">
+										<a ng-click="ServiceError.showError(error);">
+											<span class="fa" ng-class="{'fa-envelope': !error._read, 'fa-check': error._read}"></span>
+											{{error.title}}
+										</a>
+									</li>
+									<li role="separator" class="divider"></li>
+									<li>
+										<a ng-click="ServiceError.clearAll();">
+											<span class="fa fa-trash"></span>
+											Clear All
+										</a>
+									</li>
+								</ul>
+							</li>
+
+							<!-- Site -->
+							<li class="dropdown" ng-show="!PageConfig.hideSite && !PageConfig.lockSite">
+								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+									<i class="fa fa-server"></i>
+									{{Site.current().tags.site}}
+									<i class="fa fa-caret-down"></i>
+								</a>
+								<ul class="dropdown-menu">
+									<li ng-repeat="_site in Site.list" ng-if="_site.enabled">
+										<a ng-click="Site.current(_site);">
+											<span class="fa fa-database"></span> {{_site.tags.site}}
+										</a>
+									</li>
+								</ul>
+							</li>
+							<li class="dropdown" ng-show="PageConfig.lockSite">
+								<a>
+									<i class="fa fa-server"></i>
+									{{Site.current().tags.site}}
+								</a>
+							</li>
+
+							<!-- User -->
+							<li class="dropdown user user-menu" ng-hide="PageConfig.hideUser">
+								<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+									<i class="fa fa-user"></i>
+									{{Auth.userProfile.username}}
+								</a>
+								<ul class="dropdown-menu">
+									<!-- User image -->
+									<li class="user-header">
+										<span class="img-circle">
+											<span class="fa fa-user" alt="User Image"></span>
+										</span>
+										<p>
+											{{Auth.userProfile.username}}
+											<small>
+												<span ng-repeat="role in Auth.userProfile.authorities">{{role.authority}} </span>
+											</small>
+										</p>
+									</li>
+									<!-- Menu Footer-->
+									<li class="user-footer">
+										<div class="pull-left" ng-if="Auth.isRole('ROLE_ADMIN')">
+											<a href="#/config/site" class="btn btn-default btn-flat">Management</a>
+										</div>
+										<div class="pull-right">
+											<a ng-click="logout();" class="btn btn-default btn-flat">Sign out</a>
+										</div>
+									</li>
+								</ul>
+							</li>
+						</ul>
+					</div>
+
+					<!-- Applications -->
+					<div ng-hide="PageConfig.hideApplication">
+						<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#moduleMenu">
+							<span class="sr-only">Toggle navigation</span>
+							<span class="fa fa-map"></span>
+						</button>
+						<div class="collapse navbar-collapse" id="moduleMenu">
+							<ul class="nav navbar-nav">
+								<li ng-repeat="_grp in Site.current().applicationGroupList" ng-if="_grp.enabledList.length"
+									class="dropdown" ng-class="{active: Application.current().group === _grp.name}">
+									<a class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
+										{{_grp.name}}
+									</a>
+									<ul class="dropdown-menu">
+										<li ng-repeat="_app in _grp.enabledList">
+											<a ng-click="Application.current(_app);">
+												<span class="fa fa-cubes"></span> {{_app.displayName}}
+											</a>
+										</li>
+									</ul>
+								</li>
+							</ul>
+						</div>
+					</div>
+				</nav>
+			</header>
+
+			<!-- =============================================== -->
+			<!-- Left side column. contains the side bar -->
+			<aside class="main-sidebar" ng-hide="PageConfig.hideSidebar">
+				<!-- side bar: style can be found in sidebar.less -->
+				<section class="sidebar">
+					<ul class="sidebar-menu">
+						<li class="header">
+							{{Application.current().group || 'Application'}} >
+							{{Application.current().displayName || 'Features'}}
+						</li>
+						<li ng-repeat="page in PageConfig.navConfig.pageList track by $index" ng-class="getNavClass(page)" ng-show="getNavVisible(page)">
+							<a href="{{page.url}}">
+								<i class="fa fa-{{page.icon}}"></i> <span>{{page.title}}</span> 
+							</a>
+						</li>
+					</ul>
+				</section>
+				<!-- /.sidebar -->
+			</aside>
+
+			<!-- =============================================== -->
+			<!-- Right side column. Contains the navbar and content of the page -->
+			<div class="content-wrapper">
+				<!-- Content Header (Page header) -->
+				<section class="content-header" ng-hide="PageConfig.hideSidebar">
+					<h1>
+						<span class="pageTitle">{{PageConfig.pageTitle}}</span>
+						<small class="pageSubTitle">{{PageConfig.pageSubTitle}}</small>
+					</h1>
+
+
+					<ol class="breadcrumb">
+						<li ng-repeat="navPath in PageConfig.navPath">
+							<a ng-href="#{{navPath.path}}">
+								<span class="fa fa-home" ng-if="$first"></span>
+								{{navPath.title || navPath.path}}
+							</a>
+						</li>
+					</ol>
+				</section>
+
+				<!-- Main content -->
+				<section class="content">
+					<div id="content">
+						<div ui-view></div>
+					</div>
+				</section><!-- /.content -->
+			</div><!-- /.content-wrapper -->
+
+			<footer class="main-footer">
+				<div class="pull-right hidden-xs">
+					<b>License</b>
+					<a href="http://www.apache.org/licenses/LICENSE-2.0" class="text-muted">Apache-2.0</a>
+				</div>
+				<strong>
+					Apache Eagle
+					<a target="_blank" href="https://eagle.incubator.apache.org/">Home</a> /
+					<a target="_blank" href="https://eagle.incubator.apache.org/docs/community.html">Community</a> /
+					<a target="_blank" href="https://cwiki.apache.org/confluence/display/EAG/FAQ">FAQ</a>
+				</strong>
+			</footer>
+		</div><!-- ./wrapper -->
+
+		<!-- ref:js public/js/doc.js -->
+		<script src="../node_modules/jquery/dist/jquery.js"></script>
+		<script src="../node_modules/jquery-slimscroll/jquery.slimscroll.min.js"></script>
+		<script src="../node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
+		<script src="../node_modules/zombiej-bootstrap-components/bootstrap-components/js/bootstrap-components.min.js"></script>
+		<script src="../node_modules/moment/min/moment-with-locales.min.js"></script>
+		<script src="../node_modules/moment-timezone/builds/moment-timezone-with-data.min.js"></script>
+		<script src="../node_modules/admin-lte/dist/js/app.min.js"></script>
+		<script src="../node_modules/angular/angular.js"></script>
+		<script src="../node_modules/angular-resource/angular-resource.js"></script>
+		<script src="../node_modules/angular-route/angular-route.js"></script>
+		<script src="../node_modules/angular-animate/angular-animate.js"></script>
+		<script src="../node_modules/angular-ui-bootstrap/dist/ui-bootstrap-tpls.js"></script>
+		<script src="../node_modules/angular-ui-router/release/angular-ui-router.js"></script>
+		<script src="../node_modules/d3/d3.js"></script>
+		<script src="../node_modules/zombiej-nvd3/build/nv.d3.js"></script>
+
+		<!-- Application -->
+		<script src="public/js/app.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Service -->
+		<script src="public/js/srv/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/applicationSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/authorizationSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/entitiesSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/siteSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/pageSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/wrapStateSrv.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/srv/uiSrv.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Misc -->
+		<script src="public/js/app.ui.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/app.time.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/app.config.js" type="text/javascript" charset="utf-8"></script>
+
+		<script src="public/js/common.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Components -->
+		<script src="public/js/components/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/sortTable.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/tabs.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/file.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/charts/line3d.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/nvd3.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/components/sortable.js" type="text/javascript" charset="utf-8"></script>
+
+		<!-- Controllers -->
+		<script src="public/js/ctrl/main.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrl/authController.js" type="text/javascript" charset="utf-8"></script>
+		<script src="public/js/ctrl/configurationController.js" type="text/javascript" charset="utf-8"></script>
+		<!-- endref -->
+	</body>
+</html>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/partials/config/application.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/partials/config/application.html b/eagle-webservice/src/main/webapp/_app/partials/config/application.html
new file mode 100644
index 0000000..0bf194c
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/partials/config/application.html
@@ -0,0 +1,124 @@
+<!--
+  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.
+  -->
+
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			<span class="fa fa-cogs"></span>
+			Configuration
+			<small class="text-danger" ng-show="changed">
+				<span class="label label-warning label-sm">Unsaved</span>
+			</small>
+		</h3>
+	</div><!-- /.box-header -->
+
+	<div class="box-body">
+		<div class="row">
+			<div class="col-md-3">
+				<ul class="nav nav-pills nav-stacked">
+					<li class="disabled"><a>Application</a></li>
+					<li role="presentation" ng-repeat="_application in Application.list track by $index" ng-class="{active: application === _application}">
+						<a ng-click="setApplication(_application)">
+							<span class="fa fa-server"></span>
+							{{_application.tags.application}}
+							<span ng-if="_application.alias">({{_application.alias}})</span>
+						</a>
+					</li>
+
+					<li>
+						<a class="text-light-blue" ng-click="newApplication()" ng-disabled="_pageLock">
+							<span class="fa fa-plus-square"></span>
+							New Application
+						</a>
+					</li>
+				</ul>
+			</div>
+
+			<div class="col-md-9">
+				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteApplication(application)" ng-disabled="_pageLock">
+					<span class="fa fa-trash-o"></span>
+					Delete Application
+				</a>
+
+				<!-- Title -->
+				<h3 class="guideline">
+					Application
+					<small>{{application.tags.application}}</small>
+				</h3>
+				<hr/>
+
+				<!-- Config -->
+				<div class="form-group">
+					<label for="displayName">Display Name</label>
+					<input type="text" class="form-control" id="displayName" placeholder="(Optional) Display name." ng-model="applications[application.tags.application].alias">
+				</div>
+				<div class="form-group">
+					<label for="applicationGroup">Group</label>
+					<input type="text" class="form-control" id="applicationGroup" placeholder="(Optional) Group name" ng-model="applications[application.tags.application].groupName">
+				</div>
+				<div class="form-group">
+					<label for="applicationDescription">Description</label>
+					<textarea id="applicationDescription" class="form-control" placeholder="(Optional) Application description" rows="2" ng-model="applications[application.tags.application].description"></textarea>
+				</div>
+				<div class="form-group">
+					<label for="applicationConfiguration">Configuration</label>
+					<span class="text-danger">{{configCheck(applications[application.tags.application].config)}}</span>
+					<textarea id="applicationConfiguration" class="form-control" placeholder="Application configuration. Feature can read this " rows="5" ng-model="applications[application.tags.application].config"></textarea>
+				</div>
+
+				<!-- Feature -->
+				<label>* Feature</label>
+				<div class="row">
+					<div class="col-sm-6">
+						<h1 class="text-muted text-center" ng-show="applications[application.tags.application].features.length === 0">No feature in using</h1>
+						<ul class="products-list product-list-in-box fixed-height" ng-show="applications[application.tags.application].features.length !== 0">
+							<li class="item" ng-repeat="feature in applications[application.tags.application].features track by $index" ng-class="{active: _feature === feature}">
+								<div class="product-operation">
+									<a class="fa fa-chevron-up" ng-click="moveFeature(feature, applications[application.tags.application].features, -1)"></a>
+									<a class="fa fa-chevron-down" ng-click="moveFeature(feature, applications[application.tags.application].features, 1)"></a>
+								</div>
+								<div class="product-info">
+									<a class="fa fa-times pull-right" ng-click="removeFeature(feature, applications[application.tags.application])"></a>
+									<span class="product-title">{{feature}}</span>
+									<span class="product-description">{{Application.featureList.set[feature].description}}</span>
+								</div>
+							</li>
+						</ul>
+					</div>
+					<div class="col-sm-6">
+						<ul class="products-list product-list-in-box fixed-height">
+							<li class="item" ng-repeat="feature in applications[application.tags.application].optionalFeatures track by $index">
+								<button class="btn btn-lg btn-primary pull-left" ng-click="addFeature(feature, applications[application.tags.application])" ng-disabled="_pageLock">
+									<span class="fa fa-star-o"></span>
+								</button>
+								<div class="product-info">
+									<span class="product-title">{{feature}}</span>
+									<span class="product-description">{{Application.featureList.set[feature].description}}</span>
+								</div>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div><!-- /.box-body -->
+
+	<div class="box-footer clearfix">
+		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/partials/config/feature.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/partials/config/feature.html b/eagle-webservice/src/main/webapp/_app/partials/config/feature.html
new file mode 100644
index 0000000..945d90b
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/partials/config/feature.html
@@ -0,0 +1,85 @@
+<!--
+  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.
+  -->
+
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			<span class="fa fa-cogs"></span>
+			Configuration
+			<small class="text-danger" ng-show="changed">
+				<span class="label label-warning label-sm">Unsaved</span>
+			</small>
+		</h3>
+	</div><!-- /.box-header -->
+
+	<div class="box-body">
+		<div class="row">
+			<div class="col-md-3">
+				<ul class="nav nav-pills nav-stacked">
+					<li class="disabled">
+						<a>Feature</a>
+					</li>
+					<li role="presentation" ng-repeat="_feature in Application.featureList" ng-class="{active: feature === _feature}">
+						<a ng-click="setFeature(_feature)">
+							<span class="fa fa-leaf" ng-class="{'text-danger': _feature._loaded === false}" uib-tooltip="Module load failed!" tooltip-enable="_feature._loaded === false"></span>
+							{{_feature.tags.feature}}
+						</a>
+					</li>
+					<li>
+						<a class="text-light-blue" ng-click="newFeature()" ng-disabled="_pageLock">
+							<span class="fa fa-plus-square"></span>
+							New Feature
+						</a>
+					</li>
+				</ul>
+			</div>
+
+			<div class="col-md-9">
+				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteFeature(feature)" ng-disabled="_pageLock">
+					<span class="fa fa-trash-o"></span>
+					Delete Feature
+				</a>
+
+				<h3 class="guideline">
+					<span class="fa fa-exclamation-triangle text-danger" uib-tooltip="Module load failed!" ng-show="feature._loaded === false"></span>
+					{{feature.tags.feature}}
+				</h3>
+				<hr/>
+
+				<p class="text text-muted">
+					Will load the start up file <code>controller.js</code> from <code>public/feature/{{feature.tags.feature}}</code>.
+					If you are developing customized feature, please reference provided feature.
+				</p>
+
+				<!-- Config -->
+				<div class="form-group">
+					<label for="featureVersion">Version</label>
+					<input id="featureVersion" type="text" class="form-control" placeholder="(Optional) Feature version." ng-model="features[feature.tags.feature].version">
+				</div>
+				<div class="form-group">
+					<label for="featureDescription">Description</label>
+					<textarea id="featureDescription" class="form-control" placeholder="(Optional) Feature description." rows="10" ng-model="features[feature.tags.feature].description"></textarea>
+				</div>
+			</div>
+		</div>
+	</div><!-- /.box-body -->
+
+	<div class="box-footer clearfix">
+		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/partials/config/site.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/partials/config/site.html b/eagle-webservice/src/main/webapp/_app/partials/config/site.html
new file mode 100644
index 0000000..f7d43eb
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/partials/config/site.html
@@ -0,0 +1,115 @@
+<!--
+  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.
+  -->
+
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			<span class="fa fa-cogs"></span>
+			Configuration
+			<small class="text-danger" ng-show="changed">
+				<span class="label label-warning label-sm">Unsaved</span>
+			</small>
+		</h3>
+	</div><!-- /.box-header -->
+
+	<div class="box-body">
+		<div class="row">
+			<div class="col-md-3">
+				<ul class="nav nav-pills nav-stacked">
+					<li class="disabled"><a>Site</a></li>
+					<li role="presentation" ng-repeat="_site in Site.list track by $index" ng-class="{active: site === _site}">
+						<a ng-click="setSite(_site)">
+							<span class="fa fa-server"></span>
+							{{_site.tags.site}}
+						</a>
+					</li>
+
+					<li>
+						<a class="text-light-blue" ng-click="newSite()" ng-disabled="_pageLock">
+							<span class="fa fa-plus-square"></span>
+							New Site
+						</a>
+					</li>
+				</ul>
+			</div>
+
+			<div class="col-md-9">
+				<a class="pull-right btn btn-danger btn-xs" ng-click="deleteSite(site)" ng-disabled="_pageLock">
+					<span class="fa fa-trash-o"></span>
+					Delete Site
+				</a>
+
+				<!-- Title -->
+				<h3 class="guideline">
+					Site
+					<small>{{site.tags.site}}</small>
+				</h3>
+				<hr/>
+
+				<!-- Config -->
+				<div class="checkbox">
+					<label>
+						<input type="checkbox" ng-checked="sites[site.tags.site].enabled" ng-click="sites[site.tags.site].enabled = !sites[site.tags.site].enabled">
+						<strong>Enabled</strong>
+					</label>
+				</div>
+				<hr/>
+
+				<!-- Application -->
+				<label>* Application</label>
+				<div class="row">
+					<div class="col-sm-6">
+						<h1 class="text-muted text-center" ng-show="sites[site.tags.site].applications.length === 0">No application in using</h1>
+						<ul class="products-list product-list-in-box fixed-height" ng-show="sites[site.tags.site].applications.length !== 0">
+							<li class="item" ng-repeat="application in sites[site.tags.site].applications track by $index" ng-class="{active: _application === application}">
+								<div class="product-operation single">
+									<span class="fa fa-cubes"></span>
+								</div>
+								<div class="product-info">
+									<a class="fa fa-times pull-right" ng-click="removeApplication(application, sites[site.tags.site])"></a>
+									<span class="product-title">
+										<a class="fa fa-cog" ng-click="setApplication(application)"></a>
+										{{application.tags.application}}
+									</span>
+									<span class="product-description">{{Application.list.set[application.tags.application].description}}</span>
+								</div>
+							</li>
+						</ul>
+					</div>
+					<div class="col-sm-6">
+						<ul class="products-list product-list-in-box fixed-height">
+							<li class="item" ng-repeat="application in sites[site.tags.site].optionalApplications track by $index">
+								<button class="btn btn-lg btn-primary pull-left" ng-click="addApplication(application, sites[site.tags.site])" ng-disabled="_pageLock">
+									<span class="fa fa-star-o"></span>
+								</button>
+								<div class="product-info">
+									<span class="product-title">{{application.tags.application}}</span>
+									<span class="product-description">{{Application.list.set[application.tags.application].description}}</span>
+								</div>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div><!-- /.box-body -->
+
+	<div class="box-footer clearfix">
+		<button class="btn btn-primary" ng-click="saveAll()" ng-disabled="_pageLock">Save All</button>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/partials/landing.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/partials/landing.html b/eagle-webservice/src/main/webapp/_app/partials/landing.html
new file mode 100644
index 0000000..a2e0f47
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/partials/landing.html
@@ -0,0 +1,30 @@
+<!--
+  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.
+  -->
+
+<p class="lead">
+	<span ng-if="!Application.current()">Current site do not use any application.</span>
+	<span ng-if="Application.current()">Current application do not install any feature.</span>
+
+	<span ng-if="Auth.isRole('ROLE_ADMIN')">
+		Click
+		<a href="#/config/site" ng-if="!Application.current()">here</a>
+		<a href="#/config/application" ng-if="Application.current()">here</a>
+		to configure.
+	</span>
+	<span ng-if="!Auth.isRole('ROLE_ADMIN')">Please contact your admin.</span>
+</p>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/partials/login.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/partials/login.html b/eagle-webservice/src/main/webapp/_app/partials/login.html
new file mode 100644
index 0000000..7faef42
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/partials/login.html
@@ -0,0 +1,54 @@
+<!--
+  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.
+  -->
+
+<div class="login-box">
+	<div class="login-logo">
+		<a href="#/">Apache Eagle</a>
+	</div>
+
+	<div class="login-box-body" ng-show="!loginSuccess">
+		<p class="login-box-msg">Sign in to start your session</p>
+		<div class="form-group has-feedback">
+			<input type="text" class="form-control" placeholder="User Name" ng-model="username" ng-keypress="login($event)" autocomplete="off" id="username">
+			<span class="glyphicon glyphicon-user form-control-feedback"></span>
+		</div>
+		<div class="form-group has-feedback">
+			<input type="password" class="form-control" placeholder="Password" ng-model="password" ng-keypress="login($event)">
+			<span class="glyphicon glyphicon-lock form-control-feedback"></span>
+		</div>
+		<div class="row">
+			<div class="col-xs-8">
+				<div class="checkbox">
+					<label> <input type="checkbox" ng-checked="rememberUser" ng-click="rememberUser = !rememberUser;" /> Remember Me
+					</label>
+				</div>
+			</div>
+			<div class="col-xs-4">
+				<button class="btn btn-primary btn-block btn-flat" ng-click="login($event, true)" ng-disabled="lock">Sign In</button>
+			</div>
+		</div>
+	</div>
+
+	<div class="login-box-body text-center" ng-show="loginSuccess">
+		<p class="login-box-msg">Login success</p>
+		<p>
+			<span class="fa fa-refresh fa-spin"></span>
+			Loading environment...
+		</p>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/css/animation.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/css/animation.css b/eagle-webservice/src/main/webapp/_app/public/css/animation.css
new file mode 100644
index 0000000..954bd29
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/css/animation.css
@@ -0,0 +1,46 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+[ui-view].ng-enter, [ui-view].ng-leave {
+	position: absolute;
+	left: 0;
+	right: 0;
+	-webkit-transition: all .5s ease-in-out;
+	-moz-transition: all .5s ease-in-out;
+	-o-transition: all .5s ease-in-out;
+	transition: all .3s ease-in-out;
+}
+
+[ui-view].ng-enter {
+	opacity: 0;
+}
+
+[ui-view].ng-enter-active {
+	opacity: 1;
+}
+
+[ui-view].ng-leave {
+	opacity: 1;
+	transform:translate3d(0, 0, 0);
+}
+
+[ui-view].ng-leave-active {
+	opacity: 0;
+	transform:translate3d(20%, 0, 0);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/css/main.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/css/main.css b/eagle-webservice/src/main/webapp/_app/public/css/main.css
new file mode 100644
index 0000000..a7eba4b
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/css/main.css
@@ -0,0 +1,805 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+/* Frame */
+body.no-sidebar .content-wrapper {
+	margin-left: 0;
+
+	-webkit-transition: none;
+	-moz-transition: none;
+	-o-transition: none;
+	transition: none;
+}
+
+body.no-sidebar .main-footer {
+	margin-left: 0;
+}
+
+/* Navigation */
+.navbar-nav > .user-menu > .dropdown-menu > li.user-header .img-circle {
+	display: inline-block;
+	border: 3px solid;
+	border-color: rgba(255,255,255,0.2);
+	width: 90px;
+	height: 90px;
+	margin-top: 10px;
+}
+
+.navbar-nav > .user-menu > .dropdown-menu > li.user-header .fa {
+	font-size: 60px;
+	color: rgba(255,255,255,0.8);
+	margin-top: 10px;
+}
+
+	/* Common */
+a {
+	cursor: pointer;
+}
+
+/* Table */
+.table.table-sm>tbody>tr>td,
+.table.table-sm>tbody>tr>th,
+.table.table-sm>tfoot>tr>td,
+.table.table-sm>tfoot>tr>th,
+.table.table-sm>thead>tr>td,
+.table.table-sm>thead>tr>th{
+	padding: 3px 8px;
+}
+
+.table thead th .fa.fa-sort,
+.table thead th .fa.fa-sort-asc,
+.table thead th .fa.fa-sort-desc {
+	margin-top: 5px;
+	opacity: 0.3;
+	float: right;
+}
+.table thead th:hover .fa.fa-sort,
+.table thead th:hover .fa.fa-sort-asc,
+.table thead th:hover .fa.fa-sort-desc {
+	opacity: 0.8;
+}
+
+.table tr th,
+.table tr td {
+	-webkit-transition: background .5s linear;
+	-o-transition: background .5s linear;
+	transition: background .5s linear;
+}
+
+.sortTable-cntr .pagination {
+	margin-top: 0;
+}
+
+.table th.input-field,
+.table td.input-field {
+	padding: 0;
+	vertical-align: middle;
+}
+
+.table th.input-field > input,
+.table td.input-field > input,
+.table th.input-field > select,
+.table td.input-field > select {
+	border: none;
+	transition: border-color 0s;
+}
+
+.table th.input-field > input:focus,
+.table td.input-field > input:focus,
+.table th.input-field > select:focus,
+.table td.input-field > select:focus {
+	box-shadow: inset 1px 1px 0px #3c8dbc, inset -1px -1px 0px #3c8dbc;
+}
+
+.table th.input-field > input.has-warning,
+.table td.input-field > input.has-warning {
+	box-shadow: inset 1px 1px 0px #f39c12, inset -1px -1px 0px #f39c12;
+}
+
+/* Box */
+.small-box > a.inner {
+	color: #FFF;
+	display: block;
+}
+
+.small-box > a.inner h3 {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	font-size: 32px;
+}
+
+.info-box.bg-gray,
+.info-box a {
+	color: #FFFFFF;
+}
+.info-box a:hover {
+	color: #FFFFFF;
+	text-decoration: underline;
+}
+
+.info-box-content a.config {
+	color: rgba(255,255,255,0.8);
+}
+.info-box-content a.config:hover {
+	color: #FFFFFF;
+}
+
+.info-box-content.box-clickable {
+	box-shadow: 0 0 3px;
+}
+.box-clickable {
+	cursor: pointer;
+}
+
+.info-box-content .info-box-text.text-large {
+	font-size: 26px;
+	margin: 5px 0 10px 0;
+}
+
+/* inline group */
+.inline-group dl,
+.inline-group dl dt,
+.inline-group dl dd {
+	display: inline-block;
+}
+
+.inline-group dl {
+	margin-right: 35px;
+}
+.inline-group dl dt {
+	margin-right: 20px;
+}
+
+.inline-group.form-inline {
+	margin-top: 5px;
+}
+.inline-group dl {
+	margin-right: 25px;
+}
+.inline-group dl dt {
+	margin-right: 5px;
+}
+
+/* Search box */
+.search-box {
+	position: relative;
+	margin-bottom: 15px;
+}
+.search-box input[type="search"] {
+	padding-left: 26px;
+}
+.search-box .fa.fa-search {
+	position: absolute;
+	top: 8px;
+	left: 8px;
+	z-index: 2;
+	pointer-events: none;
+	color: #999;
+}
+
+/* Navigation Tab */
+ul.nav.nav-tabs li .btn {
+	margin-top: 1px;
+}
+
+.modal-body ul.nav.nav-tabs {
+	border-bottom-color: #F4F4F4;
+	margin-bottom: 15px;
+}
+
+.modal-body ul.nav.nav-tabs li {
+	border-top: 3px solid #FFFFFF;
+	margin-right: 3px;
+}
+.modal-body ul.nav.nav-tabs li.active {
+	border-top-color: #3c8dbc;
+}
+
+.modal-body ul.nav.nav-tabs li > a,
+.modal-body ul.nav.nav-tabs li > a:active,
+.modal-body ul.nav.nav-tabs li > a:hover {
+	border: none;
+	border-radius: 0;
+	margin: 0;
+	padding: 6px 15px 8px 15px;
+	color: #444;
+}
+.modal-body ul.nav.nav-tabs li:not(.active) > a:hover {
+	background: rgba(0,0,0,0);
+	color: #999;
+}
+.modal-body ul.nav.nav-tabs li.active > a {
+	border-left: 1px solid #F4F4F4;
+	border-right: 1px solid #F4F4F4;
+}
+
+/* Step Navigation */
+.step-cntr .step {
+	background: #3c8dbc;
+	margin: 0 0 20px 0;
+	color: #FFF;
+	height: 60px;
+	border-radius: 3px;
+	box-shadow: 0 1px 1px rgba(0,0,0,0.1);
+	display: block;
+
+	-webkit-transition: background .15s linear;
+	-o-transition: background .15s linear;
+	transition: background .15s linear;
+}
+.step-cntr .step.active {
+	background: #f39c12;
+}
+
+.step-cntr .step h1,
+.step-cntr .step h2,
+.step-cntr .step p {
+	margin: 0;
+	padding: 0;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+.step-cntr .step h1 {
+	display: inline-block;
+	font-size: 30px;
+	float: left;
+	border-right: 2px solid rgba(255,255,255,0.2);
+	width: 60px;
+	height: 60px;
+	text-align: center;
+	padding-top: 12px;
+	margin-right: 10px;
+}
+.step-cntr .step h2 {
+	font-size: 18px;
+	padding: 8px 0 5px 0;
+}
+
+/* Panel */
+.panel-group.panel-group-sm .panel .panel-heading {
+	padding: 5px 6px 5px 10px;
+}
+.panel-group.panel-group-sm .panel .panel-heading h4 {
+	font-size: 14px;
+}
+.panel-group.panel-group-sm .panel .panel-heading h4 a {
+	display: block;
+}
+.panel-group.panel-group-sm .panel .panel-heading .pull-right {
+	padding-left: 5px;
+	padding-right: 5px;
+	border-radius: 3px;
+}
+
+/* Drop Down */
+.dropdown-menu > li.danger > a {
+	color: #dd4b39;
+}
+.dropdown-menu > li.danger > a:hover {
+	color: #FFFFFF;
+	background: #dd4b39;
+}
+
+/* Drop Down */
+.dropdown-menu.left {
+	right: 0;
+	left: auto;
+}
+
+.dropdown-submenu{position:relative;}
+.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.dropdown-submenu:hover>.dropdown-menu{display:block;}
+.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
+.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
+.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+
+/* Input Group */
+.input-group .input-group-btn select {
+	width: auto;
+}
+
+/* Form group */
+.form-group .checkbox {
+	display: inline;
+	margin-right: 10px;
+}
+
+.form-group select.has-warning,
+.form-group input.has-warning {
+	border-color: #f39c12;
+	box-shadow: none;
+}
+
+.checkbox.noMargin {
+	margin-top: 0;
+	margin-bottom: 5px;
+}
+
+/* UL */
+ul.path {
+	margin-left: 0;
+}
+
+ul.path li {
+	padding: 0;
+	margin-right: 5px;
+}
+ul.path li a {
+	color: #FFFFFF;
+}
+
+ul.tree {
+	padding: 0 0 0 5px;
+}
+
+ul.tree > li,
+ul.tree > li > ul > li {
+	list-style-type: none;
+}
+
+ul.tree .tree-item .hover {
+	display: none;
+}
+ul.tree .tree-item:hover .hover {
+	display: inline-block;
+}
+
+ul.tree > li > ul {
+	padding: 0 0 0 25px;
+}
+
+ul.tree > li > ul > li.active {
+	background: #F4F4F4;
+}
+
+ul.tree.tree-bordered {
+	border: 1px solid #f4f4f4;
+}
+ul.tree.tree-bordered,
+ul.tree.tree-bordered > li > ul {
+	padding: 0;
+}
+ul.tree.tree-bordered > li:not(:last-child) {
+	border-bottom: 1px solid #f4f4f4;
+}
+ul.tree.tree-bordered > li > ul > li {
+	border-top: 1px solid #f4f4f4;
+}
+ul.tree.tree-bordered > li > span,
+ul.tree.tree-bordered > li > a {
+	display: block;
+	padding: 8px;
+}
+ul.tree.tree-bordered > li > ul > li > span,
+ul.tree.tree-bordered > li > ul > li > a {
+	display: block;
+	padding: 8px 8px 8px 30px;
+}
+
+.product-list-in-box > .item {
+	-webkit-transition: background .5s linear;
+	-o-transition: background .5s linear;
+	transition: background .5s linear;
+}
+.product-list-in-box > .item.ng-animate {
+	transition: 0s;
+}
+.product-list-in-box > .item.active {
+	background: #F5FAFC;
+	-webkit-transition: none;
+	-o-transition: none;
+	transition: none;
+}
+
+.nav.fixed-height,
+.products-list.fixed-height {
+	height: 402px;
+	overflow-y: auto;
+}
+
+.products-list .product-operation {
+	float: left;
+	border: 1px solid #9EC8E0;
+	border-radius: 5px;
+	overflow: hidden;
+}
+
+.products-list .product-operation .fa {
+	display: block;
+	padding: 4px 16px;
+	color: #3c8dbc;
+}
+.products-list .product-operation a.fa:hover {
+	color: #FFFFFF;
+	background: #337ab7;
+}
+
+.products-list .product-operation.single .fa {
+	padding: 12px 12px;
+	font-size: 20px;
+}
+
+.products-list .item .product-info a.fa.fa-times {
+	display: none;
+}
+.products-list .item:hover .product-info a.fa.fa-times {
+	display: block;
+}
+
+/* Label */
+.label.label-default {
+	color: #FFFFFF;
+}
+
+.label.label-sm {
+	padding: .0em .4em .1em;
+}
+
+/* Row */
+.row.narrow {
+	margin-left: -5px;
+	margin-right: -5px;
+	margin-bottom: -10px;
+}
+
+.row.narrow>.col-xs-1, .row.narrow>.col-sm-1, .row.narrow>.col-md-1, .row.narrow>.col-lg-1, .row.narrow>.col-xs-2, .row.narrow>.col-sm-2, .row.narrow>.col-md-2, .row.narrow>.col-lg-2, .row.narrow>.col-xs-3, .row.narrow>.col-sm-3, .row.narrow>.col-md-3, .row.narrow>.col-lg-3, .row.narrow>.col-xs-4, .row.narrow>.col-sm-4, .row.narrow>.col-md-4, .row.narrow>.col-lg-4, .row.narrow>.col-xs-5, .row.narrow>.col-sm-5, .row.narrow>.col-md-5, .row.narrow>.col-lg-5, .row.narrow>.col-xs-6, .row.narrow>.col-sm-6, .row.narrow>.col-md-6, .row.narrow>.col-lg-6, .row.narrow>.col-xs-7, .row.narrow>.col-sm-7, .row.narrow>.col-md-7, .row.narrow>.col-lg-7, .row.narrow>.col-xs-8, .row.narrow>.col-sm-8, .row.narrow>.col-md-8, .row.narrow>.col-lg-8, .row.narrow>.col-xs-9, .row.narrow>.col-sm-9, .row.narrow>.col-md-9, .row.narrow>.col-lg-9, .row.narrow>.col-xs-10, .row.narrow>.col-sm-10, .row.narrow>.col-md-10, .row.narrow>.col-lg-10, .row.narrow>.col-xs-11, .row.narrow>.col-sm-11, .row.narrow>.col-md-11, .
 row.narrow>.col-lg-11, .row.narrow>.col-xs-12, .row.narrow>.col-sm-12, .row.narrow>.col-md-12, .row.narrow>.col-lg-12 {
+	padding-left: 5px;
+	padding-right: 5px;
+}
+
+.row.narrow > [class^="col-"],
+.row.narrow > [class*=" col-"] {
+	margin-bottom: 10px;
+}
+
+/* Chart */
+.sortable-mock-element .nvd3-chart-wrapper {
+	background: #FFFFFF;
+	opacity: 0.8;
+}
+
+.sortable-enter .nvd3-chart-wrapper {
+	border-color: #3c8dbc;
+	pointer-events: none;
+}
+.sortable-enter .nvd3-chart-wrapper .nvtooltip {
+	display: none;
+}
+
+.nvd3-chart-wrapper {
+	position: relative;
+	border: 1px solid rgba(0,0,0,0.1);
+}
+.nvd3-chart-wrapper:hover {
+	//border-color: #F4F4F4;
+}
+
+.nvd3-chart-wrapper .nvd3-chart-config {
+	position: absolute;
+	top: 1px;
+	right: 1px;
+	display: none;
+	border-radius: 0;
+	padding: 0 5px;
+	background: rgba(0,0,0,0.7);
+}
+.nvd3-chart-wrapper:hover .nvd3-chart-config {
+	display: block;
+}
+
+.nvd3-chart-wrapper .nvd3-chart-config a {
+	color: rgba(255,255,255, 0.9);
+	padding: 5px 2px 4px 2px;
+	font-size: 16px;
+}
+.nvd3-chart-wrapper .nvd3-chart-config a:hover {
+	color: #FFFFFF;
+}
+
+.nvd3-chart-cntr {
+	padding: 5px;
+}
+
+.nvd3-chart-cntr > h3 {
+	text-align: center;
+	font-size: 16px;
+	font-weight: bolder;
+	margin: 0;
+	padding: 5px 0;
+
+	overflow:hidden;
+	text-overflow:ellipsis;
+
+}
+
+.nvd3-chart-cntr > svg.nvd3-svg {
+	height: 200px;
+}
+
+.nvd3-chart-cntr.lg > svg.nvd3-svg {
+	height: 400px;
+}
+
+/* Tab */
+body .tab-content>.tab-pane {
+	display: block;
+	height: 0px;
+	overflow: hidden;
+	position: relative;
+}
+body .tab-content>.tab-pane.active {
+	height: auto;
+	overflow-x: visible;
+	overflow-y: visible;
+}
+
+body .modal-body .nav-pills > li > a,
+body .box-body .nav-pills > li > a {
+	padding: 5px 15px;
+	border: none;
+}
+
+body .modal-body .nav-stacked > li {
+	border-bottom: 1px solid #f4f4f4;
+	margin: 0;
+}
+body .modal-body .nav-stacked > li:last-child {
+	border-bottom: none;
+}
+
+body .box-body .nav-tabs-custom {
+	box-shadow: none;
+	margin-bottom: 0;
+}
+body .box-body .nav-tabs-custom > .nav-tabs > li:first-of-type.active > a {
+	border-left-color: #f4f4f4;
+}
+body .box-body .nav-tabs-custom > .nav-tabs > li > a {
+	padding: 8px 15px;
+}
+body .box-body .nav-tabs-custom > .tab-content {
+	padding: 10px 0;
+}
+
+/* Box */
+.box .guideline {
+	margin-top: 0;
+}
+
+.box.inner-box {
+	border: none;
+	box-shadow: none;
+	padding: 5px 10px;
+	margin: 0;
+	border-bottom: 1px solid #f4f4f4;
+	position: relative;
+	border-radius: 0;
+}
+
+.box.inner-box .box-title {
+	margin: 0 5px 5px 0;
+	padding: 0;
+	font-size: 16px;
+	font-weight: bolder;
+	display: inline-block;
+	word-break: break-all;
+}
+
+.box.inner-box .box-tools {
+	position: absolute;
+	top: 0;
+	right: 0;
+}
+
+.box.inner-box:last-child {
+	border-bottom: none;
+}
+
+/* Navigation Tab */
+.nav-tabs-custom {
+	position: relative;
+}
+
+.nav-tabs-custom .box-tools {
+	position: absolute;
+	right: 15px;
+	top: 8px;
+}
+
+.nav-tabs-custom .box-tools .strong {
+	font-weight: bolder;
+}
+
+/* Customize */
+#content {
+	position: relative;
+}
+
+.page-fixed {
+	position: absolute;
+	top: -45px;
+	right: 0;
+}
+
+@media (max-width:991px) {
+	.page-fixed {
+		top: -70px;
+	}
+}
+
+.fixed-right {
+	position: absolute;
+	right: 0;
+	z-index: 3;
+}
+
+.main-header .logo img {
+	height: 34px;
+}
+
+.main-header .navbar-toggle {
+	float: none;
+	border-radius: 0;
+}
+.main-header .navbar-toggle:hover {
+	background: rgba(0, 0, 0, 0.1);
+}
+
+#moduleMenu > ul > li.active > a {
+	border-top: 3px solid rgba(255,255,255,0.8);
+	padding-top: 12px;
+}
+
+@media (max-width: 767px) {
+	#moduleMenu > ul > li.active > a {
+		padding: 10px 15px;
+		border-top: none;
+		border-left: 3px solid rgba(255,255,255,0.8);
+	}
+
+	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a {
+		color: #333;
+	}
+	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a:hover {
+		color: #FFF;
+	}
+}
+
+#timeRangePickerCntr .navbar-form {
+	display: inline-block;
+	padding-right: 0;
+}
+
+#timeRangePickerCntr #timeRangePicker {
+	min-width: 300px;
+}
+
+body .login-box, body .register-box {
+	margin: 3% auto;
+}
+
+.content-header > .breadcrumb > li {
+	font-size: 14px;
+}
+
+.daterangepicker .ranges {
+  width: 110px!important;
+}
+.daterangepicker .daterangepicker_start_input,
+.daterangepicker .daterangepicker_end_input {
+	display: block!important;
+	padding: 0!important;
+	float: none!important;
+}
+.daterangepicker .daterangepicker_start_input .input-mini,
+.daterangepicker .daterangepicker_end_input .input-mini {
+	width: 110px!important;
+}
+
+.form-group.inner-icon {
+	position: relative;
+}
+.form-group.inner-icon .fa {
+	position: absolute;
+	left: 10px;
+	top: 10px;
+}
+.form-group.inner-icon input {
+	padding-left: 35px;
+}
+
+#autoRefreshCntr > a {
+	border: none;
+	opacity: 0.3;
+}
+#autoRefreshCntr.autoRefresh > a {
+	opacity: 1;
+}
+
+.table-responsive .row {
+	margin: 0;
+}
+
+
+/* Misc */
+body .tooltip-inner {
+	max-width: 500px;
+}
+
+.text-nowrap {
+	white-space: nowrap;
+}
+
+.text-ellipsis,
+.label.text-ellipsis {
+	overflow:hidden;
+	text-overflow:ellipsis;
+	display: inline-block;
+	white-space: nowrap;
+	max-width: 100%;
+}
+td.text-ellipsis {
+	display: table-cell;
+}
+
+.text-breakall {
+	max-width: 100%;
+	display: inline-block;
+	word-wrap: break-word;
+}
+
+.btn.btn-xs.sm {
+	font-size: 12px;
+	padding: 2px 6px;
+}
+
+.form-control.input-xs {
+	height: 24px;
+	padding: 2px 8px;
+	font-size: 12px;
+	line-height: 100%;
+}
+
+pre.noWrap {
+	border: none;
+	border-radius: 0;
+	background: transparent;
+	margin: 0;
+	padding: 0;
+}
+
+.noSelect {
+	-khtml-user-select: none;
+	-moz-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+	-webkit-touch-callout: none;
+	-webkit-user-select: none;
+}
+
+.blink {
+	animation: blinker 1s linear infinite;
+}
+
+@keyframes blinker {
+	50% {opacity: 0.0;}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/classification/controller.js
new file mode 100644
index 0000000..462b41b
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/controller.js
@@ -0,0 +1,358 @@
+/*
+ * 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() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("classification");
+	var eagleApp = angular.module('eagleApp');
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// =============================================================
+	// =                        Sensitivity                        =
+	// =============================================================
+	feature.navItem("sensitivity", "Classification", "user-secret");
+	feature.controller('sensitivity', function(PageConfig, Site, $scope, Application, Entities, UI) {
+		PageConfig.pageTitle = "Data Classification";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		$scope.ajaxId = eagleApp._TRS();
+		$scope.viewConfig = Application.current().configObj.view;
+
+		if(!$scope.viewConfig) {
+			$.dialog({
+				title: "OPS",
+				content: "View configuration not defined in Application."
+			});
+			return;
+		}
+
+		// ===================== Function =====================
+		$scope.export = function() {
+			var _data = {};
+			UI.fieldConfirm({title: "Export Classification", confirm: false, size: "large"}, _data, [
+				{name: "Data", field: "data", type: "blob", rows: 20, optional: true, readonly: true}]
+			);
+
+			Entities.queryEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function(data) {
+				_data.data = JSON.stringify(data, null, "\t");
+			});
+		};
+
+		$scope.import = function() {
+			UI.fieldConfirm({title: "Import Classification", size: "large"}, {}, [
+				{name: "Data", field: "data", type: "blob", rows: 20, optional: true}
+			], function(entity) {
+				var _list = common.parseJSON(entity.data, false);
+				if(!_list) {
+					return "Invalid JSON format";
+				}
+				if(!$.isArray(_list)) {
+					return "Not an array";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity($scope.viewConfig.service, common.parseJSON(holder.entity.data, []), {timestamp: false})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		$scope.deleteAll = function() {
+			UI.deleteConfirm("All the Classification Data").then(null, null, function(holder) {
+				Entities.deleteEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+	});
+     // =============================================================
+    	// =                    Sensitivity - Job                   =
+    	// =============================================================
+    	feature.controller('sensitivityViewJob', function(Site, $scope, $wrapState, Entities) {
+    		$scope.items = [];
+
+    		// Mark sensitivity
+    		$scope._oriItem = {};
+    		$scope._markItem = {};
+
+    		// ======================= View =======================
+    		// Item
+    		$scope.updateItems = function() {
+    			$scope.items = Entities.query($scope.viewConfig.api, {site: Site.current().tags.site});
+    		};
+
+
+    		$scope.updateItems();
+
+    		// =================== Sensitivity ===================
+    		$scope.markSensitivity = function(item) {
+    			$scope._oriItem = item;
+    			$scope._markItem = {
+    				prefix: $scope.viewConfig.prefix,
+    				tags: {
+    					site: Site.current().tags.site
+    				},
+    				sensitivityType: ""
+    			};
+
+    			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.jobId;
+    			$("#sensitivityMDL").modal();
+    		};
+    		$scope.confirmUpateSensitivity = function() {
+    			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
+    			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
+    				Entities.dialog(data);
+    			});
+    			$("#sensitivityMDL").modal('hide');
+    		};
+    		$scope.unmarkSensitivity = function(item) {
+    			$.dialog({
+    				title: "Unmark Confirm",
+    				content: "Do you want to remove the sensitivity mark on '" + item.jobId + "'?",
+    				confirm: true
+    			}, function(ret) {
+    				if(!ret) return;
+
+    				var _cond = {site: Site.current().tags.site};
+    				_cond[$scope.viewConfig.keys[0]] = item.jobId;
+    				Entities.deleteEntities($scope.viewConfig.service, _cond);
+
+    				item.sensitiveType = null;
+    				$scope.$apply();
+    			});
+    		};
+    	});
+	// =============================================================
+	// =                    Sensitivity - Folder                   =
+	// =============================================================
+	feature.controller('sensitivityViewFolder', function(Site, $scope, $wrapState, Entities) {
+		$scope.path = $wrapState.param.path || "/";
+		$scope.pathUnitList = [];
+		$scope.items = [];
+
+		// Mark sensitivity
+		$scope._oriItem = {};
+		$scope._markItem = {};
+
+		// ======================= View =======================
+		// Path
+		function _refreshPathUnitList(_path) {
+			var _start,_current, _unitList = [];
+			_path = _path + (_path.match(/\/$/) ? "" : "/");
+			for(_current = _start = 0 ; _current < _path.length ; _current += 1) {
+				if(_path[_current] === "/") {
+					_unitList.push({
+						name: _path.substring(_start, _current + (_current === 0 ? 1 : 0)),
+						path: _path.substring(0, _current === 0 ? 1 : _current)
+					});
+					_start = _current + 1;
+				}
+			}
+			$scope.pathUnitList = _unitList;
+		}
+
+		// Item
+		$scope.updateItems = function(path) {
+			if(path) $scope.path = path;
+
+			$scope.items = Entities.query($scope.viewConfig.api, {site: Site.current().tags.site, path: $scope.path});
+			$scope.items._promise.success(function(data) {
+				Entities.dialog(data, function() {
+					if($scope.path !== "/") $scope.updateItems("/");
+				});
+			});
+			_refreshPathUnitList($scope.path);
+		};
+
+		$scope.getFileName = function(item) {
+			return (item.resource + "").replace(/^.*\//, "");
+		};
+
+		$scope.updateItems($scope.path);
+
+		// =================== Sensitivity ===================
+		$scope.markSensitivity = function(item) {
+			$scope._oriItem = item;
+			$scope._markItem = {
+				prefix: $scope.viewConfig.prefix,
+				tags: {
+					site: Site.current().tags.site
+				},
+				sensitivityType: ""
+			};
+			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
+			$("#sensitivityMDL").modal();
+		};
+		$scope.confirmUpateSensitivity = function() {
+			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
+			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
+				Entities.dialog(data);
+			});
+			$("#sensitivityMDL").modal('hide');
+		};
+		$scope.unmarkSensitivity = function(item) {
+			$.dialog({
+				title: "Unmark Confirm",
+				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _cond = {site: Site.current().tags.site};
+				_cond[$scope.viewConfig.keys[0]] = item.resource;
+				Entities.deleteEntities($scope.viewConfig.service, _cond);
+
+				item.sensitiveType = null;
+				$scope.$apply();
+			});
+		};
+	});
+
+	// =============================================================
+	// =                    Sensitivity - Table                    =
+	// =============================================================
+	feature.controller('sensitivityViewTable', function(Site, $scope, Entities) {
+		$scope.databases = null;
+		$scope.table = null;
+
+		// Mark sensitivity
+		$scope._oriItem = {};
+		$scope._markItem = {};
+
+		// ======================= View =======================
+		var _fillAttr = function(list, key, target) {
+			list._promise.then(function() {
+				$.each(list, function(i, unit) {
+					unit[key] = unit[target];
+				});
+			});
+			return list._promise;
+		};
+
+		$scope.loadDatabases = function(database) {
+			var _dbs = Entities.query($scope.viewConfig.api.database, {site: Site.current().tags.site});
+			return _fillAttr(_dbs, "database", $scope.viewConfig.mapping.database).then(function() {
+				if($scope.databases) {
+					$.each($scope.databases, function(i, oriDB) {
+						var db = common.array.find(oriDB.resource, _dbs, "resource");
+						if(db) {
+							db.show = oriDB.show;
+							db.tables = oriDB.tables;
+						}
+					});
+				}
+				$scope.databases = _dbs;
+			});
+		};
+		$scope.loadDatabases();
+
+		$scope.loadTables = function(database, force) {
+			var _tables, _qry;
+			if(database.tables && !force) return;
+			_qry = {
+				site: Site.current().tags.site
+			};
+			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
+			_tables = Entities.query($scope.viewConfig.api.table, _qry);
+			if(!database.tables) database.tables = _tables;
+			_fillAttr(_tables, "table", $scope.viewConfig.mapping.table);
+			return _fillAttr(_tables, "database", $scope.viewConfig.mapping.database).then(function() {
+				database.tables = _tables;
+			});
+		};
+
+		$scope.loadColumns = function(database, table) {
+			$scope.table = table;
+
+			if(table.columns) return;
+			var _qry = {
+				site: Site.current().tags.site
+			};
+			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
+			_qry[$scope.viewConfig.mapping.table] = table[$scope.viewConfig.mapping.table];
+			table.columns = Entities.query($scope.viewConfig.api.column, _qry);
+			_fillAttr(table.columns, "column", $scope.viewConfig.mapping.column);
+		};
+
+		$scope.refreshData = function() {
+			$scope.loadDatabases().then(function() {
+				if(!$scope.table) return;
+
+				var _table = $scope.table;
+				var _db = common.array.find($scope.table.database, $scope.databases, "database");
+				if(_db) {
+					$scope.loadTables(_db, true).then(function() {
+						$scope.table = common.array.find(_table.table, _db.tables, "table");
+						$scope.table.columns = _table.columns;
+					});
+				}
+			});
+		};
+
+		// =================== Sensitivity ===================
+		$scope.markSensitivity = function(item, event) {
+			if(event) event.stopPropagation();
+
+			$scope._oriItem = item;
+			$scope._markItem = {
+				prefix: $scope.viewConfig.prefix,
+				tags: {
+					site: Site.current().tags.site
+				},
+				sensitivityType: ""
+			};
+			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
+			$("#sensitivityMDL").modal();
+		};
+		$scope.confirmUpateSensitivity = function() {
+			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
+			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
+				Entities.dialog(data);
+				$scope.refreshData();
+			});
+			$("#sensitivityMDL").modal('hide');
+		};
+		$scope.unmarkSensitivity = function(item, event) {
+			if(event) event.stopPropagation();
+
+			$.dialog({
+				title: "Unmark Confirm",
+				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
+				confirm: true
+			}, function(ret) {
+				if(!ret) return;
+
+				var _qry = {
+					site: Site.current().tags.site
+				};
+				_qry[$scope.viewConfig.keys[0]] = item.resource;
+				Entities.deleteEntities($scope.viewConfig.service, _qry)._promise.then(function() {
+					$scope.refreshData();
+				});
+
+				item.sensitiveType = null;
+				$scope.$apply();
+			});
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity.html b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity.html
new file mode 100644
index 0000000..41fb291
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity.html
@@ -0,0 +1,40 @@
+<!--
+  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.
+  -->
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-folder-open"></i>
+		<h3 class="box-title ng-binding">{{Application.current().displayName}}</h3>
+		<div class="box-tools pull-right" ng-if="viewConfig">
+			<div class="btn-group">
+				<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
+					<span class="fa fa-wrench"></span>
+				</button>
+				<ul class="dropdown-menu" role="menu">
+					<li><a ng-click="import()"><span class="fa fa-cloud-upload"></span> Import</a></li>
+					<li><a ng-click="export()"><span class="fa fa-cloud-download"></span> Export</a></li>
+					<li class="divider"></li>
+					<li class="danger"><a ng-click="deleteAll()"><span class="fa fa-trash"></span> Delete All</a></li>
+				</ul>
+			</div>
+		</div>
+	</div>
+	<div class="box-body">
+		<ng-include ng-if="viewConfig" src="'public/feature/classification/page/sensitivity/' + viewConfig.type + '.html?_=' + ajaxId"></ng-include>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/folder.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/folder.html b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/folder.html
new file mode 100644
index 0000000..cfefffa
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/folder.html
@@ -0,0 +1,110 @@
+<!--
+  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.
+  -->
+<div ng-controller="classification_sensitivityViewFolder">
+	<ul class="list-inline path">
+		<li>Path:</li>
+		<li ng-repeat="unit in pathUnitList">
+			<a ng-click="updateItems(unit.path)" class="label bg-black">{{unit.name}}</a>
+		</li>
+	</ul>
+
+	<table class="table table-bordered">
+		<thead>
+			<tr>
+				<th width="15%">File Name</th>
+				<th width="10%">Owner</th>
+				<th width="10%">Group</th>
+				<th>Sensitivity Type</th>
+				<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
+			</tr>
+		</thead>
+		<tbody>
+			<tr ng-show="items._promise.$$state.status !== 1">
+				<td colspan="5">
+					<span class="fa fa-refresh fa-spin"> </span>
+					Loading...
+				</td>
+			</tr>
+			<tr ng-show="items._promise.$$state.status === 1 && !items.length">
+				<td colspan="5">
+					<span class="fa fa-exclamation-triangle"> </span>
+					Empty Folder
+				</td>
+			</tr>
+			<tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
+				<td>
+					<span ng-show="!item.isdir">
+						<span class="fa fa-file"> </span>
+						{{getFileName(item)}}
+					</span>
+					<a ng-show="item.isdir" ng-click="updateItems(item.resource)">
+						<span class="fa fa-folder"> </span>
+						{{getFileName(item)}}
+					</a>
+
+					<span class="pull-right" ng-show="item.childSensitiveTypes.length">
+						<span class="fa fa-dot-circle-o text-muted" uib-tooltip="Contain child sensitivity defination"> </span>
+					</span>
+				</td>
+				<td>{{item.owner}}</td>
+				<td>{{item.groupName}}</td>
+				<td>{{item.sensitiveType}}</td>
+				<td ng-show="Auth.isRole('ROLE_ADMIN')">
+					<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
+					uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
+					<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
+					uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
+				</td>
+			</tr>
+		</tbody>
+	</table>
+
+
+	<!-- Modal: Create / Edit site -->
+	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
+		<div class="modal-dialog" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+						<span aria-hidden="true">&times;</span>
+					</button>
+					<h4 class="modal-title">Mark Sensitivity Data</h4>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+						<label>Resource</label>
+						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.filedir" />
+					</div>
+					<div class="form-group">
+						<label>* Sensitivity Type</label>
+						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button type="button" class="btn btn-default" data-dismiss="modal">
+						Close
+					</button>
+					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
+						Update
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/job.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/job.html b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/job.html
new file mode 100644
index 0000000..05d70da
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/job.html
@@ -0,0 +1,92 @@
+<!--
+  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.
+  -->
+<div ng-controller="classification_sensitivityViewJob">
+    <ul class="list-inline path">
+        <li>Oozie CoordinatorJob:</li>
+    </ul>
+
+    <table class="table table-bordered">
+        <thead>
+        <tr>
+            <th width="15%">JobId</th>
+            <th width="10%">AppName</th>
+            <th>Sensitivity Type</th>
+            <th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
+        </tr>
+        </thead>
+        <tbody>
+        <tr ng-show="items._promise.$$state.status !== 1">
+            <td colspan="5">
+                <span class="fa fa-refresh fa-spin"> </span>
+                Loading...
+            </td>
+        </tr>
+        <tr ng-show="items._promise.$$state.status === 1 && !items.length">
+            <td colspan="5">
+                <span class="fa fa-exclamation-triangle"> </span>
+                Empty
+            </td>
+        </tr>
+        <tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
+            <td>{{item.jobId}}</td>
+            <td>{{item.name}}</td>
+            <td>{{item.sensitiveType}}</td>
+            <td ng-show="Auth.isRole('ROLE_ADMIN')">
+                <button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
+                        uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
+                <button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
+                        uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
+            </td>
+        </tr>
+        </tbody>
+    </table>
+
+
+    <!-- Modal: Create / Edit site -->
+    <div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                    <h4 class="modal-title">Mark Sensitivity Data</h4>
+                </div>
+                <div class="modal-body">
+                    <div class="form-group">
+                        <label>Resource</label>
+                        <input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.oozieResource" />
+                    </div>
+                    <div class="form-group">
+                        <label>* Sensitivity Type</label>
+                        <input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
+                    </div>
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-default" data-dismiss="modal">
+                        Close
+                    </button>
+                    <button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
+                        Update
+                    </button>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
\ No newline at end of file


[02/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index d61506a..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/app.time.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.
- */
-
-// Time Zone
-(function() {
-	"use strict";
-
-	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");
-	};
-
-	// Force convert date
-	var _toDate = moment.fn.toDate;
-	moment.fn.toDate = function(ignoreTimeZone) {
-		if(!ignoreTimeZone) return _toDate.bind(this)();
-		return new Date(
-			this.year(),
-			this.month(),
-			this.date(),
-			this.hour(),
-			this.minute(),
-			this.second(),
-			this.millisecond()
-		);
-	};
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index ca494d3..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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);
-		}
-	};
-
-	// Modal
-	var _modal = $.fn.modal;
-	$.fn.modal = function() {
-		setTimeout(function() {
-			$(this).find("input[type='text'], textarea").filter(':not([readonly]):enabled:visible:first').focus();
-		}.bind(this), 500);
-		_modal.apply(this, arguments);
-		return this;
-	};
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index 4c5e82f..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/common.js
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * 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 || typeof path !== "string" || 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 {
-		str = (str + "").trim();
-		if(Number(str).toString() === str) throw "Number format";
-		return JSON.parse(str);
-	} catch(err) {
-		if(defaultVal === undefined) {
-			console.warn("Can't parse JSON: " + str);
-		}
-	}
-	return defaultVal === undefined ? null : defaultVal;
-};
-
-common.stringify = function(json) {
-	return JSON.stringify(json, function(key, value) {
-		if(/^(_|\$)/.test(key)) return undefined;
-		return value;
-	});
-};
-
-common.isEmpty = function(val) {
-	if($.isArray(val)) {
-		return val.length === 0;
-	} else {
-		return val === null || val === undefined;
-	}
-};
-
-common.extend = function(target, origin) {
-	$.each(origin, function(key, value) {
-		if(/^(_|\$)/.test(key)) return;
-
-		target[key] = value;
-	});
-	return target;
-};
-
-// ====================== 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) {
-	case 'date':
-		return val.format("YYYY-MM-DD");
-	case 'time':
-		return val.format("HH:mm:ss");
-	case 'datetime':
-		return val.format("YYYY-MM-DD HH:mm:ss");
-	case 'mixed':
-		return val.format("YYYY-MM-DD HH:mm");
-	default:
-		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
-	}
-};
-
-// ===================== Property =====================
-common.properties = {};
-
-common.properties.parse = function (str, defaultValue) {
-	var regex = /\s*([\w\.]+)\s*=\s*(.*?)\s*([\r\n]+|$)/g;
-	var match, props = {};
-	var hasValue = false;
-	while((match = regex.exec(str)) !== null) {
-		props[match[1]] = match[2];
-		hasValue = true;
-	}
-	props = hasValue ? props : defaultValue;
-	props.getValueByPath = function (path) {
-		if(props[path] !== undefined) return props[path];
-		var subProps = {};
-		var prefixPath = path + ".";
-		$.each(props, function (key, value) {
-			if(typeof value === "string" && key.indexOf(prefixPath) === 0) {
-				subProps[key.replace(prefixPath, "")] = value;
-			}
-		});
-		return subProps;
-	};
-
-	return props;
-};
-
-common.properties.check = function (str) {
-	var pass = true;
-	var regex = /^\s*[\w\.]+\s*=(.*)$/;
-	$.each((str || "").trim().split(/[\r\n\s]+/g), function (i, line) {
-		if(!regex.test(line)) {
-			pass = false;
-			return false;
-		}
-	});
-	return pass;
-};
-
-// ====================== 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, caseSensitive) {
-	path = path || "";
-	val = caseSensitive === false ? (val || "").toUpperCase() : val;
-
-	var _list = $.grep(list, function(unit) {
-		if(caseSensitive === false) {
-			return val === (common.getValueByPath(unit, path) || "").toUpperCase();
-		} else {
-			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;
-		}
-	}
-};
-
-common.array.insert = function(val, list, index) {
-	list.splice(index, 0, val);
-};
-
-common.array.moveOffset = function(item, list, offset) {
-	var _index = $.inArray(item, list);
-	var _tgtPos = _index + offset;
-	if(_tgtPos < 0 || _tgtPos >= list.length) return;
-
-	common.array.remove(item, list);
-	common.array.insert(item, list, _index + offset);
-};
-
-// ======================= Map ========================
-common.map = {};
-
-common.map.toArray = function(map) {
-	return $.map(map, function(unit) {
-		return unit;
-	});
-};
-
-// ======================= Math =======================
-common.math = {};
-
-common.math.distance = function(x1,y1,x2,y2) {
-	var a = x1 - x2;
-	var b = y1 - y2;
-	return Math.sqrt(a * a + b * b);
-};

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index eb6e6d1..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/charts/line3d.js
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.directive('line3dChart', function($compile) {
-	'use strict';
-
-	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() {
-	'use strict';
-
-	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) {
-						var i, _unit;
-						for(i = _step ; i < bound.max + _step ; i += _step) {
-							_unit = [0,0,0];
-							_unit[dimension] = i;
-							_axisPoint(_unit, dimension, i);
-						}
-						for(i = -_step ; i > bound.min - _step ; i -= _step) {
-							_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/afb89794/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
deleted file mode 100644
index 82927a3..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/file.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.directive('file', function($compile) {
-	'use strict';
-
-	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/afb89794/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
deleted file mode 100644
index 6b0e21f..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/main.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * 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 eagleComponents = angular.module('eagle.components', []);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js b/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js
deleted file mode 100644
index 8687c78..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/nvd3.js
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.service('nvd3', function() {
-	var nvd3 = {
-		charts: [],
-		colors: [
-			"#7CB5EC", "#F7A35C", "#90EE7E", "#7798BF", "#AAEEEE"
-		]
-	};
-
-	// ============================================
-	// =              Format Convert              =
-	// ============================================
-
-	/***
-	 * Format: [series:{key:name, value: [{x, y}]}]
-	 */
-
-	nvd3.convert = {};
-	nvd3.convert.eagle = function(seriesList) {
-		return $.map(seriesList, function(series) {
-			var seriesObj = $.isArray(series) ? {values: series} : series;
-			if(!seriesObj.key) seriesObj.key = "value";
-			return seriesObj;
-		});
-	};
-
-	nvd3.convert.druid = function(seriesList) {
-		var _seriesList = [];
-
-		$.each(seriesList, function(i, series) {
-			if(!series.length) return;
-
-			// Fetch keys
-			var _measure = series[0];
-			var _keys = $.map(_measure.event, function(value, key) {
-				return key !== "metric" ? key : null;
-			});
-
-			// Parse series
-			_seriesList.push.apply(_seriesList, $.map(_keys, function(key) {
-				return {
-					key: key,
-					values: $.map(series, function(unit) {
-						return {
-							x: new moment(unit.timestamp).valueOf(),
-							y: unit.event[key]
-						};
-					})
-				};
-			}));
-		});
-
-		return _seriesList;
-	};
-
-	// ============================================
-	// =                    UI                    =
-	// ============================================
-	// Resize with refresh
-	function chartResize() {
-		$.each(nvd3.charts, function(i, chart) {
-			if(chart) chart.nvd3Update();
-		});
-	}
-	$(window).on("resize.components.nvd3", chartResize);
-	$("body").on("collapsed.pushMenu expanded.pushMenu", function() {
-		setTimeout(chartResize, 300);
-	});
-
-	return nvd3;
-});
-
-/**
- * config:
- * 		chart:			Defined chart type: line, column, area
- * 		xTitle:			Defined x axis title.
- * 		yTitle:			Defined y axis title.
- * 		xType:			Defined x axis label type: number, decimal, time
- * 		yType:			Defined y axis label type
- * 		yMin:			Defined minimum of y axis
- * 		yMax:			Defined maximum of y axis
- * 		displayType:	Defined the chart display type. Each chart has own type.
- */
-eagleComponents.directive('nvd3', function(nvd3) {
-	'use strict';
-
-	return {
-		restrict: 'AE',
-		scope: {
-			nvd3: "=",
-			title: "@?title",				// title
-			chart: "@?chart",				// Same as config.chart
-			config: "=?config",
-			watching: "@?watching",			// Default watching data(nvd3) only. true will also watching chart & config. false do not watching.
-
-			holder: "=?holder"				// Container for holder to call the chart function
-		},
-		controller: function($scope, $element, $attrs, $timeout) {
-			var _config, _chartType;
-			var _chart;
-			var _chartCntr;
-			var _holder, _holder_updateTimes;
-
-			// Destroy
-			function destroy() {
-				var _index = $.inArray(_chart, nvd3.charts);
-				if(!_chartCntr) return _index;
-
-				// Clean events
-				d3.select(_chartCntr)
-					.on("touchmove",null)
-					.on("mousemove",null, true)
-					.on("mouseout" ,null,true)
-					.on("dblclick" ,null)
-					.on("click", null);
-
-				// Clean elements
-				d3.select(_chartCntr).selectAll("*").remove();
-				$element.find(".nvtooltip").remove();
-				$(_chartCntr).remove();
-
-				// Clean chart in nvd3 pool
-				nvd3.charts[_index] = null;
-				_chart = null;
-
-				return _index;
-			}
-
-			// Setup chart environment. Will clean old chart and build new chart if recall.
-			function initChart() {
-				// Clean up if already have chart
-				var _preIndex = destroy();
-
-				// Initialize
-				_config = $.extend({}, $scope.config);
-				_chartType = $scope.chart || _config.chart;
-				_chartCntr = $(document.createElementNS("http://www.w3.org/2000/svg", "svg"))
-					.css("min-height", 50)
-					.attr("_rnd", Math.random())
-					.appendTo($element)[0];
-
-				// Size
-				if(_config.height) {
-					$(_chartCntr).css("height", _config.height);
-				}
-
-				switch(_chartType) {
-					case "line":
-						_chart = nv.models.lineChart()
-							.useInteractiveGuideline(true)
-							.showLegend(true)
-							.showYAxis(true)
-							.showXAxis(true)
-							.options({
-								duration: 350
-							});
-						break;
-					case "column":
-						_chart = nv.models.multiBarChart()
-							.groupSpacing(0.1)
-							.options({
-								duration: 350
-							});
-						break;
-					case "area":
-						_chart = nv.models.stackedAreaChart()
-							.useInteractiveGuideline(true)
-							.showLegend(true)
-							.showYAxis(true)
-							.showXAxis(true)
-							.options({
-								duration: 350
-							});
-						break;
-					case "pie":
-						_chart = nv.models.dimensionalPieChart()
-							.x(function(d) { return d.key; })
-							.y(function(d) { return d.values[d.values.length - 1].y; });
-						break;
-					default :
-						throw "Type not defined: " + _chartType;
-				}
-
-				// nvd3 display Type
-				// TODO: support type define
-
-				// Define title
-				if(_chartType !== 'pie') {
-					if(_config.xTitle) _chart.xAxis.axisLabel(_config.xTitle);
-					if(_config.yTitle) _chart.yAxis.axisLabel(_config.yTitle);
-				}
-
-				// Define label type
-				var _tickMultiFormat = d3.time.format.multi([
-					["%-I:%M%p", function(d) { return d.getMinutes(); }],
-					["%-I%p", function(d) { return d.getHours(); }],
-					["%b %-d", function(d) { return d.getDate() != 1; }],
-					["%b %-d", function(d) { return d.getMonth(); }],
-					["%Y", function() { return true; }]
-				]);
-
-				function _defineLabelType(axis, type) {
-					if(!_chart) return;
-
-					var _axis = _chart[axis + "Axis"];
-					if(!_axis) return;
-
-					switch(type) {
-						case "decimal":
-						case "decimals":
-							_axis.tickFormat(d3.format('.02f'));
-							break;
-						case "text":
-							if(axis === "x") {
-								_chart.rotateLabels(10);
-								_chart.reduceXTicks(false).staggerLabels(true);
-							}
-							_axis.tickFormat(function(d) {
-								return d;
-							});
-							break;
-						case "time":
-							if(_chartType !== 'column') {
-								_chart[axis + "Scale"](d3.time.scale());
-								(function () {
-									var measureSeries = null;
-									$.each($scope.nvd3 || [], function(i, series) {
-										var _len = (series.values || []).length;
-										if(_len === 0) return;
-										if(measureSeries === null || measureSeries.values.length < _len) measureSeries = series;
-									});
-
-									var width = $element.width() - 35;// Use default nvd3 margin. Hard code.
-									if(!measureSeries || width <= 0) return;
-									var count = Math.floor(width / 80);
-									var countDes = Math.floor(measureSeries.values.length / count);
-									var tickValues = [];
-									for(var loc = 0 ; loc < measureSeries.values.length ; loc += countDes) {
-										tickValues.push(measureSeries.values[loc].x);
-									}
-									_chart[axis + "Axis"].tickValues(tickValues);
-								})();
-							}
-							_axis.tickFormat(function(d) {
-								return _tickMultiFormat(app.time.offset(d).toDate(true));
-							});
-							break;
-						case "number":
-						/* falls through */
-						default:
-							_axis.tickFormat(d3.format(',r'));
-					}
-				}
-
-				if(_chartType !== 'pie') {
-					_defineLabelType("x", _config.xType || "number");
-					_defineLabelType("y", _config.yType || "decimal");
-				}
-
-				// Global chart list update
-				if(_preIndex === -1) {
-					nvd3.charts.push(_chart);
-				} else {
-					nvd3.charts[_preIndex] = _chart;
-				}
-
-				// Use customize update function to update the view
-				_chart.nvd3Update = function() {
-					if(_config.xType === "time") _defineLabelType("x", _config.xType);
-					_chart.update();
-				};
-
-				updateData();
-			}
-
-			// Update chart data
-			function updateData() {
-				var _min = null, _max = null;
-
-				// Copy series to prevent Angular loop watching
-				var _data = $.map($scope.nvd3 || [], function(series, i) {
-					var _series = $.extend(true, {}, series);
-					_series.color = _series.color || nvd3.colors[i % nvd3.colors.length];
-					return _series;
-				});
-
-				// Chart Y value
-				if(($scope.chart || _config.chart) !== "pie") {
-					$.each(_data, function(i, series) {
-						$.each(series.values, function(j, unit) {
-							if(_min === null || unit.y < _min) _min = unit.y;
-							if(_max === null || unit.y > _max) _max = unit.y;
-						});
-					});
-
-					if(_min === 0 && _max === 0) {
-						_chart.forceY([0, 10]);
-					} else if(_config.yMin !== undefined || _config.yMax !== undefined) {
-						_chart.forceY([_config.yMin, _config.yMax]);
-					} else {
-						_chart.forceY([]);
-					}
-				}
-
-				// Update data
-				d3.select(_chartCntr)						//Select the <svg> element you want to render the chart in.
-					.datum(_data)							//Populate the <svg> element with chart data...
-					.call(_chart);							//Finally, render the chart!
-
-				setTimeout(_chart.nvd3Update, 10);
-			}
-
-			// ================================================================
-			// =                           Watching                           =
-			// ================================================================
-			// Ignore initial checking
-			$timeout(function() {
-				if ($scope.watching !== "false") {
-					$scope.$watch(function() {
-						if(!$scope.nvd3) return [];
-
-						var _hashList = $.map($scope.nvd3, function(series) {
-							if(!series.values) return 0;
-							var unit = {
-								x: 0,
-								y: 0
-							};
-
-							$.each(series.values, function(j, item) {
-								unit.x += item.x;
-								unit.y += item.y;
-							});
-
-							return unit;
-						});
-
-						return _hashList;
-					}, function() {
-						updateData();
-					}, true);
-
-					// All watching mode
-					if ($scope.watching === "true") {
-						$scope.$watch("[chart, config]", function(newValue, oldValue) {
-							if(angular.equals(newValue, oldValue)) return;
-							initChart();
-						}, true);
-					}
-				}
-			});
-
-			// Holder inject
-			_holder_updateTimes = 0;
-			_holder = {
-				element: $element,
-				refresh: function() {
-					setTimeout(function() {
-						updateData();
-					}, 0);
-				},
-				refreshAll: function() {
-					setTimeout(function() {
-						initChart();
-					}, 0);
-				}
-			};
-
-			Object.defineProperty(_holder, 'chart', {
-				get: function() {return _chart;}
-			});
-
-			$scope.$watch("holder", function() {
-				// Holder times update
-				setTimeout(function() {
-					_holder_updateTimes = 0;
-				}, 0);
-				_holder_updateTimes += 1;
-				if(_holder_updateTimes > 100) throw "Holder conflict";
-
-				$scope.holder = _holder;
-			});
-
-			// ================================================================
-			// =                           Start Up                           =
-			// ================================================================
-			initChart();
-
-			// ================================================================
-			// =                           Clean Up                           =
-			// ================================================================
-			$scope.$on('$destroy', function() {
-				destroy();
-			});
-		},
-		template :
-		'<div>' +
-			'<h3 title="{{title || config.title}}">{{title || config.title}}</h3>' +
-		'</div>',
-		replace: true
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index 86b89b5..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/sortTable.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.directive('sorttable', function($compile) {
-	'use strict';
-
-	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/afb89794/eagle-webservice/src/main/webapp/app/public/js/components/sortable.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/components/sortable.js b/eagle-webservice/src/main/webapp/app/public/js/components/sortable.js
deleted file mode 100644
index c98c732..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/sortable.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.directive('uieSortable', function($rootScope) {
-	'use strict';
-
-	var COLLECTION_MATCH = /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/;
-
-	var _move = false;
-	var _selectElement;
-	var _overElement;
-	var _mockElement;
-	var _offsetX, _offsetY;
-	var _mouseDownPageX, _mouseDownPageY;
-
-	function doMock(element, event) {
-		var _offset = element.offset();
-		if(_mockElement) _mockElement.remove();
-
-		// Create mock element
-		_mockElement = element.clone(false).appendTo("body");
-		_mockElement.addClass("sortable-mock-element");
-		_mockElement.css({
-			display: "block",
-			position: "absolute",
-			"pointer-events": "none",
-			"z-index": 10001,
-			padding: element.css("padding"),
-			margin: element.css("margin")
-		});
-		_mockElement.width(element.width());
-		_mockElement.height(element.height());
-
-		_mockElement.offset(_offset);
-		_offsetX = event.pageX - _offset.left;
-		_offsetY = event.pageY - _offset.top;
-	}
-
-	$(window).on("mousemove", function(event) {
-		if(!_move) return;
-		event.preventDefault();
-
-		_mockElement.offset({
-			left: event.pageX - _offsetX,
-			top: event.pageY - _offsetY
-		});
-	});
-
-	$(window).on("mouseup", function() {
-		if(!_move) {
-			_overElement = null;
-			_selectElement = null;
-			_mockElement = null;
-			return;
-		}
-		_move = false;
-
-		if(_overElement) {
-			_overElement.removeClass("sortable-enter");
-
-			if(_overElement[0] !== _selectElement[0]) {
-				// Process switch
-				var _oriHolder = _selectElement.holder;
-				var _tgtHolder = _overElement.holder;
-				var _oriSortableScope = _oriHolder.scope;
-				var _tgtSortableScope = _tgtHolder.scope;
-				var _oriScope = angular.element(_selectElement).scope();
-				var _tgtScope = angular.element(_overElement).scope();
-
-				var _oriRepeat = _selectElement.closest("[ng-repeat]").attr("ng-repeat");
-				var _tgtRepeat = _overElement.closest("[ng-repeat]").attr("ng-repeat");
-				var _oriMatch = _oriRepeat.match(COLLECTION_MATCH)[2];
-				var _tgtMatch = _tgtRepeat.match(COLLECTION_MATCH)[2];
-				var _oriCollection = _oriScope.$parent.$eval(_oriMatch);
-				var _tgtCollection = _tgtScope.$parent.$eval(_tgtMatch);
-				var _oriIndex = $.inArray(_oriCollection[_oriScope.$index], _oriSortableScope.ngModel);
-				var _tgtIndex = $.inArray(_tgtCollection[_tgtScope.$index], _tgtSortableScope.ngModel);
-
-				var _oriUnit = _oriSortableScope.ngModel[_oriIndex];
-				var _tgtUnit = _tgtSortableScope.ngModel[_tgtIndex];
-				_oriSortableScope.ngModel[_oriIndex] = _tgtUnit;
-				_tgtSortableScope.ngModel[_tgtIndex] = _oriUnit;
-
-				// Trigger event
-				_oriHolder.change(_oriUnit, _tgtUnit);
-				if (_oriHolder !== _tgtHolder) _tgtHolder.change(_oriUnit, _tgtUnit);
-
-				$rootScope.$apply();
-			}
-		}
-
-		if(_mockElement) _mockElement.remove();
-
-		_overElement = null;
-		_selectElement = null;
-		_mockElement = null;
-	});
-
-	return {
-		require: 'ngModel',
-		restrict : 'AE',
-		scope: {
-			ngModel: "=",
-			sortableEnabled: "=?sortableEnabled",
-			sortableUpdateFunc: "=?sortableUpdateFunc"
-		},
-		link: function($scope, $element, $attrs, $ctrl) {
-			var _holder = {
-				scope: $scope,
-				change: function(source, target) {
-					if($scope.sortableUpdateFunc) $scope.sortableUpdateFunc(source, target);
-				}
-			};
-
-			$element.on("mousedown", ">", function(event) {
-				if($scope.sortableEnabled === false) return;
-
-				_selectElement = $(this);
-				_selectElement.holder = _holder;
-
-				_mouseDownPageX = event.pageX;
-				_mouseDownPageY = event.pageY;
-
-				event.preventDefault();
-			});
-
-			$element.on("mousemove", ">", function(event) {
-				if(_selectElement && !_move && common.math.distance(_mouseDownPageX, _mouseDownPageY, event.pageX, event.pageY) > 10) {
-					_move = true;
-					_overElement = _selectElement;
-					_overElement.addClass("sortable-enter");
-
-					doMock(_selectElement, event);
-				}
-			});
-
-			$element.on("mouseenter", ">", function() {
-				if(!_move) return;
-				_overElement = $(this);
-				_overElement.holder = _holder;
-				_overElement.addClass("sortable-enter");
-			});
-			$element.on("mouseleave", ">", function() {
-				if(!_move) return;
-				$(this).removeClass("sortable-enter");
-				_overElement = null;
-			});
-		},
-		replace: false
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index 21c4a4a..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/components/tabs.js
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * 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.
- */
-
-eagleComponents.directive('tabs', function() {
-	'use strict';
-
-	return {
-		restrict: 'AE',
-		transclude: {
-			'header': '?header',
-			'pane': 'pane',
-			'footer': '?footer'
-		},
-		scope : {
-			title: "@?title",
-			icon: "@",
-			selected: "@?selected",
-			holder: "=?holder",
-			sortableModel: "=?sortableModel",
-
-			menuList: "=?menu"
-		},
-
-		controller: function($scope, $element, $attrs, $timeout) {
-			var transDuration = $.fn.tab.Constructor.TRANSITION_DURATION;
-			var transTimer = null;
-			var _holder, _holder_updateTimes;
-
-			var $header, $footer;
-
-			$scope.paneList = [];
-			$scope.selectedPane = null;
-			$scope.inPane = null;
-			$scope.activePane = null;
-
-			// ================== Function ==================
-			$scope.getPaneList = function() {
-				return !$scope.title ? $scope.paneList : $scope.paneList.slice().reverse();
-			};
-
-			$scope.setSelect = function(pane) {
-				if(typeof pane === "string") {
-					pane = common.array.find(pane, $scope.paneList, "title");
-				} else if(typeof pane === "number") {
-					pane = (pane + $scope.paneList.length) % $scope.paneList.length;
-					pane = $scope.paneList[pane];
-				}
-
-				$scope.activePane = $scope.selectedPane || pane;
-				$scope.selectedPane = pane;
-				$scope.inPane = null;
-
-				if(transTimer) $timeout.cancel(transTimer);
-				transTimer = $timeout(function() {
-					$scope.activePane = $scope.selectedPane;
-					$scope.inPane = $scope.selectedPane;
-				}, transDuration);
-			};
-
-			$scope.getMenuList = function() {
-				if($scope.selectedPane && $scope.selectedPane.menuList) {
-					return $scope.selectedPane.menuList;
-				}
-				return $scope.menuList;
-			};
-
-			$scope.tabSwitchUpdate = function (src, tgt) {
-				var _srcIndex = $.inArray(src.data, $scope.sortableModel);
-				var _tgtIndex = $.inArray(tgt.data, $scope.sortableModel);
-
-				if(_srcIndex !== -1 && _tgtIndex !== -1) {
-					$scope.sortableModel[_srcIndex] = tgt.data;
-					$scope.sortableModel[_tgtIndex] = src.data;
-				}
-			};
-
-			// =================== Linker ===================
-			function _linkerProperties(pane) {
-				Object.defineProperties(pane, {
-					selected: {
-						get: function () {
-							return $scope.selectedPane === this;
-						}
-					},
-					active: {
-						get: function () {
-							return $scope.activePane === this;
-						}
-					},
-					in: {
-						get: function () {
-							return $scope.inPane === this;
-						}
-					}
-				});
-			}
-
-			this.addPane = function(pane) {
-				$scope.paneList.push(pane);
-
-				// Register properties
-				_linkerProperties(pane);
-
-				// Update select pane
-				if(pane.title === $scope.selected || !$scope.selectedPane) {
-					$scope.setSelect(pane);
-				}
-			};
-
-			this.deletePane = function(pane) {
-				common.array.remove(pane, $scope.paneList);
-
-				if($scope.selectedPane === pane) {
-					$scope.selectedPane = $scope.activePane = $scope.inPane = $scope.paneList[0];
-				}
-			};
-
-			// ===================== UI =====================
-			$header = $element.find("> .nav-tabs-custom > .box-body");
-			$footer = $element.find("> .nav-tabs-custom > .box-footer");
-
-			$scope.hasHeader = function() {
-				return !!$header.children().length;
-			};
-			$scope.hasFooter = function() {
-				return !!$footer.children().length;
-			};
-
-			// ================= Interface ==================
-			_holder_updateTimes = 0;
-			_holder = {
-				scope: $scope,
-				element: $element,
-				setSelect: $scope.setSelect
-			};
-
-			Object.defineProperty(_holder, 'selectedPane', {
-				get: function() {return $scope.selectedPane;}
-			});
-
-			$scope.$watch("holder", function(newValue, oldValue) {
-				// Holder times update
-				setTimeout(function() {
-					_holder_updateTimes = 0;
-				}, 0);
-				_holder_updateTimes += 1;
-				if(_holder_updateTimes > 100) throw "Holder conflict";
-
-				$scope.holder = _holder;
-			});
-		},
-
-		template :
-			'<div class="nav-tabs-custom">' +
-				// Menu
-				'<div class="box-tools pull-right" ng-if="getMenuList() && getMenuList().length">' +
-					'<div ng-repeat="menu in getMenuList() track by $index" class="inline">' +
-						// Button
-						'<button class="btn btn-box-tool" ng-click="menu.func($event)" ng-if="!menu.list"' +
-							' uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" tooltip-append-to-body="true">' +
-							'<span class="fa fa-{{menu.icon}}"></span>' +
-						'</button>' +
-
-						// Dropdown Group
-						'<div class="btn-group" ng-if="menu.list">' +
-							'<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"' +
-								' uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" tooltip-append-to-body="true">' +
-								'<span class="fa fa-{{menu.icon}}"></span>' +
-							'</button>' +
-							'<ul class="dropdown-menu left" role="menu">' +
-								'<li ng-repeat="item in menu.list track by $index" ng-class="{danger: item.danger, disabled: item.disabled}">' +
-									'<a ng-click="!item.disabled && item.func($event)" ng-class="{strong: item.strong}">' +
-										'<span class="fa fa-{{item.icon}}"></span> {{item.title}}' +
-									'</a>' +
-								'</li>' +
-							'</ul>' +
-						'</div>' +
-					'</div>' +
-				'</div>' +
-
-				'<ul uie-sortable sortable-enabled="!!sortableModel" sortable-update-func="tabSwitchUpdate" ng-model="paneList" class="nav nav-tabs" ng-class="{\'pull-right\': title}">' +
-					// Tabs
-					'<li ng-repeat="pane in getPaneList() track by $index" ng-class="{active: selectedPane === pane}">' +
-						'<a ng-click="setSelect(pane);">{{pane.title}}</a>' +
-					'</li>' +
-
-					// Title
-					'<li class="pull-left header" ng-if="title">' +
-						'<i class="fa fa-{{icon}}" ng-if="icon"></i> {{title}}' +
-					'</li>' +
-
-				'</ul>' +
-				'<div class="box-body" ng-transclude="header" ng-show="paneList.length && hasHeader()"></div>' +
-				'<div class="tab-content" ng-transclude="pane"></div>' +
-				'<div class="box-footer" ng-transclude="footer" ng-show="paneList.length && hasFooter()"></div>' +
-			'</div>'
-	};
-}).directive('pane', function() {
-	'use strict';
-
-	return {
-		require : '^tabs',
-		restrict : 'AE',
-		transclude : true,
-		scope : {
-			title : '@',
-			data: '=?data',
-			menuList: "=?menu"
-		},
-		link : function(scope, element, attrs, tabsController) {
-			tabsController.addPane(scope);
-			scope.$on('$destroy', function() {
-				tabsController.deletePane(scope);
-			});
-		},
-		template : '<div class="tab-pane fade" ng-class="{active: active, in: in}" ng-transclude></div>',
-		replace : true
-	};
-}).directive('footer', function() {
-	'use strict';
-
-	return {
-		require : '^tabs',
-		restrict : 'AE',
-		transclude : true,
-		scope : {},
-		controller: function($scope, $element) {
-		},
-		template : '<div ng-transclude></div>',
-		replace : true
-	};
-});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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
deleted file mode 100644
index dbdb704..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var eagleControllers = angular.module('eagleControllers');
-	// =============================================================
-	// =                     User Profile List                     =
-	// =============================================================
-	eagleControllers.controller('authLoginCtrl', function (PageConfig, Site, Authorization, Application, $scope) {
-		PageConfig.hideSidebar = true;
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		PageConfig.hideUser = true;
-
-		$scope.username = "";
-		$scope.password = "";
-		$scope.lock = false;
-		$scope.loginSuccess = false;
-
-		if(localStorage) {
-			$scope.rememberUser = localStorage.getItem("rememberUser") !== "false";
-
-			if($scope.rememberUser) {
-				$scope.username = localStorage.getItem("username");
-				$scope.password = localStorage.getItem("password");
-			}
-		}
-
-		// 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) {
-						// Check user remember
-						localStorage.setItem("rememberUser", $scope.rememberUser);
-						if($scope.rememberUser) {
-							localStorage.setItem("username", $scope.username);
-							localStorage.setItem("password", $scope.password);
-						} else {
-							localStorage.removeItem("username");
-							localStorage.removeItem("password");
-						}
-
-						// Initial environment
-						$scope.loginSuccess = true;
-						console.log("[Login] Login success! Reload data...");
-						Authorization.reload().then(function() {}, function() {console.warn("Site error!");});
-						Application.reload().then(function() {}, function() {console.warn("Site error!");});
-						Site.reload().then(function() {}, function() {console.warn("Site error!");});
-						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/afb89794/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
deleted file mode 100644
index e59198d..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/configurationController.js
+++ /dev/null
@@ -1,377 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var eagleControllers = angular.module('eagleControllers');
-
-	// =============================================================
-	// =                         Function                          =
-	// =============================================================
-	function watchEdit($scope, key) {
-		$scope.changed = false;
-		setTimeout(function() {
-			var _func = $scope.$watch(key, function(newValue, oldValue) {
-				if(angular.equals(newValue, oldValue)) return;
-				$scope.changed = true;
-				_func();
-			}, true);
-		}, 100);
-	}
-
-	// =============================================================
-	// =                       Configuration                       =
-	// =============================================================
-	// ========================== Feature ==========================
-	eagleControllers.controller('configFeatureCtrl', function ($scope, PageConfig, Application, Entities, UI) {
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		$scope._pageLock = false;
-
-		PageConfig
-			.addNavPath("Home", "/")
-			.addNavPath("Feature Config");
-
-		// ================== Feature ==================
-		// Current feature
-		$scope.feature = Application.featureList[0];
-		$scope.setFeature = function (feature) {
-			$scope.feature = feature;
-		};
-
-		// Feature list
-		$scope.features = {};
-		$.each(Application.featureList, function(i, feature) {
-			$scope.features[feature.tags.feature] = $.extend({}, feature, true);
-		});
-
-		// Create feature
-		$scope.newFeature = function() {
-			UI.createConfirm("Feature", {}, [
-				{name: "Feature Name", field: "name"}
-			], function(entity) {
-				if(entity.name && $.map($scope.features, function(feature, name) {
-						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
-					}).length) {
-					return "Feature name conflict!";
-				}
-			}).then(null, null, function(holder) {
-				Entities.updateEntity(
-					"FeatureDescService",
-					{tags: {feature: holder.entity.name}},
-					{timestamp: false}
-				)._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// Delete feature
-		$scope.deleteFeature = function(feature) {
-			UI.deleteConfirm(feature.tags.feature).then(null, null, function(holder) {
-				Entities.delete("FeatureDescService", {feature: feature.tags.feature})._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// Save feature
-		$scope.saveAll = function() {
-			$scope._pageLock = true;
-			var _list = $.map($scope.features, function(feature) {
-				return feature;
-			});
-			Entities.updateEntity("FeatureDescService", _list, {timestamp: false})._promise.success(function() {
-				location.reload();
-			}).finally(function() {
-				$scope._pageLock = false;
-			});
-		};
-
-		// Watch config update
-		watchEdit($scope, "features");
-	});
-
-	// ======================== Application ========================
-	eagleControllers.controller('configApplicationCtrl', function ($scope, $timeout, PageConfig, Application, Entities, Feature, UI) {
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		$scope._pageLock = false;
-
-		PageConfig
-			.addNavPath("Home", "/")
-			.addNavPath("Application Config");
-
-		// ================ Application ================
-		// Current application
-		$scope.application = Application.list[0];
-		$scope.setApplication = function (application) {
-			$scope.application = application;
-		};
-
-		// Application list
-		$scope.applications = {};
-		$.each(Application.list, function(i, application) {
-			var _application = $scope.applications[application.tags.application] = $.extend({}, application, {features: application.features.slice()}, true);
-			_application.optionalFeatures = $.map(Application.featureList, function(feature) {
-				var featurePlugin = Feature.get(feature.tags.feature);
-				if(featurePlugin.config.global) return null;
-				if(!common.array.find(feature.tags.feature, _application.features)) {
-					return feature.tags.feature;
-				}
-			});
-		});
-
-		// Create application
-		$scope.newApplication = function() {
-			UI.createConfirm("Application", {}, [
-				{name: "Application Name", field: "name"}
-			], function(entity) {
-				if(entity.name && $.map($scope.applications, function(application, name) {
-						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
-					}).length) {
-					return "Application name conflict!";
-				}
-			}).then(null, null, function(holder) {
-				Entities.updateEntity(
-					"ApplicationDescService",
-					{tags: {application: holder.entity.name}},
-					{timestamp: false}
-				)._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// Delete application
-		$scope.deleteApplication = function(application) {
-			UI.deleteConfirm(application.tags.application).then(null, null, function(holder) {
-				Entities.delete("ApplicationDescService", {application: application.tags.application})._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// ================= Function ==================
-		// Configuration check
-		$scope.configCheck = function(config) {
-			if(config && !common.parseJSON(config, false)) {
-				return "Invalid JSON format";
-			}
-		};
-
-		// Feature
-		$scope._feature = "";
-		function highlightFeature(feature) {
-			$scope._feature = feature;
-
-			$timeout(function() {
-				$scope._feature = "";
-			}, 100);
-		}
-
-		$scope.addFeature = function(feature, application) {
-			application.features.push(feature);
-			common.array.remove(feature, application.optionalFeatures);
-			highlightFeature(feature);
-			$scope.changed = true;
-		};
-
-		$scope.removeFeature = function(feature, application) {
-			application.optionalFeatures.push(feature);
-			common.array.remove(feature, application.features);
-			$scope.changed = true;
-		};
-
-		$scope.moveFeature = function(feature, list, offset) {
-			common.array.moveOffset(feature, list, offset);
-			highlightFeature(feature);
-			$scope.changed = true;
-		};
-
-		// Save feature
-		$scope.saveAll = function() {
-			$scope._pageLock = true;
-
-			var _list = $.map($scope.applications, function(application) {
-				return application;
-			});
-			Entities.updateEntity("ApplicationDescService", _list, {timestamp: false})._promise.success(function() {
-				location.reload();
-			}).finally(function() {
-				$scope._pageLock = false;
-			});
-		};
-
-		// Watch config update
-		watchEdit($scope, "applications");
-	});
-
-	// ============================ Site ===========================
-	eagleControllers.controller('configSiteCtrl', function ($scope, $timeout, PageConfig, Site, Application, Entities, UI) {
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		$scope._pageLock = false;
-
-		PageConfig
-			.addNavPath("Home", "/")
-			.addNavPath("Site Config");
-
-		// =================== Site ====================
-		// Current site
-		$scope.site = Site.list[0];
-		$scope.setSite = function (site) {
-			$scope.site = site;
-		};
-
-
-		// Site list
-		$scope.sites = {};
-		$.each(Site.list, function(i, site) {
-			var _site = $scope.sites[site.tags.site] = $.extend({}, site, true);
-			var _applications = [];
-			var _optionalApplications = [];
-
-			Object.defineProperties(_site, {
-				applications: {
-					get: function() {return _applications;}
-				},
-				optionalApplications: {
-					get: function() {return _optionalApplications;}
-				}
-			});
-
-			$.each(Application.list, function(i, application) {
-				var _application = site.applicationList.set[application.tags.application];
-				if(_application && _application.enabled) {
-					_site.applications.push(_application);
-				} else {
-					if(_application) {
-						_site.optionalApplications.push(_application);
-					} else {
-						_site.optionalApplications.push({
-							prefix: "eagleSiteApplication",
-							config: "",
-							enabled: false,
-							tags: {
-								application: application.tags.application,
-								site: site.tags.site
-							}
-						});
-					}
-				}
-			});
-		});
-
-		// Create site
-		$scope.newSite = function() {
-			UI.createConfirm("Site", {}, [
-				{name: "Site Name", field: "name"}
-			], function(entity) {
-				if(entity.name && $.map($scope.sites, function(site, name) {
-						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
-					}).length) {
-					return "Site name conflict!";
-				}
-			}).then(null, null, function(holder) {
-				Entities.updateEntity(
-					"SiteDescService",
-					{enabled: true, tags: {site: holder.entity.name}},
-					{timestamp: false}
-				)._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// Delete site
-		$scope.deleteSite = function(site) {
-			UI.deleteConfirm(site.tags.site).then(null, null, function(holder) {
-				Entities.delete("SiteDescService", {site: site.tags.site})._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		// ================= Function ==================
-		$scope._application = "";
-		function highlightApplication(application) {
-			$scope._application = application;
-
-			$timeout(function() {
-				$scope._application = "";
-			}, 100);
-		}
-
-		$scope.addApplication = function(application, site) {
-			site.applications.push(application);
-			common.array.remove(application, site.optionalApplications);
-			application.enabled = true;
-			highlightApplication(application);
-			$scope.changed = true;
-		};
-
-		$scope.removeApplication = function(application, site) {
-			site.optionalApplications.push(application);
-			common.array.remove(application, site.applications);
-			application.enabled = false;
-			$scope.changed = true;
-		};
-
-		$scope.setApplication = function(application) {
-			var _oriConfig = application.config;
-			UI.updateConfirm("Application", {config: _oriConfig}, [
-				{name: "Configuration", field: "config", type: "blob"}
-			], function(entity) {
-				if(entity.config !== "" && !common.properties.check(entity.config)) {
-					return "Invalid Properties format";
-				}
-			}).then(null, null, function(holder) {
-				application.config = holder.entity.config;
-				holder.closeFunc();
-				if(_oriConfig !== application.config) $scope.changed = true;
-			});
-		};
-
-		// Save feature
-		$scope.saveAll = function() {
-			$scope._pageLock = true;
-
-			var _list = $.map($scope.sites, function(site) {
-				var _clone = $.extend({applications: site.applications.concat(site.optionalApplications)}, site);
-				return _clone;
-			});
-
-			Entities.updateEntity("SiteDescService", _list, {timestamp: false, hook: true})._promise.success(function() {
-				location.reload();
-			}).finally(function() {
-				$scope._pageLock = false;
-			});
-		};
-
-		// Watch config update
-		watchEdit($scope, "sites");
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js b/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
deleted file mode 100644
index 5064a1d..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/main.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var eagleControllers = angular.module('eagleControllers', ['ui.bootstrap', 'eagle.components', 'eagle.service']);
-
-	// ===========================================================
-	// =                        Controller                       =
-	// ===========================================================
-	eagleControllers.controller('landingCtrl', function($scope, $wrapState, Site, Application, PageConfig, FeaturePageConfig, Feature) {
-		var _app = Application.current();
-
-		PageConfig.pageTitle = _app ? _app.displayName : 'OPS';
-		PageConfig.pageSubTitle = Site.current().tags.site;
-
-		$scope.Application = Application;
-
-		var _navItemList = FeaturePageConfig.pageList;
-		if(_navItemList.length) {
-			console.log("[Landing] Auto redirect.", FeaturePageConfig);
-			var _match = _navItemList[0].url.match(/#\/([^\/]+)\/([^\/]+)/);
-			Feature.go(_match[1], _match[2]);
-		}
-	});
-})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/srv/applicationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/srv/applicationSrv.js b/eagle-webservice/src/main/webapp/app/public/js/srv/applicationSrv.js
deleted file mode 100644
index 187adb4..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/srv/applicationSrv.js
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var serviceModule = angular.module('eagle.service');
-	var eagleApp = angular.module('eagleApp');
-
-	serviceModule.service('Application', function($q, $location, $wrapState, Entities) {
-		var Application = {};
-		var _current;
-		var _featureCache = {};// After loading feature will be in cache. Which will not load twice.
-		var _deferred;
-
-		Application.list = [];
-		Application.list.set = {};
-		Application.featureList = [];
-		Application.featureList.set = {};
-
-		// Set current application
-		Application.current = function(app, reload) {
-			if(arguments.length && _current !== app) {
-				var _prev = _current;
-				_current = app;
-
-				if(sessionStorage && _current) {
-					sessionStorage.setItem("application", _current.tags.application);
-				}
-
-				if(_prev && reload !== false) {
-					console.log("[Application] Switch. Redirect to landing page.");
-					$wrapState.go('landing', true);
-				}
-			}
-			return _current;
-		};
-		Application.find = function(appName) {
-			return common.array.find(appName, Application.list, "tags.application");
-		};
-
-		Application.reload = function() {
-			_deferred = $q.defer();
-
-			if(Application.list && Application.list._promise) Application.list._promise.abort();
-			if(Application.featureList && Application.featureList._promise) Application.featureList._promise.abort();
-
-			Application.list = Entities.queryEntities("ApplicationDescService", '');
-			Application.list.set = {};
-			Application.featureList = Entities.queryEntities("FeatureDescService", '');
-			Application.featureList.set = {};
-
-			Application.featureList._promise.then(function() {
-				var _promiseList;
-				// Load feature script
-				_promiseList = $.map(Application.featureList, function(feature) {
-					var _ajax_deferred, _script;
-					if(_featureCache[feature.tags.feature]) return;
-
-					_featureCache[feature.tags.feature] = true;
-					_ajax_deferred = $q.defer();
-					_script = document.createElement('script');
-					_script.type = 'text/javascript';
-					_script.src = "public/feature/" + feature.tags.feature + "/controller.js?_=" + eagleApp._TRS();
-					document.head.appendChild(_script);
-					_script.onload = function() {
-						feature._loaded = true;
-						_ajax_deferred.resolve();
-					};
-					_script.onerror = function() {
-						feature._loaded = false;
-						_featureCache[feature.tags.feature] = false;
-						_ajax_deferred.reject();
-					};
-					return _ajax_deferred.promise;
-				});
-
-				// Merge application & feature
-				Application.list._promise.then(function() {
-					// Fill feature set
-					$.each(Application.featureList, function(i, feature) {
-						Application.featureList.set[feature.tags.feature] = feature;
-					});
-
-					// Fill application set
-					$.each(Application.list, function(i, application) {
-						Application.list.set[application.tags.application] = application;
-						application.features = application.features || [];
-						var _configObj = common.parseJSON(application.config, {});
-						var _appFeatureList = $.map(application.features, function(featureName) {
-							var _feature = Application.featureList.set[featureName];
-							if(!_feature) {
-								console.warn("[Application] Feature not mapping:", application.tags.application, "-", featureName);
-							} else {
-								return _feature;
-							}
-						});
-
-						// Find feature
-						_appFeatureList.find = function(featureName) {
-							return common.array.find(featureName, _appFeatureList, "tags.feature");
-						};
-
-						Object.defineProperties(application, {
-							featureList: {
-								get: function () {
-									return _appFeatureList;
-								}
-							},
-							// Get format group name. Will mark as 'Others' if no group defined
-							group: {
-								get: function () {
-									return this.groupName || "Others";
-								}
-							},
-							configObj: {
-								get: function() {
-									return _configObj;
-								}
-							},
-							displayName: {
-								get: function() {
-									return this.alias || this.tags.application;
-								}
-							}
-						});
-					});
-
-					// Set current application
-					if(!Application.current() && sessionStorage && Application.find(sessionStorage.getItem("application"))) {
-						Application.current(Application.find(sessionStorage.getItem("application")));
-					}
-				});
-
-				// Process all promise
-				$q.all(_promiseList.concat(Application.list._promise)).finally(function() {
-					_deferred.resolve(Application);
-				});
-			}, function() {
-				_deferred.reject(Application);
-			});
-
-			return _deferred.promise;
-		};
-
-		Application._promise = function() {
-			if(!_deferred) {
-				Application.reload();
-			}
-			return _deferred.promise;
-		};
-
-		return Application;
-	});
-})();
\ No newline at end of file


[07/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..f5f41c1
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/app.time.js
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+// Time Zone
+(function() {
+	"use strict";
+
+	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");
+	};
+
+	// Force convert date
+	var _toDate = moment.fn.toDate;
+	moment.fn.toDate = function(ignoreTimeZone) {
+		if(!ignoreTimeZone) return _toDate.bind(this)();
+		return new Date(
+			this.year(),
+			this.month(),
+			this.date(),
+			this.hour(),
+			this.minute(),
+			this.second(),
+			this.millisecond()
+		);
+	};
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..ca494d3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/app.ui.js
@@ -0,0 +1,76 @@
+/*
+ * 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);
+		}
+	};
+
+	// Modal
+	var _modal = $.fn.modal;
+	$.fn.modal = function() {
+		setTimeout(function() {
+			$(this).find("input[type='text'], textarea").filter(':not([readonly]):enabled:visible:first').focus();
+		}.bind(this), 500);
+		_modal.apply(this, arguments);
+		return this;
+	};
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..4c5e82f
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/common.js
@@ -0,0 +1,304 @@
+/*
+ * 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 || typeof path !== "string" || 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 {
+		str = (str + "").trim();
+		if(Number(str).toString() === str) throw "Number format";
+		return JSON.parse(str);
+	} catch(err) {
+		if(defaultVal === undefined) {
+			console.warn("Can't parse JSON: " + str);
+		}
+	}
+	return defaultVal === undefined ? null : defaultVal;
+};
+
+common.stringify = function(json) {
+	return JSON.stringify(json, function(key, value) {
+		if(/^(_|\$)/.test(key)) return undefined;
+		return value;
+	});
+};
+
+common.isEmpty = function(val) {
+	if($.isArray(val)) {
+		return val.length === 0;
+	} else {
+		return val === null || val === undefined;
+	}
+};
+
+common.extend = function(target, origin) {
+	$.each(origin, function(key, value) {
+		if(/^(_|\$)/.test(key)) return;
+
+		target[key] = value;
+	});
+	return target;
+};
+
+// ====================== 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) {
+	case 'date':
+		return val.format("YYYY-MM-DD");
+	case 'time':
+		return val.format("HH:mm:ss");
+	case 'datetime':
+		return val.format("YYYY-MM-DD HH:mm:ss");
+	case 'mixed':
+		return val.format("YYYY-MM-DD HH:mm");
+	default:
+		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
+	}
+};
+
+// ===================== Property =====================
+common.properties = {};
+
+common.properties.parse = function (str, defaultValue) {
+	var regex = /\s*([\w\.]+)\s*=\s*(.*?)\s*([\r\n]+|$)/g;
+	var match, props = {};
+	var hasValue = false;
+	while((match = regex.exec(str)) !== null) {
+		props[match[1]] = match[2];
+		hasValue = true;
+	}
+	props = hasValue ? props : defaultValue;
+	props.getValueByPath = function (path) {
+		if(props[path] !== undefined) return props[path];
+		var subProps = {};
+		var prefixPath = path + ".";
+		$.each(props, function (key, value) {
+			if(typeof value === "string" && key.indexOf(prefixPath) === 0) {
+				subProps[key.replace(prefixPath, "")] = value;
+			}
+		});
+		return subProps;
+	};
+
+	return props;
+};
+
+common.properties.check = function (str) {
+	var pass = true;
+	var regex = /^\s*[\w\.]+\s*=(.*)$/;
+	$.each((str || "").trim().split(/[\r\n\s]+/g), function (i, line) {
+		if(!regex.test(line)) {
+			pass = false;
+			return false;
+		}
+	});
+	return pass;
+};
+
+// ====================== 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, caseSensitive) {
+	path = path || "";
+	val = caseSensitive === false ? (val || "").toUpperCase() : val;
+
+	var _list = $.grep(list, function(unit) {
+		if(caseSensitive === false) {
+			return val === (common.getValueByPath(unit, path) || "").toUpperCase();
+		} else {
+			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;
+		}
+	}
+};
+
+common.array.insert = function(val, list, index) {
+	list.splice(index, 0, val);
+};
+
+common.array.moveOffset = function(item, list, offset) {
+	var _index = $.inArray(item, list);
+	var _tgtPos = _index + offset;
+	if(_tgtPos < 0 || _tgtPos >= list.length) return;
+
+	common.array.remove(item, list);
+	common.array.insert(item, list, _index + offset);
+};
+
+// ======================= Map ========================
+common.map = {};
+
+common.map.toArray = function(map) {
+	return $.map(map, function(unit) {
+		return unit;
+	});
+};
+
+// ======================= Math =======================
+common.math = {};
+
+common.math.distance = function(x1,y1,x2,y2) {
+	var a = x1 - x2;
+	var b = y1 - y2;
+	return Math.sqrt(a * a + b * b);
+};

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..c2bf23b
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/charts/line3d.js
@@ -0,0 +1,348 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('line3dChart', function($compile) {
+	'use strict';
+
+	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() {
+	'use strict';
+
+	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) {
+						var i, _unit;
+						for(i = _step ; i < bound.max + _step ; i += _step) {
+							_unit = [0,0,0];
+							_unit[dimension] = i;
+							_axisPoint(_unit, dimension, i);
+						}
+						for(i = -_step ; i > bound.min - _step ; i -= _step) {
+							_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/afb89794/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..a8d78db
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/file.js
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('file', function($compile) {
+	'use strict';
+
+	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/afb89794/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..a0d9f9f
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/main.js
@@ -0,0 +1,19 @@
+/*
+ * 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 eagleComponents = angular.module('eagle.components', []);

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js b/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
new file mode 100644
index 0000000..8687c78
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/nvd3.js
@@ -0,0 +1,418 @@
+/*
+ * 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.
+ */
+
+eagleComponents.service('nvd3', function() {
+	var nvd3 = {
+		charts: [],
+		colors: [
+			"#7CB5EC", "#F7A35C", "#90EE7E", "#7798BF", "#AAEEEE"
+		]
+	};
+
+	// ============================================
+	// =              Format Convert              =
+	// ============================================
+
+	/***
+	 * Format: [series:{key:name, value: [{x, y}]}]
+	 */
+
+	nvd3.convert = {};
+	nvd3.convert.eagle = function(seriesList) {
+		return $.map(seriesList, function(series) {
+			var seriesObj = $.isArray(series) ? {values: series} : series;
+			if(!seriesObj.key) seriesObj.key = "value";
+			return seriesObj;
+		});
+	};
+
+	nvd3.convert.druid = function(seriesList) {
+		var _seriesList = [];
+
+		$.each(seriesList, function(i, series) {
+			if(!series.length) return;
+
+			// Fetch keys
+			var _measure = series[0];
+			var _keys = $.map(_measure.event, function(value, key) {
+				return key !== "metric" ? key : null;
+			});
+
+			// Parse series
+			_seriesList.push.apply(_seriesList, $.map(_keys, function(key) {
+				return {
+					key: key,
+					values: $.map(series, function(unit) {
+						return {
+							x: new moment(unit.timestamp).valueOf(),
+							y: unit.event[key]
+						};
+					})
+				};
+			}));
+		});
+
+		return _seriesList;
+	};
+
+	// ============================================
+	// =                    UI                    =
+	// ============================================
+	// Resize with refresh
+	function chartResize() {
+		$.each(nvd3.charts, function(i, chart) {
+			if(chart) chart.nvd3Update();
+		});
+	}
+	$(window).on("resize.components.nvd3", chartResize);
+	$("body").on("collapsed.pushMenu expanded.pushMenu", function() {
+		setTimeout(chartResize, 300);
+	});
+
+	return nvd3;
+});
+
+/**
+ * config:
+ * 		chart:			Defined chart type: line, column, area
+ * 		xTitle:			Defined x axis title.
+ * 		yTitle:			Defined y axis title.
+ * 		xType:			Defined x axis label type: number, decimal, time
+ * 		yType:			Defined y axis label type
+ * 		yMin:			Defined minimum of y axis
+ * 		yMax:			Defined maximum of y axis
+ * 		displayType:	Defined the chart display type. Each chart has own type.
+ */
+eagleComponents.directive('nvd3', function(nvd3) {
+	'use strict';
+
+	return {
+		restrict: 'AE',
+		scope: {
+			nvd3: "=",
+			title: "@?title",				// title
+			chart: "@?chart",				// Same as config.chart
+			config: "=?config",
+			watching: "@?watching",			// Default watching data(nvd3) only. true will also watching chart & config. false do not watching.
+
+			holder: "=?holder"				// Container for holder to call the chart function
+		},
+		controller: function($scope, $element, $attrs, $timeout) {
+			var _config, _chartType;
+			var _chart;
+			var _chartCntr;
+			var _holder, _holder_updateTimes;
+
+			// Destroy
+			function destroy() {
+				var _index = $.inArray(_chart, nvd3.charts);
+				if(!_chartCntr) return _index;
+
+				// Clean events
+				d3.select(_chartCntr)
+					.on("touchmove",null)
+					.on("mousemove",null, true)
+					.on("mouseout" ,null,true)
+					.on("dblclick" ,null)
+					.on("click", null);
+
+				// Clean elements
+				d3.select(_chartCntr).selectAll("*").remove();
+				$element.find(".nvtooltip").remove();
+				$(_chartCntr).remove();
+
+				// Clean chart in nvd3 pool
+				nvd3.charts[_index] = null;
+				_chart = null;
+
+				return _index;
+			}
+
+			// Setup chart environment. Will clean old chart and build new chart if recall.
+			function initChart() {
+				// Clean up if already have chart
+				var _preIndex = destroy();
+
+				// Initialize
+				_config = $.extend({}, $scope.config);
+				_chartType = $scope.chart || _config.chart;
+				_chartCntr = $(document.createElementNS("http://www.w3.org/2000/svg", "svg"))
+					.css("min-height", 50)
+					.attr("_rnd", Math.random())
+					.appendTo($element)[0];
+
+				// Size
+				if(_config.height) {
+					$(_chartCntr).css("height", _config.height);
+				}
+
+				switch(_chartType) {
+					case "line":
+						_chart = nv.models.lineChart()
+							.useInteractiveGuideline(true)
+							.showLegend(true)
+							.showYAxis(true)
+							.showXAxis(true)
+							.options({
+								duration: 350
+							});
+						break;
+					case "column":
+						_chart = nv.models.multiBarChart()
+							.groupSpacing(0.1)
+							.options({
+								duration: 350
+							});
+						break;
+					case "area":
+						_chart = nv.models.stackedAreaChart()
+							.useInteractiveGuideline(true)
+							.showLegend(true)
+							.showYAxis(true)
+							.showXAxis(true)
+							.options({
+								duration: 350
+							});
+						break;
+					case "pie":
+						_chart = nv.models.dimensionalPieChart()
+							.x(function(d) { return d.key; })
+							.y(function(d) { return d.values[d.values.length - 1].y; });
+						break;
+					default :
+						throw "Type not defined: " + _chartType;
+				}
+
+				// nvd3 display Type
+				// TODO: support type define
+
+				// Define title
+				if(_chartType !== 'pie') {
+					if(_config.xTitle) _chart.xAxis.axisLabel(_config.xTitle);
+					if(_config.yTitle) _chart.yAxis.axisLabel(_config.yTitle);
+				}
+
+				// Define label type
+				var _tickMultiFormat = d3.time.format.multi([
+					["%-I:%M%p", function(d) { return d.getMinutes(); }],
+					["%-I%p", function(d) { return d.getHours(); }],
+					["%b %-d", function(d) { return d.getDate() != 1; }],
+					["%b %-d", function(d) { return d.getMonth(); }],
+					["%Y", function() { return true; }]
+				]);
+
+				function _defineLabelType(axis, type) {
+					if(!_chart) return;
+
+					var _axis = _chart[axis + "Axis"];
+					if(!_axis) return;
+
+					switch(type) {
+						case "decimal":
+						case "decimals":
+							_axis.tickFormat(d3.format('.02f'));
+							break;
+						case "text":
+							if(axis === "x") {
+								_chart.rotateLabels(10);
+								_chart.reduceXTicks(false).staggerLabels(true);
+							}
+							_axis.tickFormat(function(d) {
+								return d;
+							});
+							break;
+						case "time":
+							if(_chartType !== 'column') {
+								_chart[axis + "Scale"](d3.time.scale());
+								(function () {
+									var measureSeries = null;
+									$.each($scope.nvd3 || [], function(i, series) {
+										var _len = (series.values || []).length;
+										if(_len === 0) return;
+										if(measureSeries === null || measureSeries.values.length < _len) measureSeries = series;
+									});
+
+									var width = $element.width() - 35;// Use default nvd3 margin. Hard code.
+									if(!measureSeries || width <= 0) return;
+									var count = Math.floor(width / 80);
+									var countDes = Math.floor(measureSeries.values.length / count);
+									var tickValues = [];
+									for(var loc = 0 ; loc < measureSeries.values.length ; loc += countDes) {
+										tickValues.push(measureSeries.values[loc].x);
+									}
+									_chart[axis + "Axis"].tickValues(tickValues);
+								})();
+							}
+							_axis.tickFormat(function(d) {
+								return _tickMultiFormat(app.time.offset(d).toDate(true));
+							});
+							break;
+						case "number":
+						/* falls through */
+						default:
+							_axis.tickFormat(d3.format(',r'));
+					}
+				}
+
+				if(_chartType !== 'pie') {
+					_defineLabelType("x", _config.xType || "number");
+					_defineLabelType("y", _config.yType || "decimal");
+				}
+
+				// Global chart list update
+				if(_preIndex === -1) {
+					nvd3.charts.push(_chart);
+				} else {
+					nvd3.charts[_preIndex] = _chart;
+				}
+
+				// Use customize update function to update the view
+				_chart.nvd3Update = function() {
+					if(_config.xType === "time") _defineLabelType("x", _config.xType);
+					_chart.update();
+				};
+
+				updateData();
+			}
+
+			// Update chart data
+			function updateData() {
+				var _min = null, _max = null;
+
+				// Copy series to prevent Angular loop watching
+				var _data = $.map($scope.nvd3 || [], function(series, i) {
+					var _series = $.extend(true, {}, series);
+					_series.color = _series.color || nvd3.colors[i % nvd3.colors.length];
+					return _series;
+				});
+
+				// Chart Y value
+				if(($scope.chart || _config.chart) !== "pie") {
+					$.each(_data, function(i, series) {
+						$.each(series.values, function(j, unit) {
+							if(_min === null || unit.y < _min) _min = unit.y;
+							if(_max === null || unit.y > _max) _max = unit.y;
+						});
+					});
+
+					if(_min === 0 && _max === 0) {
+						_chart.forceY([0, 10]);
+					} else if(_config.yMin !== undefined || _config.yMax !== undefined) {
+						_chart.forceY([_config.yMin, _config.yMax]);
+					} else {
+						_chart.forceY([]);
+					}
+				}
+
+				// Update data
+				d3.select(_chartCntr)						//Select the <svg> element you want to render the chart in.
+					.datum(_data)							//Populate the <svg> element with chart data...
+					.call(_chart);							//Finally, render the chart!
+
+				setTimeout(_chart.nvd3Update, 10);
+			}
+
+			// ================================================================
+			// =                           Watching                           =
+			// ================================================================
+			// Ignore initial checking
+			$timeout(function() {
+				if ($scope.watching !== "false") {
+					$scope.$watch(function() {
+						if(!$scope.nvd3) return [];
+
+						var _hashList = $.map($scope.nvd3, function(series) {
+							if(!series.values) return 0;
+							var unit = {
+								x: 0,
+								y: 0
+							};
+
+							$.each(series.values, function(j, item) {
+								unit.x += item.x;
+								unit.y += item.y;
+							});
+
+							return unit;
+						});
+
+						return _hashList;
+					}, function() {
+						updateData();
+					}, true);
+
+					// All watching mode
+					if ($scope.watching === "true") {
+						$scope.$watch("[chart, config]", function(newValue, oldValue) {
+							if(angular.equals(newValue, oldValue)) return;
+							initChart();
+						}, true);
+					}
+				}
+			});
+
+			// Holder inject
+			_holder_updateTimes = 0;
+			_holder = {
+				element: $element,
+				refresh: function() {
+					setTimeout(function() {
+						updateData();
+					}, 0);
+				},
+				refreshAll: function() {
+					setTimeout(function() {
+						initChart();
+					}, 0);
+				}
+			};
+
+			Object.defineProperty(_holder, 'chart', {
+				get: function() {return _chart;}
+			});
+
+			$scope.$watch("holder", function() {
+				// Holder times update
+				setTimeout(function() {
+					_holder_updateTimes = 0;
+				}, 0);
+				_holder_updateTimes += 1;
+				if(_holder_updateTimes > 100) throw "Holder conflict";
+
+				$scope.holder = _holder;
+			});
+
+			// ================================================================
+			// =                           Start Up                           =
+			// ================================================================
+			initChart();
+
+			// ================================================================
+			// =                           Clean Up                           =
+			// ================================================================
+			$scope.$on('$destroy', function() {
+				destroy();
+			});
+		},
+		template :
+		'<div>' +
+			'<h3 title="{{title || config.title}}">{{title || config.title}}</h3>' +
+		'</div>',
+		replace: true
+	};
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..bdcbca4
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/sortTable.js
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('sorttable', function($compile) {
+	'use strict';
+
+	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/afb89794/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js b/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
new file mode 100644
index 0000000..c98c732
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/sortable.js
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('uieSortable', function($rootScope) {
+	'use strict';
+
+	var COLLECTION_MATCH = /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/;
+
+	var _move = false;
+	var _selectElement;
+	var _overElement;
+	var _mockElement;
+	var _offsetX, _offsetY;
+	var _mouseDownPageX, _mouseDownPageY;
+
+	function doMock(element, event) {
+		var _offset = element.offset();
+		if(_mockElement) _mockElement.remove();
+
+		// Create mock element
+		_mockElement = element.clone(false).appendTo("body");
+		_mockElement.addClass("sortable-mock-element");
+		_mockElement.css({
+			display: "block",
+			position: "absolute",
+			"pointer-events": "none",
+			"z-index": 10001,
+			padding: element.css("padding"),
+			margin: element.css("margin")
+		});
+		_mockElement.width(element.width());
+		_mockElement.height(element.height());
+
+		_mockElement.offset(_offset);
+		_offsetX = event.pageX - _offset.left;
+		_offsetY = event.pageY - _offset.top;
+	}
+
+	$(window).on("mousemove", function(event) {
+		if(!_move) return;
+		event.preventDefault();
+
+		_mockElement.offset({
+			left: event.pageX - _offsetX,
+			top: event.pageY - _offsetY
+		});
+	});
+
+	$(window).on("mouseup", function() {
+		if(!_move) {
+			_overElement = null;
+			_selectElement = null;
+			_mockElement = null;
+			return;
+		}
+		_move = false;
+
+		if(_overElement) {
+			_overElement.removeClass("sortable-enter");
+
+			if(_overElement[0] !== _selectElement[0]) {
+				// Process switch
+				var _oriHolder = _selectElement.holder;
+				var _tgtHolder = _overElement.holder;
+				var _oriSortableScope = _oriHolder.scope;
+				var _tgtSortableScope = _tgtHolder.scope;
+				var _oriScope = angular.element(_selectElement).scope();
+				var _tgtScope = angular.element(_overElement).scope();
+
+				var _oriRepeat = _selectElement.closest("[ng-repeat]").attr("ng-repeat");
+				var _tgtRepeat = _overElement.closest("[ng-repeat]").attr("ng-repeat");
+				var _oriMatch = _oriRepeat.match(COLLECTION_MATCH)[2];
+				var _tgtMatch = _tgtRepeat.match(COLLECTION_MATCH)[2];
+				var _oriCollection = _oriScope.$parent.$eval(_oriMatch);
+				var _tgtCollection = _tgtScope.$parent.$eval(_tgtMatch);
+				var _oriIndex = $.inArray(_oriCollection[_oriScope.$index], _oriSortableScope.ngModel);
+				var _tgtIndex = $.inArray(_tgtCollection[_tgtScope.$index], _tgtSortableScope.ngModel);
+
+				var _oriUnit = _oriSortableScope.ngModel[_oriIndex];
+				var _tgtUnit = _tgtSortableScope.ngModel[_tgtIndex];
+				_oriSortableScope.ngModel[_oriIndex] = _tgtUnit;
+				_tgtSortableScope.ngModel[_tgtIndex] = _oriUnit;
+
+				// Trigger event
+				_oriHolder.change(_oriUnit, _tgtUnit);
+				if (_oriHolder !== _tgtHolder) _tgtHolder.change(_oriUnit, _tgtUnit);
+
+				$rootScope.$apply();
+			}
+		}
+
+		if(_mockElement) _mockElement.remove();
+
+		_overElement = null;
+		_selectElement = null;
+		_mockElement = null;
+	});
+
+	return {
+		require: 'ngModel',
+		restrict : 'AE',
+		scope: {
+			ngModel: "=",
+			sortableEnabled: "=?sortableEnabled",
+			sortableUpdateFunc: "=?sortableUpdateFunc"
+		},
+		link: function($scope, $element, $attrs, $ctrl) {
+			var _holder = {
+				scope: $scope,
+				change: function(source, target) {
+					if($scope.sortableUpdateFunc) $scope.sortableUpdateFunc(source, target);
+				}
+			};
+
+			$element.on("mousedown", ">", function(event) {
+				if($scope.sortableEnabled === false) return;
+
+				_selectElement = $(this);
+				_selectElement.holder = _holder;
+
+				_mouseDownPageX = event.pageX;
+				_mouseDownPageY = event.pageY;
+
+				event.preventDefault();
+			});
+
+			$element.on("mousemove", ">", function(event) {
+				if(_selectElement && !_move && common.math.distance(_mouseDownPageX, _mouseDownPageY, event.pageX, event.pageY) > 10) {
+					_move = true;
+					_overElement = _selectElement;
+					_overElement.addClass("sortable-enter");
+
+					doMock(_selectElement, event);
+				}
+			});
+
+			$element.on("mouseenter", ">", function() {
+				if(!_move) return;
+				_overElement = $(this);
+				_overElement.holder = _holder;
+				_overElement.addClass("sortable-enter");
+			});
+			$element.on("mouseleave", ">", function() {
+				if(!_move) return;
+				$(this).removeClass("sortable-enter");
+				_overElement = null;
+			});
+		},
+		replace: false
+	};
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..21c4a4a
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/components/tabs.js
@@ -0,0 +1,247 @@
+/*
+ * 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.
+ */
+
+eagleComponents.directive('tabs', function() {
+	'use strict';
+
+	return {
+		restrict: 'AE',
+		transclude: {
+			'header': '?header',
+			'pane': 'pane',
+			'footer': '?footer'
+		},
+		scope : {
+			title: "@?title",
+			icon: "@",
+			selected: "@?selected",
+			holder: "=?holder",
+			sortableModel: "=?sortableModel",
+
+			menuList: "=?menu"
+		},
+
+		controller: function($scope, $element, $attrs, $timeout) {
+			var transDuration = $.fn.tab.Constructor.TRANSITION_DURATION;
+			var transTimer = null;
+			var _holder, _holder_updateTimes;
+
+			var $header, $footer;
+
+			$scope.paneList = [];
+			$scope.selectedPane = null;
+			$scope.inPane = null;
+			$scope.activePane = null;
+
+			// ================== Function ==================
+			$scope.getPaneList = function() {
+				return !$scope.title ? $scope.paneList : $scope.paneList.slice().reverse();
+			};
+
+			$scope.setSelect = function(pane) {
+				if(typeof pane === "string") {
+					pane = common.array.find(pane, $scope.paneList, "title");
+				} else if(typeof pane === "number") {
+					pane = (pane + $scope.paneList.length) % $scope.paneList.length;
+					pane = $scope.paneList[pane];
+				}
+
+				$scope.activePane = $scope.selectedPane || pane;
+				$scope.selectedPane = pane;
+				$scope.inPane = null;
+
+				if(transTimer) $timeout.cancel(transTimer);
+				transTimer = $timeout(function() {
+					$scope.activePane = $scope.selectedPane;
+					$scope.inPane = $scope.selectedPane;
+				}, transDuration);
+			};
+
+			$scope.getMenuList = function() {
+				if($scope.selectedPane && $scope.selectedPane.menuList) {
+					return $scope.selectedPane.menuList;
+				}
+				return $scope.menuList;
+			};
+
+			$scope.tabSwitchUpdate = function (src, tgt) {
+				var _srcIndex = $.inArray(src.data, $scope.sortableModel);
+				var _tgtIndex = $.inArray(tgt.data, $scope.sortableModel);
+
+				if(_srcIndex !== -1 && _tgtIndex !== -1) {
+					$scope.sortableModel[_srcIndex] = tgt.data;
+					$scope.sortableModel[_tgtIndex] = src.data;
+				}
+			};
+
+			// =================== Linker ===================
+			function _linkerProperties(pane) {
+				Object.defineProperties(pane, {
+					selected: {
+						get: function () {
+							return $scope.selectedPane === this;
+						}
+					},
+					active: {
+						get: function () {
+							return $scope.activePane === this;
+						}
+					},
+					in: {
+						get: function () {
+							return $scope.inPane === this;
+						}
+					}
+				});
+			}
+
+			this.addPane = function(pane) {
+				$scope.paneList.push(pane);
+
+				// Register properties
+				_linkerProperties(pane);
+
+				// Update select pane
+				if(pane.title === $scope.selected || !$scope.selectedPane) {
+					$scope.setSelect(pane);
+				}
+			};
+
+			this.deletePane = function(pane) {
+				common.array.remove(pane, $scope.paneList);
+
+				if($scope.selectedPane === pane) {
+					$scope.selectedPane = $scope.activePane = $scope.inPane = $scope.paneList[0];
+				}
+			};
+
+			// ===================== UI =====================
+			$header = $element.find("> .nav-tabs-custom > .box-body");
+			$footer = $element.find("> .nav-tabs-custom > .box-footer");
+
+			$scope.hasHeader = function() {
+				return !!$header.children().length;
+			};
+			$scope.hasFooter = function() {
+				return !!$footer.children().length;
+			};
+
+			// ================= Interface ==================
+			_holder_updateTimes = 0;
+			_holder = {
+				scope: $scope,
+				element: $element,
+				setSelect: $scope.setSelect
+			};
+
+			Object.defineProperty(_holder, 'selectedPane', {
+				get: function() {return $scope.selectedPane;}
+			});
+
+			$scope.$watch("holder", function(newValue, oldValue) {
+				// Holder times update
+				setTimeout(function() {
+					_holder_updateTimes = 0;
+				}, 0);
+				_holder_updateTimes += 1;
+				if(_holder_updateTimes > 100) throw "Holder conflict";
+
+				$scope.holder = _holder;
+			});
+		},
+
+		template :
+			'<div class="nav-tabs-custom">' +
+				// Menu
+				'<div class="box-tools pull-right" ng-if="getMenuList() && getMenuList().length">' +
+					'<div ng-repeat="menu in getMenuList() track by $index" class="inline">' +
+						// Button
+						'<button class="btn btn-box-tool" ng-click="menu.func($event)" ng-if="!menu.list"' +
+							' uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" tooltip-append-to-body="true">' +
+							'<span class="fa fa-{{menu.icon}}"></span>' +
+						'</button>' +
+
+						// Dropdown Group
+						'<div class="btn-group" ng-if="menu.list">' +
+							'<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"' +
+								' uib-tooltip="{{menu.title}}" tooltip-enable="menu.title" tooltip-append-to-body="true">' +
+								'<span class="fa fa-{{menu.icon}}"></span>' +
+							'</button>' +
+							'<ul class="dropdown-menu left" role="menu">' +
+								'<li ng-repeat="item in menu.list track by $index" ng-class="{danger: item.danger, disabled: item.disabled}">' +
+									'<a ng-click="!item.disabled && item.func($event)" ng-class="{strong: item.strong}">' +
+										'<span class="fa fa-{{item.icon}}"></span> {{item.title}}' +
+									'</a>' +
+								'</li>' +
+							'</ul>' +
+						'</div>' +
+					'</div>' +
+				'</div>' +
+
+				'<ul uie-sortable sortable-enabled="!!sortableModel" sortable-update-func="tabSwitchUpdate" ng-model="paneList" class="nav nav-tabs" ng-class="{\'pull-right\': title}">' +
+					// Tabs
+					'<li ng-repeat="pane in getPaneList() track by $index" ng-class="{active: selectedPane === pane}">' +
+						'<a ng-click="setSelect(pane);">{{pane.title}}</a>' +
+					'</li>' +
+
+					// Title
+					'<li class="pull-left header" ng-if="title">' +
+						'<i class="fa fa-{{icon}}" ng-if="icon"></i> {{title}}' +
+					'</li>' +
+
+				'</ul>' +
+				'<div class="box-body" ng-transclude="header" ng-show="paneList.length && hasHeader()"></div>' +
+				'<div class="tab-content" ng-transclude="pane"></div>' +
+				'<div class="box-footer" ng-transclude="footer" ng-show="paneList.length && hasFooter()"></div>' +
+			'</div>'
+	};
+}).directive('pane', function() {
+	'use strict';
+
+	return {
+		require : '^tabs',
+		restrict : 'AE',
+		transclude : true,
+		scope : {
+			title : '@',
+			data: '=?data',
+			menuList: "=?menu"
+		},
+		link : function(scope, element, attrs, tabsController) {
+			tabsController.addPane(scope);
+			scope.$on('$destroy', function() {
+				tabsController.deletePane(scope);
+			});
+		},
+		template : '<div class="tab-pane fade" ng-class="{active: active, in: in}" ng-transclude></div>',
+		replace : true
+	};
+}).directive('footer', function() {
+	'use strict';
+
+	return {
+		require : '^tabs',
+		restrict : 'AE',
+		transclude : true,
+		scope : {},
+		controller: function($scope, $element) {
+		},
+		template : '<div ng-transclude></div>',
+		replace : true
+	};
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/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..dbdb704
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/authController.js
@@ -0,0 +1,91 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+	// =============================================================
+	// =                     User Profile List                     =
+	// =============================================================
+	eagleControllers.controller('authLoginCtrl', function (PageConfig, Site, Authorization, Application, $scope) {
+		PageConfig.hideSidebar = true;
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		PageConfig.hideUser = true;
+
+		$scope.username = "";
+		$scope.password = "";
+		$scope.lock = false;
+		$scope.loginSuccess = false;
+
+		if(localStorage) {
+			$scope.rememberUser = localStorage.getItem("rememberUser") !== "false";
+
+			if($scope.rememberUser) {
+				$scope.username = localStorage.getItem("username");
+				$scope.password = localStorage.getItem("password");
+			}
+		}
+
+		// 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) {
+						// Check user remember
+						localStorage.setItem("rememberUser", $scope.rememberUser);
+						if($scope.rememberUser) {
+							localStorage.setItem("username", $scope.username);
+							localStorage.setItem("password", $scope.password);
+						} else {
+							localStorage.removeItem("username");
+							localStorage.removeItem("password");
+						}
+
+						// Initial environment
+						$scope.loginSuccess = true;
+						console.log("[Login] Login success! Reload data...");
+						Authorization.reload().then(function() {}, function() {console.warn("Site error!");});
+						Application.reload().then(function() {}, function() {console.warn("Site error!");});
+						Site.reload().then(function() {}, function() {console.warn("Site error!");});
+						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/afb89794/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
new file mode 100644
index 0000000..e59198d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/configurationController.js
@@ -0,0 +1,377 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers');
+
+	// =============================================================
+	// =                         Function                          =
+	// =============================================================
+	function watchEdit($scope, key) {
+		$scope.changed = false;
+		setTimeout(function() {
+			var _func = $scope.$watch(key, function(newValue, oldValue) {
+				if(angular.equals(newValue, oldValue)) return;
+				$scope.changed = true;
+				_func();
+			}, true);
+		}, 100);
+	}
+
+	// =============================================================
+	// =                       Configuration                       =
+	// =============================================================
+	// ========================== Feature ==========================
+	eagleControllers.controller('configFeatureCtrl', function ($scope, PageConfig, Application, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		PageConfig
+			.addNavPath("Home", "/")
+			.addNavPath("Feature Config");
+
+		// ================== Feature ==================
+		// Current feature
+		$scope.feature = Application.featureList[0];
+		$scope.setFeature = function (feature) {
+			$scope.feature = feature;
+		};
+
+		// Feature list
+		$scope.features = {};
+		$.each(Application.featureList, function(i, feature) {
+			$scope.features[feature.tags.feature] = $.extend({}, feature, true);
+		});
+
+		// Create feature
+		$scope.newFeature = function() {
+			UI.createConfirm("Feature", {}, [
+				{name: "Feature Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.features, function(feature, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Feature name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"FeatureDescService",
+					{tags: {feature: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete feature
+		$scope.deleteFeature = function(feature) {
+			UI.deleteConfirm(feature.tags.feature).then(null, null, function(holder) {
+				Entities.delete("FeatureDescService", {feature: feature.tags.feature})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+			var _list = $.map($scope.features, function(feature) {
+				return feature;
+			});
+			Entities.updateEntity("FeatureDescService", _list, {timestamp: false})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+
+		// Watch config update
+		watchEdit($scope, "features");
+	});
+
+	// ======================== Application ========================
+	eagleControllers.controller('configApplicationCtrl', function ($scope, $timeout, PageConfig, Application, Entities, Feature, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		PageConfig
+			.addNavPath("Home", "/")
+			.addNavPath("Application Config");
+
+		// ================ Application ================
+		// Current application
+		$scope.application = Application.list[0];
+		$scope.setApplication = function (application) {
+			$scope.application = application;
+		};
+
+		// Application list
+		$scope.applications = {};
+		$.each(Application.list, function(i, application) {
+			var _application = $scope.applications[application.tags.application] = $.extend({}, application, {features: application.features.slice()}, true);
+			_application.optionalFeatures = $.map(Application.featureList, function(feature) {
+				var featurePlugin = Feature.get(feature.tags.feature);
+				if(featurePlugin.config.global) return null;
+				if(!common.array.find(feature.tags.feature, _application.features)) {
+					return feature.tags.feature;
+				}
+			});
+		});
+
+		// Create application
+		$scope.newApplication = function() {
+			UI.createConfirm("Application", {}, [
+				{name: "Application Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.applications, function(application, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Application name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"ApplicationDescService",
+					{tags: {application: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete application
+		$scope.deleteApplication = function(application) {
+			UI.deleteConfirm(application.tags.application).then(null, null, function(holder) {
+				Entities.delete("ApplicationDescService", {application: application.tags.application})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// ================= Function ==================
+		// Configuration check
+		$scope.configCheck = function(config) {
+			if(config && !common.parseJSON(config, false)) {
+				return "Invalid JSON format";
+			}
+		};
+
+		// Feature
+		$scope._feature = "";
+		function highlightFeature(feature) {
+			$scope._feature = feature;
+
+			$timeout(function() {
+				$scope._feature = "";
+			}, 100);
+		}
+
+		$scope.addFeature = function(feature, application) {
+			application.features.push(feature);
+			common.array.remove(feature, application.optionalFeatures);
+			highlightFeature(feature);
+			$scope.changed = true;
+		};
+
+		$scope.removeFeature = function(feature, application) {
+			application.optionalFeatures.push(feature);
+			common.array.remove(feature, application.features);
+			$scope.changed = true;
+		};
+
+		$scope.moveFeature = function(feature, list, offset) {
+			common.array.moveOffset(feature, list, offset);
+			highlightFeature(feature);
+			$scope.changed = true;
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+
+			var _list = $.map($scope.applications, function(application) {
+				return application;
+			});
+			Entities.updateEntity("ApplicationDescService", _list, {timestamp: false})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+
+		// Watch config update
+		watchEdit($scope, "applications");
+	});
+
+	// ============================ Site ===========================
+	eagleControllers.controller('configSiteCtrl', function ($scope, $timeout, PageConfig, Site, Application, Entities, UI) {
+		PageConfig.hideApplication = true;
+		PageConfig.hideSite = true;
+		$scope._pageLock = false;
+
+		PageConfig
+			.addNavPath("Home", "/")
+			.addNavPath("Site Config");
+
+		// =================== Site ====================
+		// Current site
+		$scope.site = Site.list[0];
+		$scope.setSite = function (site) {
+			$scope.site = site;
+		};
+
+
+		// Site list
+		$scope.sites = {};
+		$.each(Site.list, function(i, site) {
+			var _site = $scope.sites[site.tags.site] = $.extend({}, site, true);
+			var _applications = [];
+			var _optionalApplications = [];
+
+			Object.defineProperties(_site, {
+				applications: {
+					get: function() {return _applications;}
+				},
+				optionalApplications: {
+					get: function() {return _optionalApplications;}
+				}
+			});
+
+			$.each(Application.list, function(i, application) {
+				var _application = site.applicationList.set[application.tags.application];
+				if(_application && _application.enabled) {
+					_site.applications.push(_application);
+				} else {
+					if(_application) {
+						_site.optionalApplications.push(_application);
+					} else {
+						_site.optionalApplications.push({
+							prefix: "eagleSiteApplication",
+							config: "",
+							enabled: false,
+							tags: {
+								application: application.tags.application,
+								site: site.tags.site
+							}
+						});
+					}
+				}
+			});
+		});
+
+		// Create site
+		$scope.newSite = function() {
+			UI.createConfirm("Site", {}, [
+				{name: "Site Name", field: "name"}
+			], function(entity) {
+				if(entity.name && $.map($scope.sites, function(site, name) {
+						return name.toUpperCase() === entity.name.toUpperCase() ? true : null;
+					}).length) {
+					return "Site name conflict!";
+				}
+			}).then(null, null, function(holder) {
+				Entities.updateEntity(
+					"SiteDescService",
+					{enabled: true, tags: {site: holder.entity.name}},
+					{timestamp: false}
+				)._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// Delete site
+		$scope.deleteSite = function(site) {
+			UI.deleteConfirm(site.tags.site).then(null, null, function(holder) {
+				Entities.delete("SiteDescService", {site: site.tags.site})._promise.then(function() {
+					holder.closeFunc();
+					location.reload();
+				});
+			});
+		};
+
+		// ================= Function ==================
+		$scope._application = "";
+		function highlightApplication(application) {
+			$scope._application = application;
+
+			$timeout(function() {
+				$scope._application = "";
+			}, 100);
+		}
+
+		$scope.addApplication = function(application, site) {
+			site.applications.push(application);
+			common.array.remove(application, site.optionalApplications);
+			application.enabled = true;
+			highlightApplication(application);
+			$scope.changed = true;
+		};
+
+		$scope.removeApplication = function(application, site) {
+			site.optionalApplications.push(application);
+			common.array.remove(application, site.applications);
+			application.enabled = false;
+			$scope.changed = true;
+		};
+
+		$scope.setApplication = function(application) {
+			var _oriConfig = application.config;
+			UI.updateConfirm("Application", {config: _oriConfig}, [
+				{name: "Configuration", field: "config", type: "blob"}
+			], function(entity) {
+				if(entity.config !== "" && !common.properties.check(entity.config)) {
+					return "Invalid Properties format";
+				}
+			}).then(null, null, function(holder) {
+				application.config = holder.entity.config;
+				holder.closeFunc();
+				if(_oriConfig !== application.config) $scope.changed = true;
+			});
+		};
+
+		// Save feature
+		$scope.saveAll = function() {
+			$scope._pageLock = true;
+
+			var _list = $.map($scope.sites, function(site) {
+				var _clone = $.extend({applications: site.applications.concat(site.optionalApplications)}, site);
+				return _clone;
+			});
+
+			Entities.updateEntity("SiteDescService", _list, {timestamp: false, hook: true})._promise.success(function() {
+				location.reload();
+			}).finally(function() {
+				$scope._pageLock = false;
+			});
+		};
+
+		// Watch config update
+		watchEdit($scope, "sites");
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/ctrl/main.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/ctrl/main.js b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/main.js
new file mode 100644
index 0000000..5064a1d
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/ctrl/main.js
@@ -0,0 +1,42 @@
+/*
+ * 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() {
+	'use strict';
+
+	var eagleControllers = angular.module('eagleControllers', ['ui.bootstrap', 'eagle.components', 'eagle.service']);
+
+	// ===========================================================
+	// =                        Controller                       =
+	// ===========================================================
+	eagleControllers.controller('landingCtrl', function($scope, $wrapState, Site, Application, PageConfig, FeaturePageConfig, Feature) {
+		var _app = Application.current();
+
+		PageConfig.pageTitle = _app ? _app.displayName : 'OPS';
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		$scope.Application = Application;
+
+		var _navItemList = FeaturePageConfig.pageList;
+		if(_navItemList.length) {
+			console.log("[Landing] Auto redirect.", FeaturePageConfig);
+			var _match = _navItemList[0].url.match(/#\/([^\/]+)\/([^\/]+)/);
+			Feature.go(_match[1], _match[2]);
+		}
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/js/srv/applicationSrv.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/js/srv/applicationSrv.js b/eagle-webservice/src/main/webapp/_app/public/js/srv/applicationSrv.js
new file mode 100644
index 0000000..187adb4
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/js/srv/applicationSrv.js
@@ -0,0 +1,170 @@
+/*
+ * 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() {
+	'use strict';
+
+	var serviceModule = angular.module('eagle.service');
+	var eagleApp = angular.module('eagleApp');
+
+	serviceModule.service('Application', function($q, $location, $wrapState, Entities) {
+		var Application = {};
+		var _current;
+		var _featureCache = {};// After loading feature will be in cache. Which will not load twice.
+		var _deferred;
+
+		Application.list = [];
+		Application.list.set = {};
+		Application.featureList = [];
+		Application.featureList.set = {};
+
+		// Set current application
+		Application.current = function(app, reload) {
+			if(arguments.length && _current !== app) {
+				var _prev = _current;
+				_current = app;
+
+				if(sessionStorage && _current) {
+					sessionStorage.setItem("application", _current.tags.application);
+				}
+
+				if(_prev && reload !== false) {
+					console.log("[Application] Switch. Redirect to landing page.");
+					$wrapState.go('landing', true);
+				}
+			}
+			return _current;
+		};
+		Application.find = function(appName) {
+			return common.array.find(appName, Application.list, "tags.application");
+		};
+
+		Application.reload = function() {
+			_deferred = $q.defer();
+
+			if(Application.list && Application.list._promise) Application.list._promise.abort();
+			if(Application.featureList && Application.featureList._promise) Application.featureList._promise.abort();
+
+			Application.list = Entities.queryEntities("ApplicationDescService", '');
+			Application.list.set = {};
+			Application.featureList = Entities.queryEntities("FeatureDescService", '');
+			Application.featureList.set = {};
+
+			Application.featureList._promise.then(function() {
+				var _promiseList;
+				// Load feature script
+				_promiseList = $.map(Application.featureList, function(feature) {
+					var _ajax_deferred, _script;
+					if(_featureCache[feature.tags.feature]) return;
+
+					_featureCache[feature.tags.feature] = true;
+					_ajax_deferred = $q.defer();
+					_script = document.createElement('script');
+					_script.type = 'text/javascript';
+					_script.src = "public/feature/" + feature.tags.feature + "/controller.js?_=" + eagleApp._TRS();
+					document.head.appendChild(_script);
+					_script.onload = function() {
+						feature._loaded = true;
+						_ajax_deferred.resolve();
+					};
+					_script.onerror = function() {
+						feature._loaded = false;
+						_featureCache[feature.tags.feature] = false;
+						_ajax_deferred.reject();
+					};
+					return _ajax_deferred.promise;
+				});
+
+				// Merge application & feature
+				Application.list._promise.then(function() {
+					// Fill feature set
+					$.each(Application.featureList, function(i, feature) {
+						Application.featureList.set[feature.tags.feature] = feature;
+					});
+
+					// Fill application set
+					$.each(Application.list, function(i, application) {
+						Application.list.set[application.tags.application] = application;
+						application.features = application.features || [];
+						var _configObj = common.parseJSON(application.config, {});
+						var _appFeatureList = $.map(application.features, function(featureName) {
+							var _feature = Application.featureList.set[featureName];
+							if(!_feature) {
+								console.warn("[Application] Feature not mapping:", application.tags.application, "-", featureName);
+							} else {
+								return _feature;
+							}
+						});
+
+						// Find feature
+						_appFeatureList.find = function(featureName) {
+							return common.array.find(featureName, _appFeatureList, "tags.feature");
+						};
+
+						Object.defineProperties(application, {
+							featureList: {
+								get: function () {
+									return _appFeatureList;
+								}
+							},
+							// Get format group name. Will mark as 'Others' if no group defined
+							group: {
+								get: function () {
+									return this.groupName || "Others";
+								}
+							},
+							configObj: {
+								get: function() {
+									return _configObj;
+								}
+							},
+							displayName: {
+								get: function() {
+									return this.alias || this.tags.application;
+								}
+							}
+						});
+					});
+
+					// Set current application
+					if(!Application.current() && sessionStorage && Application.find(sessionStorage.getItem("application"))) {
+						Application.current(Application.find(sessionStorage.getItem("application")));
+					}
+				});
+
+				// Process all promise
+				$q.all(_promiseList.concat(Application.list._promise)).finally(function() {
+					_deferred.resolve(Application);
+				});
+			}, function() {
+				_deferred.reject(Application);
+			});
+
+			return _deferred.promise;
+		};
+
+		Application._promise = function() {
+			if(!_deferred) {
+				Application.reload();
+			}
+			return _deferred.promise;
+		};
+
+		return Application;
+	});
+})();
\ No newline at end of file


[05/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/css/main.css
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/css/main.css b/eagle-webservice/src/main/webapp/app/public/css/main.css
deleted file mode 100644
index a7eba4b..0000000
--- a/eagle-webservice/src/main/webapp/app/public/css/main.css
+++ /dev/null
@@ -1,805 +0,0 @@
-@CHARSET "UTF-8";
-/*
- * 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.
- */
-
-/* Frame */
-body.no-sidebar .content-wrapper {
-	margin-left: 0;
-
-	-webkit-transition: none;
-	-moz-transition: none;
-	-o-transition: none;
-	transition: none;
-}
-
-body.no-sidebar .main-footer {
-	margin-left: 0;
-}
-
-/* Navigation */
-.navbar-nav > .user-menu > .dropdown-menu > li.user-header .img-circle {
-	display: inline-block;
-	border: 3px solid;
-	border-color: rgba(255,255,255,0.2);
-	width: 90px;
-	height: 90px;
-	margin-top: 10px;
-}
-
-.navbar-nav > .user-menu > .dropdown-menu > li.user-header .fa {
-	font-size: 60px;
-	color: rgba(255,255,255,0.8);
-	margin-top: 10px;
-}
-
-	/* Common */
-a {
-	cursor: pointer;
-}
-
-/* Table */
-.table.table-sm>tbody>tr>td,
-.table.table-sm>tbody>tr>th,
-.table.table-sm>tfoot>tr>td,
-.table.table-sm>tfoot>tr>th,
-.table.table-sm>thead>tr>td,
-.table.table-sm>thead>tr>th{
-	padding: 3px 8px;
-}
-
-.table thead th .fa.fa-sort,
-.table thead th .fa.fa-sort-asc,
-.table thead th .fa.fa-sort-desc {
-	margin-top: 5px;
-	opacity: 0.3;
-	float: right;
-}
-.table thead th:hover .fa.fa-sort,
-.table thead th:hover .fa.fa-sort-asc,
-.table thead th:hover .fa.fa-sort-desc {
-	opacity: 0.8;
-}
-
-.table tr th,
-.table tr td {
-	-webkit-transition: background .5s linear;
-	-o-transition: background .5s linear;
-	transition: background .5s linear;
-}
-
-.sortTable-cntr .pagination {
-	margin-top: 0;
-}
-
-.table th.input-field,
-.table td.input-field {
-	padding: 0;
-	vertical-align: middle;
-}
-
-.table th.input-field > input,
-.table td.input-field > input,
-.table th.input-field > select,
-.table td.input-field > select {
-	border: none;
-	transition: border-color 0s;
-}
-
-.table th.input-field > input:focus,
-.table td.input-field > input:focus,
-.table th.input-field > select:focus,
-.table td.input-field > select:focus {
-	box-shadow: inset 1px 1px 0px #3c8dbc, inset -1px -1px 0px #3c8dbc;
-}
-
-.table th.input-field > input.has-warning,
-.table td.input-field > input.has-warning {
-	box-shadow: inset 1px 1px 0px #f39c12, inset -1px -1px 0px #f39c12;
-}
-
-/* Box */
-.small-box > a.inner {
-	color: #FFF;
-	display: block;
-}
-
-.small-box > a.inner h3 {
-	overflow: hidden;
-	white-space: nowrap;
-	text-overflow: ellipsis;
-	font-size: 32px;
-}
-
-.info-box.bg-gray,
-.info-box a {
-	color: #FFFFFF;
-}
-.info-box a:hover {
-	color: #FFFFFF;
-	text-decoration: underline;
-}
-
-.info-box-content a.config {
-	color: rgba(255,255,255,0.8);
-}
-.info-box-content a.config:hover {
-	color: #FFFFFF;
-}
-
-.info-box-content.box-clickable {
-	box-shadow: 0 0 3px;
-}
-.box-clickable {
-	cursor: pointer;
-}
-
-.info-box-content .info-box-text.text-large {
-	font-size: 26px;
-	margin: 5px 0 10px 0;
-}
-
-/* inline group */
-.inline-group dl,
-.inline-group dl dt,
-.inline-group dl dd {
-	display: inline-block;
-}
-
-.inline-group dl {
-	margin-right: 35px;
-}
-.inline-group dl dt {
-	margin-right: 20px;
-}
-
-.inline-group.form-inline {
-	margin-top: 5px;
-}
-.inline-group dl {
-	margin-right: 25px;
-}
-.inline-group dl dt {
-	margin-right: 5px;
-}
-
-/* Search box */
-.search-box {
-	position: relative;
-	margin-bottom: 15px;
-}
-.search-box input[type="search"] {
-	padding-left: 26px;
-}
-.search-box .fa.fa-search {
-	position: absolute;
-	top: 8px;
-	left: 8px;
-	z-index: 2;
-	pointer-events: none;
-	color: #999;
-}
-
-/* Navigation Tab */
-ul.nav.nav-tabs li .btn {
-	margin-top: 1px;
-}
-
-.modal-body ul.nav.nav-tabs {
-	border-bottom-color: #F4F4F4;
-	margin-bottom: 15px;
-}
-
-.modal-body ul.nav.nav-tabs li {
-	border-top: 3px solid #FFFFFF;
-	margin-right: 3px;
-}
-.modal-body ul.nav.nav-tabs li.active {
-	border-top-color: #3c8dbc;
-}
-
-.modal-body ul.nav.nav-tabs li > a,
-.modal-body ul.nav.nav-tabs li > a:active,
-.modal-body ul.nav.nav-tabs li > a:hover {
-	border: none;
-	border-radius: 0;
-	margin: 0;
-	padding: 6px 15px 8px 15px;
-	color: #444;
-}
-.modal-body ul.nav.nav-tabs li:not(.active) > a:hover {
-	background: rgba(0,0,0,0);
-	color: #999;
-}
-.modal-body ul.nav.nav-tabs li.active > a {
-	border-left: 1px solid #F4F4F4;
-	border-right: 1px solid #F4F4F4;
-}
-
-/* Step Navigation */
-.step-cntr .step {
-	background: #3c8dbc;
-	margin: 0 0 20px 0;
-	color: #FFF;
-	height: 60px;
-	border-radius: 3px;
-	box-shadow: 0 1px 1px rgba(0,0,0,0.1);
-	display: block;
-
-	-webkit-transition: background .15s linear;
-	-o-transition: background .15s linear;
-	transition: background .15s linear;
-}
-.step-cntr .step.active {
-	background: #f39c12;
-}
-
-.step-cntr .step h1,
-.step-cntr .step h2,
-.step-cntr .step p {
-	margin: 0;
-	padding: 0;
-	overflow: hidden;
-	white-space: nowrap;
-	text-overflow: ellipsis;
-}
-
-.step-cntr .step h1 {
-	display: inline-block;
-	font-size: 30px;
-	float: left;
-	border-right: 2px solid rgba(255,255,255,0.2);
-	width: 60px;
-	height: 60px;
-	text-align: center;
-	padding-top: 12px;
-	margin-right: 10px;
-}
-.step-cntr .step h2 {
-	font-size: 18px;
-	padding: 8px 0 5px 0;
-}
-
-/* Panel */
-.panel-group.panel-group-sm .panel .panel-heading {
-	padding: 5px 6px 5px 10px;
-}
-.panel-group.panel-group-sm .panel .panel-heading h4 {
-	font-size: 14px;
-}
-.panel-group.panel-group-sm .panel .panel-heading h4 a {
-	display: block;
-}
-.panel-group.panel-group-sm .panel .panel-heading .pull-right {
-	padding-left: 5px;
-	padding-right: 5px;
-	border-radius: 3px;
-}
-
-/* Drop Down */
-.dropdown-menu > li.danger > a {
-	color: #dd4b39;
-}
-.dropdown-menu > li.danger > a:hover {
-	color: #FFFFFF;
-	background: #dd4b39;
-}
-
-/* Drop Down */
-.dropdown-menu.left {
-	right: 0;
-	left: auto;
-}
-
-.dropdown-submenu{position:relative;}
-.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
-.dropdown-submenu:hover>.dropdown-menu{display:block;}
-.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
-.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
-.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
-
-/* Input Group */
-.input-group .input-group-btn select {
-	width: auto;
-}
-
-/* Form group */
-.form-group .checkbox {
-	display: inline;
-	margin-right: 10px;
-}
-
-.form-group select.has-warning,
-.form-group input.has-warning {
-	border-color: #f39c12;
-	box-shadow: none;
-}
-
-.checkbox.noMargin {
-	margin-top: 0;
-	margin-bottom: 5px;
-}
-
-/* UL */
-ul.path {
-	margin-left: 0;
-}
-
-ul.path li {
-	padding: 0;
-	margin-right: 5px;
-}
-ul.path li a {
-	color: #FFFFFF;
-}
-
-ul.tree {
-	padding: 0 0 0 5px;
-}
-
-ul.tree > li,
-ul.tree > li > ul > li {
-	list-style-type: none;
-}
-
-ul.tree .tree-item .hover {
-	display: none;
-}
-ul.tree .tree-item:hover .hover {
-	display: inline-block;
-}
-
-ul.tree > li > ul {
-	padding: 0 0 0 25px;
-}
-
-ul.tree > li > ul > li.active {
-	background: #F4F4F4;
-}
-
-ul.tree.tree-bordered {
-	border: 1px solid #f4f4f4;
-}
-ul.tree.tree-bordered,
-ul.tree.tree-bordered > li > ul {
-	padding: 0;
-}
-ul.tree.tree-bordered > li:not(:last-child) {
-	border-bottom: 1px solid #f4f4f4;
-}
-ul.tree.tree-bordered > li > ul > li {
-	border-top: 1px solid #f4f4f4;
-}
-ul.tree.tree-bordered > li > span,
-ul.tree.tree-bordered > li > a {
-	display: block;
-	padding: 8px;
-}
-ul.tree.tree-bordered > li > ul > li > span,
-ul.tree.tree-bordered > li > ul > li > a {
-	display: block;
-	padding: 8px 8px 8px 30px;
-}
-
-.product-list-in-box > .item {
-	-webkit-transition: background .5s linear;
-	-o-transition: background .5s linear;
-	transition: background .5s linear;
-}
-.product-list-in-box > .item.ng-animate {
-	transition: 0s;
-}
-.product-list-in-box > .item.active {
-	background: #F5FAFC;
-	-webkit-transition: none;
-	-o-transition: none;
-	transition: none;
-}
-
-.nav.fixed-height,
-.products-list.fixed-height {
-	height: 402px;
-	overflow-y: auto;
-}
-
-.products-list .product-operation {
-	float: left;
-	border: 1px solid #9EC8E0;
-	border-radius: 5px;
-	overflow: hidden;
-}
-
-.products-list .product-operation .fa {
-	display: block;
-	padding: 4px 16px;
-	color: #3c8dbc;
-}
-.products-list .product-operation a.fa:hover {
-	color: #FFFFFF;
-	background: #337ab7;
-}
-
-.products-list .product-operation.single .fa {
-	padding: 12px 12px;
-	font-size: 20px;
-}
-
-.products-list .item .product-info a.fa.fa-times {
-	display: none;
-}
-.products-list .item:hover .product-info a.fa.fa-times {
-	display: block;
-}
-
-/* Label */
-.label.label-default {
-	color: #FFFFFF;
-}
-
-.label.label-sm {
-	padding: .0em .4em .1em;
-}
-
-/* Row */
-.row.narrow {
-	margin-left: -5px;
-	margin-right: -5px;
-	margin-bottom: -10px;
-}
-
-.row.narrow>.col-xs-1, .row.narrow>.col-sm-1, .row.narrow>.col-md-1, .row.narrow>.col-lg-1, .row.narrow>.col-xs-2, .row.narrow>.col-sm-2, .row.narrow>.col-md-2, .row.narrow>.col-lg-2, .row.narrow>.col-xs-3, .row.narrow>.col-sm-3, .row.narrow>.col-md-3, .row.narrow>.col-lg-3, .row.narrow>.col-xs-4, .row.narrow>.col-sm-4, .row.narrow>.col-md-4, .row.narrow>.col-lg-4, .row.narrow>.col-xs-5, .row.narrow>.col-sm-5, .row.narrow>.col-md-5, .row.narrow>.col-lg-5, .row.narrow>.col-xs-6, .row.narrow>.col-sm-6, .row.narrow>.col-md-6, .row.narrow>.col-lg-6, .row.narrow>.col-xs-7, .row.narrow>.col-sm-7, .row.narrow>.col-md-7, .row.narrow>.col-lg-7, .row.narrow>.col-xs-8, .row.narrow>.col-sm-8, .row.narrow>.col-md-8, .row.narrow>.col-lg-8, .row.narrow>.col-xs-9, .row.narrow>.col-sm-9, .row.narrow>.col-md-9, .row.narrow>.col-lg-9, .row.narrow>.col-xs-10, .row.narrow>.col-sm-10, .row.narrow>.col-md-10, .row.narrow>.col-lg-10, .row.narrow>.col-xs-11, .row.narrow>.col-sm-11, .row.narrow>.col-md-11, .
 row.narrow>.col-lg-11, .row.narrow>.col-xs-12, .row.narrow>.col-sm-12, .row.narrow>.col-md-12, .row.narrow>.col-lg-12 {
-	padding-left: 5px;
-	padding-right: 5px;
-}
-
-.row.narrow > [class^="col-"],
-.row.narrow > [class*=" col-"] {
-	margin-bottom: 10px;
-}
-
-/* Chart */
-.sortable-mock-element .nvd3-chart-wrapper {
-	background: #FFFFFF;
-	opacity: 0.8;
-}
-
-.sortable-enter .nvd3-chart-wrapper {
-	border-color: #3c8dbc;
-	pointer-events: none;
-}
-.sortable-enter .nvd3-chart-wrapper .nvtooltip {
-	display: none;
-}
-
-.nvd3-chart-wrapper {
-	position: relative;
-	border: 1px solid rgba(0,0,0,0.1);
-}
-.nvd3-chart-wrapper:hover {
-	//border-color: #F4F4F4;
-}
-
-.nvd3-chart-wrapper .nvd3-chart-config {
-	position: absolute;
-	top: 1px;
-	right: 1px;
-	display: none;
-	border-radius: 0;
-	padding: 0 5px;
-	background: rgba(0,0,0,0.7);
-}
-.nvd3-chart-wrapper:hover .nvd3-chart-config {
-	display: block;
-}
-
-.nvd3-chart-wrapper .nvd3-chart-config a {
-	color: rgba(255,255,255, 0.9);
-	padding: 5px 2px 4px 2px;
-	font-size: 16px;
-}
-.nvd3-chart-wrapper .nvd3-chart-config a:hover {
-	color: #FFFFFF;
-}
-
-.nvd3-chart-cntr {
-	padding: 5px;
-}
-
-.nvd3-chart-cntr > h3 {
-	text-align: center;
-	font-size: 16px;
-	font-weight: bolder;
-	margin: 0;
-	padding: 5px 0;
-
-	overflow:hidden;
-	text-overflow:ellipsis;
-
-}
-
-.nvd3-chart-cntr > svg.nvd3-svg {
-	height: 200px;
-}
-
-.nvd3-chart-cntr.lg > svg.nvd3-svg {
-	height: 400px;
-}
-
-/* Tab */
-body .tab-content>.tab-pane {
-	display: block;
-	height: 0px;
-	overflow: hidden;
-	position: relative;
-}
-body .tab-content>.tab-pane.active {
-	height: auto;
-	overflow-x: visible;
-	overflow-y: visible;
-}
-
-body .modal-body .nav-pills > li > a,
-body .box-body .nav-pills > li > a {
-	padding: 5px 15px;
-	border: none;
-}
-
-body .modal-body .nav-stacked > li {
-	border-bottom: 1px solid #f4f4f4;
-	margin: 0;
-}
-body .modal-body .nav-stacked > li:last-child {
-	border-bottom: none;
-}
-
-body .box-body .nav-tabs-custom {
-	box-shadow: none;
-	margin-bottom: 0;
-}
-body .box-body .nav-tabs-custom > .nav-tabs > li:first-of-type.active > a {
-	border-left-color: #f4f4f4;
-}
-body .box-body .nav-tabs-custom > .nav-tabs > li > a {
-	padding: 8px 15px;
-}
-body .box-body .nav-tabs-custom > .tab-content {
-	padding: 10px 0;
-}
-
-/* Box */
-.box .guideline {
-	margin-top: 0;
-}
-
-.box.inner-box {
-	border: none;
-	box-shadow: none;
-	padding: 5px 10px;
-	margin: 0;
-	border-bottom: 1px solid #f4f4f4;
-	position: relative;
-	border-radius: 0;
-}
-
-.box.inner-box .box-title {
-	margin: 0 5px 5px 0;
-	padding: 0;
-	font-size: 16px;
-	font-weight: bolder;
-	display: inline-block;
-	word-break: break-all;
-}
-
-.box.inner-box .box-tools {
-	position: absolute;
-	top: 0;
-	right: 0;
-}
-
-.box.inner-box:last-child {
-	border-bottom: none;
-}
-
-/* Navigation Tab */
-.nav-tabs-custom {
-	position: relative;
-}
-
-.nav-tabs-custom .box-tools {
-	position: absolute;
-	right: 15px;
-	top: 8px;
-}
-
-.nav-tabs-custom .box-tools .strong {
-	font-weight: bolder;
-}
-
-/* Customize */
-#content {
-	position: relative;
-}
-
-.page-fixed {
-	position: absolute;
-	top: -45px;
-	right: 0;
-}
-
-@media (max-width:991px) {
-	.page-fixed {
-		top: -70px;
-	}
-}
-
-.fixed-right {
-	position: absolute;
-	right: 0;
-	z-index: 3;
-}
-
-.main-header .logo img {
-	height: 34px;
-}
-
-.main-header .navbar-toggle {
-	float: none;
-	border-radius: 0;
-}
-.main-header .navbar-toggle:hover {
-	background: rgba(0, 0, 0, 0.1);
-}
-
-#moduleMenu > ul > li.active > a {
-	border-top: 3px solid rgba(255,255,255,0.8);
-	padding-top: 12px;
-}
-
-@media (max-width: 767px) {
-	#moduleMenu > ul > li.active > a {
-		padding: 10px 15px;
-		border-top: none;
-		border-left: 3px solid rgba(255,255,255,0.8);
-	}
-
-	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a {
-		color: #333;
-	}
-	.main-header .navbar .navbar-custom-menu .nav .dropdown-menu li a:hover {
-		color: #FFF;
-	}
-}
-
-#timeRangePickerCntr .navbar-form {
-	display: inline-block;
-	padding-right: 0;
-}
-
-#timeRangePickerCntr #timeRangePicker {
-	min-width: 300px;
-}
-
-body .login-box, body .register-box {
-	margin: 3% auto;
-}
-
-.content-header > .breadcrumb > li {
-	font-size: 14px;
-}
-
-.daterangepicker .ranges {
-  width: 110px!important;
-}
-.daterangepicker .daterangepicker_start_input,
-.daterangepicker .daterangepicker_end_input {
-	display: block!important;
-	padding: 0!important;
-	float: none!important;
-}
-.daterangepicker .daterangepicker_start_input .input-mini,
-.daterangepicker .daterangepicker_end_input .input-mini {
-	width: 110px!important;
-}
-
-.form-group.inner-icon {
-	position: relative;
-}
-.form-group.inner-icon .fa {
-	position: absolute;
-	left: 10px;
-	top: 10px;
-}
-.form-group.inner-icon input {
-	padding-left: 35px;
-}
-
-#autoRefreshCntr > a {
-	border: none;
-	opacity: 0.3;
-}
-#autoRefreshCntr.autoRefresh > a {
-	opacity: 1;
-}
-
-.table-responsive .row {
-	margin: 0;
-}
-
-
-/* Misc */
-body .tooltip-inner {
-	max-width: 500px;
-}
-
-.text-nowrap {
-	white-space: nowrap;
-}
-
-.text-ellipsis,
-.label.text-ellipsis {
-	overflow:hidden;
-	text-overflow:ellipsis;
-	display: inline-block;
-	white-space: nowrap;
-	max-width: 100%;
-}
-td.text-ellipsis {
-	display: table-cell;
-}
-
-.text-breakall {
-	max-width: 100%;
-	display: inline-block;
-	word-wrap: break-word;
-}
-
-.btn.btn-xs.sm {
-	font-size: 12px;
-	padding: 2px 6px;
-}
-
-.form-control.input-xs {
-	height: 24px;
-	padding: 2px 8px;
-	font-size: 12px;
-	line-height: 100%;
-}
-
-pre.noWrap {
-	border: none;
-	border-radius: 0;
-	background: transparent;
-	margin: 0;
-	padding: 0;
-}
-
-.noSelect {
-	-khtml-user-select: none;
-	-moz-user-select: none;
-	-ms-user-select: none;
-	user-select: none;
-	-webkit-touch-callout: none;
-	-webkit-user-select: none;
-}
-
-.blink {
-	animation: blinker 1s linear infinite;
-}
-
-@keyframes blinker {
-	50% {opacity: 0.0;}
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
deleted file mode 100644
index 462b41b..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/classification/controller.js
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module('featureControllers');
-	var feature = featureControllers.register("classification");
-	var eagleApp = angular.module('eagleApp');
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-
-	// =============================================================
-	// =                        Sensitivity                        =
-	// =============================================================
-	feature.navItem("sensitivity", "Classification", "user-secret");
-	feature.controller('sensitivity', function(PageConfig, Site, $scope, Application, Entities, UI) {
-		PageConfig.pageTitle = "Data Classification";
-		PageConfig.pageSubTitle = Site.current().tags.site;
-		$scope.ajaxId = eagleApp._TRS();
-		$scope.viewConfig = Application.current().configObj.view;
-
-		if(!$scope.viewConfig) {
-			$.dialog({
-				title: "OPS",
-				content: "View configuration not defined in Application."
-			});
-			return;
-		}
-
-		// ===================== Function =====================
-		$scope.export = function() {
-			var _data = {};
-			UI.fieldConfirm({title: "Export Classification", confirm: false, size: "large"}, _data, [
-				{name: "Data", field: "data", type: "blob", rows: 20, optional: true, readonly: true}]
-			);
-
-			Entities.queryEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function(data) {
-				_data.data = JSON.stringify(data, null, "\t");
-			});
-		};
-
-		$scope.import = function() {
-			UI.fieldConfirm({title: "Import Classification", size: "large"}, {}, [
-				{name: "Data", field: "data", type: "blob", rows: 20, optional: true}
-			], function(entity) {
-				var _list = common.parseJSON(entity.data, false);
-				if(!_list) {
-					return "Invalid JSON format";
-				}
-				if(!$.isArray(_list)) {
-					return "Not an array";
-				}
-			}).then(null, null, function(holder) {
-				Entities.updateEntity($scope.viewConfig.service, common.parseJSON(holder.entity.data, []), {timestamp: false})._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-
-		$scope.deleteAll = function() {
-			UI.deleteConfirm("All the Classification Data").then(null, null, function(holder) {
-				Entities.deleteEntities($scope.viewConfig.service, {site: Site.current().tags.site})._promise.then(function() {
-					holder.closeFunc();
-					location.reload();
-				});
-			});
-		};
-	});
-     // =============================================================
-    	// =                    Sensitivity - Job                   =
-    	// =============================================================
-    	feature.controller('sensitivityViewJob', function(Site, $scope, $wrapState, Entities) {
-    		$scope.items = [];
-
-    		// Mark sensitivity
-    		$scope._oriItem = {};
-    		$scope._markItem = {};
-
-    		// ======================= View =======================
-    		// Item
-    		$scope.updateItems = function() {
-    			$scope.items = Entities.query($scope.viewConfig.api, {site: Site.current().tags.site});
-    		};
-
-
-    		$scope.updateItems();
-
-    		// =================== Sensitivity ===================
-    		$scope.markSensitivity = function(item) {
-    			$scope._oriItem = item;
-    			$scope._markItem = {
-    				prefix: $scope.viewConfig.prefix,
-    				tags: {
-    					site: Site.current().tags.site
-    				},
-    				sensitivityType: ""
-    			};
-
-    			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.jobId;
-    			$("#sensitivityMDL").modal();
-    		};
-    		$scope.confirmUpateSensitivity = function() {
-    			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
-    			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
-    				Entities.dialog(data);
-    			});
-    			$("#sensitivityMDL").modal('hide');
-    		};
-    		$scope.unmarkSensitivity = function(item) {
-    			$.dialog({
-    				title: "Unmark Confirm",
-    				content: "Do you want to remove the sensitivity mark on '" + item.jobId + "'?",
-    				confirm: true
-    			}, function(ret) {
-    				if(!ret) return;
-
-    				var _cond = {site: Site.current().tags.site};
-    				_cond[$scope.viewConfig.keys[0]] = item.jobId;
-    				Entities.deleteEntities($scope.viewConfig.service, _cond);
-
-    				item.sensitiveType = null;
-    				$scope.$apply();
-    			});
-    		};
-    	});
-	// =============================================================
-	// =                    Sensitivity - Folder                   =
-	// =============================================================
-	feature.controller('sensitivityViewFolder', function(Site, $scope, $wrapState, Entities) {
-		$scope.path = $wrapState.param.path || "/";
-		$scope.pathUnitList = [];
-		$scope.items = [];
-
-		// Mark sensitivity
-		$scope._oriItem = {};
-		$scope._markItem = {};
-
-		// ======================= View =======================
-		// Path
-		function _refreshPathUnitList(_path) {
-			var _start,_current, _unitList = [];
-			_path = _path + (_path.match(/\/$/) ? "" : "/");
-			for(_current = _start = 0 ; _current < _path.length ; _current += 1) {
-				if(_path[_current] === "/") {
-					_unitList.push({
-						name: _path.substring(_start, _current + (_current === 0 ? 1 : 0)),
-						path: _path.substring(0, _current === 0 ? 1 : _current)
-					});
-					_start = _current + 1;
-				}
-			}
-			$scope.pathUnitList = _unitList;
-		}
-
-		// Item
-		$scope.updateItems = function(path) {
-			if(path) $scope.path = path;
-
-			$scope.items = Entities.query($scope.viewConfig.api, {site: Site.current().tags.site, path: $scope.path});
-			$scope.items._promise.success(function(data) {
-				Entities.dialog(data, function() {
-					if($scope.path !== "/") $scope.updateItems("/");
-				});
-			});
-			_refreshPathUnitList($scope.path);
-		};
-
-		$scope.getFileName = function(item) {
-			return (item.resource + "").replace(/^.*\//, "");
-		};
-
-		$scope.updateItems($scope.path);
-
-		// =================== Sensitivity ===================
-		$scope.markSensitivity = function(item) {
-			$scope._oriItem = item;
-			$scope._markItem = {
-				prefix: $scope.viewConfig.prefix,
-				tags: {
-					site: Site.current().tags.site
-				},
-				sensitivityType: ""
-			};
-			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
-			$("#sensitivityMDL").modal();
-		};
-		$scope.confirmUpateSensitivity = function() {
-			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
-			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
-				Entities.dialog(data);
-			});
-			$("#sensitivityMDL").modal('hide');
-		};
-		$scope.unmarkSensitivity = function(item) {
-			$.dialog({
-				title: "Unmark Confirm",
-				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
-				confirm: true
-			}, function(ret) {
-				if(!ret) return;
-
-				var _cond = {site: Site.current().tags.site};
-				_cond[$scope.viewConfig.keys[0]] = item.resource;
-				Entities.deleteEntities($scope.viewConfig.service, _cond);
-
-				item.sensitiveType = null;
-				$scope.$apply();
-			});
-		};
-	});
-
-	// =============================================================
-	// =                    Sensitivity - Table                    =
-	// =============================================================
-	feature.controller('sensitivityViewTable', function(Site, $scope, Entities) {
-		$scope.databases = null;
-		$scope.table = null;
-
-		// Mark sensitivity
-		$scope._oriItem = {};
-		$scope._markItem = {};
-
-		// ======================= View =======================
-		var _fillAttr = function(list, key, target) {
-			list._promise.then(function() {
-				$.each(list, function(i, unit) {
-					unit[key] = unit[target];
-				});
-			});
-			return list._promise;
-		};
-
-		$scope.loadDatabases = function(database) {
-			var _dbs = Entities.query($scope.viewConfig.api.database, {site: Site.current().tags.site});
-			return _fillAttr(_dbs, "database", $scope.viewConfig.mapping.database).then(function() {
-				if($scope.databases) {
-					$.each($scope.databases, function(i, oriDB) {
-						var db = common.array.find(oriDB.resource, _dbs, "resource");
-						if(db) {
-							db.show = oriDB.show;
-							db.tables = oriDB.tables;
-						}
-					});
-				}
-				$scope.databases = _dbs;
-			});
-		};
-		$scope.loadDatabases();
-
-		$scope.loadTables = function(database, force) {
-			var _tables, _qry;
-			if(database.tables && !force) return;
-			_qry = {
-				site: Site.current().tags.site
-			};
-			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
-			_tables = Entities.query($scope.viewConfig.api.table, _qry);
-			if(!database.tables) database.tables = _tables;
-			_fillAttr(_tables, "table", $scope.viewConfig.mapping.table);
-			return _fillAttr(_tables, "database", $scope.viewConfig.mapping.database).then(function() {
-				database.tables = _tables;
-			});
-		};
-
-		$scope.loadColumns = function(database, table) {
-			$scope.table = table;
-
-			if(table.columns) return;
-			var _qry = {
-				site: Site.current().tags.site
-			};
-			_qry[$scope.viewConfig.mapping.database] = database[$scope.viewConfig.mapping.database];
-			_qry[$scope.viewConfig.mapping.table] = table[$scope.viewConfig.mapping.table];
-			table.columns = Entities.query($scope.viewConfig.api.column, _qry);
-			_fillAttr(table.columns, "column", $scope.viewConfig.mapping.column);
-		};
-
-		$scope.refreshData = function() {
-			$scope.loadDatabases().then(function() {
-				if(!$scope.table) return;
-
-				var _table = $scope.table;
-				var _db = common.array.find($scope.table.database, $scope.databases, "database");
-				if(_db) {
-					$scope.loadTables(_db, true).then(function() {
-						$scope.table = common.array.find(_table.table, _db.tables, "table");
-						$scope.table.columns = _table.columns;
-					});
-				}
-			});
-		};
-
-		// =================== Sensitivity ===================
-		$scope.markSensitivity = function(item, event) {
-			if(event) event.stopPropagation();
-
-			$scope._oriItem = item;
-			$scope._markItem = {
-				prefix: $scope.viewConfig.prefix,
-				tags: {
-					site: Site.current().tags.site
-				},
-				sensitivityType: ""
-			};
-			$scope._markItem.tags[$scope.viewConfig.keys[0]] = item.resource;
-			$("#sensitivityMDL").modal();
-		};
-		$scope.confirmUpateSensitivity = function() {
-			$scope._oriItem.sensitiveType = $scope._markItem.sensitivityType;
-			Entities.updateEntity($scope.viewConfig.service, $scope._markItem, {timestamp: false})._promise.success(function(data) {
-				Entities.dialog(data);
-				$scope.refreshData();
-			});
-			$("#sensitivityMDL").modal('hide');
-		};
-		$scope.unmarkSensitivity = function(item, event) {
-			if(event) event.stopPropagation();
-
-			$.dialog({
-				title: "Unmark Confirm",
-				content: "Do you want to remove the sensitivity mark on '" + item.resource + "'?",
-				confirm: true
-			}, function(ret) {
-				if(!ret) return;
-
-				var _qry = {
-					site: Site.current().tags.site
-				};
-				_qry[$scope.viewConfig.keys[0]] = item.resource;
-				Entities.deleteEntities($scope.viewConfig.service, _qry)._promise.then(function() {
-					$scope.refreshData();
-				});
-
-				item.sensitiveType = null;
-				$scope.$apply();
-			});
-		};
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
deleted file mode 100644
index 41fb291..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity.html
+++ /dev/null
@@ -1,40 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-folder-open"></i>
-		<h3 class="box-title ng-binding">{{Application.current().displayName}}</h3>
-		<div class="box-tools pull-right" ng-if="viewConfig">
-			<div class="btn-group">
-				<button type="button" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown">
-					<span class="fa fa-wrench"></span>
-				</button>
-				<ul class="dropdown-menu" role="menu">
-					<li><a ng-click="import()"><span class="fa fa-cloud-upload"></span> Import</a></li>
-					<li><a ng-click="export()"><span class="fa fa-cloud-download"></span> Export</a></li>
-					<li class="divider"></li>
-					<li class="danger"><a ng-click="deleteAll()"><span class="fa fa-trash"></span> Delete All</a></li>
-				</ul>
-			</div>
-		</div>
-	</div>
-	<div class="box-body">
-		<ng-include ng-if="viewConfig" src="'public/feature/classification/page/sensitivity/' + viewConfig.type + '.html?_=' + ajaxId"></ng-include>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
deleted file mode 100644
index cfefffa..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/folder.html
+++ /dev/null
@@ -1,110 +0,0 @@
-<!--
-  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.
-  -->
-<div ng-controller="classification_sensitivityViewFolder">
-	<ul class="list-inline path">
-		<li>Path:</li>
-		<li ng-repeat="unit in pathUnitList">
-			<a ng-click="updateItems(unit.path)" class="label bg-black">{{unit.name}}</a>
-		</li>
-	</ul>
-
-	<table class="table table-bordered">
-		<thead>
-			<tr>
-				<th width="15%">File Name</th>
-				<th width="10%">Owner</th>
-				<th width="10%">Group</th>
-				<th>Sensitivity Type</th>
-				<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
-			</tr>
-		</thead>
-		<tbody>
-			<tr ng-show="items._promise.$$state.status !== 1">
-				<td colspan="5">
-					<span class="fa fa-refresh fa-spin"> </span>
-					Loading...
-				</td>
-			</tr>
-			<tr ng-show="items._promise.$$state.status === 1 && !items.length">
-				<td colspan="5">
-					<span class="fa fa-exclamation-triangle"> </span>
-					Empty Folder
-				</td>
-			</tr>
-			<tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
-				<td>
-					<span ng-show="!item.isdir">
-						<span class="fa fa-file"> </span>
-						{{getFileName(item)}}
-					</span>
-					<a ng-show="item.isdir" ng-click="updateItems(item.resource)">
-						<span class="fa fa-folder"> </span>
-						{{getFileName(item)}}
-					</a>
-
-					<span class="pull-right" ng-show="item.childSensitiveTypes.length">
-						<span class="fa fa-dot-circle-o text-muted" uib-tooltip="Contain child sensitivity defination"> </span>
-					</span>
-				</td>
-				<td>{{item.owner}}</td>
-				<td>{{item.groupName}}</td>
-				<td>{{item.sensitiveType}}</td>
-				<td ng-show="Auth.isRole('ROLE_ADMIN')">
-					<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
-					uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
-					<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
-					uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
-				</td>
-			</tr>
-		</tbody>
-	</table>
-
-
-	<!-- Modal: Create / Edit site -->
-	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-		<div class="modal-dialog" role="document">
-			<div class="modal-content">
-				<div class="modal-header">
-					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-						<span aria-hidden="true">&times;</span>
-					</button>
-					<h4 class="modal-title">Mark Sensitivity Data</h4>
-				</div>
-				<div class="modal-body">
-					<div class="form-group">
-						<label>Resource</label>
-						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.filedir" />
-					</div>
-					<div class="form-group">
-						<label>* Sensitivity Type</label>
-						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
-					</div>
-				</div>
-				<div class="modal-footer">
-					<button type="button" class="btn btn-default" data-dismiss="modal">
-						Close
-					</button>
-					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
-						Update
-					</button>
-				</div>
-			</div>
-		</div>
-	</div>
-
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/job.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/job.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/job.html
deleted file mode 100644
index 05d70da..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/job.html
+++ /dev/null
@@ -1,92 +0,0 @@
-<!--
-  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.
-  -->
-<div ng-controller="classification_sensitivityViewJob">
-    <ul class="list-inline path">
-        <li>Oozie CoordinatorJob:</li>
-    </ul>
-
-    <table class="table table-bordered">
-        <thead>
-        <tr>
-            <th width="15%">JobId</th>
-            <th width="10%">AppName</th>
-            <th>Sensitivity Type</th>
-            <th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
-        </tr>
-        </thead>
-        <tbody>
-        <tr ng-show="items._promise.$$state.status !== 1">
-            <td colspan="5">
-                <span class="fa fa-refresh fa-spin"> </span>
-                Loading...
-            </td>
-        </tr>
-        <tr ng-show="items._promise.$$state.status === 1 && !items.length">
-            <td colspan="5">
-                <span class="fa fa-exclamation-triangle"> </span>
-                Empty
-            </td>
-        </tr>
-        <tr ng-repeat="item in items" ng-class="{warning : item.sensitiveType}">
-            <td>{{item.jobId}}</td>
-            <td>{{item.name}}</td>
-            <td>{{item.sensitiveType}}</td>
-            <td ng-show="Auth.isRole('ROLE_ADMIN')">
-                <button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(item)" ng-show="!item.sensitiveType"
-                        uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
-                <button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(item)" ng-show="item.sensitiveType"
-                        uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
-            </td>
-        </tr>
-        </tbody>
-    </table>
-
-
-    <!-- Modal: Create / Edit site -->
-    <div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-        <div class="modal-dialog" role="document">
-            <div class="modal-content">
-                <div class="modal-header">
-                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                    <h4 class="modal-title">Mark Sensitivity Data</h4>
-                </div>
-                <div class="modal-body">
-                    <div class="form-group">
-                        <label>Resource</label>
-                        <input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags.oozieResource" />
-                    </div>
-                    <div class="form-group">
-                        <label>* Sensitivity Type</label>
-                        <input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
-                    </div>
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-default" data-dismiss="modal">
-                        Close
-                    </button>
-                    <button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
-                        Update
-                    </button>
-                </div>
-            </div>
-        </div>
-    </div>
-
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html b/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
deleted file mode 100644
index 13d5807..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/classification/page/sensitivity/table.html
+++ /dev/null
@@ -1,150 +0,0 @@
-<!--
-  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.
-  -->
-<div ng-controller="classification_sensitivityViewTable">
-	<p ng-show="databases._promise.$$state.status !== 1">
-		<span class="fa fa-refresh fa-spin"> </span>
-		Loading...
-	</p>
-
-	<div ng-show="databases._promise.$$state.status === 1" class="row">
-		<div class="col-md-4">
-			<label>
-				Databases
-				({{databases.length}})
-			</label>
-			<ul class="tree tree-bordered" style="max-height: 500px; overflow-y: auto;">
-				<li ng-repeat="db in databases">
-					<span class="tree-item box-clickable text-primary" ng-click="db.show = !db.show; loadTables(db);">
-						<span ng-class="{'text-warning' : db.sensitiveType}">
-							<span class="fa fa-database"> </span>
-							{{db.database}}
-							<span ng-show="db.tables._promise.$$state.status === 1">({{db.tables.length}})</span>
-
-							<span ng-show="Auth.isRole('ROLE_ADMIN')">
-								<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(db, $event)" ng-show="!db.sensitiveType"
-								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
-								<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(db, $event)" ng-show="db.sensitiveType"
-								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
-							</span>
-
-							<span class="pull-right" ng-show="db.childSensitiveTypes.length">
-								<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
-							</span>
-							<span ng-show="db.sensitiveType" class="pull-right">[{{db.sensitiveType}}]</span>
-						</span>
-					</span>
-					<ul ng-show="db.show">
-						<li ng-show="db.tables._promise.$$state.status !== 1">
-							<span>
-								<span class="fa fa-refresh fa-spin"> </span>
-								Loading...
-							</span>
-						</li>
-						<li ng-repeat="tb in db.tables" ng-class="{active : tb === table}">
-							<span class="tree-item box-clickable text-primary" ng-click="loadColumns(db, tb)">
-								<span ng-class="{'text-warning' : tb.sensitiveType}">
-									<span class="fa fa-table"> </span>
-									{{tb.table}}
-
-									<span ng-show="Auth.isRole('ROLE_ADMIN')">
-										<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(tb, $event)" ng-show="!tb.sensitiveType"
-										uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
-										<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(tb, $event)" ng-show="tb.sensitiveType"
-										uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
-									</span>
-
-									<span class="pull-right" ng-show="tb.childSensitiveTypes.length">
-										<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
-									</span>
-									<span ng-show="tb.sensitiveType" class="pull-right">[{{tb.sensitiveType}}]</span>
-								</span>
-							</span>
-						</li>
-					</ul>
-				</li>
-			</ul>
-		</div>
-		<div class="col-md-8">
-			<label ng-show="table">Route: {{table.database}} > {{table.table}}</label>
-			<p ng-show="table && table.columns._promise.$$state.status !== 1">
-				<span class="fa fa-refresh fa-spin"> </span>
-				Loading...
-			</p>
-			<div ng-show="table && table.columns._promise.$$state.status === 1">
-				<table class="table table-bordered">
-					<thead>
-						<tr>
-							<th width="40%">Column Name</th>
-							<th>Sensitivity Type</th>
-							<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
-						</tr>
-					</thead>
-					<tbody>
-						<tr ng-repeat="col in table.columns" ng-class="{warning : col.sensitiveType}">
-							<td>{{col.column}}</td>
-							<td>{{col.sensitiveType}}</td>
-							<td ng-show="Auth.isRole('ROLE_ADMIN')">
-								<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(col)" ng-show="!col.sensitiveType"
-								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
-								<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(col)" ng-show="col.sensitiveType"
-								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
-							</td>
-						</tr>
-					</tbody>
-				</table>
-			</div>
-		</div>
-	</div>
-
-
-
-
-
-
-	<!-- Modal: Create / Edit site -->
-	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
-		<div class="modal-dialog" role="document">
-			<div class="modal-content">
-				<div class="modal-header">
-					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-						<span aria-hidden="true">&times;</span>
-					</button>
-					<h4 class="modal-title">Mark Sensitivity Data</h4>
-				</div>
-				<div class="modal-body">
-					<div class="form-group">
-						<label>Resource</label>
-						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags[viewConfig.keys[0]]" />
-					</div>
-					<div class="form-group">
-						<label>* Sensitivity Type</label>
-						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
-					</div>
-				</div>
-				<div class="modal-footer">
-					<button type="button" class="btn btn-default" data-dismiss="modal">
-						Close
-					</button>
-					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
-						Update
-					</button>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file


[09/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html
new file mode 100644
index 0000000..13d5807
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/classification/page/sensitivity/table.html
@@ -0,0 +1,150 @@
+<!--
+  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.
+  -->
+<div ng-controller="classification_sensitivityViewTable">
+	<p ng-show="databases._promise.$$state.status !== 1">
+		<span class="fa fa-refresh fa-spin"> </span>
+		Loading...
+	</p>
+
+	<div ng-show="databases._promise.$$state.status === 1" class="row">
+		<div class="col-md-4">
+			<label>
+				Databases
+				({{databases.length}})
+			</label>
+			<ul class="tree tree-bordered" style="max-height: 500px; overflow-y: auto;">
+				<li ng-repeat="db in databases">
+					<span class="tree-item box-clickable text-primary" ng-click="db.show = !db.show; loadTables(db);">
+						<span ng-class="{'text-warning' : db.sensitiveType}">
+							<span class="fa fa-database"> </span>
+							{{db.database}}
+							<span ng-show="db.tables._promise.$$state.status === 1">({{db.tables.length}})</span>
+
+							<span ng-show="Auth.isRole('ROLE_ADMIN')">
+								<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(db, $event)" ng-show="!db.sensitiveType"
+								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
+								<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(db, $event)" ng-show="db.sensitiveType"
+								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
+							</span>
+
+							<span class="pull-right" ng-show="db.childSensitiveTypes.length">
+								<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
+							</span>
+							<span ng-show="db.sensitiveType" class="pull-right">[{{db.sensitiveType}}]</span>
+						</span>
+					</span>
+					<ul ng-show="db.show">
+						<li ng-show="db.tables._promise.$$state.status !== 1">
+							<span>
+								<span class="fa fa-refresh fa-spin"> </span>
+								Loading...
+							</span>
+						</li>
+						<li ng-repeat="tb in db.tables" ng-class="{active : tb === table}">
+							<span class="tree-item box-clickable text-primary" ng-click="loadColumns(db, tb)">
+								<span ng-class="{'text-warning' : tb.sensitiveType}">
+									<span class="fa fa-table"> </span>
+									{{tb.table}}
+
+									<span ng-show="Auth.isRole('ROLE_ADMIN')">
+										<a class="fa fa-eye text-muted hover" ng-click="markSensitivity(tb, $event)" ng-show="!tb.sensitiveType"
+										uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="right"></a>
+										<a class="fa fa-eye-slash text-muted hover" ng-click="unmarkSensitivity(tb, $event)" ng-show="tb.sensitiveType"
+										uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="right"></a>
+									</span>
+
+									<span class="pull-right" ng-show="tb.childSensitiveTypes.length">
+										<span class="fa fa-dot-circle-o" uib-tooltip="Contain child sensitivity defination" tooltip-placement="right" tooltip-append-to-body="true"> </span>
+									</span>
+									<span ng-show="tb.sensitiveType" class="pull-right">[{{tb.sensitiveType}}]</span>
+								</span>
+							</span>
+						</li>
+					</ul>
+				</li>
+			</ul>
+		</div>
+		<div class="col-md-8">
+			<label ng-show="table">Route: {{table.database}} > {{table.table}}</label>
+			<p ng-show="table && table.columns._promise.$$state.status !== 1">
+				<span class="fa fa-refresh fa-spin"> </span>
+				Loading...
+			</p>
+			<div ng-show="table && table.columns._promise.$$state.status === 1">
+				<table class="table table-bordered">
+					<thead>
+						<tr>
+							<th width="40%">Column Name</th>
+							<th>Sensitivity Type</th>
+							<th width="10" ng-show="Auth.isRole('ROLE_ADMIN')"> </th>
+						</tr>
+					</thead>
+					<tbody>
+						<tr ng-repeat="col in table.columns" ng-class="{warning : col.sensitiveType}">
+							<td>{{col.column}}</td>
+							<td>{{col.sensitiveType}}</td>
+							<td ng-show="Auth.isRole('ROLE_ADMIN')">
+								<button class="fa fa-eye btn btn-primary btn-xs" ng-click="markSensitivity(col)" ng-show="!col.sensitiveType"
+								uib-tooltip="Mark as sensitivity data" tooltip-animation="false" tooltip-placement="left"> </button>
+								<button class="fa fa-eye-slash btn btn-warning btn-xs" ng-click="unmarkSensitivity(col)" ng-show="col.sensitiveType"
+								uib-tooltip="Remove the sensitivity mark" tooltip-animation="false" tooltip-placement="left"> </button>
+							</td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+		</div>
+	</div>
+
+
+
+
+
+
+	<!-- Modal: Create / Edit site -->
+	<div class="modal fade" id="sensitivityMDL" tabindex="-1" role="dialog">
+		<div class="modal-dialog" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close">
+						<span aria-hidden="true">&times;</span>
+					</button>
+					<h4 class="modal-title">Mark Sensitivity Data</h4>
+				</div>
+				<div class="modal-body">
+					<div class="form-group">
+						<label>Resource</label>
+						<input type="text" readonly="readonly" class="form-control" ng-model="_markItem.tags[viewConfig.keys[0]]" />
+					</div>
+					<div class="form-group">
+						<label>* Sensitivity Type</label>
+						<input type="text" class="form-control" ng-model="_markItem.sensitivityType" id="sensitiveType" />
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button type="button" class="btn btn-default" data-dismiss="modal">
+						Close
+					</button>
+					<button type="button" class="btn btn-primary" ng-click="confirmUpateSensitivity()" ng-disabled="!_markItem.sensitivityType">
+						Update
+					</button>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js
new file mode 100644
index 0000000..207c8df
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/controller.js
@@ -0,0 +1,1224 @@
+/*
+ * 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() {
+	'use strict';
+
+	var featureControllers = angular.module("featureControllers");
+	var feature = featureControllers.register("common");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+	feature.service("Policy", function(Entities) {
+		var Policy = function () {};
+
+		Policy.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);
+				}
+			});
+		};
+		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 Policy;
+	});
+
+	feature.service("Notification", function(Entities) {
+		var Notification = function () {};
+		Notification.map = {};
+
+		Notification.list = Entities.queryEntities("AlertNotificationService");
+		Notification.list._promise.then(function () {
+			$.each(Notification.list, function (i, notification) {
+				// Parse fields
+				notification.fieldList = common.parseJSON(notification.fields, []);
+
+				// Fill map
+				Notification.map[notification.tags.notificationType] = notification;
+			});
+		});
+
+		Notification.promise = Notification.list._promise;
+
+		return Notification;
+	});
+
+	// ==============================================================
+	// =                          Policies                          =
+	// ==============================================================
+
+	// ========================= Policy List ========================
+	feature.navItem("policyList", "Policies", "list");
+	feature.controller('policyList', function(PageConfig, Site, $scope, Application, Entities, Policy) {
+		PageConfig.pageTitle = "Policy List";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		// Initial load
+		$scope.policyList = [];
+		$scope.application = Application.current();
+
+		// List policies
+		var _policyList = Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, application: $scope.application.tags.application});
+		_policyList._promise.then(function() {
+			$.each(_policyList, function(i, policy) {
+				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
+
+				$scope.policyList.push(policy);
+			});
+		});
+		$scope.policyList._promise = _policyList._promise;
+
+		// Function
+		$scope.searchFunc = function(item) {
+			var key = $scope.search;
+			if(!key) return true;
+
+			var _key = key.toLowerCase();
+			function _hasKey(item, path) {
+				var _value = common.getValueByPath(item, path, "").toLowerCase();
+				return _value.indexOf(_key) !== -1;
+			}
+			return _hasKey(item, "tags.policyId") || _hasKey(item, "__expression") || _hasKey(item, "description") || _hasKey(item, "owner");
+		};
+
+		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
+		$scope.deletePolicy = function(policy) {
+			Policy.deletePolicy(policy, function(policy) {
+				var _index = $scope.policyList.indexOf(policy);
+				$scope.policyList.splice(_index, 1);
+			});
+		};
+	});
+
+	// ======================= Policy Detail ========================
+	feature.controller('policyDetail', function(PageConfig, Site, $scope, $wrapState, $interval, Entities, Policy, nvd3) {
+		var MAX_PAGESIZE = 10000;
+		var seriesRefreshInterval;
+
+		PageConfig.pageTitle = "Policy Detail";
+		PageConfig.lockSite = true;
+		PageConfig
+			.addNavPath("Policy List", "/common/policyList")
+			.addNavPath("Policy Detail");
+
+		$scope.chartConfig = {
+			chart: "line",
+			xType: "time"
+		};
+
+		// Query policy
+		if($wrapState.param.filter) {
+			$scope.policyList = Entities.queryEntity("AlertDefinitionService", $wrapState.param.filter);
+		} else {
+			$scope.policyList = Entities.queryEntities("AlertDefinitionService", {
+				policyId: $wrapState.param.policy,
+				site: $wrapState.param.site,
+				alertExecutorId: $wrapState.param.executor
+			});
+		}
+
+		$scope.policyList._promise.then(function() {
+			var policy = null;
+
+			if($scope.policyList.length === 0) {
+				$.dialog({
+					title: "OPS!",
+					content: "Policy not found!"
+				}, function() {
+					location.href = "#/common/policyList";
+				});
+				return;
+			} else {
+				policy = $scope.policyList[0];
+
+				policy.__notificationList = common.parseJSON(policy.notificationDef, []);
+
+				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
+
+				$scope.policy = policy;
+				Site.current(Site.find($scope.policy.tags.site));
+				console.log($scope.policy);
+			}
+
+			// Visualization
+			var _intervalType = 0;
+			var _intervalList = [
+				["Daily", 1440, function() {
+					var _endTime = app.time.now().hour(23).minute(59).second(59).millisecond(0);
+					var _startTime = _endTime.clone().subtract(1, "month").hour(0).minute(0).second(0).millisecond(0);
+					return [_startTime, _endTime];
+				}],
+				["Hourly", 60, function() {
+					var _endTime = app.time.now().minute(59).second(59).millisecond(0);
+					var _startTime = _endTime.clone().subtract(48, "hour").minute(0).second(0).millisecond(0);
+					return [_startTime, _endTime];
+				}],
+				["Every 5 minutes", 5, function() {
+					var _endTime = app.time.now().second(59).millisecond(0);
+					var _minute = Math.floor(_endTime.minute() / 5) * 5;
+					var _startTime = _endTime.clone().minute(_minute).subtract(5 * 30, "minute").second(0).millisecond(0);
+					_endTime.minute(_minute + 4);
+					return [_startTime, _endTime];
+				}]
+			];
+
+			function _loadSeries(seriesName, metricName, condition) {
+				var list = Entities.querySeries("GenericMetricService", $.extend({_metricName: metricName}, condition), "@site", "sum(value)", _intervalList[_intervalType][1]);
+				var seriesList = nvd3.convert.eagle([list]);
+				if(!$scope[seriesName]) $scope[seriesName] = seriesList;
+				list._promise.then(function() {
+					$scope[seriesName] = seriesList;
+				});
+			}
+
+			function refreshSeries() {
+				var _timeRange = _intervalList[_intervalType][2]();
+				var _startTime = _timeRange[0];
+				var _endTime = _timeRange[1];
+				var _cond = {
+					application: policy.tags.application,
+					policyId: policy.tags.policyId,
+					_startTime: _startTime,
+					_endTime: _endTime
+				};
+				console.log("Range:", common.format.date(_startTime, "datetime"), common.format.date(_endTime, "datetime"));
+
+				// > eagle.policy.eval.count
+				_loadSeries("policyEvalSeries", "eagle.policy.eval.count", _cond);
+
+				// > eagle.policy.eval.fail.count
+				_loadSeries("policyEvalFailSeries", "eagle.policy.eval.fail.count", _cond);
+
+				// > eagle.alert.count
+				_loadSeries("alertSeries", "eagle.alert.count", _cond);
+
+				// > eagle.alert.fail.count
+				_loadSeries("alertFailSeries", "eagle.alert.fail.count", _cond);
+			}
+
+			// Alert list
+			$scope.alertList = Entities.queryEntities("AlertService", {
+				site: Site.current().tags.site,
+				application: policy.tags.application,
+				policyId: policy.tags.policyId,
+				_pageSize: MAX_PAGESIZE,
+				_duration: 1000 * 60 * 60 * 24 * 30,
+				__ETD: 1000 * 60 * 60 * 24
+			});
+
+			refreshSeries();
+			seriesRefreshInterval = $interval(refreshSeries, 60000);
+
+			// Menu
+			$scope.visualizationMenu = [
+				{icon: "clock-o", title: "Interval", list:
+					$.map(_intervalList, function(item, index) {
+						var _item = {icon: "clock-o", title: item[0], func: function() {
+							_intervalType = index;
+							refreshSeries();
+						}};
+						Object.defineProperty(_item, 'strong', {
+							get: function() {return _intervalType === index;}
+						});
+						return _item;
+					})
+				}
+			];
+		});
+
+		// Function
+		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
+		$scope.deletePolicy = function(policy) {
+			Policy.deletePolicy(policy, function() {
+				location.href = "#/common/policyList";
+			});
+		};
+
+		// Clean up
+		$scope.$on('$destroy', function() {
+			$interval.cancel(seriesRefreshInterval);
+		});
+	});
+
+	// ======================== Policy Edit =========================
+	function policyCtrl(create, PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
+		PageConfig.pageTitle = create ? "Policy Create" : "Policy Edit";
+		PageConfig.pageSubTitle = Site.current().tags.site;
+		PageConfig
+			.addNavPath("Policy List", "/common/policyList")
+			.addNavPath("Policy Edit");
+
+		var _winTimeDesc = "Number unit[millisecond, sec, min, hour, day, month, year]. e.g. 23 sec";
+		var _winTimeRegex = /^\d+\s+(millisecond|milliseconds|second|seconds|sec|minute|minutes|min|hour|hours|day|days|week|weeks|month|months|year|years)$/;
+		var _winTimeDefaultValue = '10 min';
+		$scope.config = {
+			window: [
+				// Display name, window type, required columns[Title, Description || "LONG_FIELD", Regex check, default value]
+				{
+					title: "Message Time Slide",
+					description: "Using timestamp filed from input is used as event's timestamp",
+					type: "externalTime",
+					fields:[
+						{title: "Field", defaultValue: "timestamp", hide: true},
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "System Time Slide",
+					description: "Using System time is used as timestamp for event's timestamp",
+					type: "time",
+					fields:[
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "System Time Batch",
+					description: "Same as System Time Window except the policy is evaluated at fixed duration",
+					type: "timeBatch",
+					fields:[
+						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
+					]
+				},
+				{
+					title: "Length Slide",
+					description: "The slide window has a fixed length",
+					type: "length",
+					fields:[
+						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
+					]
+				},
+				{
+					title: "Length Batch",
+					description: "Same as Length window except the policy is evaluated in batch mode when fixed event count reached",
+					type: "lengthBatch",
+					fields:[
+						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
+					]
+				}
+			]
+		};
+
+		$scope.Notification = Notification;
+
+		$scope.create = create;
+		$scope.encodedRowkey = $wrapState.param.filter;
+
+		$scope.step = 0;
+		$scope.applications = {};
+		$scope.policy = {};
+
+		// ==========================================
+		// =              Notification              =
+		// ==========================================
+		$scope.notificationTabHolder = null;
+
+		$scope.newNotification = function (notificationType) {
+			var __notification = {
+				notificationType: notificationType
+			};
+
+			$.each(Notification.map[notificationType].fieldList, function (i, field) {
+				__notification[field.name] = field.value || "";
+			});
+
+			$scope.policy.__.notification.push(__notification);
+		};
+
+		Notification.promise.then(function () {
+			$scope.menu = Authorization.isRole('ROLE_ADMIN') ? [
+				{icon: "cog", title: "Configuration", list: [
+					{icon: "trash", title: "Delete", danger: true, func: function () {
+						var notification = $scope.notificationTabHolder.selectedPane.data;
+						UI.deleteConfirm(notification.notificationType).then(null, null, function(holder) {
+							common.array.remove(notification, $scope.policy.__.notification);
+							holder.closeFunc();
+						});
+					}}
+				]},
+				{icon: "plus", title: "New", list: $.map(Notification.list, function(notification) {
+					return {
+						icon: "plus",
+						title: notification.tags.notificationType,
+						func: function () {
+							$scope.newNotification(notification.tags.notificationType);
+							setTimeout(function() {
+								$scope.notificationTabHolder.setSelect(-1);
+								$scope.$apply();
+							}, 0);
+						}
+					};
+				})}
+			] : [];
+		});
+
+		// ==========================================
+		// =            Data Preparation            =
+		// ==========================================
+		// Steam list
+		var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application});
+		var _executorList = Entities.queryEntities("AlertExecutorService", {application: Application.current().tags.application});
+		$scope.streamList = _streamList;
+		$scope.executorList = _executorList;
+		$scope.streamReady = false;
+
+		$q.all([_streamList._promise, _executorList._promise]).then(function() {
+			// Map executor with stream
+			$.each(_executorList, function(i, executor) {
+				$.each(_streamList, function(j, stream) {
+					if(stream.tags.application === executor.tags.application && stream.tags.streamName === executor.tags.streamName) {
+						stream.alertExecutor = executor;
+						return false;
+					}
+				});
+			});
+
+			// Fill stream list
+			$.each(_streamList, function(i, unit) {
+				var _srcStreamList = $scope.applications[unit.tags.application] = $scope.applications[unit.tags.application] || [];
+				_srcStreamList.push(unit);
+			});
+
+			$scope.streamReady = true;
+
+			// ==========================================
+			// =                Function                =
+			// ==========================================
+			function _findStream(application, streamName) {
+				var _streamList = $scope.applications[application];
+				if(!_streamList) return null;
+
+				for(var i = 0 ; i < _streamList.length ; i += 1) {
+					if(_streamList[i].tags.streamName === streamName) {
+						return _streamList[i];
+					}
+				}
+				return null;
+			}
+
+			// ==========================================
+			// =              Step control              =
+			// ==========================================
+			$scope.steps = [
+				// >> Select stream
+				{
+					title: "Select Stream",
+					ready: function() {
+						return $scope.streamReady;
+					},
+					init: function() {
+						$scope.policy.__.streamName = $scope.policy.__.streamName ||
+							common.array.find($scope.policy.tags.application, _streamList, "tags.application").tags.streamName;
+					},
+					nextable: function() {
+						var _streamName = common.getValueByPath($scope.policy, "__.streamName");
+						if(!_streamName) return false;
+
+						// Detect stream in current data source list
+						return !!common.array.find(_streamName, $scope.applications[$scope.policy.tags.application], "tags.streamName");
+					}
+				},
+
+				// >> Define Alert Policy
+				{
+					title: "Define Alert Policy",
+					init: function() {
+						// Normal mode will fetch meta list
+						if(!$scope.policy.__.advanced) {
+							var _stream = _findStream($scope.policy.tags.application, $scope.policy.__.streamName);
+							$scope._stream = _stream;
+
+							if(!_stream) {
+								$.dialog({
+									title: "OPS",
+									content: "Stream not found! Current application don't match any stream."
+								});
+								return;
+							}
+
+							if(!_stream.metas) {
+								_stream.metas = Entities.queryEntities("AlertStreamSchemaService", {application: $scope.policy.tags.application, streamName: $scope.policy.__.streamName});
+								_stream.metas._promise.then(function() {
+									_stream.metas.sort(function(a, b) {
+										if(a.tags.attrName < b.tags.attrName) {
+											return -1;
+										} else if(a.tags.attrName > b.tags.attrName) {
+											return 1;
+										}
+										return 0;
+									});
+								});
+							}
+						}
+					},
+					ready: function() {
+						if(!$scope.policy.__.advanced) {
+							return $scope._stream.metas._promise.$$state.status === 1;
+						}
+						return true;
+					},
+					nextable: function() {
+						if($scope.policy.__.advanced) {
+							// Check stream source
+							$scope._stream = null;
+							$.each($scope.applications[$scope.policy.tags.application], function(i, stream) {
+								if(($scope.policy.__._expression || "").indexOf(stream.tags.streamName) !== -1) {
+									$scope._stream = stream;
+									return false;
+								}
+							});
+							return $scope._stream;
+						} else {
+							// Window
+							if($scope.policy.__.windowConfig) {
+								var _winMatch = true;
+								var _winConds = $scope.getWindow().fields;
+								$.each(_winConds, function(i, cond) {
+									if(!(cond.val || "").match(cond.regex)) {
+										_winMatch = false;
+										return false;
+									}
+								});
+								if(!_winMatch) return false;
+
+								// Aggregation
+								if($scope.policy.__.groupAgg) {
+									if(!$scope.policy.__.groupAggPath ||
+										!$scope.policy.__.groupCondOp ||
+										!$scope.policy.__.groupCondVal) {
+										return false;
+									}
+								}
+							}
+						}
+						return true;
+					}
+				},
+
+				// >> Configuration & Notification
+				{
+					title: "Configuration & Notification",
+					nextable: function() {
+						return !!$scope.policy.tags.policyId;
+					}
+				}
+			];
+
+			// ==========================================
+			// =              Policy Logic              =
+			// ==========================================
+			_streamList._promise.then(function() {
+				// Initial policy entity
+				if(create) {
+					$scope.policy = {
+						__: {
+							toJSON: jQuery.noop,
+							conditions: {},
+							notification: [],
+							dedupe: {
+								alertDedupIntervalMin: 10,
+								fields: []
+							},
+							_dedupTags: {},
+							policy: {},
+							window: "externalTime",
+							group: "",
+							groupAgg: "count",
+							groupAggPath: "timestamp",
+							groupCondOp: ">=",
+							groupCondVal: "2"
+						},
+						description: "",
+						enabled: true,
+						prefix: "alertdef",
+						remediationDef: "",
+						tags: {
+							application: Application.current().tags.application,
+							policyType: "siddhiCEPEngine"
+						}
+					};
+
+					// If configured data source
+					if($wrapState.param.app) {
+						$scope.policy.tags.application = $wrapState.param.app;
+						if(common.array.find($wrapState.param.app, Site.current().applicationList, "tags.application")) {
+							setTimeout(function() {
+								$scope.changeStep(0, 2, false);
+								$scope.$apply();
+							}, 1);
+						}
+					}
+
+					// Start step
+					$scope.changeStep(0, 1, false);
+					console.log($scope.policy);
+				} else {
+					var _policy = Entities.queryEntity("AlertDefinitionService", $scope.encodedRowkey);
+					_policy._promise.then(function() {
+						if(_policy.length) {
+							$scope.policy = _policy[0];
+							$scope.policy.__ = {
+								toJSON: jQuery.noop
+							};
+
+							Site.current(Site.find($scope.policy.tags.site));
+						} else {
+							$.dialog({
+								title: "OPS",
+								content: "Policy not found!"
+							}, function() {
+								$wrapState.path("/common/policyList");
+								$scope.$apply();
+							});
+							return;
+						}
+
+						var _application = Application.current();
+						if(_application.tags.application !== $scope.policy.tags.application) {
+							_application = Application.find($scope.policy.tags.application);
+							if(_application) {
+								Application.current(_application, false);
+								console.log("Application not match. Do reload...");
+								$wrapState.reload();
+							} else {
+								$.dialog({
+									title: "OPS",
+									content: "Application not found! Current policy don't match any application."
+								}, function() {
+									$location.path("/common/policyList");
+									$scope.$apply();
+								});
+							}
+							return;
+						}
+
+						// === Revert inner data ===
+						// >> De-dupe
+						$scope.policy.__._dedupTags = {};
+						$scope.policy.__.dedupe = common.parseJSON($scope.policy.dedupeDef, {});
+						$.each($scope.policy.__.dedupe.fields || [], function (i, field) {
+							$scope.policy.__._dedupTags[field] = true;
+						});
+
+						// >> Notification
+						$scope.policy.__.notification = common.parseJSON($scope.policy.notificationDef, []);
+
+						// >> Policy
+						var _policyUnit = $scope.policy.__.policy = common.parseJSON($scope.policy.policyDef);
+
+						// >> Parse expression
+						$scope.policy.__.conditions = {};
+						var _condition = _policyUnit.expression.match(/from\s+(\w+)(\[(.*)])?(#window[^\)]*\))?\s+(select (\w+, )?(\w+)\((\w+)\) as [\w\d_]+ (group by (\w+) )?having ([\w\d_]+) ([<>=]+) ([^\s]+))?/);
+						var _cond_stream = _condition[1];
+						var _cond_query = _condition[3] || "";
+						var _cond_window = _condition[4];
+						var _cond_group = _condition[5];
+						var _cond_groupUnit = _condition.slice(7,14);
+
+						if(!_condition) {
+							$scope.policy.__.advanced = true;
+						} else {
+							// > StreamName
+							var _streamName = _cond_stream;
+							var _cond = _cond_query;
+
+							$scope.policy.__.streamName = _streamName;
+
+							// > Conditions
+							// Loop condition groups
+							if(_cond.trim() !== "" && /^\(.*\)$/.test(_cond)) {
+								var _condGrps = _cond.substring(1, _cond.length - 1).split(/\)\s+and\s+\(/);
+								$.each(_condGrps, function(i, line) {
+									// Loop condition cells
+									var _condCells = line.split(/\s+or\s+/);
+									$.each(_condCells, function(i, cell) {
+										var _opMatch = cell.match(/(\S*)\s*(==|!=|>|<|>=|<=|contains)\s*('(?:[^'\\]|\\.)*'|[\w\d]+)/);
+										if(!common.isEmpty(_opMatch)) {
+											var _key = _opMatch[1];
+											var _op = _opMatch[2];
+											var _val = _opMatch[3];
+											var _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
+											var _type = "";
+											if(_val.match(/'.*'/)) {
+												_val = _val.slice(1, -1);
+												_type = "string";
+											} else if(_val === "true" || _val === "false") {
+												var _regexMatch = _key.match(/^str:regexp\((\w+),'(.*)'\)/);
+												var _containsMatch = _key.match(/^str:contains\((\w+),'(.*)'\)/);
+												var _mathes = _regexMatch || _containsMatch;
+												if(_mathes) {
+													_key = _mathes[1];
+													_val = _mathes[2];
+													_type = "string";
+													_op = _regexMatch ? "regex" : "contains";
+													_conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
+												} else {
+													_type = "bool";
+												}
+											} else {
+												_type = "number";
+											}
+											_conds.push($scope._CondUnit(_key, _op, _val, _type));
+										}
+									});
+								});
+							} else if(_cond_query !== "") {
+								$scope.policy.__.advanced = true;
+							}
+						}
+
+						if($scope.policy.__.advanced) {
+							$scope.policy.__._expression = _policyUnit.expression;
+						} else {
+							// > window
+							if(!_cond_window) {
+								$scope.policy.__.window = "externalTime";
+								$scope.policy.__.group = "";
+								$scope.policy.__.groupAgg = "count";
+								$scope.policy.__.groupAggPath = "timestamp";
+								$scope.policy.__.groupCondOp = ">=";
+								$scope.policy.__.groupCondVal = "2";
+							} else {
+								try {
+									$scope.policy.__.windowConfig = true;
+
+									var _winCells = _cond_window.match(/\.(\w+)\((.*)\)/);
+									$scope.policy.__.window = _winCells[1];
+									var _winConds = $scope.getWindow().fields;
+									$.each(_winCells[2].split(","), function(i, val) {
+										_winConds[i].val = val;
+									});
+
+									// Group
+									if(_cond_group) {
+										$scope.policy.__.group = _cond_groupUnit[3];
+										$scope.policy.__.groupAgg = _cond_groupUnit[0];
+										$scope.policy.__.groupAggPath = _cond_groupUnit[1];
+										$scope.policy.__.groupAggAlias = _cond_groupUnit[4] === "aggValue" ? "" : _cond_groupUnit[4];
+										$scope.policy.__.groupCondOp = _cond_groupUnit[5];
+										$scope.policy.__.groupCondVal = _cond_groupUnit[6];
+									} else {
+										$scope.policy.__.group = "";
+										$scope.policy.__.groupAgg = "count";
+										$scope.policy.__.groupAggPath = "timestamp";
+										$scope.policy.__.groupCondOp = ">=";
+										$scope.policy.__.groupCondVal = "2";
+									}
+								} catch(err) {
+									$scope.policy.__.window = "externalTime";
+								}
+							}
+						}
+
+						$scope.changeStep(0, 2, false);
+						console.log($scope.policy);
+					});
+				}
+			});
+
+			// ==========================================
+			// =                Function                =
+			// ==========================================
+			// UI: Highlight select step
+			$scope.stepSelect = function(step) {
+				return step === $scope.step ? "active" : "";
+			};
+
+			// UI: Collapse all
+			$scope.collapse = function(cntr) {
+				var _list = $(cntr).find(".collapse").css("height", "auto");
+				if(_list.hasClass("in")) {
+					_list.removeClass("in");
+				} else {
+					_list.addClass("in");
+				}
+			};
+
+			// Step process. Will fetch target step attribute and return boolean
+			function _check(key, step) {
+				var _step = $scope.steps[step - 1];
+				if(!_step) return;
+
+				var _value = _step[key];
+				if(typeof _value === "function") {
+					return _value();
+				} else if(typeof _value === "boolean") {
+					return _value;
+				}
+				return true;
+			}
+			// Check step is ready. Otherwise will display load animation
+			$scope.stepReady = function(step) {
+				return _check("ready", step);
+			};
+			// Check whether process next step. Otherwise will disable next button
+			$scope.checkNextable = function(step) {
+				return !_check("nextable", step);
+			};
+			// Switch step
+			$scope.changeStep = function(step, targetStep, check) {
+				if(check === false || _check("checkStep", step)) {
+					$scope.step =  targetStep;
+
+					_check("init", targetStep);
+				}
+			};
+
+			// Window
+			$scope.getWindow = function() {
+				if(!$scope.policy || !$scope.policy.__) return null;
+				return common.array.find($scope.policy.__.window, $scope.config.window, "type");
+			};
+
+			// Aggregation
+			$scope.groupAggPathList = function() {
+				return $.grep(common.getValueByPath($scope, "_stream.metas", []), function(meta) {
+					return $.inArray(meta.attrType, ['long','integer','number', 'double', 'float']) !== -1;
+				});
+			};
+
+			$scope.updateGroupAgg = function() {
+				$scope.policy.__.groupAggPath = $scope.policy.__.groupAggPath || common.getValueByPath($scope.groupAggPathList()[0], "tags.attrName");
+
+				if($scope.policy.__.groupAgg === 'count') {
+					$scope.policy.__.groupAggPath = 'timestamp';
+				}
+			};
+
+			// Resolver
+			$scope.resolverTypeahead = function(value, resolver) {
+				var _resolverList = Entities.query("stream/attributeresolve", {
+					site: Site.current().tags.site,
+					resolver: resolver,
+					query: value
+				});
+				return _resolverList._promise.then(function() {
+					return _resolverList;
+				});
+			};
+
+			// Used for input box when pressing enter
+			$scope.conditionPress = function(event) {
+				if(event.which == 13) {
+					setTimeout(function() {
+						$(event.currentTarget).closest(".input-group").find("button").click();
+					}, 1);
+				}
+			};
+			// Check whether has condition
+			$scope.hasCondition = function(key, type) {
+				var _list = common.getValueByPath($scope.policy.__.conditions, key, []);
+				if(_list.length === 0) return false;
+
+				if(type === "bool") {
+					return !_list[0].ignored();
+				}
+				return true;
+			};
+			// Condition unit definition
+			$scope._CondUnit = function(key, op, value, type) {
+				return {
+					key: key,
+					op: op,
+					val: value,
+					type: type,
+					ignored: function() {
+						return this.type === "bool" && this.val === "none";
+					},
+					getVal: function() {
+						return this.type === "string" ? "'" + this.val + "'" : this.val;
+					},
+					toString: function() {
+						return this.op + " " + this.getVal();
+					},
+					toCondString: function() {
+						var _op = this.op === "=" ? "==" : this.op;
+						if(_op === "regex") {
+							return "str:regexp(" + this.key + "," + this.getVal() + ")==true";
+						} else if(_op === "contains") {
+							return "str:contains(" + this.key + "," + this.getVal() + ")==true";
+						} else {
+							return this.key + " " + _op + " " + this.getVal();
+						}
+					}
+				};
+			};
+
+			//for maprfs, if key is status or volume or src/dst, convert these names to id.
+			$scope.convertToID = function(_condList, key, op, name,  type, site){
+				if(key == "dst" || key == "src") {
+					Entities.maprfsNameToID("fNameResolver", name, site)._promise.then(
+						function(response){
+							console.log("success");
+							console.log(response);
+							_condList.push($scope._CondUnit(key, op, response.data, type));
+						},
+						function(error, status){
+							console.log("error: " + status);
+						}
+					);
+				}
+				else if (key == "status"){
+					Entities.maprfsNameToID("sNameResolver", name, site)._promise.then(
+						function(response){
+							console.log("success");
+							console.log(response);
+							_condList.push($scope._CondUnit(key, op, response.data, type));
+						},
+						function(error, status){
+							console.log("error: " + status);
+						}
+					);
+				}
+				else if (key == "volume") {
+					Entities.maprfsNameToID("vNameResolver", name, site)._promise.then(
+						function(response){
+							console.log("success");
+							console.log(response);
+							_condList.push($scope._CondUnit(key, op, response.data, type));
+						},
+						function(error, status){
+							console.log("error: " + status);
+						}
+					);
+				}
+			};
+
+			// Add condition for policy
+			$scope.addCondition = function(key, op, value, type) {
+				if(value === "" || value === undefined) return false;
+
+				var _condList = $scope.policy.__.conditions[key] = $scope.policy.__.conditions[key] || [];
+				_condList.push($scope._CondUnit(key, op, value, type));
+
+				//if it is mapr application, covert human readable name to ids
+				if(Application.current().tags.application == "maprFSAuditLog")  {
+					if ( key == "dst" || key == "src" || key == "volume" || key == "status")  {
+						$scope.convertToID(_condList, key, op, value , type, Site.current().tags.site);
+					}
+				}
+
+				return true;
+			};
+			// Convert condition list to description string
+			$scope.parseConditionDesc = function(key) {
+				return $.map($scope.policy.__.conditions[key] || [], function(cond) {
+					if(!cond.ignored()) return "[" + cond.toString() + "]";
+				}).join(" or ");
+			};
+
+			// To query
+			$scope.toQuery = function() {
+				if(!$scope.policy.__) return "";
+
+				if($scope.policy.__.advanced) return $scope.policy.__._expression;
+
+				// > Query
+				var _query = $.map(common.getValueByPath($scope.policy, "__.conditions", {}), function(list) {
+					var _conds = $.map(list, function(cond) {
+						if(!cond.ignored()) return cond.toCondString();
+					});
+					if(_conds.length) {
+						return "(" + _conds.join(" or ") + ")";
+					}
+				}).join(" and ");
+				if(_query) {
+					_query = "[" + _query + "]";
+				}
+
+				// > Window
+				var _window = $scope.getWindow();
+				var _windowStr = "";
+				if($scope.policy.__.windowConfig) {
+					_windowStr = $.map(_window.fields, function(field) {
+						return field.val;
+					}).join(",");
+					_windowStr = "#window." + _window.type + "(" + _windowStr + ")";
+
+					// > Group
+					if($scope.policy.__.group) {
+						_windowStr += common.template(" select ${group}, ${groupAgg}(${groupAggPath}) as ${groupAggAlias} group by ${group} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
+							group: $scope.policy.__.group,
+							groupAgg: $scope.policy.__.groupAgg,
+							groupAggPath: $scope.policy.__.groupAggPath,
+							groupCondOp: $scope.policy.__.groupCondOp,
+							groupCondVal: $scope.policy.__.groupCondVal,
+							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
+						});
+					} else {
+						_windowStr += common.template(" select ${groupAgg}(${groupAggPath}) as ${groupAggAlias} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
+							groupAgg: $scope.policy.__.groupAgg,
+							groupAggPath: $scope.policy.__.groupAggPath,
+							groupCondOp: $scope.policy.__.groupCondOp,
+							groupCondVal: $scope.policy.__.groupCondVal,
+							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
+						});
+					}
+				} else {
+					_windowStr = " select *";
+				}
+
+				return common.template("from ${stream}${query}${window} insert into outputStream;", {
+					stream: $scope.policy.__.streamName,
+					query: _query,
+					window: _windowStr
+				});
+			};
+
+			// ==========================================
+			// =             Update Policy              =
+			// ==========================================
+			// dedupeDef notificationDef policyDef
+			$scope.finishPolicy = function() {
+				$scope.lock = true;
+
+				// dedupeDef
+				$scope.policy.__.dedupe.fields = $.map($scope.policy.__._dedupTags, function (value, key) {
+					if(value) return key;
+				});
+				$scope.policy.dedupeDef = JSON.stringify($scope.policy.__.dedupe);
+
+				// notificationDef
+				$scope.policy.__.notification = $scope.policy.__.notification || [];
+
+				$scope.policy.notificationDef = JSON.stringify($scope.policy.__.notification);
+
+				// policyDef
+				$scope.policy.__.policy = {
+					expression: $scope.toQuery(),
+					type: "siddhiCEPEngine"
+				};
+				$scope.policy.policyDef = JSON.stringify($scope.policy.__.policy);
+
+				// alertExecutorId
+				if($scope._stream.alertExecutor) {
+					$scope.policy.tags.alertExecutorId = $scope._stream.alertExecutor.tags.alertExecutorId;
+				} else {
+					$scope.lock = false;
+					$.dialog({
+						title: "OPS!",
+						content: "Alert Executor not defined! Please check 'AlertExecutorService'!"
+					});
+					return;
+				}
+
+				// site
+				$scope.policy.tags.site = $scope.policy.tags.site || Site.current().tags.site;
+
+				// owner
+				$scope.policy.owner = Authorization.userProfile.username;
+
+				// Update function
+				function _updatePolicy() {
+					Entities.updateEntity("AlertDefinitionService", $scope.policy)._promise.success(function(data) {
+						$.dialog({
+							title: "Success",
+							content: (create ? "Create" : "Update") + " success!"
+						}, function() {
+							if(data.success) {
+								location.href = "#/common/policyList";
+							} else {
+								$.dialog({
+									title: "OPS",
+									content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
+								});
+							}
+						});
+
+						$scope.create = create = false;
+						$scope.encodedRowkey = data.obj[0];
+					}).error(function(data) {
+						$.dialog({
+							title: "OPS",
+							content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
+						});
+					}).then(function() {
+						$scope.lock = false;
+					});
+				}
+
+				// Check if already exist
+				if($scope.create) {
+					var _checkList = Entities.queryEntities("AlertDefinitionService", {
+						alertExecutorId: $scope.policy.tags.alertExecutorId,
+						policyId: $scope.policy.tags.policyId,
+						policyType: "siddhiCEPEngine",
+						application: $scope.policy.tags.application
+					});
+					_checkList._promise.then(function() {
+						if(_checkList.length) {
+							$.dialog({
+								title: "Override Confirm",
+								content: "Already exists PolicyID '" + $scope.policy.tags.policyId + "'. Do you want to override?",
+								confirm: true
+							}, function(ret) {
+								if(ret) {
+									_updatePolicy();
+								} else {
+									$scope.lock = false;
+									$scope.$apply();
+								}
+							});
+						} else {
+							_updatePolicy();
+						}
+					});
+				} else {
+					_updatePolicy();
+				}
+			};
+		});
+	}
+
+	feature.controller('policyCreate', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
+		var _args = [true];
+		_args.push.apply(_args, arguments);
+		policyCtrl.apply(this, _args);
+	}, "policyEdit");
+	feature.controller('policyEdit', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
+		PageConfig.lockSite = true;
+		var _args = [false];
+		_args.push.apply(_args, arguments);
+		policyCtrl.apply(this, _args);
+	});
+
+	// ==============================================================
+	// =                           Alerts                           =
+	// ==============================================================
+
+	// ========================= Alert List =========================
+	feature.navItem("alertList", "Alerts", "exclamation-triangle");
+	feature.controller('alertList', function(PageConfig, Site, $scope, $wrapState, $interval, $timeout, Entities, Application) {
+		PageConfig.pageSubTitle = Site.current().tags.site;
+
+		var MAX_PAGESIZE = 10000;
+
+		// Initial load
+		$scope.application = Application.current();
+
+		$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().tags.site,
+				application: $scope.application.tags.application,
+				_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 ========================
+	feature.controller('alertDetail', function(PageConfig, Site, $scope, $wrapState, Entities) {
+		PageConfig.pageTitle = "Alert Detail";
+		PageConfig.lockSite = true;
+		PageConfig
+			.addNavPath("Alert List", "/common/alertList")
+			.addNavPath("Alert Detail");
+
+		$scope.common = common;
+
+		// Query policy
+		$scope.alertList = Entities.queryEntity("AlertService", $wrapState.param.filter);
+		$scope.alertList._promise.then(function() {
+			if($scope.alertList.length === 0) {
+				$.dialog({
+					title: "OPS!",
+					content: "Alert not found!"
+				}, function() {
+					location.href = "#/common/alertList";
+				});
+			} else {
+				$scope.alert = $scope.alertList[0];
+				$scope.alert.rawAlertContext = JSON.stringify($scope.alert.alertContext, null, "\t");
+				Site.current(Site.find($scope.alert.tags.site));
+				console.log($scope.alert);
+			}
+		});
+
+		// UI
+		$scope.getMessageTime = function(alert) {
+			var _time = common.getValueByPath(alert, "alertContext.timestamp");
+			return Number(_time);
+		};
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html
new file mode 100644
index 0000000..309fac3
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertDetail.html
@@ -0,0 +1,67 @@
+<!--
+  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.
+  -->
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title" id="policyId">
+			{{alert.tags.policyId}}
+			<small>{{common.format.date(alert.timestamp)}}</small>
+		</h3>
+	</div><!-- /.box-header -->
+
+	<div class="box-body">
+		<a class="btn btn-primary pull-right" href="#/common/policyDetail/?policy={{alert.tags.policyId}}&site={{alert.tags.site}}&executor={{alert.tags.alertExecutorId}}">View Policy</a>
+
+		<div class="inline-group">
+			<dl><dt>Site</dt><dd>{{alert.tags.site}}</dd></dl>
+			<dl><dt>Data Source</dt><dd>{{alert.tags.application}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Alert Time</dt><dd>{{common.format.date(alert.timestamp)}}</dd></dl>
+			<dl><dt>Message Time</dt><dd>{{common.format.date(alert.alertContext.properties.timestamp)}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Stream Name</dt><dd>{{alert.tags.sourceStreams}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Alert Source</dt><dd>{{alert.tags.alertSource}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>User</dt><dd>{{alert.alertContext.properties.user}}</dd></dl>
+			<dl><dt>Host</dt><dd>{{alert.alertContext.properties.host}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Event</dt><dd>{{alert.alertContext.properties.alertEvent}}</dd></dl>
+		</div>
+		<div class="inline-group">
+			<dl><dt>Message</dt><dd>{{alert.alertContext.properties.alertMessage}}</dd></dl>
+		</div>
+	</div><!-- /.box-body -->
+
+	<div class="overlay" ng-hide="alertList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+
+	<div class="box-footer clearfix">
+		<a data-toggle="collapse" href="[data-id='rawAlertContext']">
+			Raw Alert Context
+		</a>
+		<div data-id="rawAlertContext" class="collapse">
+			<pre>{{alert.rawAlertContext}}</pre>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html
new file mode 100644
index 0000000..0415cc0
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/alertList.html
@@ -0,0 +1,67 @@
+<!--
+  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.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			{{application.displayName}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<p ng-show="!alertList.ready">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="alertList" sort="-timestamp">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="170" sortpath="timestamp">Alert Time</th>
+						<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
+						<th width="105" sortpath="tags.application">Application</th>
+						<th width="150" sortpath="tags.policyId">Policy Name</th>
+						<th width="60" sortpath="alertContext.properties.user">User</th>
+						<th width="150" sortpath="tags.alertSource">Source</th>
+						<th sortpath="alertContext.properties.emailMessage">Description</th>
+						<th width="50"> </th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-class="{info : item.__new}">
+						<td>{{common.format.date(item.timestamp)}}</td>
+						<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
+						<td>{{item.tags.application}}</td>
+						<td class="text-nowrap">
+							<a class="fa fa-share-square-o" ng-show="item.tags.policyId"
+							href="#/common/policyDetail/?policy={{item.tags.policyId}}&site={{item.tags.site}}&executor={{item.tags.alertExecutorId}}"> </a>
+							{{item.tags.policyId}}
+						</td>
+						<td>{{item.alertContext.properties.user}}</td>
+						<td>{{item.tags.alertSource}}</td>
+						<td>{{item.alertContext.properties.alertMessage}}</td>
+						<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+
+	</div>
+	<!--div class="box-footer clearfix">
+	</div-->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html
new file mode 100644
index 0000000..cdddc43
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyDetail.html
@@ -0,0 +1,173 @@
+<!--
+  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.
+  -->
+
+<div class="box box-info">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			{{policy.tags.policyId}}
+			<small>{{policy.tags.site}}</small>
+		</h3>
+	</div>
+
+	<div class="box-body">
+		<div class="row">
+			<div class="col-xs-8">
+				<div class="inline-group">
+					<dl><dt>Data Source</dt><dd>{{policy.tags.application}}</dd></dl>
+					<dl><dt>Status</dt><dd>
+						<span ng-show="policy.enabled" class="text-muted"><i class="fa fa-square text-green"></i> Enabled</span>
+						<span ng-show="!policy.enabled" class="text-muted"><i class="fa fa-square text-muted"></i> Disabled</span>
+					</dd></dl>
+				</div>
+				<div class="inline-group">
+					<dl><dt>Description</dt><dd>{{policy.description}}</dd></dl>
+				</div>
+				<!--div class="inline-group">
+					<dl><dt>Alert</dt><dd>
+						<a href="mailto:{{mail}}" ng-repeat="mail in policy.__mailList track by $index" style="margin-right: 10px;">{{mail}}</a>
+						<div tabs>
+							<pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}">
+							</pane>
+						</div>
+					</dd></dl>
+				</div-->
+				<label>Notification</label>
+				<div tabs>
+					<pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}">
+						<table class="table table-bordered">
+							<tbody>
+								<tr ng-repeat="(key, value) in notification track by $index">
+									<th width="30%">{{key}}</th>
+									<td>{{value}}</td>
+								</tr>
+							</tbody>
+						</table>
+					</pane>
+				</div>
+			</div>
+			<div class="col-xs-4 text-right" ng-show="Auth.isRole('ROLE_ADMIN')">
+				<a class="btn btn-primary" href="#/common/policyEdit/{{policy.encodedRowkey}}">Edit</a>
+				<button class="btn btn-warning" ng-show="!policy.enabled" ng-click="updatePolicyStatus(policy, true)">Enable</button>
+				<button class="btn btn-warning" ng-show="policy.enabled" ng-click="updatePolicyStatus(policy, false)">Disable</button>
+				<button class="btn btn-danger" ng-click="deletePolicy(policy)">Delete</button>
+			</div>
+		</div>
+	</div>
+
+	<div class="overlay" ng-hide="policyList._promise.$$state.status === 1;">
+		<i class="fa fa-refresh fa-spin"></i>
+	</div>
+
+	<div class="box-footer clearfix">
+		<a data-toggle="collapse" href="[data-id='query']">
+			View Query
+		</a>
+		<div data-id="query" class="collapse in">
+			<pre>{{policy.__expression}}</pre>
+		</div>
+	</div>
+</div>
+
+<div tabs>
+	<pane data-title="Visualization" menu="visualizationMenu">
+		<div class="row">
+			<div class="col-xs-6">
+				<div nvd3="policyEvalSeries" data-title="Policy Eval Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+			</div>
+			<div class="col-xs-6">
+				<div nvd3="policyEvalFailSeries" data-title="Policy Eval Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+			</div>
+			<div class="col-xs-6">
+				<div nvd3="alertSeries" data-title="Alert Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+			</div>
+			<div class="col-xs-6">
+				<div nvd3="alertFailSeries" data-title="Alert Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
+			</div>
+		</div>
+	</pane>
+	<pane data-title="Statistics">
+		<div class="row">
+			<div class="col-xs-3">
+				<div class="info-box bg-aqua">
+					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+					<div class="info-box-content">
+						<span class="info-box-text">Policy Eval Count</span>
+						<span class="info-box-number">{{common.array.sum(policyEvalSeries, "1")}} <small>(Monthly)</small></span>
+						<span class="info-box-number">{{policyEvalSeries[policyEvalSeries.length - 1][1]}} <small>(Daily)</small></span>
+					</div>
+				</div>
+			</div>
+			<div class="col-xs-3">
+				<div class="info-box bg-red">
+					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+					<div class="info-box-content">
+						<span class="info-box-text">Policy Eval Fail Count</span>
+						<span class="info-box-number">{{common.array.sum(policyEvalFailSeries, "1")}} <small>(Monthly)</small></span>
+						<span class="info-box-number">{{policyEvalFailSeries[policyEvalFailSeries.length - 1][1]}} <small>(Daily)</small></span>
+					</div>
+				</div>
+			</div>
+			<div class="col-xs-3">
+				<div class="info-box bg-aqua">
+					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+					<div class="info-box-content">
+						<span class="info-box-text">Alert Count</span>
+						<span class="info-box-number">{{common.array.sum(alertSeries, "1")}} <small>(Monthly)</small></span>
+						<span class="info-box-number">{{alertSeries[alertSeries.length - 1][1]}} <small>(Daily)</small></span>
+					</div>
+				</div>
+			</div>
+			<div class="col-xs-3">
+				<div class="info-box bg-red">
+					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
+					<div class="info-box-content">
+						<span class="info-box-text">Alert Fail Count</span>
+						<span class="info-box-number">{{common.array.sum(alertFailSeries, "1")}} <small>(Monthly)</small></span>
+						<span class="info-box-number">{{alertFailSeries[alertFailSeries.length - 1][1]}} <small>(Daily)</small></span>
+					</div>
+				</div>
+			</div>
+		</div>
+	</pane>
+	<pane data-title="Alerts">
+		<div sorttable source="alertList" sort="-timestamp">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+				<tr>
+					<th width="170" sortpath="timestamp">Alert Time</th>
+					<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
+					<th width="60" sortpath="alertContext.properties.user">User</th>
+					<th width="150" sortpath="alertContext.properties.host">Host</th>
+					<th sortpath="alertContext.properties.emailMessage">Description</th>
+					<th width="50"> </th>
+				</tr>
+				</thead>
+				<tbody>
+				<tr ng-class="{info : item.__new}">
+					<td>{{common.format.date(item.timestamp)}}</td>
+					<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
+					<td>{{item.alertContext.properties.user}}</td>
+					<td>{{item.alertContext.properties.host}}</td>
+					<td>{{item.alertContext.properties.alertMessage}}</td>
+					<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
+				</tr>
+				</tbody>
+			</table>
+		</div>
+	</pane>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html
new file mode 100644
index 0000000..33d4cde
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyEdit.html
@@ -0,0 +1,346 @@
+<!--
+  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.
+  -->
+<div class="progress active" ng-show="!streamReady">
+	<div class="progress-bar progress-bar-primary progress-bar-striped" style="width: 100%">
+	</div>
+</div>
+
+<!-- Step navigation -->
+<div ng-show="streamReady">
+	<div class="row step-cntr">
+		<div class="col-sm-4" ng-repeat="step in steps">
+			<div class="step" ng-class="stepSelect($index + 1)">
+				<h1>{{$index + 1}}</h1>
+				<h2>Step {{$index + 1}}</h2>
+				<p title="{{step.title}}">{{step.title}}</p>
+			</div>
+		</div>
+	</div>
+
+	<!-- Step container -->
+	<div class="box box-info">
+		<div class="box-header with-border">
+			<h3 class="box-title">Step {{step}} - {{steps[step - 1].title}}</h3>
+		</div><!-- /.box-header -->
+
+		<div class="box-body">
+			<!-- ---------------------- Step Body Start ---------------------- -->
+
+			<!-- Step 1: Stream -->
+			<div ng-show="step === 1">
+				<div class="pull-right" ng-show="policy.__.advanced === undefined">
+					<span class="text-muted">or</span>
+					<a ng-click="policy.__.advanced = true;">Advanced</a>
+				</div>
+
+				<div class="form-group">
+					<label>Select Stream</label>
+					<select class="form-control" ng-model="policy.__.streamName" ng-show="!policy.__.advanced">
+						<option ng-repeat="stream in applications[policy.tags.application]">{{stream.tags.streamName}}</option>
+					</select>
+					<select class="form-control" ng-show="policy.__.advanced" disabled="disabled">
+						<option>[Advanced Mode]</option>
+					</select>
+				</div>
+
+				<div class="checkbox" ng-show="policy.__.advanced !== undefined">
+					<label>
+						<input type="checkbox" ng-model="policy.__.advanced">
+						Advanced Mode
+					</label>
+				</div>
+			</div>
+
+			<!-- Step 2: Define Alert Policy -->
+			<div ng-show="step === 2 && !policy.__.advanced">
+				<!-- Criteria -->
+				<div>
+					<label>Match Criteria</label>
+					<a ng-click="collapse('.panel-group')">expand / collapse all</a>
+
+					<div class="panel-group panel-group-sm" role="tablist">
+						<div class="panel panel-default"
+							ng-repeat="meta in _stream.metas"
+							ng-init="op = '=='; val = null; type = (meta.attrType || '').toLowerCase();">
+							<div class="panel-heading" role="tab">
+								<h4 class="panel-title">
+									<span class="bg-navy disabled color-palette pull-right">
+										{{parseConditionDesc(meta.tags.attrName)}}
+									</span>
+
+									<a role="button" data-toggle="collapse" href="[data-name='{{meta.tags.attrName}}']" class="collapsed">
+										<span class="fa fa-square" ng-class="hasCondition(meta.tags.attrName, type) ? 'text-green' : 'text-muted'"> </span>
+										{{meta.attrDisplayName || meta.tags.attrName}}
+										<span class="fa fa-question-circle" ng-show="meta.attrDescription"
+										uib-tooltip="{{meta.attrDescription}}" tooltip-placement="right" tooltip-animation="false"> </span>
+									</a>
+								</h4>
+							</div>
+							<div data-name="{{meta.tags.attrName}}" data-type="{{meta.attrType}}" role="tabpanel" class="collapse">
+								<div class="panel-body">
+									<ul ng-show="type !== 'bool'">
+										<li ng-repeat="cond in policy.__.conditions[meta.tags.attrName]">
+											[<a ng-click="policy.__.conditions[meta.tags.attrName].splice($index, 1)">X</a>]
+											{{cond.toString()}}
+										</li>
+									</ul>
+
+									<!-- String -->
+									<div ng-if="type == 'string'">
+										<div class="input-group" style="max-width: 450px;">
+											<div class="input-group-btn">
+												<select class="form-control" ng-model="op">
+													<option ng-repeat="mark in ['==','!=','contains','regex']">{{mark}}</option>
+												</select>
+											</div>
+
+											<!-- With resolver -->
+											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="meta.attrValueResolver"
+												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"
+												uib-typeahead="item for item in resolverTypeahead($viewValue, meta.attrValueResolver)">
+											<!-- Without resolver -->
+											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="!meta.attrValueResolver"
+												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
+
+											<span class="input-group-btn">
+												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type);val=null;">Add</button>
+											</span>
+										</div>
+									</div>
+
+									<!-- Number -->
+									<div ng-if="type == 'long' || type == 'integer' || type == 'number' || type == 'double' || type == 'float'">
+										<div class="input-group" style="max-width: 450px;">
+											<div class="input-group-btn">
+												<select class="form-control" ng-model="op">
+													<option ng-repeat="mark in ['==','!=','>','>=','<','<=']">{{mark}}</option>
+												</select>
+											</div>
+
+											<input type="number" class="form-control" autocomplete="off" placeholder="Number Only..." ng-model="val" ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
+											<span class="input-group-btn">
+												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type) ? val=null : void(0);">Add</button>
+											</span>
+										</div>
+									</div>
+
+									<!-- Boolean -->
+									<div ng-if="type == 'bool'" ng-init="policy.__.conditions[meta.tags.attrName] = policy.__.conditions[meta.tags.attrName] || [_CondUnit(meta.tags.attrName, '==', 'none', 'bool')]">
+										<select class="form-control" ng-model="policy.__.conditions[meta.tags.attrName][0].val" style="max-width: 100px;">
+											<option ng-repeat="bool in ['none','true','false']">{{bool}}</option>
+										</select>
+									</div>
+								</div>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<!-- Window -->
+				<div class="checkbox">
+					<label>
+						<input type="checkbox" ng-checked="policy.__.windowConfig" ng-click="policy.__.windowConfig = !policy.__.windowConfig"> Slide Window
+					</label>
+				</div>
+				<div ng-show="policy.__.windowConfig">
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Window</label>
+								<select class="form-control" ng-model="policy.__.window"
+								uib-tooltip="{{getWindow().description}}" tooltip-animation="false">
+									<option ng-repeat="item in config.window" value="{{item.type}}">{{item.title}}</option>
+								</select>
+							</div>
+						</div>
+
+						<!-- fields -->
+						<div class="col-md-4" ng-repeat="field in getWindow().fields" ng-init="field.val = field.val || (field.defaultValue || '');" ng-hide="field.hide">
+							<div class="form-group" ng-class="{'has-warning' : !field.val || !field.val.match(field.regex)}">
+								<label>Window - {{field.title}}</label>
+								<input type="text" class="form-control" autocomplete="off" placeholder="{{field.description}}" ng-model="field.val" title="{{field.description}}">
+							</div>
+						</div>
+					</div>
+
+					<!-- Aggregation -->
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group" ng-class="{'text-yellow' : (policy.__.groupAgg && !policy.__.groupAggPath)}">
+								<label>Aggregation</label>
+								<div class="input-group">
+									<div class="input-group-btn">
+										<select class="form-control" ng-model="policy.__.groupAgg" ng-change="updateGroupAgg()">
+											<option ng-repeat="op in ['max','min','avg','count', 'sum']">{{op}}</option>
+										</select>
+									</div>
+									<select class="form-control" ng-model="policy.__.groupAggPath" ng-class="{'has-warning' : !policy.__.groupAggPath}" id="groupAggPath"
+											ng-show="policy.__.groupAgg" ng-disabled="policy.__.groupAgg === 'count'">
+										<option ng-repeat="meta in groupAggPathList()">{{meta.tags.attrName}}</option>
+									</select>
+								</div>
+							</div>
+						</div>
+
+						<div class="col-md-4">
+							<div class="form-group" ng-class="{'text-yellow' : (!policy.__.groupCondOp || !policy.__.groupCondVal)}">
+								<label>Condition</label>
+								<div class="input-group">
+									<div class="input-group-btn">
+										<select class="form-control" ng-model="policy.__.groupCondOp" ng-class="{'has-warning' : !policy.__.groupCondOp}">
+											<option ng-repeat="op in ['>','<','>=','<=','==']">{{op}}</option>
+										</select>
+									</div>
+									<input type="text" class="form-control" ng-model="policy.__.groupCondVal" ng-class="{'has-warning' : !policy.__.groupCondVal}" />
+								</div>
+							</div>
+						</div>
+
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Alias (Optional)</label>
+								<input type="text" class="form-control" ng-model="policy.__.groupAggAlias" placeholder="Default: aggValue" />
+							</div>
+						</div>
+					</div>
+
+					<!-- Group -->
+					<div class="row">
+						<div class="col-md-4">
+							<div class="form-group">
+								<label>Group By</label>
+								<select class="form-control" ng-model="policy.__.group">
+									<option value="">None</option>
+									<option ng-repeat="meta in _stream.metas">{{meta.tags.attrName}}</option>
+								</select>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<!-- Step 2: Define Alert Policy -->
+			<div ng-show="step === 2 && policy.__.advanced">
+				<div class="form-group">
+					<label>Query Expression</label>
+					<textarea class="form-control" ng-model="policy.__._expression"
+					placeholder="Query expression. e.g. from hdfsAuditLogEventStream[(cmd=='open') and (host=='localhost' or host=='127.0.0.1')]#window.time(2 sec) select * insert into outputStream;" rows="5"></textarea>
+				</div>
+			</div>
+
+			<!-- Step 3: Email Notification -->
+			<div ng-show="step === 3">
+				<div class="row">
+					<div class="col-xs-4">
+						<div class="form-group" ng-class="{'has-warning' : !policy.tags.policyId}">
+							<label>Policy Name</label>
+							<input type="text" class="form-control" placeholder="" ng-model="policy.tags.policyId" ng-disabled="!create">
+						</div>
+					</div>
+					<div class="col-xs-3">
+						<div class="form-group">
+							<label>
+								Alert De-Dup Interval(min)
+								<span class="fa fa-question-circle" uib-tooltip="Same type alert will be De-dup in configured interval"> </span>
+							</label>
+							<input type="number" class="form-control" ng-model="policy.__.dedupe.alertDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 720">
+						</div>
+					</div>
+					<div class="col-xs-2">
+						<div class="form-group">
+							<label>
+								Enabled
+							</label>
+							<p>
+								<input type="checkbox" checked="checked" ng-model="policy.enabled">
+							</p>
+						</div>
+					</div>
+				</div>
+
+				<div>
+					<a data-toggle="collapse" href="[data-id='advancedDeDup']">Advanced De-Dup</a>
+					<div data-id='advancedDeDup' class="collapse">
+						<label>
+							De-Dup Key
+							<span class="fa fa-question-circle" uib-tooltip="Type of grouping alerts. If you don't know how to config, leave it default."> </span>
+						</label>
+						<div class="form-group">
+							<div class="checkbox" ng-repeat="meta in _stream.metas" ng-init="create ? policy.__._dedupTags[meta.tags.attrName] = !!meta.usedAsTag : void(0);">
+								<label>
+									<input type="checkbox" ng-model="policy.__._dedupTags[meta.tags.attrName]">
+									{{meta.tags.attrName}}
+								</label>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<hr/>
+
+				<label>Notification</label>
+				<div tabs menu="menu" holder="notificationTabHolder" ng-show="policy.__.notification.length">
+					<pane ng-repeat="notification in policy.__.notification track by $index" data-data="notification" data-title="{{notification.notificationType}}">
+						<div class="form-group" ng-repeat="field in Notification.map[notification.notificationType].fieldList track by $index">
+							<label>{{field.name}}</label>
+							<input type="text" class="form-control" ng-model="notification[field.name]">
+						</div>
+						<p class="text-muted" ng-if="Notification.map[notification.notificationType].fieldList.length === 0">No configuration required</p>
+					</pane>
+				</div>
+				<ul ng-show="policy.__.notification.length === 0">
+					<li ng-repeat="notification in Notification.list track by $index">
+						<a ng-click="newNotification(notification.tags.notificationType)">+ New {{notification.tags.notificationType}} Notification</a>
+					</li>
+				</ul>
+
+				<hr/>
+
+				<div class="form-group">
+					<label>Description</label>
+					<textarea class="form-control" placeholder="Policy description" ng-model="policy.description"></textarea>
+				</div>
+
+				<a data-toggle="collapse" href="[data-id='policyQuery']">
+					View Query
+				</a>
+				<div class="collapse in" data-id="policyQuery">
+					<pre>{{toQuery()}}</pre>
+				</div>
+			</div>
+
+			<!-- ----------------------- Step Body End ----------------------- -->
+		</div><!-- /.box-body -->
+
+		<div class="overlay" ng-hide="stepReady(step)">
+			<span class="fa fa-refresh fa-spin"> </span>
+		</div>
+
+		<div class="box-footer text-right">
+			<button class="btn btn-info" ng-show="step > 1" ng-click="changeStep(step, step - 1, false)" ng-disabled="lock">
+				Prev <span class="fa fa-arrow-circle-o-left"> </span>
+			</button>
+			<button class="btn btn-info" ng-show="step < steps.length" ng-click="changeStep(step, step + 1)" ng-disabled="checkNextable(step) || lock">
+				Next <span class="fa fa-arrow-circle-o-right"> </span>
+			</button>
+			<button class="btn btn-info" ng-show="step === steps.length" ng-click="finishPolicy()" ng-disabled="checkNextable(step) || lock">
+				Done <span class="fa fa-check-circle-o"> </span>
+			</button>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html
new file mode 100644
index 0000000..20a38dd
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/common/page/policyList.html
@@ -0,0 +1,84 @@
+<!--
+  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.
+  -->
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<i class="fa fa-list-alt"> </i>
+		<h3 class="box-title">
+			{{application.displayName}}
+		</h3>
+	</div>
+	<div class="box-body">
+		<div class="row">
+			<div class="col-xs-3">
+				<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 class="col-xs-6">
+				<div class="inline-group form-inline text-muted">
+					<dl><dt><i class="fa fa-square text-green"> </i></dt><dd>Enabled</dd></dl>
+					<dl><dt><i class="fa fa-square text-muted"> </i></dt><dd>Disabled</dd></dl>
+				</div>
+			</div>
+			<div class="col-xs-3 text-right">
+				<a class="btn btn-primary" href="#/common/policyCreate/{{!application ? '' : '?app=' + application.tags.application}}" ng-show="Auth.isRole('ROLE_ADMIN')">
+					New Policy
+					<i class="fa fa-plus-circle"> </i>
+				</a>
+			</div>
+		</div>
+
+		<p ng-show="policyList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div sorttable source="policyList" ng-show="policyList._promise.$$state.status === 1" search="false" searchfunc="searchFunc">
+			<table class="table table-bordered" ng-non-bindable>
+				<thead>
+					<tr>
+						<th width="30" sortpath="enabled"> </th>
+						<th width="200" sortpath="tags.policyId">Policy Name</th>
+						<th sortpath="description">Description</th>
+						<th width="150" sortpath="owner">Owner</th>
+						<th width="170" sortpath="lastModifiedDate">Last Modified</th>
+						<th width="95" ng-show="_parent.Auth.isRole('ROLE_ADMIN')">Action</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td><span class='fa fa-square' ng-class="item.enabled ? 'text-green' : 'text-muted'"> </span></td>
+						<td><a href="#/common/policyDetail/{{item.encodedRowkey}}" style="width: 200px;" class="text-breakall">{{item.tags.policyId}}</a></td>
+						<td>{{item.description}}</td>
+						<td>{{item.owner}}</td>
+						<td>{{common.format.date(item.lastModifiedDate) || "-"}}</td>
+						<td ng-show="_parent.Auth.isRole('ROLE_ADMIN')">
+							<a class="fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" href="#/common/policyEdit/{{item.encodedRowkey}}"> </a>
+							<button class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Enable" tooltip-animation="false" ng-show="!item.enabled" ng-click="_parent.updatePolicyStatus(item, true)"> </button>
+							<button class="fa fa-pause sm btn btn-default btn-xs" uib-tooltip="Disable" tooltip-animation="false" ng-show="item.enabled" ng-click="_parent.updatePolicyStatus(item, false)"> </button>
+							<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deletePolicy(item)"> </button>
+						</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+	<!--div class="box-footer clearfix">
+	</div-->
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.js
new file mode 100644
index 0000000..c17d612
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/controller.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() {
+	'use strict';
+
+	var featureControllers = angular.module('featureControllers');
+	var feature = featureControllers.register("metadata");
+
+	// ==============================================================
+	// =                          Function                          =
+	// ==============================================================
+
+	// ==============================================================
+	// =                          Metadata                          =
+	// ==============================================================
+
+	// ======================= Metadata List ========================
+	feature.navItem("streamList", "Metadata", "bullseye");
+	feature.controller('streamList', function(PageConfig, Site, $scope, $q, Application, Entities) {
+		PageConfig.hideSite = true;
+
+		$scope.streams = {};
+		$scope._streamEntity = null;
+		$scope._streamEntityLock = false;
+
+		// =========================================== List ===========================================
+		var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application});
+		var _streamSchemaList = Entities.queryEntities("AlertStreamSchemaService", {application: Application.current().tags.application});
+		$scope.streamList = _streamList;
+		$scope.streamSchemaList = _streamSchemaList;
+
+		_streamList._promise.then(function() {
+			$.each(_streamList, function(i, stream) {
+				stream.metaList = [];
+				$scope.streams[stream.tags.application + "_" + stream.tags.streamName] = stream;
+			});
+		});
+
+		$q.all([_streamList._promise, _streamSchemaList._promise]).then(function() {
+			$.each(_streamSchemaList, function(i, meta) {
+				var _stream = $scope.streams[meta.tags.application + "_" + meta.tags.streamName];
+				if(_stream) {
+					_stream.metaList.push(meta);
+				} else {
+					console.warn("[Meta] Stream not match:", meta.tags.application + "_" + meta.tags.streamName);
+				}
+			});
+		});
+	});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html
new file mode 100644
index 0000000..2b73300
--- /dev/null
+++ b/eagle-webservice/src/main/webapp/_app/public/feature/metadata/page/streamList.html
@@ -0,0 +1,84 @@
+<!--
+  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.
+  -->
+<p ng-show="streamList._promise.$$state.status !== 1">
+	<span class="fa fa-refresh fa-spin"> </span>
+	Loading...
+</p>
+
+<div class="box box-primary" ng-repeat="stream in streams">
+	<div class="box-header with-border">
+		<h3 class="box-title">{{stream.tags.streamName}}</h3>
+	</div>
+	<div class="box-body">
+		<div class="inline-group">
+			<dl>
+				<dt>
+					Data Source
+				</dt>
+				<dd>
+					{{stream.tags.application}}
+				</dd>
+			</dl>
+			<dl>
+				<dt>
+					Stream Name
+				</dt>
+				<dd>
+					{{stream.tags.streamName}}
+				</dd>
+			</dl>
+		</div>
+		<div>
+			<dl>
+				<dt>
+					Description
+				</dt>
+				<dd>
+					{{stream.description}}
+				</dd>
+			</dl>
+		</div>
+
+		<p ng-show="streamSchemaList._promise.$$state.status !== 1">
+			<span class="fa fa-refresh fa-spin"> </span>
+			Loading...
+		</p>
+
+		<div class="list" ng-show="streamSchemaList._promise.$$state.status === 1">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th width="10%">Attribute Name</th>
+						<th width="12%">Display Name</th>
+						<th width="8%">Type</th>
+						<th>Description</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-repeat="meta in stream.metaList">
+						<td>{{meta.tags.attrName}}</td>
+						<td>{{meta.attrDisplayName}}</td>
+						<td><span class="label label-warning">{{meta.attrType}}</span></td>
+						<td>{{meta.attrDescription}}</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div><!-- /.box-body -->
+	<!-- Loading (remove the following to stop the loading)-->
+</div>
\ No newline at end of file



[14/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
[EAGLE-574] UI refactor for support 0.5 api

Eagle 0.5 updates the rest api interface and UI need also update for support the api.

* Remove feature
* Support app provider
* Create JPM UI application
* Redesign site logic
* Widget support

Author: jiljiang <ji...@ebay.com>

Closes #460 from zombieJ/ui.


Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/afb89794
Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/afb89794
Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/afb89794

Branch: refs/heads/master
Commit: afb897940021caec79982e29f7194f2b6c733b49
Parents: 1fa490e
Author: zombiej <ji...@apache.org>
Authored: Wed Sep 28 13:38:17 2016 +0800
Committer: jiljiang <ji...@ebay.com>
Committed: Wed Sep 28 13:38:17 2016 +0800

----------------------------------------------------------------------
 .../webapp/app/apps/jpm/ctrl/compareCtrl.js     |  380 ++++++
 .../main/webapp/app/apps/jpm/ctrl/detailCtrl.js |  192 +++
 .../webapp/app/apps/jpm/ctrl/jobTaskCtrl.js     |  551 ++++++++
 .../main/webapp/app/apps/jpm/ctrl/listCtrl.js   |  239 ++++
 .../webapp/app/apps/jpm/ctrl/overviewCtrl.js    |  140 ++
 .../webapp/app/apps/jpm/ctrl/statisticCtrl.js   |  386 ++++++
 .../src/main/webapp/app/apps/jpm/index.js       |  489 +++++++
 .../app/apps/jpm/partials/job/compare.html      |  274 ++++
 .../app/apps/jpm/partials/job/detail.html       |  256 ++++
 .../webapp/app/apps/jpm/partials/job/list.html  |  131 ++
 .../app/apps/jpm/partials/job/overview.html     |  347 +++++
 .../app/apps/jpm/partials/job/statistic.html    |  120 ++
 .../webapp/app/apps/jpm/partials/job/task.html  |  149 +++
 .../main/webapp/app/apps/jpm/style/index.css    |   76 ++
 .../webapp/app/apps/jpm/widget/jobStatistic.js  |  108 ++
 eagle-server/.gitignore                         |    7 +
 eagle-server/pom.xml                            |   23 +-
 eagle-server/src/main/webapp/app/.editorconfig  |   27 +
 eagle-server/src/main/webapp/app/Gruntfile.js   |  190 +++
 eagle-server/src/main/webapp/app/README.md      |    4 +
 eagle-server/src/main/webapp/app/build/index.js |  144 +++
 eagle-server/src/main/webapp/app/dev/index.html |  250 ++++
 .../webapp/app/dev/partials/alert/list.html     |   21 +
 .../webapp/app/dev/partials/alert/main.html     |   29 +
 .../app/dev/partials/alert/policyEdit.back.html |  108 ++
 .../app/dev/partials/alert/policyEdit.html      |   29 +
 .../app/dev/partials/alert/policyList.html      |   63 +
 .../src/main/webapp/app/dev/partials/home.html  |   60 +
 .../partials/integration/applicationList.html   |   80 ++
 .../app/dev/partials/integration/main.html      |   29 +
 .../app/dev/partials/integration/site.html      |   95 ++
 .../app/dev/partials/integration/siteList.html  |   60 +
 .../dev/partials/integration/streamList.html    |   52 +
 .../src/main/webapp/app/dev/partials/setup.html |   46 +
 .../webapp/app/dev/public/css/animation.css     |   47 +
 .../src/main/webapp/app/dev/public/css/main.css |  317 +++++
 .../webapp/app/dev/public/css/sortTable.css     |   61 +
 .../webapp/app/dev/public/images/favicon.png    |  Bin 0 -> 4209 bytes
 .../app/dev/public/images/favicon_white.png     |  Bin 0 -> 1621 bytes
 .../src/main/webapp/app/dev/public/js/app.js    |  311 +++++
 .../src/main/webapp/app/dev/public/js/common.js |  387 ++++++
 .../app/dev/public/js/components/chart.js       |  188 +++
 .../webapp/app/dev/public/js/components/main.js |   24 +
 .../app/dev/public/js/components/sortTable.js   |  231 ++++
 .../app/dev/public/js/components/widget.js      |   52 +
 .../webapp/app/dev/public/js/ctrls/alertCtrl.js |  119 ++
 .../app/dev/public/js/ctrls/integrationCtrl.js  |  226 ++++
 .../main/webapp/app/dev/public/js/ctrls/main.js |   27 +
 .../webapp/app/dev/public/js/ctrls/mainCtrl.js  |   62 +
 .../webapp/app/dev/public/js/ctrls/siteCtrl.js  |   33 +
 .../src/main/webapp/app/dev/public/js/index.js  |  326 +++++
 .../dev/public/js/services/applicationSrv.js    |   71 +
 .../app/dev/public/js/services/entitySrv.js     |  135 ++
 .../webapp/app/dev/public/js/services/main.js   |   23 +
 .../app/dev/public/js/services/pageSrv.js       |  137 ++
 .../app/dev/public/js/services/siteSrv.js       |  111 ++
 .../app/dev/public/js/services/timeSrv.js       |  277 ++++
 .../webapp/app/dev/public/js/services/uiSrv.js  |  276 ++++
 .../app/dev/public/js/services/widgetSrv.js     |   79 ++
 .../app/dev/public/js/services/wrapStateSrv.js  |  119 ++
 .../app/dev/public/js/worker/sortTableFunc.js   |   93 ++
 .../app/dev/public/js/worker/sortTableWorker.js |   32 +
 eagle-server/src/main/webapp/app/index.html     |   11 +-
 eagle-server/src/main/webapp/app/package.json   |   47 +
 eagle-server/src/main/webapp/package.json       |    0
 eagle-server/ui-build.sh                        |   40 +
 eagle-webservice/src/main/webapp/Gruntfile.js   |  175 ---
 eagle-webservice/src/main/webapp/README.md      |    4 -
 .../src/main/webapp/_app/index.html             |  281 ++++
 .../_app/partials/config/application.html       |  124 ++
 .../webapp/_app/partials/config/feature.html    |   85 ++
 .../main/webapp/_app/partials/config/site.html  |  115 ++
 .../src/main/webapp/_app/partials/landing.html  |   30 +
 .../src/main/webapp/_app/partials/login.html    |   54 +
 .../main/webapp/_app/public/css/animation.css   |   46 +
 .../src/main/webapp/_app/public/css/main.css    |  805 ++++++++++++
 .../public/feature/classification/controller.js |  358 +++++
 .../classification/page/sensitivity.html        |   40 +
 .../classification/page/sensitivity/folder.html |  110 ++
 .../classification/page/sensitivity/job.html    |   92 ++
 .../classification/page/sensitivity/table.html  |  150 +++
 .../_app/public/feature/common/controller.js    | 1224 ++++++++++++++++++
 .../public/feature/common/page/alertDetail.html |   67 +
 .../public/feature/common/page/alertList.html   |   67 +
 .../feature/common/page/policyDetail.html       |  173 +++
 .../public/feature/common/page/policyEdit.html  |  346 +++++
 .../public/feature/common/page/policyList.html  |   84 ++
 .../_app/public/feature/metadata/controller.js  |   66 +
 .../feature/metadata/page/streamList.html       |   84 ++
 .../_app/public/feature/metrics/controller.js   |  571 ++++++++
 .../public/feature/metrics/page/dashboard.html  |  250 ++++
 .../_app/public/feature/topology/controller.js  |  257 ++++
 .../feature/topology/page/management.html       |   52 +
 .../feature/topology/page/monitoring.html       |  151 +++
 .../public/feature/userProfile/controller.js    |  268 ++++
 .../public/feature/userProfile/page/detail.html |   87 ++
 .../public/feature/userProfile/page/list.html   |  138 ++
 .../main/webapp/_app/public/images/favicon.png  |  Bin 0 -> 4209 bytes
 .../webapp/_app/public/images/favicon_white.png |  Bin 0 -> 1621 bytes
 .../main/webapp/_app/public/js/app.config.js    |  126 ++
 .../src/main/webapp/_app/public/js/app.js       |  499 +++++++
 .../src/main/webapp/_app/public/js/app.time.js  |   70 +
 .../src/main/webapp/_app/public/js/app.ui.js    |   76 ++
 .../src/main/webapp/_app/public/js/common.js    |  304 +++++
 .../_app/public/js/components/charts/line3d.js  |  348 +++++
 .../webapp/_app/public/js/components/file.js    |   50 +
 .../webapp/_app/public/js/components/main.js    |   19 +
 .../webapp/_app/public/js/components/nvd3.js    |  418 ++++++
 .../_app/public/js/components/sortTable.js      |  113 ++
 .../_app/public/js/components/sortable.js       |  166 +++
 .../webapp/_app/public/js/components/tabs.js    |  247 ++++
 .../_app/public/js/ctrl/authController.js       |   91 ++
 .../public/js/ctrl/configurationController.js   |  377 ++++++
 .../src/main/webapp/_app/public/js/ctrl/main.js |   42 +
 .../webapp/_app/public/js/srv/applicationSrv.js |  170 +++
 .../_app/public/js/srv/authorizationSrv.js      |  143 ++
 .../webapp/_app/public/js/srv/entitiesSrv.js    |  301 +++++
 .../src/main/webapp/_app/public/js/srv/main.js  |   72 ++
 .../main/webapp/_app/public/js/srv/pageSrv.js   |  131 ++
 .../main/webapp/_app/public/js/srv/siteSrv.js   |  193 +++
 .../src/main/webapp/_app/public/js/srv/uiSrv.js |  247 ++++
 .../webapp/_app/public/js/srv/wrapStateSrv.js   |  109 ++
 eagle-webservice/src/main/webapp/app/index.html |  281 ----
 .../webapp/app/partials/config/application.html |  124 --
 .../webapp/app/partials/config/feature.html     |   85 --
 .../main/webapp/app/partials/config/site.html   |  115 --
 .../src/main/webapp/app/partials/landing.html   |   30 -
 .../src/main/webapp/app/partials/login.html     |   54 -
 .../main/webapp/app/public/css/animation.css    |   46 -
 .../src/main/webapp/app/public/css/main.css     |  805 ------------
 .../public/feature/classification/controller.js |  358 -----
 .../classification/page/sensitivity.html        |   40 -
 .../classification/page/sensitivity/folder.html |  110 --
 .../classification/page/sensitivity/job.html    |   92 --
 .../classification/page/sensitivity/table.html  |  150 ---
 .../app/public/feature/common/controller.js     | 1224 ------------------
 .../public/feature/common/page/alertDetail.html |   67 -
 .../public/feature/common/page/alertList.html   |   67 -
 .../feature/common/page/policyDetail.html       |  173 ---
 .../public/feature/common/page/policyEdit.html  |  346 -----
 .../public/feature/common/page/policyList.html  |   84 --
 .../app/public/feature/metadata/controller.js   |   66 -
 .../feature/metadata/page/streamList.html       |   84 --
 .../app/public/feature/metrics/controller.js    |  571 --------
 .../public/feature/metrics/page/dashboard.html  |  250 ----
 .../app/public/feature/topology/controller.js   |  257 ----
 .../feature/topology/page/management.html       |   52 -
 .../feature/topology/page/monitoring.html       |  151 ---
 .../public/feature/userProfile/controller.js    |  268 ----
 .../public/feature/userProfile/page/detail.html |   87 --
 .../public/feature/userProfile/page/list.html   |  138 --
 .../main/webapp/app/public/images/favicon.png   |  Bin 4209 -> 0 bytes
 .../webapp/app/public/images/favicon_white.png  |  Bin 1621 -> 0 bytes
 .../src/main/webapp/app/public/js/app.config.js |  126 --
 .../src/main/webapp/app/public/js/app.js        |  499 -------
 .../src/main/webapp/app/public/js/app.time.js   |   70 -
 .../src/main/webapp/app/public/js/app.ui.js     |   76 --
 .../src/main/webapp/app/public/js/common.js     |  304 -----
 .../app/public/js/components/charts/line3d.js   |  348 -----
 .../webapp/app/public/js/components/file.js     |   50 -
 .../webapp/app/public/js/components/main.js     |   19 -
 .../webapp/app/public/js/components/nvd3.js     |  418 ------
 .../app/public/js/components/sortTable.js       |  113 --
 .../webapp/app/public/js/components/sortable.js |  166 ---
 .../webapp/app/public/js/components/tabs.js     |  247 ----
 .../webapp/app/public/js/ctrl/authController.js |   91 --
 .../public/js/ctrl/configurationController.js   |  377 ------
 .../src/main/webapp/app/public/js/ctrl/main.js  |   42 -
 .../webapp/app/public/js/srv/applicationSrv.js  |  170 ---
 .../app/public/js/srv/authorizationSrv.js       |  143 --
 .../webapp/app/public/js/srv/entitiesSrv.js     |  301 -----
 .../src/main/webapp/app/public/js/srv/main.js   |   72 --
 .../main/webapp/app/public/js/srv/pageSrv.js    |  131 --
 .../main/webapp/app/public/js/srv/siteSrv.js    |  193 ---
 .../src/main/webapp/app/public/js/srv/uiSrv.js  |  240 ----
 .../webapp/app/public/js/srv/wrapStateSrv.js    |  109 --
 eagle-webservice/src/main/webapp/grunt.json     |   42 -
 eagle-webservice/src/main/webapp/index.html     |   28 -
 eagle-webservice/src/main/webapp/package.json   |   48 -
 eagle-webservice/ui-build.sh                    |   23 +-
 180 files changed, 19503 insertions(+), 10801 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
new file mode 100644
index 0000000..121e80e
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js
@@ -0,0 +1,380 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		/**
+		 * @param {{}} $scope
+		 * @param {{}} $scope.trendChart
+		 */
+		jpmApp.controller("compareCtrl", function ($q, $wrapState, $scope, PageConfig, Time, Entity, JPM) {
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobDefId = $wrapState.param.jobDefId;
+
+			$scope.jobTrendCategory = [];
+
+			$scope.fromJob = null;
+			$scope.toJob = null;
+
+			PageConfig.title = "Job History";
+			PageConfig.subTitle = $scope.jobDefId;
+
+			$scope.getStateClass = JPM.getStateClass;
+
+			var browserAction = true;
+
+			// ==================================================================
+			// =                         Fetch Job List                         =
+			// ==================================================================
+			var jobList = $scope.jobList = JPM.findMRJobs($scope.site, $scope.jobDefId);
+			jobList._promise.then(function () {
+				if(jobList.length <= 1) {
+					$.dialog({
+						title: "No statistic info",
+						content: "Current job do not have enough statistic info. Please check 'jobDefId' if your job have run many times."
+					});
+				}
+
+				function findJob(jobId) {
+					return common.array.find(jobId, jobList, "tags.jobId");
+				}
+
+				var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000];
+				function taskDistribution(jobId) {
+					return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then(
+						/**
+						 * @param {{}} res
+						 * @param {{}} res.data
+						 * @param {[]} res.data.finishedTaskCount
+						 * @param {[]} res.data.runningTaskCount
+						 */
+						function (res) {
+							var result = {};
+							var data = res.data;
+							var finishedTaskCount = data.finishedTaskCount;
+							var runningTaskCount = data.runningTaskCount;
+
+							/**
+							 * @param {number} item.taskCount
+							 */
+							var finishedTaskData = $.map(finishedTaskCount, function (item) {
+								return item.taskCount;
+							});
+							/**
+							 * @param {number} item.taskCount
+							 */
+							var runningTaskData = $.map(runningTaskCount, function (item) {
+								return item.taskCount;
+							});
+
+							result.taskSeries = [{
+								name: "Finished Tasks",
+								type: "bar",
+								stack: jobId,
+								data: finishedTaskData
+							}, {
+								name: "Running Tasks",
+								type: "bar",
+								stack: jobId,
+								data: runningTaskData
+							}];
+
+							result.finishedTaskCount = finishedTaskCount;
+							result.runningTaskCount = runningTaskCount;
+
+							return result;
+						});
+				}
+
+				var taskDistributionCategory = $.map(TASK_BUCKET_TIMES, function (current, i) {
+					var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000);
+					var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000);
+
+					if(!curDes && nextDes) {
+						return "<" + nextDes;
+					} else if(nextDes) {
+						return curDes + "\n~\n" + nextDes;
+					}
+					return ">" + curDes;
+				});
+
+				// ========================= Job Trend ==========================
+				function refreshParam() {
+					browserAction = false;
+					$wrapState.go(".", {
+						from: common.getValueByPath($scope.fromJob, "tags.jobId"),
+						to: common.getValueByPath($scope.toJob, "tags.jobId")
+					});
+					setTimeout(function () {
+						browserAction = true;
+					}, 0);
+				}
+
+				function getMarkPoint(name, x, y, color) {
+					return {
+						name: name,
+						silent: true,
+						coord: [x, y],
+						symbolSize: 20,
+						label: {
+							normal: { show: false },
+							emphasis: { show: false }
+						}, itemStyle: {
+							normal: { color: color }
+						}
+					};
+				}
+
+				function refreshTrendMarkPoint() {
+					var fromX = null, fromY = null;
+					var toX = null, toY = null;
+					$.each(jobList, function (index, job) {
+						if($scope.fromJob && $scope.fromJob.tags.jobId === job.tags.jobId) {
+							fromX = index;
+							fromY = job.durationTime;
+						}
+						if($scope.toJob && $scope.toJob.tags.jobId === job.tags.jobId) {
+							toX = index;
+							toY = job.durationTime;
+						}
+					});
+
+					markPoint.data = [];
+					if(!common.isEmpty(fromX)) {
+						markPoint.data.push(getMarkPoint("<From Job>", fromX, fromY, "#00c0ef"));
+					}
+					if(!common.isEmpty(toX)) {
+						markPoint.data.push(getMarkPoint("<To Job>", toX, toY, "#3c8dbc"));
+					}
+
+					$scope.trendChart.refresh();
+				}
+
+				var jobListTrend = $.map(jobList, function (job) {
+					var time = Time.format(job.startTime);
+					$scope.jobTrendCategory.push(time);
+					return job.durationTime;
+				});
+
+				$scope.jobTrendOption = {
+					yAxis: [{
+						axisLabel: {
+							formatter: function (value) {
+								return Time.diffStr(value);
+							}
+						}
+					}],
+					tooltip: {
+						formatter: function (points) {
+							var point = points[0];
+							return point.name + "<br/>" +
+								'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+								point.seriesName + ": " + Time.diffStr(point.value);
+						}
+					}
+				};
+
+				var markPoint = {
+					data: [],
+					silent: true
+				};
+
+				$scope.jobTrendSeries = [{
+					name: "Job Duration",
+					type: "line",
+					data: jobListTrend,
+					symbolSize: 10,
+					showSymbol: false,
+					markPoint: markPoint
+				}];
+
+				$scope.compareJobSelect = function (e, job) {
+					var index = e.dataIndex;
+					job = job || jobList[index];
+					if(!job) return;
+
+					var event = e.event ? e.event.event : e;
+
+					if(event.ctrlKey) {
+						$scope.fromJob = job;
+					} else if(event.shiftKey) {
+						$scope.toJob = job;
+					} else {
+						if ($scope.fromJob) {
+							$scope.toJob = $scope.fromJob;
+						}
+						$scope.fromJob = job;
+					}
+
+					refreshTrendMarkPoint();
+					refreshParam();
+					refreshComparisonDashboard();
+				};
+
+				$scope.exchangeJobs = function () {
+					var tmpJob = $scope.fromJob;
+					$scope.fromJob = $scope.toJob;
+					$scope.toJob = tmpJob;
+					refreshTrendMarkPoint();
+					refreshParam();
+					refreshComparisonDashboard();
+				};
+
+				// ======================= Job Comparison =======================
+				$scope.taskOption = {
+					xAxis: {axisTick: { show: true }}
+				};
+
+				function getComparedValue(path) {
+					var val1 = common.getValueByPath($scope.fromJob, path);
+					var val2 = common.getValueByPath($scope.toJob, path);
+					return common.number.compare(val1, val2);
+				}
+
+				$scope.jobCompareClass = function (path) {
+					var diff = getComparedValue(path);
+					if(typeof diff !== "number" || Math.abs(diff) < 0.01) return "hide";
+					if(diff < 0.05) return "label label-success";
+					if(diff < 0.15) return "label label-warning";
+					return "label label-danger";
+				};
+
+				$scope.jobCompareValue = function (path) {
+					var diff = getComparedValue(path);
+					return (diff >= 0 ? "+" : "") + Math.floor(diff * 100) + "%";
+				};
+
+				/**
+				 * get 2 interval data list category. (minutes level)
+				 * @param {[]} list1
+				 * @param {[]} list2
+				 * @return {Array}
+				 */
+				function intervalCategories(list1, list2) {
+					var len = Math.max(list1.length, list2.length);
+					var category = [];
+					for(var i = 0 ; i < len ; i += 1) {
+						category.push((i + 1) + "min");
+					}
+					return category;
+				}
+
+				function refreshComparisonDashboard() {
+					if(!$scope.fromJob || !$scope.toJob) return;
+
+					var fromJobCond = {
+						jobId: $scope.fromJob.tags.jobId,
+						site: $scope.site
+					};
+					var toJobCond = {
+						jobId: $scope.toJob.tags.jobId,
+						site: $scope.site
+					};
+
+					var from_startTime = Time($scope.fromJob.startTime).subtract(1, "hour");
+					var from_endTime = ($scope.fromJob.endTime ? Time($scope.fromJob.endTime) : Time()).add(1, "hour");
+					var to_startTime = Time($scope.toJob.startTime).subtract(1, "hour");
+					var to_endTime = ($scope.toJob.endTime ? Time($scope.toJob.endTime) : Time()).add(1, "hour");
+
+					$scope.fromJob._cache = $scope.fromJob._cache || {};
+					$scope.toJob._cache = $scope.toJob._cache || {};
+
+					/**
+					 * Generate metric level chart series
+					 * @param {string} metric
+					 * @param {string} seriesName
+					 */
+					function metricComparison(metric, seriesName) {
+						var from_metric = $scope.fromJob._cache[metric] =
+							$scope.fromJob._cache[metric] || JPM.metrics(fromJobCond, metric, from_startTime, from_endTime);
+						var to_metric = $scope.toJob._cache[metric] =
+							$scope.toJob._cache[metric] || JPM.metrics(toJobCond, metric, to_startTime, to_endTime);
+
+						var holder = {};
+
+						$q.all([from_metric._promise, to_metric._promise]).then(function () {
+							from_metric = JPM.metricsToInterval(from_metric, 1000 * 60);
+							to_metric = JPM.metricsToInterval(to_metric, 1000 * 60);
+
+							var series_from = JPM.metricsToSeries("from job " + seriesName, from_metric, true);
+							var series_to = JPM.metricsToSeries("to job " + seriesName, to_metric, true);
+
+							holder.categories = intervalCategories(from_metric, to_metric);
+							holder.series = [series_from, series_to];
+						});
+
+						return holder;
+					}
+
+					// Dashboard1: Containers metrics
+					$scope.comparisonChart_Container = metricComparison("hadoop.job.runningcontainers", "running containers");
+
+					// Dashboard 2: Allocated
+					$scope.comparisonChart_allocatedMB = metricComparison("hadoop.job.allocatedmb", "allocated MB");
+
+					// Dashboard 3: vCores
+					$scope.comparisonChart_vCores = metricComparison("hadoop.job.allocatedvcores", "vCores");
+
+					// Dashboard 4: Task distribution
+					var from_distributionPromise = $scope.fromJob._cache.distributionPromise =
+						$scope.fromJob._cache.distributionPromise || taskDistribution($scope.fromJob.tags.jobId);
+					var to_distributionPromise = $scope.toJob._cache.distributionPromise =
+						$scope.toJob._cache.distributionPromise || taskDistribution($scope.toJob.tags.jobId);
+					var comparisonChart_taskDistribution = $scope.comparisonChart_taskDistribution = {
+						categories: taskDistributionCategory
+					};
+
+					$q.all([from_distributionPromise, to_distributionPromise]).then(function (args) {
+						var from_data = args[0];
+						var to_data = args[1];
+
+						var from_taskSeries = $.map(from_data.taskSeries, function (series) {
+							return $.extend({}, series, {name: "From " + series.name});
+						});
+						var to_taskSeries = $.map(to_data.taskSeries, function (series) {
+							return $.extend({}, series, {name: "To " + series.name});
+						});
+
+						comparisonChart_taskDistribution.series = from_taskSeries.concat(to_taskSeries);
+					});
+				}
+
+				// ======================== Job Refresh =========================
+				function jobRefresh() {
+					$scope.fromJob = findJob($wrapState.param.from);
+					$scope.toJob = findJob($wrapState.param.to);
+					$(window).resize();
+					refreshTrendMarkPoint();
+					refreshComparisonDashboard();
+				}
+
+				// ======================= Initialization =======================
+				jobRefresh();
+
+				$scope.$on('$locationChangeSuccess', function() {
+					if(browserAction) {
+						jobRefresh();
+					}
+				});
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
new file mode 100644
index 0000000..be7631f
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js
@@ -0,0 +1,192 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		jpmApp.controller("detailCtrl", function ($q, $wrapState, $element, $scope, PageConfig, Time, Entity, JPM) {
+			var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000];
+			var i;
+			var startTime, endTime;
+			var metric_allocatedMB, metric_allocatedVCores, metric_runningContainers;
+			var nodeTaskCountList;
+
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobId = $wrapState.param.jobId;
+
+			PageConfig.title = "Job";
+			PageConfig.subTitle = $scope.jobId;
+
+			$scope.getStateClass = JPM.getStateClass;
+			$scope.compareChart = null;
+
+			var jobCond = {
+				jobId: $scope.jobId,
+				site: $scope.site
+			};
+
+			function taskDistribution(jobId) {
+				return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then(
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.finishedTaskCount
+					 * @param {[]} res.data.runningTaskCount
+					 */
+					function (res) {
+						var result = {};
+						var data = res.data;
+						var finishedTaskCount = data.finishedTaskCount;
+						var runningTaskCount = data.runningTaskCount;
+
+						/**
+						 * @param {number} item.taskCount
+						 */
+						var finishedTaskData = $.map(finishedTaskCount, function (item) {
+							return item.taskCount;
+						});
+						/**
+						 * @param {number} item.taskCount
+						 */
+						var runningTaskData = $.map(runningTaskCount, function (item) {
+							return item.taskCount;
+						});
+
+						result.taskSeries = [{
+							name: "Finished Tasks",
+							type: "bar",
+							stack: jobId,
+							data: finishedTaskData
+						}, {
+							name: "Running Tasks",
+							type: "bar",
+							stack: jobId,
+							data: runningTaskData
+						}];
+
+						result.finishedTaskCount = finishedTaskCount;
+						result.runningTaskCount = runningTaskCount;
+
+						return result;
+					});
+			}
+
+			$scope.taskCategory = $.map(TASK_BUCKET_TIMES, function (current, i) {
+				var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000);
+				var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000);
+
+				if(!curDes && nextDes) {
+					return "<" + nextDes;
+				} else if(nextDes) {
+					return curDes + "\n~\n" + nextDes;
+				}
+				return ">" + curDes;
+			});
+
+			// =========================================================================
+			// =                               Fetch Job                               =
+			// =========================================================================
+			JPM.findMRJobs($scope.site, undefined, $scope.jobId)._promise.then(function (list) {
+				$scope.job = list[list.length - 1];
+				console.log("[JPM] Fetch job:", $scope.job);
+
+				if(!$scope.job) {
+					$.dialog({
+						title: "OPS!",
+						content: "Job not found!"
+					}, function () {
+						$wrapState.go("jpmList", {siteId: $scope.site});
+					});
+					return;
+				}
+
+				startTime = Time($scope.job.startTime).subtract(1, "hour");
+				endTime = Time().add(1, "hour");
+				$scope.startTimestamp = $scope.job.startTime;
+				$scope.endTimestamp = $scope.job.endTime;
+				$scope.isRunning = !$scope.job.currentState || ($scope.job.currentState || "").toUpperCase() === 'RUNNING';
+
+				// Dashboard 1: Allocated MB
+				metric_allocatedMB = JPM.metrics(jobCond, "hadoop.job.allocatedmb", startTime, endTime);
+
+				metric_allocatedMB._promise.then(function () {
+					var series_allocatedMB = JPM.metricsToSeries("allocated MB", metric_allocatedMB);
+					$scope.allocatedSeries = [series_allocatedMB];
+				});
+
+				// Dashboard 2: vCores & Containers metrics
+				metric_allocatedVCores = JPM.metrics(jobCond, "hadoop.job.allocatedvcores", startTime, endTime);
+				metric_runningContainers = JPM.metrics(jobCond, "hadoop.job.runningcontainers", startTime, endTime);
+
+				$q.all([metric_allocatedVCores._promise, metric_runningContainers._promise]).then(function () {
+					var series_allocatedVCores = JPM.metricsToSeries("vCores", metric_allocatedVCores);
+					var series_runningContainers = JPM.metricsToSeries("running containers", metric_runningContainers);
+					$scope.vCoresSeries = [series_allocatedVCores, series_runningContainers];
+				});
+
+				// Dashboard 3: Task duration
+				taskDistribution($scope.job.tags.jobId).then(function (data) {
+					$scope.taskSeries = data.taskSeries;
+
+					$scope.taskSeriesClick = function (e) {
+						var taskCount = e.seriesIndex === 0 ? data.finishedTaskCount : data.runningTaskCount;
+						$scope.taskBucket = taskCount[e.dataIndex];
+					};
+
+					$scope.backToTaskSeries = function () {
+						$scope.taskBucket = null;
+						setTimeout(function () {
+							$(window).resize();
+						}, 0);
+					};
+				});
+
+				// Dashboard 4: Running task
+				nodeTaskCountList = JPM.groups(
+					$scope.isRunning ? "RunningTaskExecutionService" : "TaskExecutionService",
+					jobCond, ["hostname"], "count", null, startTime, endTime, null, 1000000);
+				nodeTaskCountList._promise.then(function () {
+					var nodeTaskCountMap = [];
+
+					$.each(nodeTaskCountList, function (i, obj) {
+						var count = obj.value[0];
+						nodeTaskCountMap[count] = (nodeTaskCountMap[count] || 0) + 1;
+					});
+
+					$scope.nodeTaskCountCategory = [];
+					for(i = 0 ; i < nodeTaskCountMap.length ; i += 1) {
+						nodeTaskCountMap[i] = nodeTaskCountMap[i] || 0;
+						$scope.nodeTaskCountCategory.push(i + " tasks");
+					}
+
+					nodeTaskCountMap.splice(0, 1);
+					$scope.nodeTaskCountCategory.splice(0, 1);
+
+					$scope.nodeTaskCountSeries = [{
+						name: "node count",
+						type: "bar",
+						data: nodeTaskCountMap
+					}];
+				});
+
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
new file mode 100644
index 0000000..9f0e7f4
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js
@@ -0,0 +1,551 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var TREND_INTERVAL = 60;
+		var SCHEDULE_BUCKET_COUNT = 30;
+		var TASK_STATUS = ["SUCCEEDED", "FAILED", "KILLED"];
+		var TASK_TYPE = ["MAP", "REDUCE"];
+		var DURATION_BUCKETS = [0, 30 * 1000, 60 * 1000, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000, 2 * 3600 * 1000, 3 * 3600 * 1000];
+		var TASK_FIELDS = [
+			"rack",
+			"hostname",
+			"taskType",
+			"taskId",
+			"taskStatus",
+			"startTime",
+			"endTime",
+			"jobCounters"
+		];
+
+		function getCommonHeatMapOption(categoryList, maxCount) {
+			return {
+				animation: false,
+				tooltip: {
+					trigger: 'item'
+				},
+				xAxis: {splitArea: {show: true}},
+				yAxis: [{
+					type: 'category',
+					data: categoryList,
+					splitArea: {show: true},
+					axisTick: {show: false}
+				}],
+				grid: { bottom: "50" },
+				visualMap: {
+					min: 0,
+					max: maxCount,
+					calculable: true,
+					orient: 'horizontal',
+					left: 'right',
+					inRange: {
+						color: ["#00a65a", "#ffdc62", "#dd4b39"]
+					}
+				}
+			};
+		}
+
+		function getCommonHeatMapSeries(name, data) {
+			return {
+				name: name,
+				type: "heatmap",
+				data: data,
+				itemStyle: {
+					normal: {
+						borderColor: "#FFF"
+					}
+				}
+			};
+		}
+
+		/**
+		 * @typedef {{}} Task
+		 * @property {string} taskStatus
+		 * @property {number} startTime
+		 * @property {number} endTime
+		 * @property {{}} jobCounters
+		 * @property {{}} jobCounters.counters
+		 * @property {{}} tags
+		 * @property {string} tags.taskType
+		 * @property {number} _bucket
+		 * @property {number} _bucketStart
+		 * @property {number} _bucketEnd
+		 * @property {number} _duration
+		 * @property {number} _durationBucket
+		 */
+
+		jpmApp.controller("jobTaskCtrl", function ($wrapState, $scope, PageConfig, Time, JPM) {
+			$scope.site = $wrapState.param.siteId;
+			$scope.jobId = $wrapState.param.jobId;
+
+			var startTime = Number($wrapState.param.startTime);
+			var endTime = Number($wrapState.param.endTime);
+
+			PageConfig.title = "Task";
+			PageConfig.subTitle = $scope.jobId;
+
+			var timeDiff = endTime - startTime;
+			var timeDes = Math.ceil(timeDiff / SCHEDULE_BUCKET_COUNT);
+
+			$scope.bucketScheduleCategory = [];
+			for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) {
+				$scope.bucketScheduleCategory.push(Time.format(startTime + i * timeDes, "HH:mm:SS") + "\n~\n" + Time.format(startTime + (i + 1) * timeDes, "HH:mm:SS"));
+			}
+
+			$scope.bucketDurationCategory = [];
+			$.each(DURATION_BUCKETS, function (i, start) {
+				var end = DURATION_BUCKETS[i + 1];
+				if(!start) {
+					$scope.bucketDurationCategory.push("<" + Time.diffStr(end));
+				} else if(!end) {
+					$scope.bucketDurationCategory.push(">" + Time.diffStr(start));
+				} else {
+					$scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end));
+				}
+			});
+
+			// ============================================================================
+			// ============================================================================
+			// ==                               Fetch Task                               ==
+			// ============================================================================
+			// ============================================================================
+			$scope.list = JPM.list("TaskExecutionService", {site: $scope.site, jobId: $scope.jobId}, startTime, endTime, TASK_FIELDS, 1000000);
+			$scope.list._promise.then(function () {
+				var i;
+
+				// ========================= Schedule Trend =========================
+				var trend_map_countList = [];
+				var trend_reduce_countList = [];
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var _task = {
+							_bucketStart: Math.floor((task.startTime - startTime) / TREND_INTERVAL),
+							_bucketEnd: Math.floor((task.endTime - startTime) / TREND_INTERVAL)
+						};
+
+						switch (task.tags.taskType) {
+							case "MAP":
+								fillBucket(trend_map_countList, _task);
+								break;
+							case "REDUCE":
+								fillBucket(trend_reduce_countList, _task);
+								break;
+							default:
+								console.warn("Task type not match:", task.tags.taskType, task);
+						}
+					});
+
+				$scope.scheduleCategory = [];
+				for(i = 0 ; i < Math.max(trend_map_countList.length, trend_reduce_countList.length) ; i += 1) {
+					$scope.scheduleCategory.push(Time.format(startTime + i * TREND_INTERVAL).replace(" ", "\n"));
+				}
+
+				$scope.scheduleSeries = [{
+					name: "Map Task Count",
+					type: "line",
+					showSymbol: false,
+					areaStyle: {normal: {}},
+					data: trend_map_countList
+				}, {
+					name: "Reduce Task Count",
+					type: "line",
+					showSymbol: false,
+					areaStyle: {normal: {}},
+					data: trend_reduce_countList
+				}];
+
+				// ======================= Bucket Distribution ======================
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						task._bucketStart = Math.floor((task.startTime - startTime) / timeDes);
+						task._bucketEnd = Math.floor((task.endTime - startTime) / timeDes);
+						task._duration = task.endTime - task.startTime;
+						task._durationBucket = common.number.inRange(DURATION_BUCKETS, task._duration);
+					});
+
+				// ==================================================================
+				// =                      Schedule Distribution                     =
+				// ==================================================================
+				function fillBucket(countList, task, maxCount) {
+					for(var bucketId = task._bucketStart ; bucketId <= task._bucketEnd ; bucketId += 1) {
+						var count = countList[bucketId] = (countList[bucketId] || 0) + 1;
+						maxCount = Math.max(maxCount, count);
+					}
+					return maxCount;
+				}
+
+				function getHeatMapOption(categoryList, maxCount) {
+					var option = getCommonHeatMapOption(categoryList, maxCount);
+					return common.merge(option, {
+						tooltip: {
+							formatter: function (point) {
+								if(point.data) {
+									return categoryList[point.data[1]] + ":<br/>" +
+										'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+										$scope.bucketScheduleCategory[point.data[0]] + ": " +
+										point.data[2];
+								}
+								return "";
+							}
+						}
+					});
+				}
+
+				function bucketToSeries(categoryList, buckets, name) {
+					var bucket_data = $.map(categoryList, function (category, index) {
+						var list = [];
+						var dataList = buckets[category] || [];
+						for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) {
+							list.push([i, index, dataList[i] || 0]);
+						}
+						return list;
+					});
+
+					return [common.merge(getCommonHeatMapSeries(name, bucket_data), {
+						label: {
+							normal: {
+								show: true,
+								formatter: function (point) {
+									if(point.data[2] === 0) return "-";
+									return " ";
+								}
+							}
+						}
+					})];
+				}
+
+				// ======================== Status Statistic ========================
+				var bucket_status = {};
+				var bucket_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = bucket_status[task.taskStatus] = (bucket_status[task.taskStatus] || []);
+
+						bucket_status_maxCount = fillBucket(countList, task, bucket_status_maxCount);
+					});
+
+				$scope.statusSeries = bucketToSeries(TASK_STATUS, bucket_status, "Task Status");
+				$scope.statusOption = getHeatMapOption(TASK_STATUS, bucket_status_maxCount);
+
+				// ======================= Duration Statistic =======================
+				var TASK_DURATION = [0, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000];
+				var bucket_durations = {};
+				var bucket_durations_maxCount = 0;
+
+				var TASK_DURATION_DISTRIBUTION = $.map(TASK_DURATION, function (start, i) {
+					var end = TASK_DURATION[i + 1];
+					if(i === 0) {
+						return "<" + Time.diffStr(end);
+					} else if(end) {
+						return Time.diffStr(start) + "~" + Time.diffStr(end);
+					}
+					return ">" + Time.diffStr(start);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_DURATION_DISTRIBUTION[common.number.inRange(TASK_DURATION, task._duration)];
+						var countList = bucket_durations[durationBucket] = (bucket_durations[durationBucket] || []);
+
+						bucket_durations_maxCount = fillBucket(countList, task, bucket_durations_maxCount);
+					});
+
+				$scope.durationSeries = bucketToSeries(TASK_DURATION_DISTRIBUTION, bucket_durations, "Task Duration Distribution");
+				$scope.durationOption = getHeatMapOption(TASK_DURATION_DISTRIBUTION, bucket_durations_maxCount);
+
+				// ======================= HDFS Read Statistic ======================
+				var TASK_HDFS_BYTES = [0, 5 * 1024 * 1024, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024];
+				var bucket_hdfs_reads = {};
+				var bucket_hdfs_reads_maxCount = 0;
+
+				var TASK_HDFS_DISTRIBUTION = $.map(TASK_HDFS_BYTES, function (start, i) {
+					var end = TASK_HDFS_BYTES[i + 1];
+					if(i === 0) {
+						return "<" + common.number.abbr(end, true);
+					} else if(end) {
+						return common.number.abbr(start, true) + "~" + common.number.abbr(end, true);
+					}
+					return ">" + common.number.abbr(start, true);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)];
+						var countList = bucket_hdfs_reads[durationBucket] = (bucket_hdfs_reads[durationBucket] || []);
+
+						bucket_hdfs_reads_maxCount = fillBucket(countList, task, bucket_hdfs_reads_maxCount);
+					});
+
+				$scope.hdfsReadSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads, "Task HDFS Read Distribution");
+				$scope.hdfsReadOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads_maxCount);
+
+				// ====================== HDFS Write Statistic ======================
+				var bucket_hdfs_writes = {};
+				var bucket_hdfs_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)];
+						var countList = bucket_hdfs_writes[durationBucket] = (bucket_hdfs_writes[durationBucket] || []);
+
+						bucket_hdfs_writes_maxCount = fillBucket(countList, task, bucket_hdfs_writes_maxCount);
+					});
+
+				$scope.hdfsWriteSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes, "Task HDFS Write Distribution");
+				$scope.hdfsWriteOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes_maxCount);
+
+				// ====================== Local Read Statistic ======================
+				var TASK_LOCAL_BYTES = [0, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024];
+				var bucket_local_reads = {};
+				var bucket_local_reads_maxCount = 0;
+
+				var TASK_LOCAL_DISTRIBUTION = $.map(TASK_LOCAL_BYTES, function (start, i) {
+					var end = TASK_LOCAL_BYTES[i + 1];
+					if(i === 0) {
+						return "<" + common.number.abbr(end, true);
+					} else if(end) {
+						return common.number.abbr(start, true) + "~" + common.number.abbr(end, true);
+					}
+					return ">" + common.number.abbr(start, true);
+				});
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)];
+						var countList = bucket_local_reads[durationBucket] = (bucket_local_reads[durationBucket] || []);
+
+						bucket_local_reads_maxCount = fillBucket(countList, task, bucket_local_reads_maxCount);
+					});
+
+				$scope.localReadSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_reads, "Task Local Read Distribution");
+				$scope.localReadOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_reads_maxCount);
+
+				// ====================== Local Write Statistic =====================
+				var bucket_local_writes = {};
+				var bucket_local_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)];
+						var countList = bucket_local_writes[durationBucket] = (bucket_local_writes[durationBucket] || []);
+
+						bucket_local_writes_maxCount = fillBucket(countList, task, bucket_local_writes_maxCount);
+					});
+
+				$scope.localWriteSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_writes, "Task Local Write Distribution");
+				$scope.localWriteOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_writes_maxCount);
+
+				// ==================================================================
+				// =                      Duration Distribution                     =
+				// ==================================================================
+				function fillDurationBucket(countList, task, maxCount) {
+					var count = countList[task._durationBucket] = (countList[task._durationBucket] || 0) + 1;
+					maxCount = Math.max(maxCount, count);
+					return maxCount;
+				}
+
+				function getDurationHeatMapOption(categoryList, maxCount) {
+					var option = getCommonHeatMapOption(categoryList, maxCount);
+					return common.merge(option, {
+						tooltip: {
+							formatter: function (point) {
+								if(point.data) {
+									return categoryList[point.data[1]] + ":<br/>" +
+										'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+										$scope.bucketDurationCategory[point.data[0]] + ": " +
+										point.data[2];
+								}
+								return "";
+							}
+						}
+					});
+				}
+
+				function bucketToDurationSeries(categoryList, buckets, name) {
+					var bucket_data = $.map(categoryList, function (category, index) {
+						var list = [];
+						var dataList = buckets[category] || [];
+						for(var i = 0 ; i < DURATION_BUCKETS.length ; i += 1) {
+							list.push([i, index, dataList[i] || 0]);
+						}
+						return list;
+					});
+
+					return [common.merge(getCommonHeatMapSeries(name, bucket_data), {
+						label: {
+							normal: {
+								show: true,
+								formatter: function (point) {
+									if(point.data[2] === 0) return "-";
+									return point.data[2] + "";
+								}
+							}
+						}
+					})];
+				}
+
+				// ======================== Status Statistic ========================
+				var duration_status = {};
+				var duration_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = duration_status[task.taskStatus] = (duration_status[task.taskStatus] || []);
+
+						duration_status_maxCount = fillDurationBucket(countList, task, duration_status_maxCount);
+					});
+
+				$scope.durationStatusSeries = bucketToDurationSeries(TASK_STATUS, duration_status, "Task Status");
+				$scope.durationStatusOption = getDurationHeatMapOption(TASK_STATUS, duration_status_maxCount);
+
+				// ===================== Map / Reduce Statistic =====================
+				var mapReduce_status = {};
+				var mapReduce_status_maxCount = 0;
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var countList = mapReduce_status[task.tags.taskType] = (mapReduce_status[task.tags.taskType] || []);
+
+						mapReduce_status_maxCount = fillDurationBucket(countList, task, mapReduce_status_maxCount);
+					});
+
+				$scope.durationMapReduceSeries = bucketToDurationSeries(TASK_TYPE, mapReduce_status, "Task Type");
+				$scope.durationMapReduceOption = getDurationHeatMapOption(TASK_TYPE, mapReduce_status_maxCount);
+
+				// ======================= HDFS Read Statistic ======================
+				var duration_hdfs_reads = {};
+				var duration_hdfs_reads_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)];
+						var countList = duration_hdfs_reads[durationBucket] = (duration_hdfs_reads[durationBucket] || []);
+
+						duration_hdfs_reads_maxCount = fillDurationBucket(countList, task, duration_hdfs_reads_maxCount);
+					});
+
+				$scope.durationHdfsReadSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads, "Task HDFS Read Distribution");
+				$scope.durationHdfsReadOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads_maxCount);
+
+				// ====================== HDFS Write Statistic ======================
+				var duration_hdfs_writes = {};
+				var duration_hdfs_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)];
+						var countList = duration_hdfs_writes[durationBucket] = (duration_hdfs_writes[durationBucket] || []);
+
+						duration_hdfs_writes_maxCount = fillDurationBucket(countList, task, duration_hdfs_writes_maxCount);
+					});
+
+				$scope.durationHdfsWriteSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes, "Task HDFS Write Distribution");
+				$scope.durationHdfsWriteOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes_maxCount);
+
+				// ====================== Local Read Statistic ======================
+				var duration_local_reads = {};
+				var duration_local_reads_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)];
+						var countList = duration_local_reads[durationBucket] = (duration_local_reads[durationBucket] || []);
+
+						duration_local_reads_maxCount = fillDurationBucket(countList, task, duration_local_reads_maxCount);
+					});
+
+				$scope.durationLocalReadSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_reads, "Task Local Read Distribution");
+				$scope.durationLocalReadOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_reads_maxCount);
+
+				// ====================== Local Write Statistic =====================
+				var duration_local_writes = {};
+				var duration_local_writes_maxCount = 0;
+
+				$.each($scope.list,
+					/**
+					 * @param {number} i
+					 * @param {Task} task
+					 */
+					function (i, task) {
+						var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)];
+						var countList = duration_local_writes[durationBucket] = (duration_local_writes[durationBucket] || []);
+
+						duration_local_writes_maxCount = fillDurationBucket(countList, task, duration_local_writes_maxCount);
+					});
+
+				$scope.durationLocalWriteSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_writes, "Task Local Write Distribution");
+				$scope.durationLocalWriteOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_writes_maxCount);
+			});
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
new file mode 100644
index 0000000..ff9ed5e
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js
@@ -0,0 +1,239 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var JOB_STATES = ["NEW", "NEW_SAVING", "SUBMITTED", "ACCEPTED", "RUNNING", "FINISHED", "SUCCEEDED", "FAILED", "KILLED"];
+
+		jpmApp.controller("listCtrl", function ($wrapState, $element, $scope, $q, PageConfig, Time, Entity, JPM) {
+			// Initialization
+			PageConfig.title = "YARN Jobs";
+			$scope.getStateClass = JPM.getStateClass;
+			$scope.tableScope = {};
+
+			$scope.site = $wrapState.param.siteId;
+			$scope.searchPathList = [["tags", "jobId"], ["tags", "user"], ["tags", "queue"], ["currentState"]];
+
+			function getCommonOption(left) {
+				return {
+					grid: {
+						left: left,
+						bottom: 20,
+						containLabel: false
+					}
+				};
+			}
+
+			$scope.chartLeftOption = getCommonOption(45);
+			$scope.chartRightOption = getCommonOption(80);
+
+			$scope.fillSearch = function (key) {
+				$("#jobList").find(".search-box input").val(key).trigger('input');
+			};
+
+			$scope.refreshList = function () {
+				var startTime = Time.startTime();
+				var endTime = Time.endTime();
+
+				// ==========================================================
+				// =                        Job List                        =
+				// ==========================================================
+
+				/**
+				 * @namespace
+				 * @property {[]} jobList
+				 * @property {{}} jobList.tags						unique job key
+				 * @property {string} jobList.tags.jobId			Job Id
+				 * @property {string} jobList.tags.user				Submit user
+				 * @property {string} jobList.tags.queue			Queue
+				 * @property {string} jobList.currentState			Job state
+				 * @property {string} jobList.submissionTime		Submission time
+				 * @property {string} jobList.startTime				Start time
+				 * @property {string} jobList.endTime				End time
+				 * @property {string} jobList.numTotalMaps			Maps count
+				 * @property {string} jobList.numTotalReduces		Reduce count
+				 * @property {string} jobList.runningContainers		Running container count
+				 */
+
+				$scope.jobList = Entity.merge($scope.jobList, JPM.jobList({site: $scope.site}, startTime, endTime, [
+					"jobId",
+					"jobDefId",
+					"jobName",
+					"jobExecId",
+					"currentState",
+					"user",
+					"queue",
+					"submissionTime",
+					"startTime",
+					"endTime",
+					"numTotalMaps",
+					"numTotalReduces",
+					"runningContainers"
+				], 100000));
+				$scope.jobStateList = [];
+
+				$scope.jobList._then(function () {
+					var now = Time();
+					var jobStates = {};
+					$.each($scope.jobList, function (i, job) {
+						jobStates[job.currentState] = (jobStates[job.currentState] || 0) + 1;
+						job.duration = Time.diff(job.startTime, job.endTime || now);
+					});
+
+					$scope.jobStateList = $.map(JOB_STATES, function (state) {
+						var value = jobStates[state];
+						delete  jobStates[state];
+						if(!value) return null;
+						return {
+							key: state,
+							value: value
+						};
+					});
+
+					$.each(jobStates, function (key, value) {
+						$scope.jobStateList.push({
+							key: key,
+							value: value
+						});
+					});
+				});
+
+				// ===========================================================
+				// =                     Statistic Trend                     =
+				// ===========================================================
+				var interval = Time.diffInterval(startTime, endTime);
+				var intervalMin = interval / 1000 / 60;
+				var trendStartTime = Time.align(startTime, interval);
+				var trendEndTime = Time.align(endTime, interval);
+				var trendStartTimestamp = trendStartTime.valueOf();
+
+				// ==================== Running Job Trend ====================
+				JPM.get(JPM.getQuery("MR_JOB_COUNT"), {
+					site: $scope.site,
+					intervalInSecs: interval / 1000,
+					durationBegin: Time.format(trendStartTime),
+					durationEnd: Time.format(trendEndTime)
+				}).then(
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.jobCounts
+					 */
+					function (res) {
+						var data = res.data;
+						var jobCounts = data.jobCounts;
+						var jobTypesData = {};
+						$.each(jobCounts,
+							/**
+							 * @param index
+							 * @param {{}} jobCount
+							 * @param {{}} jobCount.timeBucket
+							 * @param {{}} jobCount.jobCountByType
+							 */
+							function (index, jobCount) {
+								$.each(jobCount.jobCountByType, function (type, count) {
+									var countList = jobTypesData[type] = jobTypesData[type] || [];
+									countList[index] = count;
+								});
+							});
+
+						$scope.runningTrendSeries = $.map(jobTypesData, function (countList, type) {
+							var dataList = [];
+							for(var i = 0 ; i < jobCounts.length ; i += 1) {
+								dataList[i] = {
+									x: trendStartTimestamp + i * interval,
+									y: countList[i] || 0
+								};
+							}
+
+							return {
+								name: type,
+								type: "line",
+								stack: "job",
+								showSymbol: false,
+								areaStyle: {normal: {}},
+								data: dataList
+							};
+						});
+					});
+
+				// ================= Running Container Trend =================
+				JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.runningcontainers", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true)._promise.then(function (list) {
+					$scope.runningContainersSeries = [JPM.metricsToSeries("Running Containers", list, {areaStyle: {normal: {}}})];
+				});
+
+				// ================= Allocated vCores Trend ==================
+				JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedvcores", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true)._promise.then(function (list) {
+					$scope.allocatedvcoresSeries = [JPM.metricsToSeries("Allocated vCores", list, {areaStyle: {normal: {}}})];
+				});
+
+				// ==================== AllocatedMB Trend ====================
+				var allocatedMBEntities = JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedmb", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true);
+
+				var totalMemoryEntities = JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.totalmemory", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime),
+					true);
+
+				$q.all([allocatedMBEntities._promise, totalMemoryEntities._promise]).then(function (args) {
+					var allocatedMBList = args[0];
+					var totalMemoryList = args[1];
+
+					var mergedList = $.map(allocatedMBList, function (obj, index) {
+						var value = obj.value[0] / totalMemoryList[index].value[0] * 100 || 0;
+						return $.extend({}, obj, {
+							value: [value]
+						});
+					});
+
+					$scope.allocatedMBSeries = [JPM.metricsToSeries("Allocated GB", mergedList, {areaStyle: {normal: {}}})];
+					$scope.allocatedMBOption = $.extend({}, $scope.chartRightOption, {
+						yAxis: [{
+							axisLabel: {
+								formatter: "{value}%"
+							},
+							max: 100
+						}],
+						tooltip: {
+							formatter: function (points) {
+								var point = points[0];
+								var index = point.dataIndex;
+								return point.name + "<br/>" +
+									'<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+									point.seriesName + ": " + common.number.format(allocatedMBList[index].value[0] / 1024, 2);
+							}
+						}
+					});
+				});
+			};
+
+			Time.onReload($scope.refreshList, $scope);
+
+			// Load list
+			$scope.refreshList();
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
new file mode 100644
index 0000000..7d7b949
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js
@@ -0,0 +1,140 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		jpmApp.controller("overviewCtrl", function ($q, $wrapState, $element, $scope, $timeout, PageConfig, Time, Entity, JPM) {
+			var cache = {};
+			$scope.aggregationMap = {
+				job: "jobId",
+				user: "user",
+				jobType: "jobType"
+			};
+
+			$scope.site = $wrapState.param.siteId;
+
+			PageConfig.title = "Overview";
+
+			$scope.type = "job";
+
+			$scope.commonOption = {
+				animation: false,
+				tooltip: {
+					formatter: function (points) {
+						return points[0].name + "<br/>" +
+								$.map(points, function (point) {
+									return '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' +
+											point.seriesName + ": " +
+										common.number.format(point.value, true);
+								}).reverse().join("<br/>");
+					}
+				},
+				grid: {
+					top: 70
+				},
+				yAxis: [{
+					axisLabel: {formatter: function (value) {
+						return common.number.abbr(value, true);
+					}}
+				}]
+			};
+
+			// ======================================================================
+			// =                          Refresh Overview                          =
+			// ======================================================================
+			$scope.typeChange = function () {
+				$timeout($scope.refresh, 1);
+			};
+
+			// TODO: Optimize the chart count
+			// TODO: ECharts dynamic refresh series bug: https://github.com/ecomfe/echarts/issues/4033
+			$scope.refresh = function () {
+				var startTime = Time.startTime();
+				var endTime = Time.endTime();
+				var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60;
+
+				function getTopList(metric, scopeVariable) {
+					var deferred = $q.defer();
+
+					metric = common.template(metric, {
+						type: $scope.type.toLocaleLowerCase()
+					});
+
+					if(scopeVariable) {
+						$scope[scopeVariable] = [];
+						$scope[scopeVariable]._done = false;
+						$scope[scopeVariable + "List"] = [];
+					}
+
+					var aggregation = $scope.aggregationMap[$scope.type];
+
+					var aggPromise = cache[metric] = cache[metric] || JPM.aggMetricsToEntities(
+						JPM.aggMetrics({site: $scope.site}, metric, [aggregation], "avg(value), sum(value) desc", intervalMin, startTime, endTime, 10)
+					)._promise.then(function (list) {
+						var series = $.map(list, function (metrics) {
+							return JPM.metricsToSeries(metrics[0].tags[aggregation], metrics, {
+								stack: "stack",
+								areaStyle: {normal: {}}
+							});
+						});
+
+						var topList = $.map(series, function (series) {
+							return {
+								name: series.name,
+								total: common.number.sum(series.data, "y") * intervalMin
+							};
+						}).sort(function (a, b) {
+							return b.total - a.total;
+						});
+
+						return [series, topList];
+					});
+
+					aggPromise.then(function (args) {
+						if(scopeVariable) {
+							$scope[scopeVariable] = args[0];
+							$scope[scopeVariable]._done = true;
+							$scope[scopeVariable + "List"] = args[1];
+						}
+					});
+
+					return deferred.promise;
+				}
+
+				getTopList("hadoop.${type}.history.minute.cpu_milliseconds", "cpuUsageSeries");
+				getTopList("hadoop.${type}.history.minute.physical_memory_bytes", "physicalMemorySeries");
+				getTopList("hadoop.${type}.history.minute.virtual_memory_bytes", "virtualMemorySeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_bytes_read", "hdfsBtyesReadSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_bytes_written", "hdfsBtyesWrittenSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_read_ops", "hdfsReadOpsSeries");
+				getTopList("hadoop.${type}.history.minute.hdfs_write_ops", "hdfsWriteOpsSeries");
+				getTopList("hadoop.${type}.history.minute.file_bytes_read", "fileBytesReadSeries");
+				getTopList("hadoop.${type}.history.minute.file_bytes_written", "fileBytesWrittenSeries");
+			};
+
+			Time.onReload(function () {
+				cache = {};
+				$scope.refresh();
+			}, $scope);
+			$scope.refresh();
+		});
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
new file mode 100644
index 0000000..6dff7a1
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js
@@ -0,0 +1,386 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		var colorMap = {
+			"SUCCEEDED": "#00a65a",
+			"FAILED": "#dd4b39",
+			"KILLED": "#CCCCCC",
+			"ERROR": "#f39c12"
+		};
+
+		var DURATION_BUCKETS = [0, 30, 60, 120, 300, 600, 1800, 3600, 2 * 3600, 3 * 3600];
+
+		jpmApp.controller("statisticCtrl", function ($wrapState, $element, $scope, PageConfig, Time, Entity, JPM, Chart) {
+			$scope.site = $wrapState.param.siteId;
+
+			PageConfig.title = "Job Statistics";
+
+			$scope.type = "hourly";
+
+			$scope.switchType = function (type) {
+				$scope.type = type;
+				$scope.refreshDistribution();
+			};
+
+			// ===============================================================
+			// =                   Time Level Distribution                   =
+			// ===============================================================
+			function parseDayBuckets(startTime, endTime) {
+				startTime = startTime.clone().hour(0).minute(0).second(0);
+				endTime = endTime.clone().hour(0).minute(0).second(0);
+
+				var _buckets = [];
+				var _start = startTime.clone();
+
+				do {
+					var _end = _start.clone().date(1).add(1, "month").date(0);
+					if (_end.isAfter(endTime)) {
+						_end = endTime.clone();
+					}
+					var _dayDes = moment.duration(_end.diff(_start)).asDays() + 1;
+					_buckets.push(_dayDes);
+
+					_start = _end.clone().add(1, "day");
+				} while (!_start.isAfter(endTime));
+
+				return _buckets;
+			}
+
+			var distributionCache = {};
+			$scope.distributionSelectedType = "";
+			$scope.distributionSelectedIndex = -1;
+
+			$scope.jobDistributionSeriesOption = {};
+
+			$scope.refreshDistribution = function () {
+				var type = $scope.type;
+				var startTime, endTime;
+				var metric, minInterval, field;
+				$scope.distributionSelectedIndex = -1;
+
+				switch (type) {
+					case "monthly":
+						endTime = Time("monthEnd");
+						startTime = endTime.clone().subtract(365, "day").date(1).hours(0).minutes(0).seconds(0);
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440;
+						field = "max(value)";
+						break;
+					case "weekly":
+						endTime = Time("weekEnd");
+						startTime = Time("week").subtract(7 * 12, "day");
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440 * 7;
+						field = "sum(value)";
+						break;
+					case "daily":
+						endTime = Time("dayEnd");
+						startTime = Time("day").subtract(31, "day");
+						metric = "hadoop.job.history.day.count";
+						minInterval = 1440;
+						field = "max(value)";
+						break;
+					case "hourly":
+						endTime = Time("hourEnd");
+						startTime = Time("day").subtract(2, "day");
+						metric = "hadoop.job.history.hour.count";
+						minInterval = 60;
+						field = "sum(value)";
+						break;
+				}
+
+				$scope.jobDistributionSeries = [];
+				$scope.jobDistributionCategoryFunc = function (value) {
+					if(type === "hourly") {
+						return Time.format(value, "HH:mm");
+					}
+					return Time.format(value, "MM-DD");
+				};
+				var promise = distributionCache[type] = distributionCache[type] || JPM.aggMetricsToEntities(
+					JPM.aggMetrics({site: $scope.site}, metric, ["jobStatus"], field, minInterval, startTime, endTime)
+				)._promise.then(function (list) {
+						if(type === "monthly") {
+							var buckets = parseDayBuckets(startTime, endTime);
+							list = $.map(list, function (units) {
+								// Merge by day buckets
+								units = units.concat();
+								return [$.map(buckets, function (dayCount) {
+									var subUnits = units.splice(0, dayCount);
+									var sum = common.number.sum(subUnits, ["value", 0]);
+
+									return $.extend({}, subUnits[0], {
+										value: [sum]
+									});
+								})];
+							});
+						}
+						return list;
+				}).then(function(list) {
+					/**
+					 * @param {Object[]}	metrics
+					 * @param {{}}			metrics[].tags
+					 * @param {string}		metrics[].tags.jobStatus
+					 */
+					var series = $.map(list, function (metrics) {
+						return JPM.metricsToSeries(metrics[0].tags.jobStatus, metrics, {
+							stack: "stack",
+							type: "bar",
+							itemStyle: {
+								normal: {
+									borderWidth: 2
+								}
+							}
+						});
+					});
+					common.array.doSort(series, "name", true, ["SUCCEEDED", "FAILED", "KILLED", "ERROR"]);
+					$scope.jobDistributionSeriesOption.color = $.map(series, function (series) {
+						return colorMap[series.name];
+					});
+
+					return series;
+				});
+
+				promise.then(function(series) {
+					$scope.jobDistributionSeries = series;
+				});
+			};
+
+			// ==============================================================
+			// =                         Drill Down                         =
+			// ==============================================================
+			$scope.commonChartOption = {
+				grid: {
+					left: 42,
+					bottom: 60,
+					containLabel: false
+				},
+				yAxis: [{
+					minInterval: 1
+				}],
+				xAxis: {
+					axisLabel: {
+						interval: 0
+					}
+				}
+			};
+			$scope.commonTrendChartOption = {
+				yAxis: [{
+					minInterval: 1
+				}],
+				grid: {
+					top: 60,
+					left: 42,
+					bottom: 20,
+					containLabel: false
+				}
+			};
+
+			$scope.topUserJobCountSeries = [];
+			$scope.topTypeJobCountSeries = [];
+
+			$scope.drillDownCategoryFunc = function (value) {
+				switch ($scope.type) {
+					case "monthly":
+						return Time.format(value, "MM-DD");
+					case "weekly":
+					case "daily":
+						return Time.format(value, "MM-DD HH:mm");
+					default:
+						return Time.format(value, "HH:mm");
+				}
+			};
+
+			$scope.bucketDurationCategory = [];
+			$.each(DURATION_BUCKETS, function (i, start) {
+				var end = DURATION_BUCKETS[i + 1];
+
+				start *= 1000;
+				end *= 1000;
+
+				if(!start) {
+					$scope.bucketDurationCategory.push("<" + Time.diffStr(end));
+				} else if(!end) {
+					$scope.bucketDurationCategory.push(">" + Time.diffStr(start));
+				} else {
+					$scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end));
+				}
+			});
+
+			function flattenTrendSeries(name, series) {
+				var len = series.length;
+				var category = [];
+				var data = [];
+				var needBreakLine = series.length > 6;
+				$.each(series, function (i, series) {
+					category.push((needBreakLine && i % 2 !== 0 ? "\n" : "") + series.name);
+					data.push(common.number.sum(series.data, ["y"]));
+				});
+				return {
+					category: category.reverse(),
+					series: [{
+						name: name,
+						data: data.reverse(),
+						type: "bar",
+						itemStyle: {
+							normal: {
+								color: function (point) {
+									return Chart.color[len - point.dataIndex - 1];
+								}
+							}
+						}
+					}]
+				};
+			}
+
+			$scope.distributionClick = function (event) {
+				if(event.componentType !== "series") return;
+
+				$scope.distributionSelectedType = event.seriesName;
+				$scope.distributionSelectedIndex = event.dataIndex;
+				var timestamp = 0;
+
+				// Highlight logic
+				$.each($scope.jobDistributionSeries, function (i, series) {
+					timestamp = series.data[$scope.distributionSelectedIndex].x;
+
+					common.merge(series, {
+						itemStyle: {
+							normal: {
+								color: function (point) {
+									if(point.seriesName === $scope.distributionSelectedType && point.dataIndex === $scope.distributionSelectedIndex) {
+										return "#60C0DD";
+									}
+									return colorMap[point.seriesName];
+								}
+							}
+						}
+					});
+				});
+
+				// Data fetch
+				var startTime = Time(timestamp);
+				var endTime;
+
+				switch ($scope.type) {
+					case "monthly":
+						endTime = startTime.clone().add(1, "month").subtract(1, "s");
+						break;
+					case "weekly":
+						endTime = startTime.clone().add(7, "day").subtract(1, "s");
+						break;
+					case "daily":
+						endTime = startTime.clone().add(1, "day").subtract(1, "s");
+						break;
+					case "hourly":
+						endTime = startTime.clone().add(1, "hour").subtract(1, "s");
+						break;
+				}
+
+				var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60;
+
+				// ===================== Top User Job Count =====================
+				$scope.topUserJobCountSeries = [];
+				$scope.topUserJobCountTrendSeries = [];
+				JPM.aggMetricsToEntities(
+					JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["user"], "count desc", intervalMin, startTime, endTime, 10, 1000000)
+				)._promise.then(function (list) {
+					$scope.topUserJobCountTrendSeries = $.map(list, function (subList) {
+						return JPM.metricsToSeries(subList[0].tags.user, subList, {
+							stack: "user",
+							areaStyle: {normal: {}}
+						});
+					});
+
+					var flatten = flattenTrendSeries("User", $scope.topUserJobCountTrendSeries);
+					$scope.topUserJobCountSeries = flatten.series;
+					$scope.topUserJobCountSeriesCategory = flatten.category;
+				});
+
+				// ===================== Top Type Job Count =====================
+				$scope.topTypeJobCountSeries = [];
+				$scope.topTypeJobCountTrendSeries = [];
+				JPM.aggMetricsToEntities(
+					JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["jobType"], "count desc", intervalMin, startTime, endTime, 10, 1000000)
+				)._promise.then(function (list) {
+					$scope.topTypeJobCountTrendSeries = $.map(list, function (subList) {
+						return JPM.metricsToSeries(subList[0].tags.jobType, subList, {
+							stack: "type",
+							areaStyle: {normal: {}}
+						});
+					});
+
+					var flatten = flattenTrendSeries("Job Type", $scope.topTypeJobCountTrendSeries);
+					$scope.topTypeJobCountSeries = flatten.series;
+					$scope.topTypeJobCountSeriesCategory = flatten.category;
+				});
+
+				if($scope.distributionSelectedType === "FAILED") {
+					// ====================== Failure Job List ======================
+					$scope.jobList = JPM.jobList({site: $scope.site, currentState: "FAILED"}, startTime, endTime, [
+						"jobId",
+						"jobName",
+						"user",
+						"startTime",
+						"jobType"
+					]);
+				} else {
+					// ============== Job Duration Distribution Count ===============
+					$scope.jobList = null;
+
+					$scope.jobDurationDistributionSeries = [];
+					/**
+					 * @param {{}} res
+					 * @param {{}} res.data
+					 * @param {[]} res.data.jobTypes
+					 * @param {[]} res.data.jobCounts
+					 */
+					JPM.jobDistribution($scope.site, $scope.type, DURATION_BUCKETS.join(","), startTime, endTime).then(function (res) {
+						var data = res.data;
+						var jobTypes = {};
+						$.each(data.jobTypes, function (i, type) {
+							jobTypes[type] = [];
+						});
+						$.each(data.jobCounts, function (index, statistic) {
+							$.each(statistic.jobCountByType, function (type, value) {
+								jobTypes[type][index] = value;
+							});
+						});
+
+						$scope.jobDurationDistributionSeries = $.map(jobTypes, function (list, type) {
+							return {
+								name: type,
+								data: list,
+								type: "bar",
+								stack: "jobType"
+							};
+						});
+					});
+				}
+
+				return true;
+			};
+
+			$scope.refreshDistribution();
+		});
+	});
+})();


[03/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js
deleted file mode 100644
index d717ad1..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/metrics/controller.js
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module('featureControllers');
-	var feature = featureControllers.register("metrics");
-
-	// ==============================================================
-	// =                       Initialization                       =
-	// ==============================================================
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-	// Format dashboard unit. Will adjust format with old version and add miss attributes.
-	feature.service("DashboardFormatter", function() {
-		return {
-			parse: function(unit) {
-				unit = unit || {};
-				unit.groups = unit.groups || [];
-
-				$.each(unit.groups, function (i, group) {
-					group.charts = group.charts || [];
-					$.each(group.charts, function (i, chart) {
-						if (!chart.metrics && chart.metric) {
-							chart.metrics = [{
-								aggregations: chart.aggregations,
-								dataSource: chart.dataSource,
-								metric: chart.metric
-							}];
-
-							delete chart.aggregations;
-							delete chart.dataSource;
-							delete chart.metric;
-						} else if (!chart.metrics) {
-							chart.metrics = [];
-						}
-					});
-				});
-
-				return unit;
-			}
-		};
-	});
-
-	// ==============================================================
-	// =                         Controller                         =
-	// ==============================================================
-
-	// ========================= Dashboard ==========================
-	feature.navItem("dashboard", "Metrics", "line-chart");
-
-	feature.controller('dashboard', function(PageConfig, $scope, $http, $q, UI, Site, Authorization, Application, Entities, DashboardFormatter) {
-		var _siteApp = Site.currentSiteApplication();
-		var _druidConfig = _siteApp.configObj.getValueByPath("web.druid");
-		var _refreshInterval;
-
-		var _menu_newChart;
-
-		$scope.lock = false;
-
-		$scope.dataSourceListReady = false;
-		$scope.dataSourceList = [];
-		$scope.dashboard = {
-			groups: []
-		};
-		$scope.dashboardEntity = null;
-		$scope.dashboardReady = false;
-
-		$scope._newMetricFilter = "";
-		$scope._newMetricDataSrc = null;
-		$scope._newMetricDataMetric = null;
-
-		$scope.tabHolder = {};
-
-		$scope.endTime = app.time.now();
-		$scope.startTime = $scope.endTime.clone();
-
-		// =================== Initialization ===================
-		if(!_druidConfig || !_druidConfig.coordinator || !_druidConfig.broker) {
-			$.dialog({
-				title: "OPS",
-				content: "Druid configuration can't be empty!"
-			});
-			return;
-		}
-
-		$scope.autoRefreshList = [
-			{title: "Last 1 Month", timeDes: "day", getStartTime: function(endTime) {return endTime.clone().subtract(1, "month");}},
-			{title: "Last 1 Day", timeDes: "thirty_minute", getStartTime: function(endTime) {return endTime.clone().subtract(1, "day");}},
-			{title: "Last 6 Hour", timeDes: "fifteen_minute", getStartTime: function(endTime) {return endTime.clone().subtract(6, "hour");}},
-			{title: "Last 2 Hour", timeDes: "fifteen_minute", getStartTime: function(endTime) {return endTime.clone().subtract(2, "hour");}},
-			{title: "Last 1 Hour", timeDes: "minute", getStartTime: function(endTime) {return endTime.clone().subtract(1, "hour");}}
-		];
-		$scope.autoRefreshSelect = $scope.autoRefreshList[2];
-
-		// ====================== Function ======================
-		$scope.setAuthRefresh = function(item) {
-			$scope.autoRefreshSelect = item;
-			$scope.refreshAllChart(true);
-		};
-
-		$scope.refreshTimeDisplay = function() {
-			PageConfig.pageSubTitle = common.format.date($scope.startTime) + " ~ " + common.format.date($scope.endTime) + " [refresh interval: 30s]";
-		};
-		$scope.refreshTimeDisplay();
-
-		// ======================= Metric =======================
-		// Fetch metric data
-		$http.get(_druidConfig.coordinator + "/druid/coordinator/v1/metadata/datasources", {withCredentials: false}).then(function(data) {
-			var _endTime = new moment();
-			var _startTime = _endTime.clone().subtract(1, "day");
-			var _intervals = _startTime.toISOString() + "/" + _endTime.toISOString();
-
-			$scope.dataSourceList = $.map(data.data, function(dataSrc) {
-				return {
-					dataSource: dataSrc,
-					metricList: []
-				};
-			});
-
-			// List dataSource metrics
-			var _metrixList_promiseList = $.map($scope.dataSourceList, function(dataSrc) {
-				var _data = JSON.stringify({
-					"queryType": "groupBy",
-					"dataSource": dataSrc.dataSource,
-					"granularity": "all",
-					"dimensions": ["metric"],
-					"aggregations": [
-						{
-							"type":"count",
-							"name":"count"
-						}
-					],
-					"intervals": [_intervals]
-				});
-
-				return $http.post(_druidConfig.broker + "/druid/v2", _data, {withCredentials: false}).then(function(response) {
-					dataSrc.metricList = $.map(response.data, function(entity) {
-						return entity.event.metric;
-					});
-				});
-			});
-
-			$q.all(_metrixList_promiseList).finally(function() {
-				$scope.dataSourceListReady = true;
-
-				$scope._newMetricDataSrc = $scope.dataSourceList[0];
-				$scope._newMetricDataMetric = common.getValueByPath($scope._newMetricDataSrc, "metricList.0");
-			});
-		}, function() {
-			$.dialog({
-				title: "OPS",
-				content: "Fetch data source failed. Please check Site Application Metrics configuration."
-			});
-		});
-
-		// Filter data source
-		$scope.dataSourceMetricList = function(dataSrc, filter) {
-			filter = (filter || "").toLowerCase().trim().split(/\s+/);
-			return $.grep((dataSrc && dataSrc.metricList) || [], function(metric) {
-				for(var i = 0 ; i < filter.length ; i += 1) {
-					if(metric.toLowerCase().indexOf(filter[i]) === -1) return false;
-				}
-				return true;
-			});
-		};
-
-		// New metric select
-		$scope.newMetricSelectDataSource = function(dataSrc) {
-			if(dataSrc !== $scope._newMetricDataMetric) $scope._newMetricDataMetric = dataSrc.metricList[0];
-			$scope._newMetricDataSrc = dataSrc;
-		};
-		$scope.newMetricSelectMetric = function(metric) {
-			$scope._newMetricDataMetric = metric;
-		};
-
-		// Confirm new metric
-		$scope.confirmSelectMetric = function() {
-			var group = $scope.tabHolder.selectedPane.data;
-			var metric = {
-				dataSource: $scope._newMetricDataSrc.dataSource,
-				metric: $scope._newMetricDataMetric,
-				aggregations: ["max"]
-			};
-			$("#metricMDL").modal('hide');
-
-			if($scope.metricForConfigChart) {
-				$scope.configPreviewChart.metrics.push(metric);
-				$scope.refreshChart($scope.configPreviewChart, true, true);
-			} else {
-				group.charts.push({
-					chart: "line",
-					metrics: [metric]
-				});
-				$scope.refreshAllChart();
-			}
-		};
-
-		// ======================== Menu ========================
-		function _checkGroupName(entity) {
-			if(common.array.find(entity.name, $scope.dashboard.groups, "name")) {
-				return "Group name conflict";
-			}
-		}
-
-		$scope.newGroup = function() {
-			if($scope.lock) return;
-
-			UI.createConfirm("Group", {}, [{field: "name"}], _checkGroupName).then(null, null, function(holder) {
-				$scope.dashboard.groups.push({
-					name: holder.entity.name,
-					charts: []
-				});
-				holder.closeFunc();
-
-				setTimeout(function() {
-					$scope.tabHolder.setSelect(holder.entity.name);
-				}, 0);
-			});
-		};
-
-		function renameGroup() {
-			var group = $scope.tabHolder.selectedPane.data;
-			UI.updateConfirm("Group", {}, [{field: "name", name: "New Name"}], _checkGroupName).then(null, null, function(holder) {
-				group.name = holder.entity.name;
-				holder.closeFunc();
-			});
-		}
-
-		function deleteGroup() {
-			var group = $scope.tabHolder.selectedPane.data;
-			UI.deleteConfirm(group.name).then(null, null, function(holder) {
-				common.array.remove(group, $scope.dashboard.groups);
-				holder.closeFunc();
-			});
-		}
-
-		_menu_newChart = {title: "Add Metric", func: function() {$scope.newChart();}};
-		Object.defineProperties(_menu_newChart, {
-			icon: {
-				get: function() {return $scope.dataSourceListReady ? 'plus' : 'refresh fa-spin';}
-			},
-			disabled: {
-				get: function() {return !$scope.dataSourceListReady;}
-			}
-		});
-
-		$scope.menu = Authorization.isRole('ROLE_ADMIN') ? [
-			{icon: "cog", title: "Configuration", list: [
-				_menu_newChart,
-				{icon: "pencil", title: "Rename Group", func: renameGroup},
-				{icon: "trash", title: "Delete Group", danger: true, func: deleteGroup}
-			]},
-			{icon: "plus", title: "New Group", func: $scope.newGroup}
-		] : [];
-
-		// ===================== Dashboard ======================
-		$scope.dashboardList = Entities.queryEntities("GenericResourceService", {
-			site: Site.current().tags.site,
-			application: Application.current().tags.application
-		});
-		$scope.dashboardList._promise.then(function(list) {
-			$scope.dashboardEntity = list[0];
-			$scope.dashboard = DashboardFormatter.parse(common.parseJSON($scope.dashboardEntity.value));
-			$scope.refreshAllChart();
-		}).finally(function() {
-			$scope.dashboardReady = true;
-		});
-
-		$scope.saveDashboard = function() {
-			$scope.lock = true;
-
-			if(!$scope.dashboardEntity) {
-				$scope.dashboardEntity = {
-					tags: {
-						site: Site.current().tags.site,
-						application: Application.current().tags.application,
-						name: "/metric_dashboard/dashboard/default"
-					}
-				};
-			}
-			$scope.dashboardEntity.value = common.stringify($scope.dashboard);
-
-			Entities.updateEntity("GenericResourceService", $scope.dashboardEntity)._promise.then(function() {
-				$.dialog({
-					title: "Done",
-					content: "Save success!"
-				});
-			}, function() {
-				$.dialog({
-					title: "POS",
-					content: "Save failed. Please retry."
-				});
-			}).finally(function() {
-				$scope.lock = false;
-			});
-		};
-
-		// ======================= Chart ========================
-		$scope.configTargetChart = null;
-		$scope.configPreviewChart = null;
-		$scope.metricForConfigChart = false;
-		$scope.viewChart = null;
-
-		$scope.chartConfig = {
-			xType: "time"
-		};
-
-		$scope.chartTypeList = [
-			{icon: "line-chart", chart: "line"},
-			{icon: "area-chart", chart: "area"},
-			{icon: "bar-chart", chart: "column"},
-			{icon: "pie-chart", chart: "pie"}
-		];
-
-		$scope.chartSeriesList = [
-			{name: "Min", series: "min"},
-			{name: "Max", series: "max"},
-			{name: "Avg", series: "avg"},
-			{name: "Count", series: "count"},
-			{name: "Sum", series: "sum"}
-		];
-
-		$scope.newChart = function() {
-			$scope.metricForConfigChart = false;
-			$("#metricMDL").modal();
-		};
-
-		$scope.configPreviewChartMinimumCheck = function() {
-			$scope.configPreviewChart.min = $scope.configPreviewChart.min === 0 ? undefined : 0;
-		};
-
-		$scope.seriesChecked = function(metric, series) {
-			if(!metric) return false;
-			return $.inArray(series, metric.aggregations || []) !== -1;
-		};
-		$scope.seriesCheckClick = function(metric, series, chart) {
-			if(!metric || !chart) return;
-			if($scope.seriesChecked(metric, series)) {
-				common.array.remove(series, metric.aggregations);
-			} else {
-				metric.aggregations.push(series);
-			}
-			$scope.chartSeriesUpdate(chart);
-		};
-
-		$scope.chartSeriesUpdate = function(chart) {
-			chart._data = $.map(chart._oriData, function(groupData, i) {
-				var metric = chart.metrics[i];
-				return $.map(groupData, function(series) {
-					if($.inArray(series._key, metric.aggregations) !== -1) return series;
-				});
-			});
-		};
-
-		$scope.configAddMetric = function() {
-			$scope.metricForConfigChart = true;
-			$("#metricMDL").modal();
-		};
-
-		$scope.configRemoveMetric = function(metric) {
-			common.array.remove(metric, $scope.configPreviewChart.metrics);
-		};
-
-		$scope.getChartConfig = function(chart) {
-			if(!chart) return null;
-
-			var _config = chart._config = chart._config || $.extend({}, $scope.chartConfig);
-			_config.yMin = chart.min;
-
-			return _config;
-		};
-
-		$scope.configChart = function(chart) {
-			$scope.configTargetChart = chart;
-			$scope.configPreviewChart = $.extend({}, chart);
-			$scope.configPreviewChart.metrics = $.map(chart.metrics, function(metric) {
-				return $.extend({}, metric, {aggregations: (metric.aggregations || []).slice()});
-			});
-			delete $scope.configPreviewChart._config;
-			$("#chartMDL").modal();
-			setTimeout(function() {
-				$(window).resize();
-			}, 200);
-		};
-
-		$scope.confirmUpdateChart = function() {
-			$("#chartMDL").modal('hide');
-			$.extend($scope.configTargetChart, $scope.configPreviewChart);
-			$scope.chartSeriesUpdate($scope.configTargetChart);
-			if($scope.configTargetChart._holder) $scope.configTargetChart._holder.refreshAll();
-			$scope.configPreviewChart = null;
-		};
-
-		$scope.deleteChart = function(group, chart) {
-			UI.deleteConfirm(chart.metric).then(null, null, function(holder) {
-				common.array.remove(chart, group.charts);
-				holder.closeFunc();
-				$scope.refreshAllChart(false, true);
-			});
-		};
-
-		$scope.showChart = function(chart) {
-			$scope.viewChart = chart;
-			$("#chartViewMDL").modal();
-			setTimeout(function() {
-				$(window).resize();
-			}, 200);
-		};
-
-		$scope.refreshChart = function(chart, forceRefresh, refreshAll) {
-			var _intervals = $scope.startTime.toISOString() + "/" + $scope.endTime.toISOString();
-
-			function _refreshChart() {
-				if (chart._holder) {
-					if (refreshAll) {
-						chart._holder.refreshAll();
-					} else {
-						chart._holder.refresh();
-					}
-				}
-			}
-
-			var _tmpData, _metricPromiseList;
-
-			if (chart._data && !forceRefresh) {
-				// Refresh chart without reload
-				_refreshChart();
-			} else {
-				// Refresh chart with reload
-				_tmpData = [];
-				_metricPromiseList = $.map(chart.metrics, function (metric, k) {
-					// Each Metric
-					var _query = JSON.stringify({
-						"queryType": "groupBy",
-						"dataSource": metric.dataSource,
-						"granularity": $scope.autoRefreshSelect.timeDes,
-						"dimensions": ["metric"],
-						"filter": {"type": "selector", "dimension": "metric", "value": metric.metric},
-						"aggregations": [
-							{
-								"type": "max",
-								"name": "max",
-								"fieldName": "maxValue"
-							},
-							{
-								"type": "min",
-								"name": "min",
-								"fieldName": "maxValue"
-							},
-							{
-								"type": "count",
-								"name": "count",
-								"fieldName": "maxValue"
-							},
-							{
-								"type": "longSum",
-								"name": "sum",
-								"fieldName": "maxValue"
-							}
-						],
-						"postAggregations" : [
-							{
-								"type": "javascript",
-								"name": "avg",
-								"fieldNames": ["sum", "count"],
-								"function": "function(sum, cnt) { return sum / cnt;}"
-							}
-						],
-						"intervals": [_intervals]
-					});
-
-					return $http.post(_druidConfig.broker + "/druid/v2", _query, {withCredentials: false}).then(function (response) {
-						var _data = nvd3.convert.druid([response.data]);
-						_tmpData[k] = _data;
-
-						// Process series name
-						$.each(_data, function(i, series) {
-							series._key = series.key;
-							if(chart.metrics.length > 1) {
-								series.key = metric.metric.replace(/^.*\./, "") + "-" +series._key;
-							}
-						});
-					});
-				});
-
-				$q.all(_metricPromiseList).then(function() {
-					chart._oriData = _tmpData;
-					$scope.chartSeriesUpdate(chart);
-					_refreshChart();
-				});
-			}
-		};
-
-		$scope.refreshAllChart = function(forceRefresh, refreshAll) {
-			setTimeout(function() {
-				$scope.endTime = app.time.now();
-				$scope.startTime = $scope.autoRefreshSelect.getStartTime($scope.endTime);
-
-				$scope.refreshTimeDisplay();
-
-				$.each($scope.dashboard.groups, function (i, group) {
-					$.each(group.charts, function (j, chart) {
-						$scope.refreshChart(chart, forceRefresh, refreshAll);
-					});
-				});
-
-				$(window).resize();
-			}, 0);
-		};
-
-		$scope.chartSwitchRefresh = function(source, target) {
-			var _oriSize = source.size;
-			source.size = target.size;
-			target.size = _oriSize;
-
-			if(source._holder) source._holder.refreshAll();
-			if(target._holder) target._holder.refreshAll();
-
-		};
-
-		_refreshInterval = setInterval(function() {
-			if(!$scope.dashboardReady) return;
-			$scope.refreshAllChart(true);
-		}, 1000 * 30);
-
-		// > Chart UI
-		$scope.configChartSize = function(chart, sizeOffset) {
-			chart.size = (chart.size || 6) + sizeOffset;
-			if(chart.size <= 0) chart.size = 1;
-			if(chart.size > 12) chart.size = 12;
-			setTimeout(function() {
-				$(window).resize();
-			}, 1);
-		};
-
-		// ========================= UI =========================
-		$("#metricMDL").on('hidden.bs.modal', function () {
-			if($(".modal-backdrop").length) {
-				$("body").addClass("modal-open");
-			}
-		});
-
-		$("#chartViewMDL").on('hidden.bs.modal', function () {
-			$scope.viewChart = null;
-		});
-
-		// ====================== Clean Up ======================
-		$scope.$on('$destroy', function() {
-			clearInterval(_refreshInterval);
-		});
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html b/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html
deleted file mode 100644
index 2acb954..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/metrics/page/dashboard.html
+++ /dev/null
@@ -1,250 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="page-fixed">
-	<div class="dropdown inline">
-		<button data-toggle="dropdown" class="btn btn-primary">
-			<span class="fa fa-clock-o"></span> {{autoRefreshSelect.title}}
-		</button>
-		<ul class="dropdown-menu left">
-			<li ng-repeat="item in autoRefreshList track by $index">
-				<a ng-click="setAuthRefresh(item)">
-					<span class="fa fa-clock-o"></span>
-					<span ng-class="{'text-bold': item === autoRefreshSelect}">{{item.title}}</span>
-				</a>
-			</li>
-		</ul>
-	</div>
-
-	<button class="btn btn-primary" ng-if="Auth.isRole('ROLE_ADMIN')"
-			ng-click="saveDashboard()" ng-disabled="lock">
-		<span class="fa fa-floppy-o"></span> Save
-	</button>
-</div>
-
-<div class="box box-default" ng-if="!dashboard.groups.length">
-	<div class="box-header with-border">
-		<h3 class="box-title">Empty Dashboard</h3>
-	</div>
-	<div class="box-body">
-		<div ng-show="!dashboardReady">
-			Loading...
-		</div>
-		<div ng-show="dashboardReady">
-			Current dashboard is empty.
-			<span ng-if="Auth.isRole('ROLE_ADMIN')">Click <a ng-click="newGroup()">here</a> to create a new group.</span>
-		</div>
-	</div>
-	<div class="overlay" ng-show="!dashboardReady">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-</div>
-
-<div tabs menu="menu" holder="tabHolder" ng-show="dashboard.groups.length" data-sortable-model="Auth.isRole('ROLE_ADMIN') ? dashboard.groups : null">
-	<pane ng-repeat="group in dashboard.groups" data-data="group" data-title="{{group.name}}">
-		<div uie-sortable ng-model="group.charts" class="row narrow" sortable-update-func="chartSwitchRefresh" ng-show="group.charts.length">
-			<div ng-repeat="chart in group.charts track by $index" class="col-md-{{chart.size || 6}}">
-				<div class="nvd3-chart-wrapper">
-					<div nvd3="chart._data" data-holder="chart._holder" data-title="{{chart.title || chart.metrics[0].metric}}" data-watching="false"
-						 data-chart="{{chart.chart || 'line'}}" data-config="getChartConfig(chart)" class="nvd3-chart-cntr"></div>
-					<div class="nvd3-chart-config">
-						<a class="fa fa-expand" ng-click="showChart(chart, -1)"></a>
-						<span ng-if="Auth.isRole('ROLE_ADMIN')">
-							<a class="fa fa-minus" ng-click="configChartSize(chart, -1)"></a>
-							<a class="fa fa-plus" ng-click="configChartSize(chart, 1)"></a>
-							<a class="fa fa-cog" ng-click="configChart(chart)"></a>
-							<a class="fa fa-trash" ng-click="deleteChart(group, chart)"></a>
-						</span>
-					</div>
-				</div>
-			</div>
-		</div>
-
-		<p ng-if="!group.charts.length">
-			Empty group.
-			<span ng-if="Auth.isRole('ROLE_ADMIN')">
-				Click
-				<span ng-hide="dataSourceListReady" class="fa fa-refresh fa-spin"></span>
-				<a ng-show="dataSourceListReady" ng-click="newChart()">here</a>
-				to add metric.
-			</span>
-		</p>
-	</pane>
-</div>
-
-
-
-<!-- Modal: Chart configuration -->
-<div class="modal fade" id="chartMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Chart Configuration</h4>
-			</div>
-			<div class="modal-body">
-				<div class="row">
-					<div class="col-md-6">
-						<div class="nvd3-chart-wrapper">
-							<div nvd3="configPreviewChart._data" data-title="{{configPreviewChart.title || configPreviewChart.metrics[0].metric}}"
-								 data-watching="true" data-chart="{{configPreviewChart.chart || 'line'}}" data-config="getChartConfig(configPreviewChart)" class="nvd3-chart-cntr"></div>
-						</div>
-					</div>
-					<div class="col-md-6">
-						<!-- Chart Configuration -->
-						<table class="table">
-							<tbody>
-							<tr>
-								<th width="100">Name</th>
-								<td><input type="text" class="form-control input-xs" ng-model="configPreviewChart.title" placeholder="Default: {{configPreviewChart.metrics[0].metric}}" /></td>
-							</tr>
-							<tr>
-								<th>Chart Type</th>
-								<td>
-									<div class="btn-group" data-toggle="buttons">
-										<label class="btn btn-default btn-xs" ng-class="{active: (configPreviewChart.chart || 'line') === type.chart}"
-											   ng-repeat="type in chartTypeList track by $index" ng-click="configPreviewChart.chart = type.chart;">
-											<input type="radio" name="chartType" autocomplete="off"
-												   ng-checked="(configPreviewChart.chart || 'line') === type.chart">
-											<span class="fa fa-{{type.icon}}"></span>
-										</label>
-									</div>
-								</td>
-							</tr>
-							<tr>
-								<th>Minimum</th>
-								<td><input type="checkbox" ng-checked="configPreviewChart.min === 0" ng-disabled="configPreviewChart.chart === 'area' || configPreviewChart.chart === 'pie'"
-										   ng-click="configPreviewChartMinimumCheck()" /></td>
-							</tr>
-							<tr>
-								<th>Metrics</th>
-								<td>
-									<div ng-repeat="metric in configPreviewChart.metrics" class="box inner-box">
-										<div class="box-tools">
-											<button class="btn btn-box-tool" ng-click="configRemoveMetric(metric)">
-												<span class="fa fa-times"></span>
-											</button>
-										</div>
-
-										<h3 class="box-title">{{metric.metric}}</h3>
-										<div class="checkbox noMargin" ng-repeat="series in chartSeriesList track by $index">
-											<label>
-												<input type="checkbox" ng-checked="seriesChecked(metric, series.series)"
-													   ng-click="seriesCheckClick(metric, series.series, configPreviewChart)" />
-												{{series.name}}
-											</label>
-										</div>
-									</div>
-									<a ng-click="configAddMetric()">+ Add Metric</a>
-								</td>
-							</tr>
-							</tbody>
-						</table>
-					</div>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmUpdateChart()">
-					Confirm
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
-
-
-
-<!-- Modal: Metric selector -->
-<div class="modal fade" id="metricMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Select Metric</h4>
-			</div>
-			<div class="modal-body">
-				<div class="input-group" style="margin-bottom: 10px;">
-					<input type="text" class="form-control" placeholder="Filter..." ng-model="_newMetricFilter" />
-					<span class="input-group-btn">
-						<button class="btn btn-default btn-flat" ng-click="_newMetricFilter = '';"><span class="fa fa-times"></span></button>
-					</span>
-				</div>
-
-				<div class="row">
-					<div class="col-md-4">
-						<ul class="nav nav-pills nav-stacked fixed-height">
-							<li class="disabled"><a>Data Source</a></li>
-							<li ng-repeat="dataSrc in dataSourceList track by $index" ng-class="{active: _newMetricDataSrc === dataSrc}">
-								<a ng-click="newMetricSelectDataSource(dataSrc)">{{dataSrc.dataSource}}</a>
-							</li>
-						</ul>
-					</div>
-					<div class="col-md-8">
-						<ul class="nav nav-pills nav-stacked fixed-height">
-							<li class="disabled"><a>Metric</a></li>
-							<li ng-repeat="metric in dataSourceMetricList(_newMetricDataSrc, _newMetricFilter) track by $index" ng-class="{active: _newMetricDataMetric === metric}">
-								<a ng-click="newMetricSelectMetric(metric)">{{metric}}</a>
-							</li>
-						</ul>
-					</div>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<span class="text-primary pull-left">{{_newMetricDataSrc.dataSource}} <span class="fa fa-arrow-right"></span> {{_newMetricDataMetric}}</span>
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-				<button type="button" class="btn btn-primary" ng-click="confirmSelectMetric()">
-					Select
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
-
-
-
-<!-- Modal: Chart View -->
-<div class="modal fade" id="chartViewMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">{{viewChart.title || viewChart.metrics[0].metric}}</h4>
-			</div>
-			<div class="modal-body">
-				<div nvd3="viewChart._data" data-title="{{viewChart.title || viewChart.metrics[0].metric}}"
-					 data-watching="true" data-chart="{{viewChart.chart || 'line'}}" data-config="getChartConfig(viewChart)" class="nvd3-chart-cntr lg"></div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
deleted file mode 100644
index 94886c9..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/topology/controller.js
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module('featureControllers');
-	var feature = featureControllers.register("topology", {
-		global: true	// Global Feature needn't add to applications
-	});
-
-	// ==============================================================
-	// =                       Initialization                       =
-	// ==============================================================
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-	//feature.service("DashboardFormatter", function() {
-	//});
-
-	// ==============================================================
-	// =                         Controller                         =
-	// ==============================================================
-	feature.configNavItem("monitoring", "Topology", "usb");
-
-	// ========================= Monitoring =========================
-	feature.configController('monitoring', function(PageConfig, $scope, $interval, Entities, UI, Site, Application) {
-		var topologyRefreshInterval;
-
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		PageConfig.pageTitle = "Topology Execution";
-
-		$scope.topologyExecutionList = null;
-
-		$scope.currentTopologyExecution = null;
-		$scope.currentTopologyExecutionOptList = [];
-
-		// ======================= Function =======================
-		function refreshExecutionList() {
-			var _list = Entities.queryEntities("TopologyExecutionService");
-			_list._promise.then(function () {
-				$scope.topologyExecutionList = _list;
-			});
-		}
-
-		$scope.showTopologyDetail = function (topologyExecution) {
-			$scope.currentTopologyExecution = topologyExecution;
-			$("#topologyMDL").modal();
-
-			$scope.currentTopologyExecutionOptList = Entities.queryEntities("TopologyOperationService", {
-				site: topologyExecution.tags.site,
-				application: topologyExecution.tags.application,
-				topology: topologyExecution.tags.topology,
-				_pageSize: 10,
-				_duration: 1000 * 60 * 60 * 24 * 30
-			});
-		};
-
-		$scope.getStatusClass = function (status) {
-			switch (status) {
-				case "NEW":
-					return "info";
-				case "STARTING":
-				case "STOPPING":
-					return "warning";
-				case "STARTED":
-					return "success";
-				case "STOPPED":
-					return "danger";
-				default:
-					return "default";
-			}
-		};
-
-		// ==================== Initialization ====================
-		refreshExecutionList();
-		topologyRefreshInterval = $interval(refreshExecutionList, 10 * 1000);
-
-		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
-		$scope.topologyList._promise.then(function () {
-			$scope.topologyList = $.map($scope.topologyList, function (topology) {
-				return topology.tags.topology;
-			});
-		});
-
-		$scope.applicationList = $.map(Application.list, function (application) {
-			return application.tags.application;
-		});
-
-		$scope.siteList = $.map(Site.list, function (site) {
-			return site.tags.site;
-		});
-
-		// ================== Topology Execution ==================
-		$scope.newTopologyExecution = function () {
-			UI.createConfirm("Topology", {}, [
-				{field: "site", type: "select", valueList: $scope.siteList},
-				{field: "application", type: "select", valueList: $scope.applicationList},
-				{field: "topology", type: "select", valueList: $scope.topologyList}
-			], function (entity) {
-				for(var i = 0 ; i < $scope.topologyExecutionList.length; i += 1) {
-					var _entity = $scope.topologyExecutionList[i].tags;
-					if(_entity.site === entity.site && _entity.application === entity.application && _entity.topology === entity.topology) {
-						return "Topology already exist!";
-					}
-				}
-			}).then(null, null, function(holder) {
-				var _entity = {
-					tags: {
-						site: holder.entity.site,
-						application: holder.entity.application,
-						topology: holder.entity.topology
-					},
-					status: "NEW"
-				};
-				Entities.updateEntity("TopologyExecutionService", _entity)._promise.then(function() {
-					holder.closeFunc();
-					$scope.topologyExecutionList.push(_entity);
-					refreshExecutionList();
-				});
-			});
-		};
-
-		$scope.deleteTopologyExecution = function (topologyExecution) {
-			UI.deleteConfirm(topologyExecution.tags.topology).then(null, null, function(holder) {
-				Entities.deleteEntities("TopologyExecutionService", topologyExecution.tags)._promise.then(function() {
-					holder.closeFunc();
-					common.array.remove(topologyExecution, $scope.topologyExecutionList);
-				});
-			});
-		};
-
-		// ================== Topology Operation ==================
-		$scope.doTopologyOperation = function (topologyExecution, operation) {
-			$.dialog({
-				title: operation + " Confirm",
-				content: "Do you want to " + operation + " '" + topologyExecution.tags.topology + "'?",
-				confirm: true
-			}, function (ret) {
-				if(!ret) return;
-
-				var list = Entities.queryEntities("TopologyOperationService", {
-					site: topologyExecution.tags.site,
-					application: topologyExecution.tags.application,
-					topology: topologyExecution.tags.topology,
-					_pageSize: 20
-				});
-
-				list._promise.then(function () {
-					var lastOperation = common.array.find(operation, list, "tags.operation");
-					if(lastOperation && (lastOperation.status === "INITIALIZED" || lastOperation.status === "PENDING")) {
-						refreshExecutionList();
-						return;
-					}
-
-					Entities.updateEntity("rest/app/operation", {
-						tags: {
-							site: topologyExecution.tags.site,
-							application: topologyExecution.tags.application,
-							topology: topologyExecution.tags.topology,
-							operation: operation
-						},
-						status: "INITIALIZED"
-					}, {timestamp: false, hook: true});
-				});
-			});
-		};
-
-		$scope.startTopologyOperation = function (topologyExecution) {
-			$scope.doTopologyOperation(topologyExecution, "START");
-		};
-		$scope.stopTopologyOperation = function (topologyExecution) {
-			$scope.doTopologyOperation(topologyExecution, "STOP");
-		};
-
-		// ======================= Clean Up =======================
-		$scope.$on('$destroy', function() {
-			$interval.cancel(topologyRefreshInterval);
-		});
-	});
-
-	// ========================= Management =========================
-	feature.configController('management', function(PageConfig, $scope, Entities, UI) {
-		PageConfig.hideApplication = true;
-		PageConfig.hideSite = true;
-		PageConfig.pageTitle = "Topology";
-
-		var typeList = ["CLASS", "DYNAMIC"];
-		var topologyDefineAttrs = [
-			{field: "topology", name: "name"},
-			{field: "type", type: "select", valueList: typeList},
-			{field: "exeClass", name: "execution entry", description: function (entity) {
-				if(entity.type === "CLASS") return "Class implements interface TopologyExecutable";
-				if(entity.type === "DYNAMIC") return "DSL based topology definition";
-			}, type: "blob", rows: 5},
-			{field: "version", optional: true},
-			{field: "description", optional: true, type: "blob"}
-		];
-		var topologyUpdateAttrs = $.extend(topologyDefineAttrs.concat(), [{field: "topology", name: "name", readonly: true}]);
-
-		$scope.topologyList = Entities.queryEntities("TopologyDescriptionService");
-
-		$scope.newTopology = function () {
-			UI.createConfirm("Topology", {}, topologyDefineAttrs, function (entity) {
-				if(common.array.find(entity.topology, $scope.topologyList, "tags.topology", false, false)) {
-					return "Topology name conflict!";
-				}
-			}).then(null, null, function(holder) {
-				holder.entity.tags = {
-					topology: holder.entity.topology
-				};
-				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function() {
-					holder.closeFunc();
-					$scope.topologyList.push(holder.entity);
-				});
-			});
-		};
-
-		$scope.updateTopology = function (topology) {
-			UI.updateConfirm("Topology", $.extend({}, topology, {topology: topology.tags.topology}), topologyUpdateAttrs).then(null, null, function(holder) {
-				holder.entity.tags = {
-					topology: holder.entity.topology
-				};
-				Entities.updateEntity("TopologyDescriptionService", holder.entity, {timestamp: false})._promise.then(function() {
-					holder.closeFunc();
-					$.extend(topology, holder.entity);
-				});
-			});
-		};
-
-		$scope.deleteTopology = function (topology) {
-			UI.deleteConfirm(topology.tags.topology).then(null, null, function(holder) {
-				Entities.delete("TopologyDescriptionService", {topology: topology.tags.topology})._promise.then(function() {
-					holder.closeFunc();
-					common.array.remove(topology, $scope.topologyList);
-				});
-			});
-		};
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
deleted file mode 100644
index 9e22c84..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/management.html
+++ /dev/null
@@ -1,52 +0,0 @@
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-cog"></i>
-		<a class="pull-right" href="#/config/topology/monitoring"><span class="fa fa-angle-right"></span> Monitoring</a>
-		<h3 class="box-title">
-			Management
-		</h3>
-	</div>
-	<div class="box-body">
-		<table class="table table-bordered">
-			<thead>
-				<tr>
-					<th>Name</th>
-					<th width="20%">Execution Class</th>
-					<th>Type</th>
-					<th width="50%">Description</th>
-					<th>Version</th>
-					<th width="70"></th>
-				</tr>
-			</thead>
-			<tbody>
-				<tr ng-repeat="item in topologyList">
-					<td>{{item.tags.topology}}</td>
-					<td><pre class="noWrap">{{item.exeClass}}</pre></td>
-					<td>{{item.type}}</td>
-					<td>{{item.description}}</td>
-					<td>{{item.version}}</td>
-					<td class="text-center">
-						<button class="rm fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" ng-click="updateTopology(item)"> </button>
-						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="deleteTopology(item)"> </button>
-					</td>
-				</tr>
-				<tr ng-if="topologyList.length === 0">
-					<td colspan="6">
-						<span class="text-muted">Empty list</span>
-					</td>
-				</tr>
-			</tbody>
-		</table>
-	</div>
-
-	<div class="box-footer">
-		<button class="btn btn-primary pull-right" ng-click="newTopology()">
-			New Topology
-			<i class="fa fa-plus-circle"> </i>
-		</button>
-	</div>
-
-	<div class="overlay" ng-hide="topologyList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html b/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
deleted file mode 100644
index 0acb2c1..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/topology/page/monitoring.html
+++ /dev/null
@@ -1,151 +0,0 @@
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-eye"></i>
-		<a class="pull-right" href="#/config/topology/management"><span class="fa fa-angle-right"></span> Management</a>
-		<h3 class="box-title">
-			Monitoring
-		</h3>
-	</div>
-	<div class="box-body">
-		<div sorttable source="topologyExecutionList">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-				<tr>
-					<th width="70" sortpath="status">Status</th>
-					<th width="90" sortpath="tags.topology">Topology</th>
-					<th width="60" sortpath="tags.site">Site</th>
-					<th width="100" sortpath="tags.application">Application</th>
-					<th width="60" sortpath="mode">Mode</th>
-					<th sortpath="description">Description</th>
-					<th width="70" style="min-width: 70px;"></th>
-				</tr>
-				</thead>
-				<tbody>
-				<tr>
-					<td class="text-center">
-						<span class="label label-{{_parent.getStatusClass(item.status)}}">{{item.status}}</span>
-					</td>
-					<td><a ng-click="_parent.showTopologyDetail(item)">{{item.tags.topology}}</a></td>
-					<td>{{item.tags.site}}</td>
-					<td>{{item.tags.application}}</td>
-					<td>{{item.mode}}</td>
-					<td>{{item.description}}</td>
-					<td class="text-center">
-						<button ng-if="item.status === 'NEW' || item.status === 'STOPPED'" class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Start" tooltip-animation="false" ng-click="_parent.startTopologyOperation(item)"> </button>
-						<button ng-if="item.status === 'STARTED'" class="fa fa-stop sm btn btn-default btn-xs" uib-tooltip="Stop" tooltip-animation="false" ng-click="_parent.stopTopologyOperation(item)"> </button>
-						<button ng-if="item.status !== 'NEW' && item.status !== 'STARTED' && item.status !== 'STOPPED'" class="fa fa-ban sm btn btn-default btn-xs" disabled="disabled"> </button>
-						<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deleteTopologyExecution(item)"> </button>
-					</td>
-				</tr>
-				</tbody>
-			</table>
-		</div>
-	</div>
-
-	<div class="box-footer">
-		<button class="btn btn-primary pull-right" ng-click="newTopologyExecution()">
-			New Topology Execution
-			<i class="fa fa-plus-circle"> </i>
-		</button>
-	</div>
-
-	<div class="overlay" ng-hide="topologyExecutionList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-</div>
-
-
-
-
-<!-- Modal: Topology Detail -->
-<div class="modal fade" id="topologyMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Topology Detail</h4>
-			</div>
-			<div class="modal-body">
-				<h3>Detail</h3>
-				<table class="table">
-					<tbody>
-						<tr>
-							<th>Site</th>
-							<td>{{currentTopologyExecution.tags.site}}</td>
-						</tr>
-						<tr>
-							<th>Application</th>
-							<td>{{currentTopologyExecution.tags.application}}</td>
-						</tr>
-						<tr>
-							<th>Topology</th>
-							<td>{{currentTopologyExecution.tags.topology}}</td>
-						</tr>
-						<tr>
-							<th>Full Name</th>
-							<td>{{currentTopologyExecution.fullName || "-"}}</td>
-						</tr>
-						<tr>
-							<th>Status</th>
-							<td>
-								<span class="label label-{{getStatusClass(currentTopologyExecution.status)}}">{{currentTopologyExecution.status}}</span>
-							</td>
-						</tr>
-						<tr>
-							<th>Mode</th>
-							<td>{{currentTopologyExecution.mode || "-"}}</td>
-						</tr>
-						<tr>
-							<th>Environment</th>
-							<td>{{currentTopologyExecution.environment || "-"}}</td>
-						</tr>
-						<tr>
-							<th>Url</th>
-							<td>
-								<a ng-if="currentTopologyExecution.url" href="{{currentTopologyExecution.url}}" target="_blank">{{currentTopologyExecution.url}}</a>
-								<span ng-if="!currentTopologyExecution.url">-</span>
-							</td>
-						</tr>
-						<tr>
-							<th>Description</th>
-							<td>{{currentTopologyExecution.description || "-"}}</td>
-						</tr>
-						<tr>
-							<th>Last Modified Date</th>
-							<td>{{common.format.date(currentTopologyExecution.lastModifiedDate) || "-"}}</td>
-						</tr>
-					</tbody>
-				</table>
-
-				<h3>Latest Operations</h3>
-				<div class="table-responsive">
-					<table class="table table-bordered table-sm margin-bottom-none">
-						<thead>
-							<tr>
-								<th>Date Time</th>
-								<th>Operation</th>
-								<th>Status</th>
-								<th>Message</th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr ng-repeat="action in currentTopologyExecutionOptList track by $index">
-								<td>{{common.format.date(action.lastModifiedDate) || "-"}}</td>
-								<td>{{action.tags.operation}}</td>
-								<td>{{action.status}}</td>
-								<td><pre class="noWrap">{{action.message}}</pre></td>
-							</tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
deleted file mode 100644
index ed619d3..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/controller.js
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module('featureControllers');
-	var feature = featureControllers.register("userProfile");
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-
-	// ==============================================================
-	// =                        User Profile                        =
-	// ==============================================================
-
-	// ======================== Profile List ========================
-	//feature.navItem("list", "User Profiles", "graduation-cap");
-	feature.controller('list', function(PageConfig, Site, $scope, $interval, Entities) {
-		PageConfig.pageSubTitle = Site.current().tags.site;
-
-		$scope.common = common;
-		$scope.algorithms = [];
-
-		// ======================================== Algorithms ========================================
-		$scope.algorithmEntity = {};
-		Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, application: "userProfile"})._promise.then(function(data) {
-			$scope.algorithmEntity = common.getValueByPath(data, "obj[0]");
-			$scope.algorithmEntity.policy = common.parseJSON($scope.algorithmEntity.policyDef);
-		});
-
-		// ======================================= User profile =======================================
-		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site}, ["user", "algorithm", "content", "version"]);
-		$scope.profileList._promise.then(function() {
-			var _algorithms = {};
-			var _users = {};
-
-			// Map user
-			$.each($scope.profileList, function(i, unit) {
-				_algorithms[unit.tags.algorithm] = unit.tags.algorithm;
-				var _user = _users[unit.tags.user] = _users[unit.tags.user] || {user: unit.tags.user};
-				_user[unit.tags.algorithm] = {
-					version: unit.version
-				};
-
-				// DE
-				if(unit.tags.algorithm === "DE") {
-					var _statistics = common.parseJSON(unit.content);
-					_statistics = common.getValueByPath(_statistics, "statistics", []);
-					_user[unit.tags.algorithm].topCommands = $.map(common.array.top(_statistics, "mean"), function(command) {
-						return command.commandName;
-					});
-				}
-			});
-
-			// Map algorithms
-			$scope.algorithms = $.map(_algorithms, function(algorithm) {
-				return algorithm;
-			}).sort();
-
-			$scope.profileList.splice(0);
-			$scope.profileList.push.apply($scope.profileList, common.map.toArray(_users));
-		});
-
-		// =========================================== Task ===========================================
-		$scope.tasks = [];
-		function _loadTasks() {
-			var _tasks = Entities.queryEntities("ScheduleTaskService", {
-				site: Site.current().tags.site,
-				_pageSize: 100,
-				_duration: 1000 * 60 * 60 * 24 * 14,
-				__ETD: 1000 * 60 * 60 * 24
-			});
-			_tasks._promise.then(function() {
-				$scope.tasks.splice(0);
-				$scope.tasks.push.apply($scope.tasks, _tasks);
-
-				// Duration
-				$.each($scope.tasks, function(i, data) {
-					if(data.timestamp && data.updateTime) {
-						var _ms = (new moment(data.updateTime)).diff(new moment(data.timestamp));
-						var _d = moment.duration(_ms);
-						data._duration = Math.floor(_d.asHours()) + moment.utc(_ms).format(":mm:ss");
-						data.duration = _ms;
-					} else {
-						data._duration = "--";
-					}
-				});
-			});
-		}
-
-		$scope.runningTaskCount = function () {
-			return common.array.count($scope.tasks, "INITIALIZED", "status") +
-				common.array.count($scope.tasks, "PENDING", "status") +
-				common.array.count($scope.tasks, "EXECUTING", "status");
-		};
-
-		// Create task
-		$scope.updateTask = function() {
-			$.dialog({
-				title: "Confirm",
-				content: "Do you want to update now?",
-				confirm: true
-			}, function(ret) {
-				if(!ret) return;
-
-				var _entity = {
-					status: "INITIALIZED",
-					detail: "Newly created command",
-					tags: {
-						site: Site.current().tags.site,
-						type: "USER_PROFILE_TRAINING"
-					},
-					timestamp: +new Date()
-				};
-				Entities.updateEntity("ScheduleTaskService", _entity, {timestamp: false})._promise.success(function(data) {
-					if(!Entities.dialog(data)) {
-						_loadTasks();
-					}
-				});
-			});
-		};
-
-		// Show detail
-		$scope.showTaskDetail = function(task) {
-			var _content = $("<pre>").text(task.detail);
-
-			var $mdl = $.dialog({
-				title: "Detail",
-				content: _content
-			});
-
-			_content.click(function(e) {
-				if(!e.ctrlKey) return;
-
-				$.dialog({
-					title: "Confirm",
-					content: "Remove this task?",
-					confirm: true
-				}, function(ret) {
-					if(!ret) return;
-
-					$mdl.modal('hide');
-					Entities.deleteEntity("ScheduleTaskService", task)._promise.then(function() {
-						_loadTasks();
-					});
-				});
-			});
-		};
-
-		_loadTasks();
-		var _loadInterval = $interval(_loadTasks, app.time.refreshInterval);
-		$scope.$on('$destroy',function(){
-			$interval.cancel(_loadInterval);
-		});
-	});
-
-	// ======================= Profile Detail =======================
-	feature.controller('detail', function(PageConfig, Site, $scope, $wrapState, Entities) {
-		PageConfig.pageTitle = "User Profile";
-		PageConfig.pageSubTitle = Site.current().tags.site;
-		PageConfig
-			.addNavPath("User Profile", "/userProfile/list")
-			.addNavPath("Detail");
-
-		$scope.user = $wrapState.param.filter;
-
-		// User profile
-		$scope.profiles = {};
-		$scope.profileList = Entities.queryEntities("MLModelService", {site: Site.current().tags.site, user: $scope.user});
-		$scope.profileList._promise.then(function() {
-			$.each($scope.profileList, function(i, unit) {
-				unit._content = common.parseJSON(unit.content);
-				$scope.profiles[unit.tags.algorithm] = unit;
-			});
-
-			// DE
-			if($scope.profiles.DE) {
-				console.log($scope.profiles.DE);
-
-				$scope.profiles.DE._chart = {};
-
-				$scope.profiles.DE.estimates = {};
-				$.each($scope.profiles.DE._content, function(key, value) {
-					if(key !== "statistics") {
-						$scope.profiles.DE.estimates[key] = value;
-					}
-				});
-
-				var _meanList = [];
-				var _stddevList = [];
-
-				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
-					_meanList[i] = {
-						x: unit.commandName,
-						y: unit.mean
-					};
-					_stddevList[i] = {
-						x: unit.commandName,
-						y: unit.stddev
-					};
-				});
-				$scope.profiles.DE._chart.series = [
-					{
-						key: "mean",
-						values: _meanList
-					},
-					{
-						key: "stddev",
-						values: _stddevList
-					}
-				];
-
-				// Percentage table list
-				$scope.profiles.DE.meanList = [];
-				var _total = common.array.sum($scope.profiles.DE._content.statistics, "mean");
-				$.each($scope.profiles.DE._content.statistics, function(i, unit) {
-					$scope.profiles.DE.meanList.push({
-						command: unit.commandName,
-						percentage: unit.mean / _total
-					});
-				});
-			}
-
-			// EigenDecomposition
-			if($scope.profiles.EigenDecomposition && $scope.profiles.EigenDecomposition._content.principalComponents) {
-				$scope.profiles.EigenDecomposition._chart = {
-					series: [],
-				};
-
-				$.each($scope.profiles.EigenDecomposition._content.principalComponents, function(z, grp) {
-					var _line = [];
-					$.each(grp, function(x, y) {
-						_line.push([x,y,z]);
-					});
-
-					$scope.profiles.EigenDecomposition._chart.series.push({
-						data: _line
-					});
-				});
-			}
-		});
-
-		// UI
-		$scope.showRawData = function(content) {
-			$.dialog({
-				title: "Raw Data",
-				content: $("<pre>").text(content)
-			});
-		};
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
deleted file mode 100644
index 0f94e03..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/detail.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!--
-  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.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-user"> </i>
-		<h3 class="box-title">
-			{{user}}
-		</h3>
-	</div>
-	<div class="box-body">
-		<div>
-			<div class="inline-group">
-				<dl><dt>User</dt><dd>{{user}}</dd></dl>
-				<dl><dt>Site</dt><dd>{{Site.current().tags.site}}</dd></dl>
-			</div>
-			<div class="inline-group">
-				<dl><dt>Other Info</dt><dd class="text-muted">N/A</dd></dl>
-			</div>
-		</div>
-
-		<div class="overlay" ng-hide="profileList._promise.$$state.status === 1;">
-			<span class="fa fa-refresh fa-spin"></span>
-		</div>
-	</div>
-</div>
-
-<!-- Analysis -->
-<div class="nav-tabs-custom">
-	<ul class="nav nav-tabs">
-		<li class="active">
-			<a href="[data-id='DE']" data-toggle="tab" ng-click=" currentTab='DE'">DE</a>
-		</li>
-		<li>
-			<a href="[data-id='EigenDecomposition']" data-toggle="tab" ng-click=" currentTab='EigenDecomposition'">EigenDecomposition</a>
-		</li>
-		<li class="pull-right">
-			<button class="btn btn-primary" ng-click="showRawData(currentTab === 'EigenDecomposition' ? profiles.EigenDecomposition.content : profiles.DE.content)">Raw Data</button>
-		</li>
-	</ul>
-	<div class="tab-content">
-		<div class="tab-pane active" data-id="DE">
-			<div class="row">
-				<div class="col-md-9">
-					<div nvd3="profiles.DE._chart.series" data-config="{chart: 'column', xType: 'text', height: 400}" class="nvd3-chart-cntr" height="400"></div>
-					<div class="inline-group text-center">
-						<dl ng-repeat="(key, value) in profiles.DE.estimates"><dt>{{key}}</dt><dd>{{value}}</dd></dl>
-					</div>
-				</div>
-
-				<div class="col-md-3">
-					<table class="table table-bordered">
-						<thead>
-							<tr>
-								<th>Command</th>
-								<th>Percentage</th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr ng-repeat="unit in profiles.DE.meanList">
-								<td>{{unit.command}}</td>
-								<td class="text-right">{{(unit.percentage*100).toFixed(2)}}%</td>
-							</tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-		</div><!-- /.tab-pane -->
-		<div class="tab-pane" data-id="EigenDecomposition">
-			<div line3d-chart height="400" data="profiles.EigenDecomposition._chart.series"> </div>
-		</div><!-- /.tab-pane -->
-	</div><!-- /.tab-content -->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html b/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
deleted file mode 100644
index 2f14479..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/userProfile/page/list.html
+++ /dev/null
@@ -1,138 +0,0 @@
-<!--
-  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.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			User Profiles
-			<small><a data-toggle="collapse" href="[data-id='algorithms']">Detail</a></small>
-		</h3>
-		<div class="pull-right">
-			<a class="label label-primary" ng-class="runningTaskCount() ? 'label-primary' : 'label-default'" data-toggle="modal" data-target="#taskMDL">Update</a>
-		</div>
-	</div>
-	<div class="box-body">
-		<!-- Algorithms -->
-		<div data-id="algorithms" class="collapse">
-			<table class="table table-bordered">
-				<thead>
-					<tr>
-						<th>Name</th>
-						<td>Feature</td>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-repeat="algorithm in algorithmEntity.policy.algorithms">
-						<td>{{algorithm.name}}</td>
-						<td>{{algorithm.features}}</td>
-					</tr>
-				</tbody>
-			</table>
-			<hr/>
-		</div>
-
-		<!-- User Profile List -->
-		<p ng-show="profileList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="profileList" ng-show="profileList._promise.$$state.status === 1">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="10%" sortpath="user">User</th>
-						<th>Most Predominat Feature</th>
-						<th width="10"></th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr>
-						<td>
-							<a href="#/userProfile/detail/{{item.user}}">{{item.user}}</a>
-						</td>
-						<td>
-							{{item.DE.topCommands.slice(0,3).join(", ")}}
-						</td>
-						<td>
-							<a href="#/userProfile/detail/{{item.user}}">Detail</a>
-						</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div>
-</div>
-
-<!-- Modal: User profile Schedule Task -->
-<div class="modal fade" id="taskMDL" tabindex="-1" role="dialog">
-	<div class="modal-dialog modal-lg" role="document">
-		<div class="modal-content">
-			<div class="modal-header">
-				<button type="button" class="close" data-dismiss="modal" aria-label="Close">
-					<span aria-hidden="true">&times;</span>
-				</button>
-				<h4 class="modal-title">Training History</h4>
-			</div>
-			<div class="modal-body">
-				<div sorttable source="tasks">
-					<table class="table table-bordered" ng-non-bindable>
-						<thead>
-							<tr>
-								<th sortpath="tags.type">Command</th>
-								<th sortpath="timestamp">Start Time</th>
-								<th sortpath="updateTime">Update Time</th>
-								<th sortpath="duration">Duration</th>
-								<th sortpath="status">Status</th>
-								<th width="10"> </th>
-							</tr>
-						</thead>
-						<tbody>
-							<tr>
-								<td>{{item.tags.type}}</td>
-								<td>{{common.format.date(item.timestamp) || "--"}}</td>
-								<td>{{common.format.date(item.updateTime) || "--"}}</td>
-								<td>{{item._duration}}</td>
-								<td class="text-nowrap">
-									<span class="fa fa-hourglass-start text-muted" ng-show="item.status === 'INITIALIZED'"></span>
-									<span class="fa fa-hourglass-half text-info" ng-show="item.status === 'PENDING'"></span>
-									<span class="fa fa-circle-o-notch text-primary" ng-show="item.status === 'EXECUTING'"></span>
-									<span class="fa fa-check-circle text-success" ng-show="item.status === 'SUCCEEDED'"></span>
-									<span class="fa fa-exclamation-circle text-danger" ng-show="item.status === 'FAILED'"></span>
-									<span class="fa fa-ban text-muted" ng-show="item.status === 'CANCELED'"></span>
-									{{item.status}}
-								</td>
-								<td>
-									<a ng-click="_parent.showTaskDetail(item)">Detail</a>
-								</td>
-							</tr>
-						</tbody>
-					</table>
-				</div>
-			</div>
-			<div class="modal-footer">
-				<button type="button" class="btn btn-primary pull-left" ng-click="updateTask()" ng-show="Auth.isRole('ROLE_ADMIN')">
-					Update Now
-				</button>
-				<button type="button" class="btn btn-default" data-dismiss="modal">
-					Close
-				</button>
-			</div>
-		</div>
-	</div>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/images/favicon.png
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/images/favicon.png b/eagle-webservice/src/main/webapp/app/public/images/favicon.png
deleted file mode 100644
index 3bede2a..0000000
Binary files a/eagle-webservice/src/main/webapp/app/public/images/favicon.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/images/favicon_white.png
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/images/favicon_white.png b/eagle-webservice/src/main/webapp/app/public/images/favicon_white.png
deleted file mode 100644
index 9879e92..0000000
Binary files a/eagle-webservice/src/main/webapp/app/public/images/favicon_white.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/app.config.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.config.js b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
deleted file mode 100644
index d7c4be9..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/app.config.js
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	app.config = {
-		// ============================================================================
-		// =                                   URLs                                   =
-		// ============================================================================
-		urls: {
-			HOST: '..',
-
-			updateEntity: 'rest/entities?serviceName=${serviceName}',
-			queryEntity: 'rest/entities/rowkey?serviceName=${serviceName}&value=${encodedRowkey}',
-			queryEntities: 'rest/entities?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
-			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
-			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
-
-			queryGroup: 'rest/entities?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
-			querySeries: 'rest/entities?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
-
-			query: 'rest/',
-
-			userProfile: 'rest/authentication',
-			logout: 'logout',
-
-			maprNameResolver: '../rest/maprNameResolver',
-
-			DELETE_HOOK: {
-				FeatureDescService: 'rest/module/feature?feature=${feature}',
-				ApplicationDescService: 'rest/module/application?application=${application}',
-				SiteDescService: 'rest/module/site?site=${site}',
-				TopologyDescriptionService: 'rest/app/topology?topology=${topology}'
-			},
-			UPDATE_HOOK: {
-				SiteDescService: 'rest/module/siteApplication'
-			}
-		},
-	};
-
-	// ============================================================================
-	// =                                   URLs                                   =
-	// ============================================================================
-	app.getURL = function(name, kvs) {
-		var _path = app.config.urls[name];
-		if(!_path) throw "URL:'" + name + "' not exist!";
-		var _url = app.packageURL(_path);
-		if(kvs !== undefined) {
-			_url = common.template(_url, kvs);
-		}
-		return _url;
-	};
-
-	app.getMapRNameResolverURL = function(name,value, site) {
-		var key = "maprNameResolver";
-		var _path = app.config.urls[key];
-		if(!_path) throw "URL:'" + name + "' not exist!";
-		var _url = _path;
-		if(name == "fNameResolver") {
-			_url +=  "/" + name + "?fName=" + value + "&site=" + site;
-		} else if(name == "sNameResolver") {
-			_url +=  "/" + name + "?sName=" + value + "&site=" + site;
-		} else if (name == "vNameResolver") {
-			_url += "/" + name + "?vName=" + value + "&site=" + site;
-		} else{
-			throw "resolver:'" + name + "' not exist!";
-		}
-		return _url;
-	};
-
-	function getHookURL(hookType, serviceName) {
-		var _path = app.config.urls[hookType][serviceName];
-		if(!_path) return null;
-
-		return app.packageURL(_path);
-	}
-
-	/***
-	 * Eagle support delete function to process special entity delete. Which will delete all the relative entity.
-	 * @param serviceName
-	 */
-	app.getDeleteURL = function(serviceName) {
-		return getHookURL('DELETE_HOOK', serviceName);
-	};
-
-	/***
-	 * Eagle support update function to process special entity update. Which will update all the relative entity.
-	 * @param serviceName
-	 */
-	app.getUpdateURL = function(serviceName) {
-		return getHookURL('UPDATE_HOOK', serviceName);
-	};
-
-	app.packageURL = function (path) {
-		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
-		return (_host ? _host + "/" : '') + path;
-	};
-
-	app._Host = function(host) {
-		if(host) {
-			localStorage.setItem("HOST", host);
-			return app;
-		}
-		return localStorage.getItem("HOST");
-	};
-	app._Host.clear = function() {
-		localStorage.removeItem("HOST");
-	};
-	app._Host.sample = "http://localhost:9099/eagle-service";
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/js/app.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/js/app.js b/eagle-webservice/src/main/webapp/app/public/js/app.js
deleted file mode 100644
index 70b4afe..0000000
--- a/eagle-webservice/src/main/webapp/app/public/js/app.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * 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 app = {};
-
-(function() {
-	'use strict';
-
-	/* App Module */
-	var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngAnimate', 'ui.router', 'eagleControllers', 'featureControllers', 'eagle.service']);
-
-	// GRUNT REPLACEMENT: eagleApp.buildTimestamp = TIMESTAMP
-	eagleApp._TRS = function() {
-		return eagleApp.buildTimestamp || Math.random();
-	};
-
-	// ======================================================================================
-	// =                                   Feature Module                                   =
-	// ======================================================================================
-	var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
-	var FN_ARG_SPLIT = /,/;
-	var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
-	var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
-
-	var featureControllers = angular.module('featureControllers', ['ui.bootstrap', 'eagle.components']);
-	var featureControllerCustomizeHtmlTemplate = {};
-	var featureControllerProvider;
-	var featureProvider;
-
-	featureControllers.config(function ($controllerProvider, $provide) {
-		featureControllerProvider = $controllerProvider;
-		featureProvider = $provide;
-	});
-
-	featureControllers.service("Feature", function($wrapState, PageConfig, ConfigPageConfig, FeaturePageConfig) {
-		var _features = {};
-		var _services = {};
-
-		var Feature = function(name, config) {
-			this.name = name;
-			this.config = config || {};
-			this.features = {};
-		};
-
-		/***
-		 * Inner function. Replace the dependency of constructor.
-		 * @param constructor
-		 * @private
-		 */
-		Feature.prototype._replaceDependencies = function(constructor) {
-			var i, srvName;
-			var _constructor, _$inject;
-			var fnText, argDecl;
-
-			if($.isArray(constructor)) {
-				_constructor = constructor[constructor.length - 1];
-				_$inject = constructor.slice(0, -1);
-			} else if(constructor.$inject) {
-				_constructor = constructor;
-				_$inject = constructor.$inject;
-			} else {
-				_$inject = [];
-				_constructor = constructor;
-				fnText = constructor.toString().replace(STRIP_COMMENTS, '');
-				argDecl = fnText.match(FN_ARGS);
-				$.each(argDecl[1].split(FN_ARG_SPLIT), function(i, arg) {
-					arg.replace(FN_ARG, function(all, underscore, name) {
-						_$inject.push(name);
-					});
-				});
-			}
-			_constructor.$inject = _$inject;
-
-			for(i = 0 ; i < _$inject.length ; i += 1) {
-				srvName = _$inject[i];
-				_$inject[i] = this.features[srvName] || _$inject[i];
-			}
-
-			return _constructor;
-		};
-
-		/***
-		 * Register a common service for feature usage. Common service will share between the feature. If you are coding customize feature, use 'Feature.service' is the better way.
-		 * @param name
-		 * @param constructor
-		 */
-		Feature.prototype.commonService = function(name, constructor) {
-			if(!_services[name]) {
-				featureProvider.service(name, constructor);
-				_services[name] = this.name;
-			} else {
-				throw "Service '" + name + "' has already be registered by feature '" + _services[name] + "'";
-			}
-		};
-
-		/***
-		 * Register a service for feature usage.
-		 * @param name
-		 * @param constructor
-		 */
-		Feature.prototype.service = function(name, constructor) {
-			var _serviceName;
-			if(!this.features[name]) {
-				_serviceName = "__FEATURE_" + this.name + "_" + name;
-				featureProvider.service(_serviceName, this._replaceDependencies(constructor));
-				this.features[name] = _serviceName;
-			} else {
-				console.warn("Service '" + name + "' has already be registered.");
-			}
-		};
-
-		/***
-		 * Create an navigation item in left navigation bar
-		 * @param path
-		 * @param title
-		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
-		 */
-		Feature.prototype.navItem = function(path, title, icon) {
-			title = title || path;
-			icon = icon || "question";
-
-			FeaturePageConfig.addNavItem(this.name, {
-				icon: icon,
-				title: title,
-				url: "#/" + this.name + "/" + path
-			});
-		};
-
-		/***
-		 * Register a controller.
-		 * @param name
-		 * @param constructor
-		 */
-		Feature.prototype.controller = function(name, constructor, htmlTemplatePath) {
-			var _name = this.name + "_" + name;
-
-			// Replace feature registered service
-			constructor = this._replaceDependencies(constructor);
-
-			// Register controller
-			featureControllerProvider.register(_name, constructor);
-			if(htmlTemplatePath) {
-				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
-			}
-
-			return _name;
-		};
-
-		/***
-		 * Register a configuration controller for admin usage.
-		 * @param name
-		 * @param constructor
-		 */
-		Feature.prototype.configController = function(name, constructor, htmlTemplatePath) {
-			var _name = "config_" + this.name + "_" + name;
-
-			// Replace feature registered service
-			constructor = this._replaceDependencies(constructor);
-
-			// Register controller
-			featureControllerProvider.register(_name, constructor);
-			if(htmlTemplatePath) {
-				featureControllerCustomizeHtmlTemplate[_name] = htmlTemplatePath;
-			}
-
-			return _name;
-		};
-
-		/***
-		 * Create an navigation item in left navigation bar for admin configuraion page
-		 * @param path
-		 * @param title
-		 * @param icon use Font Awesome. Needn't with 'fa fa-'.
-		 */
-		Feature.prototype.configNavItem = function(path, title, icon) {
-			title = title || path;
-			icon = icon || "question";
-
-			ConfigPageConfig.addNavItem(this.name, {
-				icon: icon,
-				title: title,
-				url: "#/config/" + this.name + "/" + path
-			});
-		};
-
-		// Register
-		featureControllers.register = Feature.register = function(featureName, config) {
-			_features[featureName] = _features[featureName] || new Feature(featureName, config);
-			return _features[featureName];
-		};
-
-		// Page go
-		Feature.go = function(feature, page, filter) {
-			if(!filter) {
-				$wrapState.go("page", {
-					feature: feature,
-					page: page
-				}, 2);
-			} else {
-				$wrapState.go("pageFilter", {
-					feature: feature,
-					page: page,
-					filter: filter
-				}, 2);
-			}
-		};
-
-		// Get feature by name
-		Feature.get = function (featureName) {
-			return _features[featureName];
-		};
-
-		return Feature;
-	});
-
-	// ======================================================================================
-	// =                                   Router config                                    =
-	// ======================================================================================
-	eagleApp.config(function ($stateProvider, $urlRouterProvider, $animateProvider) {
-		// Resolve
-		function _resolve(config) {
-			config = config || {};
-
-			var resolve = {
-				Site: function (Site) {
-					return Site._promise();
-				},
-				Authorization: function (Authorization) {
-					if(!config.roleType) {
-						return Authorization._promise();
-					} else {
-						return Authorization.rolePromise(config.roleType);
-					}
-				},
-				Application: function (Application) {
-					return Application._promise();
-				}
-			};
-
-			if(config.featureCheck) {
-				resolve._navigationCheck = function($q, $wrapState, Site, Application) {
-					var _deferred = $q.defer();
-
-					$q.all(Site._promise(), Application._promise()).then(function() {
-						var _match, i, tmpApp;
-						var _site = Site.current();
-						var _app = Application.current();
-
-						// Check application
-						if(_site && (
-							!_app ||
-							!_site.applicationList.set[_app.tags.application] ||
-							!_site.applicationList.set[_app.tags.application].enabled
-							)
-						) {
-							_match = false;
-
-							for(i = 0 ; i < _site.applicationGroupList.length ; i += 1) {
-								tmpApp = _site.applicationGroupList[i].enabledList[0];
-								if(tmpApp) {
-									_app = Application.current(tmpApp);
-									_match = true;
-									break;
-								}
-							}
-
-							if(!_match) {
-								_app = null;
-								Application.current(null);
-							}
-						}
-					}).finally(function() {
-						_deferred.resolve();
-					});
-
-					return _deferred.promise;
-				};
-			}
-
-			return resolve;
-		}
-
-		// Router
-		var _featureBase = {
-			templateUrl: function ($stateParams) {
-				var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
-				return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + eagleApp._TRS();
-			},
-			controllerProvider: function ($stateParams) {
-				return $stateParams.feature + "_" + $stateParams.page;
-			},
-			resolve: _resolve({featureCheck: true}),
-			pageConfig: "FeaturePageConfig"
-		};
-
-		$urlRouterProvider.otherwise("/landing");
-		$stateProvider
-			// =================== Landing ===================
-			.state('landing', {
-				url: "/landing",
-				templateUrl: "partials/landing.html?_=" + eagleApp._TRS(),
-				controller: "landingCtrl",
-				resolve: _resolve({featureCheck: true})
-			})
-
-			// ================ Authorization ================
-			.state('login', {
-				url: "/login",
-				templateUrl: "partials/login.html?_=" + eagleApp._TRS(),
-				controller: "authLoginCtrl",
-				access: {skipCheck: true}
-			})
-
-			// ================ Configuration ================
-			// Site
-			.state('configSite', {
-				url: "/config/site",
-				templateUrl: "partials/config/site.html?_=" + eagleApp._TRS(),
-				controller: "configSiteCtrl",
-				pageConfig: "ConfigPageConfig",
-				resolve: _resolve({roleType: 'ROLE_ADMIN'})
-			})
-
-			// Application
-			.state('configApplication', {
-				url: "/config/application",
-				templateUrl: "partials/config/application.html?_=" + eagleApp._TRS(),
-				controller: "configApplicationCtrl",
-				pageConfig: "ConfigPageConfig",
-				resolve: _resolve({roleType: 'ROLE_ADMIN'})
-			})
-
-			// Feature
-			.state('configFeature', {
-				url: "/config/feature",
-				templateUrl: "partials/config/feature.html?_=" + eagleApp._TRS(),
-				controller: "configFeatureCtrl",
-				pageConfig: "ConfigPageConfig",
-				resolve: _resolve({roleType: 'ROLE_ADMIN'})
-			})
-
-			// Feature configuration page
-			.state('configFeatureDetail', $.extend({url: "/config/:feature/:page"}, {
-				templateUrl: function ($stateParams) {
-					var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
-					return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + eagleApp._TRS();
-				},
-				controllerProvider: function ($stateParams) {
-					return "config_" + $stateParams.feature + "_" + $stateParams.page;
-				},
-				pageConfig: "ConfigPageConfig",
-				resolve: _resolve({roleType: 'ROLE_ADMIN'})
-			}))
-
-			// =================== Feature ===================
-			// Dynamic feature page
-			.state('page', $.extend({url: "/:feature/:page"}, _featureBase))
-			.state('pageFilter', $.extend({url: "/:feature/:page/:filter"}, _featureBase))
-		;
-
-		// Animation
-		$animateProvider.classNameFilter(/^((?!(fa-spin)).)*$/);
-		$animateProvider.classNameFilter(/^((?!(tab-pane)).)*$/);
-	});
-
-	eagleApp.filter('parseJSON', function () {
-		return function (input, defaultVal) {
-			return common.parseJSON(input, defaultVal);
-		};
-	});
-
-	eagleApp.filter('split', function () {
-		return function (input, regex) {
-			return input.split(regex);
-		};
-	});
-
-	eagleApp.filter('reverse', function () {
-		return function (items) {
-			return items.slice().reverse();
-		};
-	});
-
-	// ======================================================================================
-	// =                                   Main Controller                                  =
-	// ======================================================================================
-	eagleApp.controller('MainCtrl', function ($scope, $wrapState, $http, $injector, ServiceError, PageConfig, FeaturePageConfig, Site, Authorization, Entities, nvd3, Application, Feature, UI) {
-		window.serviceError = $scope.ServiceError = ServiceError;
-		window.site = $scope.Site = Site;
-		window.auth = $scope.Auth = Authorization;
-		window.entities = $scope.Entities = Entities;
-		window.application = $scope.Application = Application;
-		window.pageConfig = $scope.PageConfig = PageConfig;
-		window.featurePageConfig = $scope.FeaturePageConfig = FeaturePageConfig;
-		window.feature = $scope.Feature = Feature;
-		window.ui = $scope.UI = UI;
-		window.nvd3 = nvd3;
-		$scope.app = app;
-		$scope.common = common;
-
-		Object.defineProperty(window, "scope",{
-			get: function() {
-				return angular.element("[ui-view]").scope();
-			}
-		});
-
-		// Clean up
-		$scope.$on('$stateChangeStart', function (event, next, nextParam, current, currentParam) {
-			console.log("[Switch] current ->", current, currentParam);
-			console.log("[Switch] next ->", next, nextParam);
-			// Page initialization
-			PageConfig.reset();
-
-			// Dynamic navigation list
-			if(next.pageConfig) {
-				$scope.PageConfig.navConfig = $injector.get(next.pageConfig);
-			} else {
-				$scope.PageConfig.navConfig = {};
-			}
-
-			// Authorization
-			// > Login check
-			if (!common.getValueByPath(next, "access.skipCheck", false)) {
-				if (!Authorization.isLogin) {
-					console.log("[Authorization] Need access. Redirect...");
-					$wrapState.go("login");
-				}
-			}
-
-			// > Role control
-			/*var _roles = common.getValueByPath(next, "access.roles", []);
-			if (_roles.length && Authorization.userProfile.roles) {
-				var _roleMatch = false;
-				$.each(_roles, function (i, roleName) {
-					if (Authorization.isRole(roleName)) {
-						_roleMatch = true;
-						return false;
-					}
-				});
-
-				if (!_roleMatch) {
-					$wrapState.path("/dam");
-				}
-			}*/
-		});
-
-		$scope.$on('$stateChangeError', function (event, next, nextParam, current, currentParam, error) {
-			console.error("[Switch] Error", arguments);
-		});
-
-		// Get side bar navigation item class
-		$scope.getNavClass = function (page) {
-			var path = page.url.replace(/^#/, '');
-
-			if ($wrapState.path() === path) {
-				PageConfig.pageTitle = PageConfig.pageTitle || page.title;
-				return "active";
-			} else {
-				return "";
-			}
-		};
-
-		// Get side bar navigation item class visible
-		$scope.getNavVisible = function (page) {
-			if (!page.roles) return true;
-
-			for (var i = 0; i < page.roles.length; i += 1) {
-				var roleName = page.roles[i];
-				if (Authorization.isRole(roleName)) {
-					return true;
-				}
-			}
-
-			return false;
-		};
-
-		// Authorization
-		$scope.logout = function () {
-			console.log("[Authorization] Logout. Redirect...");
-			Authorization.logout();
-			$wrapState.go("login");
-		};
-	});
-})();
\ No newline at end of file


[13/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
new file mode 100644
index 0000000..fafe699
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/index.js
@@ -0,0 +1,489 @@
+/*
+ * 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 () {
+	/**
+	 * `register` is global function that for application to set up 'controller', 'service', 'directive', 'route' in Eagle
+	 */
+	var jpmApp = register(['ngRoute', 'ngAnimate', 'ui.router', 'eagle.service']);
+
+	jpmApp.route("jpmList", {
+		url: "/jpm/list?startTime&endTime",
+		site: true,
+		templateUrl: "partials/job/list.html",
+		controller: "listCtrl",
+		resolve: { time: true }
+	}).route("jpmOverview", {
+		url: "/jpm/overview?startTime&endTime",
+		site: true,
+		templateUrl: "partials/job/overview.html",
+		controller: "overviewCtrl",
+		resolve: { time: true }
+	}).route("jpmStatistics", {
+		url: "/jpm/statistics",
+		site: true,
+		templateUrl: "partials/job/statistic.html",
+		controller: "statisticCtrl"
+	}).route("jpmDetail", {
+		url: "/jpm/detail/:jobId",
+		site: true,
+		templateUrl: "partials/job/detail.html",
+		controller: "detailCtrl"
+	}).route("jpmJobTask", {
+		url: "/jpm/jobTask/:jobId?startTime&endTime",
+		site: true,
+		templateUrl: "partials/job/task.html",
+		controller: "jobTaskCtrl"
+	}).route("jpmCompare", {
+		url: "/jpm/compare/:jobDefId?from&to",
+		site: true,
+		reloadOnSearch: false,
+		templateUrl: "partials/job/compare.html",
+		controller: "compareCtrl"
+	});
+
+	jpmApp.portal({name: "YARN Jobs", icon: "taxi", list: [
+		{name: "Overview", path: "jpm/overview"},
+		{name: "Job Statistics", path: "jpm/statistics"},
+		{name: "Job List", path: "jpm/list"}
+	]}, true);
+
+	jpmApp.service("JPM", function ($q, $http, Time, Site, Application) {
+		var JPM = window._JPM = {};
+
+		// TODO: timestamp support
+		JPM.QUERY_LIST = '${baseURL}/rest/entities?query=${query}[${condition}]{${fields}}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+		JPM.QUERY_GROUPS = '${baseURL}/rest/entities?query=${query}[${condition}]<${groups}>{${field}}${order}${top}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+		JPM.QUERY_GROUPS_INTERVAL = '${baseURL}/rest/entities?query=${query}[${condition}]<${groups}>{${field}}${order}${top}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}&intervalmin=${intervalMin}&timeSeries=true';
+		JPM.QUERY_METRICS = '${baseURL}/rest/entities?query=GenericMetricService[${condition}]{*}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+		JPM.QUERY_METRICS_AGG = '${baseURL}/rest/entities?query=GenericMetricService[${condition}]<${groups}>{${field}}${order}${top}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+		JPM.QUERY_METRICS_INTERVAL = '${baseURL}/rest/entities?query=GenericMetricService[${condition}]<${groups}>{${field}}${order}${top}&metricName=${metric}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}&intervalmin=${intervalMin}&timeSeries=true';
+		JPM.QUERY_MR_JOBS = '${baseURL}/rest/mrJobs/search';
+		JPM.QUERY_JOB_LIST = '${baseURL}/rest/mrJobs?query=%s[${condition}]{${fields}}&pageSize=${limit}&startTime=${startTime}&endTime=${endTime}';
+		JPM.QUERY_JOB_STATISTIC = '${baseURL}/rest/mrJobs/jobCountsByDuration?site=${site}&timeDistInSecs=${times}&startTime=${startTime}&endTime=${endTime}&jobType=${jobType}';
+		JPM.QUERY_TASK_STATISTIC = '${baseURL}/rest/mrTasks/taskCountsByDuration?jobId=${jobId}&site=${site}&timeDistInSecs=${times}&top=${top}';
+
+		JPM.QUERY_MR_JOB_COUNT = '${baseURL}/rest/mrJobs/runningJobCounts';
+		//JPM.QUERY_MR_JOB_METRIC_TOP = '${baseURL}eagle-service/rest/mrJobs/jobMetrics/entities';
+
+		/**
+		 * Fetch query content with current site application configuration
+		 * @param {string} queryName
+		 */
+		var getQuery = JPM.getQuery = function(queryName, siteId) {
+			var baseURL;
+			siteId = siteId || Site.current().siteId;
+			var app = Application.find("JPM_WEB_APP", siteId)[0];
+			var host = app.configuration["service.host"];
+			var port = app.configuration["service.port"];
+
+			if(!host && !port) {
+				baseURL = "";
+			} else {
+				if(host === "localhost" || !host) {
+					host = location.hostname;
+				}
+				if(!port) {
+					port = location.port;
+				}
+				baseURL = "http://" + host + ":" + port;
+			}
+
+			return common.template(JPM["QUERY_" + queryName], {baseURL: baseURL});
+		};
+
+
+		function wrapList(promise) {
+			var _list = [];
+			_list._done = false;
+
+			_list._promise = promise.then(
+				/**
+				 * @param {{}} res
+				 * @param {{}} res.data
+				 * @param {{}} res.data.obj
+				 */
+				function (res) {
+				_list.splice(0);
+				Array.prototype.push.apply(_list, res.data.obj);
+				_list._done = true;
+				return _list;
+			});
+			return _list;
+		}
+
+		function toFields(fields) {
+			return (fields || []).length > 0 ? $.map(fields, function (field) {
+				return "@" + field;
+			}).join(",") : "*";
+		}
+
+		JPM.get = function (url, params) {
+			return $http({
+				url: url,
+				method: "GET",
+				params: params
+			});
+		};
+
+		JPM.condition = function (condition) {
+			return $.map(condition, function (value, key) {
+				return "@" + key + '="' + value + '"';
+			}).join(" AND ");
+		};
+
+		/**
+		 * Fetch eagle query list
+		 * @param query
+		 * @param condition
+		 * @param {[]?} groups
+		 * @param {string} field
+		 * @param {number|null} intervalMin
+		 * @param startTime
+		 * @param endTime
+		 * @param {(number|null)?} top
+		 * @param {number?} limit
+		 * @return {[]}
+		 */
+		JPM.groups = function (query, condition, groups, field, intervalMin, startTime, endTime, top, limit) {
+			var fields = field.split(/\s*,\s*/);
+			var orderId = -1;
+			var fieldStr = $.map(fields, function (field, index) {
+				var matches = field.match(/^([^\s]*)(\s+.*)?$/);
+				if(matches[2]) {
+					orderId = index;
+				}
+				return matches[1];
+			}).join(", ");
+
+			var config = {
+				query: query,
+				condition: JPM.condition(condition),
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime),
+				groups: toFields(groups),
+				field: fieldStr,
+				order: orderId === -1 ? "" : ".{" + fields[orderId] + "}",
+				top: top ? "&top=" + top : "",
+				intervalMin: intervalMin,
+				limit: limit || 100000
+			};
+
+			var metrics_url = common.template(intervalMin ? getQuery("GROUPS_INTERVAL") : getQuery("GROUPS"), config);
+			var _list = wrapList(JPM.get(metrics_url));
+			_list._aggInfo = {
+				groups: groups,
+				startTime: Time(startTime).valueOf(),
+				interval: intervalMin * 60 * 1000
+			};
+			_list._promise.then(function () {
+				if(top) _list.reverse();
+			});
+			return _list;
+		};
+
+		/**
+		 * Fetch eagle query list
+		 * @param {string} query
+		 * @param {{}?} condition
+		 * @param {(string|number|{})?} startTime
+		 * @param {(string|number|{})?} endTime
+		 * @param {[]?} fields
+		 * @param {number?} limit
+		 * @return {[]}
+		 */
+		JPM.list = function (query, condition, startTime, endTime, fields, limit) {
+			var config = {
+				query: query,
+				condition: JPM.condition(condition),
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime),
+				fields: toFields(fields),
+				limit: limit || 10000
+			};
+
+			return wrapList(JPM.get(common.template(getQuery("LIST"), config)));
+		};
+
+		/**
+		 * Fetch job list
+		 * @param condition
+		 * @param startTime
+		 * @param endTime
+		 * @param {[]?} fields
+		 * @param {number?} limit
+		 * @return {[]}
+		 */
+		JPM.jobList = function (condition, startTime, endTime, fields, limit) {
+			var config = {
+				condition: JPM.condition(condition),
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime),
+				fields: toFields(fields),
+				limit: limit || 10000
+			};
+
+			var jobList_url = common.template(getQuery("JOB_LIST"), config);
+			return wrapList(JPM.get(jobList_url));
+		};
+
+		/**
+		 * Fetch job metric list
+		 * @param condition
+		 * @param metric
+		 * @param startTime
+		 * @param endTime
+		 * @param {number?} limit
+		 * @return {[]}
+		 */
+		JPM.metrics = function (condition, metric, startTime, endTime, limit) {
+			var config = {
+				condition: JPM.condition(condition),
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime),
+				metric: metric,
+				limit: limit || 10000
+			};
+
+			var metrics_url = common.template(getQuery("METRICS"), config);
+			var _list = wrapList(JPM.get(metrics_url));
+			_list._promise.then(function () {
+				_list.reverse();
+			});
+			return _list;
+		};
+
+		/**
+		 * Fetch job metric list
+		 * @param {{}} condition
+		 * @param {string} metric
+		 * @param {[]} groups
+		 * @param {string} field
+		 * @param {number|null|false} intervalMin
+		 * @param startTime
+		 * @param endTime
+		 * @param {number?} top
+		 * @param {number?} limit
+		 * @return {[]}
+		 */
+		JPM.aggMetrics = function (condition, metric, groups, field, intervalMin, startTime, endTime, top, limit) {
+			var fields = field.split(/\s*,\s*/);
+			var orderId = -1;
+			var fieldStr = $.map(fields, function (field, index) {
+				var matches = field.match(/^([^\s]*)(\s+.*)?$/);
+				if(matches[2]) {
+					orderId = index;
+				}
+				return matches[1];
+			}).join(", ");
+
+			var config = {
+				condition: JPM.condition(condition),
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime),
+				metric: metric,
+				groups: toFields(groups),
+				field: fieldStr,
+				order: orderId === -1 ? "" : ".{" + fields[orderId] + "}",
+				top: top ? "&top=" + top : "",
+				intervalMin: intervalMin,
+				limit: limit || 100000
+			};
+
+			var metrics_url = common.template(intervalMin ? getQuery("METRICS_INTERVAL") : getQuery("METRICS_AGG"), config);
+			var _list = wrapList(JPM.get(metrics_url));
+			_list._aggInfo = {
+				groups: groups,
+				startTime: Time(startTime).valueOf(),
+				interval: intervalMin * 60 * 1000
+			};
+			_list._promise.then(function () {
+				_list.reverse();
+			});
+			return _list;
+		};
+
+		JPM.aggMetricsToEntities = function (list, flatten) {
+			var _list = [];
+			_list.done = false;
+			_list._promise = list._promise.then(function () {
+				var _startTime = list._aggInfo.startTime;
+				var _interval = list._aggInfo.interval;
+
+				$.each(list, function (i, obj) {
+					var tags = {};
+					$.each(list._aggInfo.groups, function (j, group) {
+						tags[group] = obj.key[j];
+					});
+
+					var _subList = $.map(obj.value[0], function (value, index) {
+						return {
+							timestamp: _startTime + index * _interval,
+							value: [value],
+							tags: tags
+						};
+					});
+
+					if(flatten) {
+						_list.push.apply(_list, _subList);
+					} else {
+						_list.push(_subList);
+					}
+				});
+				_list.done = true;
+				return _list;
+			});
+			return _list;
+		};
+
+		/**
+		 * Fetch job duration distribution
+		 * @param {string} site
+		 * @param {string} jobType
+		 * @param {string} times
+		 * @param {{}} startTime
+		 * @param {{}} endTime
+		 */
+		JPM.jobDistribution = function (site, jobType, times, startTime, endTime) {
+			var url = common.template(getQuery("JOB_STATISTIC"), {
+				site: site,
+				jobType: jobType,
+				times: times,
+				startTime: Time.format(startTime),
+				endTime: Time.format(endTime)
+			});
+			return JPM.get(url);
+		};
+
+		JPM.taskDistribution = function (site, jobId, times, top) {
+			var url = common.template(getQuery("TASK_STATISTIC"), {
+				site: site,
+				jobId: jobId,
+				times: times,
+				top: top || 10
+			});
+			return JPM.get(url);
+		};
+
+		/**
+		 * Get job list by sam jobDefId
+		 * @param {string} site
+		 * @param {string|undefined?} jobDefId
+		 * @param {string|undefined?} jobId
+		 * @return {[]}
+		 */
+		JPM.findMRJobs = function (site, jobDefId, jobId) {
+			return wrapList(JPM.get(getQuery("MR_JOBS"), {
+				site: site,
+				jobDefId: jobDefId,
+				jobId: jobId
+			}));
+		};
+
+		/**
+		 * Convert Entity list data to Chart supported series
+		 * @param name
+		 * @param metrics
+		 * @param {{}|boolean?} rawData
+		 * @param {{}?} option
+		 * @return {{name: *, symbol: string, type: string, data: *}}
+		 */
+		JPM.metricsToSeries = function(name, metrics, rawData, option) {
+			if(arguments.length === 3 && typeof rawData === "object") {
+				option = rawData;
+				rawData = false;
+			}
+
+			var data = $.map(metrics, function (metric) {
+				return rawData ? metric.value[0] : {
+					x: metric.timestamp,
+					y: metric.value[0]
+				};
+			});
+			return $.extend({
+				name: name,
+				symbol: 'none',
+				type: "line",
+				data: data
+			}, option || {});
+		};
+
+		JPM.metricsToInterval = function (metricList, interval) {
+			if(metricList.length === 0) return [];
+
+			var list = $.map(metricList, function (metric) {
+				var timestamp = Math.floor(metric.timestamp / interval) * interval;
+				var remainderPtg = (metric.timestamp % interval) / interval;
+				return {
+					timestamp: remainderPtg < 0.5 ? timestamp : timestamp + interval,
+					value: [metric.value[0]]
+				};
+			});
+
+			var resultList = [list[0]];
+			for(var i = 1 ; i < list.length ; i += 1) {
+				var start = list[i - 1];
+				var end = list[i];
+
+				var distance = (end.timestamp - start.timestamp);
+				if(distance > 0) {
+					var steps = distance / interval;
+					var des = (end.value[0] - start.value[0]) / steps;
+					for (var j = 1; j <= steps; j += 1) {
+						resultList.push({
+							timestamp: start.timestamp + j * interval,
+							value: [start.value[0] + des * j]
+						});
+					}
+				}
+			}
+			return resultList;
+		};
+
+		JPM.getStateClass = function (state) {
+			switch ((state || "").toUpperCase()) {
+				case "NEW":
+				case "NEW_SAVING":
+				case "SUBMITTED":
+				case "ACCEPTED":
+					return "warning";
+				case "RUNNING":
+					return "info";
+				case "SUCCESS":
+				case "SUCCEEDED":
+					return "success";
+				case "FINISHED":
+					return "primary";
+				case "FAILED":
+					return "danger";
+			}
+			return "default";
+		};
+
+		return JPM;
+	});
+
+	jpmApp.requireCSS("style/index.css");
+	jpmApp.require("widget/jobStatistic.js");
+	jpmApp.require("ctrl/overviewCtrl.js");
+	jpmApp.require("ctrl/statisticCtrl.js");
+	jpmApp.require("ctrl/listCtrl.js");
+	jpmApp.require("ctrl/detailCtrl.js");
+	jpmApp.require("ctrl/jobTaskCtrl.js");
+	jpmApp.require("ctrl/compareCtrl.js");
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
new file mode 100644
index 0000000..4ab8140
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/compare.html
@@ -0,0 +1,274 @@
+<!--
+  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.
+  -->
+
+<div class="row flex">
+	<div class="col-sm-12 col-md-3">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<h3 class="box-title">
+					Summary
+				</h3>
+			</div>
+			<div class="box-body">
+				<table class="table table-striped">
+					<tbody>
+						<tr>
+							<th>Def Id</th>
+							<td class="text-break">{{jobDefId}}</td>
+						</tr>
+						<tr>
+							<th>Type</th>
+							<td>{{jobList[0].tags.jobType}}</td>
+						</tr>
+						<tr>
+							<th>Site</th>
+							<td>{{jobList[0].tags.site}}</td>
+						</tr>
+						<tr>
+							<th>Owner</th>
+							<td>{{jobList[0].tags.user}}</td>
+						</tr>
+						<tr>
+							<th>Queue</th>
+							<td>{{jobList[0].tags.queue}}</td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+		</div>
+	</div>
+	<div class="col-sm-12 col-md-9">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<h3 class="box-title">
+					Comparison
+					<small>
+						Click to compare job
+						(ctrl + click: set <strong>from Job</strong>, shift + click: set <strong>to Job</strong>)
+					</small>
+				</h3>
+			</div>
+			<div class="box-body">
+				<div class="jpm-chart">
+					<div chart="trendChart" class="jpm-chart-container" series="jobTrendSeries" category="jobTrendCategory"
+						 ng-click="compareJobSelect" option="jobTrendOption"></div>
+					<div ng-if="(jobTrendSeries || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="box box-primary" ng-if="fromJob && toJob">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			Comparison
+		</h3>
+		<div class="box-tools pull-right">
+			<button type="button" class="btn btn-box-tool" data-widget="collapse">
+				<i class="fa fa-minus"></i>
+			</button>
+		</div>
+	</div>
+	<div class="box-body">
+		<table class="table table-striped">
+			<thead>
+			<tr>
+				<th>Field</th>
+				<th>From</th>
+				<th>To</th>
+				<th>Field</th>
+				<th>From</th>
+				<th>To</th>
+			</tr>
+			</thead>
+			<tbody>
+			<tr>
+				<th>
+					Job Id
+					<a class="fa fa-retweet" ng-click="exchangeJobs()"></a>
+				</th>
+				<td><a ui-sref="jpmDetail({siteId: site, jobId: fromJob.tags.jobId})">{{fromJob.tags.jobId}}</a></td>
+				<td><a ui-sref="jpmDetail({siteId: site, jobId: toJob.tags.jobId})">{{toJob.tags.jobId}}</a></td>
+				<th>Duration</th>
+				<td>{{Time.diffStr(fromJob.durationTime)}}</td>
+				<td>
+					{{Time.diffStr(toJob.durationTime)}}
+					<span class="{{jobCompareClass('durationTime')}}">{{jobCompareValue('durationTime')}}</span>
+				</td>
+			</tr>
+			<tr>
+				<th>Total Maps</th>
+				<td>{{common.number.toFixed(fromJob.numTotalMaps)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.numTotalMaps)}}
+					<span class="{{jobCompareClass('numTotalMaps')}}">{{jobCompareValue('numTotalMaps')}}</span>
+				</td>
+				<th>Total Reduces</th>
+				<td>{{common.number.toFixed(fromJob.numTotalReduces)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.numTotalReduces)}}
+					<span class="{{jobCompareClass('numTotalReduces')}}">{{jobCompareValue('numTotalReduces')}}</span>
+				</td>
+			</tr>
+			<tr>
+				<th>HDFS Read Bytes</th>
+				<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}
+					<span class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_READ'])}}">
+							{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_READ'])}}
+						</span>
+				</td>
+				<th>HDFS Write Bytes</th>
+				<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}
+					<span class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_WRITTEN'])}}">
+							{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','HDFS_BYTES_WRITTEN'])}}
+						</span>
+				</td>
+			</tr>
+			<tr>
+				<th>Local Read Bytes</th>
+				<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}
+					<span class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_READ'])}}">
+							{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_READ'])}}
+						</span>
+				</td>
+				<th>Local Write Bytes</th>
+				<td>{{common.number.toFixed(fromJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}
+					<span class="{{jobCompareClass(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_WRITTEN'])}}">
+							{{jobCompareValue(['jobCounters','counters','org.apache.hadoop.mapreduce.FileSystemCounter','FILE_BYTES_WRITTEN'])}}
+						</span>
+				</td>
+			</tr>
+			<tr>
+				<th>Last Map Duration</th>
+				<td>{{common.number.toFixed(fromJob.lastMapDuration)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.lastMapDuration)}}
+					<span class="{{jobCompareClass('lastMapDuration')}}">{{jobCompareValue('lastMapDuration')}}</span>
+				</td>
+				<th>Last Reduce Duration</th>
+				<td>{{common.number.toFixed(fromJob.lastReduceDuration)}}</td>
+				<td>
+					{{common.number.toFixed(toJob.lastReduceDuration)}}
+					<span class="{{jobCompareClass('lastReduceDuration')}}">{{jobCompareValue('lastReduceDuration')}}</span>
+				</td>
+			</tr>
+			<tr>
+				<th>Data Local Maps</th>
+				<td>{{common.number.toFixed(fromJob.dataLocalMapsPercentage * 100)}}%</td>
+				<td>
+					{{common.number.toFixed(toJob.dataLocalMapsPercentage * 100)}}%
+					<span class="{{jobCompareClass('dataLocalMapsPercentage')}}">{{jobCompareValue('dataLocalMapsPercentage')}}</span>
+				</td>
+				<th>Rack Local Maps</th>
+				<td>{{common.number.toFixed(fromJob.rackLocalMapsPercentage * 100)}}%</td>
+				<td>
+					{{common.number.toFixed(toJob.rackLocalMapsPercentage * 100)}}%
+					<span class="{{jobCompareClass('rackLocalMapsPercentage')}}">{{jobCompareValue('rackLocalMapsPercentage')}}</span>
+				</td>
+			</tr>
+			</tbody>
+		</table>
+
+		<div class="row">
+			<div class="col-lg-6 col-md-12">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="comparisonChart_Container.series"
+						 category="comparisonChart_Container.categories"></div>
+					<div ng-if="(comparisonChart_Container.series || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-lg-6 col-md-12">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="comparisonChart_allocatedMB.series"
+						 category="comparisonChart_allocatedMB.categories"></div>
+					<div ng-if="(comparisonChart_allocatedMB.series || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-lg-6 col-md-12">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="comparisonChart_vCores.series"
+						 category="comparisonChart_vCores.categories"></div>
+					<div ng-if="(comparisonChart_vCores.series || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-lg-6 col-md-12">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="comparisonChart_taskDistribution.series"
+						 category="comparisonChart_taskDistribution.categories"></div>
+					<div ng-if="(comparisonChart_taskDistribution.series || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="box box-primary" ng-if="jobList.length">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			History Jobs
+		</h3>
+	</div>
+	<div class="box-body">
+		<div sort-table="jobList" sortpath="-startTime">
+			<table class="table table-bordered table-striped">
+				<thead>
+					<tr>
+						<th width="10" sortpath="currentState">Status</th>
+						<th sortpath="tags.jobId">Id</th>
+						<th sortpath="tags.jobName">Name</th>
+						<th width="140" sortpath="startTime">Start Time</th>
+						<th width="140" sortpath="durationTime">Duration</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr>
+						<td><span class="label label-{{getStateClass(item.currentState)}}">{{item.currentState}}</span></td>
+						<td class="text-no-break">
+							<span ng-if="item.tags.jobId === fromJob.tags.jobId">[From]</span>
+							<span ng-if="item.tags.jobId === toJob.tags.jobId">[To]</span>
+							<a ng-click="compareJobSelect($event, item)">{{item.tags.jobId}}</a>
+							<a class="fa fa-link" ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" target="_blank"></a>
+						</td>
+						<td class="text-break">{{item.tags.jobName}}</td>
+						<td>{{Time.format(item.startTime)}}</td>
+						<td>{{Time.diffStr(item.durationTime)}}</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
new file mode 100644
index 0000000..57561ba
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/detail.html
@@ -0,0 +1,256 @@
+<!--
+  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.
+  -->
+
+<div class="row flex">
+	<div class="col-lg-6 col-md-12">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<h3 class="box-title">
+					Job Info
+					<span class="label label-{{getStateClass(job.currentState)}}">{{job.currentState}}</span>
+				</h3>
+				<div class="pull-right box-tools">
+					<a ui-sref="jpmCompare({siteId: site, jobDefId: job.tags.jobDefId, to: job.tags.jobId})" class="btn btn-primary btn-xs">
+						<span class="fa fa-code-fork"></span>
+						Compare
+					</a>
+				</div>
+			</div>
+			<div class="box-body">
+				<table class="table table-striped">
+					<tbody>
+						<tr>
+							<th>Job Name</th>
+							<td class="text-break">{{job.tags.jobName}}</td>
+							<th>Job Def Id</th>
+							<td class="text-break">{{job.tags.jobDefId}}</td>
+						</tr>
+						<tr>
+							<th>Job Id</th>
+							<td class="text-break">
+								{{job.tags.jobId}}
+								<a class="fa fa-link" href="{{job.trackingUrl}}" target="_blank" ng-if="job.trackingUrl"></a>
+							</td>
+							<th>Job Exec Id</th>
+							<td class="text-break">{{job.tags.jobExecId}}</td>
+						</tr>
+						<tr>
+							<th>User</th>
+							<td>{{job.tags.user}}</td>
+							<th>Queue</th>
+							<td>{{job.tags.queue}}</td>
+						</tr>
+						<tr>
+							<th>Site</th>
+							<td>{{job.tags.site}}</td>
+							<th>Job Type</th>
+							<td>{{job.tags.jobType}}</td>
+						</tr>
+						<tr>
+							<th>Submission Time</th>
+							<td>{{Time.format(job.submissionTime)}}</td>
+							<th>Duration</th>
+							<td class="text-light-blue">{{Time.diffStr(job.durationTime)}}</td>
+						</tr>
+						<tr>
+							<th>Start Time</th>
+							<td>{{Time.format(job.startTime)}}</td>
+							<th>End Time</th>
+							<td>{{Time.format(job.endTime)}}</td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+
+			<div ng-if="!job" class="overlay">
+				<i class="fa fa-refresh fa-spin"></i>
+			</div>
+		</div>
+	</div>
+
+	<div class="col-lg-6 col-md-12">
+		<div class="box box-primary">
+			<div class="box-header with-border">
+				<h3 class="box-title">
+					Map Reduce
+				</h3>
+			</div>
+			<div class="box-body">
+				<table class="table table-striped">
+					<tbody>
+						<tr>
+							<th>Finished Maps</th>
+							<td class="text-success">{{common.number.toFixed(job.numFinishedMaps)}}</td>
+							<th>Failed Maps</th>
+							<td class="text-danger">{{common.number.toFixed(job.numFailedMaps)}}</td>
+							<th>Total Maps</th>
+							<td>{{common.number.toFixed(job.numTotalMaps)}}</td>
+						</tr>
+						<tr>
+							<th>Finished Reduces</th>
+							<td class="text-success">{{common.number.toFixed(job.numFinishedReduces)}}</td>
+							<th>Failed Reduces</th>
+							<td class="text-danger">{{common.number.toFixed(job.numFailedReduces)}}</td>
+							<th>Total Reduces</th>
+							<td>{{common.number.toFixed(job.numTotalReduces)}}</td>
+						</tr>
+						<tr>
+							<th>Data Local Maps</th>
+							<td>
+								{{common.number.toFixed(job.dataLocalMaps)}}
+								({{common.number.toFixed(job.dataLocalMapsPercentage * 100)}}%)
+							</td>
+							<th>Rack Local Maps</th>
+							<td>
+								{{common.number.toFixed(job.rackLocalMaps)}}
+								({{common.number.toFixed(job.rackLocalMapsPercentage * 100)}}%)
+							</td>
+							<th>Total Launched Maps</th>
+							<td>{{common.number.toFixed(job.totalLaunchedMaps)}}</td>
+						</tr>
+						<tr>
+							<th>Map vCores</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.JobCounter"].VCORES_MILLIS_MAPS)}}</td>
+							<th>Map CPU</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters.MapTaskAttemptCounter.CPU_MILLISECONDS)}}</td>
+							<th>HDFS Read Bytes</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+						</tr>
+						<tr>
+							<th>Reduce vCores</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.JobCounter"].VCORES_MILLIS_REDUCES)}}</td>
+							<th>Map CPU</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters.ReduceTaskAttemptCounter.CPU_MILLISECONDS)}}</td>
+							<th>HDFS Write Bytes</th>
+							<td>{{common.number.toFixed(job.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+						</tr>
+						<tr ng-if="!isRunning">
+							<th>Last Map Duration</th>
+							<td>{{Time.diffStr(job.lastMapDuration)}}</td>
+							<th>Last Reduce Duration</th>
+							<td>{{Time.diffStr(job.lastReduceDuration)}}</td>
+							<th></th>
+							<td></td>
+						</tr>
+						<tr ng-if="isRunning">
+							<th>Map Progress</th>
+							<td>{{common.number.toFixed(job.mapProgress)}}%</td>
+							<th>Reduce Progress</th>
+							<td>{{common.number.toFixed(job.reduceProgress)}}%</td>
+							<th></th>
+							<td></td>
+						</tr>
+					</tbody>
+				</table>
+			</div>
+
+			<div ng-if="!job" class="overlay">
+				<i class="fa fa-refresh fa-spin"></i>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			Dashboards
+		</h3>
+		<div class="pull-right box-tools">
+			<a ui-sref="jpmJobTask({siteId: site, jobId: job.tags.jobId, startTime: startTimestamp, endTime: endTimestamp})"
+			   class="btn btn-primary btn-xs" target="_blank" ng-if="!isRunning">
+				<span class="fa fa-map"></span>
+				Task Statistic
+			</a>
+		</div>
+	</div>
+	<div class="box-body">
+		<div class="row">
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="allocatedSeries"></div>
+					<div ng-if="(allocatedSeries || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="vCoresSeries"></div>
+					<div ng-if="(vCoresSeries || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+
+			<div class="col-sm-12 col-md-6" ng-hide="taskBucket">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="taskSeries" category="taskCategory" ng-click="taskSeriesClick"></div>
+					<div ng-if="(taskSeries || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+
+			<div class="col-sm-12 col-md-6" ng-show="taskBucket">
+				<div class="jpm-chart">
+					<div class="jpm-chart-container scroll">
+						<h3>
+							<a class="fa fa-arrow-circle-o-left" ng-click="backToTaskSeries()"></a>
+							Top Tasks
+						</h3>
+
+						<table class="table table-sm table-bordered no-margin">
+							<thead>
+								<tr>
+									<!--th>Task</th-->
+									<th>Host</th>
+									<th>HDFS Read</th>
+									<th>HDFS Write</th>
+									<th>Local Read</th>
+									<th>Local Write</th>
+								</tr>
+							</thead>
+							<tbody>
+								<tr ng-repeat="task in taskBucket.topEntities track by $index">
+									<!--td>{{task.tags.taskId}}</td-->
+									<td>{{task.host || "[" + task.tags.taskId + "]"}}</td>
+									<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)}}</td>
+									<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)}}</td>
+									<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)}}</td>
+									<td>{{common.number.format(task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)}}</td>
+								</tr>
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<div chart class="jpm-chart-container" series="nodeTaskCountSeries" category="nodeTaskCountCategory"
+					></div>
+					<div ng-if="(nodeTaskCountSeries || []).length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
new file mode 100644
index 0000000..d64afe3
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/list.html
@@ -0,0 +1,131 @@
+<!--
+  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.
+  -->
+
+<style>
+	.box .box-header .box-title small a {
+		cursor: pointer;
+		padding: 0 5px;
+		border-right: 1px solid #999;
+	}
+	.box .box-header .box-title small a:last-child {
+		border-right: none;
+	}
+	.box .box-header .box-title small a.text-default {
+		color: #999;
+	}
+
+	.box .box-header .box-title small a.active {
+		font-weight: bolder;
+		text-decoration: underline;
+	}
+</style>
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			Job List
+			<small>
+				<a class="no-select text-{{getStateClass(state.key)}}" ng-class="{active: (tableScope.search || '').toUpperCase() === state.key}"
+					  ng-repeat="state in jobStateList" ng-click="fillSearch(state.key)">
+					{{state.key}}: {{state.value}}
+				</a>
+			</small>
+			<span ng-show="!jobList._done || isSorting" class="fa fa-refresh fa-spin no-animate"></span>
+		</h3>
+	</div>
+	<div class="box-body">
+		<div id="jobList" sort-table="jobList" is-sorting="isSorting" search-path-list="searchPathList" scope="tableScope">
+			<table class="table table-bordered">
+				<thead>
+					<tr>
+						<th sortpath="tags.jobId">Job ID</th>
+						<th sortpath="currentState">Status</th>
+						<th sortpath="tags.user" width="10">User</th>
+						<th sortpath="tags.queue">Queue</th>
+						<th sortpath="submissionTime">Submission Time</th>
+						<th sortpath="startTime">Start Time</th>
+						<th sortpath="endTime">End Time</th>
+						<th sortpath="duration">Duration</th>
+						<th sortpath="numTotalMaps">Map Tasks</th>
+						<th sortpath="numTotalReduces">Reduce Tasks</th>
+						<th sortpath="runningContainers">Containers</th>
+					</tr>
+				</thead>
+				<tbody>
+					<tr ng-repeat="item in jobList">
+						<td>
+							<a ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" target="_blank">{{item.tags.jobId}}</a>
+						</td>
+						<td class="text-center">
+							<span class="label label-sm label-{{getStateClass(item.currentState)}}">
+								{{item.currentState}}
+							</span>
+						</td>
+						<td>{{item.tags.user}}</td>
+						<td>{{item.tags.queue}}</td>
+						<td>{{Time.format(item.submissionTime)}}</td>
+						<td>{{Time.format(item.startTime)}}</td>
+						<td>{{Time.format(item.endTime)}}</td>
+						<td>{{Time.diffStr(item.duration)}}</td>
+						<td>{{item.numTotalMaps}}</td>
+						<td>{{item.numTotalReduces}}</td>
+						<td>{{item.runningContainers || "-"}}</td>
+					</tr>
+				</tbody>
+			</table>
+		</div>
+	</div>
+</div>
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			Running Metrics
+		</h3>
+	</div>
+	<div class="box-body no-padding">
+		<div class="row border-split">
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<h3 class="text-center">Number of Running Jobs</h3>
+					<div chart class="jpm-chart-container" series="runningTrendSeries" option="chartLeftOption"></div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<h3 class="text-center">Running Containers</h3>
+					<div chart class="jpm-chart-container" series="runningContainersSeries" option="chartRightOption"></div>
+				</div>
+			</div>
+		</div>
+		<div class="row border-split">
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<h3 class="text-center">Allocated vCores</h3>
+					<div chart class="jpm-chart-container" series="allocatedvcoresSeries" option="chartLeftOption"></div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart">
+					<h3 class="text-center">Allocated Memory (GB)</h3>
+					<div chart class="jpm-chart-container" series="allocatedMBSeries" option="allocatedMBOption"></div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
new file mode 100644
index 0000000..06e85ea
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/overview.html
@@ -0,0 +1,347 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active"><a href="#hdfsBytes" data-toggle="tab">HDFS IO Bytes</a></li>
+		<li><a href="#hdfsOPs" data-toggle="tab">HDFS IO OPs</a></li>
+		<li><a href="#diskIO" data-toggle="tab">Disk IO</a></li>
+		<li><a href="#cpu" data-toggle="tab">CPU Usage</a></li>
+		<li><a href="#memory" data-toggle="tab">Memory Usage</a></li>
+		<li class="pull-right">
+			<select class="form-control" ng-model="type" ng-change="typeChange()">
+				<option ng-repeat="(type, value) in aggregationMap track by $index" value="{{type}}">By {{common.string.capitalize(type)}}</option>
+			</select>
+		</li>
+	</ul>
+	<div class="tab-content keepContent with-border">
+		<div class="tab-pane active" id="hdfsBytes">
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top HDFS Bytes Read</h3>
+						<div chart class="jpm-chart-container" series="hdfsBtyesReadSeries" option="commonOption"></div>
+						<div ng-if="!hdfsBtyesReadSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+							<tr>
+								<th>Name</th>
+								<th>Total</th>
+							</tr>
+						</thead>
+						<tbody>
+							<tr ng-repeat="item in hdfsBtyesReadSeriesList track by $index">
+								<td class="text-break">
+									<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+										{{item.name}}
+									</a>
+									<span ng-if="type !== 'job'">{{item.name}}</span>
+								</td>
+								<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+							</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+
+			<hr/>
+
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top HDFS Bytes Written</h3>
+						<div chart class="jpm-chart-container" series="hdfsBtyesWrittenSeries"
+							 option="commonOption"></div>
+						<div ng-if="!hdfsBtyesWrittenSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in hdfsBtyesWrittenSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+		<div class="tab-pane" id="hdfsOPs">
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top HDFS Read OPs</h3>
+						<div chart class="jpm-chart-container" series="hdfsReadOpsSeries" option="commonOption"></div>
+						<div ng-if="!hdfsReadOpsSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in hdfsReadOpsSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+
+			<hr/>
+
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top HDFS Write OPs</h3>
+						<div chart class="jpm-chart-container" series="hdfsWriteOpsSeries" option="commonOption"></div>
+						<div ng-if="!hdfsWriteOpsSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in hdfsWriteOpsSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+		<div class="tab-pane" id="diskIO">
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top File Bytes Read</h3>
+						<div chart class="jpm-chart-container" series="fileBytesReadSeries" option="commonOption"></div>
+						<div ng-if="!fileBytesReadSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in fileBytesReadSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+
+			<hr/>
+
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top File Bytes Written</h3>
+						<div chart class="jpm-chart-container" series="fileBytesWrittenSeries"
+							 option="commonOption"></div>
+						<div ng-if="!fileBytesWrittenSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in fileBytesWrittenSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+		<div class="tab-pane" id="cpu">
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top CPU Usage</h3>
+						<div chart class="jpm-chart-container" series="cpuUsageSeries" option="commonOption"></div>
+						<div ng-if="!cpuUsageSeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in cpuUsageSeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+		<div class="tab-pane" id="memory">
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top Physical Memory Usage</h3>
+						<div chart class="jpm-chart-container" series="physicalMemorySeries"
+							 option="commonOption"></div>
+						<div ng-if="!physicalMemorySeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in physicalMemorySeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+
+			<hr/>
+
+			<div class="row">
+				<div class="col-sm-6 col-md-8 col-lg-9">
+					<div class="jpm-chart chart-lg overlay-wrapper">
+						<h3 class="text-center">Top Virtual Memory Usage</h3>
+						<div chart class="jpm-chart-container" series="virtualMemorySeries" option="commonOption"></div>
+						<div ng-if="!virtualMemorySeries._done" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-6 col-md-4 col-lg-3">
+					<table class="table table-striped">
+						<thead>
+						<tr>
+							<th>Name</th>
+							<th>Total</th>
+						</tr>
+						</thead>
+						<tbody>
+						<tr ng-repeat="item in virtualMemorySeriesList track by $index">
+							<td class="text-break">
+								<a ui-sref="jpmDetail({siteId: site, jobId: item.name})" ng-if="type === 'job'" target="_blank">
+									{{item.name}}
+								</a>
+								<span ng-if="type !== 'job'">{{item.name}}</span>
+							</td>
+							<td title="{{item.total}}">{{common.number.abbr(item.total, true)}}</td>
+						</tr>
+						</tbody>
+					</table>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
new file mode 100644
index 0000000..9ce721a
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/statistic.html
@@ -0,0 +1,120 @@
+<!--
+  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.
+  -->
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li ng-class="{active: type === 'hourly'}"><a ng-click="switchType('hourly')">Hourly</a></li>
+		<li ng-class="{active: type === 'daily'}"><a ng-click="switchType('daily')">Daily</a></li>
+		<li ng-class="{active: type === 'weekly'}"><a ng-click="switchType('weekly')">Weekly</a></li>
+		<li ng-class="{active: type === 'monthly'}"><a ng-click="switchType('monthly')">Monthly</a></li>
+	</ul>
+	<div class="tab-content">
+		<div class="jpm-chart">
+			<h3 class="text-center">Number of Submitted Jobs</h3>
+			<div chart class="jpm-chart-container"
+				 series="jobDistributionSeries"
+				 category-func="jobDistributionCategoryFunc"
+				 option="jobDistributionSeriesOption"
+				 ng-click="distributionClick"></div>
+			<div ng-if="(jobDistributionSeries || []).length === 0" class="overlay">
+				<i class="fa fa-refresh fa-spin"></i>
+			</div>
+		</div>
+	</div>
+	<div class="box-body no-padding" ng-show="distributionSelectedIndex !== -1">
+		<div class="row border-split">
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart overlay-wrapper">
+					<h3 class="text-center">[{{distributionSelectedType}}] Top Job Count By User</h3>
+					<div chart class="jpm-chart-container" series="topUserJobCountSeries" category="topUserJobCountSeriesCategory" option="commonChartOption"></div>
+					<div ng-if="topUserJobCountSeries.length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart overlay-wrapper">
+					<h3 class="text-center">[{{distributionSelectedType}}] Top Job Count By Type</h3>
+					<div chart class="jpm-chart-container" series="topTypeJobCountSeries" category="topTypeJobCountSeriesCategory" option="commonChartOption"></div>
+					<div ng-if="topUserJobCountSeries.length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart overlay-wrapper">
+					<h3 class="text-center">[{{distributionSelectedType}}] Top Job Count Trend By User</h3>
+					<div chart class="jpm-chart-container" series="topUserJobCountTrendSeries" category-func="drillDownCategoryFunc" option="commonTrendChartOption"></div>
+					<div ng-if="topUserJobCountTrendSeries.length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6">
+				<div class="jpm-chart overlay-wrapper">
+					<h3 class="text-center">[{{distributionSelectedType}}] Top Job Count Trend By Type</h3>
+					<div chart class="jpm-chart-container" series="topTypeJobCountTrendSeries" category-func="drillDownCategoryFunc" option="commonTrendChartOption"></div>
+					<div ng-if="topUserJobCountTrendSeries.length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-6" ng-show="!jobList">
+				<div class="jpm-chart overlay-wrapper">
+					<h3 class="text-center">[{{distributionSelectedType}}] Job Duration Distribution</h3>
+					<div chart class="jpm-chart-container" series="jobDurationDistributionSeries" category="bucketDurationCategory" option="commonChartOption"></div>
+					<div ng-if="jobDurationDistributionSeries.length === 0" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+			<div class="col-sm-12 col-md-12" ng-show="jobList">
+				<div class="overlay-wrapper">
+					<div sort-table="jobList" style="margin-top: 10px;">
+						<table class="table table-bordered table-striped">
+							<thead>
+								<tr>
+									<th>Job Id</th>
+									<th>Job Name</th>
+									<th>Type</th>
+									<th>User</th>
+									<th width="135">Start Time</th>
+								</tr>
+							</thead>
+							<tbody>
+								<tr>
+									<td>
+										<a ui-sref="jpmDetail({siteId: site, jobId: item.tags.jobId})" target="_blank">{{item.tags.jobId}}</a>
+									</td>
+									<td>{{item.tags.jobName}}</td>
+									<td>{{item.tags.jobType}}</td>
+									<td>{{item.tags.user}}</td>
+									<td>{{Time.format(item.startTime)}}</td>
+								</tr>
+							</tbody>
+						</table>
+					</div>
+
+					<div ng-if="!jobList._done" class="overlay">
+						<i class="fa fa-refresh fa-spin"></i>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
new file mode 100644
index 0000000..9460db6
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/partials/job/task.html
@@ -0,0 +1,149 @@
+<!--
+  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.
+  -->
+
+<div class="box box-primary">
+	<div class="box-header with-border">
+		<h3 class="box-title">
+			Task Schedule Trend
+		</h3>
+	</div>
+	<div class="box-body">
+		<div class="jpm-chart">
+			<div chart class="jpm-chart-container" series="scheduleSeries" category="scheduleCategory"></div>
+			<div ng-if="(scheduleSeries || []).length === 0" class="overlay">
+				<i class="fa fa-refresh fa-spin"></i>
+			</div>
+		</div>
+	</div>
+</div>
+
+<div class="nav-tabs-custom">
+	<ul class="nav nav-tabs">
+		<li class="active"><a href="#scheduleDistribution" data-toggle="tab">Schedule Distribution</a></li>
+		<li><a href="#durationDistribution" data-toggle="tab">Duration Distribution</a></li>
+	</ul>
+	<div class="tab-content keepContent">
+		<!-- By Schedule Distribution -->
+		<div class="tab-pane fade in active" id="scheduleDistribution">
+			<div class="row">
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="statusSeries" category="bucketScheduleCategory" option="statusOption"></div>
+						<div ng-if="(statusSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationSeries" category="bucketScheduleCategory" option="durationOption"></div>
+						<div ng-if="(durationSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="hdfsReadSeries" category="bucketScheduleCategory" option="hdfsReadOption"></div>
+						<div ng-if="(hdfsReadSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="hdfsWriteSeries" category="bucketScheduleCategory" option="hdfsWriteOption"></div>
+						<div ng-if="(hdfsWriteSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="localReadSeries" category="bucketScheduleCategory" option="localReadOption"></div>
+						<div ng-if="(localReadSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="localWriteSeries" category="bucketScheduleCategory" option="localWriteOption"></div>
+						<div ng-if="(localWriteSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+
+		<!-- By Duration Distribution -->
+		<div class="tab-pane fade" id="durationDistribution">
+			<div class="row">
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationStatusSeries" category="bucketDurationCategory" option="durationStatusOption"></div>
+						<div ng-if="(durationStatusSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationMapReduceSeries" category="bucketDurationCategory" option="durationMapReduceOption"></div>
+						<div ng-if="(durationMapReduceSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationHdfsReadSeries" category="bucketDurationCategory" option="durationHdfsReadOption"></div>
+						<div ng-if="(durationHdfsReadSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationHdfsWriteSeries" category="bucketDurationCategory" option="durationHdfsWriteOption"></div>
+						<div ng-if="(durationHdfsWriteSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationLocalReadSeries" category="bucketDurationCategory" option="durationLocalReadOption"></div>
+						<div ng-if="(durationLocalReadSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-12 col-md-6">
+					<div class="jpm-chart">
+						<div chart class="jpm-chart-container" series="durationLocalWriteSeries" category="bucketDurationCategory" option="durationLocalWriteOption"></div>
+						<div ng-if="(durationLocalWriteSeries || []).length === 0" class="overlay">
+							<i class="fa fa-refresh fa-spin"></i>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
new file mode 100644
index 0000000..fbe238f
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/style/index.css
@@ -0,0 +1,76 @@
+@CHARSET "UTF-8";
+/*
+ * 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.
+ */
+
+.jpm-chart {
+	position: relative;
+	margin-bottom: 15px;
+}
+
+.jpm-chart h3 {
+	margin: 10px 0 10px 0;
+}
+
+.jpm-chart .jpm-chart-container {
+	height: 300px;
+	position: relative;
+}
+
+.jpm-chart .jpm-chart-container.scroll {
+	overflow-y: auto;
+}
+
+.jpm-chart.chart-lg .jpm-chart-container {
+	height: 350px;
+}
+
+.with-border .jpm-chart {
+	padding-bottom: 15px;
+	margin-bottom: 15px;
+	border-bottom: 1px solid #f4f4f4;
+}
+
+.with-border .jpm-chart:last-child {
+	padding-bottom: 0;
+	margin-bottom: 0;
+	border-bottom: 0;
+}
+
+.jpm-chart .overlay {
+	top: 0;
+	bottom: 0;
+	position: absolute;
+	width: 100%;
+	background: rgba(255,255,255,0.7);
+}
+
+.jpm-chart .overlay > .fa {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	margin-left: -15px;
+	margin-top: -15px;
+	color: #000;
+	font-size: 30px;
+}
+
+.small-box.jpm {
+	margin: 0;
+	height: 100%;
+	min-height: 110px;
+}

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
----------------------------------------------------------------------
diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
new file mode 100644
index 0000000..1572d5e
--- /dev/null
+++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/widget/jobStatistic.js
@@ -0,0 +1,108 @@
+/*
+ * 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 () {
+	/**
+	 * `register` without params will load the module which using require
+	 */
+	register(function (jpmApp) {
+		jpmApp.directive("jpmWidget", function () {
+			return {
+				restrict: 'AE',
+				controller: function($scope, $interval, Application, JPM, Time) {
+					var site = $scope.site;
+					var refreshInterval;
+
+					if(!site) {
+						$scope.list = $.map(Application.find("JPM_WEB_APP"), function (app) {
+							return {
+								siteId: app.site.siteId,
+								siteName: app.site.siteName || app.site.siteId,
+								count: -1
+							};
+						});
+					} else {
+						$scope.list = [{
+							siteId: site.siteId,
+							siteName: site.siteName || site.siteId,
+							count: -1
+						}];
+					}
+
+					function refresh() {
+						$.each($scope.list, function (i, site) {
+							var query = JPM.getQuery("GROUPS", site.siteId);
+							var url = common.template(query, {
+								query: "RunningJobExecutionService",
+								condition: '@site="' + site.siteId + '" AND @internalState="RUNNING"',
+								groups: "@site",
+								field: "count",
+								order: "",
+								top: "",
+								limit: 100000,
+								startTime: Time.format(Time().subtract(3, "d")),
+								endTime: Time.format(Time().add(1, "d"))
+							});
+							JPM.get(url).then(function (res) {
+								site.count = common.getValueByPath(res, ["data", "obj", 0, "value", 0]);
+							});
+						});
+					}
+
+					refresh();
+					refreshInterval = $interval(refresh, 30 * 1000);
+
+					$scope.$on('$destroy', function() {
+						$interval.cancel(refreshInterval);
+					});
+				},
+				template:
+				'<div class="small-box bg-aqua jpm">' +
+					'<div class="inner">' +
+						'<h3>JPM</h3>' +
+						'<p ng-repeat="site in list track by $index">' +
+							'<a ui-sref="jpmList({siteId: site.siteId})">' +
+								'<strong>{{site.siteName}}</strong>: ' +
+								'<span ng-show="site.count === -1" class="fa fa-refresh fa-spin no-animate"></span>' +
+								'<span ng-show="site.count !== -1">{{site.count}}</span> Running Jobs' +
+							'</a>' +
+						'</p>' +
+					'</div>' +
+					'<div class="icon">' +
+						'<i class="fa fa-taxi"></i>' +
+					'</div>' +
+				'</div>',
+				replace: true
+			};
+		});
+
+		/**
+		 * Customize the widget content. Return false will prevent auto compile.
+		 * @param {{}} $element
+		 * @param {function} $element.append
+		 */
+		function registerWidget($element) {
+			$element.append(
+				$("<div jpm-widget data-site='site'>")
+			);
+		}
+
+		jpmApp.widget("jobStatistic", registerWidget);
+		jpmApp.widget("jobStatistic", registerWidget, true);
+	});
+})();

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/.gitignore
----------------------------------------------------------------------
diff --git a/eagle-server/.gitignore b/eagle-server/.gitignore
new file mode 100644
index 0000000..f3d085a
--- /dev/null
+++ b/eagle-server/.gitignore
@@ -0,0 +1,7 @@
+/bin/
+/target/
+/src/main/webapp/app/dev/apps
+grunt.json
+node_modules
+ui
+tmp

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/pom.xml
----------------------------------------------------------------------
diff --git a/eagle-server/pom.xml b/eagle-server/pom.xml
index 31b219d..13cdff6 100644
--- a/eagle-server/pom.xml
+++ b/eagle-server/pom.xml
@@ -223,9 +223,30 @@
         </profile>
     </profiles>
     <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>exec-ui-install</id>
+                        <phase>generate-sources</phase>
+                        <goals>
+                            <goal>exec</goal>
+                        </goals>
+                        <configuration>
+                            <executable>bash</executable>
+                            <arguments>
+                                <argument>${basedir}/ui-build.sh</argument>
+                            </arguments>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
         <resources>
             <resource>
-                <directory>src/main/webapp/app</directory>
+                <directory>src/main/webapp/app/ui</directory>
                 <targetPath>assets</targetPath>
             </resource>
             <resource>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/.editorconfig
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/.editorconfig b/eagle-server/src/main/webapp/app/.editorconfig
new file mode 100644
index 0000000..42a9b69
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/.editorconfig
@@ -0,0 +1,27 @@
+# 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.
+
+root = true
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = tab
+indent_size = 4
+
+[*.md]
+trim_trailing_whitespace = false

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/Gruntfile.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/Gruntfile.js b/eagle-server/src/main/webapp/app/Gruntfile.js
new file mode 100644
index 0000000..3606d84
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/Gruntfile.js
@@ -0,0 +1,190 @@
+/*
+ * 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';
+
+module.exports = function (grunt) {
+	// ==========================================================
+	// =                     Parse Resource                     =
+	// ==========================================================
+	/*console.log('Generating resource tree...');
+
+	var env = require('jsdom').env;
+	var fs = require('fs');
+
+	var html = fs.readFileSync('dev/index.html', 'utf8');
+
+	console.log("1111", env);
+	env(html, function (err, window) {
+		console.log(">>>!!!");
+		if (err) console.log(err);
+
+		var $ = require('jquery')(window);
+		var $cssList = $('link[href][rel="stylesheet"]');
+		var cssList = $.map($cssList, function (ele) {
+			return $(ele).attr("href");
+		});
+
+		console.log(">>>", cssList);
+	});
+	console.log(">>>222");*/
+
+	// ==========================================================
+	// =                      Grunt Config                      =
+	// ==========================================================
+	grunt.initConfig({
+		config: grunt.file.readJSON('grunt.json'),
+
+		jshint: {
+			options: {
+				browser: true,
+				globals: {
+					$: true,
+					jQuery: true,
+					moment: true
+				}
+			},
+			all: [
+				'dev/**/*.js'
+			]
+		},
+
+		clean: {
+			build: ['ui/', 'tmp/'],
+			tmp: ['tmp/'],
+			ui: ['ui/']
+		},
+
+		copy: {
+			worker: {
+				files: [
+					{expand: true, cwd: 'dev/', src: '<%= config.copy.js.worker %>', dest: 'tmp'}
+				]
+			},
+			ui: {
+				files: [
+					{expand: true, cwd: 'tmp/', src: ['**'], dest: 'ui'},
+					{expand: true, cwd: 'dev/', src: ['public/images/**', 'partials/**'], dest: 'ui'},
+					{expand: true, cwd: 'node_modules/font-awesome/', src: ['fonts/**'], dest: 'ui/public'},
+					{expand: true, cwd: 'node_modules/bootstrap/', src: ['fonts/**'], dest: 'ui/public'}
+				]
+			}
+		},
+
+		concat: {
+			js_project: '<%= config.concat.js.project %>',
+			js_require: '<%= config.concat.js.require %>',
+			css_require: {
+				options: {
+					separator: '\n',
+					process: function(src) {
+						return "@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);" +
+							src.replace('@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);', '');
+					}
+				},
+				src: '<%= config.concat.css.require.src %>',
+				dest: '<%= config.concat.css.require.dest %>'
+			}
+		},
+
+		'regex-replace': {
+			strict: {
+				src: ['tmp/public/js/project.js'],
+				actions: [
+					{
+						name: 'use strict',
+						search: '\\\'use strict\\\';?',
+						replace: '',
+						flags: 'gmi'
+					},
+					{
+						name: 'build timestamp',
+						search: '\\/\\/ GRUNT REPLACEMENT\\: Module\\.buildTimestamp \\= TIMESTAMP',
+						replace: 'Module.buildTimestamp = ' + (+new Date()) + ';',
+						flags: 'gmi'
+					}
+				]
+			}
+		},
+
+		uglify: {
+			project: {
+				options: {
+					mangle: false,
+					sourceMap: true,
+					sourceMapIncludeSources: true
+				},
+				files: [
+					{
+						src: 'tmp/public/js/doc.js',
+						dest: 'tmp/public/js/doc.min.js'
+					}
+				]
+			}
+		},
+
+		cssmin: {
+			project: {
+				files: {
+					'tmp/public/css/project.min.css': '<%= config.concat.css.project.src %>',
+				}
+			}
+		},
+
+		htmlrefs: {
+			project: {
+				src: 'dev/index.html',
+				dest: "tmp/index.html"
+			}
+		},
+	});
+
+	grunt.loadNpmTasks('grunt-contrib-jshint');
+	grunt.loadNpmTasks('grunt-contrib-clean');
+	grunt.loadNpmTasks('grunt-contrib-concat');
+	grunt.loadNpmTasks('grunt-contrib-uglify');
+	grunt.loadNpmTasks('grunt-contrib-cssmin');
+	grunt.loadNpmTasks('grunt-htmlrefs');
+	grunt.loadNpmTasks('grunt-regex-replace');
+	grunt.loadNpmTasks('grunt-contrib-copy');
+
+	grunt.registerTask('default', [
+		// jshint
+		'jshint:all',
+
+		// Clean Env
+		'clean:build',
+
+		// Compress JS
+		'concat:js_require',
+		'copy:worker',
+		'concat:js_project',
+		'regex-replace:strict',
+		'uglify',
+
+		// Compress CSS
+		'cssmin:project',
+		'concat:css_require',
+
+		// Pass HTML Resources
+		'htmlrefs',
+		'copy:ui',
+
+		// Clean Env
+		'clean:tmp'
+	]);
+};

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/README.md
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/README.md b/eagle-server/src/main/webapp/app/README.md
new file mode 100644
index 0000000..b4168d5
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/README.md
@@ -0,0 +1,4 @@
+Apache Eagle Web APP
+==
+
+Web client for Apache Eagle
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-server/src/main/webapp/app/build/index.js
----------------------------------------------------------------------
diff --git a/eagle-server/src/main/webapp/app/build/index.js b/eagle-server/src/main/webapp/app/build/index.js
new file mode 100644
index 0000000..bacbf53
--- /dev/null
+++ b/eagle-server/src/main/webapp/app/build/index.js
@@ -0,0 +1,144 @@
+/*
+ * 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 () {
+	'use strict';
+	console.log('Generating resource tree...');
+
+	var env = require('jsdom').env;
+	var fs = require('fs');
+
+	// Parse tree
+	fs.readFile('dev/index.html', 'utf8', function (err, html) {
+		if (err) return console.log(err);
+
+		env(html, function (err, window) {
+			if (err) console.log(err);
+
+			// Get js / css resource
+			var $ = require('jquery')(window);
+			function getResList(match, attr) {
+				var $eleList = $(match);
+				var requireList = [];
+				var projectList = [];
+				var list = [];
+
+				$.each($eleList, function (i, ele) {
+					var path = $(ele).attr(attr);
+
+					if(path.match(/^apps/)) return;
+
+					if(path.match(/node_modules/)) {
+						requireList.push(path.replace(/\.\.\//, ""));
+						list.push(path.replace(/\.\.\//, ""));
+					} else {
+						projectList.push("dev/" + path);
+						list.push("dev/" + path);
+					}
+				});
+
+				return {
+					list: list,
+					requireList: requireList,
+					projectList: projectList
+				};
+			}
+
+			var cssList = getResList('link[href][rel="stylesheet"]', 'href');
+			var jsList = getResList('script[src]', 'src');
+
+			// JS Worker process
+			var workerFolderPath = 'dev/public/js/worker/';
+			var workerList = fs.readdirSync(workerFolderPath);
+			var workerRequireList = [];
+
+			workerList = workerList.map(function (path) {
+				if(!/\w+Worker\.js/.test(path)) return;
+
+				var workerPath = workerFolderPath + path;
+				var content = fs.readFileSync(workerPath, 'utf8');
+				var regex = /self\.importScripts\(["']([^"']*)["']\)/g;
+				var match;
+				while ((match = regex.exec(content)) !== null) {
+					var modulePath = match[1];
+					workerRequireList.push((workerFolderPath + modulePath).replace(/^dev\//, ""));
+				}
+
+				return workerPath.replace(/^dev\//, "");
+			}).filter(function (path) {
+				return !!path;
+			});
+
+			// Parse grunt config
+			var resJson = {
+				concat: {
+					js: {
+						require: {
+							options: {
+								separator: '\n'
+							},
+							src: jsList.requireList,
+							dest: 'tmp/public/js/modules.js'
+						},
+						project: {
+							options: {
+								separator: '\n',
+								sourceMap :true
+							},
+							src: jsList.projectList,
+							dest: 'tmp/public/js/doc.js'
+						}
+					},
+					css: {
+						require: {
+							src: cssList.requireList.concat('tmp/public/css/project.min.css'),
+							dest: 'tmp/public/css/doc.css'
+						},
+						project: {
+							options: {
+								separator: '\n'
+							},
+							src: cssList.projectList,
+							dest: 'tmp/public/js/project.min.css'
+						}
+					}
+				},
+				copy: {
+					js: {
+						worker: workerList.concat(workerRequireList)
+					}
+				}
+			};
+
+			// Save tree & call grunt
+			fs.writeFile('grunt.json', JSON.stringify(resJson, null, '\t'), 'utf8', function (err) {
+				if(err) return console.log(err);
+
+				console.log("Grunt packaging...");
+				var exec = require('child_process').exec;
+				var grunt = exec('npm run grunt');
+
+				grunt.stdout.pipe(process.stdout);
+				grunt.stderr.pipe(process.stdout);
+				grunt.on('exit', function() {
+					process.exit()
+				})
+			});
+		});
+	});
+})();


[04/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api

Posted by ji...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
deleted file mode 100644
index 207c8df..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/controller.js
+++ /dev/null
@@ -1,1224 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module("featureControllers");
-	var feature = featureControllers.register("common");
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-	feature.service("Policy", function(Entities) {
-		var Policy = function () {};
-
-		Policy.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);
-				}
-			});
-		};
-		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 Policy;
-	});
-
-	feature.service("Notification", function(Entities) {
-		var Notification = function () {};
-		Notification.map = {};
-
-		Notification.list = Entities.queryEntities("AlertNotificationService");
-		Notification.list._promise.then(function () {
-			$.each(Notification.list, function (i, notification) {
-				// Parse fields
-				notification.fieldList = common.parseJSON(notification.fields, []);
-
-				// Fill map
-				Notification.map[notification.tags.notificationType] = notification;
-			});
-		});
-
-		Notification.promise = Notification.list._promise;
-
-		return Notification;
-	});
-
-	// ==============================================================
-	// =                          Policies                          =
-	// ==============================================================
-
-	// ========================= Policy List ========================
-	feature.navItem("policyList", "Policies", "list");
-	feature.controller('policyList', function(PageConfig, Site, $scope, Application, Entities, Policy) {
-		PageConfig.pageTitle = "Policy List";
-		PageConfig.pageSubTitle = Site.current().tags.site;
-
-		// Initial load
-		$scope.policyList = [];
-		$scope.application = Application.current();
-
-		// List policies
-		var _policyList = Entities.queryEntities("AlertDefinitionService", {site: Site.current().tags.site, application: $scope.application.tags.application});
-		_policyList._promise.then(function() {
-			$.each(_policyList, function(i, policy) {
-				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
-
-				$scope.policyList.push(policy);
-			});
-		});
-		$scope.policyList._promise = _policyList._promise;
-
-		// Function
-		$scope.searchFunc = function(item) {
-			var key = $scope.search;
-			if(!key) return true;
-
-			var _key = key.toLowerCase();
-			function _hasKey(item, path) {
-				var _value = common.getValueByPath(item, path, "").toLowerCase();
-				return _value.indexOf(_key) !== -1;
-			}
-			return _hasKey(item, "tags.policyId") || _hasKey(item, "__expression") || _hasKey(item, "description") || _hasKey(item, "owner");
-		};
-
-		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
-		$scope.deletePolicy = function(policy) {
-			Policy.deletePolicy(policy, function(policy) {
-				var _index = $scope.policyList.indexOf(policy);
-				$scope.policyList.splice(_index, 1);
-			});
-		};
-	});
-
-	// ======================= Policy Detail ========================
-	feature.controller('policyDetail', function(PageConfig, Site, $scope, $wrapState, $interval, Entities, Policy, nvd3) {
-		var MAX_PAGESIZE = 10000;
-		var seriesRefreshInterval;
-
-		PageConfig.pageTitle = "Policy Detail";
-		PageConfig.lockSite = true;
-		PageConfig
-			.addNavPath("Policy List", "/common/policyList")
-			.addNavPath("Policy Detail");
-
-		$scope.chartConfig = {
-			chart: "line",
-			xType: "time"
-		};
-
-		// Query policy
-		if($wrapState.param.filter) {
-			$scope.policyList = Entities.queryEntity("AlertDefinitionService", $wrapState.param.filter);
-		} else {
-			$scope.policyList = Entities.queryEntities("AlertDefinitionService", {
-				policyId: $wrapState.param.policy,
-				site: $wrapState.param.site,
-				alertExecutorId: $wrapState.param.executor
-			});
-		}
-
-		$scope.policyList._promise.then(function() {
-			var policy = null;
-
-			if($scope.policyList.length === 0) {
-				$.dialog({
-					title: "OPS!",
-					content: "Policy not found!"
-				}, function() {
-					location.href = "#/common/policyList";
-				});
-				return;
-			} else {
-				policy = $scope.policyList[0];
-
-				policy.__notificationList = common.parseJSON(policy.notificationDef, []);
-
-				policy.__expression = common.parseJSON(policy.policyDef, {}).expression;
-
-				$scope.policy = policy;
-				Site.current(Site.find($scope.policy.tags.site));
-				console.log($scope.policy);
-			}
-
-			// Visualization
-			var _intervalType = 0;
-			var _intervalList = [
-				["Daily", 1440, function() {
-					var _endTime = app.time.now().hour(23).minute(59).second(59).millisecond(0);
-					var _startTime = _endTime.clone().subtract(1, "month").hour(0).minute(0).second(0).millisecond(0);
-					return [_startTime, _endTime];
-				}],
-				["Hourly", 60, function() {
-					var _endTime = app.time.now().minute(59).second(59).millisecond(0);
-					var _startTime = _endTime.clone().subtract(48, "hour").minute(0).second(0).millisecond(0);
-					return [_startTime, _endTime];
-				}],
-				["Every 5 minutes", 5, function() {
-					var _endTime = app.time.now().second(59).millisecond(0);
-					var _minute = Math.floor(_endTime.minute() / 5) * 5;
-					var _startTime = _endTime.clone().minute(_minute).subtract(5 * 30, "minute").second(0).millisecond(0);
-					_endTime.minute(_minute + 4);
-					return [_startTime, _endTime];
-				}]
-			];
-
-			function _loadSeries(seriesName, metricName, condition) {
-				var list = Entities.querySeries("GenericMetricService", $.extend({_metricName: metricName}, condition), "@site", "sum(value)", _intervalList[_intervalType][1]);
-				var seriesList = nvd3.convert.eagle([list]);
-				if(!$scope[seriesName]) $scope[seriesName] = seriesList;
-				list._promise.then(function() {
-					$scope[seriesName] = seriesList;
-				});
-			}
-
-			function refreshSeries() {
-				var _timeRange = _intervalList[_intervalType][2]();
-				var _startTime = _timeRange[0];
-				var _endTime = _timeRange[1];
-				var _cond = {
-					application: policy.tags.application,
-					policyId: policy.tags.policyId,
-					_startTime: _startTime,
-					_endTime: _endTime
-				};
-				console.log("Range:", common.format.date(_startTime, "datetime"), common.format.date(_endTime, "datetime"));
-
-				// > eagle.policy.eval.count
-				_loadSeries("policyEvalSeries", "eagle.policy.eval.count", _cond);
-
-				// > eagle.policy.eval.fail.count
-				_loadSeries("policyEvalFailSeries", "eagle.policy.eval.fail.count", _cond);
-
-				// > eagle.alert.count
-				_loadSeries("alertSeries", "eagle.alert.count", _cond);
-
-				// > eagle.alert.fail.count
-				_loadSeries("alertFailSeries", "eagle.alert.fail.count", _cond);
-			}
-
-			// Alert list
-			$scope.alertList = Entities.queryEntities("AlertService", {
-				site: Site.current().tags.site,
-				application: policy.tags.application,
-				policyId: policy.tags.policyId,
-				_pageSize: MAX_PAGESIZE,
-				_duration: 1000 * 60 * 60 * 24 * 30,
-				__ETD: 1000 * 60 * 60 * 24
-			});
-
-			refreshSeries();
-			seriesRefreshInterval = $interval(refreshSeries, 60000);
-
-			// Menu
-			$scope.visualizationMenu = [
-				{icon: "clock-o", title: "Interval", list:
-					$.map(_intervalList, function(item, index) {
-						var _item = {icon: "clock-o", title: item[0], func: function() {
-							_intervalType = index;
-							refreshSeries();
-						}};
-						Object.defineProperty(_item, 'strong', {
-							get: function() {return _intervalType === index;}
-						});
-						return _item;
-					})
-				}
-			];
-		});
-
-		// Function
-		$scope.updatePolicyStatus = Policy.updatePolicyStatus;
-		$scope.deletePolicy = function(policy) {
-			Policy.deletePolicy(policy, function() {
-				location.href = "#/common/policyList";
-			});
-		};
-
-		// Clean up
-		$scope.$on('$destroy', function() {
-			$interval.cancel(seriesRefreshInterval);
-		});
-	});
-
-	// ======================== Policy Edit =========================
-	function policyCtrl(create, PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
-		PageConfig.pageTitle = create ? "Policy Create" : "Policy Edit";
-		PageConfig.pageSubTitle = Site.current().tags.site;
-		PageConfig
-			.addNavPath("Policy List", "/common/policyList")
-			.addNavPath("Policy Edit");
-
-		var _winTimeDesc = "Number unit[millisecond, sec, min, hour, day, month, year]. e.g. 23 sec";
-		var _winTimeRegex = /^\d+\s+(millisecond|milliseconds|second|seconds|sec|minute|minutes|min|hour|hours|day|days|week|weeks|month|months|year|years)$/;
-		var _winTimeDefaultValue = '10 min';
-		$scope.config = {
-			window: [
-				// Display name, window type, required columns[Title, Description || "LONG_FIELD", Regex check, default value]
-				{
-					title: "Message Time Slide",
-					description: "Using timestamp filed from input is used as event's timestamp",
-					type: "externalTime",
-					fields:[
-						{title: "Field", defaultValue: "timestamp", hide: true},
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
-					]
-				},
-				{
-					title: "System Time Slide",
-					description: "Using System time is used as timestamp for event's timestamp",
-					type: "time",
-					fields:[
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
-					]
-				},
-				{
-					title: "System Time Batch",
-					description: "Same as System Time Window except the policy is evaluated at fixed duration",
-					type: "timeBatch",
-					fields:[
-						{title: "Time", description: _winTimeDesc, regex: _winTimeRegex, defaultValue: _winTimeDefaultValue}
-					]
-				},
-				{
-					title: "Length Slide",
-					description: "The slide window has a fixed length",
-					type: "length",
-					fields:[
-						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
-					]
-				},
-				{
-					title: "Length Batch",
-					description: "Same as Length window except the policy is evaluated in batch mode when fixed event count reached",
-					type: "lengthBatch",
-					fields:[
-						{title: "Number", description: "Number only. e.g. 1023", regex: /^\d+$/}
-					]
-				}
-			]
-		};
-
-		$scope.Notification = Notification;
-
-		$scope.create = create;
-		$scope.encodedRowkey = $wrapState.param.filter;
-
-		$scope.step = 0;
-		$scope.applications = {};
-		$scope.policy = {};
-
-		// ==========================================
-		// =              Notification              =
-		// ==========================================
-		$scope.notificationTabHolder = null;
-
-		$scope.newNotification = function (notificationType) {
-			var __notification = {
-				notificationType: notificationType
-			};
-
-			$.each(Notification.map[notificationType].fieldList, function (i, field) {
-				__notification[field.name] = field.value || "";
-			});
-
-			$scope.policy.__.notification.push(__notification);
-		};
-
-		Notification.promise.then(function () {
-			$scope.menu = Authorization.isRole('ROLE_ADMIN') ? [
-				{icon: "cog", title: "Configuration", list: [
-					{icon: "trash", title: "Delete", danger: true, func: function () {
-						var notification = $scope.notificationTabHolder.selectedPane.data;
-						UI.deleteConfirm(notification.notificationType).then(null, null, function(holder) {
-							common.array.remove(notification, $scope.policy.__.notification);
-							holder.closeFunc();
-						});
-					}}
-				]},
-				{icon: "plus", title: "New", list: $.map(Notification.list, function(notification) {
-					return {
-						icon: "plus",
-						title: notification.tags.notificationType,
-						func: function () {
-							$scope.newNotification(notification.tags.notificationType);
-							setTimeout(function() {
-								$scope.notificationTabHolder.setSelect(-1);
-								$scope.$apply();
-							}, 0);
-						}
-					};
-				})}
-			] : [];
-		});
-
-		// ==========================================
-		// =            Data Preparation            =
-		// ==========================================
-		// Steam list
-		var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application});
-		var _executorList = Entities.queryEntities("AlertExecutorService", {application: Application.current().tags.application});
-		$scope.streamList = _streamList;
-		$scope.executorList = _executorList;
-		$scope.streamReady = false;
-
-		$q.all([_streamList._promise, _executorList._promise]).then(function() {
-			// Map executor with stream
-			$.each(_executorList, function(i, executor) {
-				$.each(_streamList, function(j, stream) {
-					if(stream.tags.application === executor.tags.application && stream.tags.streamName === executor.tags.streamName) {
-						stream.alertExecutor = executor;
-						return false;
-					}
-				});
-			});
-
-			// Fill stream list
-			$.each(_streamList, function(i, unit) {
-				var _srcStreamList = $scope.applications[unit.tags.application] = $scope.applications[unit.tags.application] || [];
-				_srcStreamList.push(unit);
-			});
-
-			$scope.streamReady = true;
-
-			// ==========================================
-			// =                Function                =
-			// ==========================================
-			function _findStream(application, streamName) {
-				var _streamList = $scope.applications[application];
-				if(!_streamList) return null;
-
-				for(var i = 0 ; i < _streamList.length ; i += 1) {
-					if(_streamList[i].tags.streamName === streamName) {
-						return _streamList[i];
-					}
-				}
-				return null;
-			}
-
-			// ==========================================
-			// =              Step control              =
-			// ==========================================
-			$scope.steps = [
-				// >> Select stream
-				{
-					title: "Select Stream",
-					ready: function() {
-						return $scope.streamReady;
-					},
-					init: function() {
-						$scope.policy.__.streamName = $scope.policy.__.streamName ||
-							common.array.find($scope.policy.tags.application, _streamList, "tags.application").tags.streamName;
-					},
-					nextable: function() {
-						var _streamName = common.getValueByPath($scope.policy, "__.streamName");
-						if(!_streamName) return false;
-
-						// Detect stream in current data source list
-						return !!common.array.find(_streamName, $scope.applications[$scope.policy.tags.application], "tags.streamName");
-					}
-				},
-
-				// >> Define Alert Policy
-				{
-					title: "Define Alert Policy",
-					init: function() {
-						// Normal mode will fetch meta list
-						if(!$scope.policy.__.advanced) {
-							var _stream = _findStream($scope.policy.tags.application, $scope.policy.__.streamName);
-							$scope._stream = _stream;
-
-							if(!_stream) {
-								$.dialog({
-									title: "OPS",
-									content: "Stream not found! Current application don't match any stream."
-								});
-								return;
-							}
-
-							if(!_stream.metas) {
-								_stream.metas = Entities.queryEntities("AlertStreamSchemaService", {application: $scope.policy.tags.application, streamName: $scope.policy.__.streamName});
-								_stream.metas._promise.then(function() {
-									_stream.metas.sort(function(a, b) {
-										if(a.tags.attrName < b.tags.attrName) {
-											return -1;
-										} else if(a.tags.attrName > b.tags.attrName) {
-											return 1;
-										}
-										return 0;
-									});
-								});
-							}
-						}
-					},
-					ready: function() {
-						if(!$scope.policy.__.advanced) {
-							return $scope._stream.metas._promise.$$state.status === 1;
-						}
-						return true;
-					},
-					nextable: function() {
-						if($scope.policy.__.advanced) {
-							// Check stream source
-							$scope._stream = null;
-							$.each($scope.applications[$scope.policy.tags.application], function(i, stream) {
-								if(($scope.policy.__._expression || "").indexOf(stream.tags.streamName) !== -1) {
-									$scope._stream = stream;
-									return false;
-								}
-							});
-							return $scope._stream;
-						} else {
-							// Window
-							if($scope.policy.__.windowConfig) {
-								var _winMatch = true;
-								var _winConds = $scope.getWindow().fields;
-								$.each(_winConds, function(i, cond) {
-									if(!(cond.val || "").match(cond.regex)) {
-										_winMatch = false;
-										return false;
-									}
-								});
-								if(!_winMatch) return false;
-
-								// Aggregation
-								if($scope.policy.__.groupAgg) {
-									if(!$scope.policy.__.groupAggPath ||
-										!$scope.policy.__.groupCondOp ||
-										!$scope.policy.__.groupCondVal) {
-										return false;
-									}
-								}
-							}
-						}
-						return true;
-					}
-				},
-
-				// >> Configuration & Notification
-				{
-					title: "Configuration & Notification",
-					nextable: function() {
-						return !!$scope.policy.tags.policyId;
-					}
-				}
-			];
-
-			// ==========================================
-			// =              Policy Logic              =
-			// ==========================================
-			_streamList._promise.then(function() {
-				// Initial policy entity
-				if(create) {
-					$scope.policy = {
-						__: {
-							toJSON: jQuery.noop,
-							conditions: {},
-							notification: [],
-							dedupe: {
-								alertDedupIntervalMin: 10,
-								fields: []
-							},
-							_dedupTags: {},
-							policy: {},
-							window: "externalTime",
-							group: "",
-							groupAgg: "count",
-							groupAggPath: "timestamp",
-							groupCondOp: ">=",
-							groupCondVal: "2"
-						},
-						description: "",
-						enabled: true,
-						prefix: "alertdef",
-						remediationDef: "",
-						tags: {
-							application: Application.current().tags.application,
-							policyType: "siddhiCEPEngine"
-						}
-					};
-
-					// If configured data source
-					if($wrapState.param.app) {
-						$scope.policy.tags.application = $wrapState.param.app;
-						if(common.array.find($wrapState.param.app, Site.current().applicationList, "tags.application")) {
-							setTimeout(function() {
-								$scope.changeStep(0, 2, false);
-								$scope.$apply();
-							}, 1);
-						}
-					}
-
-					// Start step
-					$scope.changeStep(0, 1, false);
-					console.log($scope.policy);
-				} else {
-					var _policy = Entities.queryEntity("AlertDefinitionService", $scope.encodedRowkey);
-					_policy._promise.then(function() {
-						if(_policy.length) {
-							$scope.policy = _policy[0];
-							$scope.policy.__ = {
-								toJSON: jQuery.noop
-							};
-
-							Site.current(Site.find($scope.policy.tags.site));
-						} else {
-							$.dialog({
-								title: "OPS",
-								content: "Policy not found!"
-							}, function() {
-								$wrapState.path("/common/policyList");
-								$scope.$apply();
-							});
-							return;
-						}
-
-						var _application = Application.current();
-						if(_application.tags.application !== $scope.policy.tags.application) {
-							_application = Application.find($scope.policy.tags.application);
-							if(_application) {
-								Application.current(_application, false);
-								console.log("Application not match. Do reload...");
-								$wrapState.reload();
-							} else {
-								$.dialog({
-									title: "OPS",
-									content: "Application not found! Current policy don't match any application."
-								}, function() {
-									$location.path("/common/policyList");
-									$scope.$apply();
-								});
-							}
-							return;
-						}
-
-						// === Revert inner data ===
-						// >> De-dupe
-						$scope.policy.__._dedupTags = {};
-						$scope.policy.__.dedupe = common.parseJSON($scope.policy.dedupeDef, {});
-						$.each($scope.policy.__.dedupe.fields || [], function (i, field) {
-							$scope.policy.__._dedupTags[field] = true;
-						});
-
-						// >> Notification
-						$scope.policy.__.notification = common.parseJSON($scope.policy.notificationDef, []);
-
-						// >> Policy
-						var _policyUnit = $scope.policy.__.policy = common.parseJSON($scope.policy.policyDef);
-
-						// >> Parse expression
-						$scope.policy.__.conditions = {};
-						var _condition = _policyUnit.expression.match(/from\s+(\w+)(\[(.*)])?(#window[^\)]*\))?\s+(select (\w+, )?(\w+)\((\w+)\) as [\w\d_]+ (group by (\w+) )?having ([\w\d_]+) ([<>=]+) ([^\s]+))?/);
-						var _cond_stream = _condition[1];
-						var _cond_query = _condition[3] || "";
-						var _cond_window = _condition[4];
-						var _cond_group = _condition[5];
-						var _cond_groupUnit = _condition.slice(7,14);
-
-						if(!_condition) {
-							$scope.policy.__.advanced = true;
-						} else {
-							// > StreamName
-							var _streamName = _cond_stream;
-							var _cond = _cond_query;
-
-							$scope.policy.__.streamName = _streamName;
-
-							// > Conditions
-							// Loop condition groups
-							if(_cond.trim() !== "" && /^\(.*\)$/.test(_cond)) {
-								var _condGrps = _cond.substring(1, _cond.length - 1).split(/\)\s+and\s+\(/);
-								$.each(_condGrps, function(i, line) {
-									// Loop condition cells
-									var _condCells = line.split(/\s+or\s+/);
-									$.each(_condCells, function(i, cell) {
-										var _opMatch = cell.match(/(\S*)\s*(==|!=|>|<|>=|<=|contains)\s*('(?:[^'\\]|\\.)*'|[\w\d]+)/);
-										if(!common.isEmpty(_opMatch)) {
-											var _key = _opMatch[1];
-											var _op = _opMatch[2];
-											var _val = _opMatch[3];
-											var _conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
-											var _type = "";
-											if(_val.match(/'.*'/)) {
-												_val = _val.slice(1, -1);
-												_type = "string";
-											} else if(_val === "true" || _val === "false") {
-												var _regexMatch = _key.match(/^str:regexp\((\w+),'(.*)'\)/);
-												var _containsMatch = _key.match(/^str:contains\((\w+),'(.*)'\)/);
-												var _mathes = _regexMatch || _containsMatch;
-												if(_mathes) {
-													_key = _mathes[1];
-													_val = _mathes[2];
-													_type = "string";
-													_op = _regexMatch ? "regex" : "contains";
-													_conds = $scope.policy.__.conditions[_key] = $scope.policy.__.conditions[_key] || [];
-												} else {
-													_type = "bool";
-												}
-											} else {
-												_type = "number";
-											}
-											_conds.push($scope._CondUnit(_key, _op, _val, _type));
-										}
-									});
-								});
-							} else if(_cond_query !== "") {
-								$scope.policy.__.advanced = true;
-							}
-						}
-
-						if($scope.policy.__.advanced) {
-							$scope.policy.__._expression = _policyUnit.expression;
-						} else {
-							// > window
-							if(!_cond_window) {
-								$scope.policy.__.window = "externalTime";
-								$scope.policy.__.group = "";
-								$scope.policy.__.groupAgg = "count";
-								$scope.policy.__.groupAggPath = "timestamp";
-								$scope.policy.__.groupCondOp = ">=";
-								$scope.policy.__.groupCondVal = "2";
-							} else {
-								try {
-									$scope.policy.__.windowConfig = true;
-
-									var _winCells = _cond_window.match(/\.(\w+)\((.*)\)/);
-									$scope.policy.__.window = _winCells[1];
-									var _winConds = $scope.getWindow().fields;
-									$.each(_winCells[2].split(","), function(i, val) {
-										_winConds[i].val = val;
-									});
-
-									// Group
-									if(_cond_group) {
-										$scope.policy.__.group = _cond_groupUnit[3];
-										$scope.policy.__.groupAgg = _cond_groupUnit[0];
-										$scope.policy.__.groupAggPath = _cond_groupUnit[1];
-										$scope.policy.__.groupAggAlias = _cond_groupUnit[4] === "aggValue" ? "" : _cond_groupUnit[4];
-										$scope.policy.__.groupCondOp = _cond_groupUnit[5];
-										$scope.policy.__.groupCondVal = _cond_groupUnit[6];
-									} else {
-										$scope.policy.__.group = "";
-										$scope.policy.__.groupAgg = "count";
-										$scope.policy.__.groupAggPath = "timestamp";
-										$scope.policy.__.groupCondOp = ">=";
-										$scope.policy.__.groupCondVal = "2";
-									}
-								} catch(err) {
-									$scope.policy.__.window = "externalTime";
-								}
-							}
-						}
-
-						$scope.changeStep(0, 2, false);
-						console.log($scope.policy);
-					});
-				}
-			});
-
-			// ==========================================
-			// =                Function                =
-			// ==========================================
-			// UI: Highlight select step
-			$scope.stepSelect = function(step) {
-				return step === $scope.step ? "active" : "";
-			};
-
-			// UI: Collapse all
-			$scope.collapse = function(cntr) {
-				var _list = $(cntr).find(".collapse").css("height", "auto");
-				if(_list.hasClass("in")) {
-					_list.removeClass("in");
-				} else {
-					_list.addClass("in");
-				}
-			};
-
-			// Step process. Will fetch target step attribute and return boolean
-			function _check(key, step) {
-				var _step = $scope.steps[step - 1];
-				if(!_step) return;
-
-				var _value = _step[key];
-				if(typeof _value === "function") {
-					return _value();
-				} else if(typeof _value === "boolean") {
-					return _value;
-				}
-				return true;
-			}
-			// Check step is ready. Otherwise will display load animation
-			$scope.stepReady = function(step) {
-				return _check("ready", step);
-			};
-			// Check whether process next step. Otherwise will disable next button
-			$scope.checkNextable = function(step) {
-				return !_check("nextable", step);
-			};
-			// Switch step
-			$scope.changeStep = function(step, targetStep, check) {
-				if(check === false || _check("checkStep", step)) {
-					$scope.step =  targetStep;
-
-					_check("init", targetStep);
-				}
-			};
-
-			// Window
-			$scope.getWindow = function() {
-				if(!$scope.policy || !$scope.policy.__) return null;
-				return common.array.find($scope.policy.__.window, $scope.config.window, "type");
-			};
-
-			// Aggregation
-			$scope.groupAggPathList = function() {
-				return $.grep(common.getValueByPath($scope, "_stream.metas", []), function(meta) {
-					return $.inArray(meta.attrType, ['long','integer','number', 'double', 'float']) !== -1;
-				});
-			};
-
-			$scope.updateGroupAgg = function() {
-				$scope.policy.__.groupAggPath = $scope.policy.__.groupAggPath || common.getValueByPath($scope.groupAggPathList()[0], "tags.attrName");
-
-				if($scope.policy.__.groupAgg === 'count') {
-					$scope.policy.__.groupAggPath = 'timestamp';
-				}
-			};
-
-			// Resolver
-			$scope.resolverTypeahead = function(value, resolver) {
-				var _resolverList = Entities.query("stream/attributeresolve", {
-					site: Site.current().tags.site,
-					resolver: resolver,
-					query: value
-				});
-				return _resolverList._promise.then(function() {
-					return _resolverList;
-				});
-			};
-
-			// Used for input box when pressing enter
-			$scope.conditionPress = function(event) {
-				if(event.which == 13) {
-					setTimeout(function() {
-						$(event.currentTarget).closest(".input-group").find("button").click();
-					}, 1);
-				}
-			};
-			// Check whether has condition
-			$scope.hasCondition = function(key, type) {
-				var _list = common.getValueByPath($scope.policy.__.conditions, key, []);
-				if(_list.length === 0) return false;
-
-				if(type === "bool") {
-					return !_list[0].ignored();
-				}
-				return true;
-			};
-			// Condition unit definition
-			$scope._CondUnit = function(key, op, value, type) {
-				return {
-					key: key,
-					op: op,
-					val: value,
-					type: type,
-					ignored: function() {
-						return this.type === "bool" && this.val === "none";
-					},
-					getVal: function() {
-						return this.type === "string" ? "'" + this.val + "'" : this.val;
-					},
-					toString: function() {
-						return this.op + " " + this.getVal();
-					},
-					toCondString: function() {
-						var _op = this.op === "=" ? "==" : this.op;
-						if(_op === "regex") {
-							return "str:regexp(" + this.key + "," + this.getVal() + ")==true";
-						} else if(_op === "contains") {
-							return "str:contains(" + this.key + "," + this.getVal() + ")==true";
-						} else {
-							return this.key + " " + _op + " " + this.getVal();
-						}
-					}
-				};
-			};
-
-			//for maprfs, if key is status or volume or src/dst, convert these names to id.
-			$scope.convertToID = function(_condList, key, op, name,  type, site){
-				if(key == "dst" || key == "src") {
-					Entities.maprfsNameToID("fNameResolver", name, site)._promise.then(
-						function(response){
-							console.log("success");
-							console.log(response);
-							_condList.push($scope._CondUnit(key, op, response.data, type));
-						},
-						function(error, status){
-							console.log("error: " + status);
-						}
-					);
-				}
-				else if (key == "status"){
-					Entities.maprfsNameToID("sNameResolver", name, site)._promise.then(
-						function(response){
-							console.log("success");
-							console.log(response);
-							_condList.push($scope._CondUnit(key, op, response.data, type));
-						},
-						function(error, status){
-							console.log("error: " + status);
-						}
-					);
-				}
-				else if (key == "volume") {
-					Entities.maprfsNameToID("vNameResolver", name, site)._promise.then(
-						function(response){
-							console.log("success");
-							console.log(response);
-							_condList.push($scope._CondUnit(key, op, response.data, type));
-						},
-						function(error, status){
-							console.log("error: " + status);
-						}
-					);
-				}
-			};
-
-			// Add condition for policy
-			$scope.addCondition = function(key, op, value, type) {
-				if(value === "" || value === undefined) return false;
-
-				var _condList = $scope.policy.__.conditions[key] = $scope.policy.__.conditions[key] || [];
-				_condList.push($scope._CondUnit(key, op, value, type));
-
-				//if it is mapr application, covert human readable name to ids
-				if(Application.current().tags.application == "maprFSAuditLog")  {
-					if ( key == "dst" || key == "src" || key == "volume" || key == "status")  {
-						$scope.convertToID(_condList, key, op, value , type, Site.current().tags.site);
-					}
-				}
-
-				return true;
-			};
-			// Convert condition list to description string
-			$scope.parseConditionDesc = function(key) {
-				return $.map($scope.policy.__.conditions[key] || [], function(cond) {
-					if(!cond.ignored()) return "[" + cond.toString() + "]";
-				}).join(" or ");
-			};
-
-			// To query
-			$scope.toQuery = function() {
-				if(!$scope.policy.__) return "";
-
-				if($scope.policy.__.advanced) return $scope.policy.__._expression;
-
-				// > Query
-				var _query = $.map(common.getValueByPath($scope.policy, "__.conditions", {}), function(list) {
-					var _conds = $.map(list, function(cond) {
-						if(!cond.ignored()) return cond.toCondString();
-					});
-					if(_conds.length) {
-						return "(" + _conds.join(" or ") + ")";
-					}
-				}).join(" and ");
-				if(_query) {
-					_query = "[" + _query + "]";
-				}
-
-				// > Window
-				var _window = $scope.getWindow();
-				var _windowStr = "";
-				if($scope.policy.__.windowConfig) {
-					_windowStr = $.map(_window.fields, function(field) {
-						return field.val;
-					}).join(",");
-					_windowStr = "#window." + _window.type + "(" + _windowStr + ")";
-
-					// > Group
-					if($scope.policy.__.group) {
-						_windowStr += common.template(" select ${group}, ${groupAgg}(${groupAggPath}) as ${groupAggAlias} group by ${group} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
-							group: $scope.policy.__.group,
-							groupAgg: $scope.policy.__.groupAgg,
-							groupAggPath: $scope.policy.__.groupAggPath,
-							groupCondOp: $scope.policy.__.groupCondOp,
-							groupCondVal: $scope.policy.__.groupCondVal,
-							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
-						});
-					} else {
-						_windowStr += common.template(" select ${groupAgg}(${groupAggPath}) as ${groupAggAlias} having ${groupAggAlias} ${groupCondOp} ${groupCondVal}", {
-							groupAgg: $scope.policy.__.groupAgg,
-							groupAggPath: $scope.policy.__.groupAggPath,
-							groupCondOp: $scope.policy.__.groupCondOp,
-							groupCondVal: $scope.policy.__.groupCondVal,
-							groupAggAlias: $scope.policy.__.groupAggAlias || "aggValue"
-						});
-					}
-				} else {
-					_windowStr = " select *";
-				}
-
-				return common.template("from ${stream}${query}${window} insert into outputStream;", {
-					stream: $scope.policy.__.streamName,
-					query: _query,
-					window: _windowStr
-				});
-			};
-
-			// ==========================================
-			// =             Update Policy              =
-			// ==========================================
-			// dedupeDef notificationDef policyDef
-			$scope.finishPolicy = function() {
-				$scope.lock = true;
-
-				// dedupeDef
-				$scope.policy.__.dedupe.fields = $.map($scope.policy.__._dedupTags, function (value, key) {
-					if(value) return key;
-				});
-				$scope.policy.dedupeDef = JSON.stringify($scope.policy.__.dedupe);
-
-				// notificationDef
-				$scope.policy.__.notification = $scope.policy.__.notification || [];
-
-				$scope.policy.notificationDef = JSON.stringify($scope.policy.__.notification);
-
-				// policyDef
-				$scope.policy.__.policy = {
-					expression: $scope.toQuery(),
-					type: "siddhiCEPEngine"
-				};
-				$scope.policy.policyDef = JSON.stringify($scope.policy.__.policy);
-
-				// alertExecutorId
-				if($scope._stream.alertExecutor) {
-					$scope.policy.tags.alertExecutorId = $scope._stream.alertExecutor.tags.alertExecutorId;
-				} else {
-					$scope.lock = false;
-					$.dialog({
-						title: "OPS!",
-						content: "Alert Executor not defined! Please check 'AlertExecutorService'!"
-					});
-					return;
-				}
-
-				// site
-				$scope.policy.tags.site = $scope.policy.tags.site || Site.current().tags.site;
-
-				// owner
-				$scope.policy.owner = Authorization.userProfile.username;
-
-				// Update function
-				function _updatePolicy() {
-					Entities.updateEntity("AlertDefinitionService", $scope.policy)._promise.success(function(data) {
-						$.dialog({
-							title: "Success",
-							content: (create ? "Create" : "Update") + " success!"
-						}, function() {
-							if(data.success) {
-								location.href = "#/common/policyList";
-							} else {
-								$.dialog({
-									title: "OPS",
-									content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
-								});
-							}
-						});
-
-						$scope.create = create = false;
-						$scope.encodedRowkey = data.obj[0];
-					}).error(function(data) {
-						$.dialog({
-							title: "OPS",
-							content: (create ? "Create" : "Update") + "failed!" + JSON.stringify(data)
-						});
-					}).then(function() {
-						$scope.lock = false;
-					});
-				}
-
-				// Check if already exist
-				if($scope.create) {
-					var _checkList = Entities.queryEntities("AlertDefinitionService", {
-						alertExecutorId: $scope.policy.tags.alertExecutorId,
-						policyId: $scope.policy.tags.policyId,
-						policyType: "siddhiCEPEngine",
-						application: $scope.policy.tags.application
-					});
-					_checkList._promise.then(function() {
-						if(_checkList.length) {
-							$.dialog({
-								title: "Override Confirm",
-								content: "Already exists PolicyID '" + $scope.policy.tags.policyId + "'. Do you want to override?",
-								confirm: true
-							}, function(ret) {
-								if(ret) {
-									_updatePolicy();
-								} else {
-									$scope.lock = false;
-									$scope.$apply();
-								}
-							});
-						} else {
-							_updatePolicy();
-						}
-					});
-				} else {
-					_updatePolicy();
-				}
-			};
-		});
-	}
-
-	feature.controller('policyCreate', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
-		var _args = [true];
-		_args.push.apply(_args, arguments);
-		policyCtrl.apply(this, _args);
-	}, "policyEdit");
-	feature.controller('policyEdit', function(PageConfig, Site, Policy, $scope, $wrapState, $q, UI, Entities, Application, Authorization, Notification) {
-		PageConfig.lockSite = true;
-		var _args = [false];
-		_args.push.apply(_args, arguments);
-		policyCtrl.apply(this, _args);
-	});
-
-	// ==============================================================
-	// =                           Alerts                           =
-	// ==============================================================
-
-	// ========================= Alert List =========================
-	feature.navItem("alertList", "Alerts", "exclamation-triangle");
-	feature.controller('alertList', function(PageConfig, Site, $scope, $wrapState, $interval, $timeout, Entities, Application) {
-		PageConfig.pageSubTitle = Site.current().tags.site;
-
-		var MAX_PAGESIZE = 10000;
-
-		// Initial load
-		$scope.application = Application.current();
-
-		$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().tags.site,
-				application: $scope.application.tags.application,
-				_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 ========================
-	feature.controller('alertDetail', function(PageConfig, Site, $scope, $wrapState, Entities) {
-		PageConfig.pageTitle = "Alert Detail";
-		PageConfig.lockSite = true;
-		PageConfig
-			.addNavPath("Alert List", "/common/alertList")
-			.addNavPath("Alert Detail");
-
-		$scope.common = common;
-
-		// Query policy
-		$scope.alertList = Entities.queryEntity("AlertService", $wrapState.param.filter);
-		$scope.alertList._promise.then(function() {
-			if($scope.alertList.length === 0) {
-				$.dialog({
-					title: "OPS!",
-					content: "Alert not found!"
-				}, function() {
-					location.href = "#/common/alertList";
-				});
-			} else {
-				$scope.alert = $scope.alertList[0];
-				$scope.alert.rawAlertContext = JSON.stringify($scope.alert.alertContext, null, "\t");
-				Site.current(Site.find($scope.alert.tags.site));
-				console.log($scope.alert);
-			}
-		});
-
-		// UI
-		$scope.getMessageTime = function(alert) {
-			var _time = common.getValueByPath(alert, "alertContext.timestamp");
-			return Number(_time);
-		};
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
deleted file mode 100644
index 309fac3..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertDetail.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!--
-  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.
-  -->
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title" id="policyId">
-			{{alert.tags.policyId}}
-			<small>{{common.format.date(alert.timestamp)}}</small>
-		</h3>
-	</div><!-- /.box-header -->
-
-	<div class="box-body">
-		<a class="btn btn-primary pull-right" href="#/common/policyDetail/?policy={{alert.tags.policyId}}&site={{alert.tags.site}}&executor={{alert.tags.alertExecutorId}}">View Policy</a>
-
-		<div class="inline-group">
-			<dl><dt>Site</dt><dd>{{alert.tags.site}}</dd></dl>
-			<dl><dt>Data Source</dt><dd>{{alert.tags.application}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Alert Time</dt><dd>{{common.format.date(alert.timestamp)}}</dd></dl>
-			<dl><dt>Message Time</dt><dd>{{common.format.date(alert.alertContext.properties.timestamp)}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Stream Name</dt><dd>{{alert.tags.sourceStreams}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Alert Source</dt><dd>{{alert.tags.alertSource}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>User</dt><dd>{{alert.alertContext.properties.user}}</dd></dl>
-			<dl><dt>Host</dt><dd>{{alert.alertContext.properties.host}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Event</dt><dd>{{alert.alertContext.properties.alertEvent}}</dd></dl>
-		</div>
-		<div class="inline-group">
-			<dl><dt>Message</dt><dd>{{alert.alertContext.properties.alertMessage}}</dd></dl>
-		</div>
-	</div><!-- /.box-body -->
-
-	<div class="overlay" ng-hide="alertList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-
-	<div class="box-footer clearfix">
-		<a data-toggle="collapse" href="[data-id='rawAlertContext']">
-			Raw Alert Context
-		</a>
-		<div data-id="rawAlertContext" class="collapse">
-			<pre>{{alert.rawAlertContext}}</pre>
-		</div>
-	</div>
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
deleted file mode 100644
index 0415cc0..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/page/alertList.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!--
-  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.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			{{application.displayName}}
-		</h3>
-	</div>
-	<div class="box-body">
-		<p ng-show="!alertList.ready">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="alertList" sort="-timestamp">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="170" sortpath="timestamp">Alert Time</th>
-						<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
-						<th width="105" sortpath="tags.application">Application</th>
-						<th width="150" sortpath="tags.policyId">Policy Name</th>
-						<th width="60" sortpath="alertContext.properties.user">User</th>
-						<th width="150" sortpath="tags.alertSource">Source</th>
-						<th sortpath="alertContext.properties.emailMessage">Description</th>
-						<th width="50"> </th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-class="{info : item.__new}">
-						<td>{{common.format.date(item.timestamp)}}</td>
-						<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
-						<td>{{item.tags.application}}</td>
-						<td class="text-nowrap">
-							<a class="fa fa-share-square-o" ng-show="item.tags.policyId"
-							href="#/common/policyDetail/?policy={{item.tags.policyId}}&site={{item.tags.site}}&executor={{item.tags.alertExecutorId}}"> </a>
-							{{item.tags.policyId}}
-						</td>
-						<td>{{item.alertContext.properties.user}}</td>
-						<td>{{item.tags.alertSource}}</td>
-						<td>{{item.alertContext.properties.alertMessage}}</td>
-						<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-
-	</div>
-	<!--div class="box-footer clearfix">
-	</div-->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
deleted file mode 100644
index cdddc43..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyDetail.html
+++ /dev/null
@@ -1,173 +0,0 @@
-<!--
-  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.
-  -->
-
-<div class="box box-info">
-	<div class="box-header with-border">
-		<h3 class="box-title">
-			{{policy.tags.policyId}}
-			<small>{{policy.tags.site}}</small>
-		</h3>
-	</div>
-
-	<div class="box-body">
-		<div class="row">
-			<div class="col-xs-8">
-				<div class="inline-group">
-					<dl><dt>Data Source</dt><dd>{{policy.tags.application}}</dd></dl>
-					<dl><dt>Status</dt><dd>
-						<span ng-show="policy.enabled" class="text-muted"><i class="fa fa-square text-green"></i> Enabled</span>
-						<span ng-show="!policy.enabled" class="text-muted"><i class="fa fa-square text-muted"></i> Disabled</span>
-					</dd></dl>
-				</div>
-				<div class="inline-group">
-					<dl><dt>Description</dt><dd>{{policy.description}}</dd></dl>
-				</div>
-				<!--div class="inline-group">
-					<dl><dt>Alert</dt><dd>
-						<a href="mailto:{{mail}}" ng-repeat="mail in policy.__mailList track by $index" style="margin-right: 10px;">{{mail}}</a>
-						<div tabs>
-							<pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}">
-							</pane>
-						</div>
-					</dd></dl>
-				</div-->
-				<label>Notification</label>
-				<div tabs>
-					<pane ng-repeat="notification in policy.__notificationList track by $index" data-title="{{notification.notificationType}}">
-						<table class="table table-bordered">
-							<tbody>
-								<tr ng-repeat="(key, value) in notification track by $index">
-									<th width="30%">{{key}}</th>
-									<td>{{value}}</td>
-								</tr>
-							</tbody>
-						</table>
-					</pane>
-				</div>
-			</div>
-			<div class="col-xs-4 text-right" ng-show="Auth.isRole('ROLE_ADMIN')">
-				<a class="btn btn-primary" href="#/common/policyEdit/{{policy.encodedRowkey}}">Edit</a>
-				<button class="btn btn-warning" ng-show="!policy.enabled" ng-click="updatePolicyStatus(policy, true)">Enable</button>
-				<button class="btn btn-warning" ng-show="policy.enabled" ng-click="updatePolicyStatus(policy, false)">Disable</button>
-				<button class="btn btn-danger" ng-click="deletePolicy(policy)">Delete</button>
-			</div>
-		</div>
-	</div>
-
-	<div class="overlay" ng-hide="policyList._promise.$$state.status === 1;">
-		<i class="fa fa-refresh fa-spin"></i>
-	</div>
-
-	<div class="box-footer clearfix">
-		<a data-toggle="collapse" href="[data-id='query']">
-			View Query
-		</a>
-		<div data-id="query" class="collapse in">
-			<pre>{{policy.__expression}}</pre>
-		</div>
-	</div>
-</div>
-
-<div tabs>
-	<pane data-title="Visualization" menu="visualizationMenu">
-		<div class="row">
-			<div class="col-xs-6">
-				<div nvd3="policyEvalSeries" data-title="Policy Eval Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-			</div>
-			<div class="col-xs-6">
-				<div nvd3="policyEvalFailSeries" data-title="Policy Eval Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-			</div>
-			<div class="col-xs-6">
-				<div nvd3="alertSeries" data-title="Alert Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-			</div>
-			<div class="col-xs-6">
-				<div nvd3="alertFailSeries" data-title="Alert Fail Count" data-config="chartConfig" class="nvd3-chart-cntr"></div>
-			</div>
-		</div>
-	</pane>
-	<pane data-title="Statistics">
-		<div class="row">
-			<div class="col-xs-3">
-				<div class="info-box bg-aqua">
-					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-					<div class="info-box-content">
-						<span class="info-box-text">Policy Eval Count</span>
-						<span class="info-box-number">{{common.array.sum(policyEvalSeries, "1")}} <small>(Monthly)</small></span>
-						<span class="info-box-number">{{policyEvalSeries[policyEvalSeries.length - 1][1]}} <small>(Daily)</small></span>
-					</div>
-				</div>
-			</div>
-			<div class="col-xs-3">
-				<div class="info-box bg-red">
-					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-					<div class="info-box-content">
-						<span class="info-box-text">Policy Eval Fail Count</span>
-						<span class="info-box-number">{{common.array.sum(policyEvalFailSeries, "1")}} <small>(Monthly)</small></span>
-						<span class="info-box-number">{{policyEvalFailSeries[policyEvalFailSeries.length - 1][1]}} <small>(Daily)</small></span>
-					</div>
-				</div>
-			</div>
-			<div class="col-xs-3">
-				<div class="info-box bg-aqua">
-					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-					<div class="info-box-content">
-						<span class="info-box-text">Alert Count</span>
-						<span class="info-box-number">{{common.array.sum(alertSeries, "1")}} <small>(Monthly)</small></span>
-						<span class="info-box-number">{{alertSeries[alertSeries.length - 1][1]}} <small>(Daily)</small></span>
-					</div>
-				</div>
-			</div>
-			<div class="col-xs-3">
-				<div class="info-box bg-red">
-					<span class="info-box-icon"><i class="fa fa-bookmark-o"></i></span>
-					<div class="info-box-content">
-						<span class="info-box-text">Alert Fail Count</span>
-						<span class="info-box-number">{{common.array.sum(alertFailSeries, "1")}} <small>(Monthly)</small></span>
-						<span class="info-box-number">{{alertFailSeries[alertFailSeries.length - 1][1]}} <small>(Daily)</small></span>
-					</div>
-				</div>
-			</div>
-		</div>
-	</pane>
-	<pane data-title="Alerts">
-		<div sorttable source="alertList" sort="-timestamp">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-				<tr>
-					<th width="170" sortpath="timestamp">Alert Time</th>
-					<th width="170" sortpath="alertContext.properties.timestamp">Message Time</th>
-					<th width="60" sortpath="alertContext.properties.user">User</th>
-					<th width="150" sortpath="alertContext.properties.host">Host</th>
-					<th sortpath="alertContext.properties.emailMessage">Description</th>
-					<th width="50"> </th>
-				</tr>
-				</thead>
-				<tbody>
-				<tr ng-class="{info : item.__new}">
-					<td>{{common.format.date(item.timestamp)}}</td>
-					<td>{{common.format.date(item.alertContext.properties.timestamp)}}</td>
-					<td>{{item.alertContext.properties.user}}</td>
-					<td>{{item.alertContext.properties.host}}</td>
-					<td>{{item.alertContext.properties.alertMessage}}</td>
-					<td><a href="#/common/alertDetail/{{item.encodedRowkey}}">Detail</a></td>
-				</tr>
-				</tbody>
-			</table>
-		</div>
-	</pane>
-</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
deleted file mode 100644
index 33d4cde..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyEdit.html
+++ /dev/null
@@ -1,346 +0,0 @@
-<!--
-  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.
-  -->
-<div class="progress active" ng-show="!streamReady">
-	<div class="progress-bar progress-bar-primary progress-bar-striped" style="width: 100%">
-	</div>
-</div>
-
-<!-- Step navigation -->
-<div ng-show="streamReady">
-	<div class="row step-cntr">
-		<div class="col-sm-4" ng-repeat="step in steps">
-			<div class="step" ng-class="stepSelect($index + 1)">
-				<h1>{{$index + 1}}</h1>
-				<h2>Step {{$index + 1}}</h2>
-				<p title="{{step.title}}">{{step.title}}</p>
-			</div>
-		</div>
-	</div>
-
-	<!-- Step container -->
-	<div class="box box-info">
-		<div class="box-header with-border">
-			<h3 class="box-title">Step {{step}} - {{steps[step - 1].title}}</h3>
-		</div><!-- /.box-header -->
-
-		<div class="box-body">
-			<!-- ---------------------- Step Body Start ---------------------- -->
-
-			<!-- Step 1: Stream -->
-			<div ng-show="step === 1">
-				<div class="pull-right" ng-show="policy.__.advanced === undefined">
-					<span class="text-muted">or</span>
-					<a ng-click="policy.__.advanced = true;">Advanced</a>
-				</div>
-
-				<div class="form-group">
-					<label>Select Stream</label>
-					<select class="form-control" ng-model="policy.__.streamName" ng-show="!policy.__.advanced">
-						<option ng-repeat="stream in applications[policy.tags.application]">{{stream.tags.streamName}}</option>
-					</select>
-					<select class="form-control" ng-show="policy.__.advanced" disabled="disabled">
-						<option>[Advanced Mode]</option>
-					</select>
-				</div>
-
-				<div class="checkbox" ng-show="policy.__.advanced !== undefined">
-					<label>
-						<input type="checkbox" ng-model="policy.__.advanced">
-						Advanced Mode
-					</label>
-				</div>
-			</div>
-
-			<!-- Step 2: Define Alert Policy -->
-			<div ng-show="step === 2 && !policy.__.advanced">
-				<!-- Criteria -->
-				<div>
-					<label>Match Criteria</label>
-					<a ng-click="collapse('.panel-group')">expand / collapse all</a>
-
-					<div class="panel-group panel-group-sm" role="tablist">
-						<div class="panel panel-default"
-							ng-repeat="meta in _stream.metas"
-							ng-init="op = '=='; val = null; type = (meta.attrType || '').toLowerCase();">
-							<div class="panel-heading" role="tab">
-								<h4 class="panel-title">
-									<span class="bg-navy disabled color-palette pull-right">
-										{{parseConditionDesc(meta.tags.attrName)}}
-									</span>
-
-									<a role="button" data-toggle="collapse" href="[data-name='{{meta.tags.attrName}}']" class="collapsed">
-										<span class="fa fa-square" ng-class="hasCondition(meta.tags.attrName, type) ? 'text-green' : 'text-muted'"> </span>
-										{{meta.attrDisplayName || meta.tags.attrName}}
-										<span class="fa fa-question-circle" ng-show="meta.attrDescription"
-										uib-tooltip="{{meta.attrDescription}}" tooltip-placement="right" tooltip-animation="false"> </span>
-									</a>
-								</h4>
-							</div>
-							<div data-name="{{meta.tags.attrName}}" data-type="{{meta.attrType}}" role="tabpanel" class="collapse">
-								<div class="panel-body">
-									<ul ng-show="type !== 'bool'">
-										<li ng-repeat="cond in policy.__.conditions[meta.tags.attrName]">
-											[<a ng-click="policy.__.conditions[meta.tags.attrName].splice($index, 1)">X</a>]
-											{{cond.toString()}}
-										</li>
-									</ul>
-
-									<!-- String -->
-									<div ng-if="type == 'string'">
-										<div class="input-group" style="max-width: 450px;">
-											<div class="input-group-btn">
-												<select class="form-control" ng-model="op">
-													<option ng-repeat="mark in ['==','!=','contains','regex']">{{mark}}</option>
-												</select>
-											</div>
-
-											<!-- With resolver -->
-											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="meta.attrValueResolver"
-												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)"
-												uib-typeahead="item for item in resolverTypeahead($viewValue, meta.attrValueResolver)">
-											<!-- Without resolver -->
-											<input type="text" class="form-control" autocomplete="off" ng-model="val" ng-show="!meta.attrValueResolver"
-												ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
-
-											<span class="input-group-btn">
-												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type);val=null;">Add</button>
-											</span>
-										</div>
-									</div>
-
-									<!-- Number -->
-									<div ng-if="type == 'long' || type == 'integer' || type == 'number' || type == 'double' || type == 'float'">
-										<div class="input-group" style="max-width: 450px;">
-											<div class="input-group-btn">
-												<select class="form-control" ng-model="op">
-													<option ng-repeat="mark in ['==','!=','>','>=','<','<=']">{{mark}}</option>
-												</select>
-											</div>
-
-											<input type="number" class="form-control" autocomplete="off" placeholder="Number Only..." ng-model="val" ng-keypress="conditionPress($event, meta.tags.attrName, op, val, type)">
-											<span class="input-group-btn">
-												<button class="btn btn-info btn-flat" type="button" ng-click="addCondition(meta.tags.attrName, op, val, type) ? val=null : void(0);">Add</button>
-											</span>
-										</div>
-									</div>
-
-									<!-- Boolean -->
-									<div ng-if="type == 'bool'" ng-init="policy.__.conditions[meta.tags.attrName] = policy.__.conditions[meta.tags.attrName] || [_CondUnit(meta.tags.attrName, '==', 'none', 'bool')]">
-										<select class="form-control" ng-model="policy.__.conditions[meta.tags.attrName][0].val" style="max-width: 100px;">
-											<option ng-repeat="bool in ['none','true','false']">{{bool}}</option>
-										</select>
-									</div>
-								</div>
-							</div>
-						</div>
-					</div>
-				</div>
-
-				<!-- Window -->
-				<div class="checkbox">
-					<label>
-						<input type="checkbox" ng-checked="policy.__.windowConfig" ng-click="policy.__.windowConfig = !policy.__.windowConfig"> Slide Window
-					</label>
-				</div>
-				<div ng-show="policy.__.windowConfig">
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group">
-								<label>Window</label>
-								<select class="form-control" ng-model="policy.__.window"
-								uib-tooltip="{{getWindow().description}}" tooltip-animation="false">
-									<option ng-repeat="item in config.window" value="{{item.type}}">{{item.title}}</option>
-								</select>
-							</div>
-						</div>
-
-						<!-- fields -->
-						<div class="col-md-4" ng-repeat="field in getWindow().fields" ng-init="field.val = field.val || (field.defaultValue || '');" ng-hide="field.hide">
-							<div class="form-group" ng-class="{'has-warning' : !field.val || !field.val.match(field.regex)}">
-								<label>Window - {{field.title}}</label>
-								<input type="text" class="form-control" autocomplete="off" placeholder="{{field.description}}" ng-model="field.val" title="{{field.description}}">
-							</div>
-						</div>
-					</div>
-
-					<!-- Aggregation -->
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group" ng-class="{'text-yellow' : (policy.__.groupAgg && !policy.__.groupAggPath)}">
-								<label>Aggregation</label>
-								<div class="input-group">
-									<div class="input-group-btn">
-										<select class="form-control" ng-model="policy.__.groupAgg" ng-change="updateGroupAgg()">
-											<option ng-repeat="op in ['max','min','avg','count', 'sum']">{{op}}</option>
-										</select>
-									</div>
-									<select class="form-control" ng-model="policy.__.groupAggPath" ng-class="{'has-warning' : !policy.__.groupAggPath}" id="groupAggPath"
-											ng-show="policy.__.groupAgg" ng-disabled="policy.__.groupAgg === 'count'">
-										<option ng-repeat="meta in groupAggPathList()">{{meta.tags.attrName}}</option>
-									</select>
-								</div>
-							</div>
-						</div>
-
-						<div class="col-md-4">
-							<div class="form-group" ng-class="{'text-yellow' : (!policy.__.groupCondOp || !policy.__.groupCondVal)}">
-								<label>Condition</label>
-								<div class="input-group">
-									<div class="input-group-btn">
-										<select class="form-control" ng-model="policy.__.groupCondOp" ng-class="{'has-warning' : !policy.__.groupCondOp}">
-											<option ng-repeat="op in ['>','<','>=','<=','==']">{{op}}</option>
-										</select>
-									</div>
-									<input type="text" class="form-control" ng-model="policy.__.groupCondVal" ng-class="{'has-warning' : !policy.__.groupCondVal}" />
-								</div>
-							</div>
-						</div>
-
-						<div class="col-md-4">
-							<div class="form-group">
-								<label>Alias (Optional)</label>
-								<input type="text" class="form-control" ng-model="policy.__.groupAggAlias" placeholder="Default: aggValue" />
-							</div>
-						</div>
-					</div>
-
-					<!-- Group -->
-					<div class="row">
-						<div class="col-md-4">
-							<div class="form-group">
-								<label>Group By</label>
-								<select class="form-control" ng-model="policy.__.group">
-									<option value="">None</option>
-									<option ng-repeat="meta in _stream.metas">{{meta.tags.attrName}}</option>
-								</select>
-							</div>
-						</div>
-					</div>
-				</div>
-			</div>
-
-			<!-- Step 2: Define Alert Policy -->
-			<div ng-show="step === 2 && policy.__.advanced">
-				<div class="form-group">
-					<label>Query Expression</label>
-					<textarea class="form-control" ng-model="policy.__._expression"
-					placeholder="Query expression. e.g. from hdfsAuditLogEventStream[(cmd=='open') and (host=='localhost' or host=='127.0.0.1')]#window.time(2 sec) select * insert into outputStream;" rows="5"></textarea>
-				</div>
-			</div>
-
-			<!-- Step 3: Email Notification -->
-			<div ng-show="step === 3">
-				<div class="row">
-					<div class="col-xs-4">
-						<div class="form-group" ng-class="{'has-warning' : !policy.tags.policyId}">
-							<label>Policy Name</label>
-							<input type="text" class="form-control" placeholder="" ng-model="policy.tags.policyId" ng-disabled="!create">
-						</div>
-					</div>
-					<div class="col-xs-3">
-						<div class="form-group">
-							<label>
-								Alert De-Dup Interval(min)
-								<span class="fa fa-question-circle" uib-tooltip="Same type alert will be De-dup in configured interval"> </span>
-							</label>
-							<input type="number" class="form-control" ng-model="policy.__.dedupe.alertDedupIntervalMin" placeholder="[Minute] Number only. Suggestion: 720">
-						</div>
-					</div>
-					<div class="col-xs-2">
-						<div class="form-group">
-							<label>
-								Enabled
-							</label>
-							<p>
-								<input type="checkbox" checked="checked" ng-model="policy.enabled">
-							</p>
-						</div>
-					</div>
-				</div>
-
-				<div>
-					<a data-toggle="collapse" href="[data-id='advancedDeDup']">Advanced De-Dup</a>
-					<div data-id='advancedDeDup' class="collapse">
-						<label>
-							De-Dup Key
-							<span class="fa fa-question-circle" uib-tooltip="Type of grouping alerts. If you don't know how to config, leave it default."> </span>
-						</label>
-						<div class="form-group">
-							<div class="checkbox" ng-repeat="meta in _stream.metas" ng-init="create ? policy.__._dedupTags[meta.tags.attrName] = !!meta.usedAsTag : void(0);">
-								<label>
-									<input type="checkbox" ng-model="policy.__._dedupTags[meta.tags.attrName]">
-									{{meta.tags.attrName}}
-								</label>
-							</div>
-						</div>
-					</div>
-				</div>
-
-				<hr/>
-
-				<label>Notification</label>
-				<div tabs menu="menu" holder="notificationTabHolder" ng-show="policy.__.notification.length">
-					<pane ng-repeat="notification in policy.__.notification track by $index" data-data="notification" data-title="{{notification.notificationType}}">
-						<div class="form-group" ng-repeat="field in Notification.map[notification.notificationType].fieldList track by $index">
-							<label>{{field.name}}</label>
-							<input type="text" class="form-control" ng-model="notification[field.name]">
-						</div>
-						<p class="text-muted" ng-if="Notification.map[notification.notificationType].fieldList.length === 0">No configuration required</p>
-					</pane>
-				</div>
-				<ul ng-show="policy.__.notification.length === 0">
-					<li ng-repeat="notification in Notification.list track by $index">
-						<a ng-click="newNotification(notification.tags.notificationType)">+ New {{notification.tags.notificationType}} Notification</a>
-					</li>
-				</ul>
-
-				<hr/>
-
-				<div class="form-group">
-					<label>Description</label>
-					<textarea class="form-control" placeholder="Policy description" ng-model="policy.description"></textarea>
-				</div>
-
-				<a data-toggle="collapse" href="[data-id='policyQuery']">
-					View Query
-				</a>
-				<div class="collapse in" data-id="policyQuery">
-					<pre>{{toQuery()}}</pre>
-				</div>
-			</div>
-
-			<!-- ----------------------- Step Body End ----------------------- -->
-		</div><!-- /.box-body -->
-
-		<div class="overlay" ng-hide="stepReady(step)">
-			<span class="fa fa-refresh fa-spin"> </span>
-		</div>
-
-		<div class="box-footer text-right">
-			<button class="btn btn-info" ng-show="step > 1" ng-click="changeStep(step, step - 1, false)" ng-disabled="lock">
-				Prev <span class="fa fa-arrow-circle-o-left"> </span>
-			</button>
-			<button class="btn btn-info" ng-show="step < steps.length" ng-click="changeStep(step, step + 1)" ng-disabled="checkNextable(step) || lock">
-				Next <span class="fa fa-arrow-circle-o-right"> </span>
-			</button>
-			<button class="btn btn-info" ng-show="step === steps.length" ng-click="finishPolicy()" ng-disabled="checkNextable(step) || lock">
-				Done <span class="fa fa-check-circle-o"> </span>
-			</button>
-		</div>
-	</div>
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html b/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
deleted file mode 100644
index 20a38dd..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/common/page/policyList.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<!--
-  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.
-  -->
-<div class="box box-primary">
-	<div class="box-header with-border">
-		<i class="fa fa-list-alt"> </i>
-		<h3 class="box-title">
-			{{application.displayName}}
-		</h3>
-	</div>
-	<div class="box-body">
-		<div class="row">
-			<div class="col-xs-3">
-				<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 class="col-xs-6">
-				<div class="inline-group form-inline text-muted">
-					<dl><dt><i class="fa fa-square text-green"> </i></dt><dd>Enabled</dd></dl>
-					<dl><dt><i class="fa fa-square text-muted"> </i></dt><dd>Disabled</dd></dl>
-				</div>
-			</div>
-			<div class="col-xs-3 text-right">
-				<a class="btn btn-primary" href="#/common/policyCreate/{{!application ? '' : '?app=' + application.tags.application}}" ng-show="Auth.isRole('ROLE_ADMIN')">
-					New Policy
-					<i class="fa fa-plus-circle"> </i>
-				</a>
-			</div>
-		</div>
-
-		<p ng-show="policyList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div sorttable source="policyList" ng-show="policyList._promise.$$state.status === 1" search="false" searchfunc="searchFunc">
-			<table class="table table-bordered" ng-non-bindable>
-				<thead>
-					<tr>
-						<th width="30" sortpath="enabled"> </th>
-						<th width="200" sortpath="tags.policyId">Policy Name</th>
-						<th sortpath="description">Description</th>
-						<th width="150" sortpath="owner">Owner</th>
-						<th width="170" sortpath="lastModifiedDate">Last Modified</th>
-						<th width="95" ng-show="_parent.Auth.isRole('ROLE_ADMIN')">Action</th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr>
-						<td><span class='fa fa-square' ng-class="item.enabled ? 'text-green' : 'text-muted'"> </span></td>
-						<td><a href="#/common/policyDetail/{{item.encodedRowkey}}" style="width: 200px;" class="text-breakall">{{item.tags.policyId}}</a></td>
-						<td>{{item.description}}</td>
-						<td>{{item.owner}}</td>
-						<td>{{common.format.date(item.lastModifiedDate) || "-"}}</td>
-						<td ng-show="_parent.Auth.isRole('ROLE_ADMIN')">
-							<a class="fa fa-pencil btn btn-default btn-xs" uib-tooltip="Edit" tooltip-animation="false" href="#/common/policyEdit/{{item.encodedRowkey}}"> </a>
-							<button class="fa fa-play sm btn btn-default btn-xs" uib-tooltip="Enable" tooltip-animation="false" ng-show="!item.enabled" ng-click="_parent.updatePolicyStatus(item, true)"> </button>
-							<button class="fa fa-pause sm btn btn-default btn-xs" uib-tooltip="Disable" tooltip-animation="false" ng-show="item.enabled" ng-click="_parent.updatePolicyStatus(item, false)"> </button>
-							<button class="rm fa fa-trash-o btn btn-danger btn-xs" uib-tooltip="Delete" tooltip-animation="false" ng-click="_parent.deletePolicy(item)"> </button>
-						</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div>
-	<!--div class="box-footer clearfix">
-	</div-->
-</div>

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js b/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
deleted file mode 100644
index c17d612..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/metadata/controller.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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() {
-	'use strict';
-
-	var featureControllers = angular.module('featureControllers');
-	var feature = featureControllers.register("metadata");
-
-	// ==============================================================
-	// =                          Function                          =
-	// ==============================================================
-
-	// ==============================================================
-	// =                          Metadata                          =
-	// ==============================================================
-
-	// ======================= Metadata List ========================
-	feature.navItem("streamList", "Metadata", "bullseye");
-	feature.controller('streamList', function(PageConfig, Site, $scope, $q, Application, Entities) {
-		PageConfig.hideSite = true;
-
-		$scope.streams = {};
-		$scope._streamEntity = null;
-		$scope._streamEntityLock = false;
-
-		// =========================================== List ===========================================
-		var _streamList = Entities.queryEntities("AlertStreamService", {application: Application.current().tags.application});
-		var _streamSchemaList = Entities.queryEntities("AlertStreamSchemaService", {application: Application.current().tags.application});
-		$scope.streamList = _streamList;
-		$scope.streamSchemaList = _streamSchemaList;
-
-		_streamList._promise.then(function() {
-			$.each(_streamList, function(i, stream) {
-				stream.metaList = [];
-				$scope.streams[stream.tags.application + "_" + stream.tags.streamName] = stream;
-			});
-		});
-
-		$q.all([_streamList._promise, _streamSchemaList._promise]).then(function() {
-			$.each(_streamSchemaList, function(i, meta) {
-				var _stream = $scope.streams[meta.tags.application + "_" + meta.tags.streamName];
-				if(_stream) {
-					_stream.metaList.push(meta);
-				} else {
-					console.warn("[Meta] Stream not match:", meta.tags.application + "_" + meta.tags.streamName);
-				}
-			});
-		});
-	});
-})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
----------------------------------------------------------------------
diff --git a/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html b/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
deleted file mode 100644
index 2b73300..0000000
--- a/eagle-webservice/src/main/webapp/app/public/feature/metadata/page/streamList.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<!--
-  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.
-  -->
-<p ng-show="streamList._promise.$$state.status !== 1">
-	<span class="fa fa-refresh fa-spin"> </span>
-	Loading...
-</p>
-
-<div class="box box-primary" ng-repeat="stream in streams">
-	<div class="box-header with-border">
-		<h3 class="box-title">{{stream.tags.streamName}}</h3>
-	</div>
-	<div class="box-body">
-		<div class="inline-group">
-			<dl>
-				<dt>
-					Data Source
-				</dt>
-				<dd>
-					{{stream.tags.application}}
-				</dd>
-			</dl>
-			<dl>
-				<dt>
-					Stream Name
-				</dt>
-				<dd>
-					{{stream.tags.streamName}}
-				</dd>
-			</dl>
-		</div>
-		<div>
-			<dl>
-				<dt>
-					Description
-				</dt>
-				<dd>
-					{{stream.description}}
-				</dd>
-			</dl>
-		</div>
-
-		<p ng-show="streamSchemaList._promise.$$state.status !== 1">
-			<span class="fa fa-refresh fa-spin"> </span>
-			Loading...
-		</p>
-
-		<div class="list" ng-show="streamSchemaList._promise.$$state.status === 1">
-			<table class="table table-bordered">
-				<thead>
-					<tr>
-						<th width="10%">Attribute Name</th>
-						<th width="12%">Display Name</th>
-						<th width="8%">Type</th>
-						<th>Description</th>
-					</tr>
-				</thead>
-				<tbody>
-					<tr ng-repeat="meta in stream.metaList">
-						<td>{{meta.tags.attrName}}</td>
-						<td>{{meta.attrDisplayName}}</td>
-						<td><span class="label label-warning">{{meta.attrType}}</span></td>
-						<td>{{meta.attrDescription}}</td>
-					</tr>
-				</tbody>
-			</table>
-		</div>
-	</div><!-- /.box-body -->
-	<!-- Loading (remove the following to stop the loading)-->
-</div>
\ No newline at end of file