eagle-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From h..@apache.org
Subject [3/7] incubator-eagle git commit: EAGLE-139 EAGLE-163 Eagle UI Modularization and fix bugs in policy extensions
Date Thu, 25 Feb 2016 10:25:19 GMT
http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/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..f048a85
--- /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, dataSource: "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/d37643b0/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/d37643b0/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/d37643b0/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/d37643b0/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
index f18b846..1e6fd79 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.config.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.config.js
@@ -1,77 +1,104 @@
-/*
- * 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/list?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
-			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
-			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
-
-			queryGroup: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
-			querySeries: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
-
-			query: 'rest/',
-
-			userProfile: 'rest/authentication',
-			logout: 'logout',
-		},
-
-		// ============================================================================
-		// =                               Data Sources                               =
-		// ============================================================================
-		dataSource: {
-			uiInvisibleList: ["userProfile"],
-		},
-	};
-
-	// ============================================================================
-	// =                                   URLs                                   =
-	// ============================================================================
-	app.getURL = function(name, kvs) {
-		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
-		var _path = app.config.urls[name];
-		if(!_path) throw "URL:'" + name + "' not exist!";
-		var _url = (_host ? _host + "/" : '') + _path;
-		if(kvs !== undefined) {
-			_url = common.template(_url, kvs);
-		}
-		return _url;
-	};
-
-	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";
+/*
+ * 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/list?query=${serviceName}[${condition}]{${values}}&pageSize=100000',
+			deleteEntity: 'rest/entities/delete?serviceName=${serviceName}&byId=true',
+			deleteEntities: 'rest/entities?query=${serviceName}[${condition}]{*}&pageSize=100000',
+
+			queryGroup: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000',
+			querySeries: 'rest/list?query=${serviceName}[${condition}]<${groupBy}>{${values}}&pageSize=100000&timeSeries=true&intervalmin=${intervalmin}',
+
+			query: 'rest/',
+
+			userProfile: 'rest/authentication',
+			logout: 'logout',
+
+			DELETE_HOOK: {
+				FeatureDescService: 'rest/module/feature?feature=${feature}',
+				ApplicationDescService: 'rest/module/application?application=${application}',
+				SiteDescService: 'rest/module/site?site=${site}'
+			},
+			UPDATE_HOOK: {
+				SiteDescService: 'rest/module/siteApplication'
+			}
+		},
+	};
+
+	// ============================================================================
+	// =                                   URLs                                   =
+	// ============================================================================
+	app.getURL = function(name, kvs) {
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		var _path = app.config.urls[name];
+		if(!_path) throw "URL:'" + name + "' not exist!";
+		var _url = (_host ? _host + "/" : '') + _path;
+		if(kvs !== undefined) {
+			_url = common.template(_url, kvs);
+		}
+		return _url;
+	};
+
+	function getHookURL(hookType, serviceName) {
+		var _path = app.config.urls[hookType][serviceName];
+		if(!_path) return null;
+
+		var _host = localStorage.getItem("HOST") || app.config.urls.HOST;
+		var _url = (_host ? _host + "/" : '') + _path;
+		return _url;
+	}
+
+	/***
+	 * 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._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/d37643b0/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
index 777844d..e9dfb2a 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.js
@@ -18,693 +18,467 @@
 
 var app = {};
 
-/* App Module */
-var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngCookies', 'damControllers']);
-
-eagleApp.config(function($routeProvider) {
+(function() {
 	'use strict';
 
-	$routeProvider.when('/dam/summary', {
-		templateUrl : 'partials/dam/summary.html',
-		controller : 'summaryCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Authorization
-	}).when('/dam/login', {
-		templateUrl : 'partials/dam/login.html',
-		controller : 'authLoginCtrl',
-		access: {skipCheck: true},
-
-	// Policy
-	}).when('/dam/policyList', {
-		templateUrl : 'partials/dam/policyList.html',
-		controller : 'policyListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyList/:dataSource', {
-		templateUrl : 'partials/dam/policyList.html',
-		controller : 'policyListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyDetail/', {
-		templateUrl : 'partials/dam/policyDetail.html',
-		controller : 'policyDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyDetail/:encodedRowkey', {
-		templateUrl : 'partials/dam/policyDetail.html',
-		controller : 'policyDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyEdit/:encodedRowkey', {
-		templateUrl : 'partials/dam/policyEdit.html',
-		controller : 'policyEditCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/policyCreate/', {
-		templateUrl : 'partials/dam/policyEdit.html',
-		controller : 'policyCreateCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Alert
-	}).when('/dam/alertList', {
-		templateUrl : 'partials/dam/alertList.html',
-		controller : 'alertListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/alertList/:dataSource', {
-		templateUrl : 'partials/dam/alertList.html',
-		controller : 'alertListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/alertDetail/:encodedRowkey', {
-		templateUrl : 'partials/dam/alertDetail.html',
-		controller : 'alertDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Stream
-	}).when('/dam/streamList', {
-		templateUrl : 'partials/dam/streamList.html',
-		controller : 'streamListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// Site
-	}).when('/dam/siteList', {
-		templateUrl : 'partials/dam/siteList.html',
-		controller : 'siteListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-		access: {roles: ["ROLE_ADMIN"]},
-
-	// Sensitivity
-	}).when('/dam/sensitivitySummary', {
-		templateUrl : 'partials/dam/sensitivitySummary.html',
-		controller : 'sensitivitySummaryCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/sensitivity/:dataSrc', {
-		templateUrl : 'partials/dam/sensitivity.html',
-		controller : 'sensitivityCtrl',
-		reloadOnSearch: false,
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	// User Profile
-	}).when('/dam/userProfileList', {
-		templateUrl : 'partials/dam/userProfileList.html',
-		controller : 'userProfileListCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-	}).when('/dam/userProfileDetail/:user', {
-		templateUrl : 'partials/dam/userProfileDetail.html',
-		controller : 'userProfileDetailCtrl',
-		resolve: {
-			site: function(Site) {return Site._promise();},
-			auth: function(Authorization) {return Authorization._promise();},
-		},
-
-	}).otherwise({
-		redirectTo : '/dam/summary'
+	/* App Module */
+	var eagleApp = angular.module('eagleApp', ['ngRoute', 'ngAnimate', 'ui.router', 'eagleControllers', 'featureControllers', 'eagle.service']);
+
+	// ======================================================================================
+	// =                                   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;
 	});
-});
-
-eagleApp.service('globalContent', function(Entities, $rootScope, $route, $location) {
-	'use strict';
-
-	var content = {
-		pageTitle: "",
-		pageSubTitle: "",
-		pageList: [],
-		navPath: [],
-		navMapping: {},
-
-		hideSite: false,
-		lockSite: false,
 
-		dataSrcList: [],
+	featureControllers.service("Feature", function($wrapState, PageConfig, ConfigPageConfig, FeaturePageConfig) {
+		var _features = {};
+		var _services = {};
 
-		setConfig: function(config) {
-			// Clean up
-			content.navPath = [];
-
-			// Fill configuration
-			$.extend(content, config);
-		},
-	};
-
-	return content;
-});
-
-// Site
-eagleApp.service('Site', function(Authorization, Entities, $rootScope, $route, $location, $q) {
-	'use strict';
-
-	var _currentSite;
-	var content = {};
-
-	content.list = [];
-	content.list.set = {};
-	content.dataSrcList = [];
-
-	content.current = function(site) {
-		if(site) {
-			var _prev = _currentSite;
-			_currentSite = site;
-
-			// Broadcast if site update
-			if(!_prev || _prev.name !== _currentSite.name) {
-				if(sessionStorage) {
-					sessionStorage.setItem("site", _currentSite.name);
-				}
+		var Feature = function(name, version) {
+			this.name = name;
+			this.version = version;
+			this.features = {};
+		};
 
-				if(!content.hideSite) $route.reload();
+		/***
+		 * 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);
+					});
+				});
 			}
-		}
-		return _currentSite;
-	};
-	content.find = function(siteName) {
-		return common.array.find(siteName, content.list, "name");
-	};
-	content.url = function(site, url) {
-		if(arguments.length == 1) {
-			url = site;
-		} else {
-			content.current(site);
-		}
-		$location.url(url);
+			_constructor.$inject = _$inject;
 
-		if ($rootScope.$$phase != '$apply' && $rootScope.$$phase != '$digest') {
-			$rootScope.$apply();
-		}
-	};
-
-	var _promise;
-	content.refresh = function() {
-		content.list = [];
-		content.list.set = {};
-
-		content.dataSrcList = Entities.queryEntities("AlertDataSourceService", '');
-		content.dataSrcList._promise.success(function() {
-			$.each(content.dataSrcList, function(i, dataSrc) {
-				var _site = content.list.set[dataSrc.tags.site];
-				if(!_site) {
-					_site = content.list.set[dataSrc.tags.site] = {
-						name: dataSrc.tags.site,
-						dataSrcList: []
-					};
-					_site.dataSrcList.find = function(dataSrcName) {
-						return common.array.find(dataSrcName, _site.dataSrcList, "tags.dataSource");
-					};
-					content.list.push(_site);
-				}
-				_site.dataSrcList.push(dataSrc);
+			for(i = 0 ; i < _$inject.length ; i += 1) {
+				srvName = _$inject[i];
+				_$inject[i] = this.features[srvName] || _$inject[i];
+			}
 
-				// UI visible check
-				if($.inArray(dataSrc.tags.dataSource, app.config.dataSource.uiInvisibleList) !== -1) {
-					dataSrc.hide = true;
-				}
-			});
+			return _constructor;
+		};
 
-			if(sessionStorage && content.find(sessionStorage.getItem("site"))) {
-				content.current(content.find(sessionStorage.getItem("site")));
+		/***
+		 * 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 {
-				content.current(content.list[0]);
+				throw "Service '" + name + "' has already be registered by feature '" + _services[name] + "'";
 			}
-		});
+		};
 
-		_promise = content.dataSrcList._promise;
-		return _promise;
-	};
+		/***
+		 * 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.");
+			}
+		};
 
-	content._promise = function() {
-		if(!_promise) {
-			content.refresh();
-		}
-		return _promise;
-	};
+		/***
+		 * 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
+			});
+		};
 
-	return content;
-});
+		/***
+		 * 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;
+			}
 
-// Authorization
-eagleApp.service('Authorization', function($http, $location, $cookies) {
-	'use strict';
+			return _name;
+		};
 
-	$http.defaults.withCredentials = true;
-
-	var _path = "";
-	var _userProfile = null;
-
-	var content = {
-		isLogin: true,	// Status mark. Work for UI status check, changed when eagle api return 403 authorization failure.
-		needLogin: function() {
-			_path = _path || $location.path();
-			content.isLogin = false;
-			$location.path("/dam/login");
-		},
-		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) {
-				$location.path(_path || "");
-				_path = "";
+		/***
+		 * 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;
 			}
-		},
-	};
-
-	var _promise;
-	content.userProfile = {};
-	content.isRole = function(role) {
-		if(!content.userProfile.roles) return null;
-
-		return content.userProfile.roles[role] === true;
-	};
-
-	content.refresh = 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 _promise;
-	};
 
-	content._promise = function() {
-		if(!_promise) {
-			content.refresh();
-		}
-		return _promise;
-	};
+			return _name;
+		};
 
-	return content;
-});
+		/***
+		 * 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
+			});
+		};
 
-eagleApp.service('Entities', function($http, $q, $rootScope, $location, Authorization) {
-	'use strict';
+		// Register
+		featureControllers.register = Feature.register = function(featureName) {
+			_features[featureName] = _features[featureName] || new Feature(featureName);
+			return _features[featureName];
+		};
 
-	// Query
-	function _query(name, kvs) {
-		kvs = kvs || {};
-		var _list = [];
-		var _condition = kvs._condition || {};
-		var _addtionalCondition = _condition.additionalCondition || {};
-		var _startTime, _endTime;
-		var _startTimeStr, _endTimeStr;
-
-		// Initial
-		// > Condition
-		delete kvs._condition;
-		if(_condition) {
-			kvs.condition = _condition.condition;
-		}
+		// 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);
+			}
+		};
 
-		// > Values
-		if(!kvs.values) {
-			kvs.values = "*";
-		} else if($.isArray(kvs.values)) {
-			kvs.values = $.map(kvs.values, function(field) {
-				return (field[0] === "@" ? '' : '@') + field;
-			}).join(",");
-		}
+		return Feature;
+	});
 
-		var _url = app.getURL(name, kvs);
+	// ======================================================================================
+	// =                                   Router config                                    =
+	// ======================================================================================
+	eagleApp.config(function ($stateProvider, $urlRouterProvider) {
+		// Resolve
+		function _resolve(config) {
+			config = config || {};
 
-		// Fill special parameters
-		// > Query by time duration
-		if(_addtionalCondition._duration) {
-			_endTime = app.time.now();
-			_startTime = _endTime.clone().subtract(_addtionalCondition._duration, "ms");
+			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();
+					});
 
-			// Debug usage. Extend more time duration for end time
-			if(_addtionalCondition.__ETD) {
-				_endTime.add(_addtionalCondition.__ETD, "ms");
+					return _deferred.promise;
+				};
 			}
 
-			_addtionalCondition._startTime = _startTime;
-			_addtionalCondition._endTime = _endTime;
-
-			_startTimeStr = _startTime.format("YYYY-MM-DD HH:mm:ss");
-			_endTimeStr = _endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+			return resolve;
+		}
 
-			_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-		} else if(_addtionalCondition._startTime && _addtionalCondition._endTime) {
-			_startTimeStr = _addtionalCondition._startTime.format("YYYY-MM-DD HH:mm:ss");
-			_endTimeStr = _addtionalCondition._endTime.clone().add(1, "s").format("YYYY-MM-DD HH:mm:ss");
+		// Router
+		var _featureBase = {
+			templateUrl: function ($stateParams) {
+				var _htmlTemplate = featureControllerCustomizeHtmlTemplate[$stateParams.feature + "_" + $stateParams.page];
+				return  "public/feature/" + $stateParams.feature + "/page/" + (_htmlTemplate ||  $stateParams.page) + ".html?_=" + Math.random();
+			},
+			controllerProvider: function ($stateParams) {
+				return $stateParams.feature + "_" + $stateParams.page;
+			},
+			resolve: _resolve({featureCheck: true}),
+			pageConfig: "FeaturePageConfig"
+		};
 
-			_url += "&startTime=" + _startTimeStr + "&endTime=" + _endTimeStr;
-		}
+		$urlRouterProvider.otherwise("/landing");
+		$stateProvider
+			// =================== Landing ===================
+			.state('landing', {
+				url: "/landing",
+				templateUrl: "partials/landing.html?_=" + Math.random(),
+				controller: "landingCtrl",
+				resolve: _resolve({featureCheck: true})
+			})
+
+			// ================ Authorization ================
+			.state('login', {
+				url: "/login",
+				templateUrl: "partials/login.html?_=" + Math.random(),
+				controller: "authLoginCtrl",
+				access: {skipCheck: true}
+			})
+
+			// ================ Configuration ================
+			// Site
+			.state('configSite', {
+				url: "/config/site",
+				templateUrl: "partials/config/site.html?_=" + Math.random(),
+				controller: "configSiteCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Application
+			.state('configApplication', {
+				url: "/config/application",
+				templateUrl: "partials/config/application.html?_=" + Math.random(),
+				controller: "configApplicationCtrl",
+				pageConfig: "ConfigPageConfig",
+				resolve: _resolve({roleType: 'ROLE_ADMIN'})
+			})
+
+			// Feature
+			.state('configFeature', {
+				url: "/config/feature",
+				templateUrl: "partials/config/feature.html?_=" + Math.random(),
+				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?_=" + Math.random();
+				},
+				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))
+		;
+	});
 
-		// > Query contains metric name
-		if(_addtionalCondition._metricName) {
-			_url += "&metricName=" + _addtionalCondition._metricName;
-		}
+	eagleApp.filter('parseJSON', function () {
+		return function (input, defaultVal) {
+			return common.parseJSON(input, defaultVal);
+		};
+	});
 
-		// > Customize page size
-		if(_addtionalCondition._pageSize) {
-			_url = _url.replace(/pageSize=\d+/, "pageSize=" + _addtionalCondition._pageSize);
-		}
+	eagleApp.filter('split', function () {
+		return function (input, regex) {
+			return input.split(regex);
+		};
+	});
 
-		// AJAX
-		var canceler = $q.defer();
-		_list._promise = $http.get(_url, {timeout: canceler.promise}).success(function(data) {
-			_list.push.apply(_list, data.obj);
-		});
-		_list._promise.abort = function() {
-			canceler.resolve();
+	eagleApp.filter('reverse', function () {
+		return function (items) {
+			return items.slice().reverse();
 		};
+	});
 
-		_list._promise.then(function() {}, function(data) {
-			if(data.status === 403) {
-				Authorization.needLogin();
+	// ======================================================================================
+	// =                                   Main Controller                                  =
+	// ======================================================================================
+	eagleApp.controller('MainCtrl', function ($scope, $wrapState, $http, $injector, PageConfig, FeaturePageConfig, Site, Authorization, Entities, nvd3, Application, Feature, UI) {
+		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();
 			}
 		});
 
-		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 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;
-	}
+		// 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();
 
-	var pkg = {
-		_query: _query,
-		_post: _post,
-
-		updateEntity: function(serviceName, entities, config) {
-			config = config || {};
-			if(!$.isArray(entities)) entities = [entities];
+			// Dynamic navigation list
+			if(next.pageConfig) {
+				$scope.PageConfig.navConfig = $injector.get(next.pageConfig);
+			} else {
+				$scope.PageConfig.navConfig = {};
+			}
 
-			// Post clone entities
-			var _entities = $.map(entities, function(entity) {
-				var _entity = {};
+			// Authorization
+			// > Login check
+			if (!common.getValueByPath(next, "access.skipCheck", false)) {
+				if (!Authorization.isLogin) {
+					console.log("[Authorization] Need access. Redirect...");
+					$wrapState.go("login");
+				}
+			}
 
-				// Clone variables
-				$.each(entity, function(key, value) {
-					// Skip inner variables
-					if(!key.match(/^__/)) {
-						_entity[key] = entity[key];
+			// > 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;
 					}
 				});
 
-				// 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();
-					}
+				if (!_roleMatch) {
+					$wrapState.path("/dam");
 				}
+			}*/
+		});
 
-				return _entity;
-			});
+		$scope.$on('$stateChangeError', function (event, next, nextParam, current, currentParam, error) {
+			console.error("[Switch] Error", arguments);
+		});
 
-			return _post(app.getURL("updateEntity", {serviceName: serviceName}), _entities);
-		},
+		// Get side bar navigation item class
+		$scope.getNavClass = function (page) {
+			var path = page.url.replace(/^#/, '');
 
-		deleteEntity: function(serviceName, entities) {
-			if(!$.isArray(entities)) entities = [entities];
+			if ($wrapState.path() === path) {
+				PageConfig.pageTitle = PageConfig.pageTitle || page.title;
+				return "active";
+			} else {
+				return "";
+			}
+		};
 
-			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}));
-		},
-
-		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.success(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]);
-				}
+		// Get side bar navigation item class visible
+		$scope.getNavVisible = function (page) {
+			if (!page.roles) return true;
 
-				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");
-					});
+			for (var i = 0; i < page.roles.length; i += 1) {
+				var roleName = page.roles[i];
+				if (Authorization.isRole(roleName)) {
+					return true;
 				}
-			});
-			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;
-});
-
-eagleApp.filter('parseJSON', function() {
-	'use strict';
-
-	return function(input, defaultVal) {
-		return common.parseJSON(input, defaultVal);
-	};
-});
 
-eagleApp.filter('split', function() {
-	'use strict';
-
-	return function(input, regex) {
-		return input.split(regex);
-	};
-});
-
-eagleApp.filter('reverse', function() {
-	'use strict';
-
-	return function(items) {
-		return items.slice().reverse();
-	};
-});
-
-eagleApp.controller('MainCtrl', function($scope, $location, $http, globalContent, Site, Authorization, Entities, nvd3) {
-	'use strict';
-
-	window.globalContent = $scope.globalContent = globalContent;
-	window.site = $scope.site = Site;
-	window.auth = $scope.auth = Authorization;
-	window.entities = $scope.entities = Entities;
-	window.nvd3 = nvd3;
-	$scope.app = app;
-
-	// Clean up
-	$scope.$on('$routeChangeStart', function(event, next, current) {
-		// Page initialization
-		globalContent.pageTitle = "";
-		globalContent.pageSubTitle = "";
-		globalContent.hideSite = false;
-		globalContent.lockSite = false;
-		globalContent.hideSidebar = false;
-		globalContent.hideUser = false;
+			return false;
+		};
 
 		// Authorization
-		// > Login check
-		if(!common.getValueByPath(next, "access.skipCheck", false)) {
-			if(!Authorization.isLogin) {
-				$location.path("/dam/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) {
-				$location.path("/dam");
-			}
-		}
+		$scope.logout = function () {
+			console.log("[Authorization] Logout. Redirect...");
+			Authorization.logout();
+			$wrapState.go("login");
+		};
 	});
-
-	// Get side bar navigation item class
-	$scope.getNavClass = function(page) {
-		var path = page.url.replace(/^#/, '');
-
-		if ($location.path() == path) {
-			globalContent.pageTitle = globalContent.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() {
-		Authorization.logout();
-		$location.path("/dam/login");
-	};
-});
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/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
index 4e0ee6e..28727ba 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/app.ui.js
@@ -1,66 +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);
-		}
-	};
+/*
+ * 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, textarea").filter(':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/d37643b0/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
index c5b385f..706eeec 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/common.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/common.js
@@ -1,211 +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.
- */
-
-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 {
-		return JSON.parse(str);
-	} catch(err) {
-		if(defaultVal === undefined) {
-			console.warn("Can't parse JSON: " + str);
-		}
-	}
-	return defaultVal === undefined ? null : defaultVal;
-};
-
-common.isEmpty = function(val) {
-	if($.isArray(val)) {
-		return val.length === 0;
-	} else {
-		return val === null || val === undefined;
-	}
-};
-
-// ====================== Format ======================
-common.format = {};
-
-/*
- * Format date to string. Support number, string, Date instance. Will auto convert time zone offset(Moment instance will keep time zone).
- */
-common.format.date = function(val, type) {
-	if(val === undefined || val === null) return "";
-
-	if(typeof val === "number" || typeof val === "string" || val instanceof Date) {
-		val = app.time.offset(val);
-	}
-	switch(type) {
-	default:
-		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
-	}
-};
-
-// ====================== Array =======================
-common.array = {};
-
-common.array.sum = function(list, path) {
-	var _sum = 0;
-	if(list) {
-		$.each(list, function(i, unit) {
-			var _val = common.getValueByPath(unit, path);
-			if(typeof _val === "number") {
-				_sum += _val;
-			}
-		});
-	}
-	return _sum;
-};
-
-common.array.max = function(list, path) {
-	var _max = null;
-	if(list) {
-		$.each(list, function(i, unit) {
-			var _val = common.getValueByPath(unit, path);
-			if(typeof _val === "number" && (_max === null || _max < _val)) {
-				_max = _val;
-			}
-		});
-	}
-	return _max;
-};
-
-common.array.bottom = function(list, path, count) {
-	var _list = list.slice();
-
-	_list.sort(function(a, b) {
-		var _a = common.getValueByPath(a, path, null);
-		var _b = common.getValueByPath(b, path, null);
-
-		if(_a === _b) return 0;
-		if(_a < _b || _a === null) {
-			return -1;
-		} else {
-			return 1;
-		}
-	});
-	return !count ? _list : _list.slice(0, count);
-};
-common.array.top = function(list, path, count) {
-	var _list = common.array.bottom(list, path);
-	_list.reverse();
-	return !count ? _list : _list.slice(0, count);
-};
-
-common.array.find = function(val, list, path, findAll) {
-	path = path || "";
-	var _list = $.grep(list, function(unit) {
-		return val === common.getValueByPath(unit, path);
-	});
-	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
-};
-
-common.array.filter = function(val, list, path) {
-	return common.array.find(val, list, path, true);
-};
-
-common.array.count = function(list, val, path) {
-	if(arguments.length === 1) {
-		return list.length;
-	} else {
-		return common.array.find(val, list, path, true).length;
-	}
-};
-
-common.array.remove = function(val, list) {
-	for(var i = 0 ; i < list.length ; i += 1) {
-		if(list[i] === val) {
-			list.splice(i, 1);
-			i -= 1;
-		}
-	}
-};
-
-// ======================= Map ========================
-common.map = {};
-
-common.map.toArray = function(map) {
-	return $.map(map, function(unit) {
-		return unit;
-	});
+/*
+ * 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.isEmpty = function(val) {
+	if($.isArray(val)) {
+		return val.length === 0;
+	} else {
+		return val === null || val === undefined;
+	}
+};
+
+// ====================== Format ======================
+common.format = {};
+
+/*
+ * Format date to string. Support number, string, Date instance. Will auto convert time zone offset(Moment instance will keep time zone).
+ */
+common.format.date = function(val, type) {
+	if(val === undefined || val === null) return "";
+
+	if(typeof val === "number" || typeof val === "string" || val instanceof Date) {
+		val = app.time.offset(val);
+	}
+	switch(type) {
+	default:
+		return val.format("YYYY-MM-DD HH:mm:ss") + (val.utcOffset() === 0 ? '[UTC]' : '');
+	}
+};
+
+// ====================== Array =======================
+common.array = {};
+
+common.array.sum = function(list, path) {
+	var _sum = 0;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number") {
+				_sum += _val;
+			}
+		});
+	}
+	return _sum;
+};
+
+common.array.max = function(list, path) {
+	var _max = null;
+	if(list) {
+		$.each(list, function(i, unit) {
+			var _val = common.getValueByPath(unit, path);
+			if(typeof _val === "number" && (_max === null || _max < _val)) {
+				_max = _val;
+			}
+		});
+	}
+	return _max;
+};
+
+common.array.bottom = function(list, path, count) {
+	var _list = list.slice();
+
+	_list.sort(function(a, b) {
+		var _a = common.getValueByPath(a, path, null);
+		var _b = common.getValueByPath(b, path, null);
+
+		if(_a === _b) return 0;
+		if(_a < _b || _a === null) {
+			return -1;
+		} else {
+			return 1;
+		}
+	});
+	return !count ? _list : _list.slice(0, count);
+};
+common.array.top = function(list, path, count) {
+	var _list = common.array.bottom(list, path);
+	_list.reverse();
+	return !count ? _list : _list.slice(0, count);
+};
+
+common.array.find = function(val, list, path, findAll) {
+	path = path || "";
+	var _list = $.grep(list, function(unit) {
+		return val === common.getValueByPath(unit, path);
+	});
+	return findAll ? _list : (_list.length === 0 ? null : _list[0]);
+};
+
+common.array.filter = function(val, list, path) {
+	return common.array.find(val, list, path, true);
+};
+
+common.array.count = function(list, val, path) {
+	if(arguments.length === 1) {
+		return list.length;
+	} else {
+		return common.array.find(val, list, path, true).length;
+	}
+};
+
+common.array.remove = function(val, list) {
+	for(var i = 0 ; i < list.length ; i += 1) {
+		if(list[i] === val) {
+			list.splice(i, 1);
+			i -= 1;
+		}
+	}
+};
+
+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;
+	});
 };
