eagle-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ji...@apache.org
Subject [11/14] incubator-eagle git commit: [EAGLE-574] UI refactor for support 0.5 api
Date Wed, 28 Sep 2016 05:38:52 GMT
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



Mime
View raw message