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


Reply via email to