\ No newline at end of file

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

http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/d37643b0/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
index 1131ca5..1b4f602 100644
--- a/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
+++ b/eagle-webservice/src/main/webapp/app/public/js/ctrl/authController.js
@@ -16,48 +16,54 @@
  * limitations under the License.
  */
 
-// =============================================================
-// =                     User Profile List                     =
-// =============================================================
-damControllers.controller('authLoginCtrl', function(globalContent, Site, Authorization, $scope) {
+(function() {
 	'use strict';
 
-	globalContent.hideSidebar = true;
-	globalContent.hideSite = true;
-	globalContent.hideUser = true;
+	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.username = "";
+		$scope.password = "";
+		$scope.lock = false;
 
-	// UI
-	setTimeout(function() {
-		$("#username").focus();
-	});
+		// UI
+		setTimeout(function () {
+			$("#username").focus();
+		});
 
-	// Login
-	$scope.login = function(event, forceSubmit) {
-		if($scope.lock) return;
+		// Login
+		$scope.login = function (event, forceSubmit) {
+			if ($scope.lock) return;
 
-		if(event.which === 13 || forceSubmit) {
-			$scope.lock = true;
+			if (event.which === 13 || forceSubmit) {
+				$scope.lock = true;
 
-			Authorization.login($scope.username, $scope.password).then(function(success) {
-				if(success) {
-					Site.refresh();
-					Authorization.refresh();
-					Authorization.path(true);
-				} else {
-					$.dialog({
-						title: "OPS",
-						content: "User name or password not correct."
-					}).on("hidden.bs.modal", function() {
-						$("#username").focus();
-					});
-				}
-			}).finally(function() {
-				$scope.lock = false;
-			});
-		}
-	};
-});
\ No newline at end of file
+				Authorization.login($scope.username, $scope.password).then(function (success) {
+					if (success) {
+						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


Mime
View raw